init
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import { api } from "@/lib/api";
|
||||
import { projectsTags } from "@/consts/projectsTags";
|
||||
import type { Product } from "@/types/Product";
|
||||
import { Button } from "@/ui/Button";
|
||||
import { CheckboxesGroup } from "@/ui/CheckboxesGroup";
|
||||
import { PhoneInputRu } from "@/ui/PhoneInputRu";
|
||||
import useAddReferer from "@/hooks/useAddReferer";
|
||||
import { useRefererStore } from "@/stores/useRefererStore";
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useForm,
|
||||
} from "react-hook-form";
|
||||
import CheckIcon from "@/components/icons/CheckIcon";
|
||||
import FeedbackModal from "@/components/modals/FeedbackFormModal";
|
||||
|
||||
const DEFAULT_STREAM_DEMO_PRODUCTS = [
|
||||
"Удаленная демонстрация",
|
||||
] as Product[];
|
||||
|
||||
export function Feedback() {
|
||||
useAddReferer();
|
||||
|
||||
return (
|
||||
<div
|
||||
id="contacts"
|
||||
className="lg:mb-20 md:mb-12 lg:flex lg:gap-[0.833vw] max-lg:space-y-12 justify-between lg:mt-[14.07vh] mt-[100px] mb-10"
|
||||
>
|
||||
<h2 className="line2 font-medium max-lg:mb-6 lg:max-w-[45%]">
|
||||
<span className="text-[#7A7A7A]">Хотите увеличить конверсию?</span>
|
||||
<br />
|
||||
Давайте обсудим детали.
|
||||
</h2>
|
||||
<FeedbackForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface IInput {
|
||||
fullname: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
products: Product[];
|
||||
referer?: string | null;
|
||||
}
|
||||
|
||||
export function FeedbackForm() {
|
||||
const { referer } = useRefererStore();
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const form = useForm<IInput>({
|
||||
defaultValues: {
|
||||
fullname: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
products: DEFAULT_STREAM_DEMO_PRODUCTS,
|
||||
},
|
||||
});
|
||||
|
||||
const { register, handleSubmit, formState, control } = form;
|
||||
|
||||
async function onSubmit(data: IInput) {
|
||||
const { id } = await api
|
||||
.post("mail", { json: { ...data, referer } })
|
||||
.json<{ id: string }>();
|
||||
setModal(<FeedbackModal id={id} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-4 lg:max-w-[47.431vw]">
|
||||
<div className="space-y-10">
|
||||
{!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>
|
||||
<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">
|
||||
<Controller
|
||||
name="phone"
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }) => (
|
||||
<PhoneInputRu
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
inputRef={field.ref}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<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
|
||||
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>
|
||||
<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"
|
||||
rel="noopener noreferrer"
|
||||
href={"/privacy-policy"}
|
||||
className="underline"
|
||||
>
|
||||
согласие на обработку персональных данных
|
||||
</a>{" "}
|
||||
<span className="text-[#7A7A7A]">и принимаете </span>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={"/policy"}
|
||||
className="underline"
|
||||
>
|
||||
условия политики
|
||||
</a>
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import ArrowMoreIcon from "@/components/icons/ArrowMoreIcon";
|
||||
import RutubeIcon from "@/components/icons/RutubeIcon";
|
||||
import TelegramIcon from "@/components/icons/TgIcon";
|
||||
import VkIcon from "@/components/icons/VKIcon";
|
||||
import YoutubeIcon from "@/components/icons/YoutubeIcon";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="lg:px-5 lg:pb-5 md:max-lg:px-4 md:max-lg:pb-4 px-[10px] pb-[10px] space-y-6 mb-0">
|
||||
<div className="max-md:flex-col lg:gap-[0.833vw] md:max-lg:gap-[1.042vw] flex gap-[1.111vw]">
|
||||
<a
|
||||
href={"tel:" + "8 800 770 00 67".replaceAll(" ", "")}
|
||||
className="lg:p-[1.667vw] p-6 flex flex-col justify-between max-md:gap-y-10 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] lg:rounded-[1.111vw] rounded-2xl lg:aspect-[696/248] lg:w-[48.333vw] md:max-lg:w-[47.656vw] hover:bg-[#37393B99] bg-transparent transition-colors"
|
||||
>
|
||||
<div className="text-[#7A7A7A] text1 font-medium">Позвонить</div>
|
||||
<div className="lg:line2 md:max-lg:heading1 line2 flex items-center font-medium">
|
||||
8 800 770 00 67
|
||||
<div className="text-white lg:size-[5.556vw] size-[10vw]">
|
||||
<ArrowMoreIcon />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href={"mailto:" + "info@graff.tech"}
|
||||
className="lg:p-[1.667vw] p-6 flex flex-col justify-between max-md:gap-y-10 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] lg:rounded-[1.111vw] rounded-2xl lg:aspect-[624/248] lg:w-[43.333vw] md:max-lg:w-[47.656vw] hover:bg-[#37393B99] bg-transparent transition-colors"
|
||||
>
|
||||
<div className="text-[#7A7A7A] text1 font-medium">Написать</div>
|
||||
<div className="lg:line2 md:max-lg:heading1 line2 flex items-center font-medium">
|
||||
info@graff.tech
|
||||
<div className="text-white lg:size-[5.556vw] size-[10vw]">
|
||||
<ArrowMoreIcon />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div className="gap-y-2 justify-between md:gap-x-[1.042vw] gap-x-[1.111vw] md:flex-col flex">
|
||||
<ContactLink href="https://t.me/graffestate">
|
||||
<div className="text-white lg:size-[1.389vw] size-[5.556vw] group-hover:text-black">
|
||||
<TelegramIcon />
|
||||
</div>
|
||||
</ContactLink>
|
||||
<ContactLink href="https://rutube.ru/channel/25505040">
|
||||
<div className="text-white lg:size-[1.389vw] size-[5.556vw] group-hover:text-black">
|
||||
<RutubeIcon />
|
||||
</div>
|
||||
</ContactLink>
|
||||
<ContactLink href="https://vk.com/graff.estate">
|
||||
<div className="text-white lg:size-[1.389vw] size-[5.556vw] group-hover:text-black">
|
||||
<VkIcon />
|
||||
</div>
|
||||
</ContactLink>
|
||||
<ContactLink href="https://www.youtube.com/@GRAFFtech">
|
||||
<div className="text-white lg:size-[1.389vw] size-[5.556vw] group-hover:text-black">
|
||||
<YoutubeIcon />
|
||||
</div>
|
||||
</ContactLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:w-full flex flex-col md:flex-row md:max-lg:gap-[1.042vw] lg:gap-x-[0.833vw] gap-6 lg:pb-6 max-lg:py-6 pb-10 md:max-lg:pt-4 !max-md:mt-[11.111vw] border-b border-[#232425] relative">
|
||||
<div className="flex flex-col gap-y-[1.111vw] lg:min-w-[48.193vw] flex-1">
|
||||
<span className=" text1 text-[#7A7A7A]">Юридический адрес:</span>
|
||||
<span className="headline2 max-md:leading-4 font-medium text-[#7A7A7A]">
|
||||
620063, г. Екатеринбург, <br /> ул. Большакова, д. 66, кв. 6
|
||||
</span>
|
||||
|
||||
<div className="flex flex-col gap-y-[1.111vw] lg:mt-[0px] md:mt-[2.083vw] mt-6">
|
||||
<span className=" text1 text-[#7A7A7A]">Наш основной стек:</span>
|
||||
<div className="headline2 max-md:leading-4 font-medium text-[#7A7A7A]">
|
||||
<p>Unreal Engine 5, C++</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-[1.111vw] flex-1">
|
||||
<span className="text1 text-[#7A7A7A]">Реквизиты:</span>
|
||||
<div className="headline2 max-md:leading-4 font-medium text-[#7A7A7A]">
|
||||
<p>ИНН: 6679174128</p>
|
||||
<p>КПП: 667101001</p>
|
||||
<p>ООО "ГРАФФ.ЭСТЕЙТ"</p>
|
||||
<p>ОГРН 1246600010140</p>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="/img/components/header/Sk.svg"
|
||||
alt="Сколково"
|
||||
className=" lg:hidden md:size-[6.25vw] size-[13.333vw] max-md:absolute max-md:right-0 max-md:bottom-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="lg:gap-x-[0.833vw] gap-y-2 flex max-lg:flex-col">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={"/policy"}
|
||||
className="text-[#37393B] text1 font-medium leading-[18.9px] lg:w-[48.193vw] w-fit"
|
||||
>
|
||||
Политика конфиденциальности и обработки персональных данных
|
||||
</a>
|
||||
<p className="text-[#37393B] text1 font-medium leading-[18.9px] col-start-1">
|
||||
© 2026 GRAFF interactive. Все права защищены
|
||||
</p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={"https://graff.tech"}
|
||||
className="text-[#37393B] text1 font-medium leading-[18.9px] lg:ml-auto w-fit md:col-start-2 md:text-right"
|
||||
>
|
||||
graff.tech
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export function ContactLink({
|
||||
children,
|
||||
href,
|
||||
className = "",
|
||||
}: PropsWithChildren<{ href: string; className?: string }>) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`lg:rounded-[1.111vw] rounded-2xl bg-[#37393B99] lg:p-[1.25vw] p-[18px] hover:bg-white transition-all hover:text-black flex justify-center w-full group ${className}`}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
interface BRProps {
|
||||
lg?: boolean;
|
||||
md?: boolean;
|
||||
sm?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function BR({ lg, md, sm, className }: BRProps) {
|
||||
const modifier =
|
||||
!lg && !md && !sm
|
||||
? ""
|
||||
: `lg:${lg ? `block` : "hidden"} md:${md ? "block" : "hidden"} ${
|
||||
sm ? "block" : "hidden"
|
||||
} `;
|
||||
const combinedClassName = `${modifier} ${className ?? ""}`;
|
||||
|
||||
return <br className={combinedClassName} />;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useModalStore } from "@/stores/useModalStore";
|
||||
import { useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
export function ModalContainer() {
|
||||
const { modal, setModal } = useModalStore();
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") setModal(null);
|
||||
};
|
||||
document.addEventListener("keydown", listener);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", listener);
|
||||
};
|
||||
}, [setModal]);
|
||||
|
||||
const jsx = modal ? (
|
||||
<div className="fixed left-0 z-[20] w-full h-full flex justify-center items-start transition-opacity">
|
||||
<div className="absolute [backdrop-filter:blur(16px)] bg-[#0F101199] w-full h-full z-[1]" />
|
||||
{modal}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return createPortal(jsx, document.body);
|
||||
}
|
||||
Reference in New Issue
Block a user