import { listPinnedMessages, type PinnedMessage } from '@chat-app/shared/chat'; import { useCallback, useEffect, useState } from 'react'; import { supabase } from './supabase'; export interface UsePinnedMessagesResult { pins: PinnedMessage[]; // Optimistic insert. Caller flips the UI immediately; server insert + // realtime echo will reconcile (dedup'd by messageId). Returns the // previous snapshot so the caller can roll back on error. applyOptimisticPin: (messageId: string, pinnedBy: string) => PinnedMessage[]; applyOptimisticUnpin: (messageId: string) => PinnedMessage[]; // Hard restore for rollback after a failed server call. restorePins: (snapshot: PinnedMessage[]) => void; } // Live list of pinned messages for one conversation. Subscribes to the // `pinned_messages` realtime channel for the conv so the header pill + // side-panel update without a refetch. The `applyOptimistic*` helpers let // callers flip local state synchronously on user action so the pin button // doesn't appear unresponsive while the ~100-200ms server roundtrip + the // realtime refetch round complete. export function usePinnedMessages(conversationId: string | undefined): UsePinnedMessagesResult { const [pins, setPins] = useState([]); useEffect(() => { if (!conversationId) { setPins([]); return; } let cancelled = false; void listPinnedMessages(supabase, conversationId).then((rows) => { if (!cancelled) setPins(rows); }); const channel = supabase .channel('pinned_messages:' + conversationId) .on( 'postgres_changes', { event: '*', schema: 'public', table: 'pinned_messages', filter: 'conversation_id=eq.' + conversationId, }, () => { void listPinnedMessages(supabase, conversationId).then((rows) => { if (!cancelled) setPins(rows); }); }, ) .subscribe(); return () => { cancelled = true; void supabase.removeChannel(channel); }; }, [conversationId]); const applyOptimisticPin = useCallback( (messageId, pinnedBy) => { if (!conversationId) return pins; let snapshot: PinnedMessage[] = pins; setPins((prev) => { snapshot = prev; if (prev.some((p) => p.messageId === messageId)) return prev; const optimistic: PinnedMessage = { conversationId, messageId, pinnedBy, pinnedAt: new Date().toISOString(), }; // Newest first matches the listPinnedMessages order. return [optimistic, ...prev]; }); return snapshot; }, [conversationId, pins], ); const applyOptimisticUnpin = useCallback( (messageId) => { let snapshot: PinnedMessage[] = pins; setPins((prev) => { snapshot = prev; return prev.filter((p) => p.messageId !== messageId); }); return snapshot; }, [pins], ); const restorePins = useCallback((snapshot) => { setPins(snapshot); }, []); return { pins, applyOptimisticPin, applyOptimisticUnpin, restorePins }; }