/** * Composable for toast notifications using Nuxt UI. * * Provides typed notification helpers with French messages and * integration with WebSocket events for real-time notifications. */ export type NotificationType = 'success' | 'error' | 'warning' | 'info' interface NotifyOptions { /** Toast title. */ title: string /** Toast description (optional). */ description?: string /** Notification type. */ type?: NotificationType /** Auto-close duration in milliseconds (default: 5000). */ duration?: number } /** Map notification types to Nuxt UI toast color props. */ const TYPE_COLORS: Record = { success: 'success', error: 'error', warning: 'warning', info: 'info', } /** Map notification types to Lucide icon names. */ const TYPE_ICONS: Record = { success: 'i-lucide-check-circle', error: 'i-lucide-alert-circle', warning: 'i-lucide-alert-triangle', info: 'i-lucide-info', } /** Default duration for toasts (ms). */ const DEFAULT_DURATION = 5_000 export function useNotifications() { const toast = useToast() /** * Show a toast notification. * * @param options - Notification options (title, description, type, duration) */ function notify(options: NotifyOptions): void function notify(title: string, description?: string, type?: NotificationType): void function notify( titleOrOptions: string | NotifyOptions, description?: string, type?: NotificationType, ): void { let opts: NotifyOptions if (typeof titleOrOptions === 'string') { opts = { title: titleOrOptions, description, type: type || 'info', } } else { opts = titleOrOptions } const notifType = opts.type || 'info' toast.add({ title: opts.title, description: opts.description, icon: TYPE_ICONS[notifType], color: TYPE_COLORS[notifType] as any, duration: opts.duration ?? DEFAULT_DURATION, }) } /** * Show a success toast. */ function notifySuccess(message: string, description?: string): void { notify({ title: message, description, type: 'success', }) } /** * Show an error toast. */ function notifyError(message: string, description?: string): void { notify({ title: message, description, type: 'error', duration: 8_000, }) } /** * Show a warning toast. */ function notifyWarning(message: string, description?: string): void { notify({ title: message, description, type: 'warning', }) } /** * Show an info toast. */ function notifyInfo(message: string, description?: string): void { notify({ title: message, description, type: 'info', }) } /** * Setup WebSocket event listeners that auto-show notifications. * Call this once in app.vue or a layout component. */ function setupWsNotifications(wsComposable: ReturnType): void { wsComposable.onVoteSubmitted((data) => { notifyInfo( 'Nouveau vote enregistre', data?.session_title || 'Un vote a ete soumis dans une session active.', ) }) wsComposable.onDecisionAdvanced((data) => { notifySuccess( 'Decision avancee', data?.title ? `La decision "${data.title}" est passee a l'etape suivante.` : 'Une decision a progresse dans son processus.', ) }) wsComposable.onMandateUpdated((data) => { notifyInfo( 'Mandat mis a jour', data?.title ? `Le mandat "${data.title}" a ete modifie.` : 'Un mandat a ete mis a jour.', ) }) wsComposable.onDocumentChanged((data) => { notifyInfo( 'Document modifie', data?.title ? `Le document "${data.title}" a ete modifie.` : 'Un document de reference a ete modifie.', ) }) wsComposable.onSanctuaryArchived((data) => { notifySuccess( 'Document archive au sanctuaire', data?.title ? `"${data.title}" a ete ancre sur IPFS.` : 'Un document a ete archive de maniere immuable.', ) }) } return { notify, notifySuccess, notifyError, notifyWarning, notifyInfo, setupWsNotifications, } }