diff --git a/src/App.tsx b/src/App.tsx index b204b47..31110c5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import Accounts from "./components/accounts/Accounts"; import Account from "./components/accounts/Account"; import TransactionView from "./components/transactions/TransactionView"; import Templates from "./components/templates/Templates"; +import DocumentCompletionScreen from "./components/process/CompletionScreen/DocumentCompletionScreen"; const App = () => { return ( @@ -47,9 +48,6 @@ const App = () => { } /> - - - @@ -62,6 +60,12 @@ const App = () => { } /> + + + + } /> + {/* Links work wothout Login */} } /> } /> diff --git a/src/components/CommonNav.tsx b/src/components/CommonNav.tsx index 49005a1..8a3d33f 100644 --- a/src/components/CommonNav.tsx +++ b/src/components/CommonNav.tsx @@ -1,363 +1,411 @@ 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; +interface AccountNavProps { + accountName?: string | null; + transactionName?: string | null; } -// Define the shape of state object interface Account { - id: number; - isDefault: boolean; - name: string; + id: number; + isDefault: boolean; + name: string; } interface UserData { - id: string; - firstName: string; - lastName: string; - email: string; - language: string; - account: Account; + id: number; + name: string; + email: string; + language: string; + account: Account; } interface AccountDataArray { - accountId: number, accountName: string, role: string, status: boolean + userId: number, + userEmail: string, + name: string, + accountId: number; + accountName: string; + role: string; + status: string; } -const CommonNav: React.FC = ({ accountName = null, transactionName = null }) => { - // const user: UserState = useSelector((state: RootState) => state.user); +// Helper function to dispatch custom storage event +const dispatchSessionStorageChange = (key: string, newValue: any) => { + window.dispatchEvent(new CustomEvent('sessionStorageChange', { + detail: { key, newValue } + })); +}; - const [open, setOpen] = useState(false); - const [accountsOpen, setAccountsOpen] = useState(false); - const [loadingAccounts, setLoadingAccounts] = useState(false); - const [accounts, setAccounts] = useState([]); - const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); // Track if modal was closed - const dropdownRef = useRef(null); +const CommonNav: React.FC = () => { + const activeAccountId = JSON.parse(sessionStorage.getItem('activeAccountId') || 'null'); + const userDetails = JSON.parse(sessionStorage.getItem('userDetails') || '{}'); + console.log("activeAccountId", activeAccountId); - const [user, setUser] = useState({ - id: "", - firstName: "", - lastName: "", - email: "", - language: "", - account: { - id: 0, - isDefault: false, - name: "", - }, + const [open, setOpen] = useState(false); + const [accountsOpen, setAccountsOpen] = useState(false); + const [loadingAccounts, setLoadingAccounts] = useState(false); + const [accounts, setAccounts] = useState([]); + const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); + const dropdownRef = useRef(null); + + const [user, setUser] = useState({ + id: 0, + name: "", + email: "", + language: "", + account: { + id: 0, + isDefault: false, + name: "", + }, + }); + + console.log("user", user) + const getAccountDetails = async () => { + try { + let res = await api.get('/api/user/accounts'); + let items = res?.data; + console.log('items', items); + + const cleanedData: AccountDataArray[] = items.map((item: any) => ({ + userId: item.userId, + userEmail: item.userEmail, + name: item.name, + 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); + + if (accounts.length === 0 || hasModalBeenClosed) { + await getAccountDetails(); + setHasModalBeenClosed(false); + } + + setLoadingAccounts(false); + } + }; + + const handleAccountSwitch = (account: any) => { + console.log("handleAccountSwitch", account); + + // Update sessionStorage + 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, + }, }); - 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, - })); + 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, + }, + })); - setAccounts(cleanedData); - } catch (error) { - console.log(error) - } + setAccountsOpen(false); + setOpen(false); + }; + + + const navigate = useNavigate(); + + const getUserDetails = async () => { + try { + let res = await api.get(`/api/user/details`); + console.log("getuser", res); + const item = res?.data; + if (item) { + const userData = { + id: item?.id, + name : item?.firstName + " " + item?.lastName , + email: item?.email, + language: "", + account: { + id: item?.account.id, + isDefault: false, + name: item?.account.name, + }, + }; + + console.log("userData",userData) + + setUser(userData); + sessionStorage.setItem('userDetails', JSON.stringify(userData)); + sessionStorage.setItem('activeAccountId', JSON.stringify(userData.account.id)); + + // Dispatch event for initial load + dispatchSessionStorageChange('activeAccountId', userData.account.id); + + console.log("Data is saved in sessionStorage"); + } + + + } catch (error) { + console.error("Error fetching user details:", 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(() => { + if (!activeAccountId) { + getUserDetails(); + } else { + setUser(userDetails); } + }, []); - useEffect(() => { - getUserDetails() - }, []) + const location = useLocation(); + const rawPathnames = location.pathname + .split("/") + .filter((x) => x && isNaN(Number(x))); - const location = useLocation(); + // 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 pathnames = location.pathname - .split("/") - .filter((x) => x && isNaN(Number(x))); // remove numeric IDs + const labelMap: Record = { + accounts: "Accounts", + transactions: "Transactions", + templates: "Templates" + }; - const labelMap: Record = { - accounts: "Accounts", - transactions: "Transactions", - templates: "Templates" - // Add more routes here if needed - }; - - // const [open, setOpen] = useState(false); - // const dropdownRef = useRef(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 = () => { + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { if (open) { - setHasModalBeenClosed(true); // Mark as closed when manually closing + setHasModalBeenClosed(true); } - setOpen(!open); - if (!open) { - setAccountsOpen(false); // Close accounts dropdown when opening main dropdown - } - }; + setOpen(false); + setAccountsOpen(false); + } + } - const formatSegment = (segment: string) => - segment - .replace(/[-_]/g, " ") - .replace(/\b\w/g, (char) => char.toUpperCase()); + 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) { + sessionStorage.clear(); + setOpen(false); + navigate("/login"); + } + } catch (error) { + console.log(error); + } + }; - const ShimmerRow = () => ( -
-
-
-
-
-
-
-
-
+ const handleMainDropdownToggle = () => { + if (open) { + setHasModalBeenClosed(true); + } + setOpen(!open); + if (!open) { + setAccountsOpen(false); + } + }; + + const formatSegment = (segment: string) => + segment + .replace(/[-_]/g, " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); + + const ShimmerRow = () => ( +
+
+
+
+
+
- ); +
+
+
+ ); + return ( +
+ -
- - {/* User Dropdown */} -
- {/* Avatar Button */} - - - {/* Enhanced Dropdown */} - {open && ( -
- {/* User Info Section */} -
-
-
- {(user) ? ( - - {(user?.firstName + " " + user?.lastName).split(' ').map((item) => item.slice(0, 1)).join("").toUpperCase()} - ) : ( - - )} -
-
-
{user.firstName + ' ' + user.lastName || '-'}
-
{user.email || '-'}
-
- Account #{user?.account?.name || '-'} -
-
-
-
- - {/* Account List Section */} -
- - - {/* Accounts Dropdown */} - {accountsOpen && ( -
- {loadingAccounts ? ( -
- - -
- ) : ( -
- {accounts.length > 0 ? (accounts?.map((account) => ( - - ))) : No Account...} - -
- )} -
- )} -
- - {/* Logout Section */} -
- -
-
- )} -
+
+
+
- ); + + + {open && ( +
+
+
+
+ {user ? ( + + {(user?.name?.slice(0, 2)).toUpperCase()} + + ) : ( + + )} +
+
+
{user?.name || '-'}
+
{user?.email || '-'}
+
+ Account #{user?.account?.name || '-'} +
+
+
+
+ +
+ + + {accountsOpen && ( +
+ {loadingAccounts ? ( +
+ + +
+ ) : ( +
+ {accounts?.length > 0 ? ( + accounts?.map((account) => ( + + )) + ) : ( + No Account... + )} +
+ )} +
+ )} +
+ +
+ +
+
+ )} +
+ + + ); }; export default CommonNav; \ No newline at end of file diff --git a/src/components/accounts/Account.tsx b/src/components/accounts/Account.tsx index be312fb..d5c7a6a 100644 --- a/src/components/accounts/Account.tsx +++ b/src/components/accounts/Account.tsx @@ -67,36 +67,36 @@ const Account = () => { textColor: 'text-teal-700', trend: 'up' }, - { - title: 'Waiting for Others', - count: data.waitingForOthers || 0, - icon: , - gradient: 'bg-gradient-to-br from-blue-50 to-blue-100', - shadowColor: 'shadow-blue-200/50', - iconBg: 'bg-blue-100 border-blue-200', - textColor: 'text-blue-700', - trend: 'down' - }, - { - title: 'declined', - count: data.declined || 0, - icon: , - 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: , - 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: 'Waiting for Others', + // count: data.waitingForOthers || 0, + // icon: , + // gradient: 'bg-gradient-to-br from-blue-50 to-blue-100', + // shadowColor: 'shadow-blue-200/50', + // iconBg: 'bg-blue-100 border-blue-200', + // textColor: 'text-blue-700', + // trend: 'down' + // }, + // { + // title: 'declined', + // count: data.declined || 0, + // icon: , + // 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: , + // 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, @@ -115,7 +115,9 @@ const Account = () => { JSON.parse(sessionStorage.getItem('activeAccountId') || 'null') ); - console.log("activeAccountId Trans.",activeAccountId) + const [currentEmailId, setCurrentEmailId] = useState('null'); + + console.log("activeAccountId Trans.", activeAccountId) // const { accountId } = useParams<{ accountId: string }>(); const [transactions, setTransactions] = useState([]); const [selectedTransactions, setSelectedTransactions] = useState([]); @@ -141,32 +143,38 @@ const Account = () => { // getUserDetails() // }, []) + useEffect(() => { + const userDetails = JSON.parse(sessionStorage.getItem('userDetails')); + setCurrentEmailId(userDetails?.email) + }, []); + + // Listen for sessionStorage changes - useEffect(() => { - const handleStorageChange = (e: StorageEvent) => { - if (e.key === 'activeAccountId') { - const newActiveAccountId = e.newValue ? JSON.parse(e.newValue) : null; - setActiveAccountId(newActiveAccountId); - } - }; - - // Listen for storage events (works for changes from other tabs) - window.addEventListener('storage', handleStorageChange); - - // Custom event listener for same-tab changes - const handleCustomStorageChange = (e: CustomEvent) => { - if (e.detail.key === 'activeAccountId') { - setActiveAccountId(e.detail.newValue); - } - }; - - window.addEventListener('sessionStorageChange', handleCustomStorageChange as EventListener); - - return () => { - window.removeEventListener('storage', handleStorageChange); - window.removeEventListener('sessionStorageChange', handleCustomStorageChange as EventListener); - }; - }, []); + 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 () => { @@ -281,7 +289,8 @@ const Account = () => { Active Last Change Expire After (days) - + Sign + Action @@ -318,6 +327,9 @@ const Account = () => { {tx.expireAfter ?? "-"} + + {tx?.createdBy?.email === currentEmailId ? : "-"} + diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx index 5865246..b35173f 100644 --- a/src/components/dashboard/Dashboard.tsx +++ b/src/components/dashboard/Dashboard.tsx @@ -103,32 +103,32 @@ function Dashboard() { }, [documentSignType]) - useEffect(() => { - if (showConfetti) { - const duration = 3000; - const end = Date.now() + duration; + useEffect(() => { + if (showConfetti) { + const duration = 3000; + const end = Date.now() + duration; - const interval = setInterval(() => { - if (Date.now() > end) { - clearInterval(interval); - return; - } + 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]); + 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]); diff --git a/src/components/process/CompletionScreen/DocumentCompletionScreen.tsx b/src/components/process/CompletionScreen/DocumentCompletionScreen.tsx new file mode 100644 index 0000000..17c961c --- /dev/null +++ b/src/components/process/CompletionScreen/DocumentCompletionScreen.tsx @@ -0,0 +1,354 @@ +import React, { useEffect } from "react"; +import { + FaCheck, + FaFileAlt, + FaUsers, + FaEnvelope, + FaClock, + FaDownload, + FaShare, + FaShieldAlt, + FaLock, + FaPen, + FaCheckCircle +} from 'react-icons/fa'; +import { fetchRecipients } from "../../../store/slices/selectedRecipientsSlice"; +import { fetchDocuments } from "../../../store/slices/documentSlice"; +import { useDispatch, useSelector } from "react-redux"; +import { Link, useNavigate, useParams } from "react-router-dom"; +import Loader from "../../dashboard/Loader"; +import confetti from 'canvas-confetti' +import { IoClose } from "react-icons/io5"; +import { IoMdHome } from "react-icons/io"; + +type Signer = { + id: number; + name: string; + email: string; + routingOrder: number; + completed: boolean; +}; + +// Signers List Component +const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => { + return ( +
+
+
+

+ + Signature Status +

+ + {signers.filter(s => s.completed).length} of {signers.length} signed + +
+ +
+ {signers.map((signer) => ( +
+
+ {signer.name.split(" ").map(n => n[0].toUpperCase()).join("")} +
+
+
{signer.name}
+
{signer.email}
+
Order: {signer.routingOrder}
+
+
+ {signer.completed ? ( +
+ + Signed +
+ ) : ( +
+ + Pending +
+ )} +
+
+ ))} +
+
+
+ ); +}; + +// Fixed Signing Order Diagram Component +const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => { + const groupByRoutingOrder = (data: Signer[]) => { + const grouped: Record = {}; + 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 ( +
+
+

Signing Flow

+
+
+ {/* Mail icon */} +
+
+ +
+

Sent

+
+ + {/* Dotted line */} +
+
+
+ + {/* Signers by routing order */} +
+ {sortedOrders.map((order, index) => ( + +
+
+ Step {order} +
+
+ {grouped[order].map((signer) => { + let bgColor = ""; + let icon = null; + + if (signer.completed) { + bgColor = "bg-[var(--primary)]"; + icon = ; + } else if (order === currentProcessingOrder) { + bgColor = "bg-yellow-500"; + icon = ; + } else { + bgColor = "bg-gray-400"; + icon = ; + } + + return ( +
+
+ {getInitials(signer.name)} + + {icon} + +
+

+ {signer.name.split(" ")[0]} +

+
+ ); + })} +
+
+ + {index < sortedOrders.length - 1 && ( +
+
+
+ )} +
+ ))} +
+
+
+
+
+ ); +}; + +// 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 && ( +
+ {/* */} + +
+ {/* Close Icon */} + + + {/* Success Icon */} + + + {/* Message */} +

Transaction Successful

+

Your transaction has been created and saved successfully.

+
+
+ ) + } + +
+ {/* Top Banner */} + +
+
+
+
+ + + +
+ +
+
+

Awesome! Your document is sent for signature

+
+
+ + + Edit + +
+
+
+ {(selectedRecipients.length > 0 && selectedDocuments.length > 0) ? +
+
+ {/* Header */} +
+ {/* Left Section - Document Details */} +
+ {/* Document Preview Card */} +
+
+ {selectedDocuments?.map((item, index) => ( +
+
+
+ {/* */} + document +
+
+

+ Name : {item.name ?? '--'} +

+
+ Status : {item.status ?? '--'} +
+ +
+
+
+ ))} +
+
+
+ + {/* Right Section - Signer Components */} +
+ {/* Signing Order Diagram */} + + + {/* Signers List */} + +
+
+
+
+ : } +
+ + ); +}; + +export default DocumentCompletionScreen; \ No newline at end of file diff --git a/src/components/process/processStepTwo/NavTwo.tsx b/src/components/process/processStepTwo/NavTwo.tsx index 19d652a..d6b2268 100644 --- a/src/components/process/processStepTwo/NavTwo.tsx +++ b/src/components/process/processStepTwo/NavTwo.tsx @@ -10,7 +10,7 @@ import { MdKeyboardArrowDown } from 'react-icons/md' import { RiResetLeftLine, RiResetRightLine } from 'react-icons/ri' import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice' import { fetchDocuments } from '../../../store/slices/documentSlice' -import { useParams } from 'react-router-dom' +import { useNavigate, useParams } from 'react-router-dom' interface NavTwoProps { isFields: boolean; @@ -57,6 +57,8 @@ const NavTwo: React.FC = ({ isFields, setIsFields, isSend, setIsSen const [isFitDd, setIsFitDd] = useState(false) const [selFitIndx, setSelFitIndx] = useState(0); + const navigate = useNavigate() + const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients) const selectedDocuments = useSelector((state: RootState) => state.documents.documents) @@ -76,6 +78,7 @@ const NavTwo: React.FC = ({ isFields, setIsFields, isSend, setIsSen setIsSend(false); } setIsSend(!isSend) + navigate(`/document-completion-screen/${accountId}/${transactionId}`) } const handleSetIsFields = (setTo: boolean) => { diff --git a/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx b/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx index 4644623..95bb714 100644 --- a/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx +++ b/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx @@ -101,7 +101,7 @@ const DirectionalControls: React.FC = ({ - Right: {Math.round(right)}px + {/* Right: {Math.round(right)}px */} {/* Bottom Row */} @@ -110,7 +110,7 @@ const DirectionalControls: React.FC = ({ - Bottom: {Math.round(bottom)}px + {/* Bottom: {Math.round(bottom)}px */}
diff --git a/src/components/setting/Setting.tsx b/src/components/setting/Setting.tsx index 000d1d0..623cc53 100644 --- a/src/components/setting/Setting.tsx +++ b/src/components/setting/Setting.tsx @@ -19,8 +19,7 @@ interface Account { interface UserData { id: string; - firstName: string; - lastName: string; + name: string; email: string; language: string; account: Account; @@ -43,32 +42,35 @@ interface AccountOption { const Setting = () => { const { page1 } = useParams<{ page1: string }>(); - console.log("page1" , page1) + console.log("page1", page1) const location = useLocation(); - console.log("location",location) + console.log("location", location) const navigate = useNavigate(); + const [activeAccountId, setActiveAccountId] = useState( + JSON.parse(sessionStorage.getItem('activeAccountId') || 'null') + ); + // Refs for subsections within each main tab const subSectionRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const scrollContainerRef = useRef(null); const [activeTab, setActiveTab] = useState("userProfile"); const [activeSubSection, setActiveSubSection] = useState(""); - const [user, setUser] = useState({ - id: "", - firstName: "", - lastName: "", - email: "", - language: "", + const [user, setUser] = useState({ + id: '', + name: '', + email: '', + language: '', account: { id: 0, isDefault: false, - name: "", + name: '', }, }); const [createAndInviteLoader, setCreateAndInviteLoader] = useState(false); - + // Dropdown data states const [accountOptions, setAccountOptions] = useState([]); const [accountOptionsLoading, setAccountOptionsLoading] = useState(false); @@ -203,8 +205,8 @@ const Setting = () => { return ; case "create and invite": return ( - { { id: "contactShare", label: "Share Contact", type: "contact" }, ]; - 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); + + // 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); + }; + }, []); + + // 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(() => { - getUserDetails() // Fetch account list when component mounts getAccountList() }, []) @@ -259,7 +301,7 @@ const Setting = () => { // Set initial subsection when component mounts or activeTab changes useEffect(() => { const subSections = getSubSections(activeTab); - console.log("subSections",subSections) + console.log("subSections", subSections) const hash = location.hash.replace('#', ''); if (subSections.length > 0) { @@ -292,7 +334,7 @@ const Setting = () => { setCreateAndInviteLoader(true) try { let res; - let toastMesaage ; + let toastMesaage; if (action === 'create') { console.log('Creating account:', data); const payloadAccount = { @@ -310,10 +352,10 @@ const Setting = () => { role: data.role } res = await api.post(`/api/accounts/${data.i_accountName}/invite`, payloadInvite) - console.log("InviteNAme",res) + console.log("InviteNAme", res) toastMesaage = "Invite Successfull...!" } - + if (res?.data) { toast.success(toastMesaage, { position: "top-center", @@ -326,7 +368,7 @@ const Setting = () => { theme: "light", transition: Bounce, }); - + // Refetch account list after successful submission await getAccountList(); } @@ -340,7 +382,7 @@ const Setting = () => { return ( <> {/* */} - + {/* */}
{/* Main Navigation Tabs */}
@@ -364,7 +406,7 @@ const Setting = () => {
{/* Content with Aside Navigation */} -
+
{/* Fixed Aside Navigation - Shows subsections of active tab */} {activeTab === 'userProfile' &&
diff --git a/src/components/setting/Signature/AddSignatureModal.tsx b/src/components/setting/Signature/AddSignatureModal.tsx index a92c172..285a997 100644 --- a/src/components/setting/Signature/AddSignatureModal.tsx +++ b/src/components/setting/Signature/AddSignatureModal.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { AiOutlineUpload, AiOutlineEdit } from 'react-icons/ai'; -import { MdCloudUpload } from 'react-icons/md'; +import { MdCloudUpload } from 'react-icons/md'; import api from '../../../config/api'; import DrawSignatureCanvas from './DrawSignatureCanvas'; import { FaSpinner } from "react-icons/fa"; @@ -54,7 +54,7 @@ const AddSignatureModal: React.FC = ({ getAllSignatures, editingSignature }) => { - const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT'); + const [activeTab, setActiveTab] = useState<'TYPE' | 'DRAW' | 'UPLOAD' | 'SELECT'>('TYPE'); const [fullName, setFullName] = useState(''); const [initials, setInitials] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); @@ -102,9 +102,10 @@ const AddSignatureModal: React.FC = ({ // Navigation items const navigationItems = [ - { id: 'SELECT', label: 'SELECT', icon: }, + { id: 'TYPE', label: 'TYPE', icon: }, { id: 'DRAW', label: 'DRAW', icon: }, { id: 'UPLOAD', label: 'UPLOAD', icon: }, + { id: 'SELECT', label: 'SELECT', icon: }, ]; // Initialize form with editing signature data @@ -112,7 +113,7 @@ const AddSignatureModal: React.FC = ({ if (editingSignature) { setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`); setInitials(`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`); - setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD'); + setActiveTab(editingSignature.signatureType as 'TYPE' | 'DRAW' | 'UPLOAD'); // Set font for SELECT tab const fontOption = signatureOptions.find(option => option.style === editingSignature.font); @@ -262,7 +263,7 @@ const AddSignatureModal: React.FC = ({ const renderContent = () => { switch (activeTab) { - case 'SELECT': + case 'TYPE': return (
@@ -383,6 +384,16 @@ const AddSignatureModal: React.FC = ({
); + + + case 'SELECT': + return ( +
+
+

Select Signature

+
+
+ ); default: return null; } @@ -462,7 +473,7 @@ const AddSignatureModal: React.FC = ({ {navigationItems.map((item) => (
diff --git a/src/components/setting/UserProfile.tsx b/src/components/setting/UserProfile.tsx index ce58abd..95b0412 100644 --- a/src/components/setting/UserProfile.tsx +++ b/src/components/setting/UserProfile.tsx @@ -6,9 +6,8 @@ interface Account { } interface UserData { - id: string; - firstName: string; - lastName: string; + id: number; + name:string email: string; language: string; account: Account; @@ -26,7 +25,7 @@ const UserProfile: React.FC = ({ userData }) => { {/* Profile Picture */}
- {getInitials(userData.firstName, userData.lastName)} + {userData.name.slice(0,2).toUpperCase()}
@@ -36,7 +35,7 @@ const UserProfile: React.FC = ({ userData }) => {
Name
- {userData.firstName} {userData.lastName} + {userData.name}
diff --git a/src/components/transactions/TransactionView.tsx b/src/components/transactions/TransactionView.tsx index 9f1bbe3..5ec0c86 100644 --- a/src/components/transactions/TransactionView.tsx +++ b/src/components/transactions/TransactionView.tsx @@ -64,13 +64,14 @@ const TransactionView = () => {
{/* Header */}

- Complete with Forms Made Easy: { transactionDetails.transaction.name ?? "Untitled"} + Complete with Forms Made Easy: { transactionDetails?.transaction?.subject ?? "Untitled"}

+ {/* new Date(transactionDetails?.transaction?.lastModifiedAt * 1000) */}

From Shanu.R

-

Last Change on 12/02/25

-

Sent on 11/02/25

+

Last Change on {new Date(transactionDetails?.transaction?.lastModifiedAt * 1000).toISOString()}

+

Created at {new Date(transactionDetails?.transaction?.createdAt * 1000).toISOString()}

Draft

@@ -139,7 +140,7 @@ const TransactionView = () => {

Message

-

No Message Found

+

{ transactionDetails?.transaction?.message ?? "No Message Found...!"}

diff --git a/vite.config.ts b/vite.config.ts index 70397db..0472e82 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,7 +12,8 @@ export default defineConfig({ 'officially-powerful-goat.ngrok-free.app', '89f0-49-36-189-120.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: {