import type { PresenceState } from '@chat-app/shared/supabase'; import { useEffect, useState } from 'react'; import { supabase } from './supabase'; export interface PeerPresence { state: PresenceState; statusMessage: string | null; } // Subscribe to a single peer's presence_state + status_message via Supabase // realtime. Returns null until the first row arrives, or when userId is // undefined. export function usePeerPresence(userId: string | undefined): PeerPresence | null { const [presence, setPresence] = useState(null); useEffect(() => { if (!userId) { setPresence(null); return; } let cancelled = false; void supabase .from('profiles') .select('presence_state, status_message') .eq('user_id', userId) .maybeSingle() .then(({ data }) => { if (cancelled || !data) return; setPresence({ state: (data.presence_state as PresenceState | null) ?? 'offline', statusMessage: data.status_message ?? null, }); }); const channel = supabase .channel('peer-presence:' + userId) .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'profiles', filter: 'user_id=eq.' + userId, }, (payload: { new: Record }) => { const nextState = payload.new['presence_state']; const nextMsg = payload.new['status_message']; setPresence((prev) => { const state = typeof nextState === 'string' ? (nextState as PresenceState) : prev?.state ?? 'offline'; const statusMessage = nextMsg === null ? null : typeof nextMsg === 'string' ? nextMsg : prev?.statusMessage ?? null; return { state, statusMessage }; }); }, ) .subscribe(); return () => { cancelled = true; void supabase.removeChannel(channel); }; }, [userId]); return presence; }