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">
<div className="w-full flex flex-col sm:flex-row gap-4 justify-center items-stretch 2xl:gap-6 2xl:mt-6">
{/* Forms Made Easy */} {/* Forms Made Easy */}
<div className="bg-white rounded-lg shadow p-4 2xl:p-6"> <div className="bg-white rounded-lg shadow p-4 2xl:p-6">
<div className=""> <p className="text-sm font-semibold text-left mb-4 2xl:text-lg min-w-min">
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
Generate Forms with Ease Generate Forms with Ease
</p> </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"> <div className="w-full flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
<img src="/forms-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" /> <img
src="/forms-made-easy.png"
className="w-[140px] sm:w-[40%] 2xl:w-[40%] object-contain"
alt="Forms Made Easy"
/>
<button <button
onClick={() => window.open("https://formsmadeasy.com/", "_blank")} 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"> 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 Try Now
</button> </button>
</div> </div>
</div> </div>
</div>
</div>
{/* Docs Made Easy */} {/* Docs Made Easy */}
<div className="bg-white rounded-lg shadow p-4 2xl:p-6"> <div className="bg-white rounded-lg shadow p-4 2xl:p-6">
<div className=""> <p className="text-sm font-semibold text-left mb-4 2xl:text-lg">
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
Generate Documents (Invoices, Reports, Quotes) Generate Documents (Invoices, Reports, Quotes)
</p> </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"> <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-[40%] 2xl:w-[60%]" alt="Docs Made Easy" /> <img
src="/docs-made-easy.png"
className="w-[140px] sm:w-[40%] 2xl:w-[40%] object-contain"
alt="Docs Made Easy"
/>
<button <button
onClick={() => window.open("https://docsmadeasy.com/", "_blank")} 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"> 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 Try Now
</button> </button>
</div> </div>
</div> </div>
</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,8 +92,8 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
</div> </div>
{/* Page Thumbnails - DocuSign style */} {/* Page Thumbnails - DocuSign style */}
{isOpen && (
<div className="flex flex-col px-2"> <div className={`flex flex-col px-2 ${isOpen ? 'block' : 'hidden'}`}>
{document.pages?.map((page, pageIndex) => { {document.pages?.map((page, pageIndex) => {
const key = `${document.documentId}_${pageIndex}`; const key = `${document.documentId}_${pageIndex}`;
const isActive = activePage === key; const isActive = activePage === key;
@ -144,7 +144,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
) )
})} })}
</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"

View File

@ -11,9 +11,9 @@ import { Bounce, toast } from "react-toastify";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { hideLoader, showLoader } from "../../store/slices/loaderSlice"; import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
import { FaCheck, FaQuestionCircle, FaDownload, FaAngleUp } from 'react-icons/fa' import { FaCheck, FaQuestionCircle, FaDownload, FaAngleUp, FaPrint } from 'react-icons/fa'
import { IoIosDocument, IoMdClose } from 'react-icons/io' import { IoIosDocument, IoMdClose } from 'react-icons/io'
import { MdKeyboardArrowDown } from 'react-icons/md' import { MdKeyboardArrowDown, MdNavigateNext } from 'react-icons/md'
import confetti from 'canvas-confetti' import confetti from 'canvas-confetti'
import dayjs from "dayjs"; import dayjs from "dayjs";
@ -33,6 +33,13 @@ import ClientAuditModal from "./ClientAuditModal";
import axiosInstance from "../../config/axiosInstance"; import axiosInstance from "../../config/axiosInstance";
import "./style.css"; import "./style.css";
import ConfirmSignModal from "./ConfirmSignModal";
import { AiOutlineAudit, AiOutlineDownload } from "react-icons/ai";
import { SlPrinter } from "react-icons/sl";
import { CgPrinter } from "react-icons/cg";
import { ImPrinter } from "react-icons/im";
import { RiFileHistoryFill } from "react-icons/ri";
import { IoDocumentsOutline } from "react-icons/io5";
interface RecipientType { interface RecipientType {
id: string; id: string;
@ -212,7 +219,7 @@ const SignDocument = () => {
const [isFields, setIsFields] = useState<boolean>(true); const [isFields, setIsFields] = useState<boolean>(true);
const [isFitDd, setIsFitDd] = useState<boolean>(false) const [isFitDd, setIsFitDd] = useState<boolean>(false)
const [selFitIndx, setSelFitIndx] = useState<number>(2); const [selFitIndx, setSelFitIndx] = useState<number>(windowWidth > 638 ? 3 : 0);
const globalLoader = useSelector((state: RootState) => state.loader) const globalLoader = useSelector((state: RootState) => state.loader)
@ -228,9 +235,24 @@ const SignDocument = () => {
const [provideTags, setProvideTags] = useState(false) const [provideTags, setProvideTags] = useState(false)
const [showCompletionScreen, setShowCompletionScreen] = useState(false) const [showCompletionScreen, setShowCompletionScreen] = useState(false)
const [zoomScale, setZoomScale] = useState<number>(100) const [zoomScale, setZoomScale] = useState<number>(windowWidth > 638 ? 150 : 100)
const scaleFactor: number = (zoomScale ?? 100) / 100; const scaleFactor: number = (zoomScale ?? 100) / 100;
// Handle Confirm Sign Modal - Start
// Add state
const [confirmSignOpen, setConfirmSignOpen] = useState(false);
const [confirmSignLoading, setConfirmSignLoading] = useState(false);
// Wrap handleFormSubmit
const handleConfirmSign = async () => {
setConfirmSignLoading(true);
await handleFormSubmit();
setConfirmSignLoading(false);
setConfirmSignOpen(false);
};
// Handle Confirm Sign Modal - END
// To center the container on every zoom scale change - START // To center the container on every zoom scale change - START
useEffect(() => { useEffect(() => {
const container = document.getElementById("doc-con"); const container = document.getElementById("doc-con");
@ -597,16 +619,16 @@ const SignDocument = () => {
} }
const signRes = await axiosInstance.post(`/api/v1/sign/${recipientId}`, {}, { headers: { "ngrok-skip-browser-warning": "69420", } }) const signRes = await axiosInstance.post(`/api/v1/sign/${recipientId}`, {}, { headers: { "ngrok-skip-browser-warning": "69420", } })
dispatch(hideLoader());
if (signRes.status == 200) { if (signRes.status == 200) {
dispatch(hideLoader());
setTransparentTags(true) setTransparentTags(true)
// setShowConfetti(true) // setShowConfetti(true)
// setShowCompletionScreen(true) // setShowCompletionScreen(true)
setSelectedTag(null) setSelectedTag(null)
// navigate("/transaction-complete") // navigate("/transaction-complete")
dispatch(hideLoader());
window.location.href = `/transaction-complete/${recipientId}` window.location.href = `/transaction-complete/${recipientId}`
} }
dispatch(hideLoader());
} catch (e) { } catch (e) {
dispatch(hideLoader()); dispatch(hideLoader());
} }
@ -625,7 +647,7 @@ const SignDocument = () => {
} }
if (requiredFilled && focusElement.required == true) { if (requiredFilled && focusElement.required == true) {
handleFormSubmit(); setConfirmSignOpen(true);
return; return;
} }
@ -935,7 +957,8 @@ const SignDocument = () => {
// } // }
return ( return (
<div className={`w-full h-screen overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}> <>
<div className={`w-full overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>
{/* <TermsModal showTerms={showTerms} setShowTerms={setShowTerms} recipientId={recipientId || ""} /> */} {/* <TermsModal showTerms={showTerms} setShowTerms={setShowTerms} recipientId={recipientId || ""} /> */}
{/* {showCompletionScreen && <SignCompletionScreen />} */} {/* {showCompletionScreen && <SignCompletionScreen />} */}
@ -951,17 +974,43 @@ const SignDocument = () => {
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)] mt-0'> <div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)] mt-0'>
<div className="flex items-center"> <div className="flex items-center">
<img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" /> <img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
</div>
<div className="flex justify-center items-center text-shite gap-2">
{/* DOWNLOAD AND PRINT OPTION - START */}
{/* <div className='flex'>
<p className='p-2 cursor-pointer rounded-sm' title="Download Documents">
<FaDownload className='hover:text-slate-100 text-sm text-slate-200' />
</p>
<p className='p-2 cursor-pointer rounded-sm' title="Print">
<ImPrinter className='hover:text-slate-100 text-sm text-slate-200' />
</p>
</div>
<div className='w-full h-[20px] border-[1px] border-slate-400'></div>
<div className='flex' title="Audit">
<p className='p-2 cursor-pointer rounded-sm'>
<RiFileHistoryFill className='hover:text-slate-100 text-sm text-slate-200' />
</p>
</div> */}
{/* <div className='w-full h-[20px] border-[1px] border-slate-400 mr-2'></div> */}
{/* DOWNLOAD AND PRINT OPTION - START */}
{/* FIT - START */} {/* FIT - START */}
<div <div
className="hidden sm:block relative" className="hidden sm:block relative border border-slate-200 rounded-sm"
tabIndex={1} tabIndex={1}
onBlur={() => setIsFitDd(false)} onBlur={() => setIsFitDd(false)}
> >
<div <div
onClick={() => setIsFitDd(!isFitDd)} onClick={() => setIsFitDd(!isFitDd)}
className='text-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'> 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> <p className='tracking-wide'>{fitObj[selFitIndx].label}</p>
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} /> <MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
</div> </div>
@ -1050,7 +1099,7 @@ const SignDocument = () => {
<SignButton <SignButton
transparentTags={transparentTags} transparentTags={transparentTags}
hideTags={hideTags} hideTags={hideTags}
handleFormSubmit={handleFormSubmit} handleFormSubmit={() => setConfirmSignOpen(true)}
onDeclineClick={() => setDeclineOpen(true)} onDeclineClick={() => setDeclineOpen(true)}
onReassignClick={() => setReassignOpen(true)} onReassignClick={() => setReassignOpen(true)}
onAuditHistoryClick={() => setAuditHistoryOpen(true)} onAuditHistoryClick={() => setAuditHistoryOpen(true)}
@ -1058,29 +1107,6 @@ const SignDocument = () => {
transactionId={"1"} transactionId={"1"}
/> />
{!transparentTags && recipient?.type != "CC" && (
<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" : "translate-y-full hidden"}`}
>
<div className="py-6 text-white">
<p className="text-2xl font-semibold">All Set to Wrap This Up?</p>
<p className="text-sm">
You've completed the required fields. Review your work, then select Finish.
</p>
</div>
<button
onClick={handleFormSubmit}
disabled={!requiredFilled}
className="font-semibold text-sm px-8 py-2 bg-slate-100 rounded-md text-[var(--primary)] cursor-pointer"
>
Finish
</button>
</div>
)}
</div> </div>
{/* Right Side - ENDS */} {/* Right Side - ENDS */}
</div> </div>
@ -1126,10 +1152,11 @@ const SignDocument = () => {
</div> </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 && !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 {provideTags && recipient?.type != "CC" && <div
onClick={() => handleSetIsFields(true)} 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'}`} 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`}
> >
<IoIosDocument /> <IoIosDocument />
<p className='tracking-wide'>Tags</p> <p className='tracking-wide'>Tags</p>
@ -1143,8 +1170,10 @@ const SignDocument = () => {
<p className='tracking-wide'>Pages</p> <p className='tracking-wide'>Pages</p>
</div> </div>
{/* <p className="hidden sm:block text-xs text-white">Hi! {recipient && recipient.firstName} {recipient && recipient.lastName}</p> */} </div>
</div>} }
</>
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />} {!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
@ -1161,7 +1190,7 @@ const SignDocument = () => {
{/* Main container */} {/* Main container */}
<div <div
id="doc-con" id="doc-con"
className={`zoom-con relative ${provideTags ? (isFields && selectedTag != null ? "col-span-8" : "col-span-10") : (isFields ? "col-span-12" : "col-span-10")} sm:px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth transition-all duration-200 pb-[4rem]`} className={`col-span-12 zoom-con relative ${provideTags ? (isFields && selectedTag != null ? "sm:col-span-8" : "sm:col-span-10") : (isFields ? "sm:col-span-12" : "sm:col-span-10")} sm:px-4 bg-slate-100 h-full w-full ${zoomScale > 150 ? "overflow-x-auto" : "overflow-x-hidden"} scroll-smooth transition-all duration-200 pb-[4rem]`}
> >
@ -1294,7 +1323,8 @@ const SignDocument = () => {
</svg> </svg>
{/* NEXT Button - END */} {/* NEXT Button - END */}
</div>} </div>
}
</div> </div>
) )
}) })
@ -1308,24 +1338,36 @@ const SignDocument = () => {
} }
<div className="flex justify-center mt-[60px] w-full">
{/* <div>
<button
onClick={() => handleConfirmSign()}
className="sm:hidden mb-[40px] bg-[var(--primary)] text-white px-4 py-2 cursor-pointer rounded-sm">Finish</button>
</div> */}
</div>
</div>
</div>
{(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) && {(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) &&
<div <div
onClick={() => scrollToElement(focusElement?.navigationOrder)} onClick={() => scrollToElement(focusElement?.navigationOrder)}
className={`hidden sm:block cursor-pointer fixed ${navigationTagPos == NavigationTagPosEnum.ABOVE ? "top-[45px]" : requiredFilled ? "bottom-[82px]" : "bottom-[35px]"} right-[8%] flex flex-col items-center`}> className={`hidden sm:block cursor-pointer fixed ${navigationTagPos == NavigationTagPosEnum.ABOVE ? "top-[55px]" : requiredFilled ? "bottom-[82px]" : "bottom-[55px]"} right-[8%] flex flex-col items-center`}>
{/* Arrow pointing up */} {/* 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>} {/* {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>} */}
{/* Label box */} {/* Label box */}
<div <div
className="bg-[var(--primary)] text-white text-sm px-3 py-1 rounded-md shadow-md flex items-center gap-1"> className="bg-[var(--primary)] text-white text-sm px-6 py-3 rounded-md shadow-md flex items-center gap-1 font-medium">
<span className="capitalize">{focusElement?.type.toLowerCase()}</span> {/* <span className="capitalize">{focusElement?.type.toLowerCase()}</span> */}
<span className="capitalize">Next</span>
</div> </div>
{navigationTagPos == NavigationTagPosEnum.BELOW && <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)] rotate-[180deg]"></div>} {/* {navigationTagPos == NavigationTagPosEnum.BELOW && <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)] rotate-[180deg]"></div>} */}
</div>} </div>}
</div>
</div>
</div>
</div>
{provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag && {provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag &&
<div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}> <div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}>
@ -1375,15 +1417,16 @@ const SignDocument = () => {
setIsFields(false) 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'> 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'> <div className='py-2 pl-2 pr-2'>
<FaAngleUp className='rotate-90' /> {/* <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'> <IoDocumentsOutline />
{/* <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>P</span>
<span>A</span> <span>A</span>
<span>G</span> <span>G</span>
<span>E</span> <span>E</span>
<span>S</span> <span>S</span>
</p> </p> */}
</div> </div>
</div> </div>
@ -1415,49 +1458,120 @@ const SignDocument = () => {
// onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })} // onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })}
/> />
<ConfirmSignModal
open={confirmSignOpen}
onClose={() => setConfirmSignOpen(false)}
onConfirm={handleConfirmSign}
loading={confirmSignLoading}
recipientName={recipient ? `${recipient.firstName ?? ""} ${recipient.lastName ?? ""}`.trim() : ""}
documentCount={selectedDocuments?.length ?? 1}
/>
{!transparentTags && recipient?.type != "CC" && <>
<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")}
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
<div>
<span className="text-[var(--mainPrimaryBtn)]">{focusElement?.navigationOrder}</span>
<span> / </span>
<span>{droppedItems.length}</span>
<span> Fields</span>
</div> </div>
<FaAngleRight
onClick={() => handleTagNavClick("N")} {!transparentTags && recipient?.type != "CC" && (
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
</div> */}
<div>
{!transparentTags && focusElement != null && Object.keys(focusElement).length > 1 &&
<div <div
id="tag-navigation-btn-mb" className={`absolute z-80 left-0 bottom-0 bg-[var(--primary)] w-full flex justify-around items-center
className={`sm:hidden flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`} px-2.5 transition-all duration-500 ease-in-out
style={{ top: (focusElement.dimensions.top) + "px" }} ${requiredFilled ? "translate-y-0" : "translate-y-full hidden"}`}
onClick={() => handleTagNavClick("N")}
> >
<div className="py-5 sm:py-6 text-white">
<p className="text-lg sm:text-2xl font-semibold leading-snug">
All Set to Wrap This Up?
</p>
<p className="bg-[var(--mainPrimaryBtn)] p-2 rounded-md text-white capitalize">{requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()}</p> <p className="text-xs sm:text-sm leading-relaxed mt-2 max-w-md">
You've completed the required fields. Review your work, then select Finish.
{/* NEXT Button - END */} </p>
</div>}
</div> </div>
{/* <p className='cursor-pointer text-sm text-white bg-[var(--mainPrimaryBtn)] px-4 py-2 rounded' <button
onClick={handleFormSubmit} onClick={() => setConfirmSignOpen(true)}
>Sign</p> */} disabled={!requiredFilled}
className="font-semibold text-sm px-8 py-2 bg-slate-100 rounded-md text-[var(--primary)] cursor-pointer"
>
Finish
</button>
</div> </div>
</>} )}
{!transparentTags && recipient?.type !== "CC" && (
<footer className="fixed bottom-0 sm:static flex justify-between items-center sm:h-[30px] w-full bg-white border-t border-slate-200 z-50">
{/* Mobile Navigation */}
<div className="border-t-2 border-slate-200 py-2 sm:border-none sm:hidden justify-between flex items-center px-4 sm:py-1 w-full ">
{/* Left */}
<div className="flex items-center leading-tight gap-2">
{/* <span className="text-[11px] text-slate-500">
Powered by
</span> */}
<span className="font-normal text-sm text-slate-600">
Verify and Complete Tags
</span>
</div> </div>
{/* Center Action */}
{!transparentTags &&
focusElement &&
Object.keys(focusElement).length > 1 && (
<>
<div className="flex bg-[var(--primary)] items-center gap-1 pl-2 pr-4 py-2 rounded-sm">
<MdNavigateNext className="text-white" />
<button
id="tag-navigation-btn-mb"
onClick={() => handleTagNavClick("N")}
className="rounded-sm text-white text-sm font-normal shadow-md active:scale-[0.98] transition-all duration-200"
>
{requiredFilled && focusElement.required
? "Submit"
: `${focusElement.type}`}
</button>
</div>
</>
)}
{/* Right */}
{/* <div className="text-right">
<p className="text-[11px] text-slate-400">
© 2026
</p>
</div> */}
</div>
{/* Desktop Footer */}
<div className="hidden sm:flex w-full items-center justify-between px-6 py-3 ">
{/* Left */}
<div className="flex items-center gap-2 text-sm text-slate-500">
<span>Powered by</span>
<span className="font-semibold text-[var(--mainPrimaryBtn)]">
Sign Made Easy
</span>
</div>
{/* Center */}
<div className="flex flex-col items-center gap-1">
{/* <select
className="px-3 py-1.5 rounded-lg border border-slate-200 text-sm outline-none focus:border-[var(--mainPrimaryBtn)] bg-white text-slate-600"
>
<option>English</option>
<option>Hindi</option>
<option>Spanish</option>
</select> */}
<p className="hidden sm:block text-xs text-slate-400 text-center">
By signing you agree to SME Terms & Conditions
</p>
</div>
{/* Right */}
<div className="hidden sm:block text-sm text-slate-500">
© 2026 SME. All rights reserved.
</div>
</div>
</footer>
)}
</>
) )
} }

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,48 +194,26 @@ 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="
absolute
left-0
top-full
mt-1
min-w-[180px]
bg-white
border
border-slate-200
rounded-md
shadow-lg
overflow-hidden
z-50
opacity-0
invisible
translate-y-[-5px]
scale-[0.98]
transition-all
duration-200
ease-out
group-hover:opacity-100
group-hover:visible
group-hover:translate-y-0
group-hover:scale-100
"
>
<button <button
onClick={() => fileInputRef.current?.click()} onClick={() => {
fileInputRef.current?.click();
setUploadDropdownOpen(false);
}}
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 cursor-pointer flex gap-2 items-center" 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)]' /> <FaDesktop className='text-[var(--primary)]' />
@ -225,13 +221,17 @@ const StartNow = () => {
</button> </button>
<button <button
onClick={() => navigate("/templates")} onClick={() => {
navigate("/templates");
setUploadDropdownOpen(false);
}}
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" 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)]' /> <HiTemplate className='text-[var(--primary)]' />
<span>Use a Template</span> <span>Use a Template</span>
</button> </button>
</div> </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

@ -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;
copiedTags = [{ ...tempCopy, dimensions: { ...tempCopy.dimensions, top: tempCopy.dimensions.top + 10, left: tempCopy.dimensions.left + 10 } }]
console.log("from Copy copiedTags", copiedTags)
break;
case "Ctrl+v":
console.log(selectedDoc.key, "keyss")
let updatedKey = droppedItems[selectedDoc.key]
updatedKey.push(...copiedTags)
console.log("copiedTags", copiedTags) const { id, ...tagWithoutId } = tag as any;
console.log("updatedKey", updatedKey) 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({ dispatch(setDroppedItems({
transactionKey, transactionKey,
items: { items: {
...droppedItems, ...currentDroppedItems,
[selectedDoc.key]: updatedKey [selectedDoc.key]: [...existingTags, ...copiedTags.map(t => ({ ...t, update: true }))]
} }
})); }));
dispatch(incrementCounter(transactionKey));
};
// Handle Ctrl+V // Replace the hotkeys useEffect with a direct handler function
break; const handleHotKey = (hotKey: string, docKey: string) => {
case "Ctrl+x": const currentDroppedItems = droppedItemsRef.current;
// Handle Ctrl+V const currentSelectedTag = selectedTagRef.current;
break; const docCon = document.getElementById('doc-con');
case "Ctrl+z":
// Handle Ctrl+V
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", () => { switch (hotKey) {
tag.classList.remove("border-2", "border-sky-800") case 'Ctrl+c': {
tag.removeAttribute("all-select-group") handleCopy();
}) break;
// docCon.addEventListener("focusout", () => { }
// console.log("focus out", tag) case 'Ctrl+v': {
// tag.classList.remove("border-2", "border-slate-800") handlePaste();
// tag.removeAttribute("all-select-group") 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');
}); });
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;
// Using setTimeout to let DOM settle after zoom change
setTimeout(() => {
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
container.scrollTo({ container.scrollTo({
top: newScrollTop, top: newScrollTop,
left: newScrollLeft, left: newScrollLeft,
behavior: "instant" as ScrollBehavior 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
"
>
<div
className="
bg-white
h-[calc(100vh-100px)]
rounded-2xl
border
border-slate-200
overflow-hidden
flex
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 && ( tab.type == page1 && (
<button <button
key={tab.id} key={tab.id}
onClick={() => handleMainTabClick(tab.id)} onClick={() => handleMainTabClick(tab.id)}
className={`cursor-pointer relative px-4 pb-3 text-sm font-medium transition-all duration-200 className={`
relative
whitespace-nowrap
px-4
py-3
text-sm
font-medium
rounded-t-xl
transition-all
duration-200
cursor-pointer
${activeTab == tab.id ${activeTab == tab.id
? "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" ? `
: "text-gray-600 hover:text-[#34BEFF]" text-[#34BEFF]
}`} bg-[#EAF8FF]
`
: `
text-slate-600
hover:text-[#34BEFF]
hover:bg-slate-50
`
}
`}
> >
{tab.label} {tab.label}
{activeTab == tab.id && (
<span
className="
absolute
bottom-0
left-0
w-full
h-[2px]
bg-[#34BEFF]
"
/>
)}
</button> </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]
xl:w-[320px]
border-r
border-slate-200
flex-col
bg-white
"
>
{/* 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>
<div>
<p className="font-semibold">{user.name}</p> <div className="min-w-0">
<p className="text-sm text-slate-600 w-[24ch] overflow-hidden text-ellipsis whitespace-nowrap"> <p className="font-semibold text-slate-900 truncate">
{user.name}
</p>
<p className="text-sm text-slate-500 truncate">
{user.email} {user.email}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div className="sticky top-6 z-20 px-6 pt-2"> {/* Navigation */}
<div className="flex-1 overflow-y-auto no-scrollbar px-4 py-5">
<AsideSection <AsideSection
activeSubSection={activeSubSection} activeSubSection={activeSubSection}
onSubSectionClick={handleSubSectionClick} onSubSectionClick={handleSubSectionClick}
subSections={getSubSections(activeTab)} subSections={getSubSections(activeTab)}
mainTabTitle={tabs.find(t => t.id == activeTab)?.label || ""} mainTabTitle={
tabs.find((t) => t.id == activeTab)?.label || ""
}
/> />
</div> </div>
</section> </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 */}
{activeTab == "userProfile" && (
<div
className="
lg:hidden
sticky
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
"
>
{getSubSections(activeTab).map((sub) => ( {getSubSections(activeTab).map((sub) => (
<button <button
key={sub.id} key={sub.id}
onClick={() => handleSubSectionClick(sub.id)} onClick={() =>
className={`whitespace-nowrap text-sm font-medium px-2 py-2 rounded-md cursor-pointer handleSubSectionClick(sub.id)
${activeSubSection == sub.id ? "bg-blue-100 text-blue-600" : "text-gray-600"} }
className={`
whitespace-nowrap
px-4
py-2
rounded-xl
text-sm
font-medium
transition-all
duration-200
border
${activeSubSection == sub.id
? `
bg-[#EAF8FF]
text-[#34BEFF]
border-[#B8EAFF]
`
: `
bg-white
text-slate-600
border-slate-200
`
}
`} `}
> >
{sub.label} {sub.label}
</button> </button>
))} ))}
</div> </div>
</section> </div>
)} )}
{/* Main Content */} {/* Scroll Content */}
<div <div
ref={scrollContainerRef} ref={scrollContainerRef}
className="flex-1 min-w-0 overflow-y-auto no-scrollbar" 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()} {renderContent()}
</div> </div>
</div> </div>
</main>
</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> {/* 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>
<p className="text-sm text-slate-500 mt-1 max-w-xl">
Basic information like your name, email address, and account details.
</p>
</div> </div>
<button
onClick={() => setOpenEditModal(true)}
<div className="bg-slate-50 w-full px-3 py-2 rounded-lg"> className="
<p className="text-sm font-semibold">Information</p> inline-flex
items-center
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>
{/* Section Tag */}
{/* <div className="bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 mb-5">
<p className="text-sm font-semibold text-slate-700">
Information
</p>
</div> */}
{/* Info Card */}
<div className="rounded-2xl border border-slate-200 overflow-hidden divide-y divide-slate-100">
<div className="w-full border-gray-200 rounded-md bg-white font-sans"> {infoItems.map((item, index) => (
<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"> <div
<span className="text-gray-600">Full Name :</span> key={index}
<span className="font-medium text-black">{userData.name}</span> 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 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>
<span className="font-medium text-black">{userData.email}</span>
</div>
<div className="sm:flex sm:flex-col md:grid md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] px-4 py-4">
<span className="text-gray-600">Account Name :</span>
<span className="font-medium text-black">{userData.account.name}</span>
</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">