SME-Web/src/components/setting/Signature/AddSignatureModal.tsx

717 lines
36 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { AiOutlineUpload, AiOutlineEdit } from 'react-icons/ai';
import { MdCloudUpload } from 'react-icons/md';
import api from '../../../config/api';
import DrawSignatureCanvas from './DrawSignatureCanvas';
import { FaSpinner } from "react-icons/fa";
import { GrCheckboxSelected } from "react-icons/gr";
import { Bounce, toast } from "react-toastify";
import { FiCheck } from 'react-icons/fi';
interface Signature {
id: 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
}
interface AddSignatureModalProps {
setShowAddSignature?: (value: boolean) => void;
getAllSignatures: () => Promise<void>;
editingSignature?: Signature | null;
}
interface Account {
id: number;
isDefault: boolean;
name: string;
}
interface UserData {
id: string;
firstName: string;
lastName: string;
email: string;
language: string;
account: Account;
}
interface SelectStyle {
id: number,
style: string
}
const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
setShowAddSignature,
getAllSignatures,
editingSignature
}) => {
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT');
const [fullName, setFullName] = useState('');
const [initials, setInitials] = useState('');
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [signatures, setSignatures] = useState<{
fullSignature: string | null;
initials: string | null;
}>({ fullSignature: null, initials: null });
const signatureOptions = [
{ id: 1, name: 'print', initials: 'LA', style: 'print' },
{ id: 2, name: 'cursive', initials: 'LA', style: 'cursive' },
{ id: 3, name: 'script', initials: 'LA', style: 'script' },
{ id: 4, name: 'elegant', initials: 'LA', style: 'elegant' },
{ id: 5, name: 'modern', initials: 'LA', style: 'modern' },
{ id: 6, name: 'classic', initials: 'LA', style: 'classic' },
];
const [checked, setChecked] = useState(false);
const toggleCheckbox = () => setChecked(!checked);
const [selectedSignature, setSelectedSignature] = useState<SelectStyle>(
signatureOptions.length > 0 ? { id: signatureOptions[0].id, style: signatureOptions[0].style } : { id: 0, style: "" }
);
// Store the actual file
const [file1, setFile1] = useState<File | null>(null);
const [file2, setFile2] = useState<File | null>(null);
// Store the preview URL
const [preview1, setPreview1] = useState<string | null>(null);
const [preview2, setPreview2] = useState<string | null>(null);
// User data
const [user, setUser] = useState<UserData>({
id: "",
firstName: "",
lastName: "",
email: "",
language: "",
account: {
id: 0,
isDefault: false,
name: "",
},
});
const isEditMode = !!editingSignature;
// Navigation items
const navigationItems = [
{ id: 'SELECT', label: 'SELECT', icon: <GrCheckboxSelected className="w-3 h-3 md:w-4 md:h-4" /> },
{ id: 'DRAW', label: 'DRAW', icon: <AiOutlineEdit className="w-3 h-3 md:w-4 md:h-4" /> },
{ id: 'UPLOAD', label: 'UPLOAD', icon: <AiOutlineUpload className="w-3 h-3 md:w-4 md:h-4" /> },
];
// Initialize form with editing signature data
useEffect(() => {
if (editingSignature) {
setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`);
setInitials((`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`).toUpperCase());
setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD');
// Set font for SELECT tab
const fontOption = signatureOptions.find(option => option.style === editingSignature.font);
if (fontOption) {
setSelectedSignature({ id: fontOption.id, style: fontOption.style });
}
// Set signature blobs for all tabs
if (editingSignature.signatureBlob && editingSignature.initialsBlob) {
setSignatures({
fullSignature: `data:image/png;base64,${editingSignature.signatureBlob}`,
initials: `data:image/png;base64,${editingSignature.initialsBlob}`
});
// Set preview URLs for UPLOAD tab
if (editingSignature.signatureType === 'UPLOAD') {
setPreview1(`data:image/png;base64,${editingSignature.signatureBlob}`);
setPreview2(`data:image/png;base64,${editingSignature.initialsBlob}`);
}
}
if (editingSignature.default) {
setChecked(editingSignature.default)
}
}
}, [editingSignature]);
const handleSignatureChange = (data: { fullSignature: string | null; initials: string | null }) => {
setSignatures(data);
};
// Cleanup preview URLs when component unmounts or image changes
useEffect(() => {
return () => {
if (preview1) URL.revokeObjectURL(preview1);
if (preview2) URL.revokeObjectURL(preview2);
};
}, [preview1, preview2]);
const handleImageChange = async (
e: React.ChangeEvent<HTMLInputElement>,
setFile: React.Dispatch<React.SetStateAction<File | null>>,
setPreview: React.Dispatch<React.SetStateAction<string | null>>,
type: "fullSignature" | "initials"
) => {
const file = e.target.files?.[0];
if (file) {
setFile(file);
const previewUrl = URL.createObjectURL(file);
setPreview(previewUrl);
// Convert to base64
const base64 = await convertToBase64(file);
// Update the appropriate signature
setSignatures((prev) => ({
...prev,
[type]: base64,
}));
}
};
const convertToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
};
const handleSubmit = async () => {
setIsSubmitting(true);
// Only validate for DRAW and UPLOAD
if ((activeTab === 'DRAW' || activeTab === 'UPLOAD') &&
(!signatures.fullSignature || !signatures.initials)) {
// alert('Please provide both signature and initials');
toast.error('Please provide both signature and initials in draw and upload case', {
position: "top-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
setIsSubmitting(false);
return;
}
try {
// Base payload
const payload = {
signatureType: activeTab,
firstName: fullName.split(' ')[0] || user.firstName,
lastName: fullName.split(' ')[1] || user.lastName,
font: selectedSignature.style,
default: checked
};
// Only add signatures if not SELECT
if (activeTab !== 'SELECT') {
payload.signatureBase64 = signatures.fullSignature;
payload.initialsBase64 = signatures.initials;
}
let res;
if (isEditMode && editingSignature) {
// Update existing signature
const updatePayload = {
...payload,
signatureId: editingSignature.id
};
res = await api.put(`/api/user/signatures`, updatePayload);
toast.success("Signature Updated Successfully", {
position: "top-center",
autoClose: 500,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
} else {
// Create new signature
res = await api.post(`/api/user/signatures`, payload);
toast.success("Signature Created Successfully", {
position: "top-center",
autoClose: 500,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}
if (res?.data) {
await getAllSignatures();
setShowAddSignature?.(false);
}
} catch (error) {
console.error("Error submitting signature:", error);
toast.error(isEditMode ? "Failed to update signature" : "Failed to create signature", {
position: "top-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
} finally {
setIsSubmitting(false);
}
};
// Fixed renderContent function for SELECT case with proper radio button styling
const renderContent = () => {
switch (activeTab) {
case 'SELECT':
return (
<div className="h-full flex flex-col">
<div className="mb-1 md:mb-4 lg:mb-6 flex-shrink-0">
<h2 className="text-base md:text-lg lg:text-xl font-semibold text-gray-900 mb-1">Choose Signature Style</h2>
<p className="text-xs md:text-sm text-gray-600 hidden md:block">Select from pre-designed signature styles</p>
</div>
<div className="flex-1 min-h-0 overflow-y-auto no-scrollbar">
<div className="space-y-2 md:space-y-3 pb-2">
{signatureOptions.map((option) => (
<div
key={option.id}
className={`flex items-center space-x-2 md:space-x-3 p-2 md:p-3 border rounded-lg transition-all duration-200 cursor-pointer ${selectedSignature.id === option.id
? 'border-gray-100 shadow-sm'
: 'border-gray-200 hover:bg-gray-50 hover:border-gray-300'
}`}
onClick={() => setSelectedSignature({ id: option.id, style: option.style })}
>
{/* Custom Radio Button */}
<div className="flex-shrink-0">
<div
className={`w-4 h-4 md:w-3 md:h-3 rounded-full border-2 flex items-center justify-center transition-all duration-200 ${selectedSignature.id === option.id
? 'border-gray-300 bg-blue-600'
: 'border-gray-300 bg-white hover:border-gray-400'
}`}
>
{selectedSignature.id === option.id && (
<div className="w-2 h-2 md:w-3 md:h-2 rounded-full bg-blue-500"></div>
)}
</div>
</div>
{/* Hidden native radio button for accessibility */}
<input
type="radio"
id={`signature-${option.id}`}
name="signature"
checked={selectedSignature.id === option.id}
onChange={() => setSelectedSignature({ id: option.id, style: option.style })}
className="sr-only"
/>
<div className="flex-1 min-w-0 grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-3">
<div className="border border-gray-300 rounded-lg px-2 md:px-3 py-2 md:py-3 bg-white min-w-0">
<div className="text-xs text-gray-500 mb-1">Signed by:</div>
<div className={`text-sm md:text-base truncate ${option.style === 'cursive' ? 'font-serif italic' :
option.style === 'script' ? 'font-mono' :
option.style === 'elegant' ? 'font-serif' :
option.style === 'modern' ? 'font-sans tracking-wide' :
'font-sans'
}`}>
{fullName || option.name}
</div>
<div className="text-xs text-gray-400 mt-1 truncate">
dca3e646-62cd-4cc...
</div>
</div>
<div className="border border-gray-300 rounded-lg px-2 md:px-3 py-2 md:py-3 bg-white min-w-0">
<div className="text-xs text-gray-500 mb-1">DS</div>
<div className={`text-lg md:text-xl truncate ${option.style === 'cursive' ? 'font-serif italic' :
option.style === 'script' ? 'font-mono' :
option.style === 'elegant' ? 'font-serif' :
option.style === 'modern' ? 'font-sans tracking-wide' :
'font-sans'
}`}>
{initials || option.initials}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
case 'DRAW':
return (
<div className="h-full">
<DrawSignatureCanvas
onSignatureChange={handleSignatureChange}
initialSignatures={isEditMode ? signatures : undefined}
/>
</div>
);
case 'UPLOAD':
return (
<div className="h-full flex flex-col">
<div className="mb-3 md:mb-4 lg:mb-6 flex-shrink-0">
<h2 className="text-base md:text-lg lg:text-xl font-semibold text-gray-900 mb-1">Upload Signature</h2>
<p className="text-xs md:text-sm text-gray-600 hidden md:block">Upload your signature images</p>
</div>
<div className="flex-1 min-h-0 overflow-y-auto">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-3 md:gap-4 pb-2">
{/* Upload 1 - Full Signature */}
<div className="lg:col-span-8">
<label className="block text-xs md:text-sm font-medium text-gray-700 mb-2">Full Signature</label>
<label htmlFor="upload1" className="block border-2 border-dashed border-gray-300 rounded-lg h-32 md:h-40 lg:h-48 xl:h-56 flex items-center justify-center bg-gray-50 hover:bg-gray-100 cursor-pointer transition-colors">
{preview1 ? (
<img src={preview1} alt="Signature Preview" className="max-h-full max-w-full object-contain p-2" />
) : (
<div className="text-center px-4">
<MdCloudUpload className="mx-auto h-6 w-6 md:h-8 md:w-8 lg:h-10 lg:w-10 text-gray-400 mb-2" />
<p className="text-xs md:text-sm text-gray-500 mb-1">Click to upload or drag and drop</p>
<p className="text-xs text-gray-400">PNG, JPG, GIF up to 10MB</p>
</div>
)}
</label>
<input
id="upload1"
type="file"
accept="image/*"
className="hidden"
onChange={(e) => handleImageChange(e, setFile1, setPreview1, "fullSignature")}
/>
</div>
{/* Upload 2 - Initials */}
<div className="lg:col-span-4">
<label className="block text-xs md:text-sm font-medium text-gray-700 mb-2">Initials</label>
<label htmlFor="upload2" className="block border-2 border-dashed border-gray-300 rounded-lg h-32 md:h-40 lg:h-48 xl:h-56 flex items-center justify-center bg-gray-50 hover:bg-gray-100 cursor-pointer transition-colors">
{preview2 ? (
<img src={preview2} alt="Initials Preview" className="max-h-full max-w-full object-contain p-2" />
) : (
<div className="text-center px-2">
<MdCloudUpload className="mx-auto h-5 w-5 md:h-6 md:w-6 lg:h-8 lg:w-8 text-gray-400 mb-2" />
<p className="text-xs md:text-sm text-gray-500 mb-1">Upload initials</p>
<p className="text-xs text-gray-400 hidden md:block">PNG, JPG, GIF</p>
</div>
)}
</label>
<input
id="upload2"
type="file"
accept="image/*"
className="hidden"
onChange={(e) => handleImageChange(e, setFile2, setPreview2, "initials")}
/>
</div>
</div>
</div>
</div>
);
default:
return null;
}
};
const getUserDetails = async () => {
try {
let res = await api.get(`/api/user/details`);
let currUser = res?.data;
setUser(currUser);
// Only set default values if not in edit mode
if (!isEditMode) {
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);
}
};
useEffect(() => {
getUserDetails();
}, []);
return (
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 bg-black/30 flex items-center justify-center z-50 p-2 md:p-4">
<div className="bg-white rounded-md shadow-xl w-full h-full md:max-w-[95vw] lg:max-w-[85vw] xl:max-w-[80vw] md:max-h-[95vh] lg:max-h-[90vh] flex flex-col md:flex-row relative overflow-hidden ">
{/* Mobile Navigation - Fixed at top on mobile only */}
<div className="md:hidden flex-1 flex flex-col min-h-0">
{/* Scrollable area (mobile: inputs + nav + content) */}
<div className="flex-1 min-h-0 overflow-y-auto no-scrollbar">
<div className="px-3 lg:px-6 py-2 border-b border-gray-200 flex-shrink-0">
<h3 className="text-sm font-semibold text-gray-900">
{isEditMode ? 'Edit Signature' : 'Add Signature'}
</h3>
</div>
{/* Mobile Inputs */}
<div className="md:hidden w-full bg-white border-b border-gray-200 flex-shrink-0 px-3 py-2">
<div className="grid grid-cols-3 gap-2">
<div className='col-span-2'>
<label className="block text-xs font-medium text-gray-700 mb-1">
Full Name
</label>
<input
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
placeholder="Enter full name"
className="w-full px-2 py-2 text-xs border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className='col-span-1'>
<label className="block text-xs font-medium text-gray-700 mb-1">
Initials
</label>
<input
type="text"
value={initials}
onChange={(e) => setInitials(e.target.value)}
placeholder="Enter initials"
className="w-full px-2 py-2 text-xs border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
</div>
{/* Mobile Navigation - Sticky inside scroll */}
<div className="md:hidden w-full bg-gray-50 border-b border-gray-200 sticky top-0 z-20">
<div className="px-3 py-1.5">
<nav className="flex gap-1">
{navigationItems.map((item) => (
<button
key={item.id}
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
className={`flex-1 flex items-center justify-center space-x-1 px-2 py-2 text-xs font-medium rounded-lg transition-all duration-200 whitespace-nowrap ${activeTab === item.id
? 'bg-blue-50 text-blue-700 border border-blue-200 shadow-sm'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
}`}
>
<span className={activeTab === item.id ? 'text-blue-600' : 'text-gray-400'}>
{item.icon}
</span>
<span className="hidden sm:block">{item.label}</span>
</button>
))}
</nav>
</div>
</div>
{/* Main Content */}
<div className="md:hidden p-3 md:p-4 lg:py-5 lg:px-5">
<div className="h-full min-h-[300px] md:min-h-[400px]">
{renderContent()}
</div>
</div>
</div>
{/* Checkbox - Fixed */}
<div className="px-3 md:px-4 lg:px-6 py-2 md:py-3 text-gray-700 border-t border-gray-100 flex-shrink-0">
<label className="inline-flex items-center cursor-pointer text-xs md:text-sm hover:text-gray-900 transition-colors">
<input
type="checkbox"
checked={checked}
onChange={toggleCheckbox}
className="sr-only"
/>
<div
className={`w-3 h-3 md:w-4 md:h-4 border rounded flex items-center justify-center mr-2 md:mr-3 transition-colors flex-shrink-0 ${checked
? 'bg-[var(--modalSubmitBG)] border-[var(--modalSubmitBG)]'
: 'bg-white border-gray-400 hover:border-gray-500'
}`}
>
{checked && <FiCheck className="text-white text-xs" />}
</div>
<span>Save as default signature</span>
</label>
</div>
{/* Footer Buttons - Fixed */}
<div className="p-3 md:p-4 lg:p-3 border-t border-gray-200 flex sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0">
<button
className="w-full sm:w-auto px-3 md:px-4 lg:px-6 py-2 md:py-3 text-xs md:text-sm font-medium rounded-md border border-gray-300 text-gray-700 hover:bg-gray-50 hover:border-gray-400 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
onClick={() => setShowAddSignature?.(false)}
disabled={isSubmitting}
>
CANCEL
</button>
<button
className="w-full sm:w-auto px-3 md:px-4 lg:px-6 py-2 md:py-3 text-xs md:text-sm font-medium rounded-md bg-gradient-to-br from-[var(--primary)] to-blue-600 text-white hover:opacity-90 hover:shadow-lg flex items-center justify-center gap-1 md:gap-2 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
onClick={handleSubmit}
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<FaSpinner className="animate-spin w-3 h-3 md:w-4 md:h-4" />
<span>{isEditMode ? 'UPDATING...' : 'CREATING...'}</span>
</>
) : (
<span>{isEditMode ? 'UPDATE SIGNATURE' : 'CREATE SIGNATURE'}</span>
)}
</button>
</div>
</div>
{/* Desktop Sidebar Navigation */}
<div className="hidden md:flex md:w-64 lg:w-72 xl:w-80 bg-gray-50 md:border-r border-gray-200 flex-col">
{/* Header - Fixed */}
<div className="px-4 lg:px-6 py-4 border-b border-gray-200 flex-shrink-0">
<h3 className="text-base lg:text-lg font-semibold text-gray-900">
{isEditMode ? 'Edit Signature' : 'Add Signature'}
</h3>
<p className="text-sm text-gray-500">
{isEditMode ? 'Update your digital signature' : 'Create your digital signature'}
</p>
</div>
{/* Form Inputs - Fixed */}
<div className="px-4 lg:px-6 py-4 border-b border-gray-200 flex-shrink-0">
<div className="grid grid-cols-1 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Full Name
</label>
<input
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
placeholder="Enter full name"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Initials
</label>
<input
type="text"
value={initials}
onChange={(e) => setInitials(e.target.value)}
placeholder="Enter initials"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
</div>
{/* Navigation Menu - Fixed */}
<div className="px-4 lg:px-6 py-4 flex-shrink-0">
<nav className="flex flex-col gap-2">
{navigationItems.map((item) => (
<button
key={item.id}
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
className={`w-full flex items-center justify-start space-x-2 px-3 py-3 text-sm font-medium rounded-lg transition-all duration-200 whitespace-nowrap ${activeTab === item.id
? 'bg-blue-50 text-blue-700 border border-blue-200 shadow-sm'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
}`}
>
<span className={activeTab === item.id ? 'text-blue-600' : 'text-gray-400'}>
{item.icon}
</span>
<span>{item.label}</span>
</button>
))}
</nav>
</div>
{/* Spacer for desktop */}
<div className="flex-1"></div>
</div>
{/* Main Content Area */}
<div className="hidden flex-1 md:flex flex-col min-h-0">
{/* Content - Scrollable */}
<div className="flex-1 min-h-0 p-3 md:p-4 lg:py-5 lg:px-5 overflow-y-auto no-scrollbar">
<div className="h-full min-h-[300px] md:min-h-[400px]">
{renderContent()}
</div>
</div>
{/* Checkbox - Fixed */}
<div className="px-3 md:px-4 lg:px-6 py-2 md:py-3 text-gray-700 border-t border-gray-100 flex-shrink-0">
<label className="inline-flex items-center cursor-pointer text-xs md:text-sm hover:text-gray-900 transition-colors">
<input
type="checkbox"
checked={checked}
onChange={toggleCheckbox}
className="sr-only"
/>
<div
className={`w-3 h-3 md:w-4 md:h-4 border rounded flex items-center justify-center mr-2 md:mr-3 transition-colors flex-shrink-0 ${checked ? 'bg-[var(--modalSubmitBG)] border-[var(--modalSubmitBG)]' : 'bg-white border-gray-400 hover:border-gray-500'
}`}
>
{checked && <FiCheck className="text-white text-xs" />}
</div>
<span>Save as default signature</span>
</label>
</div>
{/* Footer Buttons - Fixed */}
<div className="p-3 md:p-4 lg:p-3 border-t border-gray-200 flex sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0">
<button
className="w-full sm:w-auto px-3 md:px-4 lg:px-6 py-2 md:py-3 text-xs md:text-sm font-medium rounded-md border border-gray-300 text-gray-700 hover:bg-gray-50 hover:border-gray-400 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
onClick={() => setShowAddSignature?.(false)}
disabled={isSubmitting}
>
CANCEL
</button>
<button
className="w-full sm:w-auto px-3 md:px-4 lg:px-6 py-2 md:py-3 text-xs md:text-sm font-medium rounded-md bg-gradient-to-br from-[var(--primary)] to-blue-600 text-white hover:opacity-90 hover:shadow-lg flex items-center justify-center gap-1 md:gap-2 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
onClick={handleSubmit}
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<FaSpinner className="animate-spin w-3 h-3 md:w-4 md:h-4" />
<span>{isEditMode ? 'UPDATING...' : 'CREATING...'}</span>
</>
) : (
<span>{isEditMode ? 'UPDATE SIGNATURE' : 'CREATE SIGNATURE'}</span>
)}
</button>
</div>
</div>
</div>
</div>
);
};
export default AddSignatureModal;