Feat/LOY-495 Scroll to Top Functionality in Previous Stays Sidepeek * feat(LOY-495): enable scroll to top functionality for past stays sidepeek Approved-by: Emma Zettervall Approved-by: Matilda Landström
135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { useScrollToTop } from "@scandic-hotels/common/hooks/useScrollToTop"
|
|
import { BackToTopButton } from "@scandic-hotels/design-system/BackToTopButton"
|
|
import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
|
|
import SidePeekSelfControlled from "@scandic-hotels/design-system/SidePeekSelfControlled"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
import { trpc } from "@scandic-hotels/trpc/client"
|
|
|
|
import useLang from "@/hooks/useLang"
|
|
|
|
import ShowMoreButton from "../../ShowMoreButton"
|
|
import { Card } from "../Card"
|
|
import { groupStaysByYear } from "../utils/groupStaysByYear"
|
|
|
|
import styles from "./previousStaysSidePeek.module.css"
|
|
|
|
import type { PreviousStaysNonNullResponseObject } from "@/types/components/myPages/stays/previous"
|
|
|
|
interface PreviousStaysSidePeekProps {
|
|
isOpen: boolean
|
|
onClose: () => void
|
|
}
|
|
|
|
export function PreviousStaysSidePeek({
|
|
isOpen,
|
|
onClose,
|
|
}: PreviousStaysSidePeekProps) {
|
|
const intl = useIntl()
|
|
const lang = useLang()
|
|
|
|
const [scrollContainer, setScrollContainer] = useState<HTMLElement | null>(
|
|
null
|
|
)
|
|
|
|
const scrollContainerRef = (node: HTMLDivElement | null) => {
|
|
const parent = node?.parentElement
|
|
// SidePeekSelfControlled renders children twice: in the modal & in an sr-only SEO wrapper.
|
|
// We filter out the SEO wrapper to get the actual scrollable container.
|
|
if (parent && !parent.classList.contains("sr-only")) {
|
|
setScrollContainer(parent)
|
|
}
|
|
}
|
|
|
|
const { showBackToTop, scrollToTop } = useScrollToTop({
|
|
threshold: 200,
|
|
element: scrollContainer,
|
|
refScrollable: true,
|
|
})
|
|
|
|
const { data, isFetching, fetchNextPage, hasNextPage, isLoading } =
|
|
trpc.user.stays.previous.useInfiniteQuery(
|
|
{
|
|
limit: 10,
|
|
lang,
|
|
},
|
|
{
|
|
getNextPageParam: (lastPage) => {
|
|
return lastPage?.nextCursor
|
|
},
|
|
enabled: isOpen,
|
|
}
|
|
)
|
|
|
|
function loadMoreData() {
|
|
if (hasNextPage) {
|
|
fetchNextPage()
|
|
}
|
|
}
|
|
|
|
const stays = data?.pages
|
|
.filter((page): page is PreviousStaysNonNullResponseObject => !!page?.data)
|
|
.flatMap((page) => page.data)
|
|
|
|
const staysByYear = stays ? groupStaysByYear(stays) : []
|
|
|
|
return (
|
|
<SidePeekSelfControlled
|
|
title={intl.formatMessage({
|
|
id: "stays.previous.title",
|
|
defaultMessage: "Previous stays",
|
|
})}
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
>
|
|
<div ref={scrollContainerRef} className={styles.content}>
|
|
{isLoading ? (
|
|
<div className={styles.loadingContainer}>
|
|
<LoadingSpinner />
|
|
</div>
|
|
) : (
|
|
<>
|
|
{staysByYear.map(({ year, stays }) => (
|
|
<section key={year} className={styles.yearSection}>
|
|
<div className={styles.yearHeader}>
|
|
<Typography variant="Title/Overline/sm">
|
|
<span className={styles.yearText}>{year}</span>
|
|
</Typography>
|
|
</div>
|
|
<div className={styles.staysList}>
|
|
{stays.map((stay) => (
|
|
<Card
|
|
key={stay.attributes.confirmationNumber}
|
|
stay={stay}
|
|
/>
|
|
))}
|
|
</div>
|
|
</section>
|
|
))}
|
|
{hasNextPage && (
|
|
<ShowMoreButton
|
|
disabled={isFetching}
|
|
loadMoreData={loadMoreData}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
{showBackToTop && !isLoading && (
|
|
<BackToTopButton
|
|
label={intl.formatMessage({
|
|
id: "common.backToTop",
|
|
defaultMessage: "Back to top",
|
|
})}
|
|
onPress={scrollToTop}
|
|
position="right"
|
|
/>
|
|
)}
|
|
</div>
|
|
</SidePeekSelfControlled>
|
|
)
|
|
}
|