import { useState, useEffect, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { supabase } from '@/config'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { useRealtimeSubscription } from '@/hooks/useRealtimeSubscription'; import { RealtimeStatusIndicator } from '@/components/RealtimeStatusIndicator'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Tabs, TabsContent, TabsList, TabsTrigger, } from '@/components/ui/tabs'; import { formatDistanceToNow, format } from 'date-fns'; import { toast } from 'sonner'; import { Activity, ThumbsUp, ThumbsDown, Minus, Heart, MessageCircle, UserPlus, ExternalLink, } from 'lucide-react'; // Types interface ResponseActivity { id: string; user_id: string; answer: 'yes' | 'no' | 'maybe'; created_at: string; profile: { id: string; name: string | null; email: string | null; } | null; question: { id: string; text: string; pack: { name: string; } | null; } | null; } interface MatchActivity { id: string; couple_id: string; match_type: 'yes_yes' | 'yes_maybe' | 'maybe_maybe'; is_new: boolean; created_at: string; question: { id: string; text: string; } | null; coupleProfiles?: Array<{ id: string; name: string | null; email: string | null; }>; } interface MessageActivity { id: string; user_id: string; content: string | null; media_path: string | null; media_type: 'image' | 'video' | null; created_at: string; profile: { id: string; name: string | null; email: string | null; } | null; match: { id: string; question: { text: string; } | null; } | null; } interface SignupActivity { id: string; name: string | null; email: string | null; avatar_url: string | null; created_at: string; onboarding_completed: boolean | null; couple_id: string | null; } type ActivityTab = 'responses' | 'matches' | 'messages' | 'signups'; // Answer icons const answerConfig = { yes: { icon: , color: 'text-green-500', bg: 'bg-green-500/10' }, no: { icon: , color: 'text-red-500', bg: 'bg-red-500/10' }, maybe: { icon: , color: 'text-yellow-500', bg: 'bg-yellow-500/10' }, }; const matchTypeLabels = { yes_yes: { label: 'Both Yes!', variant: 'default' as const }, yes_maybe: { label: 'Yes + Maybe', variant: 'secondary' as const }, maybe_maybe: { label: 'Both Maybe', variant: 'outline' as const }, }; export function UserActivityPage() { const [activeTab, setActiveTab] = useState('responses'); const [responses, setResponses] = useState([]); const [matches, setMatches] = useState([]); const [messages, setMessages] = useState([]); const [signups, setSignups] = useState([]); const [loading, setLoading] = useState({ responses: true, matches: true, messages: true, signups: true, }); // Fetch responses const fetchResponses = useCallback(async () => { setLoading(prev => ({ ...prev, responses: true })); try { const { data, error } = await supabase .from('responses') .select(` id, user_id, answer, created_at, profile:profiles!responses_user_id_fkey(id, name, email), question:questions( id, text, pack:question_packs(name) ) `) .order('created_at', { ascending: false }) .limit(50); if (error) throw error; // Transform data to match our types (Supabase returns nested objects, not arrays for 1:1 relations) const transformed: ResponseActivity[] = (data || []).map((item: any) => ({ id: item.id, user_id: item.user_id, answer: item.answer, created_at: item.created_at, profile: item.profile, question: item.question ? { id: item.question.id, text: item.question.text, pack: item.question.pack, } : null, })); setResponses(transformed); } catch (error) { console.error('Failed to load responses:', error); toast.error("Failed to load responses"); } finally { setLoading(prev => ({ ...prev, responses: false })); } }, []); // Fetch matches with couple profiles const fetchMatches = useCallback(async () => { setLoading(prev => ({ ...prev, matches: true })); try { const { data: matchesData, error: matchesError } = await supabase .from('matches') .select(` id, couple_id, match_type, is_new, created_at, question:questions(id, text) `) .order('created_at', { ascending: false }) .limit(50); if (matchesError) throw matchesError; // Get unique couple IDs and fetch profiles const coupleIds = [...new Set((matchesData || []).map((m: any) => m.couple_id))]; if (coupleIds.length > 0) { const { data: profiles, error: profilesError } = await supabase .from('profiles') .select('id, name, email, couple_id') .in('couple_id', coupleIds); if (profilesError) throw profilesError; // Attach profiles to matches const matchesWithProfiles: MatchActivity[] = (matchesData || []).map((match: any) => ({ id: match.id, couple_id: match.couple_id, match_type: match.match_type, is_new: match.is_new, created_at: match.created_at, question: match.question, coupleProfiles: (profiles || []).filter((p: any) => p.couple_id === match.couple_id), })); setMatches(matchesWithProfiles); } else { const transformed: MatchActivity[] = (matchesData || []).map((match: any) => ({ id: match.id, couple_id: match.couple_id, match_type: match.match_type, is_new: match.is_new, created_at: match.created_at, question: match.question, coupleProfiles: [], })); setMatches(transformed); } } catch (error) { console.error('Failed to load matches:', error); toast.error("Failed to load matches"); } finally { setLoading(prev => ({ ...prev, matches: false })); } }, []); // Fetch messages const fetchMessages = useCallback(async () => { setLoading(prev => ({ ...prev, messages: true })); try { // Fetch messages with match data const { data: messagesData, error: messagesError } = await supabase .from('messages') .select(` id, user_id, content, media_path, media_type, created_at, match:matches( id, question:questions(text) ) `) .order('created_at', { ascending: false }) .limit(50); if (messagesError) throw messagesError; // Get unique user IDs and fetch profiles const userIds = [...new Set((messagesData || []).map((m: any) => m.user_id))]; let profilesMap: Record = {}; if (userIds.length > 0) { const { data: profiles, error: profilesError } = await supabase .from('profiles') .select('id, name, email') .in('id', userIds); if (profilesError) throw profilesError; profilesMap = (profiles || []).reduce((acc: any, p: any) => { acc[p.id] = p; return acc; }, {}); } // Transform data to match our types const transformed: MessageActivity[] = (messagesData || []).map((item: any) => ({ id: item.id, user_id: item.user_id, content: item.content, media_path: item.media_path, media_type: item.media_type, created_at: item.created_at, profile: profilesMap[item.user_id] || null, match: item.match ? { id: item.match.id, question: item.match.question, } : null, })); setMessages(transformed); } catch (error) { console.error('Failed to load messages:', error); toast.error("Failed to load messages"); } finally { setLoading(prev => ({ ...prev, messages: false })); } }, []); // Fetch signups const fetchSignups = useCallback(async () => { setLoading(prev => ({ ...prev, signups: true })); try { const { data, error } = await supabase .from('profiles') .select('id, name, email, avatar_url, created_at, onboarding_completed, couple_id') .order('created_at', { ascending: false }) .limit(50); if (error) throw error; setSignups(data || []); } catch (error) { console.error('Failed to load signups:', error); toast.error("Failed to load signups"); } finally { setLoading(prev => ({ ...prev, signups: false })); } }, []); // Real-time subscriptions const { status: responsesStatus } = useRealtimeSubscription({ table: 'responses', onInsert: useCallback(() => fetchResponses(), [fetchResponses]), insertToast: { enabled: true, message: () => 'New response', type: 'info' }, }); const { status: matchesStatus } = useRealtimeSubscription({ table: 'matches', onInsert: useCallback(() => fetchMatches(), [fetchMatches]), insertToast: { enabled: true, message: () => 'New match!', type: 'success' }, }); const { status: messagesStatus } = useRealtimeSubscription({ table: 'messages', onInsert: useCallback(() => fetchMessages(), [fetchMessages]), insertToast: { enabled: true, message: () => 'New message', type: 'info' }, }); const { status: signupsStatus } = useRealtimeSubscription({ table: 'profiles', onInsert: useCallback(() => fetchSignups(), [fetchSignups]), insertToast: { enabled: true, message: () => 'New user signed up!', type: 'success' }, }); // Fetch all data on mount useEffect(() => { fetchResponses(); fetchMatches(); fetchMessages(); fetchSignups(); }, [fetchResponses, fetchMatches, fetchMessages, fetchSignups]); // Get current realtime status based on active tab const getCurrentRealtimeStatus = () => { switch (activeTab) { case 'responses': return responsesStatus; case 'matches': return matchesStatus; case 'messages': return messagesStatus; case 'signups': return signupsStatus; } }; // Render user link const renderUserLink = (profile: { id: string; name: string | null; email: string | null } | null) => { if (!profile) return Unknown User; return ( {profile.name || profile.email || 'Unknown'} ); }; // Render answer badge const renderAnswerBadge = (answer: 'yes' | 'no' | 'maybe') => { const config = answerConfig[answer]; return ( {config.icon} {answer} ); }; return (
{/* Header */}

User Activity

Monitor recent user activity across the platform

{/* Tabs */} setActiveTab(v as ActivityTab)}> Responses Matches Messages Signups {/* Responses Tab */}
User Question Pack Answer Time {loading.responses ? (
) : responses.length === 0 ? ( No responses yet. ) : ( responses.map((item) => ( {renderUserLink(item.profile)} {item.question?.text || 'Unknown question'} {item.question?.pack?.name || 'Unknown'} {renderAnswerBadge(item.answer)} {formatDistanceToNow(new Date(item.created_at), { addSuffix: true })} )) )}
{/* Matches Tab */}
Couple Question Match Type Status Time {loading.matches ? (
) : matches.length === 0 ? ( No matches yet. ) : ( matches.map((item) => (
{item.coupleProfiles && item.coupleProfiles.length > 0 ? ( <> {renderUserLink(item.coupleProfiles[0])} {item.coupleProfiles.length > 1 && ( <> & {renderUserLink(item.coupleProfiles[1])} )} ) : ( Unknown couple )}
{item.question?.text || 'Unknown question'} {matchTypeLabels[item.match_type]?.label || item.match_type} {item.is_new ? ( New ) : ( Viewed )} {formatDistanceToNow(new Date(item.created_at), { addSuffix: true })}
)) )}
{/* Messages Tab */}
User Context Time Actions {loading.messages ? (
) : messages.length === 0 ? ( No messages yet. ) : ( messages.map((item) => ( {renderUserLink(item.profile)} {item.match?.question?.text || 'Unknown match'} {formatDistanceToNow(new Date(item.created_at), { addSuffix: true })} {item.match && item.profile ? ( View Chat ) : ( - )} )) )}
{/* Signups Tab */}
User Email Status Paired Joined {loading.signups ? (
) : signups.length === 0 ? ( No signups yet. ) : ( signups.map((item) => ( {(item.name || item.email || '?').charAt(0).toUpperCase()} {item.name || 'No name'} {item.email || 'No email'} {item.onboarding_completed ? ( Onboarded ) : ( Pending )} {item.couple_id ? ( Paired ) : ( Single )} {format(new Date(item.created_at), 'MMM d, yyyy')} )) )}
); }