Compare commits

..

No commits in common. "0cae5b1ffb744bdfeb249ccceb166dd4a72492b0" and "ba150f7cb2409237abb47bc77b74bba0a7dddb8e" have entirely different histories.

16 changed files with 648 additions and 1195 deletions

View File

@ -19,7 +19,6 @@ 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 (
@ -48,6 +47,9 @@ 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 />
@ -60,12 +62,6 @@ 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,14 +1,22 @@
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';
interface AccountNavProps { // import { RootState } from '../../store/store';
// 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;
@ -16,45 +24,32 @@ interface Account {
} }
interface UserData { interface UserData {
id: number; id: string;
name: string; firstName: string;
lastName: string;
email: string; email: string;
language: string; language: string;
account: Account; account: Account;
} }
interface AccountDataArray { interface AccountDataArray {
userId: number, accountId: number, accountName: string, role: string, status: boolean
userEmail: string,
name: string,
accountId: number;
accountName: string;
role: string;
status: string;
} }
// Helper function to dispatch custom storage event const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionName = null }) => {
const dispatchSessionStorageChange = (key: string, newValue: any) => { // const user: UserState = useSelector((state: RootState) => state.user);
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); const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); // Track if modal was closed
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef(null);
const [user, setUser] = useState<UserData>({ const [user, setUser] = useState<UserData>({
id: 0, id: "",
name: "", firstName: "",
lastName: "",
email: "", email: "",
language: "", language: "",
account: { account: {
@ -64,17 +59,13 @@ const CommonNav: React.FC<AccountNavProps> = () => {
}, },
}); });
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,
@ -83,9 +74,10 @@ const CommonNav: React.FC<AccountNavProps> = () => {
setAccounts(cleanedData); setAccounts(cleanedData);
} catch (error) { } catch (error) {
console.log(error); console.log(error)
} }
}; }
const handleAccountsClick = async () => { const handleAccountsClick = async () => {
setAccountsOpen(!accountsOpen); setAccountsOpen(!accountsOpen);
@ -93,127 +85,78 @@ const CommonNav: React.FC<AccountNavProps> = () => {
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); setHasModalBeenClosed(false); // Reset the flag after fetching
} }
setLoadingAccounts(false); setLoadingAccounts(false);
} }
}; };
const handleAccountSwitch = (account: any) => { const handleAccountSwitch = (account) => {
console.log("handleAccountSwitch", account); setUser(prev => ({
...prev,
// Update sessionStorage account: { name: account.name, id: account.id }
sessionStorage.setItem('activeAccountId', JSON.stringify(account.accountId));
// Dispatch custom event
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,
},
})); }));
// Update accounts active status
setAccounts(prevAccounts =>
prevAccounts.map(acc => ({
...acc,
isActive: acc.id === account.id
}))
);
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)
const item = res?.data; setUser(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(() => {
if (!activeAccountId) { getUserDetails()
getUserDetails(); }, [])
} else {
setUser(userDetails);
}
}, []);
const location = useLocation(); const location = useLocation();
const rawPathnames = location.pathname const pathnames = location.pathname
.split("/") .split("/")
.filter((x) => x && isNaN(Number(x))); .filter((x) => x && isNaN(Number(x))); // remove numeric IDs
// 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); setHasModalBeenClosed(true); // Mark modal as closed when clicking outside
} }
setOpen(false); setOpen(false);
setAccountsOpen(false); setAccountsOpen(false); // Also close accounts dropdown
} }
} }
@ -223,24 +166,24 @@ const CommonNav: React.FC<AccountNavProps> = () => {
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); setHasModalBeenClosed(true); // Mark as closed when manually closing
} }
setOpen(!open); setOpen(!open);
if (!open) { if (!open) {
setAccountsOpen(false); setAccountsOpen(false); // Close accounts dropdown when opening main dropdown
} }
}; };
@ -249,6 +192,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
.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">
@ -262,6 +206,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
</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">
@ -293,7 +238,10 @@ const CommonNav: React.FC<AccountNavProps> = () => {
</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}
@ -302,31 +250,34 @@ const CommonNav: React.FC<AccountNavProps> = () => {
> >
<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 className="w-full h-full object-cover p-1.5 font-bold"> <span
{(user?.name?.slice(0, 2)).toUpperCase()} className="w-full h-full object-cover p-1.5 font-bold"
</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>
</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 className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold"> <span
{(user?.name?.slice(0, 2)).toUpperCase()} className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold"
</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?.name || '-'}</div> <div className="font-semibold text-gray-900 truncate">{user.firstName + ' ' + user.lastName || '-'}</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>
@ -334,6 +285,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
</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}
@ -346,6 +298,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
<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 ? (
@ -355,14 +308,13 @@ const CommonNav: React.FC<AccountNavProps> = () => {
</div> </div>
) : ( ) : (
<div className="divide-y divide-gray-200"> <div className="divide-y divide-gray-200">
{accounts?.length > 0 ? ( {accounts.length > 0 ? (accounts?.map((account) => (
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()}
@ -370,10 +322,11 @@ const CommonNav: React.FC<AccountNavProps> = () => {
<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.accountId === activeAccountId && ( {account.status && (
<FaCheck className="text-green-600 text-xs" /> <FaCheck className="text-green-500 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">
@ -381,17 +334,16 @@ const CommonNav: React.FC<AccountNavProps> = () => {
</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>
<div className="border-t border-gray-100"> {/* Logout Section */}
<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,14 +110,6 @@ 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[]>([]);
@ -129,58 +121,25 @@ 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(() => {
const userDetails = JSON.parse(sessionStorage.getItem('userDetails')); getUserDetails()
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: activeAccountId account: accountID
}; };
try { try {
@ -195,51 +154,50 @@ 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/${activeAccountId}/transactions`); const response = await api(`/api/accounts/${accountID}/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/${activeAccountId}/transactions/${id}`); navigate(`/accounts/${accountID}/transactions/${id}`);
}; };
const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => { const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => {
@ -277,7 +235,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">
@ -289,8 +247,7 @@ 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">Sign</th> <th className="px-4 py-3 text-center"></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">
@ -327,9 +284,6 @@ 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,13 +1,21 @@
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;
@ -15,45 +23,32 @@ interface Account {
} }
interface UserData { interface UserData {
id: number; id: string;
name: string; firstName: string;
lastName: string;
email: string; email: string;
language: string; language: string;
account: Account; account: Account;
} }
interface AccountDataArray { interface AccountDataArray {
userId: number, accountId: number, accountName: string, role: string, status: boolean
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 activeAccountId = JSON.parse(sessionStorage.getItem('activeAccountId') || 'null'); // const user: UserState = useSelector((state: RootState) => state.user);
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); const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); // Track if modal was closed
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef(null);
const [user, setUser] = useState<UserData>({ const [user, setUser] = useState<UserData>({
id: 0, id: "",
name: "", firstName: "",
lastName: "",
email: "", email: "",
language: "", language: "",
account: { account: {
@ -63,17 +58,13 @@ 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,
@ -82,9 +73,10 @@ 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);
@ -92,127 +84,78 @@ 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); setHasModalBeenClosed(false); // Reset the flag after fetching
} }
setLoadingAccounts(false); setLoadingAccounts(false);
} }
}; };
const handleAccountSwitch = (account: any) => { const handleAccountSwitch = (account) => {
console.log("handleAccountSwitch", account); setUser(prev => ({
...prev,
// Update sessionStorage account: { name: account.name, id: account.id }
sessionStorage.setItem('activeAccountId', JSON.stringify(account.accountId));
// Dispatch custom event
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,
},
})); }));
// Update accounts active status
setAccounts(prevAccounts =>
prevAccounts.map(acc => ({
...acc,
isActive: acc.id === account.id
}))
);
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)
const item = res?.data; setUser(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(() => {
if (!activeAccountId) { getUserDetails()
getUserDetails(); }, [])
} else {
setUser(userDetails);
}
}, []);
const location = useLocation(); const location = useLocation();
const rawPathnames = location.pathname const pathnames = location.pathname
.split("/") .split("/")
.filter((x) => x && isNaN(Number(x))); .filter((x) => x && isNaN(Number(x))); // remove numeric IDs
// 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); setHasModalBeenClosed(true); // Mark modal as closed when clicking outside
} }
setOpen(false); setOpen(false);
setAccountsOpen(false); setAccountsOpen(false); // Also close accounts dropdown
} }
} }
@ -222,24 +165,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); setHasModalBeenClosed(true); // Mark as closed when manually closing
} }
setOpen(!open); setOpen(!open);
if (!open) { if (!open) {
setAccountsOpen(false); setAccountsOpen(false); // Close accounts dropdown when opening main dropdown
} }
}; };
@ -248,6 +191,7 @@ 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">
@ -261,6 +205,7 @@ 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">
@ -292,7 +237,10 @@ 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}
@ -301,31 +249,34 @@ 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 className="w-full h-full object-cover p-1.5 font-bold"> <span
{(user?.name?.slice(0, 2)).toUpperCase()} className="w-full h-full object-cover p-1.5 font-bold"
</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>
</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 className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold"> <span
{(user?.name?.slice(0, 2)).toUpperCase()} className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold"
</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?.name || '-'}</div> <div className="font-semibold text-gray-900 truncate">{user.firstName + ' ' + user.lastName || '-'}</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>
@ -333,6 +284,7 @@ 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}
@ -345,6 +297,7 @@ 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 ? (
@ -354,25 +307,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.length > 0 ? (accounts?.map((account) => (
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.accountId === activeAccountId && ( {account.status && (
<FaCheck className="text-green-600 text-xs" /> <FaCheck className="text-green-500 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">
@ -380,17 +333,16 @@ 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>
<div className="border-t border-gray-100"> {/* Logout Section */}
<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,7 +42,6 @@ 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: 'Draft', title: 'Waiting for Others',
count: data.draft || 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',
@ -70,27 +70,69 @@ 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>(''); const [fromDate, setFromDate] = useState<string>(''); // ISO string
const [toDate, setToDate] = useState<string>(''); const [toDate, setToDate] = useState<string>(''); // ISO 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("activeAccountId", activeAccountId); console.log("cards", cards)
console.log("cards", cards);
const getDashboardData = async () => { const getUserDetails = async () => {
if (!activeAccountId) return; try {
let res = await api.get('/api/user/details')
let accountIDData = res?.data?.account?.id
setAccountID(accountIDData)
} catch (error) {
console.log(error)
}
}
setIsLoading(true); useEffect(() => {
getUserDetails()
}, [])
const getDashboardData = async (from: string, to: string) => {
setIsLoading(true)
const payload = { const payload = {
account: activeAccountId fromDate: from, // already ISO string
toDate: to,
account : accountID
}; };
try { try {
@ -101,8 +143,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)
} }
}; };
@ -113,41 +155,16 @@ const Home = () => {
setToDate(isoTo); setToDate(isoTo);
}, []); }, []);
// Listen for sessionStorage changes
useEffect(() => { useEffect(() => {
const handleStorageChange = (e: StorageEvent) => { if (fromDate && toDate && accountID) {
if (e.key === 'activeAccountId') { getDashboardData(fromDate, toDate);
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);
return () => { // Set default range on mount (last 7 days)
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

@ -1,354 +0,0 @@
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 { useNavigate, useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
interface NavTwoProps { interface NavTwoProps {
isFields: boolean; isFields: boolean;
@ -57,8 +57,6 @@ 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)
@ -78,7 +76,6 @@ 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,7 +19,8 @@ interface Account {
interface UserData { interface UserData {
id: string; id: string;
name: string; firstName: string;
lastName: string;
email: string; email: string;
language: string; language: string;
account: Account; account: Account;
@ -42,15 +43,9 @@ 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);
@ -58,14 +53,15 @@ 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: "",
name: '', firstName: "",
email: '', lastName: "",
language: '', email: "",
language: "",
account: { account: {
id: 0, id: 0,
isDefault: false, isDefault: false,
name: '', name: "",
}, },
}); });
@ -242,58 +238,18 @@ const Setting = () => {
{ id: "contactShare", label: "Share Contact", type: "contact" }, { id: "contactShare", label: "Share Contact", type: "contact" },
]; ];
const getUserDetails = async () => {
// Listen for sessionStorage changes try {
useEffect(() => { let res = await api.get(`/api/user/details`)
const handleStorageChange = (e: StorageEvent) => { console.log("getuser", res)
if (e.key === 'activeAccountId') { setUser(res?.data)
const newActiveAccountId = e.newValue ? JSON.parse(e.newValue) : null; } catch (error) {
setActiveAccountId(newActiveAccountId); console.error("Error fetching user details:", error);
} }
};
// 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()
}, []) }, [])
@ -301,7 +257,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) {
@ -334,7 +290,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 = {
@ -352,7 +308,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...!"
} }
@ -381,7 +337,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<'TYPE' | 'DRAW' | 'UPLOAD' | 'SELECT'>('TYPE'); const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT');
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,10 +102,9 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
// Navigation items // Navigation items
const navigationItems = [ const navigationItems = [
{ id: 'TYPE', label: 'TYPE', icon: <GrCheckboxSelected className="w-4 h-4" /> }, { id: 'SELECT', label: 'SELECT', 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
@ -113,7 +112,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 'TYPE' | 'DRAW' | 'UPLOAD'); setActiveTab(editingSignature.signatureType as 'SELECT' | '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);
@ -263,7 +262,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
const renderContent = () => { const renderContent = () => {
switch (activeTab) { switch (activeTab) {
case 'TYPE': case 'SELECT':
return ( return (
<div className="h-full flex flex-col "> <div className="h-full flex flex-col ">
<div className="mb-6"> <div className="mb-6">
@ -384,16 +383,6 @@ 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;
} }
@ -473,7 +462,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 'TYPE' | 'DRAW' | 'UPLOAD')} onClick={() => setActiveTab(item.id as 'SELECT' | '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">
Initials DS
</div> </div>
{/* Signature Image */} {/* Signature Image */}
@ -186,10 +186,6 @@ 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,8 +6,9 @@ interface Account {
} }
interface UserData { interface UserData {
id: number; id: string;
name:string firstName: string;
lastName: string;
email: string; email: string;
language: string; language: string;
account: Account; account: Account;
@ -25,7 +26,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">
{userData.name.slice(0,2).toUpperCase()} {getInitials(userData.firstName, userData.lastName)}
</div> </div>
</div> </div>
@ -35,7 +36,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.name} {userData.firstName} {userData.lastName}
</div> </div>
</div> </div>

View File

@ -64,14 +64,13 @@ 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?.subject ?? "Untitled"}</span> Complete with Forms Made Easy: <span className='text-slate-600'>{ transactionDetails.transaction.name ?? "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=''><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'>Last Change on <span>12/02/25</span></p>
<p className='text-slate-600'>Created at <span>{new Date(transactionDetails?.transaction?.createdAt * 1000).toISOString()}</span></p> <p className='text-slate-600'>Sent on <span>11/02/25</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>
@ -140,7 +139,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'>{ transactionDetails?.transaction?.message ?? "No Message Found...!"}</p> <p className='mt-2 text-slate-600'>No Message Found</p>
</div> </div>
</div> </div>

View File

@ -12,8 +12,7 @@ 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: {