478 lines
18 KiB
TypeScript
478 lines
18 KiB
TypeScript
import { IoMdClose } from "react-icons/io";
|
|
|
|
import RecipientAddCard from './RecipientAddCard';
|
|
import RecipientCards from './RecipientCards';
|
|
import SelectedDocuments from "./SelectedDocuments";
|
|
import api from "../../../config/api";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { RootState } from "../../../store/store";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
|
import { setImages } from "../../../store/slices/selectedDocsSlice";
|
|
import { addRecipient, addRecipientAsync, fetchRecipients, recipientType, removeAllRecipients, removeRecipientAsync, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE, updateRecipientIds } from "../../../store/slices/selectedRecipientsSlice";
|
|
import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from "../../../store/slices/transactionSlice";
|
|
import { hideLoader, showLoader } from "../../../store/slices/loaderSlice";
|
|
import Loader from "../../dashboard/Loader";
|
|
import { Bounce, toast } from "react-toastify";
|
|
import { fetchDocuments, resetDocuments, setDocuments, uploadDocuments } from "../../../store/slices/documentSlice";
|
|
import { SignTypeEnum } from "../../../utlity/types";
|
|
import { RiPencilFill } from "react-icons/ri";
|
|
import SelfSignWarnModal from "./SelfSignWarnModal";
|
|
|
|
const ProcessStepOne = () => {
|
|
interface EmailDtls {
|
|
subject: string;
|
|
message: string;
|
|
}
|
|
|
|
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
|
|
|
const signType = useParams<{ signType: string }>().signType;
|
|
const templateId = useParams<{ templateId: string }>().templateId;
|
|
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
|
const [searchParams] = useSearchParams();
|
|
|
|
const isTamplate = searchParams.get('useTemplate')
|
|
|
|
const navigate = useNavigate();
|
|
const dispatch = useDispatch();
|
|
|
|
if (!transactionId) {
|
|
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
|
|
|
|
transactionId = templateId;
|
|
dispatch(toggleToTemplates());
|
|
}
|
|
|
|
const [editCase, setEditCase] = useState<string>("")
|
|
const [editRecipient, setEditRecipient] = useState({})
|
|
|
|
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
|
|
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
|
|
const [showSelfSignWarnModal, setShowSelfSignWarnModal] = useState(false)
|
|
const [editRecipientCurrentEmail, setEditRecipientCurrentEmail] = useState('')
|
|
|
|
const userData = useSelector((state: RootState) => state.user)
|
|
// ProcessStepOne.tsx
|
|
const topRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
const [templateName, setTemplateName] = useState('Untitled');
|
|
const [isTyping, setIsTyping] = useState(false);
|
|
|
|
// Optional: use a timer to detect pause in typing
|
|
let typingTimeout = null;
|
|
|
|
const handleChange = (e) => {
|
|
setTemplateName(e.target.value);
|
|
setIsTyping(true);
|
|
|
|
clearTimeout(typingTimeout);
|
|
typingTimeout = setTimeout(() => {
|
|
setIsTyping(false);
|
|
}, 5000); // hide icon while typing, show it again after 500ms of no input
|
|
};
|
|
|
|
const [transactionDetails, setTransactionDetails] = useState<any>([]);
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
try {
|
|
const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`);
|
|
setTransactionDetails(res.data)
|
|
|
|
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
|
|
setTemplateName(res.data.name)
|
|
}
|
|
|
|
setEmailDtls(
|
|
{ subject: res.data.subject ? res.data.subject : "Documents for Signature", message: res.data.message ? res.data.message : "" }
|
|
)
|
|
|
|
} catch (e) {
|
|
}
|
|
})()
|
|
|
|
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
|
|
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
|
|
}, [])
|
|
|
|
const [emailDtls, setEmailDtls] = useState<EmailDtls>({ subject: "Documents for Signature", message: "" });
|
|
|
|
useEffect(() => {
|
|
dispatch(hideLoader());
|
|
}, [])
|
|
|
|
|
|
const getMinRoutingOrder = () => {
|
|
return selectedRecipients.reduce((min, recipient) => {
|
|
return Math.min(min, recipient.routingOrder ?? Infinity);
|
|
}, Infinity);
|
|
};
|
|
|
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
const handleNextClick = async (saveAndClose: boolean = false) => {
|
|
let formError = {
|
|
documents: false,
|
|
recipients: false,
|
|
subject: false,
|
|
recipientEmailAndLastName: false
|
|
}
|
|
|
|
if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
|
|
if (!selectedDocuments.length) {
|
|
formError.documents = true
|
|
}
|
|
if (!selectedRecipients.length) {
|
|
formError.recipients = true;
|
|
} else {
|
|
// Check if all recipients have email and lastName
|
|
const isValid = selectedRecipients.every(
|
|
(item) => item.email?.trim() !== '' && item.lastName?.trim() !== ''
|
|
);
|
|
|
|
if (!isValid) {
|
|
// alert('All recipients must have both Email and Last Name filled.');
|
|
formError.recipientEmailAndLastName = true;
|
|
}
|
|
}
|
|
if (!emailDtls.subject.length) {
|
|
formError.subject = true
|
|
}
|
|
|
|
|
|
if (SignTypeEnum.selfSign === signType) formError.recipients = false;
|
|
|
|
if (formError.documents || formError.recipients || formError.subject || formError.recipientEmailAndLastName) {
|
|
toast.dismiss();
|
|
toast.error(`${formError.documents ? "Add Document" : ""}
|
|
${(formError.documents && formError.recipients) ? " | " : ""}
|
|
${formError.recipients ? "Add Recipient" : ""}
|
|
${formError.recipientEmailAndLastName ? "All recipients must have both Email and Last Name." : ""}
|
|
${(formError.documents || formError.recipients) ? " | " : ""}
|
|
${formError.subject ? "Add Subject" : ""}`, {
|
|
position: "top-center",
|
|
autoClose: 5000,
|
|
hideProgressBar: false,
|
|
closeOnClick: false,
|
|
pauseOnHover: true,
|
|
draggable: true,
|
|
progress: undefined,
|
|
theme: "light",
|
|
transition: Bounce,
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
try {
|
|
dispatch(showLoader("Processing Documents..."));
|
|
|
|
const documentsData = selectedDocuments.map((doc, index) => ({
|
|
name: doc.name,
|
|
documentId: doc.id,
|
|
order: index,
|
|
}));
|
|
|
|
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 => {
|
|
if (res.status === 200) {
|
|
dispatch(setDocuments(res.data));
|
|
}
|
|
}).catch((err) => {
|
|
console.error(err)
|
|
});
|
|
|
|
dispatch(showLoader("Processing Recipients..."));
|
|
|
|
let minRoutingOrder = getMinRoutingOrder();
|
|
// selectedRecipients.forEach((recipient) => {
|
|
// if (recipient.routingOrder ?? 1 < minRoutingOrder) {
|
|
// minRoutingOrder = recipient.routingOrder ?? 1;
|
|
// }
|
|
// });
|
|
|
|
const recipientsData = selectedRecipients.map((recipient, index) => ({
|
|
id: "id" in recipient ? recipient.id : null,
|
|
routingOrder: "routingOrder" in recipient && recipient.routingOrder ? recipient.routingOrder : 1,
|
|
provideOwnTags: userData.email === recipient.email && (recipient.routingOrder ?? 1) === minRoutingOrder ? true : false,
|
|
type: recipient.type,
|
|
firstName: recipient.firstName,
|
|
lastName: recipient.lastName,
|
|
authType: "NONE",
|
|
deliveryMethod: "EMAIL",
|
|
email: recipient.email,
|
|
hostName: recipient.hostName,
|
|
hostEmail: recipient.hostEmail,
|
|
company: "",
|
|
title: "",
|
|
website: "",
|
|
phone: "",
|
|
accessCode: "",
|
|
accessCodeDelivery: true,
|
|
language: "en",
|
|
name: `${recipient.firstName} ${recipient.lastName}`,
|
|
roleName: recipient.roleName,
|
|
allowSenderToEdit: recipient.allowSenderToEdit,
|
|
allowSenderToDelete: recipient.allowSenderToDelete,
|
|
}));
|
|
|
|
let recipientsDataTemp = recipientsData;
|
|
let recipientId = null;
|
|
|
|
if (signType === SignTypeEnum.selfSign) {
|
|
dispatch(removeAllRecipients());
|
|
await Promise.all(
|
|
selectedRecipients.map((recipient) => dispatch(removeRecipientAsync({
|
|
recipientId: recipient.id,
|
|
accountId,
|
|
transactionId,
|
|
email: recipient.email,
|
|
})))
|
|
);
|
|
|
|
recipientsDataTemp = [{
|
|
id: null,
|
|
routingOrder: 1,
|
|
provideOwnTags: true,
|
|
type: recipientType.SIGNER,
|
|
firstName: userData.firstName,
|
|
lastName: userData.lastName,
|
|
authType: "NONE",
|
|
deliveryMethod: "EMAIL",
|
|
email: userData.email,
|
|
hostName: "",
|
|
hostEmail: "",
|
|
company: "",
|
|
title: "",
|
|
website: "",
|
|
phone: "",
|
|
accessCode: "",
|
|
accessCodeDelivery: true,
|
|
language: userData.language ? userData.language : "en",
|
|
name: `${userData.firstName} ${userData.lastName}`,
|
|
roleName: "",
|
|
allowSenderToEdit: true,
|
|
allowSenderToDelete: true,
|
|
}]
|
|
|
|
try {
|
|
const basePath = `/api/accounts/${accountId}/transactions/${transactionId}/recipients`;
|
|
const res = await api.post(basePath, recipientsDataTemp)
|
|
recipientId = res.data[0].id;
|
|
} catch (error) {
|
|
console.log("Error Creating Self Sign Recipient", error.message)
|
|
}
|
|
|
|
} else {
|
|
if(!accountId || !transactionId) return;
|
|
dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId }));
|
|
}
|
|
dispatch(showLoader("Processing Message..."));
|
|
|
|
let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, {
|
|
name: templateName,
|
|
subject: emailDtls.subject, message: emailDtls.message,
|
|
status: "CREATED"
|
|
})
|
|
|
|
dispatch(resetDocuments())
|
|
dispatch(resetRecipients())
|
|
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
|
|
dispatch(hideLoader());
|
|
if (!saveAndClose) {
|
|
navigate(`/process-step-two/${accountId}/template/${transactionId}`)
|
|
} else {
|
|
navigate(`/templates`)
|
|
}
|
|
} else {
|
|
if (signType === SignTypeEnum.selfSign) {
|
|
dispatch(showLoader("Processing eSignature..."));
|
|
(async () => {
|
|
try {
|
|
await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" })
|
|
dispatch(hideLoader());
|
|
navigate(`/sign/${recipientId}/self_sign`);
|
|
} catch (err) {
|
|
console.log("Error Transaction Sent", err.message);
|
|
}
|
|
})()
|
|
} else {
|
|
navigate(`/process-step-two/${accountId}/${transactionId}`);
|
|
}
|
|
}
|
|
// }, 1000);
|
|
// }
|
|
|
|
} catch (error) {
|
|
dispatch(hideLoader());
|
|
console.error("Error submitting data step-by-step:", error);
|
|
toast.error("Something went wrong. Please try again.", {
|
|
position: "top-center",
|
|
autoClose: 5000,
|
|
theme: "light",
|
|
transition: Bounce,
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED))
|
|
}, [])
|
|
|
|
const handleClose = () => {
|
|
dispatch(resetDocuments())
|
|
navigate('/')
|
|
}
|
|
|
|
|
|
const handleSelfSignCb = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (e.currentTarget.checked) {
|
|
navigate("self_sign");
|
|
setShowSelfSignWarnModal((prev) => !prev)
|
|
} else {
|
|
navigate("./../");
|
|
}
|
|
}
|
|
|
|
const globalLoader = useSelector((state: RootState) => state.loader)
|
|
|
|
return (
|
|
<>
|
|
<div className={`relative w-full h-screen bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none overflow-hidden' : ''}`}>
|
|
|
|
{/* Top Navigation - Fixed */}
|
|
<div className='fixed top-0 left-0 right-0 w-full flex items-center justify-between bg-white shadow z-50 px-4 py-3'>
|
|
<div className='flex items-center gap-2'>
|
|
<IoMdClose
|
|
onClick={handleClose}
|
|
className='text-2xl text-slate-500 cursor-pointer hover:text-[var(--primary)] transition'
|
|
/>
|
|
<p className="text-base font-normal text-gray-700 border-l pl-4 relative">
|
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES ?
|
|
<>
|
|
<input
|
|
value={templateName}
|
|
onChange={handleChange}
|
|
className="w-full px-4 py-1.5 border-2 border-dashed rounded-md text-sm focus:outline-none transition border-gray-300 focus:ring-2 focus:border-dashed focus:ring-[var(--primary)]"
|
|
/>
|
|
{!isTyping && (
|
|
<RiPencilFill className="absolute top-2.5 right-2 text-[17px]" />
|
|
)}
|
|
</> :
|
|
"Add Recipient and Message"
|
|
}
|
|
</p>
|
|
</div>
|
|
|
|
<div
|
|
id="add-tags-btn-step-one"
|
|
className='flex items-center gap-3'>
|
|
|
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && <button
|
|
onClick={() => handleNextClick(true)}
|
|
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer"
|
|
>
|
|
Save & Close
|
|
</button>}
|
|
|
|
<button
|
|
onClick={() => handleNextClick(false)}
|
|
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer"
|
|
>
|
|
{signType === SignTypeEnum.selfSign ? "Sign" : "Next"}
|
|
</button>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content - Scrollable */}
|
|
<div className="h-screen pt-[80px] overflow-y-auto no-scrollbar">
|
|
<div className="max-w-4xl rounded-xl shadow-md mx-auto px-6 pb-10 bg-[var(--documentUploadBg)] mb-5">
|
|
|
|
{/* Documents */}
|
|
<section>
|
|
<h2 className="text-lg font-medium mb-4 pt-3 text-[var(--processStepOneTextHead)]">Documents</h2>
|
|
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} />
|
|
</section>
|
|
|
|
{/* Recipients */}
|
|
<section className="mt-8">
|
|
<p className="border-t border-gray-200"></p>
|
|
{!isTamplate &&
|
|
transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES &&
|
|
<div className="flex justify-between items-center">
|
|
<h2 className=" text-lg font-medium mb-4 pt-6 text-[var(--processStepOneTextHead)]">Signers</h2>
|
|
<div className="flex items-center mr-3">
|
|
<input
|
|
checked={signType === SignTypeEnum.selfSign}
|
|
onChange={(e) => handleSelfSignCb(e)}
|
|
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 text-blue-600 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">
|
|
<span>SELF </span><span>SIGN</span></label>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
{
|
|
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
|
|
<div className="flex flex-col gap-4 align-start">
|
|
|
|
<RecipientCards userEmail={userData.email} getMinRoutingOrder={getMinRoutingOrder} transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} editRecipient={editRecipient} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} editRecipientCurrentEmail={editRecipientCurrentEmail} />
|
|
|
|
<div
|
|
ref={topRef}
|
|
className={`relative w-full bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none' : ''}`}
|
|
>
|
|
<RecipientAddCard userEmail={userData.email} getMinRoutingOrder={getMinRoutingOrder} transactionTemplateToggle={transactionTemplateToggle} editRecipient={editRecipient} editCase={editCase} setEditRecipient={setEditRecipient} setEditCase={setEditCase} topRef={topRef} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} />
|
|
</div>
|
|
|
|
</div>
|
|
}
|
|
</section>
|
|
|
|
{/* Email */}
|
|
<section className="mt-8">
|
|
<h2 className="text-lg font-medium mb-4 pt-6 border-t border-gray-200 text-[var(--processStepOneTextHead)]">Email</h2>
|
|
|
|
{/* Subject */}
|
|
<div className='flex flex-col mb-4'>
|
|
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Subject</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={emailDtls.subject}
|
|
onChange={(e) => setEmailDtls({ ...emailDtls, subject: e.target.value })}
|
|
placeholder="Email Subject"
|
|
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
|
|
/>
|
|
</div>
|
|
|
|
{/* Message */}
|
|
<div className='flex flex-col'>
|
|
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Message</label>
|
|
<textarea
|
|
rows={4}
|
|
value={emailDtls.message}
|
|
onChange={(e) => setEmailDtls({ ...emailDtls, message: e.target.value })}
|
|
placeholder="Message"
|
|
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{globalLoader.isLoading && <Loader />}
|
|
{showSelfSignWarnModal && <SelfSignWarnModal setShowSelfSignWarnModal={setShowSelfSignWarnModal} />}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default ProcessStepOne |