From e7a7bfbed13915b578c8406a2c8169b13e936018 Mon Sep 17 00:00:00 2001 From: =bhagwanSingh <=bhagwan.r@hicglobalsolutions.com> Date: Fri, 1 Aug 2025 19:35:02 +0530 Subject: [PATCH] MultiSign --- src/components/accounts/Account.tsx | 12 +- src/components/home/Home.tsx | 6 +- .../DocumentCompletionScreen.tsx | 115 ++++++++-------- .../process/processStepOne/ProcessStepOne.tsx | 6 +- .../processStepTwo/DocumentContainer.tsx | 89 +++++++++---- .../process/processStepTwo/NavTwo.tsx | 124 ++++++++++++------ .../process/processStepTwo/ProcessStepTwo.tsx | 6 + 7 files changed, 228 insertions(+), 130 deletions(-) diff --git a/src/components/accounts/Account.tsx b/src/components/accounts/Account.tsx index 90c091b..1080df5 100644 --- a/src/components/accounts/Account.tsx +++ b/src/components/accounts/Account.tsx @@ -19,6 +19,8 @@ import { FaCalendarAlt } from 'react-icons/fa'; import TransactionDataTable from "./TransactionDataTable"; +import { useSelector } from "react-redux"; +import { RootState } from "../../store/store"; interface Transaction { id: number; @@ -121,6 +123,8 @@ const Account = () => { const [isModalOpen, setIsModalOpen] = useState(false); + const userData = useSelector((state: RootState) => state.user); + // const getUserDetails = async () => { // try { // let res = await api.get('/api/user/details') @@ -170,7 +174,7 @@ const Account = () => { }; try { - const res = await api.get('/api/dashboard', { params: payload }); + const res = await api.get(`/api/accounts/${userData.account.id}/dashboard`, { params: payload }); console.log("Dashboard Data:", res.data); const cardDataArray = mapApiDataToCards(res.data); setCards(cardDataArray); @@ -245,12 +249,12 @@ const Account = () => { return ( <> - + - - + + {isModalOpen && setIsModalOpen(false)} onInvite={handleInvite} inviteLoading={inviteLoading} accountOptions={accountsData} />} diff --git a/src/components/home/Home.tsx b/src/components/home/Home.tsx index c9769b7..67c1ff5 100644 --- a/src/components/home/Home.tsx +++ b/src/components/home/Home.tsx @@ -14,6 +14,8 @@ import { FaFilter, FaCalendarAlt } from 'react-icons/fa'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../store/store'; interface CardData { title: string; @@ -82,6 +84,8 @@ const Home = () => { const [cards, setCards] = useState([]); const [isLoading, setIsLoading] = useState(false); + const userData = useSelector((state : RootState) => state.user); + console.log("activeAccountId", activeAccountId); console.log("cards", cards); @@ -94,7 +98,7 @@ const Home = () => { }; try { - const res = await api.get('/api/dashboard', { params: payload }); + const res = await api.get(`/api/accounts/${userData.account.id}/dashboard`, { params: payload }); setDashboardData(res.data); console.log("Dashboard Data:", res.data); const cardDataArray = mapApiDataToCards(res.data); diff --git a/src/components/process/CompletionScreen/DocumentCompletionScreen.tsx b/src/components/process/CompletionScreen/DocumentCompletionScreen.tsx index aae9504..0d96c73 100644 --- a/src/components/process/CompletionScreen/DocumentCompletionScreen.tsx +++ b/src/components/process/CompletionScreen/DocumentCompletionScreen.tsx @@ -19,7 +19,8 @@ import { Link, useNavigate, useParams } from "react-router-dom"; import Loader from "../../dashboard/Loader"; import confetti from 'canvas-confetti' import { IoClose } from "react-icons/io5"; -import { IoMdHome } from "react-icons/io"; +import { IoMdClose, IoMdHome } from "react-icons/io"; +import { Bounce, toast } from "react-toastify"; type Signer = { id: number; @@ -222,6 +223,19 @@ const DocumentCompletionScreen = () => { }, []) useEffect(() => { + // toast.dismiss(); + // toast.success("Your document has been successfully sent for signature.", { + // position: "top-center", + // autoClose: 1500, + // hideProgressBar: false, + // closeOnClick: false, + // pauseOnHover: true, + // draggable: true, + // progress: undefined, + // theme: "light", + // transition: Bounce, + // }); + if (showConfetti) { const duration = 3000; const end = Date.now() + duration; @@ -233,76 +247,63 @@ const DocumentCompletionScreen = () => { } confetti({ - particleCount: 80, // more particles - angle: Math.random() * 360, // fire in random direction - spread: 220, // wider spread - startVelocity: 15, // more power - scalar: 1.2, // bigger pieces (default is 1) + particleCount: 5, // fewer particles per burst for smoothness + angle: 90, // falling straight down + spread: 30, // slight spread + startVelocity: 8, // gentle falling speed origin: { - x: Math.random(), - y: Math.random() * 0.6, + x: Math.random(), // random horizontal position across the top + y: 0, }, + scalar: 1, zIndex: 9999, }); - }, 350); // fire every 150ms + }, 50); // fire every 50ms for continuous effect + + return () => clearInterval(interval); } }, [showConfetti]); + + return ( <> - <> - { - showConfetti && ( -
- {/* */} - -
- {/* Close Icon */} - - - {/* Success Icon */} - - - {/* Message */} -

Transaction Successful

-

Your transaction has been created and saved successfully.

-
-
- ) - } -
{/* Top Banner */} +
+
+ + + +

🎉 Awesome! The document’s been sent for signature. 🎉

+
+ +
+ + + + Edit + + + {/* */} -
-
-
-
- - - - {/*
- -
*/} -
-

Awesome! Your document is sent for signature

-
-
- - - Edit - -
+ + + + {(selectedRecipients.length > 0 && selectedDocuments.length > 0) ? -
+
{/* Header */}
@@ -342,7 +343,7 @@ const DocumentCompletionScreen = () => { {/* Signers List */} - +
diff --git a/src/components/process/processStepOne/ProcessStepOne.tsx b/src/components/process/processStepOne/ProcessStepOne.tsx index a6eda9b..4d2b0b0 100644 --- a/src/components/process/processStepOne/ProcessStepOne.tsx +++ b/src/components/process/processStepOne/ProcessStepOne.tsx @@ -168,6 +168,7 @@ const ProcessStepOne = () => { const recipientsData = selectedRecipients.map((recipient, index) => ({ id: "id" in recipient ? recipient.id : null, routingOrder: "routingOrder" in recipient && recipient.routingOrder ? recipient.routingOrder : 1, + provideOwnTags: userData.email === recipient.email ? true : false, type: recipient.type, firstName: recipient.firstName, lastName: recipient.lastName, @@ -225,18 +226,15 @@ const ProcessStepOne = () => { } } else { - dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId })); + await dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId })); } dispatch(showLoader("Processing Message...")); - console.log(`1/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`) - let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { subject: emailDtls.subject, message: emailDtls.message, status: "CREATED" }) - console.log("Line 158", res.data) if (isSendNow) { dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS)); setTimeout(() => { diff --git a/src/components/process/processStepTwo/DocumentContainer.tsx b/src/components/process/processStepTwo/DocumentContainer.tsx index 2284e4d..22f1b3a 100644 --- a/src/components/process/processStepTwo/DocumentContainer.tsx +++ b/src/components/process/processStepTwo/DocumentContainer.tsx @@ -20,6 +20,7 @@ import Loader from '../../dashboard/Loader'; // import { TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice"; import { handleGroupBorderPosition } from '../../../utlity/tagGroupConUtils'; import { hideLoader, showLoader } from '../../../store/slices/loaderSlice'; +import { IsSendInterface } from './ProcessStepTwo'; export interface DroppedItemsObj { [key: string]: DroppedItem[] @@ -65,7 +66,7 @@ const DocumentContainer: React.FC = ({ isSend }) => { const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients); // const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments) const selectedDocuments = useSelector((state: RootState) => state.documents.documents) - + const [droppedItems, setDroppedItems] = useState({}); @@ -74,9 +75,12 @@ const DocumentContainer: React.FC = ({ isSend }) => { selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); }, []) + console.log(droppedItems, "line 78") + // FETCHING TAG - START useEffect(() => { if (selectedRecipients.length === 0) return; + const fetchTags = async () => { try { const requests = selectedRecipients.map((recipient) => @@ -176,17 +180,22 @@ const DocumentContainer: React.FC = ({ isSend }) => { return [recipientId, tags.map(tag => ({ ...tag, update: false }))]; }) ) + + console.log(droppedItemsUpdateFalse, "Line 182") + setDroppedItems(droppedItemsUpdateFalse); } catch (error) { console.error("Error submitting recipient tags:", error); } }; - // Save Tags - START - useEffect(() => { - if (isSend === null) return; - dispatch(showLoader("Loading ...")) + const handleTransactionProcess = async (save: boolean = false) => { + console.log(droppedItems, "called Line 189"); + + if (!save && isSend === null) return; + + !save && dispatch(showLoader("Loading ...")) let updatedTags = { ...droppedItems } @@ -291,17 +300,37 @@ const DocumentContainer: React.FC = ({ isSend }) => { tagsData = groupByRecipientId(tagsData); - sendTagsToRecipients(tagsData, accountId, transactionId); + console.log(tagsData, "Line 302"); - (async () => { - const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" }) - // const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { status: "SENT" }) - console.log(res, "Check123") - dispatch(hideLoader()) - })() + await sendTagsToRecipients(tagsData, accountId, transactionId); + + + // SENDING Transaction - START + // if (!save) { + // const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" }) + // // const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { status: "SENT" }) + // console.log(res, "Check123") + // dispatch(hideLoader()) + // } + + // SENDING Transaction - END + + } + + // Save Tags - START + useEffect(() => { + handleTransactionProcess() }, [isSend]) // Save Tags - END + // useEffect(() => { + // setTimeout(() => { + // setInterval(async () => { + // await handleTransactionProcess(true); + // }, 8000) + // }, 8000) + // }, []) + const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); // Allows the drop event to work }; @@ -1026,9 +1055,9 @@ const DocumentContainer: React.FC = ({ isSend }) => { const IconComponent = typeToIconMap[type] || ; const selectedRecipient = selectedRecipients.filter((obj, indx) => obj.id === item.recipientId); return ( - <> +
handleTagClick(key, index)} @@ -1090,20 +1119,22 @@ const DocumentContainer: React.FC = ({ isSend }) => {
- { - "group" in item && item.group && "isGroupExist" in item && !item.isGroupExist && - <> -
-
-
-
handleAddGroupItemClicked(key, item)}>
-
-
- + <> + { + "group" in item && item.group && "isGroupExist" in item && !item.isGroupExist && + <> +
+
+
+
handleAddGroupItemClicked(key, item)}>
+
+
+ - } + } + - +
) } @@ -1146,7 +1177,11 @@ const DocumentContainer: React.FC = ({ isSend }) => {
- {selectedTag && = ({ isFields, setIsFields, isSend, setIsSen const [isFitDd, setIsFitDd] = useState(false) const [selFitIndx, setSelFitIndx] = useState(0); + const [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 }); const navigate = useNavigate() const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients) const selectedDocuments = useSelector((state: RootState) => state.documents.documents) + const userDetails = useSelector((state: RootState) => state.user); useEffect(() => { - selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId })); - selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); + if (selectedRecipients.length || selectedDocuments.length) return; + (async () => { + selectedRecipients.length === 0 && await dispatch(fetchRecipients({ accountId, transactionId })); + selectedDocuments.length === 0 && await dispatch(fetchDocuments({ accountId, transactionId })); + })() }, []) + useEffect(() => { + if (selectedRecipients.length > 0) { + let minRoutingOrder: number = Infinity; + let userEmailPresent = { + present: false, + routingOrder: 0, + recipientId: 0 + }; + // Getting Minimum RoutingOrder + for (let i = 0; i < selectedRecipients.length; i++) { + const recipient = selectedRecipients[i] + if (recipient.routingOrder < minRoutingOrder) { + minRoutingOrder = recipient.routingOrder; + } + + if (recipient.email === userDetails.email) { + userEmailPresent.present = true; + userEmailPresent.routingOrder = recipient.routingOrder; + userEmailPresent.recipientId = recipient.id; + } + } + + if (userEmailPresent.present && userEmailPresent.routingOrder === minRoutingOrder) { + setSelfUserSign({ sign: true, recipientId: userEmailPresent.recipientId }) + } + } + }, [selectedRecipients]) + const [nav2RecToggle, setNav2RecToggle] = useState(false); const [nav2DocToggle, setNav2DocToggle] = useState(false); - function handleSend() { + function handleSend(isSignSend: boolean = false) { // dispatch(showLoader("Loading ...")); if (isSend === null) { setIsSend(false); } - setIsSend(!isSend) - if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) { - navigate(`/document-completion-screen/${accountId}/${transactionId}`) - } else { - navigate(`/templates`); - toast.success("Template Saved...!", { - position: "top-center", - autoClose: 1500, - hideProgressBar: false, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "light", - transition: Bounce, - }); - } + setIsSend(!isSend); + + setTimeout(() => { + if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) { + if (isSignSend) { + navigate(`./../../../sign/${selfUserSign.recipientId}`); + } + // else { + // navigate(`/document-completion-screen/${accountId}/${transactionId}`) + // } + } else { + navigate(`/templates`); + toast.success("Template Saved...!", { + position: "top-center", + autoClose: 1500, + hideProgressBar: false, + closeOnClick: false, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + transition: Bounce, + }); + } + }, 0) } const handleSetIsFields = (setTo: boolean) => { @@ -123,24 +164,24 @@ const NavTwo: React.FC = ({ isFields, setIsFields, isSend, setIsSen zoom(Number(indx), 'zoom-con') }, [selFitIndx]) - useEffect(() => { - let timeoutId: number; - const timeOutIn = 8000; + // useEffect(() => { + // let timeoutId: number; + // const timeOutIn = 8000; - const updateLabel = () => { - if (document.visibilityState === 'visible') { - setTransactionSaving(true); - setTimeout(() => { - setTransactionSaving(false); - }, 4000) - } - timeoutId = setTimeout(updateLabel, timeOutIn); - }; - timeoutId = setTimeout(updateLabel, timeOutIn); + // const updateLabel = () => { + // if (document.visibilityState === 'visible') { + // setTransactionSaving(true); + // setTimeout(() => { + // setTransactionSaving(false); + // }, 4000) + // } + // timeoutId = setTimeout(updateLabel, timeOutIn); + // }; + // timeoutId = setTimeout(updateLabel, timeOutIn); - // Cleanup on unmount - return () => clearTimeout(timeoutId); - }, []); + // // Cleanup on unmount + // return () => clearTimeout(timeoutId); + // }, []); useEffect(() => { if (!transactionSaving) return; @@ -286,6 +327,15 @@ const NavTwo: React.FC = ({ isFields, setIsFields, isSend, setIsSen

+ {transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && selfUserSign.sign &&
handleSend(selfUserSign.sign)} + className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]'> +

+ Sign & Send +

+ +
} diff --git a/src/components/process/processStepTwo/ProcessStepTwo.tsx b/src/components/process/processStepTwo/ProcessStepTwo.tsx index 19dbaad..9c47219 100644 --- a/src/components/process/processStepTwo/ProcessStepTwo.tsx +++ b/src/components/process/processStepTwo/ProcessStepTwo.tsx @@ -9,10 +9,16 @@ import { RootState } from '../../../store/store'; import Loader from '../../dashboard/Loader'; import { hideLoader } from '../../../store/slices/loaderSlice'; +export interface IsSendInterface { + send: boolean | null; + save: boolean; +} + const ProcessStepTwo = () => { const [isFields, setIsFields] = useState(true); const [selectedRecipient, setSelectedRecipient] = useState(0); + const [isSend, setIsSend] = useState(null); const globalLoader = useSelector((state: RootState) => state.loader) const dispatch = useDispatch();