self_Sign & completion screen navigate to back & without email and lastname show error toast

This commit is contained in:
JaiPandey425 2025-08-07 16:06:32 +05:30
parent 4de7c2540e
commit 002c8e6110
11 changed files with 242 additions and 65 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 = [
@ -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

@ -161,6 +161,7 @@ const SignDocument = () => {
const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" }); const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" });
console.log(recSignStatus, "recSignStatus")
// Handling Date Opacity // Handling Date Opacity
const [dateValues, setDateValues] = useState<{ [key: string]: string }>({}); const [dateValues, setDateValues] = useState<{ [key: string]: string }>({});
@ -261,6 +262,8 @@ const SignDocument = () => {
} }
}, [saved]) }, [saved])
const handleSignClick = (index: number, id: number) => { const handleSignClick = (index: number, id: number) => {
setSignTagId(id) setSignTagId(id)
if (saved.save) { if (saved.save) {
@ -305,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}`,
{ {
@ -315,17 +347,17 @@ const SignDocument = () => {
if (res.status === 200) { if (res.status === 200) {
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)
} }
@ -337,6 +369,7 @@ const SignDocument = () => {
}) })
.catch((err) => { .catch((err) => {
console.log(err)
}); });
} }
@ -867,6 +900,8 @@ const SignDocument = () => {
}; };
// const [showTerms, setShowTerms] = useState<boolean>(true); // const [showTerms, setShowTerms] = useState<boolean>(true);
return ( return (
<div className={`w-full h-screen overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}> <div className={`w-full h-screen overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>

View File

@ -134,7 +134,7 @@ const Contacts = () => {
</button> </button>
</div> </div>
</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"> <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"> <thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider sticky top-0 z-10">
<tr> <tr>
@ -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

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

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)
@ -142,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) {
@ -150,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",
@ -434,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 editRecipient={editRecipient} 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
@ -442,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

@ -30,12 +30,17 @@ type CardsData = {
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 = ({ editRecipient, setEditRecipient, setEditCase, transactionTemplateToggle }: 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) {
@ -57,6 +62,11 @@ const RecipientCards = ({ editRecipient, setEditRecipient, setEditCase, transact
// 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) => {
@ -128,40 +138,36 @@ const RecipientCards = ({ editRecipient, setEditRecipient, setEditCase, transact
)} )}
<div className="flex flex-col divide-y divide-gray-200"> <div className="flex flex-col divide-y divide-gray-200">
{recipients.map((recipient, idx) => { {recipients.map((recipient, idx) => (
// if(recipient.id === editRecipient.id) return <div
key={recipient.email + idx}
return ( 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 >
key={recipient.email + idx} <div className="flex items-center gap-3 min-w-0">
className="flex items-center justify-between px-4 py-3 hover:bg-gray-50 transition-colors" {/* {signingOrder && ( */}
> {recipients.length > 1 ? <div className="relative">
<div className="flex items-center gap-3 min-w-0"> <input
{/* {signingOrder && ( */} type="number"
{recipients.length > 1 ? <div className="relative"> value={recipient.routingOrder || 1}
<input min={1}
type="number" onChange={(e) =>
value={recipient.routingOrder || 1} handleOrderChange(recipient.id || recipient.email, Number(e.target.value))
min={1} }
onChange={(e) => className="w-10 h-6 text-center rounded text-sm text-gray-600 focus:outline-none focus:ring-[var(--primary)] border border-dashed border-gray-400 bg-slate-100"
handleOrderChange(recipient.id || recipient.email, Number(e.target.value)) />
} {/* <RiPencilFill className="absolute top-1.5 right-0 text-[14px] text-gray-500" /> */}
className="w-10 h-6 text-center rounded text-sm text-gray-600 focus:outline-none focus:ring-[var(--primary)] border border-dashed border-gray-400 bg-slate-100" </div>
/> : <input
{/* <RiPencilFill className="absolute top-1.5 right-0 text-[14px] text-gray-500" /> */} type="number"
</div> disabled
: <input value={recipient.routingOrder || 1}
type="number" min={1}
disabled onChange={(e) =>
value={recipient.routingOrder || 1} handleOrderChange(recipient.id || recipient.email, Number(e.target.value))
min={1} }
onChange={(e) => className="w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)]"
handleOrderChange(recipient.id || recipient.email, Number(e.target.value)) />}
}
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 || ""}`)}
</div> </div>
@ -219,7 +225,7 @@ const RecipientCards = ({ editRecipient, setEditRecipient, setEditCase, transact
)} )}
</div> </div>
) )
})} )}
</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,10 +126,10 @@ 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) {
window.open(`./../../../sign/${selfUserSign.recipientId}`, "_self"); navigate(`./../../../sign/${selfUserSign.recipientId}`,{ replace: true });
} }
else { else {
navigate(`/document-completion-screen/${accountId}/${transactionId}`) navigate(`/document-completion-screen/${accountId}/${transactionId}`, { replace: true })
} }
} else { } else {
navigate(`/templates`); navigate(`/templates`);
@ -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;
@ -25,7 +26,7 @@ const ProcessStepTwo = () => {
const [isSend, setIsSend] = useState<boolean | null>(null); const [isSend, setIsSend] = useState<boolean | null>(null);
const globalLoader = useSelector((state: RootState) => state.loader) const globalLoader = useSelector((state: RootState) => state.loader)
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
@ -33,6 +34,8 @@ const ProcessStepTwo = () => {
}, 800) }, 800)
}, []) }, [])
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' : ''}`}>

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