The 1st decentralized social network for sharing when you're on the toilet. Post a "flush" today! Powered by the AT Protocol.
at main 218 lines 6.8 kB view raw
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