diff --git a/src/components/CommonNav.tsx b/src/components/CommonNav.tsx index 59354d1..4a2d5a4 100644 --- a/src/components/CommonNav.tsx +++ b/src/components/CommonNav.tsx @@ -451,7 +451,7 @@ const CommonNav: React.FC = ({ {/* */} -
+ {/*
)} -
+ */}
@@ -118,49 +118,77 @@ const CompletionScreen: React.FC = ({

Boost Efficiency with Our Apps

-
- {/* Forms Made Easy */} -
-
-

- Generate Forms with Ease -

-
-
- Docs Made Easy - -
-
-
+
-
+ {/* Forms Made Easy */} +
+

+ Generate Forms with Ease +

- {/* Docs Made Easy */} -
-
-

- Generate Documents (Invoices, Reports, Quotes) -

-
-
- Docs Made Easy - -
-
-
+
+ Forms Made Easy -
+ +
+
+ + {/* Docs Made Easy */} +
+

+ Generate Documents (Invoices, Reports, Quotes) +

+ +
+ Docs Made Easy + + +
+
+ +
-
diff --git a/src/components/client/ConfirmSignModal.tsx b/src/components/client/ConfirmSignModal.tsx new file mode 100644 index 0000000..25e1699 --- /dev/null +++ b/src/components/client/ConfirmSignModal.tsx @@ -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 ( +
+
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 */} +
+
+
+ +
+
+

+ Confirm Signature +

+

+ You're about to sign this document +

+
+
+ +
+ + {/* Body */} +
+ + {/* Summary */} +
+ {recipientName && ( +
+ Signing as + {recipientName} +
+ )} +
+ Documents + {documentCount} document{documentCount > 1 ? "s" : ""} +
+
+ Status + Ready to sign +
+
+ + {/* Legal notice */} +
+ +

+ By confirming, you agree that your electronic signature is legally binding and equivalent to a handwritten signature. +

+
+
+ + {/* Footer */} +
+ + +
+
+
+ ); +}; + +export default ConfirmSignModal; \ No newline at end of file diff --git a/src/components/client/DocumentPages.tsx b/src/components/client/DocumentPages.tsx index 8bcd1fa..b25ff7b 100644 --- a/src/components/client/DocumentPages.tsx +++ b/src/components/client/DocumentPages.tsx @@ -65,7 +65,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl return ( -
+
{selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((document, indx) => { const isOpen = dropDownTogglePage === 'pageDropDown' + indx; @@ -92,59 +92,59 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
{/* Page Thumbnails - DocuSign style */} - {isOpen && ( -
- {document.pages?.map((page, pageIndex) => { - const key = `${document.documentId}_${pageIndex}`; - const isActive = activePage === key; - return ( -
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 */} -
+ {document.pages?.map((page, pageIndex) => { + const key = `${document.documentId}_${pageIndex}`; + const isActive = activePage === key; + + return ( +
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 */} +
- {`Page + > + {`Page - {/* Page number + dots */} -
- {pageIndex + 1} - { - // <> - // - // - } -
+ {/* Page number + dots */} +
+ {pageIndex + 1} + { + // <> + // + // + } +
- {documentPagesTagsMapping[document.documentId]?.[pageIndex] && ( -
- {/* + {documentPagesTagsMapping[document.documentId]?.[pageIndex] && ( +
+ {/* {documentPagesTagsMapping[document.documentId][pageIndex][0]} */} - -
- )} + +
+ )} -
- ) - })} -
- )} +
+ ) + })} +
+
) diff --git a/src/components/client/SignButton.tsx b/src/components/client/SignButton.tsx index 88ff9a4..1002abe 100644 --- a/src/components/client/SignButton.tsx +++ b/src/components/client/SignButton.tsx @@ -58,16 +58,15 @@ export default function SignButton({ {/* Sign Button */} {/* */} - {/* Three Dots Dropdown Trigger */} + {/* Three Dots Dropdown Trigger */} - - )} - + setConfirmSignOpen(true)} + onDeclineClick={() => setDeclineOpen(true)} + onReassignClick={() => setReassignOpen(true)} + onAuditHistoryClick={() => setAuditHistoryOpen(true)} + showDecAndReassign={recipient?.type != "CC"} + transactionId={"1"} + /> + + {/* Right Side - ENDS */} - {/* Right Side - ENDS */} + + + {/* Horizontal Loader */} + {horizontalFormLoader.totalRequired != null && !transparentTags && ( +
+
+
+ )}
- - {/* Horizontal Loader */} - {horizontalFormLoader.totalRequired != null && !transparentTags && ( -
-
-
- )} -
- - {/* Nav Two - Options - End */} + {/* Nav Two - Options - End */} - {/* Document Container Start */} -
- - {/* Left Side pages start */} - {/*
*/} -
- + {/* Document Container Start */} +
+ {/* Left Side pages start */} + {/*
*/}
{ - setIsPages(false) - setIsFields(true) - }} - className='absolute sm:hidden top-[5px] right-[10px] bg-white rounded-full text-2xl z-50'> - -
+ id="tags-list-client-side" + className={`${isPages ? "block" : "hidden"} bg-slate-100 sm:block absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-full overflow-y-auto ${recipient?.type != "CC" ? "bg-slate-100" : ""} ${!provideTags && isFields ? "hidden" : ""}`}> - {provideTags && !transparentTags &&
- {provideTags && recipient?.type != "CC" &&
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'}`} - > - -

Tags

-
} -
handleSetIsFields(provideTags ? false : !isFields)} - > - -

Pages

+ onClick={() => { + setIsPages(false) + setIsFields(true) + }} + className='absolute sm:hidden top-[5px] right-[10px] bg-white rounded-full text-2xl z-50'> +
- {/*

Hi! {recipient && recipient.firstName} {recipient && recipient.lastName}

*/} -
} - {!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && } - - - -
- {/* Pages - END */} - - {/* Main container */} -
- - -
-
- - { - selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => { - return ( -
-
-

{doc.name}

-
- -
- { - doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => { - const key = `${doc.documentId}_${page.index}`; - // const tagJsonData = [...droppedItems]; - const currDocTags = droppedItems - .map((val, index) => ({ ...val, originalIndex: index })) // add original index - .filter(val => val.pageIndex === page.index); - let scale: { x: number | null, y: number | null } = { x: null, y: null } - return ( -
handleDrop(e, doc.documentId, doc.documentId, page.index)} - onDragOver={(e) => handleDragOver(e)} - className='drop-page-in shadow-md relative' - > - - (imageRefs.current[page.index] = el)} - onLoad={(e) => { - const el = e.currentTarget; - setImageSizes((prev) => ({ - ...prev, - [page.index]: { - width: el.offsetWidth, - height: el.offsetHeight, - }, - })); - }} - draggable="false" - // onLoad={(imgElem) => { - // setPageScales((prev) => ({ - // ...prev, - // [page.index]: { - // x: imgElem.clientWidth / page.width, - // y: imgElem.clientHeight / page.height, - // }, - // })); - // }} - width={page.width} - height={page.height} - src={page.src} - loading='lazy' - /> - - {!hideTags && <> - - } - - {/* Side details of documents Pages and document Name - START */} - < p className='hidden sm:block absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-50px] text-slate-600' > - page {indx + 1} of {doc.pages && doc.pages.length} -

- {/* Side details of documents Pages and document Name - END */} - - - {!transparentTags && focusElement != null && focusElement && Object.keys(focusElement).length > 1 && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index && -
handleTagNavClick("N")} - > - - - - {/* */} - - - {requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()} - - - - {/* NEXT Button - END */} -
} -
- ) - }) - } - -
- -
- ) - }) - } - - - {(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) && -
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`}> - {/* Arrow pointing up */} - {navigationTagPos == NavigationTagPosEnum.ABOVE &&
} - - {/* Label box */} -
- {focusElement?.type.toLowerCase()} -
- {navigationTagPos == NavigationTagPosEnum.BELOW &&
} + <> + {provideTags && !transparentTags &&
+ {provideTags && recipient?.type != "CC" &&
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`} + > + +

Tags

} -
-
-
+
handleSetIsFields(provideTags ? false : !isFields)} + > + +

Pages

+
- {provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag && -
- {/* Right Side Attributes Section if providedTags - START */} - + } + + + + {!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && } + + - {/* Right Side Attributes Section if providedTags - END */} +
- } + {/* Pages - END */} + + {/* Main container */} +
150 ? "overflow-x-auto" : "overflow-x-hidden"} scroll-smooth transition-all duration-200 pb-[4rem]`} + > + + +
+
+ + { + selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => { + return ( +
+
+

{doc.name}

+
+ +
+ { + doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => { + const key = `${doc.documentId}_${page.index}`; + // const tagJsonData = [...droppedItems]; + const currDocTags = droppedItems + .map((val, index) => ({ ...val, originalIndex: index })) // add original index + .filter(val => val.pageIndex === page.index); + let scale: { x: number | null, y: number | null } = { x: null, y: null } + return ( +
handleDrop(e, doc.documentId, doc.documentId, page.index)} + onDragOver={(e) => handleDragOver(e)} + className='drop-page-in shadow-md relative' + > + + (imageRefs.current[page.index] = el)} + onLoad={(e) => { + const el = e.currentTarget; + setImageSizes((prev) => ({ + ...prev, + [page.index]: { + width: el.offsetWidth, + height: el.offsetHeight, + }, + })); + }} + draggable="false" + // onLoad={(imgElem) => { + // setPageScales((prev) => ({ + // ...prev, + // [page.index]: { + // x: imgElem.clientWidth / page.width, + // y: imgElem.clientHeight / page.height, + // }, + // })); + // }} + width={page.width} + height={page.height} + src={page.src} + loading='lazy' + /> + + {!hideTags && <> + + } + + {/* Side details of documents Pages and document Name - START */} +

+ page {indx + 1} of {doc.pages && doc.pages.length} +

+ {/* Side details of documents Pages and document Name - END */} + + + {!transparentTags && focusElement != null && focusElement && Object.keys(focusElement).length > 1 && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index && +
handleTagNavClick("N")} + > + + + + {/* */} + + + {requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()} + + + + {/* NEXT Button - END */} +
+ } +
+ ) + }) + } + +
+ +
+ ) + }) + } + + + +
+ {/*
+ +
*/} +
+ + + +
+
+ {(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) && +
scrollToElement(focusElement?.navigationOrder)} + 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 */} + {/* {navigationTagPos == NavigationTagPosEnum.ABOVE &&
} */} + + {/* Label box */} +
+ {/* {focusElement?.type.toLowerCase()} */} + Next +
+ {/* {navigationTagPos == NavigationTagPosEnum.BELOW &&
} */} +
} +
+ + + {provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag && +
+ {/* Right Side Attributes Section if providedTags - START */} + + {/* Right Side Attributes Section if providedTags - END */} +
+ } +
+ {/* Document Container End */} +
- {/* Document Container End */} -
- - {/* {showCompletionScreen && recSignStatus == "COMPLETED" && } + {/* {showCompletionScreen && recSignStatus == "COMPLETED" && } {showCompletionScreen && recSignStatus == "DECLINED" && } */} -
- {recipient && - } -
+
+ {recipient && + } +
-
- -
+
+ +
- {/* + {/* */} - {globalLoader.isLoading && } + {globalLoader.isLoading && } -
{ - setIsPages(true) - setIsFields(false) - }} - className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] flex items-center text-white rounded-tr-md rounded-br-md cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'> -
- -

- P - A - G - E - S -

+
{ + setIsPages(true) + setIsFields(false) + }} + className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] flex items-center text-white rounded-tr-md rounded-br-md cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'> +
+ {/* */} + + {/*

+ P + A + G + E + S +

*/} +
+ + + { + !transparentTags && selectedDocuments != null && !showCompletionScreen && !signerConsent.consent && recipient?.type != "CC" && + handleOnConsent()} + // onDecline={() => console.log("User declined")} + /> + } + + setDeclineOpen(false)} + onConfirm={({ reason, comments }) => handleDeclineConfirm(reason, comments)} + /> + + setReassignOpen(false)} + onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })} + /> + + setAuditHistoryOpen(false)} + recipientId={recipientId} + // onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })} + /> + + setConfirmSignOpen(false)} + onConfirm={handleConfirmSign} + loading={confirmSignLoading} + recipientName={recipient ? `${recipient.firstName ?? ""} ${recipient.lastName ?? ""}`.trim() : ""} + documentCount={selectedDocuments?.length ?? 1} + /> +
+ {!transparentTags && recipient?.type != "CC" && ( +
+
+

+ All Set to Wrap This Up? +

- { - !transparentTags && selectedDocuments != null && !showCompletionScreen && !signerConsent.consent && recipient?.type != "CC" && - handleOnConsent()} - // onDecline={() => console.log("User declined")} - /> - } - - setDeclineOpen(false)} - onConfirm={({ reason, comments }) => handleDeclineConfirm(reason, comments)} - /> - - setReassignOpen(false)} - onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })} - /> - - setAuditHistoryOpen(false)} - recipientId={recipientId} - // onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })} - /> - - - - {!transparentTags && recipient?.type != "CC" && <> -
-

By Signing I agree Sign Made Easy Terms & Conditions.

- {/*
- handleTagNavClick("P")} - className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" /> -
- {focusElement?.navigationOrder} - / - {droppedItems.length} - Fields -
- handleTagNavClick("N")} - className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" /> -
*/} -
- {!transparentTags && focusElement != null && Object.keys(focusElement).length > 1 && -
handleTagNavClick("N")} - > - -

{requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()}

- - {/* NEXT Button - END */} -
} +

+ You've completed the required fields. Review your work, then select Finish. +

- {/*

Sign

*/} - - +
- } + )} + {!transparentTags && recipient?.type !== "CC" && ( +
-
+ {/* Mobile Navigation */} +
+ + {/* Left */} +
+ {/* + Powered by + */} + + Verify and Complete Tags + +
+ + {/* Center Action */} + {!transparentTags && + focusElement && + Object.keys(focusElement).length > 1 && ( + <> +
+ + +
+ + )} + + {/* Right */} + {/*
+

+ © 2026 +

+
*/} +
+ + {/* Desktop Footer */} +
+ + {/* Left */} +
+ Powered by + + Sign Made Easy + +
+ + {/* Center */} +
+ {/* */} + +

+ By signing you agree to SME Terms & Conditions +

+
+ + {/* Right */} +
+ © 2026 SME. All rights reserved. +
+
+ + )} + ) } diff --git a/src/components/client/style.css b/src/components/client/style.css index c38868e..9065c43 100644 --- a/src/components/client/style.css +++ b/src/components/client/style.css @@ -18,4 +18,21 @@ font-family: 'Allura'; src: url('/fonts/Allura/Allura-Regular.ttf') format('truetype'); } -/* Adding SELECT SIGNATURE FONTS - ENDS */ \ No newline at end of file + +/* 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; +} + + + diff --git a/src/components/home/StartNow.tsx b/src/components/home/StartNow.tsx index b1eb641..159522d 100644 --- a/src/components/home/StartNow.tsx +++ b/src/components/home/StartNow.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { handleMultipleTransactionThunk, resetDocuments, uploadDocuments } from '../../store/slices/documentSlice'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; @@ -30,6 +30,24 @@ const StartNow = () => { const dispatch = useDispatch(); const navigate = useNavigate(); + + const [uploadDropdownOpen, setUploadDropdownOpen] = useState(false); + const dropdownRef = useRef(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 accountId = userData.account?.id; @@ -176,62 +194,44 @@ const StartNow = () => { */} -
- + {/* ❌ Remove the old "relative inline-block group" div entirely, replace with: */} +
- {/* Dropdown */} -
+ - 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 - " - > - - - -
+ +
+ )}
{filesError && {filesError}} diff --git a/src/components/home/TransactionChoiceModal.tsx b/src/components/home/TransactionChoiceModal.tsx index aaf28ea..6a2c861 100644 --- a/src/components/home/TransactionChoiceModal.tsx +++ b/src/components/home/TransactionChoiceModal.tsx @@ -11,7 +11,7 @@ export default function TransactionChoiceModal({ open, onClose, onCreateTransact if (!open) return null; return ( -
+
diff --git a/src/components/process/processStepOne/ProcessStepOne.tsx b/src/components/process/processStepOne/ProcessStepOne.tsx index dbc572c..9ca4437 100644 --- a/src/components/process/processStepOne/ProcessStepOne.tsx +++ b/src/components/process/processStepOne/ProcessStepOne.tsx @@ -543,7 +543,6 @@ const ProcessStepOne = () => { } - {/* HERE WE AREEEEEEEE */} {selectedRecipients.length > 1 &&
{ Set signing order
} - {/* HERE WE AREEEEEEEE */}
diff --git a/src/components/process/processStepOne/RecipientCards.tsx b/src/components/process/processStepOne/RecipientCards.tsx index 6ba3161..638fb0d 100644 --- a/src/components/process/processStepOne/RecipientCards.tsx +++ b/src/components/process/processStepOne/RecipientCards.tsx @@ -67,7 +67,7 @@ const RecipientCards = ({ let { accountId, transactionId } = useParams<{ accountId: string; transactionId: string }>(); const dispatch = useDispatch(); - const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder) + const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder) if (!transactionId) { transactionId = templateId; @@ -256,43 +256,6 @@ const RecipientCards = ({ let newActiveRecRo = 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) { console.log(activeIndex, overIndex, "283 1") newActiveRecRo = activeRecRo > 1 ? overRecRo : 1 @@ -304,9 +267,6 @@ const RecipientCards = ({ newOverRecRo = overRecRo > 1 ? overRecRo : 1 } - - - if (activeRecRo < overRecRo && activeIndex < overIndex) { console.log(activeIndex, overIndex, "283 4") newActiveRecRo = overRecRo > 1 ? overRecRo : 2 @@ -318,8 +278,6 @@ const RecipientCards = ({ newOverRecRo = overRecRo > 1 ? overRecRo : 1 } - - if (activeRecRo == activeRecRo && activeIndex < overIndex) { newActiveRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2 newOverRecRo = overRecRo > 1 ? overRecRo : 1 @@ -329,51 +287,13 @@ const RecipientCards = ({ 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(reorderAllRecipientAscOrder()); }; return (
{recipients.length > 0 ? ( <> - {/* {recipients.length > 1 && ( -
-

- Recipients ({recipients.length}) -

- -
- )} */} @@ -399,7 +319,7 @@ const RecipientCards = ({ <> {/* Main Content Section */} -
+
{/* Order Number */}
@@ -527,7 +447,7 @@ const RecipientCards = ({ {/* Name and Email Fields */}
{/* Mobile: Stack vertically, Tablet+: Side by side */} -
+
@@ -558,7 +478,7 @@ const RecipientCards = ({ ? false : !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" > @@ -582,6 +502,8 @@ const RecipientCards = ({ {renderEditableField(recipient, "hostEmail", "Host Email", "", "email")}
+
+
)} @@ -632,8 +554,6 @@ const RecipientCards = ({
- -
@@ -689,44 +609,6 @@ export default RecipientCards; -// -// r.id || r.uuid)} -// strategy={verticalListSortingStrategy} -// > -//
-// {recipients.map((recipient, idx) => ( -// -// {/* 🔹 your existing recipient card content goes here */} -// -// ))} -//
-//
-//
- - - - - - - - - - - - - - - - - - - diff --git a/src/components/process/processStepOne/SortableRecipient.tsx b/src/components/process/processStepOne/SortableRecipient.tsx index df867af..d0cf29e 100644 --- a/src/components/process/processStepOne/SortableRecipient.tsx +++ b/src/components/process/processStepOne/SortableRecipient.tsx @@ -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" > - {signingOrder && } + {signingOrder && }
} diff --git a/src/components/process/processStepTwo/DocumentContainer.tsx b/src/components/process/processStepTwo/DocumentContainer.tsx index 0c8af89..4c494ed 100644 --- a/src/components/process/processStepTwo/DocumentContainer.tsx +++ b/src/components/process/processStepTwo/DocumentContainer.tsx @@ -53,6 +53,7 @@ interface DocumentContainerProps { aId?: string; tId?: string; selectedRecipientIndx?: number; + onHistoryChange?: (canUndo: boolean, canRedo: boolean) => void; } let copiedTags: DroppedItem[] = [] @@ -63,10 +64,14 @@ const fetchedTags: string[] = [] // 👇 Create an interface for the exposed methods export interface DocumentContainerRef { handleTransactionProcess: (isSave?: boolean, isNavigate?: boolean, navigateTo?: string) => void; + handleUndo: () => void; + handleRedo: () => void; + handleCopy: () => void; + handlePaste: () => void; } const DocumentContainer = forwardRef( - ({ 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; @@ -94,7 +99,7 @@ const DocumentContainer = forwardRef state.transaction.transactionId) const navigate = useNavigate(); const childRef = useRef(null); - const tagUndoRef = useRef([]); + // const tagUndoRef = useRef([]); const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({}); @@ -152,7 +157,7 @@ const DocumentContainer = forwardRef(hotKeysObjInitial); + // const [hotKeysObj, setHotKeysObj] = useState(hotKeysObjInitial); useEffect(() => { selectedRecipients.length == 0 && dispatch(fetchRecipients({ accountId, transactionId })); @@ -214,6 +219,79 @@ const DocumentContainer = forwardRef(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([]); + const cursorRef = useRef(-1); + const isUndoRedoRef = useRef(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 tagsData: Record = {}; @@ -340,6 +418,10 @@ const DocumentContainer = forwardRef ({ handleTransactionProcess, + handleUndo, + handleRedo, + handleCopy, + handlePaste })); // Save Tags - END @@ -478,124 +560,148 @@ const DocumentContainer = forwardRef { - // const tagIndex = key in droppedItems ? droppedItems[key].length : 0; - // setSelectedTag({ key, index: tagIndex }) - // }, 1000) }; // HANDLE DROP - END useEffect(() => { - if (!hotKeysObj.isActive) return - if ((!selectedTag?.key || `${selectedTag?.key}` in droppedItems && droppedItems[selectedTag?.key].length == 0)) return; + 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) { - case "Ctrl+c": - 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) + const tag = currentDroppedItems[currentSelectedTag.key]?.[currentSelectedTag.index]; + if (!tag) return; + + const { id, ...tagWithoutId } = tag as any; + 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; - case "Ctrl+v": - console.log(selectedDoc.key, "keyss") - let updatedKey = droppedItems[selectedDoc.key] - updatedKey.push(...copiedTags) - - console.log("copiedTags", copiedTags) - console.log("updatedKey", updatedKey) - - dispatch(setDroppedItems({ - transactionKey, - items: { - ...droppedItems, - [selectedDoc.key]: updatedKey - } - })); - - // Handle Ctrl+V + } + case 'Ctrl+v': { + handlePaste(); break; - case "Ctrl+x": - // Handle Ctrl+V - break; - 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", () => { - 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") - // }) + } + 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) - }) - - if (allselectedTags.length == 0) childRef.current.handleRemoveTag(selectedTag?.key, selectedTag?.index) + // One-time click to deselect all + const deselect = () => { + docCon?.querySelectorAll('[all-select-group]').forEach(tag => { + tag.classList.remove('border-2', 'border-sky-700'); + tag.removeAttribute('all-select-group'); + }); + docCon?.removeEventListener('click', deselect); + }; + docCon?.addEventListener('click', deselect); 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: break; } - }, [hotKeysObj]) + }; const handleDocumentFocus = (key: string) => { - // const id = key.replace("_", ""); - const targetCon = document.getElementById('doc-con'); 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(); - const ctrl = event.ctrlKey; - const alt = event.altKey; - const metaKey = event.metaKey; + const ctrl = event.ctrlKey || event.metaKey; const shift = event.shiftKey; 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) { - hotKey = "Ctrl+" + pressedKey; - } 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"); - } + docKeyListenerRef.current = handler; + targetCon.addEventListener('keydown', handler); + }; return ( @@ -696,7 +802,7 @@ const DocumentContainer = forwardRef diff --git a/src/components/process/processStepTwo/DroppedItemsTagsCon.tsx b/src/components/process/processStepTwo/DroppedItemsTagsCon.tsx index 98e99cd..c2b0b17 100644 --- a/src/components/process/processStepTwo/DroppedItemsTagsCon.tsx +++ b/src/components/process/processStepTwo/DroppedItemsTagsCon.tsx @@ -36,7 +36,7 @@ interface DroppedItemsTagsConProps { accountId: string; transactionId: string; transactionTemplateToggle: string; - tagUndoRef: React.MutableRefObject; + // tagUndoRef: React.MutableRefObject; transactionKey: string; } @@ -56,7 +56,7 @@ const DroppedItemsTagsCon = forwardRef(({ accountId, transactionId, transactionTemplateToggle, - tagUndoRef, + // tagUndoRef, transactionKey }: DroppedItemsTagsConProps, ref) => { @@ -688,7 +688,7 @@ const DroppedItemsTagsCon = forwardRef(({ }} onBlur={(e: React.FocusEvent) => { 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); }} style={{ diff --git a/src/components/process/processStepTwo/LeftSideBar.tsx b/src/components/process/processStepTwo/LeftSideBar.tsx index 6402844..cd0f443 100644 --- a/src/components/process/processStepTwo/LeftSideBar.tsx +++ b/src/components/process/processStepTwo/LeftSideBar.tsx @@ -143,7 +143,7 @@ const LeftSideBar: React.FC = ({ isFields, selectedRecipientIn
+ 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) => { if (recipient.type == recipientType.CC) return null; // Skip CC recipients return (
void; onZoomChange?: React.Dispatch> + canUndo: boolean; + canRedo: boolean; + onUndo: () => void; + onRedo: () => void; + onCopy: () => void; + onPaste: () => void; } @@ -86,7 +92,13 @@ const NavTwo: React.FC = ({ aId = null, tId = null, onSendClick, - onZoomChange + onZoomChange, + canUndo, + canRedo, + onUndo, + onRedo, + onCopy, + onPaste }) => { let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; const dispatch = useDispatch(); @@ -127,7 +139,7 @@ const NavTwo: React.FC = ({ const [closePage, setClosePage] = useState(false); const [isFitDd, setIsFitDd] = useState(false) - const [selFitIndx, setSelFitIndx] = useState(0); + const [selFitIndx, setSelFitIndx] = useState(window.innerWidth > 638 ? 2 : 0); const [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 }); const navigate = useNavigate() @@ -174,6 +186,19 @@ const NavTwo: React.FC = ({ } }, [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(false); const [nav2DocToggle, setNav2DocToggle] = useState(false); @@ -331,25 +356,38 @@ const NavTwo: React.FC = ({
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' >
-

- +

+

-

- +

+

-

+

-

+

@@ -474,7 +512,7 @@ const NavTwo: React.FC = ({
+ 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) => { return (
@@ -497,7 +535,7 @@ const NavTwo: React.FC = ({
+ 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) => { return (
diff --git a/src/components/process/processStepTwo/ProcessStepTwo.tsx b/src/components/process/processStepTwo/ProcessStepTwo.tsx index 0d03ab9..202c780 100644 --- a/src/components/process/processStepTwo/ProcessStepTwo.tsx +++ b/src/components/process/processStepTwo/ProcessStepTwo.tsx @@ -22,6 +22,10 @@ const ProcessStepTwo = () => { const dispatch = useDispatch(); const navigate = useNavigate(); + const [canUndo, setCanUndo] = useState(false); + const [canRedo, setCanRedo] = useState(false); + // const documentContainerRef = useRef(null); + const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>(); let accountId = params.accountId; let transactionId = params.transactionId @@ -85,7 +89,7 @@ const ProcessStepTwo = () => { docRef.current?.handleTransactionProcess(isSave, isNavigate, navigateTo); }; - const [zoomScale, setZoomScale] = useState(100); // percentage + const [zoomScale, setZoomScale] = useState(windowWidth > 638 ? 150 : 100); // percentage // To center the container on every zoom scale change - START useEffect(() => { @@ -93,15 +97,18 @@ const ProcessStepTwo = () => { if (!container) return; const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1); - const newScrollTop = prevScrollRatio * container.scrollHeight; - const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2; - container.scrollTo({ - top: newScrollTop, - left: newScrollLeft, - behavior: "instant" as ScrollBehavior - }); + // Using setTimeout to let DOM settle after zoom change + setTimeout(() => { + const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2; + container.scrollTo({ + top: newScrollTop, + left: newScrollLeft, + behavior: "instant" as ScrollBehavior + }); + }, 50); + }, [zoomScale]); // To center the container on every zoom scale change - END @@ -116,7 +123,14 @@ const ProcessStepTwo = () => { // setIsFields={setIsFields} // isSend={isSend} // 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} + />
{ setTransactionSaving={setTransactionSaving} isSend={isSend} setIsSend={setIsSend} + onHistoryChange={(canUndo, canRedo) => { + setCanUndo(canUndo); + setCanRedo(canRedo); + }} />
diff --git a/src/components/process/processStepTwo/documentPages/DocumentPages.tsx b/src/components/process/processStepTwo/documentPages/DocumentPages.tsx index f310b2a..b6e125d 100644 --- a/src/components/process/processStepTwo/documentPages/DocumentPages.tsx +++ b/src/components/process/processStepTwo/documentPages/DocumentPages.tsx @@ -151,7 +151,7 @@ const DocumentPages: React.FC<{
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) => (
diff --git a/src/components/process/processStepTwo/tagProperties/TagProperties.tsx b/src/components/process/processStepTwo/tagProperties/TagProperties.tsx index 928b415..c1b4e62 100644 --- a/src/components/process/processStepTwo/tagProperties/TagProperties.tsx +++ b/src/components/process/processStepTwo/tagProperties/TagProperties.tsx @@ -20,6 +20,7 @@ import CurrTimeStampAttr from "./attributes/CurrTimeStampAttr"; import ToolTipAttr from "./attributes/ToolTipAttr"; import "./style.css"; +import { RiDeleteBinLine } from "react-icons/ri"; export interface DroppedItemProps { droppedItems: DroppedItem[] @@ -175,6 +176,22 @@ const TagProperties: React.FC = ( 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(() => { if (selectedTag.decimalPlace == undefined) return; if (selectedTag.decimalPlace == decimalPlace) return; @@ -498,6 +515,17 @@ const TagProperties: React.FC = ( } + {/* Delete Tag */} +
+ +
+ {/* Delete Tag */}
diff --git a/src/components/setting/EditProfileModal.tsx b/src/components/setting/EditProfileModal.tsx new file mode 100644 index 0000000..f111e16 --- /dev/null +++ b/src/components/setting/EditProfileModal.tsx @@ -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 = ({ + 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 ( +
+
+ + {/* Header */} +
+
+

+ Edit Profile +

+ +

+ Update your profile information and password. +

+
+ + +
+ + {/* Body */} +
+ + {/* Full Name */} +
+ + + 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] + " + /> +
+ + {/* Password Section */} +
+

+ Change Password +

+ +
+ + {/* Current Password */} +
+ + +
+ 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] + " + /> + + +
+
+ + {/* New Password */} +
+ + + 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] + " + /> +
+ + {/* Confirm Password */} +
+ + + 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] + " + /> +
+ +
+
+
+ + {/* Footer */} +
+ + + + +
+
+
+ ); +}; + +export default EditProfileModal; \ No newline at end of file diff --git a/src/components/setting/Setting.tsx b/src/components/setting/Setting.tsx index cac11ea..eb1308a 100644 --- a/src/components/setting/Setting.tsx +++ b/src/components/setting/Setting.tsx @@ -411,92 +411,257 @@ const Setting = () => { return ( <> - {/* */} - {/* */} -
- {/* Tabs */} -
-
- {tabs.map((tab) => - tab.type == page1 && ( - - ) - )} +
+
+ + {/* Top Tabs */} +
+
+ {tabs.map( + (tab) => + tab.type == page1 && ( + + ) + )} +
-
- {/* Responsive Layout */} -
+ {/* Layout */} +
- {/* Sidebar (hidden on small, shown on lg) */} - {activeTab == 'userProfile' && ( -
-
-
-
-

{getInitialsUtils(user.name)}

-
-
-

{user.name}

-

- {user.email} -

+ {/* Desktop Sidebar */} + {activeTab == "userProfile" && ( +
-
- t.id == activeTab)?.label || ""} - /> -
-
- )} + {/* Navigation */} +
+ t.id == activeTab)?.label || "" + } + /> +
+ + )} - {/* Sidebar as horizontal nav (only on mobile) */} - {activeTab == 'userProfile' && ( -
-
- {getSubSections(activeTab).map((sub) => ( - - ))} -
-
- )} + {getSubSections(activeTab).map((sub) => ( + + ))} +
+
+ )} + + {/* Scroll Content */} +
+
+ {renderContent()} +
+
+
- - ) + ); } export default Setting; \ No newline at end of file diff --git a/src/components/setting/Signature/Signature.tsx b/src/components/setting/Signature/Signature.tsx index fa6a885..217973f 100644 --- a/src/components/setting/Signature/Signature.tsx +++ b/src/components/setting/Signature/Signature.tsx @@ -160,7 +160,7 @@ const SignatureManager: React.FC = () => {
diff --git a/src/components/setting/UserProfile.tsx b/src/components/setting/UserProfile.tsx index f6da564..eba95c3 100644 --- a/src/components/setting/UserProfile.tsx +++ b/src/components/setting/UserProfile.tsx @@ -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 { id: number; @@ -19,39 +22,110 @@ interface UserProfileProps { } const UserProfile: React.FC = ({ 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 ( -
-
-

Personal Information

-

Basic info, like your name and email address.

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

+ Personal Information +

-
-

Information

-
+

+ Basic information like your name, email address, and account details. +

+
- - -
-
- Full Name : - {userData.name} +
-
- Email Address : - {userData.email} -
-
- Account Name : - {userData.account.name} + + {/* Section Tag */} + {/*
+

+ Information +

+
*/} + + {/* Info Card */} +
+ + {infoItems.map((item, index) => ( +
+

+ {item.label} +

+ +

+ {item.value || "-"} +

+
+ ))} +
- -
+ {/* Modal */} + setOpenEditModal(false)} + userData={userData} + /> + ); }; -export default UserProfile; +export default UserProfile; \ No newline at end of file diff --git a/src/components/signature/ClientSignCon.tsx b/src/components/signature/ClientSignCon.tsx index 77407d6..ac5d1e9 100644 --- a/src/components/signature/ClientSignCon.tsx +++ b/src/components/signature/ClientSignCon.tsx @@ -603,7 +603,7 @@ const ClientSignCon: React.FC = ({ }, []); return ( -
+
{/* Main Content Area */} diff --git a/src/helper/iconMap.tsx b/src/helper/iconMap.tsx index cc4d752..538a81e 100644 --- a/src/helper/iconMap.tsx +++ b/src/helper/iconMap.tsx @@ -43,10 +43,11 @@ const typeToIconMap = { , - INITIALS: - - - + INITIALS: + + + + , TEMPORAL: