import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useState, useEffect, useMemo, useCallback } from 'react'; import { useAuth } from '../auth/AuthProvider'; import { getUserNotifications, getNotifications, createNotification, markNotificationAsRead, getCurrentUserTenantId, } from '../services/api'; import { Notification, NotificationPlatform, NotificationStatus, CreateNotificationRequest, } from '../types'; export interface DraftMessage { id: string; recipientId: string; recipientEmail: string; recipientName?: string; subject: string; message: string; platform?: NotificationPlatform; updatedAt: string; } export function useInAppNotifications() { const { user, claims } = useAuth(); const queryClient = useQueryClient(); const userId = (user?.id || claims?.sub || '') as string; const userEmail = (user?.email || claims?.email || '') as string; const draftsStorageKey = `pfm_partner_mailbox_drafts_${userId || 'default'}`; const starredStorageKey = `pfm_partner_mailbox_starred_${userId || 'default'}`; const trashStorageKey = `pfm_partner_mailbox_trash_${userId || 'default'}`; const sentStorageKey = `pfm_partner_mailbox_sent_${userId || 'default'}`; const readStorageList = useCallback((key: string, fallbackKey?: string): T[] => { try { const stored = localStorage.getItem(key); const parsed: T[] = stored ? JSON.parse(stored) : []; if (fallbackKey && fallbackKey !== key) { const fallbackStored = localStorage.getItem(fallbackKey); const fallbackParsed: T[] = fallbackStored ? JSON.parse(fallbackStored) : []; if (fallbackParsed.length > 0) { const merged = [...parsed]; for (const item of fallbackParsed) { const itemId = (item as any)?.id ?? item; if (!merged.some(m => ((m as any)?.id ?? m) === itemId)) { merged.push(item); } } localStorage.setItem(key, JSON.stringify(merged)); localStorage.removeItem(fallbackKey); return merged; } } return parsed; } catch { return []; } }, []); const [starredIds, setStarredIds] = useState(() => readStorageList(starredStorageKey, 'pfm_partner_mailbox_starred_default') ); const [trashedIds, setTrashedIds] = useState(() => readStorageList(trashStorageKey, 'pfm_partner_mailbox_trash_default') ); const [drafts, setDrafts] = useState(() => readStorageList(draftsStorageKey, 'pfm_partner_mailbox_drafts_default') ); const [localSent, setLocalSent] = useState(() => readStorageList(sentStorageKey, 'pfm_partner_mailbox_sent_default') ); const syncFromStorage = useCallback(() => { setStarredIds(readStorageList(starredStorageKey, 'pfm_partner_mailbox_starred_default')); setTrashedIds(readStorageList(trashStorageKey, 'pfm_partner_mailbox_trash_default')); setDrafts(readStorageList(draftsStorageKey, 'pfm_partner_mailbox_drafts_default')); setLocalSent(readStorageList(sentStorageKey, 'pfm_partner_mailbox_sent_default')); }, [readStorageList, starredStorageKey, trashStorageKey, draftsStorageKey, sentStorageKey]); useEffect(() => { syncFromStorage(); }, [syncFromStorage]); useEffect(() => { const handleSync = () => syncFromStorage(); window.addEventListener('pfm-mailbox-sync', handleSync); window.addEventListener('storage', handleSync); return () => { window.removeEventListener('pfm-mailbox-sync', handleSync); window.removeEventListener('storage', handleSync); }; }, [syncFromStorage]); const { data: rawNotifications = [], isLoading, refetch } = useQuery({ queryKey: ['inapp-notifications', userId], queryFn: async () => { let list = await getUserNotifications(userId || undefined); if (!list || list.length === 0) { const orgList = await getNotifications(); if (orgList && orgList.length > 0) { list = orgList; } } return list || []; }, refetchInterval: 30000, staleTime: 10000, }); const inAppNotifications = useMemo(() => { return rawNotifications.filter( n => Number(n.platform) === NotificationPlatform.InApp || n.platform === (NotificationPlatform.InApp as any) ); }, [rawNotifications]); const inboxNotifications = useMemo(() => { return inAppNotifications.filter( n => !trashedIds.includes(String(n.id)) && Number(n.status) !== NotificationStatus.Draft ); }, [inAppNotifications, trashedIds]); const allMailboxNotifications = inboxNotifications; const unreadNotifications = useMemo(() => { return inboxNotifications.filter( n => Number(n.status) !== NotificationStatus.Read && Number(n.status) !== NotificationStatus.Cancelled && Number(n.status) !== NotificationStatus.Failed ); }, [inboxNotifications]); const unreadCount = unreadNotifications.length; const top5Unread = useMemo(() => unreadNotifications.slice(0, 5), [unreadNotifications]); const starredNotifications = useMemo(() => { return inAppNotifications.filter( n => starredIds.includes(String(n.id)) && !trashedIds.includes(String(n.id)) ); }, [inAppNotifications, starredIds, trashedIds]); const trashedNotifications = useMemo(() => { return inAppNotifications.filter(n => trashedIds.includes(String(n.id))); }, [inAppNotifications, trashedIds]); const markAsReadMutation = useMutation({ mutationFn: async (id: number | string) => { await markNotificationAsRead(id); }, onMutate: async (id) => { await queryClient.cancelQueries({ queryKey: ['inapp-notifications', userId] }); queryClient.setQueryData(['inapp-notifications', userId], (old = []) => old.map(n => (String(n.id) === String(id) ? { ...n, status: NotificationStatus.Read } : n)) ); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ['inapp-notifications'] }); }, }); const markAllAsRead = useCallback(async () => { const unread = unreadNotifications; for (const item of unread) { try { await markNotificationAsRead(item.id); } catch {} } queryClient.invalidateQueries({ queryKey: ['inapp-notifications'] }); }, [unreadNotifications, queryClient]); const toggleStar = useCallback((id: string | number) => { const strId = String(id); setStarredIds(prev => { const next = prev.includes(strId) ? prev.filter(x => x !== strId) : [...prev, strId]; try { localStorage.setItem(starredStorageKey, JSON.stringify(next)); window.dispatchEvent(new Event('pfm-mailbox-sync')); } catch {} return next; }); }, [starredStorageKey]); const deleteMessage = useCallback((id: string | number) => { const strId = String(id); setTrashedIds(prev => { const next = prev.includes(strId) ? prev : [...prev, strId]; try { localStorage.setItem(trashStorageKey, JSON.stringify(next)); window.dispatchEvent(new Event('pfm-mailbox-sync')); } catch {} return next; }); }, [trashStorageKey]); const restoreMessage = useCallback((id: string | number) => { const strId = String(id); setTrashedIds(prev => { const next = prev.filter(x => x !== strId); try { localStorage.setItem(trashStorageKey, JSON.stringify(next)); window.dispatchEvent(new Event('pfm-mailbox-sync')); } catch {} return next; }); }, [trashStorageKey]); const permanentDelete = useCallback((id: string | number) => { const strId = String(id); setTrashedIds(prev => { const next = prev.filter(x => x !== strId); try { localStorage.setItem(trashStorageKey, JSON.stringify(next)); } catch {} return next; }); setLocalSent(prev => { const next = prev.filter(n => String(n.id) !== strId); try { localStorage.setItem(sentStorageKey, JSON.stringify(next)); window.dispatchEvent(new Event('pfm-mailbox-sync')); } catch {} return next; }); }, [trashStorageKey, sentStorageKey]); const saveDraft = useCallback((draft: Omit & { id?: string }) => { const draftId = draft.id || `draft_${Date.now()}`; const newDraft: DraftMessage = { ...draft, id: draftId, updatedAt: new Date().toISOString(), }; setDrafts(prev => { const current = readStorageList(draftsStorageKey, 'pfm_partner_mailbox_drafts_default'); const base = current.length >= prev.length ? current : prev; const filtered = base.filter(d => d.id !== draftId); const next = [newDraft, ...filtered]; try { localStorage.setItem(draftsStorageKey, JSON.stringify(next)); window.dispatchEvent(new Event('pfm-mailbox-sync')); } catch {} return next; }); return draftId; }, [draftsStorageKey, readStorageList]); const deleteDraft = useCallback((id: string) => { setDrafts(prev => { const current = readStorageList(draftsStorageKey, 'pfm_partner_mailbox_drafts_default'); const base = current.length >= prev.length ? current : prev; const next = base.filter(d => d.id !== id); try { localStorage.setItem(draftsStorageKey, JSON.stringify(next)); window.dispatchEvent(new Event('pfm-mailbox-sync')); } catch {} return next; }); }, [draftsStorageKey, readStorageList]); const sendMessageMutation = useMutation({ mutationFn: async (payload: { recipientId: string; recipientEmail: string; recipientName?: string; subject: string; message: string; platform?: NotificationPlatform; organisationId?: number | string; draftId?: string; }) => { const orgId = payload.organisationId || getCurrentUserTenantId() || 1; const targetPlatform = payload.platform || NotificationPlatform.InApp; const formattedDestination = `To: ${payload.recipientEmail} | From: ${userEmail || 'Partner User'}`; const req: CreateNotificationRequest = { organisationId: orgId, recipient: payload.recipientId, platform: targetPlatform, subject: payload.subject, destination: formattedDestination, message: payload.message, status: targetPlatform === NotificationPlatform.InApp ? NotificationStatus.Delivered : NotificationStatus.Queued, }; await createNotification(req); const sentItem: Notification = { id: `sent_${Date.now()}`, organisationId: orgId, recipient: payload.recipientId, platform: targetPlatform, status: NotificationStatus.Sent, subject: payload.subject, message: payload.message, destination: formattedDestination, createdAt: new Date().toISOString(), sentAt: new Date().toISOString(), }; setLocalSent(prev => { const next = [sentItem, ...prev]; try { localStorage.setItem(sentStorageKey, JSON.stringify(next)); window.dispatchEvent(new Event('pfm-mailbox-sync')); } catch {} return next; }); if (payload.draftId) { deleteDraft(payload.draftId); } }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['inapp-notifications'] }); }, }); return { rawNotifications, inAppNotifications, allMailboxNotifications, inboxNotifications, unreadNotifications, unreadCount, top5Unread, starredNotifications, trashedNotifications, sentNotifications: localSent, drafts, starredIds, trashedIds, isLoading, refetch, markAsRead: (id: string | number) => markAsReadMutation.mutate(id), markAllAsRead, toggleStar, deleteMessage, restoreMessage, permanentDelete, saveDraft, deleteDraft, sendMessage: sendMessageMutation.mutateAsync, isSending: sendMessageMutation.isPending, }; }