virtual page
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
import useModal from "../store/useModal";
|
import useModal from "../store/useModal";
|
||||||
import useSequence from "../store/useSequence";
|
import useCompass from "../store/useCompass";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import DisclaimerIcon from "./icons/DisclaimerIcon";
|
import DisclaimerIcon from "./icons/DisclaimerIcon";
|
||||||
import DisclaimerModal from "./modals/DisclaimerModal";
|
import DisclaimerModal from "./modals/DisclaimerModal";
|
||||||
|
|
||||||
const ComplexButtomPanel = () => {
|
const ButtomPanelCompass = () => {
|
||||||
const { setModal } = useModal();
|
const { setModal } = useModal();
|
||||||
const { currentCompassRotate } = useSequence();
|
const { currentCompassRotate } = useCompass();
|
||||||
|
|
||||||
const handleOnDisclaimerClick = () => {
|
const handleOnDisclaimerClick = () => {
|
||||||
setModal(<DisclaimerModal />);
|
setModal(<DisclaimerModal />);
|
||||||
@@ -40,4 +40,4 @@ const ComplexButtomPanel = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ComplexButtomPanel;
|
export default ButtomPanelCompass;
|
||||||
@@ -8,13 +8,11 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import Button from "../Button";
|
import Button from "../Button";
|
||||||
import LeftArrowSliderIcon from "../icons/LeftArrowSliderIcon";
|
import LeftArrowSliderIcon from "../icons/LeftArrowSliderIcon";
|
||||||
import RightArrowSliderIcon from "../icons/RightArrowSliderIcon";
|
import RightArrowSliderIcon from "../icons/RightArrowSliderIcon";
|
||||||
import useSequence from "../../store/useSequence";
|
import useCompass from "../../store/useCompass";
|
||||||
import SequenceHighlighting from "./SequenceHighlighting";
|
import SequenceHighlighting from "./SequenceHighlighting";
|
||||||
|
|
||||||
const arrayLength = 360;
|
const arrayLength = 360;
|
||||||
const keyframes: number[] = [51, 178, 249, 339];
|
const keyframes: number[] = [51, 178, 249, 339];
|
||||||
// const keyframes: number[] = [0, 60, 120, 180, 240, 300];
|
|
||||||
// const keyframes: number[] = [50, 177, 248, 338];
|
|
||||||
|
|
||||||
interface SequenceSliderProps {
|
interface SequenceSliderProps {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -28,16 +26,11 @@ function SequenceSlider({ path }: SequenceSliderProps) {
|
|||||||
const [isAnimate, setIsAnimate] = useState<boolean>(false);
|
const [isAnimate, setIsAnimate] = useState<boolean>(false);
|
||||||
const [loadedImages, setLoadedImages] = useState<number>(0);
|
const [loadedImages, setLoadedImages] = useState<number>(0);
|
||||||
const [keyframeIndex, setKeyframeIndex] = useState<number>(3);
|
const [keyframeIndex, setKeyframeIndex] = useState<number>(3);
|
||||||
const { setCurrentCompassRotate, currentCompassRotate } = useSequence();
|
const { setCurrentCompassRotate, currentCompassRotate } = useCompass();
|
||||||
const [width, setWidth] = useState<number>();
|
const [width, setWidth] = useState<number>();
|
||||||
const [top, setTop] = useState<number>();
|
const [top, setTop] = useState<number>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("selectedImageRightIndex", selectedImageRightIndex);
|
|
||||||
console.log("isAnimate", isAnimate);
|
|
||||||
}, [selectedImageRightIndex]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentCompassRotate(keyframes[3]);
|
setCurrentCompassRotate(keyframes[3]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
const ChevronDownIcon = () => {
|
interface ChevronDownIconProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChevronDownIcon = ({ className }: ChevronDownIconProps) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
className={`${!className ? "" : className}`}
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Sphere, useTexture } from "@react-three/drei";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import { BackSide, MeshBasicMaterial } from "three";
|
||||||
|
import { IAppartmentSphere } from "../../types/apartmentSphere";
|
||||||
|
import gsap from "gsap";
|
||||||
|
// import gsap from "gsap";
|
||||||
|
|
||||||
|
interface SphereTourProps {
|
||||||
|
sphere: IAppartmentSphere;
|
||||||
|
selectedSphere: IAppartmentSphere;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SphereTour = ({ sphere, selectedSphere }: SphereTourProps) => {
|
||||||
|
const materialRef = useRef<MeshBasicMaterial>(null);
|
||||||
|
const texture = useTexture(selectedSphere.sphereImage);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const opacity = Number(selectedSphere.id === sphere.id);
|
||||||
|
gsap.to(materialRef.current, { opacity, duration: 1 });
|
||||||
|
}, [selectedSphere, sphere]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SphereTour;
|
||||||
@@ -1,39 +1,58 @@
|
|||||||
import { BackSide, MeshBasicMaterial } from "three";
|
import { OrbitControls, Html, useTexture } from "@react-three/drei";
|
||||||
import { OrbitControls, Html, Sphere, useTexture } from "@react-three/drei";
|
import { Suspense, useEffect, useRef, useState } from "react";
|
||||||
import { Suspense, useRef } from "react";
|
|
||||||
import { OrbitControls as OrbitControlsImpl } from "three-stdlib";
|
import { OrbitControls as OrbitControlsImpl } from "three-stdlib";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
import useCompass from "../../store/useCompass";
|
||||||
|
import SphereTour from "./SphereTour";
|
||||||
|
import { spheres } from "../../consts/spheres";
|
||||||
|
import useSphere from "../../store/useSphere";
|
||||||
|
|
||||||
const VirtualTourWrapper = () => {
|
const VirtualTourWrapper = () => {
|
||||||
const orbitRef = useRef<OrbitControlsImpl>(null);
|
const orbitRef = useRef<OrbitControlsImpl>(null);
|
||||||
const materialRef = useRef<MeshBasicMaterial>(null);
|
|
||||||
const texture = useTexture(
|
const { setCurrentCompassRotate, currentCompassRotate } = useCompass();
|
||||||
"/images/virtual-tour/studio1/Studio1_w-12_13_sp-01.webp"
|
|
||||||
);
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const [startRotatingPos, setStartRotatingPos] = useState(0);
|
||||||
|
const { selectedSphere, setSelectedSphere } = useSphere();
|
||||||
|
// const [selectedSphere, setSelectedSphere] = useState<IAppartmentSphere>(
|
||||||
|
// spheres[1]
|
||||||
|
// );
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedSphere(spheres[0]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOnRotating = () => {
|
||||||
|
const radian = orbitRef.current?.getAzimuthalAngle();
|
||||||
|
if (radian) {
|
||||||
|
const currentCompasDegrees = (radian * 180) / Math.PI + 180;
|
||||||
|
const compassOffsetDegres = startRotatingPos - currentCompasDegrees;
|
||||||
|
setCurrentCompassRotate(currentCompassRotate + compassOffsetDegres);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div>Loading ...</div>}>
|
<Suspense
|
||||||
<Sphere
|
fallback={
|
||||||
args={[1, 64, 64]}
|
<Html>
|
||||||
position={[-14.16, 0, 24.11]}
|
<div>Loading</div>
|
||||||
scale={[-1, 1, 1]}
|
</Html>
|
||||||
rotation={[0, Math.PI, 0]}
|
}
|
||||||
>
|
>
|
||||||
<meshBasicMaterial
|
{spheres.map((sphere) => (
|
||||||
ref={materialRef}
|
<SphereTour
|
||||||
map={texture}
|
sphere={sphere}
|
||||||
side={BackSide}
|
selectedSphere={selectedSphere || spheres[0]}
|
||||||
transparent
|
|
||||||
/>
|
/>
|
||||||
</Sphere>
|
))}
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
ref={orbitRef}
|
ref={orbitRef}
|
||||||
maxDistance={0.001}
|
maxDistance={0.001}
|
||||||
enableZoom={false}
|
enableZoom={false}
|
||||||
rotateSpeed={0.5}
|
rotateSpeed={0.5}
|
||||||
reverseOrbit
|
reverseOrbit
|
||||||
onChange={() => console.log("e", orbitRef.current?.getAzimuthalAngle())}
|
// onStart={(e) => console.log("e", e)}
|
||||||
|
// onChange={(e) => console.log("e", e?.target)}
|
||||||
|
onChange={handleOnRotating}
|
||||||
target={[-14.16, 0, 24.11]}
|
target={[-14.16, 0, 24.11]}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { IAppartmentSphere } from "../types/apartmentSphere";
|
||||||
|
|
||||||
|
const spheres: IAppartmentSphere[] = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-01.webp",
|
||||||
|
roomType: "room 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-02.webp",
|
||||||
|
roomType: "room 2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-03.webp",
|
||||||
|
roomType: "room 3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4",
|
||||||
|
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-04.webp",
|
||||||
|
roomType: "room 4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "5",
|
||||||
|
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-05.webp",
|
||||||
|
roomType: "room 5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "6",
|
||||||
|
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-06.webp",
|
||||||
|
roomType: "room 6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "7",
|
||||||
|
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-07.webp",
|
||||||
|
roomType: "room 7",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export { spheres };
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import ComplexButtomPanel from "../components/ComplexButtomPanel";
|
import ButtomPanelCompass from "../components/ButtomPanelCompass";
|
||||||
import ComplexTopPanel from "../components/complexPage/ComplexTopPanel";
|
import ComplexTopPanel from "../components/complexPage/ComplexTopPanel";
|
||||||
import SequenceSlider from "../components/complexPage/SequenceSlider";
|
import SequenceSlider from "../components/complexPage/SequenceSlider";
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ const Complex = () => {
|
|||||||
<div className="overflow-hidden h-screen w-screen">
|
<div className="overflow-hidden h-screen w-screen">
|
||||||
<ComplexTopPanel />
|
<ComplexTopPanel />
|
||||||
<SequenceSlider path={"/images/sequence"} />
|
<SequenceSlider path={"/images/sequence"} />
|
||||||
<ComplexButtomPanel />
|
<ButtomPanelCompass />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import ComplexTopPanel from "../components/complexPage/ComplexTopPanel";
|
import ComplexTopPanel from "../components/complexPage/ComplexTopPanel";
|
||||||
import ComplexButtomPanel from "../components/ComplexButtomPanel";
|
import ButtomPanelCompass from "../components/ButtomPanelCompass";
|
||||||
import SequenceWing from "../components/complexWingPage/SequenceWing";
|
import SequenceWing from "../components/complexWingPage/SequenceWing";
|
||||||
|
|
||||||
const ComplexWing = () => {
|
const ComplexWing = () => {
|
||||||
@@ -7,7 +7,7 @@ const ComplexWing = () => {
|
|||||||
<div className="overflow-hidden h-screen w-screen ">
|
<div className="overflow-hidden h-screen w-screen ">
|
||||||
<ComplexTopPanel />
|
<ComplexTopPanel />
|
||||||
<SequenceWing />
|
<SequenceWing />
|
||||||
<ComplexButtomPanel />
|
<ButtomPanelCompass />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+103
-4
@@ -1,14 +1,32 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { Canvas } from "@react-three/fiber";
|
import { Canvas } from "@react-three/fiber";
|
||||||
import ButtomPanel from "../components/ButtomPanel";
|
|
||||||
import VirtualTourWrapper from "../components/virtualTour/VirtualTourWrapper";
|
import VirtualTourWrapper from "../components/virtualTour/VirtualTourWrapper";
|
||||||
|
import Button from "../components/Button";
|
||||||
|
import HeartIcon from "../components/icons/Heart";
|
||||||
|
import ChevronDownIcon from "../components/icons/ChevronDownIcon";
|
||||||
|
import ButtomPanelCompass from "../components/ButtomPanelCompass";
|
||||||
|
import BookingIcon from "../components/icons/BookingIcon";
|
||||||
|
import { spheres } from "../consts/spheres";
|
||||||
|
import { IAppartmentSphere } from "../types/apartmentSphere";
|
||||||
|
import useSphere from "../store/useSphere";
|
||||||
|
|
||||||
const VirtualTour = () => {
|
const VirtualTour = () => {
|
||||||
|
const [isActive, setIsActive] = useState(false);
|
||||||
|
const { setSelectedSphere } = useSphere();
|
||||||
|
|
||||||
|
const handleOnShowClick = () => {
|
||||||
|
setIsActive((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnLabelClick = (sphere: IAppartmentSphere) => {
|
||||||
|
setSelectedSphere(sphere);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-hidden h-screen w-screen">
|
<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="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="col-span-3 h-screen py-[68px] px-3">
|
||||||
<div className="bg-white w-full rounded-lg p-4 flex flex-col">
|
<div className="bg-white w-full rounded-lg p-5 flex flex-col relative rounded-ee-none">
|
||||||
<div className="flex flex-col gap-1 pb-[18px] border-b">
|
<div className="flex flex-col gap-1 pb-[18px] border-b">
|
||||||
<p className="text-[#00BED7] text-caption-m font-medium">
|
<p className="text-[#00BED7] text-caption-m font-medium">
|
||||||
Rove Home Marasi Drive{" "}
|
Rove Home Marasi Drive{" "}
|
||||||
@@ -25,13 +43,94 @@ const VirtualTour = () => {
|
|||||||
<p className="text-caption-m font-semibold leading-4">№ 213</p>
|
<p className="text-caption-m font-semibold leading-4">№ 213</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="pt-3">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<div className="bg-[#00BED7] py-[3px] px-2 rounded-full text-white text-caption-m flex items-center h-[22px]">
|
||||||
|
234
|
||||||
|
</div>
|
||||||
|
<p className="text-[#0D1922] font-semibold text-subheadline-s">
|
||||||
|
1 bedroom appartment
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`transition-all duration-700 ease-in-out ${
|
||||||
|
!isActive ? "max-h-0 opacity-0" : "max-h-screen opacity-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="aspect-square w-full bg-black my-4"></div>
|
||||||
|
<div className="pt-4 border-t">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex justify-between text-m">
|
||||||
|
<p className="text-[#73787C]">Size</p>
|
||||||
|
<p className="text-[#0D1922]">609 Sqft</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-m">
|
||||||
|
<p className="text-[#73787C]">Status</p>
|
||||||
|
<p className="text-[#0D1922]">Available</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 pt-6">
|
||||||
|
<Button
|
||||||
|
icon={<BookingIcon />}
|
||||||
|
text="Send Enquiry"
|
||||||
|
buttonType="cta"
|
||||||
|
className="flex-1 flex items-center justify-center"
|
||||||
|
/>
|
||||||
|
<Button icon={<HeartIcon />} buttonType="secondary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-14 absolute -bottom-14 left-0 w-full flex justify-between">
|
||||||
|
<div className="flex gap-1 py-2 items-start flex-wrap">
|
||||||
|
{spheres.map((sphere) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => handleOnLabelClick(sphere)}
|
||||||
|
className="bg-[#F3F3F2] font-semibold text-[#0D1922] text-caption-s py-0.5 px-2 w-fit rounded-full cursor-pointer pointer-events-auto select-none"
|
||||||
|
key={sphere.id}
|
||||||
|
>
|
||||||
|
{sphere.roomType}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="transition-all duration-300 bg-[#FFFFFFCC] px-4 py-3 w-fit h-12 rounded-ee-lg rounded-es-lg select-none cursor-pointer pointer-events-auto items-start border border-t-[#0D1922B2] active:border-[#00BED7] hover:bg-[#FFFFFF] text-[#0D1922B2] hover:text-[#0D1922]">
|
||||||
|
<div
|
||||||
|
className="flex justify-center items-center gap-2"
|
||||||
|
onClick={handleOnShowClick}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<div
|
||||||
|
className={`transition-opacity duration-300 ${
|
||||||
|
!isActive ? "opacity-100" : "opacity-0"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Show
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`transition-opacity duration-300 absolute top-0 ${
|
||||||
|
!isActive ? "opacity-0" : "opacity-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Hide
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ChevronDownIcon
|
||||||
|
className={`transition-all duration-300 ${
|
||||||
|
!isActive ? "rotate-0" : "rotate-180"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<VirtualTourWrapper />
|
<VirtualTourWrapper />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
<ButtomPanel />
|
<ButtomPanelCompass />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ interface SequenceStore {
|
|||||||
setCurrentCompassRotate: (keyframe: number) => void;
|
setCurrentCompassRotate: (keyframe: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useSequence = create<SequenceStore>((set) => ({
|
const useCompass = create<SequenceStore>((set) => ({
|
||||||
currentCompassRotate: 0,
|
currentCompassRotate: 0,
|
||||||
setCurrentCompassRotate: (keyframe) =>
|
setCurrentCompassRotate: (keyframe) =>
|
||||||
set(() => ({ currentCompassRotate: keyframe })),
|
set(() => ({ currentCompassRotate: keyframe })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useSequence;
|
export default useCompass;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { IAppartmentSphere } from "../types/apartmentSphere";
|
||||||
|
|
||||||
|
interface SequenceStore {
|
||||||
|
selectedSphere: null | IAppartmentSphere;
|
||||||
|
setSelectedSphere: (sphere: null | IAppartmentSphere) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSphere = create<SequenceStore>((set) => ({
|
||||||
|
selectedSphere: null,
|
||||||
|
setSelectedSphere: (sphere) => set(() => ({ selectedSphere: sphere })),
|
||||||
|
}));
|
||||||
|
export default useSphere;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
interface IAppartmentSphere {
|
||||||
|
id: string;
|
||||||
|
sphereImage: string;
|
||||||
|
roomType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { IAppartmentSphere };
|
||||||
Reference in New Issue
Block a user