feat/SW-1546-list-ancillaries-my-stay (pull request #1259)

Feat/SW-1546 list ancillaries my stay

* feat(SW-1546): foundation for listing ancillaries

* feat(SW-1546): foundation for listing ancillaries

* feat(SW-1546): refactor type

* feat(SW-1546): fix date format

* feat(SW-1546): refactor usestate

* feat(SW-1546): refactor typing

* feat(SW-1546): refactor types

* feat(SW-1546): responsive width on modal

* feat(SW-1546): update design

* feat(SW-1546): rebase master

* feat(SW-1546): show points only if logged in

* feat(SW-1546): always show points

* feat(SW-1546): small fix

* feat(SW-1546): remove spread object

* feat(SW-1546): fix import order


Approved-by: Simon.Emanuelsson
This commit is contained in:
Bianca Widstam
2025-02-14 10:37:17 +00:00
parent f5e0214313
commit 224a41ec74
19 changed files with 420 additions and 7 deletions

View File

@@ -0,0 +1,86 @@
.container {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
}
.title {
display: flex;
justify-content: space-between;
}
.modalContent {
width: 100%;
}
.tabs {
display: flex;
gap: var(--Spacing-x1);
padding: var(--Spacing-x3) 0;
flex-wrap: wrap;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(251px, 1fr));
gap: var(--Spacing-x2);
max-height: 417px;
overflow-y: auto;
padding-right: var(--Spacing-x1);
margin-top: var(--Spacing-x2);
}
.chip {
border-radius: 28px;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
border: none;
cursor: pointer;
background: var(--Base-Surface-Subtle-Normal);
}
.chip.selected {
background: var(--Base-Text-High-contrast);
}
.ancillaries {
display: none;
}
.mobileAncillaries {
display: flex;
gap: var(--Spacing-x2);
overflow: hidden;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
flex-wrap: nowrap;
}
.modal {
display: none;
}
@media screen and (min-width: 768px) {
.modalContent {
width: 600px;
}
}
@media screen and (min-width: 1052px) {
.mobileAncillaries {
display: none;
}
.modalContent {
width: 833px;
}
.ancillaries {
display: grid;
grid-template-columns: repeat(4, minmax(251px, 1fr));
gap: var(--Spacing-x2);
}
.modal {
display: block;
}
}

View File

@@ -1,3 +1,111 @@
export function Ancillaries() {
return <div>Add Ancillaries</div>
"use client"
import { useState } from "react"
import { useIntl } from "react-intl"
import { ChevronRightSmallIcon } from "@/components/Icons"
import Modal from "@/components/Modal"
import { AncillaryCard } from "@/components/TempDesignSystem/AncillaryCard"
import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import styles from "./ancillaries.module.css"
import type {
Ancillaries,
AncillariesProps,
Ancillary,
} from "@/types/components/myPages/myStay/ancillaries"
export function Ancillaries({ ancillaries }: AncillariesProps) {
const intl = useIntl()
const [selectedCategory, setSelectedCategory] = useState<string | null>(
() => {
return ancillaries?.[0]?.categoryName ?? null
}
)
if (!ancillaries?.length) {
return null
}
function mergeAncillaries(
ancillaries: Ancillaries
): Ancillary["ancillaryContent"] {
const uniqueAncillaries = new Map(
ancillaries
.flatMap((category) => category.ancillaryContent)
.map((ancillary) => [ancillary.id, ancillary])
)
return [...uniqueAncillaries.values()]
}
const allAncillaries = mergeAncillaries(ancillaries)
return (
<div className={styles.container}>
<div className={styles.title}>
<Title as="h5">{intl.formatMessage({ id: "Upgrade your stay" })}</Title>
<div className={styles.modal}>
<Modal
trigger={
<Button theme="base" variant="icon" intent="text" size="small">
{intl.formatMessage({ id: "View all" })}
<ChevronRightSmallIcon
width={20}
height={20}
color="baseButtonTextOnFillNormal"
/>
</Button>
}
title={intl.formatMessage({ id: "Upgrade your stay" })}
>
<div className={styles.modalContent}>
<div className={styles.tabs}>
{ancillaries.map((category) => (
<button
key={category.categoryName}
className={`${styles.chip} ${category.categoryName === selectedCategory ? styles.selected : ""}`}
onClick={() => setSelectedCategory(category.categoryName)}
>
<Caption
color={
category.categoryName === selectedCategory
? "pale"
: "baseTextHighContrast"
}
>
{category.categoryName}
</Caption>
</button>
))}
</div>
<div className={styles.grid}>
{ancillaries
.find(
(category) => category.categoryName === selectedCategory
)
?.ancillaryContent.map(({ description, ...ancillary }) => (
<AncillaryCard key={ancillary.id} ancillary={ancillary} />
))}
</div>
</div>
</Modal>
</div>
</div>
<div className={styles.ancillaries}>
{allAncillaries.slice(0, 4).map(({ description, ...ancillary }) => (
<AncillaryCard key={ancillary.id} ancillary={ancillary} />
))}
</div>
<div className={styles.mobileAncillaries}>
{allAncillaries.map(({ description, ...ancillary }) => (
<AncillaryCard key={ancillary.id} ancillary={ancillary} />
))}
</div>
</div>
)
}

View File

@@ -1,4 +1,8 @@
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
import { dt } from "@/lib/dt"
import {
getAncillaryPackages,
getBookingConfirmation,
} from "@/lib/trpc/memoizedRequests"
import Divider from "@/components/TempDesignSystem/Divider"
@@ -14,13 +18,21 @@ import styles from "./myStay.module.css"
export async function MyStay({ reservationId }: { reservationId: string }) {
const { booking, hotel, room } = await getBookingConfirmation(reservationId)
const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD")
const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
const hotelId = hotel.operaId
const ancillaryInput = { fromDate, hotelId, toDate }
void getAncillaryPackages(ancillaryInput)
const ancillaryPackages = await getAncillaryPackages(ancillaryInput)
return (
<main className={styles.main}>
<Header booking={booking} hotel={hotel} />
<BookingActions />
{room && <Rooms booking={booking} mainRoom={room} />}
<Ancillaries />
{booking.showAncillaries && (
<Ancillaries ancillaries={ancillaryPackages} />
)}
<Divider color="primaryLightSubtle" />
<PaymentDetails booking={booking} />
<Divider color="primaryLightSubtle" />