/** * Lightweight confirm dialog and toast system for the plugin admin UI. * Replaces window.confirm() and alert() throughout. * * Usage: * const { confirm, toast, NotifyPortal } = useNotify(); * * await confirm({ message: 'Delete this?' }) → boolean * toast({ message: 'Saved!', type: 'success' }) */ import { useCallback, useState } from 'react'; import { createPortal } from 'react-dom'; interface ConfirmOptions { title?: string; message: string; confirmLabel?: string; cancelLabel?: string; danger?: boolean; } interface ToastOptions { message: string; type?: 'success' | 'error' | 'info'; duration?: number; } interface ToastItem extends ToastOptions { id: number; } interface ConfirmState extends ConfirmOptions { resolve: (v: boolean) => void; } let _toastId = 0; export function useNotify() { const [confirmState, setConfirmState] = useState(null); const [toasts, setToasts] = useState([]); const confirm = useCallback((opts: ConfirmOptions): Promise => { return new Promise((resolve) => { setConfirmState({ ...opts, resolve }); }); }, []); const toast = useCallback((opts: ToastOptions) => { const id = ++_toastId; setToasts((prev) => [...prev, { ...opts, id }]); const duration = opts.duration ?? (opts.type === 'error' ? 5000 : 3000); setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)); }, duration); }, []); const dismissConfirm = (value: boolean) => { confirmState?.resolve(value); setConfirmState(null); }; const NotifyPortal = useCallback(() => { return createPortal( <> {confirmState && (
dismissConfirm(false)}>
e.stopPropagation()}> {confirmState.title &&

{confirmState.title}

}

{confirmState.message}

)} {toasts.length > 0 && (
{toasts.map((t) => (
{t.message}
))}
)} , document.body ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [confirmState, toasts]); return { confirm, toast, NotifyPortal }; }