Backend: rate limiter, security headers, blockchain cache service avec RPC, public API (7 endpoints read-only), WebSocket auth + heartbeat, DB connection pooling, structured logging, health check DB. Frontend: API retry/timeout, WebSocket auth + heartbeat + typed events, notifications toast, mobile hamburger + drawer, error boundary, offline banner, loading skeletons, dashboard enrichi. Documentation: guides utilisateur complets (demarrage, vote, sanctuaire, FAQ 30+), guide deploiement, politique securite. 123 tests, 155 fichiers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
4.2 KiB
TypeScript
181 lines
4.2 KiB
TypeScript
/**
|
|
* 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<NotificationType, string> = {
|
|
success: 'success',
|
|
error: 'error',
|
|
warning: 'warning',
|
|
info: 'info',
|
|
}
|
|
|
|
/** Map notification types to Lucide icon names. */
|
|
const TYPE_ICONS: Record<NotificationType, string> = {
|
|
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<typeof useWebSocket>): 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,
|
|
}
|
|
}
|