/** * ========================================================================= * PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0 * Socle UI (4/4) - Modales, Coffre-fort et Paiement (Stripe) * ========================================================================= */ // ========================================================================= // 1. WRAPPER STRIPE ELEMENTS // ========================================================================= window.StripeElements = ({ clientSecret, onSuccess }) => { const stripePromise = React.useMemo(() => window.Stripe ? window.Stripe(window.ECO_CONFIG.stripePubKey) : null, []); if (!window.ReactStripe || !stripePromise) { return
Erreur : Le module de paiement sécurisé est indisponible.
; } const { Elements, PaymentElement, useStripe, useElements } = window.ReactStripe; const CheckoutForm = () => { const stripe = useStripe(); const elements = useElements(); const [error, ReactSetError] = React.useState(null); const [processing, setProcessing] = React.useState(false); const handleSubmit = async (event) => { event.preventDefault(); if (!stripe || !elements) return; setProcessing(true); const result = await stripe.confirmPayment({ elements, redirect: 'if_required' }); if (result.error) { ReactSetError(result.error.message); setProcessing(false); } else if (result.paymentIntent && result.paymentIntent.status === 'succeeded') { onSuccess(result.paymentIntent.id); } }; return (
{error &&
{error}
} {processing ? 'Traitement en cours...' : 'Payer de manière sécurisée'} ); }; return ( ); }; // ========================================================================= // 2. MODALES GÉNÉRIQUES (SPA URL LAYER 1 ET LAYER 2) // ========================================================================= window.ModalOverlay = ({ children, zIndex = "z-[100]", onClose, preventClose = false, className = "flex items-center justify-center p-4" }) => { return (
{ if (e.target === e.currentTarget && !preventClose && onClose) { onClose(); } }} > {children}
); }; window.Modal = ({ title, type = 'info', onClose, children, preventClose = false, requireConfirm = false, zIndex = "z-[100]" }) => { // FACTORISATION : Utilisation des utilitaires globaux const { XIcon } = window.getIcons(); const { alertParam, setAlert } = window.useUrlModal(); // Le parent envoie le close, mais s'il y a un requireConfirm, on doit l'intercepter via l'URL (Layer 2) const handleCloseAttempt = React.useCallback(() => { if (requireConfirm) { setAlert('unsaved'); } else { if (onClose) onClose(); } }, [requireConfirm, onClose, setAlert]); const preventRef = React.useRef(preventClose); const closeRef = React.useRef(handleCloseAttempt); React.useEffect(() => { preventRef.current = preventClose; closeRef.current = handleCloseAttempt; }, [preventClose, handleCloseAttempt]); // Fermeture propre via la touche Échap (Accessibilité) React.useEffect(() => { const handleEsc = (e) => { if (e.key === 'Escape' && !preventRef.current) closeRef.current(); }; window.addEventListener('keydown', handleEsc); return () => window.removeEventListener('keydown', handleEsc); }, []); // Lecture de l'URL via le Hook pour afficher l'alerte d'annulation (Couche 2 / Dialog) const showConfirm = alertParam === 'unsaved'; let headerBgClass = 'bg-slate-50/50'; let headerTextClass = 'text-slate-800'; let closeIconClass = 'text-slate-400 hover:text-blue-500 hover:bg-blue-50'; if (type === 'success') { headerBgClass = 'bg-emerald-50 border-b border-emerald-100'; headerTextClass = 'text-emerald-900'; closeIconClass = 'text-emerald-500 hover:text-emerald-700 hover:bg-emerald-100'; } else if (type === 'info') { headerBgClass = 'bg-blue-50 border-b border-blue-100'; headerTextClass = 'text-blue-900'; closeIconClass = 'text-blue-500 hover:text-blue-700 hover:bg-blue-100'; } else if (type === 'error') { headerBgClass = 'bg-red-50 border-b border-red-100'; headerTextClass = 'text-red-900'; closeIconClass = 'text-red-500 hover:text-red-700 hover:bg-red-100'; } else if (type === 'warning') { headerBgClass = 'bg-orange-50 border-b border-orange-100'; headerTextClass = 'text-orange-900'; closeIconClass = 'text-orange-500 hover:text-orange-700 hover:bg-orange-100'; } else if (type === 'confirm' || type === 'black') { headerBgClass = 'bg-slate-900 border-b border-slate-900'; headerTextClass = 'text-white'; closeIconClass = 'text-slate-400 hover:text-white hover:bg-slate-800'; } else { headerBgClass = 'bg-slate-50/50 border-b border-slate-100'; } return ( <>
e.stopPropagation()}>

{title}

{!preventClose && (
)}
{children}
{showConfirm && window.ConfirmModal && ( { window.history.back(); // Enlève le paramètre d'alerte de l'URL setTimeout(() => { if (onClose) onClose(); }, 50); // Laisse le temps au navigateur de nettoyer l'URL avant de fermer la modale principale }} onCancel={() => window.history.back()} zIndex="z-[9999]" /> )} ); }; window.ConfirmModal = ({ title, message, confirmText = "Confirmer", cancelText = "Annuler", isDestructive = false, type = 'confirm', onConfirm, onCancel, zIndex = "z-[200]" }) => { const { LoaderIcon } = window.getIcons(); const [isProcessing, ReactSetIsProcessing] = React.useState(false); const modalType = isDestructive ? 'error' : type; return (

{message}

{cancelText} { ReactSetIsProcessing(true); await onConfirm(); }} disabled={isProcessing} icon={isProcessing ? LoaderIcon : null} className="flex-1 py-3 uppercase tracking-widest justify-center" > {confirmText}
); }; window.PromptModal = ({ title, message, placeholder = "", confirmText = "Valider", cancelText = "Annuler", type = 'confirm', onConfirm, onCancel, zIndex = "z-[200]" }) => { const { LoaderIcon } = window.getIcons(); const [val, setVal] = React.useState(''); const [isProcessing, setIsProcessing] = React.useState(false); return (
{ e.preventDefault(); setIsProcessing(true); await onConfirm(val); }} className="space-y-6">
* Champs obligatoires

{message}

setVal(e.target.value)} placeholder={placeholder} className="w-full border-2 border-slate-200 rounded-xl p-3 focus:border-emerald-500 outline-none transition font-bold" autoFocus required />
{cancelText} {confirmText}
); }; window.PasswordPromptModal = ({ title = "Confirmation de sécurité", desc, onConfirm, onCancel, isSaving }) => { const { LoaderIcon } = window.getIcons(); const [pwd, setPwd] = React.useState(''); return (
{desc}
{ e.preventDefault(); if(pwd) onConfirm(pwd); }}> setPwd(e.target.value)} className="w-full border-2 border-slate-200 rounded-xl p-3 text-sm focus:border-emerald-500 outline-none transition mb-4" /> Confirmer l'action
); }; // ========================================================================= // 3. COMPOSANTS GLOBAUX DU COFFRE-FORT // ========================================================================= window.VaultDocumentThumbnail = ({ doc }) => { const { FileIcon, LoaderIcon } = window.getIcons(); const [status, setStatus] = React.useState('loading'); const [retryCount, setRetryCount] = React.useState(0); const handleError = () => { if (retryCount < 5) { setTimeout(() => { setRetryCount(prev => prev + 1); setStatus('loading'); }, 1000 + (retryCount * 500)); } else { setStatus('error'); } }; const typeLabel = doc.type === 'image' ? 'IMG' : 'PDF'; const srcUrl = doc.type === 'image' ? `?api=file/download&type=image&id=${doc.id}&private=1&retry=${retryCount}` : `?api=file/download&type=pdf&id=${doc.id}&preview=1&page=0&private=1&retry=${retryCount}`; return (
{(status === 'loading' || status === 'error') && (
{typeLabel} {status === 'loading' && }
)} {doc.name setStatus('loaded')} onError={handleError} />
); }; window.VaultDocumentViewer = ({ viewingDoc, setViewingDoc }) => { const { ArrowLeftIcon, DownloadIcon, FileIcon } = window.getIcons(); if (!viewingDoc) return null; const numPages = viewingDoc.numPages || 1; const pages = Array.from({ length: numPages }, (_, i) => i + 1); const VaultPageImage = ({ src, alt }) => { const [error, setError] = React.useState(false); if (error) { return (
e.stopPropagation()}>

Aperçu web indisponible

Le document a été sauvegardé brut. Veuillez télécharger le fichier original pour le visualiser.

); } return ( {alt} e.stopPropagation()} onError={() => setError(true)} /> ); }; return (
setViewingDoc(null)}>
e.stopPropagation()}>
setViewingDoc(null)} className="flex items-center gap-2 px-2 sm:px-4 py-2 hover:bg-white/10 rounded-xl transition text-sm font-bold cursor-pointer shrink-0"> Retour
{viewingDoc.name}
Original
setViewingDoc(null)}> {viewingDoc.type === 'image' ? ( ) : ( <> {pages.map(p => ( ))} {numPages === 50 && (
e.stopPropagation()}> L'aperçu en ligne est limité aux 50 premières pages.
Veuillez télécharger le document original pour le consulter en intégralité.
)} )}
); }; window.UploadErrorsModal = ({ errors, onClose, zIndex = "z-[350]" }) => { const Modal = window.Modal || (() => null); const Button = window.Button || (() => null); if (!errors || errors.length === 0) return null; return (

Certains fichiers ont été ignorés lors de la sélection ou du transfert :

{errors.map((e, i) => (
{e.file} {e.reason}
))}
); }; /* EOF ========== [_react/_ui_modals.jsx] */