This commit is contained in:
Bhagwan Singh 2026-05-15 17:06:32 +05:30
parent 42b4c1dd5b
commit 7612f6a3d3
14 changed files with 232 additions and 142 deletions

2
.env
View File

@ -6,5 +6,7 @@
# VITE_BASE_URI="https://smashing-albacore-engaging.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="https://basically-polished-yeti.ngrok-free.app"
# VITE_BASE_URI="https://sacha-countryfied-evolutionistically.ngrok-free.dev" # VITE_BASE_URI="https://sacha-countryfied-evolutionistically.ngrok-free.dev"
# VITE_BASE_URI="https://web.signmadeasy.app"
# VITE_SSE_URI="https://web.signmadeasy.app"
VITE_BASE_URI="https://web.signmadeasy.app" VITE_BASE_URI="https://web.signmadeasy.app"
VITE_SSE_URI="https://web.signmadeasy.app" VITE_SSE_URI="https://web.signmadeasy.app"

View File

@ -9,7 +9,7 @@
"build:skip-check": "tsc --noEmit && vite build", "build:skip-check": "tsc --noEmit && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
"deploy": "npm run build:skip-check && scp -i private\\SME-Web.pem -r dist root@100.22.185.166:/var/www/sme" "deploy": "npm run build:skip-check && pscp -i private\\SME-Web.ppk -r dist root@100.22.185.166:/var/www/sme"
}, },
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",

View File

@ -114,7 +114,7 @@ const App = () => {
<Route path="/view" element={<DocView />} /> <Route path="/view" element={<DocView />} />
<Route path="/session_expired" element={<SessionExpired />} /> <Route path="/session_expired" element={<SessionExpired />} />
<Route path="/transaction-complete" element={<CompletionScreen />} /> <Route path="/transaction-complete/:recipientId/" element={<CompletionScreen />} />
<Route path="/transaction-declined" element={<CompletionScreen hit="Action Canceled! " title="Maybe Next Time!" message={" Your request has been declined successfully. You can revisit anytime if you change your mind."} textColorSuccess={false} />} /> <Route path="/transaction-declined" element={<CompletionScreen hit="Action Canceled! " title="Maybe Next Time!" message={" Your request has been declined successfully. You can revisit anytime if you change your mind."} textColorSuccess={false} />} />
<Route path="/transaction-reassigned" element={<CompletionScreen hit="Reasssigned Successfully" title=" The transaction has been reassigned!" message={"The transaction has been reassigned to another user"} textColorSuccess={false} />} /> <Route path="/transaction-reassigned" element={<CompletionScreen hit="Reasssigned Successfully" title=" The transaction has been reassigned!" message={"The transaction has been reassigned to another user"} textColorSuccess={false} />} />

View File

@ -1,6 +1,8 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { RiFileList3Line } from "react-icons/ri"; import { RiFileList3Line } from "react-icons/ri";
import confetti from 'canvas-confetti' import confetti from 'canvas-confetti'
import handleDownloadDocument from "../../helper/handleDownloadDocumentRecSide";
import { useParams } from "react-router-dom";
interface CompletionScreenProps { interface CompletionScreenProps {
hit?: string; hit?: string;
@ -15,7 +17,7 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
message = "Your document is now complete and securely stored. We’ve emailed you a copy for your reference.", message = "Your document is now complete and securely stored. We’ve emailed you a copy for your reference.",
textColorSuccess = true textColorSuccess = true
}) => { }) => {
const { recipientId } = useParams();
useEffect(() => { useEffect(() => {
@ -60,10 +62,11 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
{/* Header */} {/* Header */}
<header className="px-4 py-3 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"> <div className="flex items-center gap-2">
<RiFileList3Line className="text-[var(--mainPrimaryBtn)] text-2xl" /> {/* <RiFileList3Line className="text-[var(--mainPrimaryBtn)] text-2xl" />
<span className="text-md font-semibold text-[var(--mainPrimaryBtn)]"> <span className="text-md font-semibold text-[var(--mainPrimaryBtn)]">
Sign made easy Sign made easy
</span> </span> */}
<img class="py-2 w-[140px]" alt="" src="/sme-colored.png"></img>
</div> </div>
</header> </header>
@ -76,6 +79,13 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
{message} {message}
</p> </p>
<p
onClick={() => { handleDownloadDocument(recipientId || "") }}
className="text-white bg-[var(--mainPrimaryBtn)] px-3 py-1 rounded-sm mt-4 cursor-pointer">
Download Signed Documents
</p>
{/* <button className="flex items-center gap-2 mt-6 bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 transition"> {/* <button className="flex items-center gap-2 mt-6 bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 transition">
<FiDownload /> <FiDownload />
Download Download
@ -84,11 +94,10 @@ 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"> {/* <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 <FiZap /> Login
</button> */} </button> */}
<p className="relative z-50 font-semibold mt-4 2xl:mt-6 text-md 2xl:text-xl"> <p className="relative z-50 font-semibold mt-2 2xl:mt-4 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> Access your documents anytime — just <a href="/login" className="text-[var(--mainPrimaryBtn)] py-1 border-b-1 border-dashed">Login</a>
</p> </p>
{/* Illustration placeholder */} {/* Illustration placeholder */}
<div className="mt-6 2xl:mt-10"> <div className="mt-6 2xl:mt-10">
{textColorSuccess ? <img {textColorSuccess ? <img

View File

@ -3,6 +3,8 @@ import { FaSignature, FaDownload, FaUserEdit, FaTimesCircle, FaAngleLeft, FaAngl
import { BsThreeDotsVertical } from "react-icons/bs"; import { BsThreeDotsVertical } from "react-icons/bs";
import ClientDownloadDocument from "./ClientDownloadDocument"; import ClientDownloadDocument from "./ClientDownloadDocument";
import { AiOutlineAudit } from "react-icons/ai"; import { AiOutlineAudit } from "react-icons/ai";
import { useParams } from "react-router-dom";
import handleDownloadDocument from "../../helper/handleDownloadDocumentRecSide";
interface Props { interface Props {
transparentTags?: boolean; transparentTags?: boolean;
@ -30,6 +32,8 @@ export default function SignButton({
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
const { recipientId } = useParams();
const [showMenuDownload, setShowMenuDownload] = useState<{ open: boolean, transactionId: string | null }>({ const [showMenuDownload, setShowMenuDownload] = useState<{ open: boolean, transactionId: string | null }>({
open: false, open: false,
transactionId: null transactionId: null
@ -91,6 +95,18 @@ export default function SignButton({
<span>Download Document</span> <span>Download Document</span>
</button> </button>
</li> */} </li> */}
<li>
<button
onClick={() => {
handleDownloadDocument(recipientId || "")
setMenuOpen(false);
}}
className="flex items-center gap-3 w-full cursor-pointer text-left px-4 py-2 hover:bg-gray-100"
>
<AiOutlineAudit />
<span>Download Documents</span>
</button>
</li>
<li> <li>
<button <button
onClick={() => { onClick={() => {

View File

@ -529,7 +529,7 @@ const SignDocument = () => {
// setShowCompletionScreen(true) // setShowCompletionScreen(true)
setSelectedTag(null) setSelectedTag(null)
// navigate("/transaction-complete") // navigate("/transaction-complete")
window.location.href = `/transaction-complete` window.location.href = `/transaction-complete/${recipientId}`
} }
} catch (e) { } catch (e) {
dispatch(hideLoader()); dispatch(hideLoader());
@ -1170,7 +1170,7 @@ const SignDocument = () => {
{/* Side details of documents Pages and document Name - END */} {/* Side details of documents Pages and document Name - END */}
{!transparentTags && focusElement != null && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index && {!transparentTags && focusElement != null && focusElement && Object.keys(focusElement).length > 1 && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index &&
<div <div
id="tag-navigation-btn" id="tag-navigation-btn"
className={`hidden absolute right-0 translate-x-[100%] sm:flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`} className={`hidden absolute right-0 translate-x-[100%] sm:flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
@ -1326,7 +1326,7 @@ const SignDocument = () => {
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" /> className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
</div> */} </div> */}
<div> <div>
{!transparentTags && focusElement != null && {!transparentTags && focusElement != null && Object.keys(focusElement).length > 1 &&
<div <div
id="tag-navigation-btn-mb" id="tag-navigation-btn-mb"
className={`sm:hidden flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`} className={`sm:hidden flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}

View File

@ -5,7 +5,7 @@ import { RootState } from "../../../store/store";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { addRecipientAsync, fetchRecipients, recipientType, removeAllRecipients, removeRecipientAsync, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice"; import { addRecipientAsync, fetchRecipients, recipientType, removeAllRecipients, removeRecipientAsync, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE, updateRecipientOrder } from "../../../store/slices/selectedRecipientsSlice";
import { TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from "../../../store/slices/transactionSlice"; import { TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from "../../../store/slices/transactionSlice";
import { hideLoader, showLoader } from "../../../store/slices/loaderSlice"; import { hideLoader, showLoader } from "../../../store/slices/loaderSlice";
import Loader from "../../dashboard/Loader"; import Loader from "../../dashboard/Loader";
@ -415,13 +415,29 @@ const ProcessStepOne = () => {
) )
} }
function handleSigningOrderChange() {
setSigningOrder(!signingOrder)
if (!signingOrder) {
selectedRecipients.forEach((recipient, index) => {
const id = recipient.id || recipient.uuid || "1"
dispatch(updateRecipientOrder({ id, routingOrder: index + 1 }));
})
} else {
selectedRecipients.forEach((recipient, index) => {
const id = recipient.id || recipient.uuid || "1"
dispatch(updateRecipientOrder({ id, routingOrder: 1 }));
})
}
}
return ( return (
<> <>
<div className={`relative w-full h-screen bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none overflow-hidden' : ''}`}> <div className={`relative w-full h-screen bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none overflow-hidden' : ''}`}>
{/* Top Navigation - Fixed */} {/* Top Navigation - Fixed */}
<div className='fixed top-0 left-0 right-0 w-full bg-white shadow z-50 px-4 py-1.5 lg:py-3'> <div className='fixed top-0 left-0 right-0 w-full bg-white shadow z-50 px-4 py-1.5 lg:py-3 z-50'>
<div className='flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 flex-wrap'> <div className='flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 flex-wrap'>
{/* Left Section */} {/* Left Section */}
@ -490,7 +506,7 @@ const ProcessStepOne = () => {
{/* Documents */} {/* Documents */}
<section> <section>
<h2 className="text-lg font-medium mb-4 pt-3 text-[var(--processStepOneTextHead)]">Documents</h2> <h2 className="text-lg font-medium mb-4 pt-3 text-[var(--processStepOneTextHead)]">Add documents</h2>
<div id="documents_con" className={`${submitError && submitError.documents && submitError.showError ? "border border-red-500 rounded-sm p-2 ring-2 ring-red-300" : ""}`}> <div id="documents_con" className={`${submitError && submitError.documents && submitError.showError ? "border border-red-500 rounded-sm p-2 ring-2 ring-red-300" : ""}`}>
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} /> <SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} />
<p className="text-xs text-right px-2 pt-2 text-red-500">{submitError && submitError.documents && submitError.showError && "At least one document is required."}</p> <p className="text-xs text-right px-2 pt-2 text-red-500">{submitError && submitError.documents && submitError.showError && "At least one document is required."}</p>
@ -500,40 +516,40 @@ const ProcessStepOne = () => {
{/* Recipients */} {/* Recipients */}
<section className="mt-8"> <section className="mt-8">
<p className="border-t border-gray-200"></p> <p className="border-t border-gray-200"></p>
{!isTemplate && <div className="flex justify-between items-center">
transactionTemplateToggle != TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && <h2 className=" text-lg font-medium mb-4 pt-6 text-[var(--processStepOneTextHead)]">Add recipients</h2>
<div className="flex justify-between items-center">
<h2 className=" text-lg font-medium mb-4 pt-6 text-[var(--processStepOneTextHead)]">Signers</h2> {!isTemplate &&
{/* <div className="flex items-center mr-3"> transactionTemplateToggle != TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES &&
<>
{/* <div className="flex items-center mr-3">
<input <input
checked={signType == SignTypeEnum.selfSign} checked={signType == SignTypeEnum.selfSign}
onChange={(e) => handleSelfSignCb(e)} onChange={(e) => handleSelfSignCb(e)}
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 checked:bg-[var(--mainPrimaryBtn)] bg-gray-100 border-gray-300 rounded-sm outline-none" id="default-checkbox" type="checkbox" value="" className="w-4 h-4 checked:bg-[var(--mainPrimaryBtn)] bg-gray-100 border-gray-300 rounded-sm outline-none"
/> />
<label htmlFor="default-checkbox" className="ms-2 text-sm flex gap-1 text-slate-600"> <label htmlFor="default-checkbox" className="ms-2 text-sm flex gap-1 text-slate-600">
I AM THE ONLY SIGNER</label> I AM THE ONLY SIGNER</label>
</div> */} </div> */}
</>
}
{/* HERE WE AREEEEEEEE */}
{selectedRecipients.length > 1 && <div className="flex items-center">
<input
// checked={signType == SignTypeEnum.selfSign}
onChange={handleSigningOrderChange}
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 checked:bg-[var(--mainPrimaryBtn)] bg-gray-100 border-gray-300 rounded-sm outline-none"
/>
<label htmlFor="default-checkbox" className="ms-2 text-sm flex gap-1 text-slate-600">
Set signing order
</label>
</div>}
{/* HERE WE AREEEEEEEE */}
</div>
{/* HERE WE AREEEEEEEE */}
<div className="flex items-center">
<input
// checked={signType == SignTypeEnum.selfSign}
onChange={() => setSigningOrder(!signingOrder)}
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 checked:bg-[var(--mainPrimaryBtn)] bg-gray-100 border-gray-300 rounded-sm outline-none"
/>
<label htmlFor="default-checkbox" className="ms-2 text-sm flex gap-1 text-slate-600">
Set signing order
</label>
</div>
{/* HERE WE AREEEEEEEE */}
</div>
}
{ {
(signType != SignTypeEnum.selfSign || showSelfSignWarnModal) && (signType != SignTypeEnum.selfSign || showSelfSignWarnModal) &&
<> <>
@ -553,7 +569,7 @@ const ProcessStepOne = () => {
{/* Email */} {/* Email */}
<section className="mt-8"> <section className="mt-8">
<h2 className="text-lg font-medium mb-4 pt-6 border-t border-gray-200 text-[var(--processStepOneTextHead)]">Email</h2> <h2 className="text-lg font-medium mb-4 pt-6 border-t border-gray-200 text-[var(--processStepOneTextHead)]">Add Message</h2>
{/* Subject */} {/* Subject */}
<div className="border-1 rounded-xl border-gray-300 px-6 py-8"> <div className="border-1 rounded-xl border-gray-300 px-6 py-8">

View File

@ -357,10 +357,10 @@ const RecipientCards = ({
}; };
return ( return (
<div className="w-full rounded-md bg-slate-50"> <div className="w-full rounded-md">
{recipients.length > 0 ? ( {recipients.length > 0 ? (
<> <>
{recipients.length > 1 && ( {/* {recipients.length > 1 && (
<div className="flex sm:flex-row sm:items-center justify-between px-3 sm:px-4 py-2 border-b border-gray-300 gap-2"> <div className="flex sm:flex-row sm:items-center justify-between px-3 sm:px-4 py-2 border-b border-gray-300 gap-2">
<h3 className="text-sm font-semibold text-gray-700"> <h3 className="text-sm font-semibold text-gray-700">
Recipients ({recipients.length}) Recipients ({recipients.length})
@ -373,7 +373,7 @@ const RecipientCards = ({
<FaUsersLine className="text-[var(--primary)] mt-0.5 text-lg" /> <FaUsersLine className="text-[var(--primary)] mt-0.5 text-lg" />
</label> </label>
</div> </div>
)} )} */}
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}> <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
@ -381,13 +381,14 @@ const RecipientCards = ({
items={recipients.map((r) => r.id || r.uuid)} items={recipients.map((r) => r.id || r.uuid)}
strategy={verticalListSortingStrategy} strategy={verticalListSortingStrategy}
> >
<div className="flex flex-col divide-y divide-gray-200 max-h-[47vh] overflow-y-auto custom-scrollbar"> <div className="flex flex-col max-h-[47vh] overflow-y-auto custom-scrollbar">
{recipients.map((recipient, idx) => ( {recipients.map((recipient, idx) => (
<SortableRecipient <SortableRecipient
key={recipient.id || recipient.uuid} key={recipient.id || recipient.uuid}
recipient={recipient} recipient={recipient}
idx={idx} idx={idx}
editRecipientCurrentEmail={editRecipientCurrentEmail} editRecipientCurrentEmail={editRecipientCurrentEmail}
signingOrder={signingOrder}
> >
<div <div
key={recipient.email + idx} key={recipient.email + idx}
@ -399,63 +400,73 @@ const RecipientCards = ({
<> <>
{/* Main Content Section */} {/* Main Content Section */}
<div className="flex items-start sm:items-center gap-2 sm:gap-3 min-w-0 flex-1 pl-8"> <div className="flex items-start sm:items-center gap-2 sm:gap-3 min-w-0 flex-1">
{/* Order Number */} {/* Order Number */}
{recipients.length > 1 && signingOrder ? (
<div className="relative flex-shrink-0"> <div className="">
<input {recipients.length > 1 && signingOrder ? (
type="text" <div className="relative flex-shrink-0">
value={recipient.routingOrder == 0 ? "" : recipient.routingOrder} <input
onChange={(e) => { type="text"
const value = e.target.value; value={recipient.routingOrder == 0 ? "" : recipient.routingOrder}
if (/^\d*$/.test(value)) { onChange={(e) => {
const num = Number(value); const value = e.target.value;
handleOrderChange(recipient.id || recipient.uuid, num); if (/^\d*$/.test(value)) {
const num = Number(value);
handleOrderChange(recipient.id || recipient.uuid, num);
}
}}
onKeyDown={(e) => {
if (!/[0-9]/.test(e.key) && e.key != "Backspace" && e.key != "Tab") {
e.preventDefault();
}
}}
className="w-8 sm:w-10 h-6 text-center rounded text-sm text-gray-600 focus:outline-none focus:ring-[var(--primary)] border border-dashed border-gray-400"
/>
<>
{idx == 0 && recipients.length > 1 && <p className="group">
<>
{/* <BiSolidHelpCircle className="absolute top-[-12px] right-[-10px] text-gray-400 cursor-pointer" />
<span className="absolute top-[2px] left-[48px] hidden group-hover:block w-56 p-2 rounded-lg text-sm bg-white text-slate-600 shadow-lg z-10">
Signing order: Recipients receive emails one after another.
Same order means they all receive at once.
</span> */}
</>
</p>}
</>
</div>
) : (
<>{signingOrder &&
<input
type="number"
disabled
value={recipient.routingOrder}
min={1}
onChange={(e) =>
handleOrderChange(
recipient.id || recipient.uuid,
Number(e.target.value)
)
} }
}} className="w-6 sm:w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)] flex-shrink-0 border-2 border-slate-200"
onKeyDown={(e) => { />}
if (!/[0-9]/.test(e.key) && e.key != "Backspace" && e.key != "Tab") { </>
e.preventDefault(); )}
} </div>
}}
className="w-8 sm:w-10 h-6 text-center rounded text-sm text-gray-600 focus:outline-none focus:ring-[var(--primary)] border border-dashed border-gray-400"
/>
{idx == 0 && recipients.length > 1 && <p className="group">
<BiSolidHelpCircle className="absolute top-[-12px] right-[-10px] text-gray-400 cursor-pointer" />
{/* Tooltip */}
<span className="absolute top-[2px] left-[48px] hidden group-hover:block w-56 p-2 rounded-lg text-sm bg-white text-slate-600 shadow-lg z-10">
Signing order: Recipients receive emails one after another.
Same order means they all receive at once.
</span>
</p>}
</div>
) : (
<>{signingOrder &&
<input
type="number"
disabled
value={recipient.routingOrder}
min={1}
onChange={(e) =>
handleOrderChange(
recipient.id || recipient.uuid,
Number(e.target.value)
)
}
className="w-6 sm:w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)] flex-shrink-0"
/>}
</>
)}
{/* Avatar */} {/* Avatar */}
{/* <div className="flex-shrink-0 w-7 h-7 sm:w-8 sm:h-8 rounded-full bg-[var(--primary)] text-white flex items-center justify-center font-semibold text-xs select-none"> {/* <div className="flex-shrink-0 w-7 h-7 sm:w-8 sm:h-8 rounded-full bg-[var(--primary)] text-white flex items-center justify-center font-semibold text-xs select-none">
{getInitials(`${recipient.firstName} ${recipient.lastName || ""}`)} {getInitials(`${recipient.firstName} ${recipient.lastName || ""}`)}
</div> */} </div> */}
{/* Form Fields Section */} {/* Form Fields Section */}
<div className="min-w-0 flex-1"> <div className={`min-w-0 py-4 ${signingOrder ? "pl-8" : "pl-2"} flex-1 border-l-8 ${idx % 2 == 0 ? 'border-[#A5EDFC]' : 'border-[#84EFC3]'}`}>
{/* Role Name and Permissions Row */} {/* Role Name and Permissions Row */}
<div className="flex flex-col lg:flex-row lg:items-center gap-2 mb-2"> <div className="flex flex-col lg:flex-row lg:items-center gap-2 mb-2">
{<div className="flex lg:w-1/2"> {<div className="flex lg:w-1/2">
@ -515,9 +526,9 @@ const RecipientCards = ({
</div> </div>
{/* Name and Email Fields */} {/* Name and Email Fields */}
<div className="mb-2 w-[92%]"> <div className="mb-2 w-[100%]">
{/* Mobile: Stack vertically, Tablet+: Side by side */} {/* Mobile: Stack vertically, Tablet+: Side by side */}
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2"> <div className="flex flex-col justify-center items-center sm:flex-row gap-1 sm:gap-2">
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1"> <div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1">
<div className="w-full sm:w-1/2"> <div className="w-full sm:w-1/2">
<div className="font-semibold text-gray-800 text-xs"> <div className="font-semibold text-gray-800 text-xs">
@ -535,6 +546,26 @@ const RecipientCards = ({
{renderEditableField(recipient, "email", "Recipient Email", "", "email")} {renderEditableField(recipient, "email", "Recipient Email", "", "email")}
</div> </div>
</div> </div>
{/* Action Buttons - START*/}
{signType != "self-sign" && (
<div className="ml-4">
<button
type="button"
title="Remove recipient"
aria-label={`Remove recipient ${recipient.firstName}`}
onClick={() => handleRemoveRecipient(recipient)}
disabled={
transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES
? false
: !recipient.newOne && !recipient.allowSenderToDelete
}
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1"
>
<ImBin size={14} className="sm:w-4 sm:h-4" />
</button>
</div>
)}
{/* Action Buttons - END*/}
</div> </div>
</div> </div>
@ -600,26 +631,6 @@ const RecipientCards = ({
</div> </div>
</div> </div>
</div> </div>
{/* Action Buttons */}
{signType != "self-sign" && (
<div className="lg:ml-2 absolute top-[-8px] right-[-5px] px-1.5 py-1.5">
<button
type="button"
title="Remove recipient"
aria-label={`Remove recipient ${recipient.firstName}`}
onClick={() => handleRemoveRecipient(recipient)}
disabled={
transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES
? false
: !recipient.newOne && !recipient.allowSenderToDelete
}
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1"
>
<ImBin size={14} className="sm:w-4 sm:h-4" />
</button>
</div>
)}
</> </>

View File

@ -4,7 +4,7 @@ import { MdDragIndicator } from "react-icons/md";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../../store/store"; import { RootState } from "../../../store/store";
const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children }: any) => { const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children, signingOrder }: any) => {
const selectedRecipientsLen = useSelector((state: RootState) => state.selectedRecipients.recipients.length); const selectedRecipientsLen = useSelector((state: RootState) => state.selectedRecipients.recipients.length);
@ -22,12 +22,12 @@ const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children
transition, transition,
opacity: isDragging ? 0.6 : 1, opacity: isDragging ? 0.6 : 1,
}; };
return ( return (
<div <div
ref={setNodeRef} ref={setNodeRef}
style={style} style={style}
className={`relative flex flex-col lg:flex-row lg:items-center justify-between px-2 sm:px-3 lg:px-2 py-3 transition-colors gap-3 lg:gap-0 ${(recipient.uuid || recipient.id) == editRecipientCurrentEmail ? "bg-neutral-100 opacity-40" : ""} className={`relative flex flex-col lg:flex-row lg:items-center justify-between gap-3 lg:gap-0 my-3 ${(recipient.uuid || recipient.id) == editRecipientCurrentEmail ? "bg-neutral-100 opacity-40" : ""}
${isDragging ? "bg-blue-50 shadow-lg" : ""} ${isDragging ? "bg-blue-50 shadow-lg" : ""}
`} `}
> >
@ -37,7 +37,9 @@ const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children
{...listeners} {...listeners}
className="cursor-grab text-gray-500 flex items-center pr-2 absolute top-0 left-0 z-50" className="cursor-grab text-gray-500 flex items-center pr-2 absolute top-0 left-0 z-50"
> >
<MdDragIndicator className="absolute top-[10px] left-[10px] text-xl text-gray-300" />
{signingOrder && <MdDragIndicator className="absolute top-[59px] left-[70px] text-xl text-gray-300" />}
</div>} </div>}
{/* Your card content */} {/* Your card content */}

View File

@ -311,7 +311,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
setShowActionsDropDown(false) setShowActionsDropDown(false)
}} }}
className="cursor-pointer w-full text-left px-4 py-2 text-sm hover:bg-gray-100"> className="cursor-pointer w-full text-left px-4 py-2 text-sm hover:bg-gray-100">
Save Save Changes
</button> </button>
<button <button
onClick={() => { onClick={() => {
@ -319,7 +319,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
setShowActionsDropDown(false); setShowActionsDropDown(false);
}} }}
className="cursor-pointer w-full text-left px-4 py-2 text-sm hover:bg-gray-100"> className="cursor-pointer w-full text-left px-4 py-2 text-sm hover:bg-gray-100">
Save & Close Save & Exit
</button> </button>
<button <button
onClick={() => { onClick={() => {

View File

@ -133,7 +133,7 @@ const TransactionView = () => {
<> <>
{transactionDetails && {transactionDetails &&
<> <>
<div className='mt-2 grid grid-cols-1 lg:grid-cols-12 items-stretch max-w-90vw min-h-[88vh] h-full bg-white rounded-lg shadow-sm mx-3'> <div className='mt-2 grid grid-cols-1 lg:grid-cols-12 items-stretch max-w-[95vw] min-h-[88vh] h-full bg-white rounded-lg shadow-sm mx-3'>
<div className="lg:col-span-10 w-full px-6 py-4 rounded-tl-sm text-xs border-r-2 border-gray-200 overflow-y-auto"> <div className="lg:col-span-10 w-full px-6 py-4 rounded-tl-sm text-xs border-r-2 border-gray-200 overflow-y-auto">
{/* Header */} {/* Header */}
<h1 className='text-lg font-semibold text-gray-800'> <h1 className='text-lg font-semibold text-gray-800'>
@ -264,12 +264,12 @@ const TransactionView = () => {
<div className='lg:col-span-2 text-xs rounded-sm max-h-[88vh] overflow-y-auto no-scrollbar'> <div className='lg:col-span-2 text-xs rounded-sm max-h-[88vh] overflow-y-auto no-scrollbar'>
{documentsData.map((docsD, index) => ( {documentsData.map((docsD, index) => (
<div className='p-4 flex flex-col gap-2 cursor-pointer' key={docsD.id}> <div className='p-4 flex flex-col gap-2 cursor-pointer items-center' key={docsD.id}>
<p>{docsD.name}</p> <p>{docsD.name}</p>
<div className='border-3 rounded-sm border-slate-300 relative text-xs'> <div className='border-3 rounded-sm border-slate-300 relative text-xs w-[160px] h-[168px]'>
{docsD?.thumbnail ? ( {docsD?.thumbnail ? (
<img <img
style={{ width: "200px", height: "160px" }} style={{ width: "160px", height: "160px" }}
src={`data:image/jpeg;base64,${docsD.thumbnail}`} // Adjust MIME type if needed src={`data:image/jpeg;base64,${docsD.thumbnail}`} // Adjust MIME type if needed
alt="" alt=""
loading="lazy" loading="lazy"

View File

@ -0,0 +1,33 @@
import axiosInstance from "../config/axiosInstance";
const handleDownloadDocument = async (recipientId: string): Promise<void> => {
try {
const response = await axiosInstance.get(
`/api/v1/sign/${recipientId}/documents`,
{
responseType: "blob",
}
);
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
// IMPORTANT
link.download = "document.zip";
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
} catch (err) {
console.error(err);
}
};
export default handleDownloadDocument;

View File

@ -268,11 +268,11 @@ const selectedRecipientsSlice = createSlice({
}) })
.addCase(fetchRecipients.rejected, (state, action) => { .addCase(fetchRecipients.rejected, (state, action) => {
console.error("Failed to fetch recipients:", action.payload); console.error("Failed to fetch recipients:", action.payload);
toast.error("Failed to fetch recipients", { // toast.error("Failed to fetch recipients", {
position: "bottom-right", // position: "bottom-right",
autoClose: 5000, // autoClose: 5000,
theme: "light", // theme: "light",
}); // });
}) })
.addCase(addRecipientAsync.pending, () => { }) .addCase(addRecipientAsync.pending, () => { })
.addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient[]>) => { .addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
@ -283,11 +283,11 @@ const selectedRecipientsSlice = createSlice({
}) })
.addCase(addRecipientAsync.rejected, (state, action) => { .addCase(addRecipientAsync.rejected, (state, action) => {
console.error("Failed to add recipients:", action.payload); console.error("Failed to add recipients:", action.payload);
toast.error("Failed to add recipients", { // toast.error("Failed to add recipients", {
position: "bottom-right", // position: "bottom-right",
autoClose: 5000, // autoClose: 5000,
theme: "light", // theme: "light",
}); // });
}) })
.addCase(removeRecipientAsync.pending, () => { }) .addCase(removeRecipientAsync.pending, () => { })
.addCase(removeRecipientAsync.fulfilled, (state, action: PayloadAction<string | number>) => { .addCase(removeRecipientAsync.fulfilled, (state, action: PayloadAction<string | number>) => {
@ -297,11 +297,11 @@ const selectedRecipientsSlice = createSlice({
}) })
.addCase(removeRecipientAsync.rejected, (state, action) => { .addCase(removeRecipientAsync.rejected, (state, action) => {
console.error("Failed to delete recipient:", action.payload); console.error("Failed to delete recipient:", action.payload);
toast.error("Failed to delete recipient", { // toast.error("Failed to delete recipient", {
position: "bottom-right", // position: "bottom-right",
autoClose: 5000, // autoClose: 5000,
theme: "light", // theme: "light",
}); // });
}); });
} }
}); });

View File

@ -16,6 +16,7 @@ export default defineConfig({
'c76f1251c173.ngrok-free.app', 'c76f1251c173.ngrok-free.app',
'enabled-upright-boxer.ngrok-free.app', 'enabled-upright-boxer.ngrok-free.app',
'basically-polished-yeti.ngrok-free.app', 'basically-polished-yeti.ngrok-free.app',
'grace-antral-shari.ngrok-free.dev',
'10.10.1.248:8083' '10.10.1.248:8083'
], ],
}, },