From 9d1b9a78c4f567b68c79a952a1feff0352e5c4d0 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 22 Oct 2025 12:37:11 +0000 Subject: [PATCH] refactor: unify notifications into global toast --- frontend-modern/src/App.tsx | 2 - .../src/components/NotificationContainer.tsx | 25 ---- .../src/components/NotificationToast.tsx | 136 ------------------ .../src/components/Toast/Toast.tsx | 5 +- frontend-modern/src/stores/notifications.ts | 47 ++---- frontend-modern/src/utils/toast.ts | 27 ++-- 6 files changed, 32 insertions(+), 210 deletions(-) delete mode 100644 frontend-modern/src/components/NotificationContainer.tsx delete mode 100644 frontend-modern/src/components/NotificationToast.tsx diff --git a/frontend-modern/src/App.tsx b/frontend-modern/src/App.tsx index 8a2c3b3..985d8fb 100644 --- a/frontend-modern/src/App.tsx +++ b/frontend-modern/src/App.tsx @@ -22,7 +22,6 @@ import { Alerts } from './pages/Alerts'; import { DockerHosts } from './components/Docker/DockerHosts'; import { ToastContainer } from './components/Toast/Toast'; import { ErrorBoundary } from './components/ErrorBoundary'; -import NotificationContainer from './components/NotificationContainer'; import { SecurityWarning } from './components/SecurityWarning'; import { Login } from './components/Login'; import { logger } from './utils/logger'; @@ -585,7 +584,6 @@ function App() { - diff --git a/frontend-modern/src/components/NotificationContainer.tsx b/frontend-modern/src/components/NotificationContainer.tsx deleted file mode 100644 index 5288412..0000000 --- a/frontend-modern/src/components/NotificationContainer.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, For } from 'solid-js'; -import { Portal } from 'solid-js/web'; -import NotificationToast from './NotificationToast'; -import { notificationStore } from '../stores/notifications'; - -const NotificationContainer: Component = () => { - return ( - -
- - {(notification) => ( - notificationStore.remove(notification.id)} - /> - )} - -
-
- ); -}; - -export default NotificationContainer; diff --git a/frontend-modern/src/components/NotificationToast.tsx b/frontend-modern/src/components/NotificationToast.tsx deleted file mode 100644 index 20acd1b..0000000 --- a/frontend-modern/src/components/NotificationToast.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { Component, createSignal, onCleanup, onMount, Show } from 'solid-js'; - -interface NotificationToastProps { - message: string; - type: 'success' | 'error' | 'info'; - duration?: number; - onClose?: () => void; -} - -const NotificationToast: Component = (props) => { - const [isVisible, setIsVisible] = createSignal(true); - const [isLeaving, setIsLeaving] = createSignal(false); - - const handleClose = () => { - setIsLeaving(true); - setTimeout(() => { - setIsVisible(false); - props.onClose?.(); - }, 300); - }; - - onMount(() => { - if (props.duration && props.duration > 0) { - const timer = setTimeout(() => { - handleClose(); - }, props.duration); - - onCleanup(() => clearTimeout(timer)); - } - }); - - const iconColor = () => { - switch (props.type) { - case 'success': - return 'text-green-400'; - case 'error': - return 'text-red-400'; - default: - return 'text-blue-400'; - } - }; - - const icon = () => { - switch (props.type) { - case 'success': - return ( -
-
- - - -
- ); - case 'error': - return ( -
-
- - - -
- ); - default: - return ( -
-
- - - -
- ); - } - }; - - return ( - -
-
{icon()}
-
-

- {props.message} -

-
- -
-
- ); -}; - -export default NotificationToast; diff --git a/frontend-modern/src/components/Toast/Toast.tsx b/frontend-modern/src/components/Toast/Toast.tsx index a981e40..232eb64 100644 --- a/frontend-modern/src/components/Toast/Toast.tsx +++ b/frontend-modern/src/components/Toast/Toast.tsx @@ -145,7 +145,7 @@ const Toast: Component = (props) => { // Declare global interface extension declare global { interface Window { - showToast: (type: ToastType, title: string, message?: string, duration?: number) => void; + showToast: (type: ToastType, title: string, message?: string, duration?: number) => string; } } @@ -158,8 +158,9 @@ export const ToastContainer: Component = () => { // Expose global toast function window.showToast = (type: ToastType, title: string, message?: string, duration?: number) => { - const id = Date.now().toString(); + const id = (typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : Date.now().toString()); setToasts([...toasts(), { id, type, title, message, duration }]); + return id; }; return ( diff --git a/frontend-modern/src/stores/notifications.ts b/frontend-modern/src/stores/notifications.ts index d8b54fd..db7e7bd 100644 --- a/frontend-modern/src/stores/notifications.ts +++ b/frontend-modern/src/stores/notifications.ts @@ -1,44 +1,21 @@ -import { createStore } from 'solid-js/store'; +import { showToast } from '@/utils/toast'; -export interface Notification { - id: string; - message: string; - type: 'success' | 'error' | 'info'; - duration?: number; -} - -const [notifications, setNotifications] = createStore([]); +const DEFAULT_DURATIONS: Record<'success' | 'error' | 'info', number> = { + success: 5000, + error: 10000, + info: 5000, +}; export const notificationStore = { - notifications, - - add: (notification: Omit) => { - const id = Date.now().toString(); - setNotifications((prev) => [...prev, { ...notification, id }]); - - // Auto-remove after duration - if (notification.duration && notification.duration > 0) { - setTimeout(() => { - notificationStore.remove(id); - }, notification.duration); - } - - return id; + success: (message: string, duration: number = DEFAULT_DURATIONS.success) => { + return showToast('success', message, undefined, duration); }, - remove: (id: string) => { - setNotifications((prev) => prev.filter((n) => n.id !== id)); + error: (message: string, duration: number = DEFAULT_DURATIONS.error) => { + return showToast('error', message, undefined, duration); }, - success: (message: string, duration = 5000) => { - return notificationStore.add({ message, type: 'success', duration }); - }, - - error: (message: string, duration = 10000) => { - return notificationStore.add({ message, type: 'error', duration }); - }, - - info: (message: string, duration = 5000) => { - return notificationStore.add({ message, type: 'info', duration }); + info: (message: string, duration: number = DEFAULT_DURATIONS.info) => { + return showToast('info', message, undefined, duration); }, }; diff --git a/frontend-modern/src/utils/toast.ts b/frontend-modern/src/utils/toast.ts index b88ff25..b5a0656 100644 --- a/frontend-modern/src/utils/toast.ts +++ b/frontend-modern/src/utils/toast.ts @@ -2,18 +2,25 @@ import type { ToastType } from '@/components/Toast/Toast'; // Global declaration is in Toast.tsx -export const showToast = (type: ToastType, title: string, message?: string, duration?: number) => { - // Use the global toast function exposed by ToastContainer +export const showToast = ( + type: ToastType, + title: string, + message?: string, + duration?: number, +): string | undefined => { if (typeof window !== 'undefined' && window.showToast) { - window.showToast(type, title, message, duration); - } else { - // Fallback to console if toast system not ready - console.log(`[${type.toUpperCase()}] ${title}${message ? ': ' + message : ''}`); + return window.showToast(type, title, message, duration); } + + // Fallback to console if toast system not ready + console.log(`[${type.toUpperCase()}] ${title}${message ? `: ${message}` : ''}`); + return undefined; }; // Convenience functions - only export what's used -export const showSuccess = (title: string, message?: string) => - showToast('success', title, message); -export const showError = (title: string, message?: string) => showToast('error', title, message); -export const showInfo = (title: string, message?: string) => showToast('info', title, message); +export const showSuccess = (title: string, message?: string, duration?: number) => + showToast('success', title, message, duration); +export const showError = (title: string, message?: string, duration?: number) => + showToast('error', title, message, duration); +export const showInfo = (title: string, message?: string, duration?: number) => + showToast('info', title, message, duration);