Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
09e7f85e29
|
|
@ -245,6 +245,7 @@ const Account = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
|
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
|
||||||
isLoading={isLoading} />
|
isLoading={isLoading} />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
|
||||||
let res = await api.delete(`/api/accounts/{accountId}/contacts/{contactId}`)
|
|
||||||
if (res?.data) {
|
|
||||||
|
|
||||||
}
|
try {
|
||||||
|
let res = await api.delete(`/api/accounts/${activeAccountId}/contacts/${contactId}`)
|
||||||
|
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';
|
||||||
|
|
|
||||||
|
|
@ -139,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 (
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue