Tech4biz-channel/Channel-Frontend/src/features/agreements/components/SignatureCapture.tsx
2026-07-09 15:32:31 +05:30

178 lines
5.3 KiB
TypeScript

import React, { useRef, useState, useEffect } from 'react';
import { Eraser, Check } from 'lucide-react';
interface SignatureCaptureProps {
onSignatureComplete: (signatureDataUrl: string) => void;
width?: number;
height?: number;
}
export const SignatureCapture: React.FC<SignatureCaptureProps> = ({
onSignatureComplete,
width = 600,
height = 200
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [isDrawing, setIsDrawing] = useState(false);
const [hasSignature, setHasSignature] = useState(false);
// Initialize canvas context
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Detect theme for stroke color
const isDark = document.documentElement.classList.contains('dark');
ctx.strokeStyle = isDark ? '#fafafa' : '#09090b';
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Handle high DPI displays for crisp rendering
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.scale(dpr, dpr);
}, [width, height]);
// Adjust stroke color if theme toggles during active view
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const isDark = document.documentElement.classList.contains('dark');
ctx.strokeStyle = isDark ? '#fafafa' : '#09090b';
});
const startDrawing = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
e.preventDefault();
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const rect = canvas.getBoundingClientRect();
let clientX, clientY;
if ('touches' in e) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = (e as React.MouseEvent).clientX;
clientY = (e as React.MouseEvent).clientY;
}
ctx.beginPath();
ctx.moveTo(clientX - rect.left, clientY - rect.top);
setIsDrawing(true);
};
const draw = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
e.preventDefault();
if (!isDrawing) return;
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const rect = canvas.getBoundingClientRect();
let clientX, clientY;
if ('touches' in e) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = (e as React.MouseEvent).clientX;
clientY = (e as React.MouseEvent).clientY;
}
ctx.lineTo(clientX - rect.left, clientY - rect.top);
ctx.stroke();
setHasSignature(true);
};
const stopDrawing = () => {
setIsDrawing(false);
};
const clearSignature = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
setHasSignature(false);
};
const handleSave = () => {
const canvas = canvasRef.current;
if (!canvas || !hasSignature) return;
const dataUrl = canvas.toDataURL('image/png');
onSignatureComplete(dataUrl);
};
return (
<div className="flex flex-col w-full max-w-2xl mx-auto">
<div className="relative rounded-2xl bg-ink-50 border-2 border-dashed border-ink-200 overflow-hidden shadow-inner group">
{/* Helper Text */}
{!hasSignature && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<p className="text-ink-400 font-medium text-sm">
Draw your signature here
</p>
</div>
)}
<canvas
ref={canvasRef}
onMouseDown={startDrawing}
onMouseMove={draw}
onMouseUp={stopDrawing}
onMouseOut={stopDrawing}
onTouchStart={startDrawing}
onTouchMove={draw}
onTouchEnd={stopDrawing}
className="touch-none cursor-crosshair relative z-10 w-full"
style={{ width: '100%', height: `${height}px`, maxWidth: `${width}px` }}
/>
</div>
<div className="flex items-center justify-between mt-4">
<button
type="button"
onClick={clearSignature}
disabled={!hasSignature}
className="flex items-center gap-2 px-4 py-2 text-sm font-semibold text-ink-500 hover:text-red-600 hover:bg-red-500/10 rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<Eraser className="w-4 h-4" />
Clear
</button>
<button
type="button"
onClick={handleSave}
disabled={!hasSignature}
className="flex items-center gap-2 px-6 py-2.5 text-sm font-bold text-ink-0 bg-ink-900 hover:bg-ink-800 rounded-xl transition-all shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-md hover:-translate-y-0.5"
>
<Check className="w-4 h-4" />
Accept & Sign
</button>
</div>
</div>
);
};