- Remplacement de DocuSeal par solution souveraine Odentas Sign - Système d'authentification OTP pour signataires (bcryptjs + JWT) - 8 routes API: send-otp, verify-otp, sign, pdf-url, positions, status, webhook, signers - Interface moderne avec canvas de signature et animations (framer-motion, confetti) - Système de templates pour auto-détection des positions de signature (CDDU, RG, avenants) - PDF viewer avec @react-pdf-viewer (compatible Next.js) - Stockage S3: source/, signatures/, evidence/, signed/, certs/ - Tables Supabase: sign_requests, signers, sign_positions, sign_events, sign_assets - Evidence bundle automatique (JSON metadata + timestamps) - Templates emails: OTP et completion - Scripts Lambda prêts: pades-sign (KMS seal) et tsaStamp (RFC3161) - Mode test détecté automatiquement (emails whitelist) - Tests complets avec PDF CDDU réel (2 signataires)
84 lines
No EOL
2.7 KiB
TypeScript
84 lines
No EOL
2.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { Viewer, Worker } from '@react-pdf-viewer/core';
|
|
import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout';
|
|
|
|
// Import des styles
|
|
import '@react-pdf-viewer/core/lib/styles/index.css';
|
|
import '@react-pdf-viewer/default-layout/lib/styles/index.css';
|
|
|
|
interface SignaturePosition {
|
|
page: number;
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
role: string;
|
|
}
|
|
|
|
interface PDFViewerProps {
|
|
pdfUrl: string;
|
|
positions: SignaturePosition[];
|
|
currentSignerRole: string;
|
|
}
|
|
|
|
export default function PDFViewer({ pdfUrl, positions, currentSignerRole }: PDFViewerProps) {
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
// Plugin pour la mise en page par défaut
|
|
const defaultLayoutPluginInstance = defaultLayoutPlugin();
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
if (!mounted) {
|
|
return (
|
|
<div className="flex items-center justify-center h-full bg-gray-50 rounded-lg border border-gray-200">
|
|
<div className="text-center">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
|
<p className="text-gray-600">Initialisation du viewer...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="h-full bg-gray-50 rounded-lg overflow-hidden border border-gray-200">
|
|
<Worker workerUrl={`https://unpkg.com/pdfjs-dist@3.11.174/build/pdf.worker.min.js`}>
|
|
<div className="relative h-full">
|
|
<Viewer
|
|
fileUrl={pdfUrl}
|
|
plugins={[defaultLayoutPluginInstance]}
|
|
defaultScale={1.2}
|
|
/>
|
|
|
|
{/* Overlay custom pour les zones de signature */}
|
|
<div className="absolute top-16 right-4 bg-white/95 backdrop-blur-sm p-3 rounded-lg shadow-lg border border-gray-200">
|
|
<h4 className="text-xs font-semibold text-gray-700 mb-2">Zones de signature</h4>
|
|
<div className="space-y-2">
|
|
{positions.map((pos, index) => {
|
|
const isCurrentSigner = pos.role === currentSignerRole;
|
|
return (
|
|
<div
|
|
key={index}
|
|
className={`flex items-center gap-2 text-xs ${
|
|
isCurrentSigner ? 'text-blue-600 font-semibold' : 'text-gray-600'
|
|
}`}
|
|
>
|
|
{isCurrentSigner ? '✍️' : '📝'}
|
|
<span>Page {pos.page}: {pos.role}</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
{positions.length === 0 && (
|
|
<p className="text-xs text-gray-500 italic">Aucune zone définie</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Worker>
|
|
</div>
|
|
);
|
|
} |