ReviewEditor.tsx raw
1 import { Button } from '@/components/ui/button'
2 import { Textarea } from '@/components/ui/textarea'
3 import { createRelayReviewDraftEvent } from '@/lib/draft-event'
4 import { useNostr } from '@/providers/NostrProvider'
5 import { Loader2, Star } from 'lucide-react'
6 import { NostrEvent } from 'nostr-tools'
7 import { useMemo, useState } from 'react'
8 import { useTranslation } from 'react-i18next'
9 import { toast } from 'sonner'
10
11 export default function ReviewEditor({
12 relayUrl,
13 onReviewed
14 }: {
15 relayUrl: string
16 onReviewed: (evt: NostrEvent) => void
17 }) {
18 const { t } = useTranslation()
19 const { publish } = useNostr()
20 const [stars, setStars] = useState(0)
21 const [hoverStars, setHoverStars] = useState(0)
22 const [review, setReview] = useState('')
23 const [submitting, setSubmitting] = useState(false)
24 const canSubmit = useMemo(() => stars > 0 && !!review.trim(), [stars, review])
25
26 const submit = async () => {
27 if (!canSubmit) return
28
29 setSubmitting(true)
30 try {
31 const draftEvent = createRelayReviewDraftEvent(relayUrl, review, stars)
32 const evt = await publish(draftEvent)
33 onReviewed(evt)
34 } catch (error) {
35 if (error instanceof AggregateError) {
36 error.errors.forEach((e) => toast.error(`${t('Failed to review')}: ${e.message}`))
37 } else if (error instanceof Error) {
38 toast.error(`${t('Failed to review')}: ${error.message}`)
39 }
40 console.error(error)
41 return
42 } finally {
43 setSubmitting(false)
44 }
45 }
46
47 return (
48 <div className="px-4 space-y-2">
49 <Textarea
50 className="min-h-36"
51 placeholder={t('Write a review and pick a star rating')}
52 value={review}
53 onChange={(e) => setReview(e.target.value)}
54 />
55 <div className="flex justify-between items-center">
56 <div className="flex items-center">
57 {Array.from({ length: 5 }).map((_, index) => (
58 <div
59 key={index}
60 className="pr-2 cursor-pointer"
61 onMouseEnter={() => setHoverStars(index + 1)}
62 onMouseLeave={() => setHoverStars(0)}
63 >
64 {index < (hoverStars || stars) ? (
65 <Star
66 className="size-6 text-yellow-400 fill-yellow-400"
67 onClick={() => setStars(index + 1)}
68 />
69 ) : (
70 <Star
71 className="size-6 text-muted-foreground"
72 onClick={() => setStars(index + 1)}
73 />
74 )}
75 </div>
76 ))}
77 </div>
78 <Button
79 disabled={!canSubmit}
80 variant={canSubmit ? 'default' : 'secondary'}
81 onClick={submit}
82 >
83 {submitting && <Loader2 className="animate-spin" />}
84 {t('Submit')}
85 </Button>
86 </div>
87 </div>
88 )
89 }
90