This commit is contained in:
=bhagwanSingh 2025-10-27 12:25:48 +05:30
parent 950601171c
commit ca1a4da954
21 changed files with 757 additions and 368 deletions

View File

@ -6,6 +6,7 @@ import { IoSettings, IoSettingsOutline } from 'react-icons/io5';
import { useSelector } from 'react-redux';
import { RootState } from '../store/store';
import { GoBell } from 'react-icons/go';
import getInitialsUtils from '../utlity/getInitialUtils';
interface AccountNavProps {
accountName?: string | null;
@ -65,7 +66,7 @@ const CommonNav: React.FC<AccountNavProps> = ({
const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const [initial, setInitial] = useState<string>("");
// const [initial, setInitial] = useState<string>("");
const [user, setUser] = useState<UserData>({
id: 0,
@ -86,13 +87,13 @@ const CommonNav: React.FC<AccountNavProps> = ({
navigate("/s/settings#profile-info");
}
useEffect(() => {
if (initial != "") return
console.log(userDetailsAsync, 'line 89')
let initialN = (userDetailsAsync.firstName && userDetailsAsync.firstName[0])
initialN = initialN + "" + (userDetailsAsync.lastName && userDetailsAsync.lastName[0])
setInitial(initialN)
}, [userDetailsAsync])
// useEffect(() => {
// if (initial != "") return
// console.log(userDetailsAsync, 'line 89')
// let initialN = (userDetailsAsync.firstName && userDetailsAsync.firstName[0])
// initialN = initialN + "" + (userDetailsAsync.lastName && userDetailsAsync.lastName[0])
// setInitial(initialN)
// }, [userDetailsAsync])
const getAccountDetails = async () => {
try {
@ -153,11 +154,11 @@ const CommonNav: React.FC<AccountNavProps> = ({
},
});
if (account.firstName && account.lastName) {
setInitial((account?.firstName?.slice(0, 1)).toUpperCase() + "" + (account?.lastName?.slice(0, 1)).toUpperCase())
} else {
setInitial((account?.name?.slice(0, 1)).toUpperCase())
}
// if (account.firstName && account.lastName) {
// setInitial((account?.firstName?.slice(0, 1)).toUpperCase() + "" + (account?.lastName?.slice(0, 1)).toUpperCase())
// } else {
// setInitial((account?.name?.slice(0, 1)).toUpperCase())
// }
sessionStorage.setItem('userDetails', JSON.stringify({
id: account.userId,
@ -198,11 +199,12 @@ const CommonNav: React.FC<AccountNavProps> = ({
};
setUser(userData);
if (userData.firstName && userData.lastName) {
setInitial((userData?.firstName?.slice(0, 1)).toUpperCase() + "" + (userData?.lastName?.slice(0, 1)).toUpperCase())
} else {
setInitial((userData?.name?.slice(0, 1)).toUpperCase())
}
// if (userData.firstName && userData.lastName) {
// setInitial((userData?.firstName?.slice(0, 1)).toUpperCase() + "" + (userData?.lastName?.slice(0, 1)).toUpperCase())
// } else {
// setInitial((userData?.name?.slice(0, 1)).toUpperCase())
// }
sessionStorage.setItem('userDetails', JSON.stringify(userData));
sessionStorage.setItem('activeAccountId', JSON.stringify(userData.account.id));
@ -405,8 +407,8 @@ const CommonNav: React.FC<AccountNavProps> = ({
>
<div className="w-8 h-8 rounded-md overflow-hidden bg-gradient-to-br from-[var(--primary)] to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md">
{user ? (
<span className="w-full h-full object-cover p-1.5 font-bold">
{initial}
<span className="w-full h-full object-cover p-1.5 font-bold capitalize">
{getInitialsUtils((user.firstName ?? '') + (user.firstName && user.lastName ? ' ' : '') + (user.lastName ?? ''))}
</span>
) : (
<FaUser className="text-lg" />
@ -420,8 +422,8 @@ const CommonNav: React.FC<AccountNavProps> = ({
<div className="flex items-center space-x-3">
<div className="w-12 h-12 rounded-full overflow-hidden bg-gradient-to-br from-[var(--primary)] to-blue-600 flex items-center justify-center text-white font-semibold shadow-md">
{user ? (
<span className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold">
{initial}
<span className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold capitalize">
{getInitialsUtils((user.firstName ?? '') + (user.firstName && user.lastName ? ' ' : '') + (user.lastName ?? ''))}
</span>
) : (
<FaUser className="text-lg" />

View File

@ -0,0 +1,45 @@
type PropsType = {
setShowConfirmationModal: React.Dispatch<{ show: boolean, id: string }>;
onConfirm: () => void;
message: string;
title: string;
textColor: string;
hoverTextColor: string;
rejectBtnText: string;
confirmBtnText: string;
};
const ConfirmationModal = ({ setShowConfirmationModal, onConfirm, message, title, textColor, hoverTextColor, rejectBtnText, confirmBtnText }: PropsType) => {
return (
<div className="fixed inset-0 backdrop-blur-sm bg-black/30 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl p-6 w-100">
<h3 className="text-lg font-medium text-gray-800 mb-4">{title}</h3>
<p className="text-gray-600 mb-6">
{message}
</p>
<div className="flex justify-end gap-3">
<button
onClick={() => {
setShowConfirmationModal({ show: false, id: "" });
}}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md transition-colors cursor-pointer"
>
{rejectBtnText}
</button>
<button
onClick={() => {
onConfirm();
}}
className={`px-4 py-2 ${textColor} text-white hover:${hoverTextColor} rounded-md transition-colors cursor-pointer`}
>
{confirmBtnText}
</button>
</div>
</div>
</div>
);
};
export default ConfirmationModal;

View File

@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa";
import api from '../../config/api';
import getInitialsUtils from '../../utlity/getInitialUtils';
interface AccountNavProps {
accountName?: string | null;

View File

@ -20,6 +20,7 @@ const DownloadDocument: React.FC<DownloadDocumentInt> = ({ showMenu, setShowMenu
const includeCertificate = formData.get('download-doc-type-include-cert');
downloadDocumentUtils(accountId, showMenu.transactionId as string, docType as string, includeCertificate as string | null)
setShowMenu({ open: false, transactionId: null });
}

View File

@ -158,7 +158,7 @@ const TransactionDataTable = ({ activeAccountId }) => {
<td className="px-6 py-3 text-[var(--mainPrimaryBtn)] font-medium">
<button
onClick={() => setShowMenu({ open: true, transactionId: tx.id })}
className="flex items-center gap-2 text-sm hover:underline"
className="flex items-center gap-2 text-sm hover:underline cursor-pointer"
>
<MdOutlineFileDownload className="text-lg" /> Download
</button>

View File

@ -26,6 +26,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
<div
onClick={() => {
setDropDownTogglePage(`${(dropDownTogglePage == 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`);
setCurrentPage(0);
window.location.hash = document.documentId + "" + 0;
}
}

View File

@ -11,8 +11,8 @@ import { Bounce, toast } from "react-toastify";
import { useDispatch, useSelector } from "react-redux";
import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
import { FaCheck, FaQuestionCircle, FaDownload } from 'react-icons/fa'
import { IoIosDocument } from 'react-icons/io'
import { FaCheck, FaQuestionCircle, FaDownload, FaAngleUp } from 'react-icons/fa'
import { IoIosDocument, IoMdClose } from 'react-icons/io'
import { MdKeyboardArrowDown } from 'react-icons/md'
import confetti from 'canvas-confetti'
@ -134,6 +134,10 @@ const fitObj = [
label: "50%",
value: "50"
},
{
label: "130%",
value: "130"
},
{
label: "150%",
value: "150"
@ -159,6 +163,7 @@ const SignDocument = () => {
// Signer data - START
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
const [isPages, setIsPages] = useState<boolean>(true);
const [recipient, setRecipient] = useState<RecipientType | null>(null);
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
@ -190,7 +195,7 @@ const SignDocument = () => {
const [isFields, setIsFields] = useState<boolean>(true);
const [isFitDd, setIsFitDd] = useState<boolean>(false)
const [selFitIndx, setSelFitIndx] = useState<number>(0);
const [selFitIndx, setSelFitIndx] = useState<number>(2);
const globalLoader = useSelector((state: RootState) => state.loader)
@ -833,7 +838,7 @@ const SignDocument = () => {
} else {
setNavigationTagPos(NavigationTagPosEnum.VISIBLE)
}
}, 150);
}, 10);
// Initial check
checkPosition();
@ -871,9 +876,43 @@ const SignDocument = () => {
{/* Nav Two - Options - START */}
<div className="flex flex-col w-full">
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)]'>
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)] mt-0'>
<div className="flex items-center">
<img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
{/* FIT - START */}
<div
className="hidden sm:block relative"
tabIndex={1}
onBlur={() => setIsFitDd(false)}
>
<div
onClick={() => setIsFitDd(!isFitDd)}
className='text-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'>
<p className='tracking-wide'>Fit</p>
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
</div>
{isFitDd && <div className='absolute top-[30px] left-[20px] bg-white rounded-sm z-50'>
{fitObj.map((val, indx) => {
return (<div
key={indx}
onClick={() => setSelFitIndx(indx)}
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
<div
className='flex items-center justify-between w-full'>
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{val.label}</p>
{selFitIndx == indx && <FaCheck className='text-sm text-slate-400' />}
</div>
</div>)
})
}
</div>}
</div>
{/* FIT - END */}
</div>
{/* Right Side - STARTS */}
<div className='flex justify-center items-center'>
@ -950,7 +989,7 @@ const SignDocument = () => {
<div
className={`absolute z-50 left-0 bottom-0 bg-[var(--primary)] w-full flex justify-around items-center
px-2.5 transition-all duration-500 ease-in-out
${requiredFilled ? "translate-y-0 opacity-100" : "translate-y-full opacity-0"}`}
${requiredFilled ? "translate-y-0" : "translate-y-full hidden"}`}
>
<div className="py-6 text-white">
<p className="text-2xl font-semibold">All Set to Wrap This Up?</p>
@ -995,16 +1034,26 @@ const SignDocument = () => {
{/* Document Container Start */}
<div className="grid grid-cols-12 gap-1 mt-1 h-[calc(100vh-86px)] overflow-hidden">
<div className={`grid grid-cols-12 gap-1 mt-1 ${requiredFilled ? "h-[calc(100vh-100px)]" : "h-[calc(100vh-86px)]"} overflow-hidden`}>
{/* Left Side pages start */}
{/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
<div
id="tags-list-client-side"
className={`absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-full p-4 overflow-y-auto ${recipient?.type != "CC" ? "bg-white" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
className={`${isPages ? "block" : "hidden"} sm:block absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-full p-4 overflow-y-auto ${recipient?.type != "CC" ? "bg-white" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
{!transparentTags && <div className='flex flex-col md:flex-row justify-between items-center bg-slate-100 p-1.5 rounded-2xl border-1 border-gray-200 mb-3'>
<div
onClick={() => {
setIsPages(false)
setIsFields(true)
}}
className='absolute sm:hidden top-[5px] right-[10px] bg-white rounded-full text-2xl z-50'>
<IoMdClose />
</div>
{provideTags && !transparentTags && <div className='flex flex-col md:flex-row justify-between items-center bg-slate-100 p-1.5 rounded-2xl border-1 border-gray-200 mb-3'>
{provideTags && recipient?.type != "CC" && <div
onClick={() => handleSetIsFields(true)}
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isFields ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300'}`}
@ -1026,10 +1075,10 @@ const SignDocument = () => {
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
{!isFields && <DocumentPages
<DocumentPages
selectedDocuments={selectedDocuments}
setDropDownTogglePage={setDropDownTogglePage}
dropDownTogglePage={dropDownTogglePage} />}
dropDownTogglePage={dropDownTogglePage} />
</div>
{/* Pages - END */}
@ -1084,6 +1133,7 @@ const SignDocument = () => {
width={page.width}
height={page.height}
src={page.src}
loading='lazy'
/>
{!hideTags && <>
@ -1127,7 +1177,7 @@ const SignDocument = () => {
{!transparentTags && focusElement != null && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index &&
<div
id="tag-navigation-btn"
className={`absolute right-0 translate-x-[100%] flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
className={`hidden absolute right-0 translate-x-[100%] sm:flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
style={{ top: (focusElement.dimensions.top) + "px" }}
onClick={() => handleTagNavClick("N")}
>
@ -1159,7 +1209,7 @@ const SignDocument = () => {
{(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) &&
<div
onClick={() => scrollToElement(focusElement?.navigationOrder)}
className={`cursor-pointer fixed ${navigationTagPos == NavigationTagPosEnum.ABOVE ? "top-[45px]" : "bottom-[45px]"} right-[8%] flex flex-col items-center`}>
className={`hidden sm:block cursor-pointer fixed ${navigationTagPos == NavigationTagPosEnum.ABOVE ? "top-[45px]" : requiredFilled ? "bottom-[82px]" : "bottom-[35px]"} right-[8%] flex flex-col items-center`}>
{/* Arrow pointing up */}
{navigationTagPos == NavigationTagPosEnum.ABOVE && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)]"></div>}
@ -1214,6 +1264,25 @@ const SignDocument = () => {
{globalLoader.isLoading && <Loader />}
<div
onClick={() => {
setIsPages(true)
setIsFields(false)
}}
className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] flex items-center text-white rounded-tr-md rounded-br-md cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'>
<div className='py-2 pl-2 pr-1'>
<FaAngleUp className='rotate-90' />
<p className='flex flex-col justify-center items-center text-xs tracking-widest mt-1 pt-1 border-t-1 border-white'>
<span>P</span>
<span>A</span>
<span>G</span>
<span>E</span>
<span>S</span>
</p>
</div>
</div>
{
!transparentTags && selectedDocuments != null && !showCompletionScreen && !signerConsent.consent && recipient?.type != "CC" &&
<ConsentScreen
@ -1244,8 +1313,8 @@ const SignDocument = () => {
{!transparentTags && recipient?.type != "CC" && <>
<div className={`fixed bottom-0 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] bottom-0 left-0 bg-white px-4 py-2 flex justify-between items-center gap-4`}>
<p className='text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="text-[var(--mainPrimaryBtn)]">Terms & Conditions.</a></p>
<div className={`fixed bottom-0 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] left-0 bg-white px-4 py-2 flex justify-between items-center gap-4`}>
<p className='text-xs sm:text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="block py-1 sm:py-0 sm:inline text-[var(--mainPrimaryBtn)]">Terms & Conditions.</a></p>
{/* <div className='flex items-center gap-8 cursor-pointer text-xs px-2 py-1 rounded-sm outline-none'>
<FaAngleLeft
onClick={() => handleTagNavClick("P")}
@ -1260,44 +1329,25 @@ const SignDocument = () => {
onClick={() => handleTagNavClick("N")}
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
</div> */}
<div></div>
<div>
{!transparentTags && focusElement != null &&
<div
id="tag-navigation-btn-mb"
className={`sm:hidden flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
style={{ top: (focusElement.dimensions.top) + "px" }}
onClick={() => handleTagNavClick("N")}
>
<p className="bg-[var(--mainPrimaryBtn)] p-2 rounded-md text-white capitalize">{requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()}</p>
{/* NEXT Button - END */}
</div>}
</div>
{/* <p className='cursor-pointer text-sm text-white bg-[var(--mainPrimaryBtn)] px-4 py-2 rounded'
onClick={handleFormSubmit}
>Sign</p> */}
<div>
{/* FIT - START */}
<div
className="hidden sm:block"
tabIndex={1}
onBlur={() => setIsFitDd(false)}
>
<div
onClick={() => setIsFitDd(!isFitDd)}
className='bg-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'>
<p className='tracking-wide'>Fit</p>
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
</div>
{isFitDd && <div className='absolute top-[120px] bg-white rounded-sm z-50'>
{fitObj.map((val, indx) => {
return (<div
key={indx}
onClick={() => setSelFitIndx(indx)}
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
<div
className='flex items-center justify-between w-full'>
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{val.label}</p>
{selFitIndx == indx && <FaCheck className='text-sm text-slate-400' />}
</div>
</div>)
})
}
</div>}
</div>
{/* FIT - END */}
</div>
</div>
</>}

View File

@ -7,6 +7,7 @@ import { IoMdAdd } from 'react-icons/io';
import LoaderUi from '../../helper/LoaderUi';
import logoRecord from '../../assets/record.svg'
import Loader from '../dashboard/Loader';
import ConfirmationModal from '../ConfirmationModal';
type ContactSchema = [{
id: number,
@ -27,6 +28,12 @@ const Contacts = () => {
const [editJsonData, setEditJsonData] = useState({})
const [actionStatus, setActionStatus] = useState("")
// Delete modal
const [showDeleteModal, setShowDeleteModal] = useState<{
show: boolean;
id: string;
}>({ show: false, id: "" });
const fetchContacts = async () => {
setLoading(true)
try {
@ -65,6 +72,7 @@ const Contacts = () => {
// });
fetchContacts()
setOpenMenuId(null);
setShowDeleteModal({ show: false, id: "" });
}
} catch (error) {
console.log(error)
@ -88,9 +96,9 @@ const Contacts = () => {
let res;
setCreateAndInviteLoader(true);
if (type == 'Edit') {
res = await api.put(`/api/v1/accounts/${activeAccountId}/contacts`, {contacts: [data], empty: true})
res = await api.put(`/api/v1/accounts/${activeAccountId}/contacts`, { contacts: [data], empty: true })
} else {
res = await api.post(`/api/v1/accounts/${activeAccountId}/contacts`, {contacts: [data], empty: true});
res = await api.post(`/api/v1/accounts/${activeAccountId}/contacts`, { contacts: [data], empty: true });
}
if (res.status == 200) {
// toast.success('Contact created successfully!', {
@ -177,7 +185,7 @@ const Contacts = () => {
onBlur={handleBlur}
>
<button
className="text-gray-400 hover:text-gray-600 p-1 focus:outline-none"
className="text-gray-400 hover:text-gray-600 p-1 focus:outline-none cursor-pointer"
onClick={() => toggleMenu(contact.id)}
>
<FaEllipsisV />
@ -192,13 +200,16 @@ const Contacts = () => {
>
<button
onMouseDown={() => handleEdit(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600 cursor-pointer"
>
Edit
</button>
<button
onMouseDown={() => handleDelete(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600"
onMouseDown={() => setShowDeleteModal({
show: true,
id: contact.id + "",
})}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600 cursor-pointer"
>
Delete
</button>
@ -224,6 +235,19 @@ const Contacts = () => {
</div>
</div>
{showDeleteModal.show && <ConfirmationModal
setShowConfirmationModal={setShowDeleteModal}
onConfirm={() => handleDelete(showDeleteModal.id)}
message={"Are you sure you want to delete this contact? This action cannot be undone."}
title={"Delete Confirmation."}
textColor={"bg-red-600"}
hoverTextColor={"bg-red-700"}
rejectBtnText={"Cancel"}
confirmBtnText={"Yes, Delete it!"}
/>}
</>
);
};

View File

@ -1,15 +1,11 @@
import { useRef, useState } from 'react';
import { FiUpload } from 'react-icons/fi'
import { handleMultipleTransactionThunk, resetDocuments, uploadDocuments } from '../../store/slices/documentSlice';
import { useDispatch, useSelector } from 'react-redux';
import { toast } from 'react-toastify';
import { useNavigate } from 'react-router-dom';
import { RootState } from '../../store/store';
import api from '../../config/api';
import TagsCon from '../client/TagsCon';
import TransactionChoiceModal from './TransactionChoiceModal';
import { resetRecipients } from '../../store/slices/selectedRecipientsSlice';
import { AiOutlineConsoleSql } from 'react-icons/ai';
const StartNow = () => {

View File

@ -228,6 +228,15 @@ const ProcessStepOne = () => {
order: index,
}));
// ---- Neeed to add documents check tha tif the processed completed only then allow for next step ----
// for (let i = 0; i < documentsData.length; i++) {
// const res = await api.get(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents/${documentsData[i].documentId}`);
// console.log(res)
// }
await api.patch(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents`, { "documents": documentsData });
await api.get(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents?include_pages=true`).then(res => {

View File

@ -419,6 +419,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
useEffect(() => {
if (!hotKeysObj.isActive) return
if((!selectedTag?.key || `${selectedTag?.key}` in droppedItems && droppedItems[selectedTag?.key].length == 0)) return;
const docCon = document.getElementById("doc-con");

View File

@ -203,7 +203,11 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
// onBlur={() => setDropDownTogglePage('')}
>
<div
onClick={() => setDropDownTogglePage(`${(dropDownTogglePage == 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`)}
onClick={() => {
setDropDownTogglePage(`${(dropDownTogglePage == 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`);
setCurrentPage(0);
window.location.hash = document.id + "" + 0;
}}
className={`p-2 ${(dropDownTogglePage == 'pageDropDown' + indx) ? "bg-[var(--primary)]" : "bg-slate-200"} flex flex-col justify-center items-center gap-2 rounded`}>
{
document.pages != undefined && <img

View File

@ -13,6 +13,7 @@ import { RootState } from "../../store/store";
import { LuUserRound } from "react-icons/lu";
import { AiFillSignature, AiOutlineSignature } from "react-icons/ai";
import { FaRegUser } from "react-icons/fa";
import getInitialsUtils from "../../utlity/getInitialUtils";
// Define the shape of state object
interface Account {
@ -247,7 +248,7 @@ const Setting = () => {
const tabs = [
{ id: "userProfile", label: "User Profile", type: "settings" },
// { id: "create and invite", label: "Create and invite", type: "settings" },
{ id: "contacts", label: "Contacts", type: "settings" },
// { id: "contacts", label: "Contacts", type: "settings" },
// { id: "language", label: "Language", type: "settings" },
{ id: "contactAdd", label: "Add Contact", type: "contact" },
{ id: "contactRemove", label: "Remove Contact", type: "contact" },
@ -443,7 +444,7 @@ const Setting = () => {
<div className="border-b-1 border-slate-200 py-4 px-6">
<div className="flex justify-start items-center gap-2">
<div>
<p className="bg-[#FAF0FF] text-[#A915FF] font-semibold max-w-max px-3 py-2 rounded-full ">PK</p>
<p className="bg-[#FAF0FF] text-[#A915FF] font-semibold max-w-max px-3 py-2 rounded-full capitalize">{getInitialsUtils(user.name)}</p>
</div>
<div>
<p className="font-semibold">{user.name}</p>
@ -468,12 +469,12 @@ const Setting = () => {
{/* Sidebar as horizontal nav (only on mobile) */}
{activeTab == 'userProfile' && (
<section className="lg:hidden mb-2">
<div className="flex overflow-x-auto no-scrollbar gap-4 border-b border-gray-200 pb-2">
<div className="flex overflow-x-auto no-scrollbar gap-2 border-b border-gray-200 p-2">
{getSubSections(activeTab).map((sub) => (
<button
key={sub.id}
onClick={() => handleSubSectionClick(sub.id)}
className={`whitespace-nowrap text-sm font-medium px-2 py-1 rounded-md
className={`whitespace-nowrap text-sm font-medium px-2 py-2 rounded-md cursor-pointer
${activeSubSection == sub.id ? "bg-blue-100 text-blue-600" : "text-gray-600"}
`}
>

View File

@ -8,6 +8,7 @@ import { GrCheckboxSelected } from "react-icons/gr";
import { Bounce, toast } from "react-toastify";
import { FiCheck } from 'react-icons/fi';
import { IoMdClose } from 'react-icons/io';
import getInitialsUtils from '../../../utlity/getInitialUtils';
interface Signature {
id: string;
@ -125,7 +126,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
useEffect(() => {
if (editingSignature) {
setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`);
setInitials((`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`).toUpperCase());
setInitials(getInitialsUtils((editingSignature.firstName ?? '') + (editingSignature.firstName && editingSignature.lastName ? ' ' : '') + (editingSignature.lastName ?? '')))
setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD');
// Set font for SELECT tab
@ -222,11 +223,12 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
try {
// Base payload
const nameParts = fullName.trim().split(/\s+/);
const payload = {
signatureType: activeTab,
initials: activeTab == "SELECT" ? initials : "",
firstName: fullName.split(' ').length > 1 ? fullName.split(' ')[0] : "",
lastName: fullName.split(' ').length > 1 ? fullName.split(' ')[1] : fullName.split(' ')[0],
firstName: nameParts[0] || '',
lastName: nameParts.length > 1 ? nameParts.slice(1).join(' ') : nameParts[0] || '',
font: activeTab == "SELECT" ? selectedSignature.id : "",
default: checked
};
@ -317,7 +319,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
value={fullName}
onChange={(e) => {
setFullName(e.target.value)
setInitials(e.target.value.trim().split(' ').length > 1 ? e.target.value.split(' ')[0][0] + "" + e.target.value.split(' ')[1][0] : e.target.value.split(' ')[0][0])
setInitials(getInitialsUtils(e.target.value))
}}
placeholder="Enter full name"
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-md outline-none"
@ -470,7 +472,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
// 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));
setInitials(getInitialsUtils(currUser.firstName + " " + currUser.lastName));
}
} catch (error) {
console.error("Error fetching user details:", error);
@ -519,7 +521,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
<button
key={item.id}
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
className={`flex items-center justify-start space-x-1 px-2 sm:space-x-2 sm:px-3 py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap ${activeTab == item.id
className={`flex items-center justify-start space-x-1 px-2 sm:space-x-2 sm:px-3 py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap cursor-pointer ${activeTab == item.id
? ' text-[var(--primary)]'
: 'text-gray-400'
}`}
@ -569,14 +571,14 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
<div className='flex sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0 px-2 mt-4 mb-2'>
<button
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg 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"
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg 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 cursor-pointer"
onClick={() => setShowAddSignature?.(false)}
disabled={isSubmitting}
>
CANCEL
</button>
<button
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg bg-[var(--mainPrimaryBtn)] 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"
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg bg-[var(--mainPrimaryBtn)] 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 cursor-pointer"
onClick={handleSubmit}
disabled={isSubmitting}
>

View File

@ -1,3 +1,318 @@
// import React, { useRef, useState, useEffect } from 'react';
// import { IoMdClose } from 'react-icons/io';
// type Props = {
// onSignatureChange: (data: { fullSignature: string | null; initials: string | null }) => void;
// initialSignatures?: { fullSignature: string | null; initials: string | null };
// };
// const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignatures }) => {
// const canvasRef1 = useRef<HTMLCanvasElement>(null);
// const canvasRef2 = useRef<HTMLCanvasElement>(null);
// const [isDrawing, setIsDrawing] = useState(false);
// const [isDrawingType, setIsDrawingType] = useState<{ signatureDrawn: boolean, initialDrawn: boolean }>({ signatureDrawn: false, initialDrawn: 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 [initialLoaded, setInitialLoaded] = useState(false);
// // Store initial signature images to avoid reloading
// const [initialImage1, setInitialImage1] = useState<HTMLImageElement | null>(null);
// const [initialImage2, setInitialImage2] = useState<HTMLImageElement | null>(null);
// // Setup canvas dimensions when component mounts
// useEffect(() => {
// const resizeCanvas = (canvas: HTMLCanvasElement | null) => {
// if (canvas) {
// const rect = canvas.getBoundingClientRect();
// if (rect.width > 0 && rect.height > 0) {
// canvas.width = rect.width;
// canvas.height = rect.height;
// }
// }
// };
// const observer1 = new ResizeObserver(() => resizeCanvas(canvasRef1.current));
// const observer2 = new ResizeObserver(() => resizeCanvas(canvasRef2.current));
// if (canvasRef1.current) observer1.observe(canvasRef1.current);
// if (canvasRef2.current) observer2.observe(canvasRef2.current);
// // initial setup
// resizeCanvas(canvasRef1.current);
// resizeCanvas(canvasRef2.current);
// return () => {
// observer1.disconnect();
// observer2.disconnect();
// };
// }, []);
// const loadInitialSignatures = () => {
// if (!initialSignatures) return;
// let loadedCount = 0;
// const totalToLoad = (initialSignatures.fullSignature ? 1 : 0) + (initialSignatures.initials ? 1 : 0);
// const checkComplete = () => {
// loadedCount++;
// if (loadedCount == totalToLoad) {
// setInitialLoaded(true);
// }
// };
// // Load full signature
// if (initialSignatures.fullSignature && canvasRef1.current) {
// const img1 = new Image();
// img1.onload = () => {
// setInitialImage1(img1);
// const ctx1 = canvasRef1.current?.getContext('2d');
// if (ctx1) {
// ctx1.clearRect(0, 0, canvasRef1.current!.width, canvasRef1.current!.height);
// ctx1.drawImage(img1, 0, 0);
// }
// checkComplete();
// };
// img1.src = initialSignatures.fullSignature;
// }
// // Load initials
// if (initialSignatures.initials && canvasRef2.current) {
// const img2 = new Image();
// img2.onload = () => {
// setInitialImage2(img2);
// const ctx2 = canvasRef2.current?.getContext('2d');
// if (ctx2) {
// ctx2.clearRect(0, 0, canvasRef2.current!.width, canvasRef2.current!.height);
// ctx2.drawImage(img2, 0, 0);
// }
// checkComplete();
// };
// img2.src = initialSignatures.initials;
// }
// if (totalToLoad == 0) {
// setInitialLoaded(true);
// }
// };
// 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>, type: "SIGNATURE" | "INITIAL") => {
// if (!isDrawing) return;
// setIsDrawingType((prev) => {
// return { ...prev, signatureDrawn: type == "SIGNATURE" ? true : prev.signatureDrawn, initialDrawn: type == "INITIAL" ? true : prev.initialDrawn }
// })
// const rect = e.currentTarget.getBoundingClientRect();
// const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
// const newPath = [...currentPath, point];
// setCurrentPath(newPath);
// // Draw immediately for smooth experience
// drawCurrentPath(e.currentTarget as HTMLCanvasElement, newPath);
// };
// const drawCurrentPath = (canvas: HTMLCanvasElement, path: Array<{ x: number; y: number }>) => {
// if (!canvas || path.length < 2) return;
// const ctx = canvas.getContext('2d');
// if (!ctx) return;
// // Only draw the current path segment without clearing the canvas
// ctx.strokeStyle = '#000';
// ctx.lineWidth = 2;
// ctx.lineJoin = 'round';
// ctx.lineCap = 'round';
// const lastPoint = path[path.length - 2];
// const currentPoint = path[path.length - 1];
// ctx.beginPath();
// ctx.moveTo(lastPoint.x, lastPoint.y);
// ctx.lineTo(currentPoint.x, currentPoint.y);
// ctx.stroke();
// };
// 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);
// } else {
// const newPaths = [...paths2, currentPath];
// setPaths2(newPaths);
// }
// setCurrentPath([]);
// // Export both canvases and notify parent
// const fullSignature = exportCanvasAsImage(canvasRef1.current);
// const initials = exportCanvasAsImage(canvasRef2.current);
// onSignatureChange({ fullSignature: (isDrawingType.signatureDrawn) ? fullSignature : null, initials: (isDrawingType.initialDrawn) ? initials : null });
// // onSignatureChange({ fullSignature, initials });
// };
// const redrawCanvas = (canvas: HTMLCanvasElement, paths: Array<Array<{ x: number; y: number }>>, initialImage: HTMLImageElement | null) => {
// const ctx = canvas.getContext('2d');
// if (!ctx) return;
// // Clear canvas
// ctx.clearRect(0, 0, canvas.width, canvas.height);
// // Draw initial signature if exists
// if (initialImage) {
// ctx.drawImage(initialImage, 0, 0);
// }
// // Draw all paths
// ctx.strokeStyle = '#000';
// ctx.lineWidth = 2;
// ctx.lineJoin = 'round';
// ctx.lineCap = 'round';
// paths.forEach((path) => {
// if (path.length < 2) return;
// ctx.beginPath();
// ctx.moveTo(path[0].x, path[0].y);
// for (let i = 1; i < path.length; i++) {
// ctx.lineTo(path[i].x, path[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);
// }
// setIsDrawingType({ signatureDrawn: false, initialDrawn: false })
// setPaths1([]);
// setPaths2([]);
// setInitialImage1(null);
// setInitialImage2(null);
// setInitialLoaded(false);
// onSignatureChange({ fullSignature: null, initials: null });
// };
// const undoLastPath = () => {
// if (activeCanvas == 'full' && paths1.length > 0) {
// const newPaths = paths1.slice(0, -1);
// setPaths1(newPaths);
// redrawCanvas(canvasRef1.current!, newPaths, initialImage1);
// } else if (activeCanvas == 'initials' && paths2.length > 0) {
// const newPaths = paths2.slice(0, -1);
// setPaths2(newPaths);
// redrawCanvas(canvasRef2.current!, newPaths, initialImage2);
// }
// // Update parent with new signature data
// setTimeout(() => {
// const fullSignature = exportCanvasAsImage(canvasRef1.current);
// const initials = exportCanvasAsImage(canvasRef2.current);
// onSignatureChange({ fullSignature, initials });
// }, 10);
// };
// return (
// <div className="px-6 py-2 sm:py-4 flex-shrink-0">
// <div className='flex justify-between'>
// {/* <h2 className="text-lg sm:text-xl lg:text-2xl font-semibold text-gray-900 mb-1 sm:mb-2 ">Draw Signature</h2> */}
// <h2 className="mt-4 mb-5 text-sm">Draw your signature. <span className='text-red-600'>*</span> Are required fields.</h2>
// {/* Controls */}
// <div className="flex gap-2 mt-3">
// <div
// onClick={clearCanvas}
// className='flex justify-center items-center gap-1 font-medium'>
// <IoMdClose className='text-red-500' />
// <button
// className="text-xs text-red-500 text-nowrap"
// >
// Clear Signature
// </button>
// </div>
// {/* <button
// className="text-xs"
// onClick={undoLastPath}
// disabled={!activeCanvas || (activeCanvas == 'full' ? paths1.length == 0 : paths2.length == 0)}
// >
// Undo Last
// </button> */}
// </div>
// </div>
// <div className="flex-1 overflow-y-auto">
// <div className="sm:grid sm:grid-cols-12 gap-4 lg:gap-6 overflow-x-auto">
// {/* Full Signature Canvas */}
// <div className="sm:col-span-8">
// <label className="block text-sm font-medium mb-2">Full Name
// <span className='text-red-600'> *</span>
// </label>
// <div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
// <canvas
// ref={canvasRef1}
// className="w-full h-36 sm:h-51 cursor-crosshair"
// onPointerDown={handleMouseDown}
// onPointerMove={(e) => handleMouseMove(e, "SIGNATURE")}
// onPointerUp={() => handleMouseUp()}
// onPointerLeave={() => handleMouseUp()}
// />
// </div>
// </div>
// {/* Initials Canvas */}
// <div className="mt-2 sm:mt-0 sm:col-span-4">
// <label className="block text-sm font-medium mb-2">Initials
// <span className='text-red-600'> *</span>
// </label>
// <div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
// <canvas
// ref={canvasRef2}
// className="w-full h-36 sm:h-51 cursor-crosshair"
// onPointerDown={handleMouseDown}
// onPointerMove={(e) => handleMouseMove(e, "INITIAL")}
// onPointerUp={() => handleMouseUp()}
// onPointerLeave={() => handleMouseUp()}
// />
// </div>
// </div>
// </div>
// </div>
// </div>
// );
// };
// export default DrawSignatureCanvas;
import React, { useRef, useState, useEffect } from 'react';
import { IoMdClose } from 'react-icons/io';
@ -9,26 +324,36 @@ type Props = {
const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignatures }) => {
const canvasRef1 = useRef<HTMLCanvasElement>(null);
const canvasRef2 = useRef<HTMLCanvasElement>(null);
const [isDrawing, setIsDrawing] = useState(false);
const [isDrawingType, setIsDrawingType] = useState<{ signatureDrawn: boolean, initialDrawn: boolean }>({ signatureDrawn: false, initialDrawn: false });
const [isDrawingType, setIsDrawingType] = useState({
signatureDrawn: false,
initialDrawn: false,
});
const [activeCanvas, setActiveCanvas] = useState<'full' | 'initials' | null>(null);
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 [initialLoaded, setInitialLoaded] = useState(false);
const [pointerDown, setPointerDown] = useState(false);
const [pointerId, setPointerId] = useState<number | null>(null);
// Store initial signature images to avoid reloading
const [initialImage1, setInitialImage1] = useState<HTMLImageElement | null>(null);
const [initialImage2, setInitialImage2] = useState<HTMLImageElement | null>(null);
// Setup canvas dimensions when component mounts
// 📏 Resize observer to match canvas with container size
useEffect(() => {
const resizeCanvas = (canvas: HTMLCanvasElement | null) => {
if (canvas) {
const rect = canvas.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
const imgData = canvas.toDataURL(); // save before resize
canvas.width = rect.width;
canvas.height = rect.height;
if (imgData) {
const img = new Image();
img.onload = () => {
canvas.getContext('2d')?.drawImage(img, 0, 0);
};
img.src = imgData;
}
}
}
};
@ -39,7 +364,6 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
if (canvasRef1.current) observer1.observe(canvasRef1.current);
if (canvasRef2.current) observer2.observe(canvasRef2.current);
// initial setup
resizeCanvas(canvasRef1.current);
resizeCanvas(canvasRef2.current);
@ -49,94 +373,31 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
};
}, []);
const loadInitialSignatures = () => {
if (!initialSignatures) return;
let loadedCount = 0;
const totalToLoad = (initialSignatures.fullSignature ? 1 : 0) + (initialSignatures.initials ? 1 : 0);
const checkComplete = () => {
loadedCount++;
if (loadedCount == totalToLoad) {
setInitialLoaded(true);
}
};
// Load full signature
if (initialSignatures.fullSignature && canvasRef1.current) {
const img1 = new Image();
img1.onload = () => {
setInitialImage1(img1);
const ctx1 = canvasRef1.current?.getContext('2d');
if (ctx1) {
ctx1.clearRect(0, 0, canvasRef1.current!.width, canvasRef1.current!.height);
ctx1.drawImage(img1, 0, 0);
}
checkComplete();
};
img1.src = initialSignatures.fullSignature;
}
// Load initials
if (initialSignatures.initials && canvasRef2.current) {
const img2 = new Image();
img2.onload = () => {
setInitialImage2(img2);
const ctx2 = canvasRef2.current?.getContext('2d');
if (ctx2) {
ctx2.clearRect(0, 0, canvasRef2.current!.width, canvasRef2.current!.height);
ctx2.drawImage(img2, 0, 0);
}
checkComplete();
};
img2.src = initialSignatures.initials;
}
if (totalToLoad == 0) {
setInitialLoaded(true);
}
};
const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
setIsDrawing(true);
const getPoint = (e: React.PointerEvent<HTMLCanvasElement>) => {
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');
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
};
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>, type: "SIGNATURE" | "INITIAL") => {
if (!isDrawing) return;
setIsDrawingType((prev) => {
return { ...prev, signatureDrawn: type == "SIGNATURE" ? true : prev.signatureDrawn, initialDrawn: type == "INITIAL" ? true : prev.initialDrawn }
})
const rect = e.currentTarget.getBoundingClientRect();
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
const newPath = [...currentPath, point];
setCurrentPath(newPath);
// Draw immediately for smooth experience
drawCurrentPath(e.currentTarget as HTMLCanvasElement, newPath);
};
const drawCurrentPath = (canvas: HTMLCanvasElement, path: Array<{ x: number; y: number }>) => {
if (!canvas || path.length < 2) return;
const drawSmoothLine = (
canvas: HTMLCanvasElement,
path: Array<{ x: number; y: number }>
) => {
const ctx = canvas.getContext('2d');
if (!ctx) return;
if (!ctx || path.length < 2) return;
// Only draw the current path segment without clearing the canvas
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
const lastPoint = path[path.length - 2];
const currentPoint = path[path.length - 1];
ctx.beginPath();
ctx.moveTo(lastPoint.x, lastPoint.y);
ctx.lineTo(currentPoint.x, currentPoint.y);
ctx.moveTo(path[0].x, path[0].y);
for (let i = 1; i < path.length - 1; i++) {
const midX = (path[i].x + path[i + 1].x) / 2;
const midY = (path[i].y + path[i + 1].y) / 2;
ctx.quadraticCurveTo(path[i].x, path[i].y, midX, midY);
}
ctx.lineTo(path[path.length - 1].x, path[path.length - 1].y);
ctx.stroke();
};
@ -145,158 +406,105 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
return canvas.toDataURL('image/png');
};
const handleMouseUp = () => {
setIsDrawing(false);
if (currentPath.length == 0 || !activeCanvas) return;
if (activeCanvas == 'full') {
const newPaths = [...paths1, currentPath];
setPaths1(newPaths);
} else {
const newPaths = [...paths2, currentPath];
setPaths2(newPaths);
}
setCurrentPath([]);
// Export both canvases and notify parent
const fullSignature = exportCanvasAsImage(canvasRef1.current);
const initials = exportCanvasAsImage(canvasRef2.current);
onSignatureChange({ fullSignature: (isDrawingType.signatureDrawn) ? fullSignature : null, initials: (isDrawingType.initialDrawn) ? initials : null });
// onSignatureChange({ fullSignature, initials });
const handlePointerDown = (e: React.PointerEvent<HTMLCanvasElement>) => {
e.currentTarget.setPointerCapture(e.pointerId);
setPointerDown(true);
setPointerId(e.pointerId);
setCurrentPath([getPoint(e)]);
setActiveCanvas(e.currentTarget === canvasRef1.current ? 'full' : 'initials');
};
const redrawCanvas = (canvas: HTMLCanvasElement, paths: Array<Array<{ x: number; y: number }>>, initialImage: HTMLImageElement | null) => {
const ctx = canvas.getContext('2d');
if (!ctx) return;
const handlePointerMove = (e: React.PointerEvent<HTMLCanvasElement>, type: 'SIGNATURE' | 'INITIAL') => {
if (!pointerDown || pointerId !== e.pointerId) return;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
const newPath = [...currentPath, getPoint(e)];
setCurrentPath(newPath);
// Draw initial signature if exists
if (initialImage) {
ctx.drawImage(initialImage, 0, 0);
setIsDrawingType(prev => ({
...prev,
signatureDrawn: type === 'SIGNATURE' ? true : prev.signatureDrawn,
initialDrawn: type === 'INITIAL' ? true : prev.initialDrawn,
}));
drawSmoothLine(e.currentTarget, newPath);
};
const handlePointerUp = (e: React.PointerEvent<HTMLCanvasElement>) => {
if (!pointerDown || pointerId !== e.pointerId) return;
setPointerDown(false);
setPointerId(null);
if (currentPath.length > 0 && activeCanvas) {
if (activeCanvas === 'full') {
setPaths1(prev => [...prev, currentPath]);
} else {
setPaths2(prev => [...prev, currentPath]);
}
// Draw all paths
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
paths.forEach((path) => {
if (path.length < 2) return;
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
for (let i = 1; i < path.length; i++) {
ctx.lineTo(path[i].x, path[i].y);
}
ctx.stroke();
setCurrentPath([]);
const fullSignature = exportCanvasAsImage(canvasRef1.current);
const initials = exportCanvasAsImage(canvasRef2.current);
onSignatureChange({
fullSignature: isDrawingType.signatureDrawn ? fullSignature : null,
initials: isDrawingType.initialDrawn ? initials : null,
});
};
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);
}
setIsDrawingType({ signatureDrawn: false, initialDrawn: false })
const ctx1 = canvasRef1.current?.getContext('2d');
const ctx2 = canvasRef2.current?.getContext('2d');
if (ctx1 && canvasRef1.current) ctx1.clearRect(0, 0, canvasRef1.current.width, canvasRef1.current.height);
if (ctx2 && canvasRef2.current) ctx2.clearRect(0, 0, canvasRef2.current.width, canvasRef2.current.height);
setPaths1([]);
setPaths2([]);
setInitialImage1(null);
setInitialImage2(null);
setInitialLoaded(false);
setIsDrawingType({ signatureDrawn: false, initialDrawn: false });
onSignatureChange({ fullSignature: null, initials: null });
};
const undoLastPath = () => {
if (activeCanvas == 'full' && paths1.length > 0) {
const newPaths = paths1.slice(0, -1);
setPaths1(newPaths);
redrawCanvas(canvasRef1.current!, newPaths, initialImage1);
} else if (activeCanvas == 'initials' && paths2.length > 0) {
const newPaths = paths2.slice(0, -1);
setPaths2(newPaths);
redrawCanvas(canvasRef2.current!, newPaths, initialImage2);
}
// Update parent with new signature data
setTimeout(() => {
const fullSignature = exportCanvasAsImage(canvasRef1.current);
const initials = exportCanvasAsImage(canvasRef2.current);
onSignatureChange({ fullSignature, initials });
}, 10);
};
return (
<div className="lg:px-6 py-4 flex-shrink-0">
<div className='flex justify-between'>
{/* <h2 className="text-lg sm:text-xl lg:text-2xl font-semibold text-gray-900 mb-1 sm:mb-2 ">Draw Signature</h2> */}
<h2 className="mt-4 mb-5 text-sm">Draw your signature. <span className='text-red-600'>*</span> Are required fields.</h2>
{/* Controls */}
<div className="flex gap-2 mt-3">
<div
onClick={clearCanvas}
className='flex justify-center items-center gap-1 font-medium'>
<IoMdClose className='text-red-500' />
<button
className="text-xs text-red-500"
>
Clear Signature
</button>
</div>
{/* <button
className="text-xs"
onClick={undoLastPath}
disabled={!activeCanvas || (activeCanvas == 'full' ? paths1.length == 0 : paths2.length == 0)}
>
Undo Last
</button> */}
<div className="px-6 py-2 sm:py-4 flex-shrink-0">
<div className="flex justify-between">
<h2 className="mt-4 mb-5 text-sm">
Draw your signature. <span className="text-red-600">*</span> Required fields.
</h2>
<div className="flex gap-2 mt-3 items-center" onClick={clearCanvas}>
<IoMdClose className="text-red-500 cursor-pointer" />
<button className="text-xs text-red-500 text-nowrap cursor-pointer">Clear Signature</button>
</div>
</div>
<div className="flex-1 overflow-y-auto">
<div className="grid grid-cols-12 gap-4 lg:gap-6 overflow-x-auto">
{/* Full Signature Canvas */}
<div className="col-span-8">
<label className="block text-sm font-medium mb-2">Full Name
<span className='text-red-600'> *</span>
<div className="sm:grid sm:grid-cols-12 gap-4 lg:gap-6 overflow-x-auto">
{/* Full Signature */}
<div className="sm:col-span-8">
<label className="block text-sm font-medium mb-2">
Full Name <span className="text-red-600">*</span>
</label>
<div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
<div className="border border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
<canvas
ref={canvasRef1}
style={{ width: "100%", height: "200px" }}
className="w-full h-51 cursor-crosshair"
onPointerDown={handleMouseDown}
onPointerMove={(e) => handleMouseMove(e, "SIGNATURE")}
onPointerUp={() => handleMouseUp()}
onPointerLeave={() => handleMouseUp()}
className="w-full h-36 sm:h-51 cursor-crosshair"
onPointerDown={handlePointerDown}
onPointerMove={e => handlePointerMove(e, 'SIGNATURE')}
onPointerUp={handlePointerUp}
/>
</div>
</div>
{/* Initials Canvas */}
<div className="col-span-4">
<label className="block text-sm font-medium mb-2">Initials
<span className='text-red-600'> *</span>
{/* Initials */}
<div className="mt-2 sm:mt-0 sm:col-span-4">
<label className="block text-sm font-medium mb-2">
Initials <span className="text-red-600">*</span>
</label>
<div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
<div className="border border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
<canvas
ref={canvasRef2}
className="w-full h-51 cursor-crosshair"
onPointerDown={handleMouseDown}
onPointerMove={(e) => handleMouseMove(e, "INITIAL")}
onPointerUp={() => handleMouseUp()}
onPointerLeave={() => handleMouseUp()}
className="w-full h-36 sm:h-51 cursor-crosshair"
onPointerDown={handlePointerDown}
onPointerMove={e => handlePointerMove(e, 'INITIAL')}
onPointerUp={handlePointerUp}
/>
</div>
</div>

View File

@ -4,6 +4,9 @@ import AddSignatureModal from './AddSignatureModal';
import api from '../../../config/api';
import { Bounce, toast } from "react-toastify";
import { FaSpinner } from 'react-icons/fa';
import getInitialsUtils from '../../../utlity/getInitialUtils';
import DeleteModal from '../../ConfirmationModal';
import ConfirmationModal from '../../ConfirmationModal';
interface Signature {
id: string;
@ -30,6 +33,12 @@ const SignatureManager: React.FC = () => {
const [showAddSignature, setShowAddSignature] = useState<boolean>(false);
const [editingSignature, setEditingSignature] = useState<Signature | null>(null);
// Delete modal
const [showDeleteModal, setShowDeleteModal] = useState<{
show: boolean;
id: string;
}>({ show: false, id: "" });
const signatureOptions = [
{ id: 1, name: 'print', initials: 'LA', style: 'print' },
{ id: 2, name: 'cursive', initials: 'LA', style: 'cursive' },
@ -71,17 +80,8 @@ const SignatureManager: React.FC = () => {
setLoading(true)
try {
let res = await api.delete(`/api/v1/user/signatures/${id}`)
// toast.success("Signature Delete Successfull", {
// position: "top-center",
// autoClose: 500,
// hideProgressBar: false,
// closeOnClick: false,
// pauseOnHover: true,
// draggable: true,
// progress: undefined,
// theme: "light",
// transition: Bounce,
// });
setShowDeleteModal({ show: false, id: "" });
await getAllSignatures();
} catch (error) {
console.log("delete signature", error)
@ -113,7 +113,7 @@ const SignatureManager: React.FC = () => {
const fontClass = `${selectedFont?.style || 'print'}`;
if (mode == 'initials') {
const initials = `${(firstName?.[0] ?? '')}${(lastName?.[0] ?? '')}`.toUpperCase();
const initials = getInitialsUtils((firstName ?? '') + (firstName && lastName ? ' ' : '') + (lastName ?? ''));
return (
<div className={`text-center text-[24px]`} style={{ fontFamily: selectedFont?.style }}>
{initials}
@ -243,13 +243,18 @@ const SignatureManager: React.FC = () => {
<div className="relative flex justify-start gap-4">
<button
onClick={() => handleEdit(signature.id)}
className="max-w-max text-gray-500 text-lg"
className="max-w-max text-gray-500 text-lg cursor-pointer"
>
<FiEdit3 />
</button>
<button
onClick={() => handleDelete(signature.id)}
className="max-w-max text-red-500 text-lg"
onClick={() =>
setShowDeleteModal({
show: true,
id: signature.id + "",
})
}
className="max-w-max text-red-500 text-lg cursor-pointer"
>
<FiTrash2 />
</button>
@ -290,7 +295,21 @@ const SignatureManager: React.FC = () => {
/>
)}
</div>
}</>
}
{showDeleteModal.show && <ConfirmationModal
setShowConfirmationModal={setShowDeleteModal}
onConfirm={() => handleDelete(showDeleteModal.id)}
message={"Are you sure you want to delete this signature? This action cannot be undone."}
title={"Delete Confirmation."}
textColor={"bg-red-600"}
hoverTextColor={"bg-red-700"}
rejectBtnText={"Cancel"}
confirmBtnText={"Yes, Delete it!"}
/>}
</>
);
};

View File

@ -30,6 +30,7 @@ import { SaveType, SaveTypeEnum } from '../client/SignDocument';
import { CiSaveDown1 } from 'react-icons/ci';
import api from '../../config/api';
import { IoMdClose } from 'react-icons/io';
import getInitialsUtils from '../../utlity/getInitialUtils';
interface Signature {
id: string;
@ -244,12 +245,13 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
}
try {
const nameParts = fullName.trim().split(/\s+/);
const payload = {
tagId: signTagId,
// signatureId: signatureId,
type: activeTab,
firstName: fullName.split(' ').length > 1 ? fullName.split(' ')[0] : "",
lastName: fullName.split(' ').length > 1 ? fullName.split(' ')[1] : fullName.split(' ')[0],
firstName: nameParts[0] || '',
lastName: nameParts.length > 1 ? nameParts.slice(1).join(' ') : nameParts[0] || '',
initials: initials,
initialsBase64: signatures.initials,
signatureBase64: signatures.fullSignature,
@ -356,7 +358,7 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
value={fullName}
onChange={(e) => {
setFullName(e.target.value)
setInitials(e.target.value.trim().split(' ').length > 1 ? e.target.value.split(' ')[0][0]+""+e.target.value.split(' ')[1][0] : e.target.value.split(' ')[0][0])
setInitials(getInitialsUtils(e.target.value))
}}
placeholder="Enter full name"
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-md outline-none"
@ -417,9 +419,9 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
case 'UPLOAD':
return (
<div className="h-full flex flex-col">
<div className="h-full flex flex-col px-6">
<div className="mt-4 mb-5 text-sm">
<h2 className="text-sm font-semibold text-gray-900 mb-1">Upload your Signature and confirm your name * Are required fields.</h2>
<h2 className="text-sm text-gray-900 mb-1">Upload your Signature and confirm your name * Are required fields.</h2>
<p className="text-xs md:text-sm text-gray-400 hidden md:block mt-2">For best results, use an image that is 400 x 150 pixels for signature and 150 x 150 for initials</p>
<p className="text-xs md:text-sm text-gray-400 hidden md:block">The maximum file size for the image is 300kb. File types supported include .png, .jpg, .jpeg</p>
</div>
@ -586,7 +588,7 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
// let currUser = res?.data;
setUser(recipient);
setFullName(recipient.firstName + " " + recipient.lastName);
setInitials((recipient.firstName.slice(0, 1) + recipient.lastName.slice(0, 1)).toUpperCase());
setInitials(getInitialsUtils((recipient.firstName ?? '') + (recipient.firstName && recipient.lastName ? ' ' : '') + (recipient.lastName ?? '')));
} catch (error) {
console.error("Error fetching user details:", error);
}
@ -597,8 +599,8 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
}, []);
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 max-w-[80%] rounded-2xl shadow-xl w-full relative overflow-hidden ">
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 bg-black/30 flex items-center justify-center z-50 p-0 sm:p-2 md:p-4">
<div className="bg-white sm:max-w-[80%] max-w-[100%] h-[100dvh] sm:h-auto sm:rounded-2xl shadow-xl w-full relative overflow-hidden ">
{/* Main Content Area */}
<div className="flex-1 md:flex flex-col min-h-0">
@ -608,9 +610,9 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
{/* Navigation Menu - START */}
<div className="px-6 flex-shrink-0 border-b-2 border-gray-200">
<div className="px-1 sm:px-6 flex-shrink-0 border-b-2 border-gray-200">
<div className='pt-6 pb-4 relative '>
<div className='px-6 sm:px-0 pt-6 pb-4 relative '>
<div className='flex justify-between'>
<p className='text-2xl font-semibold'>Signature Style Selection</p>
<IoMdClose
@ -620,14 +622,17 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
<p className='text-gray-500'>Create your digital signatures</p>
</div>
<nav className="flex gap-4">
{navigationItems.map((item) => (
<nav className="flex gap-0 sm:gap-4">
{navigationItems.map((item) => {
if (item.label === 'SAVED' && preSaveSignatures.length === 0) return null;
return (
<>
<div className='relative'>
<button
key={item.id}
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
className={`flex items-center justify-start space-x-2 px-3 py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap ${activeTab == item.id
className={`flex items-center justify-start space-x-2 px-2.5 sm:px-3 py-2 sm:py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap cursor-pointer ${activeTab == item.id
? ' text-[var(--primary)]'
: 'text-gray-400'
}`}
@ -640,7 +645,12 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
{activeTab == item.id && <div className='absolute bottom-[-1px] w-full h-[3px] bg-[var(--mainPrimaryBtn)]'></div>}
</div>
</>
))}
)
}
)}
</nav>
</div>
@ -652,18 +662,18 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
</div>
{/* Footer Buttons - Fixed */}
<div className="px-4 py-3 flex sm:flex-row justify-between items-center gap-2 md:gap-3 flex-shrink-0">
<div className="absolute bottom-0 w-full sm:relative px-6 py-3 flex flex-col sm:flex-row justify-center sm:justify-between items-start sm:items-center gap-2 md:gap-3 flex-shrink-0">
<p className='text-sm'>* I understand that I am signing this document.</p>
<div className='flex sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0 px-2 mt-4 mb-2'>
<div className='flex w-full sm:max-w-max sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0 sm:px-2 sm:mt-4 mb-2'>
<button
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg 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"
className="px-4 py-3 sm:py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg 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 cursor-pointer"
onClick={() => setOpenSign?.(false)}
disabled={isSubmitting}
>
CANCEL
</button>
<button
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg bg-[var(--mainPrimaryBtn)] 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"
className="cursor-pointer px-4 py-3 sm:py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg bg-[var(--mainPrimaryBtn)] 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}
>
@ -673,7 +683,7 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
<span>CREATING...</span>
</>
) : (
<span>CREATE SIGNATURE</span>
<span>Sign</span>
)}
</button>
</div>

View File

@ -22,7 +22,7 @@ const TemplateDeleteWarnModal = ({ setShowTemplateDeleteWarnModal, onConfirm }:
setShowTemplateDeleteWarnModal({show: false, templateId: ""});
}}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md transition-colors cursor-pointer"
>
No
</button>
@ -30,7 +30,7 @@ const TemplateDeleteWarnModal = ({ setShowTemplateDeleteWarnModal, onConfirm }:
onClick={() => {
onConfirm();
}}
className="px-4 py-2 bg-red-600 text-white hover:bg-red-700 rounded-md transition-colors"
className="px-4 py-2 bg-red-600 text-white hover:bg-red-700 rounded-md transition-colors cursor-pointer"
>
Yes
</button>

View File

@ -9,6 +9,7 @@ import { resetRecipients } from "../../store/slices/selectedRecipientsSlice";
import { resetDocuments } from "../../store/slices/documentSlice";
import { AppDispatch } from "../../store/store";
import { useDispatch } from "react-redux";
import { showLoader } from "../../store/slices/loaderSlice";
interface Template {
id: number;
@ -80,6 +81,7 @@ const TemplateTable = () => {
const handleUseClick = async (item: Template) => {
try {
await dispatch(showLoader("Creating Transaction..."))
await dispatch(resetRecipients())
await dispatch(resetDocuments())
const payload = {
@ -97,6 +99,7 @@ const TemplateTable = () => {
};
const handleEditClick = (templateId: string) => {
dispatch(showLoader("Loading..."))
setShowMenu({ open: false, templateId: null });
navigate(`/setup-transaction/${activeAccountId}/template/${templateId}`);
};
@ -181,7 +184,7 @@ const TemplateTable = () => {
templateId: template.id,
})
}
className="text-gray-500 hover:text-gray-700 p-1 rounded-md hover:bg-gray-100"
className="text-gray-500 hover:text-gray-700 p-1 rounded-md hover:bg-gray-100 cursor-pointer"
>
<PiDotsThreeOutlineVerticalFill className="text-lg" />
</button>
@ -190,13 +193,13 @@ const TemplateTable = () => {
<div className="absolute right-0 mt-2 w-40 bg-white border border-gray-200 rounded-lg shadow-lg z-20 animate-fade-in">
<button
onClick={() => handleUseClick(template)}
className="w-full text-left px-4 py-2 text-sm hover:bg-blue-50 hover:text-blue-600"
className="w-full text-left px-4 py-2 text-sm hover:bg-blue-50 hover:text-blue-600 cursor-pointer"
>
Use
</button>
<button
onClick={() => handleEditClick(template.id + "")}
className="w-full text-left px-4 py-2 text-sm hover:bg-yellow-50 hover:text-yellow-600"
className="w-full text-left px-4 py-2 text-sm hover:bg-yellow-50 hover:text-yellow-600 cursor-pointer"
>
Edit
</button>
@ -207,7 +210,7 @@ const TemplateTable = () => {
templateId: template.id + "",
})
}
className="w-full text-left px-4 py-2 text-sm hover:bg-red-50 hover:text-red-600"
className="w-full text-left px-4 py-2 text-sm hover:bg-red-50 hover:text-red-600 cursor-pointer"
>
Delete
</button>

View File

@ -54,7 +54,7 @@ const Templates = () => {
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-3 px-4">
{/* Filter as pill buttons */}
<div className="flex flex-wrap gap-2">
{filters.map((filter) => (
{/* {filters.map((filter) => (
<button
key={filter}
onClick={() => setActiveFilter(filter)}
@ -66,7 +66,7 @@ const Templates = () => {
>
{filter}
</button>
))}
))} */}
</div>
{/* Add Template Button */}
@ -75,7 +75,7 @@ const Templates = () => {
className="flex items-center justify-center gap-1 bg-gradient-to-br from-[var(--primary)] to-blue-600 px-3 py-2 rounded-sm cursor-pointer hover:opacity-90"
>
<IoMdAdd className="text-white text-lg" />
<button className="text-white text-sm font-medium">
<button className="text-white text-sm font-medium cursor-pointer">
Add Template
</button>
</div>

View File

@ -0,0 +1,12 @@
function getInitialsUtils(name) {
if (!name || typeof name !== 'string') return '';
return name
.trim()
.split(/\s+/) // split by spaces (handles multiple spaces)
.filter(word => word.length) // remove empty strings
.map(word => word[0]) // take first letter of each word
.join('');
}
export default getInitialsUtils;