upd
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
add,
|
||||
eachDayOfInterval,
|
||||
endOfMonth,
|
||||
format,
|
||||
getDay,
|
||||
isEqual,
|
||||
parse,
|
||||
startOfToday,
|
||||
} from "date-fns";
|
||||
import { enUS, ru } from "date-fns/locale";
|
||||
import { useEffect, useState } from "react";
|
||||
import ChevronRightIcon from "./icons/ChevronRightIcon";
|
||||
import ChevronLeftIcon from "./icons/ChevronLeftIcon";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18n from "../i18n";
|
||||
|
||||
interface CalendarProps {
|
||||
handleSelect: (day: Date) => void;
|
||||
}
|
||||
|
||||
function classNames(...classes: (string | boolean)[]) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function Calendar({ handleSelect }: CalendarProps) {
|
||||
const today = startOfToday();
|
||||
const [selectedDay, setSelectedDay] = useState<Date | null>(null);
|
||||
const [currentMonth, setCurrentMonth] = useState(format(today, "MMM-yyyy"));
|
||||
const firstDayCurrentMonth = parse(currentMonth, "MMM-yyyy", new Date());
|
||||
|
||||
const days = eachDayOfInterval({
|
||||
start: firstDayCurrentMonth,
|
||||
end: endOfMonth(firstDayCurrentMonth),
|
||||
});
|
||||
|
||||
function previousMonth() {
|
||||
const firstDayNextMonth = add(firstDayCurrentMonth, { months: -1 });
|
||||
setCurrentMonth(format(firstDayNextMonth, "MMM-yyyy"));
|
||||
}
|
||||
|
||||
function nextMonth() {
|
||||
const firstDayNextMonth = add(firstDayCurrentMonth, { months: 1 });
|
||||
setCurrentMonth(format(firstDayNextMonth, "MMM-yyyy"));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDay !== null) {
|
||||
handleSelect(selectedDay);
|
||||
}
|
||||
}, [selectedDay]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<button onClick={previousMonth}>
|
||||
<ChevronLeftIcon />
|
||||
</button>
|
||||
|
||||
<p className="text-sm text-white first-letter:uppercase w-fit">
|
||||
{format(
|
||||
firstDayCurrentMonth,
|
||||
"LLLL, yyyy",
|
||||
i18n.language === "ru" ? { locale: ru } : { locale: enUS }
|
||||
)}
|
||||
</p>
|
||||
|
||||
<button onClick={nextMonth}>
|
||||
<ChevronRightIcon />
|
||||
</button>
|
||||
</div>
|
||||
<div className="sm:mt-8 mt-5 grid grid-cols-7 gap-2 font-gilroy text-sm text-center text-white font-semibold">
|
||||
<div>{i18n.language === "ru" ? "пн" : "Mo"}</div>
|
||||
<div>{i18n.language === "ru" ? "вт" : "Tu"}</div>
|
||||
<div>{i18n.language === "ru" ? "ср" : "We"}</div>
|
||||
<div>{i18n.language === "ru" ? "чт" : "Th"}</div>
|
||||
<div>{i18n.language === "ru" ? "пт" : "Fr"}</div>
|
||||
<div>{i18n.language === "ru" ? "сб" : "Sa"}</div>
|
||||
<div>{i18n.language === "ru" ? "вс" : "Su"}</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-7 gap-2 mt-2 text-sm font-semibold">
|
||||
{days.map((day, dayIdx) => (
|
||||
<div
|
||||
key={day.toString()}
|
||||
className={classNames(
|
||||
dayIdx === 0 && colStartClasses[getDay(day) - 1]
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
disabled={day < today}
|
||||
onClick={() => setSelectedDay(day)}
|
||||
className={classNames(
|
||||
selectedDay !== null &&
|
||||
isEqual(day, selectedDay) &&
|
||||
"bg-[#798FFF] text-white hover:bg-opacity-100",
|
||||
"mx-auto flex min-w-[40px] h-10 items-center justify-center rounded-full transition-all text-[#798FFF] border border-[#798FFF] hover:bg-[#798FFF] hover:bg-opacity-20 disabled:text-[#3D425C] disabled:border-transparent disabled:hover:bg-transparent"
|
||||
)}
|
||||
>
|
||||
<time dateTime={format(day, "yyyy-MM-dd")}>
|
||||
{format(day, "d")}
|
||||
</time>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-8 flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-[#798FFF] rounded"></div>
|
||||
<p className="text-[#798FFF]">
|
||||
- <Trans i18nKey={"sidebar.available"}>запись доступна</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const colStartClasses = [
|
||||
"",
|
||||
"col-start-2",
|
||||
"col-start-3",
|
||||
"col-start-4",
|
||||
"col-start-5",
|
||||
"col-start-6",
|
||||
"col-start-7",
|
||||
];
|
||||
|
||||
export default Calendar;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
interface CardProps {
|
||||
icon: string;
|
||||
image: string;
|
||||
title: string | JSX.Element;
|
||||
location: string | JSX.Element;
|
||||
handleClick: () => void;
|
||||
}
|
||||
|
||||
function Card({ icon, image, title, location, handleClick }: CardProps) {
|
||||
return (
|
||||
<div className="rounded-lg overflow-hidden flex flex-col justify-between bg-[#22222A]">
|
||||
<div
|
||||
className="aspect-video bg-no-repeat bg-center bg-cover"
|
||||
style={{ backgroundImage: `url('${image}')` }}
|
||||
></div>
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="flex items-center space-x-4">
|
||||
<img src={icon} alt="" className="w-12 h-12" />
|
||||
<div>
|
||||
<p className="sm:text-xl font-gilroy">{title}</p>
|
||||
<p className="sm:text-base text-sm text-[#ABABBA]">{location}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className="px-4 py-2 bg-gradient rounded w-full opacity-90 hover:opacity-100 transition-opacity font-gilroy"
|
||||
>
|
||||
<Trans i18nKey="button" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Card;
|
||||
@@ -0,0 +1,20 @@
|
||||
.contacts-field:focus ~ .contacts-placeholder {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.contacts-field:focus ~ .contacts-placeholder-2 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.contacts-field:valid ~ .contacts-placeholder {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.contacts-field:valid ~ .contacts-placeholder-2 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.contacts-field::placeholder {
|
||||
font-weight: 600;
|
||||
color: #77787d;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { ChangeEvent } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import AsteriskIcon from "./icons/AsteriskIcon";
|
||||
import InputMask from "react-input-mask";
|
||||
import "./ContactsForm.css";
|
||||
import useSidebarStore from "../stores/useSidebarStore";
|
||||
|
||||
function ContactsForm() {
|
||||
const [name, setName, phone, setPhone, email, setEmail] = useSidebarStore(
|
||||
(state) => [
|
||||
state.name,
|
||||
state.setName,
|
||||
state.phone,
|
||||
state.setPhone,
|
||||
state.email,
|
||||
state.setEmail,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="relative">
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="feedback-field bg-transparent border border-[#3D425C] rounded-none sm:pt-12 sm:pb-4 sm:px-4 pt-8 pb-3 px-3 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full"
|
||||
/>
|
||||
<p className="feedback-placeholder lg:text-base text-sm absolute sm:pt-4 sm:pb-4 sm:px-4 sm:top-4 pt-3 pb-3 px-3 top-3 w-full opacity-50 transition-all pointer-events-none flex justify-between items-center">
|
||||
<span>
|
||||
<Trans i18nKey={"feedback.form.field1"}>Имя</Trans>
|
||||
</span>
|
||||
<AsteriskIcon />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<InputMask
|
||||
required
|
||||
type="tel"
|
||||
mask={"+999999999999999"}
|
||||
maskChar={null}
|
||||
value={phone}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setPhone(e.target.value)
|
||||
}
|
||||
className={[
|
||||
"feedback-field bg-transparent border rounded-none border-t-0 border-[#3D425C] sm:pt-12 sm:pb-4 sm:px-4 pt-8 pb-3 px-3 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full",
|
||||
].join(" ")}
|
||||
/>
|
||||
<p className="feedback-placeholder lg:text-base text-sm absolute sm:pt-4 sm:pb-4 sm:px-4 sm:top-4 pt-3 pb-3 px-3 top-3 w-full opacity-50 transition-all pointer-events-none flex justify-between items-center">
|
||||
<span>
|
||||
<Trans i18nKey={"feedback.form.field2"}>Телефон</Trans>
|
||||
</span>
|
||||
<AsteriskIcon />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="feedback-field bg-transparent border rounded-none border-t-0 border-[#3D425C] sm:pt-12 sm:pb-4 sm:px-4 pt-8 pb-3 px-3 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full"
|
||||
/>
|
||||
<p className="feedback-placeholder lg:text-base text-sm absolute sm:pt-4 sm:pb-4 sm:px-4 sm:top-4 pt-3 pb-3 px-3 top-3 w-full opacity-50 transition-all pointer-events-none flex justify-between items-center">
|
||||
<span>Email</span>
|
||||
<AsteriskIcon />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border border-t-0 border-[#3D425C] sm:px-4 sm:py-6 px-3 py-4 text-xs flex items-center gap-2">
|
||||
<div className="flex gap-2">
|
||||
<div className="">
|
||||
<AsteriskIcon />
|
||||
</div>
|
||||
<p>—</p>
|
||||
<p>
|
||||
<Trans i18nKey={"feedback.form.desc2"}>
|
||||
Звездочкой отмечены обязательные
|
||||
<br />
|
||||
для заполнения поля
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContactsForm;
|
||||
@@ -0,0 +1,187 @@
|
||||
import ky from "ky";
|
||||
import { ChangeEvent, FormEvent, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import InputMask from "react-input-mask";
|
||||
import AsteriskIcon from "./icons/AsteriskIcon";
|
||||
import SendIcon from "./icons/SendIcon";
|
||||
import CheckGradientIcon from "./icons/CheckGradientIcon";
|
||||
import LoaderIcon from "./icons/LoaderIcon";
|
||||
|
||||
function FeedbackForm() {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState<string>("");
|
||||
const [phone, setPhone] = useState<string>("");
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [description, setDescription] = useState<string>("");
|
||||
const [isSend, setIsSend] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
async function sendMail(e: FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
await ky
|
||||
.post(`https://estate.graff.tech/api/mail`, {
|
||||
json: {
|
||||
fullname: name,
|
||||
phone,
|
||||
email,
|
||||
request: description,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
setIsSend(true);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
if (error instanceof Error) {
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
className="grid lg:grid-cols-3 sm:grid-cols-2 relative"
|
||||
onSubmit={(e) => void sendMail(e)}
|
||||
>
|
||||
<div className="relative col-span-1">
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="feedback-field bg-transparent border border-[#3D425C] rounded-none lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full"
|
||||
/>
|
||||
<p className="feedback-placeholder lg:text-base text-sm absolute lg:top-4 top-5 left-0 w-full lg:p-6 p-4 opacity-50 transition-all pointer-events-none flex justify-between">
|
||||
<span>
|
||||
<Trans i18nKey={"feedback.form.field1"}>Имя</Trans>
|
||||
</span>
|
||||
<AsteriskIcon />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<InputMask
|
||||
required
|
||||
type="tel"
|
||||
mask={"+999999999999999"}
|
||||
maskChar={null}
|
||||
value={phone}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setPhone(e.target.value)
|
||||
}
|
||||
className={[
|
||||
"feedback-field bg-transparent border rounded-none sm:border-l-0 sm:border-t border-t-0 border-l border-[#3D425C] lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full",
|
||||
].join(" ")}
|
||||
/>
|
||||
<p className="feedback-placeholder lg:text-base text-sm absolute lg:top-4 top-5 left-0 w-full lg:p-6 p-4 opacity-50 transition-all pointer-events-none flex justify-between">
|
||||
<span>
|
||||
<Trans i18nKey={"feedback.form.field2"}>Телефон</Trans>
|
||||
</span>
|
||||
<AsteriskIcon />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative lg:col-span-1 sm:col-span-2 col-span-1">
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="feedback-field bg-transparent border rounded-none lg:border-l-0 lg:border-t border-t-0 border-[#3D425C] lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full"
|
||||
/>
|
||||
<p className="feedback-placeholder lg:text-base text-sm absolute lg:top-4 top-5 left-0 w-full lg:p-6 p-4 opacity-50 transition-all pointer-events-none flex justify-between">
|
||||
<span>Email</span>
|
||||
<AsteriskIcon />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative lg:col-span-3 sm:col-span-2 h-[194px]">
|
||||
<textarea
|
||||
placeholder={t("feedback.form.field3").toString()}
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
className="feedback-field bg-transparent resize-none border rounded-none border-t-0 border-[#3D425C] lg:p-6 p-4 h-full outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div className="2xl:pt-6 2xl:pr-6 pt-4 sm:pr-4 lg:order-none order-last flex items-center">
|
||||
<button
|
||||
disabled={isLoading}
|
||||
className="group relative px-6 py-4 2xl:text-base text-sm bg-gradient rounded-full font-medium flex justify-between items-center w-full transition-opacity disabled:opacity-75"
|
||||
>
|
||||
<div className="absolute top-0 left-0 w-full h-full rounded-full bg-black opacity-0 group-hover:opacity-10 transition-all"></div>
|
||||
<span className="relative">
|
||||
<Trans i18nKey={"feedback.form.button"}>Отправить</Trans>
|
||||
</span>
|
||||
{!isLoading ? (
|
||||
<SendIcon className="relative 2xl:w-8 2xl:h-8 w-6 h-6" />
|
||||
) : (
|
||||
<LoaderIcon className="relative 2xl:w-8 2xl:h-8 w-6 h-6 animate-spin" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="border sm:border-t-0 border-t border-[#3D425C] 2xl:p-6 p-4 sm:mt-0 mt-6 flex items-center">
|
||||
<div className="text-xs leading-tight">
|
||||
<Trans i18nKey={"feedback.form.desc1.text1"}>
|
||||
Нажимая кнопку отправить, вы принимаете
|
||||
</Trans>{" "}
|
||||
<a className="text-[#798FFF] cursor-pointer opacity-95 hover:opacity-100 transition-all">
|
||||
<Trans i18nKey={"feedback.form.desc1.link1"}>
|
||||
условия использования
|
||||
</Trans>
|
||||
</a>{" "}
|
||||
<Trans i18nKey={"feedback.form.desc1.text2"}>и</Trans>{" "}
|
||||
<a className="text-[#798FFF] cursor-pointer opacity-95 hover:opacity-100 transition-all">
|
||||
<Trans i18nKey={"feedback.form.desc1.link2"}>
|
||||
политику конфиденциальности
|
||||
</Trans>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-t-0 sm:border-l-0 border-[#3D425C] 2xl:p-6 p-4 text-xs flex items-center gap-2">
|
||||
<div className="flex gap-2">
|
||||
<div className="">
|
||||
<AsteriskIcon />
|
||||
</div>
|
||||
<p>—</p>
|
||||
<p className="leading-tight">
|
||||
<Trans i18nKey={"feedback.form.desc2"}>
|
||||
Звездочкой отмечены обязательные
|
||||
<br />
|
||||
для заполнения поля
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isSend && (
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-[#14161F] border border-[#3D425C] p-6 flex flex-col justify-between">
|
||||
<p className="text-gradient text-xl font-gilroy leading-tight font-semibold flex items-center gap-2">
|
||||
<span>Заявка отправлена</span>
|
||||
<CheckGradientIcon className="lg:w-8 lg:h-8 w-6 h-6" />
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="font-gilroy leading-snug lg:text-2xl text-xl font-semibold">
|
||||
Спасибо за подачу заявки!
|
||||
</p>
|
||||
|
||||
<p className="lg:w-1/2 sm:w-2/3 lg:text-base text-sm">
|
||||
Мы ценим ваш интерес к нашей компании и в ближайшее время свяжемся
|
||||
с вами для уточнения деталей проекта.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default FeedbackForm;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Trans } from "react-i18next";
|
||||
import LogoIcon from "./icons/LogoIcon";
|
||||
import LogoMobileIcon from "./icons/LogoMobileIcon";
|
||||
import i18n from "../i18n";
|
||||
import useSidebarStore from "../stores/useSidebarStore";
|
||||
|
||||
interface HeaderProps {
|
||||
handleChangeLang: (lang: string) => void;
|
||||
}
|
||||
|
||||
function Header({ handleChangeLang }: HeaderProps) {
|
||||
const [setIsOpen] = useSidebarStore((state) => [state.setIsOpen]);
|
||||
|
||||
return (
|
||||
<header className="sm:py-6 py-4 flex justify-between">
|
||||
<a href="/" className="sm:block hidden">
|
||||
<LogoIcon />
|
||||
</a>
|
||||
<a href="/" className="sm:hidden block">
|
||||
<LogoMobileIcon />
|
||||
</a>
|
||||
<div className="flex sm:gap-8 gap-2">
|
||||
<button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="group relative sm:px-8 px-6 py-2 bg-gradient rounded-full lg:text-base text-sm font-medium leading-normal"
|
||||
>
|
||||
<div className="absolute top-0 left-0 w-full h-full rounded-full bg-black opacity-0 group-hover:opacity-10 transition-all"></div>
|
||||
<span className="relative">
|
||||
<Trans i18nKey={"header.buttonFirst"}>Записаться</Trans>{" "}
|
||||
<span className="sm:inline hidden">
|
||||
<Trans i18nKey={"header.buttonSecond"}>на демонстрацию</Trans>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
className={[
|
||||
"px-3 py-1.5 border rounded-full",
|
||||
i18n.language === "ru"
|
||||
? "border-[#D375FF]"
|
||||
: "border-transparent hover:bg-[#3D425C] transition-colors",
|
||||
].join(" ")}
|
||||
onClick={() => handleChangeLang("ru")}
|
||||
>
|
||||
RU
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"px-3 py-1.5 border rounded-full",
|
||||
i18n.language === "en"
|
||||
? "border-[#D375FF]"
|
||||
: "border-transparent hover:bg-[#3D425C] transition-colors",
|
||||
].join(" ")}
|
||||
onClick={() => handleChangeLang("en")}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,15 @@
|
||||
.entering {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.entered {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.exiting {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.exited {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useEffect } from "react";
|
||||
import useModalStore from "../stores/useModalStore";
|
||||
import "./ModalContainer.css";
|
||||
|
||||
interface ModalContainerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ModalContainer({ className }: ModalContainerProps) {
|
||||
const [modal, setModal] = useModalStore((state) => [
|
||||
state.modal,
|
||||
state.setModal,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.code === "Escape") {
|
||||
setModal(null);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => setModal(null)}
|
||||
className={[
|
||||
"absolute w-full min-h-screen top-0 left-0 flex flex-col justify-center items-center p-8 bg-black bg-opacity-75 transition-opacity cursor-pointer",
|
||||
className,
|
||||
].join(" ")}
|
||||
>
|
||||
<div onClick={(e) => e.stopPropagation()} className="cursor-default">
|
||||
{modal}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalContainer;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ChangeEvent, useState } from "react";
|
||||
|
||||
interface NumberInputProps {
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
defaultValue?: number;
|
||||
onChange: (value: number) => void;
|
||||
}
|
||||
|
||||
function NumberInput({
|
||||
min = 1,
|
||||
max = 100,
|
||||
step = 1,
|
||||
defaultValue = 1,
|
||||
onChange,
|
||||
}: NumberInputProps) {
|
||||
const [value, setValue] = useState<number>(defaultValue);
|
||||
|
||||
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
let value = +e.target.value;
|
||||
|
||||
if (value < min) value = min;
|
||||
if (value > max) value = max;
|
||||
|
||||
setValue(value);
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type="number"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
className="px-3 py-2 rounded w-full outline-none"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default NumberInput;
|
||||
@@ -0,0 +1,159 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Config,
|
||||
AllSettings,
|
||||
PixelStreaming,
|
||||
} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.3";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
export interface PixelStreamingWrapperProps {
|
||||
initialSettings?: Partial<AllSettings>;
|
||||
}
|
||||
|
||||
export const PixelStreamingWrapper = ({
|
||||
initialSettings,
|
||||
}: PixelStreamingWrapperProps) => {
|
||||
// A reference to parent div element that the Pixel Streaming library attaches into:
|
||||
const videoParent = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Pixel streaming library instance is stored into this state variable after initialization:
|
||||
const [pixelStreaming, setPixelStreaming] = useState<PixelStreaming>();
|
||||
|
||||
// A boolean state variable that determines if the Click to play overlay is shown:
|
||||
const [clickToPlayVisible, setClickToPlayVisible] = useState<boolean>(false);
|
||||
const [videoInitialized, setVideoInitialized] = useState<boolean>(false);
|
||||
|
||||
// Run on component mount:
|
||||
useEffect(() => {
|
||||
if (videoParent.current) {
|
||||
// Attach Pixel Streaming library to videoParent element:
|
||||
const config = new Config({ initialSettings });
|
||||
const streaming = new PixelStreaming(config, {
|
||||
videoElementParent: videoParent.current,
|
||||
});
|
||||
|
||||
streaming.addEventListener("videoInitialized", () => {
|
||||
setVideoInitialized(true);
|
||||
});
|
||||
|
||||
// register a playStreamRejected handler to show Click to play overlay if needed:
|
||||
streaming.addEventListener("playStreamRejected", () => {
|
||||
setClickToPlayVisible(true);
|
||||
});
|
||||
|
||||
// Save the library instance into component state so that it can be accessed later:
|
||||
setPixelStreaming(streaming);
|
||||
|
||||
document.getElementById("hiddenInput")?.remove();
|
||||
document.getElementById("editTextButton")?.remove();
|
||||
|
||||
// Clean up on component unmount:
|
||||
return () => {
|
||||
try {
|
||||
streaming.disconnect();
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative w-screen h-screen">
|
||||
<div className="w-full h-[100svh]" ref={videoParent} />
|
||||
{!videoInitialized && (
|
||||
<div className="absolute top-0 left-0 w-full h-full flex justify-center items-center">
|
||||
<Trans i18nKey="streamBuffering">Буферизация потока</Trans>
|
||||
</div>
|
||||
)}
|
||||
{clickToPlayVisible && (
|
||||
<div className="absolute top-0 left-0 w-full h-full flex justify-center items-center z-10 bg-[#131317]">
|
||||
<div className="flex flex-col justify-center items-center w-[400px] p-10 space-y-10 rounded-lg">
|
||||
<div className="space-y-4 text-center">
|
||||
<p className="text-4xl font-gilroy">
|
||||
<Trans i18nKey="demoStarted">Демонстрация начата</Trans>
|
||||
</p>
|
||||
<p className="text-[#C5C7CE]">
|
||||
<Trans i18nKey="clickToContinue">
|
||||
Нажмите, чтобы продолжить
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
pixelStreaming?.play();
|
||||
setClickToPlayVisible(false);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="88"
|
||||
height="88"
|
||||
viewBox="0 0 88 88"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter0_b_0_1121)">
|
||||
<path
|
||||
d="M55.6667 43.9999L34.6668 57.9999L34.6668 30L55.6667 43.9999Z"
|
||||
fill="#F2F2F2"
|
||||
stroke="#F2F2F2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<rect
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
width="87"
|
||||
height="87"
|
||||
rx="43.5"
|
||||
stroke="url(#paint0_linear_0_1121)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_b_0_1121"
|
||||
x="-20"
|
||||
y="-20"
|
||||
width="128"
|
||||
height="128"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="10" />
|
||||
<feComposite
|
||||
in2="SourceAlpha"
|
||||
operator="in"
|
||||
result="effect1_backgroundBlur_0_1121"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_backgroundBlur_0_1121"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_0_1121"
|
||||
x1="88"
|
||||
y1="-2.6226e-06"
|
||||
x2="2.6226e-06"
|
||||
y2="88"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#BC75FF" />
|
||||
<stop offset="1" stopColor="#798FFF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { PixelStreamingWrapper } from "./PixelStreamingWrapper";
|
||||
|
||||
interface PlayerProps {
|
||||
ss: string;
|
||||
}
|
||||
|
||||
export const Player = ({ ss }: PlayerProps) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<PixelStreamingWrapper
|
||||
initialSettings={{
|
||||
AutoPlayVideo: true,
|
||||
AutoConnect: true,
|
||||
ss,
|
||||
StartVideoMuted: false,
|
||||
HoveringMouse: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import NumberInput from "./NumberInput";
|
||||
import TimePicker from "./TimePicker";
|
||||
|
||||
function SessionScheduleSettings() {
|
||||
return (
|
||||
<div className="text-white flex gap-4">
|
||||
<div className="bg-[#212121] rounded p-8 w-80 h-fit shadow space-y-8">
|
||||
<p className="text-2xl font-gilroy">Настройки расписания сеансов</p>
|
||||
<form className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<div className="space-y-1">
|
||||
<p>Время начала:</p>
|
||||
<TimePicker />
|
||||
</div>
|
||||
<div>—</div>
|
||||
<div className="space-y-1">
|
||||
<p>Время конца:</p>
|
||||
<TimePicker />
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<p>Длительность:</p>
|
||||
<NumberInput
|
||||
min={15}
|
||||
max={60}
|
||||
step={5}
|
||||
defaultValue={30}
|
||||
onChange={(value) => console.log(value)}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" className="px-4 py-2 bg-gradient rounded">
|
||||
Сохранить
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="bg-[#212121] rounded p-4 w-80 h-fit shadow">preview</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SessionScheduleSettings;
|
||||
@@ -0,0 +1,33 @@
|
||||
.sidebar.entering {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar.entered {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar.exiting {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sidebar.exited {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
import "./Sidebar.css";
|
||||
import useSidebarTabStore from "../stores/useSidebarStore";
|
||||
import SidebarTab1 from "./SidebarTab1";
|
||||
import SidebarTab2 from "./SidebarTab2";
|
||||
import SidebarTab3 from "./SidebarTab3";
|
||||
import SidebarTab4 from "./SidebarTab4";
|
||||
import SidebarTab5 from "./SidebarTab5";
|
||||
|
||||
interface SidebarProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Sidebar({ className }: SidebarProps) {
|
||||
const [currentTab] = useSidebarTabStore((state) => [state.currentTab]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"sidebar fixed top-0 left-0 w-full h-full bg-[#14161F] bg-opacity-90 transition-opacity text-white",
|
||||
className,
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="absolute right-0 h-full sm:w-[408px] w-full bg-[#14161F] overflow-y-auto">
|
||||
{currentTab === 1 && <SidebarTab1 />}
|
||||
{currentTab === 2 && <SidebarTab2 />}
|
||||
{currentTab === 3 && <SidebarTab3 />}
|
||||
{currentTab === 4 && <SidebarTab4 />}
|
||||
{currentTab === 5 && <SidebarTab5 />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
@@ -0,0 +1,69 @@
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
import { Trans } from "react-i18next";
|
||||
import useSidebarTabStore from "../stores/useSidebarStore";
|
||||
import Calendar from "./Calendar";
|
||||
import CloseIcon from "./icons/CloseIcon";
|
||||
|
||||
function SidebarTab1() {
|
||||
const [currentTab, setCurrentTab, setIsOpen, setSelectedDay] =
|
||||
useSidebarTabStore((state) => [
|
||||
state.currentTab,
|
||||
state.setCurrentTab,
|
||||
state.setIsOpen,
|
||||
state.setSelectedDay,
|
||||
]);
|
||||
|
||||
function handleSelectDay(day: Date) {
|
||||
setSelectedDay(day);
|
||||
setCurrentTab(currentTab + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sm:p-8 p-6 flex flex-col justify-between sm:gap-8 gap-6 min-h-full">
|
||||
<div>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<p className="text-2xl text-gradient font-semibold font-gilroy w-fit leading-snug">
|
||||
<Trans i18nKey={"sidebar.title1"}>Дата и время</Trans>
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="transition-opacity hover:opacity-50"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="border-b border-[#798FFF] p-4 text-center">
|
||||
<p className="leading-none font-gilroy font-semibold text-sm">
|
||||
<Trans i18nKey={"sidebar.date"}>Дата</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border-b border-[#3D425C] p-4 text-center">
|
||||
<p className="leading-none font-gilroy font-semibold text-sm">
|
||||
<Trans i18nKey={"sidebar.time"}>Время</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:mt-8 mt-6">
|
||||
<Calendar handleSelect={(day) => handleSelectDay(day)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:gap-6 gap-4">
|
||||
<p className="text-center text-xs opacity-50 leading-tight">
|
||||
<Trans i18nKey={"sidebar.notice"}>
|
||||
Запись на демонстрацию работает в ознакомительном режиме и не
|
||||
сохраняет введенные данные
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarTab1;
|
||||
@@ -0,0 +1,102 @@
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
import { Trans } from "react-i18next";
|
||||
import useSidebarTabStore from "../stores/useSidebarStore";
|
||||
import TimeSelector from "./TimeSelector";
|
||||
import CloseIcon from "./icons/CloseIcon";
|
||||
import { format } from "date-fns";
|
||||
import i18n from "../i18n";
|
||||
import { enUS, ru } from "date-fns/locale";
|
||||
|
||||
function SidebarTab2() {
|
||||
const [
|
||||
currentTab,
|
||||
setCurrentTab,
|
||||
setIsOpen,
|
||||
setSelectedTime,
|
||||
selectedDay,
|
||||
selectedTime,
|
||||
] = useSidebarTabStore((state) => [
|
||||
state.currentTab,
|
||||
state.setCurrentTab,
|
||||
state.setIsOpen,
|
||||
state.setSelectedTime,
|
||||
state.selectedDay,
|
||||
state.selectedTime,
|
||||
]);
|
||||
|
||||
function handleSelectTime(time: string) {
|
||||
setSelectedTime(time);
|
||||
setCurrentTab(currentTab + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sm:p-8 p-6 flex flex-col justify-between sm:gap-8 gap-6 min-h-full">
|
||||
<div>
|
||||
<div className="flex items-start justify-between">
|
||||
<p className="text-2xl text-gradient font-semibold font-gilroy w-fit leading-snug">
|
||||
<Trans i18nKey={"sidebar.title1"}>Дата и время</Trans>
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="transition-opacity hover:opacity-50"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="border-b border-[#3D425C] p-4 text-center">
|
||||
<p className="leading-none font-gilroy font-semibold text-sm">
|
||||
{selectedDay &&
|
||||
format(
|
||||
selectedDay,
|
||||
"dd MMMM",
|
||||
i18n.language === "ru" ? { locale: ru } : { locale: enUS }
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="border-b border-[#798FFF] p-4 text-center">
|
||||
<p className="leading-none font-gilroy font-semibold text-sm">
|
||||
{selectedTime ? (
|
||||
selectedTime
|
||||
) : (
|
||||
<Trans i18nKey={"sidebar.time"}>Время</Trans>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<TimeSelector
|
||||
handleSelect={(time: string) => handleSelectTime(time)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:gap-6 gap-4">
|
||||
<p className="text-center text-xs opacity-50 leading-tight">
|
||||
<Trans i18nKey={"sidebar.notice"}>
|
||||
Запись на демонстрацию работает в ознакомительном режиме и не
|
||||
сохраняет введенные данные
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="flex sm:gap-4 gap-2">
|
||||
<button
|
||||
onClick={() => setCurrentTab(currentTab - 1)}
|
||||
className="px-6 sm:py-4 py-3.5 border border-[#3D425C] rounded-full font-medium group w-full"
|
||||
>
|
||||
<span className="opacity-80 transition-opacity group-hover:opacity-100 sm:text-base text-sm">
|
||||
<Trans i18nKey={"sidebar.buttonBack"}>Назад</Trans>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarTab2;
|
||||
@@ -0,0 +1,91 @@
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
import { Trans } from "react-i18next";
|
||||
import useSidebarTabStore from "../stores/useSidebarStore";
|
||||
import ContactsForm from "./ContactsForm";
|
||||
import ArrowRightIcon from "./icons/ArrowRightIcon";
|
||||
import CloseIcon from "./icons/CloseIcon";
|
||||
|
||||
function SidebarTab3() {
|
||||
const [
|
||||
currentTab,
|
||||
setCurrentTab,
|
||||
setIsOpen,
|
||||
name,
|
||||
phone,
|
||||
email,
|
||||
] = useSidebarTabStore((state) => [
|
||||
state.currentTab,
|
||||
state.setCurrentTab,
|
||||
state.setIsOpen,
|
||||
state.name,
|
||||
state.phone,
|
||||
state.email,
|
||||
state.selectedDay,
|
||||
state.selectedTime,
|
||||
]);
|
||||
|
||||
function handleSubmit() {
|
||||
if (!name || !phone || !email) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentTab(currentTab + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="sm:p-8 p-6 flex flex-col justify-between sm:gap-8 gap-6 min-h-full"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-start justify-between">
|
||||
<p className="text-2xl text-gradient font-semibold font-gilroy w-fit leading-snug">
|
||||
<Trans i18nKey={"sidebar.title2"}>Контакты</Trans>
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="transition-opacity hover:opacity-50"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="sm:mt-6 mt-4">
|
||||
<ContactsForm />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:gap-6 gap-4">
|
||||
<p className="text-center text-xs opacity-50 leading-tight">
|
||||
<Trans i18nKey={"sidebar.notice"}>
|
||||
Запись на демонстрацию работает в ознакомительном режиме и не
|
||||
сохраняет введенные данные
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="flex sm:gap-4 gap-2">
|
||||
<button
|
||||
onClick={() => setCurrentTab(currentTab - 1)}
|
||||
className="px-6 py-4 border border-[#3D425C] rounded-full sm:text-base text-sm font-medium group w-fit"
|
||||
>
|
||||
<span className="opacity-80 transition-opacity group-hover:opacity-100">
|
||||
<Trans i18nKey={"sidebar.buttonBack"}>Назад</Trans>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="px-6 py-3 bg-gradient rounded-full sm:text-base text-sm font-medium flex items-center justify-between w-full"
|
||||
>
|
||||
<span>
|
||||
<Trans i18nKey={"sidebar.buttonNext"}>Далее</Trans>
|
||||
</span>
|
||||
<ArrowRightIcon className="sm:w-8 sm:h-8 w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarTab3;
|
||||
@@ -0,0 +1,198 @@
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
import { format, parse } from "date-fns";
|
||||
import useSidebarTabStore from "../stores/useSidebarStore";
|
||||
import ArrowRightIcon from "./icons/ArrowRightIcon";
|
||||
import CloseIcon from "./icons/CloseIcon";
|
||||
import { enUS, ru } from "date-fns/locale";
|
||||
import ky from "ky";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18n from "../i18n";
|
||||
import { useState } from "react";
|
||||
import LoaderIcon from "./icons/LoaderIcon";
|
||||
|
||||
function SidebarTab4() {
|
||||
const [
|
||||
currentTab,
|
||||
setCurrentTab,
|
||||
setIsOpen,
|
||||
selectedDay,
|
||||
selectedTime,
|
||||
name,
|
||||
phone,
|
||||
email,
|
||||
] = useSidebarTabStore((state) => [
|
||||
state.currentTab,
|
||||
state.setCurrentTab,
|
||||
state.setIsOpen,
|
||||
state.selectedDay,
|
||||
state.selectedTime,
|
||||
state.name,
|
||||
state.phone,
|
||||
state.email,
|
||||
]);
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
async function handleClickSignUp() {
|
||||
if (!selectedTime || !selectedDay) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const startAt = parse(selectedTime, "HH:mm", selectedDay);
|
||||
|
||||
try {
|
||||
await ky
|
||||
.post("https://coord.graff.tech/scheduled_sessions", {
|
||||
json: {
|
||||
username: "test",
|
||||
name,
|
||||
phone,
|
||||
email,
|
||||
title: "nksJukovaDev",
|
||||
startAt,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
setCurrentTab(currentTab + 1);
|
||||
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
if (error instanceof Error) {
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sm:p-8 p-6 flex flex-col justify-between sm:gap-8 gap-6 min-h-full">
|
||||
<div>
|
||||
<div className="flex items-start justify-between">
|
||||
<p className="text-2xl text-gradient font-semibold font-gilroy w-fit leading-snug">
|
||||
<Trans i18nKey={"sidebar.title3"}>Проверка заявки</Trans>
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="transition-opacity hover:opacity-50"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="sm:mt-6 mt-4">
|
||||
<div className="sm:mt-6 mt-4 sm:p-6 p-4 flex flex-col gap-6 font-semibold font-gilroy border border-[#3D425C] ">
|
||||
<p className="leading-tight">
|
||||
<Trans i18nKey={"sidebar.sessionDetails"}>Детали сеанса</Trans>
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between sm:text-base text-sm">
|
||||
<p className="opacity-50">
|
||||
<Trans i18nKey={"sidebar.date"}>Дата</Trans>
|
||||
</p>
|
||||
<p>
|
||||
{selectedDay &&
|
||||
format(
|
||||
selectedDay,
|
||||
"dd MMMM",
|
||||
i18n.language === "ru" ? { locale: ru } : { locale: enUS }
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between sm:text-base text-sm">
|
||||
<p className="opacity-50">
|
||||
<Trans i18nKey={"sidebar.time"}>Время</Trans>
|
||||
</p>
|
||||
<p>{selectedTime}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:p-6 p-4 flex flex-col gap-6 font-semibold font-gilroy border border-[#3D425C] border-t-0">
|
||||
<p className="leading-tight">
|
||||
<Trans i18nKey={"sidebar.contactDetails"}>
|
||||
Контактные данные
|
||||
</Trans>
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between sm:text-base text-sm">
|
||||
<p className="opacity-50">
|
||||
<Trans i18nKey={"sidebar.name"}>Имя</Trans>
|
||||
</p>
|
||||
<p>{name}</p>
|
||||
</div>
|
||||
<div className="flex justify-between sm:text-base text-sm">
|
||||
<p className="opacity-50">
|
||||
<Trans i18nKey={"sidebar.phone"}>Телефон</Trans>
|
||||
</p>
|
||||
<p>{phone}</p>
|
||||
</div>
|
||||
<div className="flex justify-between sm:text-base text-sm">
|
||||
<p className="opacity-50">Email</p>
|
||||
<p>{email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:p-6 p-4 border border-t-0 border-[#3D425C]">
|
||||
<p className="text-xs">
|
||||
<Trans i18nKey={"sidebar.submitNotice1"}>
|
||||
Нажимая кнопку записаться, вы принимаете
|
||||
</Trans>{" "}
|
||||
<a href="#" className="text-[#798FFF]">
|
||||
<Trans i18nKey={"sidebar.submitNotice2"}>
|
||||
условия использования
|
||||
</Trans>
|
||||
</a>{" "}
|
||||
<Trans i18nKey={"sidebar.submitNotice3"}>и</Trans>{" "}
|
||||
<a href="#" className="text-[#798FFF]">
|
||||
<Trans i18nKey={"sidebar.submitNotice4"}>
|
||||
политику конфиденциальности
|
||||
</Trans>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:gap-6 gap-4">
|
||||
<p className="text-center text-xs opacity-50 leading-tight">
|
||||
<Trans i18nKey={"sidebar.notice"}>
|
||||
Запись на демонстрацию работает в ознакомительном режиме и не
|
||||
сохраняет введенные данные
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="flex sm:gap-4 gap-2">
|
||||
<button
|
||||
onClick={() => setCurrentTab(currentTab - 1)}
|
||||
className="px-6 py-4 border border-[#3D425C] rounded-full sm:text-base text-sm font-medium group w-fit"
|
||||
>
|
||||
<span className="opacity-80 transition-opacity group-hover:opacity-100">
|
||||
<Trans i18nKey={"sidebar.buttonBack"}>Назад</Trans>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
disabled={isLoading}
|
||||
onClick={() => void handleClickSignUp()}
|
||||
className="px-6 py-3 bg-gradient rounded-full sm:text-base text-sm font-medium flex items-center justify-between w-full disabled:opacity-75"
|
||||
>
|
||||
<span>
|
||||
<Trans i18nKey={"sidebar.buttonSignUp"}>Записаться</Trans>
|
||||
</span>
|
||||
{!isLoading ? (
|
||||
<ArrowRightIcon className="sm:w-8 sm:h-8 w-6 h-6" />
|
||||
) : (
|
||||
<LoaderIcon className="sm:w-8 sm:h-8 w-6 h-6 animate-spin" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarTab4;
|
||||
@@ -0,0 +1,97 @@
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
import { Trans } from "react-i18next";
|
||||
import useSidebarTabStore from "../stores/useSidebarStore";
|
||||
import ArrowRightIcon from "./icons/ArrowRightIcon";
|
||||
import MailGradientIcon from "./icons/MailGradientIcon";
|
||||
import PhoneGradientIcon from "./icons/PhoneGradientIcon";
|
||||
import WebGradientIcon from "./icons/WebGradientIcon";
|
||||
|
||||
function SidebarTab5() {
|
||||
const [setIsOpen, name] = useSidebarTabStore((state) => [
|
||||
state.setIsOpen,
|
||||
state.name,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="sm:p-8 p-6 flex flex-col justify-between sm:gap-8 gap-6 min-h-full">
|
||||
<div>
|
||||
<div className="flex items-start justify-between">
|
||||
<p className="text-2xl font-semibold font-gilroy w-fit leading-snug">
|
||||
<span className="text-gradient">{name},</span>
|
||||
<br />
|
||||
<span className="text-gradient">
|
||||
<Trans i18nKey={"sidebar.title4_1"}>спасибо за запись</Trans>
|
||||
</span>
|
||||
<br />
|
||||
<Trans i18nKey={"sidebar.title4_2"}>
|
||||
на удаленную демонстрацию!
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="sm:mt-6 mt-4 text-sm">
|
||||
<Trans i18nKey={"sidebar.tab5text1"}>
|
||||
В ближайшее время мы отправим на ваш почтовый адрес всю
|
||||
дополнительную информацию о сеансе и ссылку для подключения.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="mt-8 pb-6 font-gilroy font-semibold border-b border-[#3D425C]">
|
||||
<p>
|
||||
<Trans i18nKey={"sidebar.tab5text2"}>Возникли вопросы?</Trans>
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex justify-between">
|
||||
<div>
|
||||
<p className="opacity-50 text-sm leading-none">
|
||||
<Trans i18nKey={"sidebar.tab5text3"}>Свяжитесь с нами</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-sm font-gilroy flex gap-3 items-center justify-end leading-none">
|
||||
<a href="tel:88007700076">8 800 770 00 76</a>
|
||||
<PhoneGradientIcon />
|
||||
</p>
|
||||
|
||||
<p className="text-sm font-gilroy flex gap-3 items-center justify-end leading-none">
|
||||
<a href="mailto:info@graff.tech">info@graff.tech</a>
|
||||
<MailGradientIcon />
|
||||
</p>
|
||||
|
||||
<p className="text-sm font-gilroy flex gap-3 items-center justify-end leading-none">
|
||||
<a href="https://estate.graff.tech" target="_blank">
|
||||
estate.graff.tech
|
||||
</a>
|
||||
<WebGradientIcon />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:gap-6 gap-4">
|
||||
<p className="text-center text-xs opacity-50 leading-tight">
|
||||
<Trans i18nKey={"sidebar.notice"}>
|
||||
Запись на демонстрацию работает в ознакомительном режиме и не
|
||||
сохраняет введенные данные
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="flex sm:gap-4 gap-2">
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="px-6 py-3 bg-gradient rounded-full sm:text-base text-sm font-medium flex items-center justify-between w-full"
|
||||
>
|
||||
<span>
|
||||
<Trans i18nKey={"sidebar.buttonHome"}>На главную</Trans>
|
||||
</span>
|
||||
<ArrowRightIcon className="w-8 h-8" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarTab5;
|
||||
@@ -0,0 +1,5 @@
|
||||
function TimePicker() {
|
||||
return <div>TimePicker</div>;
|
||||
}
|
||||
|
||||
export default TimePicker;
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Trans } from "react-i18next";
|
||||
import useSidebarStore from "../stores/useSidebarStore";
|
||||
|
||||
interface TimeSelectorProps {
|
||||
handleSelect: (time: string) => void;
|
||||
}
|
||||
|
||||
function TimeSelector({ handleSelect }: TimeSelectorProps) {
|
||||
const [selectedTime] = useSidebarStore((state) => [state.selectedTime]);
|
||||
|
||||
const times = [
|
||||
{ value: "10:00", active: true },
|
||||
{ value: "10:30", active: false },
|
||||
{ value: "11:00", active: true },
|
||||
{ value: "11:30", active: true },
|
||||
{ value: "12:00", active: true },
|
||||
{ value: "12:30", active: true },
|
||||
{ value: "13:00", active: true },
|
||||
{ value: "13:30", active: true },
|
||||
{ value: "14:00", active: false },
|
||||
{ value: "14:30", active: false },
|
||||
{ value: "15:30", active: true },
|
||||
{ value: "15:00", active: true },
|
||||
{ value: "16:30", active: true },
|
||||
{ value: "16:00", active: true },
|
||||
{ value: "17:30", active: true },
|
||||
{ value: "17:00", active: true },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-4 gap-2 font-medium">
|
||||
{times.map((time, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleSelect(time.value)}
|
||||
disabled={!time.active}
|
||||
className={[
|
||||
"min-w-[40px] h-10 text-[#798FFF] text-sm rounded-full border border-[#798FFF] flex justify-center items-center transition-colors hover:bg-[#798FFF] hover:bg-opacity-20 hover:text-white disabled:text-[#3D425C] disabled:hover:bg-transparent disabled:border-transparent",
|
||||
time.value === selectedTime
|
||||
? "bg-[#798FFF] text-white"
|
||||
: "text-[#798FFF]",
|
||||
].join(" ")}
|
||||
>
|
||||
{time.value}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-[#798FFF] rounded"></div>
|
||||
<p className="text-[#798FFF]">
|
||||
- <Trans i18nKey={"sidebar.available"}>запись доступна</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TimeSelector;
|
||||
@@ -0,0 +1,28 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ArrowRightIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/Arrow_Right">
|
||||
<path
|
||||
id="Vector 106"
|
||||
d="M18 12L6 12M18 12L11.6364 18M18 12L11.6364 6"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="square"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default ArrowRightIcon;
|
||||
@@ -0,0 +1,18 @@
|
||||
function AsteriskIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="12"
|
||||
height="13"
|
||||
viewBox="0 0 12 13"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.81534 12.2727L4.9858 7.58523L1.02273 10.0994L0 8.30966L4.17614 6.13636L0 3.96307L1.02273 2.1733L4.9858 4.6875L4.81534 0H6.8608L6.69034 4.6875L10.6534 2.1733L11.6761 3.96307L7.5 6.13636L11.6761 8.30966L10.6534 10.0994L6.69034 7.58523L6.8608 12.2727H4.81534Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default AsteriskIcon;
|
||||
@@ -0,0 +1,41 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function CheckGradientIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/Check">
|
||||
<path
|
||||
id="Vector 1836 (Stroke)"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M26.3298 9.78103C26.819 10.3314 26.7694 11.1742 26.2191 11.6634L14.2191 22.3301C13.6914 22.7991 12.8896 22.7755 12.3904 22.2763L5.72378 15.6097C5.20308 15.089 5.20308 14.2447 5.72378 13.724C6.24448 13.2033 7.0887 13.2033 7.60939 13.724L13.3871 19.5017L24.4474 9.6703C24.9978 9.18107 25.8406 9.23065 26.3298 9.78103Z"
|
||||
fill="url(#paint0_linear_53_10278)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_53_10278"
|
||||
x1="5.33325"
|
||||
y1="32.1907"
|
||||
x2="29.4088"
|
||||
y2="29.927"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.167052" stopColor="#798FFF" />
|
||||
<stop offset="0.963542" stopColor="#D375FF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default CheckGradientIcon;
|
||||
@@ -0,0 +1,24 @@
|
||||
function ChevronLeftIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon/Chevron_Left">
|
||||
<path
|
||||
id="Vector 106"
|
||||
d="M15 19L8 12L15 5"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChevronLeftIcon;
|
||||
@@ -0,0 +1,24 @@
|
||||
function ChevronRightIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon/Chevron_Right">
|
||||
<path
|
||||
id="Vector 106"
|
||||
d="M9.00002 19L16 12L9.00002 5"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChevronRightIcon;
|
||||
@@ -0,0 +1,24 @@
|
||||
function CloseIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon/Close">
|
||||
<path
|
||||
id="Vector 106"
|
||||
d="M12.0002 11.9999L17.6572 6.34331M12.0002 11.9999L6.34337 6.34302M12.0002 11.9999L17.6571 17.6567M12.0002 11.9999L6.34326 17.6568"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default CloseIcon;
|
||||
@@ -0,0 +1,39 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function LoaderIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/Circle">
|
||||
<path
|
||||
id="Ellipse 221"
|
||||
d="M18.9999 12C19.5523 12 20.0064 11.5505 19.9376 11.0025C19.745 9.46994 19.1116 8.01808 18.1039 6.82871C16.8797 5.38372 15.1826 4.41989 13.3144 4.10872C11.4463 3.79755 9.52839 4.15922 7.90189 5.12938C6.27539 6.09953 5.04582 7.61525 4.43194 9.40685C3.81806 11.1985 3.85968 13.1497 4.54941 14.9135C5.23914 16.6773 6.53224 18.1392 8.19863 19.0391C9.86502 19.9391 11.7966 20.2186 13.6498 19.828C15.1751 19.5066 16.5658 18.7483 17.6578 17.6559C18.0483 17.2653 17.9652 16.6317 17.529 16.2929C17.0927 15.9542 16.4693 16.0409 16.0629 16.4149C15.2735 17.1413 14.2989 17.6472 13.2373 17.8709C11.8475 18.1638 10.3988 17.9541 9.14904 17.2792C7.89928 16.6043 6.92948 15.5079 6.4122 14.1851C5.89491 12.8623 5.86369 11.3989 6.32409 10.0552C6.78449 8.71152 7.70665 7.57476 8.92649 6.84716C10.1463 6.11956 11.5847 5.84832 12.9858 6.08169C14.3869 6.31506 15.6597 7.03791 16.5778 8.12163C17.2791 8.94938 17.7387 9.94667 17.9167 11.0045C18.0083 11.5492 18.4476 12 18.9999 12Z"
|
||||
fill="url(#paint0_angular_16_1186)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="paint0_angular_16_1186"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(12 12) rotate(45) scale(7.9196)"
|
||||
>
|
||||
<stop offset="0.874517" stopColor="white" />
|
||||
<stop offset="0.982613" stopColor="white" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoaderIcon;
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,175 @@
|
||||
function LogoMobileIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="LogoMobile_GRAFFinteractive">
|
||||
<path
|
||||
id="G Base"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M20.008 39.7195C30.8979 39.7195 39.7259 30.828 39.7259 19.8597C39.7259 18.5165 39.5935 17.2043 39.3411 15.9358H27.4287V23.8103H31.2726C29.6515 28.4886 25.2322 31.845 20.0345 31.845C13.4625 31.845 8.13482 26.479 8.13482 19.8597C8.13482 13.2405 13.4625 7.8745 20.0345 7.8745V0H20.008C9.11807 0 0.290039 8.89152 0.290039 19.8597C0.290039 30.828 9.11807 39.7195 20.008 39.7195Z"
|
||||
fill="#798FFF"
|
||||
/>
|
||||
<path
|
||||
id="Inner G"
|
||||
d="M19.0163 0.0241028C10.5443 1.34406 4.05957 8.70687 4.05957 17.592C4.05957 27.4109 11.9788 35.3706 21.7477 35.3706C31.5166 35.3706 39.4359 27.4109 39.4359 17.592C39.4359 17.0257 39.4096 16.4656 39.358 15.9128H27.4228V23.7759H31.269C29.647 28.4474 25.2249 31.7989 20.024 31.7989C13.4479 31.7989 8.11698 26.4407 8.11698 19.831C8.11698 13.5806 12.8841 8.44938 18.9632 7.90996C19.3127 7.87896 19.6665 7.8631 20.024 7.8631V0H19.9975C19.6685 0 19.3413 0.00810087 19.0163 0.0241028Z"
|
||||
fill="#D375FF"
|
||||
/>
|
||||
<path
|
||||
id="G_01"
|
||||
opacity="0.3"
|
||||
d="M17.7148 8.11785C18.4573 7.97602 19.2239 7.90176 20.0079 7.90176V0H19.981C18.3936 0 16.8493 0.183713 15.3684 0.530935L17.7148 8.11785Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="G_02"
|
||||
opacity="0.3"
|
||||
d="M7.31813 4.49037L6.08936 22.3241L7.84573 20.6509C7.8353 20.4452 7.83003 20.2381 7.83003 20.0298C7.83003 13.4214 13.14 8.05568 19.7179 7.98257L11.8297 1.7395C10.2002 2.45588 8.68475 3.38447 7.31813 4.49037Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="G_03"
|
||||
opacity="0.3"
|
||||
d="M0.290807 20.0851L9.2762 24.9334C8.5649 23.4064 8.16779 21.7041 8.16779 19.9091C8.16779 18.1118 8.56599 16.4072 9.27911 14.8786L6.52677 5.50854C2.68747 9.11382 0.290039 14.232 0.290039 19.9091C0.290039 19.9679 0.290296 20.0265 0.290807 20.0851Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="G_04"
|
||||
opacity="0.3"
|
||||
d="M0.290039 19.2891L12.2113 37.1621L25.2275 37.4002L20.1497 31.8045C20.0963 31.8052 20.0428 31.8056 19.9892 31.8056C13.4289 31.8056 8.1107 26.488 8.1107 19.9286C8.1107 19.6619 8.11948 19.3973 8.13679 19.135L0.295555 19.135C0.293519 19.1863 0.291681 19.2377 0.290039 19.2891Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="G_05"
|
||||
opacity="0.3"
|
||||
d="M8.94359 36.2405L16.5283 31.2537C11.6213 29.7362 8.06526 25.2578 8.06526 19.9692C8.06526 19.2897 8.12397 18.6236 8.23671 17.9753L3.1897 30.4732C4.6799 32.7607 6.6425 34.7267 8.94359 36.2405Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="G_06"
|
||||
opacity="0.3"
|
||||
d="M26.3874 38.6783C24.3639 39.3535 22.1958 39.7195 19.9409 39.7195C15.7761 39.7195 11.9074 38.4707 8.69922 36.3323L16.2931 31.3118C17.4522 31.6746 18.6869 31.8704 19.9679 31.8704C20.0152 31.8704 20.0624 31.8702 20.1095 31.8696L26.3874 38.6783Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="G_07"
|
||||
opacity="0.3"
|
||||
d="M24.3574 39.2353C22.9375 39.5522 21.4602 39.7195 19.9434 39.7195C19.2861 39.7195 18.6364 39.6881 17.9954 39.6268L16.2383 31.3118C16.4755 31.3881 16.716 31.4574 16.9594 31.5194L24.3574 39.2353Z"
|
||||
fill="black"
|
||||
fillOpacity="0.4"
|
||||
/>
|
||||
<path
|
||||
id="G_08"
|
||||
opacity="0.3"
|
||||
d="M27.5471 19.135L39.4359 16.3252C39.414 16.1983 39.3911 16.0719 39.367 15.9458H27.5471V19.135Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="G_09"
|
||||
opacity="0.3"
|
||||
d="M30.0187 23.7737L39.436 15.9589C39.4351 15.9545 39.4343 15.9502 39.4334 15.9458L27.8372 23.7737H30.0187Z"
|
||||
fill="black"
|
||||
fillOpacity="0.4"
|
||||
/>
|
||||
<path
|
||||
id="G_10"
|
||||
opacity="0.3"
|
||||
d="M35.5462 15.9458L26.9673 38.2699C30.3467 36.9788 33.2796 34.7825 35.4704 31.977L36.8263 15.9458H35.5462Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="G_11"
|
||||
opacity="0.3"
|
||||
d="M39.3486 15.9458L26.9673 38.2699C34.4249 35.4453 39.726 28.2432 39.726 19.8045C39.726 18.484 39.5961 17.1937 39.3486 15.9458Z"
|
||||
fill="black"
|
||||
fillOpacity="0.4"
|
||||
/>
|
||||
<g id="Cube base">
|
||||
<path d="M31.6154 0H39.4359V7.82793H31.6154V0Z" fill="#798FFF" />
|
||||
<path
|
||||
d="M39.4359 7.82793H31.6154L27.5471 11.8869H35.078L39.4359 7.82793Z"
|
||||
fill="#798FFF"
|
||||
/>
|
||||
<path
|
||||
d="M31.6154 7.82793V0L27.5471 4.34885V11.8869L31.6154 7.82793Z"
|
||||
fill="#798FFF"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
id="Cube_01"
|
||||
opacity="0.3"
|
||||
d="M39.436 6.74606V7.83813L35.1277 11.8868H34.2166V6.66821L39.436 6.74606Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="Cube_02"
|
||||
opacity="0.3"
|
||||
d="M27.5471 11.8869V4.23088L33.0071 3.76904L34.7964 4.24201L27.5471 11.8869Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="Cube_03"
|
||||
opacity="0.3"
|
||||
d="M31.0253 0.869873L27.5471 4.45208V11.887H32.1866L31.0253 0.869873Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="Cube_04"
|
||||
opacity="0.3"
|
||||
d="M27.7534 4.04764L27.5471 4.26983V11.8868L31.9148 7.68376L33.9593 7.60343L38.566 8.37993L38.1279 7.72392L32.6052 1.7395L27.7534 4.04764Z"
|
||||
fill="#D375FF"
|
||||
/>
|
||||
<path
|
||||
id="Cube_05"
|
||||
opacity="0.3"
|
||||
d="M31.8966 0H31.6089L30.4468 1.23821L31.8179 2.02946L31.8966 0Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="Cube_06"
|
||||
opacity="0.3"
|
||||
d="M37.9861 9.33119L35.2733 11.887H34.7964L35.2224 6.7774L37.3326 6.37842L37.9861 9.33119Z"
|
||||
fill="black"
|
||||
fillOpacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="Cube"
|
||||
d="M31.6067 7.82793H39.4359V0H31.6067V7.82793Z"
|
||||
fill="url(#paint0_linear_112_1536)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_112_1536"
|
||||
x1="36.0617"
|
||||
y1="0"
|
||||
x2="36.0617"
|
||||
y2="7.82793"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#D375FF" />
|
||||
<stop offset="1" stopColor="#798FFF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogoMobileIcon;
|
||||
@@ -0,0 +1,50 @@
|
||||
function MailGradientIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon/Mail">
|
||||
<g id="Vector">
|
||||
<path
|
||||
d="M3 8.48345C3 8.09481 3.42397 7.85476 3.75723 8.0547L11.4855 12.6913C11.8022 12.8813 12.1978 12.8813 12.5145 12.6913L20.2428 8.05435C20.576 7.85439 21 8.09445 21 8.4831V17C21 17.5523 20.5523 18 20 18H4C3.44772 18 3 17.5523 3 17V8.48345Z"
|
||||
fill="url(#paint0_linear_56_11108)"
|
||||
/>
|
||||
<path
|
||||
d="M3.54791 5.92875C3.11307 5.66784 3.29805 5 3.80516 5H20.1948C20.702 5 20.8869 5.66784 20.4521 5.92875L12.5145 10.6913C12.1978 10.8813 11.8022 10.8813 11.4855 10.6913L3.54791 5.92875Z"
|
||||
fill="url(#paint1_linear_56_11108)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_56_11108"
|
||||
x1="3"
|
||||
y1="27.2857"
|
||||
x2="23.3585"
|
||||
y2="25.6292"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.167052" stopColor="#798FFF" />
|
||||
<stop offset="0.963542" stopColor="#D375FF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_56_11108"
|
||||
x1="3"
|
||||
y1="27.2857"
|
||||
x2="23.3585"
|
||||
y2="25.6292"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.167052" stopColor="#798FFF" />
|
||||
<stop offset="0.963542" stopColor="#D375FF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MailGradientIcon;
|
||||
@@ -0,0 +1,31 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function MailIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/Mail" opacity="0.8">
|
||||
<g id="Vector">
|
||||
<path
|
||||
d="M4 11.3111C4 10.7929 4.5653 10.4728 5.00965 10.7394L15.314 16.9216C15.7363 17.1749 16.2637 17.1749 16.686 16.9216L26.9903 10.739C27.4347 10.4724 28 10.7924 28 11.3106V22.6665C28 23.4029 27.403 23.9998 26.6667 23.9998H5.33333C4.59695 23.9998 4 23.4029 4 22.6665V11.3111Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M4.73055 7.90483C4.15076 7.55696 4.3974 6.6665 5.07354 6.6665H26.9265C27.6026 6.6665 27.8492 7.55696 27.2695 7.90483L16.686 14.2549C16.2638 14.5083 15.7362 14.5083 15.314 14.2549L4.73055 7.90483Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MailIcon;
|
||||
@@ -0,0 +1,34 @@
|
||||
function PhoneGradientIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon/Phone">
|
||||
<path
|
||||
id="phone"
|
||||
d="M9.93505 6.37967L7.83082 4.2815C7.45441 3.90617 6.84412 3.90617 6.4677 4.2815L4.72211 6.02206C2.54982 8.18809 5.73849 13.3499 8.21649 15.8207C10.6796 18.2768 15.7998 21.4441 17.9721 19.2781L19.7177 17.5375C20.0941 17.1622 20.0941 16.5537 19.7177 16.1783L17.6134 14.0802C17.237 13.7048 16.6267 13.7048 16.2503 14.0802L14.6522 15.6737C14.555 15.7706 14.4313 15.8284 14.3036 15.7778C13.9576 15.6405 13.0744 15.1026 10.9904 13.0548C8.89823 10.999 8.36142 10.0733 8.22847 9.70017C8.17833 9.55945 8.24399 9.42505 8.34989 9.31945L9.9355 7.73841C10.3119 7.36308 10.3115 6.755 9.93505 6.37967Z"
|
||||
fill="url(#paint0_linear_56_11105)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_56_11105"
|
||||
x1="4"
|
||||
y1="31.4286"
|
||||
x2="22.1536"
|
||||
y2="30.3618"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.167052" stopColor="#798FFF" />
|
||||
<stop offset="0.963542" stopColor="#D375FF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default PhoneGradientIcon;
|
||||
@@ -0,0 +1,26 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function PhoneIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/Phone" opacity="0.8">
|
||||
<path
|
||||
id="phone"
|
||||
d="M13.2467 8.50638L10.4411 5.70883C9.93919 5.20838 9.12547 5.20839 8.62358 5.70883L6.29613 8.02957C3.39974 10.9176 7.6513 17.8 10.9553 21.0945C14.2395 24.3692 21.0664 28.5923 23.9628 25.7043L26.2902 23.3835C26.7921 22.8831 26.7921 22.0717 26.2902 21.5713L23.4846 18.7737C22.9827 18.2733 22.169 18.2733 21.6671 18.7737L19.5362 20.8984C19.4067 21.0276 19.2417 21.1047 19.0714 21.0372C18.6101 20.8542 17.4325 20.137 14.6538 17.4066C11.8643 14.6655 11.1485 13.4312 10.9713 12.9337C10.9044 12.7461 10.992 12.5669 11.1332 12.4261L13.2473 10.318C13.7492 9.8176 13.7486 9.00683 13.2467 8.50638Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default PhoneIcon;
|
||||
@@ -0,0 +1,28 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function SendIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/Send">
|
||||
<path
|
||||
id="Vector 164 (Stroke)"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M25.2649 8.4215C25.432 7.92033 25.2874 7.36779 24.8963 7.01269C24.5051 6.65759 23.9412 6.56689 23.4585 6.78145L6.56115 14.2914C4.82296 15.0639 5.043 17.5979 6.88835 18.0593L10.0482 18.8492C10.6608 19.0024 11.3097 18.8572 11.7987 18.4577L19.8248 11.8996C20.0112 11.7473 20.2583 11.9935 20.1068 12.1805L14.0759 19.62C13.5817 20.2296 13.4898 21.0719 13.8407 21.7738L15.8654 25.8233C16.6623 27.417 18.9882 27.2516 19.5517 25.5613L25.2649 8.4215Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default SendIcon;
|
||||
@@ -0,0 +1,25 @@
|
||||
function ShareIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon/Share">
|
||||
<path
|
||||
id="Vector 1835"
|
||||
d="M16 18L5 12L16 6"
|
||||
stroke="#F2F2F2"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<circle id="Ellipse 226" cx="5" cy="12" r="3" fill="#F2F2F2" />
|
||||
<circle id="Ellipse 227" cx="16" cy="6" r="3" fill="#F2F2F2" />
|
||||
<circle id="Ellipse 228" cx="16" cy="18" r="3" fill="#F2F2F2" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default ShareIcon;
|
||||
@@ -0,0 +1,28 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function TelegramIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/Telegram" opacity="0.8">
|
||||
<path
|
||||
id="Path-3"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.35909 15.8481C10.7925 12.8123 15.0929 10.8501 17.2259 9.88748C23.3497 7.1478 24.6226 6.6665 25.4483 6.6665C25.6203 6.6665 26.0331 6.70353 26.3083 6.92566C26.5148 7.11078 26.5836 7.36994 26.618 7.55505C26.6524 7.74016 26.6868 8.14741 26.6524 8.48062C26.3083 12.2199 24.8978 21.3645 24.1409 25.5481C23.8313 27.3252 23.2121 27.9176 22.6272 27.9916C21.3543 28.1027 20.3566 27.066 19.1181 26.2145C17.1915 24.8447 16.0906 23.9932 14.1984 22.6603C12.031 21.1054 13.4415 20.2539 14.6801 18.884C14.9897 18.5138 20.6662 12.9974 20.7694 12.4791C20.7694 12.405 20.8038 12.1829 20.6662 12.0718C20.5286 11.9608 20.3566 11.9978 20.219 12.0348C20.0126 12.0718 16.9163 14.2932 10.8957 18.6619C10.0012 19.3283 9.20995 19.6245 8.48748 19.6245C7.69621 19.6245 6.18246 19.1432 5.04716 18.7359C3.67103 18.2546 2.57013 17.9955 2.67334 17.181C2.77655 16.7367 3.327 16.2924 4.35909 15.8481Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default TelegramIcon;
|
||||
@@ -0,0 +1,28 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function VKIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/VK" opacity="0.8">
|
||||
<path
|
||||
id="Vector"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.44687 8H2.38363C1.50842 8 1.33337 8.39579 1.33337 8.83368C1.33337 9.61432 2.37137 13.4863 6.16892 18.6072C8.70003 22.1044 12.2665 24 15.5118 24C17.4592 24 17.6998 23.5789 17.6998 22.8539V20.2105C17.6998 19.3684 17.8836 19.2 18.5007 19.2C18.9558 19.2 19.7347 19.4189 21.5516 21.1048C23.6285 23.1032 23.9707 24 25.1391 24H28.2024C29.0776 24 29.5152 23.5789 29.2623 22.7478C28.9866 21.92 27.9949 20.7183 26.6786 19.2943C25.9645 18.4825 24.8932 17.6076 24.5694 17.1705C24.1143 16.6088 24.2455 16.3587 24.5694 15.8594C24.5694 15.8594 28.3013 10.8008 28.6916 9.08379C28.8859 8.45895 28.6916 8 27.7639 8H24.7015C23.9226 8 23.5638 8.39579 23.3695 8.83368C23.3695 8.83368 21.8116 12.4867 19.6052 14.8598C18.891 15.5469 18.5663 15.7659 18.1768 15.7659C17.9825 15.7659 17.7007 15.5469 17.7007 14.9229V9.08379C17.7007 8.33432 17.474 8 16.8255 8H12.0118C11.5252 8 11.2329 8.34779 11.2329 8.6779C11.2329 9.38779 12.3357 9.552 12.4494 11.5495V15.8905C12.4494 16.8421 12.2709 17.0147 11.8805 17.0147C10.8425 17.0147 8.31669 13.3448 6.81833 9.14611C6.52425 8.32842 6.22931 8 5.44687 8Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default VKIcon;
|
||||
@@ -0,0 +1,36 @@
|
||||
function WebGradientIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon/Web">
|
||||
<path
|
||||
id="globe"
|
||||
d="M21 12C21 16.9706 16.9706 21 12 21M21 12C21 7.02944 16.9706 3 12 3M21 12H3M12 21C7.02944 21 3 16.9706 3 12M12 21V3M12 21C14.7614 21 17 16.9706 17 12M12 21C9.23858 21 7.00001 16.9706 7.00001 12M3 12C3 7.02944 7.02944 3 12 3M12 3C9.23858 3 7.00001 7.02944 7.00001 12M12 3C14.7614 3 17 7.02944 17 12M17 12H7.00001"
|
||||
stroke="url(#paint0_linear_56_11111)"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="square"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_56_11111"
|
||||
x1="3"
|
||||
y1="33.8571"
|
||||
x2="23.4228"
|
||||
y2="32.657"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.167052" stopColor="#798FFF" />
|
||||
<stop offset="0.963542" stopColor="#D375FF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default WebGradientIcon;
|
||||
@@ -0,0 +1,28 @@
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function YouTubeIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g id="Icon/YouTube" opacity="0.8">
|
||||
<path
|
||||
id="Exclude"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M26.4187 7.2304C27.566 7.54067 28.4695 8.45509 28.7762 9.61601C29.3333 11.7204 29.3333 16.1109 29.3333 16.1109C29.3333 16.1109 29.3333 20.5015 28.7762 22.6058C28.4695 23.7666 27.566 24.6811 26.4187 24.9913C24.339 25.5554 16 25.5554 16 25.5554C16 25.5554 7.66111 25.5554 5.58145 24.9913C4.43418 24.6811 3.53051 23.7666 3.22388 22.6058C2.66663 20.5015 2.66663 16.1109 2.66663 16.1109C2.66663 16.1109 2.66663 11.7204 3.22388 9.61601C3.53051 8.45509 4.43418 7.54067 5.58145 7.2304C7.66111 6.6665 16 6.6665 16 6.6665C16 6.6665 24.339 6.6665 26.4187 7.2304ZM13.7777 12.2219V19.9997L20.4444 16.1109L13.7777 12.2219Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default YouTubeIcon;
|
||||
@@ -0,0 +1,72 @@
|
||||
import { useClipboard } from "use-clipboard-copy";
|
||||
import ShareIcon from "../icons/ShareIcon";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import useModalStore from "../../stores/useModalStore";
|
||||
import CloseIcon from "../icons/CloseIcon";
|
||||
|
||||
function ShareModal() {
|
||||
const [setModal] = useModalStore((state) => [state.setModal]);
|
||||
const clipboard = useClipboard();
|
||||
|
||||
function toastInfo(text: string) {
|
||||
toast.info(text, {
|
||||
position: "bottom-right",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "dark",
|
||||
});
|
||||
}
|
||||
|
||||
function handleClickCopy() {
|
||||
clipboard.copy();
|
||||
toastInfo("Ссылка скопирована в буфер обмена");
|
||||
setModal(null);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative p-10 bg-[#131317] rounded shadow-lg w-[320px]">
|
||||
<div className="flex flex-col gap-8">
|
||||
<p className="font-gilroy text-2xl">
|
||||
Пригласить
|
||||
<br />
|
||||
на демонстрацию
|
||||
</p>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="font-gilroy text-xl">Ссылка для подключения</p>
|
||||
<input
|
||||
ref={clipboard.target}
|
||||
readOnly
|
||||
type="text"
|
||||
className="bg-[#23242A] rounded px-4 py-3 outline-none"
|
||||
value={window.location.href}
|
||||
/>
|
||||
<button
|
||||
onClick={handleClickCopy}
|
||||
className="pl-3 pr-4 py-2 bg-gradient rounded flex items-center gap-1 w-fit"
|
||||
>
|
||||
<ShareIcon />
|
||||
<span className="text-sm">Скопировать</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setModal(null)}
|
||||
className="absolute top-3 right-3 p-2 rounded-full transition-colors hover:bg-white hover:bg-opacity-5"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ToastContainer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ShareModal;
|
||||
Reference in New Issue
Block a user