diff --git a/.husky/pre-commit b/.husky/pre-commit index bc3ab45..258dc3d 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,5 @@ npx lint-staged -echo "Running test build..." +echo "" +echo "🚀 Running test build..." npm run build \ No newline at end of file diff --git a/README.md b/README.md index 54662ec..6916a02 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + diff --git a/eslint.config.mjs b/eslint.config.mjs index 7f86eca..c765879 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,6 +11,11 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends('next/core-web-vitals', 'next/typescript'), + { + rules: { + "@typescript-eslint/no-unused-vars": "off", + }, + }, ]; export default eslintConfig; diff --git a/package-lock.json b/package-lock.json index df7ece4..ac66e8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,15 +8,24 @@ "name": "fire-exam", "version": "0.1.0", "dependencies": { + "@hookform/resolvers": "^5.1.1", + "@maskito/core": "^3.9.0", + "@maskito/phone": "^3.9.0", + "@maskito/react": "^3.9.0", + "libphonenumber-js": "^1.12.9", "next": "15.3.2", + "nodemailer": "^7.0.3", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hook-form": "^7.57.0", "react-hot-toast": "^2.5.2", - "swiper": "^11.2.8" + "swiper": "^11.2.8", + "zod": "^3.25.56" }, "devDependencies": { "@eslint/eslintrc": "^3", "@types/node": "^20", + "@types/nodemailer": "^6.4.17", "@types/react": "^19", "@types/react-dom": "^19", "clsx": "^2.1.1", @@ -203,6 +212,18 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.1.1.tgz", + "integrity": "sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -665,6 +686,44 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@maskito/core": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.9.0.tgz", + "integrity": "sha512-OgzzgzJTXFZH79mqyHFVUZ5/bUhSW147+JzYVX+DdmQ5zc+mxmFQqsUS5ffVxd2C7/bnEmC7+savYbcae2IhBw==", + "license": "Apache-2.0" + }, + "node_modules/@maskito/kit": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.9.0.tgz", + "integrity": "sha512-CD7TQ7WUMtZ8jkhOsislbqht1gMuNHVQsLJG9tXcGvZbegkgJ6wdkggkol1y1/0F5eh/fT+RzzKD9dVjSQon2g==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@maskito/core": "^3.9.0" + } + }, + "node_modules/@maskito/phone": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.9.0.tgz", + "integrity": "sha512-EUCOmOscoQM+vnJwOAiBXVpZVVYkHc7rhnqLqfkslXsZCg5VLNNpzAb8SuFQMxYJYM2NnMawErvL7CjOdVDmvQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@maskito/core": "^3.9.0", + "@maskito/kit": "^3.9.0", + "libphonenumber-js": ">=1.0.0" + } + }, + "node_modules/@maskito/react": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@maskito/react/-/react-3.9.0.tgz", + "integrity": "sha512-qYGncdyaPbi50rDkg0gwh64DHPBCT6YMdkWsMbqG57bVjE1S0X9zbZQICieRQXGVkRjlgJenoMu3b5svdH4ysQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@maskito/core": "^3.9.0", + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", @@ -884,6 +943,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -941,6 +1006,16 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/react": { "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", @@ -3992,6 +4067,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.9", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.9.tgz", + "integrity": "sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg==", + "license": "MIT" + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -4351,6 +4432,15 @@ } } }, + "node_modules/nodemailer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz", + "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4768,6 +4858,22 @@ "react": "^19.1.0" } }, + "node_modules/react-hook-form": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.57.0.tgz", + "integrity": "sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-hot-toast": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", @@ -5998,6 +6104,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.56", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.56.tgz", + "integrity": "sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index b7dc14a..b2ff3b1 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,24 @@ "prepare": "husky" }, "dependencies": { + "@hookform/resolvers": "^5.1.1", + "@maskito/core": "^3.9.0", + "@maskito/phone": "^3.9.0", + "@maskito/react": "^3.9.0", + "libphonenumber-js": "^1.12.9", "next": "15.3.2", + "nodemailer": "^7.0.3", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hook-form": "^7.57.0", "react-hot-toast": "^2.5.2", - "swiper": "^11.2.8" + "swiper": "^11.2.8", + "zod": "^3.25.56" }, "devDependencies": { "@eslint/eslintrc": "^3", "@types/node": "^20", + "@types/nodemailer": "^6.4.17", "@types/react": "^19", "@types/react-dom": "^19", "clsx": "^2.1.1", diff --git a/public/images/dtr-logo.png b/public/images/dtr-logo.png new file mode 100644 index 0000000..157e80b Binary files /dev/null and b/public/images/dtr-logo.png differ diff --git a/public/images/footer-man.png b/public/images/footer-man.png new file mode 100644 index 0000000..f61b406 Binary files /dev/null and b/public/images/footer-man.png differ diff --git a/public/images/license-dtr-eac.png b/public/images/license-dtr-eac.png new file mode 100644 index 0000000..fd78b4b Binary files /dev/null and b/public/images/license-dtr-eac.png differ diff --git a/public/images/license-mcs.jpg b/public/images/license-mcs.jpg new file mode 100644 index 0000000..1f10f69 Binary files /dev/null and b/public/images/license-mcs.jpg differ diff --git a/public/svg/checkbox.svg b/public/svg/checkbox.svg new file mode 100644 index 0000000..3c744ee --- /dev/null +++ b/public/svg/checkbox.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/public/svg/email-icon.svg b/public/svg/email-icon.svg new file mode 100644 index 0000000..622dddd --- /dev/null +++ b/public/svg/email-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/svg/sochi-adm-logo.svg b/public/svg/sochi-adm-logo.svg new file mode 100644 index 0000000..581b53d --- /dev/null +++ b/public/svg/sochi-adm-logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/app/api/heartbeat/route.ts b/src/app/api/heartbeat/route.ts new file mode 100644 index 0000000..4867ec7 --- /dev/null +++ b/src/app/api/heartbeat/route.ts @@ -0,0 +1,9 @@ +export async function GET(request: Request) { + return new Response('Heartbeat is OK!', { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + }, + }); +} diff --git a/src/app/api/sendform/route.ts b/src/app/api/sendform/route.ts new file mode 100644 index 0000000..a083b63 --- /dev/null +++ b/src/app/api/sendform/route.ts @@ -0,0 +1,66 @@ +import nodemailer from 'nodemailer'; +import { TBaseForm } from '@shared/api/api.types'; +import { CORE } from '@shared/config/core'; + +async function sendMail(data: TBaseForm) { + const { name, phone, message, form } = data; + + const formattedBody = ` + + +

Сообщение с сайта "Пожарная экспертиза"

+

Форма отправки: ${form}

+

Имя отправителя: ${name ?? 'не указано'}

+

Номер телефона: ${phone}

+

Сообщение: ${message ?? 'отсутствует'}

+ + + `; + + const transporter = nodemailer.createTransport({ + service: 'yandex', + auth: { + user: CORE.MAIL_USER, + pass: CORE.MAIL_PASS, + }, + }); + + return await transporter.sendMail({ + from: CORE.MAIL_FROM, + to: CORE.MAIL_TO, + subject: 'Заявка с сайта FireExams', + html: formattedBody, + }); +} + +export async function POST(request: Request) { + try { + const payload = await request.json(); + + if (payload.secure !== CORE.MAIL_SECURE_KEY) { + await Promise.reject('Request failure!'); + } + + const sendResult = await sendMail({ ...payload }); + + const data = { message: 'Form accepted' }; + const headers = new Headers({ + 'Content-Type': 'application/json', + }); + const options = { + status: 200, + statusText: 'OK', + headers: headers, + }; + + if (sendResult?.messageId) { + return new Response(JSON.stringify(data), options); + } else { + await Promise.reject('Sending request failure!'); + } + } catch (error) { + return new Response(`Api error: ${error}`, { + status: 400, + }); + } +} diff --git a/src/app/cookie/page.tsx b/src/app/cookie/page.tsx new file mode 100644 index 0000000..1c0ca3f --- /dev/null +++ b/src/app/cookie/page.tsx @@ -0,0 +1,9 @@ +import { Cookie } from '@views/cookie'; + +export default function CookiePage() { + return ( +
+ +
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ed599af..716f773 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import { Open_Sans } from 'next/font/google'; import '@core/styles/reset.scss'; import '@core/styles/globals.scss'; import { Toaster } from 'react-hot-toast'; +import { ModalProvider } from '@core/providers/modal-provider'; +import { CookieNotice } from '@/widgets'; const openSans = Open_Sans({ subsets: ['cyrillic'], @@ -34,8 +36,9 @@ export default function RootLayout({ return ( - {children} + {children} + ); diff --git a/src/app/page.tsx b/src/app/page.tsx index de0ec89..40cb571 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,9 +1,9 @@ -import { HomePage } from '@views/home'; +import { Home } from '@views/home'; -export default function Home() { +export default function HomePage() { return (
- +
); } diff --git a/src/app/privacy-policy/page.tsx b/src/app/privacy-policy/page.tsx new file mode 100644 index 0000000..5346dc2 --- /dev/null +++ b/src/app/privacy-policy/page.tsx @@ -0,0 +1,9 @@ +import { PrivacyPolicy } from '@views/privacy-policy'; + +export default function PrivacyPolicyPage() { + return ( +
+ +
+ ); +} diff --git a/src/app/user-agreement/page.tsx b/src/app/user-agreement/page.tsx new file mode 100644 index 0000000..3fdd265 --- /dev/null +++ b/src/app/user-agreement/page.tsx @@ -0,0 +1,9 @@ +import { UserAgreement } from '@views/user-agreement'; + +export default function UserAgreementPage() { + return ( +
+ +
+ ); +} diff --git a/src/core/constants/privacy-policy.ts b/src/core/constants/privacy-policy.ts new file mode 100644 index 0000000..c76d67c --- /dev/null +++ b/src/core/constants/privacy-policy.ts @@ -0,0 +1,5 @@ +const COMPANY = '«ООО ДИТРАСО»'; +const WEB = 'https://www.fire-expert.ru/'; +const EMAIL = 'spo-71@yandex.ru'; + +export { COMPANY, WEB, EMAIL }; diff --git a/src/core/providers/modal-provider.tsx b/src/core/providers/modal-provider.tsx new file mode 100644 index 0000000..af19951 --- /dev/null +++ b/src/core/providers/modal-provider.tsx @@ -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(null); + + const showModal = useCallback((content: ReactNode) => { + setModalContent(content); + }, []); + + const hideModal = useCallback(() => { + setModalContent(null); + }, []); + + return ( + + {children} + {/* Ваш Modal компонент здесь */} + + {modalContent} + + + ); +}; + +export { useModal, ModalProvider }; diff --git a/src/core/styles/variables.scss b/src/core/styles/variables.scss index c518824..9c570b1 100644 --- a/src/core/styles/variables.scss +++ b/src/core/styles/variables.scss @@ -1,4 +1,3 @@ - //frontend breakpoint $mobile: 360px; $tablet: 768px; @@ -19,10 +18,12 @@ $font-semi-bold: 600; $color-white: #FFFFFF; $color-black: #000000; $color-orange: #E96526; +$color-orange-hover: #ea4b05; $color-lightgray: #E4E1E1; $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; \ No newline at end of file diff --git a/src/entities/home/ConsultationOrder/index.ts b/src/entities/home/ConsultationOrder/index.ts deleted file mode 100644 index dac7ffd..0000000 --- a/src/entities/home/ConsultationOrder/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ConsultationOrder } from './ui'; diff --git a/src/entities/home/ConsultationOrder/ui.tsx b/src/entities/home/ConsultationOrder/ui.tsx deleted file mode 100644 index 5a410bb..0000000 --- a/src/entities/home/ConsultationOrder/ui.tsx +++ /dev/null @@ -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 ( - - ); -} diff --git a/src/entities/home/callback-order/index.ts b/src/entities/home/callback-order/index.ts new file mode 100644 index 0000000..84a1b20 --- /dev/null +++ b/src/entities/home/callback-order/index.ts @@ -0,0 +1 @@ +export { CallbackOrder } from './ui'; diff --git a/src/entities/home/callback-order/styles.module.scss b/src/entities/home/callback-order/styles.module.scss new file mode 100644 index 0000000..5435b1a --- /dev/null +++ b/src/entities/home/callback-order/styles.module.scss @@ -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); + } + } +} \ No newline at end of file diff --git a/src/entities/home/callback-order/ui.tsx b/src/entities/home/callback-order/ui.tsx new file mode 100644 index 0000000..aed940a --- /dev/null +++ b/src/entities/home/callback-order/ui.tsx @@ -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(); + + return ( +
+ +
+ ); +} + +export { CallbackOrder }; diff --git a/src/entities/home/consultation-modal/index.ts b/src/entities/home/consultation-modal/index.ts new file mode 100644 index 0000000..e9f2654 --- /dev/null +++ b/src/entities/home/consultation-modal/index.ts @@ -0,0 +1 @@ +export { ConsultationModal } from './ui'; diff --git a/src/entities/home/consultation-modal/styles.module.scss b/src/entities/home/consultation-modal/styles.module.scss new file mode 100644 index 0000000..141e086 --- /dev/null +++ b/src/entities/home/consultation-modal/styles.module.scss @@ -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; +} \ No newline at end of file diff --git a/src/entities/home/consultation-modal/ui.tsx b/src/entities/home/consultation-modal/ui.tsx new file mode 100644 index 0000000..5eaa309 --- /dev/null +++ b/src/entities/home/consultation-modal/ui.tsx @@ -0,0 +1,138 @@ +import s from './styles.module.scss'; +import { Button, Input } from '@/shared/ui'; +import { Checkbox, 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; + +const defaultValues = { + name: '', + phone: '', + message: '', +}; + +type ConsultationModalProps = { + className?: string; +}; + +function ConsultationModal({}: ConsultationModalProps) { + const { + handleSubmit, + control, + reset, + clearErrors, + formState: { errors }, + } = useForm({ + 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 ( + +
+

+ Мы подскажем, как решить ваши вопросы по пожарной безопасности. +

+ ( + { + clearErrors('name'); + field.onChange(e); + }} + error={errors && errors.name?.message} + /> + )} + /> + ( + { + clearErrors('phone'); + field.onChange(e); + }} + error={errors && errors.phone?.message} + /> + )} + /> +

Кратко опишите интересующий Вас вопрос

+ ( + + {error && ( + + {error} + + )} ); -} +}); + +export default TextArea; diff --git a/src/views/cookie/index.ts b/src/views/cookie/index.ts new file mode 100644 index 0000000..bfeb621 --- /dev/null +++ b/src/views/cookie/index.ts @@ -0,0 +1 @@ +export { default as Cookie } from './ui'; diff --git a/src/views/cookie/styles.module.scss b/src/views/cookie/styles.module.scss new file mode 100644 index 0000000..4d2911c --- /dev/null +++ b/src/views/cookie/styles.module.scss @@ -0,0 +1,79 @@ +.Cookie { + position: relative; + margin: rem(60px) auto rem(20px); + width: rem(360px); + background: #EEE; + padding: rem(20px); + border-radius: rem(28px); + + @include iftablet{ + width: rem(600px); + margin: rem(40px) auto rem(20px); + padding: rem(20px); + } + + @include iflaptop{ + width: rem(800px); + margin: rem(60px) auto rem(20px); + padding: rem(60px); + } + + @include ifdesktop{ + width: rem(1200px); + margin: rem(60px) auto rem(20px); + padding: rem(60px); + } + + h2 { + font-family: $font-open-sans; + font-weight: $font-regular; + font-size: rem(20px); + line-height: 130%; + color: $color-text; + + @include iftablet{ + font-size: rem(24px); + } + } + + p { + font-family: $font-open-sans; + font-weight: $font-regular; + font-size: rem(14px); + line-height: 130%; + color: $color-text; + margin: 8px 0; + + @include iftablet{ + font-size: rem(16px); + } + } + + a { + color: $color-orange; + } +} + +.FloatBtn { + position: fixed; + top: rem(10px); + right: 50%; + transform: translateX(50%); + + @include iftablet{ + position: absolute; + top: rem(-20px); + right: rem(-100px); + transform: none; + } + + @include iflaptop{ + top: rem(-20px); + right: rem(-100px); + } + + @include ifdesktop{ + top: rem(-20px); + right: rem(-150px); + } +} \ No newline at end of file diff --git a/src/views/cookie/ui.tsx b/src/views/cookie/ui.tsx new file mode 100644 index 0000000..dbd2418 --- /dev/null +++ b/src/views/cookie/ui.tsx @@ -0,0 +1,50 @@ +import s from './styles.module.scss'; +import { Button } from '@shared/ui'; +import Link from 'next/link'; +import { WEB } from '@core/constants/privacy-policy'; + +export default function Cookie() { + return ( +
+ + + + +

Мы используем cookie

+

+ Когда вы посещаете сайт {WEB}, наша компания может использовать + общеотраслевую технологию, называемую cookie. Файлы cookie представляют + собой небольшие фрагменты данных, которые временно сохраняются на вашем + компьютере или мобильном устройстве и обеспечивают более эффективную + работу сайта. +

+

+ Сайт {WEB} использует метрические системы для сбора статистики: + «Яндекс.Метрика». На основе этих данных мы делаем наш сайт лучше и + эффективнее для пользователей. +

+

+ Продолжая пользоваться этим сайтом, вы соглашаетесь на использование + cookie и обработку данных в соответствии с Политикой сайта в области + обработки и защиты персональных данных. +

+

+ + Согласие на обработку персональных данных посетителей сайта + +

+

+ Если вы не хотите использовать cookie, вы можете отключить их в + настройках безопасности вашего браузера. Отключение cookie следует + выполнить для каждого браузера и устройства, с помощью которого + осуществляется вход на сайт. +

+

+ Обратите внимание, что в случае, если использование сайтом файлов cookie + отключено, некоторые возможности и услуги сайта могут быть недоступны. +

+
+ ); +} diff --git a/src/views/home/index.ts b/src/views/home/index.ts index 370eca4..e187952 100644 --- a/src/views/home/index.ts +++ b/src/views/home/index.ts @@ -1 +1 @@ -export { default as HomePage } from './ui/home'; +export { default as Home } from './ui/home'; diff --git a/src/views/home/ui/contacts/contacts.module.scss b/src/views/home/ui/contacts/contacts.module.scss index e25a581..17c2c02 100644 --- a/src/views/home/ui/contacts/contacts.module.scss +++ b/src/views/home/ui/contacts/contacts.module.scss @@ -236,6 +236,11 @@ margin-left: rem(42px); } + a:hover { + color: #163055; + text-decoration: underline; + } + .Icon { position: absolute; top: 0; diff --git a/src/views/home/ui/contacts/contacts.tsx b/src/views/home/ui/contacts/contacts.tsx index 3bbc273..d57ec79 100644 --- a/src/views/home/ui/contacts/contacts.tsx +++ b/src/views/home/ui/contacts/contacts.tsx @@ -8,6 +8,7 @@ import sochiparkLogo from '@public/images/logo-sochipark.png'; import chateauLogo from '@public/images/logo-chateau-de-talu.png'; import gazpromLogo from '@public/images/logo-gazprom.png'; import kraspolLogo from '@public/images/logo-kraspol.png'; +import sochiAdmLogo from '@public/svg/sochi-adm-logo.svg'; import ledOn from '@public/svg/led-on.svg'; import phoneCall from '@public/svg/phone-call.svg'; import map from '@public/svg/map.svg'; @@ -53,11 +54,11 @@ export default function Contacts() {

- +7 (988) 400 93 93 + +7 (988) 400-93-93

- office@firecheck.ru + spo-71@yandex.ru

@@ -69,7 +70,7 @@ export default function Contacts() { const clientsLogos = [ { logo: bogatyrLogo }, { logo: sochiparkLogo }, + { logo: sochiAdmLogo }, { logo: chateauLogo }, - { logo: gazpromLogo }, { logo: kraspolLogo }, ]; diff --git a/src/views/home/ui/footer/footer.module.scss b/src/views/home/ui/footer/footer.module.scss index d476492..d827e7b 100644 --- a/src/views/home/ui/footer/footer.module.scss +++ b/src/views/home/ui/footer/footer.module.scss @@ -39,6 +39,29 @@ @include ifdesktop { width: rem(1340px); } + + .Picture { + display: none; + position: absolute; + top: 0; + height: auto; + + @include iftablet{ + display: block; + right: rem(40px); + width: rem(130px); + } + + @include iflaptop{ + right: rem(80px); + width: rem(150px); + } + + @include ifdesktop{ + right: rem(160px); + width: rem(180px); + } + } } .Bottom { diff --git a/src/views/home/ui/footer/footer.tsx b/src/views/home/ui/footer/footer.tsx index d4de59d..cc25290 100644 --- a/src/views/home/ui/footer/footer.tsx +++ b/src/views/home/ui/footer/footer.tsx @@ -2,19 +2,37 @@ import s from './footer.module.scss'; import { Button } from '@shared/ui'; import { FooterForm } from '@/widgets'; import Link from 'next/link'; +import Image from 'next/image'; + +import man from '@public/images/footer-man.png'; export default function Footer() { return (
+
- - - + + + + + + + + +

- Политика конфиденциальности + Политика конфиденциальности

diff --git a/src/views/home/ui/home.tsx b/src/views/home/ui/home.tsx index f5ac30d..71b993e 100644 --- a/src/views/home/ui/home.tsx +++ b/src/views/home/ui/home.tsx @@ -10,7 +10,7 @@ export default function HomePage() { <>
- + {/**/}