import { useEffect, useState } from "react"; import axios from "axios" import { RootState } from "../../store/store"; import { useParams } from "react-router-dom" import { DroppedItem } from "../../utlity/types"; import Loader from "../dashboard/Loader"; import SignCon from "../signature/SignCon"; import { Bounce, toast } from "react-toastify"; import { useDispatch, useSelector } from "react-redux"; import { hideLoader, showLoader } from "../../store/slices/loaderSlice"; import { FaRegImage, FaFileAlt, FaSignature, FaStamp, FaCheck } from 'react-icons/fa' import { IoIosDocument } from 'react-icons/io' import { MdKeyboardArrowDown } from 'react-icons/md' import { TbChevronsDownLeft, TbSignature } from "react-icons/tb"; import { BsFileArrowDownFill } from "react-icons/bs"; import dayjs from "dayjs"; import maskUtils from "../../utlity/maskUtils"; import StampUpload from "../stamp/StampUpload"; import TermsModal from "./TermsModal"; interface RecipientType { id: string; firstName: string | null; lastName: string; fullName: string | null; email: string; title: string | null; initials: string | null; company: string | null; website: string | null; phone: string | null; index: number; type: string; recipientStatus: string; authType: string | null; language: string | null; transaction: string | null; acceptedTerms: boolean; name: string | null; } interface ImagesType { index: number; type: string; src: string; height: number; width: number; } interface DocumentType { documentId: string; images: ImagesType[]; name: string; } interface TransactionType { status: string; transationId: string; } interface TagSubmitData { id: string; type?: string; subType: string; signed: boolean; value: string; } interface TagSubmitRecData { recipientId: string; } export enum SaveTypeEnum { DRAW = "DRAW", TYPE = "TEXT", UPLOAD = "IMAGE", } export interface SaveType { saveType: SaveTypeEnum | null; save: boolean; signature: string | null; initial: string | null; } interface TagsSubmitDataObj { tags: TagSubmitData[]; recipient?: TagSubmitRecData } interface SignConfType { accepted: boolean; index: number; } interface SignConfTypeObj { [key: number]: SignConfType; } export interface StampTypeObj { open: boolean; blob: string; } interface FIT { label: string, value: number | null } const fitObj = [ { label: "Fit", value: "FIT" }, { label: "50%", value: "50" }, { label: "150%", value: "150" }, { label: "200%", value: "200" }, ] // Functions - LIST // handleSignClick - Handle when sign || Initial tag got clicked // fetchData - Fetch document images and tags data // handleFormSubmit - handle form submit and send data to server const SignDocument = () => { const { recipientId } = useParams(); const dispatch = useDispatch(); // Signer data - START const [selectedDocuments, setSelectedDocuments] = useState(null); const [recipient, setRecipient] = useState(null); const [tagJson, setTagJson] = useState([]); const [transaction, setTransaction] = useState(null); const [recSignStatus, setRecSignStatus] = useState(null); const [stamp, setStamp] = useState({ open: false, blob: "" }); // Handling Date Opacity const [dateValues, setDateValues] = useState<{ [key: string]: string }>({}); const [isDateOpen, setIsDateOpen] = useState(null); // Signer data - END // Show is fields - Showing all fields There const [isFields, setIsFields] = useState(true); const [isFitDd, setIsFitDd] = useState(false) const [selFitIndx, setSelFitIndx] = useState(0); const globalLoader = useSelector((state: RootState) => state.loader) const [openSign, setOpenSign] = useState(false); const [focusElement, setFocusElement] = useState(null); const [dropDownTogglePage, setDropDownTogglePage] = useState(""); const [nav2DocToggle, setNav2DocToggle] = useState(false); const [hideTags, setHideTags] = useState(false); const [transparentTags, setTransparentTags] = useState(false); const [saved, setSaved] = useState({ saveType: null, save: false, signature: null, initial: null, }); const [signConfObj, setSignConfObj] = useState({}); const zoom = (percentage: number, contentClass: string) => { const content = document.querySelector(`.${contentClass}`); if (content) { (content as HTMLElement).style.zoom = `${percentage}%`; } } useEffect(() => { let indx = fitObj[selFitIndx].value if (indx === "FIT") { indx = "100"; } zoom(Number(indx), 'zoom-con') }, [selFitIndx]) useEffect(() => { dispatch(showLoader("Loading ...")); fetchData(); }, []) useEffect(() => { if (!focusElement) { if (tagJson && tagJson.length) { setFocusElement({ ...tagJson[0], focusElementIndx: 0 }); } } }, [tagJson]) console.log(saved, "Line 162") useEffect(() => { if (recSignStatus === "COMPLETED") { console.log("SETTING HIDE TAGS to TRUE LIne 161") // setHideTags(true); setTransparentTags(true); } else { console.log("SETTING HIDE TAGS to TRUE LIne 161") setHideTags(false); } }, [recSignStatus]) useEffect(() => { if (saved.save && Object.keys(signConfObj).length) { const keys = Object.keys(signConfObj) const index = Number(keys[0]) const newObj = { accepted: true, index: index } setSignConfObj((prev) => { return ( { ...prev, [index]: { ...newObj } } ) }); } }, [saved]) const handleSignClick = (index: number) => { if (saved.save) { if (index in signConfObj) { setOpenSign(true); return }; setSignConfObj({ ...signConfObj, [index]: { accepted: true, index: index } }) return } setOpenSign(true); const newObj = { [index]: { "accepted": false, index: index } } setSignConfObj({ ...newObj }) } const handleStampClick = (index: number) => { console.log("Stamp Clicked", index) setStamp({ open: true, blob: "" }) } const handleSignAcknowledgeClick = (index: number) => { if (saved.save && signConfObj === null) { setSignConfObj([...signConfObj, index]) } } const fetchData = (isSign: boolean = false) => { axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`, { headers: { "ngrok-skip-browser-warning": "69420", } }) .then((res) => { console.dir(res) if (res.status === 200) { setSelectedDocuments(res.data.documents) setRecipient(res.data.recipient) setTagJson(res.data.tagJsons) setTransaction(res.data.transaction) setRecSignStatus(res.data.status) setTimeout(() => { dispatch(hideLoader()); }, 600); } }) .catch((err) => { console.error(err.message) }); } console.log(tagJson, "Line 261") const handleFormSubmit = (e: React.FormEvent) => { e.preventDefault(); const submitData: TagsSubmitDataObj = { tags: [] } if (!tagJson) return; // const form = e.currentTarget; // const formData = new FormData(form); // / Convert FormData to an object // const data: { [key: string]: FormDataEntryValue } = {}; // formData.forEach((value, key) => { // data[key] = value; // }); let signInitialsCount = 0; for (let i = 0; i < tagJson.length; i++) { if (tagJson[i].type === "SIGNATURE" || tagJson[i].type === "INITIALS") { signInitialsCount += 1; } } console.log(signInitialsCount, Object.keys(signConfObj).length, "boom") if (signInitialsCount !== Object.keys(signConfObj).length) { toast.error("Please ensure all signature and initials are signed", { position: "bottom-right", autoClose: 5000, hideProgressBar: false, closeOnClick: false, pauseOnHover: true, draggable: true, progress: undefined, theme: "light", transition: Bounce, }); return; } for (const tag of tagJson) { console.log(tag, "Tag Data") // if ((tag.type === "SIGNATURE" || tag.subType === "INITIALS") && !tag.value) { if (("required" in tag && tag.required) && ((tag.type !== "SIGNATURE" && tag.type !== "INITIALS") && (tag.value === "" || !("value" in tag)))) { toast.error(`${tag.label} is required`, { position: "bottom-right", autoClose: 5000, hideProgressBar: false, closeOnClick: false, pauseOnHover: true, draggable: true, progress: undefined, theme: "light", transition: Bounce, }); return; } if (tag.type === "SIGNATURE" || tag.type === "INITIALS") { const tagData: TagSubmitData = { id: tag.id as string, type: tag.type, subType: saved.saveType as string, signed: true, value: "" } submitData.tags.push(tagData) } else { const tagData: TagSubmitData = { id: tag.id as string, type: tag.type, subType: tag.subType ? tag.subType : "", signed: true, value: tag.value ? tag.value as string : "" } submitData.tags.push(tagData); } } // for (const tag of tagJson) { // if (data[tag.id as string]) { // const tagData: TagSubmitData = { // id: tag.id as string, // type: tag.type, // subType: tag.subType ? tag.subType : "", // signed: true, // value: data[tag.id as string] as string // } // submitData.tags.push(tagData) // } // if (tag.type === "SIGNATURE") { // submitData.tags.push({ // id: tag.id as string, // type: "SIGNATURE", // subType: "DRAW", // signed: true, // value: "" // }) // } // if (tag.type === "INITIALS") { // submitData.tags.push({ // id: tag.id as string, // type: "INITIALS", // subType: "DRAW", // signed: true, // value: "" // }) // } // } dispatch(showLoader("Signing ...")); axios.post(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`, submitData, { headers: { "ngrok-skip-browser-warning": "69420", } }) .then((res) => { console.group("Submitting Sign Details"); console.dir(res); console.groupEnd(); if (res.status === 200) { setTransparentTags(true) setTimeout(() => { dispatch(hideLoader()) toast.dismiss(); toast.info("Signed Successfully!", { position: "bottom-right", autoClose: 5000, hideProgressBar: false, closeOnClick: false, pauseOnHover: true, draggable: true, progress: undefined, theme: "light", transition: Bounce, }); }, 800) } }) .catch((err) => { console.error(err.message) } ) }; const handleTagNavClick = (action: "N" | "P") => { if (!tagJson || !tagJson.length) return // If FocusElement is Null Setting up 0 th Tag if (!focusElement) { setFocusElement({ ...tagJson[0], focusElementIndx: 0 }); return; } // 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) if (updIndx >= tagJson.length || updIndx < 0 || !tagJson[updIndx].id) return scrollToElement(tagJson[updIndx].id) setFocusElement({ ...tagJson[updIndx], focusElementIndx: updIndx }) } const scrollToElement = (elementId: string) => { const element = document.getElementById(elementId); if (element) { element.scrollIntoView({ behavior: "smooth", // You can adjust this to "auto" if you don't want smooth scrolling block: "center" // Align the element in the center of the viewport }); } } // const handleSelectOptClick = (tagId: string | undefined, optionIndx: number) => { // if (!tagId) return; // const selectedTag = tagJson?.filter((val, indx) => val.id === tagId) // if (selectedTag && selectedTag?.length && "options" in selectedTag[0] && selectedTag[0]["options"]) { // selectedTag[0].label = selectedTag[0]["options"][optionIndx] // setTagJson(tagJson) // } // } const fetchSignatureData = async (signatureId: string) => { try { const response = await axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign/signatures/${signatureId}`, { headers: { "ngrok-skip-browser-warning": "69420" } }); if (response.status === 200) { return response.data.signature; } } catch (error) { console.error("Error fetching signature data:", error); } return null; } useEffect(() => { (async () => { if (tagJson) { for (const tag of tagJson) { if (tag.type === "SIGNATURE" && "value" in tag) { const signatureData = await fetchSignatureData(tag.value); console.log(signatureData); } } } })() }, [tagJson]) const handleTagUpdate = (updateIndx: number, updateValue: string) => { if (tagJson && tagJson.length) { const updatedTag = tagJson.map((val, indx) => { if (indx === updateIndx) { return { ...val, value: updateValue } } return val; }) setTagJson(updatedTag); } // setTagJson((prev) => { // const newTagJson = [...prev]; // if (newTagJson[updateIndx]) { // newTagJson[updateIndx].label = updateValue; // } // return newTagJson; // }); } // const [showTerms, setShowTerms] = useState(true); return (
<> {/* */}
) => handleFormSubmit(e)} > {/* Nav Two - Options - START */}
{/* Left Side */}

Sign Made Easy

{/* FIT - START */}
setIsFitDd(false)} >
setIsFitDd(!isFitDd)} className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>

Fit

{isFitDd &&
{fitObj.map((val, indx) => { return (
setSelFitIndx(indx)} className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>

{val.label}

{selFitIndx === indx && }
) }) }
}
{/* FIT - END */}
{/* Right Side - STARTS */}
{/* Documnt Nav */} <> {selectedDocuments !== null &&
setNav2DocToggle(false)}>
setNav2DocToggle(!nav2DocToggle)} className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>

{selectedDocuments && selectedDocuments.length} Documents

{selectedDocuments.length && selectedDocuments.map((document, indx) => { return (
{/*

*/}

{document.name}

) })}
} {/* Send */} {(!transparentTags && !hideTags) ? :

Signed Successfully

}

{recipient && recipient.firstName} {recipient && recipient.lastName}

{/* Right Side - ENDS */}
{/* Nav Two - Options - End */} {/* Document Container Start */}
{/* Left Side pages start */} {/*
*/}
{selectedDocuments && selectedDocuments.length && selectedDocuments.map((document, indx) => { return (
setDropDownTogglePage('')} >
setDropDownTogglePage(`${(dropDownTogglePage === 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`)} className={`p-2 ${(dropDownTogglePage === 'pageDropDown' + indx) ? "bg-[var(--primary)]" : "bg-slate-200"} flex flex-col justify-center items-center gap-2 rounded`}> { document.images != undefined && }

{document.name}

{ dropDownTogglePage === 'pageDropDown' + indx &&
{document.images != undefined && document.images.map((img, indx) => (

{indx + 1}

))}
}
) })}
{/* Pages - END */} {/* Main container */}
{ selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => { console.log(tagJson, "tagJsontagJsontagJson") return (

{doc.name}

{ tagJson && tagJson.length > 0 && doc.images && doc.images.length > 0 && doc.images.map((page, indx) => { const key = `${doc.documentId}_${page.index}`; const currDocTags = tagJson.filter((val) => val.pageIndex === page.index) console.log(currDocTags, tagJson, page, doc, "innnnnnnnnnnnn") return (
handleDrop(e, doc.documentId, doc.documentId, page.index)} // onDragOver={handleDragOver} className='drop-page-in shadow-md relative' > {!hideTags && <> {currDocTags.map((item, index) => { if (item.pageIndex !== page.index) return; return (
setFocusElement({ ...item, focusElementIndx: index })} key={item.id} draggable="false" 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'}` }} > {/* TEXT FIELD - START */} {item.type === "TEXT" &&
{/* */} { item.subType === "SINGLE_LINE" &&

{ const val = e.currentTarget.textContent; handleTagUpdate(index, val as string); }} onKeyDown={(e) => { if (e.key === "Enter") e.preventDefault(); }} contentEditable={true} className="outline-none whitespace-nowrap w-full max-h-max" >

} { item.subType === "PARAGRAPH" &&

{ const val = e.currentTarget.textContent; handleTagUpdate(index, val as string); }} contentEditable={true} className="w-full outline-none max-h-max" >

} { item.subType === "PASSWORD" && item.mask &&

{ const val = e.currentTarget.textContent; handleTagUpdate(index, val as string); }} onKeyDown={(e) => { if (e.key === "Enter") e.preventDefault(); }} onFocus={() => { console.log("focus") }} onBlur={(e) => { let val = e.target.textContent; console.log(item.mask, val, "Line 586") if (typeof item.mask?.maskType === "string" && typeof val === "string") { val = val.replace(/ /g, ""); console.log("onBlur", item.mask.maskCharacter, maskUtils((item.mask as { maskType: string }).maskType, val, (item.mask as { maskCharacter: string }).maskCharacter)) e.target.textContent = maskUtils((item.mask as { maskType: string }).maskType, val, (item.mask as { maskCharacter: string }).maskCharacter) } }} contentEditable={true} className="outline-none whitespace-nowrap w-full max-h-max" >

}
} {/* TEXT FIELD - END */} {/* CHECKBOX FIELD - START */} {item.type === "CHECKBOX" &&
} {/* CHECKBOX FIELD - END */} {/* SIGNATURE FIELD - START */} {item.type === "SIGNATURE" &&
handleSignClick(index)} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none bg-transparent" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} // style={{ height: item.dimensions.height + "px" }} > {/* */} { (!transparentTags && !hideTags && !(saved.save && (index in signConfObj && signConfObj[index].accepted))) ? <> : }
} {/* SIGNATURE FIELD - END */} {/* INITIALS FIELD - START */} {item.type === "INITIALS" &&
handleSignClick(index)} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} // style={{ height: item.dimensions.height + "px" }} > {((!transparentTags && !hideTags) && !(saved.save && (index in signConfObj) && signConfObj[index].accepted)) ? : }
} {/* INITIALS FIELD - END */} {/* STAMP FIELD - START */} {item.type === "STAMP" &&
handleStampClick(index)} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} // style={{ height: item.dimensions.height + "px" }} > {!transparentTags && !hideTags ? : }
} {/* STAMP FIELD - END */} {/* IMAGE FIELD - START */} {item.type === "FILE" &&
handleSignClick(index)} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} // style={{ height: item.dimensions.height + "px" }} > {!transparentTags && !hideTags ? : }
} {/* IMAGE FIELD - END */} {/* SELECT FIELD - START */} {item.type === "SELECT" &&
} {/* SELECT FIELD - END */} {/* TEMPORAL FIELD - START */} {item.type === "TEMPORAL" &&

{(item.id && item.id in dateValues) ? dateValues[item.id] : item.subType?.toLowerCase()}

{ e.target.showPicker?.() if (item.id) setIsDateOpen(item.id) }} onBlur={(e) => { let val = e.target.value; // Adding dummy time in case of time as it giving Invalid date without that if (item.subType?.toLowerCase() === "time") val = "09/02/2025 " + val if (!item.id) return; setIsDateOpen(null); setDateValues({ ...dateValues, [item.id as string]: dayjs(val).format(item.format) }) }} style={{ opacity: `${item.id === isDateOpen ? "100" : "0"}` }} className="w-full px-2 outline-none absolute" onClick={() => { }} type={`${item.subType?.toLowerCase() === "date" ? "date" : (item.subType?.toLowerCase() === "time" ? "time" : "datetime-local")}`} />
} {/* TEMPORAL FIELD - END */} {/* Focus Show Element */} {(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) && <>

{focusElement.label}

{/* */}
}
) }) } } {/* Side details of documents Pages and document Name - START */}

page {indx + 1} of {doc.images && doc.images.length}

{/* Side details of documents Pages and document Name - END */}
) }) }
) }) } {/* tag pagination Start */} {/* {!transparentTags && !hideTags &&
handleTagNavClick("P")} >Prev
{focusElement ? focusElement.label : "NotSelected"}
handleTagNavClick("N")} >Next
} */}
{/* Right Side - START */} {/*
{ focusElement && <> }
*/} {/* Right Side - END */}
{/* Document Container End */}
{/* */} {globalLoader.isLoading && }
) } export default SignDocument