Merge branch
This commit is contained in:
commit
cdbada638b
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import "../styles/style.css";
|
// import "../styles/style.css";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
import LoginForm from "./LoginForm";
|
import LoginForm from "./LoginForm";
|
||||||
import RegisterForm from "./RegisterForm";
|
import RegisterForm from "./RegisterForm";
|
||||||
|
|
@ -37,7 +37,7 @@ function UserAuthUI() {
|
||||||
return (
|
return (
|
||||||
<div className="max-h-screen bg-white flex">
|
<div className="max-h-screen bg-white flex">
|
||||||
{/* Left Side - Branding */}
|
{/* Left Side - Branding */}
|
||||||
<div className="hidden lg:flex lg:w-1/2 bg-[var(--primary)] text-white p-12">
|
<div className="hidden lg:flex lg:w-1/2 bg-[var(--primary)] text-white p-12 lg:h-[100vh]">
|
||||||
<div className="flex flex-col items-center justify-center text-center w-full h-full gap-6">
|
<div className="flex flex-col items-center justify-center text-center w-full h-full gap-6">
|
||||||
{/* Logos */}
|
{/* Logos */}
|
||||||
<div className="flex flex-col items-center gap-2 space-y-3">
|
<div className="flex flex-col items-center gap-2 space-y-3">
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ const ProcessStepOne = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if(!accountId || !transactionId) return;
|
if (!accountId || !transactionId) return;
|
||||||
dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId }));
|
dispatch(addRecipientAsync({ recipientsData: recipientsDataTemp, accountId, transactionId }));
|
||||||
}
|
}
|
||||||
dispatch(showLoader("Processing Message..."));
|
dispatch(showLoader("Processing Message..."));
|
||||||
|
|
@ -345,50 +345,60 @@ const ProcessStepOne = () => {
|
||||||
<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 flex items-center justify-between bg-white shadow z-50 px-4 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'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 flex-wrap'>
|
||||||
<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
|
{/* Left Section */}
|
||||||
id="add-tags-btn-step-one"
|
<div className='flex sm:flex-row items-start sm:items-center gap-2 sm:gap-4 w-full sm:w-auto'>
|
||||||
className='flex items-center gap-3'>
|
|
||||||
|
|
||||||
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && <button
|
<IoMdClose
|
||||||
onClick={() => handleNextClick(true)}
|
onClick={handleClose}
|
||||||
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer"
|
className='text-2xl text-slate-500 cursor-pointer hover:text-[var(--primary)] transition self-start sm:self-auto'
|
||||||
>
|
/>
|
||||||
Save & Close
|
|
||||||
</button>}
|
|
||||||
|
|
||||||
<button
|
<p className="text-base font-normal text-gray-700 border-l pl-4 relative w-full sm:w-auto">
|
||||||
onClick={() => handleNextClick(false)}
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES ? (
|
||||||
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer"
|
<>
|
||||||
>
|
<input
|
||||||
{signType === SignTypeEnum.selfSign ? "Sign" : "Next"}
|
value={templateName}
|
||||||
</button>
|
onChange={handleChange}
|
||||||
|
className="w-full sm:w-[250px] 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>
|
||||||
|
|
||||||
|
{/* Right Section */}
|
||||||
|
<div
|
||||||
|
id="add-tags-btn-step-one"
|
||||||
|
className='flex sm:flex-row items-stretch sm:items-center gap-2 sm:gap-3 w-full sm:w-auto '>
|
||||||
|
|
||||||
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleNextClick(true)}
|
||||||
|
className="text-sm w-full sm:w-auto 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 w-full sm:w-auto 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Main Content - Scrollable */}
|
{/* Main Content - Scrollable */}
|
||||||
<div className="h-screen pt-[80px] overflow-y-auto no-scrollbar">
|
<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">
|
<div className="max-w-4xl rounded-xl shadow-md mx-auto px-6 pb-10 bg-[var(--documentUploadBg)] mb-5">
|
||||||
|
|
|
||||||
|
|
@ -363,27 +363,26 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
<form
|
<form
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
onSubmit={handleRecipientAdd}
|
onSubmit={handleRecipientAdd}
|
||||||
id="add-recipient-step-one"
|
id="add-recipient-step-one"
|
||||||
className="w-full bg-white border border-gray-200 rounded-lg shadow-sm px-6 py-4 space-y-3 relative"
|
className="w-full bg-white border border-gray-200 rounded-lg shadow-sm px-3 sm:px-4 lg:px-6 py-3 sm:py-4 space-y-3 sm:space-y-4 relative"
|
||||||
>
|
>
|
||||||
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-2 sm:space-y-3">
|
||||||
{/* Top Row: Role Input + Settings */}
|
{/* Role Name and Permissions Section - Responsive Layout */}
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex flex-col lg:flex-row lg:justify-between lg:items-start gap-3 lg:gap-4">
|
||||||
{/* Role Name Input */}
|
{/* Role Name Input */}
|
||||||
{/* Role Name Input */}
|
<div className="w-full lg:w-64 xl:w-80">
|
||||||
<div className="w-64">
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={recipient.roleName}
|
value={recipient.roleName}
|
||||||
placeholder="Role name"
|
placeholder="Role name"
|
||||||
className={`w-full px-3 py-2 border rounded-md text-sm focus:outline-none transition
|
className={`w-full px-3 py-2 border rounded-md text-sm focus:outline-none transition
|
||||||
${errors.roleName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
${errors.roleName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setRecipients({ ...recipient, roleName: e.target.value });
|
setRecipients({ ...recipient, roleName: e.target.value });
|
||||||
setErrors(prev => ({ ...prev, roleName: false }));
|
setErrors(prev => ({ ...prev, roleName: false }));
|
||||||
|
|
@ -394,125 +393,76 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Permissions Section */}
|
||||||
{/* Settings + Permissions */}
|
<div className="flex items-center justify-start lg:justify-end">
|
||||||
<div className="flex items-center">
|
<div className="flex sm:flex-row items-start sm:items-center gap-2 sm:gap-3">
|
||||||
<div className="flex justify-between w-full">
|
{/* Edit Permission */}
|
||||||
{/* Permission Status Text - Always Visible */}
|
<div className="relative group">
|
||||||
<div className="max-h-10 opacity-100 me-3 overflow-hidden">
|
<label className="flex items-center gap-1 text-xs sm:text-sm text-gray-700">
|
||||||
<div className="text-sm text-gray-600 pt-0.5">
|
<input
|
||||||
{/* {recipient.allowSenderToEdit && recipient.allowSenderToDelete && "Full permissions granted"}
|
type="checkbox"
|
||||||
{recipient.allowSenderToEdit && !recipient.allowSenderToDelete && "Edit permission granted"}
|
checked={recipient.allowSenderToEdit}
|
||||||
{!recipient.allowSenderToEdit && recipient.allowSenderToDelete && "Delete permission granted"}
|
onChange={(e) =>
|
||||||
{!recipient.allowSenderToEdit && !recipient.allowSenderToDelete && "No permissions granted"} */}
|
setRecipients({ ...recipient, allowSenderToEdit: e.target.checked })
|
||||||
Permissions granted :
|
}
|
||||||
</div>
|
onFocus={() => setOpenContactModal(true)}
|
||||||
|
onBlur={handleContactModalBlur}
|
||||||
|
className='accent-blue-500 w-3 h-3 sm:w-4 sm:h-4'
|
||||||
|
/>
|
||||||
|
<span className="whitespace-nowrap">Allow Edit</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Permission Controls - Always Visible */}
|
{/* Delete Permission */}
|
||||||
<div className="max-w-80 opacity-100 overflow-visible">
|
<div className="relative group">
|
||||||
<div className="flex items-center space-x-3 pr-1.5">
|
<label className="flex items-center gap-1 text-xs sm:text-sm text-gray-700">
|
||||||
{/* Edit Permission */}
|
<input
|
||||||
<div className="relative group">
|
type="checkbox"
|
||||||
<label
|
checked={recipient.allowSenderToDelete}
|
||||||
className={`relative flex items-center justify-center w-12 h-7 border-2 rounded-lg cursor-pointer transition-all duration-300 transform hover:scale-105 ${recipient.allowSenderToEdit
|
onChange={(e) =>
|
||||||
? 'bg-blue-100 border-blue-500 shadow-md'
|
setRecipients({ ...recipient, allowSenderToDelete: e.target.checked })
|
||||||
: 'bg-white border-gray-300 hover:border-blue-400 text-gray-400'
|
}
|
||||||
}`}
|
onFocus={() => setOpenContactModal(true)}
|
||||||
>
|
onBlur={handleContactModalBlur}
|
||||||
<input
|
className="accent-red-500 w-3 h-3 sm:w-4 sm:h-4"
|
||||||
type="checkbox"
|
/>
|
||||||
checked={recipient.allowSenderToEdit}
|
<span className="whitespace-nowrap">Allow Delete</span>
|
||||||
onChange={(e) =>
|
</label>
|
||||||
setRecipients({ ...recipient, allowSenderToEdit: e.target.checked })
|
|
||||||
}
|
|
||||||
onFocus={() => setOpenContactModal(true)}
|
|
||||||
onBlur={handleContactModalBlur}
|
|
||||||
className="absolute w-full h-full opacity-0 cursor-pointer"
|
|
||||||
/>
|
|
||||||
<FiEdit
|
|
||||||
className={`w-2.5 h-2.5 pointer-events-none transition-all duration-300 ${recipient.allowSenderToEdit ? 'text-blue-600' : 'text-gray-400'
|
|
||||||
}`}
|
|
||||||
/><span className="ms-0.5 text-[12px]">Edit</span>
|
|
||||||
</label>
|
|
||||||
{/* Tooltip */}
|
|
||||||
<div className="absolute bottom-0 -translate-y-full left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-20">
|
|
||||||
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap">
|
|
||||||
Edit Permission
|
|
||||||
<div className="absolute bottom-[-5px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Delete Permission */}
|
|
||||||
<div className="relative group">
|
|
||||||
<label
|
|
||||||
className={`relative flex items-center justify-center w-15 h-7 border-2 rounded-lg cursor-pointer transition-all duration-300 transform hover:scale-105 ${recipient.allowSenderToDelete
|
|
||||||
? 'bg-red-100 border-red-500 shadow-md'
|
|
||||||
: 'bg-white border-gray-300 hover:border-red-400 text-gray-400'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={recipient.allowSenderToDelete}
|
|
||||||
onChange={(e) =>
|
|
||||||
setRecipients({ ...recipient, allowSenderToDelete: e.target.checked })
|
|
||||||
}
|
|
||||||
onFocus={() => setOpenContactModal(true)}
|
|
||||||
onBlur={handleContactModalBlur}
|
|
||||||
className="absolute w-full h-full opacity-0 cursor-pointer"
|
|
||||||
/>
|
|
||||||
<FiTrash
|
|
||||||
className={`w-3 h-3 pointer-events-none transition-all duration-300 ${recipient.allowSenderToDelete ? 'text-red-600' : 'text-gray-400'
|
|
||||||
}`}
|
|
||||||
/><span className="ms-0.5 text-[12px]">Delete</span>
|
|
||||||
</label>
|
|
||||||
{/* Tooltip */}
|
|
||||||
<div className="absolute bottom-0 -translate-y-full left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-20">
|
|
||||||
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap">
|
|
||||||
Delete Permission
|
|
||||||
<div className="absolute bottom-[-5px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Name Inputs */}
|
{/* Name and Email Inputs - Responsive Grid */}
|
||||||
<div className="grid grid-cols-12 gap-4 mt-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 mt-3">
|
||||||
<div className="col-span-4">
|
{/* First Name */}
|
||||||
|
<div className="w-full">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
onFocus={() => handleFocus("firstName")}
|
onFocus={() => handleFocus("firstName")}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
value={recipient.firstName}
|
value={recipient.firstName}
|
||||||
placeholder="First Name"
|
placeholder="First Name"
|
||||||
className={`w-full px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
className={`w-full px-3 sm:px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
||||||
${errors.firstName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
${errors.firstName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setRecipients({ ...recipient, firstName: e.target.value });
|
setRecipients({ ...recipient, firstName: e.target.value });
|
||||||
// setErrors((prev) => ({ ...prev, firstName: false }));
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* {errors.firstName && (
|
|
||||||
<p className="text-red-500 text-xs mt-1">First Name is required</p>
|
{/* Last Name */}
|
||||||
)} */}
|
<div className="w-full">
|
||||||
<div className="col-span-4">
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
onFocus={() => handleFocus("lastName")}
|
onFocus={() => handleFocus("lastName")}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
value={recipient.lastName}
|
value={recipient.lastName}
|
||||||
placeholder="Last Name"
|
placeholder="Last Name"
|
||||||
className={` w-full px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
className={`w-full px-3 sm:px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
||||||
${errors.lastName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
${errors.lastName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setRecipients({ ...recipient, lastName: e.target.value });
|
setRecipients({ ...recipient, lastName: e.target.value });
|
||||||
setErrors((prev) => ({ ...prev, lastName: false }));
|
setErrors((prev) => ({ ...prev, lastName: false }));
|
||||||
|
|
@ -522,15 +472,17 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
||||||
<p className="text-red-500 text-xs mt-1">Last Name is required</p>
|
<p className="text-red-500 text-xs mt-1">Last Name is required</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4">
|
|
||||||
|
{/* Email - Full width on small screens, shares row on larger */}
|
||||||
|
<div className="w-full sm:col-span-2 lg:col-span-1">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
onFocus={() => handleFocus("email")}
|
onFocus={() => handleFocus("email")}
|
||||||
onBlur={handleContactModalBlur}
|
onBlur={handleContactModalBlur}
|
||||||
value={recipient.email}
|
value={recipient.email}
|
||||||
placeholder="Recipient Email"
|
placeholder="Recipient Email"
|
||||||
className={`w-full px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
className={`w-full px-3 sm:px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
||||||
${errors.email ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
${errors.email ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setRecipients({ ...recipient, email: e.target.value });
|
setRecipients({ ...recipient, email: e.target.value });
|
||||||
setErrors((prev) => ({ ...prev, email: false }));
|
setErrors((prev) => ({ ...prev, email: false }));
|
||||||
|
|
@ -542,33 +494,35 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Host Fields for In-Person Signers */}
|
||||||
|
{recipient.type === recipientType.INPERSONSIGNER && (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||||
|
<input
|
||||||
|
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
||||||
|
type="text"
|
||||||
|
onFocus={() => setOpenContactModal(true)}
|
||||||
|
onBlur={handleContactModalBlur}
|
||||||
|
value={recipient.hostName}
|
||||||
|
placeholder="Host Name"
|
||||||
|
className="w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
|
||||||
|
onChange={(e) => setRecipients({ ...recipient, hostName: e.target.value })}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
||||||
|
type="email"
|
||||||
|
onFocus={() => setOpenContactModal(true)}
|
||||||
|
onBlur={handleContactModalBlur}
|
||||||
|
value={recipient.hostEmail}
|
||||||
|
placeholder="Host Email"
|
||||||
|
className="w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
|
||||||
|
onChange={(e) => setRecipients({ ...recipient, hostEmail: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{recipient.type === recipientType.INPERSONSIGNER && <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
{/* Contact Modal - Responsive Positioning */}
|
||||||
<input
|
|
||||||
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
|
||||||
type="text"
|
|
||||||
onFocus={() => setOpenContactModal(true)}
|
|
||||||
onBlur={handleContactModalBlur}
|
|
||||||
value={recipient.hostName}
|
|
||||||
placeholder="Host Name"
|
|
||||||
className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
|
|
||||||
onChange={(e) => setRecipients({ ...recipient, hostName: e.target.value })}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
|
||||||
type="email"
|
|
||||||
onFocus={() => setOpenContactModal(true)}
|
|
||||||
onBlur={handleContactModalBlur}
|
|
||||||
value={recipient.hostEmail}
|
|
||||||
placeholder="Host Email"
|
|
||||||
className="w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
|
|
||||||
onChange={(e) => setRecipients({ ...recipient, hostEmail: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{/* Contact Modal */}
|
|
||||||
{openContactModal && contactData.contacts.length > 0 && (
|
{openContactModal && contactData.contacts.length > 0 && (
|
||||||
<div className="absolute z-50 mt-1 w-2/3 max-h-[50vh] bg-white shadow-lg rounded-md p-3 border border-gray-100 overflow-y-auto no-scrollbar">
|
<div className="absolute z-50 mt-1 w-full sm:w-2/3 lg:w-1/2 xl:w-2/3 max-h-[40vh] sm:max-h-[50vh] bg-white shadow-lg rounded-md p-2 sm:p-3 border border-gray-100 overflow-y-auto no-scrollbar">
|
||||||
<ContactModal
|
<ContactModal
|
||||||
contactData={contactData}
|
contactData={contactData}
|
||||||
inpFirstName={recipient.firstName}
|
inpFirstName={recipient.firstName}
|
||||||
|
|
@ -583,36 +537,41 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Signing Options and Add Button - Responsive Layout */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4">
|
||||||
{/* Signing options + Add button in one line */}
|
{/* Recipient Type Radio Options */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:flex-wrap gap-2 sm:gap-4 lg:gap-6">
|
||||||
<div className="flex flex-wrap gap-6">
|
|
||||||
{[
|
{[
|
||||||
{ label: "Signer", value: recipientType.SIGNER },
|
{ label: "Signer", value: recipientType.SIGNER },
|
||||||
{ label: "In Person Signer", value: recipientType.INPERSONSIGNER },
|
{ label: "In Person Signer", value: recipientType.INPERSONSIGNER },
|
||||||
{ label: "CC", value: recipientType.CC },
|
{ label: "CC", value: recipientType.CC },
|
||||||
].map((option) => (
|
].map((option) => (
|
||||||
<label key={option.value} className="flex items-center gap-2 text-gray-700 text-sm cursor-pointer">
|
<label key={option.value} className="flex items-center gap-2 text-gray-700 text-xs sm:text-sm cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="recipient-type"
|
name="recipient-type"
|
||||||
value={option.value}
|
value={option.value}
|
||||||
checked={recipient.type === option.value}
|
checked={recipient.type === option.value}
|
||||||
onClick={(e) => setRecipients({ ...recipient, type: e.target.value })}
|
onClick={(e) => setRecipients({ ...recipient, type: e.target.value })}
|
||||||
className="accent-[var(--primary)] cursor-pointer"
|
className="accent-[var(--primary)] cursor-pointer w-3 h-3 sm:w-4 sm:h-4"
|
||||||
/>
|
/>
|
||||||
{option.label}
|
<span className="whitespace-nowrap">{option.label}</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!showCard && <button
|
{/* Add/Edit Button */}
|
||||||
type="submit"
|
{!showCard && (
|
||||||
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"
|
<button
|
||||||
>
|
type="submit"
|
||||||
<FaPlus size={14} /> {editCase === "Edit" ? "Edit Recipient" : "Add Recipient"}
|
className="inline-flex items-center justify-center gap-2 bg-[var(--primary)] hover:bg-[var(--primaryLight2)] text-white text-xs sm:text-sm font-semibold px-4 sm:px-5 py-2 rounded-md transition w-full sm:w-auto min-w-fit"
|
||||||
</button>}
|
>
|
||||||
|
<FaPlus size={12} className="sm:w-3.5 sm:h-3.5" />
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
|
{editCase === "Edit" ? "Edit Recipient" : "Add Recipient"}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ const RecipientCards = ({
|
||||||
return currentValue && currentValue.toString().trim() !== "";
|
return currentValue && currentValue.toString().trim() !== "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderEditableField = (
|
const renderEditableField = (
|
||||||
recipient: any,
|
recipient: any,
|
||||||
fieldName: string,
|
fieldName: string,
|
||||||
placeholder: string,
|
placeholder: string,
|
||||||
|
|
@ -130,48 +130,28 @@ const RecipientCards = ({
|
||||||
) => {
|
) => {
|
||||||
const identifier = getRecipientIdentifier(recipient);
|
const identifier = getRecipientIdentifier(recipient);
|
||||||
const currentValue = getCurrentValue(recipient, fieldName);
|
const currentValue = getCurrentValue(recipient, fieldName);
|
||||||
const isEditing = editingField?.recipientId === identifier && editingField?.fieldName === fieldName;
|
|
||||||
const showValue = hasValue(recipient, fieldName);
|
|
||||||
const error = errors[identifier]?.[fieldName];
|
const error = errors[identifier]?.[fieldName];
|
||||||
const editable = isEditable(recipient);
|
const editable = isEditable(recipient);
|
||||||
|
|
||||||
if (isEditing && editable) {
|
|
||||||
return (
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
type={type}
|
|
||||||
value={currentValue || ""}
|
|
||||||
placeholder={placeholder}
|
|
||||||
autoFocus
|
|
||||||
onFocus={() => handleFieldFocus(identifier, fieldName)}
|
|
||||||
onBlur={(e) => handleFieldBlur(recipient, fieldName, e.target.value)}
|
|
||||||
onChange={(e) => handleFieldChange(recipient, fieldName, e.target.value)}
|
|
||||||
onKeyPress={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.currentTarget.blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={`w-full px-3 py-1 border ${error ? "border-red-500 placeholder-red-500 text-xs" : "border-gray-300"
|
|
||||||
} rounded-lg text-sm focus:outline-none focus:ring-0 focus:ring-gray-300 ${className}`}
|
|
||||||
/>
|
|
||||||
{/* {error && (
|
|
||||||
<span className="text-red-500 text-xs absolute left-0">{`${placeholder} is required`}</span>
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<div className="relative inline-block w-full sm:mx-1 lg:mx-3">
|
||||||
onClick={editable ? () => handleFieldFocus(identifier, fieldName) : undefined}
|
<input
|
||||||
className={`${editable
|
type={type}
|
||||||
? "cursor-pointer hover:bg-gray-100 rounded px-1 transition-colors"
|
value={currentValue || ""}
|
||||||
: "cursor-not-allowed"
|
placeholder={placeholder}
|
||||||
} ${showValue ? "text-gray-800" : "text-gray-400 italic"} ${className}`}
|
onFocus={() => handleFieldFocus(identifier, fieldName)}
|
||||||
title={editable ? "Click to edit" : "Editing disabled"}
|
onBlur={(e) => handleFieldBlur(recipient, fieldName, e.target.value)}
|
||||||
>
|
onChange={(e) => handleFieldChange(recipient, fieldName, e.target.value)}
|
||||||
{showValue ? currentValue : placeholder}
|
onKeyPress={(e) => {
|
||||||
</span>
|
if (e.key === "Enter") {
|
||||||
|
e.currentTarget.blur();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!editable}
|
||||||
|
className={`w-full px-2 sm:px-3 lg:px-4 py-1.5 sm:py-2 font-normal border focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition ${error ? "border-red-500 placeholder-red-500 text-xs" : "border-gray-300"} rounded-lg text-xs sm:text-sm ${className} ${!editable ? "bg-gray-100 cursor-not-allowed" : ""}`}
|
||||||
|
title={editable ? "Edit field" : "Editing disabled"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -235,17 +215,17 @@ const RecipientCards = ({
|
||||||
setEditingField(null);
|
setEditingField(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full bg-white rounded-md shadow-sm border border-gray-200">
|
<div className="w-full bg-white rounded-md shadow-sm border border-gray-200">
|
||||||
{recipients.length > 0 ? (
|
{recipients.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{recipients.length > 1 && (
|
{recipients.length > 1 && (
|
||||||
<div className="flex items-center justify-between px-4 py-2 border-b border-gray-300">
|
<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})
|
||||||
</h3>
|
</h3>
|
||||||
<label
|
<label
|
||||||
className="flex items-center gap-2 cursor-pointer select-none"
|
className="flex items-center gap-2 cursor-pointer select-none justify-center sm:justify-end"
|
||||||
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: true })}
|
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: true })}
|
||||||
>
|
>
|
||||||
<span className="text-xs font-medium text-gray-600">Signing Order</span>
|
<span className="text-xs font-medium text-gray-600">Signing Order</span>
|
||||||
|
|
@ -254,18 +234,20 @@ const RecipientCards = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col divide-y divide-gray-200">
|
<div className="flex flex-col divide-y divide-gray-200 max-h-[47vh] overflow-y-auto custom-scrollbar">
|
||||||
{recipients.map((recipient, idx) => (
|
{recipients.map((recipient, idx) => (
|
||||||
<div
|
<div
|
||||||
key={recipient.email + idx}
|
key={recipient.email + idx}
|
||||||
className={`flex items-center justify-between px-4 py-3 hover:bg-gray-50 transition-colors ${(recipient.uuid || recipient.id) === editRecipientCurrentEmail
|
className={`flex flex-col lg:flex-row lg:items-center justify-between px-2 sm:px-3 lg:px-2 py-3 hover:bg-gray-50 transition-colors gap-3 lg:gap-0 ${(recipient.uuid || recipient.id) === editRecipientCurrentEmail
|
||||||
? "bg-neutral-100 opacity-40"
|
? "bg-neutral-100 opacity-40"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
{/* Main Content Section */}
|
||||||
|
<div className="flex items-start sm:items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||||
|
{/* Order Number */}
|
||||||
{recipients.length > 1 ? (
|
{recipients.length > 1 ? (
|
||||||
<div className="relative">
|
<div className="relative flex-shrink-0">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={recipient.routingOrder || 1}
|
value={recipient.routingOrder || 1}
|
||||||
|
|
@ -276,7 +258,7 @@ const RecipientCards = ({
|
||||||
Number(e.target.value)
|
Number(e.target.value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="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 bg-slate-100"
|
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 bg-slate-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -291,140 +273,159 @@ const RecipientCards = ({
|
||||||
Number(e.target.value)
|
Number(e.target.value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)]"
|
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"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-[var(--primary)] text-white flex items-center justify-center font-semibold text-xs select-none">
|
|
||||||
|
{/* 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">
|
||||||
{getInitials(`${recipient.firstName} ${recipient.lastName || ""}`)}
|
{getInitials(`${recipient.firstName} ${recipient.lastName || ""}`)}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
|
||||||
<p className="font-semibold text-gray-800 truncate text-xs flex items-center gap-0.5">
|
{/* Form Fields Section */}
|
||||||
{renderEditableField(recipient, "firstName", "First Name")}
|
<div className="min-w-0 flex-1">
|
||||||
{renderEditableField(recipient, "lastName", "Last Name")}
|
{/* Role Name and Permissions Row */}
|
||||||
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
|
<div className="flex flex-col lg:flex-row lg:items-center gap-2 mb-2">
|
||||||
<span className="flex items-center gap-2">
|
<div className="flex-1 lg:w-1/2">
|
||||||
<span className="text-gray-400 ">| Permissions granted :</span>
|
<p className="font-semibold text-gray-800 text-xs">
|
||||||
<FiEdit
|
{renderEditableField(recipient, "roleName", "Role name")}
|
||||||
className={`cursor-pointer h-3 w-3 ${recipient.allowSenderToEdit
|
</p>
|
||||||
? "text-blue-500"
|
</div>
|
||||||
: "text-gray-400"
|
|
||||||
} hover:text-blue-800`}
|
{/* Permissions - Mobile: Below role, Desktop: Right side */}
|
||||||
onClick={() =>
|
<div className="lg:w-1/2 lg:flex lg:items-center lg:justify-end">
|
||||||
dispatch(
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
|
||||||
updateRecipient({
|
<div className="flex ps-2.5 sm:flex-row items-start sm:items-center gap-2 sm:gap-4">
|
||||||
...recipient,
|
<label className="flex items-center gap-1 text-xs sm:text-sm text-gray-700">
|
||||||
allowSenderToEdit: !recipient.allowSenderToEdit,
|
<input
|
||||||
uuid: getRecipientIdentifier(recipient),
|
type="checkbox"
|
||||||
})
|
checked={recipient.allowSenderToEdit}
|
||||||
)
|
onChange={() =>
|
||||||
}
|
dispatch(
|
||||||
/>
|
updateRecipient({
|
||||||
<ImBin
|
...recipient,
|
||||||
className={`cursor-pointer h-3 w-3 ${recipient.allowSenderToDelete
|
allowSenderToEdit: !recipient.allowSenderToEdit,
|
||||||
? "text-red-500"
|
uuid: getRecipientIdentifier(recipient),
|
||||||
: "text-gray-400"
|
})
|
||||||
} hover:text-red-700`}
|
)
|
||||||
onClick={() =>
|
}
|
||||||
dispatch(
|
className="accent-blue-500 w-3 h-3 sm:w-4 sm:h-4"
|
||||||
updateRecipient({
|
/>
|
||||||
...recipient,
|
<span className="whitespace-nowrap">Allow Edit</span>
|
||||||
allowSenderToDelete: !recipient.allowSenderToDelete,
|
</label>
|
||||||
uuid: getRecipientIdentifier(recipient),
|
|
||||||
})
|
<label className="flex items-center gap-1 text-xs sm:text-sm text-gray-700">
|
||||||
)
|
<input
|
||||||
}
|
type="checkbox"
|
||||||
/>
|
checked={recipient.allowSenderToDelete}
|
||||||
</span>
|
onChange={() =>
|
||||||
)}
|
dispatch(
|
||||||
</p>
|
updateRecipient({
|
||||||
<p className="text-xs text-gray-500 truncate flex items-center gap-1">
|
...recipient,
|
||||||
{renderEditableField(recipient, "email", "Recipient Email", "", "email")} |
|
allowSenderToDelete: !recipient.allowSenderToDelete,
|
||||||
<span
|
uuid: getRecipientIdentifier(recipient),
|
||||||
onClick={
|
})
|
||||||
isEditable(recipient)
|
)
|
||||||
? () => handleFieldFocus(getRecipientIdentifier(recipient), "type")
|
}
|
||||||
: undefined
|
className="accent-red-500 w-3 h-3 sm:w-4 sm:h-4"
|
||||||
}
|
/>
|
||||||
className={`${isEditable(recipient)
|
<span className="whitespace-nowrap">Allow Delete</span>
|
||||||
? "cursor-pointer hover:bg-gray-100"
|
</label>
|
||||||
: "cursor-not-allowed"
|
</div>
|
||||||
} rounded px-1 transition-colors`}
|
|
||||||
>
|
|
||||||
{recipient.type}
|
|
||||||
</span>
|
|
||||||
{(hasValue(recipient, "roleName") ||
|
|
||||||
transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) && (
|
|
||||||
<span>
|
|
||||||
{" | Role: "}
|
|
||||||
{renderEditableField(recipient, "roleName", "Role name")}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
{recipient.type === recipientType.INPERSONSIGNER && (
|
</div>
|
||||||
<>
|
</div>
|
||||||
<span className="flex items-center">
|
|
||||||
<span>{" | Host Name: "}</span>
|
{/* Name and Email Fields */}
|
||||||
{renderEditableField(recipient, "hostName", "Host Name")}
|
<div className="mb-2">
|
||||||
<span>{" | Host Email: "}</span>
|
{/* Mobile: Stack vertically, Tablet+: Side by side */}
|
||||||
{renderEditableField(recipient, "hostEmail", "Host Email", "", "email")}
|
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2">
|
||||||
</span>
|
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1">
|
||||||
</>
|
<div className="w-full sm:w-1/2">
|
||||||
)}
|
<p className="font-semibold text-gray-800 text-xs">
|
||||||
</p>
|
{renderEditableField(recipient, "firstName", "First Name")}
|
||||||
{editingField?.recipientId === getRecipientIdentifier(recipient) &&
|
</p>
|
||||||
editingField?.fieldName === "type" &&
|
</div>
|
||||||
isEditable(recipient) && (
|
<div className="w-full sm:w-1/2">
|
||||||
<div className="flex items-center gap-2 mt-1">
|
<p className="font-semibold text-gray-800 text-xs">
|
||||||
<label className="flex items-center gap-1">
|
{renderEditableField(recipient, "lastName", "Last Name")}
|
||||||
<input
|
</p>
|
||||||
type="radio"
|
</div>
|
||||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
|
||||||
value={recipientType.SIGNER}
|
|
||||||
checked={recipient.type === recipientType.SIGNER}
|
|
||||||
onChange={() =>
|
|
||||||
handleTypeChange(recipient, recipientType.SIGNER)
|
|
||||||
}
|
|
||||||
onBlur={() => setEditingField(null)}
|
|
||||||
className="accent-[var(--primary)]"
|
|
||||||
disabled={!isEditable(recipient)}
|
|
||||||
/>
|
|
||||||
<span className="text-xs">Signer</span>
|
|
||||||
</label>
|
|
||||||
<label className="flex items-center gap-1">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
|
||||||
value={recipientType.INPERSONSIGNER}
|
|
||||||
checked={recipient.type === recipientType.INPERSONSIGNER}
|
|
||||||
onChange={() =>
|
|
||||||
handleTypeChange(recipient, recipientType.INPERSONSIGNER)
|
|
||||||
}
|
|
||||||
onBlur={() => setEditingField(null)}
|
|
||||||
className="accent-[var(--primary)]"
|
|
||||||
disabled={!isEditable(recipient)}
|
|
||||||
/>
|
|
||||||
<span className="text-xs">In Person Signer</span>
|
|
||||||
</label>
|
|
||||||
<label className="flex items-center gap-1">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
|
||||||
value={recipientType.CC}
|
|
||||||
checked={recipient.type === recipientType.CC}
|
|
||||||
onChange={() =>
|
|
||||||
handleTypeChange(recipient, recipientType.CC)
|
|
||||||
}
|
|
||||||
onBlur={() => setEditingField(null)}
|
|
||||||
className="accent-[var(--primary)]"
|
|
||||||
disabled={!isEditable(recipient)}
|
|
||||||
/>
|
|
||||||
<span className="text-xs">CC</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="w-full sm:flex-1">
|
||||||
|
<p className="font-semibold text-gray-800 text-xs">
|
||||||
|
{renderEditableField(recipient, "email", "Recipient Email", "", "email")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Host Fields (for In-Person Signers) */}
|
||||||
|
{recipient.type === recipientType.INPERSONSIGNER && (
|
||||||
|
<div className="mb-2">
|
||||||
|
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2">
|
||||||
|
<div className="w-full sm:w-1/2">
|
||||||
|
<p className="font-semibold text-gray-800 text-xs">
|
||||||
|
{renderEditableField(recipient, "hostName", "Host Name")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-full sm:w-1/2">
|
||||||
|
<p className="font-semibold text-gray-800 text-xs">
|
||||||
|
{renderEditableField(recipient, "hostEmail", "Host Email", "", "email")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Recipient Type Radio Buttons */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 my-1.5 mx-1 sm:mx-2">
|
||||||
|
<label className="flex items-center gap-1">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||||
|
value={recipientType.SIGNER}
|
||||||
|
checked={recipient.type === recipientType.SIGNER}
|
||||||
|
onChange={() => handleTypeChange(recipient, recipientType.SIGNER)}
|
||||||
|
onBlur={() => setEditingField(null)}
|
||||||
|
className="accent-[var(--primary)] w-3 h-3"
|
||||||
|
disabled={!isEditable(recipient)}
|
||||||
|
/>
|
||||||
|
<span className="text-xs sm:text-sm">Signer</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-1">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||||
|
value={recipientType.INPERSONSIGNER}
|
||||||
|
checked={recipient.type === recipientType.INPERSONSIGNER}
|
||||||
|
onChange={() => handleTypeChange(recipient, recipientType.INPERSONSIGNER)}
|
||||||
|
onBlur={() => setEditingField(null)}
|
||||||
|
className="accent-[var(--primary)] w-3 h-3"
|
||||||
|
disabled={!isEditable(recipient)}
|
||||||
|
/>
|
||||||
|
<span className="text-xs sm:text-sm whitespace-nowrap">In Person Signer</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-1">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||||
|
value={recipientType.CC}
|
||||||
|
checked={recipient.type === recipientType.CC}
|
||||||
|
onChange={() => handleTypeChange(recipient, recipientType.CC)}
|
||||||
|
onBlur={() => setEditingField(null)}
|
||||||
|
className="accent-[var(--primary)] w-3 h-3"
|
||||||
|
disabled={!isEditable(recipient)}
|
||||||
|
/>
|
||||||
|
<span className="text-xs sm:text-sm">CC</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
{signType !== "self-sign" && (
|
{signType !== "self-sign" && (
|
||||||
<div className="flex justify-between gap-2 items-center">
|
<div className="flex justify-end lg:justify-center gap-2 items-center lg:ml-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="Remove recipient"
|
title="Remove recipient"
|
||||||
|
|
@ -435,9 +436,9 @@ const RecipientCards = ({
|
||||||
? false
|
? false
|
||||||
: !recipient.newOne && !recipient.allowSenderToDelete
|
: !recipient.newOne && !recipient.allowSenderToDelete
|
||||||
}
|
}
|
||||||
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50"
|
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1"
|
||||||
>
|
>
|
||||||
<ImBin size={16} />
|
<ImBin size={14} className="sm:w-4 sm:h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -446,24 +447,27 @@ const RecipientCards = ({
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-6 text-center text-gray-500 font-medium text-sm">
|
<div className="p-4 sm:p-6 text-center text-gray-500 font-medium text-sm">
|
||||||
No Recipients Added
|
No Recipients Added
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Responsive Modal */}
|
||||||
{signingOrderModal.open && (
|
{signingOrderModal.open && (
|
||||||
<div className="fixed top-0 left-0 inset-0 z-50 bg-black/50 backdrop-blur-sm flex justify-center items-center">
|
<div className="fixed top-0 left-0 inset-0 z-50 bg-black/50 backdrop-blur-sm flex justify-center items-center p-4">
|
||||||
<div className="flex-col relative justify-center items-center max-w-[80%] max-h-[60vh] h-full overflow-auto">
|
<div className="flex-col relative justify-center items-center w-full max-w-xs sm:max-w-md md:max-w-lg lg:max-w-2xl xl:max-w-4xl max-h-[80vh] sm:max-h-[70vh] lg:max-h-[60vh] h-full overflow-auto">
|
||||||
<div className="p-2 rounded-t-lg text-white flex justify-between items-center bg-[var(--primary)]">
|
<div className="p-3 sm:p-4 rounded-t-lg text-white flex justify-between items-center bg-[var(--primary)]">
|
||||||
<p className="font-semibold">Signing Order</p>
|
<p className="font-semibold text-sm sm:text-base">Signing Order</p>
|
||||||
<div
|
<div
|
||||||
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: false })}
|
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: false })}
|
||||||
className="text-xl cursor-pointer hover:text-gray-200 transition-all"
|
className="text-xl sm:text-2xl cursor-pointer hover:text-gray-200 transition-all p-1"
|
||||||
>
|
>
|
||||||
<IoCloseOutline />
|
<IoCloseOutline />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SigningOrderDiagram signers={recipients} />
|
<div className="bg-white rounded-b-lg">
|
||||||
|
<SigningOrderDiagram signers={recipients} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -90,15 +90,26 @@ const SelectedDocuments = ({ transactionTemplateToggle }) => {
|
||||||
|
|
||||||
// Get proper Tailwind class names for upload card span
|
// Get proper Tailwind class names for upload card span
|
||||||
const getUploadCardSpanClass = () => {
|
const getUploadCardSpanClass = () => {
|
||||||
switch (docCount) {
|
if (docCount === 0) {
|
||||||
case 0: return 'col-span-5'; // Full width when no docs
|
// Upload card takes all columns only when no docs
|
||||||
case 1: return 'col-span-4'; // 4 cols for upload, 1 for doc
|
return "col-span-1 sm:col-span-2 md:col-span-3 lg:col-span-4 xl:col-span-5";
|
||||||
case 2: return 'col-span-3'; // 3 cols for upload, 2 for docs
|
}
|
||||||
case 3: return 'col-span-2'; // 2 cols for upload, 3 for docs
|
|
||||||
case 4: return 'col-span-1'; // 1 col for upload, 4 for docs
|
switch (docCount) {
|
||||||
default: return 'col-span-1'; // 1 col for upload, rest for docs
|
case 1:
|
||||||
}
|
return "col-span-1 sm:col-span-1 md:col-span-2 lg:col-span-3 xl:col-span-4";
|
||||||
};
|
case 2:
|
||||||
|
return "col-span-1 sm:col-span-1 md:col-span-1 lg:col-span-2 xl:col-span-3";
|
||||||
|
case 3:
|
||||||
|
return "col-span-1 sm:col-span-1 md:col-span-1 lg:col-span-1 xl:col-span-2";
|
||||||
|
case 4:
|
||||||
|
return "col-span-1"; // balanced
|
||||||
|
default:
|
||||||
|
return "col-span-1";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Calculate how many documents should be in the first row
|
// Calculate how many documents should be in the first row
|
||||||
const getFirstRowDocCount = () => {
|
const getFirstRowDocCount = () => {
|
||||||
|
|
@ -113,150 +124,153 @@ const SelectedDocuments = ({ transactionTemplateToggle }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Upload Card First */}
|
{/* Upload Card First */}
|
||||||
<div className='space-y-3'>
|
<div className="space-y-3">
|
||||||
<div id='upload-doc-step-one' className="grid grid-cols-5 gap-2">
|
|
||||||
{/* Upload Document Card - Always First */}
|
|
||||||
<div
|
<div
|
||||||
onClick={() => setModalOpen(true)}
|
id="upload-doc-step-one"
|
||||||
className={`${getUploadCardSpanClass()} cursor-pointer h-[170px] flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl bg-white hover:bg-gray-50 transition shadow-sm hover:shadow-md`}
|
className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-5 gap-2"
|
||||||
>
|
>
|
||||||
<FaPlus className="text-[var(--primary)] text-3xl mb-2" />
|
{/* Upload Document Card */}
|
||||||
<p className="text-sm text-gray-600 text-center px-2">Upload Document</p>
|
<div
|
||||||
|
onClick={() => setModalOpen(true)}
|
||||||
|
className={`${getUploadCardSpanClass()} cursor-pointer h-[170px] flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl bg-white hover:bg-gray-50 transition shadow-sm hover:shadow-md`}
|
||||||
|
>
|
||||||
|
<FaPlus className="text-[var(--primary)] text-3xl mb-2" />
|
||||||
|
<p className="text-sm text-gray-600 text-center px-2">Upload Document</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* First Row Documents */}
|
||||||
|
<ReactSortable
|
||||||
|
className="contents"
|
||||||
|
list={documents.slice(0, firstRowDocCount).map((doc) => ({ ...doc }))}
|
||||||
|
setList={(newState) => {
|
||||||
|
const updatedDocs = [...newState, ...documents.slice(firstRowDocCount)];
|
||||||
|
dispatch(setDocuments(updatedDocs));
|
||||||
|
}}
|
||||||
|
handle=".drag-handle"
|
||||||
|
animation={200}
|
||||||
|
ghostClass="sortable-ghost"
|
||||||
|
disabled={false}
|
||||||
|
>
|
||||||
|
{documents.slice(0, firstRowDocCount).map((doc) => (
|
||||||
|
<div key={doc.id} className="col-span-1 mx-auto">
|
||||||
|
<DocumentCard
|
||||||
|
id={doc.id}
|
||||||
|
name={doc.name}
|
||||||
|
isCompleted={
|
||||||
|
"isCompleted" in doc ||
|
||||||
|
("status" in doc && doc.status === "UPLOADED")
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
transactionTemplateToggle={transactionTemplateToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ReactSortable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* First Row Documents */}
|
{/* Additional Rows */}
|
||||||
<ReactSortable
|
{remainingDocs > 0 && (
|
||||||
className="contents" // Use 'contents' to make children participate in parent grid
|
<ReactSortable
|
||||||
list={documents.slice(0, firstRowDocCount).map(doc => ({ ...doc }))}
|
className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-5 gap-2"
|
||||||
setList={(newState) => {
|
list={documents.slice(firstRowDocCount).map((doc) => ({ ...doc }))}
|
||||||
const updatedDocs = [
|
setList={(newState) => {
|
||||||
...newState,
|
const updatedDocs = [
|
||||||
...documents.slice(firstRowDocCount)
|
...documents.slice(0, firstRowDocCount),
|
||||||
];
|
...newState,
|
||||||
dispatch(setDocuments(updatedDocs));
|
];
|
||||||
}}
|
dispatch(setDocuments(updatedDocs));
|
||||||
handle=".drag-handle"
|
}}
|
||||||
animation={200}
|
handle=".drag-handle"
|
||||||
ghostClass="sortable-ghost"
|
animation={200}
|
||||||
disabled={false}
|
ghostClass="sortable-ghost"
|
||||||
>
|
disabled={false}
|
||||||
{documents.slice(0, firstRowDocCount).map((doc) => (
|
>
|
||||||
<div key={doc.id} className="col-span-1">
|
{documents.slice(firstRowDocCount).map((doc) => (
|
||||||
<DocumentCard
|
<div key={doc.id} className="col-span-1 mx-auto">
|
||||||
id={doc.id}
|
<DocumentCard
|
||||||
name={doc.name}
|
id={doc.id}
|
||||||
isCompleted={
|
name={doc.name}
|
||||||
("isCompleted" in doc || ("status" in doc && doc.status === "UPLOADED"))
|
isCompleted={
|
||||||
? true
|
"isCompleted" in doc ||
|
||||||
: false
|
("status" in doc && doc.status === "UPLOADED")
|
||||||
}
|
? true
|
||||||
transactionTemplateToggle={transactionTemplateToggle}
|
: false
|
||||||
/>
|
}
|
||||||
</div>
|
transactionTemplateToggle={transactionTemplateToggle}
|
||||||
))}
|
/>
|
||||||
</ReactSortable>
|
</div>
|
||||||
</div>
|
))}
|
||||||
|
</ReactSortable>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Additional Rows - Remaining Documents (5th, 6th, 7th, etc.) */}
|
{/* Modal */}
|
||||||
{remainingDocs > 0 && (
|
{isModalOpen && (
|
||||||
<ReactSortable
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-100/60 backdrop-blur-sm px-2 sm:px-4">
|
||||||
className="grid grid-cols-5 gap-2"
|
<div className="bg-white w-full h-full sm:max-w-5xl sm:h-[500px] rounded-xl shadow-lg flex flex-col sm:flex-row overflow-hidden relative">
|
||||||
list={documents.slice(firstRowDocCount).map(doc => ({ ...doc }))}
|
{/* Close Button */}
|
||||||
setList={(newState) => {
|
<button
|
||||||
const updatedDocs = [
|
className="absolute top-2 right-2 text-gray-500 hover:text-red-500 transition text-xl bg-white rounded-full p-2"
|
||||||
...documents.slice(0, firstRowDocCount),
|
onClick={() => setModalOpen(false)}
|
||||||
...newState
|
>
|
||||||
];
|
<IoMdClose />
|
||||||
dispatch(setDocuments(updatedDocs));
|
</button>
|
||||||
}}
|
|
||||||
handle=".drag-handle"
|
|
||||||
animation={200}
|
|
||||||
ghostClass="sortable-ghost"
|
|
||||||
disabled={false}
|
|
||||||
>
|
|
||||||
{documents.slice(firstRowDocCount).map((doc) => (
|
|
||||||
<div key={doc.id} className="col-span-1">
|
|
||||||
<DocumentCard
|
|
||||||
id={doc.id}
|
|
||||||
name={doc.name}
|
|
||||||
isCompleted={
|
|
||||||
("isCompleted" in doc || ("status" in doc && doc.status === "UPLOADED"))
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
transactionTemplateToggle={transactionTemplateToggle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</ReactSortable>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
{/* Sidebar Tabs */}
|
||||||
{/* Modal */}
|
<div className="w-full sm:w-1/4 bg-gray-100 p-4 flex flex-row sm:flex-col gap-2 border-b sm:border-b-0 sm:border-r border-gray-200 overflow-x-auto sm:overflow-hidden">
|
||||||
{isModalOpen && (
|
{tabs.map((tab) => (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-100/60 backdrop-blur-sm px-4">
|
<button
|
||||||
<div className="bg-white w-full max-w-5xl h-[500px] rounded-xl shadow-lg flex overflow-hidden relative">
|
key={tab.name}
|
||||||
{/* Close Button */}
|
className={`flex items-center gap-2 p-3 rounded-md text-sm font-medium transition whitespace-nowrap ${activeTab === tab.name
|
||||||
<button
|
? "bg-white shadow text-[var(--primary)]"
|
||||||
className="absolute top-2 right-2 text-gray-500 hover:text-red-500 transition text-xl bg-white rounded-full p-2"
|
: "hover:bg-gray-200 text-gray-600"
|
||||||
onClick={() => setModalOpen(false)}
|
}`}
|
||||||
>
|
onClick={() => setActiveTab(tab.name)}
|
||||||
<IoMdClose />
|
>
|
||||||
</button>
|
{tab.icon} {tab.name}
|
||||||
|
|
||||||
{/* Sidebar Tabs */}
|
|
||||||
<div className="w-1/4 bg-gray-100 p-4 flex flex-col gap-2 border-r border-gray-200">
|
|
||||||
{tabs.map(tab => (
|
|
||||||
<button
|
|
||||||
key={tab.name}
|
|
||||||
className={`flex items-center gap-2 p-3 rounded-md text-sm font-medium transition ${activeTab === tab.name
|
|
||||||
? 'bg-white shadow text-[var(--primary)]'
|
|
||||||
: 'hover:bg-gray-200 text-gray-600'
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab(tab.name)}
|
|
||||||
>
|
|
||||||
{tab.icon} {tab.name}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Modal Content */}
|
|
||||||
<div className="w-3/4 p-6 overflow-y-auto">
|
|
||||||
{activeTab === 'Upload' && (
|
|
||||||
<div
|
|
||||||
onClick={() => fileInputRef.current?.click()}
|
|
||||||
onDragOver={(e) => e.preventDefault()}
|
|
||||||
onDrop={handleDrop}
|
|
||||||
className="flex flex-col items-center justify-center h-full border-2 border-dashed border-gray-300 rounded-xl p-6 text-center cursor-pointer hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<FaCloudUploadAlt className="text-5xl text-[var(--primary)] mb-4" />
|
|
||||||
<p className="text-sm text-gray-500 mb-2">Drag & drop files here or</p>
|
|
||||||
<button className="px-5 py-2 text-sm bg-[var(--primary)] text-white rounded-md hover:scale-103 cursor-pointer transition-all">
|
|
||||||
Browse Files
|
|
||||||
</button>
|
</button>
|
||||||
<input
|
))}
|
||||||
type="file"
|
</div>
|
||||||
ref={fileInputRef}
|
|
||||||
className="hidden"
|
{/* Modal Content */}
|
||||||
onChange={handleFileChange}
|
<div className="w-full sm:w-3/4 p-4 sm:p-6 overflow-y-auto">
|
||||||
accept=".pdf,.doc,.docx,.jpg,.png,.jpeg,.txt"
|
{activeTab === "Upload" && (
|
||||||
multiple
|
<div
|
||||||
/>
|
onClick={() => fileInputRef.current?.click()}
|
||||||
</div>
|
onDragOver={(e) => e.preventDefault()}
|
||||||
)}
|
onDrop={handleDrop}
|
||||||
{activeTab !== 'Upload' && (
|
className="flex flex-col items-center justify-center h-full border-2 border-dashed border-gray-300 rounded-xl p-6 text-center cursor-pointer hover:bg-gray-50"
|
||||||
<div className="flex items-center justify-center h-full text-gray-400 text-sm">
|
>
|
||||||
<p>{activeTab} integration coming soon...</p>
|
<FaCloudUploadAlt className="text-5xl text-[var(--primary)] mb-4" />
|
||||||
</div>
|
<p className="text-sm text-gray-500 mb-2">
|
||||||
)}
|
Drag & drop files here or
|
||||||
|
</p>
|
||||||
|
<button className="px-5 py-2 text-sm bg-[var(--primary)] text-white rounded-md hover:scale-103 cursor-pointer transition-all">
|
||||||
|
Browse Files
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={fileInputRef}
|
||||||
|
className="hidden"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
accept=".pdf,.doc,.docx,.jpg,.png,.jpeg,.txt"
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeTab !== "Upload" && (
|
||||||
|
<div className="flex items-center justify-center h-full text-gray-400 text-sm">
|
||||||
|
<p>{activeTab} integration coming soon...</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,34 @@ canvas {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar size via CSS */
|
||||||
|
@layer utilities {
|
||||||
|
.custom-scrollbar {
|
||||||
|
scrollbar-width: thin; /* For Firefox */
|
||||||
|
scrollbar-color: rgba(120, 120, 120, 0.4) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(120, 120, 120, 0.4);
|
||||||
|
border-radius: 9999px; /* fully rounded */
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(120, 120, 120, 0.6); /* slightly darker on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Chrome, Safari, Edge, Opera */
|
/* Chrome, Safari, Edge, Opera */
|
||||||
input::-webkit-outer-spin-button,
|
input::-webkit-outer-spin-button,
|
||||||
input::-webkit-inner-spin-button {
|
input::-webkit-inner-spin-button {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue