diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/Cards.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/Cards.tsx index e2977b2d5..c5c8afa65 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/Cards.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/Cards.tsx @@ -1,64 +1,38 @@ "use client" -import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner" -import { trpc } from "@scandic-hotels/trpc/client" - -import useLang from "@/hooks/useLang" +import { useState } from "react" import ListContainer from "../ListContainer" -import ShowMoreButton from "../ShowMoreButton" import { Card } from "./Card" +import { INITIAL_STAYS_FETCH_LIMIT } from "./data" +import { PreviousStaysSidePeek } from "./PreviousStaysSidePeek" +import { SeeAllCard } from "./SeeAllCard" import styles from "./cards.module.css" -import type { - PreviousStaysClientProps, - PreviousStaysNonNullResponseObject, -} from "@/types/components/myPages/stays/previous" +import type { PreviousStaysClientProps } from "@/types/components/myPages/stays/previous" + +const MAX_VISIBLE_STAYS = 5 export function Cards({ initialPreviousStays }: PreviousStaysClientProps) { - const lang = useLang() - const { data, isFetching, fetchNextPage, hasNextPage, isLoading } = - trpc.user.stays.previous.useInfiniteQuery( - { - limit: 6, - lang, - }, - { - getNextPageParam: (lastPage) => { - return lastPage?.nextCursor - }, - initialData: { - pageParams: [undefined, 1], - pages: [initialPreviousStays], - }, - } - ) + const [isSidePeekOpen, setIsSidePeekOpen] = useState(false) - if (isLoading) { - return - } - - function loadMoreData() { - if (hasNextPage) { - fetchNextPage() - } - } - - const stays = data.pages - .filter((page): page is PreviousStaysNonNullResponseObject => !!page?.data) - .flatMap((page) => page.data) + const stays = initialPreviousStays.data + const visibleStays = stays.slice(0, MAX_VISIBLE_STAYS) + const hasMoreStays = stays.length >= INITIAL_STAYS_FETCH_LIMIT return (
- {stays.map((stay) => ( + {visibleStays.map((stay) => ( ))} + {hasMoreStays && setIsSidePeekOpen(true)} />}
- {hasNextPage ? ( - - ) : null} + setIsSidePeekOpen(false)} + />
) } diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/PreviousStaysSidePeek/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/PreviousStaysSidePeek/index.tsx new file mode 100644 index 000000000..505a87003 --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/PreviousStaysSidePeek/index.tsx @@ -0,0 +1,102 @@ +"use client" + +import { useIntl } from "react-intl" + +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 { 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 ( + +
+ {isLoading ? ( +
+ +
+ ) : ( + <> + {staysByYear.map(({ year, stays }) => ( +
+
+ + {year} + +
+
+ {stays.map((stay) => ( + + ))} +
+
+ ))} + {hasNextPage && ( + + )} + + )} +
+
+ ) +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/PreviousStaysSidePeek/previousStaysSidePeek.module.css b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/PreviousStaysSidePeek/previousStaysSidePeek.module.css new file mode 100644 index 000000000..f653a0768 --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/PreviousStaysSidePeek/previousStaysSidePeek.module.css @@ -0,0 +1,34 @@ +.content { + display: flex; + flex-direction: column; + gap: var(--Space-x3); +} + +.loadingContainer { + display: flex; + justify-content: center; + align-items: center; + padding: var(--Space-x4); +} + +.yearSection { + display: flex; + flex-direction: column; + gap: var(--Space-x2); +} + +.yearHeader { + background: var(--Surface-Primary-Hover-Accent); + padding: var(--Space-x1) var(--Space-x2); + border-radius: var(--Corner-radius-sm); +} + +.yearText { + color: var(--Text-Heading); +} + +.staysList { + display: flex; + flex-direction: column; + gap: var(--Space-x2); +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SeeAllCard/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SeeAllCard/index.tsx new file mode 100644 index 000000000..8cf590234 --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SeeAllCard/index.tsx @@ -0,0 +1,30 @@ +"use client" + +import { useIntl } from "react-intl" + +import { Button } from "@scandic-hotels/design-system/Button" +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" + +import styles from "./seeAllCard.module.css" + +interface SeeAllCardProps { + onPress: () => void +} + +export function SeeAllCard({ onPress }: SeeAllCardProps) { + const intl = useIntl() + + return ( +
+ +
+ ) +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SeeAllCard/seeAllCard.module.css b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SeeAllCard/seeAllCard.module.css new file mode 100644 index 000000000..225a87f0c --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/SeeAllCard/seeAllCard.module.css @@ -0,0 +1,12 @@ +.card { + border-radius: var(--Corner-radius-lg); + border: 1px solid var(--Border-Default); + background: var(--Surface-Secondary-Default); + display: flex; + padding: var(--Space-x15); + align-items: center; + justify-content: center; + align-self: stretch; + height: 100%; + min-height: 134px; +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/data.ts b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/data.ts new file mode 100644 index 000000000..b084ffdfb --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/data.ts @@ -0,0 +1 @@ +export const INITIAL_STAYS_FETCH_LIMIT = 6 diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/index.tsx index eb15fd2a4..4dc692448 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/index.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/index.tsx @@ -3,11 +3,12 @@ import { serverClient } from "@/lib/trpc/server" import ClaimPoints from "@/components/Blocks/DynamicContent/Points/ClaimPoints" import { Section } from "@/components/Section" -import SectionHeader from "@/components/Section/Header/Deprecated" +import { SectionHeader } from "@/components/Section/Header" import SectionLink from "@/components/Section/Link" import { Cards } from "./Cards" import { ClientPreviousStays } from "./Client" +import { INITIAL_STAYS_FETCH_LIMIT } from "./data" import styles from "./previous.module.css" @@ -19,7 +20,7 @@ export default async function PreviousStays({ }: AccountPageComponentProps) { const caller = await serverClient() const initialPreviousStays = await caller.user.stays.previous({ - limit: 6, + limit: INITIAL_STAYS_FETCH_LIMIT, }) if (!initialPreviousStays?.data.length) { @@ -31,7 +32,7 @@ export default async function PreviousStays({ return (
- +
diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/utils/groupStaysByYear.ts b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/utils/groupStaysByYear.ts new file mode 100644 index 000000000..9c5107f85 --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/utils/groupStaysByYear.ts @@ -0,0 +1,29 @@ +import { dt } from "@scandic-hotels/common/dt" + +import type { Stay } from "@scandic-hotels/trpc/routers/user/output" + +export interface StaysByYear { + year: number + stays: Stay[] +} + +/** + * Groups stays by year based on checkinDate. + * @returns an array sorted by year in descending order (most recent first). + */ +export function groupStaysByYear(stays: Stay[]): StaysByYear[] { + const groupedMap = new Map() + + for (const stay of stays) { + const year = dt(stay.attributes.checkinDate).year() + + if (!groupedMap.has(year)) { + groupedMap.set(year, []) + } + groupedMap.get(year)!.push(stay) + } + + return Array.from(groupedMap.entries()) + .sort(([yearA], [yearB]) => yearB - yearA) + .map(([year, stays]) => ({ year, stays })) +}