import { getDeviceId } from '../lib/device'; export type GuestNotificationCta = { label: string; href: string; }; export type GuestNotificationItem = { id: number; type: string; title: string; body: string | null; status: 'new' | 'read' | 'dismissed'; createdAt: string; readAt?: string | null; dismissedAt?: string | null; cta?: GuestNotificationCta | null; payload?: Record | null; }; export type GuestNotificationFetchResult = { notifications: GuestNotificationItem[]; unreadCount: number; etag: string | null; notModified: boolean; }; type GuestNotificationResponse = { data?: Array<{ id?: number | string; type?: string; title?: string; body?: string | null; status?: 'new' | 'read' | 'dismissed'; created_at?: string; read_at?: string | null; dismissed_at?: string | null; cta?: GuestNotificationCta | null; payload?: Record | null; }>; meta?: { unread_count?: number; }; }; type GuestNotificationRow = NonNullable[number]; function buildHeaders(etag?: string | null): HeadersInit { const headers: Record = { Accept: 'application/json', 'Content-Type': 'application/json', 'X-Device-Id': getDeviceId(), }; if (etag) { headers['If-None-Match'] = etag; } return headers; } function mapNotification(payload: GuestNotificationRow): GuestNotificationItem { return { id: Number(payload.id ?? 0), type: payload.type ?? 'broadcast', title: payload.title ?? '', body: payload.body ?? null, status: payload.status === 'read' || payload.status === 'dismissed' ? payload.status : 'new', createdAt: payload.created_at ?? new Date().toISOString(), readAt: payload.read_at ?? null, dismissedAt: payload.dismissed_at ?? null, cta: payload.cta ?? null, payload: payload.payload ?? null, }; } export async function fetchGuestNotifications(eventToken: string, etag?: string | null): Promise { const response = await fetch(`/api/v1/events/${encodeURIComponent(eventToken)}/notifications`, { method: 'GET', headers: buildHeaders(etag), credentials: 'include', }); if (response.status === 304 && etag) { return { notifications: [], unreadCount: 0, etag, notModified: true, }; } if (!response.ok) { const reason = await safeParseError(response); throw new Error(reason ?? 'Benachrichtigungen konnten nicht geladen werden.'); } const body = (await response.json()) as GuestNotificationResponse; const rows = Array.isArray(body.data) ? body.data : []; const notifications = rows.map(mapNotification); const unreadCount = typeof body.meta?.unread_count === 'number' ? body.meta.unread_count : notifications.filter((item) => item.status === 'new').length; return { notifications, unreadCount, etag: response.headers.get('ETag'), notModified: false, }; } export async function markGuestNotificationRead(eventToken: string, notificationId: number): Promise { await postNotificationAction(eventToken, notificationId, 'read'); } export async function dismissGuestNotification(eventToken: string, notificationId: number): Promise { await postNotificationAction(eventToken, notificationId, 'dismiss'); } async function postNotificationAction(eventToken: string, notificationId: number, action: 'read' | 'dismiss'): Promise { const response = await fetch(`/api/v1/events/${encodeURIComponent(eventToken)}/notifications/${notificationId}/${action}`, { method: 'POST', headers: buildHeaders(), credentials: 'include', }); if (!response.ok) { const reason = await safeParseError(response); throw new Error(reason ?? 'Aktion konnte nicht ausgeführt werden.'); } } async function safeParseError(response: Response): Promise { try { const payload = await response.clone().json(); const message = payload?.error?.message ?? payload?.message; if (typeof message === 'string' && message.trim() !== '') { return message.trim(); } } catch (error) { console.warn('Failed to parse notification API error', error); } return null; }