feat(inquires): Make contact form configurable via CMS data

- Add CmsContact type support to FooterForm, Inquires, and InquiresForm components
- Make form fields dynamic by accepting contactData prop with configurable Fields array
- Add placeholder prop to InputGroup component for enhanced field customization
- Replace hardcoded form fields with dynamic field mapping from CMS data
- Support dynamic form title and description from contactData
- Use CMS-provided submit button text with fallback to default "Submit"
- Implement field-level validation configuration including required field handling
- Auto-detect input type based on field name (email, phone, text)
- Pass contact data from marketing page to FooterForm component
- Maintain backward compatibility with default fields when contactData is not provided
This commit is contained in:
AmirReza Jamali
2025-11-23 11:31:52 +03:30
parent 7ecb9a1084
commit cfe943d269
5 changed files with 82 additions and 45 deletions
+3 -2
View File
@@ -1,9 +1,10 @@
import { CmsContact } from "@/types/TCms";
import Inquires from "../_inquires/page"; import Inquires from "../_inquires/page";
const FooterForm = () => { const FooterForm = ({ contactData }: { contactData?: CmsContact }) => {
return ( return (
<div className="absolute w-full -top-[420px] lg:-top-72 z-10"> <div className="absolute w-full -top-[420px] lg:-top-72 z-10">
<Inquires /> <Inquires contactData={contactData} />
</div> </div>
); );
}; };
+3
View File
@@ -9,6 +9,7 @@ function InputGroup({
type, type,
validation, validation,
error, error,
placeholder,
}: { }: {
label: string; label: string;
id: string; id: string;
@@ -16,6 +17,7 @@ function InputGroup({
type: HTMLInputTypeAttribute; type: HTMLInputTypeAttribute;
validation?: RegisterOptions; validation?: RegisterOptions;
error?: string; error?: string;
placeholder?: string;
}) { }) {
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const [hasValue, setHasValue] = useState(false); const [hasValue, setHasValue] = useState(false);
@@ -40,6 +42,7 @@ function InputGroup({
: "border-gray-200" : "border-gray-200"
} p-2 py-3 rounded-lg w-full outline-none`} } p-2 py-3 rounded-lg w-full outline-none`}
id={id} id={id}
placeholder={placeholder}
{...rest} {...rest}
onChange={handleChange} onChange={handleChange}
onFocus={() => setIsFocused(true)} onFocus={() => setIsFocused(true)}
+66 -37
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import api from "@/service/api"; import api from "@/service/api";
import { CmsContact } from "@/types/TCms";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -7,20 +8,19 @@ import InputGroup from "../_components/InputGroup";
import Loading from "../_components/Loading"; import Loading from "../_components/Loading";
type TInquiresForm = { type TInquiresForm = {
full_name: string; [key: string]: string;
company_name: string;
work_email: string;
phone_number: string;
}; };
type InquiresFormProps = { type InquiresFormProps = {
columnsPerRow?: 1 | 2 | 3 | 4; columnsPerRow?: 1 | 2 | 3 | 4;
buttonText?: string; buttonText?: string;
contactData?: CmsContact;
}; };
const InquiresForm = ({ const InquiresForm = ({
columnsPerRow = 2, columnsPerRow = 2,
buttonText = "Submit", buttonText,
contactData,
}: InquiresFormProps) => { }: InquiresFormProps) => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { const {
@@ -59,44 +59,73 @@ const InquiresForm = ({
4: "lg:col-span-4", 4: "lg:col-span-4",
}[columnsPerRow]; }[columnsPerRow];
const defaultFields = [
{ Name: "full_name", Label: "Full Name", Placeholder: "", Required: false },
{
Name: "company_name",
Label: "Company Name",
Placeholder: "",
Required: false,
},
{
Name: "work_email",
Label: "Work Email",
Placeholder: "",
Required: false,
},
{
Name: "phone_number",
Label: "Phone Number",
Placeholder: "",
Required: false,
},
];
const fields = contactData?.Fields || defaultFields;
const submitText = contactData?.SubmitButtonText || buttonText || "Submit";
return ( return (
<form <form
className={`w-full px-4 lg:px-16 py-10 grid ${gridColsClass} gap-4`} className={`w-full px-4 lg:px-16 py-10 grid ${gridColsClass} gap-4`}
onSubmit={handleSubmit(onSubmit)}> onSubmit={handleSubmit(onSubmit)}>
<InputGroup {fields.map((field) => {
id="full_name" const inputType =
label="Full Name" field.Name.includes("email") || field.Name === "work_email"
register={register} ? "email"
type="text" : "text";
/> const isPhoneField =
<InputGroup field.Name.includes("phone") || field.Name === "phone_number";
id="company_name"
label="Company Name" return (
register={register} <InputGroup
type="text" key={field.Name}
/> id={field.Name}
<InputGroup label={field.Label}
id="work_email" placeholder={field.Placeholder}
label="Work Email" register={register}
register={register} type={inputType}
type="email" error={errors[field.Name]?.message}
/> validation={
<InputGroup isPhoneField
id="phone_number" ? {
label="Phone Number" pattern: {
register={register} value: /^[+\d]+$/,
type="text" message: "Please enter a valid phone number",
error={errors.phone_number?.message} },
validation={{ required: field.Required
pattern: { ? "This field is required"
value: /^[+\d]+$/, : undefined,
message: "Please enter a valid phone number", }
}, : field.Required
}} ? { required: "This field is required" }
/> : undefined
}
/>
);
})}
<div className={`${colSpanClass} flex justify-end`}> <div className={`${colSpanClass} flex justify-end`}>
<button className="bg-(--primary) cursor-pointer w-fit text-white rounded-full py-4 px-6"> <button className="bg-(--primary) cursor-pointer w-fit text-white rounded-full py-4 px-6">
{isLoading ? <Loading /> : buttonText} {isLoading ? <Loading /> : submitText}
</button> </button>
</div> </div>
</form> </form>
+8 -5
View File
@@ -1,17 +1,20 @@
import { CmsContact } from "@/types/TCms";
import InquiresForm from "./form"; import InquiresForm from "./form";
const Inquires = () => { const Inquires = ({ contactData }: { contactData?: CmsContact }) => {
return ( return (
<div className="flex flex-col items-center mt-16 px-8 "> <div className="flex flex-col items-center mt-16 px-8 ">
<div className="lg:w-2/3 m-auto flex flex-col gap-4 mb-12"> <div className="lg:w-2/3 m-auto flex flex-col gap-4 mb-12">
<h2 className="font-bold text-4xl">Unlock Your Growth Potential</h2> <h2 className="font-bold text-4xl">
{contactData?.Title || "Unlock Your Growth Potential"}
</h2>
<p className="font-normal text-[16px] text-gray-600"> <p className="font-normal text-[16px] text-gray-600">
Register for early access and a free consultation. Let's start winning {contactData?.Description ||
together. "Register for early access and a free consultation. Let's start winning together."}
</p> </p>
</div> </div>
<section className="border border-gray-300 rounded-4xl w-full lg:max-w-2/3 flex flex-col items-center bg-white"> <section className="border border-gray-300 rounded-4xl w-full lg:max-w-2/3 flex flex-col items-center bg-white">
<InquiresForm /> <InquiresForm contactData={contactData} />
</section> </section>
</div> </div>
); );
+2 -1
View File
@@ -39,6 +39,7 @@ export default async function Home({ params }: IProps) {
} catch (error) { } catch (error) {
console.error("Error fetching CMS data:", error); console.error("Error fetching CMS data:", error);
} }
console.log(cmsData);
const centerFrames = [ const centerFrames = [
{ {
@@ -247,7 +248,7 @@ export default async function Home({ params }: IProps) {
aria-hidden="true" aria-hidden="true"
/> />
<FooterForm /> <FooterForm contactData={cmsData?.data.contact} />
<div className="relative z-10 container px-4 py-4 m-auto flex flex-col justify-center items-center mt-80 "> <div className="relative z-10 container px-4 py-4 m-auto flex flex-col justify-center items-center mt-80 ">
<div className="flex justify-between flex-col lg:flex-row gap-14 w-full lg:w-1/2"> <div className="flex justify-between flex-col lg:flex-row gap-14 w-full lg:w-1/2">