diff --git a/src/components/process/processStepOne/ContactModal.tsx b/src/components/process/processStepOne/ContactModal.tsx index 88ba9e6..853bc56 100644 --- a/src/components/process/processStepOne/ContactModal.tsx +++ b/src/components/process/processStepOne/ContactModal.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react' -import { ContactTypeObj } from './RecipientAddCard' +import React, { useState } from 'react'; +import { ContactTypeObj } from './RecipientAddCard'; import { Recipient } from '../../../store/slices/selectedRecipientsSlice'; interface ContactDataProps { @@ -10,66 +10,127 @@ interface ContactDataProps { openContactModal: boolean; setOpenContactModal: React.Dispatch>; setRecipients: React.Dispatch>; + topRef?: React.RefObject; + setManualFocusTriggered: React.Dispatch> } -const ContactModal: React.FC = ({ contactData, inpFirstName, inpLastName, inpEmail, openContactModal, setRecipients, setOpenContactModal }) => { +// Helper function to highlight matching text +const highlightText = (text: string, searchTerm: string, shouldHighlight: boolean) => { + if (!shouldHighlight || !searchTerm.trim()) { + return {text}; + } + + const searchTermLower = searchTerm.toLowerCase(); + const textLower = text.toLowerCase(); + const index = textLower.indexOf(searchTermLower); + if (index === -1) { + return {text}; + } + + const beforeMatch = text.slice(0, index); + const match = text.slice(index, index + searchTerm.length); + const afterMatch = text.slice(index + searchTerm.length); + + return ( + + {beforeMatch} + {match} + {afterMatch} + + ); +}; + +const ContactModal: React.FC = ({ + contactData, + inpFirstName, + inpLastName, + inpEmail, + openContactModal, + setRecipients, + setOpenContactModal , + setManualFocusTriggered +}) => { const handleContactClick = (indx: number) => { if (!contactData.contacts.length) return; - setRecipients((prev) => { - return { - firstName: contactData.contacts[indx].firstName, - lastName: contactData.contacts[indx].lastName, - email: contactData.contacts[indx].email, - type: prev.type - } - }); + setRecipients((prev) => ({ + ...prev, // Preserve other properties like uuid, newOne, etc. + firstName: contactData.contacts[indx].firstName, + lastName: contactData.contacts[indx].lastName, + email: contactData.contacts[indx].email, + })); setOpenContactModal(false); + setManualFocusTriggered(false) + + }; + + const handleKeyDown = (event: React.KeyboardEvent, indx: number) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + handleContactClick(indx); + } + }; + + if (!openContactModal || !contactData.contacts.length) { + return null; } return ( - <> - { - contactData.contacts.map((recipient, indx) => { - return
handleContactClick(indx)} - style={{display: `${openContactModal ? "flex" : "none"}`}} - className='flex items-center px-3 py-2 cursor-pointer hover:bg-slate-100 transiton-all w-full'> -
-
-
-

- {recipient.firstName} -

-

- {recipient.lastName} -

-
-

- {recipient.email} +

+ {contactData.contacts.map((contact, indx) => ( +
handleContactClick(indx)} + onKeyDown={(e) => handleKeyDown(e, indx)} + className="flex items-center px-3 py-2 cursor-pointer hover:bg-gray-50 transition-colors duration-150 focus:bg-gray-100 focus:outline-none rounded-md" + role="button" + aria-label={`Select contact ${contact.firstName} ${contact.lastName}`} + > + {/* Avatar */} +
+ {(contact.firstName?.[0] || '') + (contact.lastName?.[0] || '')} +
+ + {/* Contact Info */} +
+
+

+ {highlightText( + contact.firstName, + inpFirstName, + contactData.type === "firstName" + )} +

+

+ {highlightText( + contact.lastName, + inpLastName, + contactData.type === "lastName" + )}

+

+ {highlightText( + contact.email, + inpEmail, + contactData.type === "email" + )} +

- }) - } - - ) -} +
+ ))} + + {/* No results message */} + {contactData.contacts.length === 0 && ( +
+ No contacts found +
+ )} +
+ ); +}; -// return

-// {val.firstName} -//

-export default ContactModal \ No newline at end of file +export default ContactModal; \ No newline at end of file diff --git a/src/components/process/processStepOne/ProcessStepOne.tsx b/src/components/process/processStepOne/ProcessStepOne.tsx index b7dfb30..ae412fe 100644 --- a/src/components/process/processStepOne/ProcessStepOne.tsx +++ b/src/components/process/processStepOne/ProcessStepOne.tsx @@ -6,7 +6,7 @@ import SelectedDocuments from "./SelectedDocuments"; import api from "../../../config/api"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../../../store/store"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { setImages } from "../../../store/slices/selectedDocsSlice"; import { addRecipient, addRecipientAsync, fetchRecipients, recipientType, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE, updateRecipientIds } from "../../../store/slices/selectedRecipientsSlice"; @@ -39,10 +39,10 @@ const ProcessStepOne = () => { dispatch(toggleToTemplates()); } - const [editCase , setEditCase] = useState("") - const [editRecipient , setEditRecipient] = useState({}) + const [editCase, setEditCase] = useState("") + const [editRecipient, setEditRecipient] = useState({}) + - // if (!accountId || !transactionId) { // window.location.href = "/"; @@ -55,6 +55,9 @@ const ProcessStepOne = () => { const selectedDocuments = useSelector((state: RootState) => state.documents.documents) const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients) const userData = useSelector((state: RootState) => state.user) + // ProcessStepOne.tsx + const topRef = useRef(null); + // const userData = useSelector((state: RootState) => state.user) const [transactionDetails, setTransactionDetails] = useState([]); @@ -62,24 +65,10 @@ const ProcessStepOne = () => { console.log("selectedRecipients", selectedRecipients) useEffect(() => { -<<<<<<< HEAD - // Fetching Transaction Details - START - (async () => { - try { - const res = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}`); - setTransactionDetails(res.data) - } catch (e) { - console.log("Error - transactions details") - } - })() - // Fetching Transaction Details - END - -======= (async () => { const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`) console.log(res.data); })() ->>>>>>> origin/main selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId })); selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); @@ -177,7 +166,7 @@ const ProcessStepOne = () => { language: "en", name: `${recipient.firstName} ${recipient.lastName}`, roleName: recipient.roleName, - allowSenderToEdit: recipient.allowSenderToEdit , + allowSenderToEdit: recipient.allowSenderToEdit, allowSenderToDelete: recipient.allowSenderToDelete, })); @@ -348,9 +337,17 @@ const ProcessStepOne = () => { { signType !== SignTypeEnum.selfSign &&
- + {/* {signType !== "self-sign" && } */} - + +
+ + +
+
} diff --git a/src/components/process/processStepOne/RecipientAddCard.tsx b/src/components/process/processStepOne/RecipientAddCard.tsx index 8711c50..2bfa22f 100644 --- a/src/components/process/processStepOne/RecipientAddCard.tsx +++ b/src/components/process/processStepOne/RecipientAddCard.tsx @@ -24,7 +24,11 @@ export interface ContactTypeObj { } -const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase ,setEditRecipient, setEditCase }) => { +const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase, setEditRecipient, setEditCase, topRef }) => { + const [activeAccountId, setActiveAccountId] = useState( + JSON.parse(sessionStorage.getItem('activeAccountId') || 'null') + ); + const [contactData, setContactData] = useState({ type: "", contacts: [] }); const [openContactModal, setOpenContactModal] = useState(false); @@ -32,11 +36,31 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase , const dispatch = useDispatch(); const formRef = useRef(null); - const [recipient, setRecipients] = useState({ uuid:null, newOne:false, firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: false, allowSenderToDelete: false }); + const [recipient, setRecipients] = useState({ uuid: null, newOne: false, firstName: "", lastName: "", email: "", hostName: "", hostEmail: "", roleName: "", type: recipientType.SIGNER, allowSenderToEdit: false, allowSenderToDelete: false }); const [showAdvanceSettings, setShowAdvanceSettings] = useState(false) + + const [cachedContacts, setCachedContacts] = useState([]); + const [filteredContacts, setFilteredContacts] = useState([]); + const [activeField, setActiveField] = useState<"firstName" | "lastName" | "email" | null>(null); + const [hasFetchedInitial, setHasFetchedInitial] = useState(false); + const [lastQuery, setLastQuery] = useState(null); + const containerRef = useRef(null); + const [manualFocusTriggered, setManualFocusTriggered] = useState(false); + + console.log(recipient) + useEffect(() => { + if (openContactModal) { + if (topRef?.current) { + topRef.current.scrollIntoView({ behavior: "smooth", block: "start" }); + } else { + window.scrollTo({ top: 0, behavior: "smooth" }); + } + } + }, [openContactModal]); + useEffect(() => { if (editCase === "Edit" && editRecipient) { setRecipients({ @@ -51,56 +75,55 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase , allowSenderToEdit: editRecipient.allowSenderToEdit ?? false, allowSenderToDelete: editRecipient.allowSenderToDelete ?? false, uuid: String(editRecipient?.id ?? editRecipient?.uuid ?? ''), - newOne:editRecipient.newOne ?? false + newOne: editRecipient.newOne ?? false }); } }, [editCase, editRecipient]); const handleRecipientAdd = (e: React.FormEvent) => { - e.preventDefault(); + e.preventDefault(); - console.log("recipientrecipient",recipient) + console.log("recipientrecipient", recipient) - if (!recipient.lastName || !recipient.email || !recipient.type) return; + if (!recipient.lastName || !recipient.email || !recipient.type) return; - // ✅ Email format validation - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(recipient.email)) { - console.log("Invalid email format"); - return; - } + // ✅ Email format validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(recipient.email)) { + console.log("Invalid email format"); + return; + } - if (editCase === 'Edit') { - dispatch(updateRecipient(recipient)); - } else { - dispatch(addRecipient({ - ...recipient, - uuid:uuidv4(), - newOne:true - })); - } + if (editCase === 'Edit') { + dispatch(updateRecipient(recipient)); + } else { + dispatch(addRecipient({ + ...recipient, + uuid: uuidv4(), + newOne: true + })); + } - // ✅ Reset state and form - setRecipients({ - firstName: "", - lastName: "", - email: "", - hostName: "", - hostEmail: "", - roleName: "", - type: recipientType.SIGNER, - allowSenderToEdit: false, - allowSenderToDelete: false, - uuid:"", - newOne:false - }); + setRecipients({ + firstName: "", + lastName: "", + email: "", + hostName: "", + hostEmail: "", + roleName: "", + type: recipientType.SIGNER, + allowSenderToEdit: false, + allowSenderToDelete: false, + uuid: "", + newOne: false + }); - // setRecipients(null), - setEditCase("") - - formRef.current?.reset(); -}; + // setRecipients(null), + setEditCase("") +setManualFocusTriggered(false) + formRef.current?.reset(); + }; @@ -155,7 +178,97 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase , }, 350) } + + const handleFocus = async (field: "firstName" | "lastName" | "email") => { + setManualFocusTriggered(true); + setOpenContactModal(true); + setActiveField(field); + setLastQuery(null); // Reset to allow fresh input fetch if needed + + if (!hasFetchedInitial) { + try { + const res = await api.get(`/api/accounts/${activeAccountId}/contacts?pageSize=10`); + const contacts = res.data?.content || []; + setCachedContacts(contacts); + setFilteredContacts(contacts); + setContactData({ type: field, contacts }); + setHasFetchedInitial(true); + } catch (err) { + console.error("Initial contact fetch failed:", err); + } + } else { + setFilteredContacts(cachedContacts); + setContactData({ type: field, contacts: cachedContacts }); + } + }; + + + + + + useDebouncedEffect(() => { + console.log("useDebouncedEffect") + if (!activeField || !manualFocusTriggered) return; + + const inputValue = recipient[activeField].trim().toLowerCase(); + + if (inputValue.length === 0) { + setFilteredContacts(cachedContacts); + setContactData({ type: activeField, contacts: cachedContacts }); + setOpenContactModal(true); // Keep showing modal with all options + return; + } + + // Local match + const localMatches = cachedContacts.filter(contact => + contact[activeField]?.toLowerCase().includes(inputValue) + ); + + if (localMatches.length > 0) { + setFilteredContacts(localMatches); + setContactData({ type: activeField, contacts: localMatches }); + setOpenContactModal(true); + return; + } + + if (lastQuery !== inputValue) { + setLastQuery(inputValue); + api + .get(`/api/accounts/${activeAccountId}/contacts?pageSize=5&${activeField}=${inputValue}`) + .then((res) => { + const fetched = res.data?.content || []; + setFilteredContacts(fetched); + setContactData({ type: activeField, contacts: fetched }); + setOpenContactModal(true); + }) + .catch(console.error); + } + }, [recipient.firstName, recipient.lastName, recipient.email], 300); + +useEffect(() => { + const handleClickOutside = (e: MouseEvent | FocusEvent) => { + if ( + containerRef.current && + !containerRef.current.contains(e.target as Node) + ) { + setOpenContactModal(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("focusin", handleClickOutside); // Also handles tabbing out + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("focusin", handleClickOutside); + }; +}, []); + + + + return ( +
setOpenContactModal(true)} + onFocus={() => handleFocus("firstName")} onBlur={handleContactModalBlur} value={recipient.firstName} placeholder="First Name" @@ -301,7 +414,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase , setOpenContactModal(true)} + onFocus={() => handleFocus("lastName")} onBlur={handleContactModalBlur} value={recipient.lastName} placeholder="Last Name" @@ -311,7 +424,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase , setOpenContactModal(true)} + onFocus={() => handleFocus("email")} onBlur={handleContactModalBlur} value={recipient.email} placeholder="Recipient Email" @@ -345,8 +458,8 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase ,
} {/* Contact Modal */} - {contactData.contacts.length > 0 && openContactModal && ( -
+ {openContactModal && contactData.contacts.length > 0 && ( +
)} + + {/* Signing options + Add button in one line */}
@@ -385,11 +502,11 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase , type="submit" className="inline-flex items-center gap-2 bg-[var(--primary)] hover:bg-[var(--primaryLight2)] text-white text-sm font-semibold px-5 py-2 rounded-md transition" > - {editCase === "Edit" ? "Edit Recipient" : "Add Recipient"} + {editCase === "Edit" ? "Edit Recipient" : "Add Recipient"}
- +
); }; diff --git a/src/components/process/processStepOne/RecipientCards.tsx b/src/components/process/processStepOne/RecipientCards.tsx index 3492139..68116c6 100644 --- a/src/components/process/processStepOne/RecipientCards.tsx +++ b/src/components/process/processStepOne/RecipientCards.tsx @@ -59,15 +59,6 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg const handleRemoveRecipient = (recipient) => { if (!accountId || !transactionId) return; -<<<<<<< HEAD - // dispatch(removeSelectedRecipient(recipient)) - dispatch(removeRecipientAsync([{ - recipientId: recipient.id, - email: recipient.email, - accountId: accountId, - transactionId: transactionId, - }])) -======= if (recipient.uuid) { dispatch(removeSelectedRecipient(recipient)) } else { @@ -78,7 +69,6 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg transactionId: transactionId, })) } ->>>>>>> origin/main if (recipients.length === 2) { let leftRecipient = recipients.filter((obj) => { if ("id" in obj && "id" in recipient) { diff --git a/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx b/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx index 493e4b8..2f62066 100644 --- a/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx +++ b/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx @@ -157,7 +157,7 @@ const DirectionalControls: React.FC = ({
{/* Center Panel */} -
+
{/* Floating dimensions tooltip */}
= ({ onBlur={() => handlePositionInputBlur('left')} onKeyPress={(e) => handleKeyPress(e, 'left')} disabled={disabled} - className="w-[40px] h-[16px] text-[10px] text-center border-none focus:outline-none text-black font-mono" + className="w-[40px] h-[16px] text-[10px] text-center border-none focus:outline-none text-black font-mono" /> ×