fix: add preview modal

This commit is contained in:
2025-12-04 12:40:53 +03:00
parent bd9937e86c
commit 1a49b3f8e2
20 changed files with 193 additions and 43 deletions

31
.idea/workspace.xml generated
View File

@@ -5,20 +5,26 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="2a96f817-9dc2-4f3c-893a-c4974c750774" name="Changes" comment=""> <list default="true" id="2a96f817-9dc2-4f3c-893a-c4974c750774" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/src/app/cookie/page.tsx" afterDir="false" /> <change afterPath="$PROJECT_DIR$/src/widgets/preview-modal/index.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/views/cookie/index.ts" afterDir="false" /> <change afterPath="$PROJECT_DIR$/src/widgets/preview-modal/styles.module.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/views/cookie/styles.module.scss" afterDir="false" /> <change afterPath="$PROJECT_DIR$/src/widgets/preview-modal/ui.tsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/views/cookie/ui.tsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/widgets/cookie-notice/index.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/widgets/cookie-notice/styles.module.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/widgets/cookie-notice/ui.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/app/cookie/page.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/cookies/page.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/app/layout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/layout.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/app/layout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/layout.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/entities/beauty-button/ui.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/entities/beauty-button/ui.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/entities/beauty-button/ui.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/entities/beauty-button/ui.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/views/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/views/index.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/feature/article/consultation-modal/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/widgets/consultation-modal/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/feature/article/consultation-modal/styles.module.scss" beforeDir="false" afterPath="$PROJECT_DIR$/src/widgets/consultation-modal/styles.module.scss" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/feature/article/consultation-modal/ui.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/widgets/consultation-modal/ui.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/feature/article/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/feature/article/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/feature/article/sidebar/ui.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/feature/article/sidebar/ui.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/views/home/about/styles.module.scss" beforeDir="false" afterPath="$PROJECT_DIR$/src/views/home/about/styles.module.scss" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/views/home/about/ui.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/views/home/about/ui.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/views/home/call-us/styles.module.scss" beforeDir="false" afterPath="$PROJECT_DIR$/src/views/home/call-us/styles.module.scss" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/views/home/call-us/ui.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/views/home/call-us/ui.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/widgets/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/widgets/index.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/widgets/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/widgets/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/widgets/mobile-callback/ui.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/widgets/mobile-callback/ui.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/widgets/mobile-callback/ui.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/widgets/mobile-callback/ui.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/widgets/scroll-to-top/styles.module.scss" beforeDir="false" afterPath="$PROJECT_DIR$/src/widgets/scroll-to-top/styles.module.scss" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -26,7 +32,7 @@
<option name="LAST_RESOLUTION" value="IGNORE" /> <option name="LAST_RESOLUTION" value="IGNORE" />
</component> </component>
<component name="DarkyenusTimeTracker"> <component name="DarkyenusTimeTracker">
<option name="totalTimeSeconds" value="199383" /> <option name="totalTimeSeconds" value="203480" />
<option name="gitIntegration" value="true" /> <option name="gitIntegration" value="true" />
<option name="naggedAbout" value="1" /> <option name="naggedAbout" value="1" />
</component> </component>
@@ -43,7 +49,7 @@
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="ProblemsViewState"> <component name="ProblemsViewState">
<option name="selectedTabId" value="CurrentFile" /> <option name="selectedTabId" value="DEPENDENCY_CHECKER_PROBLEMS_TAB" />
</component> </component>
<component name="ProjectColorInfo">{ <component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;, &quot;customColor&quot;: &quot;&quot;,
@@ -82,6 +88,7 @@
}</component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="C:\dev-personal\ocenka-web\src\widgets" />
<recent name="C:\dev-personal\ocenka-web\src\widgets\sidebar" /> <recent name="C:\dev-personal\ocenka-web\src\widgets\sidebar" />
<recent name="C:\dev-personal\ocenka-web\src\views\home" /> <recent name="C:\dev-personal\ocenka-web\src\views\home" />
</key> </key>
@@ -121,7 +128,7 @@
<workItem from="1764591867512" duration="4332000" /> <workItem from="1764591867512" duration="4332000" />
<workItem from="1764657017067" duration="21490000" /> <workItem from="1764657017067" duration="21490000" />
<workItem from="1764741053553" duration="18011000" /> <workItem from="1764741053553" duration="18011000" />
<workItem from="1764825390464" duration="8920000" /> <workItem from="1764825390464" duration="14873000" />
</task> </task>
<servers /> <servers />
</component> </component>

8
package-lock.json generated
View File

@@ -14,7 +14,7 @@
"@maskito/react": "^3.9.1", "@maskito/react": "^3.9.1",
"libphonenumber-js": "^1.12.9", "libphonenumber-js": "^1.12.9",
"next": "15.5.2", "next": "15.5.2",
"nodemailer": "7.0.7", "nodemailer": "7.0.11",
"react": "19.1.1", "react": "19.1.1",
"react-dom": "19.1.1", "react-dom": "19.1.1",
"react-hook-form": "^7.60.0", "react-hook-form": "^7.60.0",
@@ -4535,9 +4535,9 @@
} }
}, },
"node_modules/nodemailer": { "node_modules/nodemailer": {
"version": "7.0.7", "version": "7.0.11",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.7.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz",
"integrity": "sha512-jGOaRznodf62TVzdyhKt/f1Q/c3kYynk8629sgJHpRzGZj01ezbgMMWJSAjHADcwTKxco3B68/R+KHJY2T5BaA==", "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==",
"license": "MIT-0", "license": "MIT-0",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"

View File

@@ -17,7 +17,7 @@
"@maskito/react": "^3.9.1", "@maskito/react": "^3.9.1",
"libphonenumber-js": "^1.12.9", "libphonenumber-js": "^1.12.9",
"next": "15.5.2", "next": "15.5.2",
"nodemailer": "7.0.7", "nodemailer": "7.0.11",
"react": "19.1.1", "react": "19.1.1",
"react-dom": "19.1.1", "react-dom": "19.1.1",
"react-hook-form": "^7.60.0", "react-hook-form": "^7.60.0",

View File

@@ -11,6 +11,7 @@ import {
ScrollToTop, ScrollToTop,
} from '@/widgets'; } from '@/widgets';
import { ModalProvider } from '@core/providers/modal-provider'; import { ModalProvider } from '@core/providers/modal-provider';
import { Toaster } from 'react-hot-toast';
const roboto = Roboto({ const roboto = Roboto({
subsets: ['cyrillic'], subsets: ['cyrillic'],
@@ -43,6 +44,7 @@ export default function RootLayout({
<MobileCallback /> <MobileCallback />
</ModalProvider> </ModalProvider>
<ScrollToTop /> <ScrollToTop />
<Toaster />
<CookieNotice /> <CookieNotice />
</body> </body>
</html> </html>

View File

@@ -2,9 +2,9 @@
import s from './styles.module.scss'; import s from './styles.module.scss';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useModal } from '@core/providers/modal-provider';
import { ConsultationModal } from '@/feature/article';
import { Icons } from '@/shared/ui/icon'; import { Icons } from '@/shared/ui/icon';
import { useModal } from '@core/providers/modal-provider';
import { ConsultationModal } from '@/widgets';
type TBeautyButtonProps = { type TBeautyButtonProps = {
children?: ReactNode; children?: ReactNode;

View File

@@ -1,6 +1,5 @@
export * from './related-articles'; export * from './related-articles';
export * from './consultation'; export * from './consultation';
export * from './sidebar'; export * from './sidebar';
export * from './consultation-modal';
export * from './partners'; export * from './partners';
export * from './documents'; export * from './documents';

View File

@@ -5,8 +5,8 @@ import Link from 'next/link';
import { Button } from '@shared/ui'; import { Button } from '@shared/ui';
import { TSidebar } from '@shared/types/sidebar'; import { TSidebar } from '@shared/types/sidebar';
import { useModal } from '@core/providers/modal-provider'; import { useModal } from '@core/providers/modal-provider';
import { ConsultationModal } from '@/feature/article';
import { CONTACTS } from '@shared/const/contacts'; import { CONTACTS } from '@shared/const/contacts';
import { ConsultationModal } from '@/widgets';
type SidebarProps = { pageName?: string } & TSidebar; type SidebarProps = { pageName?: string } & TSidebar;

View File

@@ -1,8 +1,8 @@
.About{ .About {
background: #f7f7f7; background: #f7f7f7;
} }
.Inner{ .Inner {
margin: 0 auto; margin: 0 auto;
max-width: rem(1540px); max-width: rem(1540px);
padding: rem(80px) rem(10px); padding: rem(80px) rem(10px);
@@ -20,13 +20,13 @@
margin-bottom: rem(80px); margin-bottom: rem(80px);
} }
.Block{ .Block {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: rem(40px); gap: rem(40px);
@include iflaptop{ @include iflaptop {
flex-direction: row; flex-direction: row;
} }
} }
@@ -49,17 +49,17 @@
box-shadow: rem(15px) 0 rem(40px) 0 rgba(0, 0, 0, 0.5); box-shadow: rem(15px) 0 rem(40px) 0 rgba(0, 0, 0, 0.5);
transition: background 0.3s, border 0.3s, border-radius 0.3s, box-shadow 0.3s; transition: background 0.3s, border 0.3s, border-radius 0.3s, box-shadow 0.3s;
@include iftablet{ @include iftablet {
gap: rem(80px) rem(20px); gap: rem(80px) rem(20px);
margin: 0 rem(40px); margin: 0 rem(40px);
padding: rem(40px); padding: rem(40px);
max-width: rem(760px); max-width: rem(760px);
} }
@include iflaptop{ @include iflaptop {
max-width: unset; max-width: unset;
} }
.Left{ .Left {
grid-area: left; grid-area: left;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -72,24 +72,24 @@
color: $color-white; color: $color-white;
text-align: center; text-align: center;
@include iftablet{ @include iftablet {
font-size: rem(20px); font-size: rem(20px);
} }
& p:first-child{ & p:first-child {
font-weight: 500; font-weight: 500;
font-size: rem(20px); font-size: rem(20px);
margin-bottom: rem(16px); margin-bottom: rem(16px);
@include iftablet{ @include iftablet {
font-size: rem(28px); font-size: rem(28px);
margin-bottom: rem(16px); margin-bottom: rem(16px);
} }
} }
} }
.Right{ .Right {
grid-area: right; grid-area: right;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -102,29 +102,39 @@
color: $color-white; color: $color-white;
text-align: center; text-align: center;
@include iftablet{ @include iftablet {
font-size: rem(20px); font-size: rem(20px);
} }
& p:first-child{ & p:first-child {
font-weight: 500; font-weight: 500;
font-size: rem(20px); font-size: rem(20px);
margin-bottom: rem(16px); margin-bottom: rem(16px);
@include iftablet{ @include iftablet {
font-size: rem(28px); font-size: rem(28px);
margin-bottom: rem(16px); margin-bottom: rem(16px);
} }
} }
} }
.Bottom{ .Bottom {
grid-area: bottom; grid-area: bottom;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.Btn {
background: #58c644;
}
.Icon {
path {
fill: $color-white;
}
}
} }
} }
@@ -134,7 +144,7 @@
padding: rem(20px) rem(20px); padding: rem(20px) rem(20px);
width: 100%; width: 100%;
@include iftablet{ @include iftablet {
flex: 1; flex: 1;
display: block; display: block;
padding: 0 rem(20px); padding: 0 rem(20px);
@@ -146,3 +156,32 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.ModalContainer {
background: $color-white;
width: 100%;
border-radius: rem(16px);
padding: rem(10px) rem(10px);
margin-bottom: 20px;
display: flex;
flex-direction: column;
max-width: 460px;
justify-content: center;
align-self: center;
@include iftablet{
max-width: unset;
align-self: unset;
width: max-content;
height: auto;
}
& > svg {
z-index: 1;
path {
stroke: #FFFFFF;
stroke-opacity: 0.8;
}
}
}

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import s from './styles.module.scss'; import s from './styles.module.scss';
import { Button } from '@shared/ui'; import { Button, Icons } from '@shared/ui';
import 'swiper/css'; import 'swiper/css';
import 'swiper/css/navigation'; import 'swiper/css/navigation';
import 'swiper/css/grid'; import 'swiper/css/grid';
@@ -14,6 +14,8 @@ import lic1 from '@public/images/licence/dtr-eac-0.png';
import lic2 from '@public/images/licence/dtr-eac-1.png'; import lic2 from '@public/images/licence/dtr-eac-1.png';
import lic3 from '@public/images/licence/dtr-eac-2.png'; import lic3 from '@public/images/licence/dtr-eac-2.png';
import lic4 from '@public/images/licence/dtr-eac-3.png'; import lic4 from '@public/images/licence/dtr-eac-3.png';
import { useModal } from '@core/providers/modal-provider';
import { Preview } from '@/widgets';
const slidesData = [ const slidesData = [
{ {
@@ -63,6 +65,23 @@ const swiperBreakpoints = {
}; };
function About() { function About() {
const modal = useModal();
const openModal = () =>
modal.showModal(
<Preview className={s.ModalContainer}>
<iframe
width='560'
height='315'
src='https://www.youtube.com/embed/-CgIZzd4qNY?si=wSZSMXolujovqQxT'
title='YouTube video player'
frameBorder='0'
allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
referrerPolicy='strict-origin-when-cross-origin'
allowFullScreen
></iframe>
</Preview>,
);
return ( return (
<section className={s.About}> <section className={s.About}>
<div className={s.Inner}> <div className={s.Inner}>
@@ -84,7 +103,10 @@ function About() {
</p> </p>
</div> </div>
<div className={s.Bottom}> <div className={s.Bottom}>
<Button>Посмотреть видео о нас</Button> <Button className={s.Btn} onClick={openModal}>
<Icons.Youtube className={s.Icon} />
Посмотреть видео о нас
</Button>
</div> </div>
</div> </div>
<Swiper <Swiper

View File

@@ -45,3 +45,19 @@
padding: rem(20px) rem(40px); padding: rem(20px) rem(40px);
} }
} }
.Btn {
border: 1px solid #eeeeee44;
}
.Icon {
width: rem(20px);
@include iftablet{
width: rem(40px);
}
path {
fill: $color-white;
}
}

View File

@@ -1,7 +1,19 @@
'use client';
import s from './styles.module.scss'; import s from './styles.module.scss';
import { Button } from '@shared/ui'; import { Button, Icons } from '@shared/ui';
import { CONTACTS } from '@shared/const/contacts';
import { useModal } from '@core/providers/modal-provider';
import { ConsultationModal } from '@/widgets';
function CallUs() { function CallUs() {
const modal = useModal();
const openModal = () => modal.showModal(<ConsultationModal />);
const handlePhoneClick = () => {
window.open(`tel:${CONTACTS.PHONE}`, '_self');
};
return ( return (
<section className={s.CallUs}> <section className={s.CallUs}>
<div className={s.LeftBlock}> <div className={s.LeftBlock}>
@@ -9,8 +21,14 @@ function CallUs() {
<p className={s.LineText}>Свяжитесь с нами любым удобным способом</p> <p className={s.LineText}>Свяжитесь с нами любым удобным способом</p>
</div> </div>
<div className={s.RightBlock}> <div className={s.RightBlock}>
<Button>Написать в чат</Button> <Button className={s.Btn} onClick={openModal}>
<Button>Бесплатный звонок</Button> <Icons.MobilePhone className={s.Icon} />
Мы вам перезвоним
</Button>
<Button className={s.Btn} onClick={handlePhoneClick}>
<Icons.MobileContact className={s.Icon} />
Связаться с нами
</Button>
</div> </div>
</section> </section>
); );

View File

@@ -5,3 +5,5 @@ export * from './mobile-callback';
export * from './sidebar'; export * from './sidebar';
export * from './scroll-to-top'; export * from './scroll-to-top';
export * from './cookie-notice'; export * from './cookie-notice';
export * from './consultation-modal';
export * from './preview-modal';

View File

@@ -5,7 +5,7 @@ import { Icons } from '@shared/ui';
import { phoneBeautify } from '@shared/lib/phoneBeautify/phoneBeautify'; import { phoneBeautify } from '@shared/lib/phoneBeautify/phoneBeautify';
import { CONTACTS } from '@shared/const/contacts'; import { CONTACTS } from '@shared/const/contacts';
import { useModal } from '@core/providers/modal-provider'; import { useModal } from '@core/providers/modal-provider';
import { ConsultationModal } from '@/feature/article'; import { ConsultationModal } from '@/widgets';
function MobileCallback() { function MobileCallback() {
const modal = useModal(); const modal = useModal();

View File

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

View File

@@ -0,0 +1,27 @@
.Container {
background: $color-white;
width: 100%;
border-radius: rem(16px);
padding: rem(10px) rem(10px);
margin-bottom: 20px;
display: flex;
flex-direction: column;
max-width: 460px;
justify-content: center;
align-self: center;
@include iftablet{
max-width: unset;
align-self: unset;
width: max-content;
height: auto;
}
& > svg {
z-index: 1;
path {
stroke: #FFFFFF;
stroke-opacity: 0.8;
}
}
}

View File

@@ -0,0 +1,17 @@
import { ReactNode } from 'react';
import { ModalContent } from '@shared/ui';
type PreviewProps = {
className?: string;
children: ReactNode;
};
function Preview({ children, className }: PreviewProps) {
return (
<ModalContent closeByClickOutside className={className}>
{children}
</ModalContent>
);
}
export { Preview };