import { useState } from 'react'; import { useNostrPublish } from '@/hooks/useNostrPublish'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useToast } from '@/hooks/useToast'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { Loader2, Flag, AlertTriangle, CheckCircle2 } from 'lucide-react'; import { cn } from '@/lib/utils'; // NIP-56 Report types with skate-specific labels const REPORT_REASONS = [ { id: 'spam', label: 'Fake/Non-existent spot', description: 'This spot does not exist or location is wrong', icon: '📍', }, { id: 'other', label: 'Stolen/Fake photos', description: 'Photos are stock images or stolen from elsewhere', icon: '📸', }, { id: 'spam', label: 'Spam or advertising', description: 'Promotional content or duplicate listings', icon: '🗑️', subtype: 'spam-ad', }, { id: 'illegal', label: 'Dangerous location', description: 'Private property, unsafe, or illegal to access', icon: '⚠️', }, { id: 'nudity', label: 'Inappropriate content', description: 'Contains offensive or adult content', icon: '🔞', }, { id: 'impersonation', label: 'Impersonation', description: 'Someone pretending to be someone else', icon: '👤', }, { id: 'other', label: 'Other', description: 'Something else not listed above', icon: '📝', subtype: 'other-general', }, ] as const; interface ReportDialogProps { open: boolean; onOpenChange: (open: boolean) => void; // What we're reporting targetType: 'spot' | 'event' | 'tour' | 'shop' | 'user' | 'listing' | 'coach' | 'filmer'; targetPubkey: string; targetEventId?: string; targetTitle?: string; // Callback after report onReported?: () => void; } export function ReportDialog({ open, onOpenChange, targetType, targetPubkey, targetEventId, targetTitle, onReported, }: ReportDialogProps) { const { user } = useCurrentUser(); const { mutateAsync: publishEvent, isPending } = useNostrPublish(); const { toast } = useToast(); const [selectedReason, setSelectedReason] = useState(''); const [additionalDetails, setAdditionalDetails] = useState(''); const [step, setStep] = useState<'form' | 'success'>('form'); const handleSubmit = async () => { if (!user || !selectedReason) return; const reason = REPORT_REASONS.find( (r) => r.id === selectedReason || r.subtype === selectedReason ); if (!reason) return; try { // Build NIP-56 report tags const tags: string[][] = []; // Always include the pubkey being reported if (targetEventId) { // Reporting an event (spot, tour, etc.) tags.push(['e', targetEventId, reason.id]); tags.push(['p', targetPubkey]); } else { // Reporting just a user/profile tags.push(['p', targetPubkey, reason.id]); } // Build content with context let content = ''; if (additionalDetails.trim()) { content = additionalDetails.trim(); } if (targetTitle) { content = content ? `${content}\n\nReported: ${targetTitle} (${targetType})` : `Reported: ${targetTitle} (${targetType})`; } await publishEvent({ kind: 1984, // NIP-56 Report content, tags, }); setStep('success'); onReported?.(); toast({ title: 'Report submitted', description: 'Thank you for helping keep SkateSpots safe.', }); } catch (error) { toast({ title: 'Failed to submit report', description: error instanceof Error ? error.message : 'Please try again', variant: 'destructive', }); } }; const handleClose = () => { onOpenChange(false); // Reset form after animation setTimeout(() => { setSelectedReason(''); setAdditionalDetails(''); setStep('form'); }, 200); }; const getReasonKey = (reason: typeof REPORT_REASONS[number]) => { return reason.subtype || reason.id; }; if (!user) { return ( Report Content

You need to be logged in to report content.

); } return ( Report Content {step === 'form' && ( Help keep the community safe by reporting inappropriate content. )} {step === 'form' ? (
{/* Target info */} {targetTitle && (

Reporting {targetType}

{targetTitle}

)} {/* Reason selection */}
{REPORT_REASONS.map((reason) => { const key = getReasonKey(reason); return (
setSelectedReason(key)} >

{reason.description}

); })}
{/* Additional details */}