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 DocumentCompletionScreen from "./components/process/CompletionScreen/DocumentCompletionScreen";
import JoyrideController from "./utlity/JoyRideTour"; import JoyrideController from "./utlity/JoyRideTour";
import Contacts from "./components/contacts/Contacts"; import Contacts from "./components/contacts/Contacts";
const App = () => { const App = () => {
return ( return (

View File

@ -17,6 +17,32 @@ const SignCompletionScreen: React.FC = () => {
const [showDownloadModal, setShowDownloadModal] = useState(false); const [showDownloadModal, setShowDownloadModal] = useState(false);
const [animationPhase, setAnimationPhase] = useState(0); 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(() => { useEffect(() => {
// Trigger animation phases sequentially // Trigger animation phases sequentially
const timers = [ const timers = [
@ -301,7 +327,7 @@ const SignCompletionScreen: React.FC = () => {
> >
{/* <a> */} {/* <a> */}
<IoMdArrowRoundBack className="w-5 h-5 mr-3" /> <IoMdArrowRoundBack className="w-5 h-5 mr-3" />
Back Sign Made Easy Back to Sign Made Easy
{/* </a> */} {/* </a> */}
</a> : </a> :
<a <a
@ -321,7 +347,7 @@ const SignCompletionScreen: React.FC = () => {
{/* Additional Info */} {/* Additional Info */}
<div className={`mt-8 pt-3 border-t border-gray-200 border-opacity-50`}> <div className={`mt-8 pt-3 border-t border-gray-200 border-opacity-50`}>
<p className="text-sm text-gray-500"> <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> </p>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@ import { useDispatch, useSelector } from "react-redux";
import { hideLoader, showLoader } from "../../store/slices/loaderSlice"; import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
import { FaRegImage, FaFileAlt, FaSignature, FaStamp, FaCheck, FaQuestionCircle } from 'react-icons/fa' 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 { MdKeyboardArrowDown } from 'react-icons/md'
import { TbChevronsDownLeft, TbSignature } from "react-icons/tb"; import { TbChevronsDownLeft, TbSignature } from "react-icons/tb";
import { BsFileArrowDownFill } from "react-icons/bs"; import { BsFileArrowDownFill } from "react-icons/bs";
@ -25,6 +25,7 @@ import FieldList from "../process/processStepTwo/FieldList";
import typeToIconMap from "../../helper/iconMap"; import typeToIconMap from "../../helper/iconMap";
import { droppedTagsDefaultField } from "../process/processStepTwo/DocumentContainer"; import { droppedTagsDefaultField } from "../process/processStepTwo/DocumentContainer";
import SignCompletionScreen from "./SignCompletionScreen"; import SignCompletionScreen from "./SignCompletionScreen";
import api from "../../config/api";
@ -81,6 +82,7 @@ export enum SaveTypeEnum {
UPLOAD = "IMAGE", UPLOAD = "IMAGE",
} }
export interface SaveType { export interface SaveType {
signatureId: string | null
saveType: SaveTypeEnum | null; saveType: SaveTypeEnum | null;
save: boolean; save: boolean;
signature: string | null; signature: string | null;
@ -134,12 +136,22 @@ const fitObj = [
// fetchData - Fetch document pages 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 = () => {
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 { recipientId } = useParams();
const dispatch = useDispatch(); const dispatch = useDispatch();
// Signer data - START // Signer data - START
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null); 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 [recipient, setRecipient] = useState<RecipientType | null>(null);
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]); const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
const [transaction, setTransaction] = useState<TransactionType | null>(null); const [transaction, setTransaction] = useState<TransactionType | null>(null);
@ -149,7 +161,7 @@ const SignDocument = () => {
const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" }); const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" });
console.log(recipient, "recipient Line 145") console.log(recSignStatus, "recSignStatus")
// Handling Date Opacity // Handling Date Opacity
const [dateValues, setDateValues] = useState<{ [key: string]: string }>({}); const [dateValues, setDateValues] = useState<{ [key: string]: string }>({});
@ -177,6 +189,7 @@ const SignDocument = () => {
const [showCompletionScreen, setShowCompletionScreen] = useState(false) const [showCompletionScreen, setShowCompletionScreen] = useState(false)
const [saved, setSaved] = useState<SaveType>({ const [saved, setSaved] = useState<SaveType>({
signatureId: null,
saveType: null, saveType: null,
save: false, save: false,
signature: null, signature: null,
@ -188,7 +201,6 @@ const SignDocument = () => {
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}`);
@ -197,6 +209,10 @@ const SignDocument = () => {
} }
} }
const handleTagClick = (key: string, index: number) => {
setSelectedTag({ key, index })
}
useEffect(() => { useEffect(() => {
let indx = fitObj[selFitIndx].value let indx = fitObj[selFitIndx].value
if (indx === "FIT") { if (indx === "FIT") {
@ -218,15 +234,12 @@ const SignDocument = () => {
} }
}, [droppedItems]) }, [droppedItems])
console.log(saved, "Line 162")
useEffect(() => { useEffect(() => {
if (recSignStatus === "COMPLETED") { if (recSignStatus === "COMPLETED") {
console.log("SETTING HIDE TAGS to TRUE LIne 161")
// setHideTags(true); // setHideTags(true);
setTransparentTags(true); setTransparentTags(true);
} else { } else {
console.log("SETTING HIDE TAGS to TRUE LIne 161")
setHideTags(false); setHideTags(false);
} }
}, [recSignStatus]) }, [recSignStatus])
@ -249,8 +262,9 @@ const SignDocument = () => {
} }
}, [saved]) }, [saved])
const handleSignClick = (index: number, id: number) => { const handleSignClick = (index: number, id: number) => {
console.log("IDNumber", id)
setSignTagId(id) setSignTagId(id)
if (saved.save) { if (saved.save) {
if (index in signConfObj) { if (index in signConfObj) {
@ -281,7 +295,6 @@ const SignDocument = () => {
const handleStampClick = (index: number) => { const handleStampClick = (index: number) => {
console.log("Stamp Clicked", index)
setStamp({ setStamp({
open: true, open: true,
blob: "" 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) => { const fetchData = (isSign: boolean = false) => {
axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`, axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`,
{ {
@ -302,22 +344,20 @@ const SignDocument = () => {
{ "ngrok-skip-browser-warning": "69420", } { "ngrok-skip-browser-warning": "69420", }
}) })
.then((res) => { .then((res) => {
console.dir(res)
if (res.status === 200) { if (res.status === 200) {
console.log(res, "Line 288")
setSelectedDocuments(res.data.documents) setSelectedDocuments(res?.data?.documents)
setRecipient(res.data.details) setRecipient(res?.data?.details)
setDroppedItems(res.data.tagJsons) setDroppedItems(res?.data?.tagJsons)
setTransaction(res.data.transaction) setTransaction(res?.data?.transaction)
setRecSignStatus(res.data.status) 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) setProvideTags(true)
} }
if (res.data.status ==="COMPLETED" ) { if (res?.data?.status === "COMPLETED") {
setShowCompletionScreen(true) setShowCompletionScreen(true)
} }
@ -329,12 +369,11 @@ const SignDocument = () => {
}) })
.catch((err) => { .catch((err) => {
console.error(err.message) console.log(err)
}); });
} }
console.log(droppedItems, "Line 261")
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
@ -360,7 +399,6 @@ const SignDocument = () => {
} }
} }
console.log(signInitialsCount, Object.keys(signConfObj).length, "boom")
if (signInitialsCount !== Object.keys(signConfObj).length) { if (signInitialsCount !== Object.keys(signConfObj).length) {
toast.error("Please ensure all signature and initials are signed", { toast.error("Please ensure all signature and initials are signed", {
@ -378,7 +416,6 @@ const SignDocument = () => {
} }
for (const tag of droppedItems) { for (const tag of droppedItems) {
console.log(tag, "Tag Data")
// if ((tag.type === "SIGNATURE" || tag.subType === "INITIALS") && !tag.value) { // 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)))) { 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 ...")); dispatch(showLoader("Signing ..."));
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`, submitData, { headers: { "ngrok-skip-browser-warning": "69420", } }) axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`, submitData, { headers: { "ngrok-skip-browser-warning": "69420", } })
.then((res) => { .then((res) => {
console.group("Submitting Sign Details");
console.dir(res);
console.groupEnd();
if (res.status === 200) { if (res.status === 200) {
setTransparentTags(true) setTransparentTags(true)
setTimeout(() => { setTimeout(() => {
@ -480,7 +514,6 @@ const SignDocument = () => {
} }
}) })
.catch((err) => { .catch((err) => {
console.error(err.message)
} }
) )
}; };
@ -532,11 +565,9 @@ const SignDocument = () => {
headers: { "ngrok-skip-browser-warning": "69420" } headers: { "ngrok-skip-browser-warning": "69420" }
}); });
if (response.status === 200) { if (response.status === 200) {
console.log("line 516", response.data)
return response.data; return response.data;
} }
} catch (error) { } catch (error) {
console.error("Error fetching signature data:", error);
} }
return null; return null;
} }
@ -546,9 +577,7 @@ const SignDocument = () => {
// 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) {
// console.log(tag, "line 533")
// const signatureData = await fetchSignatureData(tag["value"]); // const signatureData = await fetchSignatureData(tag["value"]);
// console.log("Currnet Signature",signatureData);
// } // }
// } // }
// } // }
@ -582,7 +611,6 @@ const SignDocument = () => {
// HANDLE DROP - START // HANDLE DROP - START
const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number) => { const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number) => {
console.log("// HANDLE DROP - START")
event.preventDefault(); event.preventDefault();
const data = window.dragElemData; const data = window.dragElemData;
@ -690,6 +718,17 @@ const SignDocument = () => {
// setDroppedItems(dataToSet); // 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]); setDroppedItems((prev) => [...prev, prepData]);
// setDroppedItemsCounter((prev) => prev + 1); // setDroppedItemsCounter((prev) => prev + 1);
@ -706,6 +745,8 @@ const SignDocument = () => {
// HANDLE TAG MOUSE DOWN - START // HANDLE TAG MOUSE DOWN - START
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => { const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
setSelectedTag({ key, index })
event.preventDefault(); event.preventDefault();
let targetContainer = event?.currentTarget?.parentElement?.parentElement; let targetContainer = event?.currentTarget?.parentElement?.parentElement;
@ -744,7 +785,6 @@ const SignDocument = () => {
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width)); left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height)); 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); if (groupElements != null && groupElements.length) handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
@ -769,7 +809,98 @@ const SignDocument = () => {
// HANDLE TAG MOUSE DOWN - END // 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); // const [showTerms, setShowTerms] = useState<boolean>(true);
return ( return (
@ -905,7 +1036,6 @@ const SignDocument = () => {
{ {
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => { selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
console.log(droppedItems, "tagJsontagJsontagJson")
return ( return (
<div key={doc.documentId} className='flex flex-col'> <div key={doc.documentId} className='flex flex-col'>
<div className='py-2 text-sm'> <div className='py-2 text-sm'>
@ -916,12 +1046,9 @@ const SignDocument = () => {
{ {
doc.pages && doc.pages.length > 0 && doc.pages.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")
// const tagJsonData = [...droppedItems]; // const tagJsonData = [...droppedItems];
// console.log(tagJsonData, "Line 821")
const currDocTags = droppedItems.filter((val) => val.pageIndex === page.index) const currDocTags = droppedItems.filter((val) => val.pageIndex === page.index)
console.log(currDocTags, droppedItems, page, doc, "innnnnnnnnnnnn")
return ( return (
<div <div
key={indx} key={indx}
@ -935,9 +1062,10 @@ const SignDocument = () => {
{currDocTags.map((item, index) => { {currDocTags.map((item, index) => {
console.log(item, "micCheck 842") if (item.pageIndex !== page.index || item.documentId != doc.documentId) return <></>;
if (item.pageIndex !== page.index) return;
return ( return (
<>
<div <div
tabIndex={1} tabIndex={1}
onFocus={() => setFocusElement({ ...item, focusElementIndx: index })} 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 */} {/* TEXT FIELD - START */}
{item.type === "TEXT" && {
item.type === "TEXT" &&
<div <div
id={item.id} id={item.id}
tabIndex={1} tabIndex={1}
@ -1007,14 +1158,11 @@ const SignDocument = () => {
if (e.key === "Enter") e.preventDefault(); if (e.key === "Enter") e.preventDefault();
}} }}
onFocus={() => { onFocus={() => {
console.log("focus")
}} }}
onBlur={(e) => { onBlur={(e) => {
let val = e.target.textContent; let val = e.target.textContent;
console.log(item.mask, val, "Line 586")
if (typeof item.mask?.maskType === "string" && typeof val === "string") { if (typeof item.mask?.maskType === "string" && typeof val === "string") {
val = val.replace(/ /g, ""); 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) 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" className="outline-none whitespace-nowrap w-full max-h-max"
></p> ></p>
} }
</div>} </div>
}
{/* TEXT FIELD - END */} {/* TEXT FIELD - END */}
{/* CHECKBOX FIELD - START */} {/* CHECKBOX FIELD - START */}
{item.type === "CHECKBOX" && {
item.type === "CHECKBOX" &&
<div <div
id={item.id} 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"}`} 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" }} // style={{ height: item.dimensions.height + "px" }}
className="w-full px-2 outline-none" className="w-full px-2 outline-none"
type="checkbox" /> type="checkbox" />
</div>} </div>
}
{/* CHECKBOX FIELD - END */} {/* CHECKBOX FIELD - END */}
{/* SIGNATURE FIELD - START */} {/* SIGNATURE FIELD - START */}
{item.type === "SIGNATURE" && {
item.type === "SIGNATURE" &&
<div <div
tabIndex={1} tabIndex={1}
id={item.id} id={item.id}
@ -1067,12 +1219,14 @@ const SignDocument = () => {
src={`data:image/png;base64,${saved.signature}`} src={`data:image/png;base64,${saved.signature}`}
/> />
} }
</div>} </div>
}
{/* SIGNATURE FIELD - END */} {/* SIGNATURE FIELD - END */}
{/* INITIALS FIELD - START */} {/* INITIALS FIELD - START */}
{item.type === "INITIALS" && {
item.type === "INITIALS" &&
<div <div
tabIndex={1} tabIndex={1}
id={item.id} id={item.id}
@ -1093,12 +1247,14 @@ const SignDocument = () => {
src={`data:image/png;base64,${saved.initial}`} src={`data:image/png;base64,${saved.initial}`}
/> />
} }
</div>} </div>
}
{/* INITIALS FIELD - END */} {/* INITIALS FIELD - END */}
{/* STAMP FIELD - START */} {/* STAMP FIELD - START */}
{item.type === "STAMP" && {
item.type === "STAMP" &&
<div <div
tabIndex={1} tabIndex={1}
id={item.id} id={item.id}
@ -1111,11 +1267,13 @@ const SignDocument = () => {
: :
<img className="w-[100%] h-full" src={saved.initial as string} /> <img className="w-[100%] h-full" src={saved.initial as string} />
} }
</div>} </div>
}
{/* STAMP FIELD - END */} {/* STAMP FIELD - END */}
{/* IMAGE FIELD - START */} {/* IMAGE FIELD - START */}
{item.type === "FILE" && {
item.type === "FILE" &&
<div <div
tabIndex={1} tabIndex={1}
id={item.id} id={item.id}
@ -1128,12 +1286,14 @@ const SignDocument = () => {
: :
<img className="w-[100%] h-full" src={saved.initial as string} /> <img className="w-[100%] h-full" src={saved.initial as string} />
} }
</div>} </div>
}
{/* IMAGE FIELD - END */} {/* IMAGE FIELD - END */}
{/* SELECT FIELD - START */} {/* SELECT FIELD - START */}
{item.type === "SELECT" && {
item.type === "SELECT" &&
<div <div
id={item.id} id={item.id}
tabIndex={1} tabIndex={1}
@ -1156,7 +1316,8 @@ const SignDocument = () => {
}) })
} }
</select> </select>
</div>} </div>
}
{/* SELECT FIELD - END */} {/* SELECT FIELD - END */}
@ -1164,7 +1325,8 @@ const SignDocument = () => {
{/* TEMPORAL FIELD - START */} {/* TEMPORAL FIELD - START */}
{item.type === "TEMPORAL" && {
item.type === "TEMPORAL" &&
<div <div
id={item.id} id={item.id}
tabIndex={1} tabIndex={1}
@ -1202,13 +1364,15 @@ const SignDocument = () => {
type={`${item.subType?.toLowerCase() === "date" ? "date" : (item.subType?.toLowerCase() === "time" ? "time" : "datetime-local")}`} type={`${item.subType?.toLowerCase() === "date" ? "date" : (item.subType?.toLowerCase() === "time" ? "time" : "datetime-local")}`}
/> />
</div>} </div>
}
{/* TEMPORAL FIELD - END */} {/* TEMPORAL FIELD - END */}
{/* Attachment Field - START */} {/* Attachment Field - START */}
{item.type.toLowerCase() === TagsTypes.ATTACHMENT && {
item.type.toLowerCase() === TagsTypes.ATTACHMENT &&
<div <div
tabIndex={1} tabIndex={1}
id={item.id} id={item.id}
@ -1229,13 +1393,15 @@ const SignDocument = () => {
</p> </p>
) )
} }
</div>} </div>
}
{/* Attachment Field - END */} {/* Attachment Field - END */}
{/* Focus Show Element */} {/* Focus Show Element */}
{(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) && {
(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) &&
<> <>
<div <div
className="absolute left-0 z-50 bg-blue-900 text-xs text-slate-100 px-2 py-[2px] rounded-sm whitespace-nowrap" 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> </div>
</>
) )
}) })
} }

View File

@ -162,7 +162,7 @@ const Contacts = () => {
</td> </td>
</tr> </tr>
) : ( ) : (
contacts.map((contact) => ( contacts.map((contact, index) => (
<tr key={contact.id} className="hover:bg-gray-50"> <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.firstName}</td>
<td className="px-4 py-3">{contact.lastName}</td> <td className="px-4 py-3">{contact.lastName}</td>
@ -181,7 +181,7 @@ const Contacts = () => {
</button> </button>
{openMenuId === contact.id && ( {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 <button
onMouseDown={() => handleEdit(contact.id)} onMouseDown={() => handleEdit(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600" 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 <div
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
id='start-now-upload-con' 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 */} {/* Upload icon with subtle animation */}
<div className="p-2 text-blue-600 group-hover:text-blue-700 transition-colors"> <div className="p-2 text-blue-600 group-hover:text-blue-700 transition-colors">
<FiUpload className="text-lg" /> <FiUpload className="text-lg" />

View File

@ -28,16 +28,17 @@ const cards = [
// buttonText: 'Start Now', // buttonText: 'Start Now',
// bg: 'bg-white/60', // bg: 'bg-white/60',
// }, // },
{ // {
icon: <FiEdit className="w-5 h-5 text-green-700" />, // icon: <FiEdit className="w-5 h-5 text-green-700" />,
title: 'Sign an Agreement', // title: 'Sign an Agreement',
description: 'Upload a document you need to sign and place your signature.', // description: 'Upload a document you need to sign and place your signature.',
bg: 'bg-white/60', // bg: 'bg-white/60',
}, // },
{ {
icon: <FiFolder className="w-5 h-5 text-purple-700" />, icon: <FiFolder className="w-5 h-5 text-purple-700" />,
title: 'Use a Template', title: 'Use a Template',
description: 'Pick from ready-made templates for common agreements.', description: 'Pick from ready-made templates for common agreements.',
link: "/templates",
bg: 'bg-white/60', bg: 'bg-white/60',
}, },
]; ];
@ -54,19 +55,19 @@ const StartSection = () => {
const defaultAccountId = user.account.id; const defaultAccountId = user.account.id;
const handleStartSectionClick = (card: StartCard) => { const handleStartSectionClick = (link: string) => {
if ("buttonText" in card && card.buttonText === "Start Now") { // if ("buttonText" in card && card.buttonText === "Start Now") {
api.post(`/api/accounts/${defaultAccountId}/transactions`) // api.post(`/api/accounts/${defaultAccountId}/transactions`)
.then(res => { // .then(res => {
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.id}`), 0) // setTimeout(() => navigate(`/process-step-one/${defaultAccountId}/${res.data.id}`), 0)
} // }
}) // })
.catch(err => console.log(err)) // .catch(err => console.log(err))
} // }
// navigate("/process-step-one"); navigate(link);
} }
// useEffect(() => { // useEffect(() => {
@ -87,10 +88,10 @@ const StartSection = () => {
<StartNow /> <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) => ( {cards.map((card, index) => (
<div <div
onClick={() => handleStartSectionClick(card)} onClick={() => handleStartSectionClick(card.link)}
key={index} 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`} 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 { import {
FaCheck, FaCheck,
FaFileAlt, FaFileAlt,
@ -15,12 +15,13 @@ import {
import { fetchRecipients } from "../../../store/slices/selectedRecipientsSlice"; import { fetchRecipients } from "../../../store/slices/selectedRecipientsSlice";
import { fetchDocuments } from "../../../store/slices/documentSlice"; import { fetchDocuments } from "../../../store/slices/documentSlice";
import { useDispatch, useSelector } from "react-redux"; 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 Loader from "../../dashboard/Loader";
import confetti from 'canvas-confetti' import confetti from 'canvas-confetti'
import { IoClose } from "react-icons/io5"; import { IoClose } from "react-icons/io5";
import { IoMdClose, IoMdHome } from "react-icons/io"; import { IoMdClose, IoMdHome } from "react-icons/io";
import { Bounce, toast } from "react-toastify"; import { Bounce, toast } from "react-toastify";
import api from "../../../config/api";
type Signer = { type Signer = {
id: number; id: number;
@ -200,7 +201,9 @@ const DocumentCompletionScreen = () => {
{ title: 'Document 10', pages: 11 }, { title: 'Document 10', pages: 11 },
{ title: 'Document 11', pages: 4 }, // Won't be shown due to slice { 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 { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const [showConfetti, setShowConfetti] = React.useState(true) const [showConfetti, setShowConfetti] = React.useState(true)
if (!accountId || !transactionId) { if (!accountId || !transactionId) {
@ -265,6 +268,77 @@ const DocumentCompletionScreen = () => {
}, [showConfetti]); }, [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 ( return (
<> <>
@ -285,8 +359,8 @@ const DocumentCompletionScreen = () => {
className='flex items-center gap-3'> 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)]"> <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" /> {/* <FaPen className=" text-white text-md" /> */}
<span className="tracking-wide text-white">Edit</span> <span className="tracking-wide text-white">View Transaction</span>
</Link> </Link>
{/* <button {/* <button

View File

@ -60,6 +60,7 @@ 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 [showSelfSignWarnModal, setShowSelfSignWarnModal] = useState(false) const [showSelfSignWarnModal, setShowSelfSignWarnModal] = useState(false)
const [editRecipientCurrentEmail, setEditRecipientCurrentEmail] = useState('')
console.log() console.log()
const userData = useSelector((state: RootState) => state.user) const userData = useSelector((state: RootState) => state.user)
@ -95,6 +96,15 @@ const ProcessStepOne = () => {
try { try {
const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`); const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`);
setTransactionDetails(res.data) 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) // console.log("setTransactionDetails",res.data)
} catch (e) { } catch (e) {
console.log("Error - transactions details") console.log("Error - transactions details")
@ -133,7 +143,8 @@ const ProcessStepOne = () => {
let formError = { let formError = {
documents: false, documents: false,
recipients: false, recipients: false,
subject: false subject: false,
recipientEmailAndLastName:false
} }
if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) { if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
@ -141,20 +152,32 @@ const ProcessStepOne = () => {
formError.documents = true formError.documents = true
} }
if (!selectedRecipients.length) { 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) { if (!emailDtls.subject.length) {
formError.subject = true formError.subject = true
} }
if (SignTypeEnum.selfSign === signType) formError.recipients = false; 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') console.log(selectedDocuments, 'line 193')
toast.dismiss(); toast.dismiss();
toast.error(`${formError.documents ? "Add Document" : ""} toast.error(`${formError.documents ? "Add Document" : ""}
${(formError.documents && formError.recipients) ? " | " : ""} ${(formError.documents && formError.recipients) ? " | " : ""}
${formError.recipients ? "Add Recipient" : ""} ${formError.recipients ? "Add Recipient" : ""}
${formError.recipientEmailAndLastName ? "All recipients must have both Email and Last Name." : ""}
${(formError.documents || formError.recipients) ? " | " : ""} ${(formError.documents || formError.recipients) ? " | " : ""}
${formError.subject ? "Add Subject" : ""}`, { ${formError.subject ? "Add Subject" : ""}`, {
position: "top-center", position: "top-center",
@ -425,7 +448,8 @@ const ProcessStepOne = () => {
{ {
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) && (signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
<div className="flex flex-col gap-4 align-start"> <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 />} */} {/* {signType !== "self-sign" && <RecipientAddCard />} */}
<div <div
@ -433,7 +457,7 @@ const ProcessStepOne = () => {
className={`relative w-full bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none h-screen overflow-hidden' : ''}`} 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>
</div> </div>

View File

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

View File

@ -26,15 +26,21 @@ const getInitials = (name: string) => {
}; };
type CardsData = { type CardsData = {
editRecipient: any;
setEditRecipient: (recipient: any) => void; // Replace `any` with the actual type if known setEditRecipient: (recipient: any) => void; // Replace `any` with the actual type if known
setEditCase: (string: any) => void; setEditCase: (string: any) => void;
transactionTemplateToggle: string 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; const templateId = useParams<{ templateId: string }>().templateId;
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>(); let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const dispatch = useDispatch(); const dispatch = useDispatch();
if (!transactionId) { if (!transactionId) {
@ -56,6 +62,11 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
// alert(JSON.stringify(recipient)) // alert(JSON.stringify(recipient))
setEditCase('Edit') setEditCase('Edit')
setEditRecipient(recipient) setEditRecipient(recipient)
if (recipient.uuid) {
setEditRecipientCurrentEmail(recipient.uuid)
}else{
setEditRecipientCurrentEmail(recipient.id)
}
} }
const handleRemoveRecipient = (recipient) => { const handleRemoveRecipient = (recipient) => {
@ -130,7 +141,7 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
{recipients.map((recipient, idx) => ( {recipients.map((recipient, idx) => (
<div <div
key={recipient.email + idx} 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"> <div className="flex items-center gap-3 min-w-0">
{/* {signingOrder && ( */} {/* {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)]" 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"> <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 || ""}`)} {getInitials(`${recipient.firstName} ${recipient.lastName || ""}`)}
@ -214,7 +224,8 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
)} )}
</div> </div>
))} )
)}
</div> </div>
</> </>
) : ( ) : (

View File

@ -77,9 +77,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
}, []) }, [])
// FETCHING TAG - START
useEffect(() => {
if (selectedRecipients.length === 0) return;
const fetchTags = async () => { const fetchTags = async () => {
try { try {
@ -92,9 +89,9 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
for (const tag of res.data) { for (const tag of res.data) {
const key = `${tag.documentId}_${tag.pageIndex}`; const key = `${tag.documentId}_${tag.pageIndex}`;
if (recTags[key]) { if (recTags[key]) {
recTags[key].push(tag); recTags[key].push({ ...tag, update: false });
} else { } 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(); fetchTags();
}, [selectedRecipients]); }, [selectedRecipients]);
@ -161,7 +161,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
delete updateTags[updateTags.length - 1].update delete updateTags[updateTags.length - 1].update
} }
} }
updatedData[recipientId] = updateTags updatedData[recipientId] = updateTags
} }
@ -174,16 +173,30 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
}); });
try { try {
const responses = await Promise.all(requests); const response = await Promise.all(requests);
const droppedItemsUpdateFalse = Object.fromEntries(
Object.entries(droppedItems).map(([recipientId, tags]) => {
return [recipientId, tags.map(tag => ({ ...tag, update: false }))];
})
)
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) { } catch (error) {
console.error("Error submitting recipient tags:", error); console.error("Error submitting recipient tags:", error);
} }
@ -300,6 +313,11 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
await sendTagsToRecipients(tagsData, accountId, transactionId); await sendTagsToRecipients(tagsData, accountId, transactionId);
// if (save) {
// setSelectedTag(null);
// await fetchTags();
// }
// SENDING Transaction - START // SENDING Transaction - START
if (!save) { if (!save) {
const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" }) 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 key = `${docId}_${pageIndex}`;
// const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />; // 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 = { let prepData = {
properties: data.properties, properties: data.properties,
icon: typeToIconMap[field.label.toUpperCase()] || <FaQuestionCircle />, icon: typeToIconMap[field.label.toUpperCase()] || <FaQuestionCircle />,

View File

@ -4,7 +4,7 @@ import fields from '../../../helper/fieldsListData';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import typeToIconMap from '../../../helper/iconMap'; import typeToIconMap from '../../../helper/iconMap';
import { FaQuestionCircle } from 'react-icons/fa'; import { FaQuestionCircle } from 'react-icons/fa';
import { TagsProperties } from '../../../utlity/types'; import { TagsProperties, TagsTypes } from '../../../utlity/types';
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import axios from 'axios'; import axios from 'axios';
@ -13,12 +13,25 @@ const handlePointerDown = (event: React.DragEvent<HTMLDivElement>, label: string
let width = dragElemRect.width let width = dragElemRect.width
let height = dragElemRect.height let height = dragElemRect.height
if (label === "radio" || label === "checkbox") { if (label === TagsTypes.RADIO || label === TagsTypes.CHECKBOX) {
const innerIconConRect = event.currentTarget.children[0].getBoundingClientRect(); const innerIconConRect = event.currentTarget.children[0].getBoundingClientRect();
width = innerIconConRect.width + 10 width = innerIconConRect.width + 10
height = innerIconConRect.height + 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 offsetX = event.clientX - dragElemRect.left;
const offsetY = event.clientY - dragElemRect.top; const offsetY = event.clientY - dragElemRect.top;

View File

@ -80,7 +80,13 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
<div className='flex items-center gap-1.5'> <div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: selectedRecipient.color }}></p> <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 }} /> */} {/* <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> </div>
<MdKeyboardArrowDown /> <MdKeyboardArrowDown />
</div> </div>
@ -101,7 +107,13 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
<div className='flex items-center gap-1.5'> <div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p> <p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
{/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */} {/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */}
<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> </div>
{selectedRecipientIndx === indx && <IoCheckmark className='text-[var(--primary)]' />} {selectedRecipientIndx === indx && <IoCheckmark className='text-[var(--primary)]' />}
</div>) </div>)

View File

@ -63,6 +63,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
return null; return null;
} }
// const [transactionSaving, setTransactionSaving] = useState<boolean>(false); // const [transactionSaving, setTransactionSaving] = useState<boolean>(false);
const [dotCount, setDotCount] = useState(0); const [dotCount, setDotCount] = useState(0);
@ -125,11 +126,11 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
setTimeout(() => { setTimeout(() => {
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) { if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
if (isSignSend) { 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 { } else {
navigate(`/templates`); navigate(`/templates`);
toast.success("Template Saved...!", { 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'> 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'> <div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p> <p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
<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>
</div>) </div>)
})} })}

View File

@ -9,6 +9,7 @@ import { RootState } from '../../../store/store';
import Loader from '../../dashboard/Loader'; import Loader from '../../dashboard/Loader';
import { hideLoader } from '../../../store/slices/loaderSlice'; import { hideLoader } from '../../../store/slices/loaderSlice';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import api from '../../../config/api';
export interface IsSendInterface { export interface IsSendInterface {
send: boolean | null; send: boolean | null;
@ -34,6 +35,8 @@ const ProcessStepTwo = () => {
}, []) }, [])
return ( return (
<div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}> <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} /> <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'> <div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: selectedRecipient.color }}></p> <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 }} /> */} {/* <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> </div>
<MdKeyboardArrowDown /> <MdKeyboardArrowDown />
</div> </div>
@ -74,7 +80,13 @@ const RecipientsAttrCon: React.FC<RecipientsAttrConIntr> = ({ selectedRecipientI
<div className='flex items-center gap-1.5'> <div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p> <p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
{/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */} {/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */}
<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> </div>
{selectedRecipientId === recipient.id && <IoCheckmark className='text-[var(--primary)]' />} {selectedRecipientId === recipient.id && <IoCheckmark className='text-[var(--primary)]' />}
</div>) </div>)

View File

@ -26,7 +26,7 @@ import { FaSpinner } from "react-icons/fa";
import { GrCheckboxSelected } from "react-icons/gr"; import { GrCheckboxSelected } from "react-icons/gr";
import { Bounce, toast } from "react-toastify"; import { Bounce, toast } from "react-toastify";
import axios from 'axios'; import axios from 'axios';
import { SaveType } from '../client/SignDocument'; import { SaveType, SaveTypeEnum } from '../client/SignDocument';
import { CiSaveDown1 } from 'react-icons/ci'; import { CiSaveDown1 } from 'react-icons/ci';
import api from '../../config/api'; import api from '../../config/api';
@ -160,6 +160,21 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
}) })
if (res?.data) { if (res?.data) {
let preSavedSignatures = 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) setPreSaveSignatures(preSavedSignatures)
} }
})() })()
@ -279,6 +294,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
initials: "", initials: "",
}) })
setSaved({ setSaved({
signatureId: res.data.id ?? null,
saveType: res.data.signatureType ?? null, saveType: res.data.signatureType ?? null,
save: true, save: true,
signature: res.data.signatureBlob ?? null, 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' */} {/* isEditMode ? 'UPDATE SIGNATURE' : 'CREATE SIGNATURE' */}
</> </>
)} )}

View File

@ -90,6 +90,22 @@ const Templates = () => {
navigate(`/process-step-one/${accountId}/template/${templateId}`); 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 ( return (
<div className="p-3 text-sm"> <div className="p-3 text-sm">
{/* Header */} {/* Header */}
@ -159,8 +175,8 @@ const Templates = () => {
</div> </div>
</td> </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">{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">{formatTimestamp(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.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> <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 */} {/* Dropdown Trigger + Tooltip */}
<td className="px-4 py-2 text-center relative"> <td className="px-4 py-2 text-center relative">
@ -173,7 +189,7 @@ const Templates = () => {
{showMenu === index && ( {showMenu === index && (
<div <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 tabIndex={0} // Make it focusable
onBlur={(e) => { onBlur={(e) => {
// Ensure it only closes if focus truly leaves the container // Ensure it only closes if focus truly leaves the container
@ -183,7 +199,7 @@ const Templates = () => {
}} }}
> >
<button <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)} onClick={() => handleEditClick(item.id)}
> >
Edit Edit

View File

@ -9,6 +9,7 @@ import { IoCloseOutline } from 'react-icons/io5';
import { MdEditDocument } from "react-icons/md"; import { MdEditDocument } from "react-icons/md";
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { RootState } from '../../store/store'; import { RootState } from '../../store/store';
import { SignTypeEnum } from '../../utlity/types';
// Sample recipient data — you can replace this with actual API data later // Sample recipient data — you can replace this with actual API data later
const recipients = [ const recipients = [
@ -62,7 +63,7 @@ const TransactionView = () => {
const handleNavigateToSign = (id: number) => { const handleNavigateToSign = (id: number) => {
window.open(`./../../../../sign/${id}`) window.open(`./../../../../sign/${id}/${SignTypeEnum.selfSign}`)
} }
return ( return (
@ -138,7 +139,7 @@ const TransactionView = () => {
{recipient.email === user.email && recipient.status !== "COMPLETED" && {recipient.email === user.email && recipient.status !== "COMPLETED" &&
<p <p
onClick={() => handleNavigateToSign(recipient.id)} 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 Sign
</p>} </p>}
</div> </div>