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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user