feat: add forms logic

This commit is contained in:
2025-06-09 14:11:36 +03:00
parent 9b6e163da5
commit a17afb39ba
18 changed files with 481 additions and 84 deletions

View File

@@ -11,6 +11,11 @@ const compat = new FlatCompat({
const eslintConfig = [ const eslintConfig = [
...compat.extends('next/core-web-vitals', 'next/typescript'), ...compat.extends('next/core-web-vitals', 'next/typescript'),
{
rules: {
"@typescript-eslint/no-unused-vars": "off",
},
},
]; ];
export default eslintConfig; export default eslintConfig;

48
package-lock.json generated
View File

@@ -8,11 +8,14 @@
"name": "fire-exam", "name": "fire-exam",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.1.1",
"next": "15.3.2", "next": "15.3.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.57.0",
"react-hot-toast": "^2.5.2", "react-hot-toast": "^2.5.2",
"swiper": "^11.2.8" "swiper": "^11.2.8",
"zod": "^3.25.56"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
@@ -203,6 +206,18 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@hookform/resolvers": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.1.1.tgz",
"integrity": "sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==",
"license": "MIT",
"dependencies": {
"@standard-schema/utils": "^0.3.0"
},
"peerDependencies": {
"react-hook-form": "^7.55.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -884,6 +899,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
"node_modules/@swc/counter": { "node_modules/@swc/counter": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -4768,6 +4789,22 @@
"react": "^19.1.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-hook-form": {
"version": "7.57.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.57.0.tgz",
"integrity": "sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-hot-toast": { "node_modules/react-hot-toast": {
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz",
@@ -5998,6 +6035,15 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zod": {
"version": "3.25.56",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.56.tgz",
"integrity": "sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }

View File

@@ -11,11 +11,14 @@
"prepare": "husky" "prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.1.1",
"next": "15.3.2", "next": "15.3.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.57.0",
"react-hot-toast": "^2.5.2", "react-hot-toast": "^2.5.2",
"swiper": "^11.2.8" "swiper": "^11.2.8",
"zod": "^3.25.56"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",

View File

@@ -0,0 +1,5 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="30" cy="30" r="30" fill="#D9D9D9"/>
<path d="M14.1666 23.3751L26.825 32.8687C29.0028 34.5021 31.9971 34.5021 34.175 32.8687L46.8333 23.375" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.125 23.736C12.125 22.253 12.929 20.8867 14.2253 20.1665L28.5169 12.2267C29.7503 11.5415 31.2497 11.5415 32.4831 12.2267L46.7747 20.1665C48.071 20.8867 48.875 22.253 48.875 23.736V39.7083C48.875 41.9636 47.0469 43.7917 44.7917 43.7917H16.2083C13.9532 43.7917 12.125 41.9636 12.125 39.7083V23.736Z" stroke="black" stroke-width="3" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 715 B

View File

@@ -1,5 +1,5 @@
import s from './advancedPhoneInput.module.scss'; import s from './advancedPhoneInput.module.scss';
import { DetailedHTMLProps, InputHTMLAttributes } from 'react'; import { DetailedHTMLProps, forwardRef, InputHTMLAttributes, Ref } from 'react';
import { clsx } from 'clsx'; import { clsx } from 'clsx';
import { Button, Input } from '@shared/ui'; import { Button, Input } from '@shared/ui';
@@ -11,17 +11,20 @@ type AdvancedPhoneInputProps = {
text: string; text: string;
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>; } & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
export default function AdvancedPhoneInput({ const AdvancedPhoneInput = forwardRef(function AdvancedPhoneInput(
{
containerClassName, containerClassName,
inputClassName, inputClassName,
buttonClassName, buttonClassName,
onClick, onClick,
text, text,
...props ...props
}: AdvancedPhoneInputProps) { }: AdvancedPhoneInputProps,
ref: Ref<HTMLInputElement>,
) {
return ( return (
<div className={clsx(containerClassName, s.Container)}> <div className={clsx(containerClassName, s.Container)}>
<Input {...props} className={clsx(inputClassName, s.Phone)} /> <Input {...props} ref={ref} className={clsx(inputClassName, s.Phone)} />
<Button <Button
className={clsx(buttonClassName, s.Button)} className={clsx(buttonClassName, s.Button)}
onClick={onClick} onClick={onClick}
@@ -31,4 +34,6 @@ export default function AdvancedPhoneInput({
</Button> </Button>
</div> </div>
); );
} });
export default AdvancedPhoneInput;

View File

@@ -2,7 +2,7 @@
import s from './input.module.scss'; import s from './input.module.scss';
import { DetailedHTMLProps, InputHTMLAttributes } from 'react'; import { DetailedHTMLProps, forwardRef, InputHTMLAttributes, Ref } from 'react';
import { clsx } from 'clsx'; import { clsx } from 'clsx';
type InputProps = { type InputProps = {
@@ -12,15 +12,14 @@ type InputProps = {
variant?: 'default' | 'ghost'; variant?: 'default' | 'ghost';
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>; } & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
const Input = ({ const Input = forwardRef(function Input(
className, { className, fullWidth = false, variant = 'default', ...props }: InputProps,
fullWidth = false, ref: Ref<HTMLInputElement>,
variant = 'default', ) {
...props
}: InputProps) => {
return ( return (
<input <input
{...props} {...props}
ref={ref}
className={clsx( className={clsx(
s.Input, s.Input,
s['Input_' + variant], s['Input_' + variant],
@@ -29,6 +28,6 @@ const Input = ({
)} )}
/> />
); );
}; });
export default Input; export default Input;

View File

@@ -1,5 +1,11 @@
import s from './text-area.module.scss'; import s from './text-area.module.scss';
import { DetailedHTMLProps, ReactNode, TextareaHTMLAttributes } from 'react'; import {
DetailedHTMLProps,
forwardRef,
ReactNode,
Ref,
TextareaHTMLAttributes,
} from 'react';
import { clsx } from 'clsx'; import { clsx } from 'clsx';
type TextAreaProps = { type TextAreaProps = {
@@ -12,21 +18,27 @@ type TextAreaProps = {
HTMLTextAreaElement HTMLTextAreaElement
>; >;
export default function TextArea({ const TextArea = forwardRef(function TextArea(
{
className, className,
children, children,
variant = 'default', variant = 'default',
fullWidth = false, fullWidth = false,
...props ...props
}: TextAreaProps) { }: TextAreaProps,
ref: Ref<HTMLTextAreaElement>,
) {
return ( return (
<div className={clsx(s.Container, fullWidth && s.Container_fullWidth)}> <div className={clsx(s.Container, fullWidth && s.Container_fullWidth)}>
<textarea <textarea
{...props} {...props}
ref={ref}
className={clsx(className, s.Area, s['Area_' + variant])} className={clsx(className, s.Area, s['Area_' + variant])}
> >
{children} {children}
</textarea> </textarea>
</div> </div>
); );
} });
export default TextArea;

View File

@@ -236,6 +236,11 @@
margin-left: rem(42px); margin-left: rem(42px);
} }
a:hover {
color: #163055;
text-decoration: underline;
}
.Icon { .Icon {
position: absolute; position: absolute;
top: 0; top: 0;

View File

@@ -53,11 +53,11 @@ export default function Contacts() {
</p> </p>
<p className={s.Address}> <p className={s.Address}>
<Image className={s.Icon} src={phoneCall} alt='' /> <Image className={s.Icon} src={phoneCall} alt='' />
+7 (988) 400 93 93 <a href='tel:+79884009393'>+7 (988) 400-93-93</a>
</p> </p>
<p className={s.Address}> <p className={s.Address}>
<Image className={s.Icon} src={email} alt='' /> <Image className={s.Icon} src={email} alt='' />
office@firecheck.ru <a href='mailto:spo-71@yandex.ru'>spo-71@yandex.ru</a>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -10,9 +10,23 @@ export default function Footer() {
<FooterForm /> <FooterForm />
<div className={s.Bottom}> <div className={s.Bottom}>
<Button variant='ghost'>Telegram</Button> <a
href='https://wa.me/?phone=79884009393&text=Консультация+по+пожарной+безопасности'
target='_blank'
rel='noopener noreferrer'
>
<Button variant='ghost'>WhatsApp</Button> <Button variant='ghost'>WhatsApp</Button>
<Button variant='ghost'>+7 (999) 123 45 67</Button> </a>
<a
href='mailto: spo-71@yandex.ru'
target='_blank'
rel='noopener noreferrer'
>
<Button variant='ghost'>spo-71@yandex.ru</Button>
</a>
<a href='tel:+79884009393' target='_blank' rel='noopener noreferrer'>
<Button variant='ghost'>+7 (988) 400-93-93</Button>
</a>
<p className={s.Policy}> <p className={s.Policy}>
<Link href='#'>Политика конфиденциальности</Link> <Link href='#'>Политика конфиденциальности</Link>
</p> </p>

View File

@@ -10,7 +10,7 @@ export default function HomePage() {
<> <>
<Main /> <Main />
<Offer /> <Offer />
<Result /> {/*<Result />*/}
<License /> <License />
<Contacts /> <Contacts />
<Footer /> <Footer />

View File

@@ -5,17 +5,17 @@
@include iftablet { @include iftablet {
margin: 0 auto; margin: 0 auto;
width: rem(712px); width: rem(712px);
padding: 0 0 rem(40px); padding: rem(40px) 0 rem(40px);
} }
@include iflaptop { @include iflaptop {
width: rem(930px); width: rem(930px);
padding: 0 0 rem(60px); padding: rem(60px) 0 rem(60px);
} }
@include ifdesktop { @include ifdesktop {
width: rem(1340px); width: rem(1340px);
padding: 0 0 rem(160px); padding: rem(160px) 0 rem(160px);
} }
.Header { .Header {

View File

@@ -6,7 +6,7 @@ import Image from 'next/image';
import bgStart from '@public/images/bg-start-desktop.jpg'; import bgStart from '@public/images/bg-start-desktop.jpg';
import waIcon from '@public/svg/whatsapp.svg'; import waIcon from '@public/svg/whatsapp.svg';
import tgIcon from '@public/svg/telegram.svg'; import emailIcon from '@public/svg/email-icon.svg';
import callBtn from '@public/svg/phone-calling.svg'; import callBtn from '@public/svg/phone-calling.svg';
export default function Main() { export default function Main() {
@@ -26,8 +26,20 @@ export default function Main() {
<div className={s.Header}> <div className={s.Header}>
<div className={s.Logo}>Пожарная экспертиза</div> <div className={s.Logo}>Пожарная экспертиза</div>
<div className={s.Buttons}> <div className={s.Buttons}>
<a
href='mailto: spo-71@yandex.ru'
target='_blank'
rel='noopener noreferrer'
>
<Image className={s.Icon} src={emailIcon} alt='email' />
</a>
<a
href='https://wa.me/?phone=79884009393&text=Консультация+по+пожарной+безопасности'
target='_blank'
rel='noopener noreferrer'
>
<Image className={s.Icon} src={waIcon} alt='whatsapp' /> <Image className={s.Icon} src={waIcon} alt='whatsapp' />
<Image className={s.Icon} src={tgIcon} alt='telegram' /> </a>
<Button className={s.Button}> <Button className={s.Button}>
<Image src={callBtn} alt='Call' /> <Image src={callBtn} alt='Call' />
Обратный звонок Обратный звонок
@@ -51,7 +63,7 @@ export default function Main() {
</ul> </ul>
</div> </div>
<div className={s.Phone}> <div className={s.Phone}>
<p className={s.Title}>+7 988 400 93 93</p> <p className={s.Title}>+7 (988) 400-93-93</p>
<ConsultationOrder /> <ConsultationOrder />
</div> </div>
</div> </div>

View File

@@ -4,11 +4,48 @@ import s from './styles.module.scss';
import { Button, Input } from '@shared/ui'; import { Button, Input } from '@shared/ui';
import Image from 'next/image'; import Image from 'next/image';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import bgForm from '@public/images/bg-form.jpg'; import bgForm from '@public/images/bg-form.jpg';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
});
type TForm = z.infer<typeof FormSchema>;
const defaultValues = {
name: '',
phone: '',
};
export default function ContactsForm() { export default function ContactsForm() {
const notify = () => toast.success('Заявка на консультацию принята'); const {
handleSubmit,
control,
reset,
formState: { errors },
clearErrors,
} = useForm<TForm>({
mode: 'onSubmit',
reValidateMode: 'onBlur',
resolver: zodResolver(FormSchema),
defaultValues,
});
const onSubmit = async (data: TForm) => {
try {
console.log('Form', data);
toast.success('Заявка на консультацию принята');
reset(defaultValues);
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
}
};
return ( return (
<div className={s.Form}> <div className={s.Form}>
@@ -28,10 +65,27 @@ export default function ContactsForm() {
точной стоимости работ точной стоимости работ
</p> </p>
</div> </div>
<form className={s.Inputs}> <form className={s.Inputs} onSubmit={handleSubmit(onSubmit)}>
<Input placeholder='Ваше имя' fullWidth /> <Controller
<Input type='text' placeholder='+7 (999) 123 45 67' fullWidth /> control={control}
<Button variant='orange' fullWidth onClick={notify}> name={'name'}
render={({ field }) => (
<Input {...field} type='text' placeholder='Ваше имя' fullWidth />
)}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<Input
{...field}
type='text'
placeholder='+7 (999) 123 45 67'
fullWidth
/>
)}
/>
<Button variant='orange' fullWidth>
Получить консультацию Получить консультацию
</Button> </Button>
</form> </form>

View File

@@ -3,18 +3,84 @@
import s from './styles.module.scss'; import s from './styles.module.scss';
import { Button, Input, Mark, TextArea } from '@shared/ui'; import { Button, Input, Mark, TextArea } from '@shared/ui';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
message: z.string(),
});
type TForm = z.infer<typeof FormSchema>;
const defaultValues = {
name: '',
phone: '',
message: '',
};
export default function FooterForm() { export default function FooterForm() {
const notify = () => toast.success('Заявка на консультацию принята'); const {
handleSubmit,
control,
reset,
formState: { errors },
clearErrors,
} = useForm<TForm>({
mode: 'onSubmit',
reValidateMode: 'onBlur',
resolver: zodResolver(FormSchema),
defaultValues,
});
const onSubmit = async (data: TForm) => {
try {
console.log('Form', data);
toast.success('Заявка на консультацию принята');
reset(defaultValues);
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
}
};
return ( return (
<form className={s.Form}> <form className={s.Form} onSubmit={handleSubmit(onSubmit)}>
<h2 className={s.Header}> <h2 className={s.Header}>
Давайте <Mark>обсудим</Mark> ваши задачи Давайте <Mark>обсудим</Mark> ваши задачи
</h2> </h2>
<Input variant='ghost' placeholder={'Ваше имя'} fullWidth /> <Controller
<Input variant='ghost' placeholder={'+7 999 1234567'} fullWidth /> control={control}
name={'name'}
render={({ field }) => (
<Input
{...field}
variant='ghost'
placeholder={'Ваше имя'}
fullWidth
/>
)}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<Input
{...field}
variant='ghost'
placeholder={'+7 999 1234567'}
fullWidth
/>
)}
/>
<Controller
control={control}
name={'message'}
render={({ field }) => (
<TextArea <TextArea
{...field}
variant='ghost' variant='ghost'
placeholder={'Кратко опишите вашу задачу'} placeholder={'Кратко опишите вашу задачу'}
fullWidth fullWidth
@@ -22,7 +88,9 @@ export default function FooterForm() {
name='story' name='story'
rows={6} rows={6}
/> />
<Button className={s.SendBtn} variant='orange' fullWidth onClick={notify}> )}
/>
<Button className={s.SendBtn} variant='orange' fullWidth>
Отправить Отправить
</Button> </Button>
</form> </form>

View File

@@ -6,9 +6,46 @@ import Image from 'next/image';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import bgForm from '@public/images/bg-form.jpg'; import bgForm from '@public/images/bg-form.jpg';
import { z } from 'zod';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
});
type TForm = z.infer<typeof FormSchema>;
const defaultValues = {
name: '',
phone: '',
};
export default function LicenseForm() { export default function LicenseForm() {
const notify = () => toast.success('Заявка на консультацию принята'); const {
handleSubmit,
control,
reset,
formState: { errors },
clearErrors,
} = useForm<TForm>({
mode: 'onSubmit',
reValidateMode: 'onBlur',
resolver: zodResolver(FormSchema),
defaultValues,
});
const onSubmit = async (data: TForm) => {
try {
console.log('Form', data);
toast.success('Заявка на консультацию принята');
reset(defaultValues);
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
}
};
return ( return (
<div className={s.Form}> <div className={s.Form}>
@@ -30,10 +67,22 @@ export default function LicenseForm() {
Оставьте свои контактные данные и мы закрепим скидку до 1 июля за вами Оставьте свои контактные данные и мы закрепим скидку до 1 июля за вами
</p> </p>
</div> </div>
<form className={s.Inputs}> <form className={s.Inputs} onSubmit={handleSubmit(onSubmit)}>
<Input placeholder={'Ваше имя'} fullWidth /> <Controller
<Input placeholder={'+7 (999) 123 45 67'} fullWidth /> control={control}
<Button variant='orange' fullWidth onClick={notify}> name={'name'}
render={({ field }) => (
<Input {...field} placeholder={'Ваше имя'} fullWidth />
)}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<Input {...field} placeholder={'+7 (999) 123 45 67'} fullWidth />
)}
/>
<Button variant='orange' fullWidth>
Получить консультацию Получить консультацию
</Button> </Button>
</form> </form>

View File

@@ -2,24 +2,75 @@
import s from './styles.module.scss'; import s from './styles.module.scss';
import { Button, Input } from '@shared/ui'; import { Button, Input } from '@shared/ui';
import { useState } from 'react'; import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
});
type TForm = z.infer<typeof FormSchema>;
const defaultValues = {
name: '',
phone: '',
};
export default function OfferForm() { export default function OfferForm() {
const [name, setName] = useState(''); const {
const notify = () => toast.success('Заявка на консультацию принята'); handleSubmit,
control,
reset,
formState: { errors },
clearErrors,
} = useForm<TForm>({
mode: 'onSubmit',
reValidateMode: 'onBlur',
resolver: zodResolver(FormSchema),
defaultValues,
});
const onSubmit = async (data: TForm) => {
try {
console.log('Form', data);
toast.success('Заявка на консультацию принята');
reset(defaultValues);
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
}
};
return ( return (
<form className={s.RowForm}> <form className={s.RowForm} onSubmit={handleSubmit(onSubmit)}>
<Input className={s.Unit} type='text' placeholder='+7 (999) 123 45 67' /> <Controller
control={control}
name={'name'}
render={({ field }) => (
<Input <Input
{...field}
className={s.Unit} className={s.Unit}
type='text' type='text'
placeholder='Ваше имя' placeholder='Ваше имя'
value={name}
onChange={(e) => setName(e.target.value)}
/> />
<Button className={s.Unit} variant='orange' onClick={notify}> )}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<Input
{...field}
className={s.Unit}
type='text'
placeholder='+7 (999) 123 45 67'
/>
)}
/>
<Button className={s.Unit} variant='orange'>
Получить консультацию Получить консультацию
</Button> </Button>
</form> </form>

View File

@@ -3,12 +3,67 @@
import s from './styles.module.scss'; import s from './styles.module.scss';
import { AdvancedPhoneInput, Button, Input } from '@shared/ui'; import { AdvancedPhoneInput, Button, Input } from '@shared/ui';
import Image from 'next/image'; import Image from 'next/image';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import bgForm from '@public/images/bg-form.jpg'; import bgForm from '@public/images/bg-form.jpg';
import { useState } from 'react';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
});
type TForm = z.infer<typeof FormSchema>;
const defaultValues = {
name: '',
phone: '',
};
export default function OfferRequest() { export default function OfferRequest() {
const notify = () => toast.success('Заявка на консультацию принята'); const {
handleSubmit,
control,
reset,
formState: { errors },
clearErrors,
} = useForm<TForm>({
mode: 'onSubmit',
reValidateMode: 'onBlur',
resolver: zodResolver(FormSchema),
defaultValues,
});
const [inputPhone, setInputPhone] = useState('');
const onSubmitForm = async (data: TForm) => {
try {
console.log('Form', data);
toast.success('Заявка на консультацию принята');
reset(defaultValues);
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
}
};
const onSubmitPhone = async (phone: string) => {
const data = {
phone,
};
try {
console.log('Form', data);
toast.success('Заявка на консультацию принята');
setInputPhone('');
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
}
};
return ( return (
<div className={s.Form}> <div className={s.Form}>
@@ -27,19 +82,33 @@ export default function OfferRequest() {
</div> </div>
<div className={s.PanelRight}> <div className={s.PanelRight}>
<AdvancedPhoneInput <AdvancedPhoneInput
value={inputPhone}
onChange={(e) => setInputPhone(e.target.value)}
onClick={() => onSubmitPhone(inputPhone)}
containerClassName={s.AdvPhoneInput} containerClassName={s.AdvPhoneInput}
text='Отправить заявку' text='Отправить заявку'
placeholder={'+7 (999) 123 45 67'} placeholder={'+7 (999) 123 45 67'}
onClick={notify}
/> />
<div className={s.MobileBtns}> <form className={s.MobileBtns} onSubmit={handleSubmit(onSubmitForm)}>
<Input placeholder='Ваше имя' fullWidth /> <Controller
<Input placeholder='+7 999 123 45 67' fullWidth /> control={control}
<Button variant='orange' fullWidth onClick={notify}> name={'name'}
render={({ field }) => (
<Input {...field} placeholder='Ваше имя' fullWidth />
)}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<Input {...field} placeholder='+7 999 123 45 67' fullWidth />
)}
/>
<Button variant='orange' fullWidth>
Отправить заявку Отправить заявку
</Button> </Button>
</div> </form>
</div> </div>
</div> </div>
); );