Compare commits
No commits in common. "bdd984942226eb1f89bf3716bfce60cbf627470c" and "79151e2cf3438dbfb0868e68fe678ce31975fb6a" have entirely different histories.
bdd9849422
...
79151e2cf3
16
src/App.tsx
16
src/App.tsx
|
|
@ -14,11 +14,18 @@ import SignDocument from "./components/client/SignDocument";
|
||||||
import Transactions from "./components/transactions/Transactions";
|
import Transactions from "./components/transactions/Transactions";
|
||||||
import ProtectedRoute from "./components/routes_helper/ProtectedRoute";
|
import ProtectedRoute from "./components/routes_helper/ProtectedRoute";
|
||||||
|
|
||||||
|
import { HashRouter } from 'react-router-dom';
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { fetchUserDetails } from "./store/slices/userSlice";
|
||||||
|
import { AppDispatch, RootState } from "./store/store";
|
||||||
|
import { useEffect, useLayoutEffect } from "react";
|
||||||
|
|
||||||
import Setting from "./components/setting/Setting";
|
import Setting from "./components/setting/Setting";
|
||||||
import Accounts from "./components/accounts/Accounts";
|
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 AccountTransactionWrapper from "./components/AccountTransactionWrapper";
|
||||||
|
import SigningOrderDiagram from "./SigningOrderDiagram ";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -34,15 +41,16 @@ const App = () => {
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}>
|
}>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/templates" element={<Home />} />
|
||||||
{/* <Route path="/s/settings" element={<Setting />} /> */}
|
{/* <Route path="/s/settings" element={<Setting />} /> */}
|
||||||
{/* <Route element={<AccountTransactionWrapper />}> */}
|
|
||||||
|
<Route element={<AccountTransactionWrapper />}>
|
||||||
<Route path="/accounts" element={<Accounts />} />
|
<Route path="/accounts" element={<Accounts />} />
|
||||||
<Route path="/accounts/:accountId/transactions" element={<Account />} />
|
<Route path="/accounts/:accountId/transactions" element={<Account />} />
|
||||||
<Route path="/transactions" element={<Account />} />
|
<Route path="/transactions" element={<Account />} />
|
||||||
<Route path="/accounts/:accountId/transactions/:transactionId" element={<TransactionView />} />
|
<Route path="/accounts/:accountId/transactions/:transactionId" element={<TransactionView />} />
|
||||||
<Route path="/transactions/:filterParam" element={<Transactions />} />
|
<Route path="/transactions/:filterParam" element={<Transactions />} />
|
||||||
<Route path="/templates" element={<Templates />} />
|
</Route>
|
||||||
{/* </Route> */}
|
|
||||||
|
|
||||||
<Route path="/s/:page1" element={<Setting />} />
|
<Route path="/s/:page1" element={<Setting />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
|
|
||||||
import { Outlet } from 'react-router-dom'
|
import { Outlet } from 'react-router-dom'
|
||||||
// import AccountNav from './accounts/AccountNav'
|
import AccountNav from './accounts/AccountNav'
|
||||||
import CommonNav from './CommonNav'
|
|
||||||
|
|
||||||
const AccountTransactionWrapper = () => {
|
const AccountTransactionWrapper = () => {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CommonNav />
|
<AccountNav />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,363 +0,0 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
|
||||||
// import { useSelector } from 'react-redux';
|
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
|
||||||
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa";
|
|
||||||
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 CommonNavProps {
|
|
||||||
accountName?: string | null;
|
|
||||||
transactionName?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the shape of state object
|
|
||||||
interface Account {
|
|
||||||
id: number;
|
|
||||||
isDefault: boolean;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserData {
|
|
||||||
id: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
email: string;
|
|
||||||
language: string;
|
|
||||||
account: Account;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AccountDataArray {
|
|
||||||
accountId: number, accountName: string, role: string, status: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommonNav: React.FC<CommonNavProps> = ({ accountName = null, transactionName = null }) => {
|
|
||||||
// const user: UserState = useSelector((state: RootState) => state.user);
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [accountsOpen, setAccountsOpen] = useState(false);
|
|
||||||
const [loadingAccounts, setLoadingAccounts] = useState(false);
|
|
||||||
const [accounts, setAccounts] = useState<AccountDataArray[]>([]);
|
|
||||||
const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); // Track if modal was closed
|
|
||||||
const dropdownRef = useRef(null);
|
|
||||||
|
|
||||||
const [user, setUser] = useState<UserData>({
|
|
||||||
id: "",
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
email: "",
|
|
||||||
language: "",
|
|
||||||
account: {
|
|
||||||
id: 0,
|
|
||||||
isDefault: false,
|
|
||||||
name: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const getAccountDetails = async () => {
|
|
||||||
try {
|
|
||||||
let res = await api.get('/api/user/accounts')
|
|
||||||
let items = res?.data
|
|
||||||
console.log('items', items)
|
|
||||||
// Extract only the fields you want
|
|
||||||
const cleanedData: AccountDataArray[] = items.map((item: any) => ({
|
|
||||||
accountId: item.accountId,
|
|
||||||
accountName: item.accountName,
|
|
||||||
role: item.role,
|
|
||||||
status: item.status,
|
|
||||||
}));
|
|
||||||
|
|
||||||
setAccounts(cleanedData);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const handleAccountsClick = async () => {
|
|
||||||
setAccountsOpen(!accountsOpen);
|
|
||||||
|
|
||||||
if (!accountsOpen) {
|
|
||||||
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) {
|
|
||||||
await getAccountDetails();
|
|
||||||
setHasModalBeenClosed(false); // Reset the flag after fetching
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadingAccounts(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAccountSwitch = (account) => {
|
|
||||||
setUser(prev => ({
|
|
||||||
...prev,
|
|
||||||
account: { name: account.name, id: account.id }
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Update accounts active status
|
|
||||||
setAccounts(prevAccounts =>
|
|
||||||
prevAccounts.map(acc => ({
|
|
||||||
...acc,
|
|
||||||
isActive: acc.id === account.id
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
setAccountsOpen(false);
|
|
||||||
setOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const getUserDetails = async () => {
|
|
||||||
try {
|
|
||||||
let res = await api.get(`/api/user/details`)
|
|
||||||
console.log("getuser", res)
|
|
||||||
setUser(res?.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching user details:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getUserDetails()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const pathnames = location.pathname
|
|
||||||
.split("/")
|
|
||||||
.filter((x) => x && isNaN(Number(x))); // remove numeric IDs
|
|
||||||
|
|
||||||
const labelMap: Record<string, string> = {
|
|
||||||
accounts: "Accounts",
|
|
||||||
transactions: "Transactions",
|
|
||||||
templates: "Templates"
|
|
||||||
// Add more routes here if needed
|
|
||||||
};
|
|
||||||
|
|
||||||
// const [open, setOpen] = useState(false);
|
|
||||||
// const dropdownRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// Close dropdown when clicked outside
|
|
||||||
useEffect(() => {
|
|
||||||
function handleClickOutside(event: MouseEvent) {
|
|
||||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
||||||
if (open) {
|
|
||||||
setHasModalBeenClosed(true); // Mark modal as closed when clicking outside
|
|
||||||
}
|
|
||||||
setOpen(false);
|
|
||||||
setAccountsOpen(false); // Also close accounts dropdown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
}, [open]);
|
|
||||||
|
|
||||||
const handleLogout = async () => {
|
|
||||||
try {
|
|
||||||
let res = await api.post('/api/auth/logout')
|
|
||||||
if (res.status === 200) {
|
|
||||||
setOpen(false);
|
|
||||||
navigate("/login")
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle main dropdown toggle
|
|
||||||
const handleMainDropdownToggle = () => {
|
|
||||||
if (open) {
|
|
||||||
setHasModalBeenClosed(true); // Mark as closed when manually closing
|
|
||||||
}
|
|
||||||
setOpen(!open);
|
|
||||||
if (!open) {
|
|
||||||
setAccountsOpen(false); // Close accounts dropdown when opening main dropdown
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatSegment = (segment: string) =>
|
|
||||||
segment
|
|
||||||
.replace(/[-_]/g, " ")
|
|
||||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
||||||
|
|
||||||
|
|
||||||
const ShimmerRow = () => (
|
|
||||||
<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="w-8 h-8 bg-gray-200 rounded-full"></div>
|
|
||||||
<div className="flex-1 space-y-2">
|
|
||||||
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
||||||
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
|
|
||||||
</div>
|
|
||||||
<div className="w-16 h-6 bg-gray-200 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<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">
|
|
||||||
{pathnames.length === 0 ? (
|
|
||||||
<span className="text-gray-900 font-semibold">Dashboard</span>
|
|
||||||
) : (
|
|
||||||
pathnames.map((segment, index) => {
|
|
||||||
const label = labelMap[segment] || formatSegment(segment);
|
|
||||||
const to = "/" + pathnames.slice(0, index + 1).join("/");
|
|
||||||
const isLast = index === pathnames.length - 1;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span key={to} className="flex items-center">
|
|
||||||
{index > 0 && <span className="mx-2 text-gray-400">{">"}</span>}
|
|
||||||
{!isLast ? (
|
|
||||||
<Link
|
|
||||||
to={to}
|
|
||||||
className="text-blue-600 hover:text-blue-700 hover:underline transition-colors"
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<span className="text-gray-900 font-semibold">{label}</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
|
|
||||||
{/* User Dropdown */}
|
|
||||||
<div className="relative" ref={dropdownRef}>
|
|
||||||
{/* Avatar Button */}
|
|
||||||
<button
|
|
||||||
className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors"
|
|
||||||
onClick={handleMainDropdownToggle}
|
|
||||||
aria-expanded={open}
|
|
||||||
aria-haspopup="true"
|
|
||||||
>
|
|
||||||
<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 ? (
|
|
||||||
<span
|
|
||||||
className="w-full h-full object-cover p-1.5 font-bold"
|
|
||||||
>{(user?.firstName + " " + user?.lastName).split(' ').map((item) => item.slice(0, 1)).join("").toUpperCase()}</span>
|
|
||||||
) : (
|
|
||||||
<FaUser className="text-lg" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Enhanced Dropdown */}
|
|
||||||
{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">
|
|
||||||
{/* 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="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">
|
|
||||||
{(user) ? (
|
|
||||||
<span
|
|
||||||
className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold"
|
|
||||||
>
|
|
||||||
{(user?.firstName + " " + user?.lastName).split(' ').map((item) => item.slice(0, 1)).join("").toUpperCase()}</span>
|
|
||||||
) : (
|
|
||||||
<FaUser className="text-lg" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<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-xs text-gray-500 mt-1">
|
|
||||||
Account #{user?.account?.name || '-'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Account List Section */}
|
|
||||||
<div className="border-b border-gray-100">
|
|
||||||
<button
|
|
||||||
onClick={handleAccountsClick}
|
|
||||||
className="w-full flex items-center justify-between px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer"
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<FaBuilding className="mr-3 text-gray-500" />
|
|
||||||
<span>Switch Account</span>
|
|
||||||
</div>
|
|
||||||
<FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Accounts Dropdown */}
|
|
||||||
{accountsOpen && (
|
|
||||||
<div className="bg-gray-50 max-h-64 overflow-y-auto">
|
|
||||||
{loadingAccounts ? (
|
|
||||||
<div className="space-y-0">
|
|
||||||
<ShimmerRow />
|
|
||||||
<ShimmerRow />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="divide-y divide-gray-200">
|
|
||||||
{accounts.length > 0 ? (accounts?.map((account) => (
|
|
||||||
<button
|
|
||||||
key={account.accountId}
|
|
||||||
// onClick={() => handleAccountSwitch(account)}
|
|
||||||
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 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">
|
|
||||||
{account.accountName.slice(0, 2).toUpperCase()}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<span className="font-medium text-gray-900 text-sm">{account.accountName}</span>
|
|
||||||
{account.status && (
|
|
||||||
<FaCheck className="text-green-500 text-xs" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-600 truncate">{account.company}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-500 bg-gray-200 px-2 py-1 rounded">
|
|
||||||
{account.role}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
))) : <span className='text-gray-400 font-medium text-[12px] flex justify-center p-3'>No Account...</span>}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Logout Section */}
|
|
||||||
<div className="border-t border-gray-100 ">
|
|
||||||
<button
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<FaSignOutAlt className="mr-3" />
|
|
||||||
<span>Log Out</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CommonNav;
|
|
||||||
|
|
@ -17,7 +17,6 @@ import confetti from 'canvas-confetti'
|
||||||
import { useWindowSize } from 'react-use'
|
import { useWindowSize } from 'react-use'
|
||||||
import { FaCheckCircle } from 'react-icons/fa'
|
import { FaCheckCircle } from 'react-icons/fa'
|
||||||
import { IoClose } from 'react-icons/io5'
|
import { IoClose } from 'react-icons/io5'
|
||||||
import CommonNav from '../CommonNav'
|
|
||||||
|
|
||||||
function Dashboard() {
|
function Dashboard() {
|
||||||
const [showSideBar, setShowSideBar] = React.useState(true)
|
const [showSideBar, setShowSideBar] = React.useState(true)
|
||||||
|
|
@ -104,31 +103,31 @@ function Dashboard() {
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showConfetti) {
|
if (showConfetti) {
|
||||||
const duration = 3000;
|
const duration = 3000;
|
||||||
const end = Date.now() + duration;
|
const end = Date.now() + duration;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (Date.now() > end) {
|
if (Date.now() > end) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
confetti({
|
confetti({
|
||||||
particleCount: 80, // more particles
|
particleCount: 80, // more particles
|
||||||
angle: Math.random() * 360, // fire in random direction
|
angle: Math.random() * 360, // fire in random direction
|
||||||
spread: 220, // wider spread
|
spread: 220, // wider spread
|
||||||
startVelocity: 15, // more power
|
startVelocity: 15, // more power
|
||||||
scalar: 1.2, // bigger pieces (default is 1)
|
scalar: 1.2, // bigger pieces (default is 1)
|
||||||
origin: {
|
origin: {
|
||||||
x: Math.random(),
|
x: Math.random(),
|
||||||
y: Math.random() * 0.6,
|
y: Math.random() * 0.6,
|
||||||
},
|
},
|
||||||
zIndex: 9999,
|
zIndex: 9999,
|
||||||
});
|
});
|
||||||
}, 350); // fire every 150ms
|
}, 350); // fire every 150ms
|
||||||
}
|
}
|
||||||
}, [showConfetti]);
|
}, [showConfetti]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -174,7 +173,6 @@ function Dashboard() {
|
||||||
<div className={`overflow-y-auto absolute ${showSideBar ? 'w-[calc(100%-250px)]' : 'w-[calc(100%-60px)]'} h-full right-0 bottom-0 overflow-hidden bg-[var(--mainViewBg)] transition-all duration-300`}>
|
<div className={`overflow-y-auto absolute ${showSideBar ? 'w-[calc(100%-250px)]' : 'w-[calc(100%-60px)]'} h-full right-0 bottom-0 overflow-hidden bg-[var(--mainViewBg)] transition-all duration-300`}>
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className='px-2 py-1.5'>
|
<div className='px-2 py-1.5'>
|
||||||
<CommonNav />
|
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,7 @@ const Home = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<AccountNav />
|
||||||
<Cards
|
<Cards
|
||||||
onDateRangeChange={handleDateRangeChange}
|
onDateRangeChange={handleDateRangeChange}
|
||||||
dashboardData={dashboardData}
|
dashboardData={dashboardData}
|
||||||
|
|
|
||||||
|
|
@ -53,30 +53,10 @@ const ProcessStepOne = () => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleNextClick = async (isSendNow: boolean) => {
|
const handleNextClick = async (isSendNow: boolean) => {
|
||||||
let formError = {
|
if (!selectedDocuments.length || !selectedRecipients.length) {
|
||||||
documents: false,
|
|
||||||
recipients: false,
|
|
||||||
subject: false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedDocuments.length) {
|
|
||||||
formError.documents = true
|
|
||||||
}
|
|
||||||
if (!selectedRecipients.length) {
|
|
||||||
formError.recipients = true
|
|
||||||
}
|
|
||||||
if (!emailDtls.subject.length) {
|
|
||||||
formError.subject = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formError.documents || formError.recipients || formError.subject) {
|
|
||||||
console.log(selectedDocuments, 'line 193')
|
console.log(selectedDocuments, 'line 193')
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
toast.error(`${formError.documents ? "Add Document" : ""}
|
toast.error("Add Documents | Recipients First", {
|
||||||
${(formError.documents && formError.recipients) ? " | " : ""}
|
|
||||||
${formError.recipients ? "Add Recipient" : ""}
|
|
||||||
${(formError.documents || formError.recipients) ? " | " : ""}
|
|
||||||
${formError.subject ? "Add Subject" : ""}`, {
|
|
||||||
position: "top-center",
|
position: "top-center",
|
||||||
autoClose: 5000,
|
autoClose: 5000,
|
||||||
hideProgressBar: false,
|
hideProgressBar: false,
|
||||||
|
|
@ -238,8 +218,7 @@ const ProcessStepOne = () => {
|
||||||
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Subject</label>
|
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Subject</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
required
|
value={emailDtls.subject || "Documents for Signature"}
|
||||||
value={emailDtls.subject}
|
|
||||||
onChange={(e) => setEmailDtls({ ...emailDtls, subject: e.target.value })}
|
onChange={(e) => setEmailDtls({ ...emailDtls, subject: e.target.value })}
|
||||||
placeholder="Email Subject"
|
placeholder="Email Subject"
|
||||||
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
|
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const RecipientAddCard = () => {
|
||||||
const handleRecipientAdd = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleRecipientAdd = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (recipient.lastName === "" || recipient.email === "" || !recipient.type) return;
|
if (recipient.firstName === "" || recipient.lastName === "" || recipient.email === "" || !recipient.type) return;
|
||||||
|
|
||||||
// Validate Email Format
|
// Validate Email Format
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
@ -106,7 +106,7 @@ const RecipientAddCard = () => {
|
||||||
{/* Name Inputs */}
|
{/* Name Inputs */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<input
|
<input
|
||||||
// required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
onFocus={() => setOpenContactModal(true)}
|
onFocus={() => setOpenContactModal(true)}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice';
|
||||||
import { fetchDocuments } from '../../../store/slices/documentSlice';
|
import { fetchDocuments } from '../../../store/slices/documentSlice';
|
||||||
import { FaQuestionCircle } from 'react-icons/fa';
|
import { FaQuestionCircle } from 'react-icons/fa';
|
||||||
import typeToIconMap from '../../../helper/iconMap';
|
import typeToIconMap from '../../../helper/iconMap';
|
||||||
|
|
||||||
import { IoAdd } from 'react-icons/io5';
|
|
||||||
import Loader from '../../dashboard/Loader';
|
import Loader from '../../dashboard/Loader';
|
||||||
|
|
||||||
export interface DroppedItemsObj {
|
export interface DroppedItemsObj {
|
||||||
|
|
@ -214,10 +212,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
"type": dropTag.type.toUpperCase(),
|
"type": dropTag.type.toUpperCase(),
|
||||||
// "subType": (dropTag.subType ? dropTag.subType.toUpperCase() : null),
|
// "subType": (dropTag.subType ? dropTag.subType.toUpperCase() : null),
|
||||||
// validation: {
|
// validation: {
|
||||||
"minLength": 0,
|
|
||||||
"maxLength": 0,
|
|
||||||
"error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error",
|
|
||||||
// },
|
|
||||||
"minLength": "minLength" in dropTag ? dropTag.minLength : 0,
|
"minLength": "minLength" in dropTag ? dropTag.minLength : 0,
|
||||||
"maxLength": "maxLength" in dropTag ? dropTag.maxLength : 0,
|
"maxLength": "maxLength" in dropTag ? dropTag.maxLength : 0,
|
||||||
"error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error",
|
"error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error",
|
||||||
|
|
@ -283,10 +277,10 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
tagsData = groupByRecipientId(tagsData);
|
tagsData = groupByRecipientId(tagsData);
|
||||||
sendTagsToRecipients(tagsData, accountId, transactionId);
|
sendTagsToRecipients(tagsData, accountId, transactionId);
|
||||||
|
|
||||||
(async () => {
|
// (async () => {
|
||||||
const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, {status: "SENT"})
|
// const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, {status: "SENT"})
|
||||||
console.log(res, "Check123")
|
// console.log(res, "Check123")
|
||||||
})()
|
// })()
|
||||||
}, [isSend])
|
}, [isSend])
|
||||||
// Save Tags - END
|
// Save Tags - END
|
||||||
|
|
||||||
|
|
@ -529,11 +523,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddRadioClick = (key:string) => {
|
|
||||||
console.log("Handle Radio Clicled", key)
|
|
||||||
console.log(droppedItems, "line 525")
|
|
||||||
}
|
|
||||||
|
|
||||||
// const handleAddRadio = (e: React.MouseEvent<HTMLElement>, key: string, index: number) => {
|
// const handleAddRadio = (e: React.MouseEvent<HTMLElement>, key: string, index: number) => {
|
||||||
// // const dataToSet: DroppedItemsObj = {
|
// // const dataToSet: DroppedItemsObj = {
|
||||||
// // ...droppedItems,
|
// // ...droppedItems,
|
||||||
|
|
@ -758,53 +747,21 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
{/* Expand Circle - ENDS */}
|
{/* Expand Circle - ENDS */}
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
item.type !== "radio" ?
|
|
||||||
<div
|
|
||||||
onPointerDown={(e) => handleTagMouseDown(e, key, index)}
|
|
||||||
// style={{ width: `${item.width}px !important` }}
|
|
||||||
className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full'
|
|
||||||
style={{ background: selectedRecipient[0]?.color }}
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className={`flex justify-center items-center w-full text-xl text-white h-full`}
|
|
||||||
// style={{ color: selectedRecipients[item.recipientIndx]?.color }}
|
|
||||||
>
|
|
||||||
{IconComponent}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
{/* Radio - Container - START */}
|
|
||||||
<div>
|
|
||||||
|
|
||||||
{/* Radio- Item - START */}
|
<div
|
||||||
<div
|
onPointerDown={(e) => handleTagMouseDown(e, key, index)}
|
||||||
onPointerDown={(e) => handleTagMouseDown(e, key, index)}
|
// style={{ width: `${item.width}px !important` }}
|
||||||
// style={{ width: `${item.width}px !important` }}
|
className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full'
|
||||||
className='p-2 rounded-sm h-full shadow opacity-40 cursor-grab w-full'
|
style={{ background: selectedRecipient[0]?.color }}
|
||||||
style={{ background: selectedRecipient[0]?.color }}
|
>
|
||||||
>
|
<p
|
||||||
<p
|
className={`flex justify-center items-center w-full text-xl text-white h-full`}
|
||||||
className={`flex justify-center items-center w-full text-xl text-white h-full`}
|
// style={{ color: selectedRecipients[item.recipientIndx]?.color }}
|
||||||
// style={{ color: selectedRecipients[item.recipientIndx]?.color }}
|
>
|
||||||
>
|
{IconComponent}
|
||||||
{IconComponent}
|
</p>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
|
||||||
{/* Radio- Item - END */}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{/* Radio - Container - END */}
|
|
||||||
|
|
||||||
|
|
||||||
<IoAdd onClick={() => handleAddRadioClick(key)} className='ml-[50%] translate-x-[-50%] cursor-pointer' />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
import { PiDotsThreeOutlineVerticalFill } from "react-icons/pi";
|
|
||||||
import { FaCheck, FaDatabase } from "react-icons/fa";
|
|
||||||
|
|
||||||
import AddAccountModal from "../accounts/AddAccountModal";
|
|
||||||
import { Bounce, toast } from "react-toastify";
|
|
||||||
import api from "../../config/api";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
const Templates = () => {
|
|
||||||
const [openMenuId, setOpenMenuId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
|
|
||||||
const handleAction = async (accountId: string, action: 'accept' | 'reject') => {
|
|
||||||
try {
|
|
||||||
let response = await api.post(`/api/accounts/${accountId}/accept/invite`)
|
|
||||||
console.log(response)
|
|
||||||
if (response.data.status === 202) {
|
|
||||||
toast.success("Login Successful", {
|
|
||||||
position: "top-center",
|
|
||||||
autoClose: 5000,
|
|
||||||
hideProgressBar: false,
|
|
||||||
closeOnClick: false,
|
|
||||||
pauseOnHover: true,
|
|
||||||
draggable: true,
|
|
||||||
progress: undefined,
|
|
||||||
theme: "light",
|
|
||||||
transition: Bounce,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error posting data:', error);
|
|
||||||
} finally {
|
|
||||||
setOpenMenuId(null); // close menu
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
<div className="overflow-x-auto py-2">
|
|
||||||
<table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">
|
|
||||||
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider">
|
|
||||||
<tr>
|
|
||||||
<th className="px-4 py-3 w-12"></th>
|
|
||||||
<th className="px-4 py-3 text-left">Name</th>
|
|
||||||
<th className="px-4 py-3 text-center">Owner</th>
|
|
||||||
<th className="px-4 py-3 text-center">Created Date</th>
|
|
||||||
<th className="px-4 py-3 text-center">Last Change</th>
|
|
||||||
<th className="px-4 py-3 text-center"></th>
|
|
||||||
<th className="px-4 py-3 text-center"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-gray-200">
|
|
||||||
|
|
||||||
|
|
||||||
<tr
|
|
||||||
className="hover:bg-gray-50 cursor-pointer"
|
|
||||||
>
|
|
||||||
<td className="px-4 py-3">
|
|
||||||
<div className="w-5 h-5 rounded border border-slate-300 flex justify-center items-center">
|
|
||||||
{/* {selectedTransactions.includes(tx.id) && (
|
|
||||||
<FaCheck className="text-purple-600 text-sm" />
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-3">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>{"test"}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-3 text-center">{"1"}</td>
|
|
||||||
<td className="px-4 py-3 text-center">
|
|
||||||
<span className={`px-2 py-0.5 rounded-xl text-xs font-medium`}>
|
|
||||||
{"role"}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-3 text-center">
|
|
||||||
<span className={`px-2 py-0.5 rounded-xl text-xs font-medium`}>
|
|
||||||
{"text"}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className="px-4 py-3 text-center relative">
|
|
||||||
<button
|
|
||||||
|
|
||||||
className="mx-auto"
|
|
||||||
aria-label="Actions menu"
|
|
||||||
>
|
|
||||||
<PiDotsThreeOutlineVerticalFill className="text-lg text-slate-300 hover:text-black" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Dropdown menu */}
|
|
||||||
{/* {openMenuId === "1" && (
|
|
||||||
<div
|
|
||||||
className="absolute right-13 top-6 mt-1 w-18 bg-white border text-[13px] font-bold border-gray-300 rounded-md shadow-lg z-10"
|
|
||||||
onClick={(e) => e.stopPropagation()} // prevent row click closing
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="w-full px-3 py-1 text-left text-green-700 hover:bg-green-100 rounded-t-md cursor-pointer"
|
|
||||||
onClick={() => handleAction(1, 'accept')}
|
|
||||||
>
|
|
||||||
Accept
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="w-full px-3 py-1 text-left text-red-700 hover:bg-red-100 rounded-b-md cursor-pointer"
|
|
||||||
onClick={() => handleAction(1, 'reject')}
|
|
||||||
>
|
|
||||||
Reject
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)} */}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Templates;
|
|
||||||
|
|
@ -29,7 +29,7 @@ api.interceptors.response.use(
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
// Prevent multiple refresh attempts
|
// Prevent multiple refresh attempts
|
||||||
// if (error.response?.status === 401 || error.response?.status === 500) {
|
// if (error.response?.status === 401 || error.response?.status === 500) {
|
||||||
if (error.response?.status === 401 || error.response?.status === 500) {
|
if (error.response?.status === 401) {
|
||||||
originalRequest._retry = true;
|
originalRequest._retry = true;
|
||||||
try {
|
try {
|
||||||
if (!isRefreshing) {
|
if (!isRefreshing) {
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ export default defineConfig({
|
||||||
'quality-friendly-goldfish.ngrok-free.app',
|
'quality-friendly-goldfish.ngrok-free.app',
|
||||||
'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'
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue