espace-paie-odentas/app/signature-salarie/DocuSealSignatureModal.tsx
odentas 8cb366ee53 chore: RGPD compliance audit & cleanup + UX signature salarie
- Audit RGPD complet: création RGPD_AUDIT_LOCALISATION_DONNEES.md
- Suppression complète intégration n8n (API route, hooks, env vars)
- Suppression variables Airtable (env vars)
- Confirmation GoCardless: serveurs EEE + SCC
- 8/9 services confirmés UE (89% compliance)
- Ajout message info dans modale signature salarie (scroll down)
2025-10-23 17:32:56 +02:00

251 lines
8.3 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
import { XCircle, CheckCircle2 } from "lucide-react";
import Script from "next/script";
interface DocuSealSignatureModalProps {
isOpen: boolean;
docusealId: string;
onClose: () => void;
onSignatureCompleted: () => void;
}
export default function DocuSealSignatureModal({
isOpen,
docusealId,
onClose,
onSignatureCompleted
}: DocuSealSignatureModalProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
const [docusealLoaded, setDocusealLoaded] = useState(false);
const [showCompletedModal, setShowCompletedModal] = useState(false);
// Ouvrir/fermer le dialog
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (isOpen) {
if (!dialog.open) {
try {
dialog.showModal();
} catch (err) {
console.warn('Impossible d\'ouvrir le modal', err);
}
}
} else {
if (dialog.open) {
dialog.close();
}
}
}, [isOpen]);
// Écouter l'événement de complétion DocuSeal
useEffect(() => {
if (!docusealLoaded || !isOpen) return;
const formElement = document.getElementById('docusealFormModal');
if (!formElement) {
console.log('⚠️ docusealFormModal non trouvé');
return;
}
// Chercher l'élément <docuseal-form> à l'intérieur
const docusealForm = formElement.querySelector('docuseal-form') as HTMLElement | null;
if (!docusealForm) {
console.log('⚠️ Element <docuseal-form> non trouvé');
return;
}
const handleCompleted = (e: any) => {
console.log('✅ [SIGNATURE SALARIE] Event completed reçu:', e.detail);
setShowCompletedModal(true);
};
console.log('🎯 Ajout du listener "completed" sur docuseal-form');
docusealForm.addEventListener('completed', handleCompleted);
return () => {
console.log('🧹 Retrait du listener "completed"');
docusealForm.removeEventListener('completed', handleCompleted);
};
}, [docusealLoaded, isOpen]);
// Empêcher la fermeture du dialog si la confirmation est affichée
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
const handleCancel = (e: Event) => {
if (showCompletedModal) {
e.preventDefault();
// Ré-ouvrir le dialog immédiatement
setTimeout(() => {
try {
if (dialog && !dialog.open) {
dialog.showModal();
}
} catch (err) {
console.warn('Impossible de ré-ouvrir le modal', err);
}
}, 0);
return;
}
};
const handleClose = () => {
if (showCompletedModal) {
// Si la confirmation est affichée, ré-ouvrir le dialog
setTimeout(() => {
try {
if (dialog && !dialog.open) {
dialog.showModal();
}
} catch (err) {
console.warn('Impossible de ré-ouvrir le modal', err);
}
}, 0);
}
};
dialog.addEventListener('cancel', handleCancel);
dialog.addEventListener('close', handleClose);
return () => {
dialog.removeEventListener('cancel', handleCancel);
dialog.removeEventListener('close', handleClose);
};
}, [showCompletedModal]);
const handleCloseCompleted = () => {
setShowCompletedModal(false);
const dialog = dialogRef.current;
if (dialog) dialog.close();
onSignatureCompleted();
};
if (!isOpen) return null;
return (
<>
{/* Charger le script DocuSeal */}
<Script
src="https://cdn.docuseal.com/js/form.js"
onLoad={() => setDocusealLoaded(true)}
strategy="afterInteractive"
/>
{/* Dialog modal */}
<dialog
ref={dialogRef}
className="backdrop:bg-black/60 bg-transparent p-0 rounded-2xl shadow-2xl max-w-4xl w-full max-h-[90vh] z-50"
onClose={() => {
if (!showCompletedModal) {
onClose();
}
}}
>
<div className="bg-white rounded-2xl h-full flex flex-col max-h-[90vh]">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b bg-gradient-to-r from-blue-50 to-indigo-50 flex-shrink-0">
<div className="flex-1">
<h2 className="text-xl font-semibold text-slate-900">
Signature électronique du contrat
</h2>
<p className="text-xs text-slate-600 mt-1 flex items-center gap-1">
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Descendez tout en bas du contrat pour accéder au champs de signature.
</p>
</div>
<button
onClick={() => {
if (!showCompletedModal) {
const dialog = dialogRef.current;
if (dialog) dialog.close();
onClose();
}
}}
disabled={showCompletedModal}
className={`p-2 rounded-lg transition-colors ${
showCompletedModal
? 'opacity-50 cursor-not-allowed'
: 'hover:bg-white/60'
}`}
aria-label="Fermer"
>
<XCircle className="w-5 h-5 text-slate-500" />
</button>
</div>
{/* Body avec DocuSeal */}
<div className="flex-1 overflow-y-auto p-6 min-h-0">
<div
id="docusealFormModal"
dangerouslySetInnerHTML={{
__html: `
<docuseal-form
data-src="https://docuseal.eu/s/${docusealId}"
data-language="fr"
data-with-send-copy-button="false"
data-allow-to-resubmit="false"
data-allow-typed-signature="false"
data-remember-signature="true"
data-with-title="false"
data-completed-message-body="Le document a été signé."
>
</docuseal-form>
`
}}
/>
</div>
</div>
</dialog>
{/* Modal de confirmation après signature */}
{showCompletedModal && (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 p-4">
<div className="bg-white rounded-2xl shadow-2xl max-w-lg w-full overflow-hidden">
{/* Header */}
<div className="p-6 bg-emerald-50 border-b border-emerald-100">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-emerald-100 flex items-center justify-center">
<CheckCircle2 className="w-6 h-6 text-emerald-600" />
</div>
<div>
<h2 className="text-xl font-semibold text-slate-900">Signature reçue avec succès</h2>
<p className="text-sm text-slate-600 mt-1">Votre signature électronique a bien é enregistrée.</p>
</div>
</div>
</div>
</div>
{/* Content */}
<div className="p-6 space-y-4">
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4">
<ul className="text-sm text-slate-700 space-y-2 list-disc pl-5">
<li>Votre contrat est en cours de traitement.</li>
<li>Le contrat signé sera disponible au téléchargement dans quelques instants.</li>
<li>Vous recevrez également une copie par email.</li>
</ul>
</div>
</div>
{/* Footer */}
<div className="px-6 py-4 bg-gray-50 border-t flex justify-end">
<button
onClick={handleCloseCompleted}
className="px-6 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-medium transition-colors"
>
J'ai compris
</button>
</div>
</div>
</div>
)}
</>
);
}