7b6cd4d965
- Add validation rules for form fields with XSS protection - Create API error handler to parse and manage server validation errors - Integrate validation rules into contact and inquiries forms - Enhance error handling with user feedback for form submissions - Introduce unit tests for validation and error handling functionalities
136 lines
3.6 KiB
TypeScript
136 lines
3.6 KiB
TypeScript
"use client";
|
|
import { handleApiError } from "@/lib/apiErrorHandler";
|
|
import { validationRules } from "@/lib/validation";
|
|
import api from "@/service/api";
|
|
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
|
import { useRef, useState } from "react";
|
|
import { useForm } from "react-hook-form";
|
|
import { toast } from "react-toastify";
|
|
import InputGroup from "../_components/InputGroup";
|
|
import Loading from "../_components/Loading";
|
|
import TextareaGroup from "../_components/Textarea";
|
|
|
|
type TContactUsForm = {
|
|
full_name: string;
|
|
email: string;
|
|
phone: string;
|
|
message: string;
|
|
};
|
|
|
|
const FORM_FIELDS = ["full_name", "email", "phone", "message"];
|
|
|
|
interface IProps {
|
|
columnsPerRow?: 1 | 2 | 3 | 4;
|
|
buttonText?: string;
|
|
}
|
|
const ContactUsForm = ({
|
|
buttonText = "Submit",
|
|
columnsPerRow = 1,
|
|
}: IProps) => {
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [hcaptchaToken, setHcaptchaToken] = useState<string | null>(null);
|
|
const captchaRef = useRef<HCaptcha>(null);
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors },
|
|
reset,
|
|
setError,
|
|
} = useForm<TContactUsForm>();
|
|
|
|
const onCaptchaVerify = (token: string) => {
|
|
setHcaptchaToken(token);
|
|
};
|
|
|
|
const onCaptchaExpire = () => {
|
|
setHcaptchaToken(null);
|
|
};
|
|
|
|
const onSubmit = async (data: TContactUsForm) => {
|
|
if (!hcaptchaToken) {
|
|
toast.error("Please complete the captcha verification");
|
|
return;
|
|
}
|
|
setIsLoading(() => true);
|
|
try {
|
|
const response = (
|
|
await api.post("contacts", { ...data, hcaptcha_token: hcaptchaToken })
|
|
).data;
|
|
toast.success(response.message);
|
|
reset();
|
|
setHcaptchaToken(null);
|
|
captchaRef.current?.resetCaptcha();
|
|
} catch (error) {
|
|
handleApiError(error, setError, toast.error, FORM_FIELDS);
|
|
} finally {
|
|
setIsLoading(() => false);
|
|
}
|
|
};
|
|
const gridColsClass = {
|
|
1: "lg:grid-cols-1",
|
|
2: "lg:grid-cols-2",
|
|
3: "lg:grid-cols-3",
|
|
4: "lg:grid-cols-4",
|
|
}[columnsPerRow];
|
|
|
|
const colSpanClass = {
|
|
1: "lg:col-span-1",
|
|
2: "lg:col-span-2",
|
|
3: "lg:col-span-3",
|
|
4: "lg:col-span-4",
|
|
}[columnsPerRow];
|
|
|
|
return (
|
|
<form
|
|
className={`w-full px-4 lg:px-16 py-10 grid ${gridColsClass} gap-4`}
|
|
onSubmit={handleSubmit(onSubmit)}>
|
|
<InputGroup
|
|
id="full_name"
|
|
label="Full Name"
|
|
register={register}
|
|
validation={validationRules.fullName}
|
|
type="text"
|
|
error={errors.full_name?.message}
|
|
/>
|
|
<InputGroup
|
|
id="email"
|
|
label="Email"
|
|
register={register}
|
|
validation={validationRules.email}
|
|
type="email"
|
|
error={errors.email?.message}
|
|
/>
|
|
<InputGroup
|
|
id="phone"
|
|
label="Phone"
|
|
register={register}
|
|
validation={validationRules.phone}
|
|
type="text"
|
|
error={errors.phone?.message}
|
|
/>
|
|
<TextareaGroup
|
|
id="message"
|
|
label="Message"
|
|
register={register}
|
|
error={errors.message?.message}
|
|
validation={validationRules.message}
|
|
/>
|
|
<div className={`${colSpanClass}`}>
|
|
<HCaptcha
|
|
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY || ""}
|
|
onVerify={onCaptchaVerify}
|
|
onExpire={onCaptchaExpire}
|
|
ref={captchaRef}
|
|
/>
|
|
</div>
|
|
<div className={`${colSpanClass} flex justify-end`}>
|
|
<button className="bg-(--primary) cursor-pointer w-fit text-white rounded-full py-4 px-6 flex justify-center items-center text-center">
|
|
{isLoading ? <Loading /> : buttonText}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
);
|
|
};
|
|
|
|
export default ContactUsForm;
|