feat: add modal
This commit is contained in:
1
src/feature/article/consultation-modal/index.ts
Normal file
1
src/feature/article/consultation-modal/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './ui';
|
||||
46
src/feature/article/consultation-modal/styles.module.scss
Normal file
46
src/feature/article/consultation-modal/styles.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/feature/article/consultation-modal/ui.tsx
Normal file
145
src/feature/article/consultation-modal/ui.tsx
Normal 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 };
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './related-articles';
|
||||
export * from './consultation';
|
||||
export * from './sidebar';
|
||||
export * from './consultation-modal';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user