import { useState, useEffect, useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { adminData } from '@/lib/adminApi'; import { auditedAdminData } from '@/hooks/useAuditedAdminData'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { PaginationControls } from '@/components/ui/pagination'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Card } from '@/components/ui/card'; import { Plus, Pencil, Trash2, Zap, Loader2, Clock } from 'lucide-react'; import { Checkbox } from '@/components/ui/checkbox'; import { toast } from 'sonner'; import { IconPreview } from '@/components/ui/icon-picker'; import { ContentReviewControl } from '@/components/content/ContentReviewControl'; import type { ContentReviewStatus } from '@sauci/shared'; import { clampDareIntensity, MAX_DARE_INTENSITY, MIN_DARE_INTENSITY } from '@/lib/dareIntensity'; // ============================================================================= // Types // ============================================================================= interface DarePack { id: string; name: string; description: string | null; icon: string | null; } interface Dare { id: string; pack_id: string; text: string; intensity: number; suggested_duration_hours: number | null; created_at: string | null; content_status?: ContentReviewStatus | null; content_review_reason?: string | null; } // Duration options for dares const DURATION_OPTIONS = [ { value: '', label: 'No suggestion' }, { value: '1', label: '1 hour' }, { value: '6', label: '6 hours' }, { value: '12', label: '12 hours' }, { value: '24', label: '24 hours' }, { value: '72', label: '3 days' }, { value: '168', label: '1 week' }, ]; // Format duration for display const formatDuration = (hours: number | null): string => { if (hours === null) return '—'; if (hours < 24) return `${hours}h`; if (hours < 168) return `${Math.round(hours / 24)}d`; return `${Math.round(hours / 168)}w`; }; // ============================================================================= // Component // ============================================================================= export function DaresPage() { const { packId } = useParams<{ packId: string }>(); const [pack, setPack] = useState(null); const [dares, setDares] = useState([]); const [loading, setLoading] = useState(true); const [dialogOpen, setDialogOpen] = useState(false); const [editingDare, setEditingDare] = useState(null); const [saving, setSaving] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); const [bulkDeleting, setBulkDeleting] = useState(false); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(25); const [totalCount, setTotalCount] = useState(0); // Form state const [formData, setFormData] = useState<{ text: string; intensity: string; suggested_duration_hours: string; }>({ text: '', intensity: '1', suggested_duration_hours: '', }); const fetchData = useCallback(async () => { if (!packId) return; setLoading(true); try { // Fetch pack info const { data: packData } = await adminData .from('dare_packs') .select('id, name, description, icon') .eq('id', packId) .single(); setPack(packData); const from = (page - 1) * pageSize; const to = from + pageSize - 1; // Fetch dares const { data: dareData, error, count } = await adminData .from('dares') .select('*', { count: 'exact' }) .eq('pack_id', packId) .order('created_at', { ascending: true }) .range(from, to); if (error) throw error; setTotalCount(count || 0); setDares(dareData || []); } catch (error) { toast.error('Failed to load dares'); console.error(error); } finally { setLoading(false); } }, [packId, page, pageSize]); useEffect(() => { fetchData(); }, [fetchData]); useEffect(() => { const totalPages = Math.max(1, Math.ceil(totalCount / pageSize)); if (page > totalPages) { setPage(totalPages); } }, [page, pageSize, totalCount]); useEffect(() => { setPage(1); setSelectedIds(new Set()); }, [packId]); useEffect(() => { setSelectedIds(new Set()); }, [page, pageSize]); const openCreateDialog = () => { setEditingDare(null); setFormData({ text: '', intensity: '1', suggested_duration_hours: '' }); setDialogOpen(true); }; const openEditDialog = (dare: Dare) => { setEditingDare(dare); setFormData({ text: dare.text, intensity: String(dare.intensity ?? 1), suggested_duration_hours: dare.suggested_duration_hours?.toString() || '', }); setDialogOpen(true); }; const handleSave = async () => { if (!formData.text.trim()) { toast.error('Dare text is required'); return; } const intensity = clampDareIntensity(formData.intensity); setSaving(true); try { const dareData = { text: formData.text, intensity, suggested_duration_hours: formData.suggested_duration_hours ? parseInt(formData.suggested_duration_hours, 10) : null, }; if (editingDare) { const { error } = await auditedAdminData.update('dares', editingDare.id, dareData); if (error) throw error; toast.success('Dare updated'); } else { const { error } = await auditedAdminData.insert('dares', { ...dareData, pack_id: packId, }); if (error) throw error; toast.success('Dare created'); } setDialogOpen(false); fetchData(); } catch (error) { toast.error('Failed to save dare'); console.error(error); } finally { setSaving(false); } }; const handleDelete = async (dare: Dare) => { if (!confirm('Delete this dare? This cannot be undone.')) { return; } try { const { error } = await auditedAdminData.delete('dares', dare.id); if (error) throw error; toast.success('Dare deleted'); fetchData(); } catch (error) { toast.error('Failed to delete dare'); console.error(error); } }; // Selection handlers const toggleSelection = (id: string) => { const next = new Set(selectedIds); if (next.has(id)) { next.delete(id); } else { next.add(id); } setSelectedIds(next); }; const toggleSelectAll = () => { if (selectedIds.size === dares.length) { setSelectedIds(new Set()); } else { setSelectedIds(new Set(dares.map(d => d.id))); } }; const handleBulkDelete = async () => { if (selectedIds.size === 0) return; const count = selectedIds.size; if (!confirm(`Delete ${count} dare${count !== 1 ? 's' : ''}? This cannot be undone.`)) { return; } setBulkDeleting(true); try { await Promise.all( Array.from(selectedIds).map(id => auditedAdminData.delete('dares', id)) ); toast.success(`${count} dare${count !== 1 ? 's' : ''} deleted`); setSelectedIds(new Set()); fetchData(); } catch (error) { toast.error('Failed to delete dares'); console.error(error); } finally { setBulkDeleting(false); } }; if (loading) { return (
); } return (
{/* Header */}

{pack && ( <> {pack.name} )}

{totalCount} dare{totalCount !== 1 ? 's' : ''} in this pack

{editingDare ? 'Edit Dare' : 'Add Dare'} {editingDare ? 'Update the dare details below.' : 'Add a new dare to this pack.'}