Compare commits

..

6 Commits

19 changed files with 884 additions and 501 deletions

View File

@ -22,7 +22,6 @@ import Templates from "./components/templates/Templates";
import DocumentCompletionScreen from "./components/process/CompletionScreen/DocumentCompletionScreen";
import JoyrideController from "./utlity/JoyRideTour";
import Contacts from "./components/contacts/Contacts";
const App = () => {
return (

View File

@ -17,6 +17,32 @@ const SignCompletionScreen: React.FC = () => {
const [showDownloadModal, setShowDownloadModal] = useState(false);
const [animationPhase, setAnimationPhase] = useState(0);
useEffect(() => {
const currentPath = window.location.pathname;
const handlePopState = (event) => {
// Show warning
// alert("Back navigation is blocked on this screen.");
// Push the current URL back to prevent navigating away
window.history.pushState(null, "", currentPath);
};
// Push extra states to build history buffer
for (let i = 0; i < 50; i++) {
window.history.pushState(null, "", currentPath);
}
// Add popstate listener
window.addEventListener("popstate", handlePopState);
// Cleanup on unmount
return () => {
window.removeEventListener("popstate", handlePopState);
};
}, []);
useEffect(() => {
// Trigger animation phases sequentially
const timers = [
@ -301,7 +327,7 @@ const SignCompletionScreen: React.FC = () => {
>
{/* <a> */}
<IoMdArrowRoundBack className="w-5 h-5 mr-3" />
Back Sign Made Easy
Back to Sign Made Easy
{/* </a> */}
</a> :
<a
@ -321,7 +347,7 @@ const SignCompletionScreen: React.FC = () => {
{/* Additional Info */}
<div className={`mt-8 pt-3 border-t border-gray-200 border-opacity-50`}>
<p className="text-sm text-gray-500">
Your document is signed and ready. You can download it or return to the sign made easy.
Your document is signed and saved. Download it or return to Sign Made Easy to continue — navigation is paused until an action is taken.
</p>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -134,7 +134,7 @@ const Contacts = () => {
</button>
</div>
</div>
<div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar">
<div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar ">
<table className="min-w-full text-sm bg-white">
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider sticky top-0 z-10">
<tr>
@ -162,7 +162,7 @@ const Contacts = () => {
</td>
</tr>
) : (
contacts.map((contact) => (
contacts.map((contact, index) => (
<tr key={contact.id} className="hover:bg-gray-50">
<td className="px-4 py-3">{contact.firstName}</td>
<td className="px-4 py-3">{contact.lastName}</td>
@ -181,7 +181,7 @@ const Contacts = () => {
</button>
{openMenuId === contact.id && (
<div className="absolute top-full right-0 mt-1 bg-white border border-gray-200 rounded shadow-lg z-20 min-w-[100px]">
<div className={`absolute mt-1 bg-white border border-gray-200 rounded shadow-lg z-20 min-w-[100px] ${(contacts.length - 1) === index ? "bottom-1 right-4" : "bottom-0 right-5"}`}>
<button
onMouseDown={() => handleEdit(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"

View File

@ -50,7 +50,7 @@ const StartNow = () => {
<div
onClick={() => fileInputRef.current?.click()}
id='start-now-upload-con'
className="group cursor-pointer border-slate-300 rounded-lg bg-white/60 transition-all duration-200 p-4 flex flex-col items-center justify-center gap-2 text-center shadow-sm hover:shadow-md">
className="group col-span-2 cursor-pointer border-slate-300 rounded-lg bg-white/60 transition-all duration-200 p-4 flex flex-col items-center justify-center gap-2 text-center shadow-sm hover:shadow-md">
{/* Upload icon with subtle animation */}
<div className="p-2 text-blue-600 group-hover:text-blue-700 transition-colors">
<FiUpload className="text-lg" />

View File

@ -28,16 +28,17 @@ const cards = [
// buttonText: 'Start Now',
// bg: 'bg-white/60',
// },
{
icon: <FiEdit className="w-5 h-5 text-green-700" />,
title: 'Sign an Agreement',
description: 'Upload a document you need to sign and place your signature.',
bg: 'bg-white/60',
},
// {
// icon: <FiEdit className="w-5 h-5 text-green-700" />,
// title: 'Sign an Agreement',
// description: 'Upload a document you need to sign and place your signature.',
// bg: 'bg-white/60',
// },
{
icon: <FiFolder className="w-5 h-5 text-purple-700" />,
title: 'Use a Template',
description: 'Pick from ready-made templates for common agreements.',
link: "/templates",
bg: 'bg-white/60',
},
];
@ -54,19 +55,19 @@ const StartSection = () => {
const defaultAccountId = user.account.id;
const handleStartSectionClick = (card: StartCard) => {
if ("buttonText" in card && card.buttonText === "Start Now") {
api.post(`/api/accounts/${defaultAccountId}/transactions`)
.then(res => {
if (res.status === 200 || res.status === 201) {
dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED));
dispatch(setTransactionId(res.data));
setTimeout(() => navigate(`/process-step-one/${defaultAccountId}/${res.data.id}`), 0)
}
})
.catch(err => console.log(err))
}
// navigate("/process-step-one");
const handleStartSectionClick = (link: string) => {
// if ("buttonText" in card && card.buttonText === "Start Now") {
// api.post(`/api/accounts/${defaultAccountId}/transactions`)
// .then(res => {
// if (res.status === 200 || res.status === 201) {
// dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED));
// dispatch(setTransactionId(res.data));
// setTimeout(() => navigate(`/process-step-one/${defaultAccountId}/${res.data.id}`), 0)
// }
// })
// .catch(err => console.log(err))
// }
navigate(link);
}
// useEffect(() => {
@ -87,10 +88,10 @@ const StartSection = () => {
<StartNow />
<div className='col-span-2 grid grid-cols-2 justify-between gap-4'>
<div className='col-span-1 grid grid-cols-1 justify-between gap-4'>
{cards.map((card, index) => (
<div
onClick={() => handleStartSectionClick(card)}
onClick={() => handleStartSectionClick(card.link)}
key={index}
className={`${card.bg} w-full max-h-max cursor-pointer relative border border-gray-200 rounded-xl p-4 shadow-sm hover:shadow-md transition-all flex flex-col justify-between`}
>

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import {
FaCheck,
FaFileAlt,
@ -15,12 +15,13 @@ import {
import { fetchRecipients } from "../../../store/slices/selectedRecipientsSlice";
import { fetchDocuments } from "../../../store/slices/documentSlice";
import { useDispatch, useSelector } from "react-redux";
import { Link, useNavigate, useParams } from "react-router-dom";
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
import Loader from "../../dashboard/Loader";
import confetti from 'canvas-confetti'
import { IoClose } from "react-icons/io5";
import { IoMdClose, IoMdHome } from "react-icons/io";
import { Bounce, toast } from "react-toastify";
import api from "../../../config/api";
type Signer = {
id: number;
@ -200,7 +201,9 @@ const DocumentCompletionScreen = () => {
{ title: 'Document 10', pages: 11 },
{ title: 'Document 11', pages: 4 }, // Won't be shown due to slice
];
const location = useLocation();
const [status, setStatus] = useState(null);
console.log("location.pathname", location.pathname, status)
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const [showConfetti, setShowConfetti] = React.useState(true)
if (!accountId || !transactionId) {
@ -265,6 +268,77 @@ const DocumentCompletionScreen = () => {
}, [showConfetti]);
console.log("History length:", window.history.length);
// Step 1: Fetch transaction status
useEffect(() => {
const fetchTransaction = async () => {
// /api/accounts/{accountId}/transactions/{trxId}
try {
const res = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}`);
setStatus(res?.data?.status);
} catch (error) {
console.error("Failed to fetch transaction:", error);
}
};
if (accountId && transactionId) {
fetchTransaction();
}
}, [accountId, transactionId]);
// Step 2: Prevent browser back when status === 'SENT'
useEffect(() => {
if (status === "SENT") {
const currentPath = window.location.pathname;
const handlePopState = (event) => {
// Show warning
alert("Back navigation is blocked on this screen.");
// Push the current URL back to prevent navigating away
window.history.pushState(null, "", currentPath);
};
// Push extra states to build history buffer
for (let i = 0; i < 50; i++) {
window.history.pushState(null, "", currentPath);
}
// Add popstate listener
window.addEventListener("popstate", handlePopState);
// Cleanup on unmount
return () => {
window.removeEventListener("popstate", handlePopState);
};
}
}, [status]);
// useEffect(() => {
// if (status === 'SENT') {
// // Step 1: Push a dummy state to trap the user
// window.history.pushState(null, '', window.location.href);
// // Step 2: Intercept back button
// const handlePopState = (event) => {
// // Push the same dummy state again to block back
// window.history.pushState(null, '', window.location.href);
// };
// window.addEventListener('popstate', handlePopState);
// return () => {
// window.removeEventListener('popstate', handlePopState);
// };
// }
// }, [status]);
return (
<>
@ -285,8 +359,8 @@ const DocumentCompletionScreen = () => {
className='flex items-center gap-3'>
<Link to={`/accounts/${accountId}/transactions/${transactionId}`} className="flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]">
<FaPen className=" text-white text-md" />
<span className="tracking-wide text-white">Edit</span>
{/* <FaPen className=" text-white text-md" /> */}
<span className="tracking-wide text-white">View Transaction</span>
</Link>
{/* <button

View File

@ -60,6 +60,7 @@ const ProcessStepOne = () => {
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
const [showSelfSignWarnModal, setShowSelfSignWarnModal] = useState(false)
const [editRecipientCurrentEmail, setEditRecipientCurrentEmail] = useState('')
console.log()
const userData = useSelector((state: RootState) => state.user)
@ -95,6 +96,15 @@ const ProcessStepOne = () => {
try {
const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`);
setTransactionDetails(res.data)
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
setTemplateName(res.data.name)
}
setEmailDtls(
{ subject: res.data.subject ? res.data.subject : "Documents for Signature", message: res.data.message ? res.data.message : "" }
)
// console.log("setTransactionDetails",res.data)
} catch (e) {
console.log("Error - transactions details")
@ -133,7 +143,8 @@ const ProcessStepOne = () => {
let formError = {
documents: false,
recipients: false,
subject: false
subject: false,
recipientEmailAndLastName:false
}
if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
@ -141,20 +152,32 @@ const ProcessStepOne = () => {
formError.documents = true
}
if (!selectedRecipients.length) {
formError.recipients = true
formError.recipients = true;
} else {
// Check if all recipients have email and lastName
const isValid = selectedRecipients.every(
(item) => item.email?.trim() !== '' && item.lastName?.trim() !== ''
);
if (!isValid) {
// alert('All recipients must have both Email and Last Name filled.');
formError.recipientEmailAndLastName = true;
}
}
if (!emailDtls.subject.length) {
formError.subject = true
}
if (SignTypeEnum.selfSign === signType) formError.recipients = false;
if (formError.documents || formError.recipients || formError.subject) {
if (formError.documents || formError.recipients || formError.subject || formError.recipientEmailAndLastName) {
console.log(selectedDocuments, 'line 193')
toast.dismiss();
toast.error(`${formError.documents ? "Add Document" : ""}
${(formError.documents && formError.recipients) ? " | " : ""}
${formError.recipients ? "Add Recipient" : ""}
${formError.recipientEmailAndLastName ? "All recipients must have both Email and Last Name." : ""}
${(formError.documents || formError.recipients) ? " | " : ""}
${formError.subject ? "Add Subject" : ""}`, {
position: "top-center",
@ -265,7 +288,7 @@ const ProcessStepOne = () => {
}
} else {
await dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId }));
await dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId }));
}
dispatch(showLoader("Processing Message..."));
@ -425,7 +448,8 @@ const ProcessStepOne = () => {
{
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
<div className="flex flex-col gap-4 align-start">
<RecipientCards transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} />
<RecipientCards transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} editRecipient={editRecipient} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} editRecipientCurrentEmail={editRecipientCurrentEmail} />
{/* {signType !== "self-sign" && <RecipientAddCard />} */}
<div
@ -433,7 +457,7 @@ const ProcessStepOne = () => {
className={`relative w-full bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none h-screen overflow-hidden' : ''}`}
>
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} editRecipient={editRecipient} editCase={editCase} setEditRecipient={setEditRecipient} setEditCase={setEditCase} topRef={topRef} />
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} editRecipient={editRecipient} editCase={editCase} setEditRecipient={setEditRecipient} setEditCase={setEditCase} topRef={topRef} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} />
</div>
</div>

View File

@ -22,10 +22,11 @@ interface ContactType {
export interface ContactTypeObj {
type: string;
contacts: ContactType[];
setEditRecipientCurrentEmail : any
}
const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase, setEditRecipient, setEditCase, topRef }) => {
const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase, setEditRecipient, setEditCase, topRef , setEditRecipientCurrentEmail }) => {
const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
@ -241,6 +242,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
type: false,
});
setEditRecipientCurrentEmail("")
setEditCase("");
setManualFocusTriggered(false);
formRef.current?.reset();

View File

@ -26,15 +26,21 @@ const getInitials = (name: string) => {
};
type CardsData = {
editRecipient: any;
setEditRecipient: (recipient: any) => void; // Replace `any` with the actual type if known
setEditCase: (string: any) => void;
transactionTemplateToggle: string
editRecipient: any
setEditRecipientCurrentEmail: any,
editRecipientCurrentEmail: string
};
const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateToggle , editRecipient ,editCase }: CardsData) => {
const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateToggle, editRecipient, editCase, setEditRecipientCurrentEmail, editRecipientCurrentEmail }: CardsData) => {
const templateId = useParams<{ templateId: string }>().templateId;
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const dispatch = useDispatch();
if (!transactionId) {
@ -56,6 +62,11 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
// alert(JSON.stringify(recipient))
setEditCase('Edit')
setEditRecipient(recipient)
if (recipient.uuid) {
setEditRecipientCurrentEmail(recipient.uuid)
}else{
setEditRecipientCurrentEmail(recipient.id)
}
}
const handleRemoveRecipient = (recipient) => {
@ -130,7 +141,7 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
{recipients.map((recipient, idx) => (
<div
key={recipient.email + idx}
className="flex items-center justify-between px-4 py-3 hover:bg-gray-50 transition-colors"
className={`flex items-center justify-between px-4 py-3 hover:bg-gray-50 transition-colors ${(recipient.uuid || recipient.id) === editRecipientCurrentEmail ? "bg-gray-200 opacity-40" : ""}`}
>
<div className="flex items-center gap-3 min-w-0">
{/* {signingOrder && ( */}
@ -156,65 +167,65 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
}
className="w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)]"
/>}
{/* )} */}
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-[var(--primary)] text-white flex items-center justify-center font-semibold text-xs select-none">
{getInitials(`${recipient.firstName} ${recipient.lastName || ""}`)}
</div>
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-[var(--primary)] text-white flex items-center justify-center font-semibold text-xs select-none">
{getInitials(`${recipient.firstName} ${recipient.lastName || ""}`)}
<div className="min-w-0">
<p className="font-semibold text-gray-800 truncate text-sm">
{recipient.firstName} {recipient.lastName || ""}
</p>
<p className="text-xs text-gray-500 truncate">
{recipient.email} | {recipient.type} {recipient.roleName ? `| Role : ${recipient.roleName}` : ""}
</p>
</div>
</div>
<div className="min-w-0">
<p className="font-semibold text-gray-800 truncate text-sm">
{recipient.firstName} {recipient.lastName || ""}
</p>
<p className="text-xs text-gray-500 truncate">
{recipient.email} | {recipient.type} {recipient.roleName ? `| Role : ${recipient.roleName}` : ""}
</p>
</div>
{signType !== "self-sign" && (
<div className="flex justify-between gap-2 items-center">
{/* Edit Button */}
<button
type="button"
title="Edit recipient"
aria-label={`Edit recipient ${recipient.firstName}`}
onClick={() => handleEditRecipient(recipient)}
disabled={
// For templates: always enabled (never disabled)
['templates'].includes(transactionTemplateToggle)
? false
: // For transactions: disabled if not newOne AND not allowed to edit
(!recipient.newOne && !recipient.allowSenderToEdit)
}
className="text-gray-400 hover:text-blue-600 transition-colors cursor-pointer disabled:opacity-50"
>
<FiEdit size={16} />
</button>
{/* Delete Button */}
<button
type="button"
title="Remove recipient"
aria-label={`Remove recipient ${recipient.firstName}`}
onClick={() => handleRemoveRecipient(recipient)}
disabled={
// For templates: always enabled (never disabled)
['templates'].includes(transactionTemplateToggle)
? false
: // For transactions: disabled if not newOne AND not allowed to delete
(!recipient.newOne && !recipient.allowSenderToDelete)
}
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50"
>
<ImBin size={16} />
</button>
</div>
)}
</div>
{signType !== "self-sign" && (
<div className="flex justify-between gap-2 items-center">
{/* Edit Button */}
<button
type="button"
title="Edit recipient"
aria-label={`Edit recipient ${recipient.firstName}`}
onClick={() => handleEditRecipient(recipient)}
disabled={
// For templates: always enabled (never disabled)
['templates'].includes(transactionTemplateToggle)
? false
: // For transactions: disabled if not newOne AND not allowed to edit
(!recipient.newOne && !recipient.allowSenderToEdit)
}
className="text-gray-400 hover:text-blue-600 transition-colors cursor-pointer disabled:opacity-50"
>
<FiEdit size={16} />
</button>
{/* Delete Button */}
<button
type="button"
title="Remove recipient"
aria-label={`Remove recipient ${recipient.firstName}`}
onClick={() => handleRemoveRecipient(recipient)}
disabled={
// For templates: always enabled (never disabled)
['templates'].includes(transactionTemplateToggle)
? false
: // For transactions: disabled if not newOne AND not allowed to delete
(!recipient.newOne && !recipient.allowSenderToDelete)
}
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50"
>
<ImBin size={16} />
</button>
</div>
)}
</div>
))}
)
)}
</div>
</>
) : (

View File

@ -77,35 +77,35 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
}, [])
const fetchTags = async () => {
try {
const requests = selectedRecipients.map((recipient) =>
api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/recipients/${recipient.id}/tags`)
);
const response = await Promise.all(requests);
let recTags = {}
for (const res of response) {
for (const tag of res.data) {
const key = `${tag.documentId}_${tag.pageIndex}`;
if (recTags[key]) {
recTags[key].push({ ...tag, update: false });
} else {
recTags[key] = [{ ...tag, update: false }]; // Initialize with an array
}
}
}
// console.log(recTags, "Line 83");
setDroppedItems(recTags)
// Handle your response here
} catch (err) {
console.error(err);
}
};
// FETCHING TAG - START
useEffect(() => {
if (selectedRecipients.length === 0) return;
const fetchTags = async () => {
try {
const requests = selectedRecipients.map((recipient) =>
api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/recipients/${recipient.id}/tags`)
);
const response = await Promise.all(requests);
let recTags = {}
for (const res of response) {
for (const tag of res.data) {
const key = `${tag.documentId}_${tag.pageIndex}`;
if (recTags[key]) {
recTags[key].push(tag);
} else {
recTags[key] = [tag]; // Initialize with an array
}
}
}
// console.log(recTags, "Line 83");
setDroppedItems(recTags)
// Handle your response here
} catch (err) {
console.error(err);
}
};
fetchTags();
}, [selectedRecipients]);
@ -161,7 +161,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
delete updateTags[updateTags.length - 1].update
}
}
updatedData[recipientId] = updateTags
}
@ -174,16 +173,30 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
});
try {
const responses = await Promise.all(requests);
const droppedItemsUpdateFalse = Object.fromEntries(
Object.entries(droppedItems).map(([recipientId, tags]) => {
return [recipientId, tags.map(tag => ({ ...tag, update: false }))];
})
)
const response = await Promise.all(requests);
console.log(droppedItemsUpdateFalse, "Line 182")
let recTags = droppedItems;
setDroppedItems(droppedItemsUpdateFalse);
for (const res of response) {
for (const tag of res.data) {
const key = `${tag.documentId}_${tag.pageIndex}`;
if (recTags[key]) {
recTags[key].push({ ...tag, update: false });
} else {
recTags[key] = [{ ...tag, update: false }]; // Initialize with an array
}
}
}
setSelectedTag(null);
// setTimeout(async () => {
// await fetchTags()
// setSelectedTag(null);
// }, 1000)
// console.log(droppedItemsUpdateFalse, "Line 182")
// setDroppedItems(droppedItemsUpdateFalse);
} catch (error) {
console.error("Error submitting recipient tags:", error);
}
@ -300,6 +313,11 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
await sendTagsToRecipients(tagsData, accountId, transactionId);
// if (save) {
// setSelectedTag(null);
// await fetchTags();
// }
// SENDING Transaction - START
if (!save) {
const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" })
@ -363,19 +381,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
const key = `${docId}_${pageIndex}`;
// const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />;
// Handling Initia Width and height of Some tags - START
if (dragElemData.label === TagsTypes.STAMP) {
dragElemData.width = 160
dragElemData.height = 160
} else if (dragElemData.label === TagsTypes.SIGNATURE) {
dragElemData.width = 200
dragElemData.height = 75
} else if (dragElemData.label === TagsTypes.INITIALS) {
dragElemData.width = 85
dragElemData.height = 75
}
// Handling Initia Width and height of Some tags - END
let prepData = {
properties: data.properties,
icon: typeToIconMap[field.label.toUpperCase()] || <FaQuestionCircle />,

View File

@ -4,7 +4,7 @@ import fields from '../../../helper/fieldsListData';
import { useParams } from 'react-router-dom';
import typeToIconMap from '../../../helper/iconMap';
import { FaQuestionCircle } from 'react-icons/fa';
import { TagsProperties } from '../../../utlity/types';
import { TagsProperties, TagsTypes } from '../../../utlity/types';
import { v4 as uuidv4 } from "uuid";
import axios from 'axios';
@ -13,12 +13,25 @@ const handlePointerDown = (event: React.DragEvent<HTMLDivElement>, label: string
let width = dragElemRect.width
let height = dragElemRect.height
if (label === "radio" || label === "checkbox") {
if (label === TagsTypes.RADIO || label === TagsTypes.CHECKBOX) {
const innerIconConRect = event.currentTarget.children[0].getBoundingClientRect();
width = innerIconConRect.width + 10
height = innerIconConRect.height + 10
}
// Handling Initia Width and height of Some tags - START
if (label === TagsTypes.STAMP) {
width = 160
height = 160
} else if (label === TagsTypes.SIGNATURE) {
width = 200
height = 75
} else if (label === TagsTypes.INITIALS) {
width = 85
height = 75
}
// Handling Initia Width and height of Some tags - END
const offsetX = event.clientX - dragElemRect.left;
const offsetY = event.clientY - dragElemRect.top;
@ -46,12 +59,12 @@ interface FieldListProps {
}
const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx, acntId = null }) => {
const [tagsData, setTagsData] = useState<TagsProperties[]>([]);
let { accountId } = useParams<{ accountId: string }>();
if(acntId) {
if (acntId) {
accountId = acntId;
}

View File

@ -24,8 +24,8 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
let transactionId = params.transactionId
if (!transactionId) {
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
transactionId = params.templateId;
dispatch(toggleToTemplates());
transactionId = params.templateId;
dispatch(toggleToTemplates());
}
if (!accountId || !transactionId) {
window.location.href = "/";
@ -80,7 +80,13 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
<div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: selectedRecipient.color }}></p>
{/* <FaUserTie className='w-3 h-3 text-2xl mt-[1px]' style={{ color: selectedRecipient.color }} /> */}
<p>{selectedRecipient.firstName} {selectedRecipient.lastName}</p>
<p>
{
selectedRecipient.firstName.length === 0 && selectedRecipient.lastName.length === 0 ?
(selectedRecipient.email.length === 0 ? selectedRecipient.roleName : selectedRecipient.email) :
`${selectedRecipient.firstName + " " + selectedRecipient.lastName}`
}
</p>
</div>
<MdKeyboardArrowDown />
</div>
@ -101,7 +107,13 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
<div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
{/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */}
<p>{recipient.firstName} {recipient.lastName} </p>
<p>
{
recipient.firstName.length === 0 && recipient.lastName.length === 0 ?
(recipient.email.length === 0 ? recipient.roleName : recipient.email) :
`${recipient.firstName + " " + recipient.lastName}`
}
</p>
</div>
{selectedRecipientIndx === indx && <IoCheckmark className='text-[var(--primary)]' />}
</div>)
@ -111,9 +123,9 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
{/* Side Bar showing Recipients - ENDS*/}
{/* Drag Drop Tags Start */}
<div
id='tags-con-dash-left'
className='mt-4'>
<div
id='tags-con-dash-left'
className='mt-4'>
<FieldList color={selectedRecipient?.color || '#2d2d2d'} recipientId={selectedRecipient.id} recipientIndx={selectedRecipientIndx} />
</div>

View File

@ -63,6 +63,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
return null;
}
// const [transactionSaving, setTransactionSaving] = useState<boolean>(false);
const [dotCount, setDotCount] = useState(0);
@ -125,11 +126,11 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
setTimeout(() => {
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
if (isSignSend) {
navigate(`./../../../sign/${selfUserSign.recipientId}`);
navigate(`./../../../sign/${selfUserSign.recipientId}`,{ replace: true });
}
else {
navigate(`/document-completion-screen/${accountId}/${transactionId}`, { replace: true })
}
// else {
// navigate(`/document-completion-screen/${accountId}/${transactionId}`)
// }
} else {
navigate(`/templates`);
toast.success("Template Saved...!", {
@ -311,7 +312,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>
<div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{recipient.firstName} {recipient.lastName} segfsdtzgszrdtgrs serdgvsrdgs dtrgv</p>
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{recipient.firstName} {recipient.lastName}</p>
</div>
</div>)
})}

View File

@ -9,6 +9,7 @@ import { RootState } from '../../../store/store';
import Loader from '../../dashboard/Loader';
import { hideLoader } from '../../../store/slices/loaderSlice';
import { useNavigate } from 'react-router-dom';
import api from '../../../config/api';
export interface IsSendInterface {
send: boolean | null;
@ -25,7 +26,7 @@ const ProcessStepTwo = () => {
const [isSend, setIsSend] = useState<boolean | null>(null);
const globalLoader = useSelector((state: RootState) => state.loader)
const dispatch = useDispatch();
const navigate = useNavigate();
const navigate = useNavigate();
useEffect(() => {
setTimeout(() => {
@ -33,6 +34,8 @@ const ProcessStepTwo = () => {
}, 800)
}, [])
return (
<div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}>

View File

@ -53,7 +53,13 @@ const RecipientsAttrCon: React.FC<RecipientsAttrConIntr> = ({ selectedRecipientI
<div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: selectedRecipient.color }}></p>
{/* <FaUserTie className='w-3 h-3 text-2xl mt-[1px]' style={{ color: selectedRecipient.color }} /> */}
<p>{"firstName" in selectedRecipient && selectedRecipient.firstName} {"lastName" in selectedRecipient && selectedRecipient.lastName}</p>
<p>
{
selectedRecipient.firstName.length === 0 && selectedRecipient.lastName.length === 0 ?
(selectedRecipient.email.length === 0 ? selectedRecipient.roleName : selectedRecipient.email) :
`${selectedRecipient.firstName + " " + selectedRecipient.lastName}`
}
</p>
</div>
<MdKeyboardArrowDown />
</div>
@ -65,7 +71,7 @@ const RecipientsAttrCon: React.FC<RecipientsAttrConIntr> = ({ selectedRecipientI
key={indx}
className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-pointer hover:bg-slate-100'
onClick={() => {
handlePropertyChange('recipientId', recipient.id);
handlePropertyChange('recipientId', recipient.id);
setTimeout(() => {
setDropDownToggle(false);
}, 0)
@ -74,7 +80,13 @@ const RecipientsAttrCon: React.FC<RecipientsAttrConIntr> = ({ selectedRecipientI
<div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
{/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */}
<p>{recipient.firstName} {recipient.lastName} </p>
<p>
{
recipient.firstName.length === 0 && recipient.lastName.length === 0 ?
(recipient.email.length === 0 ? recipient.roleName : recipient.email) :
`${recipient.firstName + " " + recipient.lastName}`
}
</p>
</div>
{selectedRecipientId === recipient.id && <IoCheckmark className='text-[var(--primary)]' />}
</div>)

View File

@ -26,7 +26,7 @@ import { FaSpinner } from "react-icons/fa";
import { GrCheckboxSelected } from "react-icons/gr";
import { Bounce, toast } from "react-toastify";
import axios from 'axios';
import { SaveType } from '../client/SignDocument';
import { SaveType, SaveTypeEnum } from '../client/SignDocument';
import { CiSaveDown1 } from 'react-icons/ci';
import api from '../../config/api';
@ -160,6 +160,21 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
})
if (res?.data) {
let preSavedSignatures = res?.data
if (res.data.length) {
const defaultVal = res.data.filter((obj, indx) => "default" in obj && obj.default);
const { initialsBlob, signatureBlob } = defaultVal[0];
if (initialsBlob && signatureBlob) {
setSaved({
signatureId: res.data.id,
saveType: SaveTypeEnum.DRAW,
save: true,
signature: signatureBlob,
initial: initialsBlob,
});
console.log("saved Called Line 174")
}
}
setPreSaveSignatures(preSavedSignatures)
}
})()
@ -279,6 +294,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
initials: "",
})
setSaved({
signatureId: res.data.id ?? null,
saveType: res.data.signatureType ?? null,
save: true,
signature: res.data.signatureBlob ?? null,
@ -633,7 +649,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
</>
) : (
<>
CREATE SIGNATURE
{activeTab === "SAVED" ? "CHANGE SIGNATURE" : "CREATE SIGNATURE"}
{/* isEditMode ? 'UPDATE SIGNATURE' : 'CREATE SIGNATURE' */}
</>
)}

View File

@ -90,6 +90,22 @@ const Templates = () => {
navigate(`/process-step-one/${accountId}/template/${templateId}`);
}
function formatTimestamp(isoString, options = { timeZone: 'local' }) {
// Parse the ISO string to a Date object
const date = new Date(isoString);
if (isNaN(date)) {
return 'Invalid date';
}
// Return formatted date string
if (options.timeZone === 'UTC') {
return date.toUTCString(); // e.g. "Mon, 04 Aug 2025 13:46:46 GMT"
} else {
return date.toLocaleString(); // e.g. "8/4/2025, 9:46:46 AM" (based on local time zone)
}
}
return (
<div className="p-3 text-sm">
{/* Header */}
@ -159,8 +175,8 @@ const Templates = () => {
</div>
</td>
<td className="px-4 py-2 text-sm text-center">{item.name || 'Untitled'}</td>
<td className="px-4 py-2 text-sm text-center">{formatDate(item.createdAt)}</td>
<td className="px-4 py-2 text-sm text-center">{formatDate(item.lastModifiedAt)}</td>
<td className="px-4 py-2 text-sm text-center">{formatTimestamp(item.createdAt)}</td>
<td className="px-4 py-2 text-sm text-center">{formatTimestamp(item.lastModifiedAt)}</td>
<td className="px-4 py-2 text-sm text-center" onClick={() => handleUseClick(item)}><span className="bg-[var(--lightGreyCol)] cursor-pointer border-gray-300 text-gray-600 hover:bg-gray-100 px-3 py-1 rounded-lg font-semibold text-[12px]">Use</span></td>
{/* Dropdown Trigger + Tooltip */}
<td className="px-4 py-2 text-center relative">
@ -173,7 +189,7 @@ const Templates = () => {
{showMenu === index && (
<div
className="absolute right-15 top-5 mt-2 w-32 bg-white border border-gray-200 rounded shadow-lg z-10 transition-opacity duration-300 ease-in-out"
className="absolute right-21 top-0 mt-2 w-20 bg-white border border-gray-200 rounded shadow-lg z-10 transition-opacity duration-300 ease-in-out"
tabIndex={0} // Make it focusable
onBlur={(e) => {
// Ensure it only closes if focus truly leaves the container
@ -183,7 +199,7 @@ const Templates = () => {
}}
>
<button
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
className="block w-full text-left px-4 py-1 text-sm text-gray-700 hover:bg-gray-100"
onClick={() => handleEditClick(item.id)}
>
Edit

View File

@ -9,6 +9,7 @@ import { IoCloseOutline } from 'react-icons/io5';
import { MdEditDocument } from "react-icons/md";
import { useSelector } from 'react-redux';
import { RootState } from '../../store/store';
import { SignTypeEnum } from '../../utlity/types';
// Sample recipient data — you can replace this with actual API data later
const recipients = [
@ -62,7 +63,7 @@ const TransactionView = () => {
const handleNavigateToSign = (id: number) => {
window.open(`./../../../../sign/${id}`)
window.open(`./../../../../sign/${id}/${SignTypeEnum.selfSign}`)
}
return (
@ -138,7 +139,7 @@ const TransactionView = () => {
{recipient.email === user.email && recipient.status !== "COMPLETED" &&
<p
onClick={() => handleNavigateToSign(recipient.id)}
className='text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold'>
className='text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold cursor-pointer'>
Sign
</p>}
</div>