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"
|
||||
>
|
||||
<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
|
||||
key={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"
|
||||
>
|
||||
<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
|
||||
key={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 QuestionFormInput from "@/ui/QuestionFormInput";
|
||||
import { useOnClickOutside } from "usehooks-ts";
|
||||
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() {
|
||||
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);
|
||||
|
||||
useOnClickOutside(formRef, () => {
|
||||
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 (
|
||||
<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
|
||||
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]"
|
||||
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]"
|
||||
>
|
||||
<QuestionFormInput
|
||||
type={"text"}
|
||||
name={"web-q-name"}
|
||||
placeholder={"Имя"}
|
||||
activeFieldName={"name"}
|
||||
required
|
||||
/>
|
||||
<QuestionFormInput
|
||||
type={"email"}
|
||||
name={"web-q-email"}
|
||||
placeholder={"E-mail"}
|
||||
activeFieldName={"email"}
|
||||
required
|
||||
/>
|
||||
{!formState.isSubmitted ? (
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
className="lg:space-y-[1.944vw] md:max-lg:space-y-7 space-y-3"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="lg:space-y-[1.111vw] space-y-4">
|
||||
<p className="heading2 font-medium">Нам нужно</p>
|
||||
<CheckboxesGroup name="products" options={projectsTags} />
|
||||
</div>
|
||||
|
||||
<QuestionFormInput
|
||||
type={"text"}
|
||||
name={"web-q-content"}
|
||||
placeholder={"Ваш вопрос"}
|
||||
activeFieldName={"content"}
|
||||
<input
|
||||
id="name"
|
||||
autoComplete="none"
|
||||
type="text"
|
||||
required
|
||||
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
|
||||
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"
|
||||
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>
|
||||
<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="block max-md:hidden" /> и
|
||||
<div className="text2 xl:max-w-[60%] md:max-lg:max-w-[40%] md:max-lg:py-1">
|
||||
<span className="text-[#7A7A7A]">
|
||||
*Нажимая кнопку отправить, вы даете
|
||||
</span>{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
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>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</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 }}
|
||||
className="size-2.5 rounded-full mr-2"
|
||||
/>
|
||||
<span className="font-medium">{location}</span>
|
||||
<span className="btnm font-medium">{location}</span>
|
||||
</div>
|
||||
<div
|
||||
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-md:rounded-[4.444vw] max-md:px-[3.333vw] max-md:py-[1.333vw]"
|
||||
>
|
||||
<span className="font-medium">
|
||||
<span className="btnm font-medium">
|
||||
{dates
|
||||
.map((date) => format(date, "d MMMM", { locale: ru }))
|
||||
.join(" – ")}
|
||||
|
||||
@@ -19,7 +19,7 @@ function AboutEvents() {
|
||||
</span>
|
||||
где каждый девелопер может лично протестировать{" "}
|
||||
<br className="md:max-lg:block hidden" />
|
||||
и оценить функционал наших интерактивных макетов недвижимости
|
||||
и оценить функционал наших интерактивных инструментов продаж
|
||||
</p>
|
||||
<OpenFormModalWrapper modal={<EventFormModal action="create" />}>
|
||||
<GradientButton>
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function AboutExperience() {
|
||||
>
|
||||
<motion.div
|
||||
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 }}
|
||||
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]
|
||||
@@ -67,7 +67,7 @@ export default function AboutExperience() {
|
||||
</motion.div>
|
||||
<motion.div
|
||||
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 }}
|
||||
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]
|
||||
@@ -84,7 +84,7 @@ export default function AboutExperience() {
|
||||
</motion.div>
|
||||
<motion.div
|
||||
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 }}
|
||||
className="relative [grid-area:b] rounded-2xl
|
||||
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
|
||||
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 }}
|
||||
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]
|
||||
@@ -130,7 +130,7 @@ export default function AboutExperience() {
|
||||
alt=""
|
||||
/>
|
||||
<span className="headline1 text-center">
|
||||
Виртуальные туры <br /> по 360 сферам
|
||||
Виртуальные туры <br /> по 360° сферам
|
||||
</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
@@ -153,9 +153,9 @@ export default function AboutExperience() {
|
||||
в 40 выставках
|
||||
</h3>
|
||||
<p className="headline1 text-[#7A7A7A]">
|
||||
Наш продукт GRAFF.estate уже четыре раза был представлен
|
||||
на форуме “Движение”, Иннопром, Восточный экономический форум,
|
||||
100+ TechnoBuild
|
||||
Продукты GRAFF.estate уже четыре раза были представлены
|
||||
на форуме «Движение», Восточном экономическом форуме, Иннопроме
|
||||
и форуме 100+ TechnoBuild
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
@@ -247,16 +247,18 @@ export default function AboutExperience() {
|
||||
|
||||
<DisplacementCard
|
||||
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
|
||||
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"
|
||||
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]">
|
||||
Мы заняли 1 место на WOW AWARDS 2024 совместно c застройщиком
|
||||
Upside Development
|
||||
<p className="z-10 text-center btns !leading-[120%]">
|
||||
Мы дважды заняли 1 место на WOW AWARDS в 2024 и 2025 году
|
||||
совместно с застройщиками Upside Development и LEGENDA
|
||||
</p>
|
||||
</DisplacementCard>
|
||||
|
||||
|
||||
@@ -159,7 +159,6 @@ export default function Accordeon({
|
||||
ease: "easeIn",
|
||||
},
|
||||
}}
|
||||
className="text1 lg:text-[0.972vw] md:text-[1.823vw] text-[3.889vw]"
|
||||
key={index}
|
||||
>
|
||||
<span
|
||||
@@ -168,6 +167,7 @@ export default function Accordeon({
|
||||
display: isFaqPage ? "inline-block" : "inline",
|
||||
opacity: isFaqPage ? 0.6 : 1,
|
||||
}}
|
||||
className={isFaqPage ? "text1" : "text2"}
|
||||
>
|
||||
{item.content}
|
||||
</span>
|
||||
|
||||
+12
-12
@@ -7,35 +7,35 @@ function Iphone({ active }: { active: boolean }) {
|
||||
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]">
|
||||
<motion.img
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
initial={{ opacity: 0, scale: 0.75 }}
|
||||
animate={{ opacity: 1, scale: active ? 1 : 0.75 }}
|
||||
exit={{ opacity: 0, scale: 0.75 }}
|
||||
src="/img/pages/web/iphone.png"
|
||||
className="absolute w-full h-full z-[8]"
|
||||
className={`z-[8] absolute w-full h-full`}
|
||||
alt=""
|
||||
/>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{active ? (
|
||||
<motion.video
|
||||
key={1}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
initial={{ opacity: 0, scale: 0.75 }}
|
||||
animate={{ opacity: 1, scale: active ? 1 : 0.75 }}
|
||||
exit={{ opacity: 0, scale: 0.75 }}
|
||||
src={"/videos/pages/web/iphone_vid.mp4"}
|
||||
muted
|
||||
autoPlay
|
||||
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
|
||||
key={2}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
initial={{ opacity: 0, scale: 0.75 }}
|
||||
animate={{ opacity: 1, scale: active ? 1 : 0.75 }}
|
||||
exit={{ opacity: 0, scale: 0.75 }}
|
||||
src={"/img/pages/web/content/iphone_disabled.png"}
|
||||
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>
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function WebDemo() {
|
||||
</div>
|
||||
|
||||
<p className="z-10 text-center btns md:max-lg:text-[1.563vw] max-md:text-[3.333vw]">
|
||||
Люди гуляют в окрестностях ЖК
|
||||
Люди гуляют в окрестностях
|
||||
</p>
|
||||
</DisplacementCard>
|
||||
|
||||
@@ -180,7 +180,7 @@ export default function WebDemo() {
|
||||
</div>
|
||||
|
||||
<p className="z-10 text-center btns md:max-lg:text-[1.563vw] max-md:text-[3.333vw]">
|
||||
Фасады ЖК можно изучить со всех сторон
|
||||
Фасады можно изучить со всех сторон
|
||||
</p>
|
||||
</DisplacementCard>
|
||||
|
||||
|
||||
@@ -27,8 +27,7 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
||||
},
|
||||
{
|
||||
type: "new",
|
||||
content:
|
||||
"Получаем исходные данные: чертежи, фотографии, сканы помещений",
|
||||
content: "Получаем исходные данные: чертежи, фотографии, сканы помещений",
|
||||
},
|
||||
{
|
||||
type: "general",
|
||||
@@ -36,22 +35,25 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
||||
},
|
||||
],
|
||||
|
||||
"Прототипирование": [
|
||||
Прототипирование: [
|
||||
{
|
||||
type: "new",
|
||||
content: "Моделируем пространства в Blender и 3Ds Max",
|
||||
},
|
||||
{
|
||||
type: "general",
|
||||
content: "Оптимизируем полигоны для использования на сайте: уменьшаем нагрузку",
|
||||
content:
|
||||
"Оптимизируем полигоны для использования на сайте: уменьшаем нагрузку",
|
||||
},
|
||||
{
|
||||
type: "new",
|
||||
content: "Создаем материалы и текстуры для реализма картинки с помощью PBR-рендеринга ",
|
||||
content:
|
||||
"Создаем материалы и текстуры для реализма картинки с помощью PBR-рендеринга ",
|
||||
},
|
||||
{
|
||||
type: "general",
|
||||
content: "Создаем предварительный рендер и согласовываем общий стиль проекта",
|
||||
content:
|
||||
"Создаем предварительный рендер и согласовываем общий стиль проекта",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -68,10 +70,6 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
||||
type: "general",
|
||||
content: "Отрисовываем дизайн-макеты ключевых блоков",
|
||||
},
|
||||
{
|
||||
type: "general",
|
||||
content: "Согласование с заказчиком и правки",
|
||||
},
|
||||
],
|
||||
|
||||
"Дизайн сайта": [
|
||||
@@ -91,16 +89,13 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
||||
type: "general",
|
||||
content: "Наполняем страницы контентом",
|
||||
},
|
||||
{
|
||||
type: "general",
|
||||
content: "Согласование с заказчиком и правки",
|
||||
},
|
||||
],
|
||||
|
||||
"Сборка сцен": [
|
||||
{
|
||||
type: "general",
|
||||
content: "Компонуем виртуальные сцены: расставляем мебель, освещение и камеры",
|
||||
content:
|
||||
"Компонуем виртуальные сцены: расставляем мебель, освещение и камеры",
|
||||
},
|
||||
{
|
||||
type: "general",
|
||||
@@ -108,7 +103,7 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
||||
"Готовим тестовые интерактивные 360° панорамы и экспортируем в веб-формат",
|
||||
},
|
||||
],
|
||||
"Рендеринг": [
|
||||
Рендеринг: [
|
||||
{
|
||||
type: "general",
|
||||
content: "Готовим финальные версии всех интерактивных 360° панорам ",
|
||||
@@ -129,10 +124,11 @@ export const WebTimelineData: { [key: string]: IAccordeonContent[] } = {
|
||||
},
|
||||
{
|
||||
type: "general",
|
||||
content: "Адаптируем страницы под мобильные устройства: жесты, touch-управление",
|
||||
content:
|
||||
"Адаптируем страницы под мобильные устройства: жесты, touch-управление",
|
||||
},
|
||||
],
|
||||
"Оптимизация": [
|
||||
Оптимизация: [
|
||||
{
|
||||
type: "general",
|
||||
content: "Тестируем готовый сайт на разных устройствах.",
|
||||
|
||||
@@ -10,8 +10,11 @@ import {
|
||||
useState,
|
||||
} from "react";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { useAuthStore } from "@/stores/useAuthStore";
|
||||
import { useCheckAuthQuery } from "@/queries/checkAuth";
|
||||
|
||||
export function LenisProvider({ children }: PropsWithChildren) {
|
||||
const { data: auth } = useCheckAuthQuery();
|
||||
const lenis = useRef<LenisRef>(null);
|
||||
const { modal } = useModalStore();
|
||||
const [lenisKey, setLenisKey] = useState(0);
|
||||
@@ -35,6 +38,7 @@ export function LenisProvider({ children }: PropsWithChildren) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!auth) return;
|
||||
if (modal) {
|
||||
// Отключаем Lenis и блокируем скролл body
|
||||
lenis.current?.lenis?.destroy();
|
||||
@@ -61,7 +65,7 @@ export function LenisProvider({ children }: PropsWithChildren) {
|
||||
lenis.current?.lenis?.start();
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
}, [modal]);
|
||||
}, [modal, auth]);
|
||||
|
||||
return (
|
||||
<ReactLenis
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { api } from '@/api';
|
||||
import { useAuthStore } from '@/stores/useAuthStore';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from "@/api";
|
||||
import { useAuthStore } from "@/stores/useAuthStore";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
export function useCheckAuthQuery() {
|
||||
const { token } = useAuthStore();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['checkAuth'],
|
||||
queryFn: () => api.get('auth/check').json<{ auth: boolean }>(),
|
||||
queryKey: ["checkAuth"],
|
||||
queryFn: () => api.get("auth/check").json<{ auth: boolean }>(),
|
||||
select: (data) => data.auth,
|
||||
enabled: !!token,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user