Compare commits

..

No commits in common. "e7a7bfbed13915b578c8406a2c8169b13e936018" and "1a4ea46c5b8df8da79ba594fa8b0d07c979735ff" have entirely different histories.

19 changed files with 301 additions and 516 deletions

View File

@ -19,8 +19,6 @@ import {
FaCalendarAlt FaCalendarAlt
} from 'react-icons/fa'; } from 'react-icons/fa';
import TransactionDataTable from "./TransactionDataTable"; import TransactionDataTable from "./TransactionDataTable";
import { useSelector } from "react-redux";
import { RootState } from "../../store/store";
interface Transaction { interface Transaction {
id: number; id: number;
@ -123,8 +121,6 @@ const Account = () => {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const userData = useSelector((state: RootState) => state.user);
// const getUserDetails = async () => { // const getUserDetails = async () => {
// try { // try {
// let res = await api.get('/api/user/details') // let res = await api.get('/api/user/details')
@ -174,7 +170,7 @@ const Account = () => {
}; };
try { try {
const res = await api.get(`/api/accounts/${userData.account.id}/dashboard`, { params: payload }); const res = await api.get('/api/dashboard', { params: payload });
console.log("Dashboard Data:", res.data); console.log("Dashboard Data:", res.data);
const cardDataArray = mapApiDataToCards(res.data); const cardDataArray = mapApiDataToCards(res.data);
setCards(cardDataArray); setCards(cardDataArray);
@ -249,12 +245,11 @@ const Account = () => {
return ( return (
<> <>
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards} <Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
isLoading={isLoading} /> isLoading={isLoading} />
<TransactionDataTable /> <TransactionDataTable />
{isModalOpen && <AddInviteModal onClose={() => setIsModalOpen(false)} onInvite={handleInvite} inviteLoading={inviteLoading} {isModalOpen && <AddInviteModal onClose={() => setIsModalOpen(false)} onInvite={handleInvite} inviteLoading={inviteLoading}
accountOptions={accountsData} />} accountOptions={accountsData} />}
</> </>

View File

@ -3,24 +3,23 @@ import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
import api from '../../config/api'; import api from '../../config/api';
import { Transaction } from '../transactions/Transactions'; import { Transaction } from '../transactions/Transactions';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { FaCheck } from 'react-icons/fa'; import { FaCheck } from 'react-icons/fa';
const formatDate = (timestamp: number) => { const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleString(); return new Date(timestamp * 1000).toLocaleString();
}; };
const TransactionDataTable = () => { const TransactionDataTable = () => {
const navigate = useNavigate()
const [currentEmailId, setCurrentEmailId] = useState<string | null>('null'); const [currentEmailId, setCurrentEmailId] = useState<string | null>('null');
const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]); const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]);
const [activeAccountId, setActiveAccountId] = useState<string | null>( const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null') JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
); );
const navigate = useNavigate();
const fetchTransactions = async () => { const fetchTransactions = async () => {
console.log("fetchTransactions") console.log("fetchTransactions")
try { try {
@ -48,9 +47,6 @@ const TransactionDataTable = () => {
setCurrentEmailId(userDetails?.email) setCurrentEmailId(userDetails?.email)
}, []); }, []);
return ( return (
<> <>
{transactions.length > 0 && ( {transactions.length > 0 && (

View File

@ -1,32 +1,20 @@
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState, useRef } from 'react';
import { FaEllipsisV } from 'react-icons/fa'; import { FaEllipsisV } from 'react-icons/fa';
import api from '../../config/api'; import api from '../../config/api';
import ContactFormModal, { ContactData } from './ContactsFormModal'; import ContactFormModal from './ContactsFormModal';
import { Bounce, toast } from 'react-toastify'; import { Bounce, toast } from 'react-toastify';
import { IoMdAdd } from 'react-icons/io'; import { IoMdAdd } from 'react-icons/io';
import LoaderUi from '../../helper/LoaderUi';
type ContactSchema = [{
id: number,
firstName: string
lastName: string
email: string
}]
const Contacts = () => { const Contacts = () => {
const activeAccountId = JSON.parse(sessionStorage.getItem('activeAccountId') || 'null'); const activeAccountId = JSON.parse(sessionStorage.getItem('activeAccountId') || 'null');
const [contacts, setContacts] = useState<ContactSchema>([]); const [contacts, setContacts] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [openMenuId, setOpenMenuId] = useState(null); const [openMenuId, setOpenMenuId] = useState(null);
const blurTimeoutRef = useRef(null); const blurTimeoutRef = useRef(null);
const [createAndInviteLoader, setCreateAndInviteLoader] = useState(false); const [createAndInviteLoader, setCreateAndInviteLoader] = useState(false);
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const [editJsonData, setEditJsonData] = useState({})
const [actionStatus, setActionStatus] = useState("")
console.log("editJsonData", editJsonData)
const fetchContacts = async () => { const fetchContacts = async () => {
setLoading(true)
try { try {
const res = await api.get(`/api/accounts/${activeAccountId}/contacts`); const res = await api.get(`/api/accounts/${activeAccountId}/contacts`);
setContacts(res.data.content || []); setContacts(res.data.content || []);
@ -45,27 +33,17 @@ const Contacts = () => {
const handleEdit = (contactId) => { const handleEdit = (contactId) => {
console.log('Edit contact:', contactId); console.log('Edit contact:', contactId);
let jsonEditData = contacts.find((prev) => prev.id === contactId)
setEditJsonData(jsonEditData)
setActionStatus("Edit")
setShowModal((prev) => !prev)
setOpenMenuId(null); setOpenMenuId(null);
}; };
const handleDelete = async (contactId) => { const handleDelete = async(contactId) => {
console.log('Delete contact:', contactId); console.log('Delete contact:', contactId);
setOpenMenuId(null);
try { try {
let res = await api.delete(`/api/accounts/${activeAccountId}/contacts/${contactId}`) let res = await api.delete(`/api/accounts/{accountId}/contacts/{contactId}`)
if (res?.data) { if (res?.data) {
toast.error("Contact delete successfully!", {
position: "top-center", }
autoClose: 1500,
transition: Bounce,
});
fetchContacts()
setOpenMenuId(null);
}
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
@ -83,15 +61,10 @@ const Contacts = () => {
}, 100); }, 100);
}; };
const handleContactSubmit = async (data: ContactData, type: "") => { const handleContactSubmit = async (data: ContactData) => {
try { try {
let res;
setCreateAndInviteLoader(true); setCreateAndInviteLoader(true);
if (type === 'Edit') { const res = await api.post(`/api/accounts/${activeAccountId}/contacts`, [data]);
res = await api.put(`/api/accounts/${activeAccountId}/contacts`, [data])
} else {
res = await api.post(`/api/accounts/${activeAccountId}/contacts`, [data]);
}
if (res.status === 200) { if (res.status === 200) {
toast.success('Contact created successfully!', { toast.success('Contact created successfully!', {
position: "top-center", position: "top-center",
@ -99,16 +72,16 @@ const Contacts = () => {
transition: Bounce, transition: Bounce,
}); });
setShowModal(false) setShowModal(false)
fetchContacts(); fetchContacts();
} }
// return res; // return res;
} catch (err) { } catch (err) {
console.error("Error creating contact", err); console.error("Error creating contact", err);
toast.error("Failed to create contact", { toast.error("Failed to create contact", {
position: "top-center", position: "top-center",
autoClose: 1500, autoClose: 1500,
transition: Bounce, transition: Bounce,
}); });
throw err; throw err;
} finally { } finally {
setCreateAndInviteLoader(false); setCreateAndInviteLoader(false);
@ -117,84 +90,82 @@ const Contacts = () => {
return ( return (
<> <>
<div className='px-1.5'> <div className='flex justify-end my-2 cursor-pointer'>
<div className='flex justify-end my-2 cursor-pointer'> <div
<div onClick={() => setShowModal((prev) => !prev)}
onClick={() => setShowModal((prev) => !prev)} className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm hover:opacity-90 ">
className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm hover:opacity-90 "> <IoMdAdd className="text-white" />
<IoMdAdd className="text-white" /> <button
<button className="text-white rounded text-sm hover:opacity-90 cursor-pointer">
className="text-white rounded text-sm hover:opacity-90 cursor-pointer"> Add Contact
Add Contact </button>
</button>
</div>
</div> </div>
<div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar"> </div>
<table className="min-w-full text-sm bg-white"> <div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar">
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider sticky top-0 z-10"> <table className="min-w-full text-sm bg-white">
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider sticky top-0 z-10">
<tr>
<th className="px-4 py-3 text-left">First Name</th>
<th className="px-4 py-3 text-left">Last Name</th>
<th className="px-4 py-3 text-left">Email</th>
<th className="px-4 py-3 text-center">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{loading ? (
<tr> <tr>
<th className="px-4 py-3 text-left">First Name</th> <td colSpan="4" className="text-center py-4">Loading...</td>
<th className="px-4 py-3 text-left">Last Name</th>
<th className="px-4 py-3 text-left">Email</th>
<th className="px-4 py-3 text-center">Actions</th>
</tr> </tr>
</thead> ) : contacts.length === 0 ? (
<tbody className="divide-y divide-gray-200"> <tr>
{loading ? ( <td colSpan="4" className="text-center py-4">No contacts found.</td>
<tr> </tr>
<td colSpan="4" className="relative h-100"><LoaderUi/></td> ) : (
</tr> contacts.map((contact) => (
) : contacts.length === 0 ? ( <tr key={contact.id} className="hover:bg-gray-50">
<tr> <td className="px-4 py-3">{contact.firstName}</td>
<td colSpan="4" className="text-center py-4">No contacts found.</td> <td className="px-4 py-3">{contact.lastName}</td>
</tr> <td className="px-4 py-3">{contact.email}</td>
) : ( <td className="px-4 py-3 text-center">
contacts.map((contact) => ( <div
<tr key={contact.id} className="hover:bg-gray-50"> className="relative inline-block"
<td className="px-4 py-3">{contact.firstName}</td> tabIndex={0}
<td className="px-4 py-3">{contact.lastName}</td> onBlur={handleBlur}
<td className="px-4 py-3">{contact.email}</td> >
<td className="px-4 py-3 text-center"> <button
<div className="text-gray-600 hover:text-black p-1 focus:outline-none"
className="relative inline-block" onClick={() => toggleMenu(contact.id)}
tabIndex={0}
onBlur={handleBlur}
> >
<button <FaEllipsisV />
className="text-gray-600 hover:text-black p-1 focus:outline-none" </button>
onClick={() => toggleMenu(contact.id)}
>
<FaEllipsisV />
</button>
{openMenuId === contact.id && ( {openMenuId === contact.id && (
<div className="absolute top-full right-0 mt-1 bg-white border border-gray-200 rounded shadow-lg z-20 min-w-[100px]"> <div className="absolute top-full right-0 mt-1 bg-white border border-gray-200 rounded shadow-lg z-20 min-w-[100px]">
<button <button
onMouseDown={() => handleEdit(contact.id)} onClick={() => handleEdit(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600" className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"
> >
Edit Edit
</button> </button>
<button <button
onMouseDown={() => handleDelete(contact.id)} onClick={() => handleDelete(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600" className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600"
> >
Delete Delete
</button> </button>
</div> </div>
)} )}
</div> </div>
</td> </td>
</tr> </tr>
)) ))
)} )}
</tbody> </tbody>
</table> </table>
{showModal && {showModal &&
<ContactFormModal onSubmit={handleContactSubmit} <ContactFormModal onSubmit={handleContactSubmit}
loading={createAndInviteLoader} setShowModal={setShowModal} actionStatus={actionStatus} editJsonData={editJsonData} /> loading={createAndInviteLoader} setShowModal={setShowModal}/>
} }
</div>
</div> </div>
</> </>
); );

View File

@ -1,5 +1,5 @@
// ContactForm.tsx // ContactForm.tsx
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import { FaSpinner } from 'react-icons/fa'; import { FaSpinner } from 'react-icons/fa';
export interface ContactData { export interface ContactData {
@ -17,11 +17,9 @@ interface Props {
onSubmit: (data: ContactData) => void; onSubmit: (data: ContactData) => void;
loading: boolean; loading: boolean;
setShowModal: () => React.Dispatch<React.SetStateAction<boolean>> setShowModal: () => React.Dispatch<React.SetStateAction<boolean>>
actionStatus: string,
editJsonData: any
} }
const ContactFormModal = ({ onSubmit, loading, setShowModal, actionStatus, editJsonData }: Props) => { const ContactFormModal = ({ onSubmit, loading, setShowModal }: Props) => {
const [formData, setFormData] = useState<ContactData>({ const [formData, setFormData] = useState<ContactData>({
email: '', email: '',
firstName: '', firstName: '',
@ -34,21 +32,6 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal, actionStatus, editJ
const [errors, setErrors] = useState<{ [key: string]: string }>({}); const [errors, setErrors] = useState<{ [key: string]: string }>({});
useEffect(() => {
if (actionStatus === 'Edit' && editJsonData) {
setFormData({
email: editJsonData.email,
firstName: editJsonData.firstName,
lastName: editJsonData.lastName,
title: editJsonData.title,
company: editJsonData.company,
website: editJsonData.website,
phones: editJsonData.phones,
})
}
}, [actionStatus, editJsonData])
const handleChange = (field: keyof ContactData, value: string) => { const handleChange = (field: keyof ContactData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value })); setFormData((prev) => ({ ...prev, [field]: value }));
if (errors[field]) { if (errors[field]) {
@ -132,22 +115,13 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal, actionStatus, editJ
const handleSubmit = async () => { const handleSubmit = async () => {
const validationErrors = validate(); const validationErrors = validate();
let type = 'Add'
let payload = formData
if (Object.keys(validationErrors).length > 0) { if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors); setErrors(validationErrors);
return; return;
} }
try {
if (actionStatus === 'Edit') {
type = 'Edit'
payload = {
...formData,
contactId: editJsonData.id
}
}
await onSubmit(payload, type);
try {
await onSubmit(formData);
setFormData({ setFormData({
email: '', email: '',
@ -184,7 +158,7 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal, actionStatus, editJ
<label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">{label}</label> <label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">{label}</label>
<input <input
type={field === 'email' ? 'email' : 'text'} type={field === 'email' ? 'email' : 'text'}
value={(formData[field as keyof ContactData] as string) || ''} value={formData[field as keyof ContactData] as string}
onChange={(e) => handleChange(field as keyof ContactData, e.target.value)} onChange={(e) => handleChange(field as keyof ContactData, e.target.value)}
required={true} required={true}
className="px-3 py-1.5 border border-gray-400 rounded-md focus:outline-none focus:ring-1 focus:ring-[var(--primary)] text-sm" className="px-3 py-1.5 border border-gray-400 rounded-md focus:outline-none focus:ring-1 focus:ring-[var(--primary)] text-sm"
@ -198,7 +172,7 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal, actionStatus, editJ
<div> <div>
<label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">Phone Numbers</label> <label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">Phone Numbers</label>
<div className="space-y-2"> <div className="space-y-2">
{formData?.phones?.map((phone, index) => ( {formData.phones.map((phone, index) => (
<div key={index} className="flex flex-col gap-1"> <div key={index} className="flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<input <input
@ -255,9 +229,8 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal, actionStatus, editJ
SAVING... SAVING...
</> </>
) : ( ) : (
`${actionStatus?.toUpperCase() || 'SAVE'} CONTACT` 'SAVE CONTACT'
)} )}
</button> </button>

View File

@ -1,3 +1,4 @@
import React from 'react'
import { LuSignature } from "react-icons/lu"; import { LuSignature } from "react-icons/lu";
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { RootState } from '../../store/store'; import { RootState } from '../../store/store';

View File

@ -14,8 +14,6 @@ import {
FaFilter, FaFilter,
FaCalendarAlt FaCalendarAlt
} from 'react-icons/fa'; } from 'react-icons/fa';
import { useSelector } from 'react-redux';
import { RootState } from '../../store/store';
interface CardData { interface CardData {
title: string; title: string;
@ -84,8 +82,6 @@ const Home = () => {
const [cards, setCards] = useState<CardData[]>([]); const [cards, setCards] = useState<CardData[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const userData = useSelector((state : RootState) => state.user);
console.log("activeAccountId", activeAccountId); console.log("activeAccountId", activeAccountId);
console.log("cards", cards); console.log("cards", cards);
@ -98,7 +94,7 @@ const Home = () => {
}; };
try { try {
const res = await api.get(`/api/accounts/${userData.account.id}/dashboard`, { params: payload }); const res = await api.get('/api/dashboard', { params: payload });
setDashboardData(res.data); setDashboardData(res.data);
console.log("Dashboard Data:", res.data); console.log("Dashboard Data:", res.data);
const cardDataArray = mapApiDataToCards(res.data); const cardDataArray = mapApiDataToCards(res.data);

View File

@ -19,8 +19,7 @@ import { Link, useNavigate, useParams } from "react-router-dom";
import Loader from "../../dashboard/Loader"; import Loader from "../../dashboard/Loader";
import confetti from 'canvas-confetti' import confetti from 'canvas-confetti'
import { IoClose } from "react-icons/io5"; import { IoClose } from "react-icons/io5";
import { IoMdClose, IoMdHome } from "react-icons/io"; import { IoMdHome } from "react-icons/io";
import { Bounce, toast } from "react-toastify";
type Signer = { type Signer = {
id: number; id: number;
@ -49,7 +48,7 @@ const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
{signers.map((signer) => ( {signers.map((signer) => (
<div key={signer.id} className="flex items-center gap-4 p-3 bg-white/40 rounded-lg"> <div key={signer.id} className="flex items-center gap-4 p-3 bg-white/40 rounded-lg">
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center text-blue-600 font-medium"> <div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center text-blue-600 font-medium">
{signer.name.length > 2 ? signer.name.split(" ").map(n => n[0].toUpperCase()).join("") : signer.email.split("").map(n => n[0].toUpperCase()).join("")} {signer.name.split(" ").map(n => n[0].toUpperCase()).join("")}
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="font-medium text-gray-800">{signer.name}</div> <div className="font-medium text-gray-800">{signer.name}</div>
@ -223,19 +222,6 @@ const DocumentCompletionScreen = () => {
}, []) }, [])
useEffect(() => { useEffect(() => {
// toast.dismiss();
// toast.success("Your document has been successfully sent for signature.", {
// position: "top-center",
// autoClose: 1500,
// hideProgressBar: false,
// closeOnClick: false,
// pauseOnHover: true,
// draggable: true,
// progress: undefined,
// theme: "light",
// transition: Bounce,
// });
if (showConfetti) { if (showConfetti) {
const duration = 3000; const duration = 3000;
const end = Date.now() + duration; const end = Date.now() + duration;
@ -247,63 +233,76 @@ const DocumentCompletionScreen = () => {
} }
confetti({ confetti({
particleCount: 5, // fewer particles per burst for smoothness particleCount: 80, // more particles
angle: 90, // falling straight down angle: Math.random() * 360, // fire in random direction
spread: 30, // slight spread spread: 220, // wider spread
startVelocity: 8, // gentle falling speed startVelocity: 15, // more power
scalar: 1.2, // bigger pieces (default is 1)
origin: { origin: {
x: Math.random(), // random horizontal position across the top x: Math.random(),
y: 0, y: Math.random() * 0.6,
}, },
scalar: 1,
zIndex: 9999, zIndex: 9999,
}); });
}, 50); // fire every 50ms for continuous effect }, 350); // fire every 150ms
return () => clearInterval(interval);
} }
}, [showConfetti]); }, [showConfetti]);
return ( 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)]"> <div className="min-h-screen bg-[var(--mainViewBg)]">
{/* Top Banner */} {/* Top Banner */}
<div className='fixed top-0 w-full flex items-center justify-between bg-white shadow z-50 px-4 py-3'>
<div className='flex items-center gap-2'>
<Link to={"/"}>
<IoMdHome
className='text-2xl text-slate-500 cursor-pointer hover:text-[var(--primary)] transition'
/>
</Link>
<p className="text-base font-normal text-gray-700 border-l pl-4">🎉 Awesome! The document’s been sent for signature. 🎉</p>
</div>
<div
id="add-tags-btn-step-one"
className='flex items-center gap-3'>
<Link to={`/accounts/${accountId}/transactions/${transactionId}`} className="flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]">
<FaPen className=" text-white text-md" />
<span className="tracking-wide text-white">Edit</span>
</Link>
{/* <button
onClick={() => handleNextClick(false)}
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer"
>
{signType === SignTypeEnum.selfSign ? "Sign" : "Next"}
</button> */}
<div className="w-full px-6 py-2 bg-white border-b rounded-sm border-gray-200">
<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 text-green-700 rounded-full flex items-center justify-center">
<IoMdHome className="h-10 w-10" />
</Link>
{/* <div className="w-5 h-5 bg-white text-green-800 rounded-full flex items-center justify-center">
<FaCheck className="text-sm" />
</div> */}
<div>
<h2 className="text-md font-semibold text-gray-900">Awesome! Your document is sent for signature</h2>
</div>
</div>
<Link to={`/accounts/${accountId}/transactions/${transactionId}`} className="flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]">
<FaPen className=" text-white text-md" />
<span className="tracking-wide text-white">Edit</span>
</Link>
</div>
</div> </div>
</div> </div>
{(selectedRecipients.length > 0 && selectedDocuments.length > 0) ? {(selectedRecipients.length > 0 && selectedDocuments.length > 0) ?
<div className="p-3 mt-[55px]"> <div className="p-3">
<div className="max-w-[97vw] mx-auto"> <div className="max-w-[97vw] mx-auto">
{/* Header */} {/* Header */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-2"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-2">
@ -343,7 +342,7 @@ const DocumentCompletionScreen = () => {
<SigningOrderDiagram signers={selectedRecipients} /> <SigningOrderDiagram signers={selectedRecipients} />
{/* Signers List */} {/* Signers List */}
</div> </div>
</div> </div>
</div> </div>

View File

@ -75,19 +75,19 @@ const ProcessStepOne = () => {
console.log("Error - transactions details") console.log("Error - transactions details")
} }
})() })()
// Fetching Transaction Details - END // Fetching Transaction Details - END
// ======= // =======
// (async () => { // (async () => {
// const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`) // const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`)
// console.log(res.data); // console.log(res.data);
// })() // })()
// >>>>>>> origin/main // >>>>>>> origin/main
// (async () => { // (async () => {
// const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`) // const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`)
// console.log(res.data); // console.log(res.data);
// })() // })()
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId })); selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
@ -104,7 +104,7 @@ const ProcessStepOne = () => {
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const handleNextClick = async (isSendNow: boolean, saveAndClose: boolean = false) => { const handleNextClick = async (isSendNow: boolean) => {
let formError = { let formError = {
documents: false, documents: false,
recipients: false, recipients: false,
@ -168,7 +168,6 @@ const ProcessStepOne = () => {
const recipientsData = selectedRecipients.map((recipient, index) => ({ const recipientsData = selectedRecipients.map((recipient, index) => ({
id: "id" in recipient ? recipient.id : null, id: "id" in recipient ? recipient.id : null,
routingOrder: "routingOrder" in recipient && recipient.routingOrder ? recipient.routingOrder : 1, routingOrder: "routingOrder" in recipient && recipient.routingOrder ? recipient.routingOrder : 1,
provideOwnTags: userData.email === recipient.email ? true : false,
type: recipient.type, type: recipient.type,
firstName: recipient.firstName, firstName: recipient.firstName,
lastName: recipient.lastName, lastName: recipient.lastName,
@ -226,15 +225,18 @@ const ProcessStepOne = () => {
} }
} else { } else {
await dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId })); dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId }));
} }
dispatch(showLoader("Processing Message...")); dispatch(showLoader("Processing Message..."));
console.log(`1/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`)
let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, {
subject: emailDtls.subject, message: emailDtls.message, subject: emailDtls.subject, message: emailDtls.message,
status: "CREATED" status: "CREATED"
}) })
console.log("Line 158", res.data)
if (isSendNow) { if (isSendNow) {
dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS)); dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS));
setTimeout(() => { setTimeout(() => {
@ -248,11 +250,7 @@ const ProcessStepOne = () => {
dispatch(hideLoader()); dispatch(hideLoader());
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) { if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
console.log(`/process-step-two/${accountId}/template/${transactionId}`) console.log(`/process-step-two/${accountId}/template/${transactionId}`)
if(!saveAndClose) { navigate(`/process-step-two/${accountId}/template/${transactionId}`)
navigate(`/process-step-two/${accountId}/template/${transactionId}`)
} else {
navigate(`/templates`)
}
} else { } else {
if (signType === SignTypeEnum.selfSign) { if (signType === SignTypeEnum.selfSign) {
(async () => { (async () => {
@ -321,22 +319,12 @@ const ProcessStepOne = () => {
<div <div
id="add-tags-btn-step-one" id="add-tags-btn-step-one"
className='flex items-center gap-3'> className='flex items-center gap-3'>
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && <button
onClick={() => handleNextClick(false, true)}
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer"
>
Save & Close
</button>}
<button <button
onClick={() => handleNextClick(false)} onClick={() => handleNextClick(false)}
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer" className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition"
> >
{signType === SignTypeEnum.selfSign ? "Sign" : "Next"} {signType === SignTypeEnum.selfSign ? "Sign" : "Next"}
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { FaPlus } from "react-icons/fa"; import { FaPlus } from "react-icons/fa";
import { addRecipient, Recipient, recipientType, TRANSACTION_TEMPLATE_TOGGLE, updateRecipient } from "../../../store/slices/selectedRecipientsSlice"; import { addRecipient, Recipient, recipientType, updateRecipient } from "../../../store/slices/selectedRecipientsSlice";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { AppDispatch } from "../../../store/store"; import { AppDispatch } from "../../../store/store";
import api from "../../../config/api"; import api from "../../../config/api";
@ -36,7 +36,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
const dispatch = useDispatch<AppDispatch>(); const dispatch = useDispatch<AppDispatch>();
const formRef = useRef<HTMLFormElement | null>(null); const formRef = useRef<HTMLFormElement | null>(null);
const [recipient, setRecipients] = useState<Recipient>({ uuid: null, newOne: false, firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: true, allowSenderToDelete: true }); const [recipient, setRecipients] = useState<Recipient>({ uuid: null, newOne: false, firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: false, allowSenderToDelete: false });
const [showAdvanceSettings, setShowAdvanceSettings] = useState(false) const [showAdvanceSettings, setShowAdvanceSettings] = useState(false)
@ -72,8 +72,8 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
hostEmail: editRecipient.hostEmail || "", hostEmail: editRecipient.hostEmail || "",
roleName: editRecipient.roleName || "", roleName: editRecipient.roleName || "",
type: editRecipient.type || recipientType.SIGNER, type: editRecipient.type || recipientType.SIGNER,
allowSenderToEdit: editRecipient.allowSenderToEdit ?? true, allowSenderToEdit: editRecipient.allowSenderToEdit ?? false,
allowSenderToDelete: editRecipient.allowSenderToDelete ?? true, allowSenderToDelete: editRecipient.allowSenderToDelete ?? false,
uuid: String(editRecipient?.id ?? editRecipient?.uuid ?? ''), uuid: String(editRecipient?.id ?? editRecipient?.uuid ?? ''),
newOne: editRecipient.newOne ?? false newOne: editRecipient.newOne ?? false
}); });
@ -86,19 +86,15 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
console.log("recipientrecipient", recipient) console.log("recipientrecipient", recipient)
if(transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) { if (!recipient.lastName || !recipient.email || !recipient.type) return;
if (!recipient.lastName || !recipient.email || !recipient.type) return;
// ✅ Email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(recipient.email)) {
console.log("Invalid email format");
return;
}
// ✅ Email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(recipient.email)) {
console.log("Invalid email format");
return;
} }
if (editCase === 'Edit') { if (editCase === 'Edit') {
dispatch(updateRecipient(recipient)); dispatch(updateRecipient(recipient));
} else { } else {
@ -117,8 +113,8 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
hostEmail: "", hostEmail: "",
roleName: "", roleName: "",
type: recipientType.SIGNER, type: recipientType.SIGNER,
allowSenderToEdit: true, allowSenderToEdit: false,
allowSenderToDelete: true, allowSenderToDelete: false,
uuid: "", uuid: "",
newOne: false newOne: false
}); });
@ -279,13 +275,13 @@ useEffect(() => {
id="add-recipient-step-one" id="add-recipient-step-one"
className="w-full bg-white border border-gray-200 rounded-lg shadow-md px-6 py-4 space-y-3 relative" className="w-full bg-white border border-gray-200 rounded-lg shadow-md px-6 py-4 space-y-3 relative"
> >
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && ( {transactionTemplateToggle === 'templates' && (
<div className="space-y-1"> <div className="space-y-1">
{/* Top Row: Role Input + Settings */} {/* Top Row: Role Input + Settings */}
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
{/* Role Name Input */} {/* Role Name Input */}
<input <input
required={recipient.firstName === "" && recipient.lastName === "" && recipient.email === ""} required
type="text" type="text"
onFocus={() => setOpenContactModal(true)} onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur} onBlur={handleContactModalBlur}
@ -417,7 +413,7 @@ useEffect(() => {
onChange={(e) => setRecipients({ ...recipient, firstName: e.target.value })} onChange={(e) => setRecipients({ ...recipient, firstName: e.target.value })}
/> />
<input <input
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES} required
type="text" type="text"
onFocus={() => handleFocus("lastName")} onFocus={() => handleFocus("lastName")}
onBlur={handleContactModalBlur} onBlur={handleContactModalBlur}
@ -427,7 +423,7 @@ useEffect(() => {
onChange={(e) => setRecipients({ ...recipient, lastName: e.target.value })} onChange={(e) => setRecipients({ ...recipient, lastName: e.target.value })}
/> />
<input <input
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES} required
type="email" type="email"
onFocus={() => handleFocus("email")} onFocus={() => handleFocus("email")}
onBlur={handleContactModalBlur} onBlur={handleContactModalBlur}
@ -441,7 +437,7 @@ useEffect(() => {
{recipient.type === recipientType.INPERSONSIGNER && <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> {recipient.type === recipientType.INPERSONSIGNER && <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<input <input
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES} required
type="text" type="text"
onFocus={() => setOpenContactModal(true)} onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur} onBlur={handleContactModalBlur}
@ -451,7 +447,7 @@ useEffect(() => {
onChange={(e) => setRecipients({ ...recipient, hostName: e.target.value })} onChange={(e) => setRecipients({ ...recipient, hostName: e.target.value })}
/> />
<input <input
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES} required
type="email" type="email"
onFocus={() => setOpenContactModal(true)} onFocus={() => setOpenContactModal(true)}
onBlur={handleContactModalBlur} onBlur={handleContactModalBlur}

View File

@ -20,7 +20,6 @@ import Loader from '../../dashboard/Loader';
// import { TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice"; // import { TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice";
import { handleGroupBorderPosition } from '../../../utlity/tagGroupConUtils'; import { handleGroupBorderPosition } from '../../../utlity/tagGroupConUtils';
import { hideLoader, showLoader } from '../../../store/slices/loaderSlice'; import { hideLoader, showLoader } from '../../../store/slices/loaderSlice';
import { IsSendInterface } from './ProcessStepTwo';
export interface DroppedItemsObj { export interface DroppedItemsObj {
[key: string]: DroppedItem[] [key: string]: DroppedItem[]
@ -67,7 +66,12 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments) // const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents) const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
console.log("selectedDocuments", selectedDocuments)
const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({}); const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({});
console.log("droppedItems", droppedItems)
// const [uniqGroupIds, setUniqGroupIds] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
@ -75,12 +79,9 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
}, []) }, [])
console.log(droppedItems, "line 78")
// FETCHING TAG - START // FETCHING TAG - START
useEffect(() => { useEffect(() => {
if (selectedRecipients.length === 0) return; if (selectedRecipients.length === 0) return;
const fetchTags = async () => { const fetchTags = async () => {
try { try {
const requests = selectedRecipients.map((recipient) => const requests = selectedRecipients.map((recipient) =>
@ -180,22 +181,17 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
return [recipientId, tags.map(tag => ({ ...tag, update: false }))]; return [recipientId, tags.map(tag => ({ ...tag, update: false }))];
}) })
) )
console.log(droppedItemsUpdateFalse, "Line 182")
setDroppedItems(droppedItemsUpdateFalse); setDroppedItems(droppedItemsUpdateFalse);
} catch (error) { } catch (error) {
console.error("Error submitting recipient tags:", error); console.error("Error submitting recipient tags:", error);
} }
}; };
// Save Tags - START
useEffect(() => {
if (isSend === null) return;
const handleTransactionProcess = async (save: boolean = false) => { dispatch(showLoader("Loading ..."))
console.log(droppedItems, "called Line 189");
if (!save && isSend === null) return;
!save && dispatch(showLoader("Loading ..."))
let updatedTags = { ...droppedItems } let updatedTags = { ...droppedItems }
@ -300,37 +296,17 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
tagsData = groupByRecipientId(tagsData); tagsData = groupByRecipientId(tagsData);
console.log(tagsData, "Line 302"); sendTagsToRecipients(tagsData, accountId, transactionId);
await sendTagsToRecipients(tagsData, accountId, transactionId); (async () => {
const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" })
// const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { status: "SENT" })
// SENDING Transaction - START console.log(res, "Check123")
// if (!save) { dispatch(hideLoader())
// const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" }) })()
// // const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { status: "SENT" })
// console.log(res, "Check123")
// dispatch(hideLoader())
// }
// SENDING Transaction - END
}
// Save Tags - START
useEffect(() => {
handleTransactionProcess()
}, [isSend]) }, [isSend])
// Save Tags - END // Save Tags - END
// useEffect(() => {
// setTimeout(() => {
// setInterval(async () => {
// await handleTransactionProcess(true);
// }, 8000)
// }, 8000)
// }, [])
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => { const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault(); // Allows the drop event to work event.preventDefault(); // Allows the drop event to work
}; };
@ -1055,9 +1031,9 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
const IconComponent = typeToIconMap[type] || <FaQuestionCircle />; const IconComponent = typeToIconMap[type] || <FaQuestionCircle />;
const selectedRecipient = selectedRecipients.filter((obj, indx) => obj.id === item.recipientId); const selectedRecipient = selectedRecipients.filter((obj, indx) => obj.id === item.recipientId);
return ( return (
<React.Fragment key={index}> <>
<div <div
// key={index} key={index}
draggable="false" draggable="false"
className="absolute rounded dropped-item" className="absolute rounded dropped-item"
onClick={() => handleTagClick(key, index)} onClick={() => handleTagClick(key, index)}
@ -1119,22 +1095,20 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
</div> </div>
<> {
{ "group" in item && item.group && "isGroupExist" in item && !item.isGroupExist &&
"group" in item && item.group && "isGroupExist" in item && !item.isGroupExist && <>
<> <div className="group-border" border-group={item.group} border-pos="top"></div>
<div className="group-border" border-group={item.group} border-pos="top"></div> <div className="group-border" border-group={item.group} border-pos="right"></div>
<div className="group-border" border-group={item.group} border-pos="right"></div> <div className="group-border bottom-border" border-group={item.group} border-pos="bottom">
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom"> <div onClick={() => handleAddGroupItemClicked(key, item)}><IoAdd /></div>
<div onClick={() => handleAddGroupItemClicked(key, item)}><IoAdd /></div> </div>
</div> <div className="group-border" border-group={item.group} border-pos="left"></div>
<div className="group-border" border-group={item.group} border-pos="left"></div> </>
</>
} }
</>
</React.Fragment> </>
) )
} }
@ -1177,11 +1151,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
</div > </div >
{ {selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} DirectionalControlsProps={{
console.log(selectedTag, droppedItems, "Line 1175")
}
{selectedTag && selectedTag !== null && "key" in selectedTag && "index" in selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} DirectionalControlsProps={{
onMove: directionalControlsHandler, onMove: directionalControlsHandler,
onPositionChange: handleDirectPositionChange, onPositionChange: handleDirectPositionChange,
disabled: false, disabled: false,

View File

@ -7,11 +7,10 @@ import { FaCheck, FaSignature } from 'react-icons/fa'
import { IoIosDocument } from 'react-icons/io' import { IoIosDocument } from 'react-icons/io'
import { LuTableOfContents } from 'react-icons/lu' import { LuTableOfContents } from 'react-icons/lu'
import { MdKeyboardArrowDown } from 'react-icons/md' import { MdKeyboardArrowDown } from 'react-icons/md'
import { RiResetRightLine } from 'react-icons/ri' import { RiResetLeftLine, RiResetRightLine } from 'react-icons/ri'
import { fetchRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice' import { fetchRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice'
import { fetchDocuments } from '../../../store/slices/documentSlice' import { fetchDocuments } from '../../../store/slices/documentSlice'
import { useNavigate, useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
import { Bounce, toast } from 'react-toastify'
interface NavTwoProps { interface NavTwoProps {
isFields: boolean; isFields: boolean;
@ -66,83 +65,27 @@ 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 [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 });
const navigate = useNavigate() 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)
const userDetails = useSelector((state: RootState) => state.user);
useEffect(() => { useEffect(() => {
if (selectedRecipients.length || selectedDocuments.length) return; selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
(async () => { selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
selectedRecipients.length === 0 && await dispatch(fetchRecipients({ accountId, transactionId }));
selectedDocuments.length === 0 && await dispatch(fetchDocuments({ accountId, transactionId }));
})()
}, []) }, [])
useEffect(() => {
if (selectedRecipients.length > 0) {
let minRoutingOrder: number = Infinity;
let userEmailPresent = {
present: false,
routingOrder: 0,
recipientId: 0
};
// Getting Minimum RoutingOrder
for (let i = 0; i < selectedRecipients.length; i++) {
const recipient = selectedRecipients[i]
if (recipient.routingOrder < minRoutingOrder) {
minRoutingOrder = recipient.routingOrder;
}
if (recipient.email === userDetails.email) {
userEmailPresent.present = true;
userEmailPresent.routingOrder = recipient.routingOrder;
userEmailPresent.recipientId = recipient.id;
}
}
if (userEmailPresent.present && userEmailPresent.routingOrder === minRoutingOrder) {
setSelfUserSign({ sign: true, recipientId: userEmailPresent.recipientId })
}
}
}, [selectedRecipients])
const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false); const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false);
const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false); const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false);
function handleSend(isSignSend: boolean = false) { function handleSend() {
// dispatch(showLoader("Loading ...")); // dispatch(showLoader("Loading ..."));
if (isSend === null) { if (isSend === null) {
setIsSend(false); setIsSend(false);
} }
setIsSend(!isSend); setIsSend(!isSend)
navigate(`/document-completion-screen/${accountId}/${transactionId}`)
setTimeout(() => {
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
if (isSignSend) {
navigate(`./../../../sign/${selfUserSign.recipientId}`);
}
// else {
// navigate(`/document-completion-screen/${accountId}/${transactionId}`)
// }
} else {
navigate(`/templates`);
toast.success("Template Saved...!", {
position: "top-center",
autoClose: 1500,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}
}, 0)
} }
const handleSetIsFields = (setTo: boolean) => { const handleSetIsFields = (setTo: boolean) => {
@ -164,24 +107,24 @@ const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSen
zoom(Number(indx), 'zoom-con') zoom(Number(indx), 'zoom-con')
}, [selFitIndx]) }, [selFitIndx])
// useEffect(() => { useEffect(() => {
// let timeoutId: number; let timeoutId: number;
// const timeOutIn = 8000; const timeOutIn = 8000;
// const updateLabel = () => { const updateLabel = () => {
// if (document.visibilityState === 'visible') { if (document.visibilityState === 'visible') {
// setTransactionSaving(true); setTransactionSaving(true);
// setTimeout(() => { setTimeout(() => {
// setTransactionSaving(false); setTransactionSaving(false);
// }, 4000) }, 4000)
// } }
// timeoutId = setTimeout(updateLabel, timeOutIn); timeoutId = setTimeout(updateLabel, timeOutIn);
// }; };
// timeoutId = setTimeout(updateLabel, timeOutIn); timeoutId = setTimeout(updateLabel, timeOutIn);
// // Cleanup on unmount // Cleanup on unmount
// return () => clearTimeout(timeoutId); return () => clearTimeout(timeoutId);
// }, []); }, []);
useEffect(() => { useEffect(() => {
if (!transactionSaving) return; if (!transactionSaving) return;
@ -322,20 +265,9 @@ const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSen
id='send-sign-dash-btn' id='send-sign-dash-btn'
onClick={() => handleSend()} onClick={() => handleSend()}
className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]'> className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]'>
<p className='tracking-wide text-white'> <p className='tracking-wide text-white'>Send</p>
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES ? "Save" : "Send"}
</p>
<FaSignature className='text-white text-md' /> <FaSignature className='text-white text-md' />
</div> </div>
{transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && selfUserSign.sign && <div
id='send-and-sign-dash-btn'
onClick={() => handleSend(selfUserSign.sign)}
className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]'>
<p className='tracking-wide text-white'>
Sign & Send
</p>
<FaSignature className='text-white text-md' />
</div>}

View File

@ -9,16 +9,10 @@ import { RootState } from '../../../store/store';
import Loader from '../../dashboard/Loader'; import Loader from '../../dashboard/Loader';
import { hideLoader } from '../../../store/slices/loaderSlice'; import { hideLoader } from '../../../store/slices/loaderSlice';
export interface IsSendInterface {
send: boolean | null;
save: boolean;
}
const ProcessStepTwo = () => { const ProcessStepTwo = () => {
const [isFields, setIsFields] = useState(true); const [isFields, setIsFields] = useState(true);
const [selectedRecipient, setSelectedRecipient] = useState(0); const [selectedRecipient, setSelectedRecipient] = useState(0);
const [isSend, setIsSend] = useState<boolean | null>(null); const [isSend, setIsSend] = useState<boolean | null>(null);
const globalLoader = useSelector((state: RootState) => state.loader) const globalLoader = useSelector((state: RootState) => state.loader)
const dispatch = useDispatch(); const dispatch = useDispatch();

View File

@ -152,7 +152,7 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
{/* Middle Row: Left - Center (Inputs) - Right */} {/* Middle Row: Left - Center (Inputs) - Right */}
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
{/* Left Button */} {/* Left Button */}
{/* <div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<button <button
onClick={() => handleMove('left')} onClick={() => handleMove('left')}
disabled={disabled} disabled={disabled}
@ -163,12 +163,12 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
<FaArrowLeft size={12} /> <FaArrowLeft size={12} />
</button> </button>
<span className="mt-1">Left</span> <span className="mt-1">Left</span>
</div> */} </div>
{/* Center Panel */} {/* Center Panel */}
<div className="relative flex items-center justify-center bg-white rounded-lg px-1 py-2 w-24 "> <div className="relative flex items-center justify-center bg-white rounded-lg px-1 py-2 w-24 ">
{/* Floating dimensions tooltip */} {/* Floating dimensions tooltip */}
{/* <div className="absolute -top-2 left-1/2 -translate-x-1/2 px-2 py-[1px] text-[10px] font-mono text-black z-10 rounded-sm flex items-center gap-[2px]"> <div className="absolute -top-2 left-1/2 -translate-x-1/2 px-2 py-[1px] text-[10px] font-mono text-black z-10 rounded-sm flex items-center gap-[2px]">
<input <input
type="number" type="number"
value={leftValue} value={leftValue}
@ -188,12 +188,12 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
disabled={disabled} disabled={disabled}
className="w-[40px] h-[16px] text-[10px] text-center border-none focus:outline-none text-black font-mono" className="w-[40px] h-[16px] text-[10px] text-center border-none focus:outline-none text-black font-mono"
/> />
</div> */} </div>
</div> </div>
{/* Right Button */} {/* Right Button */}
{/* <div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<button <button
onClick={() => handleMove('right')} onClick={() => handleMove('right')}
disabled={disabled} disabled={disabled}
@ -204,11 +204,11 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
<FaArrowRight size={12} /> <FaArrowRight size={12} />
</button> </button>
<span className="mt-1">Right</span> <span className="mt-1">Right</span>
</div> */} </div>
</div> </div>
{/* Bottom Button */} {/* Bottom Button */}
{/* <div className="flex justify-center me-4"> <div className="flex justify-center me-4">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<button <button
onClick={() => handleMove('down')} onClick={() => handleMove('down')}
@ -221,7 +221,7 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
</button> </button>
<span className="mt-1">Bottom</span> <span className="mt-1">Bottom</span>
</div> </div>
</div> */} </div>
</div> </div>
); );
}; };

View File

@ -40,7 +40,7 @@ const Templates = () => {
const getAllTemplates = async () => { const getAllTemplates = async () => {
setLoading(true) setLoading(true)
try { try {
let res = await api.get(`/api/accounts/${activeAccountId}/templates?sortBy=lastModifiedAt desc`) let res = await api.get(`/api/accounts/${activeAccountId}/templates`)
let templateData = res?.data let templateData = res?.data
setTemplates(templateData) setTemplates(templateData)
} catch (error) { } catch (error) {
@ -84,10 +84,7 @@ const Templates = () => {
} }
} }
const handleEditClick = (templateId: string) => {
setShowMenu(null);
navigate(`/process-step-one/${accountId}/template/${templateId}`);
}
return ( return (
<div className="p-3 text-sm"> <div className="p-3 text-sm">
@ -139,15 +136,7 @@ const Templates = () => {
</tr> </tr>
</thead> </thead>
{loading ? ( {templates.length > 0 ? (
<tbody>
<tr>
<td colSpan="6" className="relative h-100">
<LoaderUi />
</td>
</tr>
</tbody>
) : templates.length > 0 ? (
<tbody className="divide-y divide-gray-200"> <tbody className="divide-y divide-gray-200">
{templates.map((item, index) => { {templates.map((item, index) => {
return ( return (
@ -157,7 +146,7 @@ const Templates = () => {
{/* <FaCheck className="text-purple-600 text-xs" /> */} {/* <FaCheck className="text-purple-600 text-xs" /> */}
</div> </div>
</td> </td>
<td className="px-4 py-2 text-sm text-center">{item.name || 'Untitled'}</td> <td className="px-4 py-2 text-sm text-center">{item.name || 'Dummy'}</td>
<td className="px-4 py-2 text-sm text-center">{formatDate(item.createdAt)}</td> <td className="px-4 py-2 text-sm text-center">{formatDate(item.createdAt)}</td>
<td className="px-4 py-2 text-sm text-center">{formatDate(item.lastModifiedAt)}</td> <td className="px-4 py-2 text-sm text-center">{formatDate(item.lastModifiedAt)}</td>
<td className="px-4 py-2 text-sm text-center" onClick={() => handleUseClick(item)}><span className="bg-[var(--lightGreyCol)] cursor-pointer border-gray-300 text-gray-600 hover:bg-gray-100 px-3 py-1 rounded-lg font-semibold text-[12px]">Use</span></td> <td className="px-4 py-2 text-sm text-center" onClick={() => handleUseClick(item)}><span className="bg-[var(--lightGreyCol)] cursor-pointer border-gray-300 text-gray-600 hover:bg-gray-100 px-3 py-1 rounded-lg font-semibold text-[12px]">Use</span></td>
@ -183,7 +172,10 @@ const Templates = () => {
> >
<button <button
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
onClick={() => handleEditClick(item.id)} onClick={() => {
setShowMenu(null);
navigate(`/process-step-two/${accountId}/template/${item.id}`);
}}
> >
Edit Edit
</button> </button>
@ -208,12 +200,18 @@ const Templates = () => {
) : ( ) : (
<tbody> <tbody>
<tr> <tr>
<td colSpan="6" className="text-center py-10 text-gray-500"> <td colSpan="5">
No templates found. <div className="relative flex justify-center items-center h-100 w-full">
{/* Pulsing background div */}
<div className="absolute inset-0 bg-gradient-to-r from-gray-200 to-white animate-pulse"></div>
{/* Loader stays on top, no animation */}
<LoaderUi classStyle="relative h-16 w-16" />
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
)} )}
</table> </table>
</div> </div>

View File

@ -135,7 +135,7 @@ const TransactionView = () => {
<AiFillSignature className='absolute left-[-30px] text-[1rem]' /> <AiFillSignature className='absolute left-[-30px] text-[1rem]' />
<p className='text-black font-semibold'>{recipient.status}</p> <p className='text-black font-semibold'>{recipient.status}</p>
</div> </div>
{recipient.email === user.email && recipient.status !== "COMPLETED" && {recipient.email === user.email &&
<p <p
onClick={() => handleNavigateToSign(recipient.id)} onClick={() => handleNavigateToSign(recipient.id)}
className='text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold'> className='text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold'>

View File

@ -1,22 +1,19 @@
import { LuSignature } from "react-icons/lu";
interface StyleProps { interface StyleProps {
classStyle?: string; classStyle : string
} }
const LoaderUi = ({ classStyle = '' }: StyleProps) => { const LoaderUi = ({ classStyle }:StyleProps) => {
return ( return (
<div className={`absolute inset-0 flex items-center justify-center z-50 ${classStyle}`}> <div className={classStyle}>
{/* Overlay */} <svg viewBox="0 0 240 240" height="240" width="240" className="pl">
<div className="absolute inset-0 bg-black/30 opacity-60"></div> <circle strokeLinecap="round" strokeDashoffset="-330" strokeDasharray="0 660" strokeWidth="20" stroke="#000" fill="none" r="105" cy="120" cx="120" className="pl__ring pl__ring--a"></circle>
<circle strokeLinecap="round" strokeDashoffset="-110" strokeDasharray="0 220" strokeWidth="20" stroke="#000" fill="none" r="35" cy="120" cx="120" className="pl__ring pl__ring--b"></circle>
<circle strokeLinecap="round" strokeDasharray="0 440" strokeWidth="20" stroke="#000" fill="none" r="70" cy="120" cx="85" className="pl__ring pl__ring--c"></circle>
<circle strokeLinecap="round" strokeDasharray="0 440" strokeWidth="20" stroke="#000" fill="none" r="70" cy="120" cx="155" className="pl__ring pl__ring--d"></circle>
</svg>
</div>
)
}
{/* Loader box */} export default LoaderUi
<div className="relative z-10 bg-[var(--loaderBg)] flex flex-col justify-center items-center gap-2 px-16 py-8 rounded-md shadow-lg">
<LuSignature className="text-4xl font-bold animate-text" />
<p>Loading...!</p>
</div>
</div>
);
};
export default LoaderUi;

View File

@ -213,13 +213,7 @@ export const fetchDocuments = createAsyncThunk<
`/api/accounts/${accountId}/${toggle}/${transactionId}/documents?include_images=true` `/api/accounts/${accountId}/${toggle}/${transactionId}/documents?include_images=true`
); );
const data = response.data as Document[]; return response.data as Document[];
if (data.length === 0 && window.location.pathname.includes('/process-step-two')) {
window.location.href = window.location.href.replace("process-step-two", "process-step-one") // ✅ navigate using window
}
return data;
} catch (error: any) { } catch (error: any) {
return rejectWithValue(error.response?.data || 'Failed to fetch documents'); return rejectWithValue(error.response?.data || 'Failed to fetch documents');
} }

View File

@ -463,13 +463,7 @@ export const fetchRecipients = createAsyncThunk<
const basePath = `/api/accounts/${accountId}/${toggle}/${transactionId}/recipients`; const basePath = `/api/accounts/${accountId}/${toggle}/${transactionId}/recipients`;
const response = await api.get(basePath); const response = await api.get(basePath);
const data = response.data as Recipient[]; return response.data as Recipient[];
if (data.length === 0 && window.location.pathname.includes('/process-step-two')) {
window.location.href = window.location.href.replace("process-step-two", "process-step-one") // ✅ navigate using window
}
return data as Recipient[];
} catch (err: any) { } catch (err: any) {
return rejectWithValue(err.response?.data || "Failed to fetch recipients"); return rejectWithValue(err.response?.data || "Failed to fetch recipients");
} }
@ -481,20 +475,17 @@ const selectedRecipientsSlice = createSlice({
initialState, initialState,
reducers: { reducers: {
addRecipient: (state, action: PayloadAction<Recipient>) => { addRecipient: (state, action: PayloadAction<Recipient>) => {
for (const recipient of state.recipients) {
// Checking if meail already exist - START if (recipient.email === action.payload.email) {
// for (const recipient of state.recipients) { toast.dismiss();
// if (recipient.email === action.payload.email) { toast.error("Email already exists !", {
// toast.dismiss(); position: "bottom-right",
// toast.error("Email already exists !", { autoClose: 5000,
// position: "bottom-right", theme: "light",
// autoClose: 5000, });
// theme: "light", return state;
// }); }
// return state; }
// }
// }
// Checking if meail already exist - END
if (action.payload.type === recipientType.INPERSONSIGNER) { if (action.payload.type === recipientType.INPERSONSIGNER) {
state.signingOrder = true; state.signingOrder = true;

View File

@ -200,12 +200,6 @@ const JoyrideController = () => {
} }
}; };
if(window.location.href.includes("template")) {
return (<></>)
} else {
return (<></>)
}
return ( return (
<Joyride <Joyride
steps={steps} steps={steps}