Compare commits
10 Commits
1a4ea46c5b
...
e7a7bfbed1
| Author | SHA1 | Date |
|---|---|---|
|
|
e7a7bfbed1 | |
|
|
09e7f85e29 | |
|
|
78efdc0829 | |
|
|
19abe70a64 | |
|
|
0cf16a05f0 | |
|
|
dd414f1de5 | |
|
|
21290040cb | |
|
|
7cbd74fc14 | |
|
|
ae53054426 | |
|
|
bdad718c92 |
|
|
@ -19,6 +19,8 @@ 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;
|
||||||
|
|
@ -121,6 +123,8 @@ 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')
|
||||||
|
|
@ -170,7 +174,7 @@ const Account = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/api/dashboard', { params: payload });
|
const res = await api.get(`/api/accounts/${userData.account.id}/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);
|
||||||
|
|
@ -245,11 +249,12 @@ 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} />}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,24 @@ 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 {
|
||||||
|
|
@ -47,6 +48,9 @@ const TransactionDataTable = () => {
|
||||||
setCurrentEmailId(userDetails?.email)
|
setCurrentEmailId(userDetails?.email)
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{transactions.length > 0 && (
|
{transactions.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,32 @@
|
||||||
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 from './ContactsFormModal';
|
import ContactFormModal, { ContactData } 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([]);
|
const [contacts, setContacts] = useState<ContactSchema>([]);
|
||||||
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 || []);
|
||||||
|
|
@ -33,17 +45,27 @@ 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/{accountId}/contacts/{contactId}`)
|
let res = await api.delete(`/api/accounts/${activeAccountId}/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)
|
||||||
}
|
}
|
||||||
|
|
@ -61,10 +83,15 @@ const Contacts = () => {
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContactSubmit = async (data: ContactData) => {
|
const handleContactSubmit = async (data: ContactData, type: "") => {
|
||||||
try {
|
try {
|
||||||
|
let res;
|
||||||
setCreateAndInviteLoader(true);
|
setCreateAndInviteLoader(true);
|
||||||
const res = await api.post(`/api/accounts/${activeAccountId}/contacts`, [data]);
|
if (type === 'Edit') {
|
||||||
|
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",
|
||||||
|
|
@ -72,16 +99,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);
|
||||||
|
|
@ -90,82 +117,84 @@ const Contacts = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex justify-end my-2 cursor-pointer'>
|
<div className='px-1.5'>
|
||||||
<div
|
<div className='flex justify-end my-2 cursor-pointer'>
|
||||||
onClick={() => setShowModal((prev) => !prev)}
|
<div
|
||||||
className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm hover:opacity-90 ">
|
onClick={() => setShowModal((prev) => !prev)}
|
||||||
<IoMdAdd className="text-white" />
|
className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm hover:opacity-90 ">
|
||||||
<button
|
<IoMdAdd className="text-white" />
|
||||||
className="text-white rounded text-sm hover:opacity-90 cursor-pointer">
|
<button
|
||||||
Add Contact
|
className="text-white rounded text-sm hover:opacity-90 cursor-pointer">
|
||||||
</button>
|
Add Contact
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar">
|
||||||
<div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar">
|
<table className="min-w-full text-sm bg-white">
|
||||||
<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">
|
||||||
<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>
|
||||||
<td colSpan="4" className="text-center py-4">Loading...</td>
|
<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>
|
</tr>
|
||||||
) : contacts.length === 0 ? (
|
</thead>
|
||||||
<tr>
|
<tbody className="divide-y divide-gray-200">
|
||||||
<td colSpan="4" className="text-center py-4">No contacts found.</td>
|
{loading ? (
|
||||||
</tr>
|
<tr>
|
||||||
) : (
|
<td colSpan="4" className="relative h-100"><LoaderUi/></td>
|
||||||
contacts.map((contact) => (
|
|
||||||
<tr key={contact.id} className="hover:bg-gray-50">
|
|
||||||
<td className="px-4 py-3">{contact.firstName}</td>
|
|
||||||
<td className="px-4 py-3">{contact.lastName}</td>
|
|
||||||
<td className="px-4 py-3">{contact.email}</td>
|
|
||||||
<td className="px-4 py-3 text-center">
|
|
||||||
<div
|
|
||||||
className="relative inline-block"
|
|
||||||
tabIndex={0}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="text-gray-600 hover:text-black p-1 focus:outline-none"
|
|
||||||
onClick={() => toggleMenu(contact.id)}
|
|
||||||
>
|
|
||||||
<FaEllipsisV />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{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]">
|
|
||||||
<button
|
|
||||||
onClick={() => handleEdit(contact.id)}
|
|
||||||
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleDelete(contact.id)}
|
|
||||||
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))
|
) : contacts.length === 0 ? (
|
||||||
)}
|
<tr>
|
||||||
</tbody>
|
<td colSpan="4" className="text-center py-4">No contacts found.</td>
|
||||||
</table>
|
</tr>
|
||||||
{showModal &&
|
) : (
|
||||||
<ContactFormModal onSubmit={handleContactSubmit}
|
contacts.map((contact) => (
|
||||||
loading={createAndInviteLoader} setShowModal={setShowModal}/>
|
<tr key={contact.id} className="hover:bg-gray-50">
|
||||||
}
|
<td className="px-4 py-3">{contact.firstName}</td>
|
||||||
|
<td className="px-4 py-3">{contact.lastName}</td>
|
||||||
|
<td className="px-4 py-3">{contact.email}</td>
|
||||||
|
<td className="px-4 py-3 text-center">
|
||||||
|
<div
|
||||||
|
className="relative inline-block"
|
||||||
|
tabIndex={0}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="text-gray-600 hover:text-black p-1 focus:outline-none"
|
||||||
|
onClick={() => toggleMenu(contact.id)}
|
||||||
|
>
|
||||||
|
<FaEllipsisV />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{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]">
|
||||||
|
<button
|
||||||
|
onMouseDown={() => handleEdit(contact.id)}
|
||||||
|
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onMouseDown={() => handleDelete(contact.id)}
|
||||||
|
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{showModal &&
|
||||||
|
<ContactFormModal onSubmit={handleContactSubmit}
|
||||||
|
loading={createAndInviteLoader} setShowModal={setShowModal} actionStatus={actionStatus} editJsonData={editJsonData} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// ContactForm.tsx
|
// ContactForm.tsx
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { FaSpinner } from 'react-icons/fa';
|
import { FaSpinner } from 'react-icons/fa';
|
||||||
|
|
||||||
export interface ContactData {
|
export interface ContactData {
|
||||||
|
|
@ -17,9 +17,11 @@ 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 }: Props) => {
|
const ContactFormModal = ({ onSubmit, loading, setShowModal, actionStatus, editJsonData }: Props) => {
|
||||||
const [formData, setFormData] = useState<ContactData>({
|
const [formData, setFormData] = useState<ContactData>({
|
||||||
email: '',
|
email: '',
|
||||||
firstName: '',
|
firstName: '',
|
||||||
|
|
@ -32,6 +34,21 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal }: Props) => {
|
||||||
|
|
||||||
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]) {
|
||||||
|
|
@ -115,13 +132,22 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal }: Props) => {
|
||||||
|
|
||||||
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 {
|
try {
|
||||||
await onSubmit(formData);
|
if (actionStatus === 'Edit') {
|
||||||
|
type = 'Edit'
|
||||||
|
payload = {
|
||||||
|
...formData,
|
||||||
|
contactId: editJsonData.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await onSubmit(payload, type);
|
||||||
|
|
||||||
|
|
||||||
setFormData({
|
setFormData({
|
||||||
email: '',
|
email: '',
|
||||||
|
|
@ -158,7 +184,7 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal }: Props) => {
|
||||||
<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"
|
||||||
|
|
@ -172,7 +198,7 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal }: Props) => {
|
||||||
<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
|
||||||
|
|
@ -229,8 +255,9 @@ const ContactFormModal = ({ onSubmit, loading, setShowModal }: Props) => {
|
||||||
SAVING...
|
SAVING...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'SAVE CONTACT'
|
`${actionStatus?.toUpperCase() || 'SAVE'} CONTACT`
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
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';
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ 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;
|
||||||
|
|
@ -82,6 +84,8 @@ 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);
|
||||||
|
|
||||||
|
|
@ -94,7 +98,7 @@ const Home = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/api/dashboard', { params: payload });
|
const res = await api.get(`/api/accounts/${userData.account.id}/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);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ 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 { IoMdHome } from "react-icons/io";
|
import { IoMdClose, IoMdHome } from "react-icons/io";
|
||||||
|
import { Bounce, toast } from "react-toastify";
|
||||||
|
|
||||||
type Signer = {
|
type Signer = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -48,7 +49,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.split(" ").map(n => n[0].toUpperCase()).join("")}
|
{signer.name.length > 2 ? signer.name.split(" ").map(n => n[0].toUpperCase()).join("") : signer.email.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>
|
||||||
|
|
@ -222,6 +223,19 @@ 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;
|
||||||
|
|
@ -233,76 +247,63 @@ const DocumentCompletionScreen = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
confetti({
|
confetti({
|
||||||
particleCount: 80, // more particles
|
particleCount: 5, // fewer particles per burst for smoothness
|
||||||
angle: Math.random() * 360, // fire in random direction
|
angle: 90, // falling straight down
|
||||||
spread: 220, // wider spread
|
spread: 30, // slight spread
|
||||||
startVelocity: 15, // more power
|
startVelocity: 8, // gentle falling speed
|
||||||
scalar: 1.2, // bigger pieces (default is 1)
|
|
||||||
origin: {
|
origin: {
|
||||||
x: Math.random(),
|
x: Math.random(), // random horizontal position across the top
|
||||||
y: Math.random() * 0.6,
|
y: 0,
|
||||||
},
|
},
|
||||||
|
scalar: 1,
|
||||||
zIndex: 9999,
|
zIndex: 9999,
|
||||||
});
|
});
|
||||||
}, 350); // fire every 150ms
|
}, 50); // fire every 50ms for continuous effect
|
||||||
|
|
||||||
|
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">
|
<div className="p-3 mt-[55px]">
|
||||||
<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">
|
||||||
|
|
@ -342,7 +343,7 @@ const DocumentCompletionScreen = () => {
|
||||||
<SigningOrderDiagram signers={selectedRecipients} />
|
<SigningOrderDiagram signers={selectedRecipients} />
|
||||||
|
|
||||||
{/* Signers List */}
|
{/* Signers List */}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
const handleNextClick = async (isSendNow: boolean, saveAndClose: boolean = false) => {
|
||||||
let formError = {
|
let formError = {
|
||||||
documents: false,
|
documents: false,
|
||||||
recipients: false,
|
recipients: false,
|
||||||
|
|
@ -168,6 +168,7 @@ 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,
|
||||||
|
|
@ -225,18 +226,15 @@ const ProcessStepOne = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId }));
|
await 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(() => {
|
||||||
|
|
@ -250,7 +248,11 @@ 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}`)
|
||||||
navigate(`/process-step-two/${accountId}/template/${transactionId}`)
|
if(!saveAndClose) {
|
||||||
|
navigate(`/process-step-two/${accountId}/template/${transactionId}`)
|
||||||
|
} else {
|
||||||
|
navigate(`/templates`)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (signType === SignTypeEnum.selfSign) {
|
if (signType === SignTypeEnum.selfSign) {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -319,12 +321,22 @@ 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"
|
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"}
|
{signType === SignTypeEnum.selfSign ? "Sign" : "Next"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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, updateRecipient } from "../../../store/slices/selectedRecipientsSlice";
|
import { addRecipient, Recipient, recipientType, TRANSACTION_TEMPLATE_TOGGLE, 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: false, allowSenderToDelete: false });
|
const [recipient, setRecipients] = useState<Recipient>({ uuid: null, newOne: false, firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: true, allowSenderToDelete: true });
|
||||||
|
|
||||||
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 ?? false,
|
allowSenderToEdit: editRecipient.allowSenderToEdit ?? true,
|
||||||
allowSenderToDelete: editRecipient.allowSenderToDelete ?? false,
|
allowSenderToDelete: editRecipient.allowSenderToDelete ?? true,
|
||||||
uuid: String(editRecipient?.id ?? editRecipient?.uuid ?? ''),
|
uuid: String(editRecipient?.id ?? editRecipient?.uuid ?? ''),
|
||||||
newOne: editRecipient.newOne ?? false
|
newOne: editRecipient.newOne ?? false
|
||||||
});
|
});
|
||||||
|
|
@ -86,15 +86,19 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
||||||
|
|
||||||
console.log("recipientrecipient", recipient)
|
console.log("recipientrecipient", recipient)
|
||||||
|
|
||||||
if (!recipient.lastName || !recipient.email || !recipient.type) return;
|
if(transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
|
||||||
|
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 {
|
||||||
|
|
@ -113,8 +117,8 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
||||||
hostEmail: "",
|
hostEmail: "",
|
||||||
roleName: "",
|
roleName: "",
|
||||||
type: recipientType.SIGNER,
|
type: recipientType.SIGNER,
|
||||||
allowSenderToEdit: false,
|
allowSenderToEdit: true,
|
||||||
allowSenderToDelete: false,
|
allowSenderToDelete: true,
|
||||||
uuid: "",
|
uuid: "",
|
||||||
newOne: false
|
newOne: false
|
||||||
});
|
});
|
||||||
|
|
@ -275,13 +279,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 === 'templates' && (
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.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
|
required={recipient.firstName === "" && recipient.lastName === "" && recipient.email === ""}
|
||||||
type="text"
|
type="text"
|
||||||
onFocus={() => setOpenContactModal(true)}
|
onFocus={() => setOpenContactModal(true)}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
|
|
@ -413,7 +417,7 @@ useEffect(() => {
|
||||||
onChange={(e) => setRecipients({ ...recipient, firstName: e.target.value })}
|
onChange={(e) => setRecipients({ ...recipient, firstName: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
required
|
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
||||||
type="text"
|
type="text"
|
||||||
onFocus={() => handleFocus("lastName")}
|
onFocus={() => handleFocus("lastName")}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
|
|
@ -423,7 +427,7 @@ useEffect(() => {
|
||||||
onChange={(e) => setRecipients({ ...recipient, lastName: e.target.value })}
|
onChange={(e) => setRecipients({ ...recipient, lastName: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
required
|
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
||||||
type="email"
|
type="email"
|
||||||
onFocus={() => handleFocus("email")}
|
onFocus={() => handleFocus("email")}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
|
|
@ -437,7 +441,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
|
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
||||||
type="text"
|
type="text"
|
||||||
onFocus={() => setOpenContactModal(true)}
|
onFocus={() => setOpenContactModal(true)}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
|
|
@ -447,7 +451,7 @@ useEffect(() => {
|
||||||
onChange={(e) => setRecipients({ ...recipient, hostName: e.target.value })}
|
onChange={(e) => setRecipients({ ...recipient, hostName: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
required
|
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
||||||
type="email"
|
type="email"
|
||||||
onFocus={() => setOpenContactModal(true)}
|
onFocus={() => setOpenContactModal(true)}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ 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[]
|
||||||
|
|
@ -66,12 +67,7 @@ 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(() => {
|
||||||
|
|
@ -79,9 +75,12 @@ 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) =>
|
||||||
|
|
@ -181,17 +180,22 @@ 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;
|
|
||||||
|
|
||||||
dispatch(showLoader("Loading ..."))
|
const handleTransactionProcess = async (save: boolean = false) => {
|
||||||
|
console.log(droppedItems, "called Line 189");
|
||||||
|
|
||||||
|
if (!save && isSend === null) return;
|
||||||
|
|
||||||
|
!save && dispatch(showLoader("Loading ..."))
|
||||||
|
|
||||||
let updatedTags = { ...droppedItems }
|
let updatedTags = { ...droppedItems }
|
||||||
|
|
||||||
|
|
@ -296,17 +300,37 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
|
|
||||||
tagsData = groupByRecipientId(tagsData);
|
tagsData = groupByRecipientId(tagsData);
|
||||||
|
|
||||||
sendTagsToRecipients(tagsData, accountId, transactionId);
|
console.log(tagsData, "Line 302");
|
||||||
|
|
||||||
(async () => {
|
await sendTagsToRecipients(tagsData, accountId, transactionId);
|
||||||
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")
|
// SENDING Transaction - START
|
||||||
dispatch(hideLoader())
|
// if (!save) {
|
||||||
})()
|
// 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
|
||||||
};
|
};
|
||||||
|
|
@ -1031,9 +1055,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)}
|
||||||
|
|
@ -1095,20 +1119,22 @@ 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="right"></div>
|
<div className="group-border" border-group={item.group} border-pos="top"></div>
|
||||||
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom">
|
<div className="group-border" border-group={item.group} border-pos="right"></div>
|
||||||
<div onClick={() => handleAddGroupItemClicked(key, item)}><IoAdd /></div>
|
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom">
|
||||||
</div>
|
<div onClick={() => handleAddGroupItemClicked(key, item)}><IoAdd /></div>
|
||||||
<div className="group-border" border-group={item.group} border-pos="left"></div>
|
</div>
|
||||||
</>
|
<div className="group-border" border-group={item.group} border-pos="left"></div>
|
||||||
|
</>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
</>
|
||||||
|
|
||||||
</>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1151,7 +1177,11 @@ 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,
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@ 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 { RiResetLeftLine, RiResetRightLine } from 'react-icons/ri'
|
import { 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;
|
||||||
|
|
@ -65,27 +66,83 @@ 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(() => {
|
||||||
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
|
if (selectedRecipients.length || selectedDocuments.length) return;
|
||||||
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
|
(async () => {
|
||||||
|
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() {
|
function handleSend(isSignSend: boolean = false) {
|
||||||
// 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) => {
|
||||||
|
|
@ -107,24 +164,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;
|
||||||
|
|
@ -265,9 +322,20 @@ 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'>Send</p>
|
<p className='tracking-wide text-white'>
|
||||||
|
{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>}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,16 @@ 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();
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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`)
|
let res = await api.get(`/api/accounts/${activeAccountId}/templates?sortBy=lastModifiedAt desc`)
|
||||||
let templateData = res?.data
|
let templateData = res?.data
|
||||||
setTemplates(templateData)
|
setTemplates(templateData)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -84,7 +84,10 @@ 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">
|
||||||
|
|
@ -136,7 +139,15 @@ const Templates = () => {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
{templates.length > 0 ? (
|
{loading ? (
|
||||||
|
<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 (
|
||||||
|
|
@ -146,7 +157,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 || 'Dummy'}</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">{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>
|
||||||
|
|
@ -172,10 +183,7 @@ 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={() => {
|
onClick={() => handleEditClick(item.id)}
|
||||||
setShowMenu(null);
|
|
||||||
navigate(`/process-step-two/${accountId}/template/${item.id}`);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -200,18 +208,12 @@ const Templates = () => {
|
||||||
) : (
|
) : (
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="5">
|
<td colSpan="6" className="text-center py-10 text-gray-500">
|
||||||
<div className="relative flex justify-center items-center h-100 w-full">
|
No templates found.
|
||||||
{/* 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>
|
||||||
|
|
|
||||||
|
|
@ -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.email === user.email && recipient.status !== "COMPLETED" &&
|
||||||
<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'>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
|
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={classStyle}>
|
<div className={`absolute inset-0 flex items-center justify-center z-50 ${classStyle}`}>
|
||||||
<svg viewBox="0 0 240 240" height="240" width="240" className="pl">
|
{/* Overlay */}
|
||||||
<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>
|
<div className="absolute inset-0 bg-black/30 opacity-60"></div>
|
||||||
<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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoaderUi
|
{/* Loader box */}
|
||||||
|
<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;
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,13 @@ export const fetchDocuments = createAsyncThunk<
|
||||||
`/api/accounts/${accountId}/${toggle}/${transactionId}/documents?include_images=true`
|
`/api/accounts/${accountId}/${toggle}/${transactionId}/documents?include_images=true`
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data as Document[];
|
const data = 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');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -463,7 +463,13 @@ 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);
|
||||||
|
|
||||||
return response.data as Recipient[];
|
const data = 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");
|
||||||
}
|
}
|
||||||
|
|
@ -475,17 +481,20 @@ const selectedRecipientsSlice = createSlice({
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
addRecipient: (state, action: PayloadAction<Recipient>) => {
|
addRecipient: (state, action: PayloadAction<Recipient>) => {
|
||||||
for (const recipient of state.recipients) {
|
|
||||||
if (recipient.email === action.payload.email) {
|
// Checking if meail already exist - START
|
||||||
toast.dismiss();
|
// for (const recipient of state.recipients) {
|
||||||
toast.error("Email already exists !", {
|
// if (recipient.email === action.payload.email) {
|
||||||
position: "bottom-right",
|
// toast.dismiss();
|
||||||
autoClose: 5000,
|
// toast.error("Email already exists !", {
|
||||||
theme: "light",
|
// position: "bottom-right",
|
||||||
});
|
// autoClose: 5000,
|
||||||
return state;
|
// theme: "light",
|
||||||
}
|
// });
|
||||||
}
|
// 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;
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,12 @@ const JoyrideController = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(window.location.href.includes("template")) {
|
||||||
|
return (<></>)
|
||||||
|
} else {
|
||||||
|
return (<></>)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Joyride
|
<Joyride
|
||||||
steps={steps}
|
steps={steps}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue