import { useState, useEffect, useCallback, useRef } from 'react'; import { useParams, Link } from 'react-router-dom'; import { supabase, supabaseConfig } from '@/config'; import { useRealtimeSubscription } from '@/hooks/useRealtimeSubscription'; import { RealtimeStatusIndicator } from '@/components/RealtimeStatusIndicator'; import { useAuth, PERMISSION_KEYS } from '@/contexts/AuthContext'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { PaginationControls } from '@/components/ui/pagination'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Crown, Users, MessageCircle, ChevronRight, Gift, Image, Video as VideoIcon, Target, User, Eye, EyeOff, CheckCircle, Package, Sparkles, Flame, Trophy, Calendar, AlertCircle, Clock, Pencil } from 'lucide-react'; import { format } from 'date-fns'; import { IconPreview } from '@/components/ui/icon-picker'; import type { AnswerType, QuestionType } from '@sauci/shared'; import { ResponseAnswer } from '@/components/content/ResponseAnswer'; import { MATCH_TYPE_LABELS, type AdminResponseData } from '@/lib/questionResponses'; type UsageReason = 'improve_communication' | 'spice_up_intimacy' | 'deeper_connection' | 'have_fun' | 'strengthen_relationship'; type Gender = 'male' | 'female' | 'non-binary' | 'prefer-not-to-say'; // ... (interfaces and constants are the same) interface Profile { id: string; name: string | null; email: string | null; avatar_url: string | null; is_premium: boolean | null; couple_id: string | null; created_at: string | null; gender: Gender | null; usage_reason: UsageReason | null; hide_nsfw: boolean | null; onboarding_completed: boolean | null; } const usageReasonLabels: Record = { improve_communication: { label: 'Improve communication', icon: 'chatbubbles' }, spice_up_intimacy: { label: 'Spice up intimacy', icon: 'flame' }, deeper_connection: { label: 'Build deeper connection', icon: 'heart' }, have_fun: { label: 'Have fun together', icon: 'happy' }, strengthen_relationship: { label: 'Strengthen relationship', icon: 'shield-checkmark' }, }; const genderLabels: Record = { male: 'Male', female: 'Female', 'non-binary': 'Non-binary', 'prefer-not-to-say': 'Prefer not to say', }; interface Partner { id: string; name: string | null; } interface Response { id: string; answer: AnswerType; response_data: AdminResponseData; created_at: string | null; question: { id: string; text: string; question_type: QuestionType; pack: { id: string; name: string; }; }; } interface Match { id: string; match_type: keyof typeof MATCH_TYPE_LABELS; is_new: boolean; created_at: string | null; question: { id: string; text: string; }; message_count?: number; } interface EnabledPack { pack_id: string; enabled: boolean; created_at: string | null; pack: { id: string; name: string; description: string | null; icon: string | null; is_premium: boolean; is_explicit: boolean; category: { id: string; name: string; } | null; }; } interface FeatureInterest { id: string; feature_name: string; created_at: string; } interface OutstandingQuestion { id: string; text: string; intensity: number | null; question_type: QuestionType; pack: { id: string; name: string; }; partner_response: { id: string; answer: AnswerType; response_data: AdminResponseData; created_at: string | null; }; } interface CoupleStreak { id: string; couple_id: string; current_streak: number; longest_streak: number; last_active_date: string | null; last_milestone_celebrated: number | null; created_at: string; updated_at: string; } const matchTypeLabels = { yes_yes: 'Both Yes!', yes_maybe: 'Yes + Maybe', maybe_maybe: 'Both Maybe', both_answered: MATCH_TYPE_LABELS.both_answered, }; function AdminDecryptedImage({ messageId, alt }: { messageId: string; alt: string }) { const { session, isSuperAdmin } = useAuth(); const [url, setUrl] = useState(null); const [error, setError] = useState(null); useEffect(() => { if (!messageId) return; if (!isSuperAdmin) { setError("Super admin required"); return; } if (!session?.access_token) { setError("Not authenticated"); return; } let cancelled = false; let objectUrl: string | null = null; const load = async () => { try { setError(null); const res = await fetch(`${supabaseConfig.url}/functions/v1/admin-decrypt-media`, { method: 'POST', headers: { 'Content-Type': 'application/json', apikey: supabaseConfig.anonKey, Authorization: `Bearer ${session.access_token}` }, body: JSON.stringify({ messageId }), }); if (!res.ok) { const message = (await res.json().catch(() => ({})))?.error || `Failed to load media (${res.status})`; throw new Error(message); } const blob = await res.blob(); objectUrl = URL.createObjectURL(blob); if (!cancelled) setUrl(objectUrl); } catch (err) { if (!cancelled) setError(err instanceof Error ? err.message : 'Failed to load'); } }; load(); return () => { cancelled = true; if (objectUrl) URL.revokeObjectURL(objectUrl); }; }, [isSuperAdmin, messageId, session?.access_token]); if (error) { return
{error}
; } if (!url) { return
; } return {alt} window.open(url, '_blank')} />; } function AdminDecryptedVideo({ messageId }: { messageId: string }) { const { session, isSuperAdmin } = useAuth(); const [url, setUrl] = useState(null); const [error, setError] = useState(null); useEffect(() => { if (!messageId) return; if (!isSuperAdmin) { setError("Super admin required"); return; } if (!session?.access_token) { setError("Not authenticated"); return; } let cancelled = false; let objectUrl: string | null = null; const load = async () => { try { setError(null); const res = await fetch(`${supabaseConfig.url}/functions/v1/admin-decrypt-media`, { method: 'POST', headers: { 'Content-Type': 'application/json', apikey: supabaseConfig.anonKey, Authorization: `Bearer ${session.access_token}` }, body: JSON.stringify({ messageId }), }); if (!res.ok) { const message = (await res.json().catch(() => ({})))?.error || `Failed to load media (${res.status})`; throw new Error(message); } const blob = await res.blob(); objectUrl = URL.createObjectURL(blob); if (!cancelled) setUrl(objectUrl); } catch (err) { if (!cancelled) setError(err instanceof Error ? err.message : 'Failed to load'); } }; load(); return () => { cancelled = true; if (objectUrl) URL.revokeObjectURL(objectUrl); }; }, [isSuperAdmin, messageId, session?.access_token]); if (error) { return
{error}
; } if (!url) { return
; } return