Compare commits
5 Commits
5c3131f3c3
...
772dec4858
| Author | SHA1 | Date |
|---|---|---|
|
|
772dec4858 | |
|
|
b65614ae69 | |
|
|
8daf9d9a11 | |
|
|
2cfe1ad364 | |
|
|
0cae5b1ffb |
12
src/App.tsx
12
src/App.tsx
|
|
@ -54,6 +54,18 @@ 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 />
|
||||||
|
|
|
||||||
|
|
@ -272,8 +272,6 @@ const Account = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
|
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
|
||||||
|
|
|
||||||
|
|
@ -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 } from 'react-icons/fa'
|
import { FaRegImage, FaFileAlt, FaSignature, FaStamp, FaCheck } 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,6 +100,30 @@ 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
|
||||||
|
|
@ -126,6 +150,9 @@ 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);
|
||||||
|
|
@ -146,6 +173,21 @@ 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();
|
||||||
|
|
@ -162,9 +204,10 @@ const SignDocument = () => {
|
||||||
console.log(saved, "Line 162")
|
console.log(saved, "Line 162")
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (recSignStatus === "COMPLETE") {
|
if (recSignStatus === "COMPLETED") {
|
||||||
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);
|
||||||
|
|
@ -447,13 +490,31 @@ 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])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -498,6 +559,40 @@ 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>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -613,7 +708,7 @@ const SignDocument = () => {
|
||||||
{/* Main container */}
|
{/* Main container */}
|
||||||
<div
|
<div
|
||||||
id="doc-con"
|
id="doc-con"
|
||||||
className="relative col-span-10 px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth"
|
className="zoom-con relative col-span-10 px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth"
|
||||||
>
|
>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -668,7 +763,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-none" : "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-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
|
||||||
// style={{ height: item.dimensions.height + "px" }}
|
// style={{ height: item.dimensions.height + "px" }}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
@ -967,7 +1062,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>
|
||||||
|
|
@ -975,7 +1070,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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dotted line */}
|
{/* Dotted line */}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center mb-3">
|
||||||
<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">
|
<div className="flex flex-col items-center space-y-4 mb-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>
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,17 @@ interface DocumentCardProps {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
isCompleted: boolean;
|
isCompleted: boolean;
|
||||||
|
transactionTemplateToggle:boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted }) => {
|
const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted , transactionTemplateToggle }) => {
|
||||||
|
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
|
||||||
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
const accountId = params.accountId;
|
||||||
if (!accountId || !transactionId) {
|
const transactionId = params.transactionId || params.templateId;
|
||||||
window.location.href = "/";
|
// if (!accountId || !transactionId) {
|
||||||
return null;
|
// window.location.href = "/";
|
||||||
}
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
const [progress, setProgress] = useState<number>(0)
|
const [progress, setProgress] = useState<number>(0)
|
||||||
const [message, setMessage] = useState<string>('')
|
const [message, setMessage] = useState<string>('')
|
||||||
|
|
@ -96,7 +98,7 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted }) =>
|
||||||
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}/transactions/${transactionId}/documents/${docId}`)
|
const res = await api.delete(`/api/accounts/${accountId}/${transactionTemplateToggle}/${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 });
|
||||||
|
|
|
||||||
|
|
@ -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, updateRecipientIds } from "../../../store/slices/selectedRecipientsSlice";
|
import { addRecipient, addRecipientAsync, fetchRecipients, recipientType, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE, 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,21 +22,35 @@ const ProcessStepOne = () => {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||||
if (!accountId || !transactionId) {
|
console.log("transactionTemplateToggle1", transactionTemplateToggle)
|
||||||
window.location.href = "/";
|
console.log()
|
||||||
return null;
|
const templateId = useParams<{ templateId: string }>().templateId;
|
||||||
}
|
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 }));
|
||||||
|
|
@ -52,6 +66,8 @@ 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,
|
||||||
|
|
@ -99,9 +115,9 @@ const ProcessStepOne = () => {
|
||||||
order: index,
|
order: index,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await api.patch(`/api/accounts/${accountId}/transactions/${transactionId}/documents`, { "documents": documentsData });
|
await api.patch(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents`, { "documents": documentsData });
|
||||||
|
|
||||||
await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/documents?include_images=true`).then(res => {
|
await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents?include_images=true`).then(res => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
dispatch(setDocuments(res.data));
|
dispatch(setDocuments(res.data));
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +128,7 @@ const ProcessStepOne = () => {
|
||||||
dispatch(showLoader("Processing Recipients..."));
|
dispatch(showLoader("Processing Recipients..."));
|
||||||
|
|
||||||
const recipientsData = selectedRecipients.map((recipient, index) => ({
|
const recipientsData = selectedRecipients.map((recipient, index) => ({
|
||||||
// index,
|
id: "id" in recipient ? recipient.id : null,
|
||||||
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,
|
||||||
|
|
@ -129,30 +145,38 @@ 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..."));
|
||||||
|
|
||||||
await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { subject: emailDtls.subject, message: emailDtls.message, status: "CREATED" }).then((res) => {
|
console.log(`1/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`)
|
||||||
console.log(res);
|
|
||||||
|
let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, {
|
||||||
|
subject: emailDtls.subject, message: emailDtls.message,
|
||||||
|
status: "CREATED"
|
||||||
})
|
})
|
||||||
|
|
||||||
dispatch(hideLoader());
|
console.log("Line 158", res.data)
|
||||||
|
|
||||||
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') {
|
||||||
|
console.log(`/process-step-two/${accountId}/template/${transactionId}`)
|
||||||
|
navigate(`/process-step-two/${accountId}/template/${transactionId}`)
|
||||||
|
} else {
|
||||||
navigate(`/process-step-two/${accountId}/${transactionId}`);
|
navigate(`/process-step-two/${accountId}/${transactionId}`);
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,7 +240,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 />
|
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Recipients */}
|
{/* Recipients */}
|
||||||
|
|
@ -225,7 +249,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 />
|
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ 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;
|
||||||
|
|
@ -19,7 +21,7 @@ export interface ContactTypeObj {
|
||||||
contacts: ContactType[];
|
contacts: ContactType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const RecipientAddCard = () => {
|
const RecipientAddCard = ({ transactionTemplateToggle }) => {
|
||||||
|
|
||||||
const [contactData, setContactData] = useState<ContactTypeObj>({ type: "", contacts: [] });
|
const [contactData, setContactData] = useState<ContactTypeObj>({ type: "", contacts: [] });
|
||||||
const [openContactModal, setOpenContactModal] = useState(false);
|
const [openContactModal, setOpenContactModal] = useState(false);
|
||||||
|
|
@ -27,7 +29,10 @@ const RecipientAddCard = () => {
|
||||||
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: "", type: recipientType.SIGNER });
|
const [recipient, setRecipients] = useState<Recipient>({ firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: Boolean, allowSenderToRemove: Boolean });
|
||||||
|
|
||||||
|
const [showAdvanceSettings, setShowAdvanceSettings] = useState(false)
|
||||||
|
console.log(recipient)
|
||||||
|
|
||||||
const handleRecipientAdd = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleRecipientAdd = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -43,7 +48,7 @@ const RecipientAddCard = () => {
|
||||||
// Validation Ends
|
// Validation Ends
|
||||||
|
|
||||||
dispatch(addRecipient(recipient));
|
dispatch(addRecipient(recipient));
|
||||||
setRecipients({ firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", type: recipientType.SIGNER });
|
setRecipients({ firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: Boolean, allowSenderToRemove: Boolean });
|
||||||
formRef.current?.reset();
|
formRef.current?.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,18 +106,143 @@ const RecipientAddCard = () => {
|
||||||
<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-6 space-y-5 relative"
|
className="w-full bg-white border border-gray-200 rounded-lg shadow-md px-6 py-4 space-y-3 relative"
|
||||||
>
|
>
|
||||||
{/* Name Inputs */}
|
{transactionTemplateToggle === 'templates' && (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="space-y-1">
|
||||||
|
{/* Top Row: Role Input + Settings */}
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
{/* Role Name Input */}
|
||||||
|
<input
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
onFocus={() => setOpenContactModal(true)}
|
||||||
|
onBlur={handleContactModalBlur}
|
||||||
|
value={recipient.roleName}
|
||||||
|
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"
|
||||||
|
onChange={(e) => setRecipients({ ...recipient, roleName: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Settings + Permissions */}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
{/* Permission Status Text - Always Visible when settings open */}
|
||||||
|
<div
|
||||||
|
className={`transition-all duration-600 ease-in-out ${showAdvanceSettings ? 'max-h-10 opacity-100 me-3' : 'max-h-0 opacity-0 '
|
||||||
|
} overflow-hidden`}
|
||||||
|
>
|
||||||
|
<div className="text-sm text-gray-600">
|
||||||
|
{recipient.allowSenderToEdit && recipient.allowSenderToRemove && "Full permissions granted"}
|
||||||
|
{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 */}
|
||||||
|
<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.allowSenderToRemove
|
||||||
|
? 'bg-red-100 border-red-500 shadow-lg'
|
||||||
|
: 'bg-white border-gray-300 hover:border-red-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={recipient.allowSenderToRemove}
|
||||||
|
onChange={(e) =>
|
||||||
|
setRecipients({ ...recipient, allowSenderToRemove: e.target.checked })
|
||||||
|
}
|
||||||
|
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 */}
|
||||||
|
<div className="relative group">
|
||||||
|
<p
|
||||||
|
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)}
|
||||||
|
>
|
||||||
|
<IoIosSettings
|
||||||
|
className={`h-6 w-6 transition-transform duration-300 ${showAdvanceSettings ? 'rotate-90' : 'rotate-0'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
{/* Tooltip */}
|
||||||
|
<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">
|
||||||
|
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap">
|
||||||
|
Advanced Settings
|
||||||
|
<div className="absolute bottom-[-6px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Name Inputs */}
|
||||||
|
<div className="grid grid-cols-12 gap-4 mt-3">
|
||||||
<input
|
<input
|
||||||
// required
|
|
||||||
type="text"
|
type="text"
|
||||||
onFocus={() => setOpenContactModal(true)}
|
onFocus={() => setOpenContactModal(true)}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
value={recipient.firstName}
|
value={recipient.firstName}
|
||||||
placeholder="First Name"
|
placeholder="First 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"
|
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 })}
|
onChange={(e) => setRecipients({ ...recipient, firstName: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
|
@ -122,11 +252,22 @@ const RecipientAddCard = () => {
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
value={recipient.lastName}
|
value={recipient.lastName}
|
||||||
placeholder="Last Name"
|
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"
|
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 })}
|
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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{recipient.type === recipientType.INPERSONSIGNER && <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
{recipient.type === recipientType.INPERSONSIGNER && <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<input
|
<input
|
||||||
required
|
required
|
||||||
|
|
@ -150,18 +291,6 @@ const RecipientAddCard = () => {
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
{/* Email Input */}
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
type="email"
|
|
||||||
onFocus={() => setOpenContactModal(true)}
|
|
||||||
onBlur={handleContactModalBlur}
|
|
||||||
value={recipient.email}
|
|
||||||
placeholder="Recipient 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, email: e.target.value })}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Contact Modal */}
|
{/* Contact Modal */}
|
||||||
{contactData.contacts.length > 0 && openContactModal && (
|
{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">
|
<div className="absolute z-50 mt-1 w-full max-w-2xl bg-white shadow-lg rounded-md p-3 border border-gray-100">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
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
|
||||||
|
|
@ -11,6 +12,7 @@ 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 "";
|
||||||
|
|
@ -22,8 +24,17 @@ const getInitials = (name: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const RecipientCards = () => {
|
const RecipientCards = () => {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const templateId = useParams<{ templateId: string }>().templateId;
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
@ -34,7 +45,14 @@ const RecipientCards = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveRecipient = (recipient) => {
|
const handleRemoveRecipient = (recipient) => {
|
||||||
dispatch(removeSelectedRecipient(recipient))
|
if (!accountId || !transactionId) return;
|
||||||
|
// 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) {
|
||||||
|
|
@ -120,7 +138,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.email} | {recipient.type} {recipient.roleName ? `| Role : ${recipient.roleName}` : ""}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useRef } from 'react';
|
import { useState, useRef, useEffect } 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,15 +11,30 @@ 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);
|
||||||
|
|
||||||
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
useEffect(() => {
|
||||||
if (!accountId || !transactionId) {
|
|
||||||
window.location.href = "/";
|
console.log("Documents updated:", documents);
|
||||||
return null;
|
}, [documents]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
@ -56,6 +71,7 @@ const SelectedDocuments = () => {
|
||||||
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) => {
|
||||||
|
|
@ -75,6 +91,7 @@ const SelectedDocuments = () => {
|
||||||
// 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);
|
||||||
})
|
})
|
||||||
|
|
@ -98,7 +115,7 @@ const SelectedDocuments = () => {
|
||||||
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} />
|
<DocumentCard key={doc.id} id={doc.id} name={doc.name} isCompleted={("isCompleted" in doc || ("status" in doc && doc.status === "UPLOADED")) ? true : false} transactionTemplateToggle={transactionTemplateToggle}/>
|
||||||
))}
|
))}
|
||||||
</ReactSortable>
|
</ReactSortable>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,14 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { IoMdClose } from 'react-icons/io';
|
import { IoMdClose } from 'react-icons/io';
|
||||||
|
|
||||||
import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice';
|
import { fetchRecipients, toggleToTemplates } 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[]
|
||||||
|
|
@ -41,7 +42,16 @@ interface DocumentContainerProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||||
|
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;
|
||||||
|
|
@ -49,7 +59,6 @@ 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)
|
||||||
|
|
@ -67,7 +76,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}/transactions/${transactionId}/recipients/${recipient.id}/tags`)
|
api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/recipients/${recipient.id}/tags`)
|
||||||
);
|
);
|
||||||
const response = await Promise.all(requests);
|
const response = await Promise.all(requests);
|
||||||
let recTags = {}
|
let recTags = {}
|
||||||
|
|
@ -151,7 +160,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}/transactions/${transactionId}/recipients/${recipientId}/tags`,
|
`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/recipients/${recipientId}/tags`,
|
||||||
tags
|
tags
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -279,7 +288,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}/transactions/${transactionId}`, {status: "SENT"})
|
const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" })
|
||||||
console.log(res, "Check123")
|
console.log(res, "Check123")
|
||||||
})()
|
})()
|
||||||
}, [isSend])
|
}, [isSend])
|
||||||
|
|
@ -507,7 +516,8 @@ 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")
|
||||||
|
|
|
||||||
|
|
@ -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 } from '../../../store/slices/selectedRecipientsSlice';
|
import { addColorsToRecipient, fetchRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } 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,13 +17,21 @@ interface LeftSideBarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIndx, setSelectedRecipient }) => {
|
const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIndx, setSelectedRecipient }) => {
|
||||||
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||||
|
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)
|
||||||
|
|
|
||||||
|
|
@ -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 } from '../../../store/slices/selectedRecipientsSlice'
|
import { fetchRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } 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,7 +45,16 @@ const fitObj = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSend }) => {
|
const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSend }) => {
|
||||||
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||||
|
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;
|
||||||
|
|
@ -70,8 +79,6 @@ 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) {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ 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);
|
||||||
|
|
|
||||||
|
|
@ -1,107 +1,549 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
// import React from 'react'
|
||||||
import Draw from './Draw'
|
// import { SaveType } from '../client/SignDocument';
|
||||||
import TypedSign from './TypedSign';
|
|
||||||
import SignUpload from './SignUpload';
|
// interface SignConProps {
|
||||||
import { IoMdClose } from 'react-icons/io';
|
// setOpenSign: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
// 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 SignConProps {
|
interface Signature {
|
||||||
setOpenSign: React.Dispatch<React.SetStateAction<boolean>>;
|
id: string;
|
||||||
recipientId: string;
|
email: string;
|
||||||
saved: SaveType;
|
signatureType: string;
|
||||||
setSaved: React.Dispatch<React.SetStateAction<SaveType>>;
|
lastName: string;
|
||||||
// save: () => void;
|
firstName: string
|
||||||
|
font: string;
|
||||||
|
initialsBlob: string;
|
||||||
|
initialsStampedBlob: string;
|
||||||
|
initialsHash: string;
|
||||||
|
signatureHash: string;
|
||||||
|
signatureBlob: string;
|
||||||
|
signatureStampedBlob: string;
|
||||||
|
default: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const SignCon: React.FC<SignConProps> = ({ setOpenSign, recipientId, saved, setSaved }) => {
|
interface AddSignatureModalProps {
|
||||||
|
setShowAddSignature?: (value: boolean) => void;
|
||||||
|
getAllSignatures: () => Promise<void>;
|
||||||
|
editingSignature?: Signature | null;
|
||||||
|
}
|
||||||
|
|
||||||
const [activeSaveType, setActiveSaveType] = React.useState<SaveTypeEnum>(SaveTypeEnum.DRAW);
|
interface Account {
|
||||||
|
id: number;
|
||||||
|
isDefault: boolean;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
const handleSaveSignature = () => {
|
interface UserData {
|
||||||
if (!saved.save) return
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
language: string;
|
||||||
|
account: Account;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("signature:", saved.initial, "initials:", saved.signature, "subType:", saved.saveType, "MIC CHECK")
|
interface SelectStyle {
|
||||||
// return
|
id: number,
|
||||||
|
style: string
|
||||||
|
}
|
||||||
|
|
||||||
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/signature?recipientId=${recipientId}`,
|
const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
|
||||||
{
|
setShowAddSignature,
|
||||||
signature: saved.signature,
|
getAllSignatures,
|
||||||
initials: saved.initial,
|
editingSignature
|
||||||
subType: saved.saveType
|
}) => {
|
||||||
|
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT');
|
||||||
|
const [fullName, setFullName] = useState('');
|
||||||
|
const [initials, setInitials] = useState('');
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||||
|
const [signatures, setSignatures] = useState<{
|
||||||
|
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: "",
|
||||||
},
|
},
|
||||||
{ headers: { "ngrok-skip-browser-warning": "69420", } })
|
});
|
||||||
.then((res) => {
|
|
||||||
console.log(res, "Signature saved response")
|
const isEditMode = !!editingSignature;
|
||||||
if (res.status === 200 || res.status === 201) {
|
|
||||||
setOpenSign(false)
|
// Navigation items
|
||||||
setActiveSaveType(saved.saveType as SaveTypeEnum)
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [editingSignature]);
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Error saving signature:', err);
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleSaveSignature();
|
getUserDetails();
|
||||||
}, [saved])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='absolute top-0 left-0 w-full h-full flex items-center justify-center '>
|
<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 bg-slate-100 opacity-60'></div>
|
<div className="bg-white rounded-md shadow-xl w-full max-w-[80vw] h-[80vh] flex relative">
|
||||||
|
|
||||||
<div className='relative lg:min-w-[650px] max-w-max mx-auto shadow-md rounded-sm bg-white pt-3 pb-1 px-3'>
|
{/* Sidebar Navigation */}
|
||||||
<IoMdClose
|
<div className="w-64 bg-gray-50 rounded-l-lg border-r border-gray-200 flex flex-col">
|
||||||
onClick={() => setOpenSign(false)}
|
{/* Header */}
|
||||||
className='absolute right-3 text-slate-400 text-lg hover:text-[var(--primary)] cursor-pointer transition-all' />
|
<div className="px-4 py-4 border-b border-gray-200">
|
||||||
{/* Signature Save types Nav */}
|
<div className="flex items-center space-x-3">
|
||||||
<div className='flex text-sm'>
|
<div className='px-1 py-1'>
|
||||||
<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"}`}
|
<h3 className="text-md font-semibold text-gray-900">Add Signature</h3>
|
||||||
onClick={() => setActiveSaveType(SaveTypeEnum.DRAW)}>
|
<p className="text-xs text-gray-500">Create your digital signature</p>
|
||||||
<p className='tracking-wide'>Draw</p>
|
|
||||||
</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"}`}
|
|
||||||
onClick={() => setActiveSaveType(SaveTypeEnum.TYPE)}>
|
|
||||||
<p className='tracking-wide'>Type</p>
|
|
||||||
</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"}`}
|
|
||||||
onClick={() => setActiveSaveType(SaveTypeEnum.UPLOAD)}>
|
|
||||||
<p className='tracking-wide'>Upload</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeSaveType === SaveTypeEnum.DRAW && <Draw
|
{/* Form Inputs */}
|
||||||
saved={saved}
|
<div className="px-6 py-2.5 border-b border-gray-200">
|
||||||
setSaved={setSaved}
|
<div className="space-y-2">
|
||||||
/>}
|
<div>
|
||||||
{activeSaveType === SaveTypeEnum.TYPE && <TypedSign
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
saved={saved}
|
Full Name
|
||||||
setSaved={setSaved}
|
</label>
|
||||||
/>}
|
<input
|
||||||
{activeSaveType === SaveTypeEnum.UPLOAD && <SignUpload
|
type="text"
|
||||||
saved={saved}
|
value={fullName}
|
||||||
setSaved={setSaved}
|
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 className="mt-3 flex justify-end gap-2">
|
<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>
|
||||||
|
|
||||||
|
{/* Navigation Menu */}
|
||||||
|
<div className="flex-1 px-6 py-2.5">
|
||||||
|
<nav className="space-y-2">
|
||||||
|
{navigationItems.map((item) => (
|
||||||
<button
|
<button
|
||||||
// onClick={close}
|
key={item.id}
|
||||||
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"
|
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'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
Cancel
|
<span className={activeTab === item.id ? 'text-blue-600' : 'text-gray-400'}>
|
||||||
|
{item.icon}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content Area */}
|
||||||
|
<div className="flex-1 flex flex-col ">
|
||||||
|
{/* Header with close button */}
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="flex-1 py-5 px-5 overflow-hidden ">
|
||||||
|
{/* <button
|
||||||
|
onClick={() => setShowAddSignature?.(false)}
|
||||||
|
className="absolute top-2 right-3 p-2 hover:bg-gray-100 rounded-full transition-colors z-20"
|
||||||
|
>
|
||||||
|
<AiOutlineClose className="w-5 h-5 text-gray-500" />
|
||||||
|
</button> */}
|
||||||
|
{renderContent()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Buttons in Main Area */}
|
||||||
|
<div className="p-4 border-t border-gray-200 flex justify-end gap-3">
|
||||||
|
<button
|
||||||
|
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>
|
||||||
<button
|
<button
|
||||||
// onClick={save}
|
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"
|
||||||
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"
|
onClick={handleSubmit}
|
||||||
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
Save
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<FaSpinner className="animate-spin" />
|
||||||
|
{isEditMode ? 'UPDATING...' : 'CREATING...'}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
isEditMode ? 'UPDATE SIGNATURE' : 'CREATE SIGNATURE'
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SignCon;
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddSignatureModal;
|
||||||
|
|
@ -1,128 +1,208 @@
|
||||||
|
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 { useState } from "react";
|
import { IoMdAdd } from "react-icons/io";
|
||||||
|
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 [openMenuId, setOpenMenuId] = useState<string | null>(null);
|
const [activeFilter, setActiveFilter] = useState("All");
|
||||||
|
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 handleAction = async (accountId: string, action: 'accept' | 'reject') => {
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const filters = ["All", "My Templates", "Shared"];
|
||||||
|
|
||||||
|
const handleAddTemplate = async () => {
|
||||||
|
console.log("handleAddTemplate called");
|
||||||
try {
|
try {
|
||||||
let response = await api.post(`/api/accounts/${accountId}/accept/invite`)
|
const res = await api.post(`/api/accounts/${activeAccountId}/templates`);
|
||||||
console.log(response)
|
navigate(`/process-step-one/${activeAccountId}/template/${res.data}`)
|
||||||
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.error('Error posting data:', error);
|
console.log(error)
|
||||||
} finally {
|
|
||||||
setOpenMenuId(null); // close menu
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeAccountId) {
|
||||||
|
getAllTemplates()
|
||||||
|
}
|
||||||
|
}, [activeAccountId])
|
||||||
|
|
||||||
|
|
||||||
|
const formatDate = (timestamp: number) => {
|
||||||
|
return new Date(timestamp * 1000).toLocaleString();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const handleUseFun = (id) => {
|
||||||
<>
|
alert(id)
|
||||||
|
}
|
||||||
|
|
||||||
<div className="overflow-x-auto py-2">
|
|
||||||
<table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">
|
|
||||||
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider">
|
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
|
||||||
|
onClick={handleAddTemplate}
|
||||||
|
className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm cursor-pointer hover:opacity-90">
|
||||||
|
<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-12"></th>
|
<th className="px-4 py-3 w-10 bg-gray-100">
|
||||||
<th className="px-4 py-3 text-left">Name</th>
|
<div className="w-4 h-4 border border-slate-300 rounded flex items-center justify-center">
|
||||||
<th className="px-4 py-3 text-center">Owner</th>
|
{/* <FaCheck className="text-purple-600 text-xs" /> */}
|
||||||
<th className="px-4 py-3 text-center">Created Date</th>
|
</div>
|
||||||
<th className="px-4 py-3 text-center">Last Change</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-center"></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-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>
|
||||||
|
|
||||||
|
{templates.length > 0 ? (
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-200">
|
||||||
|
{templates.map((item, index) => {
|
||||||
|
return (
|
||||||
<tr
|
<tr key={index} className="hover:bg-gray-50 relative">
|
||||||
className="hover:bg-gray-50 cursor-pointer"
|
<td className="px-4 py-2">
|
||||||
>
|
<div className="w-4 h-4 border border-slate-300 rounded flex items-center justify-center">
|
||||||
<td className="px-4 py-3">
|
{/* <FaCheck className="text-purple-600 text-xs" /> */}
|
||||||
<div className="w-5 h-5 rounded border border-slate-300 flex justify-center items-center">
|
|
||||||
{/* {selectedTransactions.includes(tx.id) && (
|
|
||||||
<FaCheck className="text-purple-600 text-sm" />
|
|
||||||
)} */}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-2 text-sm text-center">{item.name || 'Dummy'}</td>
|
||||||
<div className="flex items-center gap-2">
|
<td className="px-4 py-2 text-sm text-center">{formatDate(item.createdAt)}</td>
|
||||||
<span>{"test"}</span>
|
<td className="px-4 py-2 text-sm text-center">{formatDate(item.lastModifiedAt)}</td>
|
||||||
</div>
|
<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>
|
||||||
</td>
|
{/* Dropdown Trigger + Tooltip */}
|
||||||
<td className="px-4 py-3 text-center">{"1"}</td>
|
<td className="px-4 py-2 text-center relative">
|
||||||
<td className="px-4 py-3 text-center">
|
|
||||||
<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
|
<button
|
||||||
|
onClick={() => setShowMenu(showMenu === index ? null : index)}
|
||||||
className="mx-auto"
|
className="text-slate-400 hover:text-black focus:outline-none"
|
||||||
aria-label="Actions menu"
|
|
||||||
>
|
>
|
||||||
<PiDotsThreeOutlineVerticalFill className="text-lg text-slate-300 hover:text-black" />
|
<PiDotsThreeOutlineVerticalFill className="text-lg" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Dropdown menu */}
|
{showMenu === index && (
|
||||||
{/* {openMenuId === "1" && (
|
|
||||||
<div
|
<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"
|
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"
|
||||||
onClick={(e) => e.stopPropagation()} // prevent row click closing
|
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
|
<button
|
||||||
className="w-full px-3 py-1 text-left text-green-700 hover:bg-green-100 rounded-t-md cursor-pointer"
|
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||||
onClick={() => handleAction(1, 'accept')}
|
onClick={() => {
|
||||||
|
setShowMenu(null);
|
||||||
|
navigate(`/process-step-two/${accountId}/template/${item.id}`);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Accept
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="w-full px-3 py-1 text-left text-red-700 hover:bg-red-100 rounded-b-md cursor-pointer"
|
className="block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-100"
|
||||||
onClick={() => handleAction(1, 'reject')}
|
onClick={() => {
|
||||||
|
setShowMenu(null);
|
||||||
|
console.log('Delete clicked', item);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Reject
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)}
|
||||||
</td>
|
</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;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ 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) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
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
|
||||||
204
src/index.css
204
src/index.css
|
|
@ -94,4 +94,208 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,188 @@
|
||||||
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;
|
||||||
extension: string;
|
url: string;
|
||||||
original?: boolean;
|
type: string;
|
||||||
corrupted?: boolean;
|
pageCount?: number;
|
||||||
isCompleted: boolean;
|
[key: string]: any;
|
||||||
status: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocumentState {
|
// -----------------------
|
||||||
|
// Initial State
|
||||||
|
// -----------------------
|
||||||
|
interface DocumentState {
|
||||||
documents: Document[];
|
documents: Document[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
|
@ -33,80 +194,87 @@ const initialState: DocumentState = {
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface uploadArgs {
|
// -----------------------
|
||||||
formData: FormData;
|
// Thunk: Fetch Documents
|
||||||
accountId: string;
|
// -----------------------
|
||||||
transactionId: string;
|
export const fetchDocuments = createAsyncThunk<
|
||||||
}
|
Document[],
|
||||||
|
{ accountId: string; transactionId: string }
|
||||||
|
>(
|
||||||
// Fetch documents from API
|
'documents/fetchDocuments',
|
||||||
export const fetchDocuments = createAsyncThunk<Document[], { accountId: string, transactionId: string }>(
|
async ({ accountId, transactionId }, { rejectWithValue, getState }) => {
|
||||||
"documents/fetchDocuments",
|
|
||||||
async ({ accountId, transactionId }, { rejectWithValue }) => {
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/documents?include_images=true`);
|
const state = getState() as {
|
||||||
|
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) {
|
||||||
// if(error.status === 401) {}
|
return rejectWithValue(error.response?.data || 'Failed to fetch documents');
|
||||||
return rejectWithValue(error.response?.data || "Failed to fetch documents");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Upload multiple documents using FormData (Key: "document")
|
// -----------------------
|
||||||
export const uploadDocuments = createAsyncThunk<Document[], uploadArgs>(
|
// Thunk: Upload Documents
|
||||||
"documents/uploadDocuments",
|
// -----------------------
|
||||||
async ({ formData, accountId, transactionId }, { dispatch, rejectWithValue }) => {
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
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 response = await api.post(`/api/accounts/${accountId}/transactions/${transactionId}/documents`, formData, {
|
const state = getState() as {
|
||||||
|
selectedRecipients: { transactionTemplateToggle: string };
|
||||||
|
};
|
||||||
|
const toggle = state.selectedRecipients.transactionTemplateToggle;
|
||||||
|
|
||||||
|
console.log(`/api/accounts/${accountId}/${toggle}/${transactionId}/documents`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/api/accounts/${accountId}/${toggle}/${transactionId}/documents`,
|
||||||
|
formData,
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "multipart/form-data"
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
console.log("Form Uploaded Response DocumentSlice Line 185", response.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", {
|
|
||||||
position: "bottom-right",
|
toast.error('Error Uploading Document', {
|
||||||
|
position: 'bottom-right',
|
||||||
autoClose: 3000,
|
autoClose: 3000,
|
||||||
hideProgressBar: false,
|
theme: 'light',
|
||||||
closeOnClick: false,
|
|
||||||
pauseOnHover: false,
|
|
||||||
draggable: false,
|
|
||||||
progress: undefined,
|
|
||||||
theme: "light",
|
|
||||||
transition: Bounce,
|
transition: Bounce,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Failed Uploading Documents")
|
return rejectWithValue('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",
|
// Slice
|
||||||
|
// -----------------------
|
||||||
|
const documentsSlice = createSlice({
|
||||||
|
name: 'documents',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setDocuments: (state, action: PayloadAction<Document[]>) => {
|
setDocuments: (state, action: PayloadAction<Document[]>) => {
|
||||||
|
|
@ -127,7 +295,7 @@ const documentSlice = createSlice({
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder
|
builder
|
||||||
// Fetch documents
|
// fetchDocuments
|
||||||
.addCase(fetchDocuments.pending, (state) => {
|
.addCase(fetchDocuments.pending, (state) => {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
state.error = null;
|
state.error = null;
|
||||||
|
|
@ -139,16 +307,22 @@ const documentSlice = 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,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
// Upload multiple documents
|
// uploadDocuments
|
||||||
.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 = [...action.payload, ...state.documents];
|
state.documents.push(...action.payload);
|
||||||
})
|
})
|
||||||
.addCase(uploadDocuments.rejected, (state, action) => {
|
.addCase(uploadDocuments.rejected, (state, action) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
|
|
@ -157,5 +331,8 @@ const documentSlice = createSlice({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { resetDocuments, setDocuments, setDocProgComplete, deleteDocument } = documentSlice.actions;
|
// -----------------------
|
||||||
export default documentSlice.reducer;
|
// Exports
|
||||||
|
// -----------------------
|
||||||
|
export const { deleteDocument, resetDocuments, setDocuments, setDocProgComplete } = documentsSlice.actions;
|
||||||
|
export default documentsSlice.reducer;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,340 @@
|
||||||
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",
|
||||||
|
|
@ -22,58 +351,111 @@ 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
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addRecipientAsync = createAsyncThunk<Recipient[], AddRecipientArgs>(
|
// 1. Add Recipient Async
|
||||||
|
export const addRecipientAsync = createAsyncThunk<
|
||||||
|
Recipient[],
|
||||||
|
{ recipientsData: Recipient[]; accountId: string; transactionId: string }
|
||||||
|
>(
|
||||||
'selectedRecipients/addRecipientAsync',
|
'selectedRecipients/addRecipientAsync',
|
||||||
async ({ recipientsData, accountId, transactionId }, { rejectWithValue }) => {
|
async ({ recipientsData, accountId, transactionId }, { rejectWithValue, getState }) => {
|
||||||
console.log(recipientsData, accountId, transactionId, "recipientsData Line 46");
|
|
||||||
try {
|
try {
|
||||||
const response = await api.post(
|
const state = getState() as { selectedRecipients: SelectedRecipientInt };
|
||||||
`/api/accounts/${accountId}/transactions/${transactionId}/recipients`,
|
const toggle = state.selectedRecipients.transactionTemplateToggle;
|
||||||
recipientsData
|
|
||||||
);
|
const updateRecipientsData = recipientsData.filter(recipient => recipient.id !== null);
|
||||||
console.log(response)
|
const createRecipientsData = recipientsData.filter(recipient => recipient.id == null);
|
||||||
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const removeRecipientAsync = createAsyncThunk<string, string>(
|
// 2. Remove Recipient Async
|
||||||
|
export const removeRecipientAsync = createAsyncThunk<
|
||||||
|
string | number,
|
||||||
|
{ recipientId: number | null; accountId: string; transactionId: string; email: string }
|
||||||
|
>(
|
||||||
'selectedRecipients/removeRecipientAsync',
|
'selectedRecipients/removeRecipientAsync',
|
||||||
async (email, { rejectWithValue }) => {
|
async ({ recipientId, accountId, transactionId, email }, { rejectWithValue, getState }) => {
|
||||||
try {
|
try {
|
||||||
await api.delete(`/api/recipients`, { data: { email } });
|
const state = getState() as { selectedRecipients: SelectedRecipientInt };
|
||||||
return email; // return email so we can filter from state
|
const toggle = state.selectedRecipients.transactionTemplateToggle;
|
||||||
|
|
||||||
|
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(err.response?.data || "Failed to remove recipient");
|
return rejectWithValue("Failed to remove recipient");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const fetchRecipients = createAsyncThunk<Recipient[], { accountId: string, transactionId: string }>(
|
// 3. Fetch Recipients Async
|
||||||
|
export const fetchRecipients = createAsyncThunk<
|
||||||
|
Recipient[],
|
||||||
|
{ accountId: string; transactionId: string }
|
||||||
|
>(
|
||||||
'selectedRecipients/fetchRecipients',
|
'selectedRecipients/fetchRecipients',
|
||||||
async ({ accountId, transactionId }, { rejectWithValue }) => {
|
async ({ accountId, transactionId }, { rejectWithValue, getState }) => {
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/recipients`);
|
const state = getState() as { selectedRecipients: SelectedRecipientInt };
|
||||||
|
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");
|
||||||
|
|
@ -85,7 +467,6 @@ 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) {
|
||||||
|
|
@ -93,34 +474,29 @@ 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 = [];
|
||||||
},
|
},
|
||||||
// Replace the entire selectedRecipients array
|
toggleToTemplates: (state) => {
|
||||||
|
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) && action.payload[index - 1].type !== recipientType.INPERSONSIGNER) {
|
if ((recipient.type === recipientType.INPERSONSIGNER && index > 0)
|
||||||
|
&& action.payload[index - 1].type !== recipientType.INPERSONSIGNER) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,35 +504,26 @@ 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()) {
|
||||||
console.log(state)
|
recipient.color = colors[index];
|
||||||
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) {
|
||||||
|
|
@ -164,32 +531,22 @@ 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: (state) => {
|
resetSelRecipient: () => initialState
|
||||||
state = initialState;
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder
|
builder
|
||||||
.addCase(fetchRecipients.pending, (state) => {
|
.addCase(fetchRecipients.pending, () => { })
|
||||||
})
|
|
||||||
.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], // Cycle if more recipients than colors
|
color: colors[index],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
state.recipients = assignedRecipients;
|
state.recipients = assignedRecipients;
|
||||||
})
|
})
|
||||||
.addCase(fetchRecipients.rejected, (state, action) => {
|
.addCase(fetchRecipients.rejected, (state, action) => {
|
||||||
|
|
@ -197,39 +554,52 @@ 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[]>) => {
|
||||||
.addCase(addRecipientAsync.pending, (state) => {
|
state.recipients = action.payload.map((recipient, index) => ({
|
||||||
|
...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 fetch recipients:", action.payload);
|
console.error("Failed to add recipients:", action.payload);
|
||||||
toast.error("Failed to fetch recipients", {
|
toast.error("Failed to add 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 { addRecipient, setSelectedRecipients, removeSelectedRecipient, setSigningOrder, addColorsToRecipient, updateRecipientIds, resetSelRecipient, updateRecipientOrder, resetRecipients } = selectedRecipientsSlice.actions;
|
export const {
|
||||||
|
addRecipient,
|
||||||
|
setSelectedRecipients,
|
||||||
|
toggleToTemplates,
|
||||||
|
removeSelectedRecipient,
|
||||||
|
setSigningOrder,
|
||||||
|
addColorsToRecipient,
|
||||||
|
updateRecipientIds,
|
||||||
|
resetSelRecipient,
|
||||||
|
updateRecipientOrder,
|
||||||
|
resetRecipients
|
||||||
|
} = selectedRecipientsSlice.actions;
|
||||||
|
|
||||||
export default selectedRecipientsSlice.reducer;
|
export default selectedRecipientsSlice.reducer;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue