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">
|
|
||||||
|
<div className="w-full flex flex-col sm:flex-row gap-4 justify-center items-stretch 2xl:gap-6 2xl:mt-6">
|
||||||
|
|
||||||
{/* Forms Made Easy */}
|
{/* Forms Made Easy */}
|
||||||
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
|
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
|
||||||
<div className="">
|
<p className="text-sm font-semibold text-left mb-4 2xl:text-lg min-w-min">
|
||||||
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
|
|
||||||
Generate Forms with Ease
|
Generate Forms with Ease
|
||||||
</p>
|
</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">
|
<div className="w-full flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
|
||||||
<img src="/forms-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
|
<img
|
||||||
|
src="/forms-made-easy.png"
|
||||||
|
className="w-[140px] sm:w-[40%] 2xl:w-[40%] object-contain"
|
||||||
|
alt="Forms Made Easy"
|
||||||
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => window.open("https://formsmadeasy.com/", "_blank")}
|
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">
|
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
|
Try Now
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Docs Made Easy */}
|
{/* Docs Made Easy */}
|
||||||
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
|
<div className="bg-white rounded-lg shadow p-4 2xl:p-6">
|
||||||
<div className="">
|
<p className="text-sm font-semibold text-left mb-4 2xl:text-lg">
|
||||||
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
|
|
||||||
Generate Documents (Invoices, Reports, Quotes)
|
Generate Documents (Invoices, Reports, Quotes)
|
||||||
</p>
|
</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">
|
<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-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
|
<img
|
||||||
|
src="/docs-made-easy.png"
|
||||||
|
className="w-[140px] sm:w-[40%] 2xl:w-[40%] object-contain"
|
||||||
|
alt="Docs Made Easy"
|
||||||
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => window.open("https://docsmadeasy.com/", "_blank")}
|
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">
|
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
|
Try Now
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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,8 +92,8 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Page Thumbnails - DocuSign style */}
|
{/* Page Thumbnails - DocuSign style */}
|
||||||
{isOpen && (
|
|
||||||
<div className="flex flex-col px-2">
|
<div className={`flex flex-col px-2 ${isOpen ? 'block' : 'hidden'}`}>
|
||||||
{document.pages?.map((page, pageIndex) => {
|
{document.pages?.map((page, pageIndex) => {
|
||||||
const key = `${document.documentId}_${pageIndex}`;
|
const key = `${document.documentId}_${pageIndex}`;
|
||||||
const isActive = activePage === key;
|
const isActive = activePage === key;
|
||||||
|
|
@ -144,7 +144,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</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"
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ import { Bounce, toast } from "react-toastify";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
|
import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
|
||||||
|
|
||||||
import { FaCheck, FaQuestionCircle, FaDownload, FaAngleUp } from 'react-icons/fa'
|
import { FaCheck, FaQuestionCircle, FaDownload, FaAngleUp, FaPrint } from 'react-icons/fa'
|
||||||
import { IoIosDocument, IoMdClose } from 'react-icons/io'
|
import { IoIosDocument, IoMdClose } from 'react-icons/io'
|
||||||
import { MdKeyboardArrowDown } from 'react-icons/md'
|
import { MdKeyboardArrowDown, MdNavigateNext } from 'react-icons/md'
|
||||||
import confetti from 'canvas-confetti'
|
import confetti from 'canvas-confetti'
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
@ -33,6 +33,13 @@ import ClientAuditModal from "./ClientAuditModal";
|
||||||
import axiosInstance from "../../config/axiosInstance";
|
import axiosInstance from "../../config/axiosInstance";
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
import ConfirmSignModal from "./ConfirmSignModal";
|
||||||
|
import { AiOutlineAudit, AiOutlineDownload } from "react-icons/ai";
|
||||||
|
import { SlPrinter } from "react-icons/sl";
|
||||||
|
import { CgPrinter } from "react-icons/cg";
|
||||||
|
import { ImPrinter } from "react-icons/im";
|
||||||
|
import { RiFileHistoryFill } from "react-icons/ri";
|
||||||
|
import { IoDocumentsOutline } from "react-icons/io5";
|
||||||
|
|
||||||
interface RecipientType {
|
interface RecipientType {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -212,7 +219,7 @@ const SignDocument = () => {
|
||||||
const [isFields, setIsFields] = useState<boolean>(true);
|
const [isFields, setIsFields] = useState<boolean>(true);
|
||||||
|
|
||||||
const [isFitDd, setIsFitDd] = useState<boolean>(false)
|
const [isFitDd, setIsFitDd] = useState<boolean>(false)
|
||||||
const [selFitIndx, setSelFitIndx] = useState<number>(2);
|
const [selFitIndx, setSelFitIndx] = useState<number>(windowWidth > 638 ? 3 : 0);
|
||||||
|
|
||||||
const globalLoader = useSelector((state: RootState) => state.loader)
|
const globalLoader = useSelector((state: RootState) => state.loader)
|
||||||
|
|
||||||
|
|
@ -228,9 +235,24 @@ const SignDocument = () => {
|
||||||
const [provideTags, setProvideTags] = useState(false)
|
const [provideTags, setProvideTags] = useState(false)
|
||||||
const [showCompletionScreen, setShowCompletionScreen] = useState(false)
|
const [showCompletionScreen, setShowCompletionScreen] = useState(false)
|
||||||
|
|
||||||
const [zoomScale, setZoomScale] = useState<number>(100)
|
const [zoomScale, setZoomScale] = useState<number>(windowWidth > 638 ? 150 : 100)
|
||||||
const scaleFactor: number = (zoomScale ?? 100) / 100;
|
const scaleFactor: number = (zoomScale ?? 100) / 100;
|
||||||
|
|
||||||
|
// Handle Confirm Sign Modal - Start
|
||||||
|
// Add state
|
||||||
|
const [confirmSignOpen, setConfirmSignOpen] = useState(false);
|
||||||
|
const [confirmSignLoading, setConfirmSignLoading] = useState(false);
|
||||||
|
|
||||||
|
// Wrap handleFormSubmit
|
||||||
|
const handleConfirmSign = async () => {
|
||||||
|
setConfirmSignLoading(true);
|
||||||
|
await handleFormSubmit();
|
||||||
|
setConfirmSignLoading(false);
|
||||||
|
setConfirmSignOpen(false);
|
||||||
|
};
|
||||||
|
// Handle Confirm Sign Modal - END
|
||||||
|
|
||||||
|
|
||||||
// To center the container on every zoom scale change - START
|
// To center the container on every zoom scale change - START
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = document.getElementById("doc-con");
|
const container = document.getElementById("doc-con");
|
||||||
|
|
@ -597,16 +619,16 @@ const SignDocument = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const signRes = await axiosInstance.post(`/api/v1/sign/${recipientId}`, {}, { headers: { "ngrok-skip-browser-warning": "69420", } })
|
const signRes = await axiosInstance.post(`/api/v1/sign/${recipientId}`, {}, { headers: { "ngrok-skip-browser-warning": "69420", } })
|
||||||
dispatch(hideLoader());
|
|
||||||
if (signRes.status == 200) {
|
if (signRes.status == 200) {
|
||||||
dispatch(hideLoader());
|
|
||||||
setTransparentTags(true)
|
setTransparentTags(true)
|
||||||
// setShowConfetti(true)
|
// setShowConfetti(true)
|
||||||
// setShowCompletionScreen(true)
|
// setShowCompletionScreen(true)
|
||||||
setSelectedTag(null)
|
setSelectedTag(null)
|
||||||
// navigate("/transaction-complete")
|
// navigate("/transaction-complete")
|
||||||
|
dispatch(hideLoader());
|
||||||
window.location.href = `/transaction-complete/${recipientId}`
|
window.location.href = `/transaction-complete/${recipientId}`
|
||||||
}
|
}
|
||||||
|
dispatch(hideLoader());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch(hideLoader());
|
dispatch(hideLoader());
|
||||||
}
|
}
|
||||||
|
|
@ -625,7 +647,7 @@ const SignDocument = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiredFilled && focusElement.required == true) {
|
if (requiredFilled && focusElement.required == true) {
|
||||||
handleFormSubmit();
|
setConfirmSignOpen(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -935,7 +957,8 @@ const SignDocument = () => {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`w-full h-screen overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>
|
<>
|
||||||
|
<div className={`w-full overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>
|
||||||
|
|
||||||
{/* <TermsModal showTerms={showTerms} setShowTerms={setShowTerms} recipientId={recipientId || ""} /> */}
|
{/* <TermsModal showTerms={showTerms} setShowTerms={setShowTerms} recipientId={recipientId || ""} /> */}
|
||||||
{/* {showCompletionScreen && <SignCompletionScreen />} */}
|
{/* {showCompletionScreen && <SignCompletionScreen />} */}
|
||||||
|
|
@ -951,17 +974,43 @@ const SignDocument = () => {
|
||||||
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)] mt-0'>
|
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)] mt-0'>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
|
<img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="flex justify-center items-center text-shite gap-2">
|
||||||
|
|
||||||
|
{/* DOWNLOAD AND PRINT OPTION - START */}
|
||||||
|
{/* <div className='flex'>
|
||||||
|
<p className='p-2 cursor-pointer rounded-sm' title="Download Documents">
|
||||||
|
<FaDownload className='hover:text-slate-100 text-sm text-slate-200' />
|
||||||
|
</p>
|
||||||
|
<p className='p-2 cursor-pointer rounded-sm' title="Print">
|
||||||
|
<ImPrinter className='hover:text-slate-100 text-sm text-slate-200' />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='w-full h-[20px] border-[1px] border-slate-400'></div>
|
||||||
|
|
||||||
|
<div className='flex' title="Audit">
|
||||||
|
<p className='p-2 cursor-pointer rounded-sm'>
|
||||||
|
<RiFileHistoryFill className='hover:text-slate-100 text-sm text-slate-200' />
|
||||||
|
</p>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
{/* <div className='w-full h-[20px] border-[1px] border-slate-400 mr-2'></div> */}
|
||||||
|
|
||||||
|
{/* DOWNLOAD AND PRINT OPTION - START */}
|
||||||
|
|
||||||
{/* FIT - START */}
|
{/* FIT - START */}
|
||||||
<div
|
<div
|
||||||
className="hidden sm:block relative"
|
className="hidden sm:block relative border border-slate-200 rounded-sm"
|
||||||
tabIndex={1}
|
tabIndex={1}
|
||||||
onBlur={() => setIsFitDd(false)}
|
onBlur={() => setIsFitDd(false)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={() => setIsFitDd(!isFitDd)}
|
onClick={() => setIsFitDd(!isFitDd)}
|
||||||
className='text-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'>
|
className='text-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'>
|
||||||
<p className='tracking-wide'>Fit</p>
|
<p className='tracking-wide'>{fitObj[selFitIndx].label}</p>
|
||||||
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1050,7 +1099,7 @@ const SignDocument = () => {
|
||||||
<SignButton
|
<SignButton
|
||||||
transparentTags={transparentTags}
|
transparentTags={transparentTags}
|
||||||
hideTags={hideTags}
|
hideTags={hideTags}
|
||||||
handleFormSubmit={handleFormSubmit}
|
handleFormSubmit={() => setConfirmSignOpen(true)}
|
||||||
onDeclineClick={() => setDeclineOpen(true)}
|
onDeclineClick={() => setDeclineOpen(true)}
|
||||||
onReassignClick={() => setReassignOpen(true)}
|
onReassignClick={() => setReassignOpen(true)}
|
||||||
onAuditHistoryClick={() => setAuditHistoryOpen(true)}
|
onAuditHistoryClick={() => setAuditHistoryOpen(true)}
|
||||||
|
|
@ -1058,29 +1107,6 @@ const SignDocument = () => {
|
||||||
transactionId={"1"}
|
transactionId={"1"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!transparentTags && recipient?.type != "CC" && (
|
|
||||||
<div
|
|
||||||
className={`absolute z-50 left-0 bottom-0 bg-[var(--primary)] w-full flex justify-around items-center
|
|
||||||
px-2.5 transition-all duration-500 ease-in-out
|
|
||||||
${requiredFilled ? "translate-y-0" : "translate-y-full hidden"}`}
|
|
||||||
>
|
|
||||||
<div className="py-6 text-white">
|
|
||||||
<p className="text-2xl font-semibold">All Set to Wrap This Up?</p>
|
|
||||||
<p className="text-sm">
|
|
||||||
You've completed the required fields. Review your work, then select Finish.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={handleFormSubmit}
|
|
||||||
disabled={!requiredFilled}
|
|
||||||
className="font-semibold text-sm px-8 py-2 bg-slate-100 rounded-md text-[var(--primary)] cursor-pointer"
|
|
||||||
>
|
|
||||||
Finish
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/* Right Side - ENDS */}
|
{/* Right Side - ENDS */}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1126,10 +1152,11 @@ const SignDocument = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<>
|
||||||
{provideTags && !transparentTags && <div className='flex flex-col md:flex-row justify-between items-center bg-slate-100 p-1.5 rounded-2xl border-1 border-gray-200 mb-3'>
|
{provideTags && !transparentTags && <div className='flex flex-col md:flex-row justify-between items-center bg-slate-100 p-1.5 rounded-2xl border-1 border-gray-200 mb-3'>
|
||||||
{provideTags && recipient?.type != "CC" && <div
|
{provideTags && recipient?.type != "CC" && <div
|
||||||
onClick={() => handleSetIsFields(true)}
|
onClick={() => handleSetIsFields(true)}
|
||||||
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isFields ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300'}`}
|
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isFields ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300`}
|
||||||
>
|
>
|
||||||
<IoIosDocument />
|
<IoIosDocument />
|
||||||
<p className='tracking-wide'>Tags</p>
|
<p className='tracking-wide'>Tags</p>
|
||||||
|
|
@ -1143,8 +1170,10 @@ const SignDocument = () => {
|
||||||
<p className='tracking-wide'>Pages</p>
|
<p className='tracking-wide'>Pages</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <p className="hidden sm:block text-xs text-white">Hi! {recipient && recipient.firstName} {recipient && recipient.lastName}</p> */}
|
</div>
|
||||||
</div>}
|
}
|
||||||
|
</>
|
||||||
|
|
||||||
|
|
||||||
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
|
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
|
||||||
|
|
||||||
|
|
@ -1161,7 +1190,7 @@ const SignDocument = () => {
|
||||||
{/* Main container */}
|
{/* Main container */}
|
||||||
<div
|
<div
|
||||||
id="doc-con"
|
id="doc-con"
|
||||||
className={`zoom-con relative ${provideTags ? (isFields && selectedTag != null ? "col-span-8" : "col-span-10") : (isFields ? "col-span-12" : "col-span-10")} sm:px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth transition-all duration-200 pb-[4rem]`}
|
className={`col-span-12 zoom-con relative ${provideTags ? (isFields && selectedTag != null ? "sm:col-span-8" : "sm:col-span-10") : (isFields ? "sm:col-span-12" : "sm:col-span-10")} sm:px-4 bg-slate-100 h-full w-full ${zoomScale > 150 ? "overflow-x-auto" : "overflow-x-hidden"} scroll-smooth transition-all duration-200 pb-[4rem]`}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1294,7 +1323,8 @@ const SignDocument = () => {
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{/* NEXT Button - END */}
|
{/* NEXT Button - END */}
|
||||||
</div>}
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
@ -1308,24 +1338,36 @@ const SignDocument = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div className="flex justify-center mt-[60px] w-full">
|
||||||
|
{/* <div>
|
||||||
|
<button
|
||||||
|
onClick={() => handleConfirmSign()}
|
||||||
|
className="sm:hidden mb-[40px] bg-[var(--primary)] text-white px-4 py-2 cursor-pointer rounded-sm">Finish</button>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) &&
|
{(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) &&
|
||||||
<div
|
<div
|
||||||
onClick={() => scrollToElement(focusElement?.navigationOrder)}
|
onClick={() => scrollToElement(focusElement?.navigationOrder)}
|
||||||
className={`hidden sm:block cursor-pointer fixed ${navigationTagPos == NavigationTagPosEnum.ABOVE ? "top-[45px]" : requiredFilled ? "bottom-[82px]" : "bottom-[35px]"} right-[8%] flex flex-col items-center`}>
|
className={`hidden sm:block cursor-pointer fixed ${navigationTagPos == NavigationTagPosEnum.ABOVE ? "top-[55px]" : requiredFilled ? "bottom-[82px]" : "bottom-[55px]"} right-[8%] flex flex-col items-center`}>
|
||||||
{/* Arrow pointing up */}
|
{/* Arrow pointing up */}
|
||||||
{navigationTagPos == NavigationTagPosEnum.ABOVE && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)]"></div>}
|
{/* {navigationTagPos == NavigationTagPosEnum.ABOVE && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)]"></div>} */}
|
||||||
|
|
||||||
{/* Label box */}
|
{/* Label box */}
|
||||||
<div
|
<div
|
||||||
className="bg-[var(--primary)] text-white text-sm px-3 py-1 rounded-md shadow-md flex items-center gap-1">
|
className="bg-[var(--primary)] text-white text-sm px-6 py-3 rounded-md shadow-md flex items-center gap-1 font-medium">
|
||||||
<span className="capitalize">{focusElement?.type.toLowerCase()}</span>
|
{/* <span className="capitalize">{focusElement?.type.toLowerCase()}</span> */}
|
||||||
|
<span className="capitalize">Next</span>
|
||||||
</div>
|
</div>
|
||||||
{navigationTagPos == NavigationTagPosEnum.BELOW && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)] rotate-[180deg]"></div>}
|
{/* {navigationTagPos == NavigationTagPosEnum.BELOW && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)] rotate-[180deg]"></div>} */}
|
||||||
</div>}
|
</div>}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag &&
|
{provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag &&
|
||||||
<div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}>
|
<div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}>
|
||||||
|
|
@ -1375,15 +1417,16 @@ const SignDocument = () => {
|
||||||
setIsFields(false)
|
setIsFields(false)
|
||||||
}}
|
}}
|
||||||
className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] flex items-center text-white rounded-tr-md rounded-br-md cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'>
|
className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] flex items-center text-white rounded-tr-md rounded-br-md cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'>
|
||||||
<div className='py-2 pl-2 pr-1'>
|
<div className='py-2 pl-2 pr-2'>
|
||||||
<FaAngleUp className='rotate-90' />
|
{/* <FaAngleUp className='rotate-90' /> */}
|
||||||
<p className='flex flex-col justify-center items-center text-xs tracking-widest mt-1 pt-1 border-t-1 border-white'>
|
<IoDocumentsOutline />
|
||||||
|
{/* <p className='flex flex-col justify-center items-center text-xs tracking-widest mt-1 pt-1 border-t-1 border-white'>
|
||||||
<span>P</span>
|
<span>P</span>
|
||||||
<span>A</span>
|
<span>A</span>
|
||||||
<span>G</span>
|
<span>G</span>
|
||||||
<span>E</span>
|
<span>E</span>
|
||||||
<span>S</span>
|
<span>S</span>
|
||||||
</p>
|
</p> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1415,49 +1458,120 @@ const SignDocument = () => {
|
||||||
// onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })}
|
// onConfirm={({ firstName, lastName, email, message }) => handleReassignConfirm({ firstName, lastName, email, message })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ConfirmSignModal
|
||||||
|
open={confirmSignOpen}
|
||||||
|
onClose={() => setConfirmSignOpen(false)}
|
||||||
|
onConfirm={handleConfirmSign}
|
||||||
|
loading={confirmSignLoading}
|
||||||
|
recipientName={recipient ? `${recipient.firstName ?? ""} ${recipient.lastName ?? ""}`.trim() : ""}
|
||||||
|
documentCount={selectedDocuments?.length ?? 1}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
{!transparentTags && recipient?.type != "CC" && <>
|
|
||||||
<div className={`fixed bottom-0 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] left-0 bg-white px-4 py-2 flex justify-between items-center gap-4`}>
|
|
||||||
<p className='text-xs sm:text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="block py-1 sm:py-0 sm:inline text-[var(--mainPrimaryBtn)]">Terms & Conditions.</a></p>
|
|
||||||
{/* <div className='flex items-center gap-8 cursor-pointer text-xs px-2 py-1 rounded-sm outline-none'>
|
|
||||||
<FaAngleLeft
|
|
||||||
onClick={() => handleTagNavClick("P")}
|
|
||||||
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
|
|
||||||
<div>
|
|
||||||
<span className="text-[var(--mainPrimaryBtn)]">{focusElement?.navigationOrder}</span>
|
|
||||||
<span> / </span>
|
|
||||||
<span>{droppedItems.length}</span>
|
|
||||||
<span> Fields</span>
|
|
||||||
</div>
|
</div>
|
||||||
<FaAngleRight
|
|
||||||
onClick={() => handleTagNavClick("N")}
|
{!transparentTags && recipient?.type != "CC" && (
|
||||||
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
|
|
||||||
</div> */}
|
|
||||||
<div>
|
|
||||||
{!transparentTags && focusElement != null && Object.keys(focusElement).length > 1 &&
|
|
||||||
<div
|
<div
|
||||||
id="tag-navigation-btn-mb"
|
className={`absolute z-80 left-0 bottom-0 bg-[var(--primary)] w-full flex justify-around items-center
|
||||||
className={`sm:hidden flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
|
px-2.5 transition-all duration-500 ease-in-out
|
||||||
style={{ top: (focusElement.dimensions.top) + "px" }}
|
${requiredFilled ? "translate-y-0" : "translate-y-full hidden"}`}
|
||||||
onClick={() => handleTagNavClick("N")}
|
|
||||||
>
|
>
|
||||||
|
<div className="py-5 sm:py-6 text-white">
|
||||||
|
<p className="text-lg sm:text-2xl font-semibold leading-snug">
|
||||||
|
All Set to Wrap This Up?
|
||||||
|
</p>
|
||||||
|
|
||||||
<p className="bg-[var(--mainPrimaryBtn)] p-2 rounded-md text-white capitalize">{requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()}</p>
|
<p className="text-xs sm:text-sm leading-relaxed mt-2 max-w-md">
|
||||||
|
You've completed the required fields. Review your work, then select Finish.
|
||||||
{/* NEXT Button - END */}
|
</p>
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
{/* <p className='cursor-pointer text-sm text-white bg-[var(--mainPrimaryBtn)] px-4 py-2 rounded'
|
<button
|
||||||
onClick={handleFormSubmit}
|
onClick={() => setConfirmSignOpen(true)}
|
||||||
>Sign</p> */}
|
disabled={!requiredFilled}
|
||||||
|
className="font-semibold text-sm px-8 py-2 bg-slate-100 rounded-md text-[var(--primary)] cursor-pointer"
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>}
|
)}
|
||||||
|
|
||||||
|
{!transparentTags && recipient?.type !== "CC" && (
|
||||||
|
<footer className="fixed bottom-0 sm:static flex justify-between items-center sm:h-[30px] w-full bg-white border-t border-slate-200 z-50">
|
||||||
|
|
||||||
|
{/* Mobile Navigation */}
|
||||||
|
<div className="border-t-2 border-slate-200 py-2 sm:border-none sm:hidden justify-between flex items-center px-4 sm:py-1 w-full ">
|
||||||
|
|
||||||
|
{/* Left */}
|
||||||
|
<div className="flex items-center leading-tight gap-2">
|
||||||
|
{/* <span className="text-[11px] text-slate-500">
|
||||||
|
Powered by
|
||||||
|
</span> */}
|
||||||
|
<span className="font-normal text-sm text-slate-600">
|
||||||
|
Verify and Complete Tags
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Center Action */}
|
||||||
|
{!transparentTags &&
|
||||||
|
focusElement &&
|
||||||
|
Object.keys(focusElement).length > 1 && (
|
||||||
|
<>
|
||||||
|
<div className="flex bg-[var(--primary)] items-center gap-1 pl-2 pr-4 py-2 rounded-sm">
|
||||||
|
<MdNavigateNext className="text-white" />
|
||||||
|
<button
|
||||||
|
id="tag-navigation-btn-mb"
|
||||||
|
onClick={() => handleTagNavClick("N")}
|
||||||
|
className="rounded-sm text-white text-sm font-normal shadow-md active:scale-[0.98] transition-all duration-200"
|
||||||
|
>
|
||||||
|
{requiredFilled && focusElement.required
|
||||||
|
? "Submit"
|
||||||
|
: `${focusElement.type}`}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Right */}
|
||||||
|
{/* <div className="text-right">
|
||||||
|
<p className="text-[11px] text-slate-400">
|
||||||
|
© 2026
|
||||||
|
</p>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop Footer */}
|
||||||
|
<div className="hidden sm:flex w-full items-center justify-between px-6 py-3 ">
|
||||||
|
|
||||||
|
{/* Left */}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-slate-500">
|
||||||
|
<span>Powered by</span>
|
||||||
|
<span className="font-semibold text-[var(--mainPrimaryBtn)]">
|
||||||
|
Sign Made Easy
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Center */}
|
||||||
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
{/* <select
|
||||||
|
className="px-3 py-1.5 rounded-lg border border-slate-200 text-sm outline-none focus:border-[var(--mainPrimaryBtn)] bg-white text-slate-600"
|
||||||
|
>
|
||||||
|
<option>English</option>
|
||||||
|
<option>Hindi</option>
|
||||||
|
<option>Spanish</option>
|
||||||
|
</select> */}
|
||||||
|
|
||||||
|
<p className="hidden sm:block text-xs text-slate-400 text-center">
|
||||||
|
By signing you agree to SME Terms & Conditions
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right */}
|
||||||
|
<div className="hidden sm:block text-sm text-slate-500">
|
||||||
|
© 2026 SME. All rights reserved.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,48 +194,26 @@ 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="
|
|
||||||
absolute
|
|
||||||
left-0
|
|
||||||
top-full
|
|
||||||
mt-1
|
|
||||||
min-w-[180px]
|
|
||||||
bg-white
|
|
||||||
border
|
|
||||||
border-slate-200
|
|
||||||
rounded-md
|
|
||||||
shadow-lg
|
|
||||||
overflow-hidden
|
|
||||||
z-50
|
|
||||||
|
|
||||||
opacity-0
|
|
||||||
invisible
|
|
||||||
translate-y-[-5px]
|
|
||||||
scale-[0.98]
|
|
||||||
|
|
||||||
transition-all
|
|
||||||
duration-200
|
|
||||||
ease-out
|
|
||||||
|
|
||||||
group-hover:opacity-100
|
|
||||||
group-hover:visible
|
|
||||||
group-hover:translate-y-0
|
|
||||||
group-hover:scale-100
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => {
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
setUploadDropdownOpen(false);
|
||||||
|
}}
|
||||||
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 cursor-pointer flex gap-2 items-center"
|
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)]' />
|
<FaDesktop className='text-[var(--primary)]' />
|
||||||
|
|
@ -225,13 +221,17 @@ const StartNow = () => {
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate("/templates")}
|
onClick={() => {
|
||||||
|
navigate("/templates");
|
||||||
|
setUploadDropdownOpen(false);
|
||||||
|
}}
|
||||||
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"
|
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)]' />
|
<HiTemplate className='text-[var(--primary)]' />
|
||||||
<span>Use a Template</span>
|
<span>Use a Template</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
copiedTags = [{ ...tempCopy, dimensions: { ...tempCopy.dimensions, top: tempCopy.dimensions.top + 10, left: tempCopy.dimensions.left + 10 } }]
|
|
||||||
console.log("from Copy copiedTags", copiedTags)
|
|
||||||
break;
|
|
||||||
case "Ctrl+v":
|
|
||||||
console.log(selectedDoc.key, "keyss")
|
|
||||||
let updatedKey = droppedItems[selectedDoc.key]
|
|
||||||
updatedKey.push(...copiedTags)
|
|
||||||
|
|
||||||
console.log("copiedTags", copiedTags)
|
const { id, ...tagWithoutId } = tag as any;
|
||||||
console.log("updatedKey", updatedKey)
|
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({
|
dispatch(setDroppedItems({
|
||||||
transactionKey,
|
transactionKey,
|
||||||
items: {
|
items: {
|
||||||
...droppedItems,
|
...currentDroppedItems,
|
||||||
[selectedDoc.key]: updatedKey
|
[selectedDoc.key]: [...existingTags, ...copiedTags.map(t => ({ ...t, update: true }))]
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
dispatch(incrementCounter(transactionKey));
|
||||||
|
};
|
||||||
|
|
||||||
// Handle Ctrl+V
|
// Replace the hotkeys useEffect with a direct handler function
|
||||||
break;
|
const handleHotKey = (hotKey: string, docKey: string) => {
|
||||||
case "Ctrl+x":
|
const currentDroppedItems = droppedItemsRef.current;
|
||||||
// Handle Ctrl+V
|
const currentSelectedTag = selectedTagRef.current;
|
||||||
break;
|
const docCon = document.getElementById('doc-con');
|
||||||
case "Ctrl+z":
|
|
||||||
// Handle Ctrl+V
|
|
||||||
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", () => {
|
switch (hotKey) {
|
||||||
tag.classList.remove("border-2", "border-sky-800")
|
case 'Ctrl+c': {
|
||||||
tag.removeAttribute("all-select-group")
|
handleCopy();
|
||||||
})
|
break;
|
||||||
// docCon.addEventListener("focusout", () => {
|
}
|
||||||
// console.log("focus out", tag)
|
case 'Ctrl+v': {
|
||||||
// tag.classList.remove("border-2", "border-slate-800")
|
handlePaste();
|
||||||
// tag.removeAttribute("all-select-group")
|
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');
|
||||||
});
|
});
|
||||||
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;
|
|
||||||
|
|
||||||
|
// Using setTimeout to let DOM settle after zoom change
|
||||||
|
setTimeout(() => {
|
||||||
|
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
|
||||||
container.scrollTo({
|
container.scrollTo({
|
||||||
top: newScrollTop,
|
top: newScrollTop,
|
||||||
left: newScrollLeft,
|
left: newScrollLeft,
|
||||||
behavior: "instant" as ScrollBehavior
|
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
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
bg-white
|
||||||
|
h-[calc(100vh-100px)]
|
||||||
|
rounded-2xl
|
||||||
|
border
|
||||||
|
border-slate-200
|
||||||
|
overflow-hidden
|
||||||
|
flex
|
||||||
|
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 && (
|
tab.type == page1 && (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
onClick={() => handleMainTabClick(tab.id)}
|
onClick={() => handleMainTabClick(tab.id)}
|
||||||
className={`cursor-pointer relative px-4 pb-3 text-sm font-medium transition-all duration-200
|
className={`
|
||||||
|
relative
|
||||||
|
whitespace-nowrap
|
||||||
|
px-4
|
||||||
|
py-3
|
||||||
|
text-sm
|
||||||
|
font-medium
|
||||||
|
rounded-t-xl
|
||||||
|
transition-all
|
||||||
|
duration-200
|
||||||
|
cursor-pointer
|
||||||
|
|
||||||
${activeTab == tab.id
|
${activeTab == tab.id
|
||||||
? "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"
|
? `
|
||||||
: "text-gray-600 hover:text-[#34BEFF]"
|
text-[#34BEFF]
|
||||||
}`}
|
bg-[#EAF8FF]
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
text-slate-600
|
||||||
|
hover:text-[#34BEFF]
|
||||||
|
hover:bg-slate-50
|
||||||
|
`
|
||||||
|
}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
|
|
||||||
|
{activeTab == tab.id && (
|
||||||
|
<span
|
||||||
|
className="
|
||||||
|
absolute
|
||||||
|
bottom-0
|
||||||
|
left-0
|
||||||
|
w-full
|
||||||
|
h-[2px]
|
||||||
|
bg-[#34BEFF]
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</button>
|
</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]
|
||||||
|
xl:w-[320px]
|
||||||
|
border-r
|
||||||
|
border-slate-200
|
||||||
|
flex-col
|
||||||
|
bg-white
|
||||||
|
"
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
<div>
|
|
||||||
<p className="font-semibold">{user.name}</p>
|
<div className="min-w-0">
|
||||||
<p className="text-sm text-slate-600 w-[24ch] overflow-hidden text-ellipsis whitespace-nowrap">
|
<p className="font-semibold text-slate-900 truncate">
|
||||||
|
{user.name}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-sm text-slate-500 truncate">
|
||||||
{user.email}
|
{user.email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="sticky top-6 z-20 px-6 pt-2">
|
{/* Navigation */}
|
||||||
|
<div className="flex-1 overflow-y-auto no-scrollbar px-4 py-5">
|
||||||
<AsideSection
|
<AsideSection
|
||||||
activeSubSection={activeSubSection}
|
activeSubSection={activeSubSection}
|
||||||
onSubSectionClick={handleSubSectionClick}
|
onSubSectionClick={handleSubSectionClick}
|
||||||
subSections={getSubSections(activeTab)}
|
subSections={getSubSections(activeTab)}
|
||||||
mainTabTitle={tabs.find(t => t.id == activeTab)?.label || ""}
|
mainTabTitle={
|
||||||
|
tabs.find((t) => t.id == activeTab)?.label || ""
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 */}
|
||||||
|
{activeTab == "userProfile" && (
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
lg:hidden
|
||||||
|
sticky
|
||||||
|
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
|
||||||
|
"
|
||||||
|
>
|
||||||
{getSubSections(activeTab).map((sub) => (
|
{getSubSections(activeTab).map((sub) => (
|
||||||
<button
|
<button
|
||||||
key={sub.id}
|
key={sub.id}
|
||||||
onClick={() => handleSubSectionClick(sub.id)}
|
onClick={() =>
|
||||||
className={`whitespace-nowrap text-sm font-medium px-2 py-2 rounded-md cursor-pointer
|
handleSubSectionClick(sub.id)
|
||||||
${activeSubSection == sub.id ? "bg-blue-100 text-blue-600" : "text-gray-600"}
|
}
|
||||||
|
className={`
|
||||||
|
whitespace-nowrap
|
||||||
|
px-4
|
||||||
|
py-2
|
||||||
|
rounded-xl
|
||||||
|
text-sm
|
||||||
|
font-medium
|
||||||
|
transition-all
|
||||||
|
duration-200
|
||||||
|
border
|
||||||
|
|
||||||
|
${activeSubSection == sub.id
|
||||||
|
? `
|
||||||
|
bg-[#EAF8FF]
|
||||||
|
text-[#34BEFF]
|
||||||
|
border-[#B8EAFF]
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
bg-white
|
||||||
|
text-slate-600
|
||||||
|
border-slate-200
|
||||||
|
`
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{sub.label}
|
{sub.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Scroll Content */}
|
||||||
<div
|
<div
|
||||||
ref={scrollContainerRef}
|
ref={scrollContainerRef}
|
||||||
className="flex-1 min-w-0 overflow-y-auto no-scrollbar"
|
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()}
|
{renderContent()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
|
</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>
|
{/* 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>
|
||||||
|
|
||||||
|
<p className="text-sm text-slate-500 mt-1 max-w-xl">
|
||||||
|
Basic information like your name, email address, and account details.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setOpenEditModal(true)}
|
||||||
<div className="bg-slate-50 w-full px-3 py-2 rounded-lg">
|
className="
|
||||||
<p className="text-sm font-semibold">Information</p>
|
inline-flex
|
||||||
|
items-center
|
||||||
|
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>
|
||||||
|
|
||||||
|
{/* Section Tag */}
|
||||||
|
{/* <div className="bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 mb-5">
|
||||||
|
<p className="text-sm font-semibold text-slate-700">
|
||||||
|
Information
|
||||||
|
</p>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
{/* Info Card */}
|
||||||
|
<div className="rounded-2xl border border-slate-200 overflow-hidden divide-y divide-slate-100">
|
||||||
|
|
||||||
<div className="w-full border-gray-200 rounded-md bg-white font-sans">
|
{infoItems.map((item, index) => (
|
||||||
<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">
|
<div
|
||||||
<span className="text-gray-600">Full Name :</span>
|
key={index}
|
||||||
<span className="font-medium text-black">{userData.name}</span>
|
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 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>
|
|
||||||
<span className="font-medium text-black">{userData.email}</span>
|
|
||||||
</div>
|
|
||||||
<div className="sm:flex sm:flex-col md:grid md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] px-4 py-4">
|
|
||||||
<span className="text-gray-600">Account Name :</span>
|
|
||||||
<span className="font-medium text-black">{userData.account.name}</span>
|
|
||||||
</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