Merge branch 'master' of http://192.168.1.163:3000/mikhail_lanskikh/graff.estate-nextjs-updated
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 763 KiB After Width: | Height: | Size: 762 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 349 KiB After Width: | Height: | Size: 360 KiB |
@@ -85,7 +85,20 @@ export function MapPointProjectFormModal<TAction extends "create" | "edit">({
|
|||||||
className="px-8 py-7 rounded-2xl outline-none bg-[#37393B99] bg-no-repeat bg-[right_32px_top_24px] h-fit font-medium btnl appearance-none"
|
className="px-8 py-7 rounded-2xl outline-none bg-[#37393B99] bg-no-repeat bg-[right_32px_top_24px] h-fit font-medium btnl appearance-none"
|
||||||
>
|
>
|
||||||
<option value={undefined}>Не выбрано</option>
|
<option value={undefined}>Не выбрано</option>
|
||||||
{companies?.map((company) => (
|
{companies
|
||||||
|
?.sort((a, b) => {
|
||||||
|
// Проверяем, содержит ли название русские символы
|
||||||
|
const aHasCyrillic = /[а-яё]/i.test(a.title);
|
||||||
|
const bHasCyrillic = /[а-яё]/i.test(b.title);
|
||||||
|
|
||||||
|
// Если одно название латиница, а другое кириллица
|
||||||
|
if (!aHasCyrillic && bHasCyrillic) return -1; // латиница идет первой
|
||||||
|
if (aHasCyrillic && !bHasCyrillic) return 1; // кириллица идет второй
|
||||||
|
|
||||||
|
// Если оба одного типа, сортируем алфавитно
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
})
|
||||||
|
.map((company) => (
|
||||||
<option
|
<option
|
||||||
key={company.id}
|
key={company.id}
|
||||||
value={company.id}
|
value={company.id}
|
||||||
|
|||||||
@@ -130,7 +130,20 @@ export function ProjectFormModal<TAction extends "create" | "edit">({
|
|||||||
className="px-8 py-7 rounded-2xl outline-none bg-[#37393B99] bg-no-repeat bg-[right_32px_top_24px] h-fit font-medium btnl appearance-none"
|
className="px-8 py-7 rounded-2xl outline-none bg-[#37393B99] bg-no-repeat bg-[right_32px_top_24px] h-fit font-medium btnl appearance-none"
|
||||||
>
|
>
|
||||||
<option value={undefined}>Не выбрано</option>
|
<option value={undefined}>Не выбрано</option>
|
||||||
{companies?.map((company) => (
|
{companies
|
||||||
|
?.sort((a, b) => {
|
||||||
|
// Проверяем, содержит ли название русские символы
|
||||||
|
const aHasCyrillic = /[а-яё]/i.test(a.title);
|
||||||
|
const bHasCyrillic = /[а-яё]/i.test(b.title);
|
||||||
|
|
||||||
|
// Если одно название латиница, а другое кириллица
|
||||||
|
if (!aHasCyrillic && bHasCyrillic) return -1; // латиница идет первой
|
||||||
|
if (aHasCyrillic && !bHasCyrillic) return 1; // кириллица идет второй
|
||||||
|
|
||||||
|
// Если оба одного типа, сортируем алфавитно
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
})
|
||||||
|
.map((company) => (
|
||||||
<option
|
<option
|
||||||
key={company.id}
|
key={company.id}
|
||||||
value={company.id}
|
value={company.id}
|
||||||
|
|||||||
@@ -1,86 +1,241 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Button } from "@/ui/Button";
|
import { Button } from "@/ui/Button";
|
||||||
import QuestionFormInput from "@/ui/QuestionFormInput";
|
import QuestionFormInput from "@/ui/QuestionFormInput";
|
||||||
import { useOnClickOutside } from "usehooks-ts";
|
import { useOnClickOutside } from "usehooks-ts";
|
||||||
import { useModalStore } from "@/stores/useModalStore";
|
import { useModalStore } from "@/stores/useModalStore";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import CheckIcon from "../icons/CheckIcon";
|
||||||
|
import ReactInputMask from "react-input-mask";
|
||||||
|
import { projectsTags } from "@/consts/projectsTags";
|
||||||
|
import { CheckboxesGroup } from "@/ui/CheckboxesGroup";
|
||||||
|
import { Product } from "@/types/Product";
|
||||||
|
import FeedbackModal from "./FeedbackFormModal";
|
||||||
|
import { api } from "@/api";
|
||||||
|
import { useRefererStore } from "@/stores/useRefererStore";
|
||||||
|
import { Country } from "react-phone-number-input";
|
||||||
|
import { getExampleNumber } from "libphonenumber-js";
|
||||||
|
import examples from "libphonenumber-js/mobile/examples";
|
||||||
|
|
||||||
|
interface IInput {
|
||||||
|
fullname: string;
|
||||||
|
phone: string;
|
||||||
|
email: string;
|
||||||
|
products: Product[];
|
||||||
|
referer?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export default function QuestionFormModal() {
|
export default function QuestionFormModal() {
|
||||||
const { setModal } = useModalStore();
|
const [[phoneCode, country], setPhoneCodeAndCountry] = useState<
|
||||||
|
[string, Country]
|
||||||
|
>(["+7", "RU"]);
|
||||||
|
|
||||||
|
const { referer } = useRefererStore();
|
||||||
|
const { modal, setModal } = useModalStore();
|
||||||
|
|
||||||
|
const placeholder = useMemo(
|
||||||
|
() =>
|
||||||
|
getExampleNumber(country, examples)
|
||||||
|
?.formatInternational()
|
||||||
|
.split(" ")
|
||||||
|
.slice(1)
|
||||||
|
.join(" "),
|
||||||
|
[country]
|
||||||
|
);
|
||||||
|
|
||||||
const formRef = useRef(null);
|
const formRef = useRef(null);
|
||||||
|
|
||||||
useOnClickOutside(formRef, () => {
|
useOnClickOutside(formRef, () => {
|
||||||
setModal(null);
|
setModal(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const form = useForm<IInput>({
|
||||||
|
defaultValues: {
|
||||||
|
products: ["Создание сайтов", "Web-тур по 360 сферам"] as Product[],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register, handleSubmit, formState, setValue } = form;
|
||||||
|
|
||||||
|
async function onSubmit(data: IInput) {
|
||||||
|
const { id } = await api
|
||||||
|
.post("mail", { json: { ...data, referer } })
|
||||||
|
.json<{ id: string }>();
|
||||||
|
setModal(<FeedbackModal id={id} />);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
|
||||||
ref={formRef}
|
|
||||||
className="p-[3.333vw] w-[64.514vw] backdrop-blur-[20px] rounded-[1.111vw] z-10 bg-[radial-gradient(circle_at_bottom_right,rgba(24,25,26,0.84)_0%,rgba(45,46,47,0.86)_100%)] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
|
|
||||||
max-md:w-[94.444vw] max-md:px-[6.667vw] max-md:py-[5.556vw] max-md:rounded-[4.444vw]"
|
|
||||||
>
|
|
||||||
<h3 className="text-center accent bg-gradient-to-r from-[#FF79D2] to-[#C932E8] bg-clip-text mb-[1.389vw] max-md:mb-[5.556vw]">
|
|
||||||
<span className="text-transparent bg-clip-text">
|
|
||||||
{" "}
|
|
||||||
Напишите свой вопрос,{" "}
|
|
||||||
</span>
|
|
||||||
<br /> мы свяжемся с вами <br className="hidden max-md:block" /> в
|
|
||||||
ближайшее время
|
|
||||||
<br /> и ответим на него
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="flex justify-between w-full mb-[1.944vw] gap-[1.944vw]
|
ref={formRef}
|
||||||
max-md:flex-col max-md:gap-[3.333vw] max-md:mb-[3.333vw]"
|
className="p-[3.333vw] w-[64.514vw] backdrop-blur-[20px] rounded-[1.111vw] z-10 bg-[radial-gradient(circle_at_bottom_right,rgba(24,25,26,0.84)_0%,rgba(45,46,47,0.86)_100%)] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-md:w-[94.444vw] max-md:px-[6.667vw] max-md:py-[5.556vw] max-md:rounded-[4.444vw]"
|
||||||
>
|
>
|
||||||
<QuestionFormInput
|
{!formState.isSubmitted ? (
|
||||||
type={"text"}
|
<FormProvider {...form}>
|
||||||
name={"web-q-name"}
|
<form
|
||||||
placeholder={"Имя"}
|
className="lg:space-y-[1.944vw] md:max-lg:space-y-7 space-y-3"
|
||||||
activeFieldName={"name"}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
required
|
>
|
||||||
/>
|
<div className="lg:space-y-[1.111vw] space-y-4">
|
||||||
<QuestionFormInput
|
<p className="heading2 font-medium">Нам нужно</p>
|
||||||
type={"email"}
|
<CheckboxesGroup name="products" options={projectsTags} />
|
||||||
name={"web-q-email"}
|
|
||||||
placeholder={"E-mail"}
|
|
||||||
activeFieldName={"email"}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input
|
||||||
<QuestionFormInput
|
id="name"
|
||||||
type={"text"}
|
autoComplete="none"
|
||||||
name={"web-q-content"}
|
type="text"
|
||||||
placeholder={"Ваш вопрос"}
|
required
|
||||||
activeFieldName={"content"}
|
placeholder="Имя*"
|
||||||
|
{...register("fullname")}
|
||||||
|
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl btnl placeholder:font-medium placeholder:select-none"
|
||||||
/>
|
/>
|
||||||
|
<input
|
||||||
|
autoComplete="none"
|
||||||
|
required
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Email*"
|
||||||
|
{...register("email")}
|
||||||
|
className="bg-transparent border-b border-[#37393B] focus:border-white py-4 rounded-none btnl outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||||
|
/>
|
||||||
|
<div className="flex gap-x-3 py-2 border-[#3D425C] relative">
|
||||||
|
<ReactInputMask
|
||||||
|
type="tel"
|
||||||
|
autoComplete="tel"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.nativeEvent.type.startsWith("input")) {
|
||||||
|
const cleanValue = e.target.value.replaceAll(/ /g, "");
|
||||||
|
const inputType = (e.nativeEvent as InputEvent)?.inputType;
|
||||||
|
|
||||||
<div className="text-center max-md:mt-[6.667vw]">
|
const shouldAddPhoneCode =
|
||||||
|
inputType !== "insertFromPaste" &&
|
||||||
|
inputType !== "insertFromDrop" &&
|
||||||
|
inputType !== "insertCompositionText" &&
|
||||||
|
!cleanValue.startsWith("+") &&
|
||||||
|
!cleanValue.startsWith("7") &&
|
||||||
|
!cleanValue.startsWith(phoneCode.replace("+", ""));
|
||||||
|
|
||||||
|
form.setValue(
|
||||||
|
"phone",
|
||||||
|
(shouldAddPhoneCode ? phoneCode : "") + cleanValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
id={"tel"}
|
||||||
|
maskChar={null}
|
||||||
|
mask={"+7 " + (placeholder?.replace(/\d/g, "9") ?? "")}
|
||||||
|
placeholder={"+7 " + placeholder}
|
||||||
|
className="placeholder:btnl placeholder:font-medium placeholder:select-none peer btnl w-full h-full bg-transparent rounded-none transition-all outline-none"
|
||||||
|
/>
|
||||||
|
<div className="bottom-0 absolute w-full border-b border-[#37393B] peer-focus:border-white -mb-2" />
|
||||||
|
</div>
|
||||||
|
<div className="md:flex items-stretch lg:gap-[0.833vw] gap-3">
|
||||||
<Button
|
<Button
|
||||||
className="rounded-[1.111vw] py-[1.389vw] px-[2.778vw] mx-auto mt-[1.944vw] mb-[0.833vw] btn-l max-md:w-full max-md:py-[4.556vw] max-md:rounded-[4.444vw] max-md:mb-[3.333vw]"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
|
className="btnl max-md:mb-3 max-md:w-full lg:px-[2.222vw] lg:py-[1.389vw] px-8 py-5 cursor-pointer lg:rounded-[1.111vw] rounded-2xl"
|
||||||
>
|
>
|
||||||
Отправить
|
Оставить заявку
|
||||||
</Button>
|
</Button>
|
||||||
<span className="text2 w-[31.875vw] text-[#7A7A7A] max-md:text-[3.333vw]">
|
<div className="text2 xl:max-w-[60%] md:max-lg:max-w-[40%] md:max-lg:py-1">
|
||||||
*Нажимая кнопку отправить, вы принимаете
|
<span className="text-[#7A7A7A]">
|
||||||
<a
|
*Нажимая кнопку отправить, вы даете
|
||||||
href="/policy"
|
</span>{" "}
|
||||||
target="_blank"
|
|
||||||
className="text-[#FFFFFF] cursor-pointer hover:underline"
|
|
||||||
>
|
|
||||||
условия использования{" "}
|
|
||||||
</a>
|
|
||||||
<br className="block max-md:hidden" /> и
|
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={"/privacy-policy"}
|
href={"/privacy-policy"}
|
||||||
className="text-[#FFFFFF] cursor-pointer hover:underline"
|
className="underline"
|
||||||
>
|
>
|
||||||
политику конфиденциальности
|
согласие на обработку персональных данных
|
||||||
|
</a>{" "}
|
||||||
|
<span className="text-[#7A7A7A]">и принимаете </span>
|
||||||
|
<a target="_blank" href={"/policy"} className="underline">
|
||||||
|
условия политики
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
) : (
|
||||||
|
<div className="bg-[#37393B99] aspect-[643/480] w-full rounded-2xl flex justify-center items-center">
|
||||||
|
<div className="flex gap-3 justify-center items-center">
|
||||||
|
<div className="bg-gradient p-3 rounded-full">
|
||||||
|
<div className="text-white lg:size-[1.667vw] size-6">
|
||||||
|
<CheckIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="heading2 font-medium">
|
||||||
|
Мы получили заявку
|
||||||
|
<br />и скоро свяжемся с вами!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
// <form
|
||||||
|
// ref={formRef}
|
||||||
|
// className="p-[3.333vw] w-[64.514vw] backdrop-blur-[20px] rounded-[1.111vw] z-10 bg-[radial-gradient(circle_at_bottom_right,rgba(24,25,26,0.84)_0%,rgba(45,46,47,0.86)_100%)] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-md:w-[94.444vw] max-md:px-[6.667vw] max-md:py-[5.556vw] max-md:rounded-[4.444vw]"
|
||||||
|
// >
|
||||||
|
// <h3 className="text-center accent bg-gradient-to-r from-[#FF79D2] to-[#C932E8] bg-clip-text mb-[1.389vw] max-md:mb-[5.556vw]">
|
||||||
|
// <span className="text-transparent bg-clip-text">
|
||||||
|
// {" "}
|
||||||
|
// Напишите свой вопрос,{" "}
|
||||||
|
// </span>
|
||||||
|
// <br /> мы свяжемся с вами <br className="max-md:block hidden" /> в
|
||||||
|
// ближайшее время
|
||||||
|
// <br /> и ответим на него
|
||||||
|
// </h3>
|
||||||
|
|
||||||
|
// <div
|
||||||
|
// className="flex justify-between w-full mb-[1.944vw] gap-[1.944vw]
|
||||||
|
// max-md:flex-col max-md:gap-[3.333vw] max-md:mb-[3.333vw]"
|
||||||
|
// >
|
||||||
|
// <QuestionFormInput
|
||||||
|
// type={"text"}
|
||||||
|
// name={"web-q-name"}
|
||||||
|
// placeholder={"Имя"}
|
||||||
|
// activeFieldName={"name"}
|
||||||
|
// required
|
||||||
|
// />
|
||||||
|
// <QuestionFormInput
|
||||||
|
// type={"email"}
|
||||||
|
// name={"web-q-email"}
|
||||||
|
// placeholder={"E-mail"}
|
||||||
|
// activeFieldName={"email"}
|
||||||
|
// required
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <QuestionFormInput
|
||||||
|
// type={"text"}
|
||||||
|
// name={"web-q-content"}
|
||||||
|
// placeholder={"Ваш вопрос"}
|
||||||
|
// activeFieldName={"content"}
|
||||||
|
// />
|
||||||
|
|
||||||
|
// <div className="text-center max-md:mt-[6.667vw]">
|
||||||
|
// <Button
|
||||||
|
// className="rounded-[1.111vw] py-[1.389vw] px-[2.778vw] mx-auto mt-[1.944vw] mb-[0.833vw] btn-l max-md:w-full max-md:py-[4.556vw] max-md:rounded-[4.444vw] max-md:mb-[3.333vw]"
|
||||||
|
// type="submit"
|
||||||
|
// >
|
||||||
|
// Отправить
|
||||||
|
// </Button>
|
||||||
|
// <span className="text2 w-[31.875vw] text-[#7A7A7A] max-md:text-[3.333vw]">
|
||||||
|
// *Нажимая кнопку отправить, вы принимаете
|
||||||
|
// <a
|
||||||
|
// href="/policy"
|
||||||
|
// target="_blank"
|
||||||
|
// className="text-[#FFFFFF] cursor-pointer hover:underline"
|
||||||
|
// >
|
||||||
|
// условия использования{" "}
|
||||||
|
// </a>
|
||||||
|
// <br className="max-md:hidden block" /> и
|
||||||
|
// <a
|
||||||
|
// target="_blank"
|
||||||
|
// href={"/privacy-policy"}
|
||||||
|
// className="text-[#FFFFFF] cursor-pointer hover:underline"
|
||||||
|
// >
|
||||||
|
// политику конфиденциальности
|
||||||
|
// </a>
|
||||||
|
// </span>
|
||||||
|
// </div>
|
||||||
|
// </form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,14 +46,14 @@ export default function AboutEventCard({
|
|||||||
style={{ backgroundColor: color }}
|
style={{ backgroundColor: color }}
|
||||||
className="size-2.5 rounded-full mr-2"
|
className="size-2.5 rounded-full mr-2"
|
||||||
/>
|
/>
|
||||||
<span className="font-medium">{location}</span>
|
<span className="btnm font-medium">{location}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="py-2.5 px-[0.833vw] rounded-[1.111vw] border border-[#FFFFFF1A]
|
className="py-2.5 px-[0.833vw] rounded-[1.111vw] border border-[#FFFFFF1A]
|
||||||
max-lg:w-max md:max-lg:py-[1.432vw] md:max-lg:px-[2.604vw] md:max-lg:rounded-full
|
max-lg:w-max md:max-lg:py-[1.432vw] md:max-lg:px-[2.604vw] md:max-lg:rounded-full
|
||||||
max-md:rounded-[4.444vw] max-md:px-[3.333vw] max-md:py-[1.333vw]"
|
max-md:rounded-[4.444vw] max-md:px-[3.333vw] max-md:py-[1.333vw]"
|
||||||
>
|
>
|
||||||
<span className="font-medium">
|
<span className="btnm font-medium">
|
||||||
{dates
|
{dates
|
||||||
.map((date) => format(date, "d MMMM", { locale: ru }))
|
.map((date) => format(date, "d MMMM", { locale: ru }))
|
||||||
.join(" – ")}
|
.join(" – ")}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function AboutEvents() {
|
|||||||
</span>
|
</span>
|
||||||
где каждый девелопер может лично протестировать{" "}
|
где каждый девелопер может лично протестировать{" "}
|
||||||
<br className="md:max-lg:block hidden" />
|
<br className="md:max-lg:block hidden" />
|
||||||
и оценить функционал наших интерактивных макетов недвижимости
|
и оценить функционал наших интерактивных инструментов продаж
|
||||||
</p>
|
</p>
|
||||||
<OpenFormModalWrapper modal={<EventFormModal action="create" />}>
|
<OpenFormModalWrapper modal={<EventFormModal action="create" />}>
|
||||||
<GradientButton>
|
<GradientButton>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default function AboutExperience() {
|
|||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ y: 100, opacity: 0 }}
|
initial={{ y: 100, opacity: 0 }}
|
||||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
whileInView={{ y: 0, opacity: 1, repeatCount: 1 }}
|
||||||
transition={{ delay: 0.4, bounce: 0, duration: 0.5 }}
|
transition={{ delay: 0.4, bounce: 0, duration: 0.5 }}
|
||||||
className="[grid-area:a] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col items-center justify-between p-[1.667vw]
|
className="[grid-area:a] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col items-center justify-between p-[1.667vw]
|
||||||
md:max-lg:p-[3.125vw]
|
md:max-lg:p-[3.125vw]
|
||||||
@@ -67,7 +67,7 @@ export default function AboutExperience() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ y: 100, opacity: 0 }}
|
initial={{ y: 100, opacity: 0 }}
|
||||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
whileInView={{ y: 0, opacity: 1, repeatCount: 1 }}
|
||||||
transition={{ delay: 0.6, bounce: 0, duration: 0.5 }}
|
transition={{ delay: 0.6, bounce: 0, duration: 0.5 }}
|
||||||
className="relative overflow-hidden [grid-area:d] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col-reverse items-center justify-between p-[1.667vw]
|
className="relative overflow-hidden [grid-area:d] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col-reverse items-center justify-between p-[1.667vw]
|
||||||
md:max-lg:p-[3.125vw]
|
md:max-lg:p-[3.125vw]
|
||||||
@@ -84,7 +84,7 @@ export default function AboutExperience() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ y: 100, opacity: 0 }}
|
initial={{ y: 100, opacity: 0 }}
|
||||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
whileInView={{ y: 0, opacity: 1, repeatCount: 1 }}
|
||||||
transition={{ bounce: 0, duration: 0.5 }}
|
transition={{ bounce: 0, duration: 0.5 }}
|
||||||
className="relative [grid-area:b] rounded-2xl
|
className="relative [grid-area:b] rounded-2xl
|
||||||
max-lg:border max-lg:border-[#FFFFFF1A] md:max-lg:p-[3.125vw]
|
max-lg:border max-lg:border-[#FFFFFF1A] md:max-lg:p-[3.125vw]
|
||||||
@@ -101,7 +101,7 @@ export default function AboutExperience() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ y: 100, opacity: 0 }}
|
initial={{ y: 100, opacity: 0 }}
|
||||||
whileInView={{ y: 0, opacity: 1, repeatCount: 0 }}
|
whileInView={{ y: 0, opacity: 1, repeatCount: 1 }}
|
||||||
transition={{ delay: 0.8, bounce: 0, duration: 0.5 }}
|
transition={{ delay: 0.8, bounce: 0, duration: 0.5 }}
|
||||||
className="relative overflow-hidden [grid-area:c] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col-reverse items-center justify-between p-[1.667vw]
|
className="relative overflow-hidden [grid-area:c] w-full aspect-[340/360] rounded-2xl border border-[#FFFFFF1A] flex flex-col-reverse items-center justify-between p-[1.667vw]
|
||||||
md:max-lg:p-[3.125vw]
|
md:max-lg:p-[3.125vw]
|
||||||
@@ -130,7 +130,7 @@ export default function AboutExperience() {
|
|||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<span className="headline1 text-center">
|
<span className="headline1 text-center">
|
||||||
Виртуальные туры <br /> по 360 сферам
|
Виртуальные туры <br /> по 360° сферам
|
||||||
</span>
|
</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,9 +153,9 @@ export default function AboutExperience() {
|
|||||||
в 40 выставках
|
в 40 выставках
|
||||||
</h3>
|
</h3>
|
||||||
<p className="headline1 text-[#7A7A7A]">
|
<p className="headline1 text-[#7A7A7A]">
|
||||||
Наш продукт GRAFF.estate уже четыре раза был представлен
|
Продукты GRAFF.estate уже четыре раза были представлены
|
||||||
на форуме “Движение”, Иннопром, Восточный экономический форум,
|
на форуме «Движение», Восточном экономическом форуме, Иннопроме
|
||||||
100+ TechnoBuild
|
и форуме 100+ TechnoBuild
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -247,16 +247,18 @@ export default function AboutExperience() {
|
|||||||
|
|
||||||
<DisplacementCard
|
<DisplacementCard
|
||||||
index={1}
|
index={1}
|
||||||
className="lg:top-[8.333vw] lg:left-0 md:max-lg:!translate-y-[3.125vw]"
|
className={
|
||||||
|
"lg:top-[8.333vw] lg:left-0 md:max-lg:!translate-y-[3.125vw] !aspect-[223/260]"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={"/img/pages/about/experience/cards/wow.png"}
|
src={"/img/pages/about/experience/cards/wow.png"}
|
||||||
className="lg:w-[9.444vw] lg:h-[9.444vw] md:w-[17.708vw] md:h-[17.708vw] w-[33.333vw] h-[33.333vw] my-auto object-cover hover:scale-[1.15] transition-all duration-300"
|
className="lg:w-[9.444vw] lg:h-[9.444vw] md:w-[17.708vw] md:h-[17.708vw] w-[33.333vw] h-[33.333vw] my-auto object-cover hover:scale-[1.15] transition-all duration-300"
|
||||||
alt="Заняли 1 место на WOW AWARDS 2024 совместно с застройщиком Upside Development"
|
alt="Заняли 1 место на WOW AWARDS 2024 совместно с застройщиком Upside Development"
|
||||||
/>
|
/>
|
||||||
<p className="z-10 text-center btns md:max-lg:text-[1.563vw] max-md:text-[3.333vw]">
|
<p className="z-10 text-center btns !leading-[120%]">
|
||||||
Мы заняли 1 место на WOW AWARDS 2024 совместно c застройщиком
|
Мы дважды заняли 1 место на WOW AWARDS в 2024 и 2025 году
|
||||||
Upside Development
|
совместно с застройщиками Upside Development и LEGENDA
|
||||||
</p>
|
</p>
|
||||||
</DisplacementCard>
|
</DisplacementCard>
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ export default function Accordeon({
|
|||||||
ease: "easeIn",
|
ease: "easeIn",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className="text1 lg:text-[0.972vw] md:text-[1.823vw] text-[3.889vw]"
|
|
||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -168,6 +167,7 @@ export default function Accordeon({
|
|||||||
display: isFaqPage ? "inline-block" : "inline",
|
display: isFaqPage ? "inline-block" : "inline",
|
||||||
opacity: isFaqPage ? 0.6 : 1,
|
opacity: isFaqPage ? 0.6 : 1,
|
||||||
}}
|
}}
|
||||||
|
className={isFaqPage ? "text1" : "text2"}
|
||||||
>
|
>
|
||||||
{item.content}
|
{item.content}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
+12
-12
@@ -7,35 +7,35 @@ function Iphone({ active }: { active: boolean }) {
|
|||||||
return (
|
return (
|
||||||
<div className="relative w-[17vw] h-auto aspect-[576/1182] items-center mx-auto my-auto md:max-lg:w-[24.881vw] max-md:w-[50vw]">
|
<div className="relative w-[17vw] h-auto aspect-[576/1182] items-center mx-auto my-auto md:max-lg:w-[24.881vw] max-md:w-[50vw]">
|
||||||
<motion.img
|
<motion.img
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0, scale: 0.75 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1, scale: active ? 1 : 0.75 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0, scale: 0.75 }}
|
||||||
src="/img/pages/web/iphone.png"
|
src="/img/pages/web/iphone.png"
|
||||||
className="absolute w-full h-full z-[8]"
|
className={`z-[8] absolute w-full h-full`}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<AnimatePresence mode="popLayout">
|
<AnimatePresence mode="popLayout">
|
||||||
{active ? (
|
{active ? (
|
||||||
<motion.video
|
<motion.video
|
||||||
key={1}
|
key={1}
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0, scale: 0.75 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1, scale: active ? 1 : 0.75 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0, scale: 0.75 }}
|
||||||
src={"/videos/pages/web/iphone_vid.mp4"}
|
src={"/videos/pages/web/iphone_vid.mp4"}
|
||||||
muted
|
muted
|
||||||
autoPlay
|
autoPlay
|
||||||
loop
|
loop
|
||||||
className="mx-auto w-[98%] h-[99%] translate-y-[0.5%] z-[7] lg:rounded-[3vw] rounded-2xl md:max-lg:rounded-[4vw] max-md:rounded-[8vw]"
|
className={`mx-auto w-[98%] h-[99%] translate-y-[0.5%] z-[7] lg:rounded-[3vw] rounded-2xl md:max-lg:rounded-[4vw] max-md:rounded-[8vw]`}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<motion.img
|
<motion.img
|
||||||
key={2}
|
key={2}
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0, scale: 0.75 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1, scale: active ? 1 : 0.75 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0, scale: 0.75 }}
|
||||||
src={"/img/pages/web/content/iphone_disabled.png"}
|
src={"/img/pages/web/content/iphone_disabled.png"}
|
||||||
alt=""
|
alt=""
|
||||||
className="mx-auto w-auto h-full lg:rounded-[3.333vw] md:rounded-[4.444vw] rounded-3xl"
|
className={`mx-auto w-auto h-full lg:rounded-[3.333vw] md:rounded-[4.444vw] rounded-3xl`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export default function WebDemo() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="z-10 text-center btns md:max-lg:text-[1.563vw] max-md:text-[3.333vw]">
|
<p className="z-10 text-center btns md:max-lg:text-[1.563vw] max-md:text-[3.333vw]">
|
||||||
Люди гуляют в окрестностях ЖК
|
Люди гуляют в окрестностях
|
||||||
</p>
|
</p>
|
||||||
</DisplacementCard>
|
</DisplacementCard>
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ export default function WebDemo() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="z-10 text-center btns md:max-lg:text-[1.563vw] max-md:text-[3.333vw]">
|
<p className="z-10 text-center btns md:max-lg:text-[1.563vw] max-md:text-[3.333vw]">
|
||||||
Фасады ЖК можно изучить со всех сторон
|
Фасады можно изучить со всех сторон
|
||||||
</p>
|
</p>
|
||||||
</DisplacementCard>
|
</DisplacementCard>
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "new",
|
type: "new",
|
||||||
content:
|
content: "Получаем исходные данные: чертежи, фотографии, сканы помещений",
|
||||||
"Получаем исходные данные: чертежи, фотографии, сканы помещений",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "general",
|
type: "general",
|
||||||
@@ -36,22 +35,25 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
"Прототипирование": [
|
Прототипирование: [
|
||||||
{
|
{
|
||||||
type: "new",
|
type: "new",
|
||||||
content: "Моделируем пространства в Blender и 3Ds Max",
|
content: "Моделируем пространства в Blender и 3Ds Max",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "general",
|
type: "general",
|
||||||
content: "Оптимизируем полигоны для использования на сайте: уменьшаем нагрузку",
|
content:
|
||||||
|
"Оптимизируем полигоны для использования на сайте: уменьшаем нагрузку",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "new",
|
type: "new",
|
||||||
content: "Создаем материалы и текстуры для реализма картинки с помощью PBR-рендеринга ",
|
content:
|
||||||
|
"Создаем материалы и текстуры для реализма картинки с помощью PBR-рендеринга ",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "general",
|
type: "general",
|
||||||
content: "Создаем предварительный рендер и согласовываем общий стиль проекта",
|
content:
|
||||||
|
"Создаем предварительный рендер и согласовываем общий стиль проекта",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -68,10 +70,6 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
|||||||
type: "general",
|
type: "general",
|
||||||
content: "Отрисовываем дизайн-макеты ключевых блоков",
|
content: "Отрисовываем дизайн-макеты ключевых блоков",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: "general",
|
|
||||||
content: "Согласование с заказчиком и правки",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"Дизайн сайта": [
|
"Дизайн сайта": [
|
||||||
@@ -91,16 +89,13 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
|||||||
type: "general",
|
type: "general",
|
||||||
content: "Наполняем страницы контентом",
|
content: "Наполняем страницы контентом",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: "general",
|
|
||||||
content: "Согласование с заказчиком и правки",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"Сборка сцен": [
|
"Сборка сцен": [
|
||||||
{
|
{
|
||||||
type: "general",
|
type: "general",
|
||||||
content: "Компонуем виртуальные сцены: расставляем мебель, освещение и камеры",
|
content:
|
||||||
|
"Компонуем виртуальные сцены: расставляем мебель, освещение и камеры",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "general",
|
type: "general",
|
||||||
@@ -108,7 +103,7 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
|||||||
"Готовим тестовые интерактивные 360° панорамы и экспортируем в веб-формат",
|
"Готовим тестовые интерактивные 360° панорамы и экспортируем в веб-формат",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"Рендеринг": [
|
Рендеринг: [
|
||||||
{
|
{
|
||||||
type: "general",
|
type: "general",
|
||||||
content: "Готовим финальные версии всех интерактивных 360° панорам ",
|
content: "Готовим финальные версии всех интерактивных 360° панорам ",
|
||||||
@@ -129,10 +124,11 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "general",
|
type: "general",
|
||||||
content: "Адаптируем страницы под мобильные устройства: жесты, touch-управление",
|
content:
|
||||||
|
"Адаптируем страницы под мобильные устройства: жесты, touch-управление",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"Оптимизация": [
|
Оптимизация: [
|
||||||
{
|
{
|
||||||
type: "general",
|
type: "general",
|
||||||
content: "Тестируем готовый сайт на разных устройствах.",
|
content: "Тестируем готовый сайт на разных устройствах.",
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useModalStore } from "@/stores/useModalStore";
|
import { useModalStore } from "@/stores/useModalStore";
|
||||||
|
import { useAuthStore } from "@/stores/useAuthStore";
|
||||||
|
import { useCheckAuthQuery } from "@/queries/checkAuth";
|
||||||
|
|
||||||
export function LenisProvider({ children }: PropsWithChildren) {
|
export function LenisProvider({ children }: PropsWithChildren) {
|
||||||
|
const { data: auth } = useCheckAuthQuery();
|
||||||
const lenis = useRef<LenisRef>(null);
|
const lenis = useRef<LenisRef>(null);
|
||||||
const { modal } = useModalStore();
|
const { modal } = useModalStore();
|
||||||
const [lenisKey, setLenisKey] = useState(0);
|
const [lenisKey, setLenisKey] = useState(0);
|
||||||
@@ -35,6 +38,7 @@ export function LenisProvider({ children }: PropsWithChildren) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!auth) return;
|
||||||
if (modal) {
|
if (modal) {
|
||||||
// Отключаем Lenis и блокируем скролл body
|
// Отключаем Lenis и блокируем скролл body
|
||||||
lenis.current?.lenis?.destroy();
|
lenis.current?.lenis?.destroy();
|
||||||
@@ -61,7 +65,7 @@ export function LenisProvider({ children }: PropsWithChildren) {
|
|||||||
lenis.current?.lenis?.start();
|
lenis.current?.lenis?.start();
|
||||||
document.body.style.overflow = "";
|
document.body.style.overflow = "";
|
||||||
}
|
}
|
||||||
}, [modal]);
|
}, [modal, auth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactLenis
|
<ReactLenis
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { api } from '@/api';
|
import { api } from "@/api";
|
||||||
import { useAuthStore } from '@/stores/useAuthStore';
|
import { useAuthStore } from "@/stores/useAuthStore";
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
export function useCheckAuthQuery() {
|
export function useCheckAuthQuery() {
|
||||||
const { token } = useAuthStore();
|
const { token } = useAuthStore();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['checkAuth'],
|
queryKey: ["checkAuth"],
|
||||||
queryFn: () => api.get('auth/check').json<{ auth: boolean }>(),
|
queryFn: () => api.get("auth/check").json<{ auth: boolean }>(),
|
||||||
select: (data) => data.auth,
|
select: (data) => data.auth,
|
||||||
enabled: !!token,
|
enabled: !!token,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user