import { create } from 'zustand' import { inAppNotificationsApi } from '../api/client' export interface InAppNotification { id: number type: 'simple' | 'boolean' | 'navigate' scope: 'trip' | 'user' | 'admin' target: number sender_id: number | null sender_username: string | null sender_avatar: string | null recipient_id: number title_key: string title_params: Record text_key: string text_params: Record positive_text_key: string | null negative_text_key: string | null response: 'positive' | 'negative' | null navigate_text_key: string | null navigate_target: string | null is_read: boolean created_at: string } interface RawNotification extends Omit { title_params: string | Record text_params: string | Record is_read: number | boolean } function normalizeNotification(raw: RawNotification): InAppNotification { return { ...raw, title_params: typeof raw.title_params === 'string' ? JSON.parse(raw.title_params || '{}') : raw.title_params, text_params: typeof raw.text_params === 'string' ? JSON.parse(raw.text_params || '{}') : raw.text_params, is_read: Boolean(raw.is_read), } } interface NotificationState { notifications: InAppNotification[] unreadCount: number total: number isLoading: boolean hasMore: boolean fetchNotifications: (reset?: boolean) => Promise fetchUnreadCount: () => Promise markRead: (id: number) => Promise markUnread: (id: number) => Promise markAllRead: () => Promise deleteNotification: (id: number) => Promise deleteAll: () => Promise respondToBoolean: (id: number, response: 'positive' | 'negative') => Promise handleNewNotification: (notification: RawNotification) => void handleUpdatedNotification: (notification: RawNotification) => void } const PAGE_SIZE = 20 export const useNotificationStore = create((set, get) => ({ notifications: [], unreadCount: 0, total: 0, isLoading: false, hasMore: false, fetchNotifications: async (reset = false) => { const { notifications, isLoading } = get() if (isLoading) return set({ isLoading: true }) try { const offset = reset ? 0 : notifications.length const data = await inAppNotificationsApi.list({ limit: PAGE_SIZE, offset }) const normalized = (data.notifications as RawNotification[]).map(normalizeNotification) set({ notifications: reset ? normalized : [...notifications, ...normalized], total: data.total, unreadCount: data.unread_count, hasMore: (reset ? normalized.length : notifications.length + normalized.length) < data.total, isLoading: false, }) } catch { set({ isLoading: false }) } }, fetchUnreadCount: async () => { try { const data = await inAppNotificationsApi.unreadCount() set({ unreadCount: data.count }) } catch { // best-effort } }, markRead: async (id: number) => { try { await inAppNotificationsApi.markRead(id) set(state => ({ notifications: state.notifications.map(n => n.id === id ? { ...n, is_read: true } : n), unreadCount: Math.max(0, state.unreadCount - (state.notifications.find(n => n.id === id)?.is_read ? 0 : 1)), })) } catch { // best-effort } }, markUnread: async (id: number) => { try { await inAppNotificationsApi.markUnread(id) set(state => ({ notifications: state.notifications.map(n => n.id === id ? { ...n, is_read: false } : n), unreadCount: state.unreadCount + (state.notifications.find(n => n.id === id)?.is_read ? 1 : 0), })) } catch { // best-effort } }, markAllRead: async () => { try { await inAppNotificationsApi.markAllRead() set(state => ({ notifications: state.notifications.map(n => ({ ...n, is_read: true })), unreadCount: 0, })) } catch { // best-effort } }, deleteNotification: async (id: number) => { const notification = get().notifications.find(n => n.id === id) try { await inAppNotificationsApi.delete(id) set(state => ({ notifications: state.notifications.filter(n => n.id !== id), total: Math.max(0, state.total - 1), unreadCount: notification && !notification.is_read ? Math.max(0, state.unreadCount - 1) : state.unreadCount, })) } catch { // best-effort } }, deleteAll: async () => { try { await inAppNotificationsApi.deleteAll() set({ notifications: [], total: 0, unreadCount: 0, hasMore: false }) } catch { // best-effort } }, respondToBoolean: async (id: number, response: 'positive' | 'negative') => { try { const data = await inAppNotificationsApi.respond(id, response) if (data.notification) { const normalized = normalizeNotification(data.notification as RawNotification) set(state => ({ notifications: state.notifications.map(n => n.id === id ? normalized : n), unreadCount: !state.notifications.find(n => n.id === id)?.is_read ? Math.max(0, state.unreadCount - 1) : state.unreadCount, })) } } catch { // best-effort } }, handleNewNotification: (raw: RawNotification) => { const notification = normalizeNotification(raw) set(state => ({ notifications: [notification, ...state.notifications], total: state.total + 1, unreadCount: state.unreadCount + 1, })) }, handleUpdatedNotification: (raw: RawNotification) => { const notification = normalizeNotification(raw) set(state => ({ notifications: state.notifications.map(n => n.id === notification.id ? notification : n), })) }, }))