refactor(client): centralize error feedback with toast events

- emit API/network failures through a shared toast event bridge

- remove per-page inline error notifications to avoid duplicate error UI

- dedupe repeated toast messages to keep feedback readable
This commit is contained in:
2026-04-10 22:24:30 +03:00
parent 7444082b65
commit ccce3381c1
25 changed files with 59 additions and 80 deletions
+13 -1
View File
@@ -1,6 +1,7 @@
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { AlertCircle, CheckCircle2, Info, X } from 'lucide-react';
import styles from './ToastProvider.module.css';
import { subscribeApiErrorToasts } from '../../lib/toast-events';
type ToastVariant = 'success' | 'error' | 'info';
@@ -34,6 +35,7 @@ export interface ToastProviderProps {
export function ToastProvider({ children, durationMs = 3500 }: ToastProviderProps) {
const EXIT_ANIMATION_MS = 100;
const [items, setItems] = useState<ToastItem[]>([]);
const lastShownAtRef = useRef<Map<string, number>>(new Map());
const remove = useCallback((id: number) => {
setItems((prev) => prev.filter((item) => item.id !== id));
@@ -49,6 +51,12 @@ export function ToastProvider({ children, durationMs = 3500 }: ToastProviderProp
}, [remove]);
const show = useCallback((message: string, variant: ToastVariant = 'info') => {
const now = Date.now();
const dedupeKey = `${variant}:${message}`;
const previousTs = lastShownAtRef.current.get(dedupeKey) ?? 0;
if (now - previousTs < 600) return;
lastShownAtRef.current.set(dedupeKey, now);
const id = Date.now() + Math.floor(Math.random() * 1000);
setItems((prev) => [...prev, { id, message, variant, closing: false }]);
window.setTimeout(() => dismiss(id), durationMs);
@@ -61,6 +69,10 @@ export function ToastProvider({ children, durationMs = 3500 }: ToastProviderProp
info: (message: string) => show(message, 'info'),
}), [show]);
useEffect(() => {
return subscribeApiErrorToasts((message) => api.error(message));
}, [api]);
return (
<ToastContext.Provider value={api}>
{children}