feat: add forms logic
This commit is contained in:
@@ -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
48
package-lock.json
generated
@@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
5
public/svg/email-icon.svg
Normal file
5
public/svg/email-icon.svg
Normal 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 |
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default function HomePage() {
|
|||||||
<>
|
<>
|
||||||
<Main />
|
<Main />
|
||||||
<Offer />
|
<Offer />
|
||||||
<Result />
|
{/*<Result />*/}
|
||||||
<License />
|
<License />
|
||||||
<Contacts />
|
<Contacts />
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user