feat: update modal
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Open_Sans } from 'next/font/google';
|
|||||||
import '@core/styles/reset.scss';
|
import '@core/styles/reset.scss';
|
||||||
import '@core/styles/globals.scss';
|
import '@core/styles/globals.scss';
|
||||||
import { Toaster } from 'react-hot-toast';
|
import { Toaster } from 'react-hot-toast';
|
||||||
|
import { ModalProvider } from '@core/providers/modal-provider';
|
||||||
|
|
||||||
const openSans = Open_Sans({
|
const openSans = Open_Sans({
|
||||||
subsets: ['cyrillic'],
|
subsets: ['cyrillic'],
|
||||||
@@ -34,7 +35,7 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang='en'>
|
<html lang='en'>
|
||||||
<body className={`${openSans.variable}`}>
|
<body className={`${openSans.variable}`}>
|
||||||
{children}
|
<ModalProvider>{children}</ModalProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { default as ConsultationOrder } from './ui';
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import { Button } from '@shared/ui';
|
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
export default function ConsultationOrder() {
|
|
||||||
const notify = () => toast.success('Заявка на консультацию принята');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button variant='orange' onClick={notify}>
|
|
||||||
Получить консультацию
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
1
src/entities/home/callback-order/index.ts
Normal file
1
src/entities/home/callback-order/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { CallbackOrder } from './ui';
|
||||||
35
src/entities/home/callback-order/styles.module.scss
Normal file
35
src/entities/home/callback-order/styles.module.scss
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
.Container {
|
||||||
|
display: none;
|
||||||
|
@include iftablet {
|
||||||
|
display: block;
|
||||||
|
height: min-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Btn {
|
||||||
|
@include iflaptop {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: rem(16px);
|
||||||
|
height: rem(40px);
|
||||||
|
padding: rem(20px)!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include ifdesktop {
|
||||||
|
gap: rem(16px);
|
||||||
|
height: rem(48px);
|
||||||
|
padding: rem(24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: rem(30px);
|
||||||
|
height: rem(30px);
|
||||||
|
|
||||||
|
@include ifdesktop {
|
||||||
|
width: rem(40px);
|
||||||
|
height: rem(40px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/entities/home/callback-order/ui.tsx
Normal file
25
src/entities/home/callback-order/ui.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import s from './styles.module.scss';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { Button } from '@shared/ui';
|
||||||
|
import { useModal } from '@core/providers/modal-provider';
|
||||||
|
import { ConsultationModal } from '@/entities/home/consultation-modal';
|
||||||
|
|
||||||
|
import callBtn from '@public/svg/phone-calling.svg';
|
||||||
|
|
||||||
|
function CallbackOrder() {
|
||||||
|
const modal = useModal();
|
||||||
|
const openModal = () => modal.showModal(<ConsultationModal />);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={s.Container}>
|
||||||
|
<Button className={s.Btn} onClick={openModal}>
|
||||||
|
<Image src={callBtn} alt='Call' />
|
||||||
|
Обратный звонок
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CallbackOrder };
|
||||||
1
src/entities/home/consultation-modal/index.ts
Normal file
1
src/entities/home/consultation-modal/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { ConsultationModal } from './ui';
|
||||||
29
src/entities/home/consultation-modal/styles.module.scss
Normal file
29
src/entities/home/consultation-modal/styles.module.scss
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
.Form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: rem(16px);
|
||||||
|
|
||||||
|
@include iflaptop {
|
||||||
|
gap: rem(20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Title {
|
||||||
|
font-family: $font-open-sans;
|
||||||
|
font-weight: $font-medium;
|
||||||
|
font-size: rem(20px);
|
||||||
|
line-height: 130%;
|
||||||
|
color: $color-text;
|
||||||
|
|
||||||
|
@include ifdesktop {
|
||||||
|
font-size: rem(24px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Description {
|
||||||
|
font-family: $font-open-sans;
|
||||||
|
font-weight: $font-regular;
|
||||||
|
font-size: rem(16px);
|
||||||
|
line-height: 100%;
|
||||||
|
color: $color-text;
|
||||||
|
}
|
||||||
137
src/entities/home/consultation-modal/ui.tsx
Normal file
137
src/entities/home/consultation-modal/ui.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
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'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button className={s.SendBtn} variant='orange'>
|
||||||
|
Отправить
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ConsultationModal };
|
||||||
1
src/entities/home/consultation-order/index.ts
Normal file
1
src/entities/home/consultation-order/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { ConsultationOrder } from './ui';
|
||||||
18
src/entities/home/consultation-order/ui.tsx
Normal file
18
src/entities/home/consultation-order/ui.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Button } from '@shared/ui';
|
||||||
|
import { useModal } from '@core/providers/modal-provider';
|
||||||
|
import { ConsultationModal } from '@/entities/home/consultation-modal';
|
||||||
|
|
||||||
|
function ConsultationOrder() {
|
||||||
|
const modal = useModal();
|
||||||
|
const openModal = () => modal.showModal(<ConsultationModal />);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button variant='orange' onClick={openModal}>
|
||||||
|
Получить консультацию
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ConsultationOrder };
|
||||||
@@ -34,11 +34,11 @@ export default function Button({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
className,
|
||||||
s.Button,
|
s.Button,
|
||||||
disabled && s.Button_disabled,
|
disabled && s.Button_disabled,
|
||||||
s['Button_' + variant],
|
s['Button_' + variant],
|
||||||
fullWidth && s.Button_fullWidth,
|
fullWidth && s.Button_fullWidth,
|
||||||
className,
|
|
||||||
)}
|
)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export { Mark } from './mark';
|
|||||||
export { Input } from './input';
|
export { Input } from './input';
|
||||||
export { TextArea } from './text-area';
|
export { TextArea } from './text-area';
|
||||||
export { PhoneInput } from './phone-input';
|
export { PhoneInput } from './phone-input';
|
||||||
|
export { Modal, ModalContent } from './modal';
|
||||||
|
|||||||
23
src/shared/ui/modal/close-icon.tsx
Normal file
23
src/shared/ui/modal/close-icon.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { SVGProps } from 'react';
|
||||||
|
|
||||||
|
const CloseIcon = ({ ...props }: SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
{...props}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { CloseIcon };
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { Modal, ModalContent } from './modal';
|
export { Modal } from './modal';
|
||||||
|
export { ModalContent } from './modal-content';
|
||||||
|
|||||||
79
src/shared/ui/modal/modal-content.module.scss
Normal file
79
src/shared/ui/modal/modal-content.module.scss
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
.ModalContent {
|
||||||
|
position: absolute;
|
||||||
|
width: rem(360px);
|
||||||
|
padding: rem(40px) rem(20px) rem(20px);
|
||||||
|
border-radius: rem(20px);
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||||
|
overflow: auto;
|
||||||
|
animation: fadeIn ease 0.3s;
|
||||||
|
|
||||||
|
@include iftablet {
|
||||||
|
top: unset;
|
||||||
|
width: rem(540px);
|
||||||
|
padding: rem(40px) rem(24px) rem(24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include iflaptop {
|
||||||
|
padding: rem(40px) rem(30px) rem(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include ifdesktop {
|
||||||
|
width: rem(640px);
|
||||||
|
padding: rem(60px) rem(40px) rem(40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&_open {
|
||||||
|
animation: fadeOut ease 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_isIOS {
|
||||||
|
height: 100vh !important;
|
||||||
|
|
||||||
|
@include iftablet {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.Inner {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.CloseBtn {
|
||||||
|
position: absolute;
|
||||||
|
top: rem(20px);
|
||||||
|
right: rem(20px);
|
||||||
|
width: rem(24px);
|
||||||
|
height: rem(24px);
|
||||||
|
cursor: pointer;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/shared/ui/modal/modal-content.tsx
Normal file
49
src/shared/ui/modal/modal-content.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import s from './modal-content.module.scss';
|
||||||
|
import { MouseEvent, ReactNode, useRef } from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useModal } from '@core/providers/modal-provider';
|
||||||
|
import { detectIOS, useClickOutside } from '@shared/lib';
|
||||||
|
import { CloseIcon } from '@shared/ui/modal/close-icon';
|
||||||
|
|
||||||
|
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 = () => closeByClickOutside && hideModal();
|
||||||
|
|
||||||
|
useClickOutside(modalRef, handleClickOutside);
|
||||||
|
|
||||||
|
const isIOS = detectIOS();
|
||||||
|
|
||||||
|
const disableClick = (e: MouseEvent<HTMLDivElement>) => e.stopPropagation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(className, s.ModalContent, isIOS && s.ModalContent_isIOS)}
|
||||||
|
ref={modalRef}
|
||||||
|
onClick={disableClick}
|
||||||
|
>
|
||||||
|
<CloseIcon className={s.CloseBtn} onClick={hideModal} />
|
||||||
|
<div className={clsx(s.Inner, innerClassName)}>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ModalContent };
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -34,97 +35,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
@keyframes fadeIn {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import s from './modal.module.scss';
|
import s from './modal.module.scss';
|
||||||
import { ReactNode, useEffect, useRef } from 'react';
|
import { ReactNode, useEffect } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useModal } from '@core/providers/modal-provider';
|
import { detectIOS } from '@shared/lib';
|
||||||
import { detectIOS, useClickOutside } from '@shared/lib';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -28,7 +27,7 @@ const Modal = ({ children, isOpen, onClose }: ModalProps) => {
|
|||||||
|
|
||||||
const isIOS = detectIOS();
|
const isIOS = detectIOS();
|
||||||
|
|
||||||
return ReactDOM.createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
s.ModalBackdrop,
|
s.ModalBackdrop,
|
||||||
@@ -42,68 +41,4 @@ const Modal = ({ children, isOpen, onClose }: ModalProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function ModalContent(props: {
|
export { Modal };
|
||||||
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>
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
.Area {
|
.Area {
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
border: 1px solid $color-darkgray;
|
border: 1px solid $color-darkgray;
|
||||||
border-radius: rem(20px);
|
border-radius: rem(16px);
|
||||||
padding: rem(10px) rem(24px);
|
padding: rem(10px) rem(10px);
|
||||||
transition: border ease .5s;
|
transition: border ease .5s;
|
||||||
|
|
||||||
font-family: $font-open-sans;
|
font-family: $font-open-sans;
|
||||||
font-weight: $font-regular;
|
font-weight: $font-regular;
|
||||||
font-size: rem(18px);
|
font-size: rem(16px);
|
||||||
line-height: 100%;
|
line-height: 100%;
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
|
|
||||||
@@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
@include iftablet {
|
@include iftablet {
|
||||||
font-size: rem(18px);
|
font-size: rem(18px);
|
||||||
|
border-radius: rem(20px);
|
||||||
|
padding: rem(10px) rem(24px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include iflaptop {
|
@include iflaptop {
|
||||||
|
|||||||
@@ -118,33 +118,6 @@
|
|||||||
height: rem(60px);
|
height: rem(60px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
@include iflaptop {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: rem(16px);
|
|
||||||
height: rem(40px);
|
|
||||||
padding: rem(20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include ifdesktop {
|
|
||||||
gap: rem(16px);
|
|
||||||
height: rem(48px);
|
|
||||||
padding: rem(24px);
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: rem(30px);
|
|
||||||
height: rem(30px);
|
|
||||||
@include ifdesktop {
|
|
||||||
width: rem(40px);
|
|
||||||
height: rem(40px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import s from './main.module.scss';
|
import s from './main.module.scss';
|
||||||
|
|
||||||
import { Button } from '@shared/ui';
|
|
||||||
import { ConsultationOrder } from '@/entities/home/ConsultationOrder';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { Button } from '@shared/ui';
|
||||||
|
import { ConsultationOrder } from '@/entities/home/consultation-order';
|
||||||
|
|
||||||
import bgStart from '@public/images/bg-start-desktop.jpg';
|
import bgStart from '@public/images/bg-start-desktop.jpg';
|
||||||
import logo from '@public/images/dtr-logo.png';
|
import logo from '@public/images/dtr-logo.png';
|
||||||
import waIcon from '@public/svg/whatsapp.svg';
|
import waIcon from '@public/svg/whatsapp.svg';
|
||||||
import emailIcon from '@public/svg/email-icon.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';
|
||||||
|
import { CallbackOrder } from '@/entities/home/callback-order';
|
||||||
|
|
||||||
export default function Main() {
|
export default function Main() {
|
||||||
return (
|
return (
|
||||||
@@ -48,10 +49,7 @@ export default function Main() {
|
|||||||
>
|
>
|
||||||
<Image className={s.Icon} src={waIcon} alt='whatsapp' />
|
<Image className={s.Icon} src={waIcon} alt='whatsapp' />
|
||||||
</a>
|
</a>
|
||||||
<Button className={s.Button}>
|
<CallbackOrder />
|
||||||
<Image src={callBtn} alt='Call' />
|
|
||||||
Обратный звонок
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.Info}>
|
<div className={s.Info}>
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import toast from 'react-hot-toast';
|
|||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import man from '@public/images/footer-man.png';
|
|
||||||
import { sendFormFn } from '@shared/api/api.service';
|
import { sendFormFn } from '@shared/api/api.service';
|
||||||
import { isValidPhoneNumber } from 'libphonenumber-js/min';
|
import { isValidPhoneNumber } from 'libphonenumber-js/min';
|
||||||
|
|
||||||
@@ -39,8 +37,6 @@ export default function FooterForm() {
|
|||||||
clearErrors,
|
clearErrors,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<TForm>({
|
} = useForm<TForm>({
|
||||||
mode: 'onSubmit',
|
|
||||||
reValidateMode: 'onBlur',
|
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user