Worked on Responsiveness + Tag Properties + Hot Keys etc

This commit is contained in:
Bhagwan Singh 2026-05-28 18:08:23 +05:30
parent 2290eca277
commit 0a2e823db0
25 changed files with 1831 additions and 948 deletions

View File

@ -451,7 +451,7 @@ const CommonNav: React.FC<AccountNavProps> = ({
{/* <FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} /> */} {/* <FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} /> */}
</button> </button>
</div> </div>
<div className="border-b border-gray-100"> {/* <div className="border-b border-gray-100">
<button <button
onClick={handleAccountsClick} onClick={handleAccountsClick}
className="w-full flex items-center justify-between px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer" className="w-full flex items-center justify-between px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer"
@ -506,7 +506,7 @@ const CommonNav: React.FC<AccountNavProps> = ({
)} )}
</div> </div>
)} )}
</div> </div> */}
<div className="border-t border-gray-100"> <div className="border-t border-gray-100">
<button <button

View File

@ -66,7 +66,7 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
<span className="text-md font-semibold text-[var(--mainPrimaryBtn)]"> <span className="text-md font-semibold text-[var(--mainPrimaryBtn)]">
Sign made easy Sign made easy
</span> */} </span> */}
<img class="py-2 w-[140px]" alt="" src="/sme-colored.png" /> <img className="py-2 w-[140px]" alt="" src="/sme-colored.png" />
</div> </div>
</header> </header>
@ -118,49 +118,77 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
<h2 className="text-xl font-semibold text-gray-800 mb-4 2xl:text-2xl"> <h2 className="text-xl font-semibold text-gray-800 mb-4 2xl:text-2xl">
Boost Efficiency with Our Apps Boost Efficiency with Our Apps
</h2> </h2>
<div className="w-full flex flex-col sm:flex-row gap-2 justify-center items-center 2xl:gap-4 2xl:mt-6">
{/* Forms Made Easy */} <div className="w-full flex flex-col sm:flex-row gap-4 justify-center items-stretch 2xl:gap-6 2xl:mt-6">
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
<div className="">
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
Generate Forms with Ease
</p>
<div className="w-full">
<div className="w-full flex flex-col justify-start items-start sm:flex-row sm:justify-between sm:items-center">
<img src="/forms-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
<button
onClick={() => window.open("https://formsmadeasy.com/", "_blank")}
className="cursor-pointer text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
Try Now
</button>
</div>
</div>
</div>
</div> {/* Forms Made Easy */}
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
<p className="text-sm font-semibold text-left mb-4 2xl:text-lg min-w-min">
Generate Forms with Ease
</p>
{/* Docs Made Easy */} <div className="w-full flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
<div className="bg-white rounded-lg shadow p-4 2xl:p-6"> <img
<div className=""> src="/forms-made-easy.png"
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg"> className="w-[140px] sm:w-[40%] 2xl:w-[40%] object-contain"
Generate Documents (Invoices, Reports, Quotes) alt="Forms Made Easy"
</p> />
<div className="w-full">
<div className="w-full flex flex-col justify-start items-start sm:flex-row sm:justify-between sm:items-center">
<img src="/docs-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
<button
onClick={() => window.open("https://docsmadeasy.com/", "_blank")}
className="cursor-pointer text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
Try Now
</button>
</div>
</div>
</div>
</div> <button
onClick={() => window.open("https://formsmadeasy.com/", "_blank")}
className="
cursor-pointer
text-xs sm:text-sm md:text-base
bg-blue-600
text-white
px-5 py-2
rounded-md
hover:bg-blue-700
transition
shrink-0
w-full sm:w-auto
"
>
Try Now
</button>
</div>
</div>
{/* Docs Made Easy */}
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
<p className="text-sm font-semibold text-left mb-4 2xl:text-lg">
Generate Documents (Invoices, Reports, Quotes)
</p>
<div className="w-full flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
<img
src="/docs-made-easy.png"
className="w-[140px] sm:w-[40%] 2xl:w-[40%] object-contain"
alt="Docs Made Easy"
/>
<button
onClick={() => window.open("https://docsmadeasy.com/", "_blank")}
className="
cursor-pointer
text-xs sm:text-sm md:text-base
bg-blue-600
text-white
px-5 py-2
rounded-md
hover:bg-blue-700
transition
shrink-0
w-full sm:w-auto
"
>
Try Now
</button>
</div>
</div>
</div>
</div>
</section> </section>
</main> </main>

View File

@ -0,0 +1,124 @@
// ConfirmSignModal.tsx
import React, { useEffect } from "react";
import { IoClose } from "react-icons/io5";
import { FaSignature } from "react-icons/fa";
import { FiShield } from "react-icons/fi";
interface ConfirmSignModalProps {
open: boolean;
onClose: () => void;
onConfirm: () => void;
loading?: boolean;
recipientName?: string;
documentCount?: number;
}
const ConfirmSignModal = ({
open,
onClose,
onConfirm,
loading = false,
recipientName = "",
documentCount = 1,
}: ConfirmSignModalProps) => {
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
if (open) document.addEventListener("keydown", handleEsc);
return () => document.removeEventListener("keydown", handleEsc);
}, [open]);
useEffect(() => {
document.body.style.overflow = open ? "hidden" : "auto";
return () => { document.body.style.overflow = "auto"; };
}, [open]);
if (!open) return null;
return (
<div
onClick={onClose}
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm p-3 sm:p-4"
>
<div
onClick={(e) => e.stopPropagation()}
className="w-full max-w-md flex flex-col rounded-sm bg-white shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200"
>
{/* Header */}
<div className="flex items-start justify-between border-b border-slate-200 p-5 sm:p-6">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-10 h-10 rounded-sm bg-[var(--primary)] shrink-0">
<FaSignature className="text-white text-lg" />
</div>
<div>
<h2 className="text-xl font-semibold text-slate-800">
Confirm Signature
</h2>
<p className="mt-0.5 text-sm text-slate-500">
You're about to sign this document
</p>
</div>
</div>
<button
onClick={onClose}
className="shrink-0 p-2 rounded-sm hover:bg-slate-100 transition cursor-pointer"
>
<IoClose className="text-xl text-slate-500" />
</button>
</div>
{/* Body */}
<div className="p-5 sm:p-6 space-y-4">
{/* Summary */}
<div className="rounded-sm border border-slate-200 bg-slate-50 px-4 py-4 space-y-2">
{recipientName && (
<div className="flex justify-between text-sm">
<span className="text-slate-500">Signing as</span>
<span className="font-medium text-slate-800">{recipientName}</span>
</div>
)}
<div className="flex justify-between text-sm">
<span className="text-slate-500">Documents</span>
<span className="font-medium text-slate-800">{documentCount} document{documentCount > 1 ? "s" : ""}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-slate-500">Status</span>
<span className="font-medium text-green-600">Ready to sign</span>
</div>
</div>
{/* Legal notice */}
<div className="flex gap-3 rounded-sm border border-indigo-100 bg-indigo-50 px-4 py-3">
<FiShield className="text-indigo-600 shrink-0 mt-0.5" />
<p className="text-sm leading-relaxed text-indigo-900">
By confirming, you agree that your electronic signature is legally binding and equivalent to a handwritten signature.
</p>
</div>
</div>
{/* Footer */}
<div className="flex flex-col-reverse sm:flex-row items-stretch sm:items-center justify-end gap-3 border-t border-slate-200 bg-white p-4 sm:p-5">
<button
onClick={onClose}
className="w-full sm:w-auto text-sm px-4 py-2.5 rounded-sm border border-slate-300 text-slate-700 hover:bg-slate-100 transition cursor-pointer font-medium"
>
Review
</button>
<button
onClick={onConfirm}
disabled={loading}
className="w-full sm:w-auto text-sm px-5 py-2.5 rounded-sm bg-[var(--primary)] text-white hover:opacity-100 transition opacity-80 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer font-medium flex items-center justify-center gap-2"
>
<FaSignature />
{loading ? "Signing..." : "Confirm & Sign"}
</button>
</div>
</div>
</div>
);
};
export default ConfirmSignModal;

View File

@ -65,7 +65,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
return ( return (
<div className="flex flex-col w-full px-2 gap-2 pb-[6rem] bg-slate-100"> <div className="flex flex-col w-full px-2 gap-2 pb-[6rem] bg-slate-100 border-r-1 border-slate-200 h-full">
{selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((document, indx) => { {selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((document, indx) => {
const isOpen = dropDownTogglePage === 'pageDropDown' + indx; const isOpen = dropDownTogglePage === 'pageDropDown' + indx;
@ -92,59 +92,59 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
</div> </div>
{/* Page Thumbnails - DocuSign style */} {/* Page Thumbnails - DocuSign style */}
{isOpen && (
<div className="flex flex-col px-2">
{document.pages?.map((page, pageIndex) => {
const key = `${document.documentId}_${pageIndex}`;
const isActive = activePage === key;
return ( <div className={`flex flex-col px-2 ${isOpen ? 'block' : 'hidden'}`}>
<div {document.pages?.map((page, pageIndex) => {
key={pageIndex} const key = `${document.documentId}_${pageIndex}`;
onClick={() => goToPage(document.documentId, pageIndex)} const isActive = activePage === key;
className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
> return (
{/* Big Thumbnail */} <div
<div className={`relative sm:p-2 lg:p-4 bg-slate-100 rounded border flex items-center justify-center overflow-hidden key={pageIndex}
onClick={() => goToPage(document.documentId, pageIndex)}
className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
>
{/* Big Thumbnail */}
<div className={`relative sm:p-2 lg:p-4 bg-slate-100 rounded border flex items-center justify-center overflow-hidden
${isActive ? 'border-blue-500 border-2' : 'border-slate-200'}`} ${isActive ? 'border-blue-500 border-2' : 'border-slate-200'}`}
> >
<img <img
src={page.src} src={page.src}
alt={`Page ${pageIndex + 1}`} alt={`Page ${pageIndex + 1}`}
className="w-[140px] h-[180px] h-full object-cover" className="w-[140px] h-[180px] h-full object-cover"
draggable="false" draggable="false"
/> />
{/* Page number + dots */} {/* Page number + dots */}
<div className="absolute bottom-0 bg-slate-800 flex items-center justify-between w-full pl-4 pr-2 py-3 bg-white"> <div className="absolute bottom-0 bg-slate-800 flex items-center justify-between w-full pl-4 pr-2 py-3 bg-white">
<span className="text-[13px] text-slate-500">{pageIndex + 1}</span> <span className="text-[13px] text-slate-500">{pageIndex + 1}</span>
{ {
// <> // <>
// <button // <button
// onClick={(e) => { e.stopPropagation(); /* open context menu */ }} // onClick={(e) => { e.stopPropagation(); /* open context menu */ }}
// className="text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded p-0.5" // className="text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded p-0.5"
// > // >
// <MdMoreVert size={18} /> // <MdMoreVert size={18} />
// </button> // </button>
// </> // </>
} }
</div> </div>
{documentPagesTagsMapping[document.documentId]?.[pageIndex] && ( {documentPagesTagsMapping[document.documentId]?.[pageIndex] && (
<div className='absolute top-4 right-2 rotate-180'> <div className='absolute top-4 right-2 rotate-180'>
{/* <span className='absolute text-white text-[10px] font-medium text-slate-500 top-[50%] left-[50%] transform -translate-x-1/2 -translate-y-1/2 pointer-events-none select-none rotate-180'> {/* <span className='absolute text-white text-[10px] font-medium text-slate-500 top-[50%] left-[50%] transform -translate-x-1/2 -translate-y-1/2 pointer-events-none select-none rotate-180'>
{documentPagesTagsMapping[document.documentId][pageIndex][0]} {documentPagesTagsMapping[document.documentId][pageIndex][0]}
</span> */} </span> */}
<PiTagSimpleFill style={{ fontSize: '1.8rem', color: documentPagesTagsMapping[document.documentId][pageIndex][1] }} /> <PiTagSimpleFill style={{ fontSize: '1.8rem', color: documentPagesTagsMapping[document.documentId][pageIndex][1] }} />
</div> </div>
)} )}
</div>
</div> </div>
) </div>
})} )
</div> })}
)} </div>
</div> </div>
) )

View File

@ -58,16 +58,15 @@ export default function SignButton({
{/* Sign Button */} {/* Sign Button */}
{/* <button {/* <button
onClick={handleFormSubmit} onClick={handleFormSubmit}
className="flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-md bg-[var(--primary)] hover:opacity-90 transition text-white outline-none" className="flex items-center gap-2 cursor-pointer text-xs px-4 py-1.5 ml-3 rounded-sm border border-slate-300 hover:border-slate-100 hover:opacity-90 transition text-white font-normal outline-none"
> >
<span id="sign-client-side-btn" className="tracking-wide"> <span id="sign-client-side-btn" className="tracking-wide">
Sign Finish
</span> </span>
<FaSignature className="text-md" />
</button> */} </button> */}
{/* Three Dots Dropdown Trigger */}
{/* Three Dots Dropdown Trigger */}
<button <button
onClick={() => setMenuOpen((prev) => !prev)} onClick={() => setMenuOpen((prev) => !prev)}
className="py-2 rounded-full focus:outline-none transition ml-2" className="py-2 rounded-full focus:outline-none transition ml-2"

File diff suppressed because it is too large Load Diff

View File

@ -18,4 +18,21 @@
font-family: 'Allura'; font-family: 'Allura';
src: url('/fonts/Allura/Allura-Regular.ttf') format('truetype'); src: url('/fonts/Allura/Allura-Regular.ttf') format('truetype');
} }
/* Adding SELECT SIGNATURE FONTS - ENDS */ /* Adding SELECT SIGNATURE FONTS - ENDS */
/* .scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
} */
body::-webkit-scrollbar {
display: none;
}

View File

@ -1,4 +1,4 @@
import { useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { handleMultipleTransactionThunk, resetDocuments, uploadDocuments } from '../../store/slices/documentSlice'; import { handleMultipleTransactionThunk, resetDocuments, uploadDocuments } from '../../store/slices/documentSlice';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -30,6 +30,24 @@ const StartNow = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const [uploadDropdownOpen, setUploadDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setUploadDropdownOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchstart', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchstart', handleClickOutside);
};
}, []);
const handleUpload = async (filesToUpload: File[] = files) => { const handleUpload = async (filesToUpload: File[] = files) => {
const accountId = userData.account?.id; const accountId = userData.account?.id;
@ -176,62 +194,44 @@ const StartNow = () => {
</button> */} </button> */}
<div className="relative inline-block group mt-4 mb-6"> {/* ❌ Remove the old "relative inline-block group" div entirely, replace with: */}
<div ref={dropdownRef} className="relative inline-block mt-4 mb-6">
<button <button
className="flex text-lg font-medium items-center gap-1 text-xs text-white bg-[var(--dashBoardStartNowBtn)] pl-3 pr-2 py-2 rounded shadow-xs transition-colors duration-150 focus:outline-none focus:ring-1 focus:ring-blue-400 cursor-pointer" onClick={() => setUploadDropdownOpen(prev => !prev)}
className="flex text-lg font-medium items-center gap-1 text-xs text-white bg-[var(--dashBoardStartNowBtn)] pl-3 pr-2 py-2 rounded shadow-xs focus:outline-none focus:ring-1 focus:ring-blue-400 cursor-pointer"
> >
<p>Upload File</p> <p>Upload File</p>
<IoMdArrowDropdown className='text-lg' /> <IoMdArrowDropdown
className='text-lg'
style={{ transform: uploadDropdownOpen ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}
/>
</button> </button>
{/* Dropdown */} {uploadDropdownOpen && (
<div <div className="absolute left-0 top-full mt-1 min-w-[180px] bg-white border border-slate-200 rounded-md shadow-lg overflow-hidden z-50">
className=" <button
absolute onClick={() => {
left-0 fileInputRef.current?.click();
top-full setUploadDropdownOpen(false);
mt-1 }}
min-w-[180px] className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 cursor-pointer flex gap-2 items-center"
bg-white >
border <FaDesktop className='text-[var(--primary)]' />
border-slate-200 <span>Desktop</span>
rounded-md </button>
shadow-lg
overflow-hidden
z-50
opacity-0 <button
invisible onClick={() => {
translate-y-[-5px] navigate("/templates");
scale-[0.98] setUploadDropdownOpen(false);
}}
transition-all className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 border-t border-slate-100 cursor-pointer flex gap-2 items-center"
duration-200 >
ease-out <HiTemplate className='text-[var(--primary)]' />
<span>Use a Template</span>
group-hover:opacity-100 </button>
group-hover:visible </div>
group-hover:translate-y-0 )}
group-hover:scale-100
"
>
<button
onClick={() => fileInputRef.current?.click()}
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 cursor-pointer flex gap-2 items-center"
>
<FaDesktop className='text-[var(--primary)]' />
<span>Desktop</span>
</button>
<button
onClick={() => navigate("/templates")}
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 border-t border-slate-100 cursor-pointer flex gap-2 items-center"
>
<HiTemplate className='text-[var(--primary)]' />
<span>Use a Template</span>
</button>
</div>
</div> </div>
{filesError && <span className="text-red-500 text-xs">{filesError}</span>} {filesError && <span className="text-red-500 text-xs">{filesError}</span>}

View File

@ -11,7 +11,7 @@ export default function TransactionChoiceModal({ open, onClose, onCreateTransact
if (!open) return null; if (!open) return null;
return ( return (
<div className="fixed inset-0 justify-center z-50"> <div className="fixed inset-0 justify-center z-100">
<div className="w-full h-full bg-gray-200 absolute top-0 left-0 opacity-55"></div> <div className="w-full h-full bg-gray-200 absolute top-0 left-0 opacity-55"></div>
<div className="bg-white rounded-lg shadow-lg w-[320px] sm:w-[620px] p-5 relative top-[40%] translate-y-[-40%] left-[50%] translate-x-[-50%]"> <div className="bg-white rounded-lg shadow-lg w-[320px] sm:w-[620px] p-5 relative top-[40%] translate-y-[-40%] left-[50%] translate-x-[-50%]">

View File

@ -543,7 +543,6 @@ const ProcessStepOne = () => {
</> </>
} }
{/* HERE WE AREEEEEEEE */}
{selectedRecipients.length > 1 && <div className="flex items-center"> {selectedRecipients.length > 1 && <div className="flex items-center">
<input <input
// checked={signType == SignTypeEnum.selfSign} // checked={signType == SignTypeEnum.selfSign}
@ -555,7 +554,6 @@ const ProcessStepOne = () => {
Set signing order Set signing order
</label> </label>
</div>} </div>}
{/* HERE WE AREEEEEEEE */}
</div> </div>

View File

@ -67,7 +67,7 @@ const RecipientCards = ({
let { accountId, transactionId } = useParams<{ accountId: string; transactionId: string }>(); let { accountId, transactionId } = useParams<{ accountId: string; transactionId: string }>();
const dispatch = useDispatch<AppDispatch>(); const dispatch = useDispatch<AppDispatch>();
const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder) const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder)
if (!transactionId) { if (!transactionId) {
transactionId = templateId; transactionId = templateId;
@ -256,43 +256,6 @@ const RecipientCards = ({
let newActiveRecRo = 1 let newActiveRecRo = 1
let newOverRecRo = 1 let newOverRecRo = 1
// if (activeRec.routingOrder >= overRecRo && activeIndex > overIndex) {
// newActiveRecRo = activeRecRo > 1 ? activeRecRo - 1 : 1
// newOverRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2
// }
// if (activeRec.routingOrder >= overRecRo && activeIndex < overIndex) {
// newActiveRecRo = overRecRo > 1 ? overRecRo + 1 : 2
// newOverRecRo = overRecRo > 1 ? overRecRo - 1 : 1
// }
// if (activeRec.routingOrder <= overRecRo && activeIndex > overIndex) {
// newActiveRecRo = activeRecRo > 1 ? activeRecRo - 1 : 1
// newOverRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2
// }
// if (activeRec.routingOrder <= overRecRo && activeIndex < overIndex) {
// newActiveRecRo = overRecRo > 1 ? overRecRo + 1 : 2
// newOverRecRo = overRecRo > 1 ? overRecRo - 1 : 1
// }
// if (activeRec.routingOrder > overRecRo && activeIndex < overIndex) {
// console.log(activeIndex, overIndex, "283 2")
// newActiveRecRo = overRecRo > 1 ? overRecRo : 2
// newOverRecRo = activeRecRo > 1 ? activeRecRo : 1
// }
// if (activeRec.routingOrder < overRecRo && activeIndex > overIndex) {
// console.log(activeIndex, overIndex, "283 3")
// newActiveRecRo = overRecRo > 1 ? overRecRo : 1
// newOverRecRo = activeRecRo > 1 ? activeRecRo : 2
// }
if (activeRecRo > overRecRo && activeIndex > overIndex) { if (activeRecRo > overRecRo && activeIndex > overIndex) {
console.log(activeIndex, overIndex, "283 1") console.log(activeIndex, overIndex, "283 1")
newActiveRecRo = activeRecRo > 1 ? overRecRo : 1 newActiveRecRo = activeRecRo > 1 ? overRecRo : 1
@ -304,9 +267,6 @@ const RecipientCards = ({
newOverRecRo = overRecRo > 1 ? overRecRo : 1 newOverRecRo = overRecRo > 1 ? overRecRo : 1
} }
if (activeRecRo < overRecRo && activeIndex < overIndex) { if (activeRecRo < overRecRo && activeIndex < overIndex) {
console.log(activeIndex, overIndex, "283 4") console.log(activeIndex, overIndex, "283 4")
newActiveRecRo = overRecRo > 1 ? overRecRo : 2 newActiveRecRo = overRecRo > 1 ? overRecRo : 2
@ -318,8 +278,6 @@ const RecipientCards = ({
newOverRecRo = overRecRo > 1 ? overRecRo : 1 newOverRecRo = overRecRo > 1 ? overRecRo : 1
} }
if (activeRecRo == activeRecRo && activeIndex < overIndex) { if (activeRecRo == activeRecRo && activeIndex < overIndex) {
newActiveRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2 newActiveRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2
newOverRecRo = overRecRo > 1 ? overRecRo : 1 newOverRecRo = overRecRo > 1 ? overRecRo : 1
@ -329,51 +287,13 @@ const RecipientCards = ({
newOverRecRo = overRecRo > 1 ? overRecRo + 1 : 2 newOverRecRo = overRecRo > 1 ? overRecRo + 1 : 2
} }
// let reordered = recipients.map((r, idx) => {
// if (idx == activeIndex) {
// return (
// {
// ...r, routingOrder: newActiveRecRo
// }
// )
// }
// if (idx == overRecRo) {
// return (
// {
// ...r, routingOrder: newOverRecRo
// }
// )
// }
// });
// dispatch(updateRecipientOrder({ id: activeRec.id || activeRec.uuid, routingOrder: newActiveRecRo }));
// dispatch(updateRecipientOrder({ id: overRec.id || overRec.uuid, routingOrder: newOverRecRo }));
dispatch(updateRecipientByIndxAndRo({ indx1: activeIndex, ro1: newActiveRecRo, indx2: overIndex, ro2: newOverRecRo, })); dispatch(updateRecipientByIndxAndRo({ indx1: activeIndex, ro1: newActiveRecRo, indx2: overIndex, ro2: newOverRecRo, }));
// dispatch(reorderAllRecipientAscOrder());
}; };
return ( return (
<div className="w-full rounded-md"> <div className="w-full rounded-md">
{recipients.length > 0 ? ( {recipients.length > 0 ? (
<> <>
{/* {recipients.length > 1 && (
<div className="flex sm:flex-row sm:items-center justify-between px-3 sm:px-4 py-2 border-b border-gray-300 gap-2">
<h3 className="text-sm font-semibold text-gray-700">
Recipients ({recipients.length})
</h3>
<label
className="flex items-center gap-2 cursor-pointer select-none justify-center sm:justify-end"
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: true })}
>
<span className="text-xs font-medium text-gray-600">Signing Order</span>
<FaUsersLine className="text-[var(--primary)] mt-0.5 text-lg" />
</label>
</div>
)} */}
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}> <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
@ -399,7 +319,7 @@ const RecipientCards = ({
<> <>
{/* Main Content Section */} {/* Main Content Section */}
<div className="flex items-start sm:items-center gap-2 sm:gap-3 min-w-0 flex-1"> <div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
{/* Order Number */} {/* Order Number */}
<div className=""> <div className="">
@ -527,7 +447,7 @@ const RecipientCards = ({
{/* Name and Email Fields */} {/* Name and Email Fields */}
<div className="mb-2 w-[100%]"> <div className="mb-2 w-[100%]">
{/* Mobile: Stack vertically, Tablet+: Side by side */} {/* Mobile: Stack vertically, Tablet+: Side by side */}
<div className="flex flex-col justify-center items-center sm:flex-row gap-1 sm:gap-2"> <div className="flex flex-col justify-center sm:items-center sm:flex-row gap-1 sm:gap-2">
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1"> <div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1">
<div className="w-full sm:w-1/2"> <div className="w-full sm:w-1/2">
<div className="font-semibold text-gray-800 text-xs"> <div className="font-semibold text-gray-800 text-xs">
@ -558,7 +478,7 @@ const RecipientCards = ({
? false ? false
: !recipient.newOne && !recipient.allowSenderToDelete : !recipient.newOne && !recipient.allowSenderToDelete
} }
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1" className="absolute top-0 right-0 sm:static text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1"
> >
<ImBin size={14} className="sm:w-4 sm:h-4" /> <ImBin size={14} className="sm:w-4 sm:h-4" />
</button> </button>
@ -582,6 +502,8 @@ const RecipientCards = ({
{renderEditableField(recipient, "hostEmail", "Host Email", "", "email")} {renderEditableField(recipient, "hostEmail", "Host Email", "", "email")}
</div> </div>
</div> </div>
<div className="w-2 h-2 ml-8">
</div>
</div> </div>
</div> </div>
)} )}
@ -632,8 +554,6 @@ const RecipientCards = ({
</div> </div>
</> </>
</div> </div>
</SortableRecipient> </SortableRecipient>
@ -689,44 +609,6 @@ export default RecipientCards;
// <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
// <SortableContext
// items={recipients.map((r) => r.id || r.uuid)}
// strategy={verticalListSortingStrategy}
// >
// <div className="flex flex-col divide-y divide-gray-200 max-h-[47vh] overflow-y-auto custom-scrollbar">
// {recipients.map((recipient, idx) => (
// <SortableRecipient
// key={recipient.id || recipient.uuid}
// recipient={recipient}
// idx={idx}
// editRecipientCurrentEmail={editRecipientCurrentEmail}
// >
// {/* 🔹 your existing recipient card content goes here */}
// </SortableRecipient>
// ))}
// </div>
// </SortableContext>
// </DndContext>

View File

@ -39,7 +39,7 @@ const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children
className="cursor-grab text-gray-500 flex items-center pr-2 absolute top-0 left-0 z-10 h-full" className="cursor-grab text-gray-500 flex items-center pr-2 absolute top-0 left-0 z-10 h-full"
> >
{signingOrder && <MdDragIndicator className="absolute top-[50%] left-[70px] transform -translate-y-1/2 text-gray-300" />} {signingOrder && <MdDragIndicator className={`absolute top-[50%] left-[55px] sm:left-[70px] transform -translate-y-1/2 text-gray-300`} />}
</div>} </div>}

View File

@ -53,6 +53,7 @@ interface DocumentContainerProps {
aId?: string; aId?: string;
tId?: string; tId?: string;
selectedRecipientIndx?: number; selectedRecipientIndx?: number;
onHistoryChange?: (canUndo: boolean, canRedo: boolean) => void;
} }
let copiedTags: DroppedItem[] = [] let copiedTags: DroppedItem[] = []
@ -63,10 +64,14 @@ const fetchedTags: string[] = []
// 👇 Create an interface for the exposed methods // 👇 Create an interface for the exposed methods
export interface DocumentContainerRef { export interface DocumentContainerRef {
handleTransactionProcess: (isSave?: boolean, isNavigate?: boolean, navigateTo?: string) => void; handleTransactionProcess: (isSave?: boolean, isNavigate?: boolean, navigateTo?: string) => void;
handleUndo: () => void;
handleRedo: () => void;
handleCopy: () => void;
handlePaste: () => void;
} }
const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProps>( const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProps>(
({ isPages, zoomScale, transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null, selectedRecipientIndx = 0, setIsPages }, ref) => { ({ isPages, zoomScale, transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null, selectedRecipientIndx = 0, setIsPages, onHistoryChange }, ref) => {
const scaleFactor: number = (zoomScale ?? 100) / 100; const scaleFactor: number = (zoomScale ?? 100) / 100;
@ -94,7 +99,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
// const transactionId = useSelector((state: RootState) => state.transaction.transactionId) // const transactionId = useSelector((state: RootState) => state.transaction.transactionId)
const navigate = useNavigate(); const navigate = useNavigate();
const childRef = useRef<any>(null); const childRef = useRef<any>(null);
const tagUndoRef = useRef<DroppedItem[] | []>([]); // const tagUndoRef = useRef<DroppedItem[] | []>([]);
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({}); const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
@ -152,7 +157,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
hotKey: null hotKey: null
} }
const [hotKeysObj, setHotKeysObj] = useState<HotKeysObj>(hotKeysObjInitial); // const [hotKeysObj, setHotKeysObj] = useState<HotKeysObj>(hotKeysObjInitial);
useEffect(() => { useEffect(() => {
selectedRecipients.length == 0 && dispatch(fetchRecipients({ accountId, transactionId })); selectedRecipients.length == 0 && dispatch(fetchRecipients({ accountId, transactionId }));
@ -214,6 +219,79 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
}, [transactionId]) }, [transactionId])
// NAVIGATING to Dashboard if Transaction is not created - END // NAVIGATING to Dashboard if Transaction is not created - END
const hotKeysObj = useRef<HotKeysObj>(hotKeysObjInitial); // use ref, not state
const docKeyListenerRef = useRef<((e: KeyboardEvent) => void) | null>(null);
const selectedTagRef = useRef<{ key: string; index: number } | null>(null);
const droppedItemsRef = useRef(droppedItems);
const historyRef = useRef<DroppedItemsObj[]>([]);
const cursorRef = useRef<number>(-1);
const isUndoRedoRef = useRef<boolean>(false); // prevents history push during undo/redo replay
const notifyHistoryChange = () => {
onHistoryChange?.(
cursorRef.current > 0,
cursorRef.current < historyRef.current.length - 1
);
};
useEffect(() => {
droppedItemsRef.current = droppedItems;
// For Undo Redo
// Skip recording when the change was caused by undo/redo itself
if (isUndoRedoRef.current) {
isUndoRedoRef.current = false;
return;
}
if (Object.keys(droppedItems).length === 0) return;
// Trim any future states beyond cursor (new action wipes redo stack)
historyRef.current = historyRef.current.slice(0, cursorRef.current + 1);
// Deep clone so mutations don't corrupt history
historyRef.current.push(JSON.parse(JSON.stringify(droppedItems)));
cursorRef.current = historyRef.current.length - 1;
notifyHistoryChange();
}, [droppedItems]);
useEffect(() => {
selectedTagRef.current = selectedTag;
}, [selectedTag]);
const handleUndo = () => {
console.log("Handle Undo Called")
if (cursorRef.current <= 0) return; // nothing to undo
cursorRef.current -= 1;
const snapshot = historyRef.current[cursorRef.current];
isUndoRedoRef.current = true; // flag so the effect above skips pushing
dispatch(setDroppedItems({
transactionKey,
items: JSON.parse(JSON.stringify(snapshot))
}));
notifyHistoryChange();
};
const handleRedo = () => {
if (cursorRef.current >= historyRef.current.length - 1) return; // nothing to redo
cursorRef.current += 1;
const snapshot = historyRef.current[cursorRef.current];
isUndoRedoRef.current = true;
dispatch(setDroppedItems({
transactionKey,
items: JSON.parse(JSON.stringify(snapshot))
}));
notifyHistoryChange();
};
const groupByRecipientId = (tagsArray: any[]) => { const groupByRecipientId = (tagsArray: any[]) => {
const tagsData: Record<number, any[]> = {}; const tagsData: Record<number, any[]> = {};
@ -340,6 +418,10 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
// }, [isSend]) // }, [isSend])
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleTransactionProcess, handleTransactionProcess,
handleUndo,
handleRedo,
handleCopy,
handlePaste
})); }));
// Save Tags - END // Save Tags - END
@ -478,124 +560,148 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
})); }));
dispatch(incrementCounter(transactionKey)); dispatch(incrementCounter(transactionKey));
// setTimeout(() => {
// const tagIndex = key in droppedItems ? droppedItems[key].length : 0;
// setSelectedTag({ key, index: tagIndex })
// }, 1000)
}; };
// HANDLE DROP - END // HANDLE DROP - END
useEffect(() => { useEffect(() => {
if (!hotKeysObj.isActive) return return () => {
if ((!selectedTag?.key || `${selectedTag?.key}` in droppedItems && droppedItems[selectedTag?.key].length == 0)) return; const targetCon = document.getElementById('doc-con');
if (docKeyListenerRef.current && targetCon) {
targetCon.removeEventListener('keydown', docKeyListenerRef.current);
}
};
}, []);
const docCon = document.getElementById("doc-con"); const handleCopy = () => {
const currentSelectedTag = selectedTagRef.current;
const currentDroppedItems = droppedItemsRef.current;
if (!currentSelectedTag) return;
switch (hotKeysObj.hotKey) { const tag = currentDroppedItems[currentSelectedTag.key]?.[currentSelectedTag.index];
case "Ctrl+c": if (!tag) return;
const tempCopy = { ...droppedItems[selectedTag?.key][selectedTag?.index] }
delete tempCopy.id; const { id, ...tagWithoutId } = tag as any;
copiedTags = [{ ...tempCopy, dimensions: { ...tempCopy.dimensions, top: tempCopy.dimensions.top + 10, left: tempCopy.dimensions.left + 10 } }] copiedTags = [{
console.log("from Copy copiedTags", copiedTags) ...tagWithoutId,
dimensions: {
...tagWithoutId.dimensions,
top: tagWithoutId.dimensions.top + 10,
left: tagWithoutId.dimensions.left + 10,
}
}];
};
const handlePaste = () => {
if (!copiedTags.length || !selectedDoc.key) return;
const currentDroppedItems = droppedItemsRef.current;
const existingTags = currentDroppedItems[selectedDoc.key] || [];
dispatch(setDroppedItems({
transactionKey,
items: {
...currentDroppedItems,
[selectedDoc.key]: [...existingTags, ...copiedTags.map(t => ({ ...t, update: true }))]
}
}));
dispatch(incrementCounter(transactionKey));
};
// Replace the hotkeys useEffect with a direct handler function
const handleHotKey = (hotKey: string, docKey: string) => {
const currentDroppedItems = droppedItemsRef.current;
const currentSelectedTag = selectedTagRef.current;
const docCon = document.getElementById('doc-con');
switch (hotKey) {
case 'Ctrl+c': {
handleCopy();
break; break;
case "Ctrl+v": }
console.log(selectedDoc.key, "keyss") case 'Ctrl+v': {
let updatedKey = droppedItems[selectedDoc.key] handlePaste();
updatedKey.push(...copiedTags)
console.log("copiedTags", copiedTags)
console.log("updatedKey", updatedKey)
dispatch(setDroppedItems({
transactionKey,
items: {
...droppedItems,
[selectedDoc.key]: updatedKey
}
}));
// Handle Ctrl+V
break; break;
case "Ctrl+x": }
// Handle Ctrl+V case 'Ctrl+a': {
break; docCon?.querySelectorAll('[data-tag-id]').forEach(tag => {
case "Ctrl+z": tag.classList.add('border-2', 'border-sky-700');
// Handle Ctrl+V tag.setAttribute('all-select-group', 'true');
break;
case "Ctrl+y":
// Handle Ctrl+V
break;
case "Ctrl+a":
docCon?.querySelectorAll("[data-tag-id]").forEach(tag => {
tag.classList.add("border-2", "border-sky-700")
tag.setAttribute("all-select-group", "true")
docCon.addEventListener("click", () => {
tag.classList.remove("border-2", "border-sky-800")
tag.removeAttribute("all-select-group")
})
// docCon.addEventListener("focusout", () => {
// console.log("focus out", tag)
// tag.classList.remove("border-2", "border-slate-800")
// tag.removeAttribute("all-select-group")
// })
}); });
break;
case "delete":
const allselectedTags = docCon?.querySelectorAll("[all-select-group]") ?? []
allselectedTags.forEach((tag) => {
const tagIndex = tag.getAttribute("data-tag-id") ?? "";
const key = tag.getAttribute("data-drop-key") ?? "";
childRef.current.handleRemoveTag(key, tagIndex) // One-time click to deselect all
}) const deselect = () => {
docCon?.querySelectorAll('[all-select-group]').forEach(tag => {
if (allselectedTags.length == 0) childRef.current.handleRemoveTag(selectedTag?.key, selectedTag?.index) tag.classList.remove('border-2', 'border-sky-700');
tag.removeAttribute('all-select-group');
});
docCon?.removeEventListener('click', deselect);
};
docCon?.addEventListener('click', deselect);
break; break;
}
case 'delete':
case 'backspace': {
const allSelected = docCon?.querySelectorAll('[all-select-group]') ?? [];
if (allSelected.length > 0) {
allSelected.forEach(tag => {
const tagIndex = tag.getAttribute('data-tag-id') ?? '';
const key = tag.getAttribute('data-drop-key') ?? '';
childRef.current?.handleRemoveTag(key, tagIndex);
});
} else if (currentSelectedTag) {
childRef.current?.handleRemoveTag(currentSelectedTag.key, currentSelectedTag.index);
}
break;
}
case 'Ctrl+z': {
handleUndo();
break;
}
case 'Ctrl+y': {
handleRedo();
break;
}
case 'Ctrl+Shift+z': {
handleRedo(); // Mac convention
break;
}
default: default:
break; break;
} }
}, [hotKeysObj]) };
const handleDocumentFocus = (key: string) => { const handleDocumentFocus = (key: string) => {
// const id = key.replace("_", "");
const targetCon = document.getElementById('doc-con');
selectedDoc.key = key; selectedDoc.key = key;
targetCon?.addEventListener("keydown", (event) => { const targetCon = document.getElementById('doc-con');
if (!targetCon) return;
// Remove previous listener before adding new one
if (docKeyListenerRef.current) {
targetCon.removeEventListener('keydown', docKeyListenerRef.current);
}
const handler = (event: KeyboardEvent) => {
event.preventDefault(); event.preventDefault();
const ctrl = event.ctrlKey; const ctrl = event.ctrlKey || event.metaKey;
const alt = event.altKey;
const metaKey = event.metaKey;
const shift = event.shiftKey; const shift = event.shiftKey;
const pressedKey = event.key.toLowerCase(); const pressedKey = event.key.toLowerCase();
let hotKey = pressedKey let hotKey = pressedKey;
if (ctrl && shift) hotKey = 'Ctrl+Shift+' + pressedKey;
else if (ctrl) hotKey = 'Ctrl+' + pressedKey;
handleHotKey(hotKey, key);
};
if (ctrl) { docKeyListenerRef.current = handler;
hotKey = "Ctrl+" + pressedKey; targetCon.addEventListener('keydown', handler);
} else if (alt) { };
hotKey = "Alt" + pressedKey;
} else if (metaKey) {
hotKey = "Meta+" + pressedKey;
} else if (shift) {
hotKey = "Shift+" + pressedKey;
}
setHotKeysObj({
isActive: true,
key: key,
tagId: null,
hotKey: hotKey
});
});
// return () => targetCon?.removeEventListener("keydown");
}
return ( return (
@ -696,7 +802,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
accountId={accountId} accountId={accountId}
transactionId={transactionId} transactionId={transactionId}
transactionTemplateToggle={transactionTemplateToggle} transactionTemplateToggle={transactionTemplateToggle}
tagUndoRef={tagUndoRef} // tagUndoRef={tagUndoRef}
transactionKey={transactionKey} transactionKey={transactionKey}
/> />

View File

@ -36,7 +36,7 @@ interface DroppedItemsTagsConProps {
accountId: string; accountId: string;
transactionId: string; transactionId: string;
transactionTemplateToggle: string; transactionTemplateToggle: string;
tagUndoRef: React.MutableRefObject<DroppedItem[]>; // tagUndoRef: React.MutableRefObject<DroppedItem[]>;
transactionKey: string; transactionKey: string;
} }
@ -56,7 +56,7 @@ const DroppedItemsTagsCon = forwardRef(({
accountId, accountId,
transactionId, transactionId,
transactionTemplateToggle, transactionTemplateToggle,
tagUndoRef, // tagUndoRef,
transactionKey transactionKey
}: DroppedItemsTagsConProps, ref) => { }: DroppedItemsTagsConProps, ref) => {
@ -688,7 +688,7 @@ const DroppedItemsTagsCon = forwardRef(({
}} }}
onBlur={(e: React.FocusEvent) => { onBlur={(e: React.FocusEvent) => {
const nextElement = e.relatedTarget as HTMLElement | null; const nextElement = e.relatedTarget as HTMLElement | null;
if (nextElement?.hasAttribute("data-type-group") || nextElement?.closest("[data-tag-properties]")) return; if (nextElement?.hasAttribute("data-type-group") || nextElement?.hasAttribute("data-easy-tool") || nextElement?.closest("[data-tag-properties]")) return;
setSelectedTag(null); setSelectedTag(null);
}} }}
style={{ style={{

View File

@ -143,7 +143,7 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
</div> </div>
<div <div
className={`absolute w-full shadow-md rounded-md mt-1 px-2 py-1 z-20 bg-white ${dropDownToggle ? '' : 'hidden'}`}> className={`absolute bottom-full mb-2 sm:top-1 sm:bottom-auto w-full shadow-md rounded-md px-2 py-1 z-20 bg-white ${dropDownToggle ? '' : 'hidden'}`}>
{selectedRecipients.length && selectedRecipients.map((recipient, indx) => { {selectedRecipients.length && selectedRecipients.map((recipient, indx) => {
if (recipient.type == recipientType.CC) return null; // Skip CC recipients if (recipient.type == recipientType.CC) return null; // Skip CC recipients
return (<div return (<div

View File

@ -31,6 +31,12 @@ interface NavTwoProps {
tId?: string | null tId?: string | null
onSendClick?: (isSave?: boolean, navigate?: boolean, navigateTo?: string) => void; onSendClick?: (isSave?: boolean, navigate?: boolean, navigateTo?: string) => void;
onZoomChange?: React.Dispatch<React.SetStateAction<number>> onZoomChange?: React.Dispatch<React.SetStateAction<number>>
canUndo: boolean;
canRedo: boolean;
onUndo: () => void;
onRedo: () => void;
onCopy: () => void;
onPaste: () => void;
} }
@ -86,7 +92,13 @@ const NavTwo: React.FC<NavTwoProps> = ({
aId = null, aId = null,
tId = null, tId = null,
onSendClick, onSendClick,
onZoomChange onZoomChange,
canUndo,
canRedo,
onUndo,
onRedo,
onCopy,
onPaste
}) => { }) => {
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -127,7 +139,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
const [closePage, setClosePage] = useState(false); const [closePage, setClosePage] = useState(false);
const [isFitDd, setIsFitDd] = useState<boolean>(false) const [isFitDd, setIsFitDd] = useState<boolean>(false)
const [selFitIndx, setSelFitIndx] = useState<number>(0); const [selFitIndx, setSelFitIndx] = useState<number>(window.innerWidth > 638 ? 2 : 0);
const [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 }); const [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 });
const navigate = useNavigate() const navigate = useNavigate()
@ -174,6 +186,19 @@ const NavTwo: React.FC<NavTwoProps> = ({
} }
}, [selectedRecipients]) }, [selectedRecipients])
useEffect(() => {
if (!selectedDocuments || selectedDocuments.length === 0) return;
const container = document.getElementById("doc-con");
if (!container) return;
// Wait for images and zoom to apply
setTimeout(() => {
container.scrollLeft = (container.scrollWidth - container.clientWidth) / 2;
}, 200);
}, [selectedDocuments]);
const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false); const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false);
const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false); const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false);
@ -331,25 +356,38 @@ const NavTwo: React.FC<NavTwoProps> = ({
<div <div
tabIndex={1} tabIndex={1}
onBlur={() => setIsFitDd(false)} onBlur={() => setIsFitDd(false)}
data-easy-tool="true"
className='hidden absolute gap-2 lg:flex items-center left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50' className='hidden absolute gap-2 lg:flex items-center left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50'
> >
<div className='flex'> <div className='flex'>
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'> <p
<LuUndo2 className='text-sm text-slate-600' /> onClick={onUndo}
title="Undo (Ctrl+Z)"
className={`p-2 ${canUndo ? "hover:bg-slate-100" : ""} cursor-pointer rounded-sm`}>
<LuUndo2 className={`text-sm ${canUndo ? "text-slate-600" : "text-slate-400"}`} />
</p> </p>
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'> <p
<LuRedo2 className='text-sm text-slate-600' /> onClick={onRedo}
title="Redo (Ctrl+Y)"
className={`p-2 ${canRedo ? "hover:bg-slate-100" : ""} cursor-pointer rounded-sm`}>
<LuRedo2 className={`text-sm ${canRedo ? "text-slate-600" : "text-slate-400"}`} />
</p> </p>
</div> </div>
<div className='w-full h-[20px] border-[1px] border-slate-200'></div> <div className='w-full h-[20px] border-[1px] border-slate-200'></div>
<div className='flex'> <div className='flex'>
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'> <p
onClick={onCopy}
title="Copy (Ctrl+C)"
className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
<FaRegCopy className='text-sm text-slate-600' /> <FaRegCopy className='text-sm text-slate-600' />
</p> </p>
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'> <p
onClick={onPaste}
title="Paste (Ctrl+V)"
className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
<FaRegPaste className='text-sm text-slate-600' /> <FaRegPaste className='text-sm text-slate-600' />
</p> </p>
</div> </div>
@ -474,7 +512,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
</div> </div>
<div <div
className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-20 bg-white ${nav2DocToggle ? '' : 'hidden'}`}> className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-80 bg-white ${nav2DocToggle ? '' : 'hidden'}`}>
{selectedDocuments.length && selectedDocuments.map((document, indx) => { {selectedDocuments.length && selectedDocuments.map((document, indx) => {
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'> return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>
@ -497,7 +535,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
<div <div
className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-20 bg-white ${nav2RecToggle ? '' : 'hidden'}`}> className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-80 bg-white ${nav2RecToggle ? '' : 'hidden'}`}>
{selectedRecipients.length && selectedRecipients.map((recipient, indx) => { {selectedRecipients.length && selectedRecipients.map((recipient, indx) => {
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'> return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>

View File

@ -22,6 +22,10 @@ const ProcessStepTwo = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
// const documentContainerRef = useRef<DocumentContainerRef>(null);
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>(); const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
let accountId = params.accountId; let accountId = params.accountId;
let transactionId = params.transactionId let transactionId = params.transactionId
@ -85,7 +89,7 @@ const ProcessStepTwo = () => {
docRef.current?.handleTransactionProcess(isSave, isNavigate, navigateTo); docRef.current?.handleTransactionProcess(isSave, isNavigate, navigateTo);
}; };
const [zoomScale, setZoomScale] = useState<number>(100); // percentage const [zoomScale, setZoomScale] = useState<number>(windowWidth > 638 ? 150 : 100); // percentage
// To center the container on every zoom scale change - START // To center the container on every zoom scale change - START
useEffect(() => { useEffect(() => {
@ -93,15 +97,18 @@ const ProcessStepTwo = () => {
if (!container) return; if (!container) return;
const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1); const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1);
const newScrollTop = prevScrollRatio * container.scrollHeight; const newScrollTop = prevScrollRatio * container.scrollHeight;
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
container.scrollTo({ // Using setTimeout to let DOM settle after zoom change
top: newScrollTop, setTimeout(() => {
left: newScrollLeft, const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
behavior: "instant" as ScrollBehavior container.scrollTo({
}); top: newScrollTop,
left: newScrollLeft,
behavior: "instant" as ScrollBehavior
});
}, 50);
}, [zoomScale]); }, [zoomScale]);
// To center the container on every zoom scale change - END // To center the container on every zoom scale change - END
@ -116,7 +123,14 @@ const ProcessStepTwo = () => {
// setIsFields={setIsFields} // setIsFields={setIsFields}
// isSend={isSend} // isSend={isSend}
// setIsSend={setIsSend} // setIsSend={setIsSend}
onSendClick={handleSendClick} /> onCopy={() => docRef.current?.handleCopy()}
onPaste={() => docRef.current?.handlePaste()}
canUndo={canUndo}
canRedo={canRedo}
onUndo={() => docRef.current?.handleUndo()}
onRedo={() => docRef.current?.handleRedo()}
onSendClick={handleSendClick}
/>
<div className='sm:grid sm:grid-cols-12 sm:gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'> <div className='sm:grid sm:grid-cols-12 sm:gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'>
<LeftSideBar <LeftSideBar
@ -137,6 +151,10 @@ const ProcessStepTwo = () => {
setTransactionSaving={setTransactionSaving} setTransactionSaving={setTransactionSaving}
isSend={isSend} isSend={isSend}
setIsSend={setIsSend} setIsSend={setIsSend}
onHistoryChange={(canUndo, canRedo) => {
setCanUndo(canUndo);
setCanRedo(canRedo);
}}
/> />
</div> </div>

View File

@ -151,7 +151,7 @@ const DocumentPages: React.FC<{
<div id='document-pages' <div id='document-pages'
tabIndex={-1} tabIndex={-1}
onBlur={() => window.innerWidth <= 638 && setIsPages(false)} onBlur={() => window.innerWidth <= 638 && setIsPages(false)}
className={`h-[100vh] absolute ${isPages || window.innerWidth > 638 ? "" : "hidden"} top-0 left-0 sm:static max-w-max bg-slate-100 z-50 sm:col-span-2 flex flex-col w-full overflow-y-auto`}> className={`outline-none h-[100vh] absolute ${isPages || window.innerWidth > 638 ? "" : "hidden"} top-0 left-0 sm:static bg-slate-100 z-50 sm:col-span-2 flex flex-col w-min-w overflow-y-auto`}>
{selectedDocuments.map((document, indx) => ( {selectedDocuments.map((document, indx) => (
<div key={indx} className="border-b border-slate-200 pb-3 mb-1"> <div key={indx} className="border-b border-slate-200 pb-3 mb-1">

View File

@ -20,6 +20,7 @@ import CurrTimeStampAttr from "./attributes/CurrTimeStampAttr";
import ToolTipAttr from "./attributes/ToolTipAttr"; import ToolTipAttr from "./attributes/ToolTipAttr";
import "./style.css"; import "./style.css";
import { RiDeleteBinLine } from "react-icons/ri";
export interface DroppedItemProps { export interface DroppedItemProps {
droppedItems: DroppedItem[] droppedItems: DroppedItem[]
@ -175,6 +176,22 @@ const TagProperties: React.FC<DroppedItemProps> = (
handlePropertyChange("decimalPlace", val) handlePropertyChange("decimalPlace", val)
} }
const handleDeleteTag = () => {
setDroppedItems((prev) => {
if (!signClient) {
const updatedPageTags = (prev[selTagKey] || []).map((item, i) =>
i === selTagIndx ? { ...item, isDeleted: true, update: true } : item
);
return { ...prev, [selTagKey]: updatedPageTags };
} else {
return (prev as DroppedItem[]).map((item, i) =>
i === selTagIndx ? { ...item, isDeleted: true, update: true } : item
);
}
});
emptySelectedTag?.();
};
useEffect(() => { useEffect(() => {
if (selectedTag.decimalPlace == undefined) return; if (selectedTag.decimalPlace == undefined) return;
if (selectedTag.decimalPlace == decimalPlace) return; if (selectedTag.decimalPlace == decimalPlace) return;
@ -498,6 +515,17 @@ const TagProperties: React.FC<DroppedItemProps> = (
} }
{/* Delete Tag */}
<div className="pt-2 border-t border-slate-200 mt-2">
<button
onClick={handleDeleteTag}
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-xs text-red-500 border border-red-200 rounded hover:bg-red-50 transition-colors cursor-pointer"
>
<RiDeleteBinLine className="text-sm" />
Delete
</button>
</div>
{/* Delete Tag */}
</div> </div>
</div> </div>

View File

@ -0,0 +1,291 @@
import React, { useState } from "react";
import { FiX, FiEye, FiEyeOff } from "react-icons/fi";
interface Account {
id: number;
isDefault: boolean;
name: string;
}
interface UserData {
id: number;
name: string;
email: string;
language: string;
account: Account;
}
interface EditProfileModalProps {
isOpen: boolean;
onClose: () => void;
userData: UserData;
}
const EditProfileModal: React.FC<EditProfileModalProps> = ({
isOpen,
onClose,
userData,
}) => {
const [fullName, setFullName] = useState(userData.name);
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showPasswords, setShowPasswords] = useState(false);
if (!isOpen) return null;
const handleSubmit = async () => {
try {
if (newPassword !== confirmPassword) {
alert("Passwords do not match");
return;
}
const payload = {
name: fullName,
currentPassword,
newPassword,
};
console.log(payload);
/**
* API CALL HERE
* await api.put('/api/v1/user/profile', payload)
*/
onClose();
} catch (error) {
console.error(error);
}
};
return (
<div
className="
fixed
inset-0
z-50
bg-black/40
backdrop-blur-sm
flex
items-center
justify-center
p-4
"
>
<div
className="
bg-white
w-full
max-w-2xl
rounded-2xl
shadow-2xl
overflow-hidden
animate-in
fade-in
zoom-in-95
"
>
{/* Header */}
<div className="flex items-center justify-between px-6 py-5 border-b border-slate-200">
<div>
<h2 className="text-xl font-semibold text-slate-900">
Edit Profile
</h2>
<p className="text-sm text-slate-500 mt-1">
Update your profile information and password.
</p>
</div>
<button
onClick={onClose}
className="
h-10
w-10
rounded-xl
flex
items-center
justify-center
hover:bg-slate-100
transition-all
"
>
<FiX className="text-lg" />
</button>
</div>
{/* Body */}
<div className="p-6 space-y-6 max-h-[75vh] overflow-y-auto">
{/* Full Name */}
<div>
<label className="text-sm font-medium text-slate-700 block mb-2">
Full Name
</label>
<input
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
className="
w-full
rounded-xl
border
border-slate-300
px-4
py-3
outline-none
focus:ring-2
focus:ring-[#34BEFF]
"
/>
</div>
{/* Password Section */}
<div className="border-t border-slate-200 pt-6">
<h3 className="text-base font-semibold text-slate-900 mb-4">
Change Password
</h3>
<div className="space-y-4">
{/* Current Password */}
<div>
<label className="text-sm font-medium text-slate-700 block mb-2">
Current Password
</label>
<div className="relative">
<input
type={showPasswords ? "text" : "password"}
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
className="
w-full
rounded-xl
border
border-slate-300
px-4
py-3
pr-12
outline-none
focus:ring-2
focus:ring-[#34BEFF]
"
/>
<button
type="button"
onClick={() =>
setShowPasswords(!showPasswords)
}
className="
absolute
right-3
top-1/2
-translate-y-1/2
text-slate-500
"
>
{showPasswords ? <FiEyeOff /> : <FiEye />}
</button>
</div>
</div>
{/* New Password */}
<div>
<label className="text-sm font-medium text-slate-700 block mb-2">
New Password
</label>
<input
type={showPasswords ? "text" : "password"}
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="
w-full
rounded-xl
border
border-slate-300
px-4
py-3
outline-none
focus:ring-2
focus:ring-[#34BEFF]
"
/>
</div>
{/* Confirm Password */}
<div>
<label className="text-sm font-medium text-slate-700 block mb-2">
Confirm Password
</label>
<input
type={showPasswords ? "text" : "password"}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="
w-full
rounded-xl
border
border-slate-300
px-4
py-3
outline-none
focus:ring-2
focus:ring-[#34BEFF]
"
/>
</div>
</div>
</div>
</div>
{/* Footer */}
<div className="flex flex-col-reverse sm:flex-row justify-end gap-3 px-6 py-5 border-t border-slate-200">
<button
onClick={onClose}
className="
px-5
py-3
rounded-xl
border
border-slate-300
text-slate-700
font-medium
hover:bg-slate-50
"
>
Cancel
</button>
<button
onClick={handleSubmit}
className="
px-5
py-3
rounded-xl
bg-[#34BEFF]
text-white
font-medium
hover:opacity-90
"
>
Save Changes
</button>
</div>
</div>
</div>
);
};
export default EditProfileModal;

View File

@ -411,92 +411,257 @@ const Setting = () => {
return ( return (
<> <>
{/* <SettingNav /> */} <div
{/* <AccountNav/> */} className="
<div className="bg-white h-[calc(90vh-20px)] flex flex-col mx-6 my-6 rounded-lg overflow-hidden"> bg-[#F8FAFC]
{/* Tabs */} min-h-[calc(100vh-80px)]
<div className="sticky top-0 z-10 bg-white border-b border-gray-200 px-4 md:px-6 lg:px-8 pt-4"> p-2
<div className="flex flex-wrap md:gap-4 "> sm:p-4
{tabs.map((tab) => lg:p-6
tab.type == page1 && ( "
<button >
key={tab.id} <div
onClick={() => handleMainTabClick(tab.id)} className="
className={`cursor-pointer relative px-4 pb-3 text-sm font-medium transition-all duration-200 bg-white
${activeTab == tab.id h-[calc(100vh-100px)]
? "text-[#34BEFF] after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-[#34BEFF] bg-gradient-to-t from-[#E5F6FF] to-transparent" rounded-2xl
: "text-gray-600 hover:text-[#34BEFF]" border
}`} border-slate-200
> overflow-hidden
{tab.label} flex
</button> flex-col
) shadow-sm
)} "
>
{/* Top Tabs */}
<div
className="
sticky
top-0
z-30
bg-white/90
backdrop-blur-md
border-b
border-slate-200
"
>
<div
className="
flex
overflow-x-auto
no-scrollbar
gap-2
px-3
sm:px-5
lg:px-8
pt-4
"
>
{tabs.map(
(tab) =>
tab.type == page1 && (
<button
key={tab.id}
onClick={() => handleMainTabClick(tab.id)}
className={`
relative
whitespace-nowrap
px-4
py-3
text-sm
font-medium
rounded-t-xl
transition-all
duration-200
cursor-pointer
${activeTab == tab.id
? `
text-[#34BEFF]
bg-[#EAF8FF]
`
: `
text-slate-600
hover:text-[#34BEFF]
hover:bg-slate-50
`
}
`}
>
{tab.label}
{activeTab == tab.id && (
<span
className="
absolute
bottom-0
left-0
w-full
h-[2px]
bg-[#34BEFF]
"
/>
)}
</button>
)
)}
</div>
</div> </div>
</div>
{/* Responsive Layout */} {/* Layout */}
<div className="flex-1 min-h-0 flex flex-col lg:flex-row"> <div className="flex flex-1 min-h-0 overflow-hidden">
{/* Sidebar (hidden on small, shown on lg) */} {/* Desktop Sidebar */}
{activeTab == 'userProfile' && ( {activeTab == "userProfile" && (
<section className="hidden lg:block flex-shrink-0 w-70 border-r-1 border-slate-200"> <aside
<div className="border-b-1 border-slate-200 py-4 px-6"> className="
<div className="flex justify-start items-center gap-2"> hidden
<div> lg:flex
<p className="bg-[#FAF0FF] text-[#A915FF] font-semibold max-w-max px-3 py-2 rounded-full capitalize">{getInitialsUtils(user.name)}</p> w-[280px]
</div> xl:w-[320px]
<div> border-r
<p className="font-semibold">{user.name}</p> border-slate-200
<p className="text-sm text-slate-600 w-[24ch] overflow-hidden text-ellipsis whitespace-nowrap"> flex-col
{user.email} bg-white
</p> "
>
{/* User Card */}
<div className="p-6 border-b border-slate-200">
<div className="flex items-center gap-3">
<div
className="
h-12
w-12
rounded-full
bg-[#FAF0FF]
text-[#A915FF]
flex
items-center
justify-center
font-semibold
text-sm
shrink-0
"
>
{getInitialsUtils(user.name)}
</div>
<div className="min-w-0">
<p className="font-semibold text-slate-900 truncate">
{user.name}
</p>
<p className="text-sm text-slate-500 truncate">
{user.email}
</p>
</div>
</div> </div>
</div> </div>
</div>
<div className="sticky top-6 z-20 px-6 pt-2"> {/* Navigation */}
<AsideSection <div className="flex-1 overflow-y-auto no-scrollbar px-4 py-5">
activeSubSection={activeSubSection} <AsideSection
onSubSectionClick={handleSubSectionClick} activeSubSection={activeSubSection}
subSections={getSubSections(activeTab)} onSubSectionClick={handleSubSectionClick}
mainTabTitle={tabs.find(t => t.id == activeTab)?.label || ""} subSections={getSubSections(activeTab)}
/> mainTabTitle={
</div> tabs.find((t) => t.id == activeTab)?.label || ""
</section> }
)} />
</div>
</aside>
)}
{/* Sidebar as horizontal nav (only on mobile) */} {/* Main Section */}
{activeTab == 'userProfile' && ( <main className="flex-1 min-w-0 flex flex-col overflow-hidden">
<section className="lg:hidden mb-2">
<div className="flex overflow-x-auto no-scrollbar gap-2 border-b border-gray-200 p-2"> {/* Mobile Sub Navigation */}
{getSubSections(activeTab).map((sub) => ( {activeTab == "userProfile" && (
<button <div
key={sub.id} className="
onClick={() => handleSubSectionClick(sub.id)} lg:hidden
className={`whitespace-nowrap text-sm font-medium px-2 py-2 rounded-md cursor-pointer sticky
${activeSubSection == sub.id ? "bg-blue-100 text-blue-600" : "text-gray-600"} top-0
`} z-20
bg-white
border-b
border-slate-200
"
>
<div
className="
flex
overflow-x-auto
no-scrollbar
gap-2
px-3
py-3
"
> >
{sub.label} {getSubSections(activeTab).map((sub) => (
</button> <button
))} key={sub.id}
</div> onClick={() =>
</section> handleSubSectionClick(sub.id)
)} }
className={`
whitespace-nowrap
px-4
py-2
rounded-xl
text-sm
font-medium
transition-all
duration-200
border
{/* Main Content */} ${activeSubSection == sub.id
<div ? `
ref={scrollContainerRef} bg-[#EAF8FF]
className="flex-1 min-w-0 overflow-y-auto no-scrollbar" text-[#34BEFF]
> border-[#B8EAFF]
{renderContent()} `
: `
bg-white
text-slate-600
border-slate-200
`
}
`}
>
{sub.label}
</button>
))}
</div>
</div>
)}
{/* Scroll Content */}
<div
ref={scrollContainerRef}
className="
flex-1
overflow-y-auto
no-scrollbar
px-2
sm:px-4
lg:px-6
py-4
"
>
<div className="max-w-6xl mx-auto">
{renderContent()}
</div>
</div>
</main>
</div> </div>
</div> </div>
</div> </div>
</> </>
) );
} }
export default Setting; export default Setting;

View File

@ -160,7 +160,7 @@ const SignatureManager: React.FC = () => {
<button className="bg-[var(--mainPrimaryBtn)] text-white px-3.5 gap-1 py-2 rounded-lg text-sm font-semibold flex items-center cursor-pointer transition-all duration-200 transform hover:-translate-y-0.5" <button className="bg-[var(--mainPrimaryBtn)] text-white px-3.5 gap-1 py-2 rounded-lg text-sm font-semibold flex items-center cursor-pointer transition-all duration-200 transform hover:-translate-y-0.5"
onClick={handleAddNew}> onClick={handleAddNew}>
<FiPlus className="text-lg font-semibold" /> <FiPlus className="text-lg font-semibold" />
Add Signature <span>Add</span> <span>Signature</span>
</button> </button>
</div> </div>

View File

@ -1,4 +1,7 @@
import { FiX, FiEdit2 } from "react-icons/fi"; import React, { useState } from "react";
import { FiEdit2 } from "react-icons/fi";
import EditProfileModal from "./EditProfileModal";
interface Account { interface Account {
id: number; id: number;
@ -19,38 +22,109 @@ interface UserProfileProps {
} }
const UserProfile: React.FC<UserProfileProps> = ({ userData }) => { const UserProfile: React.FC<UserProfileProps> = ({ userData }) => {
const [openEditModal, setOpenEditModal] = useState(false);
const infoItems = [
{
label: "Full Name",
value: userData.name,
},
{
label: "Email Address",
value: userData.email,
},
{
label: "Account Name",
value: userData.account.name,
},
];
return ( return (
<div className="bg-white rounded-md flex flex-col items-center sm:items-start w-full p-6 lg:gap-4 "> <>
<div className="flex flex-col"> <div className="w-full bg-white rounded-2xl border border-slate-200 p-4 sm:p-6 lg:p-8">
<p className="text-lg font-semibold">Personal Information</p>
<p className="text-xs text-slate-400 mt-1">Basic info, like your name and email address.</p>
</div>
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4 mb-6">
<div>
<h2 className="text-xl sm:text-2xl font-semibold text-slate-900">
Personal Information
</h2>
<div className="bg-slate-50 w-full px-3 py-2 rounded-lg"> <p className="text-sm text-slate-500 mt-1 max-w-xl">
<p className="text-sm font-semibold">Information</p> Basic information like your name, email address, and account details.
</div> </p>
</div>
<button
onClick={() => setOpenEditModal(true)}
<div className="w-full border-gray-200 rounded-md bg-white font-sans"> className="
<div className="sm:flex sm:flex-col md:grid md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] border-b border-gray-100 px-4 py-4"> inline-flex
<span className="text-gray-600">Full Name :</span> items-center
<span className="font-medium text-black">{userData.name}</span> justify-center
gap-2
rounded-xl
border
border-slate-200
px-4
py-2.5
text-sm
font-medium
text-slate-700
hover:bg-slate-50
transition-all
duration-200
cursor-pointer
"
>
<FiEdit2 className="text-sm" />
Edit Profile
</button>
</div> </div>
<div className="sm:flex sm:flex-col md:grid md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] border-b border-gray-100 px-4 py-4">
<span className="text-gray-600">Email Address :</span> {/* Section Tag */}
<span className="font-medium text-black">{userData.email}</span> {/* <div className="bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 mb-5">
</div> <p className="text-sm font-semibold text-slate-700">
<div className="sm:flex sm:flex-col md:grid md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] px-4 py-4"> Information
<span className="text-gray-600">Account Name :</span> </p>
<span className="font-medium text-black">{userData.account.name}</span> </div> */}
{/* Info Card */}
<div className="rounded-2xl border border-slate-200 overflow-hidden divide-y divide-slate-100">
{infoItems.map((item, index) => (
<div
key={index}
className="
grid
grid-cols-1
md:grid-cols-[220px_1fr]
gap-1 md:gap-4
px-4 sm:px-6
py-4 sm:py-5
items-start
"
>
<p className="text-sm font-medium text-slate-500">
{item.label}
</p>
<p className="text-sm sm:text-base font-semibold text-slate-900 break-words">
{item.value || "-"}
</p>
</div>
))}
</div> </div>
</div> </div>
{/* Modal */}
</div> <EditProfileModal
isOpen={openEditModal}
onClose={() => setOpenEditModal(false)}
userData={userData}
/>
</>
); );
}; };

View File

@ -603,7 +603,7 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
}, []); }, []);
return ( return (
<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="fixed z-100 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 "> <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 */} {/* Main Content Area */}

View File

@ -43,10 +43,11 @@ const typeToIconMap = {
<path d="M0 26.1765C0 26.8262 0.52672 27.353 1.17649 27.353H21.1765C21.8263 27.353 22.353 26.8262 22.353 26.1765C22.353 25.5267 21.8263 25 21.1765 25H1.17649C0.52672 25 0 25.5267 0 26.1765Z" fill="#FBC249" /> <path d="M0 26.1765C0 26.8262 0.52672 27.353 1.17649 27.353H21.1765C21.8263 27.353 22.353 26.8262 22.353 26.1765C22.353 25.5267 21.8263 25 21.1765 25H1.17649C0.52672 25 0 25.5267 0 26.1765Z" fill="#FBC249" />
</svg> </svg>
, ,
INITIALS: <svg width="20" height="20" viewBox="0 0 39 36" fill="none" xmlns="http://www.w3.org/2000/svg"> INITIALS: <svg width="20" height="20" viewBox="0 0 32 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.91844 27.3C4.31844 27.3 3.10844 26.85 2.28844 25.95C1.46844 25.03 1.05844 23.72 1.05844 22.02V20.82H4.17844V22.26C4.17844 23.62 4.74844 24.3 5.88844 24.3C6.44844 24.3 6.86844 24.14 7.14844 23.82C7.44844 23.48 7.59844 22.94 7.59844 22.2C7.59844 21.32 7.39844 20.55 6.99844 19.89C6.59844 19.21 5.85844 18.4 4.77844 17.46C3.41844 16.26 2.46844 15.18 1.92844 14.22C1.38844 13.24 1.11844 12.14 1.11844 10.92C1.11844 9.26 1.53844 7.98 2.37844 7.08C3.21844 6.16 4.43844 5.7 6.03844 5.7C7.61844 5.7 8.80844 6.16 9.60844 7.08C10.4284 7.98 10.8384 9.28 10.8384 10.98V11.85H7.71844V10.77C7.71844 10.05 7.57844 9.53 7.29844 9.21C7.01844 8.87 6.60844 8.7 6.06844 8.7C4.96844 8.7 4.41844 9.37 4.41844 10.71C4.41844 11.47 4.61844 12.18 5.01844 12.84C5.43844 13.5 6.18844 14.3 7.26844 15.24C8.64844 16.44 9.59844 17.53 10.1184 18.51C10.6384 19.49 10.8984 20.64 10.8984 21.96C10.8984 23.68 10.4684 25 9.60844 25.92C8.76844 26.84 7.53844 27.3 5.91844 27.3ZM12.7905 6H17.5005L19.6005 21.03H19.6605L21.7605 6H26.4705V27H23.3505V11.1H23.2905L20.8905 27H18.1305L15.7305 11.1H15.6705V27H12.7905V6ZM28.9331 6H37.9331V9H32.2331V14.55H36.7631V17.55H32.2331V24H37.9331V27H28.9331V6Z" fill="#0D6EFD" /> <path d="M4.36852 5.32397H8.34385L11.3854 24H8.45057L7.91697 20.2915V20.3448H4.58196L4.04836 24H1.327L4.36852 5.32397ZM7.57013 17.8102L6.26281 8.57894H6.20945L4.9288 17.8102H7.57013Z" fill="#0D6EFD" />
<path d="M0.398438 29.4H38.5931V31.8H0.398438V29.4Z" fill="#0D6EFD" /> <rect x="17.5" y="5.46875" width="20.7812" height="3.28125" fill="#FBC249" />
<rect y="29" width="39" height="3" fill="#FBC249" /> <rect x="17.5" y="17.5" width="20.7812" height="3.28125" fill="#0D6EFD" />
<rect y="29.5312" width="38.2812" height="3.28125" fill="#FBC249" />
</svg>, </svg>,
TEMPORAL: TEMPORAL:
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">