feat: add modal

This commit is contained in:
2025-07-08 09:52:34 +03:00
parent 65499499b4
commit ea8a152711
24 changed files with 763 additions and 5 deletions

View File

@@ -0,0 +1 @@
export * from './ui';

View File

@@ -0,0 +1,46 @@
.Form {
display: flex;
flex-direction: column;
gap: rem(16px);
@include iflaptop {
gap: rem(20px);
}
}
.Title {
font-family: $font-roboto;
font-weight: $font-medium;
font-size: rem(20px);
line-height: 130%;
color: $color-text;
@include ifdesktop {
font-size: rem(24px);
}
}
.Description {
font-family: $font-roboto;
font-weight: $font-regular;
font-size: rem(16px);
line-height: 100%;
color: $color-text;
}
.Agreement {
font-family: $font-roboto;
font-weight: $font-regular;
font-size: rem(14px);
line-height: 100%;
color: $color-text;
a {
color: $color-green;
&:hover {
color: $color-green;
text-decoration: underline;
}
}
}

View File

@@ -0,0 +1,145 @@
'use client';
import s from './styles.module.scss';
import { Button, Input } from '@/shared/ui';
import { PhoneInput, TextArea } from '@shared/ui';
import { z } from 'zod';
import { isValidPhoneNumber } from 'libphonenumber-js/min';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { sendFormFn } from '@shared/api/api.service';
import toast from 'react-hot-toast';
import { ModalContent } from '@shared/ui/modal/modal-content';
import { useModal } from '@core/providers/modal-provider';
import Link from 'next/link';
const FormSchema = z.object({
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>;
const defaultValues = {
name: '',
phone: '',
message: '',
};
type ConsultationModalProps = {
className?: string;
};
function ConsultationModal({}: ConsultationModalProps) {
const {
handleSubmit,
control,
reset,
clearErrors,
formState: { errors },
} = useForm<TForm>({
resolver: zodResolver(FormSchema),
defaultValues,
});
const modal = useModal();
const onSubmit = async (data: TForm) => {
const payload = {
...data,
form: 'consultation-modal-form',
};
try {
await sendFormFn(payload);
toast.success('Заявка на консультацию принята');
} catch (e) {
toast.error('Ошибка при отправке заявки...', {
duration: 3000,
});
} finally {
modal.hideModal();
reset(defaultValues);
}
};
return (
<ModalContent closeByClickOutside>
<form
className={s.Form}
id='consultation-modal-form'
onSubmit={handleSubmit(onSubmit)}
>
<h2 className={s.Title}>
Мы подскажем, как решить ваши вопросы по пожарной безопасности.
</h2>
<Controller
control={control}
name={'name'}
render={({ field }) => (
<Input
{...field}
placeholder={'Ваше имя'}
fullWidth
onChange={(e) => {
clearErrors('name');
field.onChange(e);
}}
error={errors && errors.name?.message}
/>
)}
/>
<Controller
control={control}
name={'phone'}
render={({ field }) => (
<PhoneInput
{...field}
placeholder={'+7 (999) 123-45-67'}
fullWidth
onChange={(e) => {
clearErrors('phone');
field.onChange(e);
}}
error={errors && errors.phone?.message}
/>
)}
/>
<p className={s.Description}>Кратко опишите интересующий Вас вопрос</p>
<Controller
control={control}
name={'message'}
render={({ field }) => (
<TextArea
{...field}
placeholder={''}
fullWidth
id='story'
name='story'
rows={6}
error={errors && errors.message?.message}
errorTextColor={'#ff9191'}
/>
)}
/>
<span className={s.Agreement}>
Нажимая на кнопку, вы даете согласие на обработку своих персональных
данных и соглашаетесь с
<Link href={'/privacy-policy'}> Политикой конфиденциальности</Link>
</span>
<Button className={s.SendBtn} variant='green'>
Отправить
</Button>
</form>
</ModalContent>
);
}
export { ConsultationModal };

View File

@@ -1,3 +1,4 @@
export * from './related-articles';
export * from './consultation';
export * from './sidebar';
export * from './consultation-modal';

View File

@@ -2,7 +2,7 @@
.Sidebar {
display: none;
@include iflaptop{
@include iflaptop {
display: flex;
flex-direction: column;
gap: rem(40px);
@@ -54,7 +54,7 @@
color: $color-white;
text-transform: uppercase;
@include ifdesktop{
@include ifdesktop {
font-size: rem(32px);
}
}
@@ -90,6 +90,14 @@
line-height: 130%;
color: $color-text;
list-style: unset;
a {
color: $color-green;
}
a:hover {
color: $color-green-hover;
text-decoration: underline;
}
}
}
}

View File

@@ -1,7 +1,12 @@
'use client';
import s from './styles.module.scss';
import Link from 'next/link';
import { Button } from '@shared/ui';
import { TSidebar } from '@shared/types/sidebar';
import { useModal } from '@core/providers/modal-provider';
import { ConsultationModal } from '@/feature/article';
import { CONTACTS } from '@shared/const/contacts';
type SidebarProps = TSidebar;
@@ -12,6 +17,11 @@ function Sidebar({
warrantiesTitle,
warranties,
}: SidebarProps) {
const modal = useModal();
const openModal = () => modal.showModal(<ConsultationModal />);
const callTo = `tel:${CONTACTS.PHONE}`;
return (
<aside className={s.Sidebar}>
<div className={s.Estimation}>
@@ -22,8 +32,12 @@ function Sidebar({
определения точной стоимости.
</p>
<p className={s.Text}>Оставьте заявку или позвоните по телефону</p>
<Button variant={'white'}>Записаться</Button>
<p className={s.Phone}>+7 (900) 241-34-34</p>
<Button variant={'white'} onClick={openModal}>
Записаться
</Button>
<a href={callTo}>
<p className={s.Phone}>+7 (900) 241-34-34</p>
</a>
</div>
{related && (
<div className={s.Related}>