forked from
atpota.to/flushes.app
The 1st decentralized social network for sharing when you're on the toilet. Post a "flush" today! Powered by the AT Protocol.
1'use client';
2
3import { useState, useEffect } from 'react';
4import styles from './EditFlushModal.module.css';
5import { containsBannedWords, sanitizeText, isAllowedEmoji } from '@/lib/content-filter';
6
7interface EditFlushModalProps {
8 isOpen: boolean;
9 flushData: {
10 uri: string;
11 text: string;
12 emoji: string;
13 created_at: string;
14 } | null;
15 onSave: (text: string, emoji: string) => Promise<void>;
16 onDelete: () => Promise<void>;
17 onClose: () => void;
18}
19
20// Define approved emojis list
21const APPROVED_EMOJIS = [
22 '🚽', '🧻', '💩', '💨', '🚾', '🧼', '🪠', '🚻', '🩸', '💧', '💦', '😌',
23 '😣', '🤢', '🤮', '🥴', '😮💨', '😳', '😵', '🌾', '🍦', '📱', '📖', '💭',
24 '1️⃣', '2️⃣', '🟡', '🟤'
25];
26
27export default function EditFlushModal({ isOpen, flushData, onSave, onDelete, onClose }: EditFlushModalProps) {
28 const [text, setText] = useState('');
29 const [selectedEmoji, setSelectedEmoji] = useState('🚽');
30 const [isSubmitting, setIsSubmitting] = useState(false);
31 const [error, setError] = useState<string | null>(null);
32 const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
33
34 // Update form when flushData changes
35 useEffect(() => {
36 if (flushData) {
37 // Remove "is " prefix if it exists in the stored text
38 let displayText = flushData.text || '';
39 if (displayText.toLowerCase().startsWith('is ')) {
40 displayText = displayText.substring(3);
41 }
42 setText(displayText);
43 setSelectedEmoji(flushData.emoji || '🚽');
44 setError(null);
45 setShowDeleteConfirm(false);
46 }
47 }, [flushData]);
48
49 if (!isOpen || !flushData) return null;
50
51 const handleSave = async () => {
52 setError(null);
53
54 // Validate text
55 if (containsBannedWords(text)) {
56 setError('Uh oh, looks like you have a potty mouth. Try again with cleaner language please...');
57 return;
58 }
59
60 // Check character limit (text + "is " = 56 + 3 = 59)
61 if (text.length > 56) {
62 setError('Your flush status is too long! Please keep it under 56 characters (59 total with "is").');
63 return;
64 }
65
66 // Validate emoji
67 if (!isAllowedEmoji(selectedEmoji)) {
68 setError('Please select a valid emoji from the list.');
69 return;
70 }
71
72 setIsSubmitting(true);
73 try {
74 // Add "is " prefix when saving
75 const fullText = text.trim() ? `is ${text.trim()}` : 'is flushing';
76 await onSave(sanitizeText(fullText), selectedEmoji);
77 onClose();
78 } catch (err: any) {
79 console.error('Error updating flush:', err);
80 setError(err.message || 'Failed to update flush. Please try again.');
81 } finally {
82 setIsSubmitting(false);
83 }
84 };
85
86 const handleDelete = async () => {
87 setIsSubmitting(true);
88 setError(null);
89 try {
90 await onDelete();
91 onClose();
92 } catch (err: any) {
93 console.error('Error deleting flush:', err);
94 setError(err.message || 'Failed to delete flush. Please try again.');
95 } finally {
96 setIsSubmitting(false);
97 setShowDeleteConfirm(false);
98 }
99 };
100
101 const handleBackdropClick = (e: React.MouseEvent) => {
102 if (e.target === e.currentTarget && !isSubmitting) {
103 onClose();
104 }
105 };
106
107 return (
108 <div className={styles.modalBackdrop} onClick={handleBackdropClick}>
109 <div className={styles.modalContent}>
110 <div className={styles.modalHeader}>
111 <h2>Edit Your Flush</h2>
112 <button
113 className={styles.closeButton}
114 onClick={onClose}
115 disabled={isSubmitting}
116 aria-label="Close"
117 >
118 ✕
119 </button>
120 </div>
121
122 {error && (
123 <div className={styles.error}>
124 {error}
125 </div>
126 )}
127
128 <div className={styles.formGroup}>
129 <label htmlFor="flush-text">What's your status? (optional)</label>
130 <div className={styles.inputWrapper}>
131 <span className={styles.inputPrefix}>is </span>
132 <input
133 id="flush-text"
134 type="text"
135 value={text}
136 onChange={(e) => setText(e.target.value)}
137 placeholder="flushing"
138 maxLength={56}
139 disabled={isSubmitting}
140 className={styles.inputWithPrefix}
141 />
142 </div>
143 <div className={styles.charCount}>
144 {text.length + 3}/59
145 </div>
146 </div>
147
148 <div className={styles.formGroup}>
149 <label>Select Emoji</label>
150 <div className={styles.emojiGrid}>
151 {APPROVED_EMOJIS.map((emoji) => (
152 <button
153 key={emoji}
154 type="button"
155 onClick={() => setSelectedEmoji(emoji)}
156 className={`${styles.emojiButton} ${selectedEmoji === emoji ? styles.selected : ''}`}
157 disabled={isSubmitting}
158 >
159 {emoji}
160 </button>
161 ))}
162 </div>
163 </div>
164
165 <div className={styles.modalActions}>
166 {!showDeleteConfirm ? (
167 <>
168 <button
169 onClick={() => setShowDeleteConfirm(true)}
170 disabled={isSubmitting}
171 className={styles.deleteButton}
172 >
173 Delete Flush
174 </button>
175 <div className={styles.rightActions}>
176 <button
177 onClick={onClose}
178 disabled={isSubmitting}
179 className={styles.cancelButton}
180 >
181 Cancel
182 </button>
183 <button
184 onClick={handleSave}
185 disabled={isSubmitting}
186 className={styles.saveButton}
187 >
188 {isSubmitting ? 'Saving...' : 'Save Changes'}
189 </button>
190 </div>
191 </>
192 ) : (
193 <div className={styles.deleteConfirmation}>
194 <p>Are you sure you want to delete this flush? This cannot be undone.</p>
195 <div className={styles.confirmButtons}>
196 <button
197 onClick={() => setShowDeleteConfirm(false)}
198 disabled={isSubmitting}
199 className={styles.cancelButton}
200 >
201 Cancel
202 </button>
203 <button
204 onClick={handleDelete}
205 disabled={isSubmitting}
206 className={styles.confirmDeleteButton}
207 >
208 {isSubmitting ? 'Deleting...' : 'Yes, Delete'}
209 </button>
210 </div>
211 </div>
212 )}
213 </div>
214 </div>
215 </div>
216 );
217}
218