Worked on Responsiveness + Tag Properties + Hot Keys etc
This commit is contained in:
parent
2290eca277
commit
0a2e823db0
|
|
@ -451,7 +451,7 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
||||||
{/* <FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} /> */}
|
{/* <FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} /> */}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-b border-gray-100">
|
{/* <div className="border-b border-gray-100">
|
||||||
<button
|
<button
|
||||||
onClick={handleAccountsClick}
|
onClick={handleAccountsClick}
|
||||||
className="w-full flex items-center justify-between px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer"
|
className="w-full flex items-center justify-between px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer"
|
||||||
|
|
@ -506,7 +506,7 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<div className="border-t border-gray-100">
|
<div className="border-t border-gray-100">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
||||||
<span className="text-md font-semibold text-[var(--mainPrimaryBtn)]">
|
<span className="text-md font-semibold text-[var(--mainPrimaryBtn)]">
|
||||||
Sign made easy
|
Sign made easy
|
||||||
</span> */}
|
</span> */}
|
||||||
<img class="py-2 w-[140px]" alt="" src="/sme-colored.png" />
|
<img className="py-2 w-[140px]" alt="" src="/sme-colored.png" />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
@ -118,49 +118,77 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
||||||
<h2 className="text-xl font-semibold text-gray-800 mb-4 2xl:text-2xl">
|
<h2 className="text-xl font-semibold text-gray-800 mb-4 2xl:text-2xl">
|
||||||
Boost Efficiency with Our Apps
|
Boost Efficiency with Our Apps
|
||||||
</h2>
|
</h2>
|
||||||
<div className="w-full flex flex-col sm:flex-row gap-2 justify-center items-center 2xl:gap-4 2xl:mt-6">
|
|
||||||
|
|
||||||
{/* Forms Made Easy */}
|
<div className="w-full flex flex-col sm:flex-row gap-4 justify-center items-stretch 2xl:gap-6 2xl:mt-6">
|
||||||
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
|
|
||||||
<div className="">
|
|
||||||
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
|
|
||||||
Generate Forms with Ease
|
|
||||||
</p>
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="w-full flex flex-col justify-start items-start sm:flex-row sm:justify-between sm:items-center">
|
|
||||||
<img src="/forms-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
|
|
||||||
<button
|
|
||||||
onClick={() => window.open("https://formsmadeasy.com/", "_blank")}
|
|
||||||
className="cursor-pointer text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
|
|
||||||
Try Now
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
{/* Forms Made Easy */}
|
||||||
|
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
|
||||||
|
<p className="text-sm font-semibold text-left mb-4 2xl:text-lg min-w-min">
|
||||||
|
Generate Forms with Ease
|
||||||
|
</p>
|
||||||
|
|
||||||
{/* Docs Made Easy */}
|
<div className="w-full flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
|
||||||
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
|
<img
|
||||||
<div className="">
|
src="/forms-made-easy.png"
|
||||||
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
|
className="w-[140px] sm:w-[40%] 2xl:w-[40%] object-contain"
|
||||||
Generate Documents (Invoices, Reports, Quotes)
|
alt="Forms Made Easy"
|
||||||
</p>
|
/>
|
||||||
<div className="w-full">
|
|
||||||
<div className="w-full flex flex-col justify-start items-start sm:flex-row sm:justify-between sm:items-center">
|
|
||||||
<img src="/docs-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
|
|
||||||
<button
|
|
||||||
onClick={() => window.open("https://docsmadeasy.com/", "_blank")}
|
|
||||||
className="cursor-pointer text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
|
|
||||||
Try Now
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<button
|
||||||
|
onClick={() => window.open("https://formsmadeasy.com/", "_blank")}
|
||||||
|
className="
|
||||||
|
cursor-pointer
|
||||||
|
text-xs sm:text-sm md:text-base
|
||||||
|
bg-blue-600
|
||||||
|
text-white
|
||||||
|
px-5 py-2
|
||||||
|
rounded-md
|
||||||
|
hover:bg-blue-700
|
||||||
|
transition
|
||||||
|
shrink-0
|
||||||
|
w-full sm:w-auto
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Try Now
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Docs Made Easy */}
|
||||||
|
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
|
||||||
|
<p className="text-sm font-semibold text-left mb-4 2xl:text-lg">
|
||||||
|
Generate Documents (Invoices, Reports, Quotes)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="w-full flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
|
||||||
|
<img
|
||||||
|
src="/docs-made-easy.png"
|
||||||
|
className="w-[140px] sm:w-[40%] 2xl:w-[40%] object-contain"
|
||||||
|
alt="Docs Made Easy"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => window.open("https://docsmadeasy.com/", "_blank")}
|
||||||
|
className="
|
||||||
|
cursor-pointer
|
||||||
|
text-xs sm:text-sm md:text-base
|
||||||
|
bg-blue-600
|
||||||
|
text-white
|
||||||
|
px-5 py-2
|
||||||
|
rounded-md
|
||||||
|
hover:bg-blue-700
|
||||||
|
transition
|
||||||
|
shrink-0
|
||||||
|
w-full sm:w-auto
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Try Now
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
// ConfirmSignModal.tsx
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { IoClose } from "react-icons/io5";
|
||||||
|
import { FaSignature } from "react-icons/fa";
|
||||||
|
import { FiShield } from "react-icons/fi";
|
||||||
|
|
||||||
|
interface ConfirmSignModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
loading?: boolean;
|
||||||
|
recipientName?: string;
|
||||||
|
documentCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmSignModal = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
loading = false,
|
||||||
|
recipientName = "",
|
||||||
|
documentCount = 1,
|
||||||
|
}: ConfirmSignModalProps) => {
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEsc = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") onClose();
|
||||||
|
};
|
||||||
|
if (open) document.addEventListener("keydown", handleEsc);
|
||||||
|
return () => document.removeEventListener("keydown", handleEsc);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.style.overflow = open ? "hidden" : "auto";
|
||||||
|
return () => { document.body.style.overflow = "auto"; };
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={onClose}
|
||||||
|
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm p-3 sm:p-4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="w-full max-w-md flex flex-col rounded-sm bg-white shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200"
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-start justify-between border-b border-slate-200 p-5 sm:p-6">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex items-center justify-center w-10 h-10 rounded-sm bg-[var(--primary)] shrink-0">
|
||||||
|
<FaSignature className="text-white text-lg" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-slate-800">
|
||||||
|
Confirm Signature
|
||||||
|
</h2>
|
||||||
|
<p className="mt-0.5 text-sm text-slate-500">
|
||||||
|
You're about to sign this document
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="shrink-0 p-2 rounded-sm hover:bg-slate-100 transition cursor-pointer"
|
||||||
|
>
|
||||||
|
<IoClose className="text-xl text-slate-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="p-5 sm:p-6 space-y-4">
|
||||||
|
|
||||||
|
{/* Summary */}
|
||||||
|
<div className="rounded-sm border border-slate-200 bg-slate-50 px-4 py-4 space-y-2">
|
||||||
|
{recipientName && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-slate-500">Signing as</span>
|
||||||
|
<span className="font-medium text-slate-800">{recipientName}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-slate-500">Documents</span>
|
||||||
|
<span className="font-medium text-slate-800">{documentCount} document{documentCount > 1 ? "s" : ""}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-slate-500">Status</span>
|
||||||
|
<span className="font-medium text-green-600">Ready to sign</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legal notice */}
|
||||||
|
<div className="flex gap-3 rounded-sm border border-indigo-100 bg-indigo-50 px-4 py-3">
|
||||||
|
<FiShield className="text-indigo-600 shrink-0 mt-0.5" />
|
||||||
|
<p className="text-sm leading-relaxed text-indigo-900">
|
||||||
|
By confirming, you agree that your electronic signature is legally binding and equivalent to a handwritten signature.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="flex flex-col-reverse sm:flex-row items-stretch sm:items-center justify-end gap-3 border-t border-slate-200 bg-white p-4 sm:p-5">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="w-full sm:w-auto text-sm px-4 py-2.5 rounded-sm border border-slate-300 text-slate-700 hover:bg-slate-100 transition cursor-pointer font-medium"
|
||||||
|
>
|
||||||
|
Review
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onConfirm}
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full sm:w-auto text-sm px-5 py-2.5 rounded-sm bg-[var(--primary)] text-white hover:opacity-100 transition opacity-80 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer font-medium flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
<FaSignature />
|
||||||
|
{loading ? "Signing..." : "Confirm & Sign"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmSignModal;
|
||||||
|
|
@ -65,7 +65,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full px-2 gap-2 pb-[6rem] bg-slate-100">
|
<div className="flex flex-col w-full px-2 gap-2 pb-[6rem] bg-slate-100 border-r-1 border-slate-200 h-full">
|
||||||
{selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((document, indx) => {
|
{selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((document, indx) => {
|
||||||
const isOpen = dropDownTogglePage === 'pageDropDown' + indx;
|
const isOpen = dropDownTogglePage === 'pageDropDown' + indx;
|
||||||
|
|
||||||
|
|
@ -92,59 +92,59 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Page Thumbnails - DocuSign style */}
|
{/* Page Thumbnails - DocuSign style */}
|
||||||
{isOpen && (
|
|
||||||
<div className="flex flex-col px-2">
|
|
||||||
{document.pages?.map((page, pageIndex) => {
|
|
||||||
const key = `${document.documentId}_${pageIndex}`;
|
|
||||||
const isActive = activePage === key;
|
|
||||||
|
|
||||||
return (
|
<div className={`flex flex-col px-2 ${isOpen ? 'block' : 'hidden'}`}>
|
||||||
<div
|
{document.pages?.map((page, pageIndex) => {
|
||||||
key={pageIndex}
|
const key = `${document.documentId}_${pageIndex}`;
|
||||||
onClick={() => goToPage(document.documentId, pageIndex)}
|
const isActive = activePage === key;
|
||||||
className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
|
|
||||||
>
|
return (
|
||||||
{/* Big Thumbnail */}
|
<div
|
||||||
<div className={`relative sm:p-2 lg:p-4 bg-slate-100 rounded border flex items-center justify-center overflow-hidden
|
key={pageIndex}
|
||||||
|
onClick={() => goToPage(document.documentId, pageIndex)}
|
||||||
|
className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
|
||||||
|
>
|
||||||
|
{/* Big Thumbnail */}
|
||||||
|
<div className={`relative sm:p-2 lg:p-4 bg-slate-100 rounded border flex items-center justify-center overflow-hidden
|
||||||
${isActive ? 'border-blue-500 border-2' : 'border-slate-200'}`}
|
${isActive ? 'border-blue-500 border-2' : 'border-slate-200'}`}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={page.src}
|
src={page.src}
|
||||||
alt={`Page ${pageIndex + 1}`}
|
alt={`Page ${pageIndex + 1}`}
|
||||||
className="w-[140px] h-[180px] h-full object-cover"
|
className="w-[140px] h-[180px] h-full object-cover"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Page number + dots */}
|
{/* Page number + dots */}
|
||||||
<div className="absolute bottom-0 bg-slate-800 flex items-center justify-between w-full pl-4 pr-2 py-3 bg-white">
|
<div className="absolute bottom-0 bg-slate-800 flex items-center justify-between w-full pl-4 pr-2 py-3 bg-white">
|
||||||
<span className="text-[13px] text-slate-500">{pageIndex + 1}</span>
|
<span className="text-[13px] text-slate-500">{pageIndex + 1}</span>
|
||||||
{
|
{
|
||||||
// <>
|
// <>
|
||||||
// <button
|
// <button
|
||||||
// onClick={(e) => { e.stopPropagation(); /* open context menu */ }}
|
// onClick={(e) => { e.stopPropagation(); /* open context menu */ }}
|
||||||
// className="text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded p-0.5"
|
// className="text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded p-0.5"
|
||||||
// >
|
// >
|
||||||
// <MdMoreVert size={18} />
|
// <MdMoreVert size={18} />
|
||||||
// </button>
|
// </button>
|
||||||
// </>
|
// </>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{documentPagesTagsMapping[document.documentId]?.[pageIndex] && (
|
{documentPagesTagsMapping[document.documentId]?.[pageIndex] && (
|
||||||
<div className='absolute top-4 right-2 rotate-180'>
|
<div className='absolute top-4 right-2 rotate-180'>
|
||||||
{/* <span className='absolute text-white text-[10px] font-medium text-slate-500 top-[50%] left-[50%] transform -translate-x-1/2 -translate-y-1/2 pointer-events-none select-none rotate-180'>
|
{/* <span className='absolute text-white text-[10px] font-medium text-slate-500 top-[50%] left-[50%] transform -translate-x-1/2 -translate-y-1/2 pointer-events-none select-none rotate-180'>
|
||||||
{documentPagesTagsMapping[document.documentId][pageIndex][0]}
|
{documentPagesTagsMapping[document.documentId][pageIndex][0]}
|
||||||
</span> */}
|
</span> */}
|
||||||
<PiTagSimpleFill style={{ fontSize: '1.8rem', color: documentPagesTagsMapping[document.documentId][pageIndex][1] }} />
|
<PiTagSimpleFill style={{ fontSize: '1.8rem', color: documentPagesTagsMapping[document.documentId][pageIndex][1] }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
})}
|
)
|
||||||
</div>
|
})}
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -58,16 +58,15 @@ export default function SignButton({
|
||||||
{/* Sign Button */}
|
{/* Sign Button */}
|
||||||
{/* <button
|
{/* <button
|
||||||
onClick={handleFormSubmit}
|
onClick={handleFormSubmit}
|
||||||
className="flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-md bg-[var(--primary)] hover:opacity-90 transition text-white outline-none"
|
className="flex items-center gap-2 cursor-pointer text-xs px-4 py-1.5 ml-3 rounded-sm border border-slate-300 hover:border-slate-100 hover:opacity-90 transition text-white font-normal outline-none"
|
||||||
>
|
>
|
||||||
<span id="sign-client-side-btn" className="tracking-wide">
|
<span id="sign-client-side-btn" className="tracking-wide">
|
||||||
Sign
|
Finish
|
||||||
</span>
|
</span>
|
||||||
<FaSignature className="text-md" />
|
|
||||||
</button> */}
|
</button> */}
|
||||||
|
|
||||||
{/* Three Dots Dropdown Trigger */}
|
|
||||||
|
|
||||||
|
{/* Three Dots Dropdown Trigger */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setMenuOpen((prev) => !prev)}
|
onClick={() => setMenuOpen((prev) => !prev)}
|
||||||
className="py-2 rounded-full focus:outline-none transition ml-2"
|
className="py-2 rounded-full focus:outline-none transition ml-2"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,4 +18,21 @@
|
||||||
font-family: 'Allura';
|
font-family: 'Allura';
|
||||||
src: url('/fonts/Allura/Allura-Regular.ttf') format('truetype');
|
src: url('/fonts/Allura/Allura-Regular.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Adding SELECT SIGNATURE FONTS - ENDS */
|
/* Adding SELECT SIGNATURE FONTS - ENDS */
|
||||||
|
|
||||||
|
/* .scrollbar-hide::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
} */
|
||||||
|
|
||||||
|
body::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { handleMultipleTransactionThunk, resetDocuments, uploadDocuments } from '../../store/slices/documentSlice';
|
import { handleMultipleTransactionThunk, resetDocuments, uploadDocuments } from '../../store/slices/documentSlice';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
@ -30,6 +30,24 @@ const StartNow = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
||||||
|
const [uploadDropdownOpen, setUploadDropdownOpen] = useState(false);
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
||||||
|
setUploadDropdownOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
document.addEventListener('touchstart', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
document.removeEventListener('touchstart', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleUpload = async (filesToUpload: File[] = files) => {
|
const handleUpload = async (filesToUpload: File[] = files) => {
|
||||||
const accountId = userData.account?.id;
|
const accountId = userData.account?.id;
|
||||||
|
|
||||||
|
|
@ -176,62 +194,44 @@ const StartNow = () => {
|
||||||
</button> */}
|
</button> */}
|
||||||
|
|
||||||
|
|
||||||
<div className="relative inline-block group mt-4 mb-6">
|
{/* ❌ Remove the old "relative inline-block group" div entirely, replace with: */}
|
||||||
|
<div ref={dropdownRef} className="relative inline-block mt-4 mb-6">
|
||||||
<button
|
<button
|
||||||
className="flex text-lg font-medium items-center gap-1 text-xs text-white bg-[var(--dashBoardStartNowBtn)] pl-3 pr-2 py-2 rounded shadow-xs transition-colors duration-150 focus:outline-none focus:ring-1 focus:ring-blue-400 cursor-pointer"
|
onClick={() => setUploadDropdownOpen(prev => !prev)}
|
||||||
|
className="flex text-lg font-medium items-center gap-1 text-xs text-white bg-[var(--dashBoardStartNowBtn)] pl-3 pr-2 py-2 rounded shadow-xs focus:outline-none focus:ring-1 focus:ring-blue-400 cursor-pointer"
|
||||||
>
|
>
|
||||||
<p>Upload File</p>
|
<p>Upload File</p>
|
||||||
<IoMdArrowDropdown className='text-lg' />
|
<IoMdArrowDropdown
|
||||||
|
className='text-lg'
|
||||||
|
style={{ transform: uploadDropdownOpen ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Dropdown */}
|
{uploadDropdownOpen && (
|
||||||
<div
|
<div className="absolute left-0 top-full mt-1 min-w-[180px] bg-white border border-slate-200 rounded-md shadow-lg overflow-hidden z-50">
|
||||||
className="
|
<button
|
||||||
absolute
|
onClick={() => {
|
||||||
left-0
|
fileInputRef.current?.click();
|
||||||
top-full
|
setUploadDropdownOpen(false);
|
||||||
mt-1
|
}}
|
||||||
min-w-[180px]
|
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 cursor-pointer flex gap-2 items-center"
|
||||||
bg-white
|
>
|
||||||
border
|
<FaDesktop className='text-[var(--primary)]' />
|
||||||
border-slate-200
|
<span>Desktop</span>
|
||||||
rounded-md
|
</button>
|
||||||
shadow-lg
|
|
||||||
overflow-hidden
|
|
||||||
z-50
|
|
||||||
|
|
||||||
opacity-0
|
<button
|
||||||
invisible
|
onClick={() => {
|
||||||
translate-y-[-5px]
|
navigate("/templates");
|
||||||
scale-[0.98]
|
setUploadDropdownOpen(false);
|
||||||
|
}}
|
||||||
transition-all
|
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 border-t border-slate-100 cursor-pointer flex gap-2 items-center"
|
||||||
duration-200
|
>
|
||||||
ease-out
|
<HiTemplate className='text-[var(--primary)]' />
|
||||||
|
<span>Use a Template</span>
|
||||||
group-hover:opacity-100
|
</button>
|
||||||
group-hover:visible
|
</div>
|
||||||
group-hover:translate-y-0
|
)}
|
||||||
group-hover:scale-100
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={() => fileInputRef.current?.click()}
|
|
||||||
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 cursor-pointer flex gap-2 items-center"
|
|
||||||
>
|
|
||||||
<FaDesktop className='text-[var(--primary)]' />
|
|
||||||
<span>Desktop</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => navigate("/templates")}
|
|
||||||
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 border-t border-slate-100 cursor-pointer flex gap-2 items-center"
|
|
||||||
>
|
|
||||||
<HiTemplate className='text-[var(--primary)]' />
|
|
||||||
<span>Use a Template</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{filesError && <span className="text-red-500 text-xs">{filesError}</span>}
|
{filesError && <span className="text-red-500 text-xs">{filesError}</span>}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default function TransactionChoiceModal({ open, onClose, onCreateTransact
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 justify-center z-50">
|
<div className="fixed inset-0 justify-center z-100">
|
||||||
<div className="w-full h-full bg-gray-200 absolute top-0 left-0 opacity-55"></div>
|
<div className="w-full h-full bg-gray-200 absolute top-0 left-0 opacity-55"></div>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow-lg w-[320px] sm:w-[620px] p-5 relative top-[40%] translate-y-[-40%] left-[50%] translate-x-[-50%]">
|
<div className="bg-white rounded-lg shadow-lg w-[320px] sm:w-[620px] p-5 relative top-[40%] translate-y-[-40%] left-[50%] translate-x-[-50%]">
|
||||||
|
|
|
||||||
|
|
@ -543,7 +543,6 @@ const ProcessStepOne = () => {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
{/* HERE WE AREEEEEEEE */}
|
|
||||||
{selectedRecipients.length > 1 && <div className="flex items-center">
|
{selectedRecipients.length > 1 && <div className="flex items-center">
|
||||||
<input
|
<input
|
||||||
// checked={signType == SignTypeEnum.selfSign}
|
// checked={signType == SignTypeEnum.selfSign}
|
||||||
|
|
@ -555,7 +554,6 @@ const ProcessStepOne = () => {
|
||||||
Set signing order
|
Set signing order
|
||||||
</label>
|
</label>
|
||||||
</div>}
|
</div>}
|
||||||
{/* HERE WE AREEEEEEEE */}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ const RecipientCards = ({
|
||||||
let { accountId, transactionId } = useParams<{ accountId: string; transactionId: string }>();
|
let { accountId, transactionId } = useParams<{ accountId: string; transactionId: string }>();
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
|
||||||
const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder)
|
const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder)
|
||||||
|
|
||||||
if (!transactionId) {
|
if (!transactionId) {
|
||||||
transactionId = templateId;
|
transactionId = templateId;
|
||||||
|
|
@ -256,43 +256,6 @@ const RecipientCards = ({
|
||||||
let newActiveRecRo = 1
|
let newActiveRecRo = 1
|
||||||
let newOverRecRo = 1
|
let newOverRecRo = 1
|
||||||
|
|
||||||
// if (activeRec.routingOrder >= overRecRo && activeIndex > overIndex) {
|
|
||||||
// newActiveRecRo = activeRecRo > 1 ? activeRecRo - 1 : 1
|
|
||||||
// newOverRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (activeRec.routingOrder >= overRecRo && activeIndex < overIndex) {
|
|
||||||
// newActiveRecRo = overRecRo > 1 ? overRecRo + 1 : 2
|
|
||||||
// newOverRecRo = overRecRo > 1 ? overRecRo - 1 : 1
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (activeRec.routingOrder <= overRecRo && activeIndex > overIndex) {
|
|
||||||
// newActiveRecRo = activeRecRo > 1 ? activeRecRo - 1 : 1
|
|
||||||
// newOverRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (activeRec.routingOrder <= overRecRo && activeIndex < overIndex) {
|
|
||||||
// newActiveRecRo = overRecRo > 1 ? overRecRo + 1 : 2
|
|
||||||
// newOverRecRo = overRecRo > 1 ? overRecRo - 1 : 1
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// if (activeRec.routingOrder > overRecRo && activeIndex < overIndex) {
|
|
||||||
|
|
||||||
// console.log(activeIndex, overIndex, "283 2")
|
|
||||||
// newActiveRecRo = overRecRo > 1 ? overRecRo : 2
|
|
||||||
// newOverRecRo = activeRecRo > 1 ? activeRecRo : 1
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (activeRec.routingOrder < overRecRo && activeIndex > overIndex) {
|
|
||||||
// console.log(activeIndex, overIndex, "283 3")
|
|
||||||
// newActiveRecRo = overRecRo > 1 ? overRecRo : 1
|
|
||||||
// newOverRecRo = activeRecRo > 1 ? activeRecRo : 2
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (activeRecRo > overRecRo && activeIndex > overIndex) {
|
if (activeRecRo > overRecRo && activeIndex > overIndex) {
|
||||||
console.log(activeIndex, overIndex, "283 1")
|
console.log(activeIndex, overIndex, "283 1")
|
||||||
newActiveRecRo = activeRecRo > 1 ? overRecRo : 1
|
newActiveRecRo = activeRecRo > 1 ? overRecRo : 1
|
||||||
|
|
@ -304,9 +267,6 @@ const RecipientCards = ({
|
||||||
newOverRecRo = overRecRo > 1 ? overRecRo : 1
|
newOverRecRo = overRecRo > 1 ? overRecRo : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (activeRecRo < overRecRo && activeIndex < overIndex) {
|
if (activeRecRo < overRecRo && activeIndex < overIndex) {
|
||||||
console.log(activeIndex, overIndex, "283 4")
|
console.log(activeIndex, overIndex, "283 4")
|
||||||
newActiveRecRo = overRecRo > 1 ? overRecRo : 2
|
newActiveRecRo = overRecRo > 1 ? overRecRo : 2
|
||||||
|
|
@ -318,8 +278,6 @@ const RecipientCards = ({
|
||||||
newOverRecRo = overRecRo > 1 ? overRecRo : 1
|
newOverRecRo = overRecRo > 1 ? overRecRo : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (activeRecRo == activeRecRo && activeIndex < overIndex) {
|
if (activeRecRo == activeRecRo && activeIndex < overIndex) {
|
||||||
newActiveRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2
|
newActiveRecRo = activeRecRo > 1 ? activeRecRo + 1 : 2
|
||||||
newOverRecRo = overRecRo > 1 ? overRecRo : 1
|
newOverRecRo = overRecRo > 1 ? overRecRo : 1
|
||||||
|
|
@ -329,51 +287,13 @@ const RecipientCards = ({
|
||||||
newOverRecRo = overRecRo > 1 ? overRecRo + 1 : 2
|
newOverRecRo = overRecRo > 1 ? overRecRo + 1 : 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// let reordered = recipients.map((r, idx) => {
|
|
||||||
// if (idx == activeIndex) {
|
|
||||||
// return (
|
|
||||||
// {
|
|
||||||
// ...r, routingOrder: newActiveRecRo
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// if (idx == overRecRo) {
|
|
||||||
// return (
|
|
||||||
// {
|
|
||||||
// ...r, routingOrder: newOverRecRo
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// dispatch(updateRecipientOrder({ id: activeRec.id || activeRec.uuid, routingOrder: newActiveRecRo }));
|
|
||||||
// dispatch(updateRecipientOrder({ id: overRec.id || overRec.uuid, routingOrder: newOverRecRo }));
|
|
||||||
|
|
||||||
dispatch(updateRecipientByIndxAndRo({ indx1: activeIndex, ro1: newActiveRecRo, indx2: overIndex, ro2: newOverRecRo, }));
|
dispatch(updateRecipientByIndxAndRo({ indx1: activeIndex, ro1: newActiveRecRo, indx2: overIndex, ro2: newOverRecRo, }));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// dispatch(reorderAllRecipientAscOrder());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full rounded-md">
|
<div className="w-full rounded-md">
|
||||||
{recipients.length > 0 ? (
|
{recipients.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{/* {recipients.length > 1 && (
|
|
||||||
<div className="flex sm:flex-row sm:items-center justify-between px-3 sm:px-4 py-2 border-b border-gray-300 gap-2">
|
|
||||||
<h3 className="text-sm font-semibold text-gray-700">
|
|
||||||
Recipients ({recipients.length})
|
|
||||||
</h3>
|
|
||||||
<label
|
|
||||||
className="flex items-center gap-2 cursor-pointer select-none justify-center sm:justify-end"
|
|
||||||
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: true })}
|
|
||||||
>
|
|
||||||
<span className="text-xs font-medium text-gray-600">Signing Order</span>
|
|
||||||
<FaUsersLine className="text-[var(--primary)] mt-0.5 text-lg" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
|
|
||||||
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||||
|
|
@ -399,7 +319,7 @@ const RecipientCards = ({
|
||||||
|
|
||||||
<>
|
<>
|
||||||
{/* Main Content Section */}
|
{/* Main Content Section */}
|
||||||
<div className="flex items-start sm:items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||||
{/* Order Number */}
|
{/* Order Number */}
|
||||||
|
|
||||||
<div className="">
|
<div className="">
|
||||||
|
|
@ -527,7 +447,7 @@ const RecipientCards = ({
|
||||||
{/* Name and Email Fields */}
|
{/* Name and Email Fields */}
|
||||||
<div className="mb-2 w-[100%]">
|
<div className="mb-2 w-[100%]">
|
||||||
{/* Mobile: Stack vertically, Tablet+: Side by side */}
|
{/* Mobile: Stack vertically, Tablet+: Side by side */}
|
||||||
<div className="flex flex-col justify-center items-center sm:flex-row gap-1 sm:gap-2">
|
<div className="flex flex-col justify-center sm:items-center sm:flex-row gap-1 sm:gap-2">
|
||||||
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1">
|
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1">
|
||||||
<div className="w-full sm:w-1/2">
|
<div className="w-full sm:w-1/2">
|
||||||
<div className="font-semibold text-gray-800 text-xs">
|
<div className="font-semibold text-gray-800 text-xs">
|
||||||
|
|
@ -558,7 +478,7 @@ const RecipientCards = ({
|
||||||
? false
|
? false
|
||||||
: !recipient.newOne && !recipient.allowSenderToDelete
|
: !recipient.newOne && !recipient.allowSenderToDelete
|
||||||
}
|
}
|
||||||
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1"
|
className="absolute top-0 right-0 sm:static text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1"
|
||||||
>
|
>
|
||||||
<ImBin size={14} className="sm:w-4 sm:h-4" />
|
<ImBin size={14} className="sm:w-4 sm:h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -582,6 +502,8 @@ const RecipientCards = ({
|
||||||
{renderEditableField(recipient, "hostEmail", "Host Email", "", "email")}
|
{renderEditableField(recipient, "hostEmail", "Host Email", "", "email")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-2 h-2 ml-8">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -632,8 +554,6 @@ const RecipientCards = ({
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</SortableRecipient>
|
</SortableRecipient>
|
||||||
|
|
@ -689,44 +609,6 @@ export default RecipientCards;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
|
||||||
// <SortableContext
|
|
||||||
// items={recipients.map((r) => r.id || r.uuid)}
|
|
||||||
// strategy={verticalListSortingStrategy}
|
|
||||||
// >
|
|
||||||
// <div className="flex flex-col divide-y divide-gray-200 max-h-[47vh] overflow-y-auto custom-scrollbar">
|
|
||||||
// {recipients.map((recipient, idx) => (
|
|
||||||
// <SortableRecipient
|
|
||||||
// key={recipient.id || recipient.uuid}
|
|
||||||
// recipient={recipient}
|
|
||||||
// idx={idx}
|
|
||||||
// editRecipientCurrentEmail={editRecipientCurrentEmail}
|
|
||||||
// >
|
|
||||||
// {/* 🔹 your existing recipient card content goes here */}
|
|
||||||
// </SortableRecipient>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// </SortableContext>
|
|
||||||
// </DndContext>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children
|
||||||
className="cursor-grab text-gray-500 flex items-center pr-2 absolute top-0 left-0 z-10 h-full"
|
className="cursor-grab text-gray-500 flex items-center pr-2 absolute top-0 left-0 z-10 h-full"
|
||||||
>
|
>
|
||||||
|
|
||||||
{signingOrder && <MdDragIndicator className="absolute top-[50%] left-[70px] transform -translate-y-1/2 text-gray-300" />}
|
{signingOrder && <MdDragIndicator className={`absolute top-[50%] left-[55px] sm:left-[70px] transform -translate-y-1/2 text-gray-300`} />}
|
||||||
|
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ interface DocumentContainerProps {
|
||||||
aId?: string;
|
aId?: string;
|
||||||
tId?: string;
|
tId?: string;
|
||||||
selectedRecipientIndx?: number;
|
selectedRecipientIndx?: number;
|
||||||
|
onHistoryChange?: (canUndo: boolean, canRedo: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let copiedTags: DroppedItem[] = []
|
let copiedTags: DroppedItem[] = []
|
||||||
|
|
@ -63,10 +64,14 @@ const fetchedTags: string[] = []
|
||||||
// 👇 Create an interface for the exposed methods
|
// 👇 Create an interface for the exposed methods
|
||||||
export interface DocumentContainerRef {
|
export interface DocumentContainerRef {
|
||||||
handleTransactionProcess: (isSave?: boolean, isNavigate?: boolean, navigateTo?: string) => void;
|
handleTransactionProcess: (isSave?: boolean, isNavigate?: boolean, navigateTo?: string) => void;
|
||||||
|
handleUndo: () => void;
|
||||||
|
handleRedo: () => void;
|
||||||
|
handleCopy: () => void;
|
||||||
|
handlePaste: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProps>(
|
const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProps>(
|
||||||
({ isPages, zoomScale, transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null, selectedRecipientIndx = 0, setIsPages }, ref) => {
|
({ isPages, zoomScale, transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null, selectedRecipientIndx = 0, setIsPages, onHistoryChange }, ref) => {
|
||||||
|
|
||||||
const scaleFactor: number = (zoomScale ?? 100) / 100;
|
const scaleFactor: number = (zoomScale ?? 100) / 100;
|
||||||
|
|
||||||
|
|
@ -94,7 +99,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
||||||
// const transactionId = useSelector((state: RootState) => state.transaction.transactionId)
|
// const transactionId = useSelector((state: RootState) => state.transaction.transactionId)
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const childRef = useRef<any>(null);
|
const childRef = useRef<any>(null);
|
||||||
const tagUndoRef = useRef<DroppedItem[] | []>([]);
|
// const tagUndoRef = useRef<DroppedItem[] | []>([]);
|
||||||
|
|
||||||
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
|
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
|
||||||
|
|
||||||
|
|
@ -152,7 +157,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
||||||
hotKey: null
|
hotKey: null
|
||||||
}
|
}
|
||||||
|
|
||||||
const [hotKeysObj, setHotKeysObj] = useState<HotKeysObj>(hotKeysObjInitial);
|
// const [hotKeysObj, setHotKeysObj] = useState<HotKeysObj>(hotKeysObjInitial);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedRecipients.length == 0 && dispatch(fetchRecipients({ accountId, transactionId }));
|
selectedRecipients.length == 0 && dispatch(fetchRecipients({ accountId, transactionId }));
|
||||||
|
|
@ -214,6 +219,79 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
||||||
}, [transactionId])
|
}, [transactionId])
|
||||||
// NAVIGATING to Dashboard if Transaction is not created - END
|
// NAVIGATING to Dashboard if Transaction is not created - END
|
||||||
|
|
||||||
|
const hotKeysObj = useRef<HotKeysObj>(hotKeysObjInitial); // use ref, not state
|
||||||
|
const docKeyListenerRef = useRef<((e: KeyboardEvent) => void) | null>(null);
|
||||||
|
const selectedTagRef = useRef<{ key: string; index: number } | null>(null);
|
||||||
|
const droppedItemsRef = useRef(droppedItems);
|
||||||
|
|
||||||
|
const historyRef = useRef<DroppedItemsObj[]>([]);
|
||||||
|
const cursorRef = useRef<number>(-1);
|
||||||
|
const isUndoRedoRef = useRef<boolean>(false); // prevents history push during undo/redo replay
|
||||||
|
|
||||||
|
const notifyHistoryChange = () => {
|
||||||
|
onHistoryChange?.(
|
||||||
|
cursorRef.current > 0,
|
||||||
|
cursorRef.current < historyRef.current.length - 1
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
droppedItemsRef.current = droppedItems;
|
||||||
|
|
||||||
|
// For Undo Redo
|
||||||
|
// Skip recording when the change was caused by undo/redo itself
|
||||||
|
if (isUndoRedoRef.current) {
|
||||||
|
isUndoRedoRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(droppedItems).length === 0) return;
|
||||||
|
|
||||||
|
// Trim any future states beyond cursor (new action wipes redo stack)
|
||||||
|
historyRef.current = historyRef.current.slice(0, cursorRef.current + 1);
|
||||||
|
|
||||||
|
// Deep clone so mutations don't corrupt history
|
||||||
|
historyRef.current.push(JSON.parse(JSON.stringify(droppedItems)));
|
||||||
|
cursorRef.current = historyRef.current.length - 1;
|
||||||
|
|
||||||
|
notifyHistoryChange();
|
||||||
|
}, [droppedItems]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
selectedTagRef.current = selectedTag;
|
||||||
|
}, [selectedTag]);
|
||||||
|
|
||||||
|
const handleUndo = () => {
|
||||||
|
console.log("Handle Undo Called")
|
||||||
|
if (cursorRef.current <= 0) return; // nothing to undo
|
||||||
|
|
||||||
|
cursorRef.current -= 1;
|
||||||
|
const snapshot = historyRef.current[cursorRef.current];
|
||||||
|
|
||||||
|
isUndoRedoRef.current = true; // flag so the effect above skips pushing
|
||||||
|
dispatch(setDroppedItems({
|
||||||
|
transactionKey,
|
||||||
|
items: JSON.parse(JSON.stringify(snapshot))
|
||||||
|
}));
|
||||||
|
|
||||||
|
notifyHistoryChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRedo = () => {
|
||||||
|
if (cursorRef.current >= historyRef.current.length - 1) return; // nothing to redo
|
||||||
|
|
||||||
|
cursorRef.current += 1;
|
||||||
|
const snapshot = historyRef.current[cursorRef.current];
|
||||||
|
|
||||||
|
isUndoRedoRef.current = true;
|
||||||
|
dispatch(setDroppedItems({
|
||||||
|
transactionKey,
|
||||||
|
items: JSON.parse(JSON.stringify(snapshot))
|
||||||
|
}));
|
||||||
|
|
||||||
|
notifyHistoryChange();
|
||||||
|
};
|
||||||
|
|
||||||
const groupByRecipientId = (tagsArray: any[]) => {
|
const groupByRecipientId = (tagsArray: any[]) => {
|
||||||
const tagsData: Record<number, any[]> = {};
|
const tagsData: Record<number, any[]> = {};
|
||||||
|
|
@ -340,6 +418,10 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
||||||
// }, [isSend])
|
// }, [isSend])
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleTransactionProcess,
|
handleTransactionProcess,
|
||||||
|
handleUndo,
|
||||||
|
handleRedo,
|
||||||
|
handleCopy,
|
||||||
|
handlePaste
|
||||||
}));
|
}));
|
||||||
// Save Tags - END
|
// Save Tags - END
|
||||||
|
|
||||||
|
|
@ -478,124 +560,148 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
||||||
}));
|
}));
|
||||||
dispatch(incrementCounter(transactionKey));
|
dispatch(incrementCounter(transactionKey));
|
||||||
|
|
||||||
// setTimeout(() => {
|
|
||||||
// const tagIndex = key in droppedItems ? droppedItems[key].length : 0;
|
|
||||||
// setSelectedTag({ key, index: tagIndex })
|
|
||||||
// }, 1000)
|
|
||||||
};
|
};
|
||||||
// HANDLE DROP - END
|
// HANDLE DROP - END
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hotKeysObj.isActive) return
|
return () => {
|
||||||
if ((!selectedTag?.key || `${selectedTag?.key}` in droppedItems && droppedItems[selectedTag?.key].length == 0)) return;
|
const targetCon = document.getElementById('doc-con');
|
||||||
|
if (docKeyListenerRef.current && targetCon) {
|
||||||
|
targetCon.removeEventListener('keydown', docKeyListenerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const docCon = document.getElementById("doc-con");
|
const handleCopy = () => {
|
||||||
|
const currentSelectedTag = selectedTagRef.current;
|
||||||
|
const currentDroppedItems = droppedItemsRef.current;
|
||||||
|
if (!currentSelectedTag) return;
|
||||||
|
|
||||||
switch (hotKeysObj.hotKey) {
|
const tag = currentDroppedItems[currentSelectedTag.key]?.[currentSelectedTag.index];
|
||||||
case "Ctrl+c":
|
if (!tag) return;
|
||||||
const tempCopy = { ...droppedItems[selectedTag?.key][selectedTag?.index] }
|
|
||||||
delete tempCopy.id;
|
const { id, ...tagWithoutId } = tag as any;
|
||||||
copiedTags = [{ ...tempCopy, dimensions: { ...tempCopy.dimensions, top: tempCopy.dimensions.top + 10, left: tempCopy.dimensions.left + 10 } }]
|
copiedTags = [{
|
||||||
console.log("from Copy copiedTags", copiedTags)
|
...tagWithoutId,
|
||||||
|
dimensions: {
|
||||||
|
...tagWithoutId.dimensions,
|
||||||
|
top: tagWithoutId.dimensions.top + 10,
|
||||||
|
left: tagWithoutId.dimensions.left + 10,
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePaste = () => {
|
||||||
|
if (!copiedTags.length || !selectedDoc.key) return;
|
||||||
|
const currentDroppedItems = droppedItemsRef.current;
|
||||||
|
const existingTags = currentDroppedItems[selectedDoc.key] || [];
|
||||||
|
|
||||||
|
dispatch(setDroppedItems({
|
||||||
|
transactionKey,
|
||||||
|
items: {
|
||||||
|
...currentDroppedItems,
|
||||||
|
[selectedDoc.key]: [...existingTags, ...copiedTags.map(t => ({ ...t, update: true }))]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
dispatch(incrementCounter(transactionKey));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace the hotkeys useEffect with a direct handler function
|
||||||
|
const handleHotKey = (hotKey: string, docKey: string) => {
|
||||||
|
const currentDroppedItems = droppedItemsRef.current;
|
||||||
|
const currentSelectedTag = selectedTagRef.current;
|
||||||
|
const docCon = document.getElementById('doc-con');
|
||||||
|
|
||||||
|
switch (hotKey) {
|
||||||
|
case 'Ctrl+c': {
|
||||||
|
handleCopy();
|
||||||
break;
|
break;
|
||||||
case "Ctrl+v":
|
}
|
||||||
console.log(selectedDoc.key, "keyss")
|
case 'Ctrl+v': {
|
||||||
let updatedKey = droppedItems[selectedDoc.key]
|
handlePaste();
|
||||||
updatedKey.push(...copiedTags)
|
|
||||||
|
|
||||||
console.log("copiedTags", copiedTags)
|
|
||||||
console.log("updatedKey", updatedKey)
|
|
||||||
|
|
||||||
dispatch(setDroppedItems({
|
|
||||||
transactionKey,
|
|
||||||
items: {
|
|
||||||
...droppedItems,
|
|
||||||
[selectedDoc.key]: updatedKey
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Handle Ctrl+V
|
|
||||||
break;
|
break;
|
||||||
case "Ctrl+x":
|
}
|
||||||
// Handle Ctrl+V
|
case 'Ctrl+a': {
|
||||||
break;
|
docCon?.querySelectorAll('[data-tag-id]').forEach(tag => {
|
||||||
case "Ctrl+z":
|
tag.classList.add('border-2', 'border-sky-700');
|
||||||
// Handle Ctrl+V
|
tag.setAttribute('all-select-group', 'true');
|
||||||
break;
|
|
||||||
case "Ctrl+y":
|
|
||||||
// Handle Ctrl+V
|
|
||||||
break;
|
|
||||||
case "Ctrl+a":
|
|
||||||
docCon?.querySelectorAll("[data-tag-id]").forEach(tag => {
|
|
||||||
tag.classList.add("border-2", "border-sky-700")
|
|
||||||
tag.setAttribute("all-select-group", "true")
|
|
||||||
|
|
||||||
docCon.addEventListener("click", () => {
|
|
||||||
tag.classList.remove("border-2", "border-sky-800")
|
|
||||||
tag.removeAttribute("all-select-group")
|
|
||||||
})
|
|
||||||
// docCon.addEventListener("focusout", () => {
|
|
||||||
// console.log("focus out", tag)
|
|
||||||
// tag.classList.remove("border-2", "border-slate-800")
|
|
||||||
// tag.removeAttribute("all-select-group")
|
|
||||||
// })
|
|
||||||
});
|
});
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
const allselectedTags = docCon?.querySelectorAll("[all-select-group]") ?? []
|
|
||||||
allselectedTags.forEach((tag) => {
|
|
||||||
const tagIndex = tag.getAttribute("data-tag-id") ?? "";
|
|
||||||
const key = tag.getAttribute("data-drop-key") ?? "";
|
|
||||||
|
|
||||||
childRef.current.handleRemoveTag(key, tagIndex)
|
// One-time click to deselect all
|
||||||
})
|
const deselect = () => {
|
||||||
|
docCon?.querySelectorAll('[all-select-group]').forEach(tag => {
|
||||||
if (allselectedTags.length == 0) childRef.current.handleRemoveTag(selectedTag?.key, selectedTag?.index)
|
tag.classList.remove('border-2', 'border-sky-700');
|
||||||
|
tag.removeAttribute('all-select-group');
|
||||||
|
});
|
||||||
|
docCon?.removeEventListener('click', deselect);
|
||||||
|
};
|
||||||
|
docCon?.addEventListener('click', deselect);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
case 'backspace': {
|
||||||
|
const allSelected = docCon?.querySelectorAll('[all-select-group]') ?? [];
|
||||||
|
|
||||||
|
if (allSelected.length > 0) {
|
||||||
|
allSelected.forEach(tag => {
|
||||||
|
const tagIndex = tag.getAttribute('data-tag-id') ?? '';
|
||||||
|
const key = tag.getAttribute('data-drop-key') ?? '';
|
||||||
|
childRef.current?.handleRemoveTag(key, tagIndex);
|
||||||
|
});
|
||||||
|
} else if (currentSelectedTag) {
|
||||||
|
childRef.current?.handleRemoveTag(currentSelectedTag.key, currentSelectedTag.index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'Ctrl+z': {
|
||||||
|
handleUndo();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'Ctrl+y': {
|
||||||
|
handleRedo();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'Ctrl+Shift+z': {
|
||||||
|
handleRedo(); // Mac convention
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, [hotKeysObj])
|
};
|
||||||
|
|
||||||
const handleDocumentFocus = (key: string) => {
|
const handleDocumentFocus = (key: string) => {
|
||||||
// const id = key.replace("_", "");
|
|
||||||
const targetCon = document.getElementById('doc-con');
|
|
||||||
selectedDoc.key = key;
|
selectedDoc.key = key;
|
||||||
|
|
||||||
targetCon?.addEventListener("keydown", (event) => {
|
const targetCon = document.getElementById('doc-con');
|
||||||
|
if (!targetCon) return;
|
||||||
|
|
||||||
|
// Remove previous listener before adding new one
|
||||||
|
if (docKeyListenerRef.current) {
|
||||||
|
targetCon.removeEventListener('keydown', docKeyListenerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = (event: KeyboardEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const ctrl = event.ctrlKey;
|
const ctrl = event.ctrlKey || event.metaKey;
|
||||||
const alt = event.altKey;
|
|
||||||
const metaKey = event.metaKey;
|
|
||||||
const shift = event.shiftKey;
|
const shift = event.shiftKey;
|
||||||
const pressedKey = event.key.toLowerCase();
|
const pressedKey = event.key.toLowerCase();
|
||||||
|
|
||||||
let hotKey = pressedKey
|
let hotKey = pressedKey;
|
||||||
|
if (ctrl && shift) hotKey = 'Ctrl+Shift+' + pressedKey;
|
||||||
|
else if (ctrl) hotKey = 'Ctrl+' + pressedKey;
|
||||||
|
|
||||||
|
handleHotKey(hotKey, key);
|
||||||
|
};
|
||||||
|
|
||||||
if (ctrl) {
|
docKeyListenerRef.current = handler;
|
||||||
hotKey = "Ctrl+" + pressedKey;
|
targetCon.addEventListener('keydown', handler);
|
||||||
} else if (alt) {
|
};
|
||||||
hotKey = "Alt" + pressedKey;
|
|
||||||
} else if (metaKey) {
|
|
||||||
hotKey = "Meta+" + pressedKey;
|
|
||||||
} else if (shift) {
|
|
||||||
hotKey = "Shift+" + pressedKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
setHotKeysObj({
|
|
||||||
isActive: true,
|
|
||||||
key: key,
|
|
||||||
tagId: null,
|
|
||||||
hotKey: hotKey
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// return () => targetCon?.removeEventListener("keydown");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -696,7 +802,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
||||||
accountId={accountId}
|
accountId={accountId}
|
||||||
transactionId={transactionId}
|
transactionId={transactionId}
|
||||||
transactionTemplateToggle={transactionTemplateToggle}
|
transactionTemplateToggle={transactionTemplateToggle}
|
||||||
tagUndoRef={tagUndoRef}
|
// tagUndoRef={tagUndoRef}
|
||||||
transactionKey={transactionKey}
|
transactionKey={transactionKey}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ interface DroppedItemsTagsConProps {
|
||||||
accountId: string;
|
accountId: string;
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
transactionTemplateToggle: string;
|
transactionTemplateToggle: string;
|
||||||
tagUndoRef: React.MutableRefObject<DroppedItem[]>;
|
// tagUndoRef: React.MutableRefObject<DroppedItem[]>;
|
||||||
transactionKey: string;
|
transactionKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ const DroppedItemsTagsCon = forwardRef(({
|
||||||
accountId,
|
accountId,
|
||||||
transactionId,
|
transactionId,
|
||||||
transactionTemplateToggle,
|
transactionTemplateToggle,
|
||||||
tagUndoRef,
|
// tagUndoRef,
|
||||||
transactionKey
|
transactionKey
|
||||||
}: DroppedItemsTagsConProps, ref) => {
|
}: DroppedItemsTagsConProps, ref) => {
|
||||||
|
|
||||||
|
|
@ -688,7 +688,7 @@ const DroppedItemsTagsCon = forwardRef(({
|
||||||
}}
|
}}
|
||||||
onBlur={(e: React.FocusEvent) => {
|
onBlur={(e: React.FocusEvent) => {
|
||||||
const nextElement = e.relatedTarget as HTMLElement | null;
|
const nextElement = e.relatedTarget as HTMLElement | null;
|
||||||
if (nextElement?.hasAttribute("data-type-group") || nextElement?.closest("[data-tag-properties]")) return;
|
if (nextElement?.hasAttribute("data-type-group") || nextElement?.hasAttribute("data-easy-tool") || nextElement?.closest("[data-tag-properties]")) return;
|
||||||
setSelectedTag(null);
|
setSelectedTag(null);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`absolute w-full shadow-md rounded-md mt-1 px-2 py-1 z-20 bg-white ${dropDownToggle ? '' : 'hidden'}`}>
|
className={`absolute bottom-full mb-2 sm:top-1 sm:bottom-auto w-full shadow-md rounded-md px-2 py-1 z-20 bg-white ${dropDownToggle ? '' : 'hidden'}`}>
|
||||||
{selectedRecipients.length && selectedRecipients.map((recipient, indx) => {
|
{selectedRecipients.length && selectedRecipients.map((recipient, indx) => {
|
||||||
if (recipient.type == recipientType.CC) return null; // Skip CC recipients
|
if (recipient.type == recipientType.CC) return null; // Skip CC recipients
|
||||||
return (<div
|
return (<div
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,12 @@ interface NavTwoProps {
|
||||||
tId?: string | null
|
tId?: string | null
|
||||||
onSendClick?: (isSave?: boolean, navigate?: boolean, navigateTo?: string) => void;
|
onSendClick?: (isSave?: boolean, navigate?: boolean, navigateTo?: string) => void;
|
||||||
onZoomChange?: React.Dispatch<React.SetStateAction<number>>
|
onZoomChange?: React.Dispatch<React.SetStateAction<number>>
|
||||||
|
canUndo: boolean;
|
||||||
|
canRedo: boolean;
|
||||||
|
onUndo: () => void;
|
||||||
|
onRedo: () => void;
|
||||||
|
onCopy: () => void;
|
||||||
|
onPaste: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -86,7 +92,13 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
||||||
aId = null,
|
aId = null,
|
||||||
tId = null,
|
tId = null,
|
||||||
onSendClick,
|
onSendClick,
|
||||||
onZoomChange
|
onZoomChange,
|
||||||
|
canUndo,
|
||||||
|
canRedo,
|
||||||
|
onUndo,
|
||||||
|
onRedo,
|
||||||
|
onCopy,
|
||||||
|
onPaste
|
||||||
}) => {
|
}) => {
|
||||||
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
@ -127,7 +139,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
||||||
const [closePage, setClosePage] = useState(false);
|
const [closePage, setClosePage] = useState(false);
|
||||||
|
|
||||||
const [isFitDd, setIsFitDd] = useState<boolean>(false)
|
const [isFitDd, setIsFitDd] = useState<boolean>(false)
|
||||||
const [selFitIndx, setSelFitIndx] = useState<number>(0);
|
const [selFitIndx, setSelFitIndx] = useState<number>(window.innerWidth > 638 ? 2 : 0);
|
||||||
const [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 });
|
const [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 });
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
@ -174,6 +186,19 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
||||||
}
|
}
|
||||||
}, [selectedRecipients])
|
}, [selectedRecipients])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedDocuments || selectedDocuments.length === 0) return;
|
||||||
|
|
||||||
|
const container = document.getElementById("doc-con");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
// Wait for images and zoom to apply
|
||||||
|
setTimeout(() => {
|
||||||
|
container.scrollLeft = (container.scrollWidth - container.clientWidth) / 2;
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
}, [selectedDocuments]);
|
||||||
|
|
||||||
const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false);
|
const [nav2RecToggle, setNav2RecToggle] = useState<boolean>(false);
|
||||||
const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false);
|
const [nav2DocToggle, setNav2DocToggle] = useState<boolean>(false);
|
||||||
|
|
||||||
|
|
@ -331,25 +356,38 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
||||||
<div
|
<div
|
||||||
tabIndex={1}
|
tabIndex={1}
|
||||||
onBlur={() => setIsFitDd(false)}
|
onBlur={() => setIsFitDd(false)}
|
||||||
|
data-easy-tool="true"
|
||||||
className='hidden absolute gap-2 lg:flex items-center left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50'
|
className='hidden absolute gap-2 lg:flex items-center left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50'
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
<p
|
||||||
<LuUndo2 className='text-sm text-slate-600' />
|
onClick={onUndo}
|
||||||
|
title="Undo (Ctrl+Z)"
|
||||||
|
className={`p-2 ${canUndo ? "hover:bg-slate-100" : ""} cursor-pointer rounded-sm`}>
|
||||||
|
<LuUndo2 className={`text-sm ${canUndo ? "text-slate-600" : "text-slate-400"}`} />
|
||||||
</p>
|
</p>
|
||||||
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
<p
|
||||||
<LuRedo2 className='text-sm text-slate-600' />
|
onClick={onRedo}
|
||||||
|
title="Redo (Ctrl+Y)"
|
||||||
|
className={`p-2 ${canRedo ? "hover:bg-slate-100" : ""} cursor-pointer rounded-sm`}>
|
||||||
|
<LuRedo2 className={`text-sm ${canRedo ? "text-slate-600" : "text-slate-400"}`} />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='w-full h-[20px] border-[1px] border-slate-200'></div>
|
<div className='w-full h-[20px] border-[1px] border-slate-200'></div>
|
||||||
|
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
<p
|
||||||
|
onClick={onCopy}
|
||||||
|
title="Copy (Ctrl+C)"
|
||||||
|
className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
||||||
<FaRegCopy className='text-sm text-slate-600' />
|
<FaRegCopy className='text-sm text-slate-600' />
|
||||||
</p>
|
</p>
|
||||||
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
<p
|
||||||
|
onClick={onPaste}
|
||||||
|
title="Paste (Ctrl+V)"
|
||||||
|
className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
||||||
<FaRegPaste className='text-sm text-slate-600' />
|
<FaRegPaste className='text-sm text-slate-600' />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -474,7 +512,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-20 bg-white ${nav2DocToggle ? '' : 'hidden'}`}>
|
className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-80 bg-white ${nav2DocToggle ? '' : 'hidden'}`}>
|
||||||
|
|
||||||
{selectedDocuments.length && selectedDocuments.map((document, indx) => {
|
{selectedDocuments.length && selectedDocuments.map((document, indx) => {
|
||||||
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>
|
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>
|
||||||
|
|
@ -497,7 +535,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
||||||
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-20 bg-white ${nav2RecToggle ? '' : 'hidden'}`}>
|
className={`absolute max-w-max shadow rounded-md mt-1 px-2 py-1 z-80 bg-white ${nav2RecToggle ? '' : 'hidden'}`}>
|
||||||
|
|
||||||
{selectedRecipients.length && selectedRecipients.map((recipient, indx) => {
|
{selectedRecipients.length && selectedRecipients.map((recipient, indx) => {
|
||||||
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>
|
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ const ProcessStepTwo = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [canUndo, setCanUndo] = useState(false);
|
||||||
|
const [canRedo, setCanRedo] = useState(false);
|
||||||
|
// const documentContainerRef = useRef<DocumentContainerRef>(null);
|
||||||
|
|
||||||
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
|
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
|
||||||
let accountId = params.accountId;
|
let accountId = params.accountId;
|
||||||
let transactionId = params.transactionId
|
let transactionId = params.transactionId
|
||||||
|
|
@ -85,7 +89,7 @@ const ProcessStepTwo = () => {
|
||||||
docRef.current?.handleTransactionProcess(isSave, isNavigate, navigateTo);
|
docRef.current?.handleTransactionProcess(isSave, isNavigate, navigateTo);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [zoomScale, setZoomScale] = useState<number>(100); // percentage
|
const [zoomScale, setZoomScale] = useState<number>(windowWidth > 638 ? 150 : 100); // percentage
|
||||||
|
|
||||||
// To center the container on every zoom scale change - START
|
// To center the container on every zoom scale change - START
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -93,15 +97,18 @@ const ProcessStepTwo = () => {
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1);
|
const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1);
|
||||||
|
|
||||||
const newScrollTop = prevScrollRatio * container.scrollHeight;
|
const newScrollTop = prevScrollRatio * container.scrollHeight;
|
||||||
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
|
|
||||||
|
|
||||||
container.scrollTo({
|
// Using setTimeout to let DOM settle after zoom change
|
||||||
top: newScrollTop,
|
setTimeout(() => {
|
||||||
left: newScrollLeft,
|
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
|
||||||
behavior: "instant" as ScrollBehavior
|
container.scrollTo({
|
||||||
});
|
top: newScrollTop,
|
||||||
|
left: newScrollLeft,
|
||||||
|
behavior: "instant" as ScrollBehavior
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
|
|
||||||
}, [zoomScale]);
|
}, [zoomScale]);
|
||||||
// To center the container on every zoom scale change - END
|
// To center the container on every zoom scale change - END
|
||||||
|
|
||||||
|
|
@ -116,7 +123,14 @@ const ProcessStepTwo = () => {
|
||||||
// setIsFields={setIsFields}
|
// setIsFields={setIsFields}
|
||||||
// isSend={isSend}
|
// isSend={isSend}
|
||||||
// setIsSend={setIsSend}
|
// setIsSend={setIsSend}
|
||||||
onSendClick={handleSendClick} />
|
onCopy={() => docRef.current?.handleCopy()}
|
||||||
|
onPaste={() => docRef.current?.handlePaste()}
|
||||||
|
canUndo={canUndo}
|
||||||
|
canRedo={canRedo}
|
||||||
|
onUndo={() => docRef.current?.handleUndo()}
|
||||||
|
onRedo={() => docRef.current?.handleRedo()}
|
||||||
|
onSendClick={handleSendClick}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className='sm:grid sm:grid-cols-12 sm:gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'>
|
<div className='sm:grid sm:grid-cols-12 sm:gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'>
|
||||||
<LeftSideBar
|
<LeftSideBar
|
||||||
|
|
@ -137,6 +151,10 @@ const ProcessStepTwo = () => {
|
||||||
setTransactionSaving={setTransactionSaving}
|
setTransactionSaving={setTransactionSaving}
|
||||||
isSend={isSend}
|
isSend={isSend}
|
||||||
setIsSend={setIsSend}
|
setIsSend={setIsSend}
|
||||||
|
onHistoryChange={(canUndo, canRedo) => {
|
||||||
|
setCanUndo(canUndo);
|
||||||
|
setCanRedo(canRedo);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ const DocumentPages: React.FC<{
|
||||||
<div id='document-pages'
|
<div id='document-pages'
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onBlur={() => window.innerWidth <= 638 && setIsPages(false)}
|
onBlur={() => window.innerWidth <= 638 && setIsPages(false)}
|
||||||
className={`h-[100vh] absolute ${isPages || window.innerWidth > 638 ? "" : "hidden"} top-0 left-0 sm:static max-w-max bg-slate-100 z-50 sm:col-span-2 flex flex-col w-full overflow-y-auto`}>
|
className={`outline-none h-[100vh] absolute ${isPages || window.innerWidth > 638 ? "" : "hidden"} top-0 left-0 sm:static bg-slate-100 z-50 sm:col-span-2 flex flex-col w-min-w overflow-y-auto`}>
|
||||||
{selectedDocuments.map((document, indx) => (
|
{selectedDocuments.map((document, indx) => (
|
||||||
<div key={indx} className="border-b border-slate-200 pb-3 mb-1">
|
<div key={indx} className="border-b border-slate-200 pb-3 mb-1">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import CurrTimeStampAttr from "./attributes/CurrTimeStampAttr";
|
||||||
import ToolTipAttr from "./attributes/ToolTipAttr";
|
import ToolTipAttr from "./attributes/ToolTipAttr";
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
import { RiDeleteBinLine } from "react-icons/ri";
|
||||||
|
|
||||||
export interface DroppedItemProps {
|
export interface DroppedItemProps {
|
||||||
droppedItems: DroppedItem[]
|
droppedItems: DroppedItem[]
|
||||||
|
|
@ -175,6 +176,22 @@ const TagProperties: React.FC<DroppedItemProps> = (
|
||||||
handlePropertyChange("decimalPlace", val)
|
handlePropertyChange("decimalPlace", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteTag = () => {
|
||||||
|
setDroppedItems((prev) => {
|
||||||
|
if (!signClient) {
|
||||||
|
const updatedPageTags = (prev[selTagKey] || []).map((item, i) =>
|
||||||
|
i === selTagIndx ? { ...item, isDeleted: true, update: true } : item
|
||||||
|
);
|
||||||
|
return { ...prev, [selTagKey]: updatedPageTags };
|
||||||
|
} else {
|
||||||
|
return (prev as DroppedItem[]).map((item, i) =>
|
||||||
|
i === selTagIndx ? { ...item, isDeleted: true, update: true } : item
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
emptySelectedTag?.();
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedTag.decimalPlace == undefined) return;
|
if (selectedTag.decimalPlace == undefined) return;
|
||||||
if (selectedTag.decimalPlace == decimalPlace) return;
|
if (selectedTag.decimalPlace == decimalPlace) return;
|
||||||
|
|
@ -498,6 +515,17 @@ const TagProperties: React.FC<DroppedItemProps> = (
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{/* Delete Tag */}
|
||||||
|
<div className="pt-2 border-t border-slate-200 mt-2">
|
||||||
|
<button
|
||||||
|
onClick={handleDeleteTag}
|
||||||
|
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-xs text-red-500 border border-red-200 rounded hover:bg-red-50 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<RiDeleteBinLine className="text-sm" />
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/* Delete Tag */}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,291 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { FiX, FiEye, FiEyeOff } from "react-icons/fi";
|
||||||
|
|
||||||
|
interface Account {
|
||||||
|
id: number;
|
||||||
|
isDefault: boolean;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
language: string;
|
||||||
|
account: Account;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditProfileModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
userData: UserData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
userData,
|
||||||
|
}) => {
|
||||||
|
const [fullName, setFullName] = useState(userData.name);
|
||||||
|
|
||||||
|
const [currentPassword, setCurrentPassword] = useState("");
|
||||||
|
const [newPassword, setNewPassword] = useState("");
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
|
|
||||||
|
const [showPasswords, setShowPasswords] = useState(false);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
if (newPassword !== confirmPassword) {
|
||||||
|
alert("Passwords do not match");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: fullName,
|
||||||
|
currentPassword,
|
||||||
|
newPassword,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(payload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API CALL HERE
|
||||||
|
* await api.put('/api/v1/user/profile', payload)
|
||||||
|
*/
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
fixed
|
||||||
|
inset-0
|
||||||
|
z-50
|
||||||
|
bg-black/40
|
||||||
|
backdrop-blur-sm
|
||||||
|
flex
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
p-4
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
bg-white
|
||||||
|
w-full
|
||||||
|
max-w-2xl
|
||||||
|
rounded-2xl
|
||||||
|
shadow-2xl
|
||||||
|
overflow-hidden
|
||||||
|
animate-in
|
||||||
|
fade-in
|
||||||
|
zoom-in-95
|
||||||
|
"
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between px-6 py-5 border-b border-slate-200">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-slate-900">
|
||||||
|
Edit Profile
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-sm text-slate-500 mt-1">
|
||||||
|
Update your profile information and password.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="
|
||||||
|
h-10
|
||||||
|
w-10
|
||||||
|
rounded-xl
|
||||||
|
flex
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
hover:bg-slate-100
|
||||||
|
transition-all
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<FiX className="text-lg" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="p-6 space-y-6 max-h-[75vh] overflow-y-auto">
|
||||||
|
|
||||||
|
{/* Full Name */}
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-700 block mb-2">
|
||||||
|
Full Name
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={fullName}
|
||||||
|
onChange={(e) => setFullName(e.target.value)}
|
||||||
|
className="
|
||||||
|
w-full
|
||||||
|
rounded-xl
|
||||||
|
border
|
||||||
|
border-slate-300
|
||||||
|
px-4
|
||||||
|
py-3
|
||||||
|
outline-none
|
||||||
|
focus:ring-2
|
||||||
|
focus:ring-[#34BEFF]
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Password Section */}
|
||||||
|
<div className="border-t border-slate-200 pt-6">
|
||||||
|
<h3 className="text-base font-semibold text-slate-900 mb-4">
|
||||||
|
Change Password
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
|
||||||
|
{/* Current Password */}
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-700 block mb-2">
|
||||||
|
Current Password
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showPasswords ? "text" : "password"}
|
||||||
|
value={currentPassword}
|
||||||
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||||
|
className="
|
||||||
|
w-full
|
||||||
|
rounded-xl
|
||||||
|
border
|
||||||
|
border-slate-300
|
||||||
|
px-4
|
||||||
|
py-3
|
||||||
|
pr-12
|
||||||
|
outline-none
|
||||||
|
focus:ring-2
|
||||||
|
focus:ring-[#34BEFF]
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setShowPasswords(!showPasswords)
|
||||||
|
}
|
||||||
|
className="
|
||||||
|
absolute
|
||||||
|
right-3
|
||||||
|
top-1/2
|
||||||
|
-translate-y-1/2
|
||||||
|
text-slate-500
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{showPasswords ? <FiEyeOff /> : <FiEye />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* New Password */}
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-700 block mb-2">
|
||||||
|
New Password
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type={showPasswords ? "text" : "password"}
|
||||||
|
value={newPassword}
|
||||||
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
|
className="
|
||||||
|
w-full
|
||||||
|
rounded-xl
|
||||||
|
border
|
||||||
|
border-slate-300
|
||||||
|
px-4
|
||||||
|
py-3
|
||||||
|
outline-none
|
||||||
|
focus:ring-2
|
||||||
|
focus:ring-[#34BEFF]
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Confirm Password */}
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium text-slate-700 block mb-2">
|
||||||
|
Confirm Password
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type={showPasswords ? "text" : "password"}
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
className="
|
||||||
|
w-full
|
||||||
|
rounded-xl
|
||||||
|
border
|
||||||
|
border-slate-300
|
||||||
|
px-4
|
||||||
|
py-3
|
||||||
|
outline-none
|
||||||
|
focus:ring-2
|
||||||
|
focus:ring-[#34BEFF]
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div className="flex flex-col-reverse sm:flex-row justify-end gap-3 px-6 py-5 border-t border-slate-200">
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="
|
||||||
|
px-5
|
||||||
|
py-3
|
||||||
|
rounded-xl
|
||||||
|
border
|
||||||
|
border-slate-300
|
||||||
|
text-slate-700
|
||||||
|
font-medium
|
||||||
|
hover:bg-slate-50
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
className="
|
||||||
|
px-5
|
||||||
|
py-3
|
||||||
|
rounded-xl
|
||||||
|
bg-[#34BEFF]
|
||||||
|
text-white
|
||||||
|
font-medium
|
||||||
|
hover:opacity-90
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditProfileModal;
|
||||||
|
|
@ -411,92 +411,257 @@ const Setting = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* <SettingNav /> */}
|
<div
|
||||||
{/* <AccountNav/> */}
|
className="
|
||||||
<div className="bg-white h-[calc(90vh-20px)] flex flex-col mx-6 my-6 rounded-lg overflow-hidden">
|
bg-[#F8FAFC]
|
||||||
{/* Tabs */}
|
min-h-[calc(100vh-80px)]
|
||||||
<div className="sticky top-0 z-10 bg-white border-b border-gray-200 px-4 md:px-6 lg:px-8 pt-4">
|
p-2
|
||||||
<div className="flex flex-wrap md:gap-4 ">
|
sm:p-4
|
||||||
{tabs.map((tab) =>
|
lg:p-6
|
||||||
tab.type == page1 && (
|
"
|
||||||
<button
|
>
|
||||||
key={tab.id}
|
<div
|
||||||
onClick={() => handleMainTabClick(tab.id)}
|
className="
|
||||||
className={`cursor-pointer relative px-4 pb-3 text-sm font-medium transition-all duration-200
|
bg-white
|
||||||
${activeTab == tab.id
|
h-[calc(100vh-100px)]
|
||||||
? "text-[#34BEFF] after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-[#34BEFF] bg-gradient-to-t from-[#E5F6FF] to-transparent"
|
rounded-2xl
|
||||||
: "text-gray-600 hover:text-[#34BEFF]"
|
border
|
||||||
}`}
|
border-slate-200
|
||||||
>
|
overflow-hidden
|
||||||
{tab.label}
|
flex
|
||||||
</button>
|
flex-col
|
||||||
)
|
shadow-sm
|
||||||
)}
|
"
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* Top Tabs */}
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
sticky
|
||||||
|
top-0
|
||||||
|
z-30
|
||||||
|
bg-white/90
|
||||||
|
backdrop-blur-md
|
||||||
|
border-b
|
||||||
|
border-slate-200
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
flex
|
||||||
|
overflow-x-auto
|
||||||
|
no-scrollbar
|
||||||
|
gap-2
|
||||||
|
px-3
|
||||||
|
sm:px-5
|
||||||
|
lg:px-8
|
||||||
|
pt-4
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{tabs.map(
|
||||||
|
(tab) =>
|
||||||
|
tab.type == page1 && (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => handleMainTabClick(tab.id)}
|
||||||
|
className={`
|
||||||
|
relative
|
||||||
|
whitespace-nowrap
|
||||||
|
px-4
|
||||||
|
py-3
|
||||||
|
text-sm
|
||||||
|
font-medium
|
||||||
|
rounded-t-xl
|
||||||
|
transition-all
|
||||||
|
duration-200
|
||||||
|
cursor-pointer
|
||||||
|
|
||||||
|
${activeTab == tab.id
|
||||||
|
? `
|
||||||
|
text-[#34BEFF]
|
||||||
|
bg-[#EAF8FF]
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
text-slate-600
|
||||||
|
hover:text-[#34BEFF]
|
||||||
|
hover:bg-slate-50
|
||||||
|
`
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
|
||||||
|
{activeTab == tab.id && (
|
||||||
|
<span
|
||||||
|
className="
|
||||||
|
absolute
|
||||||
|
bottom-0
|
||||||
|
left-0
|
||||||
|
w-full
|
||||||
|
h-[2px]
|
||||||
|
bg-[#34BEFF]
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Responsive Layout */}
|
{/* Layout */}
|
||||||
<div className="flex-1 min-h-0 flex flex-col lg:flex-row">
|
<div className="flex flex-1 min-h-0 overflow-hidden">
|
||||||
|
|
||||||
{/* Sidebar (hidden on small, shown on lg) */}
|
{/* Desktop Sidebar */}
|
||||||
{activeTab == 'userProfile' && (
|
{activeTab == "userProfile" && (
|
||||||
<section className="hidden lg:block flex-shrink-0 w-70 border-r-1 border-slate-200">
|
<aside
|
||||||
<div className="border-b-1 border-slate-200 py-4 px-6">
|
className="
|
||||||
<div className="flex justify-start items-center gap-2">
|
hidden
|
||||||
<div>
|
lg:flex
|
||||||
<p className="bg-[#FAF0FF] text-[#A915FF] font-semibold max-w-max px-3 py-2 rounded-full capitalize">{getInitialsUtils(user.name)}</p>
|
w-[280px]
|
||||||
</div>
|
xl:w-[320px]
|
||||||
<div>
|
border-r
|
||||||
<p className="font-semibold">{user.name}</p>
|
border-slate-200
|
||||||
<p className="text-sm text-slate-600 w-[24ch] overflow-hidden text-ellipsis whitespace-nowrap">
|
flex-col
|
||||||
{user.email}
|
bg-white
|
||||||
</p>
|
"
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* User Card */}
|
||||||
|
<div className="p-6 border-b border-slate-200">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
h-12
|
||||||
|
w-12
|
||||||
|
rounded-full
|
||||||
|
bg-[#FAF0FF]
|
||||||
|
text-[#A915FF]
|
||||||
|
flex
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
font-semibold
|
||||||
|
text-sm
|
||||||
|
shrink-0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{getInitialsUtils(user.name)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="font-semibold text-slate-900 truncate">
|
||||||
|
{user.name}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-sm text-slate-500 truncate">
|
||||||
|
{user.email}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="sticky top-6 z-20 px-6 pt-2">
|
{/* Navigation */}
|
||||||
<AsideSection
|
<div className="flex-1 overflow-y-auto no-scrollbar px-4 py-5">
|
||||||
activeSubSection={activeSubSection}
|
<AsideSection
|
||||||
onSubSectionClick={handleSubSectionClick}
|
activeSubSection={activeSubSection}
|
||||||
subSections={getSubSections(activeTab)}
|
onSubSectionClick={handleSubSectionClick}
|
||||||
mainTabTitle={tabs.find(t => t.id == activeTab)?.label || ""}
|
subSections={getSubSections(activeTab)}
|
||||||
/>
|
mainTabTitle={
|
||||||
</div>
|
tabs.find((t) => t.id == activeTab)?.label || ""
|
||||||
</section>
|
}
|
||||||
)}
|
/>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Sidebar as horizontal nav (only on mobile) */}
|
{/* Main Section */}
|
||||||
{activeTab == 'userProfile' && (
|
<main className="flex-1 min-w-0 flex flex-col overflow-hidden">
|
||||||
<section className="lg:hidden mb-2">
|
|
||||||
<div className="flex overflow-x-auto no-scrollbar gap-2 border-b border-gray-200 p-2">
|
{/* Mobile Sub Navigation */}
|
||||||
{getSubSections(activeTab).map((sub) => (
|
{activeTab == "userProfile" && (
|
||||||
<button
|
<div
|
||||||
key={sub.id}
|
className="
|
||||||
onClick={() => handleSubSectionClick(sub.id)}
|
lg:hidden
|
||||||
className={`whitespace-nowrap text-sm font-medium px-2 py-2 rounded-md cursor-pointer
|
sticky
|
||||||
${activeSubSection == sub.id ? "bg-blue-100 text-blue-600" : "text-gray-600"}
|
top-0
|
||||||
`}
|
z-20
|
||||||
|
bg-white
|
||||||
|
border-b
|
||||||
|
border-slate-200
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
flex
|
||||||
|
overflow-x-auto
|
||||||
|
no-scrollbar
|
||||||
|
gap-2
|
||||||
|
px-3
|
||||||
|
py-3
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{sub.label}
|
{getSubSections(activeTab).map((sub) => (
|
||||||
</button>
|
<button
|
||||||
))}
|
key={sub.id}
|
||||||
</div>
|
onClick={() =>
|
||||||
</section>
|
handleSubSectionClick(sub.id)
|
||||||
)}
|
}
|
||||||
|
className={`
|
||||||
|
whitespace-nowrap
|
||||||
|
px-4
|
||||||
|
py-2
|
||||||
|
rounded-xl
|
||||||
|
text-sm
|
||||||
|
font-medium
|
||||||
|
transition-all
|
||||||
|
duration-200
|
||||||
|
border
|
||||||
|
|
||||||
{/* Main Content */}
|
${activeSubSection == sub.id
|
||||||
<div
|
? `
|
||||||
ref={scrollContainerRef}
|
bg-[#EAF8FF]
|
||||||
className="flex-1 min-w-0 overflow-y-auto no-scrollbar"
|
text-[#34BEFF]
|
||||||
>
|
border-[#B8EAFF]
|
||||||
{renderContent()}
|
`
|
||||||
|
: `
|
||||||
|
bg-white
|
||||||
|
text-slate-600
|
||||||
|
border-slate-200
|
||||||
|
`
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{sub.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Scroll Content */}
|
||||||
|
<div
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
className="
|
||||||
|
flex-1
|
||||||
|
overflow-y-auto
|
||||||
|
no-scrollbar
|
||||||
|
px-2
|
||||||
|
sm:px-4
|
||||||
|
lg:px-6
|
||||||
|
py-4
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
{renderContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Setting;
|
export default Setting;
|
||||||
|
|
@ -160,7 +160,7 @@ const SignatureManager: React.FC = () => {
|
||||||
<button className="bg-[var(--mainPrimaryBtn)] text-white px-3.5 gap-1 py-2 rounded-lg text-sm font-semibold flex items-center cursor-pointer transition-all duration-200 transform hover:-translate-y-0.5"
|
<button className="bg-[var(--mainPrimaryBtn)] text-white px-3.5 gap-1 py-2 rounded-lg text-sm font-semibold flex items-center cursor-pointer transition-all duration-200 transform hover:-translate-y-0.5"
|
||||||
onClick={handleAddNew}>
|
onClick={handleAddNew}>
|
||||||
<FiPlus className="text-lg font-semibold" />
|
<FiPlus className="text-lg font-semibold" />
|
||||||
Add Signature
|
<span>Add</span> <span>Signature</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { FiX, FiEdit2 } from "react-icons/fi";
|
import React, { useState } from "react";
|
||||||
|
import { FiEdit2 } from "react-icons/fi";
|
||||||
|
import EditProfileModal from "./EditProfileModal";
|
||||||
|
|
||||||
|
|
||||||
interface Account {
|
interface Account {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -19,38 +22,109 @@ interface UserProfileProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserProfile: React.FC<UserProfileProps> = ({ userData }) => {
|
const UserProfile: React.FC<UserProfileProps> = ({ userData }) => {
|
||||||
|
const [openEditModal, setOpenEditModal] = useState(false);
|
||||||
|
|
||||||
|
const infoItems = [
|
||||||
|
{
|
||||||
|
label: "Full Name",
|
||||||
|
value: userData.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Email Address",
|
||||||
|
value: userData.email,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Account Name",
|
||||||
|
value: userData.account.name,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-md flex flex-col items-center sm:items-start w-full p-6 lg:gap-4 ">
|
<>
|
||||||
<div className="flex flex-col">
|
<div className="w-full bg-white rounded-2xl border border-slate-200 p-4 sm:p-6 lg:p-8">
|
||||||
<p className="text-lg font-semibold">Personal Information</p>
|
|
||||||
<p className="text-xs text-slate-400 mt-1">Basic info, like your name and email address.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4 mb-6">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl sm:text-2xl font-semibold text-slate-900">
|
||||||
|
Personal Information
|
||||||
|
</h2>
|
||||||
|
|
||||||
<div className="bg-slate-50 w-full px-3 py-2 rounded-lg">
|
<p className="text-sm text-slate-500 mt-1 max-w-xl">
|
||||||
<p className="text-sm font-semibold">Information</p>
|
Basic information like your name, email address, and account details.
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setOpenEditModal(true)}
|
||||||
<div className="w-full border-gray-200 rounded-md bg-white font-sans">
|
className="
|
||||||
<div className="sm:flex sm:flex-col md:grid md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] border-b border-gray-100 px-4 py-4">
|
inline-flex
|
||||||
<span className="text-gray-600">Full Name :</span>
|
items-center
|
||||||
<span className="font-medium text-black">{userData.name}</span>
|
justify-center
|
||||||
|
gap-2
|
||||||
|
rounded-xl
|
||||||
|
border
|
||||||
|
border-slate-200
|
||||||
|
px-4
|
||||||
|
py-2.5
|
||||||
|
text-sm
|
||||||
|
font-medium
|
||||||
|
text-slate-700
|
||||||
|
hover:bg-slate-50
|
||||||
|
transition-all
|
||||||
|
duration-200
|
||||||
|
cursor-pointer
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<FiEdit2 className="text-sm" />
|
||||||
|
Edit Profile
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:flex sm:flex-col md:grid md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] border-b border-gray-100 px-4 py-4">
|
|
||||||
<span className="text-gray-600">Email Address :</span>
|
{/* Section Tag */}
|
||||||
<span className="font-medium text-black">{userData.email}</span>
|
{/* <div className="bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 mb-5">
|
||||||
</div>
|
<p className="text-sm font-semibold text-slate-700">
|
||||||
<div className="sm:flex sm:flex-col md:grid md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] px-4 py-4">
|
Information
|
||||||
<span className="text-gray-600">Account Name :</span>
|
</p>
|
||||||
<span className="font-medium text-black">{userData.account.name}</span>
|
</div> */}
|
||||||
|
|
||||||
|
{/* Info Card */}
|
||||||
|
<div className="rounded-2xl border border-slate-200 overflow-hidden divide-y divide-slate-100">
|
||||||
|
|
||||||
|
{infoItems.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="
|
||||||
|
grid
|
||||||
|
grid-cols-1
|
||||||
|
md:grid-cols-[220px_1fr]
|
||||||
|
gap-1 md:gap-4
|
||||||
|
px-4 sm:px-6
|
||||||
|
py-4 sm:py-5
|
||||||
|
items-start
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<p className="text-sm font-medium text-slate-500">
|
||||||
|
{item.label}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-sm sm:text-base font-semibold text-slate-900 break-words">
|
||||||
|
{item.value || "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Modal */}
|
||||||
</div>
|
<EditProfileModal
|
||||||
|
isOpen={openEditModal}
|
||||||
|
onClose={() => setOpenEditModal(false)}
|
||||||
|
userData={userData}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -603,7 +603,7 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 bg-black/30 flex items-center justify-center z-50 p-0 sm:p-2 md:p-4">
|
<div className="fixed z-100 inset-0 backdrop-blur-sm bg-opacity-50 bg-black/30 flex items-center justify-center z-50 p-0 sm:p-2 md:p-4">
|
||||||
<div className="bg-white sm:max-w-[80%] max-w-[100%] h-[100dvh] sm:h-auto sm:rounded-2xl shadow-xl w-full relative overflow-hidden ">
|
<div className="bg-white sm:max-w-[80%] max-w-[100%] h-[100dvh] sm:h-auto sm:rounded-2xl shadow-xl w-full relative overflow-hidden ">
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,11 @@ const typeToIconMap = {
|
||||||
<path d="M0 26.1765C0 26.8262 0.52672 27.353 1.17649 27.353H21.1765C21.8263 27.353 22.353 26.8262 22.353 26.1765C22.353 25.5267 21.8263 25 21.1765 25H1.17649C0.52672 25 0 25.5267 0 26.1765Z" fill="#FBC249" />
|
<path d="M0 26.1765C0 26.8262 0.52672 27.353 1.17649 27.353H21.1765C21.8263 27.353 22.353 26.8262 22.353 26.1765C22.353 25.5267 21.8263 25 21.1765 25H1.17649C0.52672 25 0 25.5267 0 26.1765Z" fill="#FBC249" />
|
||||||
</svg>
|
</svg>
|
||||||
,
|
,
|
||||||
INITIALS: <svg width="20" height="20" viewBox="0 0 39 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
INITIALS: <svg width="20" height="20" viewBox="0 0 32 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M5.91844 27.3C4.31844 27.3 3.10844 26.85 2.28844 25.95C1.46844 25.03 1.05844 23.72 1.05844 22.02V20.82H4.17844V22.26C4.17844 23.62 4.74844 24.3 5.88844 24.3C6.44844 24.3 6.86844 24.14 7.14844 23.82C7.44844 23.48 7.59844 22.94 7.59844 22.2C7.59844 21.32 7.39844 20.55 6.99844 19.89C6.59844 19.21 5.85844 18.4 4.77844 17.46C3.41844 16.26 2.46844 15.18 1.92844 14.22C1.38844 13.24 1.11844 12.14 1.11844 10.92C1.11844 9.26 1.53844 7.98 2.37844 7.08C3.21844 6.16 4.43844 5.7 6.03844 5.7C7.61844 5.7 8.80844 6.16 9.60844 7.08C10.4284 7.98 10.8384 9.28 10.8384 10.98V11.85H7.71844V10.77C7.71844 10.05 7.57844 9.53 7.29844 9.21C7.01844 8.87 6.60844 8.7 6.06844 8.7C4.96844 8.7 4.41844 9.37 4.41844 10.71C4.41844 11.47 4.61844 12.18 5.01844 12.84C5.43844 13.5 6.18844 14.3 7.26844 15.24C8.64844 16.44 9.59844 17.53 10.1184 18.51C10.6384 19.49 10.8984 20.64 10.8984 21.96C10.8984 23.68 10.4684 25 9.60844 25.92C8.76844 26.84 7.53844 27.3 5.91844 27.3ZM12.7905 6H17.5005L19.6005 21.03H19.6605L21.7605 6H26.4705V27H23.3505V11.1H23.2905L20.8905 27H18.1305L15.7305 11.1H15.6705V27H12.7905V6ZM28.9331 6H37.9331V9H32.2331V14.55H36.7631V17.55H32.2331V24H37.9331V27H28.9331V6Z" fill="#0D6EFD" />
|
<path d="M4.36852 5.32397H8.34385L11.3854 24H8.45057L7.91697 20.2915V20.3448H4.58196L4.04836 24H1.327L4.36852 5.32397ZM7.57013 17.8102L6.26281 8.57894H6.20945L4.9288 17.8102H7.57013Z" fill="#0D6EFD" />
|
||||||
<path d="M0.398438 29.4H38.5931V31.8H0.398438V29.4Z" fill="#0D6EFD" />
|
<rect x="17.5" y="5.46875" width="20.7812" height="3.28125" fill="#FBC249" />
|
||||||
<rect y="29" width="39" height="3" fill="#FBC249" />
|
<rect x="17.5" y="17.5" width="20.7812" height="3.28125" fill="#0D6EFD" />
|
||||||
|
<rect y="29.5312" width="38.2812" height="3.28125" fill="#FBC249" />
|
||||||
</svg>,
|
</svg>,
|
||||||
TEMPORAL:
|
TEMPORAL:
|
||||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue