Compare commits

..

No commits in common. "772dec48585b564d6e34f926f63392ca896db345" and "5c3131f3c3cdf66727345da1e2b0fff9fce1289e" have entirely different histories.

20 changed files with 531 additions and 2145 deletions

View File

@ -54,18 +54,6 @@ const App = () => {
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route path="/process-step-one/:accountId/template/:templateId" element={
<ProtectedRoute>
<ProcessStepOne />
</ProtectedRoute>
} />
<Route path="/process-step-two/:accountId/template/:templateId" element={
<ProtectedRoute>
<ProcessStepTwo />
</ProtectedRoute>
} />
<Route path="/process-step-two/:accountId/:transactionId" element={ <Route path="/process-step-two/:accountId/:transactionId" element={
<ProtectedRoute> <ProtectedRoute>
<ProcessStepTwo /> <ProcessStepTwo />

View File

@ -272,6 +272,8 @@ const Account = () => {
} }
}; };
return ( return (
<> <>
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards} <Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}

View File

@ -11,7 +11,7 @@ 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 { FaRegImage, FaFileAlt, FaSignature, FaStamp, FaCheck } from 'react-icons/fa' import { FaRegImage, FaFileAlt, FaSignature, FaStamp } from 'react-icons/fa'
import { IoIosDocument } from 'react-icons/io' import { IoIosDocument } from 'react-icons/io'
import { MdKeyboardArrowDown } from 'react-icons/md' import { MdKeyboardArrowDown } from 'react-icons/md'
import { TbChevronsDownLeft, TbSignature } from "react-icons/tb"; import { TbChevronsDownLeft, TbSignature } from "react-icons/tb";
@ -100,30 +100,6 @@ export interface StampTypeObj {
blob: string; blob: string;
} }
interface FIT {
label: string,
value: number | null
}
const fitObj = [
{
label: "Fit",
value: "FIT"
},
{
label: "50%",
value: "50"
},
{
label: "150%",
value: "150"
},
{
label: "200%",
value: "200"
},
]
// Functions - LIST // Functions - LIST
// handleSignClick - Handle when sign || Initial tag got clicked // handleSignClick - Handle when sign || Initial tag got clicked
// fetchData - Fetch document images and tags data // fetchData - Fetch document images and tags data
@ -150,9 +126,6 @@ const SignDocument = () => {
// Show is fields - Showing all fields There // Show is fields - Showing all fields There
const [isFields, setIsFields] = useState<boolean>(true); const [isFields, setIsFields] = useState<boolean>(true);
const [isFitDd, setIsFitDd] = useState<boolean>(false)
const [selFitIndx, setSelFitIndx] = useState<number>(0);
const globalLoader = useSelector((state: RootState) => state.loader) const globalLoader = useSelector((state: RootState) => state.loader)
const [openSign, setOpenSign] = useState<boolean>(false); const [openSign, setOpenSign] = useState<boolean>(false);
@ -173,21 +146,6 @@ const SignDocument = () => {
const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({}); const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({});
const zoom = (percentage: number, contentClass: string) => {
const content = document.querySelector(`.${contentClass}`);
if (content) {
(content as HTMLElement).style.zoom = `${percentage}%`;
}
}
useEffect(() => {
let indx = fitObj[selFitIndx].value
if (indx === "FIT") {
indx = "100";
}
zoom(Number(indx), 'zoom-con')
}, [selFitIndx])
useEffect(() => { useEffect(() => {
dispatch(showLoader("Loading ...")); dispatch(showLoader("Loading ..."));
fetchData(); fetchData();
@ -204,10 +162,9 @@ const SignDocument = () => {
console.log(saved, "Line 162") console.log(saved, "Line 162")
useEffect(() => { useEffect(() => {
if (recSignStatus === "COMPLETED") { if (recSignStatus === "COMPLETE") {
console.log("SETTING HIDE TAGS to TRUE LIne 161") console.log("SETTING HIDE TAGS to TRUE LIne 161")
// setHideTags(true); setHideTags(true);
setTransparentTags(true);
} else { } else {
console.log("SETTING HIDE TAGS to TRUE LIne 161") console.log("SETTING HIDE TAGS to TRUE LIne 161")
setHideTags(false); setHideTags(false);
@ -490,31 +447,13 @@ const SignDocument = () => {
// } // }
// } // }
const fetchSignatureData = async (signatureId: string) => {
try {
const response = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign/signatures/${signatureId}`, {
headers: { "ngrok-skip-browser-warning": "69420" }
});
if (response.status === 200) {
return response.data.signature;
}
} catch (error) {
console.error("Error fetching signature data:", error);
}
return null;
}
useEffect(() => { useEffect(() => {
(async () => { if (tagJson) {
if (tagJson) { for (let i = 0; i < tagJson?.length; i++) {
for (const tag of tagJson) {
if (tag.type === "SIGNATURE" && "value" in tag) {
const signatureData = await fetchSignatureData(tag.value);
console.log(signatureData);
}
}
} }
})() }
}, [tagJson]) }, [tagJson])
@ -559,40 +498,6 @@ const SignDocument = () => {
<div className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'> <div className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
<p className='tracking-wide font-semibold text-[1rem] text-[var(--primary)]'>Sign Made Easy</p> <p className='tracking-wide font-semibold text-[1rem] text-[var(--primary)]'>Sign Made Easy</p>
</div> </div>
{/* FIT - START */}
<div
tabIndex={1}
onBlur={() => setIsFitDd(false)}
>
<div
onClick={() => setIsFitDd(!isFitDd)}
className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
<p className='tracking-wide'>Fit</p>
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
</div>
{isFitDd && <div className='absolute bg-white rounded-sm z-50'>
{fitObj.map((val, indx) => {
return (<div
key={indx}
onClick={() => setSelFitIndx(indx)}
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
<div
className='flex items-center justify-between w-full'>
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{val.label}</p>
{selFitIndx === indx && <FaCheck className='text-sm text-slate-400' />}
</div>
</div>)
})
}
</div>}
</div>
{/* FIT - END */}
</div> </div>
@ -708,7 +613,7 @@ const SignDocument = () => {
{/* Main container */} {/* Main container */}
<div <div
id="doc-con" id="doc-con"
className="zoom-con relative col-span-10 px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth" className="relative col-span-10 px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth"
> >
{ {
@ -763,7 +668,7 @@ const SignDocument = () => {
<div <div
id={item.id} id={item.id}
tabIndex={1} tabIndex={1}
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`} className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-none" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimensions.height + "px" }}
> >
@ -1062,7 +967,7 @@ const SignDocument = () => {
} }
{/* tag pagination Start */} {/* tag pagination Start */}
{/* {!transparentTags && !hideTags && <div className="sticky rounded-md bottom-2 left-[50%] translate-[-50%] max-w-max flex bg-slate-900 text-white text-sm opacity-70"> {!transparentTags && !hideTags && <div className="sticky rounded-md bottom-2 left-[50%] translate-[-50%] max-w-max flex bg-slate-900 text-white text-sm opacity-70">
<div className="px-3 py-1 cursor-pointer hover:bg-black rounded-l-md transition-all duration-150 " <div className="px-3 py-1 cursor-pointer hover:bg-black rounded-l-md transition-all duration-150 "
onClick={() => handleTagNavClick("P")} onClick={() => handleTagNavClick("P")}
>Prev</div> >Prev</div>
@ -1070,7 +975,7 @@ const SignDocument = () => {
<div className="px-3 py-1 cursor-pointer hover:bg-black rounded-r-md transition-all duration-150 " <div className="px-3 py-1 cursor-pointer hover:bg-black rounded-r-md transition-all duration-150 "
onClick={() => handleTagNavClick("N")} onClick={() => handleTagNavClick("N")}
>Next</div> >Next</div>
</div>} */} </div>}
</div> </div>

View File

@ -121,7 +121,7 @@ const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
</div> </div>
{/* Dotted line */} {/* Dotted line */}
<div className="flex items-center mb-3"> <div className="flex items-center">
<div className="w-16 border-t-2 border-dotted border-gray-300"></div> <div className="w-16 border-t-2 border-dotted border-gray-300"></div>
</div> </div>
@ -129,7 +129,7 @@ const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
<div className="flex items-center justify-center space-x-6"> <div className="flex items-center justify-center space-x-6">
{sortedOrders.map((order, index) => ( {sortedOrders.map((order, index) => (
<React.Fragment key={order}> <React.Fragment key={order}>
<div className="flex flex-col items-center space-y-4 mb-4"> <div className="flex flex-col items-center space-y-4">
<div className="text-sm font-medium text-gray-600 mb-2"> <div className="text-sm font-medium text-gray-600 mb-2">
Step {order} Step {order}
</div> </div>

View File

@ -14,17 +14,15 @@ interface DocumentCardProps {
id: string; id: string;
name: string; name: string;
isCompleted: boolean; isCompleted: boolean;
transactionTemplateToggle:boolean;
} }
const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted , transactionTemplateToggle }) => { const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted }) => {
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
const accountId = params.accountId; const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const transactionId = params.transactionId || params.templateId; if (!accountId || !transactionId) {
// if (!accountId || !transactionId) { window.location.href = "/";
// window.location.href = "/"; return null;
// return null; }
// }
const [progress, setProgress] = useState<number>(0) const [progress, setProgress] = useState<number>(0)
const [message, setMessage] = useState<string>('') const [message, setMessage] = useState<string>('')
@ -98,7 +96,7 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted , tra
console.error("Missing document ID, account ID, or transaction ID"); console.error("Missing document ID, account ID, or transaction ID");
return; return;
} }
const res = await api.delete(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents/${docId}`) const res = await api.delete(`/api/accounts/${accountId}/transactions/${transactionId}/documents/${docId}`)
if (res.status === 202) { if (res.status === 202) {
dispatch(deleteDocument(docId)); dispatch(deleteDocument(docId));
setDocumentDel({ docId: null, isDel: false }); setDocumentDel({ docId: null, isDel: false });

View File

@ -9,7 +9,7 @@ import { RootState } from "../../../store/store";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { setImages } from "../../../store/slices/selectedDocsSlice"; import { setImages } from "../../../store/slices/selectedDocsSlice";
import { addRecipient, addRecipientAsync, fetchRecipients, recipientType, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE, updateRecipientIds } from "../../../store/slices/selectedRecipientsSlice"; import { addRecipient, addRecipientAsync, fetchRecipients, recipientType, resetRecipients, updateRecipientIds } from "../../../store/slices/selectedRecipientsSlice";
import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from "../../../store/slices/transactionSlice"; import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from "../../../store/slices/transactionSlice";
import { hideLoader, showLoader } from "../../../store/slices/loaderSlice"; import { hideLoader, showLoader } from "../../../store/slices/loaderSlice";
import Loader from "../../dashboard/Loader"; import Loader from "../../dashboard/Loader";
@ -22,35 +22,21 @@ const ProcessStepOne = () => {
message: string; message: string;
} }
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
console.log("transactionTemplateToggle1", transactionTemplateToggle) if (!accountId || !transactionId) {
console.log() window.location.href = "/";
const templateId = useParams<{ templateId: string }>().templateId; return null;
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>(); }
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
if (!transactionId) {
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
console.log("transactionTemplateToggle2", transactionTemplateToggle)
transactionId = templateId;
dispatch(toggleToTemplates());
}
console.log("ProcessStepOne called with accountId:", accountId, "and transactionId:", transactionId);
// if (!accountId || !transactionId) {
// window.location.href = "/";
// return null;
// }
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments) // const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
// const { selectedDocuments, signType } = useSelector((state: RootState) => state.selectedDocuments) // const { selectedDocuments, signType } = useSelector((state: RootState) => state.selectedDocuments)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents) const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients) const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
console.log("selectedRecipients", selectedRecipients)
useEffect(() => { useEffect(() => {
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId })); selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
@ -66,8 +52,6 @@ const ProcessStepOne = () => {
dispatch(hideLoader()); dispatch(hideLoader());
}, []) }, [])
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const handleNextClick = async (isSendNow: boolean) => { const handleNextClick = async (isSendNow: boolean) => {
let formError = { let formError = {
documents: false, documents: false,
@ -115,9 +99,9 @@ const ProcessStepOne = () => {
order: index, order: index,
})); }));
await api.patch(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents`, { "documents": documentsData }); await api.patch(`/api/accounts/${accountId}/transactions/${transactionId}/documents`, { "documents": documentsData });
await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents?include_images=true`).then(res => { await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/documents?include_images=true`).then(res => {
if (res.status === 200) { if (res.status === 200) {
dispatch(setDocuments(res.data)); dispatch(setDocuments(res.data));
} }
@ -128,7 +112,7 @@ const ProcessStepOne = () => {
dispatch(showLoader("Processing Recipients...")); dispatch(showLoader("Processing Recipients..."));
const recipientsData = selectedRecipients.map((recipient, index) => ({ const recipientsData = selectedRecipients.map((recipient, index) => ({
id: "id" in recipient ? recipient.id : null, // index,
routingOrder: "routingOrder" in recipient && recipient.routingOrder ? recipient.routingOrder : 1, routingOrder: "routingOrder" in recipient && recipient.routingOrder ? recipient.routingOrder : 1,
type: recipient.type, type: recipient.type,
firstName: recipient.firstName, firstName: recipient.firstName,
@ -145,38 +129,30 @@ const ProcessStepOne = () => {
accessCode: "", accessCode: "",
accessCodeDelivery: true, accessCodeDelivery: true,
language: "en", language: "en",
name: `${recipient.firstName} ${recipient.lastName}`, name: `${recipient.firstName} ${recipient.lastName}`
roleName: recipient.roleName
})); }));
dispatch(addRecipientAsync({ recipientsData, accountId, transactionId })); dispatch(addRecipientAsync({ recipientsData, accountId, transactionId }));
dispatch(showLoader("Processing Message...")); dispatch(showLoader("Processing Message..."));
console.log(`1/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`) await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { subject: emailDtls.subject, message: emailDtls.message, status: "CREATED" }).then((res) => {
console.log(res);
let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, {
subject: emailDtls.subject, message: emailDtls.message,
status: "CREATED"
}) })
console.log("Line 158", res.data) dispatch(hideLoader());
if (isSendNow) { if (isSendNow) {
dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS)); dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS));
setTimeout(() => { setTimeout(() => {
// navigate("/"); // navigate("/");
}, 1000); }, 1000);
} } else {
else {
setTimeout(() => { setTimeout(() => {
dispatch(resetDocuments()) dispatch(resetDocuments())
dispatch(resetRecipients()) dispatch(resetRecipients())
dispatch(hideLoader()); dispatch(hideLoader());
if (transactionTemplateToggle === 'templates') { navigate(`/process-step-two/${accountId}/${transactionId}`);
console.log(`/process-step-two/${accountId}/template/${transactionId}`)
navigate(`/process-step-two/${accountId}/template/${transactionId}`)
} else {
navigate(`/process-step-two/${accountId}/${transactionId}`);
}
}, 1000); }, 1000);
} }
@ -240,7 +216,7 @@ const ProcessStepOne = () => {
{/* Documents */} {/* Documents */}
<section> <section>
<h2 className="text-lg font-medium mb-4 text-[var(--processStepOneTextHead)]">Documents</h2> <h2 className="text-lg font-medium mb-4 text-[var(--processStepOneTextHead)]">Documents</h2>
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} /> <SelectedDocuments />
</section> </section>
{/* Recipients */} {/* Recipients */}
@ -249,7 +225,7 @@ const ProcessStepOne = () => {
<div className="flex flex-col gap-4 align-start"> <div className="flex flex-col gap-4 align-start">
<RecipientCards /> <RecipientCards />
{/* {signType !== "self-sign" && <RecipientAddCard />} */} {/* {signType !== "self-sign" && <RecipientAddCard />} */}
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} /> <RecipientAddCard />
</div> </div>
</section> </section>

View File

@ -6,8 +6,6 @@ import { AppDispatch } from "../../../store/store";
import api from "../../../config/api"; import api from "../../../config/api";
import ContactModal from "./ContactModal"; import ContactModal from "./ContactModal";
import useDebouncedEffect from "../../../utlity/useDebounce"; import useDebouncedEffect from "../../../utlity/useDebounce";
import { IoIosSettings } from "react-icons/io";
import { FiEdit, FiTrash } from "react-icons/fi";
interface ContactType { interface ContactType {
id: string; id: string;
@ -21,7 +19,7 @@ export interface ContactTypeObj {
contacts: ContactType[]; contacts: ContactType[];
} }
const RecipientAddCard = ({ transactionTemplateToggle }) => { const RecipientAddCard = () => {
const [contactData, setContactData] = useState<ContactTypeObj>({ type: "", contacts: [] }); const [contactData, setContactData] = useState<ContactTypeObj>({ type: "", contacts: [] });
const [openContactModal, setOpenContactModal] = useState(false); const [openContactModal, setOpenContactModal] = useState(false);
@ -29,10 +27,7 @@ const RecipientAddCard = ({ transactionTemplateToggle }) => {
const dispatch = useDispatch<AppDispatch>(); const dispatch = useDispatch<AppDispatch>();
const formRef = useRef<HTMLFormElement | null>(null); const formRef = useRef<HTMLFormElement | null>(null);
const [recipient, setRecipients] = useState<Recipient>({ firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: Boolean, allowSenderToRemove: Boolean }); const [recipient, setRecipients] = useState<Recipient>({ firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", type: recipientType.SIGNER });
const [showAdvanceSettings, setShowAdvanceSettings] = useState(false)
console.log(recipient)
const handleRecipientAdd = (e: React.FormEvent<HTMLFormElement>) => { const handleRecipientAdd = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
@ -48,24 +43,24 @@ const RecipientAddCard = ({ transactionTemplateToggle }) => {
// Validation Ends // Validation Ends
dispatch(addRecipient(recipient)); dispatch(addRecipient(recipient));
setRecipients({ firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: Boolean, allowSenderToRemove: Boolean }); setRecipients({ firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", type: recipientType.SIGNER });
formRef.current?.reset(); formRef.current?.reset();
} }
// useEffect(() => { // useEffect(() => {
// if (!recipient.firstName.length) return; // if (!recipient.firstName.length) return;
// const handler = setTimeout(() => { // const handler = setTimeout(() => {
// api.get(`/api/contact?firstName=${recipient.firstName}`) // api.get(`/api/contact?firstName=${recipient.firstName}`)
// .then(res => { // .then(res => {
// setContactData({ type: "firstName", contacts: res.data.content }); // setContactData({ type: "firstName", contacts: res.data.content });
// }) // })
// .catch(err => console.log(err)); // .catch(err => console.log(err));
// }, 500); // wait 500ms after the last change // }, 500); // wait 500ms after the last change
// return () => { // return () => {
// clearTimeout(handler); // cancel previous timeout if effect runs again // clearTimeout(handler); // cancel previous timeout if effect runs again
// }; // };
// }, [recipient.firstName]); // }, [recipient.firstName]);
@ -86,7 +81,7 @@ const RecipientAddCard = ({ transactionTemplateToggle }) => {
// }) // })
// .catch(err => console.log(err)) // .catch(err => console.log(err))
// }, [recipient.lastName], 500); // }, [recipient.lastName], 500);
// useDebouncedEffect(() => { // useDebouncedEffect(() => {
// if (!recipient.email.length) return // if (!recipient.email.length) return
// api.get(`/api/contact?email=${recipient.email}`) // api.get(`/api/contact?email=${recipient.email}`)
@ -104,238 +99,114 @@ const RecipientAddCard = ({ transactionTemplateToggle }) => {
return ( return (
<form <form
ref={formRef} ref={formRef}
onSubmit={handleRecipientAdd} onSubmit={handleRecipientAdd}
className="w-full bg-white border border-gray-200 rounded-lg shadow-md px-6 py-4 space-y-3 relative" className="w-full bg-white border border-gray-200 rounded-lg shadow-md px-6 py-6 space-y-5 relative"
> >
{transactionTemplateToggle === 'templates' && ( {/* Name Inputs */}
<div className="space-y-1"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Top Row: Role Input + Settings */} <input
<div className="flex justify-between items-center"> // required
{/* Role Name Input */} type="text"
<input onFocus={() => setOpenContactModal(true)}
required onBlur={handleContactModalBlur}
type="text" value={recipient.firstName}
onFocus={() => setOpenContactModal(true)} placeholder="First Name"
onBlur={handleContactModalBlur} className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
value={recipient.roleName} onChange={(e) => setRecipients({ ...recipient, firstName: e.target.value })}
placeholder="Role name" />
className="w-64 px-3 py-2 me-3 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition" <input
onChange={(e) => setRecipients({ ...recipient, roleName: e.target.value })} required
/> type="text"
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
value={recipient.lastName}
placeholder="Last Name"
className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
onChange={(e) => setRecipients({ ...recipient, lastName: e.target.value })}
/>
</div>
{recipient.type === recipientType.INPERSONSIGNER && <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<input
required
type="text"
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
value={recipient.hostName}
placeholder="Host Name"
className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
onChange={(e) => setRecipients({ ...recipient, hostName: e.target.value })}
/>
<input
required
type="email"
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
value={recipient.hostEmail}
placeholder="Host Email"
className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
onChange={(e) => setRecipients({ ...recipient, hostEmail: e.target.value })}
/>
</div>}
{/* Settings + Permissions */} {/* Email Input */}
<div className="flex items-center"> <input
<div className="flex justify-between"> required
{/* Permission Status Text - Always Visible when settings open */} type="email"
<div onFocus={() => setOpenContactModal(true)}
className={`transition-all duration-600 ease-in-out ${showAdvanceSettings ? 'max-h-10 opacity-100 me-3' : 'max-h-0 opacity-0 ' onBlur={handleContactModalBlur}
} overflow-hidden`} value={recipient.email}
> placeholder="Recipient Email"
<div className="text-sm text-gray-600"> className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
{recipient.allowSenderToEdit && recipient.allowSenderToRemove && "Full permissions granted"} onChange={(e) => setRecipients({ ...recipient, email: e.target.value })}
{recipient.allowSenderToEdit && !recipient.allowSenderToRemove && "Edit permission granted"} />
{!recipient.allowSenderToEdit && recipient.allowSenderToRemove && "Delete permission granted"}
{!recipient.allowSenderToEdit && !recipient.allowSenderToRemove && "No permissions granted"}
</div>
</div>
{/* Animated Permission Controls */}
<div className={`overflow-visible transition-all duration-900 ease-in-out ${showAdvanceSettings ? 'max-w-80 opacity-100' : 'max-w-0 opacity-0'
}`}>
<div className="flex items-center space-x-3 pr-1.5">
{/* Edit Permission */}
<div className="relative group">
<label
className={`relative flex items-center justify-center w-6 h-6 border-2 rounded-lg cursor-pointer transition-all duration-300 transform hover:scale-105 ${recipient.allowSenderToEdit
? 'bg-blue-100 border-blue-500 shadow-lg'
: 'bg-white border-gray-300 hover:border-blue-400'
}`}
>
<input
type="checkbox"
checked={recipient.allowSenderToEdit}
onChange={(e) =>
setRecipients({ ...recipient, allowSenderToEdit: e.target.checked })
}
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
className="absolute w-full h-full opacity-0 cursor-pointer"
/>
<FiEdit
className={`w-3 h-3 pointer-events-none transition-all duration-300 ${recipient.allowSenderToEdit ? 'text-blue-600' : 'text-gray-400'
}`}
/>
</label>
{/* Tooltip */}
<div className="absolute bottom-0 -translate-y-full left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-20">
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap">
Allow Edit Permission
<div className="absolute bottom-[-5px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div>
</div>
</div>
</div>
{/* Delete Permission */} {/* Contact Modal */}
<div className="relative group"> {contactData.contacts.length > 0 && openContactModal && (
<label <div className="absolute z-50 mt-1 w-full max-w-2xl bg-white shadow-lg rounded-md p-3 border border-gray-100">
className={`relative flex items-center justify-center w-6 h-6 border-2 rounded-lg cursor-pointer transition-all duration-300 transform hover:scale-105 ${recipient.allowSenderToRemove <ContactModal
? 'bg-red-100 border-red-500 shadow-lg' contactData={contactData}
: 'bg-white border-gray-300 hover:border-red-400' inpFirstName={recipient.firstName}
}`} inpLastName={recipient.lastName}
> inpEmail={recipient.email}
<input openContactModal={openContactModal}
type="checkbox" setRecipients={setRecipients}
checked={recipient.allowSenderToRemove} setOpenContactModal={setOpenContactModal}
onChange={(e) => />
setRecipients({ ...recipient, allowSenderToRemove: e.target.checked }) </div>
} )}
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
className="absolute w-full h-full opacity-0 cursor-pointer"
/>
<FiTrash
className={`w-3 h-3 pointer-events-none transition-all duration-300 ${recipient.allowSenderToRemove ? 'text-red-600' : 'text-gray-400'
}`}
/>
</label>
{/* Tooltip */}
<div className="absolute bottom-0 -translate-y-full left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-20">
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap">
Allow Delete Permission
<div className="absolute bottom-[-5px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Settings Icon */} {/* Signing options + Add button in one line */}
<div className="relative group"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<p <div className="flex flex-wrap gap-6">
className="text-blue-600 hover:text-blue-700 cursor-pointer transition-colors duration-200 p-2 rounded-full hover:bg-blue-50" {[
onClick={() => setShowAdvanceSettings((prev) => !prev)} { label: "Signer", value: recipientType.SIGNER },
> { label: "In Person Signer", value: recipientType.INPERSONSIGNER },
<IoIosSettings { label: "CC", value: recipientType.CC },
className={`h-6 w-6 transition-transform duration-300 ${showAdvanceSettings ? 'rotate-90' : 'rotate-0' ].map((option) => (
}`} <label key={option.value} className="flex items-center gap-2 text-gray-700 text-sm cursor-pointer">
/> <input
</p> type="radio"
{/* Tooltip */} name="recipient-type"
<div className="absolute top-0 -translate-y-full left-1/2 transform -translate-x-1/2 mb-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-20"> value={option.value}
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap"> defaultChecked={option.value === recipientType.SIGNER}
Advanced Settings onClick={(e) => setRecipients({ ...recipient, type: e.target.value })}
<div className="absolute bottom-[-6px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div> className="accent-[var(--primary)] cursor-pointer"
</div>
</div>
</div>
</div>
</div>
</div>
)}
{/* Name Inputs */}
<div className="grid grid-cols-12 gap-4 mt-3">
<input
type="text"
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
value={recipient.firstName}
placeholder="First Name"
className="col-span-4 w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
onChange={(e) => setRecipients({ ...recipient, firstName: e.target.value })}
/>
<input
required
type="text"
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
value={recipient.lastName}
placeholder="Last Name"
className="col-span-4 w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
onChange={(e) => setRecipients({ ...recipient, lastName: e.target.value })}
/>
<input
required
type="email"
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
value={recipient.email}
placeholder="Recipient Email"
className="col-span-4 w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
onChange={(e) => setRecipients({ ...recipient, email: e.target.value })}
/>
</div>
{recipient.type === recipientType.INPERSONSIGNER && <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<input
required
type="text"
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
value={recipient.hostName}
placeholder="Host Name"
className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
onChange={(e) => setRecipients({ ...recipient, hostName: e.target.value })}
/>
<input
required
type="email"
onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur}
value={recipient.hostEmail}
placeholder="Host Email"
className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
onChange={(e) => setRecipients({ ...recipient, hostEmail: e.target.value })}
/>
</div>}
{/* Contact Modal */}
{contactData.contacts.length > 0 && openContactModal && (
<div className="absolute z-50 mt-1 w-full max-w-2xl bg-white shadow-lg rounded-md p-3 border border-gray-100">
<ContactModal
contactData={contactData}
inpFirstName={recipient.firstName}
inpLastName={recipient.lastName}
inpEmail={recipient.email}
openContactModal={openContactModal}
setRecipients={setRecipients}
setOpenContactModal={setOpenContactModal}
/> />
</div> {option.label}
)} </label>
))}
</div>
{/* Signing options + Add button in one line */} <button
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> type="submit"
<div className="flex flex-wrap gap-6"> className="inline-flex items-center gap-2 bg-[var(--primary)] hover:bg-[var(--primaryLight2)] text-white text-sm font-semibold px-5 py-2 rounded-md transition"
{[ >
{ label: "Signer", value: recipientType.SIGNER }, <FaPlus size={14} /> Add Recipient
{ label: "In Person Signer", value: recipientType.INPERSONSIGNER }, </button>
{ label: "CC", value: recipientType.CC }, </div>
].map((option) => ( </form>
<label key={option.value} className="flex items-center gap-2 text-gray-700 text-sm cursor-pointer">
<input
type="radio"
name="recipient-type"
value={option.value}
defaultChecked={option.value === recipientType.SIGNER}
onClick={(e) => setRecipients({ ...recipient, type: e.target.value })}
className="accent-[var(--primary)] cursor-pointer"
/>
{option.label}
</label>
))}
</div>
<button
type="submit"
className="inline-flex items-center gap-2 bg-[var(--primary)] hover:bg-[var(--primaryLight2)] text-white text-sm font-semibold px-5 py-2 rounded-md transition"
>
<FaPlus size={14} /> Add Recipient
</button>
</div>
</form>
); );
}; };

View File

@ -1,6 +1,5 @@
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { import {
removeRecipientAsync,
removeSelectedRecipient, removeSelectedRecipient,
// setSigningOrder, // setSigningOrder,
updateRecipientOrder, // 👈 You need to define this in your slice updateRecipientOrder, // 👈 You need to define this in your slice
@ -12,7 +11,6 @@ import { FaUsersLine } from "react-icons/fa6";
import { useState } from "react"; import { useState } from "react";
import SigningOrderDiagram from "../../../SigningOrderDiagram "; import SigningOrderDiagram from "../../../SigningOrderDiagram ";
import { IoCloseOutline } from "react-icons/io5"; import { IoCloseOutline } from "react-icons/io5";
import { useParams } from "react-router-dom";
const getInitials = (name: string) => { const getInitials = (name: string) => {
if (!name) return ""; if (!name) return "";
@ -24,17 +22,8 @@ const getInitials = (name: string) => {
}; };
const RecipientCards = () => { const RecipientCards = () => {
const templateId = useParams<{ templateId: string }>().templateId; const dispatch = useDispatch<AppDispatch>();
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const dispatch = useDispatch();
if (!transactionId) {
transactionId = templateId;
}
const recipients = useSelector((state: RootState) => state.selectedRecipients.recipients); const recipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
console.log("recipients.length",recipients)
// const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder); // const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder);
const signType = useSelector((state: RootState) => state.selectedDocuments.signType); const signType = useSelector((state: RootState) => state.selectedDocuments.signType);
@ -45,14 +34,7 @@ const RecipientCards = () => {
}; };
const handleRemoveRecipient = (recipient) => { const handleRemoveRecipient = (recipient) => {
if (!accountId || !transactionId) return; dispatch(removeSelectedRecipient(recipient))
// dispatch(removeSelectedRecipient(recipient))
dispatch(removeRecipientAsync({
recipientId: recipient.id,
email: recipient.email,
accountId: accountId,
transactionId: transactionId,
}))
if (recipients.length === 2) { if (recipients.length === 2) {
let leftRecipient = recipients.filter((obj) => { let leftRecipient = recipients.filter((obj) => {
if ("id" in obj && "id" in recipient) { if ("id" in obj && "id" in recipient) {
@ -138,7 +120,7 @@ const RecipientCards = () => {
{recipient.firstName} {recipient.lastName || ""} {recipient.firstName} {recipient.lastName || ""}
</p> </p>
<p className="text-xs text-gray-500 truncate"> <p className="text-xs text-gray-500 truncate">
{recipient.email} | {recipient.type} {recipient.roleName ? `| Role : ${recipient.roleName}` : ""} {recipient.email} | {recipient.type}
</p> </p>
</div> </div>
</div> </div>
@ -170,8 +152,8 @@ const RecipientCards = () => {
<div className="pb-2 text-white flex justify-between items-center"> <div className="pb-2 text-white flex justify-between items-center">
<p className="font-semibold">Signing Order</p> <p className="font-semibold">Signing Order</p>
<div <div
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: false })} onClick={() => setSigningOrderModal({...signingOrderModal, open: false})}
className="text-xl cursor-pointer hover:text-gray-200 transition-all"> className="text-xl cursor-pointer hover:text-gray-200 transition-all">
<IoCloseOutline /> <IoCloseOutline />
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef } from 'react';
import { ReactSortable } from 'react-sortablejs'; import { ReactSortable } from 'react-sortablejs';
import { RiDraggable } from "react-icons/ri"; import { RiDraggable } from "react-icons/ri";
import { FaFileAlt, FaPlus, FaGoogleDrive, FaDropbox, FaMicrosoft, FaCloudUploadAlt } from "react-icons/fa"; import { FaFileAlt, FaPlus, FaGoogleDrive, FaDropbox, FaMicrosoft, FaCloudUploadAlt } from "react-icons/fa";
@ -11,30 +11,15 @@ import { Bounce, toast } from "react-toastify";
import api from '../../../config/api'; import api from '../../../config/api';
import DocumentCard from './DocumentCard'; import DocumentCard from './DocumentCard';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
const SelectedDocuments = ({transactionTemplateToggle}) => {
// let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
// const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// await sleep(5000);
const SelectedDocuments = () => {
const dispatch = useDispatch<AppDispatch>();
const documents = useSelector((state: RootState) => state.documents.documents); const documents = useSelector((state: RootState) => state.documents.documents);
useEffect(() => { const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
if (!accountId || !transactionId) {
console.log("Documents updated:", documents); window.location.href = "/";
}, [documents]); return null;
const templateId = useParams<{ templateId: string }>().templateId;
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const dispatch = useDispatch();
if (!transactionId) {
transactionId = templateId;
} }
const [isModalOpen, setModalOpen] = useState(false); const [isModalOpen, setModalOpen] = useState(false);
@ -71,7 +56,6 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
formData.append("file", filesToUpload[0]) formData.append("file", filesToUpload[0])
console.log(formData, "Line 59") console.log(formData, "Line 59")
// filesToUpload.forEach((file) => formData.append("document", file)); // filesToUpload.forEach((file) => formData.append("document", file));
dispatch(uploadDocuments({ formData, accountId, transactionId })) dispatch(uploadDocuments({ formData, accountId, transactionId }))
.unwrap() .unwrap()
.then((res) => { .then((res) => {
@ -91,7 +75,6 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
// Update selected documents after upload // Update selected documents after upload
// dispatch(setSelectedDocuments([...documents, ...res])); // dispatch(setSelectedDocuments([...documents, ...res]));
// debugger;
setFiles([]); setFiles([]);
setModalOpen(false); setModalOpen(false);
}) })
@ -115,7 +98,7 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
disabled={false} disabled={false}
> >
{documents.map((doc) => ( {documents.map((doc) => (
<DocumentCard key={doc.id} id={doc.id} name={doc.name} isCompleted={("isCompleted" in doc || ("status" in doc && doc.status === "UPLOADED")) ? true : false} transactionTemplateToggle={transactionTemplateToggle}/> <DocumentCard key={doc.id} id={doc.id} name={doc.name} isCompleted={("isCompleted" in doc || ("status" in doc && doc.status === "UPLOADED")) ? true : false} />
))} ))}
</ReactSortable> </ReactSortable>

View File

@ -10,14 +10,13 @@ import { useNavigate, useParams } from 'react-router-dom';
import { IoMdClose } from 'react-icons/io'; import { IoMdClose } from 'react-icons/io';
import { fetchRecipients, toggleToTemplates } from '../../../store/slices/selectedRecipientsSlice'; import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice';
import { fetchDocuments } from '../../../store/slices/documentSlice'; import { fetchDocuments } from '../../../store/slices/documentSlice';
import { FaQuestionCircle } from 'react-icons/fa'; import { FaQuestionCircle } from 'react-icons/fa';
import typeToIconMap from '../../../helper/iconMap'; import typeToIconMap from '../../../helper/iconMap';
import { IoAdd } from 'react-icons/io5'; import { IoAdd } from 'react-icons/io5';
import Loader from '../../dashboard/Loader'; import Loader from '../../dashboard/Loader';
import { TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice";
export interface DroppedItemsObj { export interface DroppedItemsObj {
[key: string]: DroppedItem[] [key: string]: DroppedItem[]
@ -42,16 +41,7 @@ interface DocumentContainerProps {
} }
const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => { const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const dispatch = useDispatch();
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
const accountId = params.accountId;
let transactionId = params.transactionId
if (!transactionId) {
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
transactionId = params.templateId;
dispatch(toggleToTemplates());
}
if (!accountId || !transactionId) { if (!accountId || !transactionId) {
window.location.href = "/"; window.location.href = "/";
return null; return null;
@ -59,6 +49,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// const transactionId = useSelector((state: RootState) => state.transaction.transactionId) // const transactionId = useSelector((state: RootState) => state.transaction.transactionId)
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch();
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients); const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments) // const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
@ -76,7 +67,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
const fetchTags = async () => { const fetchTags = async () => {
try { try {
const requests = selectedRecipients.map((recipient) => const requests = selectedRecipients.map((recipient) =>
api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/recipients/${recipient.id}/tags`) api.get(`/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipient.id}/tags`)
); );
const response = await Promise.all(requests); const response = await Promise.all(requests);
let recTags = {} let recTags = {}
@ -160,7 +151,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
const requests = Object.entries(updatedData).map(([recipientId, tags]) => { const requests = Object.entries(updatedData).map(([recipientId, tags]) => {
return api.put( return api.put(
`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/recipients/${recipientId}/tags`, `/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/tags`,
tags tags
); );
}); });
@ -288,7 +279,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
sendTagsToRecipients(tagsData, accountId, transactionId); sendTagsToRecipients(tagsData, accountId, transactionId);
(async () => { (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") console.log(res, "Check123")
})() })()
}, [isSend]) }, [isSend])
@ -516,8 +507,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
(async () => { (async () => {
if ("id" in tag) { if ("id" in tag) {
try { try {
const response = await api.delete(`/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/tags/${tag.id}`)
const response = await api.delete(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/recipients/${recipientId}/tags/${tag.id}`)
if (response.status !== 200) throw "Tag Deletion error" if (response.status !== 200) throw "Tag Deletion error"
} catch (err) { } catch (err) {
console.error("Tag Deletion error") console.error("Tag Deletion error")
@ -534,7 +524,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
})() })()
} }
const handleAddRadioClick = (key: string) => { const handleAddRadioClick = (key:string) => {
console.log("Handle Radio Clicled", key) console.log("Handle Radio Clicled", key)
console.log(droppedItems, "line 525") console.log(droppedItems, "line 525")
} }
@ -854,11 +844,11 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
</div> </div>
{selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} DirectionalControlsProps={{ {selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} DirectionalControlsProps={{
onMove: directionalControlsHandler, onMove: directionalControlsHandler,
disabled: false, disabled: false,
stepSize: 5, stepSize: 5,
className: '' className: ''
}} />} }} />}
{/* Center Doc Container - END */} {/* Center Doc Container - END */}
</> </>
) )

View File

@ -3,7 +3,7 @@ import { RootState } from '../../../store/store';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import FieldList from './FieldList'; import FieldList from './FieldList';
import { addColorsToRecipient, fetchRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice'; import { addColorsToRecipient, fetchRecipients } from '../../../store/slices/selectedRecipientsSlice';
import { IoCheckmark } from 'react-icons/io5' import { IoCheckmark } from 'react-icons/io5'
import { MdKeyboardArrowDown } from 'react-icons/md' import { MdKeyboardArrowDown } from 'react-icons/md'
@ -17,21 +17,13 @@ interface LeftSideBarProps {
} }
const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIndx, setSelectedRecipient }) => { const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIndx, setSelectedRecipient }) => {
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const dispatch = useDispatch();
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
const accountId = params.accountId;
let transactionId = params.transactionId
if (!transactionId) {
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
transactionId = params.templateId;
dispatch(toggleToTemplates());
}
if (!accountId || !transactionId) { if (!accountId || !transactionId) {
window.location.href = "/"; window.location.href = "/";
return null; return null;
} }
const dispatch = useDispatch();
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients) const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents) const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
@ -96,7 +88,7 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
setDropDownToggle(false); setDropDownToggle(false);
}, 0) }, 0)
}} }}
> >
<div className='flex items-center gap-1.5'> <div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p> <p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
{/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */} {/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */}

View File

@ -8,7 +8,7 @@ import { IoIosDocument } from 'react-icons/io'
import { LuTableOfContents } from 'react-icons/lu' import { LuTableOfContents } from 'react-icons/lu'
import { MdKeyboardArrowDown } from 'react-icons/md' import { MdKeyboardArrowDown } from 'react-icons/md'
import { RiResetLeftLine, RiResetRightLine } from 'react-icons/ri' import { RiResetLeftLine, RiResetRightLine } from 'react-icons/ri'
import { fetchRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice' import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice'
import { fetchDocuments } from '../../../store/slices/documentSlice' import { fetchDocuments } from '../../../store/slices/documentSlice'
import { useNavigate, useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
@ -45,16 +45,7 @@ const fitObj = [
] ]
const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSend }) => { const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSend }) => {
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const dispatch = useDispatch();
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
const accountId = params.accountId;
let transactionId = params.transactionId
if (!transactionId) {
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
transactionId = params.templateId;
dispatch(toggleToTemplates());
}
if (!accountId || !transactionId) { if (!accountId || !transactionId) {
window.location.href = "/"; window.location.href = "/";
return null; return null;
@ -79,6 +70,8 @@ const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSen
const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false); const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false);
const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false); const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false);
const dispatch = useDispatch();
function handleSend() { function handleSend() {
// dispatch(showLoader("Loading ...")); // dispatch(showLoader("Loading ..."));
if (isSend === null) { if (isSend === null) {

View File

@ -10,7 +10,6 @@ import Loader from '../../dashboard/Loader';
import { hideLoader } from '../../../store/slices/loaderSlice'; import { hideLoader } from '../../../store/slices/loaderSlice';
const ProcessStepTwo = () => { const ProcessStepTwo = () => {
const [isFields, setIsFields] = useState(true); const [isFields, setIsFields] = useState(true);
const [selectedRecipient, setSelectedRecipient] = useState(0); const [selectedRecipient, setSelectedRecipient] = useState(0);
const [isSend, setIsSend] = useState<boolean | null>(null); const [isSend, setIsSend] = useState<boolean | null>(null);

View File

@ -1,549 +1,107 @@
// import React from 'react' import React, { useEffect, useState } from 'react'
// import { SaveType } from '../client/SignDocument'; import Draw from './Draw'
import TypedSign from './TypedSign';
// interface SignConProps { import SignUpload from './SignUpload';
// setOpenSign: React.Dispatch<React.SetStateAction<boolean>>; import { IoMdClose } from 'react-icons/io';
// recipientId: string;
// saved: SaveType;
// setSaved: React.Dispatch<React.SetStateAction<SaveType>>;
// // save: () => void;
// }
// const SignCon: React.FC<SignConProps> = ({ setOpenSign, recipientId, saved, setSaved }) => {
// return (
// <div>SignCon</div>
// )
// }
// export default SignCon
import React, { useEffect, useState } from 'react';
// import api from '../../config/api';
import DrawSignatureCanvas from '../setting/Signature/DrawSignatureCanvas';
import { AiOutlineUpload, AiOutlineEdit } from 'react-icons/ai';
import { MdCloudUpload } from 'react-icons/md';
import { FaSpinner } from "react-icons/fa";
import { GrCheckboxSelected } from "react-icons/gr";
import { Bounce, toast } from "react-toastify";
import axios from 'axios'; import axios from 'axios';
import { store } from '../../store/store';
import { SaveType, SaveTypeEnum } from '../client/SignDocument';
interface Signature { interface SignConProps {
id: string; setOpenSign: React.Dispatch<React.SetStateAction<boolean>>;
email: string; recipientId: string;
signatureType: string; saved: SaveType;
lastName: string; setSaved: React.Dispatch<React.SetStateAction<SaveType>>;
firstName: string // save: () => void;
font: string;
initialsBlob: string;
initialsStampedBlob: string;
initialsHash: string;
signatureHash: string;
signatureBlob: string;
signatureStampedBlob: string;
default: boolean
} }
interface AddSignatureModalProps { const SignCon: React.FC<SignConProps> = ({ setOpenSign, recipientId, saved, setSaved }) => {
setShowAddSignature?: (value: boolean) => void;
getAllSignatures: () => Promise<void>;
editingSignature?: Signature | null;
}
interface Account { const [activeSaveType, setActiveSaveType] = React.useState<SaveTypeEnum>(SaveTypeEnum.DRAW);
id: number;
isDefault: boolean;
name: string;
}
interface UserData { const handleSaveSignature = () => {
id: string; if (!saved.save) return
firstName: string;
lastName: string;
email: string;
language: string;
account: Account;
}
interface SelectStyle { console.log("signature:", saved.initial, "initials:", saved.signature, "subType:", saved.saveType, "MIC CHECK")
id: number, // return
style: string
} axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/signature?recipientId=${recipientId}`,
{
const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ signature: saved.signature,
setShowAddSignature, initials: saved.initial,
getAllSignatures, subType: saved.saveType
editingSignature },
}) => { { headers: { "ngrok-skip-browser-warning": "69420", } })
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT'); .then((res) => {
const [fullName, setFullName] = useState(''); console.log(res, "Signature saved response")
const [initials, setInitials] = useState(''); if (res.status === 200 || res.status === 201) {
const [isSubmitting, setIsSubmitting] = useState<boolean>(false); setOpenSign(false)
const [signatures, setSignatures] = useState<{ setActiveSaveType(saved.saveType as SaveTypeEnum)
fullSignature: string | null;
initials: string | null;
}>({ fullSignature: null, initials: null });
const signatureOptions = [
{ id: 1, name: 'print', initials: 'LA', style: 'print' },
{ id: 2, name: 'cursive', initials: 'LA', style: 'cursive' },
{ id: 3, name: 'script', initials: 'LA', style: 'script' },
{ id: 4, name: 'elegant', initials: 'LA', style: 'elegant' },
{ id: 5, name: 'modern', initials: 'LA', style: 'modern' },
{ id: 6, name: 'classic', initials: 'LA', style: 'classic' },
];
const [selectedSignature, setSelectedSignature] = useState<SelectStyle>(
signatureOptions.length > 0 ? { id: signatureOptions[0].id, style: signatureOptions[0].style } : { id: 0, style: "" }
);
// Store the actual file
const [file1, setFile1] = useState<File | null>(null);
const [file2, setFile2] = useState<File | null>(null);
// Store the preview URL
const [preview1, setPreview1] = useState<string | null>(null);
const [preview2, setPreview2] = useState<string | null>(null);
// User data
const [user, setUser] = useState<UserData>({
id: "",
firstName: "",
lastName: "",
email: "",
language: "",
account: {
id: 0,
isDefault: false,
name: "",
},
});
const isEditMode = !!editingSignature;
// Navigation items
const navigationItems = [
{ id: 'SELECT', label: 'SELECT', icon: <GrCheckboxSelected className="w-4 h-4" /> },
{ id: 'DRAW', label: 'DRAW', icon: <AiOutlineEdit className="w-5 h-5" /> },
{ id: 'UPLOAD', label: 'UPLOAD', icon: <AiOutlineUpload className="w-5 h-5" /> },
];
// Initialize form with editing signature data
useEffect(() => {
if (editingSignature) {
setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`);
setInitials(`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`);
setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD');
// Set font for SELECT tab
const fontOption = signatureOptions.find(option => option.style === editingSignature.font);
if (fontOption) {
setSelectedSignature({ id: fontOption.id, style: fontOption.style });
}
// Set signature blobs for all tabs
if (editingSignature.signatureBlob && editingSignature.initialsBlob) {
setSignatures({
fullSignature: `data:image/png;base64,${editingSignature.signatureBlob}`,
initials: `data:image/png;base64,${editingSignature.initialsBlob}`
});
// Set preview URLs for UPLOAD tab
if (editingSignature.signatureType === 'UPLOAD') {
setPreview1(`data:image/png;base64,${editingSignature.signatureBlob}`);
setPreview2(`data:image/png;base64,${editingSignature.initialsBlob}`);
} }
} })
} .catch((err) => {
}, [editingSignature]); console.error('Error saving signature:', err);
})
const handleSignatureChange = (data: { fullSignature: string | null; initials: string | null }) => {
setSignatures(data);
};
// Cleanup preview URLs when component unmounts or image changes
useEffect(() => {
return () => {
if (preview1) URL.revokeObjectURL(preview1);
if (preview2) URL.revokeObjectURL(preview2);
};
}, [preview1, preview2]);
const handleImageChange = async (
e: React.ChangeEvent<HTMLInputElement>,
setFile: React.Dispatch<React.SetStateAction<File | null>>,
setPreview: React.Dispatch<React.SetStateAction<string | null>>,
type: "fullSignature" | "initials"
) => {
const file = e.target.files?.[0];
if (file) {
setFile(file);
const previewUrl = URL.createObjectURL(file);
setPreview(previewUrl);
// Convert to base64
const base64 = await convertToBase64(file);
// Update the appropriate signature
setSignatures((prev) => ({
...prev,
[type]: base64,
}));
}
};
const convertToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
};
const handleSubmit = async () => {
setIsSubmitting(true);
if (!signatures.fullSignature || !signatures.initials) {
alert('Please provide both signature and initials');
setIsSubmitting(false);
return;
}
try {
const payload = {
signatureType: activeTab,
signatureBase64: signatures.fullSignature,
initialsBase64: signatures.initials,
firstName: fullName.split(' ')[0] || user.firstName,
lastName: fullName.split(' ')[1] || user.lastName,
font: selectedSignature.style
};
let res;
if (isEditMode && editingSignature) {
// Update existing signature
const updatePayload = {
...payload,
signatureId: editingSignature.id
};
res = await axios.put(`${import.meta.env.VITE_BASE_URI}/api/user/signatures`, updatePayload);
console.log("handleUpdate", res);
toast.success("Signature Updated Successfully", {
position: "top-center",
autoClose: 500,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
} else {
// Create new signature
res = await api.post(`/api/user/signatures`, payload);
console.log("handleSubmit", res);
toast.success("Signature Created Successfully", {
position: "top-center",
autoClose: 500,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}
if (res?.data) {
await getAllSignatures();
setShowAddSignature?.(false);
}
} catch (error) {
console.error("Error submitting signature:", error);
toast.error(isEditMode ? "Failed to update signature" : "Failed to create signature", {
position: "top-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
} finally {
setIsSubmitting(false);
}
};
const renderContent = () => {
switch (activeTab) {
case 'SELECT':
return (
<div className="h-full flex flex-col ">
<div className="mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-2">Choose Signature Style</h2>
<p className="text-sm text-gray-600">Select from pre-designed signature styles</p>
</div>
<div className="flex-1 overflow-y-auto">
<div className="space-y-4">
{signatureOptions.map((option) => (
<div key={option.id} className="flex items-center space-x-4 px-2 py-1 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
<input
type="radio"
id={`signature-${option.id}`}
name="signature"
checked={selectedSignature.id === option.id}
onChange={() => setSelectedSignature({ id: option.id, style: option.style })}
className="w-4 h-4 text-blue-600 focus:ring-blue-500"
/>
<div className="flex-1 grid grid-cols-2 gap-2">
<div className=" border-gray-300 rounded-lg px-2 py-1 bg-white ">
<div className="text-xs text-gray-500 mb-2">Signed by:</div>
<div className={`text-lg ${option.style === 'cursive' ? 'font-serif italic' :
option.style === 'script' ? 'font-mono' :
option.style === 'elegant' ? 'font-serif' :
option.style === 'modern' ? 'font-sans tracking-wide' :
'font-sans'
}`}>
{fullName || option.name}
</div>
<div className="text-xs text-gray-400 mt-2">
dca3e646-62cd-4cc...
</div>
</div>
<div className=" rounded-lg px-2 py-2 bg-white ">
<div className="text-xs text-gray-500 mb-2">DS</div>
<div className={`text-2xl ${option.style === 'cursive' ? 'font-serif italic' :
option.style === 'script' ? 'font-mono' :
option.style === 'elegant' ? 'font-serif' :
option.style === 'modern' ? 'font-sans tracking-wide' :
'font-sans'
}`}>
{initials || option.initials}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
case 'DRAW':
return (
<DrawSignatureCanvas
onSignatureChange={handleSignatureChange}
initialSignatures={isEditMode ? signatures : undefined}
/>
);
case 'UPLOAD':
return (
<div className="h-full flex flex-col">
<div className="mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-2">Upload Signature</h2>
</div>
<div className="flex-1 overflow-y-auto">
<div>
<div className="grid grid-cols-12 gap-6">
{/* Upload 1 */}
<div className="col-span-8">
<label className="block text-sm font-medium text-gray-700 mb-2">Full Signature</label>
<label htmlFor="upload1" className=" border-2 border-dashed border-gray-300 rounded-lg h-62 flex items-center justify-center bg-gray-50 hover:bg-gray-100 cursor-pointer transition-colors">
{preview1 ? (
<img src={preview1} alt="Signature Preview" className="max-h-44 object-contain" />
) : (
<div className="text-center">
<MdCloudUpload className="mx-auto h-12 w-12 text-gray-400 mb-3" />
<p className="text-sm text-gray-500 mb-1">Click to upload or drag and drop</p>
<p className="text-xs text-gray-400">PNG, JPG, GIF up to 10MB</p>
</div>
)}
</label>
<input
id="upload1"
type="file"
accept="image/*"
className="hidden"
onChange={(e) => handleImageChange(e, setFile1, setPreview1, "fullSignature")}
/>
</div>
{/* Upload 2 */}
<div className="col-span-4">
<label className="block text-sm font-medium text-gray-700 mb-2">Initials</label>
<label htmlFor="upload2" className=" border-2 border-dashed border-gray-300 rounded-lg h-62 flex items-center justify-center bg-gray-50 hover:bg-gray-100 cursor-pointer transition-colors">
{preview2 ? (
<img src={preview2} alt="Initials Preview" className="max-h-44 object-contain" />
) : (
<div className="text-center">
<MdCloudUpload className="mx-auto h-8 w-8 text-gray-400 mb-2" />
<p className="text-xs text-gray-500">Upload initials</p>
</div>
)}
</label>
<input
id="upload2"
type="file"
accept="image/*"
className="hidden"
onChange={(e) => handleImageChange(e, setFile2, setPreview2, "initials")}
/>
</div>
</div>
</div>
</div>
</div>
);
default:
return null;
}
};
const getUserDetails = async () => {
try {
let res = await api.get(`/api/user/details`);
console.log("getuser", res);
let currUser = res?.data;
setUser(currUser);
// Only set default values if not in edit mode
if (!isEditMode) {
setFullName(currUser.firstName + " " + currUser.lastName);
setInitials(currUser.firstName.slice(0, 1) + currUser.lastName.slice(0, 1));
}
} catch (error) {
console.error("Error fetching user details:", error);
}
}; };
useEffect(() => { useEffect(() => {
getUserDetails(); handleSaveSignature();
}, []); }, [saved])
return ( return (
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 flex items-center justify-center z-50 p-4"> <div className='absolute top-0 left-0 w-full h-full flex items-center justify-center '>
<div className="bg-white rounded-md shadow-xl w-full max-w-[80vw] h-[80vh] flex relative"> <div className='absolute top-0 left-0 w-full h-full bg-slate-100 opacity-60'></div>
{/* Sidebar Navigation */} <div className='relative lg:min-w-[650px] max-w-max mx-auto shadow-md rounded-sm bg-white pt-3 pb-1 px-3'>
<div className="w-64 bg-gray-50 rounded-l-lg border-r border-gray-200 flex flex-col"> <IoMdClose
{/* Header */} onClick={() => setOpenSign(false)}
<div className="px-4 py-4 border-b border-gray-200"> className='absolute right-3 text-slate-400 text-lg hover:text-[var(--primary)] cursor-pointer transition-all' />
<div className="flex items-center space-x-3"> {/* Signature Save types Nav */}
<div className='px-1 py-1'> <div className='flex text-sm'>
<h3 className="text-md font-semibold text-gray-900">Add Signature</h3> <div className={`px-4 pb-1 cursor-pointer border-b-2 font-medium ${activeSaveType === SaveTypeEnum.DRAW ? "border-[var(--primary)] text-[var(--primary)]" : "border-slate-100"}`}
<p className="text-xs text-gray-500">Create your digital signature</p> onClick={() => setActiveSaveType(SaveTypeEnum.DRAW)}>
</div> <p className='tracking-wide'>Draw</p>
</div>
</div> </div>
<div className={`px-4 pb-1 cursor-pointer border-b-2 font-medium ${activeSaveType === SaveTypeEnum.TYPE ? "border-[var(--primary)] text-[var(--primary)]" : "border-slate-100"}`}
{/* Form Inputs */} onClick={() => setActiveSaveType(SaveTypeEnum.TYPE)}>
<div className="px-6 py-2.5 border-b border-gray-200"> <p className='tracking-wide'>Type</p>
<div className="space-y-2">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Full Name
</label>
<input
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
placeholder="Enter full name"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Initials
</label>
<input
type="text"
value={initials}
onChange={(e) => setInitials(e.target.value)}
placeholder="Enter initials"
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div> </div>
<div className={`px-4 pb-1 cursor-pointer border-b-2 font-medium ${activeSaveType === SaveTypeEnum.UPLOAD ? "border-[var(--primary)] text-[var(--primary)]" : "border-slate-100"}`}
{/* Navigation Menu */} onClick={() => setActiveSaveType(SaveTypeEnum.UPLOAD)}>
<div className="flex-1 px-6 py-2.5"> <p className='tracking-wide'>Upload</p>
<nav className="space-y-2">
{navigationItems.map((item) => (
<button
key={item.id}
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
className={`w-full flex items-center space-x-3 px-2 py-2 text-sm font-medium rounded-lg cursor-pointer transition-colors ${activeTab === item.id
? 'bg-blue-50 text-blue-700 border border-blue-200'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
}`}
>
<span className={activeTab === item.id ? 'text-blue-600' : 'text-gray-400'}>
{item.icon}
</span>
<span>{item.label}</span>
</button>
))}
</nav>
</div> </div>
</div> </div>
{/* Main Content Area */} {activeSaveType === SaveTypeEnum.DRAW && <Draw
<div className="flex-1 flex flex-col "> saved={saved}
{/* Header with close button */} setSaved={setSaved}
/>}
{activeSaveType === SaveTypeEnum.TYPE && <TypedSign
saved={saved}
setSaved={setSaved}
/>}
{activeSaveType === SaveTypeEnum.UPLOAD && <SignUpload
saved={saved}
setSaved={setSaved}
/>}
{/* Content */} {/* <div className="mt-3 flex justify-end gap-2">
<div className="flex-1 py-5 px-5 overflow-hidden "> <button
{/* <button // onClick={close}
onClick={() => setShowAddSignature?.(false)} className="border-1 border-[var(--primary)] text-[var(--primary)] px-4 text-sm py-1 rounded shadow opacity-70 hover:opacity-100 transition-all cursor-pointer"
className="absolute top-2 right-3 p-2 hover:bg-gray-100 rounded-full transition-colors z-20" >
> Cancel
<AiOutlineClose className="w-5 h-5 text-gray-500" /> </button>
</button> */} <button
{renderContent()} // onClick={save}
</div> className="bg-[var(--primary)] opacity-90 hover:opacity-100 text-white px-4 text-sm py-1 rounded shadow disabled:opacity-50 transition-all cursor-pointer"
>
{/* Footer Buttons in Main Area */} Save
<div className="p-4 border-t border-gray-200 flex justify-end gap-3"> </button>
<button </div> */}
className="cursor-pointer px-4 py-2 text-sm font-medium rounded-md border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
onClick={() => setShowAddSignature?.(false)}
disabled={isSubmitting}
>
CANCEL
</button>
<button
className="cursor-pointer px-4 py-2 text-sm font-medium rounded-md bg-[var(--modalSubmitBG)] text-white hover:bg-[var(--modalSubmitBGHover)] transition-colors flex items-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed"
onClick={handleSubmit}
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<FaSpinner className="animate-spin" />
{isEditMode ? 'UPDATING...' : 'CREATING...'}
</>
) : (
isEditMode ? 'UPDATE SIGNATURE' : 'CREATE SIGNATURE'
)}
</button>
</div>
</div>
</div> </div>
</div> </div>
); )
}; }
export default AddSignatureModal; export default SignCon;

View File

@ -1,208 +1,128 @@
import { use, useEffect, useState } from "react";
import { PiDotsThreeOutlineVerticalFill } from "react-icons/pi"; import { PiDotsThreeOutlineVerticalFill } from "react-icons/pi";
import { FaCheck, FaDatabase } from "react-icons/fa";
import AddAccountModal from "../accounts/AddAccountModal";
import { Bounce, toast } from "react-toastify"; import { Bounce, toast } from "react-toastify";
import api from "../../config/api"; import api from "../../config/api";
import { IoMdAdd } from "react-icons/io"; import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { FaSpinner } from "react-icons/fa";
import LoaderUi from "../../helper/LoaderUi";
const Templates = () => { const Templates = () => {
const [activeFilter, setActiveFilter] = useState("All"); const [openMenuId, setOpenMenuId] = useState<string | null>(null);
const [loading, setLoading] = useState(false)
const [templates, setTemplates] = useState([])
const accountDetails = useSelector((state: any) => state.user.account);
const accountId = accountDetails.id || "";
const [showMenu, setShowMenu] = useState(null);
const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
const navigate = useNavigate(); const handleAction = async (accountId: string, action: 'accept' | 'reject') => {
const filters = ["All", "My Templates", "Shared"];
const handleAddTemplate = async () => {
console.log("handleAddTemplate called");
try { try {
const res = await api.post(`/api/accounts/${activeAccountId}/templates`); let response = await api.post(`/api/accounts/${accountId}/accept/invite`)
navigate(`/process-step-one/${activeAccountId}/template/${res.data}`) console.log(response)
if (response.data.status === 202) {
toast.success("Login Successful", {
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}
} catch (error) { } catch (error) {
console.log(error) console.error('Error posting data:', error);
}
}
const getAllTemplates = async () => {
setLoading(true)
try {
let res = await api.get(`/api/accounts/${activeAccountId}/templates`)
let templateData = res?.data
setTemplates(templateData)
} catch (error) {
console.log(error)
} finally { } finally {
setLoading(false) setOpenMenuId(null); // close menu
} }
}
useEffect(() => {
if (activeAccountId) {
getAllTemplates()
}
}, [activeAccountId])
const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleString();
}; };
const handleUseFun = (id) => {
alert(id)
}
return ( return (
<div className="p-3 text-sm"> <>
{/* Header */}
<div className="flex items-center justify-between mb-3">
{/* Filter as pill buttons */}
<div className="flex gap-2">
{filters.map((filter) => (
<button
key={filter}
onClick={() => setActiveFilter(filter)}
className={`px-3 py-1 rounded-full text-xs border transition
${activeFilter === filter
? "bg-[var(--primary)] text-white"
: "border-gray-300 text-gray-600 hover:bg-gray-100"
}`}
>
{filter}
</button>
))}
</div>
<div <div className="overflow-x-auto py-2">
onClick={handleAddTemplate} <table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">
className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm cursor-pointer hover:opacity-90"> <thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider">
<IoMdAdd className="text-white" />
<button
className="text-white rounded text-sm hover:opacity-90">
Add Template
</button>
</div>
</div>
{/* Table */}
<div className="max-h-[77vh] pb-16 overflow-y-auto relative no-scrollbar">
<table className="min-w-full border border-gray-200 bg-white shadow rounded table-fixed">
<thead className="bg-gray-100 text-xs text-gray-700 uppercase tracking-wider sticky top-0 z-10">
<tr> <tr>
<th className="px-4 py-3 w-10 bg-gray-100"> <th className="px-4 py-3 w-12"></th>
<div className="w-4 h-4 border border-slate-300 rounded flex items-center justify-center"> <th className="px-4 py-3 text-left">Name</th>
{/* <FaCheck className="text-purple-600 text-xs" /> */} <th className="px-4 py-3 text-center">Owner</th>
</div> <th className="px-4 py-3 text-center">Created Date</th>
</th> <th className="px-4 py-3 text-center">Last Change</th>
<th className="px-4 py-2 text-center bg-gray-100">Name</th> <th className="px-4 py-3 text-center"></th>
<th className="px-4 py-2 text-center bg-gray-100">Created</th> <th className="px-4 py-3 text-center"></th>
<th className="px-4 py-2 text-center bg-gray-100">Last Updated</th>
<th className="px-4 py-2 text-center bg-gray-100">Use</th>
<th className="px-4 py-2 text-center bg-gray-100">Action</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-200">
{templates.length > 0 ? (
<tbody className="divide-y divide-gray-200"> <tr
{templates.map((item, index) => { className="hover:bg-gray-50 cursor-pointer"
return ( >
<tr key={index} className="hover:bg-gray-50 relative"> <td className="px-4 py-3">
<td className="px-4 py-2"> <div className="w-5 h-5 rounded border border-slate-300 flex justify-center items-center">
<div className="w-4 h-4 border border-slate-300 rounded flex items-center justify-center"> {/* {selectedTransactions.includes(tx.id) && (
{/* <FaCheck className="text-purple-600 text-xs" /> */} <FaCheck className="text-purple-600 text-sm" />
</div> )} */}
</td> </div>
<td className="px-4 py-2 text-sm text-center">{item.name || 'Dummy'}</td> </td>
<td className="px-4 py-2 text-sm text-center">{formatDate(item.createdAt)}</td> <td className="px-4 py-3">
<td className="px-4 py-2 text-sm text-center">{formatDate(item.lastModifiedAt)}</td> <div className="flex items-center gap-2">
<td className="px-4 py-2 text-sm text-center" onClick={() => handleUseFun(item.id)}><span className="from-green-500 cursor-pointer to-blue-500 bg-gradient-to-br px-3 py-1 rounded-lg text-white font-semibold text-[12px]">Use</span></td> <span>{"test"}</span>
{/* Dropdown Trigger + Tooltip */} </div>
<td className="px-4 py-2 text-center relative"> </td>
<button <td className="px-4 py-3 text-center">{"1"}</td>
onClick={() => setShowMenu(showMenu === index ? null : index)} <td className="px-4 py-3 text-center">
className="text-slate-400 hover:text-black focus:outline-none" <span className={`px-2 py-0.5 rounded-xl text-xs font-medium`}>
{"role"}
</span>
</td>
<td className="px-4 py-3 text-center">
<span className={`px-2 py-0.5 rounded-xl text-xs font-medium`}>
{"text"}
</span>
</td>
<td>
</td>
<td className="px-4 py-3 text-center relative">
<button
className="mx-auto"
aria-label="Actions menu"
>
<PiDotsThreeOutlineVerticalFill className="text-lg text-slate-300 hover:text-black" />
</button>
{/* Dropdown menu */}
{/* {openMenuId === "1" && (
<div
className="absolute right-13 top-6 mt-1 w-18 bg-white border text-[13px] font-bold border-gray-300 rounded-md shadow-lg z-10"
onClick={(e) => e.stopPropagation()} // prevent row click closing
> >
<PiDotsThreeOutlineVerticalFill className="text-lg" /> <button
</button> className="w-full px-3 py-1 text-left text-green-700 hover:bg-green-100 rounded-t-md cursor-pointer"
onClick={() => handleAction(1, 'accept')}
{showMenu === index && (
<div
className="absolute right-15 top-5 mt-2 w-32 bg-white border border-gray-200 rounded shadow-lg z-10 transition-opacity duration-300 ease-in-out"
tabIndex={0} // Make it focusable
onBlur={(e) => {
// Ensure it only closes if focus truly leaves the container
if (!e.currentTarget.contains(e.relatedTarget)) {
setShowMenu(null);
}
}}
> >
<button Accept
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" </button>
onClick={() => { <button
setShowMenu(null); className="w-full px-3 py-1 text-left text-red-700 hover:bg-red-100 rounded-b-md cursor-pointer"
navigate(`/process-step-two/${accountId}/template/${item.id}`); onClick={() => handleAction(1, 'reject')}
}} >
> Reject
Edit </button>
</button> </div>
<button )} */}
className="block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-100" </td>
onClick={() => {
setShowMenu(null);
console.log('Delete clicked', item);
}}
>
Delete
</button>
</div>
)}
</td>
</tr> </tr>
);
})}
</tbody>
) : ( </tbody>
<tbody>
<tr>
<td colSpan="5">
<div className="relative flex justify-center items-center h-100 w-full">
{/* Pulsing background div */}
<div className="absolute inset-0 bg-gradient-to-r from-gray-200 to-white animate-pulse"></div>
{/* Loader stays on top, no animation */}
<LoaderUi classStyle="relative h-16 w-16" />
</div>
</td>
</tr>
</tbody>
)}
</table> </table>
</div> </div>
</>
</div>
); );
}; };
export default Templates; export default Templates;

View File

@ -26,7 +26,6 @@ api.interceptors.response.use(
return response; return response;
}, },
async error => { async error => {
const originalRequest = error.config; const originalRequest = error.config;
// Prevent multiple refresh attempts // Prevent multiple refresh attempts
// if (error.response?.status === 401 || error.response?.status === 500) { // if (error.response?.status === 401 || error.response?.status === 500) {

View File

@ -1,19 +0,0 @@
interface StyleProps {
classStyle : string
}
const LoaderUi = ({ classStyle }:StyleProps) => {
return (
<div className={classStyle}>
<svg viewBox="0 0 240 240" height="240" width="240" className="pl">
<circle strokeLinecap="round" strokeDashoffset="-330" strokeDasharray="0 660" strokeWidth="20" stroke="#000" fill="none" r="105" cy="120" cx="120" className="pl__ring pl__ring--a"></circle>
<circle strokeLinecap="round" strokeDashoffset="-110" strokeDasharray="0 220" strokeWidth="20" stroke="#000" fill="none" r="35" cy="120" cx="120" className="pl__ring pl__ring--b"></circle>
<circle strokeLinecap="round" strokeDasharray="0 440" strokeWidth="20" stroke="#000" fill="none" r="70" cy="120" cx="85" className="pl__ring pl__ring--c"></circle>
<circle strokeLinecap="round" strokeDasharray="0 440" strokeWidth="20" stroke="#000" fill="none" r="70" cy="120" cx="155" className="pl__ring pl__ring--d"></circle>
</svg>
</div>
)
}
export default LoaderUi

View File

@ -94,208 +94,4 @@ canvas {
/* Loader CSS */ /* Loader CSS */
/* From Uiverse.io by Juanes200122 */
.pl {
width: 6em;
height: 6em;
}
.pl__ring {
animation: ringA 2s linear infinite;
}
.pl__ring--a {
stroke: #41644A;
}
.pl__ring--b {
animation-name: ringB;
stroke: #7e7e7e;
}
.pl__ring--c {
animation-name: ringC;
stroke: #686868;
}
.pl__ring--d {
animation-name: ringD;
stroke: #41644A;
}
/* Animations */
@keyframes ringA {
from, 4% {
stroke-dasharray: 0 660;
stroke-width: 20;
stroke-dashoffset: -330;
}
12% {
stroke-dasharray: 60 600;
stroke-width: 30;
stroke-dashoffset: -335;
}
32% {
stroke-dasharray: 60 600;
stroke-width: 30;
stroke-dashoffset: -595;
}
40%, 54% {
stroke-dasharray: 0 660;
stroke-width: 20;
stroke-dashoffset: -660;
}
62% {
stroke-dasharray: 60 600;
stroke-width: 30;
stroke-dashoffset: -665;
}
82% {
stroke-dasharray: 60 600;
stroke-width: 30;
stroke-dashoffset: -925;
}
90%, to {
stroke-dasharray: 0 660;
stroke-width: 20;
stroke-dashoffset: -990;
}
}
@keyframes ringB {
from, 12% {
stroke-dasharray: 0 220;
stroke-width: 20;
stroke-dashoffset: -110;
}
20% {
stroke-dasharray: 20 200;
stroke-width: 30;
stroke-dashoffset: -115;
}
40% {
stroke-dasharray: 20 200;
stroke-width: 30;
stroke-dashoffset: -195;
}
48%, 62% {
stroke-dasharray: 0 220;
stroke-width: 20;
stroke-dashoffset: -220;
}
70% {
stroke-dasharray: 20 200;
stroke-width: 30;
stroke-dashoffset: -225;
}
90% {
stroke-dasharray: 20 200;
stroke-width: 30;
stroke-dashoffset: -305;
}
98%, to {
stroke-dasharray: 0 220;
stroke-width: 20;
stroke-dashoffset: -330;
}
}
@keyframes ringC {
from {
stroke-dasharray: 0 440;
stroke-width: 20;
stroke-dashoffset: 0;
}
8% {
stroke-dasharray: 40 400;
stroke-width: 30;
stroke-dashoffset: -5;
}
28% {
stroke-dasharray: 40 400;
stroke-width: 30;
stroke-dashoffset: -175;
}
36%, 58% {
stroke-dasharray: 0 440;
stroke-width: 20;
stroke-dashoffset: -220;
}
66% {
stroke-dasharray: 40 400;
stroke-width: 30;
stroke-dashoffset: -225;
}
86% {
stroke-dasharray: 40 400;
stroke-width: 30;
stroke-dashoffset: -395;
}
94%, to {
stroke-dasharray: 0 440;
stroke-width: 20;
stroke-dashoffset: -440;
}
}
@keyframes ringD {
from, 8% {
stroke-dasharray: 0 440;
stroke-width: 20;
stroke-dashoffset: 0;
}
16% {
stroke-dasharray: 40 400;
stroke-width: 30;
stroke-dashoffset: -5;
}
36% {
stroke-dasharray: 40 400;
stroke-width: 30;
stroke-dashoffset: -175;
}
44%, 50% {
stroke-dasharray: 0 440;
stroke-width: 20;
stroke-dashoffset: -220;
}
58% {
stroke-dasharray: 40 400;
stroke-width: 30;
stroke-dashoffset: -225;
}
78% {
stroke-dasharray: 40 400;
stroke-width: 30;
stroke-dashoffset: -395;
}
86%, to {
stroke-dasharray: 0 440;
stroke-width: 20;
stroke-dashoffset: -440;
}
}

View File

@ -1,188 +1,27 @@
// import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
// // import { v4 as uuidv4 } from "uuid"; // import { v4 as uuidv4 } from "uuid";
// import { hideLoader, showLoader } from "./loaderSlice"; import { hideLoader, showLoader } from "./loaderSlice";
// // import { showModal } from "./modalSlice"; // import { showModal } from "./modalSlice";
// // import { updateSelectedDocuments } from "./selectedDocsSlice"; // import { updateSelectedDocuments } from "./selectedDocsSlice";
// import api from "../../config/api"; import api from "../../config/api";
// import { Bounce, toast } from "react-toastify"; import { Bounce, toast } from "react-toastify";
// // import { createAxiosInstance } from "../../config/axiosInstance"; // import { createAxiosInstance } from "../../config/axiosInstance";
// // const axiosInstance = createAxiosInstance(); // const axiosInstance = createAxiosInstance();
// export interface Document {
// id: string;
// name: string;
// extension: string;
// original?: boolean;
// corrupted?: boolean;
// isCompleted: boolean;
// status: string;
// }
// export interface DocumentState {
// documents: Document[];
// loading: boolean;
// error: string | null;
// }
// const initialState: DocumentState = {
// documents: [],
// loading: false,
// error: null,
// };
// interface uploadArgs {
// formData: FormData;
// accountId: string;
// transactionId: string;
// }
// // Fetch documents from API
// export const fetchDocuments = createAsyncThunk<Document[], { accountId: string, transactionId: string }>(
// "documents/fetchDocuments",
// async ({ accountId, transactionId }, { rejectWithValue }) => {
// try {
// const response = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/documents?include_images=true`);
// return response.data as Document[];
// } catch (error: any) {
// // if(error.status === 401) {}
// return rejectWithValue(error.response?.data || "Failed to fetch documents");
// }
// }
// );
// // Upload multiple documents using FormData (Key: "document")
// export const uploadDocuments = createAsyncThunk<Document[], uploadArgs>(
// "documents/uploadDocuments",
// async ({ formData, accountId, transactionId }, { dispatch, rejectWithValue }) => {
// try {
// dispatch(showLoader("Uploading documents..."));
// console.log("Form Data", formData.getAll("document"))
// const response = await api.post(`/api/accounts/${accountId}/transactions/${transactionId}/documents`, formData, {
// headers: {
// "Content-Type": "multipart/form-data"
// },
// });
// console.log("Form Uploaded Response DocumentSlice Line 185", response.data)
// dispatch(hideLoader());
// return [response.data];
// } catch (error: any) {
// dispatch(hideLoader());
// toast.error("Error Uploading Document", {
// position: "bottom-right",
// autoClose: 3000,
// hideProgressBar: false,
// closeOnClick: false,
// pauseOnHover: false,
// draggable: false,
// progress: undefined,
// theme: "light",
// transition: Bounce,
// });
// console.log("Failed Uploading Documents")
// throw new Error("Failed Uploading Documents")
// // Mock data in case of failure
// // const failedDocuments = formData.getAll("document").map((file: any) => ({
// // id: uuidv4(),
// // name: file.name,
// // extension: file.name.split(".").pop() || "unknown",
// // original: Math.random() < 0.5,
// // corrupted: Math.random() < 0.5,
// // }));
// // dispatch(updateSelectedDocuments({ documents: failedDocuments }));
// // return failedDocuments; // Instead of rejecting, return mock data
// }
// }
// );
// const documentSlice = createSlice({
// name: "documents",
// initialState,
// reducers: {
// setDocuments: (state, action: PayloadAction<Document[]>) => {
// state.documents = action.payload;
// },
// setDocProgComplete: (state, action: PayloadAction<string>) => {
// const docIndex = state.documents.findIndex(doc => doc.id === action.payload);
// if (docIndex !== -1) {
// state.documents[docIndex].isCompleted = true;
// }
// },
// resetDocuments: (state) => {
// state.documents = [];
// },
// deleteDocument: (state, action: PayloadAction<string>) => {
// state.documents = state.documents.filter(doc => doc.id !== action.payload);
// },
// },
// extraReducers: (builder) => {
// builder
// // Fetch documents
// .addCase(fetchDocuments.pending, (state) => {
// state.loading = true;
// state.error = null;
// })
// .addCase(fetchDocuments.fulfilled, (state, action: PayloadAction<Document[]>) => {
// state.loading = false;
// state.documents = action.payload;
// })
// .addCase(fetchDocuments.rejected, (state, action) => {
// state.loading = false;
// state.error = action.payload as string;
// })
// // Upload multiple documents
// .addCase(uploadDocuments.pending, (state) => {
// state.loading = true;
// state.error = null;
// })
// .addCase(uploadDocuments.fulfilled, (state, action: PayloadAction<Document[]>) => {
// state.loading = false;
// state.documents = [...action.payload, ...state.documents];
// })
// .addCase(uploadDocuments.rejected, (state, action) => {
// state.loading = false;
// state.error = action.payload as string;
// });
// },
// });
// export const { resetDocuments, setDocuments, setDocProgComplete, deleteDocument } = documentSlice.actions;
// export default documentSlice.reducer;
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { toast, Bounce } from 'react-toastify';
import api from '../../config/api';
import { hideLoader, showLoader } from './loaderSlice';
// import { showLoader, hideLoader } from '../loader/loaderSlice';
// -----------------------
// Document Type
// -----------------------
export interface Document { export interface Document {
id?: string; id: string;
name: string; name: string;
url: string; extension: string;
type: string; original?: boolean;
pageCount?: number; corrupted?: boolean;
[key: string]: any; isCompleted: boolean;
status: string;
} }
// ----------------------- export interface DocumentState {
// Initial State
// -----------------------
interface DocumentState {
documents: Document[]; documents: Document[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
@ -194,87 +33,80 @@ const initialState: DocumentState = {
error: null, error: null,
}; };
// ----------------------- interface uploadArgs {
// Thunk: Fetch Documents formData: FormData;
// ----------------------- accountId: string;
export const fetchDocuments = createAsyncThunk< transactionId: string;
Document[], }
{ accountId: string; transactionId: string }
>(
'documents/fetchDocuments', // Fetch documents from API
async ({ accountId, transactionId }, { rejectWithValue, getState }) => { export const fetchDocuments = createAsyncThunk<Document[], { accountId: string, transactionId: string }>(
"documents/fetchDocuments",
async ({ accountId, transactionId }, { rejectWithValue }) => {
try { try {
const state = getState() as { const response = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/documents?include_images=true`);
selectedRecipients: { transactionTemplateToggle: string };
};
const toggle = state.selectedRecipients.transactionTemplateToggle;
const response = await api.get(
`/api/accounts/${accountId}/${toggle}/${transactionId}/documents?include_images=true`
);
return response.data as Document[]; return response.data as Document[];
} catch (error: any) { } catch (error: any) {
return rejectWithValue(error.response?.data || 'Failed to fetch documents'); // if(error.status === 401) {}
return rejectWithValue(error.response?.data || "Failed to fetch documents");
} }
} }
); );
// ----------------------- // Upload multiple documents using FormData (Key: "document")
// Thunk: Upload Documents export const uploadDocuments = createAsyncThunk<Document[], uploadArgs>(
// ----------------------- "documents/uploadDocuments",
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); async ({ formData, accountId, transactionId }, { dispatch, rejectWithValue }) => {
export const uploadDocuments = createAsyncThunk<
Document[],
{ formData: FormData; accountId: string; transactionId: string }
>(
'documents/uploadDocuments',
async ({ formData, accountId, transactionId }, { dispatch, rejectWithValue, getState }) => {
try { try {
dispatch(showLoader('Uploading documents...')); dispatch(showLoader("Uploading documents..."));
console.log("Form Data", formData.getAll("document"))
const state = getState() as { const response = await api.post(`/api/accounts/${accountId}/transactions/${transactionId}/documents`, formData, {
selectedRecipients: { transactionTemplateToggle: string }; headers: {
}; "Content-Type": "multipart/form-data"
const toggle = state.selectedRecipients.transactionTemplateToggle; },
});
console.log(`/api/accounts/${accountId}/${toggle}/${transactionId}/documents`) console.log("Form Uploaded Response DocumentSlice Line 185", response.data)
const response = await api.post(
`/api/accounts/${accountId}/${toggle}/${transactionId}/documents`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
console.log("// await sleep(5000);", response.data)
// await sleep(5000);
dispatch(hideLoader()); dispatch(hideLoader());
return [response.data]; return [response.data];
} catch (error: any) { } catch (error: any) {
dispatch(hideLoader()); dispatch(hideLoader());
toast.error("Error Uploading Document", {
toast.error('Error Uploading Document', { position: "bottom-right",
position: 'bottom-right',
autoClose: 3000, autoClose: 3000,
theme: 'light', hideProgressBar: false,
closeOnClick: false,
pauseOnHover: false,
draggable: false,
progress: undefined,
theme: "light",
transition: Bounce, transition: Bounce,
}); });
return rejectWithValue('Failed Uploading Documents'); console.log("Failed Uploading Documents")
throw new Error("Failed Uploading Documents")
// Mock data in case of failure
// const failedDocuments = formData.getAll("document").map((file: any) => ({
// id: uuidv4(),
// name: file.name,
// extension: file.name.split(".").pop() || "unknown",
// original: Math.random() < 0.5,
// corrupted: Math.random() < 0.5,
// }));
// dispatch(updateSelectedDocuments({ documents: failedDocuments }));
// return failedDocuments; // Instead of rejecting, return mock data
} }
} }
); );
// ----------------------- const documentSlice = createSlice({
// Slice name: "documents",
// -----------------------
const documentsSlice = createSlice({
name: 'documents',
initialState, initialState,
reducers: { reducers: {
setDocuments: (state, action: PayloadAction<Document[]>) => { setDocuments: (state, action: PayloadAction<Document[]>) => {
@ -295,7 +127,7 @@ const documentsSlice = createSlice({
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
// fetchDocuments // Fetch documents
.addCase(fetchDocuments.pending, (state) => { .addCase(fetchDocuments.pending, (state) => {
state.loading = true; state.loading = true;
state.error = null; state.error = null;
@ -307,22 +139,16 @@ const documentsSlice = createSlice({
.addCase(fetchDocuments.rejected, (state, action) => { .addCase(fetchDocuments.rejected, (state, action) => {
state.loading = false; state.loading = false;
state.error = action.payload as string; state.error = action.payload as string;
toast.error('Failed to fetch documents', {
position: 'bottom-right',
autoClose: 3000,
theme: 'light',
transition: Bounce,
});
}) })
// uploadDocuments // Upload multiple documents
.addCase(uploadDocuments.pending, (state) => { .addCase(uploadDocuments.pending, (state) => {
state.loading = true; state.loading = true;
state.error = null; state.error = null;
}) })
.addCase(uploadDocuments.fulfilled, (state, action: PayloadAction<Document[]>) => { .addCase(uploadDocuments.fulfilled, (state, action: PayloadAction<Document[]>) => {
state.loading = false; state.loading = false;
state.documents.push(...action.payload); state.documents = [...action.payload, ...state.documents];
}) })
.addCase(uploadDocuments.rejected, (state, action) => { .addCase(uploadDocuments.rejected, (state, action) => {
state.loading = false; state.loading = false;
@ -331,8 +157,5 @@ const documentsSlice = createSlice({
}, },
}); });
// ----------------------- export const { resetDocuments, setDocuments, setDocProgComplete, deleteDocument } = documentSlice.actions;
// Exports export default documentSlice.reducer;
// -----------------------
export const { deleteDocument, resetDocuments, setDocuments, setDocProgComplete } = documentsSlice.actions;
export default documentsSlice.reducer;

View File

@ -1,340 +1,11 @@
// import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// import { createAsyncThunk } from '@reduxjs/toolkit'; import { createAsyncThunk } from '@reduxjs/toolkit';
// import { toast } from 'react-toastify';
// import colors from '../../utlity/colorsArr';
// import api from '../../config/api';
// export enum recipientType {
// SIGNER = "Signer",
// INPERSONSIGNER = "InPersonSigner",
// CC = "CC"
// }
// export interface Recipient {
// id?: string;
// firstName: string;
// lastName: string;
// routingOrder: number;
// email: string;
// hostName: string;
// hostEmail: string;
// type: recipientType;
// color?: string;
// }
// export interface SelectedRecipientInt {
// recipients: Recipient[],
// signingOrder: boolean,
// transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE
// }
// interface AddRecipientArgs {
// recipientsData: Recipient[];
// accountId: string;
// transactionId: string;
// }
// export enum TRANSACTION_TEMPLATE_TOGGLE {
// TRANSACTIONS = "transactions",
// TEMPLATES = "templates"
// }
// const initialState: SelectedRecipientInt = {
// recipients: [],
// signingOrder: false,
// transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS
// };
// export const addRecipientAsync = createAsyncThunk<Recipient[], AddRecipientArgs>(
// 'selectedRecipients/addRecipientAsync',
// async ({ recipientsData, accountId, transactionId }, { rejectWithValue }) => {
// console.log(recipientsData, accountId, transactionId, "recipientsData Line 46");
// try {
// console.log(recipientsData, "recipientsData --- Line 48");
// const updateRecipientsData = recipientsData.filter((recipient, idx) => recipient.id !== null);
// const createRecipientsData = recipientsData.filter((recipient, idx) => recipient.id == null);
// console.log(updateRecipientsData, "updateRecipientsData Line 52");
// console.log(createRecipientsData, "createRecipientsData Line 53");
// const resUpdate = await api.put(
// `/api/accounts/${accountId}/transactions/${transactionId}/recipients`,
// updateRecipientsData
// );
// const resCreate = await api.post(
// `/api/accounts/${accountId}/transactions/${transactionId}/recipients`,
// createRecipientsData
// );
// if (createRecipientsData.length === 0) resCreate.data = [];
// if (updateRecipientsData.length === 0) resUpdate.data = [];
// console.log(resCreate, "resCreate");
// console.log(resUpdate, "resUpdate");
// return [...resCreate.data, ...resUpdate.data] as Recipient[];
// } catch (err: any) {
// return rejectWithValue(err.response?.data || "Failed to add recipient");
// }
// }
// );
// interface RemoveRecipientAsyncInterfaceObj {
// id?: string;
// email?: string;
// }
// interface RemoveRecipientAsyncInterface {
// recipientId: number | null;
// accountId: string;
// transactionId: string;
// email: string;
// }
// export const removeRecipientAsync = createAsyncThunk<string | number, RemoveRecipientAsyncInterface>(
// 'selectedRecipients/removeRecipientAsync',
// async (obj, { rejectWithValue }) => {
// try {
// if (obj.recipientId) {
// const res = await api.delete(`/api/accounts/${obj.accountId}/transactions/${obj.transactionId}/recipients/${obj.recipientId}`);
// console.log(res, "Line 77")
// return obj.recipientId;
// }
// return obj.email
// } catch (err: any) {
// return rejectWithValue("Failed to remove recipient");
// }
// }
// );
// export const fetchRecipients = createAsyncThunk<Recipient[], { accountId: string, transactionId: string }>(
// 'selectedRecipients/fetchRecipients',
// async ({ accountId, transactionId }, { rejectWithValue }) => {
// try {
// const response = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/recipients`);
// return response.data as Recipient[];
// } catch (err: any) {
// return rejectWithValue(err.response?.data || "Failed to fetch recipients");
// }
// }
// );
// const selectedRecipientsSlice = createSlice({
// name: 'selectedRecipients',
// initialState,
// reducers: {
// // Add a single recipient
// addRecipient: (state, action: PayloadAction<Recipient>) => {
// for (const recipient of state.recipients) {
// if (recipient.email === action.payload.email) {
// toast.dismiss();
// toast.error("Email already exists !", {
// position: "bottom-right",
// autoClose: 5000,
// hideProgressBar: false,
// closeOnClick: false,
// pauseOnHover: true,
// draggable: true,
// progress: undefined,
// theme: "light",
// });
// return state;
// }
// }
// // Adding Recipient on top if type is inPersonSigner
// console.log("Getting Type ", action.payload.type)
// if (action.payload.type === recipientType.INPERSONSIGNER) {
// state.signingOrder = true
// state.recipients = [action.payload, ...state.recipients]
// } else {
// state.recipients.push(action.payload);
// }
// },
// resetRecipients: (state) => {
// state.recipients = [];
// },
// toggleToTemplates: (state) => {
// state.transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
// },
// // Replace the entire selectedRecipients array
// setSelectedRecipients: (state, action: PayloadAction<Recipient[]>) => {
// for (const [index, recipient] of action.payload.entries()) {
// if ((recipient.type === recipientType.INPERSONSIGNER && index > 0) && action.payload[index - 1].type !== recipientType.INPERSONSIGNER) {
// return state;
// }
// }
// state.recipients = action.payload;
// },
// setSigningOrder: (state, action: PayloadAction<boolean>) => {
// for (const recipient of state.recipients) {
// if (recipient.type === recipientType.INPERSONSIGNER) {
// if (state.signingOrder) return state;
// state.signingOrder = true;
// return state
// }
// }
// state.signingOrder = action.payload;
// },
// // Remove Recipients
// removeSelectedRecipient: (state, action: PayloadAction<Recipient>) => {
// console.log("removeSelectedRecipient Called")
// state.recipients = state.recipients.filter((recipient) => recipient.email !== action.payload.email);
// },
// // Add Color in Recipient
// addColorsToRecipient: (state) => {
// // const colors = generateUniqueColors(state.recipients.length);
// for (const [index, recipient] of state.recipients.entries()) {
// console.log(state)
// recipient.color = colors[index]
// }
// return state;
// },
// updateRecipientOrder: (state, action: PayloadAction<{ id: string; routingOrder: number }>) => {
// console.log("recipient order called Line 159")
// const { id, routingOrder } = action.payload;
// const recipient = state.recipients.find((r) => r.id === id || r.email === id);
// if (recipient) {
// recipient.routingOrder = routingOrder;
// }
// },
// updateRecipientIds: (state, action: PayloadAction<Recipient[]>) => {
// console.log("updateRecipientIds", console.table(action.payload));
// for (const recObj of state.recipients) {
// const email = recObj.email
// for (const recObj2 of action.payload) {
// recObj.id = recObj2.id;
// }
// }
// },
// resetSelRecipient: (state) => {
// state = initialState;
// return state;
// }
// },
// extraReducers: (builder) => {
// builder
// // Fetch Recipient - START
// .addCase(fetchRecipients.pending, (state) => {
// })
// .addCase(fetchRecipients.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
// // console.log("Fetched recipients successfully:", action.payload);
// // Assign colors to recipients
// const assignedRecipients = action.payload.map((recipient, index) => ({
// ...recipient,
// color: colors[index], // Cycle if more recipients than colors
// }));
// state.recipients = assignedRecipients;
// })
// .addCase(fetchRecipients.rejected, (state, action) => {
// console.error("Failed to fetch recipients:", action.payload);
// toast.error("Failed to fetch recipients", {
// position: "bottom-right",
// autoClose: 5000,
// hideProgressBar: false,
// closeOnClick: false,
// pauseOnHover: true,
// draggable: true,
// progress: undefined,
// theme: "light",
// });
// })
// // Fetch Recipient - END
// // Add Recipient - START
// .addCase(addRecipientAsync.pending, (state) => {
// })
// .addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
// // Optionally avoid duplicates if needed
// console.log("addRecipientAsync Called Line 258", action.payload);
// // const newRecipients = action.payload.filter(newRec => {
// // return !state.recipients.some(existing => existing existing.id === newRec.id);
// // });
// // const newRecipients = action.payload.filter(newRec => {
// // return !state.recipients.some(existing => "id" in existing && existing.id === newRec.id);
// // });
// // // Add new recipients and assign colors
// // const startIndex = state.recipients.length;
// // newRecipients.forEach((rec, index) => {
// // rec.color = colors[startIndex + index];
// // state.recipients.push(rec);
// // });
// state.recipients = action.payload.map((recipient, index) => ({
// ...recipient,
// color: colors[index % colors.length], // Cycle through colors
// }));
// })
// .addCase(addRecipientAsync.rejected, (state, action) => {
// console.error("Failed to fetch recipients:", action.payload);
// toast.error("Failed to fetch recipients", {
// position: "bottom-right",
// autoClose: 5000,
// hideProgressBar: false,
// closeOnClick: false,
// pauseOnHover: true,
// draggable: true,
// progress: undefined,
// theme: "light",
// });
// })
// // Add Recipient - END
// // Fetch Recipient - START
// .addCase(removeRecipientAsync.pending, (state) => {
// })
// .addCase(removeRecipientAsync.fulfilled, (state, action: PayloadAction<string | number>) => {
// state.recipients = state.recipients.filter(
// (recipient) => recipient.id !== action.payload && recipient.email !== action.payload
// );
// })
// .addCase(removeRecipientAsync.rejected, (state, action) => {
// console.error("Failed to Delete recipients:", action.payload);
// toast.error("Failed to Delete recipients", {
// position: "bottom-right",
// autoClose: 5000,
// hideProgressBar: false,
// closeOnClick: false,
// pauseOnHover: true,
// draggable: true,
// progress: undefined,
// theme: "light",
// });
// })
// // Fetch Recipient - END
// }
// });
// export const { addRecipient, setSelectedRecipients, toggleToTemplates, removeSelectedRecipient, setSigningOrder, addColorsToRecipient, updateRecipientIds, resetSelRecipient, updateRecipientOrder, resetRecipients } = selectedRecipientsSlice.actions;
// export default selectedRecipientsSlice.reducer;
import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import colors from '../../utlity/colorsArr'; import colors from '../../utlity/colorsArr';
import api from '../../config/api'; import api from '../../config/api';
export enum recipientType { export enum recipientType {
SIGNER = "Signer", SIGNER = "Signer",
INPERSONSIGNER = "InPersonSigner", INPERSONSIGNER = "InPersonSigner",
@ -351,111 +22,58 @@ export interface Recipient {
hostEmail: string; hostEmail: string;
type: recipientType; type: recipientType;
color?: string; color?: string;
roleName:string
} }
export interface SelectedRecipientInt { export interface SelectedRecipientInt {
recipients: Recipient[], recipients: Recipient[],
signingOrder: boolean, signingOrder: boolean
transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE
} }
interface AddRecipientArgs { interface AddRecipientArgs {
recipientsData: Recipient[]; recipientsData: Recipient[];
accountId: string; accountId: string;
transactionId: string; transactionId: string;
transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE;
}
interface RemoveRecipientAsyncInterface {
recipientId: number | null;
accountId: string;
transactionId: string;
email: string;
transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE;
}
export enum TRANSACTION_TEMPLATE_TOGGLE {
TRANSACTIONS = "transactions",
TEMPLATES = "templates"
} }
const initialState: SelectedRecipientInt = { const initialState: SelectedRecipientInt = {
recipients: [], recipients: [],
signingOrder: false, signingOrder: false
transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS
}; };
// 1. Add Recipient Async export const addRecipientAsync = createAsyncThunk<Recipient[], AddRecipientArgs>(
export const addRecipientAsync = createAsyncThunk<
Recipient[],
{ recipientsData: Recipient[]; accountId: string; transactionId: string }
>(
'selectedRecipients/addRecipientAsync', 'selectedRecipients/addRecipientAsync',
async ({ recipientsData, accountId, transactionId }, { rejectWithValue, getState }) => { async ({ recipientsData, accountId, transactionId }, { rejectWithValue }) => {
console.log(recipientsData, accountId, transactionId, "recipientsData Line 46");
try { try {
const state = getState() as { selectedRecipients: SelectedRecipientInt }; const response = await api.post(
const toggle = state.selectedRecipients.transactionTemplateToggle; `/api/accounts/${accountId}/transactions/${transactionId}/recipients`,
recipientsData
const updateRecipientsData = recipientsData.filter(recipient => recipient.id !== null); );
const createRecipientsData = recipientsData.filter(recipient => recipient.id == null); console.log(response)
return response.data as Recipient[];
const basePath = `/api/accounts/${accountId}/${toggle}/${transactionId}/recipients`;
const resUpdate = updateRecipientsData.length
? await api.put(basePath, updateRecipientsData)
: { data: [] };
const resCreate = createRecipientsData.length
? await api.post(basePath, createRecipientsData)
: { data: [] };
return [...resCreate.data, ...resUpdate.data] as Recipient[];
} catch (err: any) { } catch (err: any) {
return rejectWithValue(err.response?.data || "Failed to add recipient"); return rejectWithValue(err.response?.data || "Failed to add recipient");
} }
} }
); );
// 2. Remove Recipient Async export const removeRecipientAsync = createAsyncThunk<string, string>(
export const removeRecipientAsync = createAsyncThunk<
string | number,
{ recipientId: number | null; accountId: string; transactionId: string; email: string }
>(
'selectedRecipients/removeRecipientAsync', 'selectedRecipients/removeRecipientAsync',
async ({ recipientId, accountId, transactionId, email }, { rejectWithValue, getState }) => { async (email, { rejectWithValue }) => {
try { try {
const state = getState() as { selectedRecipients: SelectedRecipientInt }; await api.delete(`/api/recipients`, { data: { email } });
const toggle = state.selectedRecipients.transactionTemplateToggle; return email; // return email so we can filter from state
const basePath = `/api/accounts/${accountId}/${toggle}/${transactionId}/recipients`;
if (recipientId) {
await api.delete(`${basePath}/${recipientId}`);
return recipientId;
}
return email;
} catch (err: any) { } catch (err: any) {
return rejectWithValue("Failed to remove recipient"); return rejectWithValue(err.response?.data || "Failed to remove recipient");
} }
} }
); );
// 3. Fetch Recipients Async export const fetchRecipients = createAsyncThunk<Recipient[], { accountId: string, transactionId: string }>(
export const fetchRecipients = createAsyncThunk<
Recipient[],
{ accountId: string; transactionId: string }
>(
'selectedRecipients/fetchRecipients', 'selectedRecipients/fetchRecipients',
async ({ accountId, transactionId }, { rejectWithValue, getState }) => { async ({ accountId, transactionId }, { rejectWithValue }) => {
try { try {
const state = getState() as { selectedRecipients: SelectedRecipientInt }; const response = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/recipients`);
const toggle = state.selectedRecipients.transactionTemplateToggle;
const basePath = `/api/accounts/${accountId}/${toggle}/${transactionId}/recipients`;
const response = await api.get(basePath);
return response.data as Recipient[]; return response.data as Recipient[];
} catch (err: any) { } catch (err: any) {
return rejectWithValue(err.response?.data || "Failed to fetch recipients"); return rejectWithValue(err.response?.data || "Failed to fetch recipients");
@ -467,6 +85,7 @@ const selectedRecipientsSlice = createSlice({
name: 'selectedRecipients', name: 'selectedRecipients',
initialState, initialState,
reducers: { reducers: {
// Add a single recipient
addRecipient: (state, action: PayloadAction<Recipient>) => { addRecipient: (state, action: PayloadAction<Recipient>) => {
for (const recipient of state.recipients) { for (const recipient of state.recipients) {
if (recipient.email === action.payload.email) { if (recipient.email === action.payload.email) {
@ -474,29 +93,34 @@ const selectedRecipientsSlice = createSlice({
toast.error("Email already exists !", { toast.error("Email already exists !", {
position: "bottom-right", position: "bottom-right",
autoClose: 5000, autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light", theme: "light",
}); });
return state; return state;
} }
} }
// Adding Recipient on top if type is inPersonSigner
console.log("Getting Type ", action.payload.type)
if (action.payload.type === recipientType.INPERSONSIGNER) { if (action.payload.type === recipientType.INPERSONSIGNER) {
state.signingOrder = true; state.signingOrder = true
state.recipients = [action.payload, ...state.recipients]; state.recipients = [action.payload, ...state.recipients]
} else { } else {
state.recipients.push(action.payload); state.recipients.push(action.payload);
} }
}, },
resetRecipients: (state) => { resetRecipients: (state) => {
state.recipients = []; state.recipients = [];
}, },
toggleToTemplates: (state) => { // Replace the entire selectedRecipients array
state.transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
},
setSelectedRecipients: (state, action: PayloadAction<Recipient[]>) => { setSelectedRecipients: (state, action: PayloadAction<Recipient[]>) => {
for (const [index, recipient] of action.payload.entries()) { for (const [index, recipient] of action.payload.entries()) {
if ((recipient.type === recipientType.INPERSONSIGNER && index > 0) if ((recipient.type === recipientType.INPERSONSIGNER && index > 0) && action.payload[index - 1].type !== recipientType.INPERSONSIGNER) {
&& action.payload[index - 1].type !== recipientType.INPERSONSIGNER) {
return state; return state;
} }
} }
@ -504,26 +128,35 @@ const selectedRecipientsSlice = createSlice({
state.recipients = action.payload; state.recipients = action.payload;
}, },
setSigningOrder: (state, action: PayloadAction<boolean>) => { setSigningOrder: (state, action: PayloadAction<boolean>) => {
for (const recipient of state.recipients) { for (const recipient of state.recipients) {
if (recipient.type === recipientType.INPERSONSIGNER) { if (recipient.type === recipientType.INPERSONSIGNER) {
if (state.signingOrder) return state; if (state.signingOrder) return state;
state.signingOrder = true; state.signingOrder = true;
return state; return state
} }
} }
state.signingOrder = action.payload; state.signingOrder = action.payload;
}, },
// Remove Recipients
removeSelectedRecipient: (state, action: PayloadAction<Recipient>) => { removeSelectedRecipient: (state, action: PayloadAction<Recipient>) => {
console.log("removeSelectedRecipient Called")
state.recipients = state.recipients.filter((recipient) => recipient.email !== action.payload.email); state.recipients = state.recipients.filter((recipient) => recipient.email !== action.payload.email);
}, },
// Add Color in Recipient
addColorsToRecipient: (state) => { addColorsToRecipient: (state) => {
// const colors = generateUniqueColors(state.recipients.length);
for (const [index, recipient] of state.recipients.entries()) { for (const [index, recipient] of state.recipients.entries()) {
recipient.color = colors[index]; console.log(state)
debugger
recipient.color = colors[index]
} }
return state; return state;
}, },
updateRecipientOrder: (state, action: PayloadAction<{ id: string; routingOrder: number }>) => { updateRecipientOrder: (state, action: PayloadAction<{ id: string; routingOrder: number }>) => {
console.log("recipient order called Line 159")
const { id, routingOrder } = action.payload; const { id, routingOrder } = action.payload;
const recipient = state.recipients.find((r) => r.id === id || r.email === id); const recipient = state.recipients.find((r) => r.id === id || r.email === id);
if (recipient) { if (recipient) {
@ -531,22 +164,32 @@ const selectedRecipientsSlice = createSlice({
} }
}, },
updateRecipientIds: (state, action: PayloadAction<Recipient[]>) => { updateRecipientIds: (state, action: PayloadAction<Recipient[]>) => {
console.log("updateRecipientIds", console.table(action.payload));
for (const recObj of state.recipients) { for (const recObj of state.recipients) {
const email = recObj.email
for (const recObj2 of action.payload) { for (const recObj2 of action.payload) {
recObj.id = recObj2.id; recObj.id = recObj2.id;
} }
} }
}, },
resetSelRecipient: () => initialState resetSelRecipient: (state) => {
state = initialState;
return state;
}
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
.addCase(fetchRecipients.pending, () => { }) .addCase(fetchRecipients.pending, (state) => {
})
.addCase(fetchRecipients.fulfilled, (state, action: PayloadAction<Recipient[]>) => { .addCase(fetchRecipients.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
// console.log("Fetched recipients successfully:", action.payload);
// Assign colors to recipients
const assignedRecipients = action.payload.map((recipient, index) => ({ const assignedRecipients = action.payload.map((recipient, index) => ({
...recipient, ...recipient,
color: colors[index], color: colors[index], // Cycle if more recipients than colors
})); }));
state.recipients = assignedRecipients; state.recipients = assignedRecipients;
}) })
.addCase(fetchRecipients.rejected, (state, action) => { .addCase(fetchRecipients.rejected, (state, action) => {
@ -554,52 +197,39 @@ const selectedRecipientsSlice = createSlice({
toast.error("Failed to fetch recipients", { toast.error("Failed to fetch recipients", {
position: "bottom-right", position: "bottom-right",
autoClose: 5000, autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light", theme: "light",
}); });
}) })
.addCase(addRecipientAsync.pending, () => { })
.addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
state.recipients = action.payload.map((recipient, index) => ({ .addCase(addRecipientAsync.pending, (state) => {
...recipient,
color: colors[index % colors.length], })
})); .addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient>) => {
console.log("Added recipient successfully:", action.payload);
// state.recipients.push(action.payload);
}) })
.addCase(addRecipientAsync.rejected, (state, action) => { .addCase(addRecipientAsync.rejected, (state, action) => {
console.error("Failed to add recipients:", action.payload); console.error("Failed to fetch recipients:", action.payload);
toast.error("Failed to add recipients", { toast.error("Failed to fetch recipients", {
position: "bottom-right",
autoClose: 5000,
theme: "light",
});
})
.addCase(removeRecipientAsync.pending, () => { })
.addCase(removeRecipientAsync.fulfilled, (state, action: PayloadAction<string | number>) => {
state.recipients = state.recipients.filter(
(recipient) => recipient.id !== action.payload && recipient.email !== action.payload
);
})
.addCase(removeRecipientAsync.rejected, (state, action) => {
console.error("Failed to delete recipient:", action.payload);
toast.error("Failed to delete recipient", {
position: "bottom-right", position: "bottom-right",
autoClose: 5000, autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light", theme: "light",
}); });
}); });
} }
}); });
export const { export const { addRecipient, setSelectedRecipients, removeSelectedRecipient, setSigningOrder, addColorsToRecipient, updateRecipientIds, resetSelRecipient, updateRecipientOrder, resetRecipients } = selectedRecipientsSlice.actions;
addRecipient,
setSelectedRecipients,
toggleToTemplates,
removeSelectedRecipient,
setSigningOrder,
addColorsToRecipient,
updateRecipientIds,
resetSelRecipient,
updateRecipientOrder,
resetRecipients
} = selectedRecipientsSlice.actions;
export default selectedRecipientsSlice.reducer; export default selectedRecipientsSlice.reducer;