diff --git a/SIGNATURE_SALARIE_FEATURE.md b/SIGNATURE_SALARIE_FEATURE.md new file mode 100644 index 0000000..5bb3a44 --- /dev/null +++ b/SIGNATURE_SALARIE_FEATURE.md @@ -0,0 +1,72 @@ +# Page de Signature pour les Salariés + +## 📄 Fichiers créés + +### 1. `/app/signature-salarie/page.tsx` +Page principale qui charge le composant de signature dans un Suspense + +### 2. `/app/signature-salarie/SignatureSalarieContent.tsx` +Composant client qui affiche le formulaire DocuSeal + +### 3. Modification du `/middleware.ts` +Ajout de `/signature-salarie` aux pages publiques (pas d'authentification requise) + +## 🎯 Fonctionnalités + +✅ **Page publique** - Accessible sans authentification +✅ **Sans Header/Sidebar** - Design épuré comme `dl-contrat-signe` +✅ **DocuSeal intégré** - Widget de signature électronique +✅ **Détection d'erreurs** - Gestion des documents inexistants +✅ **Safari iOS compatible** - Classe CSS spéciale si nécessaire +✅ **Événement de complétion** - Log quand le formulaire est signé + +## 🔗 Utilisation + +La page est accessible via : +``` +https://paie.odentas.fr/signature-salarie?docuseal_id=XXXXX +``` + +Où `XXXXX` est le slug DocuSeal du document à signer. + +## 📱 Options DocuSeal configurées + +- ✅ Langue française (`data-language="fr"`) +- ✅ Pas de bouton d'envoi de copie (`data-with-send-copy-button="false"`) +- ✅ Pas de ressoumission (`data-allow-to-resubmit="false"`) +- ✅ Pas de signature tapée (`data-allow-typed-signature="false"`) +- ✅ Mémorisation de la signature (`data-remember-signature="true"`) +- ✅ Pas de titre (`data-with-title="false"`) +- ✅ Message de complétion personnalisé + +## 🔧 Messages d'erreur + +### Lien invalide +Si aucun `docuseal_id` n'est fourni dans l'URL, affiche : +> "Le lien de signature est invalide ou incomplet. Veuillez vérifier le lien reçu par email." + +### Document inexistant +Si DocuSeal retourne une erreur "Unable to find form with slug", affiche : +> "Ce document n'existe pas ou a été supprimé pour cause de modification. Dans ce cas, vous avez reçu un e-mail pour signer la nouvelle version." + +## 🎨 Design + +- Header simple avec logo Odentas centré +- Fond blanc épuré +- Conteneur max-width 4xl pour le formulaire +- Responsive et mobile-friendly + +## 🚀 Déploiement + +Aucune configuration supplémentaire nécessaire. Les fichiers sont prêts à être déployés. + +## 📧 Intégration avec les emails + +Pour utiliser cette page dans les emails de signature envoyés aux salariés, utilisez le lien : +```html + + Signer le document + +``` + +Où `{{docuseal_slug}}` est le slug DocuSeal récupéré depuis l'API. diff --git a/app/api/signatures-electroniques/relance/route.ts b/app/api/signatures-electroniques/relance/route.ts index 3b035cf..fe701f2 100644 --- a/app/api/signatures-electroniques/relance/route.ts +++ b/app/api/signatures-electroniques/relance/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'; import { cookies } from 'next/headers'; import { sendUniversalEmailV2, EmailDataV2 } from '@/lib/emailTemplateService'; +import { ENV } from '@/lib/cleanEnv'; // Envoi via le système universel (pas de configuration SES directe ici) // POST /api/signatures-electroniques/relance @@ -85,16 +86,26 @@ export async function POST(req: NextRequest) { let docusealEmail: string | null = null; if (contract.docuseal_submission_id) { try { - const docusealResponse = await fetch(`${process.env.NEXT_PUBLIC_API_BASE || ''}/api/docuseal/submissions/${contract.docuseal_submission_id}`); + // Appel direct à l'API DocuSeal (pas de route interne) + const docusealResponse = await fetch(`https://api.docuseal.eu/submissions/${contract.docuseal_submission_id}`, { + method: 'GET', + headers: { + 'X-Auth-Token': ENV.DOCUSEAL_TOKEN, + 'Content-Type': 'application/json', + }, + }); + if (docusealResponse.ok) { const docusealData = await docusealResponse.json(); const submitters = docusealData?.submitters || []; const employeeSubmitter = submitters.find((s: any) => s.role === 'Salarié'); employeeSlug = employeeSubmitter?.slug || null; - // Fallback email via DocuSeal si introuvable en base salaires (voir plus bas) + // Fallback email via DocuSeal si introuvable en base salaries (voir plus bas) if (employeeSubmitter?.email) { docusealEmail = String(employeeSubmitter.email); } + } else { + console.error('DocuSeal API error:', docusealResponse.status, await docusealResponse.text()); } } catch (error) { console.error('Erreur récupération DocuSeal:', error); @@ -102,10 +113,11 @@ export async function POST(req: NextRequest) { } // Construction du lien de signature - let signatureLink = contract.signature_link as string | null; - if (!signatureLink && employeeSlug) { - const siteBase = process.env.NEXT_PUBLIC_SITE_URL || (process.env.VERCEL_ENV === 'production' ? 'https://paie.odentas.fr' : 'https://staging.paie.odentas.fr'); - signatureLink = `${siteBase}/odentas-sign?docuseal_id=${employeeSlug}`; + // TOUJOURS utiliser le slug du salarié (pas celui de l'employeur stocké dans signature_link) + let signatureLink: string | null = null; + if (employeeSlug) { + const siteBase = process.env.NEXT_PUBLIC_SITE_URL || 'https://paie.odentas.fr'; + signatureLink = `${siteBase}/signature-salarie?docuseal_id=${employeeSlug}`; } if (!signatureLink) { diff --git a/app/signature-salarie/SignatureSalarieContent.tsx b/app/signature-salarie/SignatureSalarieContent.tsx new file mode 100644 index 0000000..61ec36b --- /dev/null +++ b/app/signature-salarie/SignatureSalarieContent.tsx @@ -0,0 +1,160 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useSearchParams } from "next/navigation"; +import { AlertTriangle } from "lucide-react"; +import { usePageTitle } from "@/hooks/usePageTitle"; +import Script from "next/script"; + +export default function SignatureSalarieContent() { + const searchParams = useSearchParams(); + const docusealId = searchParams.get("docuseal_id"); + const [showError, setShowError] = useState(false); + const [docusealLoaded, setDocusealLoaded] = useState(false); + + // Définir le titre de la page + usePageTitle("Signature électronique"); + + // Détecter Safari iOS et ajouter une classe + useEffect(() => { + const isSafariIOS = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) && /iPhone|iPad|iPod/.test(navigator.userAgent); + if (isSafariIOS) { + document.documentElement.classList.add('safari_only'); + } + }, []); + + // Intercepter les erreurs console pour détecter les problèmes DocuSeal + useEffect(() => { + const originalConsoleError = console.error; + console.error = function(...args: any[]) { + if ( + args.length > 0 && + typeof args[0] === 'string' && + args[0].includes("Unable to find form with slug") + ) { + setShowError(true); + } + originalConsoleError.apply(console, args); + }; + + return () => { + console.error = originalConsoleError; + }; + }, []); + + // Écouter l'événement de complétion + useEffect(() => { + if (!docusealLoaded) return; + + const form = document.getElementById('docusealForm'); + if (!form) return; + + const handleCompleted = (e: any) => { + console.log('Formulaire DocuSeal complété:', e.detail); + }; + + form.addEventListener('completed', handleCompleted); + + return () => { + form.removeEventListener('completed', handleCompleted); + }; + }, [docusealLoaded]); + + if (!docusealId) { + return ( +
+
+
+ +
+

Lien invalide

+

+ Le lien de signature est invalide ou incomplet. Veuillez vérifier le lien reçu par email. +

+

+ Besoin d'aide ? Contactez-nous à{' '} + + paie@odentas.fr + +

+
+
+ ); + } + + if (showError) { + return ( +
+
+
+ +
+

Document inexistant

+

+ Ce document n'existe pas ou a été supprimé pour cause de modification. Dans ce cas, vous avez reçu un e-mail pour signer la nouvelle version. +

+

+ Contactez-nous à{' '} + + paie@odentas.fr + + {' '}si vous avez besoin d'assistance. +

+
+
+ ); + } + + return ( + <> + {/* Charger le script DocuSeal */} +