virtual tour page
This commit is contained in:
@@ -14,12 +14,12 @@ const ButtomPanel = () => {
|
||||
<div className="flex gap-2 pb-6 pl-6">
|
||||
<Button
|
||||
text="Disclaimer"
|
||||
buttonType="tertiary"
|
||||
buttonType="special"
|
||||
className="pl-2"
|
||||
icon={<DisclaimerIcon />}
|
||||
onClick={handleOnDisclaimerClick}
|
||||
/>
|
||||
<Button text="Privacy Policy" buttonType="tertiary" />
|
||||
<Button text="Privacy Policy" buttonType="special" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
|
||||
@@ -18,12 +18,12 @@ const ComplexButtomPanel = () => {
|
||||
<div className="flex gap-2 pb-6 pl-6">
|
||||
<Button
|
||||
text="Disclaimer"
|
||||
buttonType="tertiary"
|
||||
buttonType="special"
|
||||
className="pl-2"
|
||||
icon={<DisclaimerIcon />}
|
||||
onClick={handleOnDisclaimerClick}
|
||||
/>
|
||||
<Button text="Privacy Policy" buttonType="tertiary" />
|
||||
<Button text="Privacy Policy" buttonType="special" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
|
||||
@@ -50,12 +50,20 @@ const MultiRangeSlider = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!firstInputRef.current) return;
|
||||
if (
|
||||
!firstInputRef.current ||
|
||||
multirangeSlider.startValue === multirangeSlider.minValue
|
||||
)
|
||||
return;
|
||||
(firstInputRef.current as HTMLInputElement).focus();
|
||||
}, [multirangeSlider.startValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!secondInputRef.current) return;
|
||||
if (
|
||||
!secondInputRef.current ||
|
||||
multirangeSlider.maxValue === multirangeSlider.endValue
|
||||
)
|
||||
return;
|
||||
(secondInputRef.current as HTMLInputElement).focus();
|
||||
}, [multirangeSlider.endValue]);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ISwitcher } from "../types/switcher";
|
||||
|
||||
interface ISwitchProps {
|
||||
switcher: ISwitcher;
|
||||
onClick: (id: string) => void;
|
||||
onClick: ((id: string) => void) | (() => void);
|
||||
}
|
||||
|
||||
const Switch = ({ switcher, onClick }: ISwitchProps) => {
|
||||
@@ -18,9 +18,8 @@ const Switch = ({ switcher, onClick }: ISwitchProps) => {
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
<div
|
||||
className={`w-5 h-5 bg-[#fff] rounded-full absolute transition-all duration-300 ease-in-out top-[2px] ${
|
||||
switcher.isSwitched ? "left-[18px]" : "left-[2px]"
|
||||
}`}
|
||||
className={`w-5 h-5 bg-[#fff] rounded-full absolute transition-all duration-300 ease-in-out top-[2px]
|
||||
${switcher.isSwitched ? "left-[18px]" : "left-[2px]"}`}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -148,7 +148,7 @@ const SequenceWing = () => {
|
||||
}, [isSidebar]);
|
||||
|
||||
return (
|
||||
<div className="absolute left-0 overflow-hidden h-screen w-screen select-none pointer-events-none">
|
||||
<div className="absolute left-0 overflow-hidden h-screen w-screen select-none ">
|
||||
<div
|
||||
className=" absolute h-[calc(100vh-56px)] right-0 w-1/2 duration-300 transition-all "
|
||||
style={{ right: `${isFloorSidebar ? "0" : "-50%"}` }}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import useModal from "../../store/useModal";
|
||||
import { ILayoutCard } from "../../types/layoutCard";
|
||||
import Button from "../Button";
|
||||
import BookingIcon from "../icons/BookingIcon";
|
||||
import CrossIcon from "../icons/CrossIcon";
|
||||
import HeartIcon from "../icons/Heart";
|
||||
|
||||
interface FavoriteAppartmentCardProps {
|
||||
@@ -8,17 +10,14 @@ interface FavoriteAppartmentCardProps {
|
||||
}
|
||||
|
||||
const FavoriteAppartmentCard = ({ card }: FavoriteAppartmentCardProps) => {
|
||||
const {
|
||||
roveHome,
|
||||
floorEnd,
|
||||
floorStart,
|
||||
wing,
|
||||
units,
|
||||
apartmentType,
|
||||
square,
|
||||
cost,
|
||||
} = card;
|
||||
const { roveHome, floorEnd, floorStart, wing, apartmentType, square, cost } =
|
||||
card;
|
||||
|
||||
const { setModal } = useModal();
|
||||
|
||||
const handleOnSendEquiryClick = () => {
|
||||
setModal(<SendEnquiryModal />);
|
||||
};
|
||||
return (
|
||||
<div className="bg-white flex flex-col p-4 rounded-lg gap-4 cursor-pointer select-none">
|
||||
<div className="flex gap-4 justify-between">
|
||||
@@ -55,10 +54,65 @@ const FavoriteAppartmentCard = ({ card }: FavoriteAppartmentCardProps) => {
|
||||
text="Send Enquiry"
|
||||
className="flex justify-center"
|
||||
buttonType="cta"
|
||||
onClick={handleOnSendEquiryClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SendEnquiryModal = () => {
|
||||
const { setModal } = useModal();
|
||||
|
||||
const handleOnModalClick = () => {
|
||||
setModal(null);
|
||||
};
|
||||
return (
|
||||
<div className="absolute z-50 top-0 left-0 w-screen bg-[#0D192266] h-screen backdrop-blur-[6px] grid grid-cols-12 items-center">
|
||||
<div className="h-full col-span-3 col-start-10 bg-[#F3F3F2] py-6 px-6 flex flex-col justify-between items-center">
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
<div className="flex justify-between pb-4 border-b border-[#E2E2DC] ">
|
||||
<h2 className="text-subheadline-m font-semibold">
|
||||
Apartment purchase enquiry
|
||||
</h2>
|
||||
<Button
|
||||
buttonType="tertiary"
|
||||
icon={<CrossIcon />}
|
||||
onClick={handleOnModalClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-lg bg-white p-4 flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-subheadline-s font-semibold text-[#0D1922]">
|
||||
1 bedroom, 609 Sqft{" "}
|
||||
</p>
|
||||
<p className="text-subheadline-s font-semibold text-[#0D192266]">
|
||||
AED 1,668,888
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-[#00BED7] text-s">1 bedroom, 609 Sqft </p>
|
||||
<div className="flex gap-1 flex-col">
|
||||
<div className="text-[#73787C] flex gap-2 items-center w-fit">
|
||||
<p className="text-caption-m font-semibold leading-4">
|
||||
East Wing
|
||||
</p>
|
||||
<div className="w-1 h-1 bg-[#E2E2DC] rounded-full"></div>
|
||||
<p className="text-caption-m font-semibold leading-4">
|
||||
Floor 11
|
||||
</p>
|
||||
<div className="w-1 h-1 bg-[#E2E2DC] rounded-full"></div>
|
||||
<p className="text-caption-m font-semibold leading-4">
|
||||
№ 213
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FavoriteAppartmentCard;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ILayoutCard } from "../../types/layoutCard";
|
||||
import FavoriteAppartmentCard from "./FavoriteApartmentCard";
|
||||
|
||||
interface FavoriteCardListProps {
|
||||
cards: ILayoutCard[];
|
||||
}
|
||||
|
||||
const FavoriteCardList = ({ cards }: FavoriteCardListProps) => {
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{cards.map((card) => (
|
||||
<FavoriteAppartmentCard card={card} key={card.id} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FavoriteCardList;
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { ILayoutCard } from "../../types/layoutCard";
|
||||
import FavoriteSliderCard from "./FavoriteSliderCard";
|
||||
import Button from "../Button";
|
||||
import RightArrowIcon from "../icons/RightArrowIcon";
|
||||
import LeftArrowIcon from "../icons/LeftArrowIcon";
|
||||
|
||||
interface FavoritesSliderProps {
|
||||
cards: ILayoutCard[];
|
||||
}
|
||||
|
||||
const cols = 4;
|
||||
|
||||
const FavoritesSlider = ({ cards }: FavoritesSliderProps) => {
|
||||
const [offset, setOffset] = useState(0);
|
||||
const cardRef = useRef<HTMLDivElement | null>(null);
|
||||
const [cardWidth, setCardWidth] = useState(0);
|
||||
const [buttonTopPos, setButtonTopPos] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const cardElement = cardRef.current;
|
||||
if (cardElement) {
|
||||
const gap = 16;
|
||||
const width = cardElement.clientWidth + gap;
|
||||
const buttonHeight = cardElement.clientHeight;
|
||||
const _buttonTopPos = buttonHeight / 2 + 20;
|
||||
setCardWidth(width);
|
||||
setButtonTopPos(_buttonTopPos);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleOnLeftBtnClick = () => {
|
||||
if (0 > offset) {
|
||||
setOffset((prev) => prev + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnRightBtnClick = () => {
|
||||
if (offset > -cards.length + cols) {
|
||||
setOffset((prev) => prev - 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-[calc(100vw - 48px)] relative">
|
||||
<div
|
||||
className="absolute -left-2 z-30"
|
||||
style={{ top: `${buttonTopPos}px` }}
|
||||
>
|
||||
<Button
|
||||
buttonType="fab"
|
||||
icon={<LeftArrowIcon />}
|
||||
onClick={handleOnLeftBtnClick}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="absolute -right-2 z-30"
|
||||
style={{ top: `${buttonTopPos}px` }}
|
||||
>
|
||||
<Button
|
||||
buttonType="fab"
|
||||
icon={<RightArrowIcon />}
|
||||
onClick={handleOnRightBtnClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full overflow-hidden">
|
||||
<div
|
||||
className="transition-all ease-in-out duration-300"
|
||||
style={{ transform: `translateX(${offset * cardWidth}px)` }}
|
||||
>
|
||||
<div className="flex w-fit gap-4">
|
||||
{Array.from({ length: Math.floor(cards.length / cols) }).map(
|
||||
(_, index) => {
|
||||
return (
|
||||
<div
|
||||
className="grid grid-cols-4 gap-4 w-[calc(100vw-48px)] h-fit"
|
||||
key={index}
|
||||
>
|
||||
{cards
|
||||
.slice(index * cols, cols + index * cols)
|
||||
.map((card) => (
|
||||
<FavoriteSliderCard
|
||||
elementRef={cardRef}
|
||||
card={card}
|
||||
key={card.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FavoritesSlider;
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ILayoutCard } from "../../types/layoutCard";
|
||||
import Button from "../Button";
|
||||
import BookingIcon from "../icons/BookingIcon";
|
||||
import HeartIcon from "../icons/Heart";
|
||||
|
||||
interface FavoriteSliderCardProps {
|
||||
card: ILayoutCard;
|
||||
elementRef: React.MutableRefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
const FavoriteSliderCard = ({ card, elementRef }: FavoriteSliderCardProps) => {
|
||||
return (
|
||||
<div className="rounded-lg flex flex-col overflow-clip gap-6">
|
||||
<div className="flex flex-col gap-4 bg-white p-4" ref={elementRef}>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex text-subheadline-s font-semibold text-[#0D1922]">
|
||||
<h2>
|
||||
{card.apartmentType}, {card.square} Sqft
|
||||
</h2>
|
||||
</div>
|
||||
<Button buttonType="favorite" icon={<HeartIcon isFilled />} />
|
||||
</div>
|
||||
<img src="/images/layout-1.png" alt="" className="w-full" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 pb-6 border-b">
|
||||
<p className="text-s text-[#73787C]">Price</p>
|
||||
<p className="text-[#0D1922] text-m">AED {card.cost}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 pb-6 border-b">
|
||||
<p className="text-s text-[#73787C]">Total Area</p>
|
||||
<p className="text-[#0D1922] text-m">{card.square} Sqft</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 pb-6 border-b">
|
||||
<p className="text-s text-[#73787C]">Project</p>
|
||||
<p className="text-[#00BED7] text-m">Rove Home {card.roveHome}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 pb-6 border-b">
|
||||
<p className="text-s text-[#73787C]">Section</p>
|
||||
<p className="text-[#0D1922] text-m">{card.wing}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 pb-6 border-b">
|
||||
<p className="text-s text-[#73787C]">Floor</p>
|
||||
<p className="text-[#0D1922] text-m">Floor 11</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 pb-6 border-b">
|
||||
<p className="text-s text-[#73787C]">Number</p>
|
||||
<p className="text-[#0D1922] text-m">213</p>
|
||||
</div>
|
||||
<Button
|
||||
icon={<BookingIcon />}
|
||||
text="Send Enquiry"
|
||||
className="flex justify-center"
|
||||
buttonType="cta"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FavoriteSliderCard;
|
||||
@@ -0,0 +1,21 @@
|
||||
const LeftArrowIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="21"
|
||||
viewBox="0 0 20 21"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.5001 16.3334L6.66675 10.5001L12.5001 4.66675"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeftArrowIcon;
|
||||
@@ -14,11 +14,6 @@ const ApartmentLayout = () => {
|
||||
};
|
||||
return (
|
||||
<div className="p-10 pt-6 rounded-lg bg-white flex flex-col items-center gap-8 relative">
|
||||
<SwitchToggle
|
||||
labels={apartmentLayouts}
|
||||
currentLabel={currentLabel}
|
||||
onClick={handleOnSwitchClick}
|
||||
/>
|
||||
<div className="w-full px-[304px] ">
|
||||
<img className="w-full " src="/images/layout-1.png" alt="" />
|
||||
</div>
|
||||
@@ -27,6 +22,11 @@ const ApartmentLayout = () => {
|
||||
alt=""
|
||||
className="absolute right-10 bottom-10"
|
||||
/>
|
||||
<SwitchToggle
|
||||
labels={apartmentLayouts}
|
||||
currentLabel={currentLabel}
|
||||
onClick={handleOnSwitchClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useLayoutEffect, useRef, useState } from "react";
|
||||
import { layoutsCards } from "../../consts/initialSearchPage";
|
||||
import LayoutCard from "../searchPage/LayoutCard";
|
||||
import SimilarAppartmentCard from "./SimilarAppartmentCard";
|
||||
import Button from "../Button";
|
||||
import RightArrowIcon from "../icons/RightArrowIcon";
|
||||
|
||||
const cols = 4;
|
||||
|
||||
const SimilarSlider = () => {
|
||||
const [cards, setCards] = useState(layoutsCards.slice(0, 4));
|
||||
const [cards, setCards] = useState(layoutsCards);
|
||||
const [offset, setOffset] = useState(0);
|
||||
const sliderRef = useRef<HTMLDivElement | null>(null);
|
||||
const cardRef = useRef<HTMLDivElement | null>(null);
|
||||
const [sliderHeight, setSliderHeight] = useState(0);
|
||||
const [cardWidth, setCardWidth] = useState(0);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const sliderElement = sliderRef.current;
|
||||
if (sliderElement) {
|
||||
const height = sliderElement.clientHeight;
|
||||
setSliderHeight(height);
|
||||
}
|
||||
const cardElement = cardRef.current;
|
||||
if (cardElement) {
|
||||
const gap = 16;
|
||||
@@ -27,21 +22,20 @@ const SimilarSlider = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("offset", offset);
|
||||
|
||||
return () => {};
|
||||
}, [offset]);
|
||||
|
||||
const handleOnLeftBtnClick = () => {
|
||||
if (0 < offset) {
|
||||
if (0 > offset) {
|
||||
setOffset((prev) => prev + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnRightBtnClick = () => {
|
||||
// if (0 > offset) {
|
||||
setOffset((prev) => prev - 1);
|
||||
if (offset > -cards.length + cols) {
|
||||
setOffset((prev) => prev - 1);
|
||||
}
|
||||
// if (offset === -cards.length + cols) {
|
||||
// const updatedCards = [...cards, ...cards.slice(0, 4)];
|
||||
// setCards(updatedCards);
|
||||
// setOffset((prev) => prev - 1);
|
||||
// }
|
||||
};
|
||||
|
||||
@@ -68,32 +62,32 @@ const SimilarSlider = () => {
|
||||
<div className="border-t border-b py-4">
|
||||
<div className="w-[100vw-16px] overflow-hidden">
|
||||
<div
|
||||
className={` relative transition-transform duration-300 ease-in-out`}
|
||||
className={`transition-transform duration-300 ease-in-out`}
|
||||
style={{
|
||||
height: sliderHeight,
|
||||
transform: `translateX(${offset * cardWidth}px)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex w-fit gap-4 absolute left-0 top-0">
|
||||
<div
|
||||
className="grid grid-cols-4 gap-4 w-[calc(100vw-32px)] h-fit"
|
||||
ref={sliderRef}
|
||||
>
|
||||
{cards.map((layoutsCard) => (
|
||||
<SimilarAppartmentCard
|
||||
elementRef={cardRef}
|
||||
layoutCard={layoutsCard}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-4 w-[calc(100vw-32px)]">
|
||||
{cards.map((layoutsCard) => (
|
||||
<SimilarAppartmentCard
|
||||
elementRef={cardRef}
|
||||
layoutCard={layoutsCard}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex w-fit gap-4">
|
||||
{Array.from({ length: Math.floor(cards.length / cols) }).map(
|
||||
(_, index) => {
|
||||
return (
|
||||
<div
|
||||
className="grid grid-cols-4 gap-4 w-[calc(100vw-48px)] h-fit"
|
||||
key={index}
|
||||
>
|
||||
{cards
|
||||
.slice(index * cols, cols + index * cols)
|
||||
.map((card) => (
|
||||
<SimilarAppartmentCard
|
||||
elementRef={cardRef}
|
||||
layoutCard={card}
|
||||
key={card.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@ const SortButton = ({ sortList, onClick }: SortButtonProps) => {
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
className={`absolute flex flex-col bg-white p-2 text-[#0D1922] rounded-lg w-full shadow-lg transition-opacity duration-300 ease-in-out ${
|
||||
className={`absolute z-20 flex flex-col bg-white p-2 text-[#0D1922] rounded-lg w-full shadow-lg transition-opacity duration-300 ease-in-out ${
|
||||
isSelected ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { BackSide, MeshBasicMaterial } from "three";
|
||||
import { OrbitControls, Html, Sphere, useTexture } from "@react-three/drei";
|
||||
import { Suspense, useRef } from "react";
|
||||
import { OrbitControls as OrbitControlsImpl } from "three-stdlib";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const VirtualTourWrapper = () => {
|
||||
const orbitRef = useRef<OrbitControlsImpl>(null);
|
||||
const materialRef = useRef<MeshBasicMaterial>(null);
|
||||
const texture = useTexture(
|
||||
"/images/virtual-tour/studio1/Studio1_w-12_13_sp-01.webp"
|
||||
);
|
||||
const { id } = useParams();
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading ...</div>}>
|
||||
<Sphere
|
||||
args={[1, 64, 64]}
|
||||
position={[-14.16, 0, 24.11]}
|
||||
scale={[-1, 1, 1]}
|
||||
rotation={[0, Math.PI, 0]}
|
||||
>
|
||||
<meshBasicMaterial
|
||||
ref={materialRef}
|
||||
map={texture}
|
||||
side={BackSide}
|
||||
transparent
|
||||
/>
|
||||
</Sphere>
|
||||
<OrbitControls
|
||||
ref={orbitRef}
|
||||
maxDistance={0.001}
|
||||
enableZoom={false}
|
||||
rotateSpeed={0.5}
|
||||
reverseOrbit
|
||||
onChange={() => console.log("e", orbitRef.current?.getAzimuthalAngle())}
|
||||
target={[-14.16, 0, 24.11]}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default VirtualTourWrapper;
|
||||
@@ -4,27 +4,30 @@ const backgroundColors: ButtonStyle = {
|
||||
cta: "bg-[#00BED7] hover:bg-[#00A8BE]",
|
||||
primary: "bg-[#ffffff] hover:bg-[#F3F3F2] active:bg-[#fff]",
|
||||
secondary: "bg-[#ffffff] hover:bg-[#F3F3F2] active:bg-[#fff]",
|
||||
tertiary: "bg-[#0D192266] hover:bg-[#0D1922B2]",
|
||||
tertiary: "",
|
||||
fab: "bg-[#ffffff] hover:bg-[#F3F3F2]",
|
||||
favorite: "bg-[#FFFFFF] hover:bg-[#F3F3F2]",
|
||||
special: "bg-[#0D192266] hover:bg-[#0D1922B2]",
|
||||
};
|
||||
|
||||
const textColors: ButtonStyle = {
|
||||
cta: "text-[#ffffff]",
|
||||
primary: "text-[#0D1922]",
|
||||
secondary: "text-[#0D1922]",
|
||||
tertiary: "text-[#ffffff]",
|
||||
tertiary: "text-[#73787C] hover:text-[#0D1922] active:text-[#00BED7]",
|
||||
fab: "text-[#0D1922]",
|
||||
favorite: "text-[#F3F3F2]",
|
||||
special: "text-[#ffffff]",
|
||||
};
|
||||
|
||||
const borders: ButtonStyle = {
|
||||
cta: "rounded-lg",
|
||||
primary: "rounded-lg border border-[#ffffff] active:border-[#00BED7]",
|
||||
secondary: "rounded-lg border border-[#E2E2DC] active:border-[#00BED7]",
|
||||
tertiary: "rounded-full",
|
||||
tertiary: "",
|
||||
fab: "rounded-full",
|
||||
favorite: "rounded-lg border border-[#E2E2DC]",
|
||||
favorite: "rounded-full border border-[#E2E2DC]",
|
||||
special: "rounded-full",
|
||||
};
|
||||
|
||||
const paddings: ButtonStyle = {
|
||||
@@ -34,6 +37,7 @@ const paddings: ButtonStyle = {
|
||||
tertiary: "py-1 px-3",
|
||||
fab: "py-3 px-6",
|
||||
favorite: "p-[10px]",
|
||||
special: "py-1 px-3",
|
||||
};
|
||||
|
||||
export { textColors, backgroundColors, borders, paddings };
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
[
|
||||
{
|
||||
"id": "studio-1-sp-01",
|
||||
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-01.webp",
|
||||
"position": [-23.12, 0, -13.4],
|
||||
"mapPosition": [97.61, 281.92],
|
||||
"links": [
|
||||
{ "toId": "Dvor_16", "label": "" },
|
||||
{ "toId": "Dvor_17", "label": "Детская площадка" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "studio-1-sp-02",
|
||||
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-02.webp",
|
||||
"position": [-14.16, 0, 24.11],
|
||||
"mapPosition": [39.57, 347.47],
|
||||
"links": [
|
||||
{ "toId": "Dvor_16", "label": "" },
|
||||
{ "toId": "Dvor_2", "label": "" },
|
||||
{ "toId": "Hall_1", "label": "Лобби" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "studio-1-sp-03",
|
||||
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-03.webp",
|
||||
"position": [-22.27, 0, 19.69],
|
||||
"mapPosition": [36.26, 327.64],
|
||||
"links": [
|
||||
{ "toId": "Dvor_1", "label": "" },
|
||||
{ "toId": "Dvor_14", "label": "" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "studio-1-sp-04",
|
||||
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-04.webp",
|
||||
"position": [-52.03, 0, -3.63],
|
||||
"mapPosition": [41.11, 241.29],
|
||||
"links": [
|
||||
{ "toId": "Dvor_15", "label": "" },
|
||||
{ "toId": "Dvor_4", "label": "" },
|
||||
{ "toId": "Dvor_9", "label": "Детская площадка" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "studio-1-sp-05",
|
||||
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-05.webp",
|
||||
"position": [-50.6, 0, -15.06],
|
||||
"mapPosition": [64.39, 228.91],
|
||||
"links": [
|
||||
{ "toId": "Dvor_9", "label": "Детская площадка" },
|
||||
{ "toId": "Dvor_5", "label": "" },
|
||||
{ "toId": "Dvor_3", "label": "" },
|
||||
{ "toId": "Dvor_6", "label": "Детская площадка" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "studio-1-sp-06",
|
||||
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-06.webp",
|
||||
"position": [-34.36, 0, -4.72],
|
||||
"mapPosition": [66.41, 273.23],
|
||||
"links": [
|
||||
{ "toId": "Dvor_4", "label": "" },
|
||||
{ "toId": "Dvor_17", "label": "Детская площадка" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "studio-1-sp-07",
|
||||
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-07.webp",
|
||||
"position": [-40.94, 0, -23.93],
|
||||
"mapPosition": [93.88, 235.03],
|
||||
"links": [
|
||||
{ "toId": "Dvor_17", "label": "Детская площадка" },
|
||||
{ "toId": "Dvor_7", "label": "" },
|
||||
{ "toId": "Dvor_4", "label": "" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -10,6 +10,7 @@ import Search from "./pages/Search";
|
||||
import SearchParticularApartments from "./pages/SearchParticularApartments";
|
||||
import SearchApartment from "./pages/SearchApartment";
|
||||
import Favorites from "./pages/Favorites";
|
||||
import VirtualTour from "./pages/VirtualTour";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@@ -52,6 +53,10 @@ const router = createBrowserRouter([
|
||||
path: "/favorites",
|
||||
element: <Favorites />,
|
||||
},
|
||||
{
|
||||
path: "virtual-tour/:appartmentTypeId",
|
||||
element: <VirtualTour />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
+47
-19
@@ -2,12 +2,14 @@ import { useState, useEffect } from "react";
|
||||
import { sortCardBy } from "../calc/sortCard";
|
||||
import Button from "../components/Button";
|
||||
import Footer from "../components/Footer";
|
||||
import FiltersIcon from "../components/icons/FiltersIcon";
|
||||
import TrashIcon from "../components/icons/TrashIcon";
|
||||
import SortButton from "../components/searchPage/SortButton";
|
||||
import { initialSortList } from "../consts/initialSearchPage";
|
||||
import { ILayoutCard } from "../types/layoutCard";
|
||||
import FavoriteAppartmentCard from "../components/favoritesPage/FavoriteApartmentCard";
|
||||
import Switch from "../components/Switch";
|
||||
import { ISwitcher } from "../types/switcher";
|
||||
import FavoritesSlider from "../components/favoritesPage/FavoriteSlider";
|
||||
import FavoriteCardList from "../components/favoritesPage/FavoriteCardList";
|
||||
|
||||
const favoriteCards: ILayoutCard[] = [
|
||||
{
|
||||
@@ -144,9 +146,16 @@ const favoriteCards: ILayoutCard[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const initialCollectionCompareSwitcher: ISwitcher = {
|
||||
id: "1",
|
||||
title: "collection/compare",
|
||||
isSwitched: false,
|
||||
};
|
||||
|
||||
const Favorites = () => {
|
||||
const [sortList, setSortList] = useState(initialSortList);
|
||||
const [cards, setCards] = useState(favoriteCards);
|
||||
const [switcher, setSwitcher] = useState(initialCollectionCompareSwitcher);
|
||||
|
||||
const handleOnSortClick = (sortId: string) => {
|
||||
const updatedSortList = sortList.map((sort) => {
|
||||
@@ -156,6 +165,12 @@ const Favorites = () => {
|
||||
setSortList(updatedSortList);
|
||||
};
|
||||
|
||||
const handleOnSwitchClick = () => {
|
||||
setSwitcher((prev) => {
|
||||
return { ...prev, isSwitched: !prev.isSwitched };
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const sortedCards = sortCardBy(sortList, favoriteCards);
|
||||
|
||||
@@ -164,37 +179,50 @@ const Favorites = () => {
|
||||
|
||||
return (
|
||||
<div className="overflow-scroll h-screen w-screen pt-14">
|
||||
<div className="p-6">
|
||||
<div className="p-6 pb-16">
|
||||
<div className="pb-6">
|
||||
<div className="flex justify-between w-full items-center border-b pb-[11px]">
|
||||
<div className="flex gap-4 font-semibold text-subheadline-s leading-7 py-[6px]">
|
||||
<h2 className="text-[#0D1922]">Units</h2>
|
||||
<p className="text-[#73787C]">145</p>
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="flex gap-4 font-semibold text-subheadline-s leading-7 py-[6px]">
|
||||
<h2 className="text-[#0D1922]">Units</h2>
|
||||
<p className="text-[#73787C]">145</p>
|
||||
</div>
|
||||
<SortButton sortList={sortList} onClick={handleOnSortClick} />
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
buttonType="fab"
|
||||
icon={<FiltersIcon />}
|
||||
className="text-[#73787C]"
|
||||
text={"Compare"}
|
||||
/>
|
||||
<Button
|
||||
buttonType="fab"
|
||||
buttonType="tertiary"
|
||||
icon={<TrashIcon />}
|
||||
className="text-[#73787C]"
|
||||
text="Remove all"
|
||||
/>
|
||||
<div className="flex gap-2 items-center text-s">
|
||||
<p
|
||||
className={`transition-all duration-300 ease-in-out ${
|
||||
!switcher.isSwitched ? "text-[#0D1922]" : "text-[#73787C]"
|
||||
}`}
|
||||
>
|
||||
Collection
|
||||
</p>
|
||||
<Switch switcher={switcher} onClick={handleOnSwitchClick} />
|
||||
<p
|
||||
className={`transition-all duration-300 ease-in-out ${
|
||||
switcher.isSwitched ? "text-[#0D1922]" : "text-[#73787C]"
|
||||
}`}
|
||||
>
|
||||
Compare
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<SortButton sortList={sortList} onClick={handleOnSortClick} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{cards.map((card) => (
|
||||
<FavoriteAppartmentCard card={card} key={card.id} />
|
||||
))}
|
||||
</div>
|
||||
{switcher.isSwitched ? (
|
||||
<FavoritesSlider cards={cards} />
|
||||
) : (
|
||||
<FavoriteCardList cards={cards} />
|
||||
)}
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import ButtomPanel from "../components/ButtomPanel";
|
||||
import VirtualTourWrapper from "../components/virtualTour/VirtualTourWrapper";
|
||||
|
||||
const VirtualTour = () => {
|
||||
return (
|
||||
<div className="overflow-hidden h-screen w-screen">
|
||||
{/* <TopPanel /> */}
|
||||
<div className="absolute w-screen h-screen grid grid-cols-12 z-30 pointer-events-none">
|
||||
<div className="col-span-3 h-screen py-[68px] px-3">
|
||||
<div className="bg-white w-full rounded-lg p-4 flex flex-col">
|
||||
<div className="flex flex-col gap-1 pb-[18px] border-b">
|
||||
<p className="text-[#00BED7] text-caption-m font-medium">
|
||||
Rove Home Marasi Drive{" "}
|
||||
</p>
|
||||
<div className="text-[#73787C] flex gap-2 items-center w-fit">
|
||||
<p className="text-caption-m font-semibold leading-4">
|
||||
East Wing
|
||||
</p>
|
||||
<div className="w-1 h-1 bg-[#E2E2DC] rounded-full"></div>
|
||||
<p className="text-caption-m font-semibold leading-4">
|
||||
Floor 11
|
||||
</p>
|
||||
<div className="w-1 h-1 bg-[#E2E2DC] rounded-full"></div>
|
||||
<p className="text-caption-m font-semibold leading-4">№ 213</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Canvas>
|
||||
<VirtualTourWrapper />
|
||||
</Canvas>
|
||||
<ButtomPanel />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VirtualTour;
|
||||
+2
-1
@@ -4,7 +4,8 @@ type ButtonType =
|
||||
| "cta"
|
||||
| "fab"
|
||||
| "secondary"
|
||||
| "favorite";
|
||||
| "favorite"
|
||||
| "special";
|
||||
type ButtonStyle = {
|
||||
[key in ButtonType]: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user