jai #4

Merged
jaipandey merged 4 commits from jai into master 2025-07-15 10:08:58 +00:00
16 changed files with 1189 additions and 642 deletions

View File

@ -19,6 +19,7 @@ import Accounts from "./components/accounts/Accounts";
import Account from "./components/accounts/Account"; import Account from "./components/accounts/Account";
import TransactionView from "./components/transactions/TransactionView"; import TransactionView from "./components/transactions/TransactionView";
import Templates from "./components/templates/Templates"; import Templates from "./components/templates/Templates";
import DocumentCompletionScreen from "./components/process/CompletionScreen/DocumentCompletionScreen";
const App = () => { const App = () => {
return ( return (
@ -47,9 +48,6 @@ const App = () => {
<Route path="/s/:page1" element={<Setting />} /> <Route path="/s/:page1" element={<Setting />} />
</Route> </Route>
<Route path="/process-step-one/:accountId/:transactionId" element={ <Route path="/process-step-one/:accountId/:transactionId" element={
<ProtectedRoute> <ProtectedRoute>
<ProcessStepOne /> <ProcessStepOne />
@ -62,6 +60,12 @@ const App = () => {
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route path="/document-completion-screen/:accountId/:transactionId" element={
<ProtectedRoute>
<DocumentCompletionScreen />
</ProtectedRoute>
} />
{/* Links work wothout Login */} {/* Links work wothout Login */}
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} /> <Route path="/register" element={<Register />} />

View File

@ -1,22 +1,14 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
// import { useSelector } from 'react-redux';
import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Link, useLocation, useNavigate } from 'react-router-dom';
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa"; import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa";
import api from '../config/api'; import api from '../config/api';
// import { FaUser, FaSignOutAlt, FaBuilding, FaChevronDown, FaCheck } from 'react-icons/fa';
// import { RootState } from '../../store/store'; interface AccountNavProps {
// import { UserState } from '../../store/slices/userSlice';
interface CommonNavProps {
accountName?: string | null; accountName?: string | null;
transactionName?: string | null; transactionName?: string | null;
} }
// Define the shape of state object
interface Account { interface Account {
id: number; id: number;
isDefault: boolean; isDefault: boolean;
@ -24,32 +16,45 @@ interface Account {
} }
interface UserData { interface UserData {
id: string; id: number;
firstName: string; name: string;
lastName: string;
email: string; email: string;
language: string; language: string;
account: Account; account: Account;
} }
interface AccountDataArray { interface AccountDataArray {
accountId: number, accountName: string, role: string, status: boolean userId: number,
userEmail: string,
name: string,
accountId: number;
accountName: string;
role: string;
status: string;
} }
const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionName = null }) => { // Helper function to dispatch custom storage event
// const user: UserState = useSelector((state: RootState) => state.user); const dispatchSessionStorageChange = (key: string, newValue: any) => {
window.dispatchEvent(new CustomEvent('sessionStorageChange', {
detail: { key, newValue }
}));
};
const CommonNav: React.FC<AccountNavProps> = () => {
const activeAccountId = JSON.parse(sessionStorage.getItem('activeAccountId') || 'null');
const userDetails = JSON.parse(sessionStorage.getItem('userDetails') || '{}');
console.log("activeAccountId", activeAccountId);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [accountsOpen, setAccountsOpen] = useState(false); const [accountsOpen, setAccountsOpen] = useState(false);
const [loadingAccounts, setLoadingAccounts] = useState(false); const [loadingAccounts, setLoadingAccounts] = useState(false);
const [accounts, setAccounts] = useState<AccountDataArray[]>([]); const [accounts, setAccounts] = useState<AccountDataArray[]>([]);
const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); // Track if modal was closed const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false);
const dropdownRef = useRef(null); const dropdownRef = useRef<HTMLDivElement>(null);
const [user, setUser] = useState<UserData>({ const [user, setUser] = useState<UserData>({
id: "", id: 0,
firstName: "", name: "",
lastName: "",
email: "", email: "",
language: "", language: "",
account: { account: {
@ -59,13 +64,17 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
}, },
}); });
console.log("user", user)
const getAccountDetails = async () => { const getAccountDetails = async () => {
try { try {
let res = await api.get('/api/user/accounts') let res = await api.get('/api/user/accounts');
let items = res?.data let items = res?.data;
console.log('items', items) console.log('items', items);
// Extract only the fields you want
const cleanedData: AccountDataArray[] = items.map((item: any) => ({ const cleanedData: AccountDataArray[] = items.map((item: any) => ({
userId: item.userId,
userEmail: item.userEmail,
name: item.name,
accountId: item.accountId, accountId: item.accountId,
accountName: item.accountName, accountName: item.accountName,
role: item.role, role: item.role,
@ -74,10 +83,9 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
setAccounts(cleanedData); setAccounts(cleanedData);
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} };
const handleAccountsClick = async () => { const handleAccountsClick = async () => {
setAccountsOpen(!accountsOpen); setAccountsOpen(!accountsOpen);
@ -85,78 +93,127 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
if (!accountsOpen) { if (!accountsOpen) {
setLoadingAccounts(true); setLoadingAccounts(true);
// Fetch API if:
// 1. No accounts data exists (first time), OR
// 2. Modal was previously closed (need fresh data)
if (accounts.length === 0 || hasModalBeenClosed) { if (accounts.length === 0 || hasModalBeenClosed) {
await getAccountDetails(); await getAccountDetails();
setHasModalBeenClosed(false); // Reset the flag after fetching setHasModalBeenClosed(false);
} }
setLoadingAccounts(false); setLoadingAccounts(false);
} }
}; };
const handleAccountSwitch = (account) => { const handleAccountSwitch = (account: any) => {
setUser(prev => ({ console.log("handleAccountSwitch", account);
...prev,
account: { name: account.name, id: account.id }
}));
// Update accounts active status // Update sessionStorage
setAccounts(prevAccounts => sessionStorage.setItem('activeAccountId', JSON.stringify(account.accountId));
prevAccounts.map(acc => ({
...acc, // Dispatch custom event
isActive: acc.id === account.id dispatchSessionStorageChange('activeAccountId', account.accountId);
}))
); // Update user state
setUser({
id: account.userId,
name: account.name,
email: account.userEmail,
language: "", // set it if available
account: {
id: account.accountId,
isDefault: false, // update as needed
name: account.accountName,
},
});
sessionStorage.setItem('userDetails', JSON.stringify({
id: account.userId,
name: account.name,
email: account.userEmail,
language: "", // set it if available
account: {
id: account.accountId,
isDefault: false, // update as needed
name: account.accountName,
},
}));
setAccountsOpen(false); setAccountsOpen(false);
setOpen(false); setOpen(false);
}; };
const navigate = useNavigate()
const navigate = useNavigate();
const getUserDetails = async () => { const getUserDetails = async () => {
try { try {
let res = await api.get(`/api/user/details`) let res = await api.get(`/api/user/details`);
console.log("getuser", res) console.log("getuser", res);
setUser(res?.data) const item = res?.data;
if (item) {
const userData = {
id: item?.id,
name : item?.firstName + " " + item?.lastName ,
email: item?.email,
language: "",
account: {
id: item?.account.id,
isDefault: false,
name: item?.account.name,
},
};
console.log("userData",userData)
setUser(userData);
sessionStorage.setItem('userDetails', JSON.stringify(userData));
sessionStorage.setItem('activeAccountId', JSON.stringify(userData.account.id));
// Dispatch event for initial load
dispatchSessionStorageChange('activeAccountId', userData.account.id);
console.log("Data is saved in sessionStorage");
}
} catch (error) { } catch (error) {
console.error("Error fetching user details:", error); console.error("Error fetching user details:", error);
} }
} };
useEffect(() => { useEffect(() => {
getUserDetails() if (!activeAccountId) {
}, []) getUserDetails();
} else {
setUser(userDetails);
}
}, []);
const location = useLocation(); const location = useLocation();
const pathnames = location.pathname const rawPathnames = location.pathname
.split("/") .split("/")
.filter((x) => x && isNaN(Number(x))); // remove numeric IDs .filter((x) => x && isNaN(Number(x)));
// Special case: if first segment is 's' and next is 'settings', remove 's'
const pathnames =
rawPathnames[0] === "s" && rawPathnames[1] === "settings"
? rawPathnames.slice(1)
: rawPathnames;
const labelMap: Record<string, string> = { const labelMap: Record<string, string> = {
accounts: "Accounts", accounts: "Accounts",
transactions: "Transactions", transactions: "Transactions",
templates: "Templates" templates: "Templates"
// Add more routes here if needed
}; };
// const [open, setOpen] = useState(false);
// const dropdownRef = useRef<HTMLDivElement>(null);
// Close dropdown when clicked outside
useEffect(() => { useEffect(() => {
function handleClickOutside(event: MouseEvent) { function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
if (open) { if (open) {
setHasModalBeenClosed(true); // Mark modal as closed when clicking outside setHasModalBeenClosed(true);
} }
setOpen(false); setOpen(false);
setAccountsOpen(false); // Also close accounts dropdown setAccountsOpen(false);
} }
} }
@ -166,24 +223,24 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
const handleLogout = async () => { const handleLogout = async () => {
try { try {
let res = await api.post('/api/auth/logout') let res = await api.post('/api/auth/logout');
if (res.status === 200) { if (res.status === 200) {
sessionStorage.clear();
setOpen(false); setOpen(false);
navigate("/login") navigate("/login");
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
}; };
// Handle main dropdown toggle
const handleMainDropdownToggle = () => { const handleMainDropdownToggle = () => {
if (open) { if (open) {
setHasModalBeenClosed(true); // Mark as closed when manually closing setHasModalBeenClosed(true);
} }
setOpen(!open); setOpen(!open);
if (!open) { if (!open) {
setAccountsOpen(false); // Close accounts dropdown when opening main dropdown setAccountsOpen(false);
} }
}; };
@ -192,7 +249,6 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
.replace(/[-_]/g, " ") .replace(/[-_]/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase()); .replace(/\b\w/g, (char) => char.toUpperCase());
const ShimmerRow = () => ( const ShimmerRow = () => (
<div className="animate-pulse px-4 py-3 border-b border-gray-100 last:border-b-0"> <div className="animate-pulse px-4 py-3 border-b border-gray-100 last:border-b-0">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
@ -206,7 +262,6 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
</div> </div>
); );
return ( return (
<header className="w-full px-6 py-2 flex justify-between items-center bg-white border-b rounded-sm border-gray-200"> <header className="w-full px-6 py-2 flex justify-between items-center bg-white border-b rounded-sm border-gray-200">
<nav className="text-md font-medium text-gray-800 capitalize flex items-center flex-wrap"> <nav className="text-md font-medium text-gray-800 capitalize flex items-center flex-wrap">
@ -238,10 +293,7 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
</nav> </nav>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{/* User Dropdown */}
<div className="relative" ref={dropdownRef}> <div className="relative" ref={dropdownRef}>
{/* Avatar Button */}
<button <button
className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors" className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors"
onClick={handleMainDropdownToggle} onClick={handleMainDropdownToggle}
@ -250,34 +302,31 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
> >
<div className="w-8 h-8 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md"> <div className="w-8 h-8 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md">
{user ? ( {user ? (
<span <span className="w-full h-full object-cover p-1.5 font-bold">
className="w-full h-full object-cover p-1.5 font-bold" {(user?.name?.slice(0, 2)).toUpperCase()}
>{(user?.firstName + " " + user?.lastName).split(' ').map((item) => item.slice(0, 1)).join("").toUpperCase()}</span> </span>
) : ( ) : (
<FaUser className="text-lg" /> <FaUser className="text-lg" />
)} )}
</div> </div>
</button> </button>
{/* Enhanced Dropdown */}
{open && ( {open && (
<div className="absolute right-0 mt-2 w-72 top-8 rounded-xl shadow-xl bg-white border border-gray-200 z-50 overflow-hidden"> <div className="absolute right-0 mt-2 w-72 top-8 rounded-xl shadow-xl bg-white border border-gray-200 z-50 overflow-hidden">
{/* User Info Section */}
<div className="px-4 py-4 bg-gradient-to-r from-blue-50 to-purple-50 border-b border-gray-100"> <div className="px-4 py-4 bg-gradient-to-r from-blue-50 to-purple-50 border-b border-gray-100">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className="w-12 h-12 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold shadow-md"> <div className="w-12 h-12 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold shadow-md">
{(user) ? ( {user ? (
<span <span className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold">
className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold" {(user?.name?.slice(0, 2)).toUpperCase()}
> </span>
{(user?.firstName + " " + user?.lastName).split(' ').map((item) => item.slice(0, 1)).join("").toUpperCase()}</span>
) : ( ) : (
<FaUser className="text-lg" /> <FaUser className="text-lg" />
)} )}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="font-semibold text-gray-900 truncate">{user.firstName + ' ' + user.lastName || '-'}</div> <div className="font-semibold text-gray-900 truncate">{user?.name || '-'}</div>
<div className="text-sm text-gray-600 truncate">{user.email || '-'}</div> <div className="text-sm text-gray-600 truncate">{user?.email || '-'}</div>
<div className="text-xs text-gray-500 mt-1"> <div className="text-xs text-gray-500 mt-1">
Account #{user?.account?.name || '-'} Account #{user?.account?.name || '-'}
</div> </div>
@ -285,7 +334,6 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
</div> </div>
</div> </div>
{/* Account List Section */}
<div className="border-b border-gray-100"> <div className="border-b border-gray-100">
<button <button
onClick={handleAccountsClick} onClick={handleAccountsClick}
@ -298,7 +346,6 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
<FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} /> <FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} />
</button> </button>
{/* Accounts Dropdown */}
{accountsOpen && ( {accountsOpen && (
<div className="bg-gray-50 max-h-64 overflow-y-auto"> <div className="bg-gray-50 max-h-64 overflow-y-auto">
{loadingAccounts ? ( {loadingAccounts ? (
@ -308,13 +355,14 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
</div> </div>
) : ( ) : (
<div className="divide-y divide-gray-200"> <div className="divide-y divide-gray-200">
{accounts.length > 0 ? (accounts?.map((account) => ( {accounts?.length > 0 ? (
accounts?.map((account) => (
<button <button
key={account.accountId} key={account.accountId}
// onClick={() => handleAccountSwitch(account)} onClick={() => handleAccountSwitch(account)}
className="w-full px-4 py-3 hover:bg-white transition-colors cursor-pointer text-left" className={`w-full px-4 py-3 hover:bg-white transition-colors cursor-pointer text-left`}
> >
<div className="flex items-center justify-between"> <div className={`flex items-center justify-between`}>
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-gray-200 to-gray-400 flex items-center justify-center text-white font-semibold text-xs"> <div className="w-8 h-8 rounded-full bg-gradient-to-br from-gray-200 to-gray-400 flex items-center justify-center text-white font-semibold text-xs">
{account.accountName.slice(0, 2).toUpperCase()} {account.accountName.slice(0, 2).toUpperCase()}
@ -322,11 +370,10 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<span className="font-medium text-gray-900 text-sm">{account.accountName}</span> <span className="font-medium text-gray-900 text-sm">{account.accountName}</span>
{account.status && ( {account.accountId === activeAccountId && (
<FaCheck className="text-green-500 text-xs" /> <FaCheck className="text-green-600 text-xs" />
)} )}
</div> </div>
<div className="text-xs text-gray-600 truncate">{account.company}</div>
</div> </div>
</div> </div>
<div className="text-xs text-gray-500 bg-gray-200 px-2 py-1 rounded"> <div className="text-xs text-gray-500 bg-gray-200 px-2 py-1 rounded">
@ -334,16 +381,17 @@ const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionNa
</div> </div>
</div> </div>
</button> </button>
))) : <span className='text-gray-400 font-medium text-[12px] flex justify-center p-3'>No Account...</span>} ))
) : (
<span className='text-gray-400 font-medium text-[12px] flex justify-center p-3'>No Account...</span>
)}
</div> </div>
)} )}
</div> </div>
)} )}
</div> </div>
{/* Logout Section */} <div className="border-t border-gray-100">
<div className="border-t border-gray-100 ">
<button <button
onClick={handleLogout} onClick={handleLogout}
className="w-full flex items-center px-4 py-3 text-sm text-red-600 hover:bg-red-50 transition-colors cursor-pointer" className="w-full flex items-center px-4 py-3 text-sm text-red-600 hover:bg-red-50 transition-colors cursor-pointer"

View File

@ -67,36 +67,36 @@ const Account = () => {
textColor: 'text-teal-700', textColor: 'text-teal-700',
trend: 'up' trend: 'up'
}, },
{ // {
title: 'Waiting for Others', // title: 'Waiting for Others',
count: data.waitingForOthers || 0, // count: data.waitingForOthers || 0,
icon: <FaClock size={18} className="text-stone-600" />, // icon: <FaClock size={18} className="text-stone-600" />,
gradient: 'bg-gradient-to-br from-blue-50 to-blue-100', // gradient: 'bg-gradient-to-br from-blue-50 to-blue-100',
shadowColor: 'shadow-blue-200/50', // shadowColor: 'shadow-blue-200/50',
iconBg: 'bg-blue-100 border-blue-200', // iconBg: 'bg-blue-100 border-blue-200',
textColor: 'text-blue-700', // textColor: 'text-blue-700',
trend: 'down' // trend: 'down'
}, // },
{ // {
title: 'declined', // title: 'declined',
count: data.declined || 0, // count: data.declined || 0,
icon: <FaClock size={18} className="text-purple-600" />, // icon: <FaClock size={18} className="text-purple-600" />,
gradient: 'bg-gradient-to-br from-purple-50 to-purple-100', // gradient: 'bg-gradient-to-br from-purple-50 to-purple-100',
shadowColor: 'shadow-purple-200/50', // shadowColor: 'shadow-purple-200/50',
iconBg: 'bg-purple-100 border-purple-200', // iconBg: 'bg-purple-100 border-purple-200',
textColor: 'text-purple-700', // textColor: 'text-purple-700',
trend: 'down' // trend: 'down'
}, // },
{ // {
title: 'cancelled', // title: 'cancelled',
count: data.cancelled || 0, // count: data.cancelled || 0,
icon: <FaClock size={18} className="text-green-600" />, // icon: <FaClock size={18} className="text-green-600" />,
gradient: 'bg-gradient-to-br from-green-50 to-green-100', // gradient: 'bg-gradient-to-br from-green-50 to-green-100',
shadowColor: 'shadow-green-200/50', // shadowColor: 'shadow-green-200/50',
iconBg: 'bg-green-100 border-green-200', // iconBg: 'bg-green-100 border-green-200',
textColor: 'text-green-700', // textColor: 'text-green-700',
trend: 'down' // trend: 'down'
}, // },
{ {
title: 'draft', title: 'draft',
count: data.draft || 0, count: data.draft || 0,
@ -110,6 +110,14 @@ const Account = () => {
// Add more cards here if needed // Add more cards here if needed
]; ];
}; };
const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
const [currentEmailId, setCurrentEmailId] = useState<string | null>('null');
console.log("activeAccountId Trans.", activeAccountId)
// const { accountId } = useParams<{ accountId: string }>(); // const { accountId } = useParams<{ accountId: string }>();
const [transactions, setTransactions] = useState<Transaction[]>([]); const [transactions, setTransactions] = useState<Transaction[]>([]);
const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]); const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]);
@ -121,25 +129,58 @@ const Account = () => {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const getUserDetails = async () => { // const getUserDetails = async () => {
try { // try {
let res = await api.get('/api/user/details') // let res = await api.get('/api/user/details')
let accountIDData = res?.data?.account?.id // let accountIDData = res?.data?.account?.id
setAccountID(accountIDData) // setAccountID(accountIDData)
} catch (error) { // } catch (error) {
console.log(error) // console.log(error)
} // }
} // }
// useEffect(() => {
// getUserDetails()
// }, [])
useEffect(() => { useEffect(() => {
getUserDetails() const userDetails = JSON.parse(sessionStorage.getItem('userDetails'));
}, []) setCurrentEmailId(userDetails?.email)
}, []);
// Listen for sessionStorage changes
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'activeAccountId') {
const newActiveAccountId = e.newValue ? JSON.parse(e.newValue) : null;
setActiveAccountId(newActiveAccountId);
}
};
// Listen for storage events (works for changes from other tabs)
window.addEventListener('storage', handleStorageChange);
// Custom event listener for same-tab changes
const handleCustomStorageChange = (e: CustomEvent) => {
if (e.detail.key === 'activeAccountId') {
setActiveAccountId(e.detail.newValue);
}
};
window.addEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
return () => {
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
};
}, []);
const getDashboardData = async () => { const getDashboardData = async () => {
setIsLoading(true) setIsLoading(true)
const payload = { const payload = {
account: accountID account: activeAccountId
}; };
try { try {
@ -154,50 +195,51 @@ const Account = () => {
} }
}; };
useEffect(() => {
if (accountID) {
getDashboardData()
fetchTransactions();
}
}, [accountID]);
const handleOpenModal = () => {
setIsModalOpen(true);
};
const fetchTransactions = async () => { const fetchTransactions = async () => {
console.log("fetchTransactions") console.log("fetchTransactions")
try { try {
const response = await api(`/api/accounts/${accountID}/transactions`); const response = await api(`/api/accounts/${activeAccountId}/transactions`);
setTransactions(response.data || []); setTransactions(response.data || []);
} catch (error) { } catch (error) {
console.error("Error fetching transactions:", error); console.error("Error fetching transactions:", error);
} }
}; };
useEffect(() => {
if (activeAccountId) {
getDashboardData()
fetchTransactions();
}
}, [activeAccountId]);
const handleOpenModal = () => {
setIsModalOpen(true);
};
// useEffect(() => { // useEffect(() => {
// if (accountID) fetchTransactions(); // if (accountID) fetchTransactions();
// }, [accountID]); // }, [accountID]);
const getAccountDetails = async () => { // const getAccountDetails = async () => {
try { // try {
let res = await api.get('/api/user/accounts') // let res = await api.get('/api/user/accounts')
let items = res?.data // let items = res?.data
setAccountsData(items) // setAccountsData(items)
} catch (error) { // } catch (error) {
console.log(error) // console.log(error)
} // }
} // }
useEffect(() => { // useEffect(() => {
getAccountDetails() // getAccountDetails()
}, []) // }, [])
const navigate = useNavigate(); const navigate = useNavigate();
const handleTransactionClick = (id: number) => { const handleTransactionClick = (id: number) => {
navigate(`/accounts/${accountID}/transactions/${id}`); navigate(`/accounts/${activeAccountId}/transactions/${id}`);
}; };
const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => { const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => {
@ -235,7 +277,7 @@ const Account = () => {
return ( return (
<> <>
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards} <Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
isLoading={isLoading}/> isLoading={isLoading} />
{transactions.length > 0 && ( {transactions.length > 0 && (
<div className="overflow-x-auto py-2"> <div className="overflow-x-auto py-2">
<table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm"> <table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">
@ -247,7 +289,8 @@ const Account = () => {
<th className="px-4 py-3 text-center">Active</th> <th className="px-4 py-3 text-center">Active</th>
<th className="px-4 py-3 text-center">Last Change</th> <th className="px-4 py-3 text-center">Last Change</th>
<th className="px-4 py-3 text-center">Expire After (days)</th> <th className="px-4 py-3 text-center">Expire After (days)</th>
<th className="px-4 py-3 text-center"></th> <th className="px-4 py-3 text-center">Sign</th>
<th className="px-4 py-3 text-center">Action</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-200"> <tbody className="divide-y divide-gray-200">
@ -284,6 +327,9 @@ const Account = () => {
<td className="px-4 py-3 text-center"> <td className="px-4 py-3 text-center">
{tx.expireAfter ?? "-"} {tx.expireAfter ?? "-"}
</td> </td>
<td className="px-4 py-3 text-center ">
{tx?.createdBy?.email === currentEmailId ? <button className="px-2 py-0.5 rounded-xl text-xs font-medium bg-green-100 text-green-700 cursor-pointer">Sign now</button> : "-"}
</td>
<td className="px-4 py-3 text-center"> <td className="px-4 py-3 text-center">
<PiDotsThreeOutlineVerticalFill className="text-lg text-slate-300 hover:text-black mx-auto" /> <PiDotsThreeOutlineVerticalFill className="text-lg text-slate-300 hover:text-black mx-auto" />
</td> </td>

View File

@ -1,21 +1,13 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
// import { useSelector } from 'react-redux';
import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Link, useLocation, useNavigate } from 'react-router-dom';
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa"; import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa";
import api from '../../config/api'; import api from '../../config/api';
// import { FaUser, FaSignOutAlt, FaBuilding, FaChevronDown, FaCheck } from 'react-icons/fa';
// import { RootState } from '../../store/store';
// import { UserState } from '../../store/slices/userSlice';
interface AccountNavProps { interface AccountNavProps {
accountName?: string | null; accountName?: string | null;
transactionName?: string | null; transactionName?: string | null;
} }
// Define the shape of state object
interface Account { interface Account {
id: number; id: number;
isDefault: boolean; isDefault: boolean;
@ -23,32 +15,45 @@ interface Account {
} }
interface UserData { interface UserData {
id: string; id: number;
firstName: string; name: string;
lastName: string;
email: string; email: string;
language: string; language: string;
account: Account; account: Account;
} }
interface AccountDataArray { interface AccountDataArray {
accountId: number, accountName: string, role: string, status: boolean userId: number,
userEmail: string,
name: string,
accountId: number;
accountName: string;
role: string;
status: string;
} }
// Helper function to dispatch custom storage event
const dispatchSessionStorageChange = (key: string, newValue: any) => {
window.dispatchEvent(new CustomEvent('sessionStorageChange', {
detail: { key, newValue }
}));
};
const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transactionName = null }) => { const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transactionName = null }) => {
// const user: UserState = useSelector((state: RootState) => state.user); const activeAccountId = JSON.parse(sessionStorage.getItem('activeAccountId') || 'null');
const userDetails = JSON.parse(sessionStorage.getItem('userDetails') || '{}');
console.log("activeAccountId", activeAccountId);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [accountsOpen, setAccountsOpen] = useState(false); const [accountsOpen, setAccountsOpen] = useState(false);
const [loadingAccounts, setLoadingAccounts] = useState(false); const [loadingAccounts, setLoadingAccounts] = useState(false);
const [accounts, setAccounts] = useState<AccountDataArray[]>([]); const [accounts, setAccounts] = useState<AccountDataArray[]>([]);
const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); // Track if modal was closed const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false);
const dropdownRef = useRef(null); const dropdownRef = useRef<HTMLDivElement>(null);
const [user, setUser] = useState<UserData>({ const [user, setUser] = useState<UserData>({
id: "", id: 0,
firstName: "", name: "",
lastName: "",
email: "", email: "",
language: "", language: "",
account: { account: {
@ -58,13 +63,17 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
}, },
}); });
console.log("user", user)
const getAccountDetails = async () => { const getAccountDetails = async () => {
try { try {
let res = await api.get('/api/user/accounts') let res = await api.get('/api/user/accounts');
let items = res?.data let items = res?.data;
console.log('items', items) console.log('items', items);
// Extract only the fields you want
const cleanedData: AccountDataArray[] = items.map((item: any) => ({ const cleanedData: AccountDataArray[] = items.map((item: any) => ({
userId: item.userId,
userEmail: item.userEmail,
name: item.name,
accountId: item.accountId, accountId: item.accountId,
accountName: item.accountName, accountName: item.accountName,
role: item.role, role: item.role,
@ -73,10 +82,9 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
setAccounts(cleanedData); setAccounts(cleanedData);
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} };
const handleAccountsClick = async () => { const handleAccountsClick = async () => {
setAccountsOpen(!accountsOpen); setAccountsOpen(!accountsOpen);
@ -84,78 +92,127 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
if (!accountsOpen) { if (!accountsOpen) {
setLoadingAccounts(true); setLoadingAccounts(true);
// Fetch API if:
// 1. No accounts data exists (first time), OR
// 2. Modal was previously closed (need fresh data)
if (accounts.length === 0 || hasModalBeenClosed) { if (accounts.length === 0 || hasModalBeenClosed) {
await getAccountDetails(); await getAccountDetails();
setHasModalBeenClosed(false); // Reset the flag after fetching setHasModalBeenClosed(false);
} }
setLoadingAccounts(false); setLoadingAccounts(false);
} }
}; };
const handleAccountSwitch = (account) => { const handleAccountSwitch = (account: any) => {
setUser(prev => ({ console.log("handleAccountSwitch", account);
...prev,
account: { name: account.name, id: account.id }
}));
// Update accounts active status // Update sessionStorage
setAccounts(prevAccounts => sessionStorage.setItem('activeAccountId', JSON.stringify(account.accountId));
prevAccounts.map(acc => ({
...acc, // Dispatch custom event
isActive: acc.id === account.id dispatchSessionStorageChange('activeAccountId', account.accountId);
}))
); // Update user state
setUser({
id: account.userId,
name: account.name,
email: account.userEmail,
language: "", // set it if available
account: {
id: account.accountId,
isDefault: false, // update as needed
name: account.accountName,
},
});
sessionStorage.setItem('userDetails', JSON.stringify({
id: account.userId,
name: account.name,
email: account.userEmail,
language: "", // set it if available
account: {
id: account.accountId,
isDefault: false, // update as needed
name: account.accountName,
},
}));
setAccountsOpen(false); setAccountsOpen(false);
setOpen(false); setOpen(false);
}; };
const navigate = useNavigate()
const navigate = useNavigate();
const getUserDetails = async () => { const getUserDetails = async () => {
try { try {
let res = await api.get(`/api/user/details`) let res = await api.get(`/api/user/details`);
console.log("getuser", res) console.log("getuser", res);
setUser(res?.data) const item = res?.data;
if (item) {
const userData = {
id: item?.id,
name : item?.firstName + " " + item?.lastName ,
email: item?.email,
language: "",
account: {
id: item?.account.id,
isDefault: false,
name: item?.account.name,
},
};
console.log("userData",userData)
setUser(userData);
sessionStorage.setItem('userDetails', JSON.stringify(userData));
sessionStorage.setItem('activeAccountId', JSON.stringify(userData.account.id));
// Dispatch event for initial load
dispatchSessionStorageChange('activeAccountId', userData.account.id);
console.log("Data is saved in sessionStorage");
}
} catch (error) { } catch (error) {
console.error("Error fetching user details:", error); console.error("Error fetching user details:", error);
} }
} };
useEffect(() => { useEffect(() => {
getUserDetails() if (!activeAccountId) {
}, []) getUserDetails();
} else {
setUser(userDetails);
}
}, []);
const location = useLocation(); const location = useLocation();
const pathnames = location.pathname const rawPathnames = location.pathname
.split("/") .split("/")
.filter((x) => x && isNaN(Number(x))); // remove numeric IDs .filter((x) => x && isNaN(Number(x)));
// Special case: if first segment is 's' and next is 'settings', remove 's'
const pathnames =
rawPathnames[0] === "s" && rawPathnames[1] === "settings"
? rawPathnames.slice(1)
: rawPathnames;
const labelMap: Record<string, string> = { const labelMap: Record<string, string> = {
accounts: "Accounts", accounts: "Accounts",
transactions: "Transactions", transactions: "Transactions",
templates: "Templates" templates: "Templates"
// Add more routes here if needed
}; };
// const [open, setOpen] = useState(false);
// const dropdownRef = useRef<HTMLDivElement>(null);
// Close dropdown when clicked outside
useEffect(() => { useEffect(() => {
function handleClickOutside(event: MouseEvent) { function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
if (open) { if (open) {
setHasModalBeenClosed(true); // Mark modal as closed when clicking outside setHasModalBeenClosed(true);
} }
setOpen(false); setOpen(false);
setAccountsOpen(false); // Also close accounts dropdown setAccountsOpen(false);
} }
} }
@ -165,24 +222,24 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
const handleLogout = async () => { const handleLogout = async () => {
try { try {
let res = await api.post('/api/auth/logout') let res = await api.post('/api/auth/logout');
if (res.status === 200) { if (res.status === 200) {
sessionStorage.clear();
setOpen(false); setOpen(false);
navigate("/login") navigate("/login");
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
}; };
// Handle main dropdown toggle
const handleMainDropdownToggle = () => { const handleMainDropdownToggle = () => {
if (open) { if (open) {
setHasModalBeenClosed(true); // Mark as closed when manually closing setHasModalBeenClosed(true);
} }
setOpen(!open); setOpen(!open);
if (!open) { if (!open) {
setAccountsOpen(false); // Close accounts dropdown when opening main dropdown setAccountsOpen(false);
} }
}; };
@ -191,7 +248,6 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
.replace(/[-_]/g, " ") .replace(/[-_]/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase()); .replace(/\b\w/g, (char) => char.toUpperCase());
const ShimmerRow = () => ( const ShimmerRow = () => (
<div className="animate-pulse px-4 py-3 border-b border-gray-100 last:border-b-0"> <div className="animate-pulse px-4 py-3 border-b border-gray-100 last:border-b-0">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
@ -205,7 +261,6 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
</div> </div>
); );
return ( return (
<header className="w-full px-6 py-2 flex justify-between items-center bg-white border-b rounded-sm border-gray-200"> <header className="w-full px-6 py-2 flex justify-between items-center bg-white border-b rounded-sm border-gray-200">
<nav className="text-md font-medium text-gray-800 capitalize flex items-center flex-wrap"> <nav className="text-md font-medium text-gray-800 capitalize flex items-center flex-wrap">
@ -237,10 +292,7 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
</nav> </nav>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{/* User Dropdown */}
<div className="relative" ref={dropdownRef}> <div className="relative" ref={dropdownRef}>
{/* Avatar Button */}
<button <button
className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors" className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors"
onClick={handleMainDropdownToggle} onClick={handleMainDropdownToggle}
@ -249,34 +301,31 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
> >
<div className="w-8 h-8 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md"> <div className="w-8 h-8 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md">
{user ? ( {user ? (
<span <span className="w-full h-full object-cover p-1.5 font-bold">
className="w-full h-full object-cover p-1.5 font-bold" {(user?.name?.slice(0, 2)).toUpperCase()}
>{(user?.firstName + " " + user?.lastName).split(' ').map((item) => item.slice(0, 1)).join("").toUpperCase()}</span> </span>
) : ( ) : (
<FaUser className="text-lg" /> <FaUser className="text-lg" />
)} )}
</div> </div>
</button> </button>
{/* Enhanced Dropdown */}
{open && ( {open && (
<div className="absolute right-0 mt-2 w-72 top-8 rounded-xl shadow-xl bg-white border border-gray-200 z-50 overflow-hidden"> <div className="absolute right-0 mt-2 w-72 top-8 rounded-xl shadow-xl bg-white border border-gray-200 z-50 overflow-hidden">
{/* User Info Section */}
<div className="px-4 py-4 bg-gradient-to-r from-blue-50 to-purple-50 border-b border-gray-100"> <div className="px-4 py-4 bg-gradient-to-r from-blue-50 to-purple-50 border-b border-gray-100">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className="w-12 h-12 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold shadow-md"> <div className="w-12 h-12 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold shadow-md">
{(user) ? ( {user ? (
<span <span className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold">
className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold" {(user?.name?.slice(0, 2)).toUpperCase()}
> </span>
{(user?.firstName + " " + user?.lastName).split(' ').map((item) => item.slice(0, 1)).join("").toUpperCase()}</span>
) : ( ) : (
<FaUser className="text-lg" /> <FaUser className="text-lg" />
)} )}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="font-semibold text-gray-900 truncate">{user.firstName + ' ' + user.lastName || '-'}</div> <div className="font-semibold text-gray-900 truncate">{user?.name || '-'}</div>
<div className="text-sm text-gray-600 truncate">{user.email || '-'}</div> <div className="text-sm text-gray-600 truncate">{user?.email || '-'}</div>
<div className="text-xs text-gray-500 mt-1"> <div className="text-xs text-gray-500 mt-1">
Account #{user?.account?.name || '-'} Account #{user?.account?.name || '-'}
</div> </div>
@ -284,7 +333,6 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
</div> </div>
</div> </div>
{/* Account List Section */}
<div className="border-b border-gray-100"> <div className="border-b border-gray-100">
<button <button
onClick={handleAccountsClick} onClick={handleAccountsClick}
@ -297,7 +345,6 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
<FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} /> <FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} />
</button> </button>
{/* Accounts Dropdown */}
{accountsOpen && ( {accountsOpen && (
<div className="bg-gray-50 max-h-64 overflow-y-auto"> <div className="bg-gray-50 max-h-64 overflow-y-auto">
{loadingAccounts ? ( {loadingAccounts ? (
@ -307,25 +354,25 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
</div> </div>
) : ( ) : (
<div className="divide-y divide-gray-200"> <div className="divide-y divide-gray-200">
{accounts.length > 0 ? (accounts?.map((account) => ( {accounts?.length > 0 ? (
accounts?.map((account) => (
<button <button
key={account.accountId} key={account.accountId}
// onClick={() => handleAccountSwitch(account)} onClick={() => handleAccountSwitch(account)}
className="w-full px-4 py-3 hover:bg-white transition-colors cursor-pointer text-left" className={`w-full px-4 py-3 hover:bg-white transition-colors cursor-pointer text-left`}
> >
<div className="flex items-center justify-between"> <div className={`flex items-center justify-between`}>
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-gray-200 to-gray-400 flex items-center justify-center text-white font-semibold text-xs"> <div className="w-8 h-8 rounded-full bg-gradient-to-br from-gray-200 to-gray-400 flex items-center justify-center text-white font-semibold text-xs">
{account.accountName.slice(0, 2).toUpperCase()} {/* {account.accountName.slice(0, 2).toUpperCase()} */}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<span className="font-medium text-gray-900 text-sm">{account.accountName}</span> <span className="font-medium text-gray-900 text-sm">{account.accountName}</span>
{account.status && ( {account.accountId === activeAccountId && (
<FaCheck className="text-green-500 text-xs" /> <FaCheck className="text-green-600 text-xs" />
)} )}
</div> </div>
<div className="text-xs text-gray-600 truncate">{account.company}</div>
</div> </div>
</div> </div>
<div className="text-xs text-gray-500 bg-gray-200 px-2 py-1 rounded"> <div className="text-xs text-gray-500 bg-gray-200 px-2 py-1 rounded">
@ -333,16 +380,17 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
</div> </div>
</div> </div>
</button> </button>
))) : <span className='text-gray-400 font-medium text-[12px] flex justify-center p-3'>No Account...</span>} ))
) : (
<span className='text-gray-400 font-medium text-[12px] flex justify-center p-3'>No Account...</span>
)}
</div> </div>
)} )}
</div> </div>
)} )}
</div> </div>
{/* Logout Section */} <div className="border-t border-gray-100">
<div className="border-t border-gray-100 ">
<button <button
onClick={handleLogout} onClick={handleLogout}
className="w-full flex items-center px-4 py-3 text-sm text-red-600 hover:bg-red-50 transition-colors cursor-pointer" className="w-full flex items-center px-4 py-3 text-sm text-red-600 hover:bg-red-50 transition-colors cursor-pointer"

View File

@ -42,6 +42,7 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
headers: { "ngrok-skip-browser-warning": "69420" } headers: { "ngrok-skip-browser-warning": "69420" }
} }
); );
sessionStorage.clear();
// Redirect to login or refresh the page after logout // Redirect to login or refresh the page after logout
window.location.href = '/login'; window.location.href = '/login';
} catch (err) { } catch (err) {

View File

@ -61,8 +61,8 @@ const Home = () => {
trend: 'up' trend: 'up'
}, },
{ {
title: 'Waiting for Others', title: 'Draft',
count: data.waitingForOthers || 0, count: data.draft || 0,
icon: <FaClock size={18} className="text-stone-600" />, icon: <FaClock size={18} className="text-stone-600" />,
gradient: 'bg-gradient-to-br from-blue-50 to-blue-100', gradient: 'bg-gradient-to-br from-blue-50 to-blue-100',
shadowColor: 'shadow-blue-200/50', shadowColor: 'shadow-blue-200/50',
@ -70,69 +70,27 @@ const Home = () => {
textColor: 'text-blue-700', textColor: 'text-blue-700',
trend: 'down' trend: 'down'
}, },
// {
// title: 'declined',
// count: data.declined || 0,
// icon: <FaClock size={18} className="text-purple-600" />,
// gradient: 'bg-gradient-to-br from-purple-50 to-purple-100',
// shadowColor: 'shadow-purple-200/50',
// iconBg: 'bg-purple-100 border-purple-200',
// textColor: 'text-purple-700',
// trend: 'down'
// },
// {
// title: 'cancelled',
// count: data.cancelled || 0,
// icon: <FaClock size={18} className="text-green-600" />,
// gradient: 'bg-gradient-to-br from-green-50 to-green-100',
// shadowColor: 'shadow-green-200/50',
// iconBg: 'bg-green-100 border-green-200',
// textColor: 'text-green-700',
// trend: 'down'
// },
// {
// title: 'draft',
// count: data.draft || 0,
// icon: <FaClock size={18} className="text-stone-600" />,
// gradient: 'bg-gradient-to-br from-stone-50 to-stone-100',
// shadowColor: 'shadow-stone-200/50',
// iconBg: 'bg-stone-100 border-stone-200',
// textColor: 'text-stone-700',
// trend: 'down'
// }
// Add more cards here if needed
]; ];
}; };
const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
const [dashboardData, setDashboardData] = useState<any>(null); const [dashboardData, setDashboardData] = useState<any>(null);
const [fromDate, setFromDate] = useState<string>(''); // ISO string const [fromDate, setFromDate] = useState<string>('');
const [toDate, setToDate] = useState<string>(''); // ISO string const [toDate, setToDate] = useState<string>('');
const [cards, setCards] = useState<CardData[]>([]); const [cards, setCards] = useState<CardData[]>([]);
const [accountID, setAccountID] = useState<number>()
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
console.log("cards", cards) console.log("activeAccountId", activeAccountId);
console.log("cards", cards);
const getUserDetails = async () => { const getDashboardData = async () => {
try { if (!activeAccountId) return;
let res = await api.get('/api/user/details')
let accountIDData = res?.data?.account?.id
setAccountID(accountIDData)
} catch (error) {
console.log(error)
}
}
useEffect(() => { setIsLoading(true);
getUserDetails()
}, [])
const getDashboardData = async (from: string, to: string) => {
setIsLoading(true)
const payload = { const payload = {
fromDate: from, // already ISO string account: activeAccountId
toDate: to,
account : accountID
}; };
try { try {
@ -143,8 +101,8 @@ const Home = () => {
setCards(cardDataArray); setCards(cardDataArray);
} catch (error) { } catch (error) {
console.error("Error fetching dashboard data:", error); console.error("Error fetching dashboard data:", error);
}finally{ } finally {
setIsLoading(false) setIsLoading(false);
} }
}; };
@ -155,16 +113,41 @@ const Home = () => {
setToDate(isoTo); setToDate(isoTo);
}, []); }, []);
// Listen for sessionStorage changes
useEffect(() => { useEffect(() => {
if (fromDate && toDate && accountID) { const handleStorageChange = (e: StorageEvent) => {
getDashboardData(fromDate, toDate); if (e.key === 'activeAccountId') {
const newActiveAccountId = e.newValue ? JSON.parse(e.newValue) : null;
setActiveAccountId(newActiveAccountId);
} }
}, [fromDate, toDate , accountID]); };
// Listen for storage events (works for changes from other tabs)
window.addEventListener('storage', handleStorageChange);
// Custom event listener for same-tab changes
const handleCustomStorageChange = (e: CustomEvent) => {
if (e.detail.key === 'activeAccountId') {
setActiveAccountId(e.detail.newValue);
}
};
window.addEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
// Set default range on mount (last 7 days) return () => {
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
};
}, []);
// Re-fetch dashboard data when activeAccountId changes
useEffect(() => {
if (activeAccountId) {
getDashboardData();
}
}, [activeAccountId]);
// Set default date range on mount (last 7 days)
useEffect(() => { useEffect(() => {
const now = new Date(); const now = new Date();
const from = new Date(); const from = new Date();

View File

@ -0,0 +1,354 @@
import React, { useEffect } from "react";
import {
FaCheck,
FaFileAlt,
FaUsers,
FaEnvelope,
FaClock,
FaDownload,
FaShare,
FaShieldAlt,
FaLock,
FaPen,
FaCheckCircle
} from 'react-icons/fa';
import { fetchRecipients } from "../../../store/slices/selectedRecipientsSlice";
import { fetchDocuments } from "../../../store/slices/documentSlice";
import { useDispatch, useSelector } from "react-redux";
import { Link, useNavigate, useParams } from "react-router-dom";
import Loader from "../../dashboard/Loader";
import confetti from 'canvas-confetti'
import { IoClose } from "react-icons/io5";
import { IoMdHome } from "react-icons/io";
type Signer = {
id: number;
name: string;
email: string;
routingOrder: number;
completed: boolean;
};
// Signers List Component
const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
return (
<div className="bg-white rounded-md shadow-sm border border-gray-200">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-800 flex items-center gap-2">
<FaUsers className="text-blue-600" />
Signature Status
</h3>
<span className="text-sm text-gray-600">
{signers.filter(s => s.completed).length} of {signers.length} signed
</span>
</div>
<div className="space-y-3 max-h-[29vh] overflow-y-auto no-scrollbar">
{signers.map((signer) => (
<div key={signer.id} className="flex items-center gap-4 p-3 bg-gray-50 rounded-lg">
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center text-blue-600 font-medium">
{signer.name.split(" ").map(n => n[0].toUpperCase()).join("")}
</div>
<div className="flex-1">
<div className="font-medium text-gray-800">{signer.name}</div>
<div className="text-sm text-gray-600">{signer.email}</div>
<div className="text-xs text-gray-500">Order: {signer.routingOrder}</div>
</div>
<div className="flex items-center gap-2">
{signer.completed ? (
<div className="flex items-center gap-1 px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm">
<FaCheck className="w-3 h-3" />
Signed
</div>
) : (
<div className="flex items-center gap-1 px-3 py-1 bg-orange-100 text-orange-700 rounded-full text-sm">
<FaClock className="w-3 h-3" />
Pending
</div>
)}
</div>
</div>
))}
</div>
</div>
</div>
);
};
// Fixed Signing Order Diagram Component
const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
const groupByRoutingOrder = (data: Signer[]) => {
const grouped: Record<number, Signer[]> = {};
data.forEach((signer) => {
if (!grouped[signer.routingOrder]) {
grouped[signer.routingOrder] = [];
}
grouped[signer.routingOrder].push(signer);
});
return grouped;
};
const getInitials = (name: string) => {
return name
.split(" ")
.map((n) => n[0].toUpperCase())
.join("");
};
const grouped = groupByRoutingOrder(signers);
const sortedOrders = Object.keys(grouped)
.map(Number)
.sort((a, b) => a - b);
const currentProcessingOrder =
sortedOrders.find((order) =>
grouped[order].some((signer) => !signer.completed)
) || -1;
return (
<div className="bg-white rounded-md shadow-sm border border-gray-200">
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">Signing Flow</h3>
<div className="overflow-x-auto overflow-y-auto max-h-96 no-scrollbar ">
<div className="flex justify-center items-center gap-4 min-w-max">
{/* Mail icon */}
<div className="flex flex-col items-center">
<div className="bg-blue-100 p-4 rounded-full">
<FaEnvelope className="text-blue-600 text-2xl" />
</div>
<p className="text-sm text-gray-600 mt-2">Sent</p>
</div>
{/* Dotted line */}
<div className="flex items-center">
<div className="w-16 border-t-2 border-dotted border-gray-300"></div>
</div>
{/* Signers by routing order */}
<div className="flex items-center justify-center space-x-6">
{sortedOrders.map((order, index) => (
<React.Fragment key={order}>
<div className="flex flex-col items-center space-y-4">
<div className="text-sm font-medium text-gray-600 mb-2">
Step {order}
</div>
<div className="flex flex-col items-center space-y-3">
{grouped[order].map((signer) => {
let bgColor = "";
let icon = null;
if (signer.completed) {
bgColor = "bg-[var(--primary)]";
icon = <FaCheck className="text-green-600" />;
} else if (order === currentProcessingOrder) {
bgColor = "bg-yellow-500";
icon = <FaClock className="text-yellow-800" />;
} else {
bgColor = "bg-gray-400";
icon = <FaLock className="text-gray-500" />;
}
return (
<div key={signer.id} className="relative">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center text-white text-sm font-semibold ${bgColor}`}
title={signer.name}
>
{getInitials(signer.name)}
<span className="absolute bottom-[-4px] right-[-8px] p-1 rounded-full bg-white shadow">
{icon}
</span>
</div>
<p className="mt-1 text-xs text-gray-600 text-center max-w-16 truncate">
{signer.name.split(" ")[0]}
</p>
</div>
);
})}
</div>
</div>
{index < sortedOrders.length - 1 && (
<div className="flex items-center">
<div className="w-16 border-t-2 border-dotted border-gray-300"></div>
</div>
)}
</React.Fragment>
))}
</div>
</div>
</div>
</div>
</div>
);
};
// Main DocumentCompletionScreen Component
const DocumentCompletionScreen = () => {
const documentList = [
{ title: 'Document 1', pages: 12 },
{ title: 'Document 2', pages: 8 },
{ title: 'Document 3', pages: 5 },
{ title: 'Document 4', pages: 22 },
{ title: 'Document 5', pages: 10 },
{ title: 'Document 6', pages: 15 },
{ title: 'Document 7', pages: 7 },
{ title: 'Document 8', pages: 9 },
{ title: 'Document 9', pages: 6 },
{ title: 'Document 10', pages: 11 },
{ title: 'Document 11', pages: 4 }, // Won't be shown due to slice
];
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const [showConfetti, setShowConfetti] = React.useState(true)
if (!accountId || !transactionId) {
window.location.href = "/";
return null;
}
// const transactionId = useSelector((state: RootState) => state.transaction.transactionId)
const navigate = useNavigate();
const dispatch = useDispatch();
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
useEffect(() => {
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
}, [])
useEffect(() => {
if (showConfetti) {
const duration = 3000;
const end = Date.now() + duration;
const interval = setInterval(() => {
if (Date.now() > end) {
clearInterval(interval);
return;
}
confetti({
particleCount: 80, // more particles
angle: Math.random() * 360, // fire in random direction
spread: 220, // wider spread
startVelocity: 15, // more power
scalar: 1.2, // bigger pieces (default is 1)
origin: {
x: Math.random(),
y: Math.random() * 0.6,
},
zIndex: 9999,
});
}, 350); // fire every 150ms
}
}, [showConfetti]);
return (
<>
<>
{
showConfetti && (
<div className="absolute top-0 left-0 w-full h-full z-[9999] bg-white/80 backdrop-blur-sm flex items-center justify-center">
{/* <Confetti width={width} height={height} /> */}
<div className="relative bg-white bg-opacity-90 backdrop-blur-lg border border-green-200 shadow-xl rounded-md px-8 py-6 w-[90%] max-w-sm text-center animate-fade-in-up">
{/* Close Icon */}
<button
onClick={() => setShowConfetti(false)} // or whatever logic hides the modal
className="absolute top-2 right-2 text-gray-500 hover:text-gray-700"
aria-label="Close"
>
<IoClose className="text-2xl" />
</button>
{/* Success Icon */}
<FaCheckCircle className="text-green-500 text-5xl mb-4 mx-auto drop-shadow-lg" />
{/* Message */}
<h2 className="text-2xl font-semibold text-gray-800 mb-1">Transaction Successful</h2>
<p className="text-gray-600 text-sm">Your transaction has been created and saved successfully.</p>
</div>
</div>
)
}
</>
<div className="min-h-screen bg-[var(--mainViewBg)]">
{/* Top Banner */}
<div className="bg-[var(--primary)] text-white shadow-lg">
<div className="max-w-[97vw] mx-auto px-2 py-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Link to='/' className="w-5 h-5 rounded-full flex items-center justify-center">
<IoMdHome className="text-xl" />
</Link>
<div className="w-5 h-5 bg-white/20 rounded-full flex items-center justify-center">
<FaCheck className="text-sm" />
</div>
<div>
<h2 className="text-lg font-bold text-white/80">Awesome! Your document is sent for signature</h2>
</div>
</div>
<Link to={`/accounts/${accountId}/transactions/${transactionId}`} className="flex items-center gap-1 cursor-pointer bg-white text-green-600 px-2.5 py-1 rounded-md font-semibold hover:bg-gray-100 transition-colors">
<FaPen className="text-[10px]" />
<span className="text-[13px]">Edit</span>
</Link>
</div>
</div>
</div>
{(selectedRecipients.length > 0 && selectedDocuments.length > 0) ?
<div className="p-3">
<div className="max-w-[97vw] mx-auto">
{/* Header */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-2">
{/* Left Section - Document Details */}
<div className="lg:col-span-5 space-y-3">
{/* Document Preview Card */}
<div className="bg-white rounded-md shadow-sm border border-gray-200 overflow-hidden max-h-[88vh] no-scrollbar overflow-y-auto">
<div className="p-3 ">
{selectedDocuments?.map((item, index) => (
<div key={item.id} className="flex items-start justify-between mb-2 px-2 py-3 bg-gray-50">
<div className="flex items-center gap-4">
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
{/* <FaFileAlt className="text-blue-600 text-xl" /> */}
<img src={item.images[0].src} alt="document" className="h-10 w-10" />
</div>
<div>
<h3 className="text-md font-semibold text-gray-800">
Name : {item.name ?? '--'}
</h3>
<div className="text-gray-600 text-sm">
Status : {item.status ?? '--'}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Right Section - Signer Components */}
<div className="lg:col-span-7 space-y-1.5">
{/* Signing Order Diagram */}
<SigningOrderDiagram signers={selectedRecipients} />
{/* Signers List */}
<SignersList signers={selectedRecipients} />
</div>
</div>
</div>
</div>
: <Loader />}
</div>
</>
);
};
export default DocumentCompletionScreen;

View File

@ -10,7 +10,7 @@ import { MdKeyboardArrowDown } from 'react-icons/md'
import { RiResetLeftLine, RiResetRightLine } from 'react-icons/ri' import { RiResetLeftLine, RiResetRightLine } from 'react-icons/ri'
import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice' import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice'
import { fetchDocuments } from '../../../store/slices/documentSlice' import { fetchDocuments } from '../../../store/slices/documentSlice'
import { useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
interface NavTwoProps { interface NavTwoProps {
isFields: boolean; isFields: boolean;
@ -57,6 +57,8 @@ const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSen
const [isFitDd, setIsFitDd] = useState<boolean>(false) const [isFitDd, setIsFitDd] = useState<boolean>(false)
const [selFitIndx, setSelFitIndx] = useState<number>(0); const [selFitIndx, setSelFitIndx] = useState<number>(0);
const navigate = useNavigate()
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients) const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents) const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
@ -76,6 +78,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSen
setIsSend(false); setIsSend(false);
} }
setIsSend(!isSend) setIsSend(!isSend)
navigate(`/document-completion-screen/${accountId}/${transactionId}`)
} }
const handleSetIsFields = (setTo: boolean) => { const handleSetIsFields = (setTo: boolean) => {

View File

@ -101,7 +101,7 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
<button onClick={() => handleMove('right')} disabled={disabled} className={buttonClass} title="Move right" type="button"> <button onClick={() => handleMove('right')} disabled={disabled} className={buttonClass} title="Move right" type="button">
<FaArrowRight size={12} /> <FaArrowRight size={12} />
</button> </button>
<span className="mt-1">Right: {Math.round(right)}px</span> {/* <span className="mt-1">Right: {Math.round(right)}px</span> */}
</div> </div>
{/* Bottom Row */} {/* Bottom Row */}
@ -110,7 +110,7 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
<button onClick={() => handleMove('down')} disabled={disabled} className={buttonClass} title="Move down" type="button"> <button onClick={() => handleMove('down')} disabled={disabled} className={buttonClass} title="Move down" type="button">
<FaArrowDown size={12} /> <FaArrowDown size={12} />
</button> </button>
<span className="mt-1">Bottom: {Math.round(bottom)}px</span> {/* <span className="mt-1">Bottom: {Math.round(bottom)}px</span> */}
</div> </div>
<div></div> <div></div>
</div> </div>

View File

@ -19,8 +19,7 @@ interface Account {
interface UserData { interface UserData {
id: string; id: string;
firstName: string; name: string;
lastName: string;
email: string; email: string;
language: string; language: string;
account: Account; account: Account;
@ -43,9 +42,15 @@ interface AccountOption {
const Setting = () => { const Setting = () => {
const { page1 } = useParams<{ page1: string }>(); const { page1 } = useParams<{ page1: string }>();
console.log("page1", page1)
const location = useLocation(); const location = useLocation();
console.log("location", location)
const navigate = useNavigate(); const navigate = useNavigate();
const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
// Refs for subsections within each main tab // Refs for subsections within each main tab
const subSectionRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const subSectionRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
const scrollContainerRef = useRef<HTMLDivElement>(null); const scrollContainerRef = useRef<HTMLDivElement>(null);
@ -53,15 +58,14 @@ const Setting = () => {
const [activeTab, setActiveTab] = useState("userProfile"); const [activeTab, setActiveTab] = useState("userProfile");
const [activeSubSection, setActiveSubSection] = useState<string>(""); const [activeSubSection, setActiveSubSection] = useState<string>("");
const [user, setUser] = useState<UserData>({ const [user, setUser] = useState<UserData>({
id: "", id: '',
firstName: "", name: '',
lastName: "", email: '',
email: "", language: '',
language: "",
account: { account: {
id: 0, id: 0,
isDefault: false, isDefault: false,
name: "", name: '',
}, },
}); });
@ -238,18 +242,58 @@ const Setting = () => {
{ id: "contactShare", label: "Share Contact", type: "contact" }, { id: "contactShare", label: "Share Contact", type: "contact" },
]; ];
const getUserDetails = async () => {
try { // Listen for sessionStorage changes
let res = await api.get(`/api/user/details`) useEffect(() => {
console.log("getuser", res) const handleStorageChange = (e: StorageEvent) => {
setUser(res?.data) if (e.key === 'activeAccountId') {
} catch (error) { const newActiveAccountId = e.newValue ? JSON.parse(e.newValue) : null;
console.error("Error fetching user details:", error); setActiveAccountId(newActiveAccountId);
} }
};
// Listen for storage events (works for changes from other tabs)
window.addEventListener('storage', handleStorageChange);
// Custom event listener for same-tab changes
const handleCustomStorageChange = (e: CustomEvent) => {
if (e.detail.key === 'activeAccountId') {
setActiveAccountId(e.detail.newValue);
} }
};
window.addEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
return () => {
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
};
}, []);
// Re-fetch dashboard data when activeAccountId changes
useEffect(() => {
const rawUser = JSON.parse(sessionStorage.getItem('userDetails') || 'null');
if (rawUser) {
const userData: UserData = {
id: String(rawUser.id || ''),
name: rawUser.name || '',
email: rawUser.email || '',
language: rawUser.language || '',
account: {
id: rawUser.account?.id || 0,
isDefault: rawUser.account?.isDefault || false,
name: rawUser.account?.name || '',
},
};
setUser(userData);
}
}, [activeAccountId]);
useEffect(() => { useEffect(() => {
getUserDetails()
// Fetch account list when component mounts // Fetch account list when component mounts
getAccountList() getAccountList()
}, []) }, [])
@ -257,7 +301,7 @@ const Setting = () => {
// Set initial subsection when component mounts or activeTab changes // Set initial subsection when component mounts or activeTab changes
useEffect(() => { useEffect(() => {
const subSections = getSubSections(activeTab); const subSections = getSubSections(activeTab);
console.log("subSections",subSections) console.log("subSections", subSections)
const hash = location.hash.replace('#', ''); const hash = location.hash.replace('#', '');
if (subSections.length > 0) { if (subSections.length > 0) {
@ -290,7 +334,7 @@ const Setting = () => {
setCreateAndInviteLoader(true) setCreateAndInviteLoader(true)
try { try {
let res; let res;
let toastMesaage ; let toastMesaage;
if (action === 'create') { if (action === 'create') {
console.log('Creating account:', data); console.log('Creating account:', data);
const payloadAccount = { const payloadAccount = {
@ -308,7 +352,7 @@ const Setting = () => {
role: data.role role: data.role
} }
res = await api.post(`/api/accounts/${data.i_accountName}/invite`, payloadInvite) res = await api.post(`/api/accounts/${data.i_accountName}/invite`, payloadInvite)
console.log("InviteNAme",res) console.log("InviteNAme", res)
toastMesaage = "Invite Successfull...!" toastMesaage = "Invite Successfull...!"
} }
@ -337,7 +381,7 @@ const Setting = () => {
return ( return (
<> <>
<SettingNav /> {/* <SettingNav /> */}
{/* <AccountNav/> */} {/* <AccountNav/> */}
<div className="bg-[#f9fafa] h-[calc(90vh-20px)] flex flex-col mt-2"> <div className="bg-[#f9fafa] h-[calc(90vh-20px)] flex flex-col mt-2">
{/* Main Navigation Tabs */} {/* Main Navigation Tabs */}

View File

@ -54,7 +54,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
getAllSignatures, getAllSignatures,
editingSignature editingSignature
}) => { }) => {
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT'); const [activeTab, setActiveTab] = useState<'TYPE' | 'DRAW' | 'UPLOAD' | 'SELECT'>('TYPE');
const [fullName, setFullName] = useState(''); const [fullName, setFullName] = useState('');
const [initials, setInitials] = useState(''); const [initials, setInitials] = useState('');
const [isSubmitting, setIsSubmitting] = useState<boolean>(false); const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
@ -102,9 +102,10 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
// Navigation items // Navigation items
const navigationItems = [ const navigationItems = [
{ id: 'SELECT', label: 'SELECT', icon: <GrCheckboxSelected className="w-4 h-4" /> }, { id: 'TYPE', label: 'TYPE', icon: <GrCheckboxSelected className="w-4 h-4" /> },
{ id: 'DRAW', label: 'DRAW', icon: <AiOutlineEdit className="w-5 h-5" /> }, { id: 'DRAW', label: 'DRAW', icon: <AiOutlineEdit className="w-5 h-5" /> },
{ id: 'UPLOAD', label: 'UPLOAD', icon: <AiOutlineUpload className="w-5 h-5" /> }, { id: 'UPLOAD', label: 'UPLOAD', icon: <AiOutlineUpload className="w-5 h-5" /> },
{ id: 'SELECT', label: 'SELECT', icon: <GrCheckboxSelected className="w-4 h-4" /> },
]; ];
// Initialize form with editing signature data // Initialize form with editing signature data
@ -112,7 +113,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
if (editingSignature) { if (editingSignature) {
setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`); setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`);
setInitials(`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`); setInitials(`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`);
setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD'); setActiveTab(editingSignature.signatureType as 'TYPE' | 'DRAW' | 'UPLOAD');
// Set font for SELECT tab // Set font for SELECT tab
const fontOption = signatureOptions.find(option => option.style === editingSignature.font); const fontOption = signatureOptions.find(option => option.style === editingSignature.font);
@ -262,7 +263,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
const renderContent = () => { const renderContent = () => {
switch (activeTab) { switch (activeTab) {
case 'SELECT': case 'TYPE':
return ( return (
<div className="h-full flex flex-col "> <div className="h-full flex flex-col ">
<div className="mb-6"> <div className="mb-6">
@ -383,6 +384,16 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
</div> </div>
); );
case 'SELECT':
return (
<div className="h-full flex flex-col">
<div className="mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-2">Select Signature</h2>
</div>
</div>
);
default: default:
return null; return null;
} }
@ -462,7 +473,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
{navigationItems.map((item) => ( {navigationItems.map((item) => (
<button <button
key={item.id} key={item.id}
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')} onClick={() => setActiveTab(item.id as 'TYPE' | 'DRAW' | 'UPLOAD')}
className={`w-full flex items-center space-x-3 px-2 py-2 text-sm font-medium rounded-lg cursor-pointer transition-colors ${activeTab === item.id className={`w-full flex items-center space-x-3 px-2 py-2 text-sm font-medium rounded-lg cursor-pointer transition-colors ${activeTab === item.id
? 'bg-blue-50 text-blue-700 border border-blue-200' ? 'bg-blue-50 text-blue-700 border border-blue-200'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900' : 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'

View File

@ -175,7 +175,7 @@ const SignatureManager: React.FC = () => {
<div className="flex flex-col space-y-2 relative ps-6 border-l-3 border-green-600 rounded-bl-2xl rounded-tl-2xl"> <div className="flex flex-col space-y-2 relative ps-6 border-l-3 border-green-600 rounded-bl-2xl rounded-tl-2xl">
{/* Label */} {/* Label */}
<div className="text-sm text-gray-600 font-medium mb-1"> <div className="text-sm text-gray-600 font-medium mb-1">
DS Initials
</div> </div>
{/* Signature Image */} {/* Signature Image */}
@ -186,6 +186,10 @@ const SignatureManager: React.FC = () => {
alt="DS Signature" alt="DS Signature"
/> />
</div> </div>
{/* Initial ID */}
<div className="text-xs text-gray-500 truncate">
{signature.initialsHash}
</div>
</div> </div>
</div> </div>

View File

@ -6,9 +6,8 @@ interface Account {
} }
interface UserData { interface UserData {
id: string; id: number;
firstName: string; name:string
lastName: string;
email: string; email: string;
language: string; language: string;
account: Account; account: Account;
@ -26,7 +25,7 @@ const UserProfile: React.FC<UserProfileProps> = ({ userData }) => {
{/* Profile Picture */} {/* Profile Picture */}
<div className="flex-shrink-0 text-center"> <div className="flex-shrink-0 text-center">
<div className="w-20 h-20 bg-green-800 text-white font-bold text-2xl rounded-full flex items-center justify-center shadow-lg"> <div className="w-20 h-20 bg-green-800 text-white font-bold text-2xl rounded-full flex items-center justify-center shadow-lg">
{getInitials(userData.firstName, userData.lastName)} {userData.name.slice(0,2).toUpperCase()}
</div> </div>
</div> </div>
@ -36,7 +35,7 @@ const UserProfile: React.FC<UserProfileProps> = ({ userData }) => {
<div className="flex py-1"> <div className="flex py-1">
<div className="text-gray-500 font-medium text-[13px] w-32">Name</div> <div className="text-gray-500 font-medium text-[13px] w-32">Name</div>
<div className="text-gray-400 font-medium text-[13px]"> <div className="text-gray-400 font-medium text-[13px]">
{userData.firstName} {userData.lastName} {userData.name}
</div> </div>
</div> </div>

View File

@ -64,13 +64,14 @@ const TransactionView = () => {
<div className="w-full px-6 py-4 rounded-tl-sm text-xs border-r-2 border-gray-200 overflow-y-auto"> <div className="w-full px-6 py-4 rounded-tl-sm text-xs border-r-2 border-gray-200 overflow-y-auto">
{/* Header */} {/* Header */}
<h1 className='text-lg font-semibold text-gray-800'> <h1 className='text-lg font-semibold text-gray-800'>
Complete with Forms Made Easy: <span className='text-slate-600'>{ transactionDetails.transaction.name ?? "Untitled"}</span> Complete with Forms Made Easy: <span className='text-slate-600'>{ transactionDetails?.transaction?.subject ?? "Untitled"}</span>
</h1> </h1>
<div className='mt-2'> <div className='mt-2'>
{/* new Date(transactionDetails?.transaction?.lastModifiedAt * 1000) */}
<p className='text-slate-600'>From <span className='text-blue-600'>Shanu.R</span></p> <p className='text-slate-600'>From <span className='text-blue-600'>Shanu.R</span></p>
<p className='text-slate-600'>Last Change on <span>12/02/25</span></p> <p className=''><span className='text-slate-600 font-semibold'>Last Change on</span> <span className='text-slate-400'>{new Date(transactionDetails?.transaction?.lastModifiedAt * 1000).toISOString()}</span></p>
<p className='text-slate-600'>Sent on <span>11/02/25</span></p> <p className='text-slate-600'>Created at <span>{new Date(transactionDetails?.transaction?.createdAt * 1000).toISOString()}</span></p>
</div> </div>
<p className='text-slate-600 text-[0.98rem] mt-2 font-semibold'>Draft</p> <p className='text-slate-600 text-[0.98rem] mt-2 font-semibold'>Draft</p>
@ -139,7 +140,7 @@ const TransactionView = () => {
<div> <div>
<h2 className='text-lg mt-4 font-semibold text-gray-800 cursor-pointer'>Message</h2> <h2 className='text-lg mt-4 font-semibold text-gray-800 cursor-pointer'>Message</h2>
<p className='mt-2 text-slate-600'>No Message Found</p> <p className='mt-2 text-slate-600'>{ transactionDetails?.transaction?.message ?? "No Message Found...!"}</p>
</div> </div>
</div> </div>

View File

@ -12,7 +12,8 @@ export default defineConfig({
'officially-powerful-goat.ngrok-free.app', 'officially-powerful-goat.ngrok-free.app',
'89f0-49-36-189-120.ngrok-free.app', '89f0-49-36-189-120.ngrok-free.app',
'c8ee-49-36-191-130.ngrok-free.app', 'c8ee-49-36-191-130.ngrok-free.app',
'c76f1251c173.ngrok-free.app' 'c76f1251c173.ngrok-free.app',
'enabled-upright-boxer.ngrok-free.app'
], ],
}, },
build: { build: {