This commit is contained in:
=bhagwanSingh 2025-10-01 20:02:11 +05:30
parent 6acf3c7d72
commit 8b7193ad2d
32 changed files with 933 additions and 840 deletions

7
.env
View File

@ -4,7 +4,6 @@
# VITE_BASE_URI="https://python-amused-adder.ngrok-free.app" # VITE_BASE_URI="https://python-amused-adder.ngrok-free.app"
# VITE_BASE_URI="https://concrete-gladly-mullet.ngrok-free.app" # VITE_BASE_URI="https://concrete-gladly-mullet.ngrok-free.app"
# VITE_BASE_URI="https://smashing-albacore-engaging.ngrok-free.app" # VITE_BASE_URI="https://smashing-albacore-engaging.ngrok-free.app"
VITE_BASE_URI="https://basically-polished-yeti.ngrok-free.app" # VITE_BASE_URI="https://basically-polished-yeti.ngrok-free.app"
VITE_BASE_URI="https://sacha-countryfied-evolutionistically.ngrok-free.dev"
VITE_SSE_URI="http://10.10.1.75:8083"
# VITE_BASE_URI="http://10.10.1.248:8083"

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

View File

@ -46,7 +46,8 @@ const App = () => {
{/* <Route element={<AccountTransactionWrapper />}> */} {/* <Route element={<AccountTransactionWrapper />}> */}
<Route path="/accounts" element={<Accounts />} /> <Route path="/accounts" element={<Accounts />} />
<Route path="/accounts/:accountId/transactions" element={<Account />} /> <Route path="/accounts/:accountId/transactions" element={<Account />} />
<Route path="/transactions" element={<Account />} /> <Route path="/transactions/:filter?" element={<Account />} />
<Route path="/accounts/:accountId/transactions/:transactionId" element={<TransactionView />} /> <Route path="/accounts/:accountId/transactions/:transactionId" element={<TransactionView />} />
{/* <Route path="/transactions/:filterParam" element={<Transactions />} /> */} {/* <Route path="/transactions/:filterParam" element={<Transactions />} /> */}
<Route path="/templates" element={<Templates />} /> <Route path="/templates" element={<Templates />} />
@ -111,6 +112,10 @@ const App = () => {
<Route path="/view" element={<DocView />} /> <Route path="/view" element={<DocView />} />
<Route path="/session_expired" element={<SessionExpired />} /> <Route path="/session_expired" element={<SessionExpired />} />
<Route path="/transaction-complete" element={<CompletionScreen />} />
<Route path="/transaction-declined" element={<CompletionScreen hit="Action Canceled! " title="Maybe Next Time!" message={"Your request has been declined successfully. You can revisit anytime if you change your mind."} textColorSuccess={false} />} />
<Route path="/transaction-reassigned" element={<CompletionScreen hit="Reasssigned Successfully" title="The transaction has been reassigned!" message={"The transaction has been reassgned to another user"} textColorSuccess={false} />} />
{/* Links work wothout Login */} {/* Links work wothout Login */}
</Routes> </Routes>

View File

@ -248,16 +248,16 @@ const CommonNav: React.FC<AccountNavProps> = ({
}, [open]); }, [open]);
const handleLogout = async () => { const handleLogout = async () => {
try { api.get(`${import.meta.env.VITE_BASE_URI}/api/v1/auth/logout`).then((res) => {
let res = await api.post('/api/v1/auth/logout'); sessionStorage.clear();
if (res.status === 200) { localStorage.clear();
sessionStorage.clear(); // Redirect to login or refresh the page after logout
setOpen(false); // window.location.href = '/login';
navigate("/login"); }).catch((err) => {
} sessionStorage.clear();
} catch (error) { localStorage.clear();
console.log(error); window.location.href = '/login';
} });
}; };
const handleMainDropdownToggle = () => { const handleMainDropdownToggle = () => {
@ -365,10 +365,10 @@ const CommonNav: React.FC<AccountNavProps> = ({
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{/* SETINGS - START */} {/* SETINGS - START */}
<div <div
onClick={handleSettingsClick} onClick={handleSettingsClick}
className="bg-[#F6F6F6] rounded-md py-1.5 px-2 cursor-pointer transition-all duration-200 hover:bg-[#DCDADA] mr-3"> className="bg-[#F6F6F6] rounded-md py-1.5 px-2 cursor-pointer transition-all duration-200 hover:bg-[#DCDADA] mr-3">
<svg width="16" height="20" viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="20" viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.54"> <g opacity="0.54">
<path d="M2 9.10996V14.88C2 17 2 17 4 18.35L9.5 21.53C10.33 22.01 11.68 22.01 12.5 21.53L18 18.35C20 17 20 17 20 14.89V9.10996C20 6.99996 20 6.99996 18 5.64996L12.5 2.46996C11.68 1.98996 10.33 1.98996 9.5 2.46996L4 5.64996C2 6.99996 2 6.99996 2 9.10996Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path d="M2 9.10996V14.88C2 17 2 17 4 18.35L9.5 21.53C10.33 22.01 11.68 22.01 12.5 21.53L18 18.35C20 17 20 17 20 14.89V9.10996C20 6.99996 20 6.99996 18 5.64996L12.5 2.46996C11.68 1.98996 10.33 1.98996 9.5 2.46996L4 5.64996C2 6.99996 2 6.99996 2 9.10996Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />

View File

@ -33,6 +33,7 @@ const Account = () => {
textColor: "text-sky-600", textColor: "text-sky-600",
gradient: "bg-white", gradient: "bg-white",
shadowColor: "shadow-sky-100", shadowColor: "shadow-sky-100",
navigateTo: "/transactions/action_required"
}, },
{ {
title: "Waiting for Response", title: "Waiting for Response",
@ -42,6 +43,7 @@ const Account = () => {
textColor: "text-yellow-600", textColor: "text-yellow-600",
gradient: "bg-white", gradient: "bg-white",
shadowColor: "shadow-yellow-100", shadowColor: "shadow-yellow-100",
navigateTo: "/transactions/waiting_for_response"
}, },
{ {
title: "Expiring Soon", title: "Expiring Soon",
@ -51,6 +53,7 @@ const Account = () => {
textColor: "text-red-600", textColor: "text-red-600",
gradient: "bg-white", gradient: "bg-white",
shadowColor: "shadow-red-100", shadowColor: "shadow-red-100",
navigateTo: "/transactions/expiring_soon"
}, },
{ {
title: "Completed", title: "Completed",
@ -60,6 +63,7 @@ const Account = () => {
textColor: "text-green-600", textColor: "text-green-600",
gradient: "bg-white", gradient: "bg-white",
shadowColor: "shadow-green-100", shadowColor: "shadow-green-100",
navigateTo: "/transactions/completed"
}, },
]; ];
}; };

View File

@ -147,7 +147,7 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
if (item) { if (item) {
const userData = { const userData = {
id: item?.id, id: item?.id,
name : item?.firstName + " " + item?.lastName , name: item?.firstName + " " + item?.lastName,
email: item?.email, email: item?.email,
language: "", language: "",
account: { account: {
@ -157,10 +157,10 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
}, },
}; };
setUser(userData); setUser(userData);
sessionStorage.setItem('userDetails', JSON.stringify(userData)); sessionStorage.setItem('userDetails', JSON.stringify(userData));
sessionStorage.setItem('activeAccountId', JSON.stringify(userData.account.id)); sessionStorage.setItem('activeAccountId', JSON.stringify(userData.account.id));
// Dispatch event for initial load // Dispatch event for initial load
dispatchSessionStorageChange('activeAccountId', userData.account.id); dispatchSessionStorageChange('activeAccountId', userData.account.id);
} }
@ -213,16 +213,16 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
}, [open]); }, [open]);
const handleLogout = async () => { const handleLogout = async () => {
try { api.get(`${import.meta.env.VITE_BASE_URI}/api/v1/auth/logout`).then((res) => {
let res = await api.post('/api/v1/auth/logout'); sessionStorage.clear();
if (res.status === 200) { localStorage.clear();
sessionStorage.clear(); // Redirect to login or refresh the page after logout
setOpen(false); // window.location.href = '/login';
navigate("/login"); }).catch((err) => {
} sessionStorage.clear();
} catch (error) { localStorage.clear();
console.log(error); window.location.href = '/login';
} });
}; };
const handleMainDropdownToggle = () => { const handleMainDropdownToggle = () => {

View File

@ -1,24 +1,17 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from "react";
import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi'; import { MdOutlineFileDownload } from "react-icons/md";
import api from '../../config/api'; import { useLocation, useNavigate, useParams } from "react-router-dom";
import { Transaction } from '../transactions/Transactions'; import LoaderUi from "../../helper/LoaderUi";
import { useLocation, useNavigate } from 'react-router-dom'; import logoRecord from "../../assets/record.svg";
import LoaderUi from '../../helper/LoaderUi'; import api from "../../config/api";
import logoRecord from '../../assets/record.svg'; import DownloadDocument from "./DownloadDocument";
import { MdOutlineFileDownload } from 'react-icons/md';
import downloadDocumentUtils from '../../utlity/downloadDocumentsUtils';
import { IoMdClose } from 'react-icons/io';
import DownloadDocument from './DownloadDocument';
const formatDate = (timestamp: number) => { const statusColorMap: Record<string, string> = {
return new Date(timestamp * 1000).toLocaleString();
};
const statusColorMap = {
CREATED: "bg-blue-100 text-blue-600", CREATED: "bg-blue-100 text-blue-600",
SENT: "bg-green-100 text-green-600", SENT: "bg-yellow-100 text-yellow-600",
COMPLETED: "bg-purple-100 text-purple-600", COMPLETED: "bg-green-100 text-green-600",
EXPIRED: "bg-red-100 text-red-600", DECLINED: "bg-red-100 text-red-600",
EXPIRED: "bg-gray-200 text-gray-600",
}; };
export enum DownloadTypesEnum { export enum DownloadTypesEnum {
@ -27,281 +20,213 @@ export enum DownloadTypesEnum {
CERTIFICATE = "certificate" CERTIFICATE = "certificate"
} }
const formatDate = (isoString: string) => {
const date = new Date(isoString);
return isNaN(date.getTime()) ? "-" : date.toLocaleString();
};
const TransactionDataTable = ({ activeAccountId }) => { const TransactionDataTable = ({ activeAccountId }) => {
const navigate = useNavigate() const { filter } = useParams<{ filter: string }>();
const loaction = useLocation() const navigate = useNavigate();
const location = useLocation();
const [currentEmailId, setCurrentEmailId] = useState<string | null>('null'); const [transactions, setTransactions] = useState<any[]>([]);
const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]); const [loading, setLoading] = useState(false);
const [showMenu, setShowMenu] = useState<{ open: boolean, transactionId: string | null }>({ // Pagination
const [page, setPage] = useState(0); // 0-based
const [size, setSize] = useState(10);
const [totalPages, setTotalPages] = useState(0);
const [showMenu, setShowMenu] = useState<{ open: boolean; transactionId: string | null }>({
open: false, open: false,
transactionId: null transactionId: null,
}); });
const [loading, setLoading] = useState(false)
const [downloadDocumentType, setDownloadDocumentType] = useState({
type: DownloadTypesEnum.ALL,
includeCertificate: false
})
const fetchTransactions = async () => { const fetchTransactions = async () => {
console.log("fetchTransactions") if (!activeAccountId) return;
setLoading(true) setLoading(true);
try { try {
const response = await api.get(`/api/v1/accounts/${activeAccountId}/transactions?sortBy=lastModifiedAt desc`); let addParams = `?sortBy=createdAt desc&page=${page}&size=${Math.min(size, 30)}`
setTransactions(response.data || []); switch(filter) {
case "action_required":
addParams = addParams+"&filter=inbox"
break;
case "waiting_for_response":
addParams = addParams+"&status=sent"
break;
case "expiring_soon":
addParams = addParams+"&filter=inbox"
break;
case "completed":
addParams = addParams+"&status=completed&filter=completed"
break;
default:
break;
}
const response = await api.get(
`/api/v1/accounts/${activeAccountId}/transactions`+addParams
);
// ✅ Use correct response structure
setTransactions(response.data?.content || []);
setTotalPages(response.data?.page?.totalPages || 0);
} catch (error) { } catch (error) {
console.error("Error fetching transactions:", error); console.error("Error fetching transactions:", error);
} finally { } finally {
setLoading(false) setLoading(false);
} }
}; };
const [transactions, setTransactions] = useState<Transaction[]>([]);
const handleTransactionClick = (id: number) => {
navigate(`/accounts/${activeAccountId}/transactions/${id}`);
};
useEffect(() => { useEffect(() => {
if (activeAccountId) { fetchTransactions();
fetchTransactions(); }, [activeAccountId, page, size, filter]);
}
}, [activeAccountId]);
useEffect(() => {
const userDetails = JSON.parse(sessionStorage.getItem('userDetails'));
setCurrentEmailId(userDetails?.email)
}, []);
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)
}
}
function getStatusColorClass(status) {
return statusColorMap[status] || "bg-gray-100 text-gray-600";
}
function handleDownloadFormSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const docType = formData.get('download-doc-type');
const includeCertificate = formData.get('download-doc-type-include-cert');
downloadDocumentUtils(activeAccountId, showMenu.transactionId as string, docType as string, includeCertificate as string | null)
}
return ( return (
<> <div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden p-5">
{/* Table Header */}
<div className={`bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden px-5 pb-5`}> {location.pathname !== "/transactions" && (
{/* Header */} <div className="flex justify-between items-center pb-3">
{location.pathname !== '/transactions' && <h2 className="text-lg font-semibold text-gray-800">Recent Transactions</h2>
<div className="flex justify-between items-center pt-4"> <span
<h2 className="text-lg font-bold text-gray-800">Recent Transactions</h2> onClick={() => navigate("/transactions")}
<span className=' text-[var(--primary)] text-xs hover:underline cursor-pointer font-semibold'>View all</span> className="text-[var(--primary)] text-sm hover:underline cursor-pointer font-medium"
</div>} >
View all
{/* Scrollable Table Container */} </span>
<div className={`overflow-auto my-4 no-scrollbar ${location.pathname === '/transactions' ? "min-h-[76vh] max-h-[76vh]" : "min-h-[30vh] max-h-[46vh]"}`}>
<table className="w-full text-sm">
{/* Sticky Header */}
<thead className="sticky top-0 z-10">
<tr className="text-xs">
<th className="text-left bg-[#F8F9FC] rounded-tl-lg rounded-bl-lg px-6 py-4 font-bold text-nowrap">Transaction</th>
<th className="text-left bg-[#F8F9FC] px-6 py-4 font-bold text-nowrap">Status</th>
<th className="text-left bg-[#F8F9FC] px-6 py-4 font-bold text-nowrap">Signed By</th>
<th className="text-left bg-[#F8F9FC] px-6 py-4 font-bold text-nowrap">Last Change</th>
<th className="text-left bg-[#F8F9FC] px-6 py-4 font-bold text-nowrap">Expire After (Days)</th>
<th className="text-left bg-[#F8F9FC] rounded-tr-lg rounded-br-lg px-6 py-4 font-bold text-nowrap">Actions</th>
</tr>
</thead>
{loading ? (
<tbody>
<tr>
<td colSpan="6" className="relative h-100">
<LoaderUi />
</td>
</tr>
</tbody>
) : transactions.length > 0 ? (
<>
{/* Scrollable Body */}
<tbody>
{transactions.map((tx, idx) => (
<tr
key={tx.id}
// onClick={() => handleTransactionClick(tx.id)}
>
<td className={`${idx % 2 !== 0 ? "bg-[#F8F9FC]" : ""} font-bold px-6 py-4 text-left`}>
<div className="text-gray-800 font-medium truncate max-w-xs cursor-pointer"
onClick={() => handleTransactionClick(tx.id)}
>
{tx?.subject || "Untitled"}
</div>
</td>
<td className={`${idx % 2 !== 0 ? "bg-[#F8F9FC]" : ""} text-left font-bold px-6 py-4`}>
<span
className={`inline-block px-3 py-1 rounded-full text-xs font-medium ${getStatusColorClass(
tx.status
)}`}
>
{tx.status}
</span>
</td>
<td className={`${idx % 2 !== 0 ? "bg-[#F8F9FC]" : ""} text-left font-bold px-6 py-4`}>
<div className="flex items-center justify-left">
{tx.signedBy ? (
<div className="flex items-center gap-2">
<div
className="w-7 h-7 rounded-full flex items-center justify-center text-xs font-medium text-white flex-shrink-0"
style={{ backgroundColor: "#d8b4fe" }}
>
{tx.signedByInitials}
</div>
<span className="text-gray-700 truncate max-w-[120px]">
{tx.signedBy}
</span>
</div>
) : (
<>
-
</>
)}
</div>
</td>
<td className={`${idx % 2 !== 0 ? "bg-[#F8F9FC]" : ""} font-bold px-6 py-4 text-left`}>
<span className="text-gray-600 whitespace-nowrap">
{formatTimestamp(tx.lastModifiedAt)}
</span>
</td>
<td className={`${idx % 2 !== 0 ? "bg-[#F8F9FC]" : ""} font-bold px-6 py-4 text-left`}>
<span className="text-gray-700">
{tx.expireAfter ?? "-"}
</span>
</td>
<td className={`${idx % 2 !== 0 ? "bg-[#F8F9FC]" : ""} font-semibold px-6 py-4 text-left text-[var(--mainPrimaryBtn)]`}>
<div className='flex justify-start items-center gap-6'>
<div className='flex gap-1 relative'
tabIndex={0}
// onBlur={(e) => {
// if (!e.currentTarget.contains(e.relatedTarget)) {
// setShowMenu(null);
// }
// }}
onClick={() =>
setShowMenu({
open: true,
transactionId: tx.id
})
}
>
<MdOutlineFileDownload className='text-xl' />
<p>
Download
</p>
{/* {showMenu === idx && (
<div className="absolute top-7.5 right-0 bg-white border border-gray-200 rounded-md shadow-lg z-20 min-w-[140px] overflow-hidden animate-fade-in">
<button
onClick={() => downloadDocumentUtils(activeAccountId, tx.id, "combined")}
className="w-full text-left px-4 py-2 text-sm cursor-pointer text-slate-600 hover:bg-slate-50 transition-colors whitespace-nowrap"
>
Download Signed
</button>
<button
className="w-full text-left px-4 py-2 text-sm cursor-pointer text-slate-600 hover:bg-slate-50 transition-colors whitespace-nowrap"
>
Download Original
</button>
</div>
)} */}
</div>
{/* <button
className="duration-150 p-1 rounded-md rotate-[90deg] text-black"
onClick={(e) => {
e.stopPropagation();
// Handle actions menu
}}
>
<PiDotsThreeOutlineVerticalFill className="text-lg" />
</button> */}
</div>
</td>
</tr>
))}
</tbody>
</>) : (
<tbody>
<tr>
<td colSpan="6" className="text-center py-10 text-gray-500">
<div className="flex flex-col items-center justify-center">
<img
src={logoRecord}
alt="Record Not Found"
className="w-70 h-70 mb-4 opacity-70"
/>
</div>
</td>
</tr>
</tbody>
)}
</table>
</div> </div>
</div>
{showMenu.open && (
<DownloadDocument
showMenu={showMenu}
setShowMenu={setShowMenu}
accountId={activeAccountId}
/>
)} )}
{/* Table */}
<div
className={`overflow-auto rounded-lg ${location.pathname === "/transactions"
? "min-h-[70vh] max-h-[70vh]"
: "min-h-[30vh] max-h-[50vh]"
}`}
>
<table className="w-full text-sm">
<thead className="sticky top-0 bg-gray-50 z-10">
<tr className="text-xs text-gray-600">
<th className="px-6 py-3 text-left font-semibold">Transaction</th>
<th className="px-6 py-3 text-left font-semibold">Status</th>
<th className="px-6 py-3 text-left font-semibold">Created By</th>
<th className="px-6 py-3 text-left font-semibold">Last Modified</th>
<th className="px-6 py-3 text-left font-semibold">Expire (Days)</th>
<th className="px-6 py-3 text-left font-semibold">Actions</th>
</tr>
</thead>
{loading ? (
<tbody>
<tr>
<td colSpan={6} className="py-10 text-center">
<LoaderUi />
</td>
</tr>
</tbody>
) : transactions.length > 0 ? (
<tbody>
{transactions.map((tx, idx) => (
<tr
key={tx.id}
className={`${idx % 2 !== 0 ? "bg-gray-50" : "bg-white"} border-b border-gray-300 hover:bg-gray-100 transition`}
>
<td
onClick={() => navigate(`/accounts/${activeAccountId}/transactions/${tx.id}`)}
className="px-6 py-3 text-gray-800 font-medium cursor-pointer truncate max-w-xs"
>
{tx.subject || "Untitled"}
</td>
<td className="px-6 py-3">
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${statusColorMap[tx.status] || "bg-gray-100 text-gray-600"
}`}
>
{tx.status}
</span>
</td>
<td className="px-6 py-3 text-gray-700 truncate max-w-[150px]">
{tx.createdBy?.name || "-"}
</td>
<td className="px-6 py-3 text-gray-600">{formatDate(tx.lastModifiedAt)}</td>
<td className="px-6 py-3 text-gray-700">{tx.expireAfter ?? "-"}</td>
<td className="px-6 py-3 text-[var(--mainPrimaryBtn)] font-medium">
<button
onClick={() => setShowMenu({ open: true, transactionId: tx.id })}
className="flex items-center gap-2 text-sm hover:underline"
>
<MdOutlineFileDownload className="text-lg" /> Download
</button>
</td>
</tr>
))}
</tbody>
) : (
<tbody>
<tr>
<td colSpan={6} className="text-center py-10 text-gray-500">
<div className="flex flex-col items-center">
<img src={logoRecord} alt="No Records" className="w-32 h-32 mb-4 opacity-70" />
<p>No transactions found</p>
</div>
</td>
</tr>
</tbody>
)}
</table>
</div>
{/* Pagination */}
{transactions.length > 0 && (
<div className="flex justify-between items-center mt-4 text-sm text-gray-600">
<div>
Page {page + 1} of {totalPages}
</div>
<div className="flex items-center gap-3">
<select
value={size}
onChange={(e) => {
setSize(Math.min(Number(e.target.value), 30));
setPage(0);
}}
className="border rounded px-2 py-1 text-sm"
>
{[5, 10, 20, 30].map((s) => (
<option key={s} value={s}>
{s} / page
</option>
))}
</select>
<button
onClick={() => setPage((prev) => Math.max(prev - 1, 0))}
disabled={page === 0}
className={`px-3 py-1 rounded border ${page === 0 ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "bg-white hover:bg-gray-50"
}`}
>
Prev
</button>
<button
onClick={() => setPage((prev) => prev + 1)}
disabled={page + 1 >= totalPages}
className={`px-3 py-1 rounded border ${page + 1 >= totalPages
? "bg-gray-100 text-gray-400 cursor-not-allowed"
: "bg-white hover:bg-gray-50"
}`}
>
Next
</button>
</div>
</div>
)}
{/* Download Modal */}
{showMenu.open && (
<DownloadDocument showMenu={showMenu} setShowMenu={setShowMenu} accountId={activeAccountId} />
)}
</div>
);
};
export default TransactionDataTable;
</>
)
}
export default TransactionDataTable

View File

@ -6,7 +6,7 @@ import axios from "axios";
import { BsEye } from "react-icons/bs"; import { BsEye } from "react-icons/bs";
import { FiEyeOff } from "react-icons/fi"; import { FiEyeOff } from "react-icons/fi";
// import api from "../../config/api"; import { TokenStorage } from "../../config/api";
interface FormDataState { interface FormDataState {
email: string; email: string;
@ -16,11 +16,9 @@ interface FormDataState {
lastName: string; lastName: string;
} }
const RegisterForm = ({ onSwitchToLogin }) => { const RegisterForm = ({ onSwitchToLogin }) => {
const formErrorRef = useRef<{ message: string; error: boolean }>({ message: "", error: false }); const formErrorRef = useRef<{ message: string; error: boolean }>({ message: "", error: false });
const [formData, setForm] = useState({ const [formData, setForm] = useState<FormDataState>({
email: "", email: "",
password: "", password: "",
confirmPassword: "", confirmPassword: "",
@ -37,136 +35,79 @@ const RegisterForm = ({ onSwitchToLogin }) => {
confirmPassword: "" confirmPassword: ""
}); });
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate() const navigate = useNavigate();
const validateFirstName = (name) => { const validateFirstName = (name: string) => {
const trimmedName = name.trim(); const trimmedName = name.trim();
if (!trimmedName) return "First name is required";
if (!trimmedName) { if (trimmedName.length < 2) return "First name must be at least 2 characters";
return "First name is required"; if (trimmedName.length > 50) return "First name is too long";
}
if (trimmedName.length < 2) {
return "First name must be at least 2 characters long";
}
if (trimmedName.length > 50) {
return "First name is too long";
}
const nameRegex = /^[a-zA-Z\s'-]+$/; const nameRegex = /^[a-zA-Z\s'-]+$/;
if (!nameRegex.test(trimmedName)) { if (!nameRegex.test(trimmedName)) return "First name can only contain letters, spaces, hyphens, and apostrophes";
return "First name can only contain letters, spaces, hyphens, and apostrophes";
}
return ""; return "";
}; };
const validateLastName = (name) => { const validateLastName = (name: string) => {
const trimmedName = name.trim(); const trimmedName = name.trim();
if (!trimmedName) return "Last name is required";
if (!trimmedName) { if (trimmedName.length < 2) return "Last name must be at least 2 characters";
return "Last name is required"; if (trimmedName.length > 50) return "Last name is too long";
}
if (trimmedName.length < 2) {
return "Last name must be at least 2 characters long";
}
if (trimmedName.length > 50) {
return "Last name is too long";
}
const nameRegex = /^[a-zA-Z\s'-]+$/; const nameRegex = /^[a-zA-Z\s'-]+$/;
if (!nameRegex.test(trimmedName)) { if (!nameRegex.test(trimmedName)) return "Last name can only contain letters, spaces, hyphens, and apostrophes";
return "Last name can only contain letters, spaces, hyphens, and apostrophes";
}
return ""; return "";
}; };
const validateEmail = (email) => { const validateEmail = (email: string) => {
const trimmedEmail = email.trim(); const trimmedEmail = email.trim();
if (!trimmedEmail) return "Email is required";
if (trimmedEmail.length > 120) return "Email is too long";
if (!trimmedEmail) { // Ensure email starts with a letter or number
return "Email address is required"; const emailRegex = /^[a-zA-Z0-9][a-zA-Z0-9._-]*@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
}
if (trimmedEmail.length > 254) {
return "Email address is too long";
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(trimmedEmail)) { if (!emailRegex.test(trimmedEmail)) {
return "Please enter a valid email address"; return "Invalid email address.";
} }
return ""; return "";
}; };
const validatePassword = (password) => {
if (!password) {
return "Password is required";
}
if (password.length < 8) { const validatePassword = (password: string) => {
return "Password must be at least 8 characters long"; if (!password) return "Password is required";
} if (password.length < 8) return "Password must be at least 8 characters long";
if (password.length > 60) return "Password is too long";
if (/\s/.test(password)) return "Password cannot contain spaces";
if (password.length > 128) {
return "Password is too long";
}
const hasUpperCase = /[A-Z]/.test(password); const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password); const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password); const hasNumber = /\d/.test(password);
// const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
// if (!hasUpperCase) { if (!hasUpperCase) return "Password must contain at least one uppercase letter";
// return "Password must contain at least one uppercase letter"; if (!hasLowerCase) return "Password must contain at least one lowercase letter";
// } if (!hasNumber) return "Password must contain at least one number";
if (!hasSpecialChar) return "Password must contain at least one special character";
// if (!hasLowerCase) {
// return "Password must contain at least one lowercase letter";
// }
// if (!hasNumbers) {
// return "Password must contain at least one number";
// }
// if (!hasSpecialChar) {
// return "Password must contain at least one special character";
// }
return ""; return "";
}; };
const validateConfirmPassword = (confirmPassword, password) => { const validateConfirmPassword = (confirmPassword: string, password: string) => {
if (!confirmPassword) { if (!confirmPassword) return "Please confirm your password";
return "Please confirm your password"; if (confirmPassword !== password) return "Passwords do not match";
}
if (confirmPassword !== password) {
return "Passwords do not match";
}
return ""; return "";
}; };
const handleInputChange = (field, value) => { const handleInputChange = (field: keyof FormDataState, value: string) => {
setForm({ ...formData, [field]: value }); setForm({ ...formData, [field]: value });
// Clear error when user starts typing // Clear error when user starts typing
if (errors[field]) { if (errors[field]) setErrors({ ...errors, [field]: "" });
setErrors({ ...errors, [field]: "" });
}
// If confirming password, also check if it matches // If password changes, check confirm password
if (field === "password" && formData.confirmPassword && errors.confirmPassword) { if (field === "password" && formData.confirmPassword && errors.confirmPassword) {
const confirmError = validateConfirmPassword(formData.confirmPassword, value); const confirmError = validateConfirmPassword(formData.confirmPassword, value);
if (!confirmError) { if (!confirmError) setErrors({ ...errors, password: "", confirmPassword: "" });
setErrors({ ...errors, [field]: "", confirmPassword: "" });
}
} }
}; };
@ -177,68 +118,40 @@ const RegisterForm = ({ onSwitchToLogin }) => {
const passwordError = validatePassword(formData.password); const passwordError = validatePassword(formData.password);
const confirmPasswordError = validateConfirmPassword(formData.confirmPassword, formData.password); const confirmPasswordError = validateConfirmPassword(formData.confirmPassword, formData.password);
const newErrors = { const newErrors = { firstName: firstNameError, lastName: lastNameError, email: emailError, password: passwordError, confirmPassword: confirmPasswordError };
firstName: firstNameError,
lastName: lastNameError,
email: emailError,
password: passwordError,
confirmPassword: confirmPasswordError,
};
setErrors(newErrors); setErrors(newErrors);
// If any errors exist, stop if (Object.values(newErrors).some(e => e !== "")) return;
if (Object.values(newErrors).some((error) => error !== "")) {
return;
}
setIsLoading(true); setIsLoading(true);
const trimmedData = {
firstName: formData.firstName.trim(),
lastName: formData.lastName.trim(),
email: formData.email.trim(),
password: formData.password,
confirmPassword: formData.confirmPassword,
};
try { try {
const res = await axios.post( const res = await axios.post(
`${import.meta.env.VITE_BASE_URI}/api/v1/auth/signup`, `${import.meta.env.VITE_BASE_URI}/api/v1/auth/signup`,
{ {
firstName: trimmedData.firstName, firstName: formData.firstName.trim(),
lastName: trimmedData.lastName, lastName: formData.lastName.trim(),
email: trimmedData.email, email: formData.email.trim(),
password: trimmedData.password, password: formData.password,
}, },
{ { withCredentials: true }
withCredentials: true,
headers: {
"ngrok-skip-browser-warning": "69420",
"Access-Control-Allow-Origin": "*",
},
}
); );
if (res.status === 201) { if (res.status === 201) {
TokenStorage.setTokens(res.data.accessToken, res.data.refreshToken);
navigate("/"); navigate("/");
} }
} catch (err: any) { } catch (err: any) {
if (err.response?.status === 409) { if (err.response?.status === 409) {
formErrorRef.current = { message: 'User Already Exists. Please try logging.', error: true } formErrorRef.current = { message: 'Email already exists. Try logging in.', error: true };
} else { } else {
formErrorRef.current = { message: 'Something went wrong. Please refresh and try again.', error: true } formErrorRef.current = { message: 'Something went wrong. Please refresh and try again.', error: true };
} }
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
const handleGoogleLogin = () => {
// Your Google OAuth logic here
console.log("Google signup clicked");
};
return ( return (
<div className="bg-white p-5 rounded-lg w-full max-w-lg"> <div className="bg-white p-5 rounded-lg w-full max-w-lg">
<div className="text-center mb-2"> <div className="text-center mb-2">
@ -257,9 +170,7 @@ const RegisterForm = ({ onSwitchToLogin }) => {
<div className="space-y-1.5"> <div className="space-y-1.5">
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Full Name</label>
Full Name
</label>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<input <input
@ -267,15 +178,10 @@ const RegisterForm = ({ onSwitchToLogin }) => {
value={formData.firstName} value={formData.firstName}
onChange={(e) => handleInputChange("firstName", e.target.value)} onChange={(e) => handleInputChange("firstName", e.target.value)}
placeholder="First name" placeholder="First name"
className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.firstName className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.firstName ? "border-red-500 focus:ring-red-500" : "border-gray-300 focus:ring-blue-500"}`}
? "border-red-500 focus:ring-red-500"
: "border-gray-300 focus:ring-blue-500"
}`}
disabled={isLoading} disabled={isLoading}
/> />
{errors.firstName && ( {errors.firstName && <p className="text-red-500 text-xs mt-0.5">{errors.firstName}</p>}
<p className="text-red-500 text-xs mt-0.5">{errors.firstName}</p>
)}
</div> </div>
<div> <div>
<input <input
@ -283,53 +189,36 @@ const RegisterForm = ({ onSwitchToLogin }) => {
value={formData.lastName} value={formData.lastName}
onChange={(e) => handleInputChange("lastName", e.target.value)} onChange={(e) => handleInputChange("lastName", e.target.value)}
placeholder="Last name" placeholder="Last name"
className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.lastName className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.lastName ? "border-red-500 focus:ring-red-500" : "border-gray-300 focus:ring-blue-500"}`}
? "border-red-500 focus:ring-red-500"
: "border-gray-300 focus:ring-blue-500"
}`}
disabled={isLoading} disabled={isLoading}
/> />
{errors.lastName && ( {errors.lastName && <p className="text-red-500 text-xs mt-0.5">{errors.lastName}</p>}
<p className="text-red-500 text-xs mt-0.5">{errors.lastName}</p>
)}
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Email Address</label>
Email Address
</label>
<input <input
type="email" type="email"
value={formData.email} value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)} onChange={(e) => handleInputChange("email", e.target.value)}
placeholder="Enter your email" placeholder="Enter your email"
className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.email className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none ${errors.email ? "border-red-500 focus:ring-red-500" : "border-gray-300 focus:ring-blue-500"}`}
? "border-red-500 focus:ring-red-500"
: "border-gray-300 focus:ring-blue-500"
}`}
disabled={isLoading} disabled={isLoading}
/> />
{errors.email && ( {errors.email && <p className="text-red-500 text-sm mt-1">{errors.email}</p>}
<p className="text-red-500 text-sm mt-1">{errors.email}</p>
)}
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Password</label>
Password
</label>
<div className="relative"> <div className="relative">
<input <input
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
value={formData.password} value={formData.password}
onChange={(e) => handleInputChange("password", e.target.value)} onChange={(e) => handleInputChange("password", e.target.value)}
placeholder="Enter your password" placeholder="Enter your password"
className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none pr-12 ${errors.password className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none pr-12 ${errors.password ? "border-red-500 focus:ring-red-500" : "border-gray-300 focus:ring-blue-500"}`}
? "border-red-500 focus:ring-red-500"
: "border-gray-300 focus:ring-blue-500"
}`}
disabled={isLoading} disabled={isLoading}
/> />
<button <button
@ -341,25 +230,18 @@ const RegisterForm = ({ onSwitchToLogin }) => {
{showPassword ? <FiEyeOff size={20} /> : <BsEye size={20} />} {showPassword ? <FiEyeOff size={20} /> : <BsEye size={20} />}
</button> </button>
</div> </div>
{errors.password && ( {errors.password && <p className="text-red-500 text-sm mt-1">{errors.password}</p>}
<p className="text-red-500 text-sm mt-1">{errors.password}</p>
)}
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">Confirm Password</label>
Confirm Password
</label>
<div className="relative"> <div className="relative">
<input <input
type={showConfirmPassword ? "text" : "password"} type={showConfirmPassword ? "text" : "password"}
value={formData.confirmPassword} value={formData.confirmPassword}
onChange={(e) => handleInputChange("confirmPassword", e.target.value)} onChange={(e) => handleInputChange("confirmPassword", e.target.value)}
placeholder="Confirm your password" placeholder="Confirm your password"
className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none pr-12 ${errors.confirmPassword className={`w-full px-4 py-1.5 border rounded-lg focus:ring-2 focus:border-transparent outline-none pr-12 ${errors.confirmPassword ? "border-red-500 focus:ring-red-500" : "border-gray-300 focus:ring-blue-500"}`}
? "border-red-500 focus:ring-red-500"
: "border-gray-300 focus:ring-blue-500"
}`}
disabled={isLoading} disabled={isLoading}
/> />
<button <button
@ -371,20 +253,15 @@ const RegisterForm = ({ onSwitchToLogin }) => {
{showConfirmPassword ? <FiEyeOff size={20} /> : <BsEye size={20} />} {showConfirmPassword ? <FiEyeOff size={20} /> : <BsEye size={20} />}
</button> </button>
</div> </div>
{errors.confirmPassword && ( {errors.confirmPassword && <p className="text-red-500 text-sm mt-1">{errors.confirmPassword}</p>}
<p className="text-red-500 text-sm mt-1">{errors.confirmPassword}</p>
)}
</div> </div>
<p className={`${formErrorRef.current.error ? 'text-red-500 text-center' : 'text-green-500 text-center'} `}>{formErrorRef.current.message}</p> <p className={`${formErrorRef.current.error ? 'text-red-500 text-center' : 'text-green-500 text-center'}`}>{formErrorRef.current.message}</p>
<button <button
onClick={handleFormRegisterClick} onClick={handleFormRegisterClick}
disabled={isLoading} disabled={isLoading}
className={`w-full mt-2 py-2 rounded-lg font-medium transition-colors ${isLoading className={`w-full mt-2 py-2 rounded-lg font-medium transition-colors ${isLoading ? "bg-gray-400 cursor-not-allowed" : "bg-blue-500 hover:bg-blue-600 text-white"}`}
? "bg-gray-400 cursor-not-allowed"
: "bg-blue-500 hover:bg-blue-600 text-white"
}`}
> >
{isLoading ? "Creating Account..." : "Create Account"} {isLoading ? "Creating Account..." : "Create Account"}
</button> </button>
@ -403,4 +280,4 @@ const RegisterForm = ({ onSwitchToLogin }) => {
); );
}; };
export default RegisterForm export default RegisterForm;

View File

@ -0,0 +1,175 @@
import axios from "axios";
import React, { useEffect, useState } from "react";
import { FiX, FiFileText, FiEye, FiSend, FiCheckCircle } from "react-icons/fi";
import { MdAccessTime, MdCancel } from "react-icons/md";
interface ClientAuditModalProps {
open: boolean;
onClose: () => void;
recipientId?: string;
}
const ClientAuditModal: React.FC<ClientAuditModalProps> = ({ open, onClose, recipientId }) => {
if (!open) return null;
const [auditStatus, setAuditStatus] = useState<any[]>([]);
const [details, setDetails] = useState<any>(null);
// const getAuditDetails = async () => {
// try {
// // fake API for demo
// const res = await ai("/api/v1/sign/" + recipientId + "/audit");
// const data = await res.json();
// setAuditStatus(data.events || []);
// setDetails(data);
// } catch (error) {
// console.error(error);
// }
// };
const getAuditDetails = async () => {
// setLoading(true)
try {
let res = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/v1/sign/${recipientId}/audit`, {
withCredentials: true,
headers: {
"ngrok-skip-browser-warning": "69420",
"Access-Control-Allow-Origin": "*",
},
})
// let res = await api.get(`/api/v1/accounts/${accountId}/transactions/${transactionId}/audit?lang=hi`)
if (res?.status === 200) {
// console.log(res?.data?.events)
// let auditResponse = res?.data?.events
setAuditStatus(res.data.events || []);
setDetails(res.data);
}
} catch (error) {
console.log(error)
} finally {
// setLoading(false)
}
}
useEffect(() => {
if (open) getAuditDetails();
}, [open]);
const getActionIcon = (action: string) => {
const icons: Record<string, JSX.Element> = {
CREATED: <FiFileText className="w-4 h-4 text-sky-500" />,
SENT: <FiSend className="w-4 h-4 text-blue-600" />,
INVITED: <FiSend className="w-4 h-4 text-indigo-600" />,
VIEWED: <FiEye className="w-4 h-4 text-purple-600" />,
SIGNED: <FiCheckCircle className="w-4 h-4 text-green-600" />,
CANCELLED: <MdCancel className="w-4 h-4 text-red-500" />,
EXPIRED: <MdAccessTime className="w-4 h-4 text-gray-500" />,
};
return icons[action] || <FiFileText className="w-4 h-4 text-gray-500" />;
};
return (
<div className="fixed inset-0 flex items-center justify-center backdrop-blur-sm bg-white/10 z-50 px-2">
<div className="bg-white rounded-md shadow-md w-full max-w-4xl h-[90vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between p-3 border-b bg-blue-600 text-white">
<h2 className="text-lg font-semibold">Transaction History</h2>
<button onClick={onClose}><FiX className="w-5 h-5" /></button>
</div>
<div className="overflow-y-auto">
{/* Envelope/Transaction Details */}
{details && (
<div className="p-4 border-b border-gray-200">
<h3 className="font-semibold mb-4">Transaction Details</h3>
<div className="pb-2 text-sm grid grid-cols-2 gap-6">
<div className="space-y-1">
<p className="flex flex-col text-sm">
<span className="font-medium">Subject:</span>
<span className="text-gray-600">{details.subject}</span>
</p>
<p className="flex flex-col text-sm">
<span className="font-medium">Transaction ID:</span>
<span className="text-gray-600">{details.transactionId}</span>
</p>
<p className="flex flex-col text-sm">
<span className="font-medium">Created:</span>
<span className="text-gray-600">{new Date(details.createAt).toLocaleString()}</span>
</p>
<p className="flex flex-col text-sm">
<span className="font-medium">Sent:</span>
<span className="text-gray-600">{new Date(details.sentAt).toLocaleString()}</span>
</p>
</div>
<div className="space-y-2">
<div>
<p className="flex flex-col text-sm">
<span className="font-medium">Sender:</span>
<span className="text-gray-600">{details.sender?.name} ({details.sender?.email})</span>
</p>
</div>
<div>
<p className="font-medium">Recipients:</p>
<ul className="ml-4 list-disc text-gray-600">
{details.recipients?.map((r: any) => (
<li key={r.id}>{r.name} ({r.email})</li>
))}
</ul>
</div>
<div>
<p className="font-medium">Documents:</p>
<ul className="ml-4 list-disc text-gray-600">
{details.documents?.map((d: any) => (
<li key={d.id}>{d.name}</li>
))}
</ul>
</div>
</div>
</div>
</div>
)}
{/* Activity History */}
<div className="flex-1 p-4">
<h3 className="font-semibold mb-4 ">Activity History</h3>
<div className="space-y-3">
{auditStatus.map((item, idx) => (
<div key={idx} className="flex items-start space-x-3 border-b border-gray-200 pb-2">
{getActionIcon(item.action.toUpperCase())}
<div>
<p className="text-gray-700">{item.message}</p>
<p className="text-xs text-gray-500">
{new Date(item.timestamp).toLocaleString()} | {item.status}
</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* Footer */}
<div className="p-3 border-t flex justify-between text-sm border-gray-200 bg-gray-50">
<span>Showing {auditStatus.length} events</span>
<button
onClick={onClose}
className="px-4 py-1 bg-red-600 text-white rounded-md"
>
Close
</button>
</div>
</div>
</div>
);
};
export default ClientAuditModal;

View File

@ -0,0 +1,14 @@
import { FaDownload } from 'react-icons/fa'
const ClientDownloadBtn = () => {
return (
<>
<button className="text-xs bg-[var(--primary)] rounded-md text-white flex items-center gap-1 w-full cursor-pointer text-left px-4 py-2">
<FaDownload className="sm" />
<span>Download</span>
</button>
</>
)
}
export default ClientDownloadBtn

View File

@ -2,6 +2,7 @@ import { useState, useRef, useEffect } from "react";
import { FaSignature, FaDownload, FaUserEdit, FaTimesCircle, FaAngleLeft, FaAngleRight } from "react-icons/fa"; import { FaSignature, FaDownload, FaUserEdit, FaTimesCircle, FaAngleLeft, FaAngleRight } from "react-icons/fa";
import { BsThreeDotsVertical } from "react-icons/bs"; import { BsThreeDotsVertical } from "react-icons/bs";
import ClientDownloadDocument from "./ClientDownloadDocument"; import ClientDownloadDocument from "./ClientDownloadDocument";
import { AiOutlineAudit } from "react-icons/ai";
interface Props { interface Props {
transparentTags?: boolean; transparentTags?: boolean;
@ -10,6 +11,9 @@ interface Props {
onDownloadClick?: () => void; onDownloadClick?: () => void;
onDeclineClick?: () => void; onDeclineClick?: () => void;
onReassignClick?: () => void; onReassignClick?: () => void;
onAuditHistoryClick?: () => void;
showDecAndReassign?: boolean;
transactionId: string;
} }
export default function SignButton({ export default function SignButton({
@ -19,6 +23,9 @@ export default function SignButton({
onDownloadClick, onDownloadClick,
onDeclineClick, onDeclineClick,
onReassignClick, onReassignClick,
onAuditHistoryClick,
showDecAndReassign,
transactionId
}: Props) { }: Props) {
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
@ -41,8 +48,8 @@ export default function SignButton({
return ( return (
<> <>
<div className="relative inline-block" ref={menuRef}> {showDecAndReassign && <div className="relative inline-block" ref={menuRef}>
{(!transparentTags && !hideTags) ? ( {(!transparentTags && !hideTags) &&
<div className="flex items-center"> <div className="flex items-center">
{/* Sign Button */} {/* Sign Button */}
{/* <button {/* <button
@ -56,6 +63,7 @@ export default function SignButton({
</button> */} </button> */}
{/* Three Dots Dropdown Trigger */} {/* Three Dots Dropdown Trigger */}
<button <button
onClick={() => setMenuOpen((prev) => !prev)} onClick={() => setMenuOpen((prev) => !prev)}
className="py-2 rounded-full focus:outline-none transition ml-2" className="py-2 rounded-full focus:outline-none transition ml-2"
@ -83,6 +91,18 @@ export default function SignButton({
<span>Download Document</span> <span>Download Document</span>
</button> </button>
</li> */} </li> */}
<li>
<button
onClick={() => {
onAuditHistoryClick?.();
setMenuOpen(false);
}}
className="flex items-center gap-3 w-full cursor-pointer text-left px-4 py-2 hover:bg-gray-100"
>
<AiOutlineAudit />
<span>Audit History</span>
</button>
</li>
<li> <li>
<button <button
onClick={() => { onClick={() => {
@ -111,16 +131,19 @@ export default function SignButton({
</div> </div>
)} )}
</div> </div>
) : ( }
<p className="text-xs text-green-600 font-medium">Signed Successfully</p> </div>}
)}
</div>
{!showDecAndReassign &&
<div>
<button
onClick={() => setShowMenuDownload({ open: true, transactionId })}
className="text-xs bg-[var(--primary)] rounded-md text-white flex items-center gap-1 w-full cursor-pointer text-left px-4 py-2">
<FaDownload className="sm" />
<span>Download</span>
</button>
</div>
}
{showMenuDownload.open && ( {showMenuDownload.open && (
<ClientDownloadDocument <ClientDownloadDocument

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import axios from "axios" import axios from "axios"
import { RootState } from "../../store/store"; import { RootState } from "../../store/store";
import { Link, useNavigate, useParams } from "react-router-dom" import { useNavigate, useParams } from "react-router-dom"
import { DroppedItem, TagsTypes } from "../../utlity/types"; import { DroppedItem, TagsTypes } from "../../utlity/types";
import Loader from "../dashboard/Loader"; import Loader from "../dashboard/Loader";
@ -11,9 +11,9 @@ import { Bounce, toast } from "react-toastify";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { hideLoader, showLoader } from "../../store/slices/loaderSlice"; import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
import { FaSignature, FaCheck, FaQuestionCircle, FaDownload, FaAngleLeft, FaAngleRight } from 'react-icons/fa' import { FaCheck, FaQuestionCircle, FaDownload } from 'react-icons/fa'
import { IoIosDocument, IoMdHome } from 'react-icons/io' import { IoIosDocument } from 'react-icons/io'
import { MdKeyboardArrowDown, MdLabel } from 'react-icons/md' import { MdKeyboardArrowDown } from 'react-icons/md'
import confetti from 'canvas-confetti' import confetti from 'canvas-confetti'
import dayjs from "dayjs"; import dayjs from "dayjs";
@ -22,7 +22,6 @@ import StampUpload from "../stamp/StampUpload";
import DocumentPages from "./DocumentPages"; import DocumentPages from "./DocumentPages";
import FieldList from "../process/processStepTwo/FieldList"; import FieldList from "../process/processStepTwo/FieldList";
import typeToIconMap from "../../helper/iconMap"; import typeToIconMap from "../../helper/iconMap";
import SignCompletionScreen from "./SignCompletionScreen";
import RightSideBar from "../process/processStepTwo/rightSideBar/RightSideBar"; import RightSideBar from "../process/processStepTwo/rightSideBar/RightSideBar";
import TagsCon from "./TagsCon"; import TagsCon from "./TagsCon";
@ -30,11 +29,11 @@ import ConsentScreen from "./ConsentScreen";
import SignButton from "./SignButton"; import SignButton from "./SignButton";
import DeclineModal from "./DeclineModal"; import DeclineModal from "./DeclineModal";
import ReassignModal from "./ReassignModal"; import ReassignModal from "./ReassignModal";
import { ImCancelCircle } from "react-icons/im";
import ReassignSuccess from "./ReassignSuccess";
import CompletionScreen from "./CompletionScreen"; import CompletionScreen from "./CompletionScreen";
import { BsPrinter } from "react-icons/bs";
import { GrPrint } from "react-icons/gr"; import { GrPrint } from "react-icons/gr";
import ClientDownloadBtn from "./ClientDownloadBtn";
import ClientAuditModal from "./ClientAuditModal";
@ -168,6 +167,7 @@ const SignDocument = () => {
const [requiredFilled, setRequiredFilled] = useState<boolean>(false); const [requiredFilled, setRequiredFilled] = useState<boolean>(false);
const [horizontalFormLoader, setHorizontalFormLoader] = useState<{ requiredFilled: number, totalRequired: number | null }>({ requiredFilled: 0, totalRequired: null });
const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" }); const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" });
@ -177,6 +177,7 @@ const SignDocument = () => {
}); });
const [declineOpen, setDeclineOpen] = useState<boolean>(false); const [declineOpen, setDeclineOpen] = useState<boolean>(false);
const [reassignOpen, setReassignOpen] = useState<boolean>(false); const [reassignOpen, setReassignOpen] = useState<boolean>(false);
const [auditHistoryOpen, setAuditHistoryOpen] = useState<boolean>(false);
const [rightSideNavToggle, setRightSideNavToggle] = useState<RightSideNav>(RightSideNav.PAGES); const [rightSideNavToggle, setRightSideNavToggle] = useState<RightSideNav>(RightSideNav.PAGES);
@ -246,6 +247,21 @@ const SignDocument = () => {
fetchData(); fetchData();
}, []) }, [])
// Ping Request - START
useEffect(() => {
const intervalId = setInterval(() => {
axios.get(
`${import.meta.env.VITE_BASE_URI}/api/v1/session/ping?sessionId=${recipientId}`,
{
headers: { "ngrok-skip-browser-warning": "69420" },
}
).catch(err => console.error("Ping failed:", err));
}, 60000); // first call happens after 1 min
return () => clearInterval(intervalId); // cleanup on unmount
}, [recipientId]);
// Ping Request - END
useEffect(() => { useEffect(() => {
if (!focusElement) { if (!focusElement) {
if (droppedItems && droppedItems.length) { if (droppedItems && droppedItems.length) {
@ -356,14 +372,14 @@ const SignDocument = () => {
setIsFields(false) setIsFields(false)
} }
if (res?.data?.status === "COMPLETED" || res?.data?.status === "DECLINED") { if (res?.data?.status === "COMPLETED" || res?.data?.status === "DECLINED" || res?.data?.details.type == "CC") {
setShowCompletionScreen(true) // setShowCompletionScreen(true)
setTransparentTags(true)
setHideTags(true)
} }
dispatch(hideLoader()); dispatch(hideLoader());
// setTimeout(() => {
// }, 600);
} }
}) })
@ -372,21 +388,6 @@ const SignDocument = () => {
}); });
} }
const handleToastError = (msg: string) => {
toast.dismiss();
toast.error(msg, {
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}
const handleFormSubmit = async () => { const handleFormSubmit = async () => {
const submitData: TagsSubmitDataObj = { const submitData: TagsSubmitDataObj = {
tags: [] tags: []
@ -402,13 +403,13 @@ const SignDocument = () => {
} }
if (signInitialsCount !== Object.keys(signConfObj).length) { if (signInitialsCount !== Object.keys(signConfObj).length) {
handleToastError("Please ensure all signature and initials are signed") // handleToastError("Please ensure all signature and initials are signed")
return; return;
} }
for (const tag of droppedItems) { for (const tag of droppedItems) {
if ("required" in tag && tag.required && ((tag.type !== "SIGNATURE" && tag.type !== "INITIALS") && (tag.value === undefined || tag.value === null || tag.value === "" || !("value" in tag)))) { if ("required" in tag && tag.required && ((tag.type !== "SIGNATURE" && tag.type !== "INITIALS") && (tag.value === undefined || tag.value === null || tag.value === "" || !("value" in tag)))) {
handleToastError(`${tag.label} is required`) // handleToastError(`${tag.label} is required`)
return; return;
} }
@ -444,8 +445,10 @@ const SignDocument = () => {
dispatch(hideLoader()); dispatch(hideLoader());
setTransparentTags(true) setTransparentTags(true)
setShowConfetti(true) setShowConfetti(true)
setShowCompletionScreen(true) // setShowCompletionScreen(true)
setSelectedTag(null) setSelectedTag(null)
// navigate("/transaction-complete")
window.location.href = `/transaction-complete`
} }
} catch (e) { } catch (e) {
dispatch(hideLoader()); dispatch(hideLoader());
@ -464,6 +467,11 @@ const SignDocument = () => {
return; return;
} }
if (requiredFilled && focusElement.required == true) {
handleFormSubmit();
return;
}
// If FocusElement is present updating in Based on Next and Prev // If FocusElement is present updating in Based on Next and Prev
const currFocusElemIndx = focusElement.focusElementIndx; const currFocusElemIndx = focusElement.focusElementIndx;
if (currFocusElemIndx === undefined) return; if (currFocusElemIndx === undefined) return;
@ -485,19 +493,6 @@ const SignDocument = () => {
} }
} }
const fetchSignatureData = async (signatureId: string) => {
try {
const response = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/v1/sign/${recipientId}signatures/${signatureId}`, {
headers: { "ngrok-skip-browser-warning": "69420" }
});
if (response.status === 200) {
return response.data;
}
} catch (error) {
}
return null;
}
const handlePrefillValues = () => { const handlePrefillValues = () => {
const prefilledTags = ["NAME", "EMAIL", "TEXT", "COMPANY", "TEMPORAL"] const prefilledTags = ["NAME", "EMAIL", "TEXT", "COMPANY", "TEMPORAL"]
@ -547,17 +542,24 @@ const SignDocument = () => {
if (droppedItems.length > 0) { if (droppedItems.length > 0) {
handlePrefillValues(); handlePrefillValues();
} }
}, [droppedItems.length]) }, [droppedItems])
useEffect(() => { useEffect(() => {
const allRequiredFields = droppedItems.filter((item, idx) => { const allRequiredFields = droppedItems.filter((item, idx) => {
if (!item.required) return false if (!item.required) return false
if ((item.required && item.value) || idx in signConfObj) return false if ((item.required && item.value && item.type != "SIGNATURE" && item.type != "INITIALS") || (idx in signConfObj && saved.save)) return false
return true return true
}) })
const totalRequiredFields = droppedItems.reduce((acc, obj) => {
if (obj.required == true) return acc + 1;
return acc;
}, 0)
setHorizontalFormLoader({ requiredFilled: (totalRequiredFields - allRequiredFields.length), totalRequired: totalRequiredFields })
setRequiredFilled(allRequiredFields.length ? false : true) setRequiredFilled(allRequiredFields.length ? false : true)
}, [droppedItems, signConfObj]) }, [droppedItems, signConfObj])
@ -653,7 +655,6 @@ const SignDocument = () => {
if (setTo !== isFields) setIsFields(setTo); if (setTo !== isFields) setIsFields(setTo);
} }
const handleOnConsent = () => { const handleOnConsent = () => {
setSignerConsent((prev) => { setSignerConsent((prev) => {
return { ...prev, consent: true } return { ...prev, consent: true }
@ -662,15 +663,17 @@ const SignDocument = () => {
const handleDeclineConfirm = async (reason: string, comments: string) => { const handleDeclineConfirm = async (reason: string, comments: string) => {
try { try {
await axios.post(`${import.meta.env.VITE_BASE_URI}/api/v1/sign/decline/${recipientId}`, { reason: reason + ", " + comments }); await axios.post(`${import.meta.env.VITE_BASE_URI}/api/v1/sign/${recipientId}/decline`, { reason: reason + ", " + comments });
setSignerConsent((prev) => ({ ...prev, decline: true })); setSignerConsent((prev) => ({ ...prev, decline: true }));
setRecSignStatus("DECLINED"); setRecSignStatus("DECLINED");
setShowCompletionScreen(true); // setShowCompletionScreen(true);
// navigate("/transaction-declined")
window.location.href = `/transaction-declined`
} catch (e) { } } catch (e) { }
} }
const handleReassignConfirm = (data: { firstName: string; lastName: string; email: string; message: string }) => { const handleReassignConfirm = (data: { firstName: string; lastName: string; email: string; message: string }) => {
axios.post(`${import.meta.env.VITE_BASE_URI}/api/v1/sign/reassign/${recipientId}`, { axios.post(`${import.meta.env.VITE_BASE_URI}/api/v1/sign/${recipientId}/reassign`, {
name: data.firstName ? data.firstName + " " + data.lastName : data.lastName, name: data.firstName ? data.firstName + " " + data.lastName : data.lastName,
email: data.email, email: data.email,
message: data.message, message: data.message,
@ -678,7 +681,9 @@ const SignDocument = () => {
}) })
.then((res) => { .then((res) => {
if (res.status === 202) { if (res.status === 202) {
setShowCompletionScreen(true); // setShowCompletionScreen(true);
// navigate("/transaction-reassigned")
window.location.href = `/transaction-reassigned`
} }
}) })
.catch((err) => { .catch((err) => {
@ -698,20 +703,6 @@ const SignDocument = () => {
}; };
} }
// Utility to check if element is out of viewport
function isOutOfViewport(el: HTMLElement) {
const rect = el.getBoundingClientRect();
const windowHeight = window.innerHeight;
const windowWidth = window.innerWidth;
return (
rect.right < 0 ||
rect.bottom < 0 ||
rect.left > windowWidth ||
rect.top > windowHeight
);
}
function printDocConDiv() { function printDocConDiv() {
var printContents = document.getElementById(divId).innerHTML; var printContents = document.getElementById(divId).innerHTML;
var originalContents = document.body.innerHTML; var originalContents = document.body.innerHTML;
@ -755,31 +746,32 @@ const SignDocument = () => {
}; };
}, []); }, []);
// if (showCompletionScreen) {
if (showCompletionScreen) { // if (recSignStatus === "COMPLETED") {
if (recSignStatus === "COMPLETED") { // return <CompletionScreen />
return <CompletionScreen /> // } else if (recSignStatus === "DECLINED") {
} else if (recSignStatus === "DECLINED") { // return <CompletionScreen hit="Action Canceled! " title="Maybe Next Time!" message={"Your request has been declined successfully. You can revisit anytime if you change your mind."} textColorSuccess={false} />
return <CompletionScreen hit="Action Canceled! " title="Maybe Next Time!" message={"Your request has been declined successfully. You can revisit anytime if you change your mind."} textColorSuccess={false} /> // } else {
} else { // return <CompletionScreen />
return <CompletionScreen /> // }
} // }
}
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' : ''}`}>
{!showCompletionScreen && <>
{/* <TermsModal showTerms={showTerms} setShowTerms={setShowTerms} recipientId={recipientId || ""} /> */} {/* <TermsModal showTerms={showTerms} setShowTerms={setShowTerms} recipientId={recipientId || ""} /> */}
{/* {showCompletionScreen && <SignCompletionScreen />} */} {/* {showCompletionScreen && <SignCompletionScreen />} */}
<div className="bg-slate-100" <div className="bg-slate-100"
// onSubmit={(e: React.FormEvent<HTMLFormElement>) => handleFormSubmit(e)} // onSubmit={(e: React.FormEvent<HTMLFormElement>) => handleFormSubmit(e)}
> >
{/* Nav Two - Options - START */} {/* Nav Two - Options - START */}
<div className="flex flex-col w-full">
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)]'> <div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)]'>
<img className="w-[10%] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" /> <img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
{/* Right Side - STARTS */} {/* Right Side - STARTS */}
<div className='flex justify-center items-center'> <div className='flex justify-center items-center'>
@ -816,9 +808,9 @@ const SignDocument = () => {
</> </>
{/* PRINT _ START */} {/* PRINT _ START */}
<GrPrint {/* <GrPrint
onClick={printDocConDiv} onClick={printDocConDiv}
className="text-white mr-2 cursor-pointer" /> className="text-white mr-2 cursor-pointer" /> */}
{/* PRINT _ END */} {/* PRINT _ END */}
@ -842,21 +834,18 @@ const SignDocument = () => {
<p className="text-xs">Signed Successfully</p> <p className="text-xs">Signed Successfully</p>
} */} } */}
{recipient?.type !== "CC" ? <SignButton <SignButton
transparentTags={transparentTags} transparentTags={transparentTags}
hideTags={hideTags} hideTags={hideTags}
handleFormSubmit={handleFormSubmit} handleFormSubmit={handleFormSubmit}
onDeclineClick={() => setDeclineOpen(true)} onDeclineClick={() => setDeclineOpen(true)}
onReassignClick={() => setReassignOpen(true)} onReassignClick={() => setReassignOpen(true)}
/> : <button onAuditHistoryClick={() => setAuditHistoryOpen(true)}
className="text-xs bg-[var(--primary)] rounded-md text-white flex items-center gap-1 w-full cursor-pointer text-left px-4 py-2 hover:bg-gray-100" showDecAndReassign={recipient?.type !== "CC"}
> transactionId={"1"}
<FaDownload className="sm" /> />
<span>Download</span>
</button>}
{!transparentTags && recipient?.type !== "CC" && <>
<>
<div className={`absolute z-50 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] bottom-0 left-0 bg-white px-4 py-2 flex justify-between items-center gap-4`}> <div className={`absolute z-50 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] bottom-0 left-0 bg-white px-4 py-2 flex justify-between items-center gap-4`}>
<p className='text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="text-[var(--mainPrimaryBtn)]">Terms & Conditions.</a></p> <p className='text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="text-[var(--mainPrimaryBtn)]">Terms & Conditions.</a></p>
{/* <div className='flex items-center gap-8 cursor-pointer text-xs px-2 py-1 rounded-sm outline-none'> {/* <div className='flex items-center gap-8 cursor-pointer text-xs px-2 py-1 rounded-sm outline-none'>
@ -912,11 +901,10 @@ const SignDocument = () => {
{/* FIT - END */} {/* FIT - END */}
</div> </div>
</div> </div>
</> </>}
{requiredFilled && !transparentTags && recipient?.type !== "CC" && <>
{requiredFilled && <>
<div className={`absolute z-50 ${requiredFilled ? "bottom-0" : "bottom-[-100px] hidden"} left-0 bg-[var(--primary)] w-full flex justify-around items-center transition-all duration-300 px-2.5`}> <div className={`absolute z-50 ${requiredFilled ? "bottom-0" : "bottom-[-100px] hidden"} left-0 bg-[var(--primary)] w-full flex justify-around items-center transition-all duration-300 px-2.5`}>
<div className="py-6 text-white"> <div className="py-6 text-white">
<p className="text-2xl font-semibold">All Set to Wrap This Up?</p> <p className="text-2xl font-semibold">All Set to Wrap This Up?</p>
@ -932,194 +920,217 @@ const SignDocument = () => {
</div> </div>
{/* Right Side - ENDS */} {/* Right Side - ENDS */}
</div> </div>
{/* Nav Two - Options - End */}
{/* Document Container Start */} {/* Horizontal Loader */}
<div className="grid grid-cols-12 gap-1 mt-1 h-[calc(100vh-48px)] overflow-hidden"> {horizontalFormLoader.totalRequired !== null && !transparentTags && (
<div className="w-full h-[4px] bg-slate-400 relative">
<div
className="h-full bg-yellow-300 transition-all duration-300"
style={{
width: `${Math.min(
(horizontalFormLoader.requiredFilled /
horizontalFormLoader.totalRequired) *
100
)}%`,
}}
/>
</div>
)}
</div>
{/* Left Side pages start */} {/* Nav Two - Options - End */}
{/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
<div
id="tags-list-client-side"
className={`absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-[100vh] p-4 overflow-y-auto ${recipient?.type !== "CC" ? "bg-white" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
<div className='flex flex-col md:flex-row justify-between items-center bg-slate-100 p-1.5 rounded-2xl border-1 border-gray-200 mb-3'> {/* Document Container Start */}
{provideTags && recipient?.type !== "CC" && <div <div className="grid grid-cols-12 gap-1 mt-1 h-[calc(100vh-40px)] overflow-hidden">
onClick={() => handleSetIsFields(true)}
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isFields ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300'}`}
>
<IoIosDocument />
<p className='tracking-wide'>Tags</p>
</div>}
<div {/* Left Side pages start */}
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${!isFields ? 'bg-[var(--primary)] text-white' : ''} ${!provideTags ? "sm:hidden" : ""} transition-all duration-300`} {/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
onClick={() => handleSetIsFields(provideTags ? false : !isFields)} <div
> id="tags-list-client-side"
<IoIosDocument /> className={`absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-[100vh] p-4 overflow-y-auto ${recipient?.type !== "CC" ? "bg-white" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
<p className='tracking-wide'>Pages</p>
</div>
{/* <p className="hidden sm:block text-xs text-white">Hi! {recipient && recipient.firstName} {recipient && recipient.lastName}</p> */}
{!transparentTags && <div className='flex flex-col md:flex-row justify-between items-center bg-slate-100 p-1.5 rounded-2xl border-1 border-gray-200 mb-3'>
{provideTags && recipient?.type !== "CC" && <div
onClick={() => handleSetIsFields(true)}
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isFields ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300'}`}
>
<IoIosDocument />
<p className='tracking-wide'>Tags</p>
</div>}
<div
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${!isFields ? 'bg-[var(--primary)] text-white' : ''} ${!provideTags ? "sm:hidden" : ""} transition-all duration-300`}
onClick={() => handleSetIsFields(provideTags ? false : !isFields)}
>
<IoIosDocument />
<p className='tracking-wide'>Pages</p>
</div> </div>
{isFields && recipientId && provideTags && recipient?.type !== "CC" && <FieldList color={"#8f87f1"} recipientId={Number(recipientId)} recipientIndx={0} acntId={"1"} />} {/* <p className="hidden sm:block text-xs text-white">Hi! {recipient && recipient.firstName} {recipient && recipient.lastName}</p> */}
</div>}
{!isFields && <DocumentPages {!transparentTags && isFields && recipientId && provideTags && recipient?.type !== "CC" && <FieldList color={"#8f87f1"} recipientId={Number(recipientId)} recipientIndx={0} acntId={"1"} />}
selectedDocuments={selectedDocuments}
setDropDownTogglePage={setDropDownTogglePage}
dropDownTogglePage={dropDownTogglePage} />}
</div> {!isFields && <DocumentPages
{/* Pages - END */} selectedDocuments={selectedDocuments}
setDropDownTogglePage={setDropDownTogglePage}
dropDownTogglePage={dropDownTogglePage} />}
{/* Main container */} </div>
<div {/* Pages - END */}
id="doc-con"
className={`zoom-con relative ${provideTags ? (isFields && selectedTag !== null ? "col-span-8" : "col-span-10") : (isFields ? "col-span-12" : "col-span-10")} sm:px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth transition-all duration-200 pb-[4rem]`}
>
{
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
return (
<div key={doc.documentId} className='flex flex-col'>
<div className='py-2 text-sm'>
<p className='text-center'>{doc.name}</p>
</div>
<div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'> {/* Main container */}
{ <div
doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => { id="doc-con"
const key = `${doc.documentId}_${page.index}`; className={`zoom-con relative ${provideTags ? (isFields && selectedTag !== null ? "col-span-8" : "col-span-10") : (isFields ? "col-span-12" : "col-span-10")} sm:px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth transition-all duration-200 pb-[4rem]`}
// const tagJsonData = [...droppedItems]; >
const currDocTags = droppedItems.filter((val) => val.pageIndex === page.index) {
let scale: { x: number | null, y: number | null } = { x: null, y: null } selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
return ( return (
<div <div key={doc.documentId} className='flex flex-col'>
key={indx} <div className='py-2 text-sm'>
id={doc.documentId + indx} <p className='text-center'>{doc.name}</p>
onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)} </div>
onDragOver={(e) => handleDragOver(e)}
className='drop-page-in shadow-md relative'
>
<img <div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'>
// style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }} {
style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }} doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
draggable="false" const key = `${doc.documentId}_${page.index}`;
onLoad={(imgElem) => { // const tagJsonData = [...droppedItems];
setPageScales((prev) => ({ const currDocTags = droppedItems.filter((val) => val.pageIndex === page.index)
...prev, let scale: { x: number | null, y: number | null } = { x: null, y: null }
[page.index]: { return (
x: imgElem.clientWidth / page.width, <div
y: imgElem.clientHeight / page.height, key={indx}
}, id={doc.documentId + indx}
})); onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)}
}} onDragOver={(e) => handleDragOver(e)}
width={page.width} className='drop-page-in shadow-md relative'
height={page.height} >
src={page.src}
<img
// style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }}
draggable="false"
onLoad={(imgElem) => {
setPageScales((prev) => ({
...prev,
[page.index]: {
x: imgElem.clientWidth / page.width,
y: imgElem.clientHeight / page.height,
},
}));
}}
width={page.width}
height={page.height}
src={page.src}
/>
{!hideTags && <>
<TagsCon
scale={pageScales[page.index] || { x: 1, y: 1 }}
currDocTags={currDocTags}
pageIdkey={key}
provideTags={provideTags}
documentId={doc.documentId}
pageIndex={page.index}
hideTags={hideTags}
transparentTags={transparentTags}
focusElement={focusElement}
selectedTag={selectedTag}
droppedItems={droppedItems}
saved={saved}
signConfObj={signConfObj}
recipientId={recipientId as string}
setSaved={setSaved}
setStamp={setStamp}
setSignConfObj={setSignConfObj}
setOpenSign={setOpenSign}
setSignTagId={setSignTagId}
setDroppedItems={setDroppedItems}
setSelectedTag={setSelectedTag}
setFocusElement={setFocusElement}
handleTagNavClick={handleTagNavClick}
/> />
</>}
{!hideTags && <> {/* Side details of documents Pages and document Name - START */}
<TagsCon < p className='hidden sm:block absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-50px] text-slate-600' >
scale={pageScales[page.index] || { x: 1, y: 1 }} page {indx + 1} of {doc.pages && doc.pages.length}
currDocTags={currDocTags} </p>
pageIdkey={key} {/* Side details of documents Pages and document Name - END */}
provideTags={provideTags}
documentId={doc.documentId}
pageIndex={page.index}
hideTags={hideTags}
transparentTags={transparentTags}
focusElement={focusElement}
selectedTag={selectedTag}
droppedItems={droppedItems}
saved={saved}
signConfObj={signConfObj}
recipientId={recipientId as string}
setSaved={setSaved}
setStamp={setStamp}
setSignConfObj={setSignConfObj}
setOpenSign={setOpenSign}
setSignTagId={setSignTagId}
setDroppedItems={setDroppedItems}
setSelectedTag={setSelectedTag}
setFocusElement={setFocusElement}
handleTagNavClick={handleTagNavClick}
/>
</>}
{/* Side details of documents Pages and document Name - START */}
< p className='hidden sm:block absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-50px] text-slate-600' >
page {indx + 1} of {doc.pages && doc.pages.length}
</p>
{/* Side details of documents Pages and document Name - END */}
{focusElement !== null && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index && {!transparentTags && focusElement !== null && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index &&
<div <div
id="tag-navigation-btn" id="tag-navigation-btn"
className={`absolute right-0 translate-x-[100%] flex flex-col justify-center items-center z-50 cursor-pointer transition-all duration-300`} className={`absolute right-0 translate-x-[100%] flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
style={{ top: (focusElement.dimensions.top + focusElement.dimensions.height / 2) + "px" }} style={{ top: (focusElement.dimensions.top) + "px" }}
onClick={() => handleTagNavClick("N")} onClick={() => handleTagNavClick("N")}
> >
{/* NEXT Button - START */}
<svg width="70" height="45" viewBox="0 0 112 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.87927 20.8542L15.0554 2.53357C15.7668 1.54438 16.9108 0.958008 18.1292 0.958008H108.049C110.14 0.958008 111.835 2.65317 111.835 4.74426V41.0235C111.835 43.1146 110.14 44.8098 108.049 44.8098H18.1084C16.9016 44.8098 15.7671 44.2345 15.054 43.261L1.89871 25.3024C0.930133 23.9802 0.92229 22.1849 1.87927 20.8542Z" fill="#005AB5" />
<path d="M53.2649 16.7474V28.3838H51.3899L45.907 20.4577H45.8104V28.3838H43.7024V16.7474H45.5888L51.0661 24.6792H51.1683V16.7474H53.2649ZM58.8545 28.5542C57.9795 28.5542 57.2238 28.3724 56.5875 28.0088C55.9549 27.6414 55.4682 27.1224 55.1272 26.452C54.7863 25.7777 54.6159 24.9842 54.6159 24.0713C54.6159 23.1736 54.7863 22.3857 55.1272 21.7077C55.4719 21.0258 55.953 20.4955 56.5704 20.1167C57.1879 19.7342 57.9132 19.5429 58.7466 19.5429C59.2844 19.5429 59.792 19.63 60.2693 19.8042C60.7504 19.9747 61.1746 20.2398 61.542 20.5997C61.9132 20.9595 62.2049 21.4179 62.417 21.9747C62.6291 22.5277 62.7352 23.1868 62.7352 23.952V24.5827H55.5818V23.1963H60.7636C60.7598 22.8023 60.6746 22.452 60.5079 22.1452C60.3413 21.8345 60.1083 21.5902 59.8091 21.4122C59.5136 21.2342 59.1689 21.1452 58.775 21.1452C58.3545 21.1452 57.9852 21.2474 57.667 21.452C57.3488 21.6527 57.1007 21.9179 56.9227 22.2474C56.7485 22.5732 56.6594 22.9311 56.6557 23.3213V24.5315C56.6557 25.0391 56.7485 25.4747 56.9341 25.8383C57.1197 26.1982 57.3791 26.4747 57.7125 26.6679C58.0458 26.8573 58.436 26.952 58.8829 26.952C59.1822 26.952 59.453 26.9103 59.6954 26.827C59.9379 26.7398 60.1481 26.613 60.3261 26.4463C60.5041 26.2796 60.6386 26.0732 60.7295 25.827L62.65 26.0429C62.5288 26.5505 62.2977 26.9936 61.9568 27.3724C61.6197 27.7474 61.1879 28.0391 60.6613 28.2474C60.1348 28.452 59.5325 28.5542 58.8545 28.5542ZM65.4896 19.6565L67.2509 22.8781L69.0407 19.6565H71.2168L68.5862 24.0202L71.2623 28.3838H69.0975L67.2509 25.2417L65.4214 28.3838H63.2396L65.8987 24.0202L63.3078 19.6565H65.4896ZM76.6857 19.6565V21.2474H71.6687V19.6565H76.6857ZM72.9073 17.5656H74.9641V25.7588C74.9641 26.0353 75.0058 26.2474 75.0891 26.3952C75.1762 26.5391 75.2899 26.6376 75.43 26.6906C75.5702 26.7436 75.7255 26.7702 75.8959 26.7702C76.0247 26.7702 76.1421 26.7607 76.2482 26.7417C76.3581 26.7228 76.4414 26.7058 76.4982 26.6906L76.8448 28.2986C76.735 28.3364 76.5778 28.3781 76.3732 28.4236C76.1725 28.469 75.9262 28.4955 75.6346 28.5031C75.1194 28.5183 74.6554 28.4406 74.2425 28.2702C73.8296 28.0959 73.502 27.827 73.2596 27.4633C73.0209 27.0997 72.9035 26.6452 72.9073 26.0997V17.5656Z" fill="white" />
</svg>
{/* NEXT Button - END */} <svg width="100" height="35" viewBox="0 0 112 45" fill="none" xmlns="http://www.w3.org/2000/svg">
</div>} <path d="M1.87927 20.8542L15.0554 2.53357C15.7668 1.54438 16.9108 0.958008 18.1292 0.958008H108.049C110.14 0.958008 111.835 2.65317 111.835 4.74426V41.0235C111.835 43.1146 110.14 44.8098 108.049 44.8098H18.1084C16.9016 44.8098 15.7671 44.2345 15.054 43.261L1.89871 25.3024C0.930133 23.9802 0.92229 22.1849 1.87927 20.8542Z" fill="#005AB5" />
</div> {/* <path d="M53.2649 16.7474V28.3838H51.3899L45.907 20.4577H45.8104V28.3838H43.7024V16.7474H45.5888L51.0661 24.6792H51.1683V16.7474H53.2649ZM58.8545 28.5542C57.9795 28.5542 57.2238 28.3724 56.5875 28.0088C55.9549 27.6414 55.4682 27.1224 55.1272 26.452C54.7863 25.7777 54.6159 24.9842 54.6159 24.0713C54.6159 23.1736 54.7863 22.3857 55.1272 21.7077C55.4719 21.0258 55.953 20.4955 56.5704 20.1167C57.1879 19.7342 57.9132 19.5429 58.7466 19.5429C59.2844 19.5429 59.792 19.63 60.2693 19.8042C60.7504 19.9747 61.1746 20.2398 61.542 20.5997C61.9132 20.9595 62.2049 21.4179 62.417 21.9747C62.6291 22.5277 62.7352 23.1868 62.7352 23.952V24.5827H55.5818V23.1963H60.7636C60.7598 22.8023 60.6746 22.452 60.5079 22.1452C60.3413 21.8345 60.1083 21.5902 59.8091 21.4122C59.5136 21.2342 59.1689 21.1452 58.775 21.1452C58.3545 21.1452 57.9852 21.2474 57.667 21.452C57.3488 21.6527 57.1007 21.9179 56.9227 22.2474C56.7485 22.5732 56.6594 22.9311 56.6557 23.3213V24.5315C56.6557 25.0391 56.7485 25.4747 56.9341 25.8383C57.1197 26.1982 57.3791 26.4747 57.7125 26.6679C58.0458 26.8573 58.436 26.952 58.8829 26.952C59.1822 26.952 59.453 26.9103 59.6954 26.827C59.9379 26.7398 60.1481 26.613 60.3261 26.4463C60.5041 26.2796 60.6386 26.0732 60.7295 25.827L62.65 26.0429C62.5288 26.5505 62.2977 26.9936 61.9568 27.3724C61.6197 27.7474 61.1879 28.0391 60.6613 28.2474C60.1348 28.452 59.5325 28.5542 58.8545 28.5542ZM65.4896 19.6565L67.2509 22.8781L69.0407 19.6565H71.2168L68.5862 24.0202L71.2623 28.3838H69.0975L67.2509 25.2417L65.4214 28.3838H63.2396L65.8987 24.0202L63.3078 19.6565H65.4896ZM76.6857 19.6565V21.2474H71.6687V19.6565H76.6857ZM72.9073 17.5656H74.9641V25.7588C74.9641 26.0353 75.0058 26.2474 75.0891 26.3952C75.1762 26.5391 75.2899 26.6376 75.43 26.6906C75.5702 26.7436 75.7255 26.7702 75.8959 26.7702C76.0247 26.7702 76.1421 26.7607 76.2482 26.7417C76.3581 26.7228 76.4414 26.7058 76.4982 26.6906L76.8448 28.2986C76.735 28.3364 76.5778 28.3781 76.3732 28.4236C76.1725 28.469 75.9262 28.4955 75.6346 28.5031C75.1194 28.5183 74.6554 28.4406 74.2425 28.2702C73.8296 28.0959 73.502 27.827 73.2596 27.4633C73.0209 27.0997 72.9035 26.6452 72.9073 26.0997V17.5656Z" fill="white" /> */}
)
})
}
</div> <text className="font-semibold capitalize" x="55%" y="54%" fill="white" font-size="14" font-family="Arial, sans-serif" dominant-baseline="middle" text-anchor="middle">
{requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()}
</text>
</svg>
{/* NEXT Button - END */}
</div>}
</div>
)
})
}
</div> </div>
)
})
}
{(navigationTagPos === NavigationTagPosEnum.ABOVE || navigationTagPos === NavigationTagPosEnum.BELOW) &&
<div
onClick={() => scrollToElement(focusElement?.navigationOrder)}
className={`cursor-pointer fixed ${navigationTagPos === NavigationTagPosEnum.ABOVE ? "top-[45px]" : "bottom-[45px] rotate-[180deg]"} right-[8%] flex flex-col items-center`}>
{/* Arrow pointing up */}
<div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--mainPrimaryBtn)]"></div>
{/* Label box */}
<div
className="bg-[var(--mainPrimaryBtn)] text-white text-sm px-3 py-1 rounded-md shadow-md flex items-center gap-1">
<span>Next</span>
</div> </div>
</div>} )
})
</div>
{provideTags && selectedTag && selectedTag !== null && "key" in selectedTag && "index" in selectedTag &&
<div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}>
{/* Right Side Attributes Section if providedTags - START */}
<RightSideBar
droppedItems={droppedItems}
selectedTag={droppedItems[selectedTag.index]}
setDroppedItems={setDroppedItems}
selTagKey={selectedTag.key}
selTagIndx={selectedTag.index}
signClient={true}
/>
{/* Right Side Attributes Section if providedTags - END */}
</div>
} }
</div>
{/* Document Container End */}
{(navigationTagPos === NavigationTagPosEnum.ABOVE || navigationTagPos === NavigationTagPosEnum.BELOW) &&
<div
onClick={() => scrollToElement(focusElement?.navigationOrder)}
className={`cursor-pointer fixed ${navigationTagPos === NavigationTagPosEnum.ABOVE ? "top-[45px]" : "bottom-[45px]"} right-[8%] flex flex-col items-center`}>
{/* Arrow pointing up */}
{navigationTagPos === NavigationTagPosEnum.ABOVE && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)]"></div>}
{/* Label box */}
<div
className="bg-[var(--primary)] text-white text-sm px-3 py-1 rounded-md shadow-md flex items-center gap-1">
<span className="capitalize">{focusElement?.type.toLowerCase()}</span>
</div>
{navigationTagPos === NavigationTagPosEnum.BELOW && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)] rotate-[180deg]"></div>}
</div>}
</div>
{provideTags && selectedTag && selectedTag !== null && "key" in selectedTag && "index" in selectedTag &&
<div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}>
{/* Right Side Attributes Section if providedTags - START */}
<RightSideBar
droppedItems={droppedItems}
selectedTag={droppedItems[selectedTag.index]}
setDroppedItems={setDroppedItems}
selTagKey={selectedTag.key}
selTagIndx={selectedTag.index}
signClient={true}
/>
{/* Right Side Attributes Section if providedTags - END */}
</div>
}
</div> </div>
</>} {/* Document Container End */}
</div>
{/* {showCompletionScreen && recSignStatus === "COMPLETED" && <CompletionScreen />} {/* {showCompletionScreen && recSignStatus === "COMPLETED" && <CompletionScreen />}
{showCompletionScreen && recSignStatus === "DECLINED" && <CompletionScreen title="No worries! you declined the signing request." message={"Your signing request has been declined. No changes were made to the document."} textColorSuccess={false} />} */} {showCompletionScreen && recSignStatus === "DECLINED" && <CompletionScreen title="No worries! you declined the signing request." message={"Your signing request has been declined. No changes were made to the document."} textColorSuccess={false} />} */}
@ -1144,7 +1155,8 @@ const SignDocument = () => {
{globalLoader.isLoading && <Loader />} {globalLoader.isLoading && <Loader />}
{ {
selectedDocuments !== null && !showCompletionScreen && !signerConsent.consent && <ConsentScreen !transparentTags && selectedDocuments !== null && !showCompletionScreen && !signerConsent.consent && recipient?.type !== "CC" &&
<ConsentScreen
onConsent={() => handleOnConsent()} onConsent={() => handleOnConsent()}
// onDecline={() => console.log("User declined")} // onDecline={() => console.log("User declined")}
/> />
@ -1162,6 +1174,13 @@ const SignDocument = () => {
onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })} onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })}
/> />
<ClientAuditModal
open={auditHistoryOpen}
onClose={() => setAuditHistoryOpen(false)}
recipientId={recipientId}
// onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })}
/>
</div > </div >
) )
} }

View File

@ -301,6 +301,8 @@ const TagsCon: React.FC<TagsConProps> = ({
} }
} }
console.log(droppedItems)
return ( return (
<> <>
{currDocTags.map((item, index) => { {currDocTags.map((item, index) => {
@ -317,21 +319,21 @@ const TagsCon: React.FC<TagsConProps> = ({
// onPointerDown={(e) => handleTagMouseDown(e, key, index)} // onPointerDown={(e) => handleTagMouseDown(e, key, index)}
className="absolute rounded dropped-item text-sm" className="absolute rounded dropped-item text-sm"
// onClick={() => handleTagClick(key, index)} // onClick={() => handleTagClick(key, index)}
// style={{
// left: `${item.dimensions.left}px`,
// top: `${item.dimensions.top}px`,
// width: `${item.dimensions.width}px`,
// height: `${item.dimensions.height}px`,
// border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
// // display: `${hideTags ? 'none' : 'block'}`
// }}
style={{ style={{
left: item.dimensions.left * scale.x, left: `${item.dimensions.left}px`,
top: item.dimensions.top * scale.y, top: `${item.dimensions.top}px`,
width: item.dimensions.width * scale.x, width: `${item.dimensions.width}px`,
height: item.dimensions.height * scale.y, height: `${item.dimensions.height}px`,
border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
// display: `${hideTags ? 'none' : 'block'}`
}} }}
// style={{
// left: item.dimensions.left * scale.x,
// top: item.dimensions.top * scale.y,
// width: item.dimensions.width * scale.x,
// height: item.dimensions.height * scale.y,
// }}
> >
{item.required && <div {item.required && <div
@ -443,7 +445,7 @@ const TagsCon: React.FC<TagsConProps> = ({
<div <div
id={item.navigationOrder} id={item.navigationOrder}
tabIndex={1} tabIndex={1}
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`} className={`h-full text-[14px] font-medium w-full pointer-events-none flex justify-start items-end`}
> >
{ {
<p <p
@ -496,7 +498,7 @@ const TagsCon: React.FC<TagsConProps> = ({
<div <div
id={item.navigationOrder} id={item.navigationOrder}
tabIndex={1} tabIndex={1}
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`} className={`h-full text-[14px] font-medium w-full border-slate-100 rounded pointer-events-nones flex justify-start items-end`}
> >
{ {
<p <p
@ -516,7 +518,7 @@ const TagsCon: React.FC<TagsConProps> = ({
}} }}
// contentEditable={true} // contentEditable={true}
className="outline-none whitespace-nowrap w-full max-h-max" className={`outline-none whitespace-nowrap w-full max-h-max text-[${item.color}] text-[${item.textSize}px] ${item.italic ? "italic": ""} ${item.bold ? "font-semibold": ""} ${item.underline ? "underline": ""}`}
> >
{item.value ?? ""} {item.value ?? ""}
</p> </p>
@ -614,20 +616,22 @@ const TagsCon: React.FC<TagsConProps> = ({
> >
{/* <input type="checkbox" className="" /> */} {/* <input type="checkbox" className="" /> */}
{ {
(!transparentTags && !hideTags && !(saved.save && (index in signConfObj && signConfObj[index].accepted))) ? (!transparentTags && !hideTags && !(saved.save && (index in signConfObj && signConfObj[index].accepted))) ? (
<> <FaSignature className="text-lg text-slate-600" />
<FaSignature className="text-lg text-slate-600" /> ) : saved.saveType === "SELECT" ? (
</> <>{saved.fullName}</>
: ) : (
<img <img
style={{ style={{
width: `${item.dimensions.width}px`, width: `${item.dimensions.width}px`,
height: `${item.dimensions.height}px`, height: `${item.dimensions.height}px`,
}} }}
// src={saved.signature as string} />
src={`data:image/png;base64,${saved.signature}`} src={`data:image/png;base64,${saved.signature}`}
alt="signature"
/> />
)
} }
</div> </div>
} }
{/* SIGNATURE FIELD - END */} {/* SIGNATURE FIELD - END */}

View File

@ -18,6 +18,8 @@ const Loader = () => {
{/* Loader */} {/* Loader */}
<div className="absolute z-50 w-screen h-screen flex items-center justify-center backdrop-blur-sm bg-opacity-50 bg-black/30"> <div className="absolute z-50 w-screen h-screen flex items-center justify-center backdrop-blur-sm bg-opacity-50 bg-black/30">
<div className='bg-[var(--loaderBg)] flex flex-col justify-center items-center gap-2 px-16 py-8 rounded-md shadow-lg'> <div className='bg-[var(--loaderBg)] flex flex-col justify-center items-center gap-2 px-16 py-8 rounded-md shadow-lg'>
{/* <img src="/Sign made easy logo.gif"></img> */}
<FaFileSignature className="text-4xl font-bold animate-text" /> <FaFileSignature className="text-4xl font-bold animate-text" />
<p> <p>
{loaderData.message != undefined ? `${loaderData.message}` : 'Loading ...'} {loaderData.message != undefined ? `${loaderData.message}` : 'Loading ...'}

View File

@ -110,7 +110,7 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
<div <div
className={`flex items-center gap-0.5 cursor-pointer text-[var(--sendForSignTextCol)]`} className={`flex items-center gap-0.5 cursor-pointer text-[var(--sendForSignTextCol)]`}
> >
<div {/* <div
onClick={() => handleStartSectionClick(SignTypeEnum.multipleSign)} onClick={() => handleStartSectionClick(SignTypeEnum.multipleSign)}
className={` className={`
flex items-center justify-center flex items-center justify-center
@ -128,7 +128,7 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
) : ( ) : (
<IoIosSend className="text-2xl" /> <IoIosSend className="text-2xl" />
)} )}
</div> </div> */}
{/* {showSideBar && ( {/* {showSideBar && (
<div <div

View File

@ -13,6 +13,7 @@ import axios from 'axios';
import { MdManageAccounts } from 'react-icons/md'; import { MdManageAccounts } from 'react-icons/md';
import { GrContactInfo, GrTransaction } from 'react-icons/gr'; import { GrContactInfo, GrTransaction } from 'react-icons/gr';
import logoSME from '../../assets/SMELogo.png' import logoSME from '../../assets/SMELogo.png'
import api from '../../config/api';
type SideBarProps = { type SideBarProps = {
showSideBar: boolean; showSideBar: boolean;
@ -37,20 +38,18 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar, isMobile
const [showLogoutModal, setShowLogoutModal] = useState(false); const [showLogoutModal, setShowLogoutModal] = useState(false);
const handleLogoutConfirm = async () => { const handleLogoutConfirm = async () => {
try {
await axios.post(`${import.meta.env.VITE_BASE_URI}/api/v1/auth/logout`, api.get(`${import.meta.env.VITE_BASE_URI}/api/v1/auth/logout`).then((res) => {
{},
{
withCredentials: true,
headers: { "ngrok-skip-browser-warning": "69420" }
}
);
sessionStorage.clear(); sessionStorage.clear();
localStorage.clear();
// Redirect to login or refresh the page after logout // Redirect to login or refresh the page after logout
// window.location.href = '/login';
}).catch((err) => {
sessionStorage.clear();
localStorage.clear();
window.location.href = '/login'; window.location.href = '/login';
} catch (err) { });
console.error('Logout failed:', err);
}
}; };
return ( return (
@ -59,8 +58,8 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar, isMobile
<div className={` <div className={`
${isMobile ? 'fixed left-0 top-0 z-50' : 'relative'} ${isMobile ? 'fixed left-0 top-0 z-50' : 'relative'}
h-screen h-screen
${isMobile ${isMobile
? (showSideBar ? 'w-[250px] translate-x-0' : 'hidden -translate-x-full') ? (showSideBar ? 'w-[250px] translate-x-0' : 'hidden -translate-x-full')
: (showSideBar ? 'w-[250px]' : 'w-[60px]') : (showSideBar ? 'w-[250px]' : 'w-[60px]')
} }
transition-all duration-600 transition-all duration-600
@ -70,7 +69,7 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar, isMobile
{/* Menu */} {/* Menu */}
<div className={`absolute top-0 ${showSideBar ? 'p-4' : 'py-4'} w-full max-h-max ${showSideBar ? '' : 'text-center'}`}> <div className={`absolute top-0 ${showSideBar ? 'p-4' : 'py-4'} w-full max-h-max ${showSideBar ? '' : 'text-center'}`}>
<h1 className='w-full text-lg text-center tracking-widest font-medium text-gray-800'> <h1 className='w-full text-lg text-center tracking-widest font-medium text-gray-800'>
<img className={`${!showSideBar ? "hidden" : ""} h-7 ms-6`} src={logoSME} loading='lazy' /> <img className={`${!showSideBar ? "hidden" : ""} h-7 ms-6`} src={logoSME} loading='lazy' />
<p className={`text-white ${showSideBar ? "hidden" : ""}`}>SME</p> <p className={`text-white ${showSideBar ? "hidden" : ""}`}>SME</p>
</h1> </h1>
@ -91,13 +90,13 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar, isMobile
onClick={() => setShowSideBar(!showSideBar)} onClick={() => setShowSideBar(!showSideBar)}
className={` className={`
absolute top-[95%] translate-y-[-60%] absolute top-[95%] translate-y-[-60%]
${isMobile ${isMobile
? (showSideBar ? 'right-2' : 'right-2') ? (showSideBar ? 'right-2' : 'right-2')
: 'right-0 top-[100%] translate-y-[-100%]' : 'right-0 top-[100%] translate-y-[-100%]'
} }
bg-[var(--dashBoardBg)] p-1 bg-[var(--dashBoardBg)] p-1
${isMobile ${isMobile
? 'rounded-md' ? 'rounded-md'
: (showSideBar ? 'rounded-tl-sm rounded-bl-sm' : 'rounded-tl-sm rounded-bl-sm') : (showSideBar ? 'rounded-tl-sm rounded-bl-sm' : 'rounded-tl-sm rounded-bl-sm')
} }
cursor-pointer transition-colors cursor-pointer transition-colors

View File

@ -10,6 +10,7 @@ import {
FaFilter, FaFilter,
FaCalendarAlt FaCalendarAlt
} from 'react-icons/fa'; } from 'react-icons/fa';
import { useNavigate } from 'react-router-dom';
// Define a type for card data // Define a type for card data
interface CardData { interface CardData {
@ -97,7 +98,9 @@ const filters = ['Last 7 Days', 'Last 30 Days', 'Last 6 Months', 'This Year'];
const accounts = ['All Accounts', 'Account 1', 'Account 2', 'Account 3']; const accounts = ['All Accounts', 'Account 1', 'Account 2', 'Account 3'];
const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRangeChange, cards, isLoading }) => { const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRangeChange, cards, isLoading }) => {
const navigate = useNavigate();
const [selectedFilter, setSelectedFilter] = useState<string>('Last 7 Days'); const [selectedFilter, setSelectedFilter] = useState<string>('Last 7 Days');
// const [isLoading, setIsLoading] = useState<boolean>(true); // const [isLoading, setIsLoading] = useState<boolean>(true);
// useEffect(() => { // useEffect(() => {
@ -205,6 +208,7 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRa
{cards.map((card, index) => ( {cards.map((card, index) => (
<div <div
key={card.title} key={card.title}
onClick={() => navigate(card.navigateTo)}
className={`flex items-center justify-between bg-white px-4 py-3.5 transition-all duration-300 cursor-pointer ${index > 0 ? "border-l-1 border-gray-200" : ""}`} className={`flex items-center justify-between bg-white px-4 py-3.5 transition-all duration-300 cursor-pointer ${index > 0 ? "border-l-1 border-gray-200" : ""}`}
style={{ style={{
animationDelay: `${index * 100}ms`, animationDelay: `${index * 100}ms`,

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback, JSX } from 'react';
import AccountNav from '../accounts/AccountNav'; import AccountNav from '../accounts/AccountNav';
import Cards from './Cards'; import Cards from './Cards';
import StartSection from './StartSection'; import StartSection from './StartSection';
@ -18,6 +18,7 @@ interface CardData {
textColor: string; textColor: string;
percentage?: number; percentage?: number;
trend?: 'up' | 'down' | 'stable'; trend?: 'up' | 'down' | 'stable';
navigateTo: string;
} }
const Home = () => { const Home = () => {
@ -26,38 +27,42 @@ const Home = () => {
{ {
title: "Action Required", title: "Action Required",
count: data.actionNeeded || 0, count: data.actionNeeded || 0,
icon: <FiFileText className="w-5 h-5 text-[#0080FF]" />, icon: <FiFileText className="w-5 h-5 text-sky-500" />,
iconBg: "bg-[#F3F9FF]", iconBg: "bg-sky-100 border-sky-200",
textColor: "text-sky-600", textColor: "text-sky-600",
gradient: "bg-white", gradient: "bg-white",
shadowColor: "shadow-sky-100", shadowColor: "shadow-sky-100",
navigateTo: "/transactions/action_required"
}, },
{ {
title: "Waiting for Response", title: "Waiting for Response",
count: data.waitingForOthers || 0, count: data.waitingForOthers || 0,
icon: <FiClock className="w-5 h-5 text-yellow-500" />, icon: <FiClock className="w-5 h-5 text-yellow-500" />,
iconBg: "bg-[#FFF9EB]", iconBg: "bg-yellow-100 border-yellow-200",
textColor: "text-yellow-600", textColor: "text-yellow-600",
gradient: "bg-white", gradient: "bg-white",
shadowColor: "shadow-yellow-100", shadowColor: "shadow-yellow-100",
navigateTo: "/transactions/waiting_for_response"
}, },
{ {
title: "Expiring Soon", title: "Expiring Soon",
count: data.expiring || 0, count: data.expiring || 0,
icon: <CgSandClock className="w-5 h-5 text-red-500" />, icon: <CgSandClock className="w-5 h-5 text-red-500" />,
iconBg: "bg-[#FFF8F8]", iconBg: "bg-red-100 border-red-200",
textColor: "text-red-600", textColor: "text-red-600",
gradient: "bg-white", gradient: "bg-white",
shadowColor: "shadow-red-100", shadowColor: "shadow-red-100",
navigateTo: "/transactions/expiring_soon"
}, },
{ {
title: "Completed", title: "Completed",
count: data.completed || 0, count: data.completed || 0,
icon: <FiCheckCircle className="w-5 h-5 text-green-500" />, icon: <FiCheckCircle className="w-5 h-5 text-green-500" />,
iconBg: "bg-[#F1FFEF]", iconBg: "bg-green-100 border-green-200",
textColor: "text-green-600", textColor: "text-green-600",
gradient: "bg-white", gradient: "bg-white",
shadowColor: "shadow-green-100", shadowColor: "shadow-green-100",
navigateTo: "/transactions/completed"
}, },
]; ];
}; };

View File

@ -32,7 +32,7 @@ const StartNow = () => {
const subject = filesToUpload[0].name ? filesToUpload[0].name : "Untitled" const subject = filesToUpload[0].name ? filesToUpload[0].name : "Untitled"
const transactionRes = await api.post(`/api/v1/accounts/${userData.account?.id}/transactions`, { subject }); const transactionRes = await api.post(`/api/v1/accounts/${userData.account?.id}/transactions`, { subject: "Sign Document - " + subject });
const transactionId = transactionRes.data.id const transactionId = transactionRes.data.id
const formData = new FormData(); const formData = new FormData();

View File

@ -82,7 +82,7 @@ const TransactionSentSuccess = () => {
className="flex items-center text-gray-600 hover:text-[var(--primary)] transition-colors duration-200" className="flex items-center text-gray-600 hover:text-[var(--primary)] transition-colors duration-200"
> >
{/* <IoMdHome className="text-xl sm:text-2xl text-slate-500 cursor-pointer" /> */} {/* <IoMdHome className="text-xl sm:text-2xl text-slate-500 cursor-pointer" /> */}
<img src="/sme-colored.png" className='py-2 px-8' alt="" /> <img src="/sme-colored.png" className='py-2 w-[140px]' alt="" />
</Link> </Link>
{/* <div className="h-6 sm:h-8 w-px bg-gray-300"></div> */} {/* <div className="h-6 sm:h-8 w-px bg-gray-300"></div> */}
{/* <h1 className="text-sm sm:text-md text-gray-800"> {/* <h1 className="text-sm sm:text-md text-gray-800">

View File

@ -40,7 +40,7 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted, tran
let lastUpdate = 0; let lastUpdate = 0;
const delay = 1000; const delay = 1000;
const docProgSSE = new EventSource(`http://10.10.1.99:8083/api/v1/progress/${id}`); const docProgSSE = new EventSource(`${import.meta.env.VITE_SSE_URI}/api/v1/progress/${id}`);
docProgSSE.addEventListener('progress', (event) => { docProgSSE.addEventListener('progress', (event) => {
const jsonData = JSON.parse(event.data); const jsonData = JSON.parse(event.data);

View File

@ -337,7 +337,7 @@ const ProcessStepOne = () => {
(async () => { (async () => {
try { try {
await api.put(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" }) await api.put(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" })
if(accountId && transactionId) signSessionRedirect(accountId, transactionId, recipientId); if (accountId && transactionId) signSessionRedirect(accountId, transactionId, recipientId);
dispatch(hideLoader()); dispatch(hideLoader());
} catch (err) { } catch (err) {
console.log("Error Transaction Sent", err.message); console.log("Error Transaction Sent", err.message);
@ -367,7 +367,13 @@ const ProcessStepOne = () => {
const handleClose = () => { const handleClose = () => {
dispatch(resetDocuments()) dispatch(resetDocuments())
navigate('/')
if (window.location.href.includes("template") || window.location.href.includes("useTemplate")) {
navigate('/templates')
} else {
navigate('/')
}
} }
useEffect(() => { useEffect(() => {

View File

@ -160,7 +160,8 @@ const RecipientCards = ({
placeholder={placeholder} placeholder={placeholder}
onFocus={() => handleFieldFocus(identifier, fieldName)} onFocus={() => handleFieldFocus(identifier, fieldName)}
onBlur={(e) => handleFieldBlur(recipient, fieldName, e.target.value)} onBlur={(e) => handleFieldBlur(recipient, fieldName, e.target.value)}
onChange={(e) => handleFieldChange(recipient, fieldName, e.target.value)} // onChange={(e) => handleFieldChange(recipient, fieldName, e.target.value)}
onInput={(e) => handleFieldChange(recipient, fieldName, (e.target as HTMLInputElement).value)}
onKeyPress={(e) => { onKeyPress={(e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
e.currentTarget.blur(); e.currentTarget.blur();
@ -450,10 +451,16 @@ const RecipientCards = ({
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
{/* Role Name and Permissions Row */} {/* Role Name and Permissions Row */}
<div className="flex flex-col lg:flex-row lg:items-center gap-2 mb-2"> <div className="flex flex-col lg:flex-row lg:items-center gap-2 mb-2">
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && <div className="flex-1 lg:w-1/2"> {<div className="flex lg:w-1/2">
<div className="font-semibold text-gray-800 text-xs"> {transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES ?
{renderEditableField(recipient, "roleName", "Role name")} <div className="font-semibold text-gray-800 text-xs">
</div> {renderEditableField(recipient, "roleName", "Role name")}
</div>
:
<div className="font-semibold text-gray-800 text-sm px-2">
{recipient.roleName}
</div>
}
</div>} </div>}
{/* Permissions - Mobile: Below role, Desktop: Right side */} {/* Permissions - Mobile: Below role, Desktop: Right side */}

View File

@ -191,7 +191,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
} }
} }
updatedData[recipientId] = updateTags updatedData[recipientId] = updateTags.filter((tag, indx) => !tag.isDeleted);
} }
const requests = Object.entries(updatedData) const requests = Object.entries(updatedData)
@ -459,12 +459,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
} }
const handleLazyLoad = (docId: string, pageId: string) => {
api.get(`http://10.10.1.99:8083/api/v1/${accountId}/transactions/${transactionId}/documents/${docId}/pages/${pageId}`).then(res => {
console.log(res)
})
}
return ( return (
<> <>
{/* Center Doc Container - START */} {/* Center Doc Container - START */}

View File

@ -7,6 +7,7 @@ import { FaQuestionCircle } from 'react-icons/fa';
import { TagsProperties, TagsTypes } 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';
import api from '../../../config/api';
const handlePointerDown = (event: React.DragEvent<HTMLDivElement>, label: string, recipientId: number, recipientIndx: number, tagsProperties: TagsProperties) => { const handlePointerDown = (event: React.DragEvent<HTMLDivElement>, label: string, recipientId: number, recipientIndx: number, tagsProperties: TagsProperties) => {
const dragElemRect = event.currentTarget.getBoundingClientRect(); const dragElemRect = event.currentTarget.getBoundingClientRect();
@ -119,8 +120,8 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
} }
useEffect(() => { useEffect(() => {
axios api
.get(`${import.meta.env.VITE_BASE_URI}/api/v1/accounts/${accountId}/tags`, { .get(`/api/v1/accounts/${accountId}/tags`, {
headers: { headers: {
"ngrok-skip-browser-warning": "69420", "ngrok-skip-browser-warning": "69420",
} }

View File

@ -149,8 +149,32 @@ const NavTwo: React.FC<NavTwoProps> = ({
const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false); const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false);
const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false); const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false);
const transactionSendValidation = () => {
const transactionSendValidationObj = {
oneSignerIsRequiredError: true
}
// One Signer is required - Validation
for (const recipient of selectedRecipients) {
if (recipient.type == recipientType.SIGNER || recipient.type == recipientType.INPERSONSIGNER) {
transactionSendValidationObj.oneSignerIsRequiredError = false; break
};
}
if (!transactionSendValidationObj.oneSignerIsRequiredError) return false;
return transactionSendValidationObj;
}
function handleSend(isSignSend: boolean = false) { function handleSend(isSignSend: boolean = false) {
// dispatch(showLoader("Loading ...")); // dispatch(showLoader("Loading ..."));
const transactionSendValidationObj = transactionSendValidation()
if(transactionSendValidationObj.oneSignerIsRequiredError) {
return
}
if (isSend === null) { if (isSend === null) {
setIsSend(false); setIsSend(false);
} }
@ -181,6 +205,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
}, 1000) }, 1000)
} }
// const handleSetIsFields = (setTo: boolean) => { // const handleSetIsFields = (setTo: boolean) => {
// if (setTo !== undefined && setTo !== isFields) { // if (setTo !== undefined && setTo !== isFields) {
// setIsFields(setTo) // setIsFields(setTo)

View File

@ -83,16 +83,14 @@ export enum DROPDOWNTOGGLE {
} }
const defaultStyles: StylesInterface = { const defaultStyles: StylesInterface = {
font: "", font: "sans-serif",
italic: false, italic: false,
bold: false, bold: false,
underline: false, underline: false,
color: "#000000", color: "#000000",
textSize: 0 textSize: 14
}; };
const RightSideBar: React.FC<DroppedItemProps> = ( const RightSideBar: React.FC<DroppedItemProps> = (
{ {
droppedItems, droppedItems,

View File

@ -109,7 +109,7 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>, type: "SIGNATURE" | "INITIAL") => { const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>, type: "SIGNATURE" | "INITIAL") => {
if (!isDrawing) return; if (!isDrawing) return;
setIsDrawingType((prev) => { setIsDrawingType((prev) => {
return {...prev, signatureDrawn: type === "SIGNATURE" ? true : prev.signatureDrawn, initialDrawn: type === "INITIAL" ? true : prev.initialDrawn } return { ...prev, signatureDrawn: type === "SIGNATURE" ? true : prev.signatureDrawn, initialDrawn: type === "INITIAL" ? true : prev.initialDrawn }
}) })
const rect = e.currentTarget.getBoundingClientRect(); const rect = e.currentTarget.getBoundingClientRect();
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top }; const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
@ -147,7 +147,7 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
const handleMouseUp = () => { const handleMouseUp = () => {
setIsDrawing(false); setIsDrawing(false);
if (currentPath.length === 0 || !activeCanvas) return; if (currentPath.length === 0 || !activeCanvas) return;
if (activeCanvas === 'full') { if (activeCanvas === 'full') {
@ -206,6 +206,8 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
ctx2?.clearRect(0, 0, canvasRef2.current.width, canvasRef2.current.height); ctx2?.clearRect(0, 0, canvasRef2.current.width, canvasRef2.current.height);
} }
setIsDrawingType({ signatureDrawn: false, initialDrawn: false })
setPaths1([]); setPaths1([]);
setPaths2([]); setPaths2([]);
setInitialImage1(null); setInitialImage1(null);
@ -241,11 +243,12 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
{/* Controls */} {/* Controls */}
<div className="flex gap-2 mt-3"> <div className="flex gap-2 mt-3">
<div className='flex justify-center items-center gap-1 font-medium'> <div
onClick={clearCanvas}
className='flex justify-center items-center gap-1 font-medium'>
<IoMdClose className='text-red-500' /> <IoMdClose className='text-red-500' />
<button <button
className="text-xs text-red-500" className="text-xs text-red-500"
onClick={clearCanvas}
> >
Clear Signature Clear Signature
</button> </button>

View File

@ -4,11 +4,13 @@ import { Bounce, toast } from "react-toastify";
import api from "../../config/api"; import api from "../../config/api";
import { IoMdAdd } from "react-icons/io"; import { IoMdAdd } from "react-icons/io";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { FaSpinner } from "react-icons/fa"; import { FaSpinner } from "react-icons/fa";
import LoaderUi from "../../helper/LoaderUi"; import LoaderUi from "../../helper/LoaderUi";
import TemplateTable from "./TemplateTable"; import TemplateTable from "./TemplateTable";
import { resetRecipients } from "../../store/slices/selectedRecipientsSlice";
import { resetDocuments } from "../../store/slices/documentSlice";
@ -25,13 +27,14 @@ const Templates = () => {
); );
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch();
const filters = ["All", "My Templates", "Shared"]; const filters = ["All", "My Templates", "Shared"];
const handleAddTemplate = async () => { const handleAddTemplate = async () => {
try { try {
await dispatch(resetRecipients())
await dispatch(resetDocuments())
const res = await api.post(`/api/v1/accounts/${activeAccountId}/templates`); const res = await api.post(`/api/v1/accounts/${activeAccountId}/templates`);
navigate(`/process-step-one/${activeAccountId}/template/${res.data.id}`) navigate(`/process-step-one/${activeAccountId}/template/${res.data.id}`)
} catch (error) { } catch (error) {

View File

@ -49,7 +49,7 @@ const AuditModal: React.FC<AuditModalProps> = ({ closeModal }) => {
const getAuditDetails = async () => { const getAuditDetails = async () => {
// setLoading(true) // setLoading(true)
try { try {
let res = await api.get(`/api/v1/accounts/${accountId}/transactions/${transactionId}/audit?lang=${userDetails.language || ''}`) let res = await api.get(`/api/v1/accounts/${accountId}/transactions/${transactionId}/audit/events?lang=${userDetails.language || ''}`)
// let res = await api.get(`/api/v1/accounts/${accountId}/transactions/${transactionId}/audit?lang=hi`) // let res = await api.get(`/api/v1/accounts/${accountId}/transactions/${transactionId}/audit?lang=hi`)
if (res?.status === 200) { if (res?.status === 200) {
// console.log(res?.data?.events) // console.log(res?.data?.events)

View File

@ -216,7 +216,7 @@ const TransactionView = () => {
{transactionDetails.transaction.status === "SENT" && recipient.email === user.email && recipient.status !== "COMPLETED" && ( {transactionDetails.transaction.status === "SENT" && recipient.email === user.email && recipient.status !== "COMPLETED" && (
<button <button
onClick={() => handleNavigateToSign(recipient.id)} onClick={() => handleNavigateToSign(recipient.id)}
className="text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold text-xs hover:bg-opacity-90 transition" className="text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold text-xs hover:bg-opacity-90 transition cursor-pointer"
> >
Sign Sign
</button> </button>

View File

@ -15,6 +15,7 @@ const LoaderUi = ({ classStyle = '' }: StyleProps) => {
{/* Loader box */} {/* Loader box */}
<div className="w-60 relative z-10 bg-[var(--loaderBg)] flex flex-col justify-center items-center gap-2 px-16 py-8 rounded-md shadow-lg"> <div className="w-60 relative z-10 bg-[var(--loaderBg)] flex flex-col justify-center items-center gap-2 px-16 py-8 rounded-md shadow-lg">
{/* <img src="/Sign made easy logo.gif"></img> */}
<FaFileSignature className="text-4xl font-bold animate-text" /> <FaFileSignature className="text-4xl font-bold animate-text" />
<p>Loading...!</p> <p>Loading...!</p>
</div> </div>