diff --git a/bun.lock b/bun.lock index b67e06e..a99a667 100644 --- a/bun.lock +++ b/bun.lock @@ -17,6 +17,7 @@ "react": "^19.0.0", "react-device-detect": "^2.2.3", "react-dom": "^19.0.0", + "react-loading-skeleton": "^3.5.0", "react-router": "^7.5.0", "react-swipeable": "^7.0.2", "tailwindcss": "^4.1.3", @@ -475,6 +476,8 @@ "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], + "react-loading-skeleton": ["react-loading-skeleton@3.5.0", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ=="], + "react-router": ["react-router@7.5.0", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0", "turbo-stream": "2.4.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g=="], "react-swipeable": ["react-swipeable@7.0.2", "", { "peerDependencies": { "react": "^16.8.3 || ^17 || ^18 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-v1Qx1l+aC2fdxKa9aKJiaU/ZxmJ5o98RMoFwUqAAzVWUcxgfHFXDDruCKXhw6zIYXm6V64JiHgP9f6mlME5l8w=="], diff --git a/package.json b/package.json index 9b3aaa0..8ebff35 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react": "^19.0.0", "react-device-detect": "^2.2.3", "react-dom": "^19.0.0", + "react-loading-skeleton": "^3.5.0", "react-router": "^7.5.0", "react-swipeable": "^7.0.2", "tailwindcss": "^4.1.3", diff --git a/public/images/map/compass.png b/public/images/map/compass.png index cee2d20..3e6073c 100644 Binary files a/public/images/map/compass.png and b/public/images/map/compass.png differ diff --git a/public/images/map/map-desktop.jpg b/public/images/map/map-desktop-old.jpg similarity index 100% rename from public/images/map/map-desktop.jpg rename to public/images/map/map-desktop-old.jpg diff --git a/public/images/map/map-mobile.jpg b/public/images/map/map-mobile-old.jpg similarity index 100% rename from public/images/map/map-mobile.jpg rename to public/images/map/map-mobile-old.jpg diff --git a/public/images/map/map-new-desktop.jpg b/public/images/map/map-new-desktop.jpg new file mode 100644 index 0000000..75a9696 Binary files /dev/null and b/public/images/map/map-new-desktop.jpg differ diff --git a/public/images/map/map-new-mobile.jpg b/public/images/map/map-new-mobile.jpg new file mode 100644 index 0000000..e872bb9 Binary files /dev/null and b/public/images/map/map-new-mobile.jpg differ diff --git a/public/images/map/map-original.jpg b/public/images/map/map-original-old.jpg similarity index 100% rename from public/images/map/map-original.jpg rename to public/images/map/map-original-old.jpg diff --git a/src/components/Compass.tsx b/src/components/Compass.tsx index 33f467c..776b492 100644 --- a/src/components/Compass.tsx +++ b/src/components/Compass.tsx @@ -1,5 +1,3 @@ -import clsx from "clsx"; - interface CompassProps { imgStyle?: React.CSSProperties; } @@ -7,13 +5,9 @@ interface CompassProps { function Compass({ imgStyle }: CompassProps) { return (
- +
+ +
IRTH -

+

For more information, visit
our website:  - + www.irth.ae

@@ -33,79 +31,79 @@ function Footer() {

Follow us for more:

-
+
-
+
-
+
-
+
-
+
-
+
Map Unit Types About IRTH
-
+
Favorites Search - - Brochures - +
Privacy Policy diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 12a6bfc..57b6c8f 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,10 +1,17 @@ -import { NavLink } from "react-router"; +import { Link, NavLink } from "react-router"; import LocationIcon from "./icons/LocationIcon"; import clsx from "clsx"; -import ArrowDownIcon from "./icons/ArrowDownIcon"; import { useState } from "react"; import Button from "./ui/Button"; import BurgerIcon from "./icons/BurgerIcon"; +import { AnimatePresence, motion } from "motion/react"; +import DownloadIcon from "./icons/DownloadIcon"; +import { useClickAway } from "@uidotdev/usehooks"; +import CloseIcon from "./icons/CloseIcon"; +import { projects } from "../data/projects"; +import useModalStore from "../stores/useModalStore"; +import PrivacyPolicyModal from "./modals/PrivacyPolicyModal"; +import ChevronDownIcon from "./icons/ChevronDownIcon"; function Header() { function handleLogoClick() { @@ -12,10 +19,10 @@ function Header() { } return ( -
+
Dubai

- +
@@ -41,28 +48,130 @@ function Header() { export default Header; -function NavBar() { +function Menu() { + const [opened, setOpened] = useState(false); + + const { setModal } = useModalStore(); + return ( -
- - -
+ <> +
+ + +
+ + {opened && ( + +
+

Projects

+
+ {projects.map(({ img, title }, index) => ( + + {title} + {title} + + ))} + + Show on Map + +
+
+
+ + + + +
+
+
+

Brochures

+
+
+

Rove Home Marasi Drive

+
+ {[ + "Rove Main Brochure", + "Rove Amenties Brochure", + "Rove Technical Brochure", + ].map((title) => ( + + ))} +
+
+
+

Rove Home Downtown

+
+ {[ + "Rove Main Brochure", + "Rove Amenties Brochure", + "Rove Technical Brochure", + ].map((title) => ( + + ))} +
+
+
+
+
+

+ For more information, visit our website:{" "} + + www.irth.ae + +

+ +
+
+ )} +
+ ); } @@ -72,8 +181,8 @@ function NavItem({ href, title }: { href: string; title: string }) { to={href} className={({ isActive }) => clsx( - "text-[0.972vw] 2xl:px-[1.25vw] 2xl:py-[0.903vw] px-4.5 py-[13px] 2xl:rounded-[0.833vw] rounded-xl transition-colors duration-300 !leading-none", - isActive && "bg-[#00BED7] text-[#FFFFFF]" + "text-btn-m 2xl:px-[1.25vw] 2xl:py-[0.903vw] p-4 2xl:rounded-[0.833vw] rounded-xl transition-colors duration-300 !leading-none max-2xl:text-center max-2xl:bg-[#F3F3F2]", + isActive && "2xl:bg-[#00BED7] 2xl:text-[#FFFFFF]" ) } > @@ -84,7 +193,10 @@ function NavItem({ href, title }: { href: string; title: string }) { function ProfileBar() { return ( - ); @@ -93,20 +205,78 @@ function ProfileBar() { function BrochuresDropdown() { const [opened, setOpened] = useState(false); + const ref = useClickAway(() => setOpened(false)); + return ( - + Brochures + + + + + + {opened && ( + +
+

Rove Home Marasi Drive

+
+ {[ + "Rove Main Brochure", + "Rove Amenties Brochure", + "Rove Technical Brochure", + ].map((title) => ( + + ))} +
+
+
+

Rove Home Downtown

+
+ {[ + "Rove Main Brochure", + "Rove Amenties Brochure", + "Rove Technical Brochure", + ].map((title) => ( + + ))} +
+
+
+ )} +
+
+ ); +} + +export function BrochureButton({ title }: { title: string }) { + return ( + ); } diff --git a/src/components/Map.tsx b/src/components/Map.tsx index f05e8ff..0483620 100644 --- a/src/components/Map.tsx +++ b/src/components/Map.tsx @@ -15,7 +15,6 @@ import PrivacyPolicyButton from "./PrivacyPolicyButton"; import { getWeather } from "../api/weather"; import { isMobile } from "react-device-detect"; import SelectedComplexCard from "./SelectedComplexCard"; -import FullScreenButton from "./FullScreenButton"; import useWindowSize from "../hooks/useWindowSize"; import TouchIcon from "./icons/map/TouchIcon"; @@ -505,23 +504,23 @@ function Map({ maxZoom = 1 }: MapProps) { }); }, []); - const [isFullScreen, setIsFullScreen] = useState(false); + // const [isFullScreen, setIsFullScreen] = useState(false); - function handleFullScreenClick() { - if (!containerRef.current) return; + // function handleFullScreenClick() { + // if (!containerRef.current) return; - const { width, height } = containerRef.current.getBoundingClientRect(); + // const { width, height } = containerRef.current.getBoundingClientRect(); - containerSizeRef.current = { width, height }; + // containerSizeRef.current = { width, height }; - const newMinZoom = calculateMinZoom({ width, height }, originalSize); - minZoomRef.current = newMinZoom; + // const newMinZoom = calculateMinZoom({ width, height }, originalSize); + // minZoomRef.current = newMinZoom; - setZoom(newMinZoom); - setIsFullScreen((prev) => !prev); - if (isFullScreen) document.exitFullscreen(); - else containerRef.current.requestFullscreen(); - } + // setZoom(newMinZoom); + // setIsFullScreen((prev) => !prev); + // if (isFullScreen) document.exitFullscreen(); + // else containerRef.current.requestFullscreen(); + // } const cloudAnimationRef = useRef(null); const [cloudOffset, setCloudOffset] = useState(0); @@ -576,7 +575,7 @@ function Map({ maxZoom = 1 }: MapProps) { {containerRef.current?.clientWidth && ( map
-

Tap to move

+

Tap to move

) : ( @@ -682,16 +681,18 @@ function Map({ maxZoom = 1 }: MapProps) {
-

Zoom and Move to select a location

+

Zoom and Move to select a location

))} - + {/*
+ +
*/}
diff --git a/src/components/ProjectSelect.tsx b/src/components/ProjectSelect.tsx index e809022..55a8416 100644 --- a/src/components/ProjectSelect.tsx +++ b/src/components/ProjectSelect.tsx @@ -4,16 +4,20 @@ import { useEffect, useState } from "react"; import Project from "../types/Project"; import Select from "./ui/Select"; -function ProjectSelect({ +function ProjectSelect({ projects, onSelect, defaultProject, + withAll, }: { projects: Project[]; - onSelect: (project: Project) => void; - defaultProject: Project; + onSelect: (project: Project | null) => void; + defaultProject: T extends false ? Project : null; + withAll?: T; }) { - const [selectedProject, setSelectedProject] = useState(defaultProject); + const [selectedProject, setSelectedProject] = useState( + defaultProject + ); useEffect(() => setSelectedProject(defaultProject), [defaultProject]); @@ -22,12 +26,23 @@ function ProjectSelect({ return ( <>
+ {withAll && ( +
setSelectedProject(null)} + > + All Projects +
+ )} {projects.map((project) => (
{project.title} @@ -49,7 +66,6 @@ function ProjectSelect({
))}
- -
-
- {inModal ? ( - - ) : ( - - {count && ( - - {count} Apartments found - - )} - - )} - - -
- - + title === project)! + } + /> + + )} + +
+
+
+
+ + {unitTypesInFilters && ( + +

Apartment type

+ +
)} +
+
+ + {costInFilters && ( + + + + )} + + + {floorInFilters && ( + + + + )} + + + {areaInFilters && ( + + + + )} + + + {viewsInFilters && ( + + {}} - defaultOption='All' - className='w-[22.778vw]' + options={Object.keys(SORT_OPTIONS)} + defaultOption={sort} + onSelect={(opt) => setSort(opt as keyof typeof SORT_OPTIONS)} + className="2xl:w-[22.778vw] md:max-2xl:max-w-[45.833vw]" /> -
-
- {favoriteUnits.map((unit) => ( - - ))} +
+ {favoriteUnits + .filter( + (unit) => !selectedProject || unit.project === selectedProject + ) + .map((unit) => ( + + ))}
diff --git a/src/pages/SearchPage.tsx b/src/pages/SearchPage.tsx index 4d3529a..8d55eb9 100644 --- a/src/pages/SearchPage.tsx +++ b/src/pages/SearchPage.tsx @@ -12,13 +12,8 @@ import clsx from "clsx"; import { AnimatePresence, motion } from "motion/react"; import { useDebounce } from "../hooks/useDebounce"; import Select from "../components/ui/Select"; - -const SORT_OPTIONS = { - "Sort by ascending price": "cost asc", - "Sort by descending price": "cost desc", - "Sort by ascending area": "sqft asc", - "Sort by descending area": "sqft desc", -}; +import Skeleton from "react-loading-skeleton"; +import { SORT_OPTIONS } from "../data/sortOptions"; const STEP = 12; @@ -43,7 +38,7 @@ function SearchPage() { "Sort by ascending price" ); - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + const { data, fetchNextPage, isLoading, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ initialPageParam: 0, queryKey: [ @@ -139,36 +134,19 @@ function SearchPage() { }; }, []); - // const [activeFiltersCount, setActiveFiltersCount] = useState(0); + const [activeFiltersCount, setActiveFiltersCount] = useState(0); - // const queryClient = useQueryClient(); - - // useEffect(() => { - // const filters = queryClient.getQueryData(["filters", project]); - // if (filters) { - // setActiveFiltersCount( - // +!!view + - // +!!unitTypes.length + - // +(debouncedCost[0] !== filters.minCost) + - // +(debouncedCost[1] !== filters.maxCost) + - // +(debouncedArea[0] !== filters.minArea) + - // +(debouncedArea[1] !== filters.maxArea) + - // +(debouncedFloor[0] !== filters.minFloor) + - // +(debouncedFloor[1] !== filters.maxFloor) - // ); - // } - // }, [ - // queryClient, - // unitTypes, - // view, - // project, - // debouncedCost, - // cost, - // debouncedArea, - // area, - // debouncedFloor, - // floor, - // ]); + useEffect( + () => + setActiveFiltersCount( + +searchParams.has("view") + + +searchParams.has("unitTypes") + + +searchParams.has("cost") + + +searchParams.has("floor") + + +searchParams.has("area") + ), + [searchParams] + ); return ( <> @@ -187,7 +165,18 @@ function SearchPage() { />
- {project && + {isLoading ? ( +
+ {Array.from({ length: STEP }).map((_, i) => ( + + ))} +
+ ) : ( + project && unitTypes && debouncedCost && debouncedArea && @@ -211,8 +200,17 @@ function SearchPage() { {data?.pages.map((page) => page.map((unit) => ) )} + {isFetchingNextPage && + Array.from({ length: STEP }).map((_, i) => ( + + ))} - )} + ) + )}
{showButtons && ( @@ -229,11 +227,11 @@ function SearchPage() { Filters - {/* {!!activeFiltersCount && ( + {!!activeFiltersCount && (
{activeFiltersCount}
- )} */} + )}