upd 26 sep
This commit is contained in:
parent
69778e81d9
commit
14ede77a16
6
.env
6
.env
|
|
@ -1,8 +1,10 @@
|
|||
VITE_BASE_URI="https://glider-precious-similarly.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://glider-precious-similarly.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://wired-terminally-muskrat.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://officially-powerful-goat.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://python-amused-adder.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://concrete-gladly-mullet.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://smashing-albacore-engaging.ngrok-free.app"
|
||||
VITE_BASE_URI="https://basically-polished-yeti.ngrok-free.app"
|
||||
|
||||
|
||||
# VITE_BASE_URI="http://192.168.2.176:8083"
|
||||
# VITE_BASE_URI="http://10.10.1.248:8083"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -24,6 +24,7 @@ import JoyrideController from "./utlity/JoyRideTour";
|
|||
import Contacts from "./components/contacts/Contacts";
|
||||
import TransactionSentSuccess from "./components/process/CompletionScreen/TransactionSentSuccess";
|
||||
import CompletionScreen from "./components/client/CompletionScreen";
|
||||
import SessionExpired from "./components/client/SessionExpired";
|
||||
const App = () => {
|
||||
|
||||
return (
|
||||
|
|
@ -108,6 +109,7 @@ const App = () => {
|
|||
<Route path="/sign/:recipientId/:signType" element={<SignDocument />} />
|
||||
<Route path="/sign/:recipientId/" element={<SignDocument />} />
|
||||
<Route path="/view" element={<DocView />} />
|
||||
<Route path="/session_expired" element={<SessionExpired />} />
|
||||
|
||||
{/* Links work wothout Login */}
|
||||
</Routes>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react'
|
||||
import { IoMdClose } from 'react-icons/io'
|
||||
import downloadDocumentUtils from '../../utlity/downloadDocumentsUtils';
|
||||
import { DownloadTypesEnum } from './TransactionDataTable';
|
||||
|
||||
interface DownloadDocumentInt {
|
||||
showMenu: { open: boolean, transactionId: string | null },
|
||||
setShowMenu: React.Dispatch<{ open: boolean, transactionId: string | null }>,
|
||||
accountId: string
|
||||
}
|
||||
|
||||
const DownloadDocument: React.FC<DownloadDocumentInt> = ({ showMenu, setShowMenu, accountId }) => {
|
||||
|
||||
|
||||
function handleDownloadFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const docType = formData.get('download-doc-type');
|
||||
const includeCertificate = formData.get('download-doc-type-include-cert');
|
||||
|
||||
downloadDocumentUtils(accountId, showMenu.transactionId as string, docType as string, includeCertificate as string | null)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{showMenu.open && (
|
||||
<form onSubmit={(e) => handleDownloadFormSubmit(e)} className='fixed inset-0 z-50'>
|
||||
<div className='absolute inset-0 bg-black/40' onClick={() => setShowMenu({ open: false, transactionId: null })}></div>
|
||||
<div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90%] max-w-md rounded-lg bg-white shadow-lg'>
|
||||
<div className='flex items-center justify-between px-5 py-4 border-b border-slate-200'>
|
||||
<p className='font-semibold text-lg'>Download Document</p>
|
||||
<button aria-label='Close download dialog' className='p-1 rounded hover:bg-slate-100' onClick={() => setShowMenu({ open: false, transactionId: null })}>
|
||||
<IoMdClose className='text-xl cursor-pointer' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='px-5 py-4'>
|
||||
<fieldset>
|
||||
<legend className='text-sm font-medium text-slate-700 mb-3'>Choose file to download</legend>
|
||||
<div className='space-y-2'>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' value={DownloadTypesEnum.ALL} />
|
||||
<span>All</span>
|
||||
</label>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' defaultChecked value={DownloadTypesEnum.COMBINED} />
|
||||
<span>Combined</span>
|
||||
</label>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' defaultChecked value={DownloadTypesEnum.CERTIFICATE} />
|
||||
<span>Certificate</span>
|
||||
</label>
|
||||
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='checkbox' className='cursor-pointer' name='download-doc-type-include-cert' value='?include_certificate=true' />
|
||||
<span>Include Certificate</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className='flex justify-end gap-2 mt-5'>
|
||||
<button className='cursor-pointer px-4 py-2 rounded border border-slate-200 text-slate-700 hover:bg-slate-50' onClick={() => setShowMenu({ open: false, transactionId: null })}>Cancel</button>
|
||||
<button className='cursor-pointer px-4 py-2 rounded bg-[var(--mainPrimaryBtn)] text-white hover:opacity-90'>Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DownloadDocument
|
||||
|
|
@ -8,6 +8,7 @@ import logoRecord from '../../assets/record.svg';
|
|||
import { MdOutlineFileDownload } from 'react-icons/md';
|
||||
import downloadDocumentUtils from '../../utlity/downloadDocumentsUtils';
|
||||
import { IoMdClose } from 'react-icons/io';
|
||||
import DownloadDocument from './DownloadDocument';
|
||||
|
||||
const formatDate = (timestamp: number) => {
|
||||
return new Date(timestamp * 1000).toLocaleString();
|
||||
|
|
@ -20,6 +21,12 @@ const statusColorMap = {
|
|||
EXPIRED: "bg-red-100 text-red-600",
|
||||
};
|
||||
|
||||
export enum DownloadTypesEnum {
|
||||
ALL = "archived",
|
||||
COMBINED = "combined",
|
||||
CERTIFICATE = "certificate"
|
||||
}
|
||||
|
||||
|
||||
const TransactionDataTable = ({ activeAccountId }) => {
|
||||
const navigate = useNavigate()
|
||||
|
|
@ -34,11 +41,7 @@ const TransactionDataTable = ({ activeAccountId }) => {
|
|||
});
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
enum DownloadTypesEnum {
|
||||
ALL = "archived",
|
||||
COMBINED = "combined",
|
||||
CERTIFICATE = "certificate"
|
||||
}
|
||||
|
||||
|
||||
const [downloadDocumentType, setDownloadDocumentType] = useState({
|
||||
type: DownloadTypesEnum.ALL,
|
||||
|
|
@ -98,7 +101,7 @@ const TransactionDataTable = ({ activeAccountId }) => {
|
|||
|
||||
function handleDownloadFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const docType = formData.get('download-doc-type');
|
||||
const includeCertificate = formData.get('download-doc-type-include-cert');
|
||||
|
|
@ -106,8 +109,6 @@ const TransactionDataTable = ({ activeAccountId }) => {
|
|||
downloadDocumentUtils(activeAccountId, showMenu.transactionId as string, docType as string, includeCertificate as string | null)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
|
|
@ -148,7 +149,7 @@ const TransactionDataTable = ({ activeAccountId }) => {
|
|||
{transactions.map((tx, idx) => (
|
||||
<tr
|
||||
key={tx.id}
|
||||
// onClick={() => handleTransactionClick(tx.id)}
|
||||
// onClick={() => handleTransactionClick(tx.id)}
|
||||
>
|
||||
<td className={`${idx % 2 !== 0 ? "bg-[#F8F9FC]" : ""} font-bold px-6 py-4 text-left`}>
|
||||
<div className="text-gray-800 font-medium truncate max-w-xs cursor-pointer"
|
||||
|
|
@ -286,53 +287,11 @@ const TransactionDataTable = ({ activeAccountId }) => {
|
|||
|
||||
|
||||
{showMenu.open && (
|
||||
<form onSubmit={(e) => handleDownloadFormSubmit(e)} className='fixed inset-0 z-50'>
|
||||
<div className='absolute inset-0 bg-black/40' onClick={() => setShowMenu({open: false, transactionId: null})}></div>
|
||||
<div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90%] max-w-md rounded-lg bg-white shadow-lg'>
|
||||
<div className='flex items-center justify-between px-5 py-4 border-b border-slate-200'>
|
||||
<p className='font-semibold text-lg'>Download Document</p>
|
||||
<button aria-label='Close download dialog' className='p-1 rounded hover:bg-slate-100' onClick={() => setShowMenu({open: false, transactionId: null})}>
|
||||
<IoMdClose className='text-xl cursor-pointer' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='px-5 py-4'>
|
||||
<fieldset>
|
||||
<legend className='text-sm font-medium text-slate-700 mb-3'>Choose file to download</legend>
|
||||
<div className='space-y-2'>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' value={DownloadTypesEnum.ALL} />
|
||||
<span>All</span>
|
||||
</label>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' defaultChecked value={DownloadTypesEnum.COMBINED} />
|
||||
<span>Combined</span>
|
||||
</label>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' defaultChecked value={DownloadTypesEnum.CERTIFICATE} />
|
||||
<span>Certificate</span>
|
||||
</label>
|
||||
{/* <label className='flex items-center gap-2'>
|
||||
<select className='w-full' name="" id="" multiple>
|
||||
<option value="123.pdf">123.pdf</option>
|
||||
<option value="123.pdf">123.pdf</option>
|
||||
<option value="123.pdf">123.pdf</option>
|
||||
<option value="123.pdf">123.pdf</option>
|
||||
</select>
|
||||
<span>Custom</span>
|
||||
</label> */}
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='checkbox' className='cursor-pointer' name='download-doc-type-include-cert' value='?include_certificate=true' />
|
||||
<span>Include Certificate</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className='flex justify-end gap-2 mt-5'>
|
||||
<button className='cursor-pointer px-4 py-2 rounded border border-slate-200 text-slate-700 hover:bg-slate-50' onClick={() => setShowMenu({open: false, transactionId: null})}>Cancel</button>
|
||||
<button className='cursor-pointer px-4 py-2 rounded bg-[var(--mainPrimaryBtn)] text-white hover:opacity-90'>Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<DownloadDocument
|
||||
showMenu={showMenu}
|
||||
setShowMenu={setShowMenu}
|
||||
accountId={activeAccountId}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import OAuthGoogle from "./OAuthGoogle";
|
|||
// import Cookies from "js-cookie";
|
||||
import { BsEye } from "react-icons/bs";
|
||||
import { FiEyeOff } from "react-icons/fi";
|
||||
import { TokenStorage } from "../../config/api";
|
||||
// import api from "../../config/api";
|
||||
|
||||
interface FormDataState {
|
||||
|
|
@ -115,8 +116,9 @@ const LoginForm = ({ onSwitchToRegister }) => {
|
|||
);
|
||||
|
||||
if (res.status === 200) {
|
||||
navigate("/");
|
||||
TokenStorage.setTokens(res.data.accessToken, res.data.refreshToken)
|
||||
formErrorRef.current = { message: "Login Successfully", error: false }
|
||||
navigate("/");
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.response?.status === 400 || err.response?.status === 401) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
import React from 'react'
|
||||
import { IoMdClose } from 'react-icons/io'
|
||||
import downloadDocumentUtils from '../../utlity/downloadDocumentsUtils';
|
||||
|
||||
interface DownloadDocumentInt {
|
||||
showMenuDownload: { open: boolean, transactionId: string | null },
|
||||
setShowMenuDownload: React.Dispatch<{ open: boolean, transactionId: string | null }>,
|
||||
accountId: string
|
||||
}
|
||||
|
||||
enum DownloadTypesEnum {
|
||||
ALL = "archived",
|
||||
COMBINED = "combined",
|
||||
CERTIFICATE = "certificate"
|
||||
}
|
||||
|
||||
const ClientDownloadDocument: React.FC<DownloadDocumentInt> = ({ showMenuDownload, setShowMenuDownload, accountId }) => {
|
||||
|
||||
|
||||
function handleDownloadFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const docType = formData.get('download-doc-type');
|
||||
const includeCertificate = formData.get('download-doc-type-include-cert');
|
||||
|
||||
downloadDocumentUtils(accountId, showMenuDownload.transactionId as string, docType as string, includeCertificate as string | null)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{showMenuDownload.open && (
|
||||
<form onSubmit={(e) => handleDownloadFormSubmit(e)} className='fixed inset-0 z-50'>
|
||||
<div className='absolute inset-0 bg-black/40' onClick={() => setShowMenuDownload({ open: false, transactionId: null })}></div>
|
||||
<div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90%] max-w-md rounded-lg bg-white shadow-lg'>
|
||||
<div className='flex items-center justify-between px-5 py-4 border-b border-slate-200'>
|
||||
<p className='font-semibold text-lg'>Download Document</p>
|
||||
<button aria-label='Close download dialog' className='p-1 rounded hover:bg-slate-100' onClick={() => setShowMenuDownload({ open: false, transactionId: null })}>
|
||||
<IoMdClose className='text-xl cursor-pointer' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='px-5 py-4'>
|
||||
<fieldset>
|
||||
<legend className='text-sm font-medium text-slate-700 mb-3'>Choose file to download</legend>
|
||||
<div className='space-y-2'>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' value={DownloadTypesEnum.ALL} />
|
||||
<span>All</span>
|
||||
</label>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' defaultChecked value={DownloadTypesEnum.COMBINED} />
|
||||
<span>Combined</span>
|
||||
</label>
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='radio' className='cursor-pointer' name='download-doc-type' defaultChecked value={DownloadTypesEnum.CERTIFICATE} />
|
||||
<span>Certificate</span>
|
||||
</label>
|
||||
|
||||
<label className='cursor-pointer flex items-center gap-2'>
|
||||
<input type='checkbox' className='cursor-pointer' name='download-doc-type-include-cert' value='?include_certificate=true' />
|
||||
<span>Include Certificate</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className='flex justify-end gap-2 mt-5'>
|
||||
<button className='cursor-pointer px-4 py-2 rounded border border-slate-200 text-slate-700 hover:bg-slate-50' onClick={() => setShowMenuDownload({ open: false, transactionId: null })}>Cancel</button>
|
||||
<button className='cursor-pointer px-4 py-2 rounded bg-[var(--mainPrimaryBtn)] text-white hover:opacity-90'>Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClientDownloadDocument
|
||||
|
|
@ -23,10 +23,10 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<header className="p-4 flex justify-center sm:justify-start items-center bg-white shadow-md z-10">
|
||||
<header className="px-4 py-3 flex justify-center sm:justify-start items-center bg-white shadow-md z-10">
|
||||
<div className="flex items-center gap-2">
|
||||
<RiFileList3Line className="text-[var(--mainPrimaryBtn)] text-2xl" />
|
||||
<span className="text-lg font-semibold text-[var(--mainPrimaryBtn)]">
|
||||
<span className="text-md font-semibold text-[var(--mainPrimaryBtn)]">
|
||||
Sign made easy
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -34,10 +34,10 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
|
||||
{/* Main content */}
|
||||
<main className="flex flex-col items-center text-center px-4 flex-grow">
|
||||
<h1 className={`text-2xl md:text-4xl font-bold mt-6`}>
|
||||
<h1 className={`text-xl md:text-3xl 2xl:text-5xl 2xl:mt-10 font-bold mt-6`}>
|
||||
<span className={`${textColorSuccess ? "text-[var(--mainPrimaryBtn)]" : "text-red-400"}`}>{hit}</span>{title}
|
||||
</h1>
|
||||
<p className="mt-3.5 max-w-xl">
|
||||
<p className="mt-2 max-w-xl 2xl:mt-4">
|
||||
{message}
|
||||
</p>
|
||||
|
||||
|
|
@ -49,42 +49,42 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
{/* <button className="mt-6 flex items-center gap-2 bg-orange-500 text-white px-6 py-2 rounded-md hover:bg-orange-600 transition">
|
||||
<FiZap /> Login
|
||||
</button> */}
|
||||
<p className="font-semibold mt-6 text-md">
|
||||
<p className="font-semibold mt-4 2xl:mt-6 text-md 2xl:text-xl">
|
||||
Access your documents anytime — just <a href="/login" className="text-white bg-[var(--mainPrimaryBtn)] px-3 py-1 rounded-sm">Login</a>
|
||||
</p>
|
||||
|
||||
|
||||
{/* Illustration placeholder */}
|
||||
<div className="mt-14">
|
||||
<div className="mt-6 2xl:mt-10">
|
||||
{textColorSuccess ? <img
|
||||
src="/completion-screen-1.png"
|
||||
alt="Document completed"
|
||||
className="w-60 md:w-64 lg:w-80"
|
||||
className="w-30 md:w-34 lg:w-42 2xl:w-70"
|
||||
/> :
|
||||
<img
|
||||
src="/completion-screen-2.png"
|
||||
alt="Document completed"
|
||||
className="w-60 md:w-64 lg:w-80"
|
||||
className="w-30 md:w-34 lg:w-42 2xl:w-70"
|
||||
/>}
|
||||
</div>
|
||||
|
||||
{/* Apps Section */}
|
||||
<section className="relative z-10 top-10 w-full max-w-5xl">
|
||||
<h2 className="text-2xl font-semibold text-gray-800 mb-6">
|
||||
<section className="relative z-10 top-6 2xl:top-10 w-full">
|
||||
<h2 className="text-xl font-semibold text-gray-800 mb-4 2xl:text-2xl">
|
||||
Boost Efficiency with Our Apps
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="w-full flex flex-col sm:flex-row gap-2 justify-center items-center 2xl:gap-4 2xl:mt-6">
|
||||
|
||||
{/* Forms Made Easy */}
|
||||
<div className="bg-white rounded-lg shadow 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-3">
|
||||
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
|
||||
Generate Forms with Ease
|
||||
</p>
|
||||
<div className="w-full">
|
||||
<div className="w-full flex flex-col justify-start items-start sm:flex-row sm:justify-between sm:items-center">
|
||||
<img src="/forms-made-easy.png" className="w-[60%]" alt="Docs Made Easy" />
|
||||
<button className="mt-4 sm:mt-0 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
<img src="/forms-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
|
||||
<button className="text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
Try Now
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -94,15 +94,15 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Docs Made Easy */}
|
||||
<div className="bg-white rounded-lg shadow 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-3">
|
||||
<p className="text-sm font-semibold text-left mb-2 2xl:text-lg">
|
||||
Generate Documents (Invoices, Reports, Quotes)
|
||||
</p>
|
||||
<div className="w-full">
|
||||
<div className="w-full flex flex-col justify-start items-start sm:flex-row sm:justify-between sm:items-center">
|
||||
<img src="/docs-made-easy.png" className="w-[60%]" alt="Docs Made Easy" />
|
||||
<button className="mt-4 sm:mt-0 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
<img src="/docs-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
|
||||
<button className="text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
Try Now
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -116,8 +116,8 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="text-center text-sm py-6 bg-white mt-[80px] ">
|
||||
<p className="px-4 font-semibold">
|
||||
<footer className="relative bottom-0 text-center text-sm bg-[#fff] opacity-100 py-2 z-50 2xl:py-3">
|
||||
<p className="px-4 font-semibold text-xs 2xl:text-sm">
|
||||
Copyright 2025 Sign Made Easy. All Rights Reserved.{" "}
|
||||
<a href="#" className="text-[var(--mainPrimaryBtn)] hover:underline">
|
||||
Privacy Policy
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
|||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
return (
|
||||
<div className='p-4'>
|
||||
|
||||
<div className="flex flex-col gap-2 pb-[6rem]">
|
||||
{selectedDocuments && selectedDocuments.length && selectedDocuments.map((document, indx) => {
|
||||
return (
|
||||
|
|
@ -103,7 +101,6 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
|||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react'
|
||||
import CompletionScreen from './CompletionScreen'
|
||||
|
||||
const SessionExpired = () => {
|
||||
return (
|
||||
<>
|
||||
<CompletionScreen
|
||||
|
||||
hit={"Session Expired!"}
|
||||
title={" Your Session is expired"}
|
||||
message={"Revisit OR request new URL"}
|
||||
textColorSuccess={false}
|
||||
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SessionExpired
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState, useRef, useEffect } from "react";
|
||||
import { FaSignature, FaDownload, FaUserEdit, FaTimesCircle, FaAngleLeft, FaAngleRight } from "react-icons/fa";
|
||||
import { BsThreeDotsVertical } from "react-icons/bs";
|
||||
import ClientDownloadDocument from "./ClientDownloadDocument";
|
||||
|
||||
interface Props {
|
||||
transparentTags?: boolean;
|
||||
|
|
@ -22,6 +23,11 @@ export default function SignButton({
|
|||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [showMenuDownload, setShowMenuDownload] = useState<{ open: boolean, transactionId: string | null }>({
|
||||
open: false,
|
||||
transactionId: null
|
||||
});
|
||||
|
||||
// Close menu on outside click
|
||||
useEffect(() => {
|
||||
function handleClickOutside(e: MouseEvent) {
|
||||
|
|
@ -65,7 +71,7 @@ export default function SignButton({
|
|||
className="absolute right-0 top-10 w-56 bg-white border border-gray-200 rounded-lg shadow-lg z-10"
|
||||
>
|
||||
<ul className="py-1 text-sm text-gray-700">
|
||||
<li>
|
||||
{/* <li>
|
||||
<button
|
||||
onClick={() => {
|
||||
onDownloadClick?.();
|
||||
|
|
@ -76,7 +82,7 @@ export default function SignButton({
|
|||
<FaDownload className="" />
|
||||
<span>Download Document</span>
|
||||
</button>
|
||||
</li>
|
||||
</li> */}
|
||||
<li>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
@ -109,6 +115,20 @@ export default function SignButton({
|
|||
<p className="text-xs text-green-600 font-medium">Signed Successfully</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{showMenuDownload.open && (
|
||||
<ClientDownloadDocument
|
||||
showMenuDownload={showMenuDownload}
|
||||
setShowMenuDownload={setShowMenuDownload}
|
||||
accountId={activeAccountId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ import { Link, useNavigate, useParams } from "react-router-dom"
|
|||
import { DroppedItem, TagsTypes } from "../../utlity/types";
|
||||
|
||||
import Loader from "../dashboard/Loader";
|
||||
import SignCon from "../signature/SignCon";
|
||||
import ClientSignCon from "../signature/ClientSignCon";
|
||||
import { Bounce, toast } from "react-toastify";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
|
||||
|
||||
import { FaSignature, FaCheck, FaQuestionCircle, FaDownload, FaAngleLeft, FaAngleRight } from 'react-icons/fa'
|
||||
import { IoIosDocument, IoMdHome } from 'react-icons/io'
|
||||
import { MdKeyboardArrowDown } from 'react-icons/md'
|
||||
import { MdKeyboardArrowDown, MdLabel } from 'react-icons/md'
|
||||
import confetti from 'canvas-confetti'
|
||||
|
||||
import dayjs from "dayjs";
|
||||
|
|
@ -33,6 +33,8 @@ import ReassignModal from "./ReassignModal";
|
|||
import { ImCancelCircle } from "react-icons/im";
|
||||
import ReassignSuccess from "./ReassignSuccess";
|
||||
import CompletionScreen from "./CompletionScreen";
|
||||
import { BsPrinter } from "react-icons/bs";
|
||||
import { GrPrint } from "react-icons/gr";
|
||||
|
||||
|
||||
|
||||
|
|
@ -95,6 +97,7 @@ export interface SaveType {
|
|||
save: boolean;
|
||||
signature: string | null;
|
||||
initial: string | null;
|
||||
isDefaultAndClicked?: boolean;
|
||||
}
|
||||
interface TagsSubmitDataObj {
|
||||
tags: TagSubmitData[];
|
||||
|
|
@ -161,6 +164,11 @@ const SignDocument = () => {
|
|||
const [signTagId, setSignTagId] = useState<number>(0)
|
||||
const [showConfetti, setShowConfetti] = useState<boolean>(false)
|
||||
|
||||
const [pageScales, setPageScales] = useState<Record<string, { x: number; y: number }>>({});
|
||||
|
||||
|
||||
const [requiredFilled, setRequiredFilled] = useState<boolean>(false);
|
||||
|
||||
const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" });
|
||||
|
||||
const [signerConsent, setSignerConsent] = useState<{ consent: boolean; decline: boolean }>({
|
||||
|
|
@ -200,6 +208,14 @@ const SignDocument = () => {
|
|||
initial: null,
|
||||
});
|
||||
|
||||
enum NavigationTagPosEnum {
|
||||
ABOVE = "above",
|
||||
BELOW = "below",
|
||||
VISIBLE = "VISIBLE"
|
||||
}
|
||||
|
||||
const [navigationTagPos, setNavigationTagPos] = useState<NavigationTagPosEnum>(NavigationTagPosEnum.VISIBLE);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
|
|
@ -238,17 +254,17 @@ const SignDocument = () => {
|
|||
|
||||
if (focusElement === null || !("focusElementIndx" in focusElement)) return;
|
||||
|
||||
const docCon = document.getElementById("doc-con");
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
// const docCon = document.getElementById("doc-con");
|
||||
// document.addEventListener("keydown", (e) => {
|
||||
// if (e.key === "Tab") {
|
||||
// e.preventDefault();
|
||||
|
||||
console.log("Listening Tab Key Press")
|
||||
let focusIndx = focusElement?.focusElementIndx;
|
||||
if (focusIndx >= droppedItems.length - 1) focusIndx = -1
|
||||
setFocusElement({ ...droppedItems[++focusIndx], focusElementIndx: ++focusIndx })
|
||||
}
|
||||
})
|
||||
// console.log("Listening Tab Key Press")
|
||||
// let focusIndx = focusElement?.focusElementIndx;
|
||||
// if (focusIndx >= droppedItems.length - 1) focusIndx = -1
|
||||
// setFocusElement({ ...droppedItems[++focusIndx], focusElementIndx: ++focusIndx })
|
||||
// }
|
||||
// })
|
||||
|
||||
}
|
||||
}, [droppedItems])
|
||||
|
|
@ -311,7 +327,7 @@ const SignDocument = () => {
|
|||
}, [saved])
|
||||
|
||||
const fetchData = (isSign: boolean = false) => {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`,
|
||||
axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign/${recipientId}`,
|
||||
{
|
||||
headers:
|
||||
{ "ngrok-skip-browser-warning": "69420", }
|
||||
|
|
@ -320,7 +336,13 @@ const SignDocument = () => {
|
|||
if (res.status === 200) {
|
||||
setSelectedDocuments(res?.data?.documents)
|
||||
setRecipient(res?.data?.details)
|
||||
setDroppedItems(res?.data?.tagJsons)
|
||||
|
||||
const sortedDroppedItems = res?.data?.tagJsons.sort((a, b) => a.navigationOrder - b.navigationOrder);
|
||||
setDroppedItems(sortedDroppedItems)
|
||||
|
||||
setFocusElement({ ...sortedDroppedItems[0], focusElementIndx: 0 });
|
||||
|
||||
|
||||
setTransaction(res?.data?.transaction)
|
||||
setRecSignStatus(res?.data?.status)
|
||||
|
||||
|
|
@ -330,7 +352,7 @@ const SignDocument = () => {
|
|||
setProvideTags(true)
|
||||
}
|
||||
|
||||
if(windowWidth > 638) {
|
||||
if (windowWidth > 638) {
|
||||
setIsFields(false)
|
||||
}
|
||||
|
||||
|
|
@ -365,7 +387,7 @@ const SignDocument = () => {
|
|||
});
|
||||
}
|
||||
|
||||
const handleFormSubmit = () => {
|
||||
const handleFormSubmit = async () => {
|
||||
const submitData: TagsSubmitDataObj = {
|
||||
tags: []
|
||||
}
|
||||
|
|
@ -394,9 +416,9 @@ const SignDocument = () => {
|
|||
const tagData: TagSubmitData = {
|
||||
id: tag.id as string,
|
||||
type: tag.type,
|
||||
subType: saved.saveType as string,
|
||||
// subType: saved.saveType as string,
|
||||
signed: true,
|
||||
value: ""
|
||||
value: saved.signatureId as string
|
||||
}
|
||||
submitData.tags.push(tagData)
|
||||
} else {
|
||||
|
|
@ -412,20 +434,23 @@ const SignDocument = () => {
|
|||
}
|
||||
|
||||
// return;
|
||||
try {
|
||||
dispatch(showLoader("Signing ..."));
|
||||
await axios.put(`${import.meta.env.VITE_BASE_URI}/api/sign/${recipientId}/tags`, submitData, { headers: { "ngrok-skip-browser-warning": "69420", } })
|
||||
|
||||
const signRes = await axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/${recipientId}`, {}, { headers: { "ngrok-skip-browser-warning": "69420", } })
|
||||
dispatch(hideLoader());
|
||||
if (signRes.status === 200) {
|
||||
dispatch(hideLoader());
|
||||
setTransparentTags(true)
|
||||
setShowConfetti(true)
|
||||
setShowCompletionScreen(true)
|
||||
setSelectedTag(null)
|
||||
}
|
||||
} catch (e) {
|
||||
dispatch(hideLoader());
|
||||
}
|
||||
|
||||
dispatch(showLoader("Signing ..."));
|
||||
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`, submitData, { headers: { "ngrok-skip-browser-warning": "69420", } })
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
dispatch(hideLoader());
|
||||
setTransparentTags(true)
|
||||
setShowConfetti(true)
|
||||
setShowCompletionScreen(true)
|
||||
setSelectedTag(null)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -442,11 +467,11 @@ const SignDocument = () => {
|
|||
// If FocusElement is present updating in Based on Next and Prev
|
||||
const currFocusElemIndx = focusElement.focusElementIndx;
|
||||
if (currFocusElemIndx === undefined) return;
|
||||
const updIndx = (action === "N") ? (currFocusElemIndx + 1) : (currFocusElemIndx - 1)
|
||||
let updIndx = (action === "N") ? (currFocusElemIndx + 1) : (currFocusElemIndx - 1)
|
||||
|
||||
if (updIndx >= droppedItems.length || updIndx < 0 || !droppedItems[updIndx].id) return
|
||||
if (updIndx >= droppedItems.length || updIndx < 0 || !droppedItems[updIndx].id) updIndx = 0;
|
||||
|
||||
scrollToElement(droppedItems[updIndx].id)
|
||||
scrollToElement(droppedItems[updIndx].navigationOrder)
|
||||
setFocusElement({ ...droppedItems[updIndx], focusElementIndx: updIndx })
|
||||
}
|
||||
|
||||
|
|
@ -462,7 +487,7 @@ const SignDocument = () => {
|
|||
|
||||
const fetchSignatureData = async (signatureId: string) => {
|
||||
try {
|
||||
const response = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign/signatures/${signatureId}?recipientId=${recipientId}`, {
|
||||
const response = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign/${recipientId}signatures/${signatureId}`, {
|
||||
headers: { "ngrok-skip-browser-warning": "69420" }
|
||||
});
|
||||
if (response.status === 200) {
|
||||
|
|
@ -525,6 +550,17 @@ const SignDocument = () => {
|
|||
}, [droppedItems.length])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const allRequiredFields = droppedItems.filter((item, idx) => {
|
||||
if (!item.required) return false
|
||||
if ((item.required && item.value) || idx in signConfObj) return false
|
||||
return true
|
||||
})
|
||||
|
||||
setRequiredFilled(allRequiredFields.length ? false : true)
|
||||
}, [droppedItems, signConfObj])
|
||||
|
||||
|
||||
// HANDLE DROP - START
|
||||
const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -626,7 +662,7 @@ const SignDocument = () => {
|
|||
|
||||
const handleDeclineConfirm = async (reason: string, comments: string) => {
|
||||
try {
|
||||
await axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/decline?recipientId=${recipientId}`, { reason: reason + ", " + comments });
|
||||
await axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/decline/${recipientId}`, { reason: reason + ", " + comments });
|
||||
setSignerConsent((prev) => ({ ...prev, decline: true }));
|
||||
setRecSignStatus("DECLINED");
|
||||
setShowCompletionScreen(true);
|
||||
|
|
@ -634,7 +670,7 @@ const SignDocument = () => {
|
|||
}
|
||||
|
||||
const handleReassignConfirm = (data: { firstName: string; lastName: string; email: string; message: string }) => {
|
||||
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/reassign?recipientId=${recipientId}`, {
|
||||
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/reassign/${recipientId}`, {
|
||||
name: data.firstName ? data.firstName + " " + data.lastName : data.lastName,
|
||||
email: data.email,
|
||||
message: data.message,
|
||||
|
|
@ -650,6 +686,76 @@ const SignDocument = () => {
|
|||
});
|
||||
}
|
||||
|
||||
// Simple throttle
|
||||
function throttle(fn: Function, wait: number) {
|
||||
let lastTime = 0;
|
||||
return (...args: any[]) => {
|
||||
const now = Date.now();
|
||||
if (now - lastTime >= wait) {
|
||||
lastTime = now;
|
||||
fn(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Utility to check if element is out of viewport
|
||||
function isOutOfViewport(el: HTMLElement) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const windowHeight = window.innerHeight;
|
||||
const windowWidth = window.innerWidth;
|
||||
|
||||
return (
|
||||
rect.right < 0 ||
|
||||
rect.bottom < 0 ||
|
||||
rect.left > windowWidth ||
|
||||
rect.top > windowHeight
|
||||
);
|
||||
}
|
||||
|
||||
function printDocConDiv() {
|
||||
var printContents = document.getElementById(divId).innerHTML;
|
||||
var originalContents = document.body.innerHTML;
|
||||
|
||||
document.body.innerHTML = printContents;
|
||||
|
||||
window.print();
|
||||
|
||||
document.body.innerHTML = originalContents;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const docCon = document.getElementById("doc-con");
|
||||
if (!docCon) return;
|
||||
|
||||
|
||||
const checkPosition = throttle(() => {
|
||||
let el = document.getElementById("tag-navigation-btn");
|
||||
if (!el) return;
|
||||
const elRect = el.getBoundingClientRect();
|
||||
const containerRect = docCon.getBoundingClientRect();
|
||||
|
||||
if (elRect.bottom < containerRect.top) {
|
||||
setNavigationTagPos(NavigationTagPosEnum.ABOVE)
|
||||
} else if (elRect.top > containerRect.bottom) {
|
||||
setNavigationTagPos(NavigationTagPosEnum.BELOW)
|
||||
} else {
|
||||
setNavigationTagPos(NavigationTagPosEnum.VISIBLE)
|
||||
}
|
||||
}, 150);
|
||||
|
||||
// Initial check
|
||||
checkPosition();
|
||||
|
||||
docCon.addEventListener("scroll", checkPosition);
|
||||
window.addEventListener("resize", checkPosition); // container may resize
|
||||
|
||||
return () => {
|
||||
docCon.removeEventListener("scroll", checkPosition);
|
||||
window.removeEventListener("resize", checkPosition);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
if (showCompletionScreen) {
|
||||
if (recSignStatus === "COMPLETED") {
|
||||
return <CompletionScreen />
|
||||
|
|
@ -668,40 +774,12 @@ const SignDocument = () => {
|
|||
<div className="bg-slate-100"
|
||||
// onSubmit={(e: React.FormEvent<HTMLFormElement>) => handleFormSubmit(e)}
|
||||
>
|
||||
|
||||
|
||||
|
||||
{/* Nav Two - Options - START */}
|
||||
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)]'>
|
||||
|
||||
{/* Left Side */}
|
||||
<div className='flex justify-start items-center gap-1'>
|
||||
|
||||
{/* <div className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm max-w-max'> */}
|
||||
{/* <p className='tracking-wide font-semibold text-[1rem] text-[var(--primary)]'> */}
|
||||
{/* Sign Made Easy */}
|
||||
{/* </p> */}
|
||||
{/* </div> */}
|
||||
|
||||
<img className="w-[50%] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
|
||||
|
||||
{provideTags && recipient?.type !== "CC" && <div
|
||||
onClick={() => handleSetIsFields(true)}
|
||||
className={`flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ${isFields ? 'bg-slate-200' : 'bg-white'}`}>
|
||||
<IoIosDocument />
|
||||
<p className='tracking-wide'>Fields</p>
|
||||
</div>}
|
||||
|
||||
<div>
|
||||
<p
|
||||
onClick={() => handleSetIsFields(provideTags ? false : !isFields)}
|
||||
className={` flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ${!isFields ? 'bg-slate-200' : 'bg-white'} ${!provideTags ? "sm:hidden" : ""}`}>
|
||||
<IoIosDocument />
|
||||
<span className='tracking-wide'>Pages</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* <p className="hidden sm:block text-xs text-white">Hi! {recipient && recipient.firstName} {recipient && recipient.lastName}</p> */}
|
||||
</div>
|
||||
|
||||
|
||||
<img className="w-[10%] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
|
||||
|
||||
{/* Right Side - STARTS */}
|
||||
<div className='flex justify-center items-center'>
|
||||
|
|
@ -713,10 +791,10 @@ const SignDocument = () => {
|
|||
className=""
|
||||
tabIndex={1}
|
||||
onBlur={() => setNav2DocToggle(false)}>
|
||||
<div onClick={() => setNav2DocToggle(!nav2DocToggle)} className='bg-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
{/* <div onClick={() => setNav2DocToggle(!nav2DocToggle)} className='bg-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
<p className='tracking-wide flex gap-1'><span>{selectedDocuments && selectedDocuments.length}</span> <span>Documents</span></p>
|
||||
<MdKeyboardArrowDown className={`${nav2DocToggle ? 'rotate-180' : ''} transition-all`} />
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
|
||||
<div
|
||||
|
|
@ -737,37 +815,15 @@ const SignDocument = () => {
|
|||
}
|
||||
</>
|
||||
|
||||
{/* FIT - START */}
|
||||
<div
|
||||
className="hidden sm:block"
|
||||
tabIndex={1}
|
||||
onBlur={() => setIsFitDd(false)}
|
||||
>
|
||||
<div
|
||||
onClick={() => setIsFitDd(!isFitDd)}
|
||||
className='bg-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'>
|
||||
<p className='tracking-wide'>Fit</p>
|
||||
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
||||
</div>
|
||||
{/* PRINT _ START */}
|
||||
<GrPrint
|
||||
onClick={printDocConDiv}
|
||||
className="text-white mr-2 cursor-pointer" />
|
||||
{/* PRINT _ END */}
|
||||
|
||||
|
||||
|
||||
{isFitDd && <div className='absolute bg-white rounded-sm z-50'>
|
||||
{fitObj.map((val, indx) => {
|
||||
return (<div
|
||||
key={indx}
|
||||
onClick={() => setSelFitIndx(indx)}
|
||||
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
|
||||
<div
|
||||
|
||||
className='flex items-center justify-between w-full'>
|
||||
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{val.label}</p>
|
||||
{selFitIndx === indx && <FaCheck className='text-sm text-slate-400' />}
|
||||
</div>
|
||||
</div>)
|
||||
})
|
||||
}
|
||||
</div>}
|
||||
</div>
|
||||
{/* FIT - END */}
|
||||
|
||||
{/* Send */}
|
||||
{/* {(!transparentTags && !hideTags) ? <button
|
||||
|
|
@ -801,9 +857,9 @@ const SignDocument = () => {
|
|||
|
||||
|
||||
<>
|
||||
<div className={`absolute z-50 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] bottom-0 left-0 bg-white px-4 py-2 flex justify-between items-center gap-4 z-10`}>
|
||||
{/* <p className='text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="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'>
|
||||
<div className={`absolute z-50 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] bottom-0 left-0 bg-white px-4 py-2 flex justify-between items-center gap-4`}>
|
||||
<p className='text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="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" />
|
||||
|
|
@ -816,23 +872,69 @@ const SignDocument = () => {
|
|||
<FaAngleRight
|
||||
onClick={() => handleTagNavClick("N")}
|
||||
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
|
||||
</div>
|
||||
<p className='text-sm text-white bg-[var(--mainPrimaryBtn)] px-4 py-2 rounded'
|
||||
</div> */}
|
||||
<div></div>
|
||||
{/* <p className='cursor-pointer text-sm text-white bg-[var(--mainPrimaryBtn)] px-4 py-2 rounded'
|
||||
onClick={handleFormSubmit}
|
||||
>Sign</p>
|
||||
>Sign</p> */}
|
||||
|
||||
<div>
|
||||
{/* FIT - START */}
|
||||
<div
|
||||
className="hidden sm:block"
|
||||
tabIndex={1}
|
||||
onBlur={() => setIsFitDd(false)}
|
||||
>
|
||||
<div
|
||||
onClick={() => setIsFitDd(!isFitDd)}
|
||||
className='bg-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'>
|
||||
<p className='tracking-wide'>Fit</p>
|
||||
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
||||
</div>
|
||||
|
||||
{isFitDd && <div className='absolute top-[120px] bg-white rounded-sm z-50'>
|
||||
{fitObj.map((val, indx) => {
|
||||
return (<div
|
||||
key={indx}
|
||||
onClick={() => setSelFitIndx(indx)}
|
||||
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
|
||||
<div
|
||||
|
||||
className='flex items-center justify-between w-full'>
|
||||
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{val.label}</p>
|
||||
{selFitIndx === indx && <FaCheck className='text-sm text-slate-400' />}
|
||||
</div>
|
||||
</div>)
|
||||
})
|
||||
}
|
||||
</div>}
|
||||
</div>
|
||||
{/* FIT - END */}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
|
||||
|
||||
{requiredFilled && <>
|
||||
<div className={`absolute z-50 ${requiredFilled ? "bottom-0" : "bottom-[-100px] hidden"} left-0 bg-[var(--primary)] w-full flex justify-around items-center transition-all duration-300 px-2.5`}>
|
||||
<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}
|
||||
className="font-semibold text-sm px-8 py-2 bg-slate-100 rounded-md text-[var(--primary)]">Finish</button>
|
||||
</div>
|
||||
</>}
|
||||
|
||||
|
||||
</div>
|
||||
{/* Right Side - ENDS */}
|
||||
</div>
|
||||
{/* Nav Two - Options - End */}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Document Container Start */}
|
||||
<div className="grid grid-cols-12 gap-1 mt-1 h-[calc(100vh-48px)] overflow-hidden">
|
||||
|
||||
|
|
@ -840,7 +942,29 @@ const SignDocument = () => {
|
|||
{/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
|
||||
<div
|
||||
id="tags-list-client-side"
|
||||
className={`absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-[100vh] ${isFields ? "p-4" : ""} overflow-y-auto ${recipient?.type !== "CC" ? "bg-white" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
|
||||
className={`absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-[100vh] p-4 overflow-y-auto ${recipient?.type !== "CC" ? "bg-white" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
|
||||
|
||||
|
||||
<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
|
||||
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'}`}
|
||||
>
|
||||
<IoIosDocument />
|
||||
<p className='tracking-wide'>Tags</p>
|
||||
</div>}
|
||||
|
||||
<div
|
||||
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' : ''} ${!provideTags ? "sm:hidden" : ""} transition-all duration-300`}
|
||||
onClick={() => handleSetIsFields(provideTags ? false : !isFields)}
|
||||
>
|
||||
<IoIosDocument />
|
||||
<p className='tracking-wide'>Pages</p>
|
||||
</div>
|
||||
|
||||
{/* <p className="hidden sm:block text-xs text-white">Hi! {recipient && recipient.firstName} {recipient && recipient.lastName}</p> */}
|
||||
</div>
|
||||
|
||||
{isFields && recipientId && provideTags && recipient?.type !== "CC" && <FieldList color={"#8f87f1"} recipientId={Number(recipientId)} recipientIndx={0} acntId={"1"} />}
|
||||
|
||||
{!isFields && <DocumentPages
|
||||
|
|
@ -854,7 +978,7 @@ const SignDocument = () => {
|
|||
{/* Main container */}
|
||||
<div
|
||||
id="doc-con"
|
||||
className={`zoom-con relative ${provideTags ? (isFields && selectedTag !== null ? "col-span-8" : "col-span-10") : (isFields ? "col-span-12" : "col-span-10")} px-[2.4rem] sm:px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth transition-all duration-200 pb-[4rem]`}
|
||||
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]`}
|
||||
>
|
||||
{
|
||||
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
|
||||
|
|
@ -870,6 +994,7 @@ const SignDocument = () => {
|
|||
const key = `${doc.documentId}_${page.index}`;
|
||||
// const tagJsonData = [...droppedItems];
|
||||
const currDocTags = droppedItems.filter((val) => val.pageIndex === page.index)
|
||||
let scale: { x: number | null, y: number | null } = { x: null, y: null }
|
||||
return (
|
||||
<div
|
||||
key={indx}
|
||||
|
|
@ -879,8 +1004,27 @@ const SignDocument = () => {
|
|||
className='drop-page-in shadow-md relative'
|
||||
>
|
||||
|
||||
<img
|
||||
// style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
|
||||
style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }}
|
||||
draggable="false"
|
||||
onLoad={(imgElem) => {
|
||||
setPageScales((prev) => ({
|
||||
...prev,
|
||||
[page.index]: {
|
||||
x: imgElem.clientWidth / page.width,
|
||||
y: imgElem.clientHeight / page.height,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
width={page.width}
|
||||
height={page.height}
|
||||
src={page.src}
|
||||
/>
|
||||
|
||||
{!hideTags && <>
|
||||
<TagsCon
|
||||
scale={pageScales[page.index] || { x: 1, y: 1 }}
|
||||
currDocTags={currDocTags}
|
||||
pageIdkey={key}
|
||||
provideTags={provideTags}
|
||||
|
|
@ -893,6 +1037,8 @@ const SignDocument = () => {
|
|||
droppedItems={droppedItems}
|
||||
saved={saved}
|
||||
signConfObj={signConfObj}
|
||||
recipientId={recipientId as string}
|
||||
setSaved={setSaved}
|
||||
setStamp={setStamp}
|
||||
setSignConfObj={setSignConfObj}
|
||||
setOpenSign={setOpenSign}
|
||||
|
|
@ -904,34 +1050,55 @@ const SignDocument = () => {
|
|||
/>
|
||||
</>}
|
||||
|
||||
<img
|
||||
style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
|
||||
draggable="false"
|
||||
width={page.width}
|
||||
height={page.height}
|
||||
src={page.src}
|
||||
/>
|
||||
|
||||
|
||||
{/* Side details of documents Pages and document Name - START */}
|
||||
< p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-50px] text-slate-600' >
|
||||
< p className='hidden sm:block absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-50px] text-slate-600' >
|
||||
page {indx + 1} of {doc.pages && doc.pages.length}
|
||||
</p>
|
||||
{/* Side details of documents Pages and document Name - END */}
|
||||
|
||||
|
||||
{focusElement !== null && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index &&
|
||||
<div
|
||||
id="tag-navigation-btn"
|
||||
className={`absolute right-0 translate-x-[100%] flex flex-col justify-center items-center z-50 cursor-pointer transition-all duration-300`}
|
||||
style={{ top: (focusElement.dimensions.top + focusElement.dimensions.height / 2) + "px" }}
|
||||
onClick={() => handleTagNavClick("N")}
|
||||
>
|
||||
{/* NEXT Button - START */}
|
||||
<svg width="70" height="45" viewBox="0 0 112 45" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.87927 20.8542L15.0554 2.53357C15.7668 1.54438 16.9108 0.958008 18.1292 0.958008H108.049C110.14 0.958008 111.835 2.65317 111.835 4.74426V41.0235C111.835 43.1146 110.14 44.8098 108.049 44.8098H18.1084C16.9016 44.8098 15.7671 44.2345 15.054 43.261L1.89871 25.3024C0.930133 23.9802 0.92229 22.1849 1.87927 20.8542Z" fill="#005AB5" />
|
||||
<path d="M53.2649 16.7474V28.3838H51.3899L45.907 20.4577H45.8104V28.3838H43.7024V16.7474H45.5888L51.0661 24.6792H51.1683V16.7474H53.2649ZM58.8545 28.5542C57.9795 28.5542 57.2238 28.3724 56.5875 28.0088C55.9549 27.6414 55.4682 27.1224 55.1272 26.452C54.7863 25.7777 54.6159 24.9842 54.6159 24.0713C54.6159 23.1736 54.7863 22.3857 55.1272 21.7077C55.4719 21.0258 55.953 20.4955 56.5704 20.1167C57.1879 19.7342 57.9132 19.5429 58.7466 19.5429C59.2844 19.5429 59.792 19.63 60.2693 19.8042C60.7504 19.9747 61.1746 20.2398 61.542 20.5997C61.9132 20.9595 62.2049 21.4179 62.417 21.9747C62.6291 22.5277 62.7352 23.1868 62.7352 23.952V24.5827H55.5818V23.1963H60.7636C60.7598 22.8023 60.6746 22.452 60.5079 22.1452C60.3413 21.8345 60.1083 21.5902 59.8091 21.4122C59.5136 21.2342 59.1689 21.1452 58.775 21.1452C58.3545 21.1452 57.9852 21.2474 57.667 21.452C57.3488 21.6527 57.1007 21.9179 56.9227 22.2474C56.7485 22.5732 56.6594 22.9311 56.6557 23.3213V24.5315C56.6557 25.0391 56.7485 25.4747 56.9341 25.8383C57.1197 26.1982 57.3791 26.4747 57.7125 26.6679C58.0458 26.8573 58.436 26.952 58.8829 26.952C59.1822 26.952 59.453 26.9103 59.6954 26.827C59.9379 26.7398 60.1481 26.613 60.3261 26.4463C60.5041 26.2796 60.6386 26.0732 60.7295 25.827L62.65 26.0429C62.5288 26.5505 62.2977 26.9936 61.9568 27.3724C61.6197 27.7474 61.1879 28.0391 60.6613 28.2474C60.1348 28.452 59.5325 28.5542 58.8545 28.5542ZM65.4896 19.6565L67.2509 22.8781L69.0407 19.6565H71.2168L68.5862 24.0202L71.2623 28.3838H69.0975L67.2509 25.2417L65.4214 28.3838H63.2396L65.8987 24.0202L63.3078 19.6565H65.4896ZM76.6857 19.6565V21.2474H71.6687V19.6565H76.6857ZM72.9073 17.5656H74.9641V25.7588C74.9641 26.0353 75.0058 26.2474 75.0891 26.3952C75.1762 26.5391 75.2899 26.6376 75.43 26.6906C75.5702 26.7436 75.7255 26.7702 75.8959 26.7702C76.0247 26.7702 76.1421 26.7607 76.2482 26.7417C76.3581 26.7228 76.4414 26.7058 76.4982 26.6906L76.8448 28.2986C76.735 28.3364 76.5778 28.3781 76.3732 28.4236C76.1725 28.469 75.9262 28.4955 75.6346 28.5031C75.1194 28.5183 74.6554 28.4406 74.2425 28.2702C73.8296 28.0959 73.502 27.827 73.2596 27.4633C73.0209 27.0997 72.9035 26.6452 72.9073 26.0997V17.5656Z" fill="white" />
|
||||
</svg>
|
||||
|
||||
{/* NEXT Button - END */}
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
{(navigationTagPos === NavigationTagPosEnum.ABOVE || navigationTagPos === NavigationTagPosEnum.BELOW) &&
|
||||
<div
|
||||
onClick={() => scrollToElement(focusElement?.navigationOrder)}
|
||||
className={`cursor-pointer fixed ${navigationTagPos === NavigationTagPosEnum.ABOVE ? "top-[45px]" : "bottom-[45px] rotate-[180deg]"} right-[8%] flex flex-col items-center`}>
|
||||
{/* Arrow pointing up */}
|
||||
<div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--mainPrimaryBtn)]"></div>
|
||||
|
||||
{/* Label box */}
|
||||
<div
|
||||
className="bg-[var(--mainPrimaryBtn)] text-white text-sm px-3 py-1 rounded-md shadow-md flex items-center gap-1">
|
||||
<span>Next</span>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
|
||||
{provideTags && selectedTag && selectedTag !== null && "key" in selectedTag && "index" in selectedTag &&
|
||||
|
|
@ -959,7 +1126,7 @@ const SignDocument = () => {
|
|||
|
||||
<div style={{ display: `${openSign ? 'block' : 'none'}` }}>
|
||||
{recipient &&
|
||||
<SignCon
|
||||
<ClientSignCon
|
||||
recipient={recipient}
|
||||
recipientId={recipientId as string}
|
||||
setOpenSign={setOpenSign} saved={saved}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@ import { IoMdClose } from 'react-icons/io';
|
|||
import maskUtils from '../../utlity/maskUtils';
|
||||
import { FaFileAlt, FaSignature, FaStamp } from 'react-icons/fa';
|
||||
import { TbSignature } from 'react-icons/tb';
|
||||
import { BsFileArrowDownFill } from 'react-icons/bs';
|
||||
import { handleGroupBorderPosition } from '../../utlity/tagGroupConUtils';
|
||||
import api from '../../config/api';
|
||||
import { SaveType, SignConfTypeObj, StampTypeObj } from './SignDocument';
|
||||
import dayjs from 'dayjs';
|
||||
import typeToIconMap from '../../helper/iconMap';
|
||||
import { TiArrowSortedUp } from "react-icons/ti";
|
||||
import axios from 'axios';
|
||||
|
||||
|
||||
interface TagsConProps {
|
||||
scale: { x: number | null, y: number | null }
|
||||
currDocTags: DroppedItem[]
|
||||
pageIdkey: string;
|
||||
provideTags: boolean
|
||||
|
|
@ -27,6 +27,8 @@ interface TagsConProps {
|
|||
droppedItems: DroppedItem[]
|
||||
saved: SaveType;
|
||||
signConfObj: SignConfTypeObj | {};
|
||||
recipientId: string;
|
||||
setSaved: React.Dispatch<React.SetStateAction<SaveType>>,
|
||||
setStamp: React.Dispatch<React.SetStateAction<StampTypeObj>>;
|
||||
setSignConfObj: React.Dispatch<React.SetStateAction<SignConfTypeObj | {}>>;
|
||||
setOpenSign: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
|
|
@ -38,6 +40,7 @@ interface TagsConProps {
|
|||
}
|
||||
|
||||
const TagsCon: React.FC<TagsConProps> = ({
|
||||
scale,
|
||||
currDocTags,
|
||||
pageIdkey,
|
||||
provideTags,
|
||||
|
|
@ -50,6 +53,8 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
droppedItems,
|
||||
saved,
|
||||
signConfObj,
|
||||
recipientId,
|
||||
setSaved,
|
||||
setStamp,
|
||||
setSignConfObj,
|
||||
setOpenSign,
|
||||
|
|
@ -134,7 +139,7 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
(async () => {
|
||||
if ("id" in tag) {
|
||||
try {
|
||||
const response = await api.delete(`/api/sign/tags?recipientId=${recipientId}`, { data: [{ tagId: tag.id }] })
|
||||
const response = await api.delete(`/api/sign/${recipientId}/tags/`, { data: [{ tagId: tag.id }] })
|
||||
if (response.status !== 200) throw "Tag Deletion error"
|
||||
} catch (err) {
|
||||
return;
|
||||
|
|
@ -233,9 +238,24 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
// });
|
||||
}
|
||||
|
||||
const handleSignClick = (index: number, id: number) => {
|
||||
const handleSignClick = async (index: number, id: number) => {
|
||||
|
||||
setSignTagId(id)
|
||||
if (saved.save) {
|
||||
if (saved.isDefaultAndClicked) {
|
||||
try {
|
||||
const res = await axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/${recipientId}/signatures`, {
|
||||
signatureId: saved.signatureId,
|
||||
tagId: id
|
||||
}, { headers: { "ngrok-skip-browser-warning": "69420", } })
|
||||
|
||||
if (res.status === 202) {
|
||||
setSaved({ ...saved, signatureId: res.data.id, isDefaultAndClicked: false });
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
|
||||
if (index in signConfObj) {
|
||||
setOpenSign(true);
|
||||
return
|
||||
|
|
@ -248,9 +268,16 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
}
|
||||
})
|
||||
return
|
||||
|
||||
|
||||
}
|
||||
setOpenSign(true);
|
||||
|
||||
// setDroppedItems((prev) => {
|
||||
// return prev.map((obj, idx) => {
|
||||
// if (idx === index && obj.id === id) return { ...obj, valus: saved.signatureId }
|
||||
// return obj;
|
||||
// })
|
||||
// })
|
||||
const newObj = {
|
||||
[index]: {
|
||||
"accepted": false,
|
||||
|
|
@ -258,7 +285,6 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
}
|
||||
}
|
||||
setSignConfObj({ ...newObj })
|
||||
|
||||
}
|
||||
|
||||
const handleStampClick = (index: number) => {
|
||||
|
|
@ -284,22 +310,34 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
tabIndex={index + 1}
|
||||
onFocus={() => {
|
||||
setSelectedTag({ key: pageIdkey, index })
|
||||
// setFocusElement({ ...item, focusElementIndx: index })
|
||||
setFocusElement({ ...item, focusElementIndx: index })
|
||||
}
|
||||
}
|
||||
key={index}
|
||||
// onPointerDown={(e) => handleTagMouseDown(e, key, index)}
|
||||
className="absolute rounded dropped-item text-sm"
|
||||
// onClick={() => handleTagClick(key, index)}
|
||||
// style={{
|
||||
// left: `${item.dimensions.left}px`,
|
||||
// top: `${item.dimensions.top}px`,
|
||||
// width: `${item.dimensions.width}px`,
|
||||
// height: `${item.dimensions.height}px`,
|
||||
// border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
|
||||
// // display: `${hideTags ? 'none' : 'block'}`
|
||||
// }}
|
||||
|
||||
style={{
|
||||
left: `${item.dimensions.left}px`,
|
||||
top: `${item.dimensions.top}px`,
|
||||
width: `${item.dimensions.width}px`,
|
||||
height: `${item.dimensions.height}px`,
|
||||
border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
|
||||
// display: `${hideTags ? 'none' : 'block'}`
|
||||
left: item.dimensions.left * scale.x,
|
||||
top: item.dimensions.top * scale.y,
|
||||
width: item.dimensions.width * scale.x,
|
||||
height: item.dimensions.height * scale.y,
|
||||
}}
|
||||
>
|
||||
|
||||
{item.required && <div
|
||||
className='absolute top-[-16px] right-[-8px] text-lg text-red-600'
|
||||
>*</div>}
|
||||
|
||||
{provideTags && !transparentTags && !hideTags && <div
|
||||
onPointerDown={(e) => handleTagMouseDown(e, pageIdkey, index)}
|
||||
// className="absolute top-[-15px] left-[-15px] w-4 h-4 bg-blue-600 rounded-tr cursor-move z-30"
|
||||
|
|
@ -319,8 +357,8 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
>
|
||||
</p> */}
|
||||
{/* <p
|
||||
className=''>{IconComponent}
|
||||
</p> */}
|
||||
className=''>{IconComponent}
|
||||
</p> */}
|
||||
<p className='capitalize text-ellipsis whitespace-nowrap'>{item.label}</p>
|
||||
{provideTags && <IoMdClose onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -788,26 +826,26 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
>
|
||||
<p>{focusElement.label}</p>
|
||||
</div> */}
|
||||
<div
|
||||
{/* <div
|
||||
className="absolute z-[1000] flex flex-col items-center max-w-max bg-[var(--primary)] rounded-tl-md rounded-bl-md"
|
||||
style={{ left: -(item.dimensions.left + 36) + "px", top: ((item.dimensions.height / 2) - 43) + "px" }}
|
||||
style={{ right: 0 + "px", top: ((item.dimensions.height / 2) - 43) + "px" }}
|
||||
>
|
||||
<span className='pt-2 pb-1 text-white cursor-pointer'
|
||||
onClick={() => {
|
||||
handleTagNavClick("P")
|
||||
}}
|
||||
onClick={() => {
|
||||
handleTagNavClick("P")
|
||||
}}
|
||||
>
|
||||
<TiArrowSortedUp />
|
||||
</span>
|
||||
<p className='p-2 border-t-1 border-b-1 border-slate-700 text-white text-xs'><span>{focusElement.navigationOrder}</span><span>/{droppedItems.length}</span></p>
|
||||
<span className='pb-2 pt-1 text-white cursor-pointer'
|
||||
onClick={() => {
|
||||
handleTagNavClick("N")
|
||||
}}
|
||||
onClick={() => {
|
||||
handleTagNavClick("N")
|
||||
}}
|
||||
>
|
||||
<TiArrowSortedUp className='rotate-180' />
|
||||
</span>
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ interface TermsModalProps {
|
|||
const TermsModal: React.FC<TermsModalProps> = ({ showTerms, setShowTerms, recipientId }) => {
|
||||
|
||||
const onAccept = () => {
|
||||
axios.put(`${import.meta.env.VITE_BASE_URI}/api/sign/accept?recipientId=${recipientId}`, { headers: { "ngrok-skip-browser-warning": "69420" } })
|
||||
axios.put(`${import.meta.env.VITE_BASE_URI}/api/sign/accept/${recipientId}`, { headers: { "ngrok-skip-browser-warning": "69420" } })
|
||||
.then(response => {
|
||||
setShowTerms(false);
|
||||
console.log(response, "Accept");
|
||||
|
|
|
|||
|
|
@ -24,10 +24,13 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
|
|||
}, [sendForSignDropdown])
|
||||
|
||||
const user = useSelector((state: RootState) => state.user);
|
||||
if (!user.account || user.account === null) {
|
||||
window.location.href = "/login";
|
||||
return;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!user.account || user.account === null) {
|
||||
window.location.href = "/login";
|
||||
return;
|
||||
}
|
||||
}, [])
|
||||
|
||||
const defaultAccountId = user.account.id;
|
||||
|
||||
|
|
@ -118,7 +121,7 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
|
|||
transition-all duration-300 ease-in-out w-full
|
||||
rounded
|
||||
`}
|
||||
style={{ whiteSpace: "nowrap" }}
|
||||
style={{ whiteSpace: "nowrap" }}
|
||||
>
|
||||
{showSideBar ? (
|
||||
<p className="text-sm select-none">Send for signature</p>
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@ import { Signer } from "./DocumentCompletionScreen";
|
|||
export const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
|
||||
return (
|
||||
// <div className="bg-white/60 rounded-md shadow-sm border border-gray-100 ">
|
||||
<div className="p-4 bg-slate-100 rounded-sm shadow-sm">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-lg font-semibold text-[var(--primary)] flex items-center gap-2 ">
|
||||
<div className="px-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2 mb-4">
|
||||
{/* <FaUsers className="text-blue-600" /> */}
|
||||
Recipients
|
||||
</h3>
|
||||
<span className="text-sm text-gray-600">
|
||||
{signers.filter(s => s.completed).length} of {signers.length} signed
|
||||
{signers.filter(s => s.completed).length}/{signers.length} Signed
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="max-h-[24vh] overflow-y-auto no-scrollbar">
|
||||
{signers.map((signer) => (
|
||||
<div key={signer.id} className="text-sm flex items-center gap-4 rounded-lg">
|
||||
<div className="overflow-y-auto no-scrollbar">
|
||||
{signers.map((signer, indx) => (
|
||||
<div key={signer.id} className={`text-sm flex items-center gap-4 ${indx !== signers.length-1 ? "border-b-1 border-gray-200" : ""} py-4`}>
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center text-blue-600 font-medium">
|
||||
{/* {signer.name.split(" ").length > 2 ? signer.name.split(" ").map(n => n[0].toUpperCase()).join("") : signer.name[0].toUpperCase()} */}
|
||||
|
||||
|
|
@ -31,13 +31,15 @@ export const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
|
|||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{signer.completed ? (
|
||||
<div className="flex items-center gap-1 px-3 py-1 bg-green-100 text-green-700 rounded-full text-xs">
|
||||
<FaCheck className="w-3 h-3" />
|
||||
<div className="flex items-center gap-2 px-3 py-1 text-green-700 rounded-full text-sm">
|
||||
{/* <FaCheck className="w-3 h-3" /> */}
|
||||
<div className="w-2.5 h-2.5 bg-green-700 rounded-full"></div>
|
||||
Signed
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 px-3 py-1 bg-slate-100 text-slate-700 rounded-full text-xs">
|
||||
<FaClock className="w-3 h-3" />
|
||||
<div className="flex items-center gap-2 px-3 py-1 text-red-700 rounded-full text-sm">
|
||||
{/* <FaClock className="w-3 h-3" /> */}
|
||||
<div className="w-2.5 h-2.5 bg-red-700 rounded-full"></div>
|
||||
Pending
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { fetchRecipients, recipientType } from '../../../store/slices/selectedRe
|
|||
import { fetchDocuments } from '../../../store/slices/documentSlice'
|
||||
import { SigningOrderDiagram } from './SigningOrderDiagram'
|
||||
import { SignersList } from './SignerList'
|
||||
import signSessionRedirect from '../../../utlity/signSessionRedirect'
|
||||
|
||||
const TransactionSentSuccess = () => {
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
|
|
@ -68,43 +69,60 @@ const TransactionSentSuccess = () => {
|
|||
}, [selectedRecipients])
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="relative w-full overflow-hidden">
|
||||
|
||||
<div>
|
||||
<img className="absolute z-0 top-[5%] right-0" src="/completion-screen-frame-1.png" alt="" />
|
||||
<img className="absolute z-0 top-[5%] left-0 rotate-y-180" src="/completion-screen-frame-1.png" alt="" />
|
||||
</div>
|
||||
{/* Header */}
|
||||
<div className="flex items-center space-x-3 bg-white w-full py-2 px-4 sm:px-5 shadow">
|
||||
<Link
|
||||
to={'/'}
|
||||
className="flex items-center text-gray-600 hover:text-[var(--primary)] transition-colors duration-200"
|
||||
>
|
||||
<IoMdHome className="text-xl sm:text-2xl text-slate-500 cursor-pointer" />
|
||||
{/* <IoMdHome className="text-xl sm:text-2xl text-slate-500 cursor-pointer" /> */}
|
||||
<img src="/sme-colored.png" className='py-2 px-8' alt="" />
|
||||
</Link>
|
||||
<div className="h-6 sm:h-8 w-px bg-gray-300"></div>
|
||||
<h1 className="text-sm sm:text-md text-gray-800">
|
||||
{/* <div className="h-6 sm:h-8 w-px bg-gray-300"></div> */}
|
||||
{/* <h1 className="text-sm sm:text-md text-gray-800">
|
||||
🎉 Awesome! The document’s been sent for signature. 🎉
|
||||
</h1>
|
||||
</h1> */}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col p-4 justify-center items-center w-full">
|
||||
<div className="mx-auto w-full max-w-6xl">
|
||||
{/* Success banner */}
|
||||
<div className="flex items-center gap-3 mb-4 bg-slate-100 p-4 rounded-md">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-teal-500 to-green-400 rounded-full flex items-center justify-center shadow-sm">
|
||||
<div className="flex flex-col justify-center items-center gap-3 mb-4 p-4 rounded-md">
|
||||
{/* <div className="w-12 h-12 bg-gradient-to-br from-teal-500 to-green-400 rounded-full flex items-center justify-center shadow-sm">
|
||||
<FaCheck className="text-white text-2xl" />
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
|
||||
<svg width="70" height="70" viewBox="0 0 114 109" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M114 54.3837L101.095 40.1811L103.115 21.1673L84.2441 17.2075L74.6181 0.631836L57 8.43682L54.6221 54.3837L57 100.33L74.6183 108.135L84.2441 91.56L103.115 87.5999L101.095 68.5861L114 54.3837Z" fill="#0ED678" />
|
||||
<path d="M39.3816 0.631836L29.7558 17.2075L10.8847 21.1675L12.9053 40.1811L0 54.3837L12.9053 68.5861L10.8847 87.5999L29.7558 91.56L39.3819 108.135L57.0001 100.33V8.43682L39.3816 0.631836Z" fill="#04EB84" />
|
||||
<path d="M80.8957 42.013L75.9064 37.5986L57 58.5946L54.6221 63.8124L57 68.5499L80.8957 42.013Z" fill="#F7F0EB" />
|
||||
<path d="M37.1311 49.3916L32.4023 54.0791L52.2842 73.7868L57.0003 68.5495V58.5942L52.0082 64.138L37.1311 49.3916Z" fill="#FFFBF5" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<p className="text-lg sm:text-2xl font-medium text-[var(--primary)]">
|
||||
<p className="text-lg sm:text-4xl font-bold text-[var(--mainPrimaryBtn)]">
|
||||
Your Document is sent for signature
|
||||
</p>
|
||||
<p className="text-sm text-slate-600">All signers will be notified via email</p>
|
||||
<p className="text-lg text-slate-600 text-center font-medium">All signers will be notified via email</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main two-column layout */}
|
||||
<div className="flex flex-col lg:flex-row gap-4">
|
||||
<div className="flex flex-col lg:flex-row gap-4 mt-6 lg:justify-center lg:items-stretch">
|
||||
{/* Left: Document Preview */}
|
||||
<div className="flex-1 bg-slate-100 rounded-md overflow-hidden">
|
||||
<div className="w-full h-[60vh] lg:h-[calc(100vh-200px)] overflow-auto">
|
||||
<div className="py-2 bg-white px-4 shadow-sm rounded-sm sticky top-0 z-10">
|
||||
<div className="w-full h-[50vh] overflow-auto">
|
||||
<div className="py-2 bg-white px-4 shadow-sm rounded-sm sticky top-0 z-10 border-1 border-gray-200">
|
||||
<p className="text-sm font-medium">Quick Preview</p>
|
||||
</div>
|
||||
<div className="px-4">
|
||||
|
|
@ -115,30 +133,36 @@ const TransactionSentSuccess = () => {
|
|||
|
||||
{/* Right: Signers list + actions */}
|
||||
<div
|
||||
className={`flex-1 transition-all rounded-lg duration-700 ${
|
||||
isVisible ? 'translate-y-0 opacity-100' : 'translate-y-8 opacity-0'
|
||||
}`}
|
||||
className={`flex-1 transition-all rounded-lg duration-700 ${isVisible ? 'translate-y-0 opacity-100' : 'translate-y-8 opacity-0'
|
||||
}`}
|
||||
>
|
||||
<div className="max-h-[30vh] overflow-auto">
|
||||
<SignersList signers={selectedRecipients} />
|
||||
<div className="p-4 h-full bg-white rounded-md shadow-2xl border-1 border-gray-100">
|
||||
|
||||
<div className='overflow-auto'>
|
||||
<SignersList signers={selectedRecipients} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2 max-h-max relative top-auto bottom-0">
|
||||
<Link
|
||||
to="/"
|
||||
className="flex-1 p-2 text-center rounded-md bg-slate-200 hover:shadow-sm transition-all duration-150"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
|
||||
{selfUserSign.sign && (
|
||||
<p
|
||||
onClick={() => signSessionRedirect(accountId, transactionId, selfUserSign.recipientId + "")}
|
||||
className="flex-1 p-2 text-center rounded-md bg-[var(--mainPrimaryBtn)] text-white hover:shadow-sm transition-all duration-150 cursor-pointer"
|
||||
>
|
||||
Sign Now
|
||||
</p>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-col sm:flex-row gap-2">
|
||||
{selfUserSign.sign && (
|
||||
<Link
|
||||
to={`/sign/${selfUserSign.recipientId}`}
|
||||
className="flex-1 p-2 text-center rounded-md bg-[var(--primary)] text-white hover:shadow-sm transition-all duration-150"
|
||||
>
|
||||
Sign Now
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
to="/"
|
||||
className="flex-1 p-2 text-center rounded-md bg-slate-200 hover:shadow-sm transition-all duration-150"
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Optional: Signing Order */}
|
||||
{/* <div className="mt-4">
|
||||
|
|
@ -148,6 +172,20 @@ const TransactionSentSuccess = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="text-center text-sm py-6 bg-white mt-[80px] ">
|
||||
<p className="px-4 font-semibold">
|
||||
Copyright 2025 Sign Made Easy. All Rights Reserved.{" "}
|
||||
<a href="#" className="text-[var(--mainPrimaryBtn)] hover:underline">
|
||||
Privacy Policy
|
||||
</a>{" "}
|
||||
|{" "}
|
||||
<a href="#" className="text-[var(--mainPrimaryBtn)] hover:underline">
|
||||
Reasonable Use Policy
|
||||
</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted, tran
|
|||
let lastUpdate = 0;
|
||||
const delay = 1000;
|
||||
|
||||
const docProgSSE = new EventSource(`http://192.168.2.176:8083/api/progress/${id}`);
|
||||
const docProgSSE = new EventSource(`http://10.10.1.99:8083/api/progress/${id}`);
|
||||
|
||||
docProgSSE.addEventListener('progress', (event) => {
|
||||
const jsonData = JSON.parse(event.data);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import getMinRoutingOrder from "../../../utlity/commonUtils";
|
|||
import MessageCon from "./MessageCon";
|
||||
import RecipientsCon from "./RecipientsCon";
|
||||
import { VscClose } from "react-icons/vsc";
|
||||
import signSessionRedirect from "../../../utlity/signSessionRedirect";
|
||||
|
||||
interface EmailDtls {
|
||||
subject: string;
|
||||
|
|
@ -227,7 +228,7 @@ const ProcessStepOne = () => {
|
|||
|
||||
await api.patch(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents`, { "documents": documentsData });
|
||||
|
||||
await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents?include_images=true`).then(res => {
|
||||
await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents?include_pages=true`).then(res => {
|
||||
if (res.status === 200) {
|
||||
dispatch(setDocuments(res.data));
|
||||
}
|
||||
|
|
@ -336,8 +337,8 @@ const ProcessStepOne = () => {
|
|||
(async () => {
|
||||
try {
|
||||
await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" })
|
||||
if(accountId && transactionId) signSessionRedirect(accountId, transactionId, recipientId);
|
||||
dispatch(hideLoader());
|
||||
navigate(`/sign/${recipientId}/self_sign`);
|
||||
} catch (err) {
|
||||
console.log("Error Transaction Sent", err.message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,8 +195,8 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
}
|
||||
|
||||
const requests = Object.entries(updatedData)
|
||||
.filter(([_, tags]) => tags.length > 0) // filter out empty ones
|
||||
.map(([recipientId, tags]) => {
|
||||
.filter(([_, tags]) => tags.length > 0) // filter out empty ones
|
||||
.map(([recipientId, tags]) => {
|
||||
const updatedTagsWithNavOrder = tags.map((tag, indx) => ({
|
||||
...tag,
|
||||
navigationOrder: indx + 1,
|
||||
|
|
@ -458,6 +458,13 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
// return () => targetCon?.removeEventListener("keydown");
|
||||
}
|
||||
|
||||
|
||||
const handleLazyLoad = (docId: string, pageId: string) => {
|
||||
api.get(`http://10.10.1.99:8083/api/${accountId}/transactions/${transactionId}/documents/${docId}/pages/${pageId}`).then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Center Doc Container - START */}
|
||||
|
|
@ -506,6 +513,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
draggable="false"
|
||||
width={page.width}
|
||||
height={page.height}
|
||||
// src={`http://10.10.1.99:8083/api/accounts/${accountId}/transactions/${transactionId}/documents/${doc.id}/pages/${page.id}`}
|
||||
src={page.src}
|
||||
loading='lazy'
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
|
|||
const priorityOrder = ['SIGNATURE', 'INITIALS', 'STAMP'];
|
||||
|
||||
const data = res.data.filter((obj, indx) => {
|
||||
// if (obj.type === "STAMP" || obj.type === "RADIO" || obj.type === "CHECKBOX" || obj.type === "ATTACHMENT") return false
|
||||
if (obj.type === "STAMP" || obj.type === "RADIO" || obj.type === "CHECKBOX" || obj.type === "ATTACHMENT") return false
|
||||
return true
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -10,15 +10,19 @@ import { MdKeyboardArrowDown, MdOutlineNavigateNext } from 'react-icons/md'
|
|||
import { useParams } from 'react-router-dom';
|
||||
import { fetchDocuments } from '../../../store/slices/documentSlice';
|
||||
import { GrFormPrevious } from 'react-icons/gr';
|
||||
import { LuTableOfContents } from 'react-icons/lu';
|
||||
import { IoIosDocument } from 'react-icons/io';
|
||||
|
||||
interface LeftSideBarProps {
|
||||
isFields: boolean;
|
||||
isPages: boolean;
|
||||
selectedRecipientIndx: number;
|
||||
setSelectedRecipient: React.Dispatch<React.SetStateAction<number>>
|
||||
handleSetIsFields: (setTo: boolean) => void;
|
||||
handleSetIsPages:(setTo: boolean) => void;
|
||||
}
|
||||
|
||||
const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRecipientIndx, setSelectedRecipient }) => {
|
||||
const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRecipientIndx, setSelectedRecipient, handleSetIsFields, handleSetIsPages }) => {
|
||||
|
||||
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -78,7 +82,7 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
};
|
||||
}, [selectedRecipient])
|
||||
|
||||
if(selectedRecipient && selectedRecipient.type === recipientType.CC){
|
||||
if (selectedRecipient && selectedRecipient.type === recipientType.CC) {
|
||||
return <div className='col-span-2 bg-white h-full p-4 overflow-y-auto'></div>;
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +91,24 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
{/* Left Side Bar - START */}
|
||||
{
|
||||
<div className={`${!isFields && !isPages ? "hidden" : ""} p-4 absolute top-[48px] sm:top-[0] rounded-tr-md z-50 sm:relative sm:col-span-2 bg-white border-t-1 border-r-1 border-slate-300 h-[calc(100vh-40px)] overflow-y-auto`}>
|
||||
|
||||
<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'>
|
||||
<div
|
||||
onClick={() => handleSetIsFields(!isFields)}
|
||||
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`}>
|
||||
<LuTableOfContents />
|
||||
<p className='tracking-wide'>Tags</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => handleSetIsPages(!isPages)}
|
||||
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isPages ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300`}>
|
||||
<IoIosDocument />
|
||||
<p className='tracking-wide'>Pages</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{selectedRecipients.length > 0 && isFields && <>
|
||||
{/* Fields List and Recipeint - Start */}
|
||||
{/* Side Bar showing Recipients */}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { MdKeyboardArrowDown } from 'react-icons/md'
|
|||
import { RiResetRightLine } from 'react-icons/ri'
|
||||
import { fetchRecipients, recipientType, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice'
|
||||
import { fetchDocuments } from '../../../store/slices/documentSlice'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { Bounce, toast } from 'react-toastify'
|
||||
import { FiChevronDown } from 'react-icons/fi'
|
||||
import RecipientsCon from '../processStepOne/RecipientsCon'
|
||||
|
|
@ -181,22 +181,23 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
}, 1000)
|
||||
}
|
||||
|
||||
const handleSetIsFields = (setTo: boolean) => {
|
||||
if (setTo !== undefined && setTo !== isFields) {
|
||||
setIsFields(setTo)
|
||||
if(setTo === true) {
|
||||
setIsPages(false)
|
||||
}
|
||||
};
|
||||
}
|
||||
const handleSetIsPages = (setTo: boolean) => {
|
||||
if (setTo !== undefined && setTo !== isPages) {
|
||||
setIsPages(setTo)
|
||||
if(setTo === true) {
|
||||
setIsFields(false)
|
||||
}
|
||||
};
|
||||
}
|
||||
// const handleSetIsFields = (setTo: boolean) => {
|
||||
// if (setTo !== undefined && setTo !== isFields) {
|
||||
// setIsFields(setTo)
|
||||
// if(setTo === true) {
|
||||
// setIsPages(false)
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
// const handleSetIsPages = (setTo: boolean) => {
|
||||
// if (setTo !== undefined && setTo !== isPages) {
|
||||
// setIsPages(setTo)
|
||||
// if(setTo === true) {
|
||||
// setIsFields(false)
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
const zoom = (percentage: number, contentClass: string) => {
|
||||
const content = document.querySelector(`.${contentClass}`);
|
||||
|
|
@ -234,24 +235,20 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
{/* Left Side */}
|
||||
<div className='flex justify-start items-center gap-2'>
|
||||
|
||||
<div
|
||||
onClick={() => handleSetIsFields(!isFields)}
|
||||
className={`flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ${isFields ? 'bg-slate-100' : ''}`}>
|
||||
<LuTableOfContents />
|
||||
<p className='tracking-wide'>Fields</p>
|
||||
<div>
|
||||
<Link to={"/"}>
|
||||
<img width={120} src="/sme-colored.png" alt="Sign Made Easy" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => handleSetIsPages(!isPages)}
|
||||
className={`flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ${isPages ? 'bg-slate-100' : ''}`}>
|
||||
<IoIosDocument />
|
||||
<p className='tracking-wide'>Pages</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
tabIndex={1}
|
||||
onBlur={() => setIsFitDd(false)}
|
||||
>
|
||||
|
||||
|
||||
|
||||
<div
|
||||
onClick={() => setIsFitDd(!isFitDd)}
|
||||
className='hidden sm:flex md:flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
|
|
@ -259,7 +256,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
||||
</div>
|
||||
|
||||
{isFitDd && <div className='absolute bg-white rounded-sm z-50'>
|
||||
{isFitDd && <div className='absolute bg-white rounded-sm z-[1000] shadow'>
|
||||
{fitObj.map((val, indx) => {
|
||||
return (<div
|
||||
key={indx}
|
||||
|
|
@ -278,12 +275,12 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
</div>
|
||||
|
||||
|
||||
{!isPreview && <div className='hidden sm:flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
{/* {!isPreview && <div className='hidden sm:flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
<RiResetRightLine
|
||||
className={`${transactionSaving ? "animate-spin" : ""} transition duration-300`}
|
||||
/>
|
||||
<p className="tracking-wide transition-all">{transactionSaving ? `Saving ${dots}` : "Save"}</p>
|
||||
</div>}
|
||||
</div>} */}
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -342,7 +339,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
|
||||
{/* Document Nav */}
|
||||
<div className='hidden sm:block'
|
||||
tabIndex={1} onBlur={() => setNav2DocToggle(false)}>
|
||||
tabIndex={1} onBlur={() => setNav2DocToggle(false)}>
|
||||
<div onClick={() => setNav2DocToggle(!nav2DocToggle)} className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
<p className='tracking-wide'>{selectedDocuments.length} Documents</p>
|
||||
<MdKeyboardArrowDown className={`${nav2DocToggle ? 'rotate-180' : ''} transition-all`} />
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const ProcessStepTwo = () => {
|
|||
|
||||
const [isFields, setIsFields] = useState(windowWidth > 638 ? true : false);
|
||||
const [isPages, setIsPages] = useState(false);
|
||||
|
||||
|
||||
const [selectedRecipient, setSelectedRecipient] = useState(0);
|
||||
|
||||
const [transactionSaving, setTransactionSaving] = useState<boolean>(false);
|
||||
|
|
@ -37,12 +37,31 @@ const ProcessStepTwo = () => {
|
|||
}, 800)
|
||||
}, [])
|
||||
|
||||
|
||||
const handleSetIsFields = (setTo: boolean) => {
|
||||
if (setTo !== undefined && setTo !== isFields) {
|
||||
setIsFields(setTo)
|
||||
if (setTo === true) {
|
||||
setIsPages(false)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleSetIsPages = (setTo: boolean) => {
|
||||
if (setTo !== undefined && setTo !== isPages) {
|
||||
setIsPages(setTo)
|
||||
if(setTo === true) {
|
||||
setIsFields(false)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}>
|
||||
<NavTwo transactionSaving={transactionSaving} isFields={isFields} isPages={isPages} setIsPages={setIsPages} setIsFields={setIsFields} isSend={isSend} setIsSend={setIsSend} />
|
||||
|
||||
<div className='sm:grid sm:grid-cols-12 sm:gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'>
|
||||
<LeftSideBar isFields={isFields} isPages={isPages} selectedRecipientIndx={selectedRecipient} setSelectedRecipient={setSelectedRecipient} />
|
||||
<LeftSideBar isFields={isFields} isPages={isPages} selectedRecipientIndx={selectedRecipient} setSelectedRecipient={setSelectedRecipient} handleSetIsFields={handleSetIsFields} handleSetIsPages={handleSetIsPages} />
|
||||
<DocumentContainer transactionSaving={transactionSaving} setTransactionSaving={setTransactionSaving} isSend={isSend} setIsSend={setIsSend} />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { JSX, useLayoutEffect } from 'react';
|
||||
import { useDispatch } from "react-redux";
|
||||
import { fetchUserDetails } from '../../store/slices/userSlice';
|
||||
import { AppDispatch } from '../../store/store';
|
||||
|
||||
const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// useLayoutEffect(() => {
|
||||
dispatch(fetchUserDetails());
|
||||
}, []);
|
||||
// }, []);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { IoMdClose } from 'react-icons/io';
|
||||
|
||||
type Props = {
|
||||
onSignatureChange: (data: { fullSignature: string | null; initials: string | null }) => void;
|
||||
|
|
@ -9,6 +10,7 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
|||
const canvasRef1 = useRef<HTMLCanvasElement>(null);
|
||||
const canvasRef2 = useRef<HTMLCanvasElement>(null);
|
||||
const [isDrawing, setIsDrawing] = useState(false);
|
||||
const [isDrawingType, setIsDrawingType] = useState<{ signatureDrawn: boolean, initialDrawn: boolean }>({ signatureDrawn: false, initialDrawn: false });
|
||||
const [paths1, setPaths1] = useState<Array<Array<{ x: number; y: number }>>>([]);
|
||||
const [paths2, setPaths2] = useState<Array<Array<{ x: number; y: number }>>>([]);
|
||||
const [currentPath, setCurrentPath] = useState<Array<{ x: number; y: number }>>([]);
|
||||
|
|
@ -21,29 +23,33 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
|||
|
||||
// Setup canvas dimensions when component mounts
|
||||
useEffect(() => {
|
||||
const setupCanvas = () => {
|
||||
if (canvasRef1.current) {
|
||||
const rect1 = canvasRef1.current.getBoundingClientRect();
|
||||
canvasRef1.current.width = rect1.width;
|
||||
canvasRef1.current.height = rect1.height;
|
||||
}
|
||||
if (canvasRef2.current) {
|
||||
const rect2 = canvasRef2.current.getBoundingClientRect();
|
||||
canvasRef2.current.width = rect2.width;
|
||||
canvasRef2.current.height = rect2.height;
|
||||
const resizeCanvas = (canvas: HTMLCanvasElement | null) => {
|
||||
if (canvas) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
canvas.width = rect.width;
|
||||
canvas.height = rect.height;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setupCanvas();
|
||||
const observer1 = new ResizeObserver(() => resizeCanvas(canvasRef1.current));
|
||||
const observer2 = new ResizeObserver(() => resizeCanvas(canvasRef2.current));
|
||||
|
||||
// Load initial signatures after canvas setup
|
||||
if (initialSignatures && !initialLoaded) {
|
||||
setTimeout(() => {
|
||||
loadInitialSignatures();
|
||||
}, 100);
|
||||
}
|
||||
if (canvasRef1.current) observer1.observe(canvasRef1.current);
|
||||
if (canvasRef2.current) observer2.observe(canvasRef2.current);
|
||||
|
||||
// initial setup
|
||||
resizeCanvas(canvasRef1.current);
|
||||
resizeCanvas(canvasRef2.current);
|
||||
|
||||
return () => {
|
||||
observer1.disconnect();
|
||||
observer2.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
const loadInitialSignatures = () => {
|
||||
if (!initialSignatures) return;
|
||||
|
||||
|
|
@ -100,8 +106,11 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
|||
setActiveCanvas(e.currentTarget === canvasRef1.current ? 'full' : 'initials');
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>, type: "SIGNATURE" | "INITIAL") => {
|
||||
if (!isDrawing) return;
|
||||
setIsDrawingType((prev) => {
|
||||
return {...prev, signatureDrawn: type === "SIGNATURE" ? true : prev.signatureDrawn, initialDrawn: type === "INITIAL" ? true : prev.initialDrawn }
|
||||
})
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
||||
const newPath = [...currentPath, point];
|
||||
|
|
@ -138,6 +147,7 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
|||
|
||||
const handleMouseUp = () => {
|
||||
setIsDrawing(false);
|
||||
|
||||
if (currentPath.length === 0 || !activeCanvas) return;
|
||||
|
||||
if (activeCanvas === 'full') {
|
||||
|
|
@ -153,7 +163,8 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
|||
// Export both canvases and notify parent
|
||||
const fullSignature = exportCanvasAsImage(canvasRef1.current);
|
||||
const initials = exportCanvasAsImage(canvasRef2.current);
|
||||
onSignatureChange({ fullSignature, initials });
|
||||
onSignatureChange({ fullSignature: (isDrawingType.signatureDrawn) ? fullSignature : null, initials: (isDrawingType.initialDrawn) ? initials : null });
|
||||
// onSignatureChange({ fullSignature, initials });
|
||||
};
|
||||
|
||||
const redrawCanvas = (canvas: HTMLCanvasElement, paths: Array<Array<{ x: number; y: number }>>, initialImage: HTMLImageElement | null) => {
|
||||
|
|
@ -223,63 +234,70 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="mb-2 lg:mb-6">
|
||||
<h2 className="text-lg sm:text-xl lg:text-2xl font-semibold text-gray-900 mb-1 sm:mb-2 ">Draw Signature</h2>
|
||||
<p className="text-xs sm:text-sm text-gray-600 hidden sm:block">Create your signature by drawing directly on the canvas</p>
|
||||
<div className="px-4 lg:px-6 py-4 flex-shrink-0">
|
||||
<div className='flex justify-between'>
|
||||
{/* <h2 className="text-lg sm:text-xl lg:text-2xl font-semibold text-gray-900 mb-1 sm:mb-2 ">Draw Signature</h2> */}
|
||||
<h2 className="mt-4 mb-5 text-sm">Draw your signature. <span className='text-red-600'>*</span> Are required fields.</h2>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex gap-2 mt-3">
|
||||
<div className='flex justify-center items-center gap-1 font-medium'>
|
||||
<IoMdClose className='text-red-500' />
|
||||
<button
|
||||
className="text-xs text-red-500"
|
||||
onClick={clearCanvas}
|
||||
>
|
||||
Clear Signature
|
||||
</button>
|
||||
</div>
|
||||
{/* <button
|
||||
className="text-xs"
|
||||
onClick={undoLastPath}
|
||||
disabled={!activeCanvas || (activeCanvas === 'full' ? paths1.length === 0 : paths2.length === 0)}
|
||||
>
|
||||
Undo Last
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4 lg:gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 lg:gap-6">
|
||||
{/* Full Signature Canvas */}
|
||||
<div className="lg:col-span-8">
|
||||
<label className="block text-xs sm:text-sm font-medium text-gray-700 mb-2">Full Signature</label>
|
||||
<div className="border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<div className="md:col-span-8">
|
||||
<label className="block text-sm font-medium mb-2">Full Name
|
||||
<span className='text-red-600'> *</span>
|
||||
</label>
|
||||
<div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
|
||||
<canvas
|
||||
ref={canvasRef1}
|
||||
style={{ width: "100%", height: "200px" }}
|
||||
className="w-full h-51 cursor-crosshair"
|
||||
onPointerDown={handleMouseDown}
|
||||
onPointerMove={handleMouseMove}
|
||||
onPointerUp={handleMouseUp}
|
||||
onPointerLeave={handleMouseUp}
|
||||
onPointerMove={(e) => handleMouseMove(e, "SIGNATURE")}
|
||||
onPointerUp={() => handleMouseUp()}
|
||||
onPointerLeave={() => handleMouseUp()}
|
||||
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Initials Canvas */}
|
||||
<div className="lg:col-span-4">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Initials</label>
|
||||
<div className="border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<div className="md:col-span-4">
|
||||
<label className="block text-sm font-medium mb-2">Initials
|
||||
<span className='text-red-600'> *</span>
|
||||
</label>
|
||||
<div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
|
||||
<canvas
|
||||
ref={canvasRef2}
|
||||
className="w-full h-51 cursor-crosshair"
|
||||
onPointerDown={handleMouseDown}
|
||||
onPointerMove={handleMouseMove}
|
||||
onPointerUp={handleMouseUp}
|
||||
onPointerLeave={handleMouseUp}
|
||||
|
||||
onPointerMove={(e) => handleMouseMove(e, "INITIAL")}
|
||||
onPointerUp={() => handleMouseUp()}
|
||||
onPointerLeave={() => handleMouseUp()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex gap-4 mt-3">
|
||||
<button
|
||||
className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||||
onClick={clearCanvas}
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||||
onClick={undoLastPath}
|
||||
disabled={!activeCanvas || (activeCanvas === 'full' ? paths1.length === 0 : paths2.length === 0)}
|
||||
>
|
||||
Undo Last
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -71,18 +71,17 @@ const SignatureManager: React.FC = () => {
|
|||
setLoading(true)
|
||||
try {
|
||||
let res = await api.delete(`/api/user/signatures/${id}`)
|
||||
|
||||
toast.success("Signature Delete Successfull", {
|
||||
position: "top-center",
|
||||
autoClose: 500,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "light",
|
||||
transition: Bounce,
|
||||
});
|
||||
// toast.success("Signature Delete Successfull", {
|
||||
// position: "top-center",
|
||||
// autoClose: 500,
|
||||
// hideProgressBar: false,
|
||||
// closeOnClick: false,
|
||||
// pauseOnHover: true,
|
||||
// draggable: true,
|
||||
// progress: undefined,
|
||||
// theme: "light",
|
||||
// transition: Bounce,
|
||||
// });
|
||||
await getAllSignatures();
|
||||
} catch (error) {
|
||||
console.log("delete signature", error)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
|
@ -14,7 +14,7 @@ interface SignatureConProps {
|
|||
const SignatureCon: React.FC<SignatureConProps> = ({ recipientId, openSign, setOpenSign, savedSignature, setSavedSignature }) => {
|
||||
|
||||
const handleSaveSignature = (signatureDataUrl: string) => {
|
||||
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/signature?recipientId=${recipientId}`,
|
||||
axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign/${recipientId}/signatures`,
|
||||
{
|
||||
signature: signatureDataUrl,
|
||||
initials: signatureDataUrl,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import AuditModal from './AuditModal';
|
|||
import { FaFile } from 'react-icons/fa6';
|
||||
import { IoMdDownload } from 'react-icons/io';
|
||||
import downloadDocumentUtils from '../../utlity/downloadDocumentsUtils';
|
||||
import signSessionRedirect from '../../utlity/signSessionRedirect';
|
||||
|
||||
// Sample recipient data — you can replace this with actual API data later
|
||||
const recipients = [
|
||||
|
|
@ -113,7 +114,7 @@ const TransactionView = () => {
|
|||
|
||||
|
||||
const handleNavigateToSign = (id: number) => {
|
||||
window.open(`./../../../../sign/${id}/${SignTypeEnum.selfSign}`)
|
||||
signSessionRedirect(accountId, transactionId, id+"" as string)
|
||||
}
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,38 @@ const api = axios.create({
|
|||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"ngrok-skip-browser-warning": "69420",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
|
||||
validateStatus: (status) => status >= 200 && status < 400,
|
||||
});
|
||||
|
||||
// Token storage utilities
|
||||
export const TokenStorage = {
|
||||
getAccessToken: () => localStorage.getItem('access_token'),
|
||||
getRefreshToken: () => localStorage.getItem('refresh_token'),
|
||||
setTokens: (accessToken: string, refreshToken: string) => {
|
||||
localStorage.setItem('access_token', accessToken);
|
||||
localStorage.setItem('refresh_token', refreshToken);
|
||||
},
|
||||
clearTokens: () => {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
}
|
||||
};
|
||||
|
||||
// Request interceptor - Add access token to headers
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const accessToken = TokenStorage.getAccessToken();
|
||||
if (accessToken) {
|
||||
config.headers.Authorization = `Bearer ${accessToken}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Flag to prevent infinite loop
|
||||
let isRefreshing = false;
|
||||
|
||||
|
|
@ -26,11 +53,22 @@ api.interceptors.response.use(
|
|||
return response;
|
||||
},
|
||||
async error => {
|
||||
|
||||
|
||||
const originalRequest = error.config;
|
||||
|
||||
// const res = error.response;
|
||||
|
||||
// 🔹 Handle 301/302 manually
|
||||
// if (res && (res.status === 301 || res.status === 302)) {
|
||||
// const redirectUrl = res.headers["location"];
|
||||
// if (redirectUrl) {
|
||||
// window.location.href = redirectUrl;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Prevent multiple refresh attempts
|
||||
// if (error.response?.status === 401 || error.response?.status === 500) {
|
||||
if (error.response?.status === 401) {
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
try {
|
||||
if (!isRefreshing) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { RootState } from "../store/store";
|
|||
|
||||
|
||||
|
||||
/ Create an Axios instance
|
||||
// Create an Axios instance
|
||||
const useAxios = () => {
|
||||
const user = useSelector((state: RootState) => state.user);
|
||||
const instance = axios.create({
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export const fetchDocuments = createAsyncThunk<
|
|||
const toggle = state.selectedRecipients.transactionTemplateToggle;
|
||||
|
||||
const response = await api.get(
|
||||
`/api/accounts/${accountId}/${toggle}/${transactionId}/documents?include_images=true`
|
||||
`/api/accounts/${accountId}/${toggle}/${transactionId}/documents?include_pages=true`
|
||||
);
|
||||
|
||||
const data = response.data as Document[];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import api from "../config/api";
|
||||
|
||||
const signSessionRedirect = async (accountId: string, transactionId: string, recipientId: string) => {
|
||||
const session = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/create_session`)
|
||||
window.location.href = session.data.url;
|
||||
return;
|
||||
}
|
||||
|
||||
export default signSessionRedirect
|
||||
|
|
@ -8,16 +8,19 @@ export default defineConfig({
|
|||
plugins: [react(), tailwindcss()],
|
||||
server: {
|
||||
allowedHosts: [
|
||||
'quality-friendly-goldfish.ngrok-free.app',
|
||||
'officially-powerful-goat.ngrok-free.app',
|
||||
'89f0-49-36-189-120.ngrok-free.app',
|
||||
'c8ee-49-36-191-130.ngrok-free.app',
|
||||
'c76f1251c173.ngrok-free.app',
|
||||
'enabled-upright-boxer.ngrok-free.app'
|
||||
],
|
||||
'quality-friendly-goldfish.ngrok-free.app',
|
||||
'officially-powerful-goat.ngrok-free.app',
|
||||
'89f0-49-36-189-120.ngrok-free.app',
|
||||
'c8ee-49-36-191-130.ngrok-free.app',
|
||||
'c76f1251c173.ngrok-free.app',
|
||||
'enabled-upright-boxer.ngrok-free.app',
|
||||
'basically-polished-yeti.ngrok-free.app',
|
||||
'10.10.1.248:8083'
|
||||
],
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 600,
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue