DrawSignature

This commit is contained in:
JaiPandey425 2025-07-03 11:33:55 +05:30
parent 1215433e16
commit faa4839fef
3 changed files with 278 additions and 110 deletions

View File

@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react';
// import { X, Upload, Edit3, Image, Cloud, HardDrive, Droplets } from 'lucide-react';
import { AiOutlineClose, AiOutlineUpload, AiOutlineEdit } from 'react-icons/ai';
import { MdImage, MdCloudUpload, MdStorage } from 'react-icons/md';
import { MdImage, MdCloudUpload } from 'react-icons/md';
import api from '../../../config/api';
import DrawSignatureCanvas from './DrawSignatureCanvas';
interface AddSignatureModalProps {
@ -28,6 +29,17 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
const [fullName, setFullName] = useState('');
const [initials, setInitials] = useState('');
const [signatures, setSignatures] = useState<{
fullSignature: string | null;
initials: string | null;
}>({ fullSignature: null, initials: null });
const handleSignatureChange = (data: { fullSignature: string | null; initials: string | null }) => {
setSignatures(data);
};
// Sample signature options for the CHOOSE tab
const signatureOptions = [
{ id: 1, name: 'print', initials: 'LA', style: 'print' },
@ -39,6 +51,8 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
];
const [selectedSignature, setSelectedSignature] = useState<number>(1);
console.log(selectedSignature);
// Store the actual file
const [file1, setFile1] = useState<File | null>(null);
@ -90,6 +104,38 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
}
};
const handleSubmit = async () => {
if (!signatures.fullSignature || !signatures.initials) {
alert('Please provide both signature and initials');
return;
}
// Example: Posting the base64 data
try {
const payload = {
signatureType: "DRAW",
// "signatureId": 0, this work on only the put req.
signatureBase64: signatures.fullSignature,
initialsBase64: signatures.initials,
firstName: user.firstName,
lastName: user.lastName,
font: ""
};
console.log('Submitting Signature:', payload);
let res = await api.post(`/api/user/signatures`,payload)
console.log("handleSubmit", res)
} catch (error) {
console.error("Error fetching user details:", error);
}
// fetch('/api/signature', { method: 'POST', body: JSON.stringify(payload) })
};
const renderContent = () => {
switch (activeTab) {
@ -149,51 +195,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
case 'draw':
return (
<div className="h-full flex flex-col">
<div className="mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-2">Draw Signature</h2>
<p className="text-sm text-gray-600">Create your signature by drawing directly on the canvas</p>
</div>
<div className="flex-1 flex flex-col">
<div className="grid grid-cols-12 gap-6 mb-6">
{/* Draw area 1 */}
<div className="col-span-8">
<label className="block text-sm font-medium text-gray-700 mb-2">Full Signature</label>
<div className="border-2 border-dashed border-gray-300 rounded-lg h-48 flex items-center justify-center bg-gray-50 hover:bg-gray-100 cursor-pointer transition-colors">
<div className="text-center">
{/* <Edit3 className="mx-auto h-12 w-12 text-gray-400 mb-3" /> */}
<p className="text-sm text-gray-500">Click and drag to draw your signature</p>
<p className="text-xs text-gray-400 mt-1">Use mouse or touch to create your signature</p>
</div>
</div>
</div>
{/* Draw area 2 */}
<div className="col-span-4">
<label className="block text-sm font-medium text-gray-700 mb-2">Initials</label>
<div className="border-2 border-dashed border-gray-300 rounded-lg h-48 flex items-center justify-center bg-gray-50 hover:bg-gray-100 cursor-pointer transition-colors">
<div className="text-center">
{/* <Edit3 className="mx-auto h-8 w-8 text-gray-400 mb-2" /> */}
<p className="text-xs text-gray-500">Draw initials</p>
</div>
</div>
</div>
</div>
<div className="flex gap-3">
<button className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors flex items-center gap-2">
{/* <X className="w-4 h-4" /> */}
Clear
</button>
<button className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors">
Undo
</button>
<button className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors">
Redo
</button>
</div>
</div>
</div>
<DrawSignatureCanvas onSignatureChange={handleSignatureChange} />
);
case 'upload':
@ -264,7 +266,10 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
try {
let res = await api.get(`/api/user/details`)
console.log("getuser", res)
setUser(res?.data)
let currUser = res?.data
setUser(currUser)
setFullName(currUser.firstName + " " + currUser.lastName)
setInitials(currUser.firstName.slice(0, 1) + currUser.lastName.slice(0, 1))
} catch (error) {
console.error("Error fetching user details:", error);
}
@ -368,7 +373,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
>
CANCEL
</button>
<button className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<button className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors" onClick={handleSubmit}>
CREATE SIGNATURE
</button>
</div>

View File

@ -0,0 +1,158 @@
import React, { useRef, useState } from 'react';
type Props = {
onSignatureChange: (data: { fullSignature: string | null; initials: string | null }) => void;
};
const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
const canvasRef1 = useRef<HTMLCanvasElement>(null);
const canvasRef2 = useRef<HTMLCanvasElement>(null);
const [isDrawing, setIsDrawing] = useState(false);
const [paths1, setPaths1] = useState<Array<Array<{ x: number; y: number }>>>([]);
const [paths2, setPaths2] = useState<Array<Array<{ x: number; y: number }>>>([]);
const [currentPath, setCurrentPath] = useState<Array<{ x: number; y: number }>>([]);
const [activeCanvas, setActiveCanvas] = useState<'full' | 'initials' | null>(null);
const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
setIsDrawing(true);
const rect = e.currentTarget.getBoundingClientRect();
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
setCurrentPath([point]);
setActiveCanvas(e.currentTarget === canvasRef1.current ? 'full' : 'initials');
};
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
if (!isDrawing) return;
const rect = e.currentTarget.getBoundingClientRect();
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
const newPath = [...currentPath, point]; // <- Build new path
setCurrentPath(newPath); // <- Save for next event
drawPath(e.currentTarget as HTMLCanvasElement, newPath); // <- Draw current
};
const exportCanvasAsImage = (canvas: HTMLCanvasElement | null): string | null => {
if (!canvas) return null;
return canvas.toDataURL('image/png');
};
const handleMouseUp = () => {
setIsDrawing(false);
if (currentPath.length === 0 || !activeCanvas) return;
if (activeCanvas === 'full') {
const newPaths = [...paths1, currentPath];
setPaths1(newPaths);
const fullSignature = exportCanvasAsImage(canvasRef1.current);
onSignatureChange({ fullSignature, initials: exportCanvasAsImage(canvasRef2.current) });
} else {
const newPaths = [...paths2, currentPath];
setPaths2(newPaths);
const initials = exportCanvasAsImage(canvasRef2.current);
onSignatureChange({ fullSignature: exportCanvasAsImage(canvasRef1.current), initials });
}
setCurrentPath([]);
};
const drawPath = (canvas: HTMLCanvasElement | null, path: Array<{ x: number; y: number }>) => {
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const allPaths =
canvas === canvasRef1.current ? [...paths1, path] : [...paths2, path];
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
allPaths.forEach((p) => {
if (p.length < 2) return;
ctx.beginPath();
ctx.moveTo(p[0].x, p[0].y);
for (let i = 1; i < p.length; i++) {
ctx.lineTo(p[i].x, p[i].y);
}
ctx.stroke();
});
};
const clearCanvas = () => {
if (canvasRef1.current) {
const ctx1 = canvasRef1.current.getContext('2d');
ctx1?.clearRect(0, 0, canvasRef1.current.width, canvasRef1.current.height);
}
if (canvasRef2.current) {
const ctx2 = canvasRef2.current.getContext('2d');
ctx2?.clearRect(0, 0, canvasRef2.current.width, canvasRef2.current.height);
}
setPaths1([]);
setPaths2([]);
onSignatureChange({ fullSignature: null, initials: null });
};
return (
<div className="h-full flex flex-col">
<div className="mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-2">Draw Signature</h2>
<p className="text-sm text-gray-600">Create your signature by drawing directly on the canvas</p>
</div>
<div className="flex-1 flex flex-col">
<div className="grid grid-cols-12 gap-6 mb-2">
{/* Full Signature Canvas */}
<div className="col-span-8">
<label className="block text-sm font-medium text-gray-700 mb-2">Full Signature</label>
<div className="border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors">
<canvas
ref={canvasRef1}
className="w-full h-48 cursor-crosshair"
width={canvasRef1.current?.clientWidth || 600} // use real width
height={canvasRef1.current?.clientHeight || 180}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
/>
</div>
</div>
{/* Initials Canvas */}
<div className="col-span-4">
<label className="block text-sm font-medium text-gray-700 mb-2">Initials</label>
<div className="border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors">
<canvas
ref={canvasRef2}
width={canvasRef2.current?.clientWidth || 300}
height={canvasRef2.current?.clientHeight || 180}
className="w-full h-48 cursor-crosshair"
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
/>
</div>
</div>
</div>
{/* Controls */}
<div className="flex gap-4">
<button
className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
onClick={clearCanvas}
>
Clear
</button>
</div>
</div>
</div>
);
};
export default DrawSignatureCanvas;

View File

@ -1,35 +1,29 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { FiEdit3, FiTrash2, FiPlus, FiMoreVertical } from 'react-icons/fi';
import AddSignatureModal from './AddSignatureModal';
import api from '../../../config/api';
interface Signature {
id: string;
signature: string;
initial: string;
createdAt: string;
email: string;
signatureType: string;
lastName: string;
firstName: string
font: string;
initialsBlob:string;
initialsStampedBlob:string;
initialsHash:string;
signatureHash:string;
signatureBlob:string;
signatureStampedBlob:string;
default:boolean
}
const SignatureManager: React.FC = () => {
const [signatures, setSignatures] = useState<Signature[]>([
{
id: '1',
signature: 'https://www.signwell.com/assets/vip-signatures/muhammad-ali-signature-3f9237f6fc48c3a04ba083117948e16ee7968aae521ae4ccebdfb8f22596ad22.svg',
initial: 'AB',
createdAt: 'dca3e646-62cd-4cc...'
},
{
id: '2',
signature: '∼∼⌒∼∼⌒∼',
initial: 'AB',
createdAt: 'dca3e646-62cd-4cc...'
},
{
id: '3',
signature: 'Digital Signature',
initial: 'DS',
createdAt: 'dca3e646-62cd-4cc...'
}
]);
const [signatures, setSignatures] = useState<Signature[]>([]);
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
const [showAddSignature, setShowAddSignature] = useState<boolean>(false)
@ -48,6 +42,21 @@ const SignatureManager: React.FC = () => {
setActiveDropdown(activeDropdown === id ? null : id);
};
const getAllSignatures = async () => {
try {
let res = await api.get("/api/user/signatures")
console.log("getAllSignatures", res.data)
let signatureData = res?.data
setSignatures(signatureData)
} catch (error) {
console.log("getAllSignatures", error)
}
}
useEffect(() => {
getAllSignatures()
}, [])
return (
<div className="min-h-screen bg-white p-3 rounded-md px-4 mb-6">
<div className="max-w-[84vw] mx-auto">
@ -95,7 +104,7 @@ const SignatureManager: React.FC = () => {
{/* Signature Image */}
<div className="bg-white rounded-lg p-1 flex items-center justify-center ">
<img
src="https://www.signwell.com/assets/vip-signatures/muhammad-ali-signature-3f9237f6fc48c3a04ba083117948e16ee7968aae521ae4ccebdfb8f22596ad22.svg"
src={`data:image/png;base64,${signature.signatureBlob}`}
className="h-[50px] w-[250px] object-contain"
alt="Signature"
/>
@ -103,7 +112,7 @@ const SignatureManager: React.FC = () => {
{/* Signature ID */}
<div className="text-xs text-gray-500 truncate">
{signature.createdAt}
{signature.signatureHash}
</div>
</div>
</div>
@ -120,7 +129,7 @@ const SignatureManager: React.FC = () => {
{/* Signature Image */}
<div className="bg-white rounded-lg p-1 flex items-center justify-center ">
<img
src="https://www.signwell.com/assets/vip-signatures/muhammad-ali-signature-3f9237f6fc48c3a04ba083117948e16ee7968aae521ae4ccebdfb8f22596ad22.svg"
src={`data:image/png;base64,${signature.initialsBlob}`}
className="h-[50px] object-contain"
alt="DS Signature"
/>
@ -174,10 +183,6 @@ const SignatureManager: React.FC = () => {
</div>
<h3 className="text-xl font-semibold text-gray-700 mb-2">No signatures yet</h3>
<p className="text-gray-500 mb-6">Create your first signature to get started.</p>
<button className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-semibold flex items-center gap-2 mx-auto transition-all duration-200">
<FiPlus className="w-5 h-5" />
ADD SIGNATURE
</button>
</div>
)}
</div>