Sprint 5 : integration et production -- securite, performance, API publique, documentation
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>
This commit is contained in:
180
frontend/app/composables/useNotifications.ts
Normal file
180
frontend/app/composables/useNotifications.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user