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>

View File

@ -12,7 +12,7 @@ import { useDispatch, useSelector } from "react-redux";
import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
import { FaRegImage, FaFileAlt, FaSignature, FaStamp, FaCheck, FaQuestionCircle } from 'react-icons/fa'
import { IoIosDocument } from 'react-icons/io'
import { IoIosDocument, IoMdClose } from 'react-icons/io'
import { MdKeyboardArrowDown } from 'react-icons/md'
import { TbChevronsDownLeft, TbSignature } from "react-icons/tb";
import { BsFileArrowDownFill } from "react-icons/bs";
@ -25,6 +25,7 @@ import FieldList from "../process/processStepTwo/FieldList";
import typeToIconMap from "../../helper/iconMap";
import { droppedTagsDefaultField } from "../process/processStepTwo/DocumentContainer";
import SignCompletionScreen from "./SignCompletionScreen";
import api from "../../config/api";
@ -81,6 +82,7 @@ export enum SaveTypeEnum {
UPLOAD = "IMAGE",
}
export interface SaveType {
signatureId: string | null
saveType: SaveTypeEnum | null;
save: boolean;
signature: string | null;
@ -134,12 +136,22 @@ const fitObj = [
// fetchData - Fetch document pages and tags data
// handleFormSubmit - handle form submit and send data to server
const SignDocument = () => {
useEffect(() => {
axios.get("https://concrete-gladly-mullet.ngrok-free.app/api/sign/session?recipientId=103&sessionId=iqlo2inofsqaqib8mcos4njssj").then((res) => {
console.log(res, "RESPONSE")
})
}, [])
const { recipientId } = useParams();
const dispatch = useDispatch();
// Signer data - START
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
const [recipient, setRecipient] = useState<RecipientType | null>(null);
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
const [transaction, setTransaction] = useState<TransactionType | null>(null);
@ -149,7 +161,7 @@ const SignDocument = () => {
const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" });
console.log(recipient, "recipient Line 145")
console.log(recSignStatus, "recSignStatus")
// Handling Date Opacity
const [dateValues, setDateValues] = useState<{ [key: string]: string }>({});
@ -177,6 +189,7 @@ const SignDocument = () => {
const [showCompletionScreen, setShowCompletionScreen] = useState(false)
const [saved, setSaved] = useState<SaveType>({
signatureId: null,
saveType: null,
save: false,
signature: null,
@ -188,7 +201,6 @@ const SignDocument = () => {
const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({});
console.log("signConfObj", signConfObj)
const zoom = (percentage: number, contentClass: string) => {
const content = document.querySelector(`.${contentClass}`);
@ -197,6 +209,10 @@ const SignDocument = () => {
}
}
const handleTagClick = (key: string, index: number) => {
setSelectedTag({ key, index })
}
useEffect(() => {
let indx = fitObj[selFitIndx].value
if (indx === "FIT") {
@ -218,15 +234,12 @@ const SignDocument = () => {
}
}, [droppedItems])
console.log(saved, "Line 162")
useEffect(() => {
if (recSignStatus === "COMPLETED") {
console.log("SETTING HIDE TAGS to TRUE LIne 161")
// setHideTags(true);
setTransparentTags(true);
} else {
console.log("SETTING HIDE TAGS to TRUE LIne 161")
setHideTags(false);
}
}, [recSignStatus])
@ -249,8 +262,9 @@ const SignDocument = () => {
}
}, [saved])
const handleSignClick = (index: number, id: number) => {
console.log("IDNumber", id)
setSignTagId(id)
if (saved.save) {
if (index in signConfObj) {
@ -281,7 +295,6 @@ const SignDocument = () => {
const handleStampClick = (index: number) => {
console.log("Stamp Clicked", index)
setStamp({
open: true,
blob: ""
@ -295,6 +308,35 @@ const SignDocument = () => {
}
}
// Step 2: Prevent browser back when status === 'COMPLETED'
// useEffect(() => {
// if (recSignStatus !== "COMPLETED") return;
// 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);
// };
// }, [recSignStatus]);
const fetchData = (isSign: boolean = false) => {
axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`,
{
@ -302,22 +344,20 @@ const SignDocument = () => {
{ "ngrok-skip-browser-warning": "69420", }
})
.then((res) => {
console.dir(res)
if (res.status === 200) {
console.log(res, "Line 288")
setSelectedDocuments(res.data.documents)
setRecipient(res.data.details)
setDroppedItems(res.data.tagJsons)
setTransaction(res.data.transaction)
setRecSignStatus(res.data.status)
setSelectedDocuments(res?.data?.documents)
setRecipient(res?.data?.details)
setDroppedItems(res?.data?.tagJsons)
setTransaction(res?.data?.transaction)
setRecSignStatus(res?.data?.status)
if ("provideOwnTags" in res.data.details && res.data.details.provideOwnTags) {
if ("provideOwnTags" in res?.data?.details && res?.data?.details.provideOwnTags) {
setProvideTags(true)
}
if (res.data.status ==="COMPLETED" ) {
if (res?.data?.status === "COMPLETED") {
setShowCompletionScreen(true)
}
@ -329,12 +369,11 @@ const SignDocument = () => {
})
.catch((err) => {
console.error(err.message)
console.log(err)
});
}
console.log(droppedItems, "Line 261")
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@ -360,7 +399,6 @@ const SignDocument = () => {
}
}
console.log(signInitialsCount, Object.keys(signConfObj).length, "boom")
if (signInitialsCount !== Object.keys(signConfObj).length) {
toast.error("Please ensure all signature and initials are signed", {
@ -378,7 +416,6 @@ const SignDocument = () => {
}
for (const tag of droppedItems) {
console.log(tag, "Tag Data")
// if ((tag.type === "SIGNATURE" || tag.subType === "INITIALS") && !tag.value) {
if (("required" in tag && tag.required) && ((tag.type !== "SIGNATURE" && tag.type !== "INITIALS") && (tag.value === "" || !("value" in tag)))) {
@ -454,9 +491,6 @@ const SignDocument = () => {
dispatch(showLoader("Signing ..."));
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`, submitData, { headers: { "ngrok-skip-browser-warning": "69420", } })
.then((res) => {
console.group("Submitting Sign Details");
console.dir(res);
console.groupEnd();
if (res.status === 200) {
setTransparentTags(true)
setTimeout(() => {
@ -480,7 +514,6 @@ const SignDocument = () => {
}
})
.catch((err) => {
console.error(err.message)
}
)
};
@ -532,11 +565,9 @@ const SignDocument = () => {
headers: { "ngrok-skip-browser-warning": "69420" }
});
if (response.status === 200) {
console.log("line 516", response.data)
return response.data;
}
} catch (error) {
console.error("Error fetching signature data:", error);
}
return null;
}
@ -546,9 +577,7 @@ const SignDocument = () => {
// if (droppedItems) {
// for (const tag of droppedItems) {
// if (tag.type === "SIGNATURE" && "value" in tag) {
// console.log(tag, "line 533")
// const signatureData = await fetchSignatureData(tag["value"]);
// console.log("Currnet Signature",signatureData);
// }
// }
// }
@ -582,7 +611,6 @@ const SignDocument = () => {
// HANDLE DROP - START
const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number) => {
console.log("// HANDLE DROP - START")
event.preventDefault();
const data = window.dragElemData;
@ -690,6 +718,17 @@ const SignDocument = () => {
// setDroppedItems(dataToSet);
// }
if (prepData.type.toLowerCase() === TagsTypes.SIGNATURE || prepData.type.toLowerCase() === TagsTypes.INITIALS) {
const currDocTags = droppedItems.filter((val) => val.pageIndex === prepData.pageIndex)
setSignConfObj({
...signConfObj,
[currDocTags.length]: {
accepted: true,
index: currDocTags.length
}
})
}
setDroppedItems((prev) => [...prev, prepData]);
// setDroppedItemsCounter((prev) => prev + 1);
@ -706,6 +745,8 @@ const SignDocument = () => {
// HANDLE TAG MOUSE DOWN - START
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
setSelectedTag({ key, index })
event.preventDefault();
let targetContainer = event?.currentTarget?.parentElement?.parentElement;
@ -744,7 +785,6 @@ const SignDocument = () => {
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height));
console.log(left, top, "LEFTTOP")
if (groupElements != null && groupElements.length) handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
@ -769,7 +809,98 @@ const SignDocument = () => {
// HANDLE TAG MOUSE DOWN - END
console.log(droppedItems, "DROPPED ITEMS LINE 680")
// Updated handleRemoveTag function with your existing API logic and group handling
const handleRemoveTag = (key: string, targetIndex: number) => {
const tag = droppedItems[key][targetIndex];
const recipientId = tag.recipientId;
console.log(tag, "line 776");
(async () => {
if ("id" in tag) {
try {
const response = await api.delete(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/recipients/${recipientId}/tags/${tag.id}`)
if (response.status !== 200) throw "Tag Deletion error"
} catch (err) {
return;
}
}
setSelectedTag(null);
// Check if this item has a group for special group handling
if ('group' in tag && tag.group) {
const groupId = tag.group;
// Find all items in the same group
const groupItems = droppedItems[key]
.map((item, idx) => ({ item, index: idx }))
.filter(({ item }) => 'group' in item && item.group === groupId && !item.isDeleted);
// Sort group items by their top position to identify the parent (top-most)
groupItems.sort((a, b) => a.item.dimensions.top - b.item.dimensions.top);
// Check if the item being removed is the parent (top-most item)
const isParentItem = groupItems.length > 0 && groupItems[0].index === targetIndex;
if (isParentItem && groupItems.length > 1) {
// If removing the parent and there are child items, mark all items in the group as deleted
// Get indices of all group items to mark as deleted
const indicesToDelete = groupItems.map(({ index }) => index);
setDroppedItems(prevState => ({
...prevState,
[key]: prevState[key].map((item, index) =>
indicesToDelete.includes(index) ? { ...item, isDeleted: true } : item
)
}));
// Remove group border elements after state update
// setTimeout(() => {
// removeGroupBorders(groupId);
// }, 100);
} else {
// If removing a child item or the only item in group, just mark this item as deleted
setDroppedItems(prevState => ({
...prevState,
[key]: prevState[key].map((item, index) =>
index === targetIndex ? { ...item, isDeleted: true } : item
)
}));
// Check if this was the last item in the group after state update
// setTimeout(() => {
// const remainingGroupItems = droppedItems[key].filter(item =>
// 'group' in item && item.group === groupId && !item.isDeleted
// );
// if (remainingGroupItems.length === 0) {
// // Remove group borders if no items left
// removeGroupBorders(groupId);
// } else {
// // Update group borders for remaining items
// handleIGroupBorderPosition(groupId);
// }
// }, 100);
}
} else {
// Handle non-group item removal (your existing logic)
setDroppedItems(prevState => ({
...prevState,
[key]: prevState[key].map((item, index) =>
index === targetIndex ? { ...item, isDeleted: true } : item
)
}));
}
})()
};
// const [showTerms, setShowTerms] = useState<boolean>(true);
return (
@ -905,7 +1036,6 @@ const SignDocument = () => {
{
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
console.log(droppedItems, "tagJsontagJsontagJson")
return (
<div key={doc.documentId} className='flex flex-col'>
<div className='py-2 text-sm'>
@ -916,12 +1046,9 @@ const SignDocument = () => {
{
doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
const key = `${doc.documentId}_${page.index}`;
console.log(droppedItems, "line 820")
// const tagJsonData = [...droppedItems];
// console.log(tagJsonData, "Line 821")
const currDocTags = droppedItems.filter((val) => val.pageIndex === page.index)
console.log(currDocTags, droppedItems, page, doc, "innnnnnnnnnnnn")
return (
<div
key={indx}
@ -935,9 +1062,10 @@ const SignDocument = () => {
{currDocTags.map((item, index) => {
console.log(item, "micCheck 842")
if (item.pageIndex !== page.index) return;
if (item.pageIndex !== page.index || item.documentId != doc.documentId) return <></>;
return (
<>
<div
tabIndex={1}
onFocus={() => setFocusElement({ ...item, focusElementIndx: index })}
@ -956,8 +1084,31 @@ const SignDocument = () => {
}}
>
<>
{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": "#2d2d2d" }}
>
</p>
{/* <p
className=''>{IconComponent}
</p> */}
<p className='capitalize text-ellipsis whitespace-nowrap'>{item.label}</p>
<IoMdClose onClick={(e) => {
console.log("Handle Remove Tag Called");
e.stopPropagation();
handleRemoveTag(key, index)
}} className='cursor-pointer' />
</div>
</div>}
</>
{/* TEXT FIELD - START */}
{item.type === "TEXT" &&
{
item.type === "TEXT" &&
<div
id={item.id}
tabIndex={1}
@ -1007,14 +1158,11 @@ const SignDocument = () => {
if (e.key === "Enter") e.preventDefault();
}}
onFocus={() => {
console.log("focus")
}}
onBlur={(e) => {
let val = e.target.textContent;
console.log(item.mask, val, "Line 586")
if (typeof item.mask?.maskType === "string" && typeof val === "string") {
val = val.replace(/ /g, "");
console.log("onBlur", item.mask.maskCharacter, maskUtils((item.mask as { maskType: string }).maskType, val, (item.mask as { maskCharacter: string }).maskCharacter))
e.target.textContent = maskUtils((item.mask as { maskType: string }).maskType, val, (item.mask as { maskCharacter: string }).maskCharacter)
}
}}
@ -1022,12 +1170,14 @@ const SignDocument = () => {
className="outline-none whitespace-nowrap w-full max-h-max"
></p>
}
</div>}
</div>
}
{/* TEXT FIELD - END */}
{/* CHECKBOX FIELD - START */}
{item.type === "CHECKBOX" &&
{
item.type === "CHECKBOX" &&
<div
id={item.id}
className={`h-full w-full rounded flex justify-center items-center ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
@ -1038,12 +1188,14 @@ const SignDocument = () => {
// style={{ height: item.dimensions.height + "px" }}
className="w-full px-2 outline-none"
type="checkbox" />
</div>}
</div>
}
{/* CHECKBOX FIELD - END */}
{/* SIGNATURE FIELD - START */}
{item.type === "SIGNATURE" &&
{
item.type === "SIGNATURE" &&
<div
tabIndex={1}
id={item.id}
@ -1067,12 +1219,14 @@ const SignDocument = () => {
src={`data:image/png;base64,${saved.signature}`}
/>
}
</div>}
</div>
}
{/* SIGNATURE FIELD - END */}
{/* INITIALS FIELD - START */}
{item.type === "INITIALS" &&
{
item.type === "INITIALS" &&
<div
tabIndex={1}
id={item.id}
@ -1093,12 +1247,14 @@ const SignDocument = () => {
src={`data:image/png;base64,${saved.initial}`}
/>
}
</div>}
</div>
}
{/* INITIALS FIELD - END */}
{/* STAMP FIELD - START */}
{item.type === "STAMP" &&
{
item.type === "STAMP" &&
<div
tabIndex={1}
id={item.id}
@ -1111,11 +1267,13 @@ const SignDocument = () => {
:
<img className="w-[100%] h-full" src={saved.initial as string} />
}
</div>}
</div>
}
{/* STAMP FIELD - END */}
{/* IMAGE FIELD - START */}
{item.type === "FILE" &&
{
item.type === "FILE" &&
<div
tabIndex={1}
id={item.id}
@ -1128,12 +1286,14 @@ const SignDocument = () => {
:
<img className="w-[100%] h-full" src={saved.initial as string} />
}
</div>}
</div>
}
{/* IMAGE FIELD - END */}
{/* SELECT FIELD - START */}
{item.type === "SELECT" &&
{
item.type === "SELECT" &&
<div
id={item.id}
tabIndex={1}
@ -1156,7 +1316,8 @@ const SignDocument = () => {
})
}
</select>
</div>}
</div>
}
{/* SELECT FIELD - END */}
@ -1164,7 +1325,8 @@ const SignDocument = () => {
{/* TEMPORAL FIELD - START */}
{item.type === "TEMPORAL" &&
{
item.type === "TEMPORAL" &&
<div
id={item.id}
tabIndex={1}
@ -1202,13 +1364,15 @@ const SignDocument = () => {
type={`${item.subType?.toLowerCase() === "date" ? "date" : (item.subType?.toLowerCase() === "time" ? "time" : "datetime-local")}`}
/>
</div>}
</div>
}
{/* TEMPORAL FIELD - END */}
{/* Attachment Field - START */}
{item.type.toLowerCase() === TagsTypes.ATTACHMENT &&
{
item.type.toLowerCase() === TagsTypes.ATTACHMENT &&
<div
tabIndex={1}
id={item.id}
@ -1229,13 +1393,15 @@ const SignDocument = () => {
</p>
)
}
</div>}
</div>
}
{/* Attachment Field - END */}
{/* Focus Show Element */}
{(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) &&
{
(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) &&
<>
<div
className="absolute left-0 z-50 bg-blue-900 text-xs text-slate-100 px-2 py-[2px] rounded-sm whitespace-nowrap"
@ -1254,6 +1420,7 @@ const SignDocument = () => {
</>
}
</div>
</>
)
})
}
@ -1270,7 +1437,7 @@ const SignDocument = () => {
{/* 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.pages && doc.pages.length}
</p>
{/* Side details of documents Pages and document Name - END */}
@ -1346,7 +1513,7 @@ const SignDocument = () => {
<InitialCon openInitial={openInitial} setOpenInitial={setOpenInitial} savedInitial={savedInitial} setSavedInitial={setSavedInitial} /> */}
{globalLoader.isLoading && <Loader />}
</div>
</div >
)
}

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",
@ -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,7 +167,6 @@ 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 || ""}`)}
@ -214,7 +224,8 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
)}
</div>
))}
)
)}
</div>
</>
) : (

View File

@ -77,9 +77,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
}, [])
// FETCHING TAG - START
useEffect(() => {
if (selectedRecipients.length === 0) return;
const fetchTags = async () => {
try {
@ -92,9 +89,9 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
for (const tag of res.data) {
const key = `${tag.documentId}_${tag.pageIndex}`;
if (recTags[key]) {
recTags[key].push(tag);
recTags[key].push({ ...tag, update: false });
} else {
recTags[key] = [tag]; // Initialize with an array
recTags[key] = [{ ...tag, update: false }]; // Initialize with an array
}
}
}
@ -106,6 +103,9 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
}
};
// FETCHING TAG - START
useEffect(() => {
if (selectedRecipients.length === 0) return;
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;
@ -51,7 +64,7 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
let { accountId } = useParams<{ accountId: string }>();
if(acntId) {
if (acntId) {
accountId = acntId;
}

View File

@ -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>)

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;
@ -34,6 +35,8 @@ const ProcessStepTwo = () => {
}, [])
return (
<div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}>
<NavTwo transactionSaving={transactionSaving} isFields={isFields} setIsFields={setIsFields} isSend={isSend} setIsSend={setIsSend} />

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>
@ -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>