Update .env file for local development and remove unused FloorPlan components to streamline the project structure; update high-quality images for desktop.

This commit is contained in:
2026-02-06 20:30:39 +05:00
parent 922bfffc18
commit f9d783ed2e
275 changed files with 868 additions and 2193 deletions
@@ -1,171 +0,0 @@
import { Fragment } from "react/jsx-runtime";
import { dubaiMarinaMasks } from "../data/floor-plan-masks/dubai-marina_39-40";
import { Unit } from "../types/IUnit";
import { usePopupStore } from "../stores/usePopupStore";
import UnitPopup from "./UnitPopup";
import { isMobile } from "react-device-detect";
import { useEffect, useState } from "react";
import clsx from "clsx";
// import { useClickAway } from "@uidotdev/usehooks";
interface FloorPlanDubaiMarinaProps {
selectedFloor: string | null;
unitsOnFloor: Unit[];
chosenUnit: Unit | null;
}
function FloorPlanDubaiMarina39_40Unit({
unit,
floor,
d,
textTransform,
formattedUnitType,
onSelect,
selectedUnit,
}: {
unit: Unit;
floor: string | null;
d: string;
textTransform: string;
formattedUnitType: string;
selectedUnit: Unit | null;
onSelect: (unit: Unit | null) => void;
}) {
const { setPopup, setSide, setPosition, popup } = usePopupStore();
function handleClick(unit: Unit) {
window.open(`/complex/dubai-marina/${unit.unitNo}`, "_blank");
}
function handleMouseEnter() {
if (floor === null) return;
// onSelect(unit.unitNo);
setSide("top");
if (!selectedUnit)
setPopup(
<UnitPopup
complexName="dubai-marina"
unitType={unit.unitType}
floor={unit.floor}
unitNumber={unit.unitNo}
squareFt={unit.squareFt}
suitesArea={unit.suitsArea}
balconyArea={unit.balconyArea}
price={unit.salesPrice}
/>
);
}
// useEffect(() => {
// if (!selectedUnit) setPopup(null);
// setSide("top");
// setPopup(
// <UnitPopup
// complexName="dubai-marina"
// unitType={unit.unitType}
// floor={unit.floor}
// unitNumber={unit.unitNo}
// squareFt={unit.squareFt}
// suitesArea={unit.suitsArea}
// balconyArea={unit.balconyArea}
// price={unit.salesPrice}
// />
// );
// }, [selectedUnit, setPopup, setSide, unit]);
useEffect(() => {
if (!popup) onSelect(null);
}, [onSelect, popup]);
// const ref = useClickAway<SVGPathElement>(
// () => !!selectedUnit && setPopup(null)
// );
return (
<g
// ref={ref}
>
<text
transform={textTransform}
className="fill-white text-[8px] select-none"
textAnchor="middle"
>
<tspan x={0} y={0}>
{unit.unitNo}
</tspan>
<tspan x={0} y={15}>
{formattedUnitType}
</tspan>
</text>
<path
onClick={() => !isMobile && !selectedUnit && handleClick(unit)}
onMouseEnter={!isMobile ? handleMouseEnter : undefined}
onMouseLeave={() => !isMobile && setPopup(null)}
onTouchStart={(e) => {
onSelect(unit);
setPosition({ x: e.touches[0].clientX, y: e.touches[0].clientY });
handleMouseEnter();
}}
className={clsx(
"fill-transparent hover:fill-[#00BED7] opacity-40 isolate cursor-pointer transition-colors",
selectedUnit?.unitNo === unit.unitNo &&
"!fill-[#00BED7] opacity-40 cursor-default"
)}
d={d}
/>
</g>
);
}
function FloorPlanDubaiMarina39_41({
selectedFloor,
unitsOnFloor,
chosenUnit,
...props
}: FloorPlanDubaiMarinaProps & React.SVGProps<SVGSVGElement>) {
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(
chosenUnit || null
);
// const ref = useClickAway<SVGSVGElement>(() => setSelectedUnit(null));
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 632 467.5"
className="w-full h-full"
// ref={ref}
{...props}
>
<image
// width={1264}
// height={935}
transform="scale(.5)"
xlinkHref="/images/floor-plans/dubai-marina/floor-plan_39-40.png"
/>
{unitsOnFloor.map((unit) =>
dubaiMarinaMasks.has(unit.unitNo.slice(-2)) ? (
<FloorPlanDubaiMarina39_40Unit
key={unit.unitNo}
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
formattedUnitType={
dubaiMarinaMasks.get(unit.unitNo.slice(-2))!.formattedUnitType
}
unit={unit}
floor={selectedFloor}
d={dubaiMarinaMasks.get(unit.unitNo.slice(-2))!.d}
textTransform={`translate(${dubaiMarinaMasks
.get(unit.unitNo.slice(-2))!
.textTransform.join(" ")})`}
/>
) : (
<Fragment key={unit.unitNo} />
)
)}
</svg>
);
}
export default FloorPlanDubaiMarina39_41;
@@ -1,171 +0,0 @@
/* eslint-disable react-refresh/only-export-components */
import { Fragment } from "react/jsx-runtime";
import { dubaiMarinaMasks } from "../data/floor-plan-masks/dubai-marina_39-40";
import { Unit } from "../types/IUnit";
import { usePopupStore } from "../stores/usePopupStore";
import UnitPopup from "./UnitPopup";
import { isMobile } from "react-device-detect";
import { useState } from "react";
import clsx from "clsx";
// import { useClickAway } from "@uidotdev/usehooks";
interface FloorPlanDubaiMarinaProps {
selectedFloor: string | null;
unitsOnFloor: Unit[];
chosenUnit: Unit | null;
}
function FloorPlanDubaiMarina41_42({
selectedFloor,
unitsOnFloor,
chosenUnit,
...props
}: FloorPlanDubaiMarinaProps & React.SVGProps<SVGSVGElement>) {
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(
chosenUnit || null
);
// const ref = useClickAway<SVGSVGElement>(() => setSelectedUnit(null));
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 632 467.5"
className="w-full h-full"
// ref={ref}
{...props}
>
<image
// width={1264}
// height={935}
transform="scale(.5)"
xlinkHref="/images/floor-plans/dubai-marina/floor-plan_39-40.png"
/>
{unitsOnFloor.map((unit) =>
dubaiMarinaMasks.has(unit.unitNo.slice(-2)) ? (
<FloorPlanDubaiMarina41_42Unit
key={unit.unitNo}
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
formattedUnitType={
dubaiMarinaMasks.get(unit.unitNo.slice(-2))!.formattedUnitType
}
unit={unit}
floor={selectedFloor}
d={dubaiMarinaMasks.get(unit.unitNo.slice(-2))!.d}
textTransform={`translate(${dubaiMarinaMasks
.get(unit.unitNo.slice(-2))!
.textTransform.join(" ")})`}
/>
) : (
<Fragment key={unit.unitNo} />
)
)}
</svg>
);
}
export default FloorPlanDubaiMarina41_42;
function FloorPlanDubaiMarina41_42Unit({
unit,
floor,
d,
textTransform,
formattedUnitType,
onSelect,
selectedUnit,
}: {
unit: Unit;
floor: string | null;
d: string;
textTransform: string;
formattedUnitType: string;
selectedUnit: Unit | null;
onSelect: (unit: Unit | null) => void;
}) {
const { setPopup, setSide, setPosition } = usePopupStore();
function handleClick(unit: Unit) {
window.open(`/complex/dubai-marina/${unit.unitNo}`, "_blank");
}
function handleMouseEnter() {
if (floor === null) return;
// onSelect(unit.unitNo);
setSide("top");
if (!selectedUnit)
setPopup(
<UnitPopup
complexName="dubai-marina"
unitType={unit.unitType}
floor={unit.floor}
unitNumber={unit.unitNo}
squareFt={unit.squareFt}
suitesArea={unit.suitsArea}
balconyArea={unit.balconyArea}
price={unit.salesPrice}
/>
);
}
// useEffect(() => {
// if (!selectedUnit) setPopup(null);
// setSide("top");
// setPopup(
// <UnitPopup
// complexName="dubai-marina"
// unitType={unit.unitType}
// floor={unit.floor}
// unitNumber={unit.unitNo}
// squareFt={unit.squareFt}
// suitesArea={unit.suitsArea}
// balconyArea={unit.balconyArea}
// price={unit.salesPrice}
// />
// );
// }, [selectedUnit, setPopup, setSide, unit]);
// useEffect(() => {
// if (!popup) onSelect(null);
// }, [onSelect, popup]);
// const ref = useClickAway<SVGPathElement>(
// () => !!selectedUnit && setPopup(null)
// );
return (
<g
// ref={ref}
>
<text
transform={textTransform}
className="fill-white text-[8px] select-none"
textAnchor="middle"
>
<tspan x={0} y={0}>
{unit.unitNo}
</tspan>
<tspan x={0} y={15}>
{formattedUnitType}
</tspan>
</text>
<path
onClick={() => !isMobile && !selectedUnit && handleClick(unit)}
onMouseEnter={!isMobile ? handleMouseEnter : undefined}
onMouseLeave={() => !isMobile && setPopup(null)}
onTouchStart={(e) => {
onSelect(unit);
setPosition({ x: e.touches[0].clientX, y: e.touches[0].clientY });
handleMouseEnter();
}}
className={clsx(
"fill-transparent hover:fill-[#00BED7] opacity-40 isolate cursor-pointer transition-colors",
selectedUnit?.unitNo === unit.unitNo &&
"!fill-[#00BED7] opacity-40 cursor-default"
)}
d={d}
/>
</g>
);
}
-171
View File
@@ -1,171 +0,0 @@
import { Fragment } from "react/jsx-runtime";
import { dubaiMarinaMasks } from "../data/floor-plan-masks/dubai-marina_7-38";
import { Unit } from "../types/IUnit";
import { usePopupStore } from "../stores/usePopupStore";
import UnitPopup from "./UnitPopup";
import { isMobile } from "react-device-detect";
import { useState } from "react";
import clsx from "clsx";
// import { useClickAway } from "@uidotdev/usehooks";
interface FloorPlanDubaiMarinaProps {
selectedFloor: string | null;
unitsOnFloor: Unit[];
chosenUnit?: Unit;
}
function FloorPlanDubaiMarina7_38({
selectedFloor,
unitsOnFloor,
chosenUnit,
...props
}: FloorPlanDubaiMarinaProps & React.SVGProps<SVGSVGElement>) {
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(
chosenUnit || null
);
// const ref = useClickAway<SVGSVGElement>(() => setSelectedUnit(null));
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 632 467.5"
className="w-full h-full"
// ref={ref}
{...props}
>
<image
// width={1264}
// height={935}
transform="scale(.5)"
xlinkHref="/images/floor-plans/dubai-marina/floor-plan_7-38.png"
/>
{unitsOnFloor.map((unit) =>
dubaiMarinaMasks.has(unit.unitNo.slice(-2)) ? (
<FloorPlanDubaiMarina7_38Unit
key={unit.unitNo}
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
formattedUnitType={
dubaiMarinaMasks.get(unit.unitNo.slice(-2))!.formattedUnitType
}
unit={unit}
floor={selectedFloor}
d={dubaiMarinaMasks.get(unit.unitNo.slice(-2))!.d}
textTransform={`translate(${dubaiMarinaMasks
.get(unit.unitNo.slice(-2))!
.textTransform.join(" ")})`}
/>
) : (
<Fragment key={unit.unitNo} />
)
)}
</svg>
);
}
export default FloorPlanDubaiMarina7_38;
export function FloorPlanDubaiMarina7_38Unit({
unit,
floor,
d,
textTransform,
formattedUnitType,
onSelect,
selectedUnit,
}: {
unit: Unit;
floor: string | null;
d: string;
textTransform: string;
formattedUnitType: string;
selectedUnit: Unit | null;
onSelect: (unit: Unit | null) => void;
}) {
const { setPopup, setSide, setPosition } = usePopupStore();
function handleClick(unit: Unit) {
window.open(`/complex/dubai-marina/${unit.unitNo}`, "_blank");
}
function handleMouseEnter() {
if (floor === null) return;
// onSelect(unit.unitNo);
setSide("top");
if (!selectedUnit)
setPopup(
<UnitPopup
complexName="dubai-marina"
unitType={unit.unitType}
floor={unit.floor}
unitNumber={unit.unitNo}
squareFt={unit.squareFt}
suitesArea={unit.suitsArea}
balconyArea={unit.balconyArea}
price={unit.salesPrice}
/>
);
}
// useEffect(() => {
// if (!selectedUnit) setPopup(null);
// setSide("top");
// setPopup(
// <UnitPopup
// complexName="dubai-marina"
// unitType={unit.unitType}
// floor={unit.floor}
// unitNumber={unit.unitNo}
// squareFt={unit.squareFt}
// suitesArea={unit.suitsArea}
// balconyArea={unit.balconyArea}
// price={unit.salesPrice}
// />
// );
// }, [selectedUnit, setPopup, setSide, unit]);
// useEffect(() => {
// if (!popup) onSelect(null);
// }, [onSelect, popup]);
// const ref = useClickAway<SVGPathElement>(
// () => !!selectedUnit && setPopup(null)
// );
return (
<g
// ref={ref}
>
<text
transform={textTransform}
className="fill-white text-[8px] select-none"
textAnchor="middle"
>
<tspan x={0} y={0}>
{unit.unitNo}
</tspan>
<tspan x={0} y={15}>
{formattedUnitType}
</tspan>
</text>
<path
onClick={() => !isMobile && !selectedUnit && handleClick(unit)}
onMouseEnter={!isMobile ? handleMouseEnter : undefined}
onMouseLeave={() => !isMobile && setPopup(null)}
onTouchStart={(e) => {
onSelect(unit);
setPosition({ x: e.touches[0].clientX, y: e.touches[0].clientY });
handleMouseEnter();
}}
className={clsx(
"fill-transparent hover:fill-[#00BED7] opacity-40 isolate cursor-pointer transition-colors",
selectedUnit?.unitNo === unit.unitNo &&
"!fill-[#00BED7] opacity-40 cursor-default"
)}
d={d}
/>
</g>
);
}
@@ -1,213 +0,0 @@
// import { Fragment } from "react/jsx-runtime";
import { Unit } from "../types/IUnit";
import { usePopupStore } from "../stores/usePopupStore";
import UnitPopup from "./UnitPopup";
import { isMobile } from "react-device-detect";
import { useState } from "react";
import clsx from "clsx";
import { dubaiMarinaMasks } from "../data/floor-plan-masks/dubai-marina_7-38_comb";
import { FloorPlanDubaiMarina7_38Unit } from "./FloorPlanDubaiMarina7_38";
import { filterDuplicateUnits } from "../utils/filterDuplicateUnits";
// import Button from "./ui/Button";
// import { useClickAway } from "@uidotdev/usehooks";
interface FloorPlanDubaiMarinaCombProps {
selectedFloor: string | null;
unitsOnFloor: Unit[];
chosenUnit?: Unit;
}
function FloorPlanDubaiMarina7_38Comb({
selectedFloor,
unitsOnFloor,
chosenUnit,
...props
}: FloorPlanDubaiMarinaCombProps & React.SVGProps<SVGSVGElement>) {
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(
chosenUnit || null
);
// const ref = useClickAway<SVGSVGElement>(() => setSelectedUnit(null));
console.log(unitsOnFloor);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 626 465"
className="w-full h-full"
// ref={ref}
{...props}
>
<image
// width={1264}
// height={935}
transform="scale(.5)"
xlinkHref="/images/floor-plans/dubai-marina/floor-plan_7-38_comb.png"
/>
{/* {unitsOnFloor.map((unit) =>
dubaiMarinaMasks.has(unit.unitNo.slice(-2)) ? (
<FloorPlanDubaiMarina7_38CombUnit
key={unit.unitNo}
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
formattedUnitType={
dubaiMarinaMasks.get(unit.unitNo.slice(-2))!.formattedUnitType
}
unit={unit}
floor={Number(selectedFloor!)}
d={dubaiMarinaMasks.get(unit.unitNo.slice(-2))!.d}
textTransform={`translate(${dubaiMarinaMasks
.get(unit.unitNo.slice(-2))!
.textTransform.join(" ")})`}
/>
) : (
<Fragment key={unit.unitNo} />
)
)} */}
{filterDuplicateUnits(unitsOnFloor).map((unit) => {
// Получаем ключ для поиска в масках: для "2901-C" и "2901" это будет "01"
const maskKey = unit.unitNo.split("-")[0].slice(-2);
const maskData = dubaiMarinaMasks.get(maskKey);
if (!maskData) {
console.warn(
`Mask not found for unit ${unit.unitNo}, key: ${maskKey}`
);
return null;
}
return !unit.unitNo.endsWith("-C") ? (
<FloorPlanDubaiMarina7_38Unit
key={unit.unitNo}
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
formattedUnitType={maskData.formattedUnitType}
unit={unit}
floor={selectedFloor}
d={maskData.d}
textTransform={`translate(${maskData.textTransform.join(" ")})`}
/>
) : (
<FloorPlanDubaiMarina7_38CombUnit
key={unit.unitNo}
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
formattedUnitType={maskData.formattedUnitType}
unit={unit}
floor={selectedFloor}
d={maskData.d}
textTransform={`translate(${maskData.textTransform.join(" ")})`}
/>
);
})}
</svg>
);
}
export default FloorPlanDubaiMarina7_38Comb;
function FloorPlanDubaiMarina7_38CombUnit({
unit,
floor,
d,
textTransform,
formattedUnitType,
onSelect,
selectedUnit,
}: {
d: string;
textTransform: string;
floor: string | null;
unit: Unit;
formattedUnitType: string;
selectedUnit: Unit | null;
onSelect: (unit: Unit | null) => void;
}) {
const { setPopup, setSide, setPosition } = usePopupStore();
function handleClick(unit: Unit) {
window.open(`/complex/dubai-marina/${unit.unitNo}`, "_blank");
}
function handleMouseEnter() {
if (floor === null) return;
// onSelect(unit.unitNo);
setSide("top");
if (!selectedUnit)
setPopup(
<UnitPopup
complexName="dubai-marina"
unitType={unit.unitType}
floor={unit.floor}
unitNumber={unit.unitNo}
squareFt={unit.squareFt}
suitesArea={unit.suitsArea}
balconyArea={unit.balconyArea}
price={unit.salesPrice}
/>
);
}
// useEffect(() => {
// if (!selectedUnit) setPopup(null);
// setSide("top");
// setPopup(
// <UnitPopup
// complexName="dubai-marina"
// unitType={unit.unitType}
// floor={unit.floor}
// unitNumber={unit.unitNo}
// squareFt={unit.squareFt}
// suitesArea={unit.suitsArea}
// balconyArea={unit.balconyArea}
// price={unit.salesPrice}
// />
// );
// }, [selectedUnit, setPopup, setSide, unit]);
// useEffect(() => {
// if (!popup) onSelect(null);
// }, [onSelect, popup]);
// const ref = useClickAway<SVGPathElement>(
// () => !!selectedUnit && setPopup(null)
// );
return (
<g
// ref={ref}
>
<text
transform={textTransform}
className="fill-white text-[8px] select-none"
textAnchor="middle"
>
<tspan x={0} y={0}>
{unit.unitNo}
</tspan>
<tspan x={0} y={15}>
{formattedUnitType}
</tspan>
</text>
<path
onClick={() => !isMobile && !selectedUnit && handleClick(unit)}
onMouseEnter={!isMobile ? handleMouseEnter : undefined}
onMouseLeave={() => !isMobile && setPopup(null)}
onTouchStart={(e) => {
onSelect(unit);
setPosition({ x: e.touches[0].clientX, y: e.touches[0].clientY });
handleMouseEnter();
}}
className={clsx(
"fill-transparent hover:fill-[#00BED7] opacity-40 isolate cursor-pointer transition-colors",
selectedUnit?.unitNo === unit.unitNo &&
"!fill-[#00BED7] opacity-40 cursor-default"
)}
d={d}
/>
</g>
);
}
+11 -119
View File
@@ -1,10 +1,6 @@
import { Fragment, useState } from "react";
import { floorPlanMarasiDriveEastMasks } from "../data/floor-plan-masks/marasi-drive";
import { usePopupStore } from "../stores/usePopupStore";
import { Unit } from "../types/IUnit";
import UnitPopup from "./UnitPopup";
import { isMobile } from "react-device-detect";
import clsx from "clsx";
import GenericFloorPlan from "./floor-plans/GenericFloorPlan";
interface FloorPlanMarasiDriveEastProps {
selectedFloor: string | null;
@@ -16,16 +12,17 @@ function FloorPlanMarasiDriveEast({
selectedFloor,
unitsOnFloor,
chosenUnit,
}: FloorPlanMarasiDriveEastProps & React.SVGProps<SVGSVGElement>) {
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(
chosenUnit || null
);
}: FloorPlanMarasiDriveEastProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
<GenericFloorPlan
complexName="marasi-drive"
viewBox="0 0 633.97 568.52"
className="w-full h-full"
masks={floorPlanMarasiDriveEastMasks}
getMaskKey={(unitNo) => unitNo.slice(-2)}
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
chosenUnit={chosenUnit}
wing="East"
>
<g>
<g>
@@ -1159,113 +1156,8 @@ function FloorPlanMarasiDriveEast({
}}
/>
</g>
{unitsOnFloor && unitsOnFloor.length && (
<g>
{unitsOnFloor.map((unit) =>
floorPlanMarasiDriveEastMasks.has(unit.unitNo.slice(-2)) ? (
<MarasiDriveEastFloorPlanUnit
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
key={unit.unitNo}
floor={selectedFloor}
unit={unit}
formattedUnitType={
floorPlanMarasiDriveEastMasks.get(unit.unitNo.slice(-2))!
.formattedUnitType
}
textTransform={`translate(${floorPlanMarasiDriveEastMasks
.get(unit.unitNo.slice(-2))
?.textTransform.join(" ")})`}
d={floorPlanMarasiDriveEastMasks.get(unit.unitNo.slice(-2))!.d}
/>
) : (
<Fragment key={unit.unitNo} />
)
)}
</g>
)}
</svg>
</GenericFloorPlan>
);
}
export default FloorPlanMarasiDriveEast;
function MarasiDriveEastFloorPlanUnit({
d,
textTransform,
floor,
unit,
formattedUnitType,
selectedUnit,
onSelect,
}: {
d: string;
textTransform: string;
floor: string | null;
unit: Unit;
formattedUnitType: string;
selectedUnit: Unit | null;
onSelect: (unit: Unit | null) => void;
}) {
function handleClick(unit: Unit) {
window.open(`/complex/marasi-drive/${unit.unitNo}`, "_blank");
}
const { setPopup, setSide, setPosition } = usePopupStore();
function handleMouseEnter() {
if (floor === null) return;
setSide("top");
if (!selectedUnit)
setPopup(
<UnitPopup
complexName="marasi-drive"
wing="East"
unitType={unit.unitType}
floor={unit.floor}
unitNumber={unit.unitNo}
squareFt={unit.squareFt}
suitesArea={unit.suitsArea}
balconyArea={unit.balconyArea}
price={unit.salesPrice}
/>
);
}
return (
<g>
<text
transform={textTransform}
style={{
fill: "#fff",
fontFamily: "Usual",
fontSize: 8,
fontWeight: 500,
}}
>
<tspan x={0} y={0}>
{unit.unitNo}
</tspan>
<tspan x={0} y={10.8}>
{formattedUnitType}
</tspan>
</text>
<path
onClick={() => !isMobile && !selectedUnit && handleClick(unit)}
onMouseEnter={!isMobile ? handleMouseEnter : undefined}
onMouseLeave={() => !isMobile && setPopup(null)}
onTouchStart={(e) => {
onSelect(unit);
setPosition({ x: e.touches[0].clientX, y: e.touches[0].clientY });
handleMouseEnter();
}}
d={d}
className={clsx(
"fill-transparent hover:fill-[#00BED7] opacity-20 transition-all cursor-pointer",
selectedUnit?.unitNo === unit.unitNo &&
"!fill-[#00BED7] opacity-20 cursor-default"
)}
/>
</g>
);
}
+16 -121
View File
@@ -1,29 +1,29 @@
import { Fragment } from "react/jsx-runtime";
import { floorPlanMarasiDriveWestLowerMasks } from "../data/floor-plan-masks/marasi-drive";
import { usePopupStore } from "../stores/usePopupStore";
import { Unit } from "../types/IUnit";
import UnitPopup from "./UnitPopup";
import { isMobile } from "react-device-detect";
import clsx from "clsx";
import { useState } from "react";
import GenericFloorPlan from "./floor-plans/GenericFloorPlan";
interface FloorPlanMarasiDriveEastProps {
interface FloorPlanMarasiDriveWestLowerProps {
selectedFloor: string | null;
unitsOnFloor: Unit[];
chosenUnit?: Unit;
}
function FloorPlanMarasiDriveEast({
function FloorPlanMarasiDriveWestLower({
selectedFloor,
unitsOnFloor,
chosenUnit,
}: FloorPlanMarasiDriveEastProps & React.SVGProps<SVGSVGElement>) {
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(
chosenUnit || null
);
}: FloorPlanMarasiDriveWestLowerProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 619.96 585.99">
<GenericFloorPlan
complexName="marasi-drive"
viewBox="0 0 619.96 585.99"
masks={floorPlanMarasiDriveWestLowerMasks}
getMaskKey={(unitNo) => unitNo.slice(-2)}
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
chosenUnit={chosenUnit}
wing="West"
>
<g>
<g>
<path
@@ -1197,113 +1197,8 @@ function FloorPlanMarasiDriveEast({
}}
/>
</g>
<g>
{unitsOnFloor.map((unit) =>
floorPlanMarasiDriveWestLowerMasks.has(unit.unitNo.slice(-2)) ? (
<MarasiDriveWestLowerFloorPlanUnit
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
formattedUnitType={
floorPlanMarasiDriveWestLowerMasks.get(unit.unitNo.slice(-2))!
.formattedUnitType
}
key={unit.unitNo}
d={
floorPlanMarasiDriveWestLowerMasks.get(unit.unitNo.slice(-2))!.d
}
textTransform={`translate(${floorPlanMarasiDriveWestLowerMasks
.get(unit.unitNo.slice(-2))!
.textTransform.join(" ")})`}
floor={selectedFloor}
unit={unit}
/>
) : (
<Fragment key={unit.unitNo} />
)
)}
</g>
</svg>
</GenericFloorPlan>
);
}
export default FloorPlanMarasiDriveEast;
function MarasiDriveWestLowerFloorPlanUnit({
d,
textTransform,
floor,
unit,
formattedUnitType,
selectedUnit,
onSelect,
}: {
d: string;
textTransform: string;
floor: string | null;
unit: Unit;
formattedUnitType: string;
selectedUnit: Unit | null;
onSelect: (unit: Unit | null) => void;
}) {
function handleClick(unitNumber: string) {
window.open(`/complex/marasi-drive/${unitNumber}`, "_blank");
}
const { setPopup, setSide, setPosition } = usePopupStore();
function handleMouseEnter() {
if (floor === null) return;
setSide("top");
if (!selectedUnit)
setPopup(
<UnitPopup
complexName="marasi-drive"
wing="West"
unitType={unit.unitType}
floor={unit.floor}
unitNumber={unit.unitNo}
squareFt={unit.squareFt}
suitesArea={unit.suitsArea}
balconyArea={unit.balconyArea}
price={unit.salesPrice}
/>
);
}
return (
<g>
<text
transform={textTransform}
style={{
fill: "#fff",
fontFamily: "Usual",
fontSize: 8,
fontWeight: 500,
}}
>
<tspan x={0} y={0}>
{unit.unitNo}
</tspan>
<tspan x={0} y={10.8}>
{formattedUnitType}
</tspan>
</text>
<path
onClick={() => !isMobile && !selectedUnit && handleClick(unit.unitNo)}
onMouseEnter={!isMobile ? handleMouseEnter : undefined}
onMouseLeave={() => !isMobile && setPopup(null)}
onTouchStart={(e) => {
onSelect(unit);
setPosition({ x: e.touches[0].clientX, y: e.touches[0].clientY });
handleMouseEnter();
}}
d={d}
className={clsx(
"fill-transparent hover:fill-[#00BED7] opacity-20 transition-[fill] cursor-pointer",
selectedUnit?.unitNo === unit.unitNo &&
"!fill-[#00BED7] opacity-20 cursor-default"
)}
/>
</g>
);
}
export default FloorPlanMarasiDriveWestLower;
+16 -118
View File
@@ -1,32 +1,29 @@
// import { selectedFloorPlanMasksMarasiDrive } from "../data/selectedFloor-plan-masks/marasi-drive";
import { Fragment } from "react/jsx-runtime";
import { floorPlanMarasiDriveWestUpperMasks } from "../data/floor-plan-masks/marasi-drive";
import { formattedUnitTypes } from "../data/formattedUnitTypes";
import { usePopupStore } from "../stores/usePopupStore";
import { Unit } from "../types/IUnit";
import UnitPopup from "./UnitPopup";
import { isMobile } from "react-device-detect";
import { useState } from "react";
import clsx from "clsx";
import GenericFloorPlan from "./floor-plans/GenericFloorPlan";
interface FloorPlanMarasiDriveEastProps {
interface FloorPlanMarasiDriveWestUpperProps {
selectedFloor: string | null;
unitsOnFloor: Unit[];
chosenUnit?: Unit;
}
function FloorPlanMarasiDriveEast({
function FloorPlanMarasiDriveWestUpper({
unitsOnFloor,
selectedFloor,
chosenUnit,
}: FloorPlanMarasiDriveEastProps & React.SVGProps<SVGSVGElement>) {
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(
chosenUnit || null
);
}: FloorPlanMarasiDriveWestUpperProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 619.96 586.04">
<GenericFloorPlan
complexName="marasi-drive"
viewBox="0 0 619.96 586.04"
masks={floorPlanMarasiDriveWestUpperMasks}
getMaskKey={(unitNo) => unitNo.slice(-2)}
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
chosenUnit={chosenUnit}
wing="West"
>
<g>
<g>
<path
@@ -1178,107 +1175,8 @@ function FloorPlanMarasiDriveEast({
}}
/>
</g>
<g>
{unitsOnFloor.map((unit) =>
floorPlanMarasiDriveWestUpperMasks.has(unit.unitNo.slice(-2)) ? (
<MarasiDriveWestUpperFloorPlanUnit
key={unit.unitNo}
unit={unit}
floor={selectedFloor}
d={
floorPlanMarasiDriveWestUpperMasks.get(unit.unitNo.slice(-2))!.d
}
textTransform={`translate(${floorPlanMarasiDriveWestUpperMasks
.get(unit.unitNo.slice(-2))
?.textTransform.join(" ")})`}
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
/>
) : (
<Fragment key={unit.unitNo} />
)
)}
</g>
</svg>
</GenericFloorPlan>
);
}
export default FloorPlanMarasiDriveEast;
function MarasiDriveWestUpperFloorPlanUnit({
d,
textTransform,
floor,
unit,
selectedUnit,
onSelect,
}: {
d: string;
textTransform: string;
floor: string | null;
unit: Unit;
selectedUnit: Unit | null;
onSelect: (unit: Unit | null) => void;
}) {
function handleClick(unit: Unit) {
window.open(`/complex/marasi-drive/${unit.unitNo}`, "_blank");
}
const { setPopup, setPosition, setSide } = usePopupStore();
function handleMouseEnter() {
if (floor === null) return;
setSide("top");
if (!selectedUnit)
setPopup(
<UnitPopup
complexName="marasi-drive"
wing="West"
unitType={unit.unitType}
floor={unit.floor}
unitNumber={unit.unitNo}
squareFt={unit.squareFt}
suitesArea={unit.suitsArea}
balconyArea={unit.balconyArea}
price={unit.salesPrice}
/>
);
}
return (
<g>
<text
transform={textTransform}
style={{
fill: "#fff",
fontFamily: "Usual",
fontSize: 8,
fontWeight: 500,
}}
>
<tspan x={0} y={0}>
{unit.unitNo}
</tspan>
<tspan x={0} y={10.8}>
{formattedUnitTypes.get(unit.unitType) || unit.unitType}
</tspan>
</text>
<path
onClick={() => !isMobile && !selectedUnit && handleClick(unit)}
onMouseEnter={!isMobile ? handleMouseEnter : undefined}
onMouseLeave={() => !isMobile && setPopup(null)}
onTouchStart={(e) => {
onSelect(unit);
setPosition({ x: e.touches[0].clientX, y: e.touches[0].clientY });
handleMouseEnter();
}}
d={d}
className={clsx(
"fill-transparent hover:fill-[#00BED7] opacity-20 transition-all cursor-pointer",
selectedUnit?.unitNo === unit.unitNo &&
"!fill-[#00BED7] opacity-20 cursor-default"
)}
/>
</g>
);
}
export default FloorPlanMarasiDriveWestUpper;
+1 -4
View File
@@ -585,10 +585,7 @@ function FloorSelect({
queryFn: () =>
api
.get(
`units/get-floors-data/Rove Home ${complexName
.split("-")
.map((w) => w[0].toUpperCase() + w.slice(1))
.join(" ")}`,
`units/get-floors-data/${complexName}`,
)
.json<FloorsData[]>(),
});
+37 -69
View File
@@ -1,76 +1,44 @@
import { Unit } from "../types/IUnit";
import FloorPlanDubaiMarina39_40 from "./FloorPlanDubaiMarina39_40";
import FloorPlanDubaiMarina41_42 from "./FloorPlanDubaiMarina41_42";
import FloorPlanDubaiMarina7_38 from "./FloorPlanDubaiMarina7_38";
import FloorPlanDubaiMarina7_38Comb from "./FloorPlanDubaiMarina7_38Comb";
import FloorPlanMarasiDriveEast from "./FloorPlanMarasiDriveEast";
import FloorPlanMarasiDriveWestLower from "./FloorPlanMarasiDriveWestLower";
import FloorPlanMarasiDriveWestUpper from "./FloorPlanMarasiDriveWestUpper";
import GenericFloorPlan from "./floor-plans/GenericFloorPlan";
import { getFloorPlanConfigForUnit } from "../data/floor-plan-config";
import { getSlugFromProjectName } from "../data/complex-config";
// ─── Component ───────────────────────────────────────────
function OnFloorMask({ unit }: { unit: Unit }) {
if (unit.project === "Rove Home Marasi Drive") {
if (unit.wing === "East") {
return (
<FloorPlanMarasiDriveEast
selectedFloor={null}
unitsOnFloor={[unit]}
chosenUnit={unit}
/>
);
} else if (unit.wing === "West") {
if (unit.floor >= 5 && unit.floor <= 21) {
return (
<FloorPlanMarasiDriveWestLower
selectedFloor={unit.floor.toString()}
unitsOnFloor={[unit]}
chosenUnit={unit}
/>
);
} else if (unit.floor >= 24 && unit.floor <= 31) {
return (
<FloorPlanMarasiDriveWestUpper
selectedFloor={unit.floor.toString()}
unitsOnFloor={[unit]}
chosenUnit={unit}
/>
);
}
}
} else if (unit.project === "Rove Home Dubai Marina") {
if (unit.floor >= 7 && unit.floor <= 38) {
if (unit.unitNo.endsWith("-C"))
return (
<FloorPlanDubaiMarina7_38Comb
selectedFloor={unit.floor.toString()}
unitsOnFloor={[unit]}
chosenUnit={unit}
/>
);
return (
<FloorPlanDubaiMarina7_38
selectedFloor={unit.floor.toString()}
unitsOnFloor={[unit]}
chosenUnit={unit}
/>
);
} else if (unit.floor >= 41 && unit.floor <= 42) {
return (
<FloorPlanDubaiMarina41_42
selectedFloor={unit.floor.toString()}
unitsOnFloor={[unit]}
chosenUnit={unit}
/>
);
} else if (unit.floor >= 39 && unit.floor <= 40) {
return (
<FloorPlanDubaiMarina39_40
selectedFloor={unit.floor.toString()}
unitsOnFloor={[unit]}
chosenUnit={unit}
/>
);
}
const complexName = getSlugFromProjectName(unit.project);
if (!complexName) return null;
const config = getFloorPlanConfigForUnit(complexName, unit);
if (!config) return null;
// Marasi Drive uses component-based rendering (inline SVG)
if (config.component) {
const Component = config.component;
return (
<Component
selectedFloor={unit.floor.toString()}
unitsOnFloor={[unit]}
chosenUnit={unit}
/>
);
}
// Dubai Marina & HQ use image-based rendering
return (
<GenericFloorPlan
complexName={complexName}
viewBox={config.viewBox}
imagePath={config.imagePath}
masks={config.masks}
getMaskKey={config.getMaskKey}
filterUnits={config.filterUnits}
selectedFloor={unit.floor.toString()}
unitsOnFloor={[unit]}
chosenUnit={unit}
wing={config.wing}
/>
);
}
export default OnFloorMask;
@@ -0,0 +1,177 @@
import { Fragment, useState } from "react";
import { Unit } from "../../types/IUnit";
import { ComplexName } from "../../types/ComplexName";
import { FloorPlanMasks } from "../../types/FloorPlanMasks";
import { usePopupStore } from "../../stores/usePopupStore";
import UnitPopup from "../UnitPopup";
import { isMobile } from "react-device-detect";
import clsx from "clsx";
// ─── Props ───────────────────────────────────────────────
interface GenericFloorPlanProps extends React.SVGProps<SVGSVGElement> {
complexName: ComplexName;
viewBox: string;
/** Floor plan background image path. Either this or `children` should be provided. */
imagePath?: string;
masks: FloorPlanMasks;
selectedFloor: string | null;
unitsOnFloor: Unit[];
chosenUnit?: Unit | null;
/** How to extract the mask key from unit.unitNo. Default: unitNo.slice(-2) */
getMaskKey?: (unitNo: string) => string;
/** Optional unit filter (e.g. filterDuplicateUnits for combinable layouts) */
filterUnits?: (units: Unit[]) => Unit[];
/** Optional wing for Marasi Drive popups */
wing?: "East" | "West";
/** Custom SVG background (inline SVG groups). Used for Marasi Drive instead of imagePath. */
children?: React.ReactNode;
}
const defaultGetMaskKey = (unitNo: string) => unitNo.slice(-2);
// ─── Main component ──────────────────────────────────────
function GenericFloorPlan({
complexName,
viewBox: vb,
imagePath,
masks,
selectedFloor,
unitsOnFloor,
chosenUnit,
getMaskKey = defaultGetMaskKey,
filterUnits,
wing,
children,
...svgProps
}: GenericFloorPlanProps) {
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(
chosenUnit || null
);
const units = filterUnits ? filterUnits(unitsOnFloor) : unitsOnFloor;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox={vb}
className="w-full h-full"
{...svgProps}
>
{/* Background: either an image or custom inline SVG */}
{imagePath && <image transform="scale(.5)" xlinkHref={imagePath} />}
{children}
{/* Unit overlays */}
{units.map((unit) => {
const maskKey = getMaskKey(unit.unitNo);
const maskData = masks.get(maskKey);
if (!maskData) return <Fragment key={unit.unitNo} />;
return (
<GenericFloorPlanUnit
key={unit.unitNo}
complexName={complexName}
wing={wing}
selectedUnit={selectedUnit}
onSelect={setSelectedUnit}
formattedUnitType={maskData.formattedUnitType}
unit={unit}
floor={selectedFloor}
d={maskData.d}
textTransform={`translate(${maskData.textTransform.join(" ")})`}
/>
);
})}
</svg>
);
}
export default GenericFloorPlan;
// ─── Unit overlay component ──────────────────────────────
function GenericFloorPlanUnit({
complexName,
wing,
unit,
floor,
d,
textTransform,
formattedUnitType,
onSelect,
selectedUnit,
}: {
complexName: ComplexName;
wing?: "East" | "West";
unit: Unit;
floor: string | null;
d: string;
textTransform: string;
formattedUnitType: string;
selectedUnit: Unit | null;
onSelect: (unit: Unit | null) => void;
}) {
const { setPopup, setSide, setPosition } = usePopupStore();
function handleClick(unit: Unit) {
window.open(`/complex/${complexName}/${unit.unitNo}`, "_blank");
}
function handleMouseEnter() {
if (floor === null) return;
setSide("top");
if (!selectedUnit)
setPopup(
<UnitPopup
complexName={complexName}
wing={wing}
unitType={unit.unitType}
floor={unit.floor}
unitNumber={unit.unitNo}
squareFt={unit.squareFt}
suitesArea={unit.suitsArea}
balconyArea={unit.balconyArea}
price={unit.salesPrice}
/>
);
}
return (
<g>
<text
transform={textTransform}
className="fill-white text-[8px] select-none"
textAnchor="middle"
>
<tspan x={0} y={0}>
{unit.unitNo}
</tspan>
<tspan x={0} y={15}>
{formattedUnitType}
</tspan>
</text>
<path
onClick={() => !isMobile && !selectedUnit && handleClick(unit)}
onMouseEnter={!isMobile ? handleMouseEnter : undefined}
onMouseLeave={() => !isMobile && setPopup(null)}
onTouchStart={(e) => {
onSelect(unit);
setPosition({ x: e.touches[0].clientX, y: e.touches[0].clientY });
handleMouseEnter();
}}
className={clsx(
"fill-transparent hover:fill-[#00BED7] opacity-40 isolate cursor-pointer transition-colors",
selectedUnit?.unitNo === unit.unitNo &&
"!fill-[#00BED7] opacity-40 cursor-default"
)}
d={d}
/>
</g>
);
}
@@ -9,15 +9,11 @@ import UnitTypeBadge from "../UnitTypeBadge";
import Button from "../ui/Button";
import { usePopupStore } from "../../stores/usePopupStore";
import { isMobile } from "react-device-detect";
import GenericFloorPlan from "./GenericFloorPlan";
import { getFloorPlanConfig } from "../../data/floor-plan-config";
import { getComplexConfig } from "../../data/complex-config";
// Import floor plan components
import FloorPlanMarasiDriveEast from "../FloorPlanMarasiDriveEast";
import FloorPlanMarasiDriveWestLower from "../FloorPlanMarasiDriveWestLower";
import FloorPlanMarasiDriveWestUpper from "../FloorPlanMarasiDriveWestUpper";
import FloorPlanDubaiMarina7_38 from "../FloorPlanDubaiMarina7_38";
import FloorPlanDubaiMarina7_38Comb from "../FloorPlanDubaiMarina7_38Comb";
import FloorPlanDubaiMarina39_40 from "../FloorPlanDubaiMarina39_40";
import FloorPlanDubaiMarina41_42 from "../FloorPlanDubaiMarina41_42";
// ─── Props ───────────────────────────────────────────────
interface ResidentialFloorViewProps {
floor: ResidentialFloorData;
@@ -28,6 +24,8 @@ interface ResidentialFloorViewProps {
onFloorSelect: (floor: string) => void;
}
// ─── Main component ──────────────────────────────────────
function ResidentialFloorView({
floor,
complexName,
@@ -39,245 +37,265 @@ function ResidentialFloorView({
const { setPopup, setPosition } = usePopupStore();
const [isCombinable, setIsCombinable] = useState(false);
// Marasi Drive specific logic
if (complexName === "marasi-drive") {
const floorNumber = floor.floorNumber;
const wing = floor.wing || selectedFloor.split(" ")[0];
const currentFloorData = floorsData?.find(
(item) => item.floor === floorNumber
);
const complexConfig = getComplexConfig(complexName);
const floorNumber = floor.floorNumber;
const wing = floor.wing || selectedFloor.split(" ")[0];
const totalUnits =
(currentFloorData?.East?.totalUnits || 0) +
(currentFloorData?.West?.totalUnits || 0);
const currentFloorData = floorsData?.find(
(item) => item.floor === floorNumber
);
const wingData =
currentFloorData?.[selectedFloor.split(" ")[0] as "West" | "East"];
// ── Resolve floor plan config ──────────────────────────
const config = getFloorPlanConfig(complexName, selectedFloor, {
isCombinable,
wing: wing as "East" | "West",
});
return (
<div className="2xl:space-y-[1.111vw] space-y-4" onScroll={() => setPopup(null)}>
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-4">
<p className="font-medium text-h4">{floorNumber} floor</p>
<div className="flex items-center 2xl:gap-[0.278vw] gap-1">
<Badge variant="secondary" text={`${totalUnits} Apartments`} />
</div>
// ── Floor display name ─────────────────────────────────
const floorTitle = complexConfig.hasWings
? `${floorNumber} floor`
: `${selectedFloor} floor`;
// ── Apartment count ────────────────────────────────────
const totalUnits = complexConfig.hasWings
? (currentFloorData?.East?.totalUnits || 0) +
(currentFloorData?.West?.totalUnits || 0)
: currentFloorData?.others?.totalUnits || 0;
// ── Special floor check (Dubai Marina: 39-40, 41-42) ──
const isSpecialFloor =
selectedFloor === "39-40" || selectedFloor === "41-42";
// ── Select options ─────────────────────────────────────
const selectOptions = getSelectOptions(complexName, floorsData);
return (
<div
className="2xl:space-y-[1.111vw] space-y-4"
onScroll={() => setPopup(null)}
>
{/* ── Header ──────────────────────────────────────── */}
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-4">
<p className="font-medium text-h4">{floorTitle}</p>
<div className="flex items-center 2xl:gap-[0.278vw] gap-1">
<Badge variant="secondary" text={`${totalUnits} Apartments`} />
{complexConfig.hasCombinable && !isSpecialFloor && (
<Badge variant="primary" text="Combinable" />
)}
</div>
<div className="2xl:space-y-[0.833vw] space-y-2">
<div className="flex items-center 2xl:gap-[1.111vw] gap-2">
<Select
options={
floorsData?.flatMap((item) => [
`East ${item.floor}`,
`West ${item.floor}`,
]) || []
}
defaultOption={selectedFloor?.toString() || ""}
onSelect={onFloorSelect}
className="2xl:w-[8.333vw] md:max-2xl:w-[120px] w-full"
maxOptionsCount={7}
/>
<div className="bg-[#E2E2DC] w-px 2xl:h-[1.667vw] h-1.5"></div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-2 max-md:hidden">
<UnitTypeBadge
type="Studio Flex"
count={wingData?.types["Studio Flex"] || 0}
/>
<UnitTypeBadge
type="Studio"
count={wingData?.types["Studio Squared"] || 0}
/>
<UnitTypeBadge
type="1 Bedroom"
count={wingData?.types["1 BR Squared"] || 0}
/>
<UnitTypeBadge
type="2 Bedroom"
count={wingData?.types["2 BR Squared"] || 0}
/>
</div>
</div>
</div>
{/* ── Controls ────────────────────────────────────── */}
<div className="2xl:space-y-[0.833vw] space-y-2">
<div className="flex items-center 2xl:gap-[1.111vw] gap-2">
<Select
options={selectOptions}
defaultOption={selectedFloor?.toString() || ""}
onSelect={onFloorSelect}
className="2xl:w-[8.333vw] md:max-2xl:w-[120px] w-full"
maxOptionsCount={7}
/>
<UnitTypeBadges
complexName={complexName}
floorsData={floorsData}
selectedFloor={selectedFloor}
currentFloorData={currentFloorData}
wing={wing}
/>
</div>
{/* Combinable toggle (Dubai Marina only) */}
{complexConfig.hasCombinable && !isSpecialFloor && (
<div className="flex gap-2 justify-center items-center">
<Button
variant={!isCombinable ? "cta" : "primary"}
onClick={() => setIsCombinable(false)}
>
Standard
</Button>
<Button
variant={isCombinable ? "cta" : "primary"}
onClick={() => setIsCombinable(true)}
>
Combinable
</Button>
</div>
)}
{/* ── Floor plan ──────────────────────────────────── */}
<div
className="2xl:p-[4.444vw] p-4 bg-[#F3F3F2] 2xl:rounded-[0.833vw] rounded-lg"
className="2xl:py-[1.667vw] 2xl:px-[1.111vw] max-2xl:p-4 bg-[#F3F3F2] 2xl:rounded-[0.833vw] rounded-lg relative 2xl:space-y-[2.222vw] space-y-8"
onMouseMove={(e) =>
!isMobile && setPosition({ x: e.clientX, y: e.clientY })
}
>
{unitsOnFloor && wing === "East" && (
<FloorPlanMarasiDriveEast
unitsOnFloor={unitsOnFloor}
selectedFloor={floorNumber.toString()}
/>
)}
{wing === "West" && unitsOnFloor && (
<>
{floorNumber < 24 ? (
<FloorPlanMarasiDriveWestLower
unitsOnFloor={unitsOnFloor}
selectedFloor={floorNumber.toString()}
/>
) : (
<FloorPlanMarasiDriveWestUpper
unitsOnFloor={unitsOnFloor}
selectedFloor={floorNumber.toString()}
/>
)}
</>
)}
<FloorPlanRenderer
complexName={complexName}
config={config}
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
floorNumber={floorNumber}
/>
</div>
</div>
);
}
// Dubai Marina specific logic
if (complexName === "dubai-marina") {
const floorNumber = floor.floorNumber;
const currentFloorData = floorsData?.find(
(item) => item.floor === floorNumber
);
const isSpecialFloor =
selectedFloor === "39-40" || selectedFloor === "41-42";
return (
<div className="2xl:space-y-[1.111vw] space-y-4" onScroll={() => setPopup(null)}>
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-4">
<p className="font-medium text-h4">{selectedFloor} floor</p>
<div className="flex items-center 2xl:gap-[0.278vw] gap-1">
<Badge
variant="secondary"
text={`${currentFloorData?.others.totalUnits || 0} Apartments`}
/>
{!isSpecialFloor && <Badge variant="primary" text="Combinable" />}
</div>
</div>
<div className="2xl:space-y-[0.833vw] space-y-2">
<div className="flex items-center 2xl:gap-[1.111vw] gap-2">
<Select
options={
floorsData?.map((item) => {
if (item.floor === 39) {
return "39-40";
}
if (item.floor === 41) {
return "41-42";
}
return item.floor.toString();
}) || []
}
defaultOption={selectedFloor?.toString() || ""}
onSelect={onFloorSelect}
className="2xl:w-[8.333vw] md:max-xl:w-[120px] w-full"
maxOptionsCount={7}
/>
<div className="bg-[#E2E2DC] w-px 2xl:h-[1.667vw] h-1.5"></div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-2 max-md:hidden">
<UnitTypeBadge
type="Studio"
count={
floorsData?.find(
(item) =>
item.floor ===
parseInt(selectedFloor!.split(" ").at(-1)!)
)?.others.types["Studio2"] || 0
}
/>
<UnitTypeBadge
type="1 Bedroom"
count={
floorsData?.find(
(item) =>
item.floor ===
parseInt(selectedFloor!.split(" ").at(-1)!)
)?.others.types["One Bedroom2"] || 0
}
/>
<UnitTypeBadge
type="1 Bedroom Loft"
count={
floorsData?.find(
(item) =>
item.floor ===
parseInt(selectedFloor!.split(" ").at(-1)!)
)?.others.types["One Bedroom Loft"] || 0
}
/>
<UnitTypeBadge
type="2 Bedroom Loft"
count={
floorsData?.find(
(item) =>
item.floor ===
parseInt(selectedFloor!.split(" ").at(-1)!)
)?.others.types["Two Bedroom Loft"] || 0
}
/>
</div>
</div>
{!isSpecialFloor && (
<div className="flex gap-2 justify-center items-center">
<Button
variant={!isCombinable ? "cta" : "primary"}
onClick={() => setIsCombinable(false)}
>
Standard
</Button>
<Button
variant={isCombinable ? "cta" : "primary"}
onClick={() => setIsCombinable(true)}
>
Combinable
</Button>
</div>
)}
<div
className="2xl:py-[1.667vw] 2xl:px-[1.111vw] max-2xl:p-4 bg-[#F3F3F2] 2xl:rounded-[0.833vw] rounded-lg relative 2xl:space-y-[2.222vw] space-y-8"
onMouseMove={(e) =>
!isMobile && setPosition({ x: e.clientX, y: e.clientY })
}
>
{selectedFloor && unitsOnFloor && (
<>
{+selectedFloor >= 7 && +selectedFloor < 39 && (
<>
{!isCombinable ? (
<FloorPlanDubaiMarina7_38
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
/>
) : (
<FloorPlanDubaiMarina7_38Comb
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
/>
)}
</>
)}
{selectedFloor === "39-40" && (
<FloorPlanDubaiMarina39_40
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
chosenUnit={null}
/>
)}
{selectedFloor === "41-42" && (
<FloorPlanDubaiMarina41_42
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
chosenUnit={null}
/>
)}
</>
)}
</div>
</div>
</div>
);
}
// Default fallback
return <div>Unsupported complex: {complexName}</div>;
</div>
);
}
export default ResidentialFloorView;
// ─── Select options helper ──────────────────────────────
function getSelectOptions(
complexName: ComplexName,
floorsData?: FloorsData[]
): string[] {
if (!floorsData) return [];
if (complexName === "marasi-drive") {
return floorsData.flatMap((item) => [
`East ${item.floor}`,
`West ${item.floor}`,
]);
}
if (complexName === "dubai-marina") {
return floorsData.map((item) => {
if (item.floor === 39) return "39-40";
if (item.floor === 41) return "41-42";
return item.floor.toString();
});
}
// HQ and default
return floorsData.map((item) => item.floor.toString());
}
// ─── Unit type badges ───────────────────────────────────
function UnitTypeBadges({
complexName,
floorsData,
selectedFloor,
currentFloorData,
wing,
}: {
complexName: ComplexName;
floorsData?: FloorsData[];
selectedFloor: string;
currentFloorData?: FloorsData;
wing: string;
}) {
if (complexName === "marasi-drive") {
const wingData =
currentFloorData?.[wing as "West" | "East"];
return (
<>
<div className="bg-[#E2E2DC] w-px 2xl:h-[1.667vw] h-1.5"></div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-2 max-md:hidden">
<UnitTypeBadge
type="Studio Flex"
count={wingData?.types["Studio Flex"] || 0}
/>
<UnitTypeBadge
type="Studio"
count={wingData?.types["Studio Squared"] || 0}
/>
<UnitTypeBadge
type="1 Bedroom"
count={wingData?.types["1 BR Squared"] || 0}
/>
<UnitTypeBadge
type="2 Bedroom"
count={wingData?.types["2 BR Squared"] || 0}
/>
</div>
</>
);
}
if (complexName === "dubai-marina") {
const floorNum = parseInt(selectedFloor.split(" ").at(-1)!);
const floorData = floorsData?.find((item) => item.floor === floorNum);
return (
<>
<div className="bg-[#E2E2DC] w-px 2xl:h-[1.667vw] h-1.5"></div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-2 max-md:hidden">
<UnitTypeBadge
type="Studio"
count={floorData?.others.types["Studio2"] || 0}
/>
<UnitTypeBadge
type="1 Bedroom"
count={floorData?.others.types["One Bedroom2"] || 0}
/>
<UnitTypeBadge
type="1 Bedroom Loft"
count={floorData?.others.types["One Bedroom Loft"] || 0}
/>
<UnitTypeBadge
type="2 Bedroom Loft"
count={floorData?.others.types["Two Bedroom Loft"] || 0}
/>
</div>
</>
);
}
// HQ: no unit type badges
return null;
}
// ─── Floor plan renderer ────────────────────────────────
function FloorPlanRenderer({
complexName,
config,
selectedFloor,
unitsOnFloor,
floorNumber,
}: {
complexName: ComplexName;
config: ReturnType<typeof getFloorPlanConfig>;
selectedFloor: string;
unitsOnFloor?: Unit[];
floorNumber: number;
}) {
if (!selectedFloor || !unitsOnFloor) return null;
if (!config) {
return (
<div className="text-center text-gray-400 py-8">
Floor plan coming soon
</div>
);
}
// Component-based rendering (Marasi Drive inline SVG)
if (config.component) {
const Component = config.component;
return (
<Component
selectedFloor={floorNumber.toString()}
unitsOnFloor={unitsOnFloor}
/>
);
}
// Image-based rendering (Dubai Marina, HQ)
return (
<GenericFloorPlan
complexName={complexName}
viewBox={config.viewBox}
imagePath={config.imagePath}
masks={config.masks}
getMaskKey={config.getMaskKey}
filterUnits={config.filterUnits}
selectedFloor={selectedFloor}
unitsOnFloor={unitsOnFloor}
wing={config.wing}
/>
);
}
@@ -1,78 +0,0 @@
import useModalStore from "../../../stores/useModalStore";
import AmentitiesBadge from "../../AmentitiesCard";
import PlayIcon from "../../icons/PlayIcon";
import Badge from "../../ui/Badge";
import Button from "../../ui/Button";
import VideoModal from "../../VideoModal";
function GroundDubaiMarina() {
const { setModal } = useModalStore();
return (
<div className="2xl:space-y-[2.222vw] space-y-8">
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Ground Level</p>
<Badge variant="secondary" text="14 Amenities" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
src="/images/floor-plans/compass.png"
className="absolute top-0 left-0 size-[7.222vw]"
alt=""
/>
<img
src="/images/floor-plans/dubai-marina/ground.png"
alt="podium"
className="w-full"
/>
<Button
variant="cta"
className="absolute 2xl:top-[1.667vw] 2xl:right-[1.667vw] md:max-2xl:top-6 md:max-2xl:right-6 top-4 right-4"
onlyIcon
size="small"
onClick={() =>
setModal(
<VideoModal src="/videos/dubai-marina/GroundDubaiMarina.mp4" />
)
}
>
<span className="2xl:size-[1.111vw] size-4">
<PlayIcon />
</span>
</Button>
</div>
</div>
<div className="2xl:space-y-[1.111vw] space-y-4">
<h4 className="text-h4 font-medium">Amenities</h4>
<div className="flex flex-wrap 2xl:gap-[0.556vw] gap-2">
{[
"Residential Entrance",
"Multifunctional Feature Staircase",
"Lobby Lounge & Concierge",
"Outdoor Landscape Seating Area",
"Lift Lobby",
"Rove Cafe & Energize Bar",
"Organic Smart Gardens & Seating",
"Co-working Area ",
"24x7 Convenience Store",
"WCs",
"Visitor Parking",
"EV Charging Stations",
"Bicycle/Scooter Rental & Storage",
"Drop-off Area",
].map((amentity) => (
<AmentitiesBadge key={amentity} title={amentity} />
))}
</div>
</div>
<img
src="/images/floor-plans/dubai-marina/ground/content.jpg"
className="2xl:rounded-[1.111vw] rounded-2xl select-none"
alt=""
/>
</div>
);
}
export default GroundDubaiMarina;
@@ -1,103 +0,0 @@
import useModalStore from "../../../stores/useModalStore";
import AmentitiesBadge from "../../AmentitiesCard";
import AmenitiesBadge from "../../icons/AmenitiesBadge";
import PlayIcon from "../../icons/PlayIcon";
import Badge from "../../ui/Badge";
import Button from "../../ui/Button";
import VideoModal from "../../VideoModal";
import AmentitiesContentSlider from "../../AmentitiesContentSlider";
function PodiumDubaiMarina() {
const { setModal } = useModalStore();
return (
<div className="2xl:space-y-[2.222vw] space-y-8">
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Podium</p>
<Badge variant="secondary" text="14 Amenities" />
</div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-6">
<AmenitiesBadge count={3} type="Indoor" />
<AmenitiesBadge count={12} type="Outdoor" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
src="/images/floor-plans/compass.png"
className="absolute top-0 left-0 size-[7.222vw]"
alt=""
/>
<img
src="/images/floor-plans/dubai-marina/podium.png"
alt="podium"
className="w-full"
/>
<Button
variant="cta"
className="absolute 2xl:top-[1.667vw] 2xl:right-[1.667vw] md:max-2xl:top-6 md:max-2xl:right-6 top-4 right-4"
onlyIcon
size="small"
onClick={() =>
setModal(
<VideoModal src="/videos/dubai-marina/PodiumDubaiMarina.mp4" />
)
}
>
<span className="2xl:size-[1.111vw] size-4">
<PlayIcon />
</span>
</Button>
</div>
</div>
<div className="2xl:space-y-[1.111vw] space-y-4">
<h4 className="text-h4 font-medium">Indoor Amenities</h4>
<div className="flex flex-wrap 2xl:gap-[0.556vw] gap-2">
{[
"Multipurpose Hall",
"Gaming Lounge",
"State-of-the-art Gym",
"7m Climbing Wall",
"Changing Rooms & Lockers",
"Hydration Station",
"Boutique Fitness Studio - Crank",
"Rentable Guest Rooms",
].map((amentity) => (
<AmentitiesBadge key={amentity} title={amentity} />
))}
</div>
</div>
<hr className="border-[#E2E2DC] 2xl:h-[0.069vw] h-px" />
<div className="2xl:space-y-[1.111vw] space-y-4">
<h4 className="text-h4 font-medium">Outdoor Amenities</h4>
<div className="flex flex-wrap 2xl:gap-[0.556vw] gap-2">
{[
"Semi-Olympic Leisure Pool",
"Outdoor Cinema & Amphitheatre",
"Water Feature Wall",
"Multipurpose Fitness Pool",
"Communal Gardens",
"BBQ & Social Zone",
"Popsicle Cart",
"Gaming Lounge - Terrace",
"Zen Library",
"Co-working Area",
"Multipurpose Hall with Terrace",
"Marina View Chill Zone",
"Outdoor Gym",
].map((amentity) => (
<AmentitiesBadge key={amentity} title={amentity} />
))}
</div>
</div>
<AmentitiesContentSlider
srcs={[
"/images/floor-plans/dubai-marina/podium/content1.jpg",
"/images/floor-plans/dubai-marina/podium/content2.jpg",
"/images/floor-plans/dubai-marina/podium/content3.jpg",
]}
/>
</div>
);
}
export default PodiumDubaiMarina;
@@ -1,80 +0,0 @@
import useModalStore from "../../../stores/useModalStore";
import AmentitiesBadge from "../../AmentitiesCard";
import PlayIcon from "../../icons/PlayIcon";
import Badge from "../../ui/Badge";
import Button from "../../ui/Button";
import VideoModal from "../../VideoModal";
const amenities = [
"Sky Viewing Lounges",
"Convertible Indoor Infinity Pool",
"Marina View Amphitheatre",
"Ultra Shield Oxygen Pod",
"Aroma Steam Pod",
"Reflexology Pool",
"Cold Bucket Experience Shower Pod",
"Experience Shower Pod",
"Cold Plunge Pool",
"Salt Steam Pod",
"Finnish Sauna Pod",
"Water Feature Wall",
"Vitality Pool",
"Changing Rooms and Lockers",
];
function RooftopDubaiMarina() {
const { setModal } = useModalStore();
return (
<div className="2xl:space-y-[2.222vw] space-y-8">
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Sky 44 - Rooftop</p>
<Badge variant="secondary" text="14 Amenities" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
src="/images/floor-plans/compass.png"
className="absolute top-0 left-0 size-[7.222vw]"
alt=""
/>
<img
src="/images/floor-plans/dubai-marina/rooftop.png"
alt="podium"
className="w-full"
/>
<Button
variant="cta"
className="absolute 2xl:top-[1.667vw] 2xl:right-[1.667vw] md:max-2xl:top-6 md:max-2xl:right-6 top-4 right-4"
onlyIcon
size="small"
onClick={() =>
setModal(
<VideoModal src="/videos/dubai-marina/SkyDubaiMarina.mp4" />
)
}
>
<span className="2xl:size-[1.111vw] size-4">
<PlayIcon />
</span>
</Button>
</div>
</div>
<div className="2xl:space-y-[1.111vw] space-y-4">
<h4 className="text-h4 font-medium">Amenities</h4>
<div className="flex flex-wrap 2xl:gap-[0.556vw] gap-2">
{amenities.map((amentity) => (
<AmentitiesBadge key={amentity} title={amentity} />
))}
</div>
</div>
<img
src="/images/floor-plans/dubai-marina/rooftop/content.jpg"
className="2xl:rounded-[1.111vw] rounded-2xl select-none"
alt=""
/>
</div>
);
}
export default RooftopDubaiMarina;
@@ -1,92 +0,0 @@
import useModalStore from "../../../stores/useModalStore";
import AmentitiesBadge from "../../AmentitiesCard";
import AmentitiesContentSlider from "../../AmentitiesContentSlider";
import ConvenienceIcon from "../../icons/amentities/ConvenienceIcon";
import CoworkingIcon from "../../icons/amentities/CoworkingIcon";
import LoungingSpaceIcon from "../../icons/amentities/LoungingSpaceIcon";
import LushLandscapeIcon from "../../icons/amentities/LushLandscapeIcon";
import PrivateMeetingRoomsIcon from "../../icons/amentities/PrivateMeetingRoomsIcon";
import RoveCafe from "../../icons/amentities/RoveCafe";
import SoundproofMeetingPodsIcon from "../../icons/amentities/SoundproofMeetingPodsIcon";
import PlayIcon from "../../icons/PlayIcon";
import Badge from "../../ui/Badge";
import Button from "../../ui/Button";
import VideoModal from "../../VideoModal";
function GroundMarasiDrive() {
const { setModal } = useModalStore();
return (
<div className="space-y-[2.222vw]">
<div className="space-y-[1.667vw]">
<div className="space-y-[0.556vw] border-b border-[#E2E2DC] pb-[1.667vw]">
<p className="text-h4 font-medium">Ground Level</p>
<Badge variant="secondary" text="7 Amenities" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
src="/images/floor-plans/compass.png"
className="absolute top-0 left-0 size-[7.222vw]"
alt=""
/>
<img
src="/images/floor-plans/marasi-drive/ground.png"
alt="podium"
className="w-full"
/>
<Button
variant="cta"
className="absolute 2xl:top-[1.667vw] 2xl:right-[1.667vw] md:max-2xl:top-6 md:max-2xl:right-6 top-4 right-4"
onlyIcon
size="small"
onClick={() =>
setModal(
<VideoModal src="/videos/marasi-drive/GroundMarasiDrive.mp4" />
)
}
>
<span className="2xl:size-[1.111vw] size-4">
<PlayIcon />
</span>
</Button>
</div>
</div>
<div className="space-y-[1.667vw]">
<p className="font-medium text-h4">Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 gap-[1.111vw]">
<AmentitiesBadge icon={<RoveCafe />} title="Rove Café" />
<AmentitiesBadge icon={<LoungingSpaceIcon />} title="Lobby Lounge" />
<AmentitiesBadge icon={<CoworkingIcon />} title="Coworking Space" />
<AmentitiesBadge
icon={<LushLandscapeIcon />}
title="Outdoor Terrace"
/>
<AmentitiesBadge
icon={<PrivateMeetingRoomsIcon />}
title="Private Meeting Rooms"
/>
<AmentitiesBadge
icon={<ConvenienceIcon />}
title="Convenience Store"
/>
<AmentitiesBadge
icon={<SoundproofMeetingPodsIcon />}
title="Soundproof Meeting Pods"
/>
</div>
</div>
<AmentitiesContentSlider
srcs={[
"/images/floor-plans/marasi-drive/ground/content1.jpg",
"/images/floor-plans/marasi-drive/ground/content2.jpg",
"/images/floor-plans/marasi-drive/ground/content3.jpg",
"/images/floor-plans/marasi-drive/ground/content4.jpg",
"/images/floor-plans/marasi-drive/ground/content5.jpg",
"/images/floor-plans/marasi-drive/ground/content6.jpg",
]}
/>
</div>
);
}
export default GroundMarasiDrive;
@@ -1,177 +0,0 @@
import AmentitiesBadge from "../../AmentitiesCard";
import AmenitiesBadge from "../../icons/AmenitiesBadge";
import ArcadeGameIcon from "../../icons/amentities/ArcadeGameIcon";
import ChangingRoomIcon from "../../icons/amentities/ChangingRoomIcon";
import ClimbingWallIcon from "../../icons/amentities/ClimbingWallIcon";
import FullyEquippedGymIcon from "../../icons/amentities/FullyEquippedGymIcon";
import GuestRooms from "../../icons/amentities/GuestRooms";
import HammockMovieLoungeIcon from "../../icons/amentities/HammockMovieLoungeIcon";
import KaraokeIcon from "../../icons/amentities/KaraokeIcon";
import LoungeIcon from "../../icons/amentities/LoungeIcon";
import MonkeyBarsIcon from "../../icons/amentities/MonkeyBarsIcon";
import MultiballInteractiveGamingIcon from "../../icons/amentities/MultiballInteractiveGamingIcon";
import MultiPurposeRoomWithKitchenIcon from "../../icons/amentities/MultiPurposeRoomWithKitchenIcon";
import PlaystationIcon from "../../icons/amentities/PlaystationIcon";
import Badge from "../../ui/Badge";
import GamingLoungeIcon from "../../icons/amentities/GamingLoungeIcon";
import UrbanBeachPoolIcon from "../../icons/amentities/UrbanBeachPoolIcon";
import JacuzziIcon from "../../icons/amentities/JacuzziIcon";
import YogaLoungeIcon from "../../icons/amentities/YogaLoungeIcon";
import SunLoungeIcon from "../../icons/amentities/SunLoungeIcon";
import CascadingLeisurePoolIcon from "../../icons/amentities/CascadingLeisurePoolIcon";
import AquaCyclingIcon from "../../icons/amentities/AquaCyclingIcon";
import OpenAirGymIcon from "../../icons/amentities/OpenAirGymIcon";
import RoveBeverageTruckIcon from "../../icons/amentities/RoveBeverageTruckIcon";
import CabanasWithDaybeds from "../../icons/amentities/CabanasWithDaybeds";
import IntegratedLapPoolIcon from "../../icons/amentities/IntegratedLapPoolIcon";
import SunkenGardensIcon from "../../icons/amentities/SunkenGardensIcon";
import GamingTerraceIcon from "../../icons/amentities/GamingTerraceIcon";
import CoworkingIcon from "../../icons/amentities/CoworkingIcon";
import Button from "../../ui/Button";
import VideoModal from "../../VideoModal";
import PlayIcon from "../../icons/PlayIcon";
import useModalStore from "../../../stores/useModalStore";
import AmentitiesContentSlider from "../../AmentitiesContentSlider";
function PodiumMarasiDrive() {
const { setModal } = useModalStore();
return (
<div className="2xl:space-y-[2.222vw] space-y-8">
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Podium Level</p>
<Badge variant="secondary" text="27 Amenities" />
</div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-6">
<AmenitiesBadge count={13} type="Indoor" />
<AmenitiesBadge count={14} type="Outdoor" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
src="/images/floor-plans/compass.png"
className="absolute top-0 left-0 size-[7.222vw]"
alt=""
/>
<img
src="/images/floor-plans/marasi-drive/podium.png"
alt="podium"
className="w-full"
/>
<Button
variant="cta"
className="absolute 2xl:top-[1.667vw] 2xl:right-[1.667vw] md:max-2xl:top-6 md:max-2xl:right-6 top-4 right-4"
onlyIcon
size="small"
onClick={() =>
setModal(
<VideoModal src="/videos/marasi-drive/PodiumMarasiDrive.mp4" />
)
}
>
<span className="2xl:size-[1.111vw] size-4">
<PlayIcon />
</span>
</Button>
</div>
</div>
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Indoor Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-[1.111vw] gap-4">
<AmentitiesBadge icon={<LoungeIcon />} title="Indoor Lounge" />
<AmentitiesBadge icon={<MonkeyBarsIcon />} title="Monkey Bars" />
<AmentitiesBadge icon={<KaraokeIcon />} title="Karaoke Room" />
<AmentitiesBadge icon={<ArcadeGameIcon />} title="Arcade Games" />
<AmentitiesBadge icon={<ClimbingWallIcon />} title="Climbing Wall" />
<AmentitiesBadge
icon={<PlaystationIcon />}
title="Playstation Deck"
/>
<AmentitiesBadge
icon={<FullyEquippedGymIcon />}
title="Fully Equipped Gym"
/>
<AmentitiesBadge icon={<ChangingRoomIcon />} title="Changing Rooms" />
<AmentitiesBadge
icon={<HammockMovieLoungeIcon />}
title="Hammock Movie Lounge"
/>
<AmentitiesBadge icon={<GuestRooms />} title="Guest Rooms" />
<AmentitiesBadge
icon={<MultiballInteractiveGamingIcon />}
title="Multi Ball Interactive Gaming"
/>
<AmentitiesBadge
icon={<MultiPurposeRoomWithKitchenIcon />}
title="Multi-purpose Room for Kitchen"
/>
<AmentitiesBadge icon={<GamingLoungeIcon />} title="Gaming Lounge" />
</div>
</div>
<hr className="border-[#E2E2DC] h-[0.069vw]" />
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Outdoor Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-x-[1.111vw] 2xl:gap-y-[1.667vw] gap-x-4 gap-y-6">
<AmentitiesBadge
icon={<UrbanBeachPoolIcon />}
title="Urban Beach Pool"
/>
<AmentitiesBadge icon={<JacuzziIcon />} title="Jacuzzi" />
<AmentitiesBadge icon={<YogaLoungeIcon />} title="Yoga Lounge" />
<AmentitiesBadge icon={<SunLoungeIcon />} title="Sun Lounging Pool" />
<AmentitiesBadge
icon={<CascadingLeisurePoolIcon />}
title="Cascading Leisure Pool"
/>
<AmentitiesBadge icon={<AquaCyclingIcon />} title="AquaCycling" />
<AmentitiesBadge icon={<OpenAirGymIcon />} title="Open-Air Gym" />
<AmentitiesBadge
icon={<RoveBeverageTruckIcon />}
title="Rove Beverage Truck"
/>
<AmentitiesBadge
icon={<CabanasWithDaybeds />}
title="Cabanas with Daybeds"
/>
<AmentitiesBadge
icon={<IntegratedLapPoolIcon />}
title="Integrated Lap Pool"
/>
<AmentitiesBadge
icon={<SunkenGardensIcon />}
title="Sunken Gardens"
/>
<AmentitiesBadge
icon={<MultiPurposeRoomWithKitchenIcon />}
title="Outdoor Multi-Purpose Terrace"
/>
<AmentitiesBadge
icon={<GamingTerraceIcon />}
title="Outdoor Gaming Terrace"
/>
<AmentitiesBadge
icon={<CoworkingIcon />}
title="Outdoor Coworking Space"
/>
</div>
</div>
<AmentitiesContentSlider
srcs={[
"/images/floor-plans/marasi-drive/podium/content1.jpg",
"/images/floor-plans/marasi-drive/podium/content2.jpg",
"/images/floor-plans/marasi-drive/podium/content3.jpg",
"/images/floor-plans/marasi-drive/podium/content4.jpg",
"/images/floor-plans/marasi-drive/podium/content5.jpg",
"/images/floor-plans/marasi-drive/podium/content6.jpg",
"/images/floor-plans/marasi-drive/podium/content7.jpg",
"/images/floor-plans/marasi-drive/podium/content8.jpg",
"/images/floor-plans/marasi-drive/podium/content9.jpg",
"/images/floor-plans/marasi-drive/podium/content10.jpg",
"/images/floor-plans/marasi-drive/podium/content11.jpg",
]}
/>
</div>
);
}
export default PodiumMarasiDrive;
@@ -1,101 +0,0 @@
import useModalStore from "../../../stores/useModalStore";
import AmentitiesBadge from "../../AmentitiesCard";
import BBQTerraceIcon from "../../icons/amentities/BBQTerraceIcon";
import CabanasWithDaybeds from "../../icons/amentities/CabanasWithDaybeds";
import CommunalDiningTablesRoundedIcon from "../../icons/amentities/CommunalDiningTablesRoundedIcon";
import FirePitIcon from "../../icons/amentities/FirePitIcon";
import LoungingSpaceIcon from "../../icons/amentities/LoungingSpaceIcon";
import OutdoorKitchenIcon from "../../icons/amentities/OutdoorKitchenIcon";
import RooftopGardenIcon from "../../icons/amentities/RooftopGardenIcon";
import StargazingIcon from "../../icons/amentities/StargazingIcon";
import SunkenSeatingIcon from "../../icons/amentities/SunkenSeatingIcon";
import ViewingDeckWithWingsIcon from "../../icons/amentities/ViewingDeckWithWingsIcon";
import PlayIcon from "../../icons/PlayIcon";
import Badge from "../../ui/Badge";
import Button from "../../ui/Button";
import VideoModal from "../../VideoModal";
function RooftopMarasiDrive() {
const { setModal } = useModalStore();
return (
<div className="2xl:space-y-[2.222vw] space-y-8">
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Rooftop</p>
<Badge variant="secondary" text="10 Amenities" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
src="/images/floor-plans/compass.png"
className="absolute top-0 left-0 size-[7.222vw]"
alt=""
/>
<img
src="/images/floor-plans/marasi-drive/rooftop.png"
alt="podium"
className="w-full"
/>
<Button
variant="cta"
className="absolute 2xl:top-[1.667vw] 2xl:right-[1.667vw] md:max-2xl:top-6 md:max-2xl:right-6 top-4 right-4"
onlyIcon
size="small"
onClick={() =>
setModal(
<VideoModal src="/videos/marasi-drive/RooftopMarasiDrive.mp4" />
)
}
>
<span className="2xl:size-[1.111vw] size-4">
<PlayIcon />
</span>
</Button>
</div>
</div>
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-[1.111vw] gap-4">
<AmentitiesBadge icon={<StargazingIcon />} title="Stargazing Point" />
<AmentitiesBadge icon={<BBQTerraceIcon />} title="BBQ Terrace" />
<AmentitiesBadge
icon={<OutdoorKitchenIcon />}
title="Outdoor Kitchen"
/>
<AmentitiesBadge
icon={<CabanasWithDaybeds />}
title="Cabanas with Daybeds"
/>
<AmentitiesBadge
icon={<ViewingDeckWithWingsIcon />}
title="Viewing Deck with Wings"
/>
<AmentitiesBadge
icon={<LoungingSpaceIcon />}
title="Lounging Space"
/>
<AmentitiesBadge
icon={<SunkenSeatingIcon />}
title="Sunken Seating"
/>
<AmentitiesBadge icon={<FirePitIcon />} title="Firepit" />
<AmentitiesBadge
icon={<RooftopGardenIcon />}
title="Rooftop Garden"
/>
<AmentitiesBadge
icon={<CommunalDiningTablesRoundedIcon />}
title="Communal Dining Tables"
/>
</div>
</div>
<img
src="/images/floor-plans/marasi-drive/rooftop/content.jpg"
alt="rooftop"
className="w-full 2xl:rounded-[1.111vw] rounded-2xl select-none"
/>
</div>
);
}
export default RooftopMarasiDrive;
@@ -1,130 +0,0 @@
import useModalStore from "../../../stores/useModalStore";
import AmentitiesBadge from "../../AmentitiesCard";
import AmentitiesContentSlider from "../../AmentitiesContentSlider";
import AmenitiesBadge from "../../icons/AmenitiesBadge";
import AmphitheatreIcon from "../../icons/amentities/AmphitheatreIcon";
import BoulderingWallIcon from "../../icons/amentities/BoulderingWallIcon";
import ChangingRoomIcon from "../../icons/amentities/ChangingRoomIcon";
import ChessIcon from "../../icons/amentities/ChessIcon";
import CinemaIcon from "../../icons/amentities/CinemaIcon";
import ClimbingWallIcon from "../../icons/amentities/ClimbingWallIcon";
import CommunalDiningTablesIcon from "../../icons/amentities/CommunalDiningTablesIcon";
import CoworkingIcon from "../../icons/amentities/CoworkingIcon";
import LushLandscapeIcon from "../../icons/amentities/LushLandscapeIcon";
import MultiPurposeIcon from "../../icons/amentities/MultiPurposeIcon";
import PingPongIcon from "../../icons/amentities/PingPongIcon";
import PingPongInTubeIcon from "../../icons/amentities/PingPongInTubeIcon";
import PoolIcon from "../../icons/amentities/PoolIcon";
import RunningWheelIcon from "../../icons/amentities/RunningWheelIcon";
import SunLoungeIcon from "../../icons/amentities/SunLoungeIcon";
import SuspendedLoungingNetsIcon from "../../icons/amentities/SuspendedLoungingNetsIcon";
import WellnessIcon from "../../icons/amentities/WellnessIcon";
import PlayIcon from "../../icons/PlayIcon";
import Badge from "../../ui/Badge";
import Button from "../../ui/Button";
import VideoModal from "../../VideoModal";
function SkyGardenMarasiDrive() {
const { setModal } = useModalStore();
return (
<div className="2xl:space-y-[2.222vw] space-y-8">
<div className="2xl:space-y-[1.667vw] space-y-6">
<div className="2xl:space-y-[0.556vw] space-y-2 border-b border-[#E2E2DC] 2xl:pb-[1.667vw] pb-6">
<p className="text-h4 font-medium">Sky Garden</p>
<Badge variant="secondary" text="15 Amenities" />
</div>
<div className="flex items-center 2xl:gap-[1.667vw] gap-4">
<AmenitiesBadge count={3} type="Indoor" />
<AmenitiesBadge count={12} type="Outdoor" />
</div>
<div className="bg-[#F3F3F2] 2xl:rounded-[1.111vw] rounded-2xl 2xl:p-[1.111vw] p-4 relative">
<img
src="/images/floor-plans/compass.png"
className="absolute top-0 left-0 size-[7.222vw]"
alt=""
/>
<img
src="/images/floor-plans/marasi-drive/sky-garden.png"
alt="sky-garden"
className="w-full"
/>
<Button
variant="cta"
className="absolute 2xl:top-[1.667vw] 2xl:right-[1.667vw] md:max-2xl:top-6 md:max-2xl:right-6 top-4 right-4"
onlyIcon
size="small"
onClick={() =>
setModal(
<VideoModal src="/videos/marasi-drive/SkyGardenMarasiDrive.mp4" />
)
}
>
<span className="2xl:size-[1.111vw] size-4">
<PlayIcon />
</span>
</Button>
</div>
</div>
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Indoor Amenities</p>
<div className="grid md:grid-cols-4 grid-cols-3 2xl:gap-[1.111vw] gap-4">
<AmentitiesBadge icon={<PoolIcon />} title="Indoor Lap Pool" />
<AmentitiesBadge icon={<WellnessIcon />} title="Wellness Features" />
<AmentitiesBadge icon={<ChangingRoomIcon />} title="Changing Rooms" />
</div>
</div>
<hr className="border-[#E2E2DC] h-[0.069vw]" />
<div className="2xl:space-y-[1.667vw] space-y-6">
<p className="font-medium text-h4">Outdoor Amenities</p>
<div className="grid grid-cols-3 2xl:gap-x-[1.111vw] 2xl:gap-y-[1.667vw] gap-x-4 gap-y-6">
<AmentitiesBadge icon={<PingPongIcon />} title="Padel Pong" />
<AmentitiesBadge icon={<SunLoungeIcon />} title="Sun Lounging Deck" />
<AmentitiesBadge icon={<CinemaIcon />} title="Outdoor Cinema" />
<AmentitiesBadge
icon={<BoulderingWallIcon />}
title="Bouldering Wall"
/>
<AmentitiesBadge
icon={<PingPongInTubeIcon />}
title="Ping Pong in a Tube"
/>
<AmentitiesBadge icon={<AmphitheatreIcon />} title="Amphitheatre" />
<AmentitiesBadge
icon={<CommunalDiningTablesIcon />}
title="Communal Dining Tables"
/>
<AmentitiesBadge
icon={<SuspendedLoungingNetsIcon />}
title="Suspended Lounging Nets "
/>
<AmentitiesBadge
icon={<LushLandscapeIcon />}
title="Lush Landscape"
/>
<AmentitiesBadge icon={<RunningWheelIcon />} title="Running Wheel" />
<AmentitiesBadge icon={<ChessIcon />} title="Chess Tables" />
<AmentitiesBadge icon={<ClimbingWallIcon />} title="Climbing Wall" />
<AmentitiesBadge
icon={<CoworkingIcon />}
title="Outdoor Coworking Space"
/>
<AmentitiesBadge
icon={<MultiPurposeIcon />}
title="Multi-purpose Court"
/>
</div>
</div>
<AmentitiesContentSlider
srcs={[
"/images/floor-plans/marasi-drive/skygarden/content1.jpg",
"/images/floor-plans/marasi-drive/skygarden/content2.jpg",
"/images/floor-plans/marasi-drive/skygarden/content3.jpg",
"/images/floor-plans/marasi-drive/skygarden/content4.jpg",
]}
/>
</div>
);
}
export default SkyGardenMarasiDrive;