From 79b121c25b0575918f71866c20d6c9339469af67 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Fri, 4 Jul 2025 12:02:45 +0530 Subject: [PATCH] chnages --- src/components/setting/CreateAndInvite.tsx | 240 ++++++++++--- src/components/setting/Setting.tsx | 138 +++++-- .../setting/Signature/AddSignatureModal.tsx | 137 ++++--- .../setting/Signature/Signature.tsx | 338 ++++++++++-------- 4 files changed, 559 insertions(+), 294 deletions(-) diff --git a/src/components/setting/CreateAndInvite.tsx b/src/components/setting/CreateAndInvite.tsx index 3cc87d7..c8d8f13 100644 --- a/src/components/setting/CreateAndInvite.tsx +++ b/src/components/setting/CreateAndInvite.tsx @@ -1,63 +1,119 @@ import React, { useRef, useState } from 'react'; +import { FaSpinner } from 'react-icons/fa'; export type ActionType = 'create' | 'invite'; type CreateData = { accountName: string }; -type InviteData = { i_accountName: string; email: string; role: string }; +type InviteData = { i_accountName: number; email: string; role: string }; -type FormDataType = CreateData | InviteData; +export type FormDataType = CreateData | InviteData; + +interface AccountOption { + userId: number; + userEmail: string; + accountId: number; + accountName: string; + role: string; + status: string; +} interface Props { onSubmit: (data: FormDataType, action: ActionType) => void; + createAndInviteLoader: boolean; + accountOptions: AccountOption[]; + accountOptionsLoading: boolean; } -const CreateAndInvite = ({ onSubmit }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const dropdownRef = useRef(null); - +const CreateAndInvite = ({ + onSubmit, + createAndInviteLoader, + accountOptions, + accountOptionsLoading +}: Props) => { + const [isRoleDropdownOpen, setIsRoleDropdownOpen] = useState(false); + const [isAccountDropdownOpen, setIsAccountDropdownOpen] = useState(false); + const roleDropdownRef = useRef(null); + const accountDropdownRef = useRef(null); + const [createFormData, setCreateFormData] = useState<{ accountName: string }>({ accountName: '', }); - + + const [accountSearchTerm, setAccountSearchTerm] = useState(''); + const [selectedAccount, setSelectedAccount] = useState(null); + const [inviteFormData, setInviteFormData] = useState<{ - i_accountName: string; + i_accountName: number; email: string; role: string; }>({ - i_accountName: '', + i_accountName: 0, email: '', role: '', }); - + const [errors, setErrors] = useState<{ [key: string]: string }>({}); + const roles = ['ADMIN', 'EDITOR', 'VIEWER']; - const roles = ['Owner', 'Client']; - - const handleFocus = () => setIsOpen(true); - - const handleBlur = (e: React.FocusEvent) => { + // Handle role dropdown + const handleRoleDropdownFocus = () => setIsRoleDropdownOpen(true); + + const handleRoleDropdownBlur = (e: React.FocusEvent) => { setTimeout(() => { const activeElement = document.activeElement as HTMLElement; - if (dropdownRef.current && !dropdownRef.current.contains(activeElement)) { - setIsOpen(false); + if (roleDropdownRef.current && !roleDropdownRef.current.contains(activeElement)) { + setIsRoleDropdownOpen(false); } }, 100); }; - const handleSelect = (role: string) => { + const handleRoleSelect = (role: string) => { setInviteFormData((prev) => ({ ...prev, role })); - setIsOpen(false); + setIsRoleDropdownOpen(false); }; + // Handle account dropdown + const handleAccountDropdownFocus = () => setIsAccountDropdownOpen(true); + + const handleAccountDropdownBlur = (e: React.FocusEvent) => { + setTimeout(() => { + const activeElement = document.activeElement as HTMLElement; + if (accountDropdownRef.current && !accountDropdownRef.current.contains(activeElement)) { + setIsAccountDropdownOpen(false); + } + }, 100); + }; + + const handleAccountSearchChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setAccountSearchTerm(value); + + // Clear selected account if user is typing + if (selectedAccount && value !== selectedAccount.accountName) { + setSelectedAccount(null); + setInviteFormData((prev) => ({ ...prev, i_accountName: 0 })); + } + }; + + const handleAccountSelect = (option: AccountOption) => { + setSelectedAccount(option); + setAccountSearchTerm(option.accountName); + setInviteFormData((prev) => ({ ...prev, i_accountName: option.accountId })); + setIsAccountDropdownOpen(false); + + // Clear any existing account name error + if (errors.i_accountName) { + setErrors(prev => ({ ...prev, i_accountName: '' })); + } + }; + + // Handle create form const handleCreateChange = (value: string) => { setCreateFormData({ accountName: value }); - }; - - const handleInviteChange = ( - field: keyof typeof inviteFormData, - value: string - ) => { - setInviteFormData((prev) => ({ ...prev, [field]: value })); + // Clear error when user starts typing + if (errors.accountName) { + setErrors(prev => ({ ...prev, accountName: '' })); + } }; const handleCreateSubmit = () => { @@ -69,11 +125,20 @@ const CreateAndInvite = ({ onSubmit }: Props) => { onSubmit(createFormData, 'create'); }; + // Handle invite form + const handleInviteChange = (field: keyof typeof inviteFormData, value: string) => { + setInviteFormData((prev) => ({ ...prev, [field]: value })); + // Clear error when user starts typing + if (errors[field]) { + setErrors(prev => ({ ...prev, [field]: '' })); + } + }; + const handleInviteSubmit = () => { const { i_accountName, email, role } = inviteFormData; const errs: { [key: string]: string } = {}; - if (!i_accountName.trim()) errs.i_accountName = 'Account name is required'; + if (!i_accountName) errs.i_accountName = 'Account name is required'; if (!email.trim()) errs.email = 'Email is required'; else if (!/\S+@\S+\.\S+/.test(email)) errs.email = 'Invalid email'; if (!role.trim()) errs.role = 'Role is required'; @@ -87,9 +152,14 @@ const CreateAndInvite = ({ onSubmit }: Props) => { onSubmit(inviteFormData, 'invite'); }; + // Filter account options based on search term + const filteredAccountOptions = accountOptions.filter((option) => + option.accountName?.toLowerCase().includes(accountSearchTerm.toLowerCase()) + ); + return ( -
- {/* Create */} +
+ {/* Create Section */}

Create Account

@@ -107,9 +177,17 @@ const CreateAndInvite = ({ onSubmit }: Props) => {
{errors.accountName && ( @@ -118,22 +196,62 @@ const CreateAndInvite = ({ onSubmit }: Props) => {
- {/* Invite */} + {/* Invite Section */}

Invite

- {/* Account Name */} -
+ {/* Account Name Dropdown */} +
- handleInviteChange('i_accountName', e.target.value)} - className="w-full px-3 py-1.5 text-gray-600 rounded-md border border-gray-400 focus:outline-none focus:ring-1 focus:ring-[var(--primary)]" - /> +
+ + + {/* Loading indicator */} + {accountOptionsLoading && ( +
+ +
+ )} + + {/* Account Dropdown */} + {isAccountDropdownOpen && !accountOptionsLoading && ( +
+
    + {filteredAccountOptions.length > 0 ? ( + filteredAccountOptions.map((option) => ( +
  • handleAccountSelect(option)} + className="px-3 py-2 text-[13px] text-gray-600 hover:bg-gray-100 cursor-pointer" + > +
    + {option.accountName} +
    +
  • + )) + ) : ( +
  • No results found
  • + )} +
+
+ )} +
+ {errors.i_accountName && ( {errors.i_accountName} )} @@ -163,8 +281,8 @@ const CreateAndInvite = ({ onSubmit }: Props) => {
{ placeholder="Select role" value={inviteFormData.role} readOnly - onFocus={handleFocus} - className="w-full px-3 py-1.5 border text-gray-600 border-gray-400 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-[var(--primary)] transition" + onFocus={handleRoleDropdownFocus} + className="w-full px-3 py-1.5 border text-gray-600 border-gray-400 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-[var(--primary)] transition cursor-pointer" />
- {roles.map((r) => ( + {roles.map((role) => (
handleSelect(r)} - className="px-4 py-2 text-[13px] hover:bg-gray-100 cursor-pointer" + key={role} + onMouseDown={() => handleRoleSelect(role)} + className="px-4 py-1 text-[13px] hover:bg-gray-100 cursor-pointer" > - {r} + {role}
))}
- {errors.role && ( - {errors.role} - )}
+ {errors.role && ( + {errors.role} + )}
@@ -203,9 +321,17 @@ const CreateAndInvite = ({ onSubmit }: Props) => {
@@ -213,4 +339,4 @@ const CreateAndInvite = ({ onSubmit }: Props) => { ); }; -export default CreateAndInvite; +export default CreateAndInvite; \ No newline at end of file diff --git a/src/components/setting/Setting.tsx b/src/components/setting/Setting.tsx index a658c73..27c551e 100644 --- a/src/components/setting/Setting.tsx +++ b/src/components/setting/Setting.tsx @@ -7,6 +7,7 @@ import Language from "./Language"; import CreateAndInvite, { ActionType, FormDataType } from './CreateAndInvite' import AsideSection from "./AsideSection"; import Signature from "./Signature/Signature"; +import { Bounce, toast } from "react-toastify"; // Define the shape of state object interface Account { @@ -30,11 +31,20 @@ interface SubSection { component?: React.ReactNode; } +interface AccountOption { + userId: number; + userEmail: string; + accountId: number; + accountName: string; + role: string; + status: string; +} + const Setting = () => { const { page1 } = useParams<{ page1: string }>(); const location = useLocation(); const navigate = useNavigate(); - + // Refs for subsections within each main tab const subSectionRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const scrollContainerRef = useRef(null); @@ -54,6 +64,12 @@ const Setting = () => { }, }); + const [createAndInviteLoader, setCreateAndInviteLoader] = useState(false); + + // Dropdown data states + const [accountOptions, setAccountOptions] = useState([]); + const [accountOptionsLoading, setAccountOptionsLoading] = useState(false); + // Define subsections for each main tab const getSubSections = (tabId: string): SubSection[] => { switch (tabId) { @@ -62,16 +78,6 @@ const Setting = () => { { id: "profile-info", label: "Profile Information" }, { id: "signature", label: "Signature" }, ]; - // case "create and invite": - // return [ - // { id: "create-account", label: "Create Account" }, - // { id: "invite-users", label: "Invite Users" }, - // { id: "manage-invitations", label: "Manage Invitations" } - // ]; - // return [ - // { id: "share-options", label: "Share Options" }, - // { id: "privacy-settings", label: "Privacy Settings" } - // ]; default: return []; } @@ -80,18 +86,18 @@ const Setting = () => { // Function to scroll to specific subsection const scrollToSubSection = (subSectionId: string) => { const targetRef = subSectionRefs.current[subSectionId]; - + if (targetRef && scrollContainerRef?.current) { const container = scrollContainerRef.current; const target = targetRef; - + // Calculate the position relative to the scroll container const containerRect = container.getBoundingClientRect(); const targetRect = target.getBoundingClientRect(); - + // Calculate scroll position (accounting for some padding) const scrollTop = container.scrollTop + targetRect.top - containerRect.top - 20; - + // Smooth scroll to the target container.scrollTo({ top: scrollTop, @@ -132,7 +138,7 @@ const Setting = () => { // Check if the hash corresponds to a valid subsection const allSubSections = getSubSections(activeTab); const validSubSection = allSubSections.find(section => section.id === hash); - + if (validSubSection) { setActiveSubSection(hash); // Delay scroll to ensure component is rendered @@ -143,6 +149,21 @@ const Setting = () => { } }, [location.hash, activeTab]); + // Fetch account list for dropdown + const getAccountList = async () => { + setAccountOptionsLoading(true); + try { + const res = await api.get('/api/user/accounts'); + if (res?.data) { + setAccountOptions(res.data); + } + } catch (error) { + console.error('Error fetching account options:', error); + } finally { + setAccountOptionsLoading(false); + } + }; + // Render subsection content based on active tab and subsection const renderSubSectionContent = (subSectionId: string) => { switch (activeTab) { @@ -152,7 +173,7 @@ const Setting = () => { return ; case "signature": return ( - + ); default: return; @@ -169,7 +190,7 @@ const Setting = () => { const renderContent = () => { const currentSubSections = getSubSections(activeTab); - + if (currentSubSections.length === 0) { // If no subsections, render the main content directly switch (activeTab) { @@ -178,7 +199,14 @@ const Setting = () => { case "language": return ; case "create and invite": - return ; + return ( + + ); default: return
Coming Soon...
; } @@ -221,18 +249,20 @@ const Setting = () => { useEffect(() => { getUserDetails() + // Fetch account list when component mounts + getAccountList() }, []) // Set initial subsection when component mounts or activeTab changes useEffect(() => { const subSections = getSubSections(activeTab); const hash = location.hash.replace('#', ''); - + if (subSections.length > 0) { // If there's a hash and it's valid for current tab, use it if (hash && subSections.find(s => s.id === hash)) { setActiveSubSection(hash); - } + } // If no valid hash or no current activeSubSection, use first subsection else if (!activeSubSection || !subSections.find(s => s.id === activeSubSection)) { const firstSubSectionId = subSections[0].id; @@ -255,14 +285,51 @@ const Setting = () => { }, [activeSubSection]); const handleSubmit = async (data: FormDataType, action: ActionType) => { + setCreateAndInviteLoader(true) try { + let res; + let toastMesaage ; if (action === 'create') { console.log('Creating account:', data); + const payloadAccount = { + name: data.accountName + } + res = await api.post(`/api/user/accounts`, payloadAccount) + console.log("resAccountName", res) + if (res.status === 201) { + toastMesaage = `${action.toUpperCase()} Created Successfull...!` + } } else if (action === 'invite') { console.log('Inviting user:', data); + const payloadInvite = { + email: data.email, + role: data.role + } + res = await api.post(`/api/accounts/${data.i_accountName}/invite`, payloadInvite) + console.log("InviteNAme",res) + toastMesaage = "Invite Successfull...!" + } + + if (res?.data) { + toast.success(toastMesaage, { + position: "top-center", + autoClose: 1500, + hideProgressBar: false, + closeOnClick: false, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + transition: Bounce, + }); + + // Refetch account list after successful submission + await getAccountList(); } } catch (err) { console.error('Submission error:', err); + } finally { + setCreateAndInviteLoader(false) } }; @@ -279,11 +346,10 @@ const Setting = () => { key={tab.id} onClick={() => handleMainTabClick(tab.id)} className={`cursor-pointer relative pb-3 text-sm font-medium transition-all duration-200 - ${ - activeTab === tab.id + ${activeTab === tab.id ? "text-blue-600 after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-blue-600" : "text-gray-600 hover:text-blue-500" - }`} + }`} > {tab.label} @@ -296,19 +362,19 @@ const Setting = () => {
{/* Fixed Aside Navigation - Shows subsections of active tab */} {activeTab === 'userProfile' && -
-
- t.id === activeTab)?.label || ""} - /> -
-
-} +
+
+ t.id === activeTab)?.label || ""} + /> +
+
+ } {/* Scrollable Main Content */} -
diff --git a/src/components/setting/Signature/AddSignatureModal.tsx b/src/components/setting/Signature/AddSignatureModal.tsx index 7612baf..fd784e3 100644 --- a/src/components/setting/Signature/AddSignatureModal.tsx +++ b/src/components/setting/Signature/AddSignatureModal.tsx @@ -1,13 +1,14 @@ 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 } 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"; interface AddSignatureModalProps { setShowAddSignature?: (value: boolean) => void; + getAllSignatures: () => Promise; } interface Account { id: number; @@ -24,23 +25,19 @@ interface UserData { account: Account; } -const AddSignatureModal: React.FC = ({ setShowAddSignature }) => { - const [activeTab, setActiveTab] = useState<'choose' | 'draw' | 'upload'>('choose'); +interface SelectStyle { + id: number, + style: string +} +const AddSignatureModal: React.FC = ({ setShowAddSignature , getAllSignatures }) => { + const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT'); const [fullName, setFullName] = useState(''); const [initials, setInitials] = useState(''); - + const [isSubmitting, setIsSubmitting] = useState(false); 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' }, { id: 2, name: 'cursive', initials: 'LA', style: 'cursive' }, @@ -49,10 +46,9 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu { id: 5, name: 'modern', initials: 'LA', style: 'modern' }, { id: 6, name: 'classic', initials: 'LA', style: 'classic' }, ]; - - const [selectedSignature, setSelectedSignature] = useState(1); - console.log(selectedSignature); - + const [selectedSignature, setSelectedSignature] = useState( + signatureOptions.length > 0 ? { id: signatureOptions[0].id, style: signatureOptions[0].style } : { id: 0, style: "" } + ); // Store the actual file const [file1, setFile1] = useState(null); @@ -75,13 +71,22 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu name: "", }, }); - // Navigation items const navigationItems = [ - { id: 'choose', label: 'Choose', icon: }, - { id: 'draw', label: 'Draw', icon: }, - { id: 'upload', label: 'Upload', icon: }, + { id: 'SELECT', label: 'SELECT', icon: }, + { id: 'DRAW', label: 'DRAW', icon: }, + { id: 'UPLOAD', label: 'UPLOAD', icon: }, ]; + console.log("signatures", signatures) + const handleSignatureChange = (data: { fullSignature: string | null; initials: string | null }) => { + setSignatures(data); + }; + console.log("activeTab", activeTab) + console.log("selectedSignature", selectedSignature); + + console.log('file1', file1) + console.log("file1", file2) + // Cleanup preview URLs when component unmounts or image changes useEffect(() => { @@ -91,55 +96,73 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu }; }, [preview1, preview2]); - const handleImageChange = ( + const handleImageChange = async ( e: React.ChangeEvent, setFile: React.Dispatch>, - setPreview: React.Dispatch> + setPreview: React.Dispatch>, + 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 => { + 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) if (!signatures.fullSignature || !signatures.initials) { alert('Please provide both signature and initials'); return; } - - // Example: Posting the base64 data - try { const payload = { - signatureType: "DRAW", + signatureType: activeTab, // "signatureId": 0, this work on only the put req. signatureBase64: signatures.fullSignature, initialsBase64: signatures.initials, firstName: user.firstName, lastName: user.lastName, - font: "" + font: selectedSignature.style }; console.log('Submitting Signature:', payload); - let res = await api.post(`/api/user/signatures`,payload) + let res = await api.post(`/api/user/signatures`, payload) console.log("handleSubmit", res) + if (res?.data) { + getAllSignatures() + setShowAddSignature?.(false) + } } catch (error) { console.error("Error fetching user details:", error); + } finally { + setIsSubmitting(false) } - - - - - // fetch('/api/signature', { method: 'POST', body: JSON.stringify(payload) }) }; const renderContent = () => { switch (activeTab) { - case 'choose': + case 'SELECT': return (
@@ -155,8 +178,8 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu type="radio" id={`signature-${option.id}`} name="signature" - checked={selectedSignature === option.id} - onChange={() => setSelectedSignature(option.id)} + checked={selectedSignature.id === option.id} + onChange={() => setSelectedSignature({ id: option.id, style: option.style })} className="w-4 h-4 text-blue-600 focus:ring-blue-500" />
@@ -193,12 +216,12 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu
); - case 'draw': + case 'DRAW': return ( ); - case 'upload': + case 'UPLOAD': return (
@@ -210,7 +233,7 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu {/* Upload 1 */}
-
{/* Upload 2 */}
-
@@ -332,8 +355,8 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu {navigationItems.map((item) => ( + */} {renderContent()}
{/* Footer Buttons in Main Area */}
-
diff --git a/src/components/setting/Signature/Signature.tsx b/src/components/setting/Signature/Signature.tsx index a175ff6..9cb6809 100644 --- a/src/components/setting/Signature/Signature.tsx +++ b/src/components/setting/Signature/Signature.tsx @@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react'; import { FiEdit3, FiTrash2, FiPlus, FiMoreVertical } from 'react-icons/fi'; import AddSignatureModal from './AddSignatureModal'; import api from '../../../config/api'; +import { Bounce, toast } from "react-toastify"; +import { FaSpinner } from 'react-icons/fa'; interface Signature { id: string; @@ -10,39 +12,23 @@ interface Signature { lastName: string; firstName: string font: string; - initialsBlob:string; - initialsStampedBlob:string; - initialsHash:string; - signatureHash:string; - signatureBlob:string; - signatureStampedBlob:string; - default:boolean + initialsBlob: string; + initialsStampedBlob: string; + initialsHash: string; + signatureHash: string; + signatureBlob: string; + signatureStampedBlob: string; + default: boolean } const SignatureManager: React.FC = () => { - - - + const [loading, setLoading] = useState(false) const [signatures, setSignatures] = useState([]); - const [activeDropdown, setActiveDropdown] = useState(null); const [showAddSignature, setShowAddSignature] = useState(false) - const handleEdit = (id: string) => { - console.log('Edit signature:', id); - setActiveDropdown(null); - }; - - const handleDelete = (id: string) => { - setSignatures(signatures.filter(sig => sig.id !== id)); - setActiveDropdown(null); - }; - - const toggleDropdown = (id: string) => { - setActiveDropdown(activeDropdown === id ? null : id); - }; - const getAllSignatures = async () => { + setLoading(true) try { let res = await api.get("/api/user/signatures") console.log("getAllSignatures", res.data) @@ -50,6 +36,8 @@ const SignatureManager: React.FC = () => { setSignatures(signatureData) } catch (error) { console.log("getAllSignatures", error) + } finally { + setLoading(false) } } @@ -57,148 +45,198 @@ const SignatureManager: React.FC = () => { getAllSignatures() }, []) + const handleEdit = (id: string) => { + console.log('Edit signature:', id); + setActiveDropdown(null); + }; + + const handleDelete = async (id: string) => { + if (!id) return; + setLoading(true) + try { + let res = await api.delete(`/api/user/signatures/${id}`) + console.log(res.data) + toast.success("Signature Delete Successfull", { + position: "top-center", + autoClose: 500, + hideProgressBar: false, + closeOnClick: false, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + transition: Bounce, + }); + await getAllSignatures(); + } catch (error) { + console.log("delete signature", error) + } finally { + setActiveDropdown(null); + setLoading(false) + } + + }; + + const toggleDropdown = (id: string) => { + setActiveDropdown(activeDropdown === id ? null : id); + }; + + + return ( -
-
- {/* Header */} -
-
-

Signatures

-

Add or update your name and signature styles.

+ <> + {loading ?
+
+
+
- +

Loading...

+
: +
+
+ <> + {/* Header */} +
+
+

Signatures

+

Add or update your name and signature styles.

+
+ +
- {/* Table Card */} -
- {/* Table Header */} -
-
-
-

Signature

-
-
-

Initial

-
-
-

Actions

-
-
-
- - {/* Table Body */} -
- {signatures.map((signature, index) => ( -
- {/* Signature Column */} -
-
- {/* Label */} -
- Signed by: + {/* Table Card */} +
+ {/* Table Header */} +
+
+
+

Signature

- - {/* Signature Image */} -
- Signature +
+

Initial

- - {/* Signature ID */} -
- {signature.signatureHash} +
+

Actions

+ {/* Table Body */} +
+ {signatures.map((signature, index) => ( +
+ {/* Signature Column */} +
+
+ {/* Label */} +
+ Signed by: +
- {/* Initial Column */} -
-
- {/* Label */} -
- DS -
+ {/* Signature Image */} +
+ Signature +
- {/* Signature Image */} -
- DS Signature -
-
-
- - - {/* Actions Column */} -
-
- - - {/* Dropdown Menu */} - {activeDropdown === signature.id && ( -
- - + {/* Signature ID */} +
+ {signature.signatureHash} +
+
- )} -
+ + + {/* Initial Column */} +
+
+ {/* Label */} +
+ DS +
+ + {/* Signature Image */} +
+ DS Signature +
+
+
+ + + {/* Actions Column */} +
+
+ + + {/* Dropdown Menu */} + {activeDropdown === signature.id && ( +
+ + +
+ )} +
+
+
+ ))}
- ))} + + {/* Empty State (if no signatures) */} + {signatures.length === 0 && ( +
+
+ +
+

No signatures yet

+

Create your first signature to get started.

+
+ )} +
+ + {/* Click outside to close dropdown */} + {activeDropdown && ( +
setActiveDropdown(null)} + /> + )} + + {showAddSignature && ( + + )}
- - {/* Empty State (if no signatures) */} - {signatures.length === 0 && ( -
-
- -
-

No signatures yet

-

Create your first signature to get started.

-
- )} -
- - {/* Click outside to close dropdown */} - {activeDropdown && ( -
setActiveDropdown(null)} - /> - )} - - {showAddSignature && ( - - )} -
+ } ); };