411 lines
17 KiB
TypeScript
411 lines
17 KiB
TypeScript
/* eslint-disable @next/next/no-img-element */
|
||
// import { createRef, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||
// import { useSwipeable } from "react-swipeable";
|
||
// import { motion } from "framer-motion";
|
||
// import { Transition } from "react-transition-group";
|
||
// import ArrowLeftIcon from "./icons/ArrowLeftIcon";
|
||
// import ArrowRightIcon from "./icons/ArrowRightIcon";
|
||
// import { Video } from "../types/Video";
|
||
import { createRef, useEffect, useLayoutEffect, useState } from "react";
|
||
import { useInView } from "react-intersection-observer";
|
||
import { useSwipeable } from "react-swipeable";
|
||
import { Video } from "../types/Video";
|
||
import ArrowLeftIcon from "./icons/ArrowLeftIcon";
|
||
import ArrowRightIcon from "./icons/ArrowRightIcon";
|
||
import { Transition } from "react-transition-group";
|
||
|
||
type Review = {
|
||
id: string;
|
||
avatar: string;
|
||
name: string;
|
||
position: string;
|
||
desc: string;
|
||
video: string;
|
||
poster: string;
|
||
};
|
||
|
||
const REVIEWS: Review[] = [
|
||
{
|
||
id: "1",
|
||
avatar: "/images/reviews/avatars/1.png",
|
||
name: "Егор бобров",
|
||
position: "Коммерческий директор авторского квартала «Машаров»",
|
||
desc: "Эффективность инструмента была подтверждена буквально в первый день после его внедрения. Например, один из клиентов, посетив офис и увидев свою будущую квартиру с помощью интерактивной панели, сразу решил купить недвижимость в этом проекте, отказавшись от других вариантов",
|
||
poster: "/images/reviews/1.png",
|
||
video: "",
|
||
},
|
||
{
|
||
id: "2",
|
||
avatar: "/images/reviews/avatars/2.png",
|
||
name: "Олег Бондорев",
|
||
position: "Ведущий менеджер компании «ЭНКО»",
|
||
desc: "Клиенты особенно ценят возможность легко выбрать квартиру с помощью 3D-модель жилого комплекса. Так же инструмент продаж позволяет клиенту увидеть расположение дома, в какое время свет будет попадать в окна, даже зайти в лифт и холл, а также оценить инфраструктуру района и «прочувствовать» его, прогулявшись по двору",
|
||
poster: "/images/reviews/1.png",
|
||
video: "",
|
||
},
|
||
{
|
||
id: "3",
|
||
avatar: "/images/reviews/avatars/3.png",
|
||
name: "Алина Веселова",
|
||
position: "Ведущий специалист отдела продаж",
|
||
desc: "Одним из преимуществ инструмента является возможность посмотреть 3D-модель квартиры с готовым дизайнерским ремонтом и оценить видовые характеристиками, изменяя время суток (день или ночь). Это важно для клиента, так как позволяет реалистично представить, как будет выглядеть будущее жилье.",
|
||
poster: "/images/reviews/2.png",
|
||
video: "",
|
||
},
|
||
];
|
||
|
||
// const REVIEWS: Video[] = [
|
||
// {
|
||
// id: "1",
|
||
// value: "/images/reviews/1.png",
|
||
// title: "",
|
||
// desc: "Интерактивный комплекс GRAFF.estate для ЖК Upside Towers, Москва",
|
||
// poster: "",
|
||
// },
|
||
// {
|
||
// id: "2",
|
||
// value: "/images/reviews/2.png",
|
||
// title: "",
|
||
// desc: "Graff.estate на выставке 100+ TechnoBuild",
|
||
// poster: "",
|
||
// },
|
||
// {
|
||
// id: "3",
|
||
// value: "/videos/histories/3.mp4",
|
||
// title: "",
|
||
// desc: "Интерактивная инсталляция graff.estate для ЖК DNS City в г.Владивосток",
|
||
// poster: "",
|
||
// },
|
||
// ];
|
||
|
||
// const Reviews = () => {
|
||
// const [isViewportEntered, setIsViewportEntered] = useState<boolean>(false);
|
||
|
||
// function handleViewportEnter() {
|
||
// if (isViewportEntered) return;
|
||
// setIsViewportEntered(true);
|
||
// }
|
||
// const [cardWidth, setCardWidth] = useState(0);
|
||
// const handlers = useSwipeable({
|
||
// onSwipedLeft: handleOnRightMove,
|
||
// onSwipedRight: handleOnLeftMove,
|
||
// trackMouse: true,
|
||
// });
|
||
// const [selectedVideo, setSelectedVideo] = useState<number>(0);
|
||
// const [_document, setDocument] = useState<Document>();
|
||
|
||
// function handleOnLeftMove(): void {
|
||
// if (selectedVideo > 0) {
|
||
// setSelectedVideo((prev) => prev - 1);
|
||
// }
|
||
// }
|
||
|
||
// function handleOnRightMove(): void {
|
||
// if (selectedVideo < 2) {
|
||
// setSelectedVideo((prev) => prev + 1);
|
||
// }
|
||
// }
|
||
|
||
// useEffect(() => {
|
||
// setDocument(document);
|
||
// }, []);
|
||
|
||
// useEffect(() => {
|
||
// if (!_document) return;
|
||
|
||
// const clientWidth = _document.children[0].clientWidth;
|
||
|
||
// if (clientWidth >= 1600) {
|
||
// setCardWidth(1000);
|
||
// } else if (clientWidth >= 1280) {
|
||
// setCardWidth(805);
|
||
// } else if (clientWidth >= 640) {
|
||
// setCardWidth(736);
|
||
// } else {
|
||
// setCardWidth(clientWidth - 16);
|
||
// }
|
||
// }, [_document]);
|
||
|
||
// return (
|
||
// <motion.div
|
||
// onViewportEnter={handleViewportEnter}
|
||
// className="container mx-auto 2xl:max-w-screen-2xl flex flex-col justify-center 2xl:mb-[200px] sm:mb-[120px] mb-20"
|
||
// >
|
||
// <div className="sm:border-b border-b-[#3D425C] 2xl:h-[594px] xl:h-[484px] sm:h-[404px] h-full">
|
||
// <div className="flex sm:h-full h-fit sm:flex-row flex-col sm:w-full sm:mx-0 mx-auto">
|
||
// <div className=" flex flex-col justify-between 2xl:min-w-[496px] xl:min-w-[395px] sm:min-w-[263px] sm:pb-6 xl:pb-10 sm:h-full">
|
||
// <div className="2xl:pr-10 xl:pr-8 sm:pr-[37px]">
|
||
// <h2 className="font-medium 2xl:text-[64px] xl:text-5xl text-[40px] leading-10 sm:mb-8 mb-6 font-gilroy">
|
||
// <p className="from-[#798FFF] to-[#D375FF] bg-gradient-to-r bg-clip-text text-transparent">
|
||
// Отзывы
|
||
// </p>
|
||
// <p>клиентов</p>
|
||
// </h2>
|
||
// <div className="flex gap-6 2xl:pb-8 xl:pb-6 ">
|
||
// <div className="w-[72px] h-[72px] bg-white rounded-full" />
|
||
// <div>
|
||
// <h2 className="text-2xl font-semibold">Егор Бобров</h2>
|
||
// <p className="w-[272px] opacity-80">
|
||
// Коммерческий директор авторского квартала «Машаров»{" "}
|
||
// </p>
|
||
// </div>
|
||
// </div>
|
||
// <div
|
||
// className={`absolute 2xl:w-[456px] xl:w-[363px] sm:w-[350px] w-[280px] transition-opacity duration-300 mb-7`}
|
||
// >
|
||
// <p className="font-normal 2xl:text-[16px] xl:text-[14px] sm:block hidden">
|
||
// Эффективность инструмента была подтверждена буквально в первый
|
||
// день после его внедрения. Например, один из клиентов, посетив
|
||
// офис и увидев свою будущую квартиру с помощью интерактивной
|
||
// панели, сразу решил купить недвижимость в этом проекте,
|
||
// отказавшись от других вариантов
|
||
// </p>
|
||
// </div>
|
||
// {/* {VIDEOS.map((item, index) => (
|
||
// <Transition
|
||
// key={index}
|
||
// in={Boolean(index === selectedVideo)}
|
||
// timeout={300}
|
||
// >
|
||
// {(state) => (
|
||
// <div
|
||
// className={`absolute 2xl:w-[456px] xl:w-[363px] sm:w-[350px] w-[280px] transition-opacity duration-300 ${state} mb-7`}
|
||
// >
|
||
// <p className="font-normal 2xl:text-[16px] xl:text-[14px] sm:block hidden">
|
||
// Эффективность инструмента была подтверждена буквально в
|
||
// первый день после его внедрения. Например, один из
|
||
// клиентов, посетив офис и увидев свою будущую квартиру с
|
||
// помощью интерактивной панели, сразу решил купить
|
||
// недвижимость в этом проекте, отказавшись от других
|
||
// вариантов
|
||
// </p>
|
||
// </div>
|
||
// )}
|
||
// </Transition>
|
||
// ))} */}
|
||
// </div>
|
||
// <div className="gap-2 sm:flex hidden">
|
||
// <button
|
||
// className="p-4 border border-[#3D425C] rounded-full"
|
||
// onClick={handleOnLeftMove}
|
||
// >
|
||
// <ArrowLeftIcon />
|
||
// </button>
|
||
// <button
|
||
// className="p-4 border border-[#3D425C] rounded-full"
|
||
// onClick={handleOnRightMove}
|
||
// >
|
||
// <ArrowRightIcon />
|
||
// </button>
|
||
// </div>
|
||
// </div>
|
||
// <div className="relative sm:border-l border-l-[#3D425C] sm:h-full h-[548px] w-full">
|
||
// <div className="absolute overflow-x-clip h-full ">
|
||
// <div {...handlers} className="absolute h-full w-full z-10" />
|
||
// <div
|
||
// className="flex gap-4 w-full h-full transition-all duration-300 ease-in-out 2xl:pl-10 xl:pl-8 sm:pl-3 xl:pb-10 sm:pb-[25px] "
|
||
// style={{
|
||
// transform: `translateX(${-selectedVideo * cardWidth}px)`,
|
||
// }}
|
||
// >
|
||
// {VIDEOS.map((video, index) => (
|
||
// <div
|
||
// key={video.id}
|
||
// className={`relative 2xl:w-[984px] xl:w-[789px] sm:w-[720px] w-[calc(100vw-32px)] 2xl:h-[554px] xl:h-[444px] sm:h-[404px] h-[206px] transition-opacity duration-300 ${
|
||
// selectedVideo === index ? "opacity-100" : "opacity-60"
|
||
// }`}
|
||
// >
|
||
// <img src="/images/posters/integra_crm.jpg" alt="img" />
|
||
// </div>
|
||
// ))}
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
// <div className=" p-6 block sm:hidden mb-6">
|
||
// <h2 className="font-semibold text-sm leading-[18px] pb-2">
|
||
// {VIDEOS[selectedVideo].title}
|
||
// </h2>
|
||
// <p className="text-[12px] leading-[18px]">
|
||
// {VIDEOS[selectedVideo].desc}
|
||
// </p>
|
||
// </div>
|
||
// <div className="mx-auto flex gap-2 h-2 sm:hidden">
|
||
// {VIDEOS.map((video, index) => (
|
||
// <div
|
||
// key={video.id}
|
||
// className="h-full w-2 rounded-full transition-all duration-300"
|
||
// style={{
|
||
// background: `${
|
||
// selectedVideo === index ? "white" : "#a1a2a6"
|
||
// }`,
|
||
// }}
|
||
// />
|
||
// ))}
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
// </motion.div>
|
||
// );
|
||
// };
|
||
function Reviews() {
|
||
const [selectedVideoIndex, setSelectedVideoIndex] = useState(0);
|
||
const videoRefs = REVIEWS.map(() => createRef<HTMLImageElement>());
|
||
const [videoWidth, setVideoWidth] = useState<number>(0);
|
||
const { ref, inView } = useInView();
|
||
const [isEntered, setIsEntered] = useState(false);
|
||
const handlers = useSwipeable({
|
||
onSwipedLeft: next,
|
||
onSwipedRight: prev,
|
||
trackMouse: true,
|
||
});
|
||
|
||
useEffect(() => {
|
||
if (videoRefs[0].current) {
|
||
const width = videoRefs[0].current.clientWidth;
|
||
setVideoWidth(width);
|
||
}
|
||
}, [videoRefs]);
|
||
|
||
function prev() {
|
||
if (selectedVideoIndex === 0) return;
|
||
setSelectedVideoIndex((prev) => prev - 1);
|
||
}
|
||
|
||
function next() {
|
||
if (selectedVideoIndex === REVIEWS.length - 1) return;
|
||
setSelectedVideoIndex((prev) => prev + 1);
|
||
}
|
||
|
||
useEffect(() => {
|
||
if (!inView || isEntered) return;
|
||
|
||
setIsEntered(true);
|
||
}, [inView]);
|
||
|
||
return (
|
||
<div
|
||
ref={ref}
|
||
className="container mx-auto 2xl:max-w-screen-2xl mb-[120px]"
|
||
>
|
||
<div className="flex 2xl:h-[594px] xl:h-[484px] sm:h-[784px] h-[552px] sm:border-b border-b-[#3D425C] xl:flex-row sm:flex-col-reverse flex-col ">
|
||
<div className="left 2xl:min-w-[496px] xl:min-w-[395px] sm:min-w-[354px] flex flex-col sm:pb-10 pb-6">
|
||
<h2 className="font-medium 2xl:text-[64px] xl:text-5xl text-[40px] sm:mb-10 mb-6 font-gilroy leading-10">
|
||
<p className="from-[#798FFF] to-[#D375FF] bg-gradient-to-r bg-clip-text text-transparent">
|
||
Отзывы
|
||
</p>
|
||
<p>клиентов</p>
|
||
</h2>
|
||
<div className="relative bg-white sm:block hidden">
|
||
{REVIEWS.map((item, index) => (
|
||
<Transition
|
||
key={index}
|
||
in={Boolean(index === selectedVideoIndex)}
|
||
timeout={300}
|
||
>
|
||
{(state) => (
|
||
<div
|
||
className={`absolute transition-opacity duration-300 pr-10 ${state}`}
|
||
>
|
||
{item.name ? (
|
||
<p className="font-semibold mb-4 2xl:text-xl xl:text-[16px]">
|
||
{item.name}
|
||
</p>
|
||
) : (
|
||
<></>
|
||
)}
|
||
<p className="font-normal 2xl:text-[16px] xl:text-[14px]">
|
||
{item.desc}
|
||
</p>
|
||
</div>
|
||
)}
|
||
</Transition>
|
||
))}
|
||
</div>
|
||
<div className="gap-2 hidden sm:flex mt-auto">
|
||
<button
|
||
className="p-4 border border-[#3D425C] rounded-full"
|
||
onClick={prev}
|
||
>
|
||
<ArrowLeftIcon />
|
||
</button>
|
||
<button
|
||
className="p-4 border border-[#3D425C] rounded-full"
|
||
onClick={next}
|
||
>
|
||
<ArrowRightIcon />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div
|
||
{...handlers}
|
||
className="right relative sm:border-l border-l-[#3D425C] flex gap-4 sm:pb-10 sm:pl-10 select-none"
|
||
style={{
|
||
clipPath: `rect(auto auto auto 0)`,
|
||
}}
|
||
>
|
||
{REVIEWS.map((video, index) => (
|
||
<div
|
||
key={video.id}
|
||
className="relative sm:aspect-[16/9] aspect-auto bg-black transition-transform duration-300 sm:h-full sm:min-w-0 h-[546px]"
|
||
// className="relative sm:aspect-[16/9] aspect-auto bg-black transition-transform duration-300 sm:h-full sm:min-w-0 h-[546px]"
|
||
style={{
|
||
transform: `translateX(-${
|
||
videoWidth * selectedVideoIndex + 16 * selectedVideoIndex
|
||
}px)`,
|
||
}}
|
||
>
|
||
<img
|
||
src={isEntered ? video.poster : undefined}
|
||
alt={video.desc}
|
||
ref={videoRefs[index]}
|
||
className="aspect-[16/9] object-cover w-full h-full min-w-screen"
|
||
/>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="relative block sm:hidden h-[100%] bg-[#212431] p-6">
|
||
{REVIEWS.map((item, index) => (
|
||
<Transition
|
||
key={index}
|
||
in={Boolean(index === selectedVideoIndex)}
|
||
timeout={300}
|
||
>
|
||
{(state) => (
|
||
<div
|
||
className={`absolute transition-opacity duration-300 pr-10 ${state}`}
|
||
>
|
||
{item.name ? (
|
||
<p className="font-semibold mb-4 2xl:text-xl xl:text-[16px]">
|
||
{item.name}
|
||
</p>
|
||
) : (
|
||
<></>
|
||
)}
|
||
<p className="font-normal 2xl:text-[16px] xl:text-[14px]">
|
||
{item.desc}
|
||
</p>
|
||
</div>
|
||
)}
|
||
</Transition>
|
||
))}
|
||
</div>
|
||
<div className="mx-auto flex gap-2 min-h-2 sm:hidden relative mt-6">
|
||
{REVIEWS.map((video, index) => (
|
||
<div
|
||
key={video.id}
|
||
className="h-full w-2 rounded-full transition-all duration-300"
|
||
style={{
|
||
background: `${
|
||
selectedVideoIndex === index ? "white" : "#a1a2a6"
|
||
}`,
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default Reviews;
|