ISSUE 11: add sign up validation. and ensure users open and read T & C and privacy policy

This commit is contained in:
Ayobami
2025-07-01 20:44:25 +01:00
parent 9867509259
commit 7cf8caf9da
3 changed files with 356 additions and 200 deletions
@@ -1,4 +1,4 @@
import React from 'react' import React from "react";
import { GlobalContext } from "@/globalContext"; import { GlobalContext } from "@/globalContext";
import { callCustomAPI } from "@/utils/callCustomAPI"; import { callCustomAPI } from "@/utils/callCustomAPI";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
@@ -8,19 +8,28 @@ import { useState } from "react";
import { Fragment } from "react"; import { Fragment } from "react";
import MkdSDK from "@/utils/MkdSDK"; import MkdSDK from "@/utils/MkdSDK";
const PrivacyAndPolicyModal = ({ isOpen, closeModal }) => { const PrivacyAndPolicyModal = ({ isOpen, closeModal, onReadToEnd }) => {
const [privacy, setPrivacy] = useState(""); const [privacy, setPrivacy] = useState("");
const { dispatch: globalDispatch } = useContext(GlobalContext); const { dispatch: globalDispatch } = useContext(GlobalContext);
const [hasReadToEnd, setHasReadToEnd] = useState(false);
async function fetchPrivacyPolicy() { async function fetchPrivacyPolicy() {
globalDispatch({ type: "START_LOADING" }); globalDispatch({ type: "START_LOADING" });
const sdk = new MkdSDK(); const sdk = new MkdSDK();
sdk.setTable("cms"); sdk.setTable("cms");
try { try {
const result = await callCustomAPI("cms", "post", { payload: { content_key: "privacy_policy" }, limit: 1000, page: 1 }, "PAGINATE"); const result = await callCustomAPI(
"cms",
"post",
{ payload: { content_key: "privacy_policy" }, limit: 1000, page: 1 },
"PAGINATE"
);
if (Array.isArray(result.list) && result.list.length > 0) { if (Array.isArray(result.list) && result.list.length > 0) {
setPrivacy(result.list.find((stg) => stg.content_key == "privacy_policy")?.content_value); setPrivacy(
result.list.find((stg) => stg.content_key == "privacy_policy")
?.content_value
);
} }
} catch (err) { } catch (err) {
globalDispatch({ globalDispatch({
@@ -36,64 +45,69 @@ const PrivacyAndPolicyModal = ({ isOpen, closeModal }) => {
useEffect(() => { useEffect(() => {
fetchPrivacyPolicy(); fetchPrivacyPolicy();
}, []); setHasReadToEnd(false);
}, [isOpen]);
function handleScroll(e) {
const { scrollTop, scrollHeight, clientHeight } = e.target;
if (!hasReadToEnd && scrollTop + clientHeight >= scrollHeight - 10) {
setHasReadToEnd(true);
if (onReadToEnd) onReadToEnd();
}
}
return ( return (
<> <>
<div className={`${isOpen ? "flex" : "hidden"} fixed inset-0 items-center justify-center`}></div> <div
className={`${
isOpen ? "flex" : "hidden"
} fixed inset-0 items-center justify-center`}
></div>
<Transition <Transition appear show={isOpen} as={Fragment}>
appear <Dialog as='div' className='relative z-10' onClose={closeModal}>
show={isOpen}
as={Fragment}
>
<Dialog
as="div"
className="relative z-10"
onClose={closeModal}
>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter='ease-out duration-300'
enterFrom="opacity-0" enterFrom='opacity-0'
enterTo="opacity-100" enterTo='opacity-100'
leave="ease-in duration-200" leave='ease-in duration-200'
leaveFrom="opacity-100" leaveFrom='opacity-100'
leaveTo="opacity-0" leaveTo='opacity-0'
> >
<div className="fixed inset-0 bg-black bg-opacity-25" /> <div className='fixed inset-0 bg-black bg-opacity-25' />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 overflow-y-auto"> <div className='fixed inset-0 overflow-y-auto'>
<div className="flex min-h-full items-center justify-center p-4 text-center"> <div className='flex min-h-full items-center justify-center p-4 text-center'>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter='ease-out duration-300'
enterFrom="opacity-0 scale-95" enterFrom='opacity-0 scale-95'
enterTo="opacity-100 scale-100" enterTo='opacity-100 scale-100'
leave="ease-in duration-200" leave='ease-in duration-200'
leaveFrom="opacity-100 scale-100" leaveFrom='opacity-100 scale-100'
leaveTo="opacity-0 scale-95" leaveTo='opacity-0 scale-95'
> >
<Dialog.Panel className="w-full max-w-6xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"> <Dialog.Panel className='w-full max-w-6xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all'>
<Dialog.Title <Dialog.Title
as="h3" as='h3'
className="text-lg font-medium leading-6 text-gray-900 flex justify-between items-center" className='text-lg flex items-center justify-between font-medium leading-6 text-gray-900'
> >
{" "}
{" "} {" "}
<button <button
type="button" type='button'
onClick={closeModal} onClick={closeModal}
className="py-2 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full flex justify-end" className='flex justify-end rounded-full border px-3 py-2 text-2xl font-normal duration-100 hover:bg-gray-200 active:bg-gray-300'
> >
&#x2715; &#x2715;
</button> </button>
</Dialog.Title> </Dialog.Title>
<div className="mt-2"> <div className='mt-2'>
<article <article
className="sun-editor-editable text-sm max-h-[600px] overflow-y-auto my-8" className='sun-editor-editable my-8 max-h-[600px] overflow-y-auto text-sm'
dangerouslySetInnerHTML={{ __html: privacy }} dangerouslySetInnerHTML={{ __html: privacy }}
onScroll={handleScroll}
></article> ></article>
</div> </div>
</Dialog.Panel> </Dialog.Panel>
@@ -103,7 +117,7 @@ const PrivacyAndPolicyModal = ({ isOpen, closeModal }) => {
</Dialog> </Dialog>
</Transition> </Transition>
</> </>
) );
} };
export default PrivacyAndPolicyModal export default PrivacyAndPolicyModal;
+197 -63
View File
@@ -16,6 +16,7 @@ import TermsAndConditionsModal from "./TermsAndConditionsModal";
import DatePickerV2 from "@/components/frontend/DatePickerV2"; import DatePickerV2 from "@/components/frontend/DatePickerV2";
import { LoadingButton } from "@/components/frontend"; import { LoadingButton } from "@/components/frontend";
import PrivacyAndPolicyModal from "./PrivacyAndPolicyModal"; import PrivacyAndPolicyModal from "./PrivacyAndPolicyModal";
import commonPasswords from "@/assets/json/common-passwords.json";
export default function SignUpDetailsForm() { export default function SignUpDetailsForm() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -28,6 +29,9 @@ export default function SignUpDetailsForm() {
const [modalOpen, setModalOpen] = React.useState(false); const [modalOpen, setModalOpen] = React.useState(false);
const [privacyOpen, setPrivacyModalOpen] = React.useState(false); const [privacyOpen, setPrivacyModalOpen] = React.useState(false);
const initialDate = useRef(new Date()); const initialDate = useRef(new Date());
const [agreedToTerms, setAgreedToTerms] = React.useState(false);
const [privacyRead, setPrivacyRead] = React.useState(false);
const [passwordErrors, setPasswordErrors] = React.useState([]);
function closeModal() { function closeModal() {
setModalOpen(false); setModalOpen(false);
@@ -36,11 +40,67 @@ export default function SignUpDetailsForm() {
setPrivacyModalOpen(false); setPrivacyModalOpen(false);
} }
// --- DOB must be at least 18 years ---
function isAtLeast18YearsOld(date) {
if (!date) return false;
const now = new Date();
const dob = new Date(date);
let age = now.getFullYear() - dob.getFullYear();
const m = now.getMonth() - dob.getMonth();
if (m < 0 || (m === 0 && now.getDate() < dob.getDate())) {
age--;
}
return age >= 18;
}
// --- Password validation ---
function validatePassword(password, firstName, lastName, dob) {
const errors = [];
if (!password) errors.push("Password is required.");
if (password && password.length < 10)
errors.push("Password must be at least 10 characters long.");
if (password && !/[a-z]/.test(password))
errors.push("Password must contain at least one lowercase letter.");
if (password && !/[A-Z]/.test(password))
errors.push("Password must contain at least one uppercase letter.");
if (password && !/[0-9]/.test(password))
errors.push("Password must contain at least one digit.");
if (password && !/[^A-Za-z0-9]/.test(password))
errors.push("Password must contain at least one symbol.");
if (
password &&
firstName &&
password.toLowerCase().includes(firstName.toLowerCase())
)
errors.push("Password must not contain your first name.");
if (
password &&
lastName &&
password.toLowerCase().includes(lastName.toLowerCase())
)
errors.push("Password must not contain your last name.");
if (password && dob) {
const dobStr = new Date(dob).toISOString().slice(0, 10).replace(/-/g, "");
if (dobStr && password.includes(dobStr))
errors.push("Password must not contain your date of birth.");
}
if (
password &&
commonPasswords.some((w) => password.toLowerCase().includes(w))
)
errors.push("Password must not contain common password words.");
return errors;
}
// --- Validation schema ---
const schema = yup.object({ const schema = yup.object({
firstName: yup.string(), firstName: yup.string().required("First name is required."),
lastName: yup.string(), lastName: yup.string().required("Last name is required."),
dob: yup.date(), dob: yup
password: yup.string() .date()
.required("Date of birth is required.")
.test("age", "You must be at least 18 years old.", isAtLeast18YearsOld),
password: yup.string().required("Password is required."),
}); });
const { const {
@@ -65,6 +125,34 @@ export default function SignUpDetailsForm() {
const data = watch(); const data = watch();
// --- Password error display ---
React.useEffect(() => {
setPasswordErrors(
validatePassword(data.password, data.firstName, data.lastName, data.dob)
);
}, [data.password, data.firstName, data.lastName, data.dob]);
// --- Terms and Privacy Modal handlers ---
function handleAgreeTerms() {
setAgreedToTerms(true);
setModalOpen(false);
}
function handlePrivacyRead() {
setPrivacyRead(true);
setPrivacyModalOpen(false);
}
// --- Disable submit logic ---
const canSubmit =
data.firstName &&
data.lastName &&
data.dob &&
isAtLeast18YearsOld(data.dob) &&
data.password &&
passwordErrors.length === 0 &&
agreedToTerms &&
privacyRead;
async function onSubmit() { async function onSubmit() {
setLoading(true); setLoading(true);
try { try {
@@ -74,7 +162,15 @@ export default function SignUpDetailsForm() {
// register device // register device
sdk.setTable("device"); sdk.setTable("device");
await sdk.callRestAPI({ active: 1, user_id: result.user_id, last_login_time: new Date().toISOString().split("T")[0], uid: localStorage.getItem("device-uid") }, "POST"); await sdk.callRestAPI(
{
active: 1,
user_id: result.user_id,
last_login_time: new Date().toISOString().split("T")[0],
uid: localStorage.getItem("device-uid"),
},
"POST"
);
await callCustomAPI( await callCustomAPI(
"edit-self", "edit-self",
@@ -85,11 +181,13 @@ export default function SignUpDetailsForm() {
last_name: data.lastName, last_name: data.lastName,
}, },
profile: { profile: {
dob: isSameDay(data.dob, initialDate.current) ? undefined : moment(data.dob).format("yyyy-MM-DD"), dob: isSameDay(data.dob, initialDate.current)
? undefined
: moment(data.dob).format("yyyy-MM-DD"),
}, },
}, },
"", "",
result.token, result.token
); );
localStorage.removeItem("token"); localStorage.removeItem("token");
@@ -108,11 +206,9 @@ export default function SignUpDetailsForm() {
type: "manual", type: "manual",
message: result.validation[field], message: result.validation[field],
}); });
} }
} }
} }
} catch (err) { } catch (err) {
setLoading(false); setLoading(false);
setError("firstName", { setError("firstName", {
@@ -126,43 +222,60 @@ export default function SignUpDetailsForm() {
return ( return (
<> <>
<section className="flex flex-col items-center justify-center bg-white md:w-1/2"> <section className='flex flex-col items-center justify-center bg-white md:w-1/2'>
<form <form
className="flex w-full max-w-md flex-col px-6" className='flex w-full max-w-md flex-col px-6'
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
autoComplete="off" autoComplete='off'
> >
<h1 className="mb-8 text-center text-5xl font-bold">Finish Signing Up</h1> <h1 className='mb-8 text-center text-5xl font-bold'>
<div className="mb-8"> Finish Signing Up
</h1>
<div className='mb-8'>
<input <input
type="text" type='text'
{...register("firstName")} {...register("firstName")}
className="w-full resize-none rounded-md border bg-transparent py-2 px-4 focus:outline-none active:outline-none" className='w-full resize-none rounded-md border bg-transparent px-4 py-2 focus:outline-none active:outline-none'
placeholder="First name" placeholder='First name'
autoComplete="off" autoComplete='off'
/> />
<p className="text-red-500 text-xs italic mt-2 block">{errors.firstName?.message}</p> <p className='mt-2 block text-xs italic text-red-500'>
{errors.firstName?.message}
</p>
</div> </div>
<div className="mb-8"> <div className='mb-8'>
<input <input
type="text" type='text'
{...register("lastName")} {...register("lastName")}
className="w-full resize-none rounded-md border bg-transparent py-2 px-4 focus:outline-none active:outline-none" className='w-full resize-none rounded-md border bg-transparent px-4 py-2 focus:outline-none active:outline-none'
placeholder="Last name" placeholder='Last name'
autoComplete="off" autoComplete='off'
/> />
<p className="text-red-500 text-xs italic mt-2 block">{errors.lastName?.message}</p> <p className='mt-2 block text-xs italic text-red-500'>
{errors.lastName?.message}
</p>
</div> </div>
<DatePickerV2 <DatePickerV2
control={control} control={control}
name="dob" name='dob'
min={new Date("1950-01-01")} min={new Date("1950-01-01")}
max={initialDate.current} max={initialDate.current}
setValue={(v) => setValue("dob", v)} setValue={(v) => setValue("dob", v)}
/> />
<div className={`${errors.password?.message && dirtyFields.password ? "border rounded-md border-[#C42945]" : "borde"} relative mb-4 flex justify-between rounded-md bg-transparent`}> <p className='mt-2 block text-xs italic text-red-500'>
{errors.dob?.message}
</p>
<div
className={`${
passwordErrors.length > 0 && dirtyFields.password
? "rounded-md border border-[#C42945]"
: "borde"
} relative mb-4 flex flex-col rounded-md bg-transparent`}
>
<div className='flex items-center justify-between'>
<input <input
autoComplete={showPassword ? "off" : "new-password"} autoComplete={showPassword ? "off" : "new-password"}
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
@@ -171,76 +284,97 @@ export default function SignUpDetailsForm() {
trigger("password"); trigger("password");
}, },
})} })}
className="flex-grow rounded-md border p-2 px-4 focus:outline-none active:outline-none " className='flex-grow rounded-md border p-2 px-4 focus:outline-none active:outline-none '
placeholder="Password" placeholder='Password'
/>{" "} />
<button <button
type="button" type='button'
onClick={() => setShowPassword((prev) => !prev)} onClick={() => setShowPassword((prev) => !prev)}
className="absolute right-1 top-[20%]" className='absolute right-1 top-[20%]'
> >
{" "}
{showPassword ? ( {showPassword ? (
<img <img src='/show.png' alt='' className='mr-2 w-6' />
src="/show.png"
alt=""
className="mr-2 w-6"
/>
) : ( ) : (
<img <img src='/invisible.png' alt='' className='mr-2 w-6' />
src="/invisible.png"
alt=""
className="mr-2 w-6"
/>
)} )}
</button> </button>
</div> </div>
{dirtyFields.password && passwordErrors.length > 0 && (
<ul className='ml-6 mt-2 list-disc text-xs text-red-500'>
{passwordErrors.map((err, idx) => (
<li key={idx}>{err}</li>
))}
</ul>
)}
</div>
<p className='mb-4 text-sm normal-case text-gray-500'>
<p className="mb-4 text-sm normal-case text-gray-500">
Select and agree to{" "} Select and agree to{" "}
<button <button
type="button" type='button'
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
className="underline" className='underline'
// target={"_blank"} // target={"_blank"}
> >
{" "} Terms and Conditions
</button>
{" "}
to continue.
{" "}
{" "} {" "}
Terms and Conditions
</button>{" "}
to continue.{" "}
<button <button
type="button" type='button'
onClick={() => setPrivacyModalOpen(true)} onClick={() => setPrivacyModalOpen(true)}
className="underline" className='underline'
> >
Privacy Policy Privacy Policy
</button> </button>
</p> </p>
<div className='mb-1 flex flex-col'>
{" "}
{!agreedToTerms && (
<span className='text-xs text-red-500'>
You must agree to the Terms and Conditions.
</span>
)}
{!privacyRead && (
<span className='text-xs text-red-500'>
You must read the Privacy Policy to the end.
</span>
)}
</div>
<LoadingButton <LoadingButton
loading={loading} loading={loading}
type="submit" type='submit'
className={`disabled:cursor-not-allowed login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none ${loading ? "py-1" : "py-2"}`} className={`login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none disabled:cursor-not-allowed ${
// disabled={!recaptchaValidated} loading ? "py-1" : "py-2"
}`}
disabled={!canSubmit}
> >
Continue Continue
</LoadingButton> </LoadingButton>
</form> </form>
</section> </section>
<section <section
style={{ backgroundImage: `url(${role == "host" ? "/host-sign-up.jpg" : "/sign-up-bg.jpg"})`, backgroundSize: "cover", backgroundRepeat: "no-repeat", backgroundPosition: "center" }} style={{
className="hidden w-1/2 md:block" backgroundImage: `url(${
> role == "host" ? "/host-sign-up.jpg" : "/sign-up-bg.jpg"
</section> })`,
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
}}
className='hidden w-1/2 md:block'
></section>
<TermsAndConditionsModal <TermsAndConditionsModal
isOpen={modalOpen} isOpen={modalOpen}
closeModal={closeModal} closeModal={() => setModalOpen(false)}
setIsAgreed={handleAgreeTerms}
/> />
<PrivacyAndPolicyModal <PrivacyAndPolicyModal
isOpen={privacyOpen} isOpen={privacyOpen}
closeModal={closePrivacyModal} closeModal={() => setPrivacyModalOpen(false)}
onReadToEnd={handlePrivacyRead}
/> />
</> </>
); );
@@ -7,14 +7,23 @@ import { useContext } from "react";
import { useState } from "react"; import { useState } from "react";
import { Fragment } from "react"; import { Fragment } from "react";
export default function TermsAndConditionsModal({ isOpen, closeModal, setIsAgreed }) { export default function TermsAndConditionsModal({
isOpen,
closeModal,
setIsAgreed,
}) {
const [termsAndConditions, setTermsAndCondition] = useState(""); const [termsAndConditions, setTermsAndCondition] = useState("");
const [agreed, setAgreed] = useState(false); const [agreed, setAgreed] = useState(false);
const { dispatch: globalDispatch } = useContext(GlobalContext); const { dispatch: globalDispatch } = useContext(GlobalContext);
async function fetchTermsAndConditions() { async function fetchTermsAndConditions() {
try { try {
const result = await callCustomAPI("cms", "post", { where: [`content_key = 'terms_and_conditions'`], limit: 1, page: 1 }, "PAGINATE"); const result = await callCustomAPI(
"cms",
"post",
{ where: [`content_key = 'terms_and_conditions'`], limit: 1, page: 1 },
"PAGINATE"
);
if (Array.isArray(result.list) && result.list.length > 0) { if (Array.isArray(result.list) && result.list.length > 0) {
setTermsAndCondition(result.list[0].content_value); setTermsAndCondition(result.list[0].content_value);
@@ -35,73 +44,72 @@ export default function TermsAndConditionsModal({ isOpen, closeModal, setIsAgree
}, []); }, []);
return ( return (
<> <>
<div className={`${isOpen ? "flex" : "hidden"} fixed inset-0 items-center justify-center`}></div> <div
className={`${
isOpen ? "flex" : "hidden"
} fixed inset-0 items-center justify-center`}
></div>
<Transition <Transition appear show={isOpen} as={Fragment}>
appear <Dialog as='div' className='relative z-10' onClose={closeModal}>
show={isOpen}
as={Fragment}
>
<Dialog
as="div"
className="relative z-10"
onClose={closeModal}
>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter='ease-out duration-300'
enterFrom="opacity-0" enterFrom='opacity-0'
enterTo="opacity-100" enterTo='opacity-100'
leave="ease-in duration-200" leave='ease-in duration-200'
leaveFrom="opacity-100" leaveFrom='opacity-100'
leaveTo="opacity-0" leaveTo='opacity-0'
> >
<div className="fixed inset-0 bg-black bg-opacity-25" /> <div className='fixed inset-0 bg-black bg-opacity-25' />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 overflow-y-auto"> <div className='fixed inset-0 overflow-y-auto'>
<div className="flex min-h-full items-center justify-center p-4 text-center"> <div className='flex min-h-full items-center justify-center p-4 text-center'>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter='ease-out duration-300'
enterFrom="opacity-0 scale-95" enterFrom='opacity-0 scale-95'
enterTo="opacity-100 scale-100" enterTo='opacity-100 scale-100'
leave="ease-in duration-200" leave='ease-in duration-200'
leaveFrom="opacity-100 scale-100" leaveFrom='opacity-100 scale-100'
leaveTo="opacity-0 scale-95" leaveTo='opacity-0 scale-95'
> >
<Dialog.Panel className="w-full max-w-6xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"> <Dialog.Panel className='w-full max-w-6xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all'>
<Dialog.Title <Dialog.Title
as="h3" as='h3'
className="text-lg font-medium leading-6 text-gray-900 flex justify-between items-center" className='text-lg flex items-center justify-between font-medium leading-6 text-gray-900'
> >
{" "}
{" "} {" "}
<button <button
type="button" type='button'
onClick={closeModal} onClick={closeModal}
className="py-2 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full flex justify-end" className='flex justify-end rounded-full border px-3 py-2 text-2xl font-normal duration-100 hover:bg-gray-200 active:bg-gray-300'
> >
&#x2715; &#x2715;
</button> </button>
</Dialog.Title> </Dialog.Title>
<div className="mt-2"> <div className='mt-2'>
<article <article
className="sun-editor-editable text-sm max-h-[600px] overflow-y-auto my-8" className='sun-editor-editable my-8 max-h-[600px] overflow-y-auto text-sm'
dangerouslySetInnerHTML={{ __html: termsAndConditions }} dangerouslySetInnerHTML={{ __html: termsAndConditions }}
></article> ></article>
</div> </div>
<div className="checkbox-container"> <div className='checkbox-container'>
<input <input
type={"checkbox"} type={"checkbox"}
name="i-agree" name='i-agree'
id="i-agree" id='i-agree'
checked={agreed} checked={agreed}
onChange={() => {setAgreed((prev) => !prev); setIsAgreed((prev) => !prev); closeModal()}} onChange={() => {
setAgreed((prev) => !prev);
setIsAgreed((prev) => !prev);
closeModal();
}}
/> />
<label <label
htmlFor="i-agree" htmlFor='i-agree'
className="items-center cursor-pointer remove-select" className='remove-select cursor-pointer items-center'
> >
Yeah, I agree to everything Yeah, I agree to everything
</label> </label>