/** * In-app notifications: toasts and modal confirm/prompt. * Replaces browser alert(), confirm(), prompt() so no "localhost says" popups. * Usage: window.otoNotify.toast('Message', 'success'|'error'|'info') * window.otoNotify.confirm({ title, message }).then(ok => ...) * window.otoNotify.prompt({ title, message, defaultValue }).then(value => ...) */ (function () { if (typeof window !== 'undefined') { window.otoNotify = window.otoNotify || { toast: function () {}, confirm: () => Promise.resolve(false), prompt: () => Promise.resolve(null), }; } const { useState, useEffect, useRef } = React; const TOAST_TTL_MS = 4500; const OTOToast = ({ item, onDismiss }) => { const typeStyles = { success: 'bg-emerald-600 text-white border-emerald-700', error: 'bg-red-600 text-white border-red-700', info: 'bg-primary text-white border-primary-dark', }; const style = typeStyles[item.type] || typeStyles.info; useEffect(() => { const t = setTimeout(() => onDismiss(item.id), TOAST_TTL_MS); return () => clearTimeout(t); }, [item.id]); return (
{item.message}
); }; const ConfirmModal = ({ title, message, confirmLabel = 'Confirm', cancelLabel = 'Cancel', danger, resolve }) => { const resolveRef = useRef(resolve); resolveRef.current = resolve; useEffect(() => { return () => { if (resolveRef.current) resolveRef.current(false); }; }, []); const handleConfirm = () => { resolveRef.current?.(true); resolveRef.current = null; }; const handleCancel = () => { resolveRef.current?.(false); resolveRef.current = null; }; return (
e.stopPropagation()}>

{title}

{message}

); }; const PromptModal = ({ title, message, defaultValue = '', placeholder, submitLabel = 'Submit', cancelLabel = 'Cancel', resolve }) => { const [value, setValue] = useState(defaultValue); const resolveRef = useRef(resolve); resolveRef.current = resolve; const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); }, []); useEffect(() => { return () => { if (resolveRef.current) resolveRef.current(null); }; }, []); const handleSubmit = () => { resolveRef.current?.(value.trim() || null); resolveRef.current = null; }; const handleCancel = () => { resolveRef.current?.(null); resolveRef.current = null; }; return (
e.stopPropagation()}>

{title}

{message &&

{message}

} setValue(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') handleSubmit(); if (e.key === 'Escape') handleCancel(); }} placeholder={placeholder} className="w-full border border-gray-300 rounded-lg px-3 py-2 text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent mb-6" />
); }; const OTONotifyPortal = () => { const [toasts, setToasts] = useState([]); const [confirmState, setConfirmState] = useState(null); const [promptState, setPromptState] = useState(null); const setStateRef = useRef(null); useEffect(() => { setStateRef.current = { setToasts, setConfirmState, setPromptState }; window.otoNotify = { toast: (message, type = 'info') => { const id = Date.now() + Math.random(); setStateRef.current?.setToasts(s => [...s, { id, message: String(message), type }]); }, confirm: (opts = {}) => { return new Promise((resolve) => { setStateRef.current?.setConfirmState({ title: opts.title || 'Confirm', message: opts.message || '', confirmLabel: opts.confirmLabel || 'Confirm', cancelLabel: opts.cancelLabel || 'Cancel', danger: !!opts.danger, resolve: (ok) => { setStateRef.current?.setConfirmState(null); resolve(!!ok); }, }); }); }, prompt: (opts = {}) => { return new Promise((resolve) => { setStateRef.current?.setPromptState({ title: opts.title || 'Input', message: opts.message || '', defaultValue: opts.defaultValue ?? '', placeholder: opts.placeholder || '', submitLabel: opts.submitLabel || 'Submit', cancelLabel: opts.cancelLabel || 'Cancel', resolve: (value) => { setStateRef.current?.setPromptState(null); resolve(value); }, }); }); }, }; return () => { delete window.otoNotify; }; }, []); const dismissToast = (id) => setToasts(s => s.filter(t => t.id !== id)); return ( <>
{toasts.map(item => ( ))}
{confirmState && ( )} {promptState && ( )} ); }; if (typeof window !== 'undefined') window.OTONotifyPortal = OTONotifyPortal; })();