fix: update form

This commit is contained in:
2025-06-16 16:26:23 +03:00
parent d53c5606ff
commit 39d4c3c362
30 changed files with 653 additions and 125 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

View File

View File

@@ -0,0 +1,41 @@
'use client';
import {
useState,
useContext,
useCallback,
ReactNode,
createContext,
} from 'react';
import { Modal } from '@shared/ui/modal';
const ModalContext = createContext({
hideModal: () => {},
showModal: (content: ReactNode) => {},
});
const useModal = () => useContext(ModalContext);
const ModalProvider = ({ children }: { children: ReactNode }) => {
const [modalContent, setModalContent] = useState<ReactNode>(null);
const showModal = useCallback((content: ReactNode) => {
setModalContent(content);
}, []);
const hideModal = useCallback(() => {
setModalContent(null);
}, []);
return (
<ModalContext.Provider value={{ hideModal, showModal }}>
{children}
{/* Ваш Modal компонент здесь */}
<Modal isOpen={modalContent !== null} onClose={hideModal}>
{modalContent}
</Modal>
</ModalContext.Provider>
);
};
export { useModal, ModalProvider };

View File

@@ -1,4 +1,3 @@
//frontend breakpoint
$mobile: 360px;
$tablet: 768px;
@@ -24,5 +23,6 @@ $color-darkgray: #999999;
$color-text: #333333;
$color-text-light: #222222;
$color-mark: #E96526;
$color-error: #FF0000;
$color-error: #ff0000;
$color-error-light: #ff9191;
$color-gray-border: #555555;

View File

@@ -0,0 +1 @@
export { useClickOutside } from './useClickOutside';

View File

@@ -0,0 +1,28 @@
import { RefObject, useEffect } from 'react';
export function useClickOutside(
ref: RefObject<HTMLDivElement | null>,
fn: (event: Event) => void,
) {
useEffect(() => {
const handleClickOutside = (event: Event) => {
const el = ref?.current;
if (event instanceof MouseEvent && window !== undefined) {
const x = event?.offsetX || 0;
const width = window?.innerWidth - 18;
if (x >= width) {
return;
}
}
if (!el || el.contains((event?.target as Node) || null)) {
return;
}
fn(event);
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [ref, fn]);
}

View File

@@ -0,0 +1,15 @@
function detectIOS() {
const iosQuirkPresent = () => {
const audio = new Audio();
audio.volume = 0.5;
return audio.volume === 1; // volume cannot be changed from "1" on iOS 12 and below
};
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isAppleDevice = navigator.userAgent.includes('Macintosh');
const isTouchScreen = navigator.maxTouchPoints >= 1; // true for iOS 13 (and hopefully beyond)
return isIOS || (isAppleDevice && (isTouchScreen || iosQuirkPresent()));
}
export { detectIOS };

View File

@@ -0,0 +1 @@
export { detectIOS } from './detectIOS';

2
src/shared/lib/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { useClickOutside } from './clickOutside';
export { detectIOS } from './detectIOS';

View File

@@ -1,39 +0,0 @@
import s from './advancedPhoneInput.module.scss';
import { DetailedHTMLProps, forwardRef, InputHTMLAttributes, Ref } from 'react';
import { clsx } from 'clsx';
import { Button, Input } from '@shared/ui';
type AdvancedPhoneInputProps = {
containerClassName?: string;
inputClassName?: string;
buttonClassName?: string;
onClick?: () => void;
text: string;
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
const AdvancedPhoneInput = forwardRef(function AdvancedPhoneInput(
{
containerClassName,
inputClassName,
buttonClassName,
onClick,
text,
...props
}: AdvancedPhoneInputProps,
ref: Ref<HTMLInputElement>,
) {
return (
<div className={clsx(containerClassName, s.Container)}>
<Input {...props} ref={ref} className={clsx(inputClassName, s.Phone)} />
<Button
className={clsx(buttonClassName, s.Button)}
onClick={onClick}
variant='orange'
>
{text}
</Button>
</div>
);
});
export default AdvancedPhoneInput;

View File

@@ -1,6 +1,5 @@
export { Button } from './button';
export { Mark } from './mark';
export { Input } from './input';
export { AdvancedPhoneInput } from './advanced-phone-input';
export { TextArea } from './text-area';
export { PhoneInput } from './phone-input';

View File

@@ -1,3 +1,8 @@
.Container {
position: relative;
display: block;
}
.Input {
display: flex;
background: $color-white;
@@ -69,3 +74,15 @@
}
}
}
.Error {
position: absolute;
z-index: 2;
left: rem(8px);
bottom: rem(-16px);
font-family: $font-open-sans;
font-weight: $font-light;
font-size: rem(12px);
line-height: 100%;
color: $color-error;
}

View File

@@ -11,6 +11,7 @@ type InputProps = {
fullWidth?: boolean;
variant?: 'default' | 'ghost';
error?: string | boolean;
errorTextColor?: string;
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
const Input = forwardRef(function Input(
@@ -19,11 +20,13 @@ const Input = forwardRef(function Input(
fullWidth = false,
variant = 'default',
error = false,
errorTextColor,
...props
}: InputProps,
ref: Ref<HTMLInputElement>,
) {
return (
<div className={s.Container}>
<input
{...props}
ref={ref}
@@ -35,6 +38,12 @@ const Input = forwardRef(function Input(
className,
)}
/>
{error && (
<span className={s.Error} style={{ color: errorTextColor }}>
{error}
</span>
)}
</div>
);
});

View File

@@ -0,0 +1 @@
export { Modal, ModalContent } from './modal';

View File

@@ -0,0 +1,144 @@
.ModalBackdrop {
z-index: 1000;
position: absolute;
top: 0;
left: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.5);
padding: 0;
@include iftablet {
position: fixed;
height: 100vh;
padding: 0 rem(20px);
}
@include iflaptop {
padding: 0 rem(48px);
}
animation: fadeIn ease 0.3s;
&_open {
animation: fadeOut ease 0.3s;
}
&_isIOS {
height: 150vh !important;
@include iftablet {
height: unset;
}
}
}
.ModalContent {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: white;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
//max-height: 90%;
overflow: auto;
width: 100%;
//border-radius: 8px;
animation: fadeIn ease 0.3s;
@include iftablet {
position: relative;
top: unset;
bottom: unset;
left: unset;
right: unset;
height: 100%;
border-radius: rem(8px);
}
&_open {
animation: fadeOut ease 0.3s;
}
&_isIOS {
height: 100vh !important;
@include iftablet {
height: 100%;
}
}
&__Inner {
position: relative;
overflow: hidden;
height: fit-content;
}
}
.Modal {
position: relative;
&__Close {
position: absolute;
top: rem(5px);
right: rem(5px);
width: rem(36px);
height: rem(36px);
padding: rem(10px);
cursor: pointer;
margin-bottom: rem(4px);
transform: rotate(0deg);
transition: transform 0.3s;
@include iftablet {
top: rem(35px);
right: rem(35px);
margin-bottom: 0;
}
@include iflaptop {
top: rem(50px);
right: rem(50px);
}
&:hover,
&:active {
transform: rotate(45deg);
transition: transform 0.3s;
}
svg {
width: rem(17px);
height: rem(17px);
}
//svg {
// transform: rotate(0deg);
// transition: transform 0.3s;
// &:hover,
// &:active {
// transform: rotate(45deg);
// transition: transform 0.3s;
// }
//}
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View File

@@ -0,0 +1,109 @@
'use client';
import s from './modal.module.scss';
import { ReactNode, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import clsx from 'clsx';
import { useModal } from '@core/providers/modal-provider';
import { detectIOS, useClickOutside } from '@shared/lib';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}
const Modal = ({ children, isOpen, onClose }: ModalProps) => {
useEffect(() => {
const html = document.documentElement;
if (isOpen) {
html.style.overflow = 'hidden';
}
return () => {
html.style.overflow = '';
};
}, [isOpen]);
if (!isOpen) return null;
const isIOS = detectIOS();
return ReactDOM.createPortal(
<div
className={clsx(
s.ModalBackdrop,
isOpen && s.ModalBackdrop__open,
isIOS && s.ModalBackdrop_isIOS,
)}
>
{children}
</div>,
document.body,
);
};
function ModalContent(props: {
children: ReactNode;
className?: string;
innerClassName?: string;
closeByClickOutside?: boolean;
}) {
const {
children,
className,
innerClassName,
closeByClickOutside = false,
} = props;
const modal = useModal();
const hideModal = () => modal.hideModal();
const modalRef = useRef<HTMLDivElement | null>(null);
const handleClickOutside = () => {
if (closeByClickOutside) {
hideModal();
}
};
useClickOutside(modalRef, handleClickOutside);
const isIOS = detectIOS();
return (
<div
className={clsx(className, s.ModalContent, isIOS && s.ModalContent_isIOS)}
onClick={(e) => e.stopPropagation()}
ref={modalRef}
>
<div className={clsx(s.ModalContent__Inner, innerClassName)}>
<div className={s.Modal__Close} onClick={hideModal}>
<CloseIcon />
</div>
{children}
</div>
</div>
);
}
export { Modal, ModalContent };
const CloseIcon = () => (
<svg
width='17'
height='17'
viewBox='0 0 17 17'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M2.5 15L15.5 2M2.5 2L15.5 15'
stroke='#053635'
strokeOpacity='0.3'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
);

View File

@@ -1,13 +1,15 @@
//import s from './phone-input.module.scss';
import { Input } from '@shared/ui';
import { useMaskito } from '@maskito/react';
import { maskitoPhoneOptionsGenerator } from '@maskito/phone';
import metadata from 'libphonenumber-js/min/metadata';
import { DetailedHTMLProps, InputHTMLAttributes } from 'react';
type PhoneInput = {
className?: string;
variant?: 'default' | 'ghost';
error?: string | boolean;
errorTextColor?: string;
fullWidth?: boolean;
} & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
export default function PhoneInput({ ...props }: PhoneInput) {
@@ -17,9 +19,5 @@ export default function PhoneInput({ ...props }: PhoneInput) {
});
const maskedInputRef = useMaskito({ options });
return (
<>
<Input {...props} ref={maskedInputRef} type='tel' />
</>
);
return <Input {...props} ref={maskedInputRef} type='tel' />;
}

View File

@@ -1,4 +1,5 @@
.Container {
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
@@ -47,27 +48,33 @@
/* Arrow mouse cursor over the scrollbar */
cursor: auto;
}
&::-webkit-scrollbar {
width: rem(12px);
height: rem(12px);
}
&::-webkit-scrollbar-track,
&::-webkit-scrollbar-thumb {
background-clip: content-box;
border: rem(4px) solid transparent;
border-radius: rem(12px);
}
&::-webkit-scrollbar-track {
background-color: #333; // цвет дорожки
}
&::-webkit-scrollbar-thumb {
background-color: #999; // цвет указателя
}
@media (hover: hover) {
:where(&:not(:disabled))::-webkit-scrollbar-thumb:hover {
background-color: #999;
}
}
&:where(:autofill) {
/* Reliably removes native autofill colors */
background-clip: text;
@@ -113,4 +120,18 @@
transition: border-color ease .5s;
}
}
&_error {
border: 1px solid $color-error;
}
}
.Error {
margin-left: 8px;
margin-top: 4px;
font-family: $font-open-sans;
font-weight: $font-light;
font-size: rem(12px);
line-height: 100%;
color: $color-error;
}

View File

@@ -13,6 +13,8 @@ type TextAreaProps = {
children?: ReactNode;
variant?: 'default' | 'ghost';
fullWidth?: boolean;
error?: string | boolean;
errorTextColor?: string;
} & DetailedHTMLProps<
TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
@@ -24,6 +26,8 @@ const TextArea = forwardRef(function TextArea(
children,
variant = 'default',
fullWidth = false,
error = false,
errorTextColor,
...props
}: TextAreaProps,
ref: Ref<HTMLTextAreaElement>,
@@ -33,10 +37,20 @@ const TextArea = forwardRef(function TextArea(
<textarea
{...props}
ref={ref}
className={clsx(className, s.Area, s['Area_' + variant])}
className={clsx(
className,
s.Area,
s['Area_' + variant],
error && s.Area_error,
)}
>
{children}
</textarea>
{error && (
<span className={s.Error} style={{ color: errorTextColor }}>
{error}
</span>
)}
</div>
);
});

View File

@@ -0,0 +1,84 @@
'use client';
import s from './advanced-phone-input.module.scss';
import { clsx } from 'clsx';
import { Button, PhoneInput } from '@shared/ui';
import { z } from 'zod';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import toast from 'react-hot-toast';
import { sendFormFn } from '@shared/api/api.service';
import { isValidPhoneNumber } from 'libphonenumber-js/min';
type AdvancedPhoneInputProps = {
containerClassName?: string;
};
const FormSchema = z.object({
phone: z.string().refine(isValidPhoneNumber, 'Некорректный номер телефона'),
});
type TForm = z.infer<typeof FormSchema>;
const defaultValues = {
phone: '',
};
export default function AdvancedPhoneInput({
containerClassName,
}: AdvancedPhoneInputProps) {
const {
handleSubmit,
control,
reset,
clearErrors,
formState: { errors },
} = useForm<TForm>({
resolver: zodResolver(FormSchema),
defaultValues,
});
const onSubmit = async (data: TForm) => {
const payload = {
...data,
form: 'offer-request-form-desktop',
};
try {
await sendFormFn(payload);
toast.success('Заявка на консультацию принята');
reset(defaultValues);
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
}
};
return (
<form
className={clsx(containerClassName, s.Container)}
onSubmit={handleSubmit(onSubmit)}
>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<PhoneInput
{...field}
className={s.Phone}
placeholder={'+7 (999) 123-45-67'}
onChange={(e) => {
clearErrors('phone');
field.onChange(e);
}}
error={errors && errors.phone?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
<Button className={s.Button} variant='orange'>
Отправить заявку
</Button>
</form>
);
}

View File

@@ -1,7 +1,7 @@
'use client';
import s from './styles.module.scss';
import { Button, Input } from '@shared/ui';
import { Button, Input, PhoneInput } from '@shared/ui';
import Image from 'next/image';
import toast from 'react-hot-toast';
import { Controller, useForm } from 'react-hook-form';
@@ -10,10 +10,16 @@ import { z } from 'zod';
import bgForm from '@public/images/bg-form.jpg';
import { sendFormFn } from '@shared/api/api.service';
import { isValidPhoneNumber } from 'libphonenumber-js/min';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
name: z
.string()
.min(3, { message: 'Поле должно содержать не менее 3-х букв' })
.regex(/^[A-Za-zА-Яа-яЁё]+(?:[ '-][A-Za-zА-Яа-яЁё]+)*$/, {
message: 'Поле содержит некорректные символы',
}),
phone: z.string().refine(isValidPhoneNumber, 'Некорректный номер телефона'),
});
type TForm = z.infer<typeof FormSchema>;
@@ -27,6 +33,7 @@ export default function ContactsForm() {
handleSubmit,
control,
reset,
clearErrors,
formState: { errors },
} = useForm<TForm>({
mode: 'onSubmit',
@@ -75,18 +82,33 @@ export default function ContactsForm() {
control={control}
name={'name'}
render={({ field }) => (
<Input {...field} type='text' placeholder='Ваше имя' fullWidth />
<Input
{...field}
placeholder={'Ваше имя'}
fullWidth
onChange={(e) => {
clearErrors('name');
field.onChange(e);
}}
error={errors && errors.name?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<Input
<PhoneInput
{...field}
type='text'
placeholder='+7 (999) 123 45 67'
placeholder={'+7 (999) 123-45-67'}
fullWidth
onChange={(e) => {
clearErrors('phone');
field.onChange(e);
}}
error={errors && errors.phone?.message}
errorTextColor={'#ff9191'}
/>
)}
/>

View File

@@ -1,7 +1,7 @@
'use client';
import s from './styles.module.scss';
import { Button, Input, Mark, TextArea } from '@shared/ui';
import { Button, Input, Mark, PhoneInput, TextArea } from '@shared/ui';
import toast from 'react-hot-toast';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -9,11 +9,19 @@ import { z } from 'zod';
import man from '@public/images/footer-man.png';
import { sendFormFn } from '@shared/api/api.service';
import { isValidPhoneNumber } from 'libphonenumber-js/min';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
message: z.string(),
name: z
.string()
.min(3, { message: 'Поле должно содержать не менее 3-х букв' })
.regex(/^[A-Za-zА-Яа-яЁё]+(?:[ '-][A-Za-zА-Яа-яЁё]+)*$/, {
message: 'Поле содержит некорректные символы',
}),
phone: z.string().refine(isValidPhoneNumber, 'Некорректный номер телефона'),
message: z
.string()
.min(21, { message: 'Оставьте сообщение мин. 20 символов' }),
});
type TForm = z.infer<typeof FormSchema>;
@@ -28,6 +36,7 @@ export default function FooterForm() {
handleSubmit,
control,
reset,
clearErrors,
formState: { errors },
} = useForm<TForm>({
mode: 'onSubmit',
@@ -67,6 +76,12 @@ export default function FooterForm() {
variant='ghost'
placeholder={'Ваше имя'}
fullWidth
onChange={(e) => {
clearErrors('name');
field.onChange(e);
}}
error={errors && errors.name?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
@@ -74,11 +89,17 @@ export default function FooterForm() {
control={control}
name={'phone'}
render={({ field }) => (
<Input
<PhoneInput
{...field}
variant='ghost'
placeholder={'+7 999 1234567'}
placeholder={'+7 (999) 123-45-67'}
fullWidth
onChange={(e) => {
clearErrors('phone');
field.onChange(e);
}}
error={errors && errors.phone?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
@@ -94,6 +115,8 @@ export default function FooterForm() {
id='story'
name='story'
rows={6}
error={errors && errors.message?.message}
errorTextColor={'#ff9191'}
/>
)}
/>

View File

@@ -4,3 +4,4 @@ export { LicenseForm } from './license-form';
export { LicenseSlider } from './license-slider';
export { OfferForm } from './offer-form';
export { OfferRequestForm } from './offer-request';
export { AdvancedPhoneInput } from './advanced-phone-input';

View File

@@ -1,7 +1,7 @@
'use client';
import s from './styles.module.scss';
import { Button, Input } from '@shared/ui';
import { Button, Input, PhoneInput } from '@shared/ui';
import Image from 'next/image';
import toast from 'react-hot-toast';
@@ -10,11 +10,18 @@ import { z } from 'zod';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { sendFormFn } from '@shared/api/api.service';
import { isValidPhoneNumber } from 'libphonenumber-js/min';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
name: z
.string()
.min(3, { message: 'Поле должно содержать не менее 3-х букв' })
.regex(/^[A-Za-zА-Яа-яЁё]+(?:[ '-][A-Za-zА-Яа-яЁё]+)*$/, {
message: 'Поле содержит некорректные символы',
}),
phone: z.string().refine(isValidPhoneNumber, 'Некорректный номер телефона'),
});
type TForm = z.infer<typeof FormSchema>;
const defaultValues = {
@@ -27,6 +34,7 @@ export default function LicenseForm() {
handleSubmit,
control,
reset,
clearErrors,
formState: { errors },
} = useForm<TForm>({
mode: 'onSubmit',
@@ -77,14 +85,34 @@ export default function LicenseForm() {
control={control}
name={'name'}
render={({ field }) => (
<Input {...field} placeholder={'Ваше имя'} fullWidth />
<Input
{...field}
placeholder={'Ваше имя'}
fullWidth
onChange={(e) => {
clearErrors('name');
field.onChange(e);
}}
error={errors && errors.name?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<Input {...field} placeholder={'+7 (999) 123 45 67'} fullWidth />
<PhoneInput
{...field}
placeholder={'+7 (999) 123-45-67'}
fullWidth
onChange={(e) => {
clearErrors('phone');
field.onChange(e);
}}
error={errors && errors.phone?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
<Button variant='orange' fullWidth>

View File

@@ -30,10 +30,9 @@ export default function OfferForm() {
handleSubmit,
control,
reset,
clearErrors,
formState: { errors },
} = useForm<TForm>({
mode: 'onSubmit',
reValidateMode: 'onBlur',
resolver: zodResolver(FormSchema),
defaultValues,
});
@@ -66,6 +65,11 @@ export default function OfferForm() {
className={s.Unit}
type='text'
placeholder='Ваше имя'
error={errors && errors.name?.message}
onChange={(e) => {
clearErrors('name');
field.onChange(e);
}}
/>
)}
/>
@@ -78,6 +82,11 @@ export default function OfferForm() {
className={s.Unit}
type='text'
placeholder='+7 999 123-45-67'
error={errors && errors.phone?.message}
onChange={(e) => {
clearErrors('phone');
field.onChange(e);
}}
/>
)}
/>

View File

@@ -69,7 +69,7 @@
position: relative;
display: flex;
flex-direction: column;
gap: rem(12px);
gap: rem(20px);
z-index: 2;
max-width: rem(400px);
@include iftablet{

View File

@@ -1,7 +1,7 @@
'use client';
import s from './styles.module.scss';
import { AdvancedPhoneInput, Button, Input } from '@shared/ui';
import { Button, Input, PhoneInput } from '@shared/ui';
import Image from 'next/image';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -9,12 +9,18 @@ import { z } from 'zod';
import toast from 'react-hot-toast';
import bgForm from '@public/images/bg-form.jpg';
import { useState } from 'react';
import { AdvancedPhoneInput } from '@/widgets';
import { sendFormFn } from '@shared/api/api.service';
import { isValidPhoneNumber } from 'libphonenumber-js/min';
const FormSchema = z.object({
name: z.string().min(3),
phone: z.string(),
name: z
.string()
.min(3, { message: 'Поле должно содержать не менее 3-х букв' })
.regex(/^[A-Za-zА-Яа-яЁё]+(?:[ '-][A-Za-zА-Яа-яЁё]+)*$/, {
message: 'Поле содержит некорректные символы',
}),
phone: z.string().refine(isValidPhoneNumber, 'Некорректный номер телефона'),
});
type TForm = z.infer<typeof FormSchema>;
@@ -28,6 +34,7 @@ export default function OfferRequest() {
handleSubmit,
control,
reset,
clearErrors,
formState: { errors },
} = useForm<TForm>({
mode: 'onSubmit',
@@ -36,12 +43,10 @@ export default function OfferRequest() {
defaultValues,
});
const [inputPhone, setInputPhone] = useState('');
const onSubmitForm = async (data: TForm) => {
const onSubmit = async (data: TForm) => {
const payload = {
...data,
form: 'offer-request-form',
form: 'offer-request-form-mobile',
};
try {
@@ -55,23 +60,6 @@ export default function OfferRequest() {
}
};
const onSubmitPhone = async (phone: string) => {
const payload = {
phone: phone,
form: 'offer-request-form',
};
try {
await sendFormFn(payload);
toast.success('Заявка на консультацию принята');
setInputPhone('');
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
}
};
return (
<div className={s.Form}>
<Image
@@ -88,28 +76,40 @@ export default function OfferRequest() {
<h3 className={s.Title}>Оставьте заявку на бесплатную консультацию</h3>
</div>
<div className={s.PanelRight}>
<AdvancedPhoneInput
value={inputPhone}
onChange={(e) => setInputPhone(e.target.value)}
onClick={() => onSubmitPhone(inputPhone)}
containerClassName={s.AdvPhoneInput}
text='Отправить заявку'
placeholder={'+7 (999) 123 45 67'}
/>
<AdvancedPhoneInput containerClassName={s.AdvPhoneInput} />
<form className={s.MobileBtns} onSubmit={handleSubmit(onSubmitForm)}>
<form className={s.MobileBtns} onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name={'name'}
render={({ field }) => (
<Input {...field} placeholder='Ваше имя' fullWidth />
<Input
{...field}
placeholder='Ваше имя'
fullWidth
onChange={(e) => {
clearErrors('name');
field.onChange(e);
}}
error={errors && errors.name?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<Input {...field} placeholder='+7 999 123 45 67' fullWidth />
<PhoneInput
{...field}
placeholder='+7 (999) 123-45-67'
onChange={(e) => {
clearErrors('phone');
field.onChange(e);
}}
error={errors && errors.phone?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
<Button variant='orange' fullWidth>