/**
* 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;
})();