Files
stream.graff.tech-client/src/landing/features/lead-form/LeadForm.tsx
T

205 lines
6.9 KiB
TypeScript

import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { MAIL_REQUEST_FROM } from "@/mailFrom";
import { mailApi } from "@/landing/lib/api";
import { productOptionsFromT } from "@/landing/lib/productLabels";
import type { Product } from "@/landing/types";
import { Button } from "@/landing/ui/Button";
import { CheckboxesGroup } from "@/landing/ui/CheckboxesGroup";
import { ruPhoneDigits } from "@/landing/lib/phoneRu";
import { INTL_PHONE_MAX_DIGITS } from "@/landing/lib/phoneIntl";
import { PhoneInputIntl } from "@/landing/ui/PhoneInputIntl";
import { PhoneInputRu } from "@/landing/ui/PhoneInputRu";
import { useRefererStore } from "@/landing/stores/useRefererStore";
import {
Controller,
FormProvider,
useForm,
type SubmitHandler,
} from "react-hook-form";
import type { LeadFormValues } from "./types";
export type { LeadFormValues } from "./types";
export function LeadForm({
defaultProducts,
idPrefix = "",
onSuccess,
phonePlaceholder,
formClassName = "lg:space-y-[1.944vw] md:max-lg:space-y-7 space-y-3",
}: {
defaultProducts: Product[];
/** Префикс для id полей (например `modal-`), чтобы избежать дублей в DOM */
idPrefix?: string;
onSuccess: (id: string) => void;
phonePlaceholder?: string;
formClassName?: string;
}) {
const { t, i18n } = useTranslation();
const isRuLocale = i18n.language.startsWith("ru");
const { referer } = useRefererStore();
const [submitError, setSubmitError] = useState<string | null>(null);
const projectOptions = useMemo(() => productOptionsFromT(t), [t]);
const form = useForm<LeadFormValues>({
defaultValues: {
fullname: "",
email: "",
phone: "",
products: defaultProducts,
},
});
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
control,
} = form;
const nameId = idPrefix ? `${idPrefix}name` : "name";
const emailId = idPrefix ? `${idPrefix}email` : "email";
const phoneId = idPrefix ? `${idPrefix}tel` : "tel";
const onSubmit: SubmitHandler<LeadFormValues> = async (data) => {
setSubmitError(null);
try {
const { id } = await mailApi
.post("mail", {
json: { ...data, referer, from: MAIL_REQUEST_FROM },
})
.json<{ id: string }>();
onSuccess(id);
} catch {
setSubmitError(t("leadForm.submitError"));
}
};
return (
<FormProvider {...form}>
<form
className={formClassName}
onSubmit={handleSubmit(onSubmit)}
noValidate
>
{submitError ? (
<p className="text-sm text-red-400" role="alert">
{submitError}
</p>
) : null}
<div className="lg:space-y-[1.111vw] space-y-4">
<p className="font-medium heading2">{t("leadForm.needTitle")}</p>
<CheckboxesGroup<LeadFormValues>
name="products"
options={projectOptions}
/>
</div>
<input
id={nameId}
autoComplete="none"
type="text"
required
placeholder={t("leadForm.namePlaceholder")}
{...register("fullname")}
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl btnl placeholder:font-medium placeholder:select-none"
/>
<input
autoComplete="none"
required
id={emailId}
type="email"
placeholder={t("leadForm.emailPlaceholder")}
{...register("email")}
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none btnl outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
/>
<div className="flex gap-x-3 py-2 border-[#3D425C] relative">
<Controller
name="phone"
control={control}
rules={{
validate: (value: string) => {
if (!value?.trim()) return t("leadForm.phoneRequired");
if (isRuLocale) {
return (
ruPhoneDigits(value).length === 11 ||
t("leadForm.phoneInvalid")
);
}
const digits = value.replace(/\D/g, "");
return (
(digits.length >= 8 &&
digits.length <= INTL_PHONE_MAX_DIGITS) ||
t("leadForm.phoneInvalid")
);
},
}}
render={({ field }) =>
isRuLocale ? (
<PhoneInputRu
id={phoneId}
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
inputRef={field.ref}
placeholder={
phonePlaceholder ?? t("leadForm.phonePlaceholder")
}
/>
) : (
<PhoneInputIntl
id={phoneId}
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
inputRef={field.ref}
placeholder={
phonePlaceholder ?? t("leadForm.phonePlaceholder")
}
/>
)
}
/>
<div className="bottom-0 absolute w-full border-b border-[#37393B] peer-focus:border-white -mb-2" />
</div>
{errors.phone?.message ? (
<p className="text-sm text-red-400 -mt-1" role="alert">
{String(errors.phone.message)}
</p>
) : null}
<div className="md:flex items-stretch lg:gap-[0.833vw] gap-3 max-md:translate-y-2">
<Button
type="submit"
disabled={isSubmitting}
className="btnl max-md:mb-3 max-md:w-full lg:px-[2.222vw] lg:py-[1.389vw] px-8 py-5 cursor-pointer lg:rounded-[1.111vw] rounded-2xl disabled:opacity-60"
>
{t("leadForm.submit")}
</Button>
<div className="text2 xl:max-w-[60%] md:max-lg:max-w-[40%] md:max-lg:py-1">
<span className="text-[#7A7A7A]">
{t("leadForm.consentBefore")}
</span>{" "}
<a
target="_blank"
rel="noopener noreferrer"
href={t("legalLinks.privacyConsent")}
className="underline"
>
{t("leadForm.consentLinkData")}
</a>{" "}
<span className="text-[#7A7A7A]">
{t("leadForm.consentMiddle")}{" "}
</span>
<a
target="_blank"
rel="noopener noreferrer"
href={t("legalLinks.policy")}
className="underline"
>
{t("leadForm.consentLinkPolicy")}
</a>
</div>
</div>
</form>
</FormProvider>
);
}