(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

at frontend-rewrite 208 lines 7.5 kB view raw
1import React, { useState } from "react"; 2import { Flag, X } from "lucide-react"; 3import { reportUser } from "../../api/client"; 4import type { ReportReasonType } from "../../types"; 5 6interface ReportModalProps { 7 isOpen: boolean; 8 onClose: () => void; 9 subjectDid: string; 10 subjectUri?: string; 11 subjectHandle?: string; 12} 13 14const REASONS: { 15 value: ReportReasonType; 16 label: string; 17 description: string; 18}[] = [ 19 { value: "spam", label: "Spam", description: "Unwanted repetitive content" }, 20 { 21 value: "violation", 22 label: "Rule violation", 23 description: "Violates community guidelines", 24 }, 25 { 26 value: "misleading", 27 label: "Misleading", 28 description: "False or misleading information", 29 }, 30 { 31 value: "rude", 32 label: "Rude or harassing", 33 description: "Targeting or harassing a user", 34 }, 35 { 36 value: "sexual", 37 label: "Inappropriate content", 38 description: "Sexual or explicit material", 39 }, 40 { 41 value: "other", 42 label: "Other", 43 description: "Something else not listed above", 44 }, 45]; 46 47export default function ReportModal({ 48 isOpen, 49 onClose, 50 subjectDid, 51 subjectUri, 52 subjectHandle, 53}: ReportModalProps) { 54 const [selectedReason, setSelectedReason] = useState<ReportReasonType | null>( 55 null, 56 ); 57 const [additionalText, setAdditionalText] = useState(""); 58 const [submitting, setSubmitting] = useState(false); 59 const [submitted, setSubmitted] = useState(false); 60 61 if (!isOpen) return null; 62 63 const handleSubmit = async () => { 64 if (!selectedReason) return; 65 66 setSubmitting(true); 67 const success = await reportUser({ 68 subjectDid: subjectDid, 69 subjectUri: subjectUri, 70 reasonType: selectedReason, 71 reasonText: additionalText || undefined, 72 }); 73 74 setSubmitting(false); 75 if (success) { 76 setSubmitted(true); 77 setTimeout(() => { 78 onClose(); 79 setSubmitted(false); 80 setSelectedReason(null); 81 setAdditionalText(""); 82 }, 1500); 83 } 84 }; 85 86 const handleClose = () => { 87 onClose(); 88 setSelectedReason(null); 89 setAdditionalText(""); 90 setSubmitted(false); 91 }; 92 93 return ( 94 <div 95 className="fixed inset-0 z-[200] flex items-center justify-center bg-black/50 backdrop-blur-sm animate-fade-in" 96 onClick={handleClose} 97 > 98 <div 99 className="bg-white dark:bg-surface-900 rounded-2xl shadow-2xl border border-surface-200 dark:border-surface-700 w-full max-w-md mx-4 overflow-hidden" 100 onClick={(e) => e.stopPropagation()} 101 > 102 {submitted ? ( 103 <div className="p-8 text-center"> 104 <div className="w-12 h-12 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-3"> 105 <Flag size={20} className="text-green-600 dark:text-green-400" /> 106 </div> 107 <h3 className="text-lg font-semibold text-surface-900 dark:text-white"> 108 Report submitted 109 </h3> 110 <p className="text-surface-500 dark:text-surface-400 text-sm mt-1"> 111 Thank you. We'll review this shortly. 112 </p> 113 </div> 114 ) : ( 115 <> 116 <div className="flex items-center justify-between p-4 border-b border-surface-200 dark:border-surface-700"> 117 <div className="flex items-center gap-2.5"> 118 <div className="w-8 h-8 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center"> 119 <Flag size={16} className="text-red-600 dark:text-red-400" /> 120 </div> 121 <div> 122 <h3 className="text-base font-semibold text-surface-900 dark:text-white"> 123 Report {subjectHandle ? `@${subjectHandle}` : "user"} 124 </h3> 125 {subjectUri && ( 126 <p className="text-xs text-surface-400 dark:text-surface-500"> 127 Reporting specific content 128 </p> 129 )} 130 </div> 131 </div> 132 <button 133 onClick={handleClose} 134 className="p-1.5 text-surface-400 hover:text-surface-600 dark:hover:text-surface-300 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors" 135 > 136 <X size={18} /> 137 </button> 138 </div> 139 140 <div className="p-4 space-y-2"> 141 <p className="text-sm font-medium text-surface-700 dark:text-surface-300 mb-3"> 142 What's the issue? 143 </p> 144 {REASONS.map((reason) => ( 145 <button 146 key={reason.value} 147 onClick={() => setSelectedReason(reason.value)} 148 className={`w-full text-left px-3.5 py-2.5 rounded-xl border transition-all ${ 149 selectedReason === reason.value 150 ? "border-primary-500 bg-primary-50 dark:bg-primary-900/20" 151 : "border-surface-200 dark:border-surface-700 hover:border-surface-300 dark:hover:border-surface-600" 152 }`} 153 > 154 <span 155 className={`text-sm font-medium ${ 156 selectedReason === reason.value 157 ? "text-primary-700 dark:text-primary-300" 158 : "text-surface-800 dark:text-surface-200" 159 }`} 160 > 161 {reason.label} 162 </span> 163 <span 164 className={`block text-xs mt-0.5 ${ 165 selectedReason === reason.value 166 ? "text-primary-600/70 dark:text-primary-400/70" 167 : "text-surface-400 dark:text-surface-500" 168 }`} 169 > 170 {reason.description} 171 </span> 172 </button> 173 ))} 174 </div> 175 176 {selectedReason && ( 177 <div className="px-4 pb-2"> 178 <textarea 179 value={additionalText} 180 onChange={(e) => setAdditionalText(e.target.value)} 181 placeholder="Additional details (optional)" 182 rows={2} 183 className="w-full px-3.5 py-2.5 text-sm rounded-xl border border-surface-200 dark:border-surface-700 bg-white dark:bg-surface-800 text-surface-800 dark:text-surface-200 placeholder:text-surface-400 focus:outline-none focus:ring-2 focus:ring-primary-500/30 focus:border-primary-500 resize-none" 184 /> 185 </div> 186 )} 187 188 <div className="flex items-center justify-end gap-2 p-4 border-t border-surface-200 dark:border-surface-700"> 189 <button 190 onClick={handleClose} 191 className="px-4 py-2 text-sm font-medium text-surface-600 dark:text-surface-400 hover:text-surface-800 dark:hover:text-surface-200 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors" 192 > 193 Cancel 194 </button> 195 <button 196 onClick={handleSubmit} 197 disabled={!selectedReason || submitting} 198 className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed" 199 > 200 {submitting ? "Submitting…" : "Submit Report"} 201 </button> 202 </div> 203 </> 204 )} 205 </div> 206 </div> 207 ); 208}