Compare commits

...

3 Commits

Author SHA1 Message Date
=bhagwanSingh 7b448558c0 API UPdate 2025-07-24 12:31:44 +05:30
JaiPandey425 1ce24675ba Merge remote-tracking branch 'origin/main' into jai 2025-07-23 12:57:08 +05:30
JaiPandey425 3b08c04463 self-sign 2025-07-23 12:18:24 +05:30
14 changed files with 693 additions and 512 deletions

View File

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

View File

@ -23,10 +23,10 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
onClick={() => (`${(dropDownTogglePage === 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`)} onClick={() => (`${(dropDownTogglePage === 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`)}
className={`p-2 ${(dropDownTogglePage === 'pageDropDown' + indx) ? "bg-[var(--primary)]" : "bg-slate-200"} flex flex-col justify-center items-center gap-2 rounded`}> className={`p-2 ${(dropDownTogglePage === 'pageDropDown' + indx) ? "bg-[var(--primary)]" : "bg-slate-200"} flex flex-col justify-center items-center gap-2 rounded`}>
{ {
document.images != undefined && <img document.pages != undefined && <img
key={indx} key={indx}
className='rounded-sm shadow' className='rounded-sm shadow'
src={document.images[0].src} src={document.pages[0].src}
alt="" /> alt="" />
} }
@ -41,7 +41,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
{ {
dropDownTogglePage === 'pageDropDown' + indx && dropDownTogglePage === 'pageDropDown' + indx &&
<div className='my-1 flex justify-center items-center gap-1 flex-wrap'> <div className='my-1 flex justify-center items-center gap-1 flex-wrap'>
{document.images != undefined && document.images.map((img, indx) => ( {document.pages != undefined && document.pages.map((img, indx) => (
<a <a
href={"#" + document.documentId + indx} href={"#" + document.documentId + indx}
className='max-w-max px-4 py-2 shadow cursor-pointer border-1 rounded-sm border-slate-200 hover:border-[var(--primary)] transition-all duration-300 bg-slate-300' className='max-w-max px-4 py-2 shadow cursor-pointer border-1 rounded-sm border-slate-200 hover:border-[var(--primary)] transition-all duration-300 bg-slate-300'

View File

@ -47,7 +47,7 @@ interface RecipientType {
acceptedTerms: boolean; acceptedTerms: boolean;
name: string | null; name: string | null;
} }
interface ImagesType { interface pagesType {
index: number; index: number;
type: string; type: string;
src: string; src: string;
@ -56,7 +56,7 @@ interface ImagesType {
} }
interface DocumentType { interface DocumentType {
documentId: string; documentId: string;
images: ImagesType[]; pages: pagesType[];
name: string; name: string;
} }
@ -130,7 +130,7 @@ const fitObj = [
// 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 pages and tags data
// handleFormSubmit - handle form submit and send data to server // handleFormSubmit - handle form submit and send data to server
const SignDocument = () => { const SignDocument = () => {
@ -144,6 +144,7 @@ const SignDocument = () => {
const [transaction, setTransaction] = useState<TransactionType | null>(null); const [transaction, setTransaction] = useState<TransactionType | null>(null);
const [recSignStatus, setRecSignStatus] = useState<string | null>(null); const [recSignStatus, setRecSignStatus] = useState<string | null>(null);
const [droppedItems, setDroppedItems] = useState([]); const [droppedItems, setDroppedItems] = useState([]);
const[signTagId , setSignTagId] = useState<number>(0)
const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" }); const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" });
@ -178,8 +179,12 @@ const SignDocument = () => {
initial: null, initial: null,
}); });
const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({}); const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({});
console.log("signConfObj",signConfObj)
const zoom = (percentage: number, contentClass: string) => { const zoom = (percentage: number, contentClass: string) => {
const content = document.querySelector(`.${contentClass}`); const content = document.querySelector(`.${contentClass}`);
if (content) { if (content) {
@ -239,7 +244,9 @@ const SignDocument = () => {
} }
}, [saved]) }, [saved])
const handleSignClick = (index: number) => { const handleSignClick = (index: number , id:number) => {
console.log("IDNumber" , id)
setSignTagId(id)
if (saved.save) { if (saved.save) {
if (index in signConfObj) { if (index in signConfObj) {
setOpenSign(true); setOpenSign(true);
@ -263,8 +270,11 @@ const SignDocument = () => {
} }
} }
setSignConfObj({ ...newObj }) setSignConfObj({ ...newObj })
} }
const handleStampClick = (index: number) => { const handleStampClick = (index: number) => {
console.log("Stamp Clicked", index) console.log("Stamp Clicked", index)
setStamp({ setStamp({
@ -502,11 +512,12 @@ const SignDocument = () => {
const fetchSignatureData = async (signatureId: string) => { const fetchSignatureData = async (signatureId: string) => {
try { try {
const response = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign/signatures/${signatureId}`, { const response = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign/signatures/${signatureId}?recipientId=${recipientId}`, {
headers: { "ngrok-skip-browser-warning": "69420" } headers: { "ngrok-skip-browser-warning": "69420" }
}); });
if (response.status === 200) { if (response.status === 200) {
return response.data.signature; console.log("line 516" , response.data)
return response.data;
} }
} catch (error) { } catch (error) {
console.error("Error fetching signature data:", error); console.error("Error fetching signature data:", error);
@ -514,18 +525,19 @@ const SignDocument = () => {
return null; return null;
} }
useEffect(() => { // useEffect(() => {
(async () => { // (async () => {
if (droppedItems) { // if (droppedItems) {
for (const tag of droppedItems) { // for (const tag of droppedItems) {
if (tag.type === "SIGNATURE" && "value" in tag) { // if (tag.type === "SIGNATURE" && "value" in tag) {
const signatureData = await fetchSignatureData(tag.value); // console.log(tag, "line 533")
console.log(signatureData); // const signatureData = await fetchSignatureData(tag["value"]);
} // console.log("Currnet Signature",signatureData);
} // }
} // }
})() // }
}, [droppedItems]) // })()
// }, [droppedItems])
const handleTagUpdate = (updateIndx: number, updateValue: string) => { const handleTagUpdate = (updateIndx: number, updateValue: string) => {
@ -879,7 +891,7 @@ const SignDocument = () => {
<div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'> <div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'>
{ {
doc.images && doc.images.length > 0 && doc.images.map((page, indx) => { doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
const key = `${doc.documentId}_${page.index}`; const key = `${doc.documentId}_${page.index}`;
console.log(droppedItems, "line 820") console.log(droppedItems, "line 820")
// const tagJsonData = [...droppedItems]; // const tagJsonData = [...droppedItems];
@ -1012,7 +1024,7 @@ const SignDocument = () => {
<div <div
tabIndex={1} tabIndex={1}
id={item.id} id={item.id}
onClick={() => handleSignClick(index)} onClick={() => handleSignClick(index , item.id)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none bg-transparent" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none bg-transparent" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimensions.height + "px" }}
> >
@ -1028,7 +1040,9 @@ const SignDocument = () => {
width: `${item.dimensions.width}px`, width: `${item.dimensions.width}px`,
height: `${item.dimensions.height}px`, height: `${item.dimensions.height}px`,
}} }}
src={saved.signature as string} /> // src={saved.signature as string} />
src={`data:image/png;base64,${saved.signature}`}
/>
} }
</div>} </div>}
{/* SIGNATURE FIELD - END */} {/* SIGNATURE FIELD - END */}
@ -1039,7 +1053,7 @@ const SignDocument = () => {
<div <div
tabIndex={1} tabIndex={1}
id={item.id} id={item.id}
onClick={() => handleSignClick(index)} onClick={() => handleSignClick(index , item.id)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimensions.height + "px" }}
> >
@ -1052,7 +1066,9 @@ const SignDocument = () => {
height: `${item.dimensions.height}px`, height: `${item.dimensions.height}px`,
}} }}
className="w-[100%] h-full" className="w-[100%] h-full"
src={saved.initial as string} /> // src={saved.initial as string}
src={`data:image/png;base64,${saved.initial}`}
/>
} }
</div>} </div>}
{/* INITIALS FIELD - END */} {/* INITIALS FIELD - END */}
@ -1207,7 +1223,7 @@ const SignDocument = () => {
{/* Side details of documents Pages and document Name - START */} {/* Side details of documents Pages and document Name - START */}
<p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'> <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'>
page {indx + 1} of {doc.images && doc.images.length} page {indx + 1} of {doc.pages && doc.pages.length}
</p> </p>
{/* Side details of documents Pages and document Name - END */} {/* Side details of documents Pages and document Name - END */}
@ -1258,7 +1274,7 @@ const SignDocument = () => {
<div style={{ display: `${openSign ? 'block' : 'none'}` }}> <div style={{ display: `${openSign ? 'block' : 'none'}` }}>
<SignCon recipientId={recipientId as string} setOpenSign={setOpenSign} saved={saved} setSaved={setSaved} /> <SignCon recipientId={recipientId as string} setOpenSign={setOpenSign} saved={saved} setSaved={setSaved} signTagId={signTagId}/>
</div> </div>
<div style={{ display: `${stamp.open ? 'block' : 'none'}` }}> <div style={{ display: `${stamp.open ? 'block' : 'none'}` }}>

View File

@ -2,10 +2,14 @@ import React, { useEffect, useRef } from 'react'
import { IoMdArrowDropdown } from "react-icons/io"; import { IoMdArrowDropdown } from "react-icons/io";
import { IoIosSend } from "react-icons/io"; import { IoIosSend } from "react-icons/io";
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { setSignType } from '../../store/slices/selectedDocsSlice'; import { setSignType, SignType } from '../../store/slices/selectedDocsSlice';
import { RootState } from '../../store/store'; import { RootState } from '../../store/store';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Bounce, toast } from 'react-toastify'; import { Bounce, toast } from 'react-toastify';
import api from '../../config/api';
import { StartCard } from '../home/StartSection';
import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from '../../store/slices/transactionSlice';
import { SignTypeEnum } from '../../utlity/types';
// import { IoMdArrowDropright } from "react-icons/io"; // import { IoMdArrowDropright } from "react-icons/io";
interface Props { interface Props {
@ -22,32 +26,55 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
sendForSignDropdown && dropDownRef.current?.focus(); sendForSignDropdown && dropDownRef.current?.focus();
}, [sendForSignDropdown]) }, [sendForSignDropdown])
const user = useSelector((state: RootState) => state.user);
if (!user.account || user.account === null) {
window.location.href = "/login";
return;
}
const defaultAccountId = user.account.id;
const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments); const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments);
const handleSendSignDispatch: (type: "self-sign" | "multiple-sign") => void = (signType) => { const handleStartSectionClick = (signType: SignTypeEnum) => {
if (selectedDocuments === null || !selectedDocuments.length) {
if (selectedDocuments === null || !selectedDocuments.length) {
toast.dismiss();
toast.error("Select 📄 Documents first!", {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
return
}
}
if (selectedDocuments.length) { api.post(`/api/accounts/${defaultAccountId}/transactions`)
dispatch(setSignType(signType)); .then(res => {
} if (res.status === 200 || res.status === 201) {
dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED));
dispatch(setTransactionId(res.data));
let navigateUrl = `/process-step-one/${defaultAccountId}/${res.data}`;
if (signType === SignTypeEnum.selfSign) navigateUrl = `/process-step-one/${defaultAccountId}/${res.data}/self_sign`;
setTimeout(() => navigate(navigateUrl), 0)
}
})
.catch(err => console.log(err))
} }
// const handleSendSignDispatch: (type: "self-sign" | "multiple-sign") => void = (signType) => {
// if (selectedDocuments === null || !selectedDocuments.length) {
// if (selectedDocuments === null || !selectedDocuments.length) {
// toast.dismiss();
// toast.error("Select 📄 Documents first!", {
// position: "bottom-right",
// autoClose: 5000,
// hideProgressBar: false,
// closeOnClick: false,
// pauseOnHover: true,
// draggable: true,
// progress: undefined,
// theme: "light",
// transition: Bounce,
// });
// return
// }
// }
// if (selectedDocuments.length) {
// dispatch(setSignType(signType));
// }
// }
return ( return (
<div className='relative max-h-max pb-3' <div className='relative max-h-max pb-3'
tabIndex={0} tabIndex={0}
@ -73,12 +100,12 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
{ {
sendForSignDropdown && sendForSignDropdown &&
<div className={`w-[300px] rounded absolute z-40 bg-[var(--sendForSignDropDownBg)] shadow-lg top-[54px] text-left outline-none`}> <div className={`w-[300px] rounded absolute z-40 bg-[var(--sendForSignDropDownBg)] shadow-lg top-[54px] text-left outline-none`}>
<div className='py-3 px-8 leading-6 rounded-t-sm hover:bg-[var(--sendForSignDropDownHover)] cursor-pointer' onClick={() => handleSendSignDispatch('self-sign')}> <div className='py-3 px-8 leading-6 rounded-t-sm hover:bg-[var(--sendForSignDropDownHover)] cursor-pointer' onClick={() => handleStartSectionClick(SignTypeEnum.selfSign)}>
<p>Self Sign</p> <p>Self Sign</p>
<p className='text-[var(--greyTextCol)] text-sm'>You're the only signer.</p> <p className='text-[var(--greyTextCol)] text-sm'>You're the only signer.</p>
</div> </div>
<div className='w-full h-[1px] opacity-30 bg-[var(--greyTextCol)]'></div> <div className='w-full h-[1px] opacity-30 bg-[var(--greyTextCol)]'></div>
<div className='py-3 px-8 leading-6 rounded-b-sm hover:bg-[var(--sendForSignDropDownHover)] cursor-pointer' onClick={() => handleSendSignDispatch('multiple-sign')}> <div className='py-3 px-8 leading-6 rounded-b-sm hover:bg-[var(--sendForSignDropDownHover)] cursor-pointer' onClick={() => handleStartSectionClick(SignTypeEnum.multipleSign)}>
<p>Send for Sign</p> <p>Send for Sign</p>
<p className='text-[var(--greyTextCol)] text-sm'>You and other need to sign.</p> <p className='text-[var(--greyTextCol)] text-sm'>You and other need to sign.</p>
</div> </div>

View File

@ -8,7 +8,7 @@ import api from '../../config/api';
import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from '../../store/slices/transactionSlice'; import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from '../../store/slices/transactionSlice';
interface StartCard { export interface StartCard {
icon: ReactNode; icon: ReactNode;
title: string; title: string;
description: string; description: string;
@ -57,7 +57,7 @@ const StartSection = () => {
if (res.status === 200 || res.status === 201) { if (res.status === 200 || res.status === 201) {
dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED)); dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED));
dispatch(setTransactionId(res.data)); dispatch(setTransactionId(res.data));
setTimeout(() => navigate(`/process-step-one/${defaultAccountId}/${res.data}`), 0) setTimeout(() => navigate(`/process-step-one/${defaultAccountId}/${res.data.id}`), 0)
} }
}) })
.catch(err => console.log(err)) .catch(err => console.log(err))

View File

@ -316,7 +316,7 @@ const DocumentCompletionScreen = () => {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center"> <div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
{/* <FaFileAlt className="text-blue-600 text-xl" /> */} {/* <FaFileAlt className="text-blue-600 text-xl" /> */}
<img src={item.images[0].src} alt="document" className="h-10 w-10" /> <img src={item.pages[0].src} alt="document" className="h-10 w-10" />
</div> </div>
<div> <div>
<h3 className="text-md font-semibold text-gray-800"> <h3 className="text-md font-semibold text-gray-800">

View File

@ -15,6 +15,7 @@ import { hideLoader, showLoader } from "../../../store/slices/loaderSlice";
import Loader from "../../dashboard/Loader"; import Loader from "../../dashboard/Loader";
import { Bounce, toast } from "react-toastify"; import { Bounce, toast } from "react-toastify";
import { fetchDocuments, resetDocuments, setDocuments, uploadDocuments } from "../../../store/slices/documentSlice"; import { fetchDocuments, resetDocuments, setDocuments, uploadDocuments } from "../../../store/slices/documentSlice";
import { SignTypeEnum } from "../../../utlity/types";
const ProcessStepOne = () => { const ProcessStepOne = () => {
interface EmailDtls { interface EmailDtls {
@ -23,8 +24,8 @@ const ProcessStepOne = () => {
} }
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS; let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
console.log("transactionTemplateToggle1", transactionTemplateToggle)
console.log() const signType = useParams<{ signType: string }>().signType;
const templateId = useParams<{ templateId: string }>().templateId; const templateId = useParams<{ templateId: string }>().templateId;
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>(); let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
@ -33,11 +34,11 @@ const ProcessStepOne = () => {
if (!transactionId) { if (!transactionId) {
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES; transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
console.log("transactionTemplateToggle2", transactionTemplateToggle)
transactionId = templateId; transactionId = templateId;
dispatch(toggleToTemplates()); dispatch(toggleToTemplates());
} }
console.log("ProcessStepOne called with accountId:", accountId, "and transactionId:", transactionId);
// if (!accountId || !transactionId) { // if (!accountId || !transactionId) {
// window.location.href = "/"; // window.location.href = "/";
// return null; // return null;
@ -48,10 +49,10 @@ const ProcessStepOne = () => {
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)
const userData = useSelector((state: RootState) => state.user)
console.log("selectedRecipients", selectedRecipients) console.log("selectedRecipients", selectedRecipients)
useEffect(() => { useEffect(() => {
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId })); selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
@ -85,6 +86,8 @@ const ProcessStepOne = () => {
formError.subject = true formError.subject = true
} }
if(SignTypeEnum.selfSign === signType) formError.recipients = false;
if (formError.documents || formError.recipients || formError.subject) { if (formError.documents || formError.recipients || formError.subject) {
console.log(selectedDocuments, 'line 193') console.log(selectedDocuments, 'line 193')
toast.dismiss(); toast.dismiss();
@ -149,11 +152,38 @@ const ProcessStepOne = () => {
roleName: recipient.roleName roleName: recipient.roleName
})); }));
dispatch(addRecipientAsync({ recipientsData, accountId, transactionId })); let recipientsDataTemp = recipientsData;
if (signType === SignTypeEnum.selfSign) {
recipientsDataTemp = [{
id: null,
routingOrder: 1,
provideOwnTags: true,
type: recipientType.SIGNER,
firstName: userData.firstName,
lastName: userData.lastName,
authType: "NONE",
deliveryMethod: "EMAIL",
email: userData.email,
hostName: "",
hostEmail: "",
company: "",
title: "",
website: "",
phone: "",
accessCode: "",
accessCodeDelivery: true,
language: "en",
name: `${userData.firstName} ${userData.lastName}`,
roleName: ""
}]
}
dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId }));
dispatch(showLoader("Processing Message...")); dispatch(showLoader("Processing Message..."));
console.log(`1/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`) console.log(`1/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`)
let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, {
subject: emailDtls.subject, message: emailDtls.message, subject: emailDtls.subject, message: emailDtls.message,
status: "CREATED" status: "CREATED"
@ -202,6 +232,15 @@ const ProcessStepOne = () => {
navigate('/') navigate('/')
} }
const handleSelfSignCb = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.currentTarget.checked) {
navigate("self_sign");
} else {
navigate("./../");
}
}
const globalLoader = useSelector((state: RootState) => state.loader) const globalLoader = useSelector((state: RootState) => state.loader)
return ( return (
@ -219,12 +258,15 @@ const ProcessStepOne = () => {
</div> </div>
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>
<button <div className="flex items-center" >
onClick={() => handleNextClick(true)} <input
className="text-sm px-4 py-2 rounded border border-gray-300 hover:bg-gray-100 transition" checked={signType === SignTypeEnum.selfSign}
> onChange={(e) => handleSelfSignCb(e)}
Send Now id="default-checkbox" type="checkbox" value="" className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm outline-none"
</button> />
<label htmlFor="default-checkbox" className="ms-2 text-sm text-slate-600">SELF SIGN</label>
</div>
<button <button
onClick={() => handleNextClick(false)} onClick={() => handleNextClick(false)}
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition" className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition"
@ -244,14 +286,17 @@ const ProcessStepOne = () => {
</section> </section>
{/* Recipients */} {/* Recipients */}
<section className="mt-8"> {
<h2 className="text-lg font-medium mb-4 pt-6 border-t text-[var(--processStepOneTextHead)]">Signers</h2> signType !== SignTypeEnum.selfSign &&
<div className="flex flex-col gap-4 align-start"> < section className="mt-8">
<RecipientCards /> <h2 className="text-lg font-medium mb-4 pt-6 border-t text-[var(--processStepOneTextHead)]">Signers</h2>
{/* {signType !== "self-sign" && <RecipientAddCard />} */} <div className="flex flex-col gap-4 align-start">
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} /> <RecipientCards />
</div> {/* {signType !== "self-sign" && <RecipientAddCard />} */}
</section> <RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} />
</div>
</section>
}
{/* Email */} {/* Email */}
<section className="mt-8"> <section className="mt-8">
@ -286,7 +331,7 @@ const ProcessStepOne = () => {
</div> </div>
{globalLoader.isLoading && <Loader />} {globalLoader.isLoading && <Loader />}
</div> </div >
</> </>
); );
} }

View File

@ -17,6 +17,7 @@ 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";
import { handleGroupBorderPosition } from '../../../utlity/tagGroupConUtils'; import { handleGroupBorderPosition } from '../../../utlity/tagGroupConUtils';
export interface DroppedItemsObj { export interface DroppedItemsObj {
@ -290,7 +291,8 @@ 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" })
// const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { status: "SENT" })
console.log(res, "Check123") console.log(res, "Check123")
})() })()
}, [isSend]) }, [isSend])
@ -581,15 +583,15 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
const handleAddGroupItemClicked = (key: string, item) => { const handleAddGroupItemClicked = (key: string, item) => {
const groupId = item.group; const groupId = item.group;
item = { ...item, dimensions: { ...item.dimensions, top: item.dimensions.top + item.dimensions.height + 20 } } item = { ...item, dimensions: { ...item.dimensions, top: item.dimensions.top + item.dimensions.height + 20 } }
// dimensions: { ...item.dimensions, left: newLeft, top: newTop } // dimensions: { ...item.dimensions, left: newLeft, top: newTop }
setTimeout(() => { setTimeout(() => {
handleIGroupBorderPosition(item.group) handleIGroupBorderPosition(item.group)
}, 0) }, 0)
setDroppedItems({ setDroppedItems({
...droppedItems, ...droppedItems,
@ -608,325 +610,325 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// // }], // // }],
// // } // // }
// // const lastRadioElemRect = e.currentTarget?.parentElement.previousSibling.getBoundingClientRect(); // // const lastRadioElemRect = e.currentTarget?.parentElement.previousSibling.getBoundingClientRect();
// // console.log(lastRadioElemRect, "lastRadioElemRect") // // console.log(lastRadioElemRect, "lastRadioElemRect")
// const setNewData = droppedItems; // const setNewData = droppedItems;
// if (Array.isArray(setNewData[key][index].radioGroups) && setNewData[key][index].radioGroups?.length > 0) { // if (Array.isArray(setNewData[key][index].radioGroups) && setNewData[key][index].radioGroups?.length > 0) {
// setNewData[key][index].radioGroups?.push(setNewData[key][index].radioGroups[0]) // setNewData[key][index].radioGroups?.push(setNewData[key][index].radioGroups[0])
// } // }
// setDroppedItems(setNewData); // setDroppedItems(setNewData);
// }; // };
// const handleRadioTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number, radioIndex: number) => { // const handleRadioTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number, radioIndex: number) => {
// event.preventDefault(); // event.preventDefault();
// let targetContainer = event?.currentTarget?.parentElement; // let targetContainer = event?.currentTarget?.parentElement;
// let targetElement = event?.currentTarget; // let targetElement = event?.currentTarget;
// if (!targetContainer || !targetElement) return; // if (!targetContainer || !targetElement) return;
// const containerRect = targetContainer.getBoundingClientRect(); // const containerRect = targetContainer.getBoundingClientRect();
// const elementRect = targetElement.getBoundingClientRect(); // const elementRect = targetElement.getBoundingClientRect();
// // Calculate the initial offset (where the mouse was clicked inside the element) // // Calculate the initial offset (where the mouse was clicked inside the element)
// const offsetX = event.clientX - elementRect.left; // const offsetX = event.clientX - elementRect.left;
// const offsetY = event.clientY - elementRect.top; // const offsetY = event.clientY - elementRect.top;
// const handleMouseMove = (event: PointerEvent) => { // const handleMouseMove = (event: PointerEvent) => {
// // Get new X and Y positions relative to the container // // Get new X and Y positions relative to the container
// // let x = offsetX; // // let x = offsetX;
// // let y = offsetY; // // let y = offsetY;
// let x = event.clientX - elementRect.left; // let x = event.clientX - elementRect.left;
// let y = event.clientY - elementRect.top; // let y = event.clientY - elementRect.top;
// const newData = droppedItems; // const newData = droppedItems;
// if (newData[key][index].radioGroups && newData[key][index].radioGroups.length > 0) { // if (newData[key][index].radioGroups && newData[key][index].radioGroups.length > 0) {
// newData[key][index].radioGroups[radioIndex].dimensions.left = x; // newData[key][index].radioGroups[radioIndex].dimensions.left = x;
// newData[key][index].radioGroups[radioIndex].dimensions.top = y; // newData[key][index].radioGroups[radioIndex].dimensions.top = y;
// let topMin = Number.POSITIVE_INFINITY; // let topMin = Number.POSITIVE_INFINITY;
// let topMax = Number.NEGATIVE_INFINITY; // let topMax = Number.NEGATIVE_INFINITY;
// let leftMin = Number.POSITIVE_INFINITY; // let leftMin = Number.POSITIVE_INFINITY;
// let leftMax = Number.NEGATIVE_INFINITY; // let leftMax = Number.NEGATIVE_INFINITY;
// for (const radio of newData[key][index].radioGroups) { // for (const radio of newData[key][index].radioGroups) {
// if (radio.dimensions.left < leftMin) { // if (radio.dimensions.left < leftMin) {
// leftMin = radio.dimensions.left; // leftMin = radio.dimensions.left;
// } // }
// if (radio.dimensions.left > leftMax) { // if (radio.dimensions.left > leftMax) {
// leftMax = radio.dimensions.left; // leftMax = radio.dimensions.left;
// } // }
// if (radio.dimensions.top < topMin) { // if (radio.dimensions.top < topMin) {
// topMin = radio.dimensions.top; // topMin = radio.dimensions.top;
// } // }
// if (radio.dimensions.top > topMax) { // if (radio.dimensions.top > topMax) {
// topMax = radio.dimensions.top; // topMax = radio.dimensions.top;
// } // }
// } // }
// newData[key][index].radioPosition = { // newData[key][index].radioPosition = {
// topMin: topMin, // topMin: topMin,
// topMax: topMax, // topMax: topMax,
// leftMin: leftMin, // leftMin: leftMin,
// leftMax: leftMax, // leftMax: leftMax,
// width: leftMax - leftMin, // width: leftMax - leftMin,
// height: topMax - topMin // height: topMax - topMin
// } // }
// newData[key][index].dimensions.left = x; // newData[key][index].dimensions.left = x;
// newData[key][index].dimensions.top = y; // newData[key][index].dimensions.top = y;
// } // }
// setDroppedItems({ ...newData }); // setDroppedItems({ ...newData });
// }; // };
// const handleMouseUp = () => { // const handleMouseUp = () => {
// document.removeEventListener("pointermove", handleMouseMove); // document.removeEventListener("pointermove", handleMouseMove);
// document.removeEventListener("pointerup", handleMouseUp); // document.removeEventListener("pointerup", handleMouseUp);
// }; // };
// // Add event listeners // // Add event listeners
// document.addEventListener("pointermove", handleMouseMove); // document.addEventListener("pointermove", handleMouseMove);
// document.addEventListener("pointerup", handleMouseUp); // document.addEventListener("pointerup", handleMouseUp);
// } // }
// NEW DIRECTIONAL MOVEMENT HANDLER - START // NEW DIRECTIONAL MOVEMENT HANDLER - START
const handleDirectionalMove = (direction: 'up' | 'down' | 'left' | 'right', stepSize: number = 5) => { const handleDirectionalMove = (direction: 'up' | 'down' | 'left' | 'right', stepSize: number = 5) => {
if (!selectedTag) return; if (!selectedTag) return;
const { key, index } = selectedTag; const { key, index } = selectedTag;
const currentItem = droppedItems[key]?.[index]; const currentItem = droppedItems[key]?.[index];
if (!currentItem) return; if (!currentItem) return;
let newLeft = currentItem.dimensions.left; let newLeft = currentItem.dimensions.left;
let newTop = currentItem.dimensions.top; let newTop = currentItem.dimensions.top;
switch (direction) { switch (direction) {
case 'up': case 'up':
newTop = Math.max(0, newTop - stepSize); newTop = Math.max(0, newTop - stepSize);
break; break;
case 'down': case 'down':
newTop = newTop + stepSize; newTop = newTop + stepSize;
break; break;
case 'left': case 'left':
newLeft = Math.max(0, newLeft - stepSize); newLeft = Math.max(0, newLeft - stepSize);
break; break;
case 'right': case 'right':
newLeft = newLeft + stepSize; newLeft = newLeft + stepSize;
break; break;
}
setDroppedItems({
...droppedItems,
[key]: Array.isArray(droppedItems[key])
? droppedItems[key].map((item, i) =>
i === index
? { ...item, dimensions: { ...item.dimensions, left: newLeft, top: newTop }, update: true }
: item
)
: [],
});
};
// Create a wrapper that matches DirectionalControls' expected signature
const directionalControlsHandler = (direction: 'up' | 'down' | 'left' | 'right') => {
handleDirectionalMove(direction, 5); // Use default stepSize of 5
};
function handleIGroupBorderPosition(groupId: string) {
const groupElements: NodeListOf<HTMLElement> = document.querySelectorAll(`div[group="${groupId}"]`);
let targetContainer = groupElements[0].parentElement?.parentElement;
console.log(targetContainer, "targetContainer line 421")
let targetElement = groupElements[0].parentElement;
if (!targetContainer || !targetElement) return;
const containerRect = targetContainer.getBoundingClientRect();
handleGroupBorderPosition(groupElements, containerRect, 40, 40);
} }
setDroppedItems({
...droppedItems,
[key]: Array.isArray(droppedItems[key])
? droppedItems[key].map((item, i) =>
i === index
? { ...item, dimensions: { ...item.dimensions, left: newLeft, top: newTop }, update: true }
: item
)
: [],
});
};
// Create a wrapper that matches DirectionalControls' expected signature return (
const directionalControlsHandler = (direction: 'up' | 'down' | 'left' | 'right') => { <>
handleDirectionalMove(direction, 5); // Use default stepSize of 5 {/* Center Doc Container - START */}
}; <div id="doc-con" className='zoom-con col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth'>
{
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => {
return (
<div key={indxDoc} className='flex flex-col'>
<div className='py-2 text-sm'>
<p className='text-center'>{doc.name}</p>
</div>
<div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'>
{
doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
const key = `${doc.id}_${page.index}`;
return (
<div
key={indx}
id={`${doc.id}${indx}`}
onDrop={(e) => handleDrop(e, doc.id, indxDoc, page.index)}
onDragOver={handleDragOver}
className='drop-page-in shadow-md relative'
>
{/* DROPPED ITEMS - Render only the dropped items for this specific docId and page - STARTS */}
{droppedItems[key]?.map((item, index) => {
if (item.isDeleted) return;
const type = item.type.toUpperCase();
const IconComponent = typeToIconMap[type] || <FaQuestionCircle />;
const selectedRecipient = selectedRecipients.filter((obj, indx) => obj.id === item.recipientId);
return (
<>
<div
key={index}
draggable="false"
className="absolute rounded dropped-item"
onClick={() => handleTagClick(key, index)}
style={{
left: `${item.dimensions.left}px`,
top: `${item.dimensions.top}px`,
width: item.type === "radio" ? `${item.dimensions.width + 20}px` : `${item.dimensions.width}px`,
height: item.type === "radio" ? `max-content` : `${item.dimensions.height}px`
}}
>
{/* If Selected Tag Recipient Info- START */}
{selectedTag?.key && selectedTag.key === key && selectedTag.index === index &&
<div className='absolute flex justify-between items-center w-full max-w-max bg-white top-[-22px] shadow z-20 text-sm rounded-t-sm'>
<div className='flex justify-center items-center gap-1 bg-white px-2 shadow'>
<p className='w-2.5 h-2.5 rounded'
style={{ background: selectedRecipient[0]?.color }}
></p>
<p
className=''>{IconComponent}
</p>
<p className='capitalize text-ellipsis whitespace-nowrap'>{item.label}</p>
<IoMdClose onClick={(e) => {
e.stopPropagation();
handleRemoveTag(key, index)
}} className='cursor-pointer' />
</div>
</div>}
{/* If Selected Tag Recipient Info- END */}
function handleIGroupBorderPosition(groupId: string) { {/* Expand Circle - STARTS */}
{item.type !== "radio" && <div
const groupElements: NodeListOf<HTMLElement> = document.querySelectorAll(`div[group="${groupId}"]`); onPointerDown={(e) => handleExpandTag(e, key, index)}
className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'>
let targetContainer = groupElements[0].parentElement?.parentElement;
console.log(targetContainer, "targetContainer line 421")
let targetElement = groupElements[0].parentElement;
if (!targetContainer || !targetElement) return;
const containerRect = targetContainer.getBoundingClientRect();
handleGroupBorderPosition(groupElements, containerRect, 40, 40);
}
return (
<>
{/* Center Doc Container - START */}
<div id="doc-con" className='zoom-con col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth'>
{
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => {
return (
<div key={indxDoc} className='flex flex-col'>
<div className='py-2 text-sm'>
<p className='text-center'>{doc.name}</p>
</div>
<div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'>
{
doc.images && doc.images.length > 0 && doc.images.map((page, indx) => {
const key = `${doc.id}_${page.index}`;
return (
<div
key={indx}
id={`${doc.id}${indx}`}
onDrop={(e) => handleDrop(e, doc.id, indxDoc, page.index)}
onDragOver={handleDragOver}
className='drop-page-in shadow-md relative'
>
{/* DROPPED ITEMS - Render only the dropped items for this specific docId and page - STARTS */}
{droppedItems[key]?.map((item, index) => {
if (item.isDeleted) return;
const type = item.type.toUpperCase();
const IconComponent = typeToIconMap[type] || <FaQuestionCircle />;
const selectedRecipient = selectedRecipients.filter((obj, indx) => obj.id === item.recipientId);
return (
<>
<div
key={index}
draggable="false"
className="absolute rounded dropped-item"
onClick={() => handleTagClick(key, index)}
style={{
left: `${item.dimensions.left}px`,
top: `${item.dimensions.top}px`,
width: item.type === "radio" ? `${item.dimensions.width + 20}px` : `${item.dimensions.width}px`,
height: item.type === "radio" ? `max-content` : `${item.dimensions.height}px`
}}
>
{/* If Selected Tag Recipient Info- START */}
{selectedTag?.key && selectedTag.key === key && selectedTag.index === index &&
<div className='absolute flex justify-between items-center w-full max-w-max bg-white top-[-22px] shadow z-20 text-sm rounded-t-sm'>
<div className='flex justify-center items-center gap-1 bg-white px-2 shadow'>
<p className='w-2.5 h-2.5 rounded'
style={{ background: selectedRecipient[0]?.color }}
></p>
<p
className=''>{IconComponent}
</p>
<p className='capitalize text-ellipsis whitespace-nowrap'>{item.label}</p>
<IoMdClose onClick={(e) => {
e.stopPropagation();
handleRemoveTag(key, index)
}} className='cursor-pointer' />
</div>
</div>} </div>}
{/* If Selected Tag Recipient Info- END */} {/* Expand Circle - ENDS */}
{/* Tag - START */}
{
<div
{/* Expand Circle - STARTS */} {...("group" in item && item.group ? { "group": item.group } : {})}
{item.type !== "radio" && <div onPointerDown={(e) => handleTagMouseDown(e, key, index)}
onPointerDown={(e) => handleExpandTag(e, key, index)} className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full'
className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'> style={{ background: selectedRecipient[0]?.color }}
</div>}
{/* Expand Circle - ENDS */}
{/* Tag - START */}
{
<div
{...("group" in item && item.group ? { "group": item.group } : {})}
onPointerDown={(e) => handleTagMouseDown(e, key, index)}
className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full'
style={{ background: selectedRecipient[0]?.color }}
>
<p
className={`flex justify-center items-center w-full text-xl text-white h-full`}
// style={{ color: selectedRecipients[item.recipientIndx]?.color }}
> >
{IconComponent} <p
</p> className={`flex justify-center items-center w-full text-xl text-white h-full`}
</div> // style={{ color: selectedRecipients[item.recipientIndx]?.color }}
>
{IconComponent}
</p>
</div>
}
{/* Tag - END */}
</div>
{
"group" in item && item.group && "isGroupExist" in item && !item.isGroupExist &&
<>
<div className="group-border" border-group={item.group} border-pos="top"></div>
<div className="group-border" border-group={item.group} border-pos="right"></div>
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom">
<div onClick={() => handleAddGroupItemClicked(key, item)}><IoAdd /></div>
</div>
<div className="group-border" border-group={item.group} border-pos="left"></div>
</>
} }
{/* Tag - END */}
</div> </>
)
}
{ )}
"group" in item && item.group && "isGroupExist" in item && !item.isGroupExist && {/* Render only the dropped items for this specific docId and page - ENDS */}
<>
<img
<div className="group-border" border-group={item.group} border-pos="top"></div> style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
<div className="group-border" border-group={item.group} border-pos="right"></div> draggable="false"
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom"> width={page.width}
<div onClick={() => handleAddGroupItemClicked(key, item)}><IoAdd /></div> height={page.height}
</div> src={page.src}
<div className="group-border" border-group={item.group} border-pos="left"></div> loading='lazy'
</> />
}
</>
)
}
)}
{/* Render only the dropped items for this specific docId and page - ENDS */}
<img
style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
draggable="false"
width={page.width}
height={page.height}
src={page.src}
loading='lazy'
/>
{/* Side details of documents Pages and document Name - START */} {/* Side details of documents Pages and document Name - START */}
<p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'> <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'>
page {indx + 1} of {doc.images && doc.images.length} page {indx + 1} of {doc.pages && doc.pages.length}
</p> </p>
{/* <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs right-[-140px] text-slate-600'> {/* <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs right-[-140px] text-slate-600'>
Document {indxDoc + 1} {doc.name} Document {indxDoc + 1} {doc.name}
</p> */} </p> */}
{/* Side details of documents Pages and document Name - END */} {/* Side details of documents Pages and document Name - END */}
</div>
)
})
}
</div>
</div>
)
})
}
</div> </div>
)
}) : <Loader />
}
</div> </div >
)
}) : <Loader />
}
{selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} DirectionalControlsProps={{
onMove: directionalControlsHandler,
disabled: false,
stepSize: 5,
className: ''
}} />}
{/* Center Doc Container - END */}
</>
)
}
</div > export default DocumentContainer;
{selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} DirectionalControlsProps={{
onMove: directionalControlsHandler,
disabled: false,
stepSize: 5,
className: ''
}} />}
{/* Center Doc Container - END */}
</>
)
}
export default DocumentContainer;

View File

@ -133,10 +133,10 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
onClick={() => setDropDownTogglePage(`${(dropDownTogglePage === 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`)} onClick={() => setDropDownTogglePage(`${(dropDownTogglePage === 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`)}
className={`p-2 ${(dropDownTogglePage === 'pageDropDown' + indx) ? "bg-[var(--primary)]" : "bg-slate-200"} flex flex-col justify-center items-center gap-2 rounded`}> className={`p-2 ${(dropDownTogglePage === 'pageDropDown' + indx) ? "bg-[var(--primary)]" : "bg-slate-200"} flex flex-col justify-center items-center gap-2 rounded`}>
{ {
document.images != undefined && <img document.pages != undefined && <img
key={indx} key={indx}
className='rounded-sm shadow' className='rounded-sm shadow'
src={document.images[0].src} src={document.pages[0].src}
alt="" /> alt="" />
} }
@ -151,7 +151,7 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
{ {
dropDownTogglePage === 'pageDropDown' + indx && dropDownTogglePage === 'pageDropDown' + indx &&
<div className='my-1 flex justify-center items-center gap-1 flex-wrap'> <div className='my-1 flex justify-center items-center gap-1 flex-wrap'>
{document.images != undefined && document.images.map((img, indx) => ( {document.pages != undefined && document.pages.map((img, indx) => (
<a <a
href={"#" + document.id + indx} href={"#" + document.id + indx}
className='max-w-max px-4 py-2 shadow cursor-pointer border-1 rounded-sm border-slate-200 hover:border-[var(--primary)] transition-all duration-300 bg-slate-300' className='max-w-max px-4 py-2 shadow cursor-pointer border-1 rounded-sm border-slate-200 hover:border-[var(--primary)] transition-all duration-300 bg-slate-300'

View File

@ -28,6 +28,7 @@ import { Bounce, toast } from "react-toastify";
import axios from 'axios'; import axios from 'axios';
import { SaveType } from '../client/SignDocument'; import { SaveType } from '../client/SignDocument';
import { CiSaveDown1 } from 'react-icons/ci'; import { CiSaveDown1 } from 'react-icons/ci';
import api from '../../config/api';
interface Signature { interface Signature {
id: string; id: string;
@ -54,6 +55,7 @@ interface AddSignatureModalProps {
setOpenSign: React.Dispatch<React.SetStateAction<boolean>>; setOpenSign: React.Dispatch<React.SetStateAction<boolean>>;
saved: SaveType; saved: SaveType;
setSaved: React.Dispatch<React.SetStateAction<SaveType>>; setSaved: React.Dispatch<React.SetStateAction<SaveType>>;
signTagId: boolean
} }
interface Account { interface Account {
@ -80,9 +82,12 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
setOpenSign, setOpenSign,
recipientId, recipientId,
saved, saved,
setSaved setSaved,
signTagId
}) => { }) => {
const [activeTab, setActiveTab] = useState<'TYPED' | 'DRAW' | 'UPLOAD'>('TYPED');
console.log("savedModal", saved)
const [activeTab, setActiveTab] = useState<'TYPED' | 'DRAW' | 'UPLOAD' | 'SAVED'>('TYPED');
const [fullName, setFullName] = useState(''); const [fullName, setFullName] = useState('');
const [initials, setInitials] = useState(''); const [initials, setInitials] = useState('');
const [isSubmitting, setIsSubmitting] = useState<boolean>(false); const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
@ -100,6 +105,22 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
{ id: 6, name: 'classic', initials: 'LA', style: 'classic' }, { id: 6, name: 'classic', initials: 'LA', style: 'classic' },
]; ];
// User data
const [user, setUser] = useState<UserData>({
id: "",
firstName: "",
lastName: "",
email: "",
language: "",
account: {
id: 0,
isDefault: false,
name: "",
},
});
console.log("User data", user)
const [selectedSignature, setSelectedSignature] = useState<SelectStyle>( const [selectedSignature, setSelectedSignature] = useState<SelectStyle>(
signatureOptions.length > 0 ? { id: signatureOptions[0].id, style: signatureOptions[0].style } : { id: 0, style: "" } signatureOptions.length > 0 ? { id: signatureOptions[0].id, style: signatureOptions[0].style } : { id: 0, style: "" }
); );
@ -112,21 +133,8 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
const [preview1, setPreview1] = useState<string | null>(null); const [preview1, setPreview1] = useState<string | null>(null);
const [preview2, setPreview2] = useState<string | null>(null); const [preview2, setPreview2] = useState<string | null>(null);
// User data const [preSaveSignatures, setPreSaveSignatures] = useState([])
// const [user, setUser] = useState<UserData>({
// id: "",
// firstName: "",
// lastName: "",
// email: "",
// language: "",
// account: {
// id: 0,
// isDefault: false,
// name: "",
// },
// });
// const isEditMode = !!editingSignature;
// Navigation items // Navigation items
const navigationItems = [ const navigationItems = [
@ -136,34 +144,6 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
{ id: 'SAVED', label: 'SAVED', icon: <CiSaveDown1 className="w-5 h-5" /> }, { id: 'SAVED', label: 'SAVED', icon: <CiSaveDown1 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 'TYPED' | '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 }) => { const handleSignatureChange = (data: { fullSignature: string | null; initials: string | null }) => {
setSignatures(data); setSignatures(data);
@ -177,8 +157,10 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
"ngrok-skip-browser-warning": "69420" "ngrok-skip-browser-warning": "69420"
} }
}) })
if (res?.data) {
console.log(res, "Geting Signatures") let preSavedSignatures = res?.data
setPreSaveSignatures(preSavedSignatures)
}
})() })()
}, []) }, [])
// Getting Signatures - END // Getting Signatures - END
@ -225,7 +207,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
const handleSubmit = async () => { const handleSubmit = async () => {
setIsSubmitting(true); setIsSubmitting(true);
console.log("handleSubmit Called") console.log("handleSubmit Called")
if (!signatures.fullSignature || !signatures.initials) { if (!signatures.fullSignature || !signatures.initials) {
alert('Please provide both signature and initials'); alert('Please provide both signature and initials');
setIsSubmitting(false); setIsSubmitting(false);
@ -234,15 +216,18 @@ console.log("handleSubmit Called")
try { try {
const payload = { const payload = {
signatureType: activeTab, tagId: signTagId,
signatureBase64: signatures.fullSignature, // signatureId: signatureId,
initialsBase64: signatures.initials, type: activeTab,
firstName: fullName.split(' ')[0] || user.firstName, firstName: fullName.split(' ')[0] || user.firstName,
lastName: fullName.split(' ')[1] || user.lastName, lastName: fullName.split(' ')[1] || user.lastName,
initials: "",
initialsBase64: signatures.initials,
signatureBase64: signatures.fullSignature,
font: selectedSignature.style font: selectedSignature.style
}; };
let res; console.log("payload", payload)
// if (isEditMode && editingSignature) { // if (isEditMode && editingSignature) {
// // Update existing signature // // Update existing signature
// const updatePayload = { // const updatePayload = {
@ -265,7 +250,7 @@ console.log("handleSubmit Called")
// }); // });
// } else { // } else {
// Create new signature // Create new signature
res = await axios.post(`${import.meta.env.VITE_BASE_URI}/api/user/signatures`, let res = await axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/signatures?recipientId=${recipientId}`,
payload, payload,
{ {
headers: { headers: {
@ -288,9 +273,19 @@ console.log("handleSubmit Called")
// } // }
if (res?.data) { if (res?.data) {
await getAllSignatures(); setSignatures({
setShowAddSignature?.(false); fullSignature:"",
initials:"",
})
setSaved({
saveType: res.data.signatureType ?? null,
save: true,
signature: res.data.signatureBlob ?? null,
initial: res.data.initialsBlob ?? null,
});
setOpenSign(false);
} }
} catch (error) { } catch (error) {
console.error("Error submitting signature:", error); console.error("Error submitting signature:", error);
// toast.error(isEditMode ? "Failed to update signature" : "Failed to create signature", { // toast.error(isEditMode ? "Failed to update signature" : "Failed to create signature", {
@ -431,32 +426,105 @@ console.log("handleSubmit Called")
</div> </div>
</div> </div>
); );
case 'SAVED':
return (
<div className="h-full flex flex-col ">
<div className="mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-2">Saved Signature's</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">
{preSaveSignatures.map((signature) => (
<div key={signature?.id} className="grid grid-cols-12 gap-4 px-6 py-9 hover:bg-gray-50 transition-colors duration-150">
{/* Signature Column */}
<div className="col-span-5">
<div className="flex flex-col space-y-2 relative ps-6 border-l-3 border-blue-600 rounded-bl-2xl rounded-tl-2xl">
<div className="text-sm text-gray-600 font-medium mb-1">
Signed by:
</div>
{/* Signature Image or Fallback Text */}
<div className="bg-white rounded-lg p-1 flex items-center justify-center">
{signature?.signatureBlob ? (
<img
src={`data:${signature?.signatureMimeType || "image/png"};base64,${signature?.signatureBlob}`}
className="h-[50px] w-[250px] object-contain"
alt="Signature"
/>
) : (
<div className="text-gray-400 italic text-sm">No Signature</div>
)}
</div>
{/* Name */}
<div className="text-xs text-gray-500 truncate">
{signature?.signatureHash ?? ' '}
</div>
</div>
</div>
{/* Initial Column */}
<div className="col-span-5">
<div className="flex flex-col space-y-2 relative ps-6 border-l-3 border-green-600 rounded-bl-2xl rounded-tl-2xl">
<div className="text-sm text-gray-600 font-medium mb-1">
Initials
</div>
{/* Initials Image */}
<div className="bg-white rounded-lg p-1 flex items-center justify-center">
{signature?.initialsBlob ? (
<img
src={`data:image/png;base64,${signature?.initialsBlob}`}
className="h-[50px] object-contain"
alt="Initials"
/>
) : (
<div className="text-gray-400 italic text-sm">No Initials</div>
)}
</div>
{/* Optional initials hash or font */}
<div className="text-xs text-gray-500 truncate">
{signature?.initialsHash || " "}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
default: default:
return null; return null;
} }
}; };
// const getUserDetails = async () => { const getUserDetails = async () => {
// try { try {
// let res = await api.get(`/api/user/details`); let res = await axios.get(`/api/user/details`, {
// console.log("getuser", res); headers: {
// let currUser = res?.data; "ngrok-skip-browser-warning": "69420",
// setUser(currUser); }
});
console.log("getuser", res);
let currUser = res?.data;
setUser(currUser);
// // Only set default values if not in edit mode setFullName(currUser.firstName + " " + currUser.lastName);
// if (!isEditMode) { setInitials(currUser.firstName.slice(0, 1) + currUser.lastName.slice(0, 1));
// setFullName(currUser.firstName + " " + currUser.lastName); } catch (error) {
// setInitials(currUser.firstName.slice(0, 1) + currUser.lastName.slice(0, 1)); console.error("Error fetching user details:", error);
// } }
// } catch (error) { };
// console.error("Error fetching user details:", error);
// }
// };
// useEffect(() => { useEffect(() => {
// getUserDetails(); getUserDetails();
// }, []); }, []);
return ( return (
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 flex items-center justify-center z-50 p-4"> <div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 flex items-center justify-center z-50 p-4">
@ -468,7 +536,7 @@ console.log("handleSubmit Called")
<div className="px-4 py-4 border-b border-gray-200"> <div className="px-4 py-4 border-b border-gray-200">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className='px-1 py-1'> <div className='px-1 py-1'>
<h3 className="text-md font-semibold text-gray-900">Add Signature</h3> <h3 className="text-md font-semibold text-gray-900">Add Signature Sign</h3>
<p className="text-xs text-gray-500">Create your digital signature</p> <p className="text-xs text-gray-500">Create your digital signature</p>
</div> </div>
</div> </div>
@ -511,7 +579,7 @@ console.log("handleSubmit Called")
{navigationItems.map((item) => ( {navigationItems.map((item) => (
<button <button
key={item.id} key={item.id}
onClick={() => setActiveTab(item.id as 'TYPED' | 'DRAW' | 'UPLOAD')} onClick={() => setActiveTab(item.id as 'TYPED' | 'DRAW' | 'UPLOAD' | 'SAVED')}
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 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' ? 'bg-blue-50 text-blue-700 border border-blue-200'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900' : 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'

View File

@ -128,6 +128,7 @@ canvas {
/* Group Border - END */ /* Group Border - END */
/* Loader CSS */ /* Loader CSS */
/* From Uiverse.io by Juanes200122 */ /* From Uiverse.io by Juanes200122 */
.pl { .pl {
@ -333,4 +334,13 @@ canvas {
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -440; stroke-dashoffset: -440;
} }
} }
.bottom-border>div:hover {
background-color: var(--primaryLight2) ;
opacity: 0.9;
transition: all 0.3s ease-in-out;
cursor: pointer;
}
/* Group Border - END */

View File

@ -21,7 +21,7 @@ export interface SelectedDocument {
extension: string; extension: string;
original: boolean; original: boolean;
corrupted?: boolean; corrupted?: boolean;
images?: Image[]; pages?: Image[];
} }
export interface PayloadSelectedDocument { export interface PayloadSelectedDocument {
@ -30,7 +30,7 @@ export interface PayloadSelectedDocument {
extension: string; extension: string;
original: boolean; original: boolean;
corrupted?: boolean; corrupted?: boolean;
images?: Image[]; pages?: Image[];
} }
interface SelectedDocumentsState { interface SelectedDocumentsState {
@ -87,7 +87,7 @@ const selectedDocumentsSlice = createSlice({
const docId = document.documentId; const docId = document.documentId;
for (const [index, selDoc] of state.selectedDocuments.entries()) { for (const [index, selDoc] of state.selectedDocuments.entries()) {
if (selDoc.id === docId) { if (selDoc.id === docId) {
state.selectedDocuments[index].images = document.images; state.selectedDocuments[index].pages = document.pages;
break; break;
} }
} }

View File

@ -352,6 +352,7 @@ export interface Recipient {
type: recipientType; type: recipientType;
color?: string; color?: string;
roleName:string roleName:string
provideOwnTags?: boolean;
} }
export interface SelectedRecipientInt { export interface SelectedRecipientInt {

View File

@ -1,61 +1,61 @@
import { JSX } from "react"; import { JSX } from "react";
export interface DroppedItem { export interface DroppedItem {
id?: string; id?: string;
icon: JSX.Element; icon: JSX.Element;
focusElementIndx?: number; focusElementIndx?: number;
label?: string; label?: string;
type: string; type: string;
subType?: string; subType?: string;
dimensions: { dimensions: {
left: number; left: number;
top: number; top: number;
width: number; width: number;
height: number; height: number;
} }
recipientId: number; recipientId: number;
// mask?: { // mask?: {
// type: string; // type: string;
// character: string; // character: string;
// } // }
maskType?: string; maskType?: string;
maskCharacter?: string; maskCharacter?: string;
required?: boolean; required?: boolean;
format?: string; format?: string;
radioGroups?: DroppedItem[]; radioGroups?: DroppedItem[];
// documentIndex: number; // documentIndex: number;
documentId: string; documentId: string;
pageIndex: number; pageIndex: number;
options?: string[] options?: string[]
selectedValue?: string | null; selectedValue?: string | null;
radioPosition?: { radioPosition?: {
topMin: number; topMin: number;
topMax: number; topMax: number;
leftMin: number; leftMin: number;
leftMax: number; leftMax: number;
width?: number; width?: number;
height?: number height?: number
}; };
value?: string | number; value?: string | number;
validation?: { validation?: {
minLength: number; minLength: number;
maxLength: number; maxLength: number;
error: string; error: string;
regex: string[]; regex: string[];
}; };
isDeleted: boolean; isDeleted: boolean;
} }
export interface DroppedItemsObj { export interface DroppedItemsObj {
[key: string]: DroppedItem[] [key: string]: DroppedItem[]
} }
export enum OpenType { export enum OpenType {
TYPED, TYPED,
DRAW, DRAW,
UPLOADED UPLOADED
} }
// types/account.ts // types/account.ts
@ -81,39 +81,45 @@ export interface AccountData {
} }
export interface TagsProperties { export interface TagsProperties {
"editable": boolean; "editable": boolean;
"required": boolean; "required": boolean;
"navigationOrder": number; "navigationOrder": number;
"type": string; "type": string;
"lastName"?: boolean; "lastName"?: boolean;
"firstName"?: boolean; "firstName"?: boolean;
"maxLength"?: number; "maxLength"?: number;
"minLength"?: number; "minLength"?: number;
"readOnly"?: boolean; "readOnly"?: boolean;
"multiLine"?: boolean; "multiLine"?: boolean;
"regex"?: string[]; "regex"?: string[];
"format"?: string; "format"?: string;
"dateFormat"?: string; "dateFormat"?: string;
"currentTimeStamp"?: boolean; "currentTimeStamp"?: boolean;
"decimalPlace"?: number; "decimalPlace"?: number;
"multiSelect"?: boolean; "multiSelect"?: boolean;
} }
export enum TagsTypes { export enum TagsTypes {
TEXT= "text", TEXT = "text",
SIGNATURE= "signature", SIGNATURE = "signature",
INITIALS= "initials", INITIALS = "initials",
TEMPORAL= "temporal", TEMPORAL = "temporal",
NUMBER= "number", NUMBER = "number",
IMAGE= "image", IMAGE = "image",
STAMP= "stamp", STAMP = "stamp",
CHECKBOX= "checkbox", CHECKBOX = "checkbox",
SELECT= "select", SELECT = "select",
FILE= "file", FILE = "file",
ATTACHMENT= "attachment", ATTACHMENT = "attachment",
RADIO= "radio", RADIO = "radio",
EMAIL= "email", EMAIL = "email",
NAME= "name", NAME = "name",
COMPANY= "company", COMPANY = "company",
} }
export enum SignTypeEnum {
selfSign = "self_sign",
multipleSign = "multi_sign"
}