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() {
|
"use client"
|
||||||
return <div>Add Ancillaries</div>
|
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"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
|
|
||||||
@@ -14,13 +18,21 @@ import styles from "./myStay.module.css"
|
|||||||
|
|
||||||
export async function MyStay({ reservationId }: { reservationId: string }) {
|
export async function MyStay({ reservationId }: { reservationId: string }) {
|
||||||
const { booking, hotel, room } = await getBookingConfirmation(reservationId)
|
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 (
|
return (
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<Header booking={booking} hotel={hotel} />
|
<Header booking={booking} hotel={hotel} />
|
||||||
<BookingActions />
|
<BookingActions />
|
||||||
{room && <Rooms booking={booking} mainRoom={room} />}
|
{room && <Rooms booking={booking} mainRoom={room} />}
|
||||||
<Ancillaries />
|
{booking.showAncillaries && (
|
||||||
|
<Ancillaries ancillaries={ancillaryPackages} />
|
||||||
|
)}
|
||||||
<Divider color="primaryLightSubtle" />
|
<Divider color="primaryLightSubtle" />
|
||||||
<PaymentDetails booking={booking} />
|
<PaymentDetails booking={booking} />
|
||||||
<Divider color="primaryLightSubtle" />
|
<Divider color="primaryLightSubtle" />
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
.ancillaryCard {
|
.ancillaryCard {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-width: 280px;
|
min-width: 251px;
|
||||||
|
max-width: 286px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.imageContainer {
|
.imageContainer {
|
||||||
|
|||||||
@@ -584,6 +584,7 @@
|
|||||||
"U-shape": "U-form",
|
"U-shape": "U-form",
|
||||||
"Unlink accounts": "Unlink accounts",
|
"Unlink accounts": "Unlink accounts",
|
||||||
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
||||||
|
"Upgrade your stay": "Opgrader dit ophold",
|
||||||
"Use Bonus Cheque": "Brug Bonus Cheque",
|
"Use Bonus Cheque": "Brug Bonus Cheque",
|
||||||
"Use bonus cheque": "Brug Bonus Cheque",
|
"Use bonus cheque": "Brug Bonus Cheque",
|
||||||
"Use code/voucher": "Brug kode/voucher",
|
"Use code/voucher": "Brug kode/voucher",
|
||||||
@@ -593,6 +594,7 @@
|
|||||||
"VAT {vat}%": "Moms {vat}%",
|
"VAT {vat}%": "Moms {vat}%",
|
||||||
"Valid through {expirationDate}": "Gyldig til og med {expirationDate}",
|
"Valid through {expirationDate}": "Gyldig til og med {expirationDate}",
|
||||||
"Verification code": "Verification code",
|
"Verification code": "Verification code",
|
||||||
|
"View all": "Vis alle",
|
||||||
"View all hotels in {country}": "Se alle hoteller i {country}",
|
"View all hotels in {country}": "Se alle hoteller i {country}",
|
||||||
"View and buy add-ons": "View and buy add-ons",
|
"View and buy add-ons": "View and buy add-ons",
|
||||||
"View as list": "Vis som liste",
|
"View as list": "Vis som liste",
|
||||||
|
|||||||
@@ -584,6 +584,7 @@
|
|||||||
"U-shape": "U-shape",
|
"U-shape": "U-shape",
|
||||||
"Unlink accounts": "Unlink accounts",
|
"Unlink accounts": "Unlink accounts",
|
||||||
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
||||||
|
"Upgrade your stay": "Werten Sie Ihren Aufenthalt auf",
|
||||||
"Use Bonus Cheque": "Bonusscheck nutzen",
|
"Use Bonus Cheque": "Bonusscheck nutzen",
|
||||||
"Use bonus cheque": "Bonusscheck nutzen",
|
"Use bonus cheque": "Bonusscheck nutzen",
|
||||||
"Use code/voucher": "Code/Gutschein nutzen",
|
"Use code/voucher": "Code/Gutschein nutzen",
|
||||||
@@ -593,6 +594,7 @@
|
|||||||
"VAT {vat}%": "MwSt. {vat}%",
|
"VAT {vat}%": "MwSt. {vat}%",
|
||||||
"Valid through {expirationDate}": "Gültig bis {expirationDate}",
|
"Valid through {expirationDate}": "Gültig bis {expirationDate}",
|
||||||
"Verification code": "Verification code",
|
"Verification code": "Verification code",
|
||||||
|
"View all": "Alle anzeigen",
|
||||||
"View all hotels in {country}": "Alle Hotels in {country} anzeigen",
|
"View all hotels in {country}": "Alle Hotels in {country} anzeigen",
|
||||||
"View and buy add-ons": "View and buy add-ons",
|
"View and buy add-ons": "View and buy add-ons",
|
||||||
"View as list": "Als Liste anzeigen",
|
"View as list": "Als Liste anzeigen",
|
||||||
|
|||||||
@@ -589,6 +589,7 @@
|
|||||||
"U-shape": "U-shape",
|
"U-shape": "U-shape",
|
||||||
"Unlink accounts": "Unlink accounts",
|
"Unlink accounts": "Unlink accounts",
|
||||||
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
||||||
|
"Upgrade your stay": "Upgrade your stay",
|
||||||
"Use Bonus Cheque": "Use Bonus Cheque",
|
"Use Bonus Cheque": "Use Bonus Cheque",
|
||||||
"Use bonus cheque": "Use bonus cheque",
|
"Use bonus cheque": "Use bonus cheque",
|
||||||
"Use code/voucher": "Use code/voucher",
|
"Use code/voucher": "Use code/voucher",
|
||||||
@@ -598,6 +599,7 @@
|
|||||||
"VAT {vat}%": "VAT {vat}%",
|
"VAT {vat}%": "VAT {vat}%",
|
||||||
"Valid through {expirationDate}": "Valid through {expirationDate}",
|
"Valid through {expirationDate}": "Valid through {expirationDate}",
|
||||||
"Verification code": "Verification code",
|
"Verification code": "Verification code",
|
||||||
|
"View all": "View all",
|
||||||
"View all hotels in {country}": "View all hotels in {country}",
|
"View all hotels in {country}": "View all hotels in {country}",
|
||||||
"View and buy add-ons": "View and buy add-ons",
|
"View and buy add-ons": "View and buy add-ons",
|
||||||
"View as list": "View as list",
|
"View as list": "View as list",
|
||||||
|
|||||||
@@ -584,6 +584,7 @@
|
|||||||
"U-shape": "U-muoto",
|
"U-shape": "U-muoto",
|
||||||
"Unlink accounts": "Unlink accounts",
|
"Unlink accounts": "Unlink accounts",
|
||||||
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
||||||
|
"Upgrade your stay": "Päivitä oleskelusi",
|
||||||
"Use Bonus Cheque": "Käytä bonussekkiä",
|
"Use Bonus Cheque": "Käytä bonussekkiä",
|
||||||
"Use bonus cheque": "Käytä bonussekkiä",
|
"Use bonus cheque": "Käytä bonussekkiä",
|
||||||
"Use code/voucher": "Käytä koodia/voucheria",
|
"Use code/voucher": "Käytä koodia/voucheria",
|
||||||
@@ -593,6 +594,7 @@
|
|||||||
"VAT {vat}%": "ALV {vat}%",
|
"VAT {vat}%": "ALV {vat}%",
|
||||||
"Valid through {expirationDate}": "Voimassa {expirationDate} asti",
|
"Valid through {expirationDate}": "Voimassa {expirationDate} asti",
|
||||||
"Verification code": "Verification code",
|
"Verification code": "Verification code",
|
||||||
|
"View all": "Näytä kaikki",
|
||||||
"View all hotels in {country}": "Näytä kaikki hotellit maassa {country}",
|
"View all hotels in {country}": "Näytä kaikki hotellit maassa {country}",
|
||||||
"View and buy add-ons": "View and buy add-ons",
|
"View and buy add-ons": "View and buy add-ons",
|
||||||
"View as list": "Näytä listana",
|
"View as list": "Näytä listana",
|
||||||
|
|||||||
@@ -582,6 +582,7 @@
|
|||||||
"U-shape": "U-form",
|
"U-shape": "U-form",
|
||||||
"Unlink accounts": "Unlink accounts",
|
"Unlink accounts": "Unlink accounts",
|
||||||
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
||||||
|
"Upgrade your stay": "Oppgrader oppholdet ditt",
|
||||||
"Use Bonus Cheque": "Bruk bonussjekk",
|
"Use Bonus Cheque": "Bruk bonussjekk",
|
||||||
"Use bonus cheque": "Bruk bonussjekk",
|
"Use bonus cheque": "Bruk bonussjekk",
|
||||||
"Use code/voucher": "Bruk kode/voucher",
|
"Use code/voucher": "Bruk kode/voucher",
|
||||||
@@ -591,6 +592,7 @@
|
|||||||
"VAT {vat}%": "mva {vat}%",
|
"VAT {vat}%": "mva {vat}%",
|
||||||
"Valid through {expirationDate}": "Gyldig til og med {expirationDate}",
|
"Valid through {expirationDate}": "Gyldig til og med {expirationDate}",
|
||||||
"Verification code": "Verification code",
|
"Verification code": "Verification code",
|
||||||
|
"View all": "Vis alle",
|
||||||
"View all hotels in {country}": "Se alle hotellene i {country}",
|
"View all hotels in {country}": "Se alle hotellene i {country}",
|
||||||
"View and buy add-ons": "View and buy add-ons",
|
"View and buy add-ons": "View and buy add-ons",
|
||||||
"View as list": "Vis som liste",
|
"View as list": "Vis som liste",
|
||||||
|
|||||||
@@ -582,6 +582,7 @@
|
|||||||
"U-shape": "U-form",
|
"U-shape": "U-form",
|
||||||
"Unlink accounts": "Unlink accounts",
|
"Unlink accounts": "Unlink accounts",
|
||||||
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
|
||||||
|
"Upgrade your stay": "Uppgradera din vistelse",
|
||||||
"Use Bonus Cheque": "Använd bonuscheck",
|
"Use Bonus Cheque": "Använd bonuscheck",
|
||||||
"Use bonus cheque": "Använd bonuscheck",
|
"Use bonus cheque": "Använd bonuscheck",
|
||||||
"Use code/voucher": "Använd kod/voucher",
|
"Use code/voucher": "Använd kod/voucher",
|
||||||
@@ -591,6 +592,7 @@
|
|||||||
"VAT {vat}%": "Moms {vat}%",
|
"VAT {vat}%": "Moms {vat}%",
|
||||||
"Valid through {expirationDate}": "Gäller till och med {expirationDate}",
|
"Valid through {expirationDate}": "Gäller till och med {expirationDate}",
|
||||||
"Verification code": "Verification code",
|
"Verification code": "Verification code",
|
||||||
|
"View all": "Visa alla",
|
||||||
"View all hotels in {country}": "Visa alla hotell i {country}",
|
"View all hotels in {country}": "Visa alla hotell i {country}",
|
||||||
"View and buy add-ons": "View and buy add-ons",
|
"View and buy add-ons": "View and buy add-ons",
|
||||||
"View as list": "Visa som lista",
|
"View as list": "Visa som lista",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { serverClient } from "../server"
|
|||||||
|
|
||||||
import type { Country } from "@/types/enums/country"
|
import type { Country } from "@/types/enums/country"
|
||||||
import type {
|
import type {
|
||||||
|
AncillaryPackagesInput,
|
||||||
BreackfastPackagesInput,
|
BreackfastPackagesInput,
|
||||||
PackagesInput,
|
PackagesInput,
|
||||||
} from "@/types/requests/packages"
|
} from "@/types/requests/packages"
|
||||||
@@ -136,6 +137,12 @@ export const getBreakfastPackages = cache(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const getAncillaryPackages = cache(
|
||||||
|
async function getMemoizedAncillaryPackages(input: AncillaryPackagesInput) {
|
||||||
|
return serverClient().hotel.packages.ancillary(input)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export const getPackages = cache(async function getMemoizedPackages(
|
export const getPackages = cache(async function getMemoizedPackages(
|
||||||
input: PackagesInput
|
input: PackagesInput
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -167,9 +167,20 @@ export const bookingConfirmationSchema = z
|
|||||||
}),
|
}),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
type: z.literal("booking"),
|
type: z.literal("booking"),
|
||||||
|
links: z.object({
|
||||||
|
addAncillary: z
|
||||||
|
.object({
|
||||||
|
href: z.string(),
|
||||||
|
meta: z.object({
|
||||||
|
method: z.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.transform(({ data }) => ({
|
.transform(({ data }) => ({
|
||||||
...data.attributes,
|
...data.attributes,
|
||||||
extraBedTypes: data.attributes.childBedPreferences,
|
extraBedTypes: data.attributes.childBedPreferences,
|
||||||
|
showAncillaries: !!data.links.addAncillary,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -93,6 +93,15 @@ export const breakfastPackageInputSchema = z.object({
|
|||||||
.pipe(z.coerce.date()),
|
.pipe(z.coerce.date()),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const ancillaryPackageInputSchema = z.object({
|
||||||
|
fromDate: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "fromDate is required" })
|
||||||
|
.pipe(z.coerce.date()),
|
||||||
|
hotelId: z.string().min(1, { message: "hotelId is required" }),
|
||||||
|
toDate: z.string().pipe(z.coerce.date()).optional(),
|
||||||
|
})
|
||||||
|
|
||||||
export const roomPackagesInputSchema = z.object({
|
export const roomPackagesInputSchema = z.object({
|
||||||
hotelId: z.string(),
|
hotelId: z.string(),
|
||||||
startDate: z.string(),
|
startDate: z.string(),
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ export const metrics = {
|
|||||||
fail: meter.createCounter("trpc.package.breakfast-fail"),
|
fail: meter.createCounter("trpc.package.breakfast-fail"),
|
||||||
success: meter.createCounter("trpc.package.breakfast-success"),
|
success: meter.createCounter("trpc.package.breakfast-success"),
|
||||||
},
|
},
|
||||||
|
ancillaryPackage: {
|
||||||
|
counter: meter.createCounter("trpc.package.ancillary"),
|
||||||
|
fail: meter.createCounter("trpc.package.ancillary-fail"),
|
||||||
|
success: meter.createCounter("trpc.package.ancillary-success"),
|
||||||
|
},
|
||||||
hotel: {
|
hotel: {
|
||||||
counter: meter.createCounter("trpc.hotel.get"),
|
counter: meter.createCounter("trpc.hotel.get"),
|
||||||
fail: meter.createCounter("trpc.hotel.get-fail"),
|
fail: meter.createCounter("trpc.hotel.get-fail"),
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ import {
|
|||||||
} from "./schemas/hotel"
|
} from "./schemas/hotel"
|
||||||
import { locationCitySchema } from "./schemas/location/city"
|
import { locationCitySchema } from "./schemas/location/city"
|
||||||
import { locationHotelSchema } from "./schemas/location/hotel"
|
import { locationHotelSchema } from "./schemas/location/hotel"
|
||||||
import { breakfastPackageSchema, packageSchema } from "./schemas/packages"
|
import {
|
||||||
|
ancillaryPackageSchema,
|
||||||
|
breakfastPackageSchema,
|
||||||
|
packageSchema,
|
||||||
|
} from "./schemas/packages"
|
||||||
import { rateSchema } from "./schemas/rate"
|
import { rateSchema } from "./schemas/rate"
|
||||||
import { relationshipsSchema } from "./schemas/relationships"
|
import { relationshipsSchema } from "./schemas/relationships"
|
||||||
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
|
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
|
||||||
@@ -343,6 +347,35 @@ export const breakfastPackagesSchema = z
|
|||||||
data.attributes.packages.filter((pkg) => pkg.code?.match(/^(BRF\d+)$/gm))
|
data.attributes.packages.filter((pkg) => pkg.code?.match(/^(BRF\d+)$/gm))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const ancillaryPackagesSchema = z
|
||||||
|
.object({
|
||||||
|
data: z.object({
|
||||||
|
attributes: z.object({
|
||||||
|
ancillaries: z.array(ancillaryPackageSchema),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.transform(({ data }) =>
|
||||||
|
data.attributes.ancillaries
|
||||||
|
.map((ancillary) => ({
|
||||||
|
categoryName: ancillary.categoryName,
|
||||||
|
ancillaryContent: ancillary.ancillaryContent
|
||||||
|
.filter((item) => item.status === "Available")
|
||||||
|
.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
description: item.descriptions.html,
|
||||||
|
imageUrl: item.images[0].imageSizes.small,
|
||||||
|
price: {
|
||||||
|
total: parseInt(item.variants.ancillary.price.totalPrice),
|
||||||
|
currency: item.variants.ancillary.price.currency,
|
||||||
|
},
|
||||||
|
points: item.variants.ancillaryLoyalty?.points,
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
.filter((ancillary) => ancillary.ancillaryContent.length > 0)
|
||||||
|
)
|
||||||
|
|
||||||
export const packagesSchema = z
|
export const packagesSchema = z
|
||||||
.object({
|
.object({
|
||||||
data: z
|
data: z
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { getVerifiedUser, parsedUser } from "../user/query"
|
|||||||
import { additionalDataSchema } from "./schemas/additionalData"
|
import { additionalDataSchema } from "./schemas/additionalData"
|
||||||
import { meetingRoomsSchema } from "./schemas/meetingRoom"
|
import { meetingRoomsSchema } from "./schemas/meetingRoom"
|
||||||
import {
|
import {
|
||||||
|
ancillaryPackageInputSchema,
|
||||||
breakfastPackageInputSchema,
|
breakfastPackageInputSchema,
|
||||||
cityCoordinatesInputSchema,
|
cityCoordinatesInputSchema,
|
||||||
getAdditionalDataInputSchema,
|
getAdditionalDataInputSchema,
|
||||||
@@ -39,6 +40,7 @@ import {
|
|||||||
} from "./input"
|
} from "./input"
|
||||||
import { metrics } from "./metrics"
|
import { metrics } from "./metrics"
|
||||||
import {
|
import {
|
||||||
|
ancillaryPackagesSchema,
|
||||||
breakfastPackagesSchema,
|
breakfastPackagesSchema,
|
||||||
getNearbyHotelIdsSchema,
|
getNearbyHotelIdsSchema,
|
||||||
hotelsAvailabilitySchema,
|
hotelsAvailabilitySchema,
|
||||||
@@ -1622,7 +1624,7 @@ export const hotelQueryRouter = router({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
console.error(
|
console.error(
|
||||||
"api.hotels.hotelsAvailability error",
|
"api.package.breakfast error",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
query: metricsData,
|
query: metricsData,
|
||||||
error: {
|
error: {
|
||||||
@@ -1683,5 +1685,90 @@ export const hotelQueryRouter = router({
|
|||||||
(pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
(pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
ancillary: safeProtectedServiceProcedure
|
||||||
|
.input(ancillaryPackageInputSchema)
|
||||||
|
.query(async function ({ ctx, input }) {
|
||||||
|
const { lang } = ctx
|
||||||
|
|
||||||
|
const apiLang = toApiLang(lang)
|
||||||
|
const params = {
|
||||||
|
EndDate: dt(input.toDate).format("YYYY-MM-DD"),
|
||||||
|
StartDate: dt(input.fromDate).format("YYYY-MM-DD"),
|
||||||
|
language: apiLang,
|
||||||
|
}
|
||||||
|
|
||||||
|
const metricsData = { ...params, hotelId: input.hotelId }
|
||||||
|
metrics.ancillaryPackage.counter.add(1, metricsData)
|
||||||
|
console.info(
|
||||||
|
"api.package.ancillary start",
|
||||||
|
JSON.stringify({ query: metricsData })
|
||||||
|
)
|
||||||
|
|
||||||
|
const apiResponse = await api.get(
|
||||||
|
api.endpoints.v1.Package.Ancillary.hotel(input.hotelId),
|
||||||
|
{
|
||||||
|
cache: undefined,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${ctx.serviceToken}`,
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
revalidate: 60,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!apiResponse.ok) {
|
||||||
|
const text = await apiResponse.text()
|
||||||
|
metrics.ancillaryPackage.fail.add(1, {
|
||||||
|
...metricsData,
|
||||||
|
error_type: "http_error",
|
||||||
|
error: JSON.stringify({
|
||||||
|
status: apiResponse.status,
|
||||||
|
statusText: apiResponse.statusText,
|
||||||
|
text,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
console.error(
|
||||||
|
"api.package.ancillary start error",
|
||||||
|
JSON.stringify({
|
||||||
|
query: metricsData,
|
||||||
|
error: {
|
||||||
|
status: apiResponse.status,
|
||||||
|
statusText: apiResponse.statusText,
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiJson = await apiResponse.json()
|
||||||
|
const ancillaryPackages = ancillaryPackagesSchema.safeParse(apiJson)
|
||||||
|
if (!ancillaryPackages.success) {
|
||||||
|
metrics.ancillaryPackage.fail.add(1, {
|
||||||
|
...metricsData,
|
||||||
|
error_type: "validation_error",
|
||||||
|
error: JSON.stringify(ancillaryPackages.error),
|
||||||
|
})
|
||||||
|
console.error(
|
||||||
|
"api.package.ancillary validation error",
|
||||||
|
JSON.stringify({
|
||||||
|
query: metricsData,
|
||||||
|
error: ancillaryPackages.error,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics.ancillaryPackage.success.add(1, metricsData)
|
||||||
|
console.info(
|
||||||
|
"api.package.ancillary success",
|
||||||
|
JSON.stringify({
|
||||||
|
query: metricsData,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return ancillaryPackages.data
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { imageSizesSchema } from "./image"
|
||||||
|
|
||||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
import { PackageTypeEnum } from "@/types/enums/packages"
|
import { PackageTypeEnum } from "@/types/enums/packages"
|
||||||
|
|
||||||
@@ -23,6 +25,18 @@ const inventorySchema = z.object({
|
|||||||
available: z.number(),
|
available: z.number(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const ancillaryContentSchema = z.object({
|
||||||
|
status: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
variants: z.object({
|
||||||
|
ancillary: z.object({ price: packagePriceSchema }),
|
||||||
|
ancillaryLoyalty: z.object({ points: z.number() }).optional(),
|
||||||
|
}),
|
||||||
|
title: z.string(),
|
||||||
|
descriptions: z.object({ html: z.string() }),
|
||||||
|
images: z.array(z.object({ imageSizes: imageSizesSchema })),
|
||||||
|
})
|
||||||
|
|
||||||
export const packageSchema = z.object({
|
export const packageSchema = z.object({
|
||||||
code: z.nativeEnum(RoomPackageCodeEnum),
|
code: z.nativeEnum(RoomPackageCodeEnum),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
@@ -39,3 +53,8 @@ export const breakfastPackageSchema = z.object({
|
|||||||
requestedPrice: packagePriceSchema,
|
requestedPrice: packagePriceSchema,
|
||||||
packageType: z.literal(PackageTypeEnum.BreakfastAdult),
|
packageType: z.literal(PackageTypeEnum.BreakfastAdult),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const ancillaryPackageSchema = z.object({
|
||||||
|
categoryName: z.string(),
|
||||||
|
ancillaryContent: z.array(ancillaryContentSchema),
|
||||||
|
})
|
||||||
|
|||||||
19
types/components/myPages/myStay/ancillaries.ts
Normal file
19
types/components/myPages/myStay/ancillaries.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { z } from "zod"
|
||||||
|
|
||||||
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
import type { ancillaryPackagesSchema } from "@/server/routers/hotels/output"
|
||||||
|
|
||||||
|
export type Ancillaries = z.output<typeof ancillaryPackagesSchema>
|
||||||
|
export type Ancillary = Ancillaries[number]
|
||||||
|
|
||||||
|
export interface AncillariesProps {
|
||||||
|
ancillaries: Ancillaries | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AncillaryProps {
|
||||||
|
ancillary: Ancillary["ancillaryContent"][number]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MyStayProps extends BookingConfirmation {
|
||||||
|
ancillaries: Ancillaries | null
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { z } from "zod"
|
import type { z } from "zod"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ancillaryPackageInputSchema,
|
||||||
breakfastPackageInputSchema,
|
breakfastPackageInputSchema,
|
||||||
roomPackagesInputSchema,
|
roomPackagesInputSchema,
|
||||||
} from "@/server/routers/hotels/input"
|
} from "@/server/routers/hotels/input"
|
||||||
@@ -9,6 +10,9 @@ import type { packagesSchema } from "@/server/routers/hotels/output"
|
|||||||
export interface BreackfastPackagesInput
|
export interface BreackfastPackagesInput
|
||||||
extends z.input<typeof breakfastPackageInputSchema> {}
|
extends z.input<typeof breakfastPackageInputSchema> {}
|
||||||
|
|
||||||
|
export interface AncillaryPackagesInput
|
||||||
|
extends z.input<typeof ancillaryPackageInputSchema> {}
|
||||||
|
|
||||||
export interface PackagesInput
|
export interface PackagesInput
|
||||||
extends z.input<typeof roomPackagesInputSchema> {}
|
extends z.input<typeof roomPackagesInputSchema> {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user