upd
This commit is contained in:
@@ -9,5 +9,49 @@
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript">
|
||||
(function (m, e, t, r, i, k, a) {
|
||||
m[i] =
|
||||
m[i] ||
|
||||
function () {
|
||||
(m[i].a = m[i].a || []).push(arguments);
|
||||
};
|
||||
m[i].l = 1 * new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {
|
||||
if (document.scripts[j].src === r) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
(k = e.createElement(t)),
|
||||
(a = e.getElementsByTagName(t)[0]),
|
||||
(k.async = 1),
|
||||
(k.src = r),
|
||||
a.parentNode.insertBefore(k, a);
|
||||
})(
|
||||
window,
|
||||
document,
|
||||
"script",
|
||||
"https://mc.yandex.ru/metrika/tag.js",
|
||||
"ym"
|
||||
);
|
||||
|
||||
ym(93606080, "init", {
|
||||
clickmap: true,
|
||||
trackLinks: true,
|
||||
accurateTrackBounce: true,
|
||||
webvisor: true,
|
||||
});
|
||||
</script>
|
||||
<noscript
|
||||
><div>
|
||||
<img
|
||||
src="https://mc.yandex.ru/watch/93606080"
|
||||
style="position: absolute; left: -9999px"
|
||||
alt=""
|
||||
/></div
|
||||
></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"react-rangeslider": "^2.2.0",
|
||||
"react-router-dom": "^6.18.0",
|
||||
"react-swipeable": "^7.0.1",
|
||||
"react-yandex-metrika": "^2.6.0",
|
||||
"usehooks-ts": "^2.9.1",
|
||||
"zustand": "^4.4.6"
|
||||
},
|
||||
|
||||
Binary file not shown.
+65
-57
@@ -2,7 +2,6 @@
|
||||
import "./App.css";
|
||||
import "react-rangeslider/lib/index.css";
|
||||
import "./components/RangeSlider.css";
|
||||
import { YMInitializer } from "react-yandex-metrika";
|
||||
import IProject from "./types/IProject";
|
||||
import api from "./utils/api";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -34,6 +33,7 @@ function App() {
|
||||
const [projects, setProjects] = useState<IProject[]>([]);
|
||||
const [setModal] = useModalStore((state) => [state.setModal]);
|
||||
const [isShownAllProjects, setIsShownAllProjects] = useState<boolean>(false);
|
||||
const [isBuffering, setIsBuffering] = useState(true);
|
||||
|
||||
async function getProjects() {
|
||||
try {
|
||||
@@ -52,7 +52,7 @@ function App() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="min-h-screen 2xl:px-10 xl:px-8 sm:px-6 px-4 overflow-x-clip">
|
||||
<div className="relative conatiner mx-auto 2xl:max-w-screen-2xl">
|
||||
<div className="flex justify-between py-6 2xl:mb-28 xl:mb-[88px] sm:mb-12 mb-14">
|
||||
@@ -104,66 +104,86 @@ function App() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="2xl:mb-[200px] sm:mb-[120px] mb-20">
|
||||
<div className="relative aspect-video w-full 2xl:mb-[200px] sm:mb-[120px] mb-20">
|
||||
<video
|
||||
src="/videos/showreel_1080p_4000k_h264.mp4"
|
||||
muted
|
||||
autoPlay
|
||||
loop
|
||||
playsInline
|
||||
preload="metadata"
|
||||
className="aspect-video w-full"
|
||||
className="absolute aspect-video w-full"
|
||||
onPlaying={() => setIsBuffering(false)}
|
||||
onWaiting={() => setIsBuffering(true)}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`absolute aspect-video w-full h-full flex justify-center items-center bg-black bg-opacity-50 transition-opacity ${
|
||||
isBuffering ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="flex gap-4 items-center">
|
||||
<span>
|
||||
<svg
|
||||
className="animate-spin h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span className="font-gilroy">Загружаем шоурил...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative conatiner mx-auto 2xl:max-w-screen-2xl">
|
||||
<div className="flex flex-col 2xl:gap-16 xl:gap-10 gap-8 2xl:mb-[200px] sm:mb-[120px] mb-20">
|
||||
<div className="xl:grid flex flex-col xl:grid-cols-4 xl:gap-0 sm:gap-6 gap-4">
|
||||
<Heading2 className="2xl:col-span-full col-span-3">
|
||||
Помогаем <br className="sm:hidden" /> продавать{" "}
|
||||
<br className="2xl:hidden" />{" "}
|
||||
<span style={{ WebkitTextFillColor: "#fff" }}>
|
||||
проще <br className="sm:hidden" /> и{" "}
|
||||
<span
|
||||
style={{ WebkitTextFillColor: "#52587A" }}
|
||||
className="relative custom-line-through"
|
||||
>
|
||||
<Heading2>
|
||||
Продавайте недвижимость
|
||||
<br />
|
||||
<span style={{ WebkitTextFillColor: "#fff" }}>
|
||||
проще и{" "}
|
||||
<span className="relative">
|
||||
<span style={{ WebkitTextFillColor: "#52587A" }}>
|
||||
быстрее
|
||||
</span>{" "}
|
||||
<br className="sm:hidden" />
|
||||
дороже
|
||||
</span>
|
||||
</Heading2>
|
||||
</span>
|
||||
<span className="absolute top-[55%] -left-[2.5%] 2xl:h-1.5 h-1 w-[105%] bg-white"></span>
|
||||
</span>{" "}
|
||||
дороже
|
||||
</span>
|
||||
</Heading2>
|
||||
|
||||
<p className="2xl:hidden 2xl:text-2xl text-sm">
|
||||
Мы собрали статистику за 13 лет работы
|
||||
<div className="relative xl:flex items-center gap-4 xl:max-h-[400px]">
|
||||
<p className="2xl:text-2xl sm:text-xl font-gilroy min-w-fit">
|
||||
Мы собрали статистику{" "}
|
||||
<span className="text-gradient font-semibold">за 13 лет</span>{" "}
|
||||
работы
|
||||
<br />
|
||||
с застройщиками, реализовав 31 проект
|
||||
с застройщиками,{" "}
|
||||
<span className="text-gradient font-semibold">
|
||||
реализовав 31 проект
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div className="2xl:flex hidden relative items-center gap-4 col-span-full h-[400px]">
|
||||
<p className="2xl:text-2xl text-sm font-gilroy font-medium min-w-[496px] ">
|
||||
Мы собрали статистику{" "}
|
||||
<span className="text-gradient font-semibold">за 13 лет</span>{" "}
|
||||
работы
|
||||
<br />
|
||||
с застройщиками,{" "}
|
||||
<span className="text-gradient font-semibold">
|
||||
реализовав 31 проект
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div className="2xl:block hidden">
|
||||
<img
|
||||
src="/images/Map.png"
|
||||
alt=""
|
||||
className="relative top-8 -z-10"
|
||||
/>
|
||||
</div>
|
||||
<div className="2xl:ml-6 xl:ml-[14px] relative 2xl:-top-8 xl:-top-4 -z-10">
|
||||
<img src="/images/Map.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="2xl:block hidden text-gradient font-gilroy font-medium text-5xl w-fit">
|
||||
<p className="2xl:text-[64px] xl:text-5xl text-[40px] text-gradient font-gilroy font-medium w-fit leading-none">
|
||||
Graff.estate
|
||||
</p>
|
||||
|
||||
@@ -409,7 +429,7 @@ function App() {
|
||||
title="ЖК «Айвазовский City»"
|
||||
location="Россия, Тюмень"
|
||||
background="/images/stream/aivaz.jpg"
|
||||
link="https://stream.graff.tech/?build=Ivazowsky&location=a1"
|
||||
link="https://stream.graff.tech/?build=IvazowskyDev&location=a1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -754,19 +774,7 @@ function App() {
|
||||
</div>
|
||||
|
||||
<ModalContainer />
|
||||
|
||||
<YMInitializer
|
||||
accounts={[93606080]}
|
||||
options={{
|
||||
defer: true,
|
||||
webvisor: true,
|
||||
clickmap: true,
|
||||
trackLinks: true,
|
||||
accurateTrackBounce: true,
|
||||
}}
|
||||
version="2"
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import ArrowRightIcon from "./icons/ArrowRightIcon";
|
||||
import regionsData from "../assets/regionsData.json";
|
||||
import { useEffect, useState } from "react";
|
||||
import CloseIcon from "./icons/CloseIcon";
|
||||
import api from "../utils/api";
|
||||
|
||||
interface Region {
|
||||
id: number;
|
||||
@@ -17,9 +18,11 @@ interface Region {
|
||||
}
|
||||
|
||||
function Calc() {
|
||||
const [consultations, setConsultations] = useState<number>(100);
|
||||
const [selectedRegion, setSelectedRegion] = useState<Region>();
|
||||
const [implementationPeriod, setImplementationPeriod] = useState<number>();
|
||||
const [consultations, setConsultations] = useState<number>(100);
|
||||
const [implementationPeriod, setImplementationPeriod] = useState<number>(
|
||||
null!
|
||||
);
|
||||
const [oldImplementationPeriod, setOldImplementationPeriod] =
|
||||
useState<number>();
|
||||
const [monthlyIncome, setMonthlyIncome] = useState<number>();
|
||||
@@ -39,8 +42,23 @@ function Calc() {
|
||||
const [diffImplementationPeriodEnding, setDiffImplementationPeriodEnding] =
|
||||
useState<string>();
|
||||
|
||||
async function getRegionName() {
|
||||
const result: any = await api.get("getRegionName").json();
|
||||
|
||||
if (result.error) {
|
||||
setSelectedRegion(regionsData.find((region) => region.id === 11));
|
||||
return;
|
||||
}
|
||||
|
||||
const foundRegion =
|
||||
regionsData.find((region) => region.name === result.regionName) ||
|
||||
regionsData.find((region) => region.id === 11);
|
||||
|
||||
setSelectedRegion(foundRegion);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedRegion(regionsData.find((region) => region.id === 11));
|
||||
getRegionName();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -94,8 +112,6 @@ function Calc() {
|
||||
}, [monthlyIncome, oldMonthlyIncome]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!implementationPeriod) return;
|
||||
|
||||
if (implementationPeriod > 10 && implementationPeriod < 15) {
|
||||
setOldImplementationPeriodEnding("месяцев");
|
||||
return;
|
||||
@@ -160,21 +176,23 @@ function Calc() {
|
||||
<div className="relative flex flex-col sm:gap-16 gap-8">
|
||||
<div className="grid xl:grid-cols-4 sm:grid-cols-3">
|
||||
<div className="xl:col-auto sm:col-span-full xl:block sm:grid grid-cols-2">
|
||||
<CalcSelect
|
||||
label="Регион"
|
||||
placeholder="Выберите регион"
|
||||
defaultOption={regionsData.find((region) => region.id === 11)?.name}
|
||||
options={regionsData.map((regionItem) => regionItem.name)}
|
||||
handleSelect={(option) => {
|
||||
const foundRegion = regionsData.find(
|
||||
(region) => region.name === option
|
||||
);
|
||||
{selectedRegion && (
|
||||
<CalcSelect
|
||||
label="Регион"
|
||||
placeholder="Выберите регион"
|
||||
defaultOption={selectedRegion.name}
|
||||
options={regionsData.map((regionItem) => regionItem.name)}
|
||||
handleSelect={(option) => {
|
||||
const foundRegion = regionsData.find(
|
||||
(region) => region.name === option
|
||||
);
|
||||
|
||||
if (foundRegion) {
|
||||
setSelectedRegion(foundRegion);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
if (foundRegion) {
|
||||
setSelectedRegion(foundRegion);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="border xl:border-t-0 xl:border-l sm:border-l-0 sm:border-t border-t-0 border-[#3D425C] 2xl:p-6 p-4 flex items-center">
|
||||
<p className="text-[#52587A] text-xs leading-[120%]">
|
||||
Установлены усредненные показатели по региону.
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useOnClickOutside } from "usehooks-ts";
|
||||
interface CalcSelectProps {
|
||||
label: string;
|
||||
placeholder: string;
|
||||
defaultOption?: string;
|
||||
defaultOption: string;
|
||||
options: string[];
|
||||
handleSelect: (option: string) => void;
|
||||
}
|
||||
|
||||
@@ -1491,11 +1491,6 @@ react-swipeable@^7.0.1:
|
||||
resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-7.0.1.tgz#cd299f5986c5e4a7ee979839658c228f660e1e0c"
|
||||
integrity sha512-RKB17JdQzvECfnVj9yDZsiYn3vH0eyva/ZbrCZXZR0qp66PBRhtg4F9yJcJTWYT5Adadi+x4NoG53BxKHwIYLQ==
|
||||
|
||||
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.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
|
||||
@@ -5,6 +5,7 @@ import cors from "cors";
|
||||
import mailRoute from "./routes/mail";
|
||||
import projectRoute from "./routes/projects";
|
||||
import uploadRoute from "./routes/upload";
|
||||
import getRegionNameRoute from "./routes/getRegionName";
|
||||
|
||||
connectDB();
|
||||
|
||||
@@ -17,6 +18,7 @@ app.use(cors({ origin: "*" }));
|
||||
app.use("/mail", mailRoute);
|
||||
app.use("/projects", projectRoute);
|
||||
app.use("/upload", uploadRoute);
|
||||
app.use("/getRegionName", getRegionNameRoute);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is listening on port ${port}`);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Router } from "express";
|
||||
import fs from "fs";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", async (req, res) => {
|
||||
const ip = req.headers["x-forwarded-for"];
|
||||
|
||||
try {
|
||||
const { countryCode, region, regionName }: any = await (
|
||||
await fetch(`http://ip-api.com/json/${ip}?lang=ru`)
|
||||
).json();
|
||||
|
||||
if (countryCode === "RU") {
|
||||
fs.appendFileSync(
|
||||
"./log.txt",
|
||||
`${countryCode}-${region} (${regionName})\n`
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ regionName });
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
res.json({ error: error.message });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const getRegionNameRoute = router;
|
||||
|
||||
export default getRegionNameRoute;
|
||||
Reference in New Issue
Block a user