Fix: created hook for useScrollToTop to reuse functionallity and util for getSortedHotels

This commit is contained in:
Pontus Dreij
2024-12-12 14:37:41 +01:00
parent 50fc8a183c
commit c2b4d8abcf
4 changed files with 85 additions and 80 deletions

View File

@@ -1,6 +1,6 @@
"use client"
import { useSearchParams } from "next/navigation"
import { useEffect, useMemo, useState } from "react"
import { useEffect, useMemo } from "react"
import { useIntl } from "react-intl"
import { useHotelFilterStore } from "@/stores/hotel-filters"
@@ -8,18 +8,18 @@ import { useHotelsMapStore } from "@/stores/hotels-map"
import Alert from "@/components/TempDesignSystem/Alert"
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
import { useScrollToTop } from "@/hooks/useScrollToTop"
import HotelCard from "../HotelCard"
import { DEFAULT_SORT } from "../SelectHotel/HotelSorter"
import { getSortedHotels } from "./utils"
import styles from "./hotelCardListing.module.css"
import {
type HotelCardListingProps,
HotelCardListingTypeEnum,
type HotelData,
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
import { AlertTypeEnum } from "@/types/enums/alert"
export default function HotelCardListing({
@@ -29,82 +29,36 @@ export default function HotelCardListing({
const searchParams = useSearchParams()
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
const intl = useIntl()
const { activeHotelCard } = useHotelsMapStore()
const { showBackToTop, scrollToTop } = useScrollToTop({ threshold: 490 })
const sortBy = useMemo(
() => searchParams.get("sort") ?? DEFAULT_SORT,
[searchParams]
)
const sortedHotels = useMemo(() => {
switch (sortBy) {
case SortOrder.Name:
return [...hotelData].sort((a, b) =>
a.hotelData.name.localeCompare(b.hotelData.name)
)
case SortOrder.TripAdvisorRating:
return [...hotelData].sort(
(a, b) =>
(b.hotelData.ratings?.tripAdvisor.rating ?? 0) -
(a.hotelData.ratings?.tripAdvisor.rating ?? 0)
)
case SortOrder.Price:
const getPricePerNight = (hotel: HotelData): number => {
return (
hotel.price?.member?.localPrice?.pricePerNight ??
hotel.price?.public?.localPrice?.pricePerNight ??
Infinity
)
}
return [...hotelData].sort(
(a, b) => getPricePerNight(a) - getPricePerNight(b)
)
case SortOrder.Distance:
default:
return [...hotelData].sort(
(a, b) =>
a.hotelData.location.distanceToCentre -
b.hotelData.location.distanceToCentre
)
}
}, [hotelData, sortBy])
const sortedHotels = useMemo(
() => getSortedHotels({ hotels: hotelData, sortBy }),
[hotelData, sortBy]
)
const hotels = useMemo(() => {
if (activeFilters.length === 0) {
return sortedHotels
}
if (activeFilters.length === 0) return sortedHotels
const filteredHotels = sortedHotels.filter((hotel) =>
return sortedHotels.filter((hotel) =>
activeFilters.every((appliedFilterId) =>
hotel.hotelData.detailedFacilities.some(
(facility) => facility.id.toString() === appliedFilterId
)
)
)
return filteredHotels
}, [activeFilters, sortedHotels])
useEffect(() => {
const handleScroll = () => {
const hasScrolledPast = window.scrollY > 490
setShowBackToTop(hasScrolledPast)
}
window.addEventListener("scroll", handleScroll, { passive: true })
return () => window.removeEventListener("scroll", handleScroll)
}, [])
useEffect(() => {
setResultCount(hotels ? hotels.length : 0)
setResultCount(hotels?.length ?? 0)
}, [hotels, setResultCount])
function scrollToTop() {
window.scrollTo({ top: 0, behavior: "smooth" })
}
return (
<section className={styles.hotelCards}>
{hotels?.length ? (

View File

@@ -0,0 +1,35 @@
import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
export function getSortedHotels({
hotels,
sortBy,
}: {
hotels: HotelData[]
sortBy: string
}) {
const getPricePerNight = (hotel: HotelData): number =>
hotel.price?.member?.localPrice?.pricePerNight ??
hotel.price?.public?.localPrice?.pricePerNight ??
Infinity
const sortingStrategies: Record<
string,
(a: HotelData, b: HotelData) => number
> = {
[SortOrder.Name]: (a: HotelData, b: HotelData) =>
a.hotelData.name.localeCompare(b.hotelData.name),
[SortOrder.TripAdvisorRating]: (a: HotelData, b: HotelData) =>
(b.hotelData.ratings?.tripAdvisor.rating ?? 0) -
(a.hotelData.ratings?.tripAdvisor.rating ?? 0),
[SortOrder.Price]: (a: HotelData, b: HotelData) =>
getPricePerNight(a) - getPricePerNight(b),
[SortOrder.Distance]: (a: HotelData, b: HotelData) =>
a.hotelData.location.distanceToCentre -
b.hotelData.location.distanceToCentre,
}
return [...hotels].sort(
sortingStrategies[sortBy] ?? sortingStrategies[SortOrder.Distance]
)
}

View File

@@ -15,6 +15,7 @@ import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import useLang from "@/hooks/useLang"
import { useScrollToTop } from "@/hooks/useScrollToTop"
import { debounce } from "@/utils/debounce"
import FilterAndSortModal from "../../FilterAndSortModal"
@@ -41,13 +42,17 @@ export default function SelectHotelContent({
const isAboveMobile = useMediaQuery("(min-width: 768px)")
const [visibleHotels, setVisibleHotels] = useState<HotelData[]>([])
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
const [showSkeleton, setShowSkeleton] = useState<boolean>(false)
const listingContainerRef = useRef<HTMLDivElement | null>(null)
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
const { activeHotelCard, activeHotelPin } = useHotelsMapStore()
const { showBackToTop, scrollToTop } = useScrollToTop({
threshold: 490,
elementRef: listingContainerRef,
})
const coordinates = useMemo(
() =>
isAboveMobile
@@ -66,28 +71,6 @@ export default function SelectHotelContent({
}
}, [activeHotelCard, activeHotelPin])
useEffect(() => {
const hotelListingElement = document.querySelector(
`.${styles.listingContainer}`
)
if (!hotelListingElement) return
const handleScroll = () => {
const hasScrolledPast = hotelListingElement.scrollTop > 490
setShowBackToTop(hasScrolledPast)
}
hotelListingElement.addEventListener("scroll", handleScroll)
return () => hotelListingElement.removeEventListener("scroll", handleScroll)
}, [])
function scrollToTop() {
const hotelListingElement = document.querySelector(
`.${styles.listingContainer}`
)
hotelListingElement?.scrollTo({ top: 0, behavior: "smooth" })
}
const filteredHotelPins = useMemo(
() =>
hotelPins.filter((hotel) =>

33
hooks/useScrollToTop.ts Normal file
View File

@@ -0,0 +1,33 @@
import { type RefObject,useEffect,useState } from "react"
interface UseScrollToTopProps {
threshold: number
elementRef?: RefObject<HTMLElement>
}
export function useScrollToTop({ threshold, elementRef }: UseScrollToTopProps) {
const [showBackToTop, setShowBackToTop] = useState(false)
useEffect(() => {
const element = elementRef?.current ?? window
function handleScroll() {
const scrollTop = elementRef?.current
? elementRef.current.scrollTop
: window.scrollY
setShowBackToTop(scrollTop > threshold)
}
element.addEventListener("scroll", handleScroll, { passive: true })
return () => element.removeEventListener("scroll", handleScroll)
}, [threshold, elementRef])
function scrollToTop() {
if (elementRef?.current)
elementRef.current.scrollTo({ top: 0, behavior: "smooth" })
else window.scrollTo({ top: 0, behavior: "smooth" })
}
return { showBackToTop, scrollToTop }
}