ReportDialog.tsx raw
1 import { Button } from '@/components/ui/button'
2 import {
3 Dialog,
4 DialogContent,
5 DialogDescription,
6 DialogHeader,
7 DialogTitle
8 } from '@/components/ui/dialog'
9 import {
10 Drawer,
11 DrawerContent,
12 DrawerDescription,
13 DrawerHeader,
14 DrawerTitle
15 } from '@/components/ui/drawer'
16 import { Label } from '@/components/ui/label'
17 import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
18 import { createReportDraftEvent } from '@/lib/draft-event'
19 import { useNostr } from '@/providers/NostrProvider'
20 import { useScreenSize } from '@/providers/ScreenSizeProvider'
21 import { Loader } from 'lucide-react'
22 import { NostrEvent } from 'nostr-tools'
23 import { useState } from 'react'
24 import { useTranslation } from 'react-i18next'
25 import { toast } from 'sonner'
26
27 export default function ReportDialog({
28 event,
29 isOpen,
30 closeDialog
31 }: {
32 event: NostrEvent
33 isOpen: boolean
34 closeDialog: () => void
35 }) {
36 const { isSmallScreen } = useScreenSize()
37
38 if (isSmallScreen) {
39 return (
40 <Drawer
41 open={isOpen}
42 onOpenChange={(open) => {
43 if (!open) {
44 closeDialog()
45 }
46 }}
47 >
48 <DrawerContent>
49 <DrawerHeader>
50 <DrawerTitle className="hidden" />
51 <DrawerDescription className="hidden" />
52 </DrawerHeader>
53 <div className="p-4">
54 <ReportContent event={event} closeDialog={closeDialog} />
55 </div>
56 </DrawerContent>
57 </Drawer>
58 )
59 }
60
61 return (
62 <Dialog
63 open={isOpen}
64 onOpenChange={(open) => {
65 if (!open) {
66 closeDialog()
67 }
68 }}
69 >
70 <DialogContent>
71 <DialogHeader>
72 <DialogTitle className="hidden" />
73 <DialogDescription className="hidden" />
74 </DialogHeader>
75 <ReportContent event={event} closeDialog={closeDialog} />
76 </DialogContent>
77 </Dialog>
78 )
79 }
80
81 function ReportContent({ event, closeDialog }: { event: NostrEvent; closeDialog: () => void }) {
82 const { t } = useTranslation()
83 const { pubkey, publish } = useNostr()
84 const [reason, setReason] = useState<string | null>(null)
85 const [reporting, setReporting] = useState(false)
86
87 const handleReport = async () => {
88 if (!reason || !pubkey) return
89
90 try {
91 setReporting(true)
92 const draftEvent = createReportDraftEvent(event, reason)
93 await publish(draftEvent)
94 toast.success(t('Successfully report'))
95 closeDialog()
96 } catch (error) {
97 const errors = error instanceof AggregateError ? error.errors : [error]
98 errors.forEach((err) => {
99 toast.error(
100 `${t('Failed to report')}: ${err instanceof Error ? err.message : String(err)}`,
101 { duration: 10_000 }
102 )
103 console.error(err)
104 })
105 return
106 } finally {
107 setReporting(false)
108 }
109 }
110
111 return (
112 <div className="w-full space-y-4">
113 <RadioGroup value={reason} onValueChange={setReason} className="space-y-2">
114 {['nudity', 'malware', 'profanity', 'illegal', 'spam', 'other'].map((item) => (
115 <div key={item} className="flex items-center space-x-2">
116 <RadioGroupItem value={item} id={item} />
117 <Label htmlFor={item} className="text-base">
118 {t(item)}
119 </Label>
120 </div>
121 ))}
122 </RadioGroup>
123 <Button
124 variant="destructive"
125 className="w-full"
126 disabled={!reason || reporting}
127 onClick={(e) => {
128 e.stopPropagation()
129 handleReport()
130 }}
131 >
132 {reporting && <Loader className="animate-spin" />}
133 {t('Report')}
134 </Button>
135 </div>
136 )
137 }
138