88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
import { Button } from '@/components/ui/button'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
import { createRelayReviewDraftEvent } from '@/lib/draft-event'
|
|
import { formatError } from '@/lib/error'
|
|
import { useNostr } from '@/providers/NostrProvider'
|
|
import { Loader2, Star } from 'lucide-react'
|
|
import { NostrEvent } from 'nostr-tools'
|
|
import { useMemo, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { toast } from 'sonner'
|
|
|
|
export default function ReviewEditor({
|
|
relayUrl,
|
|
onReviewed
|
|
}: {
|
|
relayUrl: string
|
|
onReviewed: (evt: NostrEvent) => void
|
|
}) {
|
|
const { t } = useTranslation()
|
|
const { publish } = useNostr()
|
|
const [stars, setStars] = useState(0)
|
|
const [hoverStars, setHoverStars] = useState(0)
|
|
const [review, setReview] = useState('')
|
|
const [submitting, setSubmitting] = useState(false)
|
|
const canSubmit = useMemo(() => stars > 0 && !!review.trim(), [stars, review])
|
|
|
|
const submit = async () => {
|
|
if (!canSubmit) return
|
|
|
|
setSubmitting(true)
|
|
try {
|
|
const draftEvent = createRelayReviewDraftEvent(relayUrl, review, stars)
|
|
const evt = await publish(draftEvent)
|
|
onReviewed(evt)
|
|
} catch (error) {
|
|
const errors = formatError(error)
|
|
errors.forEach((err) => {
|
|
toast.error(`${t('Failed to review')}: ${err}`, { duration: 10_000 })
|
|
})
|
|
return
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="px-4 space-y-2">
|
|
<Textarea
|
|
className="min-h-36"
|
|
placeholder={t('Write a review and pick a star rating')}
|
|
value={review}
|
|
onChange={(e) => setReview(e.target.value)}
|
|
/>
|
|
<div className="flex justify-between items-center">
|
|
<div className="flex items-center">
|
|
{Array.from({ length: 5 }).map((_, index) => (
|
|
<div
|
|
key={index}
|
|
className="pr-2 cursor-pointer"
|
|
onMouseEnter={() => setHoverStars(index + 1)}
|
|
onMouseLeave={() => setHoverStars(0)}
|
|
>
|
|
{index < (hoverStars || stars) ? (
|
|
<Star
|
|
className="size-6 text-yellow-400 fill-yellow-400"
|
|
onClick={() => setStars(index + 1)}
|
|
/>
|
|
) : (
|
|
<Star
|
|
className="size-6 text-muted-foreground"
|
|
onClick={() => setStars(index + 1)}
|
|
/>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
<Button
|
|
disabled={!canSubmit}
|
|
variant={canSubmit ? 'default' : 'secondary'}
|
|
onClick={submit}
|
|
>
|
|
{submitting && <Loader2 className="animate-spin" />}
|
|
{t('Submit')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|