27-10
This commit is contained in:
parent
950601171c
commit
ca1a4da954
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -294,7 +295,7 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
|
|||
<div className="w-8 h-8 rounded-full overflow-hidden bg-gradient-to-br from-green-500 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">
|
||||
{(user?.name?.slice(0, 2)).toUpperCase()}
|
||||
{(user?.name?.slice(0, 2)).toUpperCase()}
|
||||
</span>
|
||||
) : (
|
||||
<FaUser className="text-lg" />
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ const DownloadDocument: React.FC<DownloadDocumentInt> = ({ showMenu, setShowMenu
|
|||
<input type='radio' className='cursor-pointer' name='download-doc-type' value={DownloadTypesEnum.CERTIFICATE} />
|
||||
<span>Certificate</span>
|
||||
</label>
|
||||
|
||||
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='checkbox' className='cursor-pointer' name='download-doc-type-include-cert' value='?include_certificate=true' />
|
||||
<span>Include Certificate</span>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,8 +876,42 @@ 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)]'>
|
||||
<img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
|
||||
<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>
|
||||
</>}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
@ -186,19 +194,22 @@ const Contacts = () => {
|
|||
{openMenuId == contact.id && (
|
||||
<div
|
||||
className={`absolute mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-20 min-w-[100px] ${contacts.length - 1 == index
|
||||
? "bottom-1 right-4"
|
||||
: "bottom-0 right-5"
|
||||
? "bottom-1 right-4"
|
||||
: "bottom-0 right-5"
|
||||
}`}
|
||||
>
|
||||
<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!"}
|
||||
/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}));
|
||||
|
||||
// Draw all paths
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.lineCap = 'round';
|
||||
drawSmoothLine(e.currentTarget, newPath);
|
||||
};
|
||||
|
||||
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);
|
||||
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]);
|
||||
}
|
||||
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>
|
||||
|
|
@ -306,4 +514,4 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
|||
);
|
||||
};
|
||||
|
||||
export default DrawSignatureCanvas;
|
||||
export default DrawSignatureCanvas;
|
||||
|
|
|
|||
|
|
@ -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!"}
|
||||
/>}
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,27 +622,35 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
|||
<p className='text-gray-500'>Create your digital signatures</p>
|
||||
</div>
|
||||
|
||||
<nav className="flex gap-4">
|
||||
{navigationItems.map((item) => (
|
||||
<>
|
||||
<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
|
||||
? ' text-[var(--primary)]'
|
||||
: 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
<span className={activeTab == item.id ? 'text-[var(--mainPrimaryBtn)]' : 'text-gray-400'}>
|
||||
{item.icon}
|
||||
</span>
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
{activeTab == item.id && <div className='absolute bottom-[-1px] w-full h-[3px] bg-[var(--mainPrimaryBtn)]'></div>}
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
<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-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'
|
||||
}`}
|
||||
>
|
||||
<span className={activeTab == item.id ? 'text-[var(--mainPrimaryBtn)]' : 'text-gray-400'}>
|
||||
{item.icon}
|
||||
</span>
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
{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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue