Files
edr-platform/apps/edr-freight-web/backoffice/src/components/bookings/ContractSignaturePad.tsx
2026-06-04 15:16:27 +03:00

108 lines
3.0 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import { Eraser } from "lucide-react";
import { Button } from "@edr/ui-common";
import { cn } from "@/lib/utils";
interface ContractSignaturePadProps {
onChange: (dataUrl: string | null) => void;
className?: string;
}
export function ContractSignaturePad({
onChange,
className,
}: ContractSignaturePadProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const drawing = useRef(false);
const [empty, setEmpty] = useState(true);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const dpr = window.devicePixelRatio || 1;
const w = canvas.offsetWidth;
const h = canvas.offsetHeight;
canvas.width = w * dpr;
canvas.height = h * dpr;
ctx.scale(dpr, dpr);
ctx.strokeStyle = "#111";
ctx.lineWidth = 2;
ctx.lineCap = "round";
}, []);
const getPos = (e: React.MouseEvent | React.TouchEvent) => {
const canvas = canvasRef.current!;
const rect = canvas.getBoundingClientRect();
if ("touches" in e) {
const t = e.touches[0];
return { x: t.clientX - rect.left, y: t.clientY - rect.top };
}
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
};
const start = (e: React.MouseEvent | React.TouchEvent) => {
drawing.current = true;
const ctx = canvasRef.current?.getContext("2d");
const { x, y } = getPos(e);
ctx?.beginPath();
ctx?.moveTo(x, y);
};
const move = (e: React.MouseEvent | React.TouchEvent) => {
if (!drawing.current) return;
const ctx = canvasRef.current?.getContext("2d");
const { x, y } = getPos(e);
ctx?.lineTo(x, y);
ctx?.stroke();
setEmpty(false);
onChange(canvasRef.current?.toDataURL("image/png") ?? null);
};
const end = () => {
drawing.current = false;
};
const clear = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
setEmpty(true);
onChange(null);
};
return (
<div className={cn("space-y-2", className)}>
<div className="overflow-hidden rounded-lg border-2 border-dashed border-border bg-white">
<canvas
ref={canvasRef}
className="h-36 w-full touch-none cursor-crosshair"
onMouseDown={start}
onMouseMove={move}
onMouseUp={end}
onMouseLeave={end}
onTouchStart={start}
onTouchMove={move}
onTouchEnd={end}
/>
</div>
<div className="flex items-center justify-between">
<p className="text-xs text-muted-foreground">
Draw your signature above
</p>
<Button type="button" variant="ghost" size="sm" className="gap-1" onClick={clear}>
<Eraser className="size-3.5" />
Clear
</Button>
</div>
{empty && (
<p className="text-xs text-amber-700">Signature is required before confirming.</p>
)}
</div>
);
}