import { writable } from 'svelte/store'; // Small global toast queue. // // Any part of the UI can drop a message and it appears in the corner for a // couple of seconds, then quietly dismisses. Replaces the ad-hoc "flash" // strings scattered across pages when the message doesn't need to live inline // with a form. export type ToastVariant = 'info' | 'success' | 'warning' | 'danger'; export interface Toast { id: number; message: string; variant: ToastVariant; ttl: number; href?: string; } export const toasts = writable([]); export interface Notice extends Toast { createdAt: string; read: boolean; } export const notificationHistory = writable([]); const historyKey = 'electric-cherry:notifications'; export function startNotificationHistory(): () => void { try { const saved: unknown = JSON.parse(sessionStorage.getItem(historyKey) ?? '[]'); if (Array.isArray(saved)) { const valid = saved.filter((item): item is Notice => item && typeof item.id === 'number' && Number.isSafeInteger(item.id) && typeof item.message === 'string' && ['info', 'success', 'warning', 'danger'].includes(item.variant) && typeof item.createdAt === 'string' && Number.isFinite(Date.parse(item.createdAt)) && typeof item.read === 'boolean' && (!item.href || (typeof item.href === 'string' && item.href.startsWith('/') && !item.href.startsWith('//')))).slice(0, 50); notificationHistory.set(valid); nextId = Math.max(nextId, ...valid.map(item => item.id + 1)); } } catch { /* Notifications still work when browser storage is unavailable. */ } return notificationHistory.subscribe(items => { try { sessionStorage.setItem(historyKey, JSON.stringify(items)); } catch { /* Optional persistence. */ } }); } export function markNotificationsRead() { notificationHistory.update(items => items.map(item => ({ ...item, read: true }))); } export function clearNotifications() { notificationHistory.set([]); } let nextId = 1; export function toast( message: string, opts: { variant?: ToastVariant; ttl?: number; href?: string } = {} ): void { const t: Toast = { id: nextId++, message, variant: opts.variant ?? 'info', ttl: opts.ttl ?? 3500, href: opts.href }; toasts.update((arr) => [...arr, t]); if (t.href || t.ttl === 0 || t.variant !== 'info') { notificationHistory.update(items => [{ ...t, createdAt: new Date().toISOString(), read: false }, ...items].slice(0, 50)); } if (t.ttl > 0) { setTimeout(() => dismiss(t.id), t.ttl); } } export function dismiss(id: number): void { toasts.update((arr) => arr.filter((t) => t.id !== id)); }