import { failureMessage } from "$lib/api"; import { isAlertEnabled, type AlertKind } from "$lib/alert-prefs"; import { toast, type ToastAction } from "$lib/components/ui/toast-state"; /** Brief confirmation — readable but not sticky. */ const SUCCESS_DURATION_MS = 4000; /** Errors need more time to read. */ const ERROR_DURATION_MS = 7000; /** Link actions need dwell time to click. */ const ACTION_DURATION_MS = 10000; type NotifyOptions = { description?: string; action?: ToastAction; actions?: ToastAction[]; duration?: number; /** * Operator alert category (P0-9). When set, toast is gated by local alert prefs. * Omit for always-on UI feedback (forms, undo, validation). */ alert?: AlertKind; }; export type NotifyApiErrorOptions = NotifyOptions & { /** When set, used as toast title; failure message becomes the description. */ title?: string; }; function hasLinkActions(opts: NotifyOptions): boolean { return Boolean(opts.action || opts.actions?.length); } function resolveDuration(opts: NotifyOptions, fallbackMs: number): number { if (opts.duration !== undefined) return opts.duration; if (hasLinkActions(opts)) return ACTION_DURATION_MS; return fallbackMs; } function shouldShowToast(opts: NotifyOptions): boolean { if (!opts.alert) return true; // Never mute toasts that carry onClick actions (e.g. Undo cancel). const actions = opts.actions?.length ? opts.actions : opts.action ? [opts.action] : []; if (actions.some((a) => typeof a.onClick === "function")) return true; return isAlertEnabled(opts.alert); } /** Show a toast (and keep Alert strings in sync when callers also set local state). */ export function notifySuccess(title: string, descriptionOrOpts?: string | NotifyOptions) { const opts = typeof descriptionOrOpts === "string" ? { description: descriptionOrOpts } : (descriptionOrOpts ?? {}); if (!shouldShowToast(opts)) return; toast({ title, description: opts.description, variant: "default", action: opts.action, actions: opts.actions, duration: resolveDuration(opts, SUCCESS_DURATION_MS) }); } export function notifyError(title: string, descriptionOrOpts?: string | NotifyOptions) { const opts = typeof descriptionOrOpts === "string" ? { description: descriptionOrOpts } : (descriptionOrOpts ?? {}); if (!shouldShowToast(opts)) return; toast({ title, description: opts.description, variant: "destructive", action: opts.action, actions: opts.actions, duration: resolveDuration(opts, ERROR_DURATION_MS) }); } /** * Standard path for `api()` failures → toast (and optional page banner). * Returns the message so callers can assign `error = notifyApiError(...)`. * Message is always returned even when the toast is muted by alert prefs. */ export function notifyApiError( err: unknown, fallback: string, opts?: NotifyApiErrorOptions ): string { const message = failureMessage(err, fallback); const { title, description, action, actions, duration, alert } = opts ?? {}; const toastOpts: NotifyOptions = { description, action, actions, duration, alert }; if (title !== undefined) { notifyError(title, { ...toastOpts, description: description ?? message }); } else { notifyError(message, toastOpts); } return message; }