diff --git a/client/index.html b/client/index.html index f31b005..85c7310 100644 --- a/client/index.html +++ b/client/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + Интерактивные решения для застройщиков
diff --git a/client/package.json b/client/package.json index ea808be..db8a4e0 100644 --- a/client/package.json +++ b/client/package.json @@ -20,6 +20,7 @@ "react-rangeslider": "^2.2.0", "react-responsive-carousel": "^3.2.23", "react-scroll": "^1.8.9", + "react-yandex-metrika": "^2.6.0", "swiper": "^9.2.0", "three": "^0.151.3", "zustand": "^4.3.8" diff --git a/client/public/VRAnim_46.glb b/client/public/VRAnim_46.glb deleted file mode 100644 index 998452f..0000000 Binary files a/client/public/VRAnim_46.glb and /dev/null differ diff --git a/client/public/VRAnim_47.glb b/client/public/VRAnim_47.glb deleted file mode 100644 index 06ef449..0000000 Binary files a/client/public/VRAnim_47.glb and /dev/null differ diff --git a/client/public/favicon.svg b/client/public/favicon.svg new file mode 100644 index 0000000..2001ea6 --- /dev/null +++ b/client/public/favicon.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/slider/1.jpg b/client/public/images/slider/1.jpg new file mode 100644 index 0000000..41054e7 Binary files /dev/null and b/client/public/images/slider/1.jpg differ diff --git a/client/public/images/slider/2.jpg b/client/public/images/slider/2.jpg new file mode 100644 index 0000000..fccaedc Binary files /dev/null and b/client/public/images/slider/2.jpg differ diff --git a/client/public/images/slider/3.jpg b/client/public/images/slider/3.jpg new file mode 100644 index 0000000..ef6cd88 Binary files /dev/null and b/client/public/images/slider/3.jpg differ diff --git a/client/public/images/slider/4.jpg b/client/public/images/slider/4.jpg new file mode 100644 index 0000000..2e5aba8 Binary files /dev/null and b/client/public/images/slider/4.jpg differ diff --git a/client/public/images/slider/slide1.png b/client/public/images/slider/slide1.png deleted file mode 100644 index 40a0e9c..0000000 Binary files a/client/public/images/slider/slide1.png and /dev/null differ diff --git a/client/public/images/slider/slide2.jpg b/client/public/images/slider/slide2.jpg deleted file mode 100644 index 4f84f85..0000000 Binary files a/client/public/images/slider/slide2.jpg and /dev/null differ diff --git a/client/public/images/slider/slide3.png b/client/public/images/slider/slide3.png deleted file mode 100644 index ea6bb3f..0000000 Binary files a/client/public/images/slider/slide3.png and /dev/null differ diff --git a/client/public/images/slider/slide4.jpg b/client/public/images/slider/slide4.jpg deleted file mode 100644 index d67cf46..0000000 Binary files a/client/public/images/slider/slide4.jpg and /dev/null differ diff --git a/client/public/images/slider/slide5.png b/client/public/images/slider/slide5.png deleted file mode 100644 index 981eff1..0000000 Binary files a/client/public/images/slider/slide5.png and /dev/null differ diff --git a/client/public/images/slider/slide6.png b/client/public/images/slider/slide6.png deleted file mode 100644 index c6a4848..0000000 Binary files a/client/public/images/slider/slide6.png and /dev/null differ diff --git a/client/public/images/slider/slide7.jpg b/client/public/images/slider/slide7.jpg deleted file mode 100644 index 18d0188..0000000 Binary files a/client/public/images/slider/slide7.jpg and /dev/null differ diff --git a/client/public/images/slider/slide8.png b/client/public/images/slider/slide8.png deleted file mode 100644 index e79fb6b..0000000 Binary files a/client/public/images/slider/slide8.png and /dev/null differ diff --git a/client/public/logo-footer.svg b/client/public/logo-footer.svg new file mode 100644 index 0000000..ebfb98d --- /dev/null +++ b/client/public/logo-footer.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/logo.svg b/client/public/logo.svg new file mode 100644 index 0000000..759f30c --- /dev/null +++ b/client/public/logo.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/vite.svg b/client/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/client/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index 1d982a6..5ed3d6f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,69 +1,32 @@ import "./App.css"; -// import { Canvas } from "@react-three/fiber"; -import { FormEvent, useEffect, useRef, useState } from "react"; +import { FormEvent, useRef, useState } from "react"; import PieChart from "./components/PieChart/PieChart"; -// import { Model } from "./components/VRHemlet"; import Calc from "./components/Calc"; import { motion } from "framer-motion"; import InputMask from "react-input-mask"; import api from "./utils/api"; -import FeatureCard from "./components/FeatureCard"; import ComplexCard from "./components/ComplexCard"; import Title from "./components/Title"; import FeatureCardMobile from "./components/FeatureCardMobile"; import Modal from "./components/Modal"; import Slider from "./components/Slider/Slider"; import FeatureSlider from "./components/FeatureSlider"; +import useModalStore from "./store/modal"; +import FeedbackForm from "./components/FeedbackForm"; +import { YMInitializer } from "react-yandex-metrika"; +import Map from "./components/Map"; function App() { const parallaxRef = useRef(null); - const [fullname, setFullname] = useState(""); const [email, setEmail] = useState(""); const [company, setCompany] = useState(""); const [phone, setPhone] = useState(""); - - // const [mapPosition, setMapPosition] = useState("29% 100%"); - // const [mapCity, setMapCity] = useState("Тюмень"); - const [isShowComplexCards, setIsShowComplexCards] = useState(false); - - // const helmetRef = useRef(null); - // const helmetInView = useInView(helmetRef); - // const [helmetIsAnim, setHelmetIsAnim] = useState(true); - - // const featureImagesContainer = useRef(null); - // const featureImages = [ - // "/videos/Integra_CRM.mp4", - // "/videos/Uralsky.mp4", - // "/videos/Integra_CRM.mp4", - // "/videos/Integra_CRM.mp4", - // "/videos/Integra_CRM.mp4", - // "/videos/Integra_CRM.mp4", - // "/videos/Integra_CRM.mp4", - // "/videos/Integra_CRM.mp4", - // ]; - // const [selectedFeatureImageIndex, setSelectedFeatureImageIndex] = - // useState(0); - - // useEffect(() => { - // if (featureImagesContainer.current) { - // featureImagesContainer.current.insertAdjacentHTML( - // "beforeend", - // `
- // - //
` - // ); - - // if (featureImagesContainer.current.children.length > 1) { - // setTimeout(() => { - // featureImagesContainer.current?.firstElementChild?.remove(); - // }, 1000); - // } - // } - // }, [selectedFeatureImageIndex]); + const [modalComponent, setModalComponent] = useModalStore((state) => [ + state.component, + state.setComponent, + ]); async function handleSubmitSendMail(e: FormEvent) { e.preventDefault(); @@ -89,17 +52,24 @@ function App() {
-
+
+ +
+ +
-
- @@ -116,21 +86,20 @@ function App() {

-
- -
- +
+ +
@@ -147,6 +116,35 @@ function App() {
+
+

На

+
+ + 18% + +
+

+ увеличивает конверсию +
+ из консультации в бронирование +

+
+ +
+

На

+
+ + 12% + + {/* раз */} +
+

+ увеличивает конверсию +
+ из бронирования в продажу +

+
+

До

@@ -170,53 +168,28 @@ function App() { 26 - {/* раз */}

что-то на умном
в две строчки

- -
-

На

-
- - 18% - - {/* раз */} -
-

- увеличивает конверсию -
- из консультации в бронирование -

-
- -
-

На

-
- - 12% - - {/* раз */} -
-

- увеличивает конверсию -
- из бронирования в продажу -

-
-
-
@@ -239,95 +212,46 @@ function App() { - {/*
- setSelectedFeatureImageIndex(0)} - /> -
-
-
- setSelectedFeatureImageIndex(1)} - /> - setSelectedFeatureImageIndex(2)} - /> - setSelectedFeatureImageIndex(3)} - /> - setSelectedFeatureImageIndex(4)} - /> - setSelectedFeatureImageIndex(5)} - /> - setSelectedFeatureImageIndex(6)} - /> - setSelectedFeatureImageIndex(7)} - /> -
*/} -
@@ -347,7 +271,10 @@ function App() {

-
@@ -628,7 +555,7 @@ function App() {
-
+
Покажите все преимущества вашего жилого комплекса клиенту{" "} @@ -649,7 +576,11 @@ function App() { </div> </div> <div className="space-y-8"> - <button className="text-xl border border-[#BC75FF] px-6 py-3 pr-3 flex items-center space-x-2 rounded-full cursor-pointer"> + <a + href="https://stream.graff.tech" + target="_blank" + className="w-fit text-xl border border-[#BC75FF] px-6 py-3 pr-3 flex items-center space-x-2 rounded-full cursor-pointer" + > <span>Узнать больше</span> <svg width="24" @@ -666,13 +597,15 @@ function App() { strokeLinejoin="round" /> </svg> - </button> + </a> </div> </div> - <div className="lg:absolute z-10 xl:top-2 lg:top-[335px] right-4 2xl:w-full lg:w-[52%] w-full flex lg:justify-end"> + <Map /> + + {/* <div className="lg:absolute xl:top-2 lg:top-[335px] right-4 2xl:w-full lg:w-[52%] w-full flex lg:justify-end"> <img src="/images/devices.png" alt="" className="" /> - </div> + </div> */} </div> </div> </div> @@ -704,10 +637,16 @@ function App() { <Title>Реализованные проекты
- */} + - + {isShowComplexCards && ( <> - + - - */} + {/* + /> */} )}
@@ -966,10 +900,12 @@ function App() {

- Адрес + Сайт

- ул. Московская, 47, Екатеринбург + + graff.tech +

@@ -995,11 +931,12 @@ function App() {
@@ -1009,6 +946,8 @@ function App() {
+ +
); } diff --git a/client/src/components/FeatureCardMobile.tsx b/client/src/components/FeatureCardMobile.tsx index ca199f8..f63f2b9 100644 --- a/client/src/components/FeatureCardMobile.tsx +++ b/client/src/components/FeatureCardMobile.tsx @@ -1,22 +1,42 @@ import { useInView } from "framer-motion"; -import React, { useRef } from "react"; +import React, { useEffect, useRef } from "react"; interface IFeatureCard2 { title: string; text: string; - image: string; + src: string; } -function FeatureCardMobile2({ title, text, image }: IFeatureCard2) { - const fetureCard = useRef(null); - +function FeatureCardMobile2({ title, text, src }: IFeatureCard2) { + const fetureCard = useRef(null); + const videoRef = useRef(null); const inView = useInView(fetureCard, { margin: "-50%" }); - return ( -
+ useEffect(() => { + if (videoRef.current) { + if (inView) { + videoRef.current.play(); + } else { + videoRef.current.pause(); + } + } + }, [inView]); -
- + return ( +
+
+

diff --git a/client/src/components/FeatureSlider.tsx b/client/src/components/FeatureSlider.tsx index 08f3687..6848c67 100644 --- a/client/src/components/FeatureSlider.tsx +++ b/client/src/components/FeatureSlider.tsx @@ -1,94 +1,65 @@ -import React, { useEffect, useRef, useState } from "react"; +import React from "react"; import FeatureSliderItem from "./FeatureSliderItem"; -import { useInView } from "framer-motion"; function FeatureSlider() { - const [selectedItem, setSelectedItem] = useState(""); - const featureSliderRef = useRef(null); - const featureSliderItemRef = useRef(null); - const featureSliderInView = useInView(featureSliderRef); - - useEffect(() => { - if (featureSliderInView) { - setSelectedItem("/videos/features/virtual_tour.webm"); - } else { - setSelectedItem(""); - } - }, [featureSliderInView]); - - useEffect(() => { - if (featureSliderItemRef.current) { - featureSliderItemRef.current.insertAdjacentHTML( - "beforeend", - `` - ); - - if (featureSliderItemRef.current.children.length > 1) { - setTimeout(() => { - featureSliderItemRef.current?.firstElementChild?.remove(); - }, 1000); - } - } - }, [selectedItem]); - return ( -

-
- setSelectedItem("/videos/features/virtual_tour.webm")} - /> - setSelectedItem("/videos/features/nks_infra.webm")} - /> -
-
-
- setSelectedItem("/videos/features/uralsky.webm")} - /> - setSelectedItem("/videos/features/parametric.webm")} - /> -
-
- setSelectedItem("/videos/features/render.webm")} - /> - setSelectedItem("/videos/features/wish.webm")} - /> -
-
- setSelectedItem("/videos/features/integra_crm.webm")} - /> -
-
- setSelectedItem("/videos/features/send.webm")} - /> -
+
+ + + + + + + +
); } diff --git a/client/src/components/FeatureSliderItem.tsx b/client/src/components/FeatureSliderItem.tsx index 3b05de2..9d4c98f 100644 --- a/client/src/components/FeatureSliderItem.tsx +++ b/client/src/components/FeatureSliderItem.tsx @@ -1,37 +1,68 @@ -import React from "react"; +import React, { useEffect, useRef, useState } from "react"; interface IFeatureSliderItem { title: string; text: string; - handleHover: () => void; + video: string; } -function FeatureSliderItem({ title, text, handleHover }: IFeatureSliderItem) { +function FeatureSliderItem({ title, text, video }: IFeatureSliderItem) { + const videoRef = useRef(null); + const [isTouchScreen, setIsTouchScreen] = useState(false); + + function handleMouseEnter() { + videoRef.current?.play(); + } + + function handleMouseLeave() { + videoRef.current?.pause(); + } + + useEffect(() => { + if (navigator.maxTouchPoints > 1) { + setIsTouchScreen(true); + } + }, []); + return (
-
- - - + + +
+ {isTouchScreen && ( + + + + )}
-
-
+ +
+

{title} -

-
{text}
+

+

{text}

); diff --git a/client/src/components/FeatureSliderItemOld.tsx b/client/src/components/FeatureSliderItemOld.tsx new file mode 100644 index 0000000..3b05de2 --- /dev/null +++ b/client/src/components/FeatureSliderItemOld.tsx @@ -0,0 +1,40 @@ +import React from "react"; + +interface IFeatureSliderItem { + title: string; + text: string; + handleHover: () => void; +} + +function FeatureSliderItem({ title, text, handleHover }: IFeatureSliderItem) { + return ( +
+
+ + + +
+
+
+ {title} +
+
{text}
+
+
+ ); +} + +export default FeatureSliderItem; diff --git a/client/src/components/FeatureSliderOld.tsx b/client/src/components/FeatureSliderOld.tsx new file mode 100644 index 0000000..2acf108 --- /dev/null +++ b/client/src/components/FeatureSliderOld.tsx @@ -0,0 +1,96 @@ +import React, { useEffect, useRef, useState } from "react"; +import FeatureSliderItem from "./FeatureSliderItemOld"; +import { useInView } from "framer-motion"; + +function FeatureSlider() { + const [selectedItem, setSelectedItem] = useState(""); + const featureSliderRef = useRef(null); + const featureSliderItemRef = useRef(null); + const featureSliderInView = useInView(featureSliderRef); + + useEffect(() => { + if (featureSliderInView) { + setSelectedItem("/videos/features/virtual_tour.mp4"); + } else { + setSelectedItem(""); + } + }, [featureSliderInView]); + + useEffect(() => { + if (featureSliderItemRef.current) { + featureSliderItemRef.current.insertAdjacentHTML( + "beforeend", + `` + ); + + if (featureSliderItemRef.current.children.length > 1) { + setTimeout(() => { + featureSliderItemRef.current?.firstElementChild?.remove(); + }, 1000); + } + } + }, [selectedItem]); + + return ( +
+
+ setSelectedItem("/videos/features/virtual_tour.mp4")} + /> + setSelectedItem("/videos/features/nks_infra.mp4")} + /> +
+
+
+ setSelectedItem("/videos/features/uralsky.mp4")} + /> + setSelectedItem("/videos/features/parametric.mp4")} + /> +
+
+ setSelectedItem("/videos/features/render.mp4")} + /> + setSelectedItem("/videos/features/wish.mp4")} + /> +
+
+ setSelectedItem("/videos/features/integra_crm.mp4")} + /> +
+
+ setSelectedItem("/videos/features/send.mp4")} + /> +
+
+ ); +} + +export default FeatureSlider; diff --git a/client/src/components/FeedbackForm.tsx b/client/src/components/FeedbackForm.tsx new file mode 100644 index 0000000..d1e63be --- /dev/null +++ b/client/src/components/FeedbackForm.tsx @@ -0,0 +1,168 @@ +import { FormEvent, useState } from "react"; +import api from "../utils/api"; +import InputMask from "react-input-mask"; +import useModalStore from "../store/modal"; +import FeedbackFormSuccess from "./FeedbackFormSuccess"; + +function FeedbackForm() { + const [modalComponent, setModalComponent] = useModalStore((state) => [ + state.component, + state.setComponent, + ]); + const [fullname, setFullname] = useState(""); + const [email, setEmail] = useState(""); + const [company, setCompany] = useState(""); + const [phone, setPhone] = useState(""); + + async function handleSubmitSendMail(e: FormEvent) { + e.preventDefault(); + + await api.post("mail", { + json: { + fullname, + email, + company, + phone, + }, + }); + + setFullname(""); + setEmail(""); + setCompany(""); + setPhone(""); + + setModalComponent(); + } + + return ( +
+
+
+

+ Свяжитесь с нами +

+ +
+
+
+ + setFullname(e.target.value)} + /> +
+
+ + setCompany(e.target.value)} + /> +
+
+ +
+
+ + setEmail(e.target.value)} + /> +
+
+ + setPhone(e.target.value)} + /> +
+
+ +
+
+
+
+ +
+ + + +
+
+ +
+
+ +
+
+
+
+
+ ); +} + +export default FeedbackForm; diff --git a/client/src/components/FeedbackFormSuccess.tsx b/client/src/components/FeedbackFormSuccess.tsx new file mode 100644 index 0000000..3dd0258 --- /dev/null +++ b/client/src/components/FeedbackFormSuccess.tsx @@ -0,0 +1,55 @@ +import { FormEvent, useState } from "react"; +import api from "../utils/api"; +import InputMask from "react-input-mask"; +import useModalStore from "../store/modal"; + +function FeedbackForm() { + const [fullname, setFullname] = useState(""); + const [email, setEmail] = useState(""); + const [company, setCompany] = useState(""); + const [phone, setPhone] = useState(""); + + const [modalComponent, setModalComponent] = useModalStore((state) => [ + state.component, + state.setComponent, + ]); + + return ( +
+
+
+
+
+

+ Заявка отправлена +

+ +
+

+ Спасибо за подачу заявки! +

+

+ Мы ценим ваш интерес к нашей компании и уже начинаем работу по + заявке. +

+

+ Мы свяжемся с вами в ближайшее время для уточнения деталей и + ответим на все ваши вопросы. +

+
+
+ + +
+
+
+
+ ); +} + +export default FeedbackForm; diff --git a/client/src/components/Map2.tsx b/client/src/components/Map2.tsx index 4067bd7..2822606 100644 --- a/client/src/components/Map2.tsx +++ b/client/src/components/Map2.tsx @@ -915,8 +915,8 @@ function Map2() { y2="982.338" gradientUnits="userSpaceOnUse" > - - + + diff --git a/client/src/components/Slider/Slider.tsx b/client/src/components/Slider/Slider.tsx index eb5540a..3310a36 100644 --- a/client/src/components/Slider/Slider.tsx +++ b/client/src/components/Slider/Slider.tsx @@ -32,64 +32,18 @@ function Slider() { className="h-full" > - + - + - + + + + - {/*
-
- {realIndex + 1} -
- 8 -
-
- - -
-
*/}
); } diff --git a/client/src/index.css b/client/src/index.css index 5133838..9ce40c9 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -118,8 +118,8 @@ input:checked + div svg { } .swiper-pagination-bullet { - width: 16px; - height: 16px; + width: 12px; + height: 12px; } .swiper-pagination-bullet-active { diff --git a/client/yarn.lock b/client/yarn.lock index c3526db..f9c4a96 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1163,6 +1163,11 @@ react-use-measure@^2.1.1: dependencies: debounce "^1.2.1" +react-yandex-metrika@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/react-yandex-metrika/-/react-yandex-metrika-2.6.0.tgz#9c935c8c7ea5505e34391b9b3e86deb6d50053c9" + integrity sha512-8K4wExsNZtY3DTxh1G8a+zWH9Pg8fw23MJcoJ4I/562qrHRnh7L5nteq3lnNL58dnNQbuuHIRoGgMjIo+r1GjA== + react@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" diff --git a/server/app.js b/server/app.js index 34d7523..27cb567 100644 --- a/server/app.js +++ b/server/app.js @@ -85,9 +85,17 @@ app.post("/mail", async (req, res) => { let info = await transporter.sendMail({ from: `${email}`, // sender address to: "info@graff.tech", // list of receivers - subject: fullname, // Subject line - text: `${company} ${phone}`, // plain text body - html: `${company} ${phone}`, // html body + subject: 'Заявка с сайта estate.graff.tech', // Subject line + text: ` + Имя Фамилия: ${fullname} + Телефон: ${phone} + Компания: ${company} + `, // plain text body + html: `
+

Имя Фамилия: ${fullname}

+

Телефон: ${phone}

+

Компания: ${company}

+
`, // html body }); console.log(info);