feat: new booking confirmation page
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
|
||||||
|
|
||||||
export default function Loading() {
|
|
||||||
return <LoadingSpinner />
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,42 @@
|
|||||||
.main {
|
.main {
|
||||||
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x5);
|
||||||
|
grid-template-areas: "header" "booking";
|
||||||
|
margin: 0 auto;
|
||||||
|
min-height: 100dvh;
|
||||||
|
padding-top: var(--Spacing-x5);
|
||||||
|
width: var(--max-width-page);
|
||||||
|
}
|
||||||
|
|
||||||
|
.booking {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x5);
|
gap: var(--Spacing-x5);
|
||||||
margin: 0 auto;
|
grid-area: booking;
|
||||||
width: min(calc(100dvw - (var(--Spacing-x3) * 2)), 948px);
|
padding-bottom: var(--Spacing-x9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
.main {
|
||||||
|
grid-template-areas:
|
||||||
|
"header receipt"
|
||||||
|
"booking receipt";
|
||||||
|
grid-template-columns: 1fr 340px;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
padding-top: var(--Spacing-x9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileReceipt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
display: grid;
|
||||||
|
grid-area: receipt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import Details from "@/components/HotelReservation/BookingConfirmation/Details"
|
|
||||||
import Header from "@/components/HotelReservation/BookingConfirmation/Header"
|
import Header from "@/components/HotelReservation/BookingConfirmation/Header"
|
||||||
import TotalPrice from "@/components/HotelReservation/BookingConfirmation/TotalPrice"
|
import HotelDetails from "@/components/HotelReservation/BookingConfirmation/HotelDetails"
|
||||||
|
import PaymentDetails from "@/components/HotelReservation/BookingConfirmation/PaymentDetails"
|
||||||
|
import Promos from "@/components/HotelReservation/BookingConfirmation/Promos"
|
||||||
|
import Receipt from "@/components/HotelReservation/BookingConfirmation/Receipt"
|
||||||
|
import Rooms from "@/components/HotelReservation/BookingConfirmation/Rooms"
|
||||||
|
import SidePanel from "@/components/HotelReservation/SidePanel"
|
||||||
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
@@ -18,10 +26,27 @@ export default async function BookingConfirmationPage({
|
|||||||
const { confirmationNumber } = searchParams
|
const { confirmationNumber } = searchParams
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.main}>
|
<main className={styles.main}>
|
||||||
<Header confirmationNumber={confirmationNumber} />
|
<Suspense fallback={<LoadingSpinner fullPage />}>
|
||||||
<Details confirmationNumber={confirmationNumber} />
|
<Header confirmationNumber={searchParams.confirmationNumber} />
|
||||||
<TotalPrice confirmationNumber={confirmationNumber} />
|
<div className={styles.booking}>
|
||||||
|
<Rooms confirmationNumber={searchParams.confirmationNumber} />
|
||||||
|
<PaymentDetails
|
||||||
|
confirmationNumber={searchParams.confirmationNumber}
|
||||||
|
/>
|
||||||
|
<Divider color="primaryLightSubtle" />
|
||||||
|
<HotelDetails confirmationNumber={searchParams.confirmationNumber} />
|
||||||
|
<Promos />
|
||||||
|
<div className={styles.mobileReceipt}>
|
||||||
|
<Receipt confirmationNumber={searchParams.confirmationNumber} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside className={styles.aside}>
|
||||||
|
<SidePanel variant="receipt">
|
||||||
|
<Receipt confirmationNumber={searchParams.confirmationNumber} />
|
||||||
|
</SidePanel>
|
||||||
|
</aside>
|
||||||
|
</Suspense>
|
||||||
|
</main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
.layout {
|
|
||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
|
||||||
min-height: 100dvh;
|
|
||||||
padding: 80px 0 160px;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { notFound } from "next/navigation"
|
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
|
||||||
|
|
||||||
import styles from "./layout.module.css"
|
|
||||||
|
|
||||||
// route groups needed as layouts have different bgc
|
|
||||||
export default function ConfirmedBookingLayout({
|
|
||||||
children,
|
|
||||||
}: React.PropsWithChildren) {
|
|
||||||
return <div className={styles.layout}>{children}</div>
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
|
||||||
|
|
||||||
export default function Loading() {
|
|
||||||
return <LoadingSpinner fullPage />
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
import { notFound } from "next/navigation"
|
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
|
||||||
|
|
||||||
import styles from "./layout.module.css"
|
import styles from "./layout.module.css"
|
||||||
|
|
||||||
import { LangParams, LayoutArgs } from "@/types/params"
|
import { LangParams, LayoutArgs } from "@/types/params"
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
import { notFound } from "next/navigation"
|
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
|
||||||
|
|
||||||
import styles from "./layout.module.css"
|
import styles from "./layout.module.css"
|
||||||
|
|
||||||
import { LangParams, LayoutArgs } from "@/types/params"
|
import { LangParams, LayoutArgs } from "@/types/params"
|
||||||
@@ -12,9 +8,6 @@ export default function HotelReservationLayout({
|
|||||||
}: React.PropsWithChildren<LayoutArgs<LangParams>> & {
|
}: React.PropsWithChildren<LayoutArgs<LangParams>> & {
|
||||||
sidePeek: React.ReactNode
|
sidePeek: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
if (!env.ENABLE_BOOKING_FLOW) {
|
|
||||||
return notFound()
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.layout}>
|
<div className={styles.layout}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import HistoryStateManager from "@/components/HotelReservation/EnterDetails/Hist
|
|||||||
import Payment from "@/components/HotelReservation/EnterDetails/Payment"
|
import Payment from "@/components/HotelReservation/EnterDetails/Payment"
|
||||||
import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion"
|
import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion"
|
||||||
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
||||||
import Summary from "@/components/HotelReservation/EnterDetails/Summary"
|
import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop"
|
||||||
|
import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile"
|
||||||
import {
|
import {
|
||||||
generateChildrenString,
|
generateChildrenString,
|
||||||
getQueryParamsForEnterDetails,
|
getQueryParamsForEnterDetails,
|
||||||
@@ -140,6 +141,13 @@ export default async function StepPage({
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
const summary = {
|
||||||
|
cancellationText: roomAvailability.cancellationText,
|
||||||
|
isMember: !!user,
|
||||||
|
rateDetails: roomAvailability.rateDetails,
|
||||||
|
roomType: roomAvailability.selectedRoom.roomType,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnterDetailsProvider
|
<EnterDetailsProvider
|
||||||
bedTypes={roomAvailability.bedTypes}
|
bedTypes={roomAvailability.bedTypes}
|
||||||
@@ -220,16 +228,8 @@ export default async function StepPage({
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<aside className={styles.summary}>
|
<aside className={styles.summary}>
|
||||||
<Summary
|
<MobileSummary {...summary} />
|
||||||
adults={adults}
|
<DesktopSummary {...summary} />
|
||||||
fromDate={fromDate}
|
|
||||||
hotelId={hotelId}
|
|
||||||
kids={children}
|
|
||||||
packageCodes={packageCodes}
|
|
||||||
rateCode={rateCode}
|
|
||||||
roomTypeCode={roomTypeCode}
|
|
||||||
toDate={toDate}
|
|
||||||
/>
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
12
app/[lang]/(live)/(public)/hotelreservation/layout.tsx
Normal file
12
app/[lang]/(live)/(public)/hotelreservation/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
|
export default function HotelReservationLayout({
|
||||||
|
children,
|
||||||
|
}: React.PropsWithChildren) {
|
||||||
|
if (!env.ENABLE_BOOKING_FLOW) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
||||||
@@ -104,6 +104,7 @@
|
|||||||
--max-width-text-block: 49.5rem;
|
--max-width-text-block: 49.5rem;
|
||||||
--current-mobile-site-header-height: 70.047px;
|
--current-mobile-site-header-height: 70.047px;
|
||||||
--max-width-navigation: 89.5rem;
|
--max-width-navigation: 89.5rem;
|
||||||
|
|
||||||
--max-width-spacing: calc(var(--Layout-Mobile-Margin-Margin-min) * 2);
|
--max-width-spacing: calc(var(--Layout-Mobile-Margin-Margin-min) * 2);
|
||||||
--max-width-page: min(
|
--max-width-page: min(
|
||||||
calc(100dvw - var(--max-width-spacing)),
|
calc(100dvw - var(--max-width-spacing)),
|
||||||
@@ -156,6 +157,7 @@ ul {
|
|||||||
:root {
|
:root {
|
||||||
--max-width-spacing: calc(var(--Layout-Tablet-Margin-Margin-min) * 2);
|
--max-width-spacing: calc(var(--Layout-Tablet-Margin-Margin-min) * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.overflow-hidden {
|
body.overflow-hidden {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
.details {
|
|
||||||
background-color: var(--Base-Surface-Subtle-Normal);
|
|
||||||
border-radius: var(--Corner-radius-Medium);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--Spacing-x3);
|
|
||||||
grid-area: details;
|
|
||||||
padding: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--Spacing-x-one-and-half);
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listItem {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.details {
|
|
||||||
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { dt } from "@/lib/dt"
|
|
||||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
||||||
import { getIntl } from "@/i18n"
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
|
|
||||||
import styles from "./details.module.css"
|
|
||||||
|
|
||||||
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
|
||||||
|
|
||||||
export default async function Details({
|
|
||||||
confirmationNumber,
|
|
||||||
}: BookingConfirmationProps) {
|
|
||||||
const intl = await getIntl()
|
|
||||||
const lang = getLang()
|
|
||||||
const { booking } = await getBookingConfirmation(confirmationNumber)
|
|
||||||
|
|
||||||
const fromDate = dt(booking.checkInDate).locale(lang)
|
|
||||||
const toDate = dt(booking.checkOutDate).locale(lang)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<article className={styles.details}>
|
|
||||||
<header>
|
|
||||||
<Subtitle color="burgundy" type="two">
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "Reference #{bookingNr}" },
|
|
||||||
{ bookingNr: booking.confirmationNumber }
|
|
||||||
)}
|
|
||||||
</Subtitle>
|
|
||||||
</header>
|
|
||||||
<ul className={styles.list}>
|
|
||||||
<li className={styles.listItem}>
|
|
||||||
<Body>{intl.formatMessage({ id: "Check-in" })}</Body>
|
|
||||||
<Body>
|
|
||||||
{`${fromDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${fromDate.format("HH:mm")}`}
|
|
||||||
</Body>
|
|
||||||
</li>
|
|
||||||
<li className={styles.listItem}>
|
|
||||||
<Body>{intl.formatMessage({ id: "Check-out" })}</Body>
|
|
||||||
<Body>
|
|
||||||
{`${toDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${toDate.format("HH:mm")}`}
|
|
||||||
</Body>
|
|
||||||
</li>
|
|
||||||
<li className={styles.listItem}>
|
|
||||||
<Body>{intl.formatMessage({ id: "Breakfast" })}</Body>
|
|
||||||
<Body>N/A</Body>
|
|
||||||
</li>
|
|
||||||
<li className={styles.listItem}>
|
|
||||||
<Body>{intl.formatMessage({ id: "Cancellation policy" })}</Body>
|
|
||||||
<Body>{booking.rateDefinition.cancellationText}</Body>
|
|
||||||
</li>
|
|
||||||
<li className={styles.listItem}>
|
|
||||||
<Body>{intl.formatMessage({ id: "Rebooking" })}</Body>
|
|
||||||
<Body>N/A</Body>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</article>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
border-radius: var(--Corner-radius-Medium);
|
border-radius: var(--Corner-radius-Medium);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-area: actions;
|
grid-area: actions;
|
||||||
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
@@ -10,6 +11,5 @@
|
|||||||
grid-auto-columns: auto;
|
grid-auto-columns: auto;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-template-columns: auto;
|
grid-template-columns: auto;
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,9 @@
|
|||||||
.body {
|
.body {
|
||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
.header {
|
||||||
|
padding-bottom: var(--Spacing-x4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
.contact,
|
||||||
|
.container,
|
||||||
|
.details,
|
||||||
|
.hotel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
gap: var(--Spacing-x4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact,
|
||||||
|
.hotel {
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.coordinates {
|
||||||
|
margin-top: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
align-self: flex-start;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
padding-left: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import { Toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import styles from "./hotelDetails.module.css"
|
||||||
|
|
||||||
|
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||||
|
|
||||||
|
export default async function HotelDetails({
|
||||||
|
confirmationNumber,
|
||||||
|
}: BookingConfirmationProps) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
const { hotel } = await getBookingConfirmation(confirmationNumber)
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.details}>
|
||||||
|
<Subtitle color="uiTextHighContrast" type="two">
|
||||||
|
{intl.formatMessage({ id: "Hotel details" })}
|
||||||
|
</Subtitle>
|
||||||
|
<div className={styles.hotel}>
|
||||||
|
<Body color="uiTextHighContrast">{hotel.name}</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{hotel.address.streetAddress}, {hotel.address.zipCode}{" "}
|
||||||
|
{hotel.address.city}
|
||||||
|
</Body>
|
||||||
|
<Body asChild color="uiTextHighContrast">
|
||||||
|
<Link
|
||||||
|
className={styles.link}
|
||||||
|
href={`tel:${hotel.contactInformation.phoneNumber}`}
|
||||||
|
>
|
||||||
|
{hotel.contactInformation.phoneNumber}
|
||||||
|
</Link>
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
<Body color="uiTextPlaceholder" className={styles.coordinates}>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "Long {long} ∙ Lat {lat}" },
|
||||||
|
{
|
||||||
|
lat: hotel.location.latitude,
|
||||||
|
long: hotel.location.longitude,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
<div className={styles.contact}>
|
||||||
|
<Link
|
||||||
|
className={styles.link}
|
||||||
|
color="baseTextMediumContrast"
|
||||||
|
href={`mailto:${hotel.contactInformation.email}`}
|
||||||
|
>
|
||||||
|
{hotel.contactInformation.email}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className={styles.link}
|
||||||
|
color="baseTextMediumContrast"
|
||||||
|
href={hotel.contactInformation.websiteUrl}
|
||||||
|
>
|
||||||
|
{hotel.contactInformation.websiteUrl}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className={styles.toast}>
|
||||||
|
<Toast variant="info">
|
||||||
|
<ul className={styles.list}>
|
||||||
|
<li>N/A</li>
|
||||||
|
</ul>
|
||||||
|
</Toast>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
.imageContainer {
|
|
||||||
align-items: center;
|
|
||||||
border-radius: var(--Corner-radius-Medium);
|
|
||||||
display: flex;
|
|
||||||
grid-area: image;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import Image from "@/components/Image"
|
|
||||||
|
|
||||||
import styles from "./image.module.css"
|
|
||||||
|
|
||||||
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
|
||||||
|
|
||||||
export default async function HotelImage({
|
|
||||||
confirmationNumber,
|
|
||||||
}: BookingConfirmationProps) {
|
|
||||||
const { hotel } = await getBookingConfirmation(confirmationNumber)
|
|
||||||
return (
|
|
||||||
<aside className={styles.imageContainer}>
|
|
||||||
<Image
|
|
||||||
alt={hotel.hotelContent.images.metaData.altText}
|
|
||||||
height={256}
|
|
||||||
src={hotel.hotelContent.images.imageSizes.medium}
|
|
||||||
title={hotel.hotelContent.images.metaData.title}
|
|
||||||
width={256}
|
|
||||||
/>
|
|
||||||
</aside>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
|
import { CreditCardAddIcon } from "@/components/Icons"
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
|
import styles from "./paymentDetails.module.css"
|
||||||
|
|
||||||
|
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||||
|
|
||||||
|
export default async function PaymentDetails({
|
||||||
|
confirmationNumber,
|
||||||
|
}: BookingConfirmationProps) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
const lang = getLang()
|
||||||
|
const { booking } = await getBookingConfirmation(confirmationNumber)
|
||||||
|
return (
|
||||||
|
<div className={styles.details}>
|
||||||
|
<Subtitle color="uiTextHighContrast" type="two">
|
||||||
|
{intl.formatMessage({ id: "Payment details" })}
|
||||||
|
</Subtitle>
|
||||||
|
<div className={styles.payment}>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatNumber(booking.totalPrice, {
|
||||||
|
currency: booking.currencyCode,
|
||||||
|
style: "currency",
|
||||||
|
})}{" "}
|
||||||
|
{intl.formatMessage({ id: "has been paid" })}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{dt(booking.createDateTime)
|
||||||
|
.locale(lang)
|
||||||
|
.format("ddd D MMM YYYY, hh:mm")}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{card} ending with {cardno}" },
|
||||||
|
{ card: "N/A", cardno: "N/A" }
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className={styles.btn}
|
||||||
|
intent="text"
|
||||||
|
size="small"
|
||||||
|
theme="base"
|
||||||
|
variant="icon"
|
||||||
|
wrapping
|
||||||
|
>
|
||||||
|
<CreditCardAddIcon />
|
||||||
|
{intl.formatMessage({ id: "Save card to profile" })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.details,
|
||||||
|
.payment {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment {
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details button.btn {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-top: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
|
||||||
|
import styles from "./promo.module.css"
|
||||||
|
|
||||||
|
import type { PromoProps } from "@/types/components/hotelReservation/bookingConfirmation/promo"
|
||||||
|
|
||||||
|
export default function Promo({ buttonText, text, title }: PromoProps) {
|
||||||
|
return (
|
||||||
|
<article className={styles.promo}>
|
||||||
|
<Title color="white" level="h4">
|
||||||
|
{title}
|
||||||
|
</Title>
|
||||||
|
<Body className={styles.text} color="white" textAlign="center">
|
||||||
|
{text}
|
||||||
|
</Body>
|
||||||
|
<Button intent="primary" size="small" theme="primaryStrong">
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
.promo {
|
||||||
|
align-items: center;
|
||||||
|
background-position: 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: var(--Medium, 8px);
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 320px;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
height: 320px;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--Spacing-x4) var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo:nth-of-type(1) {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(0, 0, 0, 0.36) 37.88%,
|
||||||
|
rgba(0, 0, 0, 0.75) 100%
|
||||||
|
);
|
||||||
|
/* , url(""); uncomment and add image once we have it */
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo:nth-of-type(2) {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(0, 0, 0, 0.36) 37.88%,
|
||||||
|
rgba(0, 0, 0, 0.75) 100%
|
||||||
|
);
|
||||||
|
/* , url(""); uncomment and add image once we have it */
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import Promo from "./Promo"
|
||||||
|
|
||||||
|
import styles from "./promos.module.css"
|
||||||
|
|
||||||
|
export default async function Promos() {
|
||||||
|
const intl = await getIntl()
|
||||||
|
return (
|
||||||
|
<div className={styles.promos}>
|
||||||
|
<Promo
|
||||||
|
buttonText={intl.formatMessage({ id: "View and buy add-ons" })}
|
||||||
|
text={intl.formatMessage({
|
||||||
|
id: "Discover the little extra touches to make your upcoming stay even more unforgettable.",
|
||||||
|
})}
|
||||||
|
title={intl.formatMessage({ id: "Spice things up" })}
|
||||||
|
/>
|
||||||
|
<Promo
|
||||||
|
buttonText={intl.formatMessage({ id: "Book another stay" })}
|
||||||
|
text={intl.formatMessage({
|
||||||
|
id: "Get inspired and start dreaming beyond your next trip. Explore more Scandic destinations.",
|
||||||
|
})}
|
||||||
|
title={intl.formatMessage({ id: "Book your next stay" })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.promos {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
padding: var(--Spacing-x5) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
.promos {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
|
import { ChevronRightSmallIcon, InfoCircleIcon } from "@/components/Icons"
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
import { getBookedHotelRoom } from "@/utils/getBookedHotelRoom"
|
||||||
|
|
||||||
|
import styles from "./receipt.module.css"
|
||||||
|
|
||||||
|
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||||
|
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||||
|
|
||||||
|
export default async function Receipt({
|
||||||
|
confirmationNumber,
|
||||||
|
}: BookingConfirmationProps) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
const { booking, hotel } = await getBookingConfirmation(confirmationNumber)
|
||||||
|
const roomAndBed = getBookedHotelRoom(hotel, booking.roomTypeCode ?? "")
|
||||||
|
if (!roomAndBed) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
const breakfastPkgSelected = booking.packages.find(
|
||||||
|
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
||||||
|
)
|
||||||
|
const breakfastPkgIncluded = booking.packages.find(
|
||||||
|
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<section className={styles.receipt}>
|
||||||
|
<Subtitle type="two">{intl.formatMessage({ id: "Summary" })}</Subtitle>
|
||||||
|
<article className={styles.room}>
|
||||||
|
<header className={styles.roomHeader}>
|
||||||
|
<Body color="uiTextHighContrast">{roomAndBed.name}</Body>
|
||||||
|
{booking.rateDefinition.isMemberRate ? (
|
||||||
|
<div className={styles.memberPrice}>
|
||||||
|
<Body color="uiTextPlaceholder">
|
||||||
|
<s>N/A</s>
|
||||||
|
</Body>
|
||||||
|
<Body color="red">
|
||||||
|
{intl.formatNumber(booking.roomPrice, {
|
||||||
|
currency: booking.currencyCode,
|
||||||
|
style: "currency",
|
||||||
|
})}
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatNumber(booking.roomPrice, {
|
||||||
|
currency: booking.currencyCode,
|
||||||
|
style: "currency",
|
||||||
|
})}
|
||||||
|
</Body>
|
||||||
|
)}
|
||||||
|
<Caption color="uiTextMediumContrast">
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "booking.adults" },
|
||||||
|
{
|
||||||
|
totalAdults: booking.adults,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
<Caption color="uiTextMediumContrast">
|
||||||
|
{booking.rateDefinition.cancellationText}
|
||||||
|
</Caption>
|
||||||
|
<Link
|
||||||
|
color="peach80"
|
||||||
|
href=""
|
||||||
|
size="small"
|
||||||
|
textDecoration="underline"
|
||||||
|
variant="icon"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "Reservation policy" })}
|
||||||
|
<InfoCircleIcon color="peach80" />
|
||||||
|
</Link>
|
||||||
|
</header>
|
||||||
|
<div className={styles.entry}>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{roomAndBed.bedType.description}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatNumber(0, {
|
||||||
|
currency: booking.currencyCode,
|
||||||
|
style: "currency",
|
||||||
|
})}
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
<div className={styles.entry}>
|
||||||
|
<Body>{intl.formatMessage({ id: "Breakfast buffet" })}</Body>
|
||||||
|
{booking.rateDefinition.breakfastIncluded ?? breakfastPkgIncluded ? (
|
||||||
|
<Body color="red">{intl.formatMessage({ id: "Included" })}</Body>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{breakfastPkgSelected ? (
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatNumber(breakfastPkgSelected.totalPrice, {
|
||||||
|
currency: breakfastPkgSelected.currency,
|
||||||
|
style: "currency",
|
||||||
|
})}
|
||||||
|
</Body>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<Divider color="primaryLightSubtle" />
|
||||||
|
<div className={styles.price}>
|
||||||
|
<div className={styles.entry}>
|
||||||
|
<Body textTransform="bold">
|
||||||
|
{intl.formatMessage({ id: "Total price" })}
|
||||||
|
</Body>
|
||||||
|
<Body textTransform="bold">
|
||||||
|
{intl.formatNumber(booking.totalPrice, {
|
||||||
|
currency: booking.currencyCode,
|
||||||
|
style: "currency",
|
||||||
|
})}
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
<div className={styles.entry}>
|
||||||
|
<Button
|
||||||
|
className={styles.btn}
|
||||||
|
intent="text"
|
||||||
|
size="small"
|
||||||
|
theme="base"
|
||||||
|
variant="icon"
|
||||||
|
wrapping
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "Price details" })}
|
||||||
|
<ChevronRightSmallIcon />
|
||||||
|
</Button>
|
||||||
|
<Caption color="uiTextMediumContrast">
|
||||||
|
{intl.formatMessage({ id: "Approx." })} N/A EUR
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
.receipt {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.room {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomHeader {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomHeader :nth-child(n + 3) {
|
||||||
|
grid-column: 1/-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memberPrice {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt .price button.btn {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
.receipt {
|
||||||
|
padding: var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,139 @@
|
|||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
|
import {
|
||||||
|
CheckCircleIcon,
|
||||||
|
ChevronRightSmallIcon,
|
||||||
|
CrossCircle,
|
||||||
|
} from "@/components/Icons"
|
||||||
|
import Image from "@/components/Image"
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import styles from "./room.module.css"
|
import styles from "./room.module.css"
|
||||||
|
|
||||||
export default function Room() {
|
import type { RoomProps } from "@/types/components/hotelReservation/bookingConfirmation/room"
|
||||||
return <article className={styles.room}></article>
|
|
||||||
|
export default async function Room({ booking, img, roomName }: RoomProps) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
const lang = getLang()
|
||||||
|
|
||||||
|
const fromDate = dt(booking.checkInDate).locale(lang)
|
||||||
|
const toDate = dt(booking.checkOutDate).locale(lang)
|
||||||
|
return (
|
||||||
|
<article className={styles.room}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<div>
|
||||||
|
{/* <Subtitle color="mainGrey60" type="two">
|
||||||
|
{intl.formatMessage({ id: "Room" })} 1
|
||||||
|
</Subtitle> */}
|
||||||
|
<Subtitle color="uiTextHighContrast" type="two">
|
||||||
|
{`${intl.formatMessage({ id: "Reservation number" })} ${booking.confirmationNumber}`}
|
||||||
|
</Subtitle>
|
||||||
|
</div>
|
||||||
|
<div className={styles.benefits}>
|
||||||
|
{booking.rateDefinition.isMemberRate ? (
|
||||||
|
<>
|
||||||
|
<CheckCircleIcon color="green" height={20} width={20} />
|
||||||
|
<Caption>
|
||||||
|
{intl.formatMessage({ id: "Membership benefits applied" })}
|
||||||
|
</Caption>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CrossCircle color="red" height={20} width={20} />
|
||||||
|
<Caption>
|
||||||
|
{intl.formatMessage({ id: "No membership benefits applied" })}
|
||||||
|
</Caption>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className={styles.booking}>
|
||||||
|
<Image
|
||||||
|
alt={img.metaData.altText}
|
||||||
|
className={styles.img}
|
||||||
|
focalPoint={{ x: 50, y: 50 }}
|
||||||
|
height={204}
|
||||||
|
src={img.imageSizes.medium}
|
||||||
|
style={{ borderRadius: "var(--Corner-radius-Medium)" }}
|
||||||
|
title={img.metaData.title}
|
||||||
|
width={204}
|
||||||
|
/>
|
||||||
|
<div className={styles.roomDetails}>
|
||||||
|
<div className={styles.roomName}>
|
||||||
|
<Subtitle color="uiTextHighContrast" type="two">
|
||||||
|
{roomName}
|
||||||
|
</Subtitle>
|
||||||
|
<Link color="burgundy" href="" variant="icon">
|
||||||
|
{intl.formatMessage({ id: "View room details" })}
|
||||||
|
<ChevronRightSmallIcon color="burgundy" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<ul className={styles.details}>
|
||||||
|
<li className={styles.listItem}>
|
||||||
|
<Body color="uiTextPlaceholder">
|
||||||
|
{intl.formatMessage({ id: "Check-in" })}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{`${fromDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${fromDate.format("HH:mm")}`}
|
||||||
|
</Body>
|
||||||
|
</li>
|
||||||
|
<li className={styles.listItem}>
|
||||||
|
<Body color="uiTextPlaceholder">
|
||||||
|
{intl.formatMessage({ id: "Check-out" })}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{`${toDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${toDate.format("HH:mm")}`}
|
||||||
|
</Body>
|
||||||
|
</li>
|
||||||
|
<li className={styles.listItem}>
|
||||||
|
<Body color="uiTextPlaceholder">
|
||||||
|
{intl.formatMessage({ id: "Breakfast" })}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">N/A</Body>
|
||||||
|
</li>
|
||||||
|
<li className={styles.listItem}>
|
||||||
|
<Body color="uiTextPlaceholder">
|
||||||
|
{intl.formatMessage({ id: "Cancellation policy" })}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{booking.rateDefinition.cancellationText}
|
||||||
|
</Body>
|
||||||
|
</li>
|
||||||
|
<li className={styles.listItem}>
|
||||||
|
<Body color="uiTextPlaceholder">
|
||||||
|
{intl.formatMessage({ id: "Rebooking" })}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">N/A</Body>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div className={styles.guest}>
|
||||||
|
<Body color="uiTextPlaceholder">
|
||||||
|
{intl.formatMessage({ id: "Main guest" })}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{`${booking.guest.firstName} ${booking.guest.lastName}`}
|
||||||
|
</Body>
|
||||||
|
{booking.guest.membershipNumber ? (
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{`${intl.formatMessage({ id: "Friend no." })} ${booking.guest.membershipNumber}`}
|
||||||
|
</Body>
|
||||||
|
) : null}
|
||||||
|
{booking.guest.phoneNumber ? (
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{booking.guest.phoneNumber}
|
||||||
|
</Body>
|
||||||
|
) : null}
|
||||||
|
{booking.guest.email ? (
|
||||||
|
<Body color="uiTextHighContrast">{booking.guest.email}</Body>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
.room {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
align-items: flex-end;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits {
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--Base-Border-Subtle);
|
||||||
|
border-radius: var(--Corner-radius-Medium);
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.booking {
|
||||||
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x3)
|
||||||
|
var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomDetails {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomName {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
grid-column: 1/-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x-half) var(--Spacing-x3);
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listItem {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1366px) {
|
||||||
|
.details {
|
||||||
|
padding-bottom: var(--Spacing-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details p:nth-of-type(even) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
.header {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.booking {
|
||||||
|
gap: var(--Spacing-x3);
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x2)
|
||||||
|
var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomDetails {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest {
|
||||||
|
align-items: flex-end;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,30 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
|
import { getBookedHotelRoom } from "@/utils/getBookedHotelRoom"
|
||||||
|
|
||||||
|
import Room from "./Room"
|
||||||
|
|
||||||
import styles from "./rooms.module.css"
|
import styles from "./rooms.module.css"
|
||||||
|
|
||||||
export default function Rooms() {
|
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
||||||
return <section className={styles.rooms}></section>
|
|
||||||
|
export default async function Rooms({
|
||||||
|
confirmationNumber,
|
||||||
|
}: BookingConfirmationProps) {
|
||||||
|
const { booking, hotel } = await getBookingConfirmation(confirmationNumber)
|
||||||
|
const roomAndBed = getBookedHotelRoom(hotel, booking.roomTypeCode ?? "")
|
||||||
|
if (!roomAndBed) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<section className={styles.rooms}>
|
||||||
|
<Room
|
||||||
|
booking={booking}
|
||||||
|
img={roomAndBed.images[0]}
|
||||||
|
roomName={roomAndBed.name}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
.rooms {
|
.rooms {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x9);
|
gap: var(--Spacing-x5);
|
||||||
grid-area: booking;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import styles from "./summary.module.css"
|
|
||||||
|
|
||||||
export default function Summary() {
|
|
||||||
return <aside className={styles.summary}>SUMMARY</aside>
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
.summary {
|
|
||||||
background-color: hotpink;
|
|
||||||
grid-area: summary;
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import {
|
|
||||||
CoffeeIcon,
|
|
||||||
DiscountIcon,
|
|
||||||
DoorClosedIcon,
|
|
||||||
PriceTagIcon,
|
|
||||||
} from "@/components/Icons"
|
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
||||||
import { getIntl } from "@/i18n"
|
|
||||||
|
|
||||||
import styles from "./totalPrice.module.css"
|
|
||||||
|
|
||||||
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
|
||||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
|
||||||
|
|
||||||
export default async function TotalPrice({
|
|
||||||
confirmationNumber,
|
|
||||||
}: BookingConfirmationProps) {
|
|
||||||
const intl = await getIntl()
|
|
||||||
const { booking } = await getBookingConfirmation(confirmationNumber)
|
|
||||||
|
|
||||||
const totalPrice = intl.formatNumber(booking.totalPrice, {
|
|
||||||
currency: booking.currencyCode,
|
|
||||||
style: "currency",
|
|
||||||
})
|
|
||||||
const breakfastPackage = booking.packages.find(
|
|
||||||
(p) => p.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<section className={styles.container}>
|
|
||||||
<hgroup>
|
|
||||||
<Subtitle color="uiTextPlaceholder" type="two">
|
|
||||||
{intl.formatMessage({ id: "Total price" })}
|
|
||||||
</Subtitle>
|
|
||||||
<Subtitle color="uiTextHighContrast" type="two">
|
|
||||||
{totalPrice} (~ EUR)
|
|
||||||
</Subtitle>
|
|
||||||
</hgroup>
|
|
||||||
<div className={styles.items}>
|
|
||||||
<div>
|
|
||||||
<DoorClosedIcon />
|
|
||||||
<Body color="uiTextPlaceholder">
|
|
||||||
{`${intl.formatMessage({ id: "Room" })}, ${intl.formatMessage({ id: "booking.nights" }, { totalNights: 1 })}`}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">{totalPrice}</Body>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<CoffeeIcon />
|
|
||||||
<Body color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Breakfast" })}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">
|
|
||||||
{breakfastPackage
|
|
||||||
? intl.formatNumber(breakfastPackage.totalPrice, {
|
|
||||||
currency: breakfastPackage.currency,
|
|
||||||
style: "currency",
|
|
||||||
})
|
|
||||||
: intl.formatMessage({ id: "No breakfast" })}
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<DiscountIcon />
|
|
||||||
<Body color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Member discount" })}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">N/A</Body>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<PriceTagIcon height={20} width={20} />
|
|
||||||
<Body color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Points used" })}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">N/A</Body>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Divider color="primaryLightSubtle" />
|
|
||||||
<div className={styles.items}>
|
|
||||||
<div>
|
|
||||||
<Caption color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Price excl VAT" })}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextHighContrast">
|
|
||||||
{intl.formatNumber(booking.totalPriceExVat, {
|
|
||||||
currency: booking.currencyCode,
|
|
||||||
style: "currency",
|
|
||||||
})}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Caption color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "VAT" })}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextHighContrast">{booking.vatPercentage}%</Caption>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Caption color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "VAT amount" })}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextHighContrast">
|
|
||||||
{intl.formatNumber(booking.vatAmount, {
|
|
||||||
currency: booking.currencyCode,
|
|
||||||
style: "currency",
|
|
||||||
})}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Caption color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Price incl VAT" })}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextHighContrast">
|
|
||||||
{intl.formatNumber(booking.totalPrice, {
|
|
||||||
currency: booking.currencyCode,
|
|
||||||
style: "currency",
|
|
||||||
})}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Caption color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Payment method" })}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextHighContrast">N/A</Caption>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Caption color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Payment status" })}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextHighContrast">N/A</Caption>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
.container {
|
|
||||||
background-color: var(--Base-Background-Primary-Normal);
|
|
||||||
border-radius: var(--Corner-radius-Large);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--Spacing-x3);
|
|
||||||
padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.items {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--Spacing-x3) var(--Spacing-x1);
|
|
||||||
grid-template-columns: repeat(4, minmax(100px, 1fr));
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import { profile } from "@/constants/routes/myPages"
|
|
||||||
import { dt } from "@/lib/dt"
|
|
||||||
import {
|
|
||||||
getBookingConfirmation,
|
|
||||||
getProfileSafely,
|
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import { CreditCardAddIcon, EditIcon, PersonIcon } from "@/components/Icons"
|
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|
||||||
import { getIntl } from "@/i18n"
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
|
|
||||||
import styles from "./summary.module.css"
|
|
||||||
|
|
||||||
import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation"
|
|
||||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
|
||||||
|
|
||||||
export default async function Summary({
|
|
||||||
confirmationNumber,
|
|
||||||
}: BookingConfirmationProps) {
|
|
||||||
const intl = await getIntl()
|
|
||||||
const lang = getLang()
|
|
||||||
const { booking, hotel } = await getBookingConfirmation(confirmationNumber)
|
|
||||||
const user = await getProfileSafely()
|
|
||||||
const { firstName, lastName } = booking.guest
|
|
||||||
const membershipNumber = user?.membership?.membershipNumber
|
|
||||||
const totalNights = dt(booking.checkOutDate.setHours(0, 0, 0)).diff(
|
|
||||||
dt(booking.checkInDate.setHours(0, 0, 0)),
|
|
||||||
"days"
|
|
||||||
)
|
|
||||||
|
|
||||||
const breakfastPackage = booking.packages.find(
|
|
||||||
(pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<div className={styles.summary}>
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.textContainer}>
|
|
||||||
<Body color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Guest" })}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">{`${firstName} ${lastName}`}</Body>
|
|
||||||
{membershipNumber ? (
|
|
||||||
<Body color="uiTextHighContrast">
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "membership.no" },
|
|
||||||
{ membershipNumber }
|
|
||||||
)}
|
|
||||||
</Body>
|
|
||||||
) : null}
|
|
||||||
<Body color="uiTextHighContrast">{booking.guest.email}</Body>
|
|
||||||
<Body color="uiTextHighContrast">{booking.guest.phoneNumber}</Body>
|
|
||||||
</div>
|
|
||||||
{user ? (
|
|
||||||
<Link className={styles.link} href={profile[lang]} variant="icon">
|
|
||||||
<PersonIcon color="baseButtonTextOnFillNormal" />
|
|
||||||
<Caption color="burgundy" type="bold">
|
|
||||||
{intl.formatMessage({ id: "Go to profile" })}
|
|
||||||
</Caption>
|
|
||||||
</Link>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<Divider color="primaryLightSubtle" />
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.textContainer}>
|
|
||||||
<Body color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Payment" })}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "guest.paid" },
|
|
||||||
{
|
|
||||||
amount: intl.formatNumber(booking.totalPrice),
|
|
||||||
currency: booking.currencyCode,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">Date information N/A</Body>
|
|
||||||
<Body color="uiTextHighContrast">Card information N/A</Body>
|
|
||||||
</div>
|
|
||||||
{/* # href until more info */}
|
|
||||||
{user ? (
|
|
||||||
<Link className={styles.link} href="#" variant="icon">
|
|
||||||
<CreditCardAddIcon color="baseButtonTextOnFillNormal" />
|
|
||||||
<Caption color="burgundy" type="bold">
|
|
||||||
{intl.formatMessage({ id: "Save card to profile" })}
|
|
||||||
</Caption>
|
|
||||||
</Link>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<Divider color="primaryLightSubtle" />
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.textContainer}>
|
|
||||||
<Body color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Booking" })}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">
|
|
||||||
N/A, {intl.formatMessage({ id: "booking.nights" }, { totalNights })}
|
|
||||||
,{" "}
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "booking.adults" },
|
|
||||||
{ totalAdults: booking.adults }
|
|
||||||
)}
|
|
||||||
</Body>
|
|
||||||
{breakfastPackage ? (
|
|
||||||
<Body color="uiTextHighContrast">
|
|
||||||
{intl.formatMessage({ id: "Breakfast added" })}
|
|
||||||
</Body>
|
|
||||||
) : null}
|
|
||||||
<Body color="uiTextHighContrast">Bedtype N/A</Body>
|
|
||||||
</div>
|
|
||||||
{/* # href until more info */}
|
|
||||||
<Link className={styles.link} href="#" variant="icon">
|
|
||||||
<EditIcon color="baseButtonTextOnFillNormal" />
|
|
||||||
<Caption color="burgundy" type="bold">
|
|
||||||
{intl.formatMessage({ id: "Manage booking" })}
|
|
||||||
</Caption>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<Divider color="primaryLightSubtle" />
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.textContainer}>
|
|
||||||
<Body color="uiTextPlaceholder">
|
|
||||||
{intl.formatMessage({ id: "Hotel" })}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">{hotel.name}</Body>
|
|
||||||
<Body color="uiTextHighContrast">
|
|
||||||
{`${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`}
|
|
||||||
</Body>
|
|
||||||
<Body color="uiTextHighContrast">
|
|
||||||
{hotel.contactInformation.phoneNumber}
|
|
||||||
</Body>
|
|
||||||
<Caption color="uiTextMediumContrast" className={styles.latLong}>
|
|
||||||
{`${intl.formatMessage({ id: "Longitude" }, { long: hotel.location.longitude })} ∙ ${intl.formatMessage({ id: "Latitude" }, { lat: hotel.location.latitude })}`}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
|
||||||
<div className={styles.hotelLinks}>
|
|
||||||
<Link color="peach80" href={hotel.contactInformation.websiteUrl}>
|
|
||||||
{hotel.contactInformation.websiteUrl}
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
color="peach80"
|
|
||||||
href={`mailto:${hotel.contactInformation.email}`}
|
|
||||||
>
|
|
||||||
{hotel.contactInformation.email}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
.summary {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--Spacing-x3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container,
|
|
||||||
.textContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
gap: var(--Spacing-x-one-and-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.textContainer {
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container .textContainer .latLong {
|
|
||||||
padding-top: var(--Spacing-x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hotelLinks {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary .container .link {
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
|
||||||
|
|
||||||
import Summary from "@/components/HotelReservation/Summary"
|
|
||||||
import { SummaryBottomSheet } from "@/components/HotelReservation/Summary/BottomSheet"
|
|
||||||
|
|
||||||
import styles from "./summary.module.css"
|
|
||||||
|
|
||||||
import type { ClientSummaryProps } from "@/types/components/hotelReservation/enterDetails/summary"
|
|
||||||
import type { DetailsState } from "@/types/stores/enter-details"
|
|
||||||
|
|
||||||
function storeSelector(state: DetailsState) {
|
|
||||||
return {
|
|
||||||
bedType: state.bedType,
|
|
||||||
breakfast: state.breakfast,
|
|
||||||
fromDate: state.booking.fromDate,
|
|
||||||
join: state.guest.join,
|
|
||||||
membershipNo: state.guest.membershipNo,
|
|
||||||
packages: state.packages,
|
|
||||||
roomRate: state.roomRate,
|
|
||||||
roomPrice: state.roomPrice,
|
|
||||||
toDate: state.booking.toDate,
|
|
||||||
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
|
||||||
totalPrice: state.totalPrice,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ClientSummary({
|
|
||||||
adults,
|
|
||||||
cancellationText,
|
|
||||||
isMember,
|
|
||||||
kids,
|
|
||||||
memberRate,
|
|
||||||
rateDetails,
|
|
||||||
roomType,
|
|
||||||
}: ClientSummaryProps) {
|
|
||||||
const {
|
|
||||||
bedType,
|
|
||||||
breakfast,
|
|
||||||
fromDate,
|
|
||||||
join,
|
|
||||||
membershipNo,
|
|
||||||
packages,
|
|
||||||
roomPrice,
|
|
||||||
toDate,
|
|
||||||
toggleSummaryOpen,
|
|
||||||
totalPrice,
|
|
||||||
} = useEnterDetailsStore(storeSelector)
|
|
||||||
|
|
||||||
const showMemberPrice = !!(isMember && memberRate) || join || !!membershipNo
|
|
||||||
const room = {
|
|
||||||
adults,
|
|
||||||
cancellationText,
|
|
||||||
children: kids,
|
|
||||||
packages,
|
|
||||||
rateDetails,
|
|
||||||
roomPrice,
|
|
||||||
roomType,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={styles.mobileSummary}>
|
|
||||||
<SummaryBottomSheet>
|
|
||||||
<div className={styles.summary}>
|
|
||||||
<Summary
|
|
||||||
bedType={bedType}
|
|
||||||
breakfast={breakfast}
|
|
||||||
fromDate={fromDate}
|
|
||||||
showMemberPrice={showMemberPrice}
|
|
||||||
room={room}
|
|
||||||
toDate={toDate}
|
|
||||||
toggleSummaryOpen={toggleSummaryOpen}
|
|
||||||
totalPrice={totalPrice}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SummaryBottomSheet>
|
|
||||||
</div>
|
|
||||||
<div className={styles.desktopSummary}>
|
|
||||||
<div className={styles.hider} />
|
|
||||||
<div className={styles.summary}>
|
|
||||||
<Summary
|
|
||||||
bedType={bedType}
|
|
||||||
breakfast={breakfast}
|
|
||||||
fromDate={fromDate}
|
|
||||||
showMemberPrice={showMemberPrice}
|
|
||||||
room={room}
|
|
||||||
toDate={toDate}
|
|
||||||
totalPrice={totalPrice}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.shadow} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
13
components/HotelReservation/EnterDetails/Summary/Desktop.tsx
Normal file
13
components/HotelReservation/EnterDetails/Summary/Desktop.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import SidePanel from "@/components/HotelReservation/SidePanel"
|
||||||
|
|
||||||
|
import SummaryUI from "./UI"
|
||||||
|
|
||||||
|
import type { SummaryProps } from "@/types/components/hotelReservation/summary"
|
||||||
|
|
||||||
|
export default function DesktopSummary(props: SummaryProps) {
|
||||||
|
return (
|
||||||
|
<SidePanel variant="summary">
|
||||||
|
<SummaryUI {...props} />
|
||||||
|
</SidePanel>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|||||||
|
|
||||||
import styles from "./bottomSheet.module.css"
|
import styles from "./bottomSheet.module.css"
|
||||||
|
|
||||||
export function SummaryBottomSheet({ children }: PropsWithChildren) {
|
export default function SummaryBottomSheet({ children }: PropsWithChildren) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const { isSummaryOpen, toggleSummaryOpen, totalPrice, isSubmittingDisabled } =
|
const { isSummaryOpen, toggleSummaryOpen, totalPrice, isSubmittingDisabled } =
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import SummaryUI from "../UI"
|
||||||
|
import SummaryBottomSheet from "./BottomSheet"
|
||||||
|
|
||||||
|
import styles from "./mobile.module.css"
|
||||||
|
|
||||||
|
import type { SummaryProps } from "@/types/components/hotelReservation/summary"
|
||||||
|
|
||||||
|
export default function MobileSummary(props: SummaryProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles.mobileSummary}>
|
||||||
|
<SummaryBottomSheet>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<SummaryUI {...props} />
|
||||||
|
</div>
|
||||||
|
</SummaryBottomSheet>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.mobileSummary {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1366px) {
|
||||||
|
.wrapper {
|
||||||
|
background-color: var(--Main-Grey-White);
|
||||||
|
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-bottom: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
.mobileSummary {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||||
|
|
||||||
import { ArrowRightIcon, ChevronDownSmallIcon } from "@/components/Icons"
|
import { ArrowRightIcon, ChevronDownSmallIcon } from "@/components/Icons"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
@@ -13,25 +14,58 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
|
|||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
import styles from "./summary.module.css"
|
import styles from "./ui.module.css"
|
||||||
|
|
||||||
import type { SummaryProps } from "@/types/components/hotelReservation/summary"
|
import type { SummaryProps } from "@/types/components/hotelReservation/summary"
|
||||||
import { CurrencyEnum } from "@/types/enums/currency"
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
|
import type { DetailsState } from "@/types/stores/enter-details"
|
||||||
|
|
||||||
export default function Summary({
|
export function storeSelector(state: DetailsState) {
|
||||||
bedType,
|
return {
|
||||||
breakfast,
|
bedType: state.bedType,
|
||||||
fromDate,
|
booking: state.booking,
|
||||||
showMemberPrice,
|
breakfast: state.breakfast,
|
||||||
room,
|
join: state.guest.join,
|
||||||
toDate,
|
membershipNo: state.guest.membershipNo,
|
||||||
toggleSummaryOpen,
|
packages: state.packages,
|
||||||
totalPrice,
|
roomRate: state.roomRate,
|
||||||
|
roomPrice: state.roomPrice,
|
||||||
|
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
||||||
|
totalPrice: state.totalPrice,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SummaryUI({
|
||||||
|
cancellationText,
|
||||||
|
isMember,
|
||||||
|
rateDetails,
|
||||||
|
roomType,
|
||||||
}: SummaryProps) {
|
}: SummaryProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
const diff = dt(toDate).diff(fromDate, "days")
|
const {
|
||||||
|
bedType,
|
||||||
|
booking,
|
||||||
|
breakfast,
|
||||||
|
join,
|
||||||
|
membershipNo,
|
||||||
|
packages,
|
||||||
|
roomPrice,
|
||||||
|
roomRate,
|
||||||
|
toggleSummaryOpen,
|
||||||
|
totalPrice,
|
||||||
|
} = useEnterDetailsStore(storeSelector)
|
||||||
|
|
||||||
|
const adults = booking.rooms[0].adults
|
||||||
|
const children = booking.rooms[0].children
|
||||||
|
|
||||||
|
const showMemberPrice = !!(
|
||||||
|
(isMember || join || membershipNo) &&
|
||||||
|
roomRate.memberRate
|
||||||
|
)
|
||||||
|
|
||||||
|
const diff = dt(booking.toDate).diff(booking.fromDate, "days")
|
||||||
|
|
||||||
const nights = intl.formatMessage(
|
const nights = intl.formatMessage(
|
||||||
{ id: "booking.nights" },
|
{ id: "booking.nights" },
|
||||||
@@ -51,9 +85,9 @@ export default function Summary({
|
|||||||
{intl.formatMessage({ id: "Summary" })}
|
{intl.formatMessage({ id: "Summary" })}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<Body className={styles.date} color="baseTextMediumContrast">
|
<Body className={styles.date} color="baseTextMediumContrast">
|
||||||
{dt(fromDate).locale(lang).format("ddd, D MMM")}
|
{dt(booking.fromDate).locale(lang).format("ddd, D MMM")}
|
||||||
<ArrowRightIcon color="peach80" height={15} width={15} />
|
<ArrowRightIcon color="peach80" height={15} width={15} />
|
||||||
{dt(toDate).locale(lang).format("ddd, D MMM")} ({nights})
|
{dt(booking.toDate).locale(lang).format("ddd, D MMM")} ({nights})
|
||||||
</Body>
|
</Body>
|
||||||
<Button
|
<Button
|
||||||
intent="text"
|
intent="text"
|
||||||
@@ -68,13 +102,13 @@ export default function Summary({
|
|||||||
<div className={styles.addOns}>
|
<div className={styles.addOns}>
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<Body color="uiTextHighContrast">{room.roomType}</Body>
|
<Body color="uiTextHighContrast">{roomType}</Body>
|
||||||
<Caption color={showMemberPrice ? "red" : "uiTextHighContrast"}>
|
<Caption color={showMemberPrice ? "red" : "uiTextHighContrast"}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{
|
{
|
||||||
amount: intl.formatNumber(room.roomPrice.local.price),
|
amount: intl.formatNumber(roomPrice.local.price),
|
||||||
currency: room.roomPrice.local.currency,
|
currency: roomPrice.local.currency,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
@@ -82,20 +116,18 @@ export default function Summary({
|
|||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "booking.adults" },
|
{ id: "booking.adults" },
|
||||||
{ totalAdults: room.adults }
|
{ totalAdults: adults }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
{room.children?.length ? (
|
{children?.length ? (
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "booking.children" },
|
{ id: "booking.children" },
|
||||||
{ totalChildren: room.children.length }
|
{ totalChildren: children.length }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
) : null}
|
) : null}
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">{cancellationText}</Caption>
|
||||||
{room.cancellationText}
|
|
||||||
</Caption>
|
|
||||||
<Popover
|
<Popover
|
||||||
placement="bottom left"
|
placement="bottom left"
|
||||||
triggerContent={
|
triggerContent={
|
||||||
@@ -106,16 +138,16 @@ export default function Summary({
|
|||||||
>
|
>
|
||||||
<aside className={styles.rateDetailsPopover}>
|
<aside className={styles.rateDetailsPopover}>
|
||||||
<header>
|
<header>
|
||||||
<Caption type="bold">{room.cancellationText}</Caption>
|
<Caption type="bold">{cancellationText}</Caption>
|
||||||
</header>
|
</header>
|
||||||
{room.rateDetails?.map((detail, idx) => (
|
{rateDetails?.map((detail, idx) => (
|
||||||
<Caption key={`rateDetails-${idx}`}>{detail}</Caption>
|
<Caption key={`rateDetails-${idx}`}>{detail}</Caption>
|
||||||
))}
|
))}
|
||||||
</aside>
|
</aside>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
{room.packages
|
{packages
|
||||||
? room.packages.map((roomPackage) => (
|
? packages.map((roomPackage) => (
|
||||||
<div className={styles.entry} key={roomPackage.code}>
|
<div className={styles.entry} key={roomPackage.code}>
|
||||||
<div>
|
<div>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
@@ -147,7 +179,7 @@ export default function Summary({
|
|||||||
<Caption color="uiTextHighContrast">
|
<Caption color="uiTextHighContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{ amount: "0", currency: room.roomPrice.local.currency }
|
{ amount: "0", currency: roomPrice.local.currency }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,7 +193,7 @@ export default function Summary({
|
|||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{ amount: "0", currency: room.roomPrice.local.currency }
|
{ amount: "0", currency: roomPrice.local.currency }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { redirect } from "next/navigation"
|
|
||||||
|
|
||||||
import { selectRate } from "@/constants/routes/hotelReservation"
|
|
||||||
import {
|
|
||||||
getProfileSafely,
|
|
||||||
getSelectedRoomAvailability,
|
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
|
||||||
|
|
||||||
import { generateChildrenString } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
|
|
||||||
import ClientSummary from "./Client"
|
|
||||||
|
|
||||||
import type { SummaryPageProps } from "@/types/components/hotelReservation/summary"
|
|
||||||
|
|
||||||
export default async function Summary({
|
|
||||||
adults,
|
|
||||||
fromDate,
|
|
||||||
hotelId,
|
|
||||||
kids,
|
|
||||||
packageCodes,
|
|
||||||
rateCode,
|
|
||||||
roomTypeCode,
|
|
||||||
toDate,
|
|
||||||
}: SummaryPageProps) {
|
|
||||||
const lang = getLang()
|
|
||||||
|
|
||||||
const availability = await getSelectedRoomAvailability({
|
|
||||||
adults,
|
|
||||||
children: kids ? generateChildrenString(kids) : undefined,
|
|
||||||
hotelId,
|
|
||||||
packageCodes,
|
|
||||||
rateCode,
|
|
||||||
roomStayStartDate: fromDate,
|
|
||||||
roomStayEndDate: toDate,
|
|
||||||
roomTypeCode,
|
|
||||||
})
|
|
||||||
const user = await getProfileSafely()
|
|
||||||
|
|
||||||
if (!availability || !availability.selectedRoom) {
|
|
||||||
console.error("No hotel or availability data", availability)
|
|
||||||
// TODO: handle this case
|
|
||||||
redirect(selectRate(lang))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ClientSummary
|
|
||||||
adults={adults}
|
|
||||||
cancellationText={availability.cancellationText}
|
|
||||||
isMember={!!user}
|
|
||||||
kids={kids}
|
|
||||||
memberRate={availability.memberRate}
|
|
||||||
rateDetails={availability.rateDetails}
|
|
||||||
roomType={availability.selectedRoom.roomType}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
19
components/HotelReservation/SidePanel/index.tsx
Normal file
19
components/HotelReservation/SidePanel/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { sidePanelVariants } from "./variants"
|
||||||
|
|
||||||
|
import styles from "./sidePanel.module.css"
|
||||||
|
|
||||||
|
import type { SidePanelProps } from "@/types/components/hotelReservation/sidePanel"
|
||||||
|
|
||||||
|
export default function SidePanel({
|
||||||
|
children,
|
||||||
|
variant,
|
||||||
|
}: React.PropsWithChildren<SidePanelProps>) {
|
||||||
|
const classNames = sidePanelVariants({ variant })
|
||||||
|
return (
|
||||||
|
<div className={classNames}>
|
||||||
|
<div className={styles.hider} />
|
||||||
|
<div className={styles.wrapper}>{children}</div>
|
||||||
|
<div className={styles.shadow} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,68 +1,62 @@
|
|||||||
.mobileSummary {
|
.sidePanel,
|
||||||
display: block;
|
.hider,
|
||||||
}
|
|
||||||
|
|
||||||
.desktopSummary {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary {
|
|
||||||
background-color: var(--Main-Grey-White);
|
|
||||||
|
|
||||||
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-bottom: none;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hider {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow {
|
.shadow {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
.mobileSummary {
|
.sidePanel {
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desktopSummary {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto auto 1fr;
|
grid-template-rows: auto auto 1fr;
|
||||||
margin-top: calc(0px - var(--Spacing-x9));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary {
|
.summary {
|
||||||
|
margin-top: calc(0px - var(--Spacing-x9));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hider {
|
||||||
|
display: block;
|
||||||
|
position: sticky;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt .hider {
|
||||||
|
background-color: var(--Main-Grey-White);
|
||||||
|
height: 150px;
|
||||||
|
margin-top: -78px;
|
||||||
|
top: -40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary .hider {
|
||||||
|
background-color: var(--Scandic-Brand-Warm-White);
|
||||||
|
height: 40px;
|
||||||
|
margin-top: var(--Spacing-x4);
|
||||||
|
top: calc(var(--booking-widget-desktop-height) - 6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
background-color: var(--Main-Grey-White);
|
||||||
|
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
||||||
|
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-bottom: none;
|
||||||
|
margin-top: calc(0px - var(--Spacing-x9));
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: calc(
|
top: calc(
|
||||||
var(--booking-widget-desktop-height) + var(--Spacing-x2) +
|
var(--booking-widget-desktop-height) + var(--Spacing-x2) +
|
||||||
var(--Spacing-x-half)
|
var(--Spacing-x-half)
|
||||||
);
|
);
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
|
||||||
margin-top: calc(0px - var(--Spacing-x9));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.shadow {
|
.shadow {
|
||||||
display: block;
|
|
||||||
background-color: var(--Main-Grey-White);
|
background-color: var(--Main-Grey-White);
|
||||||
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
border-color: var(--Primary-Light-On-Surface-Divider-subtle);
|
||||||
border-style: solid;
|
|
||||||
border-left-width: 1px;
|
border-left-width: 1px;
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
border-top: none;
|
border-style: solid;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
border-top: none;
|
||||||
|
|
||||||
.hider {
|
|
||||||
display: block;
|
display: block;
|
||||||
background-color: var(--Scandic-Brand-Warm-White);
|
|
||||||
position: sticky;
|
|
||||||
top: calc(var(--booking-widget-desktop-height) - 6px);
|
|
||||||
margin-top: var(--Spacing-x4);
|
|
||||||
height: 40px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
15
components/HotelReservation/SidePanel/variants.ts
Normal file
15
components/HotelReservation/SidePanel/variants.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
import styles from "./sidePanel.module.css"
|
||||||
|
|
||||||
|
export const sidePanelVariants = cva(styles.sidePanel, {
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
receipt: styles.receipt,
|
||||||
|
summary: styles.summary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "summary",
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -16,7 +16,8 @@
|
|||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
font-family: var(--typography-Footnote-Bold-fontFamily);
|
font-family: var(--typography-Footnote-Bold-fontFamily);
|
||||||
font-size: var(--typography-Footnote-Bold-fontSize);
|
font-size: var(--typography-Footnote-Bold-fontSize);
|
||||||
font-weight: 500; /* var(--typography-Footnote-Bold-fontWeight); */
|
font-weight: 500;
|
||||||
|
/* var(--typography-Footnote-Bold-fontWeight); */
|
||||||
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
|
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
|
||||||
line-height: var(--typography-Footnote-Bold-lineHeight);
|
line-height: var(--typography-Footnote-Bold-lineHeight);
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
.link.breadcrumb {
|
.link.breadcrumb {
|
||||||
font-family: var(--typography-Footnote-Bold-fontFamily);
|
font-family: var(--typography-Footnote-Bold-fontFamily);
|
||||||
font-size: var(--typography-Footnote-Bold-fontSize);
|
font-size: var(--typography-Footnote-Bold-fontSize);
|
||||||
font-weight: 500; /* var(--typography-Footnote-Bold-fontWeight); */
|
font-weight: 500;
|
||||||
|
/* var(--typography-Footnote-Bold-fontWeight); */
|
||||||
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
|
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
|
||||||
line-height: var(--typography-Footnote-Bold-lineHeight);
|
line-height: var(--typography-Footnote-Bold-lineHeight);
|
||||||
}
|
}
|
||||||
@@ -159,12 +161,15 @@
|
|||||||
color: var(--Scandic-Peach-50);
|
color: var(--Scandic-Peach-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
.peach80 {
|
.peach80,
|
||||||
|
.baseTextMediumContrast {
|
||||||
color: var(--Base-Text-Medium-contrast);
|
color: var(--Base-Text-Medium-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.peach80:hover,
|
.peach80:hover,
|
||||||
.peach80:active {
|
.peach80:active,
|
||||||
|
.baseTextMediumContrast:hover,
|
||||||
|
.baseTextMediumContrast:active {
|
||||||
color: var(--Base-Text-High-contrast);
|
color: var(--Base-Text-High-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +240,7 @@
|
|||||||
letter-spacing: var(--typography-Caption-Bold-letterSpacing);
|
letter-spacing: var(--typography-Caption-Bold-letterSpacing);
|
||||||
line-height: var(--typography-Caption-Bold-lineHeight);
|
line-height: var(--typography-Caption-Bold-lineHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
font-family: var(--typography-Body-Bold-fontFamily);
|
font-family: var(--typography-Body-Bold-fontFamily);
|
||||||
font-size: var(--typography-Body-Bold-fontSize);
|
font-size: var(--typography-Body-Bold-fontSize);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const linkVariants = cva(styles.link, {
|
|||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
baseButtonTextOnFillNormal: styles.baseButtonTextOnFillNormal,
|
baseButtonTextOnFillNormal: styles.baseButtonTextOnFillNormal,
|
||||||
|
baseTextMediumContrast: styles.baseTextMediumContrast,
|
||||||
black: styles.black,
|
black: styles.black,
|
||||||
burgundy: styles.burgundy,
|
burgundy: styles.burgundy,
|
||||||
none: "",
|
none: "",
|
||||||
|
|||||||
@@ -86,3 +86,7 @@
|
|||||||
.baseTextDisabled {
|
.baseTextDisabled {
|
||||||
color: var(--Base-Text-Disabled);
|
color: var(--Base-Text-Disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mainGrey60 {
|
||||||
|
color: var(--Main-Grey-60);
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const config = {
|
|||||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||||
red: styles.red,
|
red: styles.red,
|
||||||
|
mainGrey60: styles.mainGrey60,
|
||||||
},
|
},
|
||||||
textAlign: {
|
textAlign: {
|
||||||
center: styles.center,
|
center: styles.center,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function getIcon(variant: ToastsProps["variant"]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Toast({ message, onClose, variant }: ToastsProps) {
|
export function Toast({ children, message, onClose, variant }: ToastsProps) {
|
||||||
const className = toastVariants({ variant })
|
const className = toastVariants({ variant })
|
||||||
const Icon = getIcon(variant)
|
const Icon = getIcon(variant)
|
||||||
return (
|
return (
|
||||||
@@ -40,10 +40,16 @@ export function Toast({ message, onClose, variant }: ToastsProps) {
|
|||||||
<div className={styles.iconContainer}>
|
<div className={styles.iconContainer}>
|
||||||
{Icon && <Icon color="white" height={24} width={24} />}
|
{Icon && <Icon color="white" height={24} width={24} />}
|
||||||
</div>
|
</div>
|
||||||
|
{message ? (
|
||||||
<Body className={styles.message}>{message}</Body>
|
<Body className={styles.message}>{message}</Body>
|
||||||
|
) : (
|
||||||
|
<div className={styles.content}>{children}</div>
|
||||||
|
)}
|
||||||
|
{onClose ? (
|
||||||
<Button onClick={onClose} variant="icon" intent="text">
|
<Button onClick={onClose} variant="icon" intent="text">
|
||||||
<CloseLargeIcon />
|
<CloseLargeIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: var(--Spacing-x-one-and-half) var(--Spacing-x3)
|
||||||
|
var(--Spacing-x-one-and-half) var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.toast {
|
.toast {
|
||||||
width: var(--width);
|
width: var(--width);
|
||||||
|
|||||||
@@ -2,9 +2,16 @@ import { toastVariants } from "./variants"
|
|||||||
|
|
||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
export interface ToastsProps
|
export type ToastsProps = Omit<React.HTMLAttributes<HTMLDivElement>, "color"> &
|
||||||
extends Omit<React.AnchorHTMLAttributes<HTMLDivElement>, "color">,
|
VariantProps<typeof toastVariants> & {
|
||||||
VariantProps<typeof toastVariants> {
|
onClose?: () => void
|
||||||
message: React.ReactNode
|
} & (
|
||||||
onClose: () => void
|
| {
|
||||||
|
children: React.ReactNode
|
||||||
|
message?: never
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
children?: never
|
||||||
|
message: React.ReactNode
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -48,7 +48,9 @@
|
|||||||
"Bed type": "Bed type",
|
"Bed type": "Bed type",
|
||||||
"Birth date": "Birth date",
|
"Birth date": "Birth date",
|
||||||
"Book": "Book",
|
"Book": "Book",
|
||||||
|
"Book another stay": "Book another stay",
|
||||||
"Book reward night": "Book reward night",
|
"Book reward night": "Book reward night",
|
||||||
|
"Book your next stay": "Book your next stay",
|
||||||
"Booking": "Booking",
|
"Booking": "Booking",
|
||||||
"Booking number": "Booking number",
|
"Booking number": "Booking number",
|
||||||
"Breakfast": "Breakfast",
|
"Breakfast": "Breakfast",
|
||||||
@@ -114,6 +116,7 @@
|
|||||||
"Disabled booking options text": "Codes, cheques and reward nights aren't available on the new website yet.",
|
"Disabled booking options text": "Codes, cheques and reward nights aren't available on the new website yet.",
|
||||||
"Discard changes": "Discard changes",
|
"Discard changes": "Discard changes",
|
||||||
"Discard unsaved changes?": "Discard unsaved changes?",
|
"Discard unsaved changes?": "Discard unsaved changes?",
|
||||||
|
"Discover the little extra touches to make your upcoming stay even more unforgettable.": "Discover the little extra touches to make your upcoming stay even more unforgettable.",
|
||||||
"Distance in km to city centre": "{number} km to city centre",
|
"Distance in km to city centre": "{number} km to city centre",
|
||||||
"Distance to city centre": "Distance to city centre",
|
"Distance to city centre": "Distance to city centre",
|
||||||
"Distance to hotel": "Distance to hotel: {distance} m",
|
"Distance to hotel": "Distance to hotel: {distance} m",
|
||||||
@@ -152,9 +155,11 @@
|
|||||||
"Free parking": "Free parking",
|
"Free parking": "Free parking",
|
||||||
"Free rebooking": "Free rebooking",
|
"Free rebooking": "Free rebooking",
|
||||||
"Free until": "Free until",
|
"Free until": "Free until",
|
||||||
|
"Friend no.": "Friend no.",
|
||||||
"From": "From",
|
"From": "From",
|
||||||
"Garage": "Garage",
|
"Garage": "Garage",
|
||||||
"Get inspired": "Get inspired",
|
"Get inspired": "Get inspired",
|
||||||
|
"Get inspired and start dreaming beyond your next trip. Explore more Scandic destinations.": "Get inspired and start dreaming beyond your next trip. Explore more Scandic destinations.",
|
||||||
"Get member benefits & offers": "Get member benefits & offers",
|
"Get member benefits & offers": "Get member benefits & offers",
|
||||||
"Gift(s) added to your benefits": "{amount, plural, one {Gift} other {Gifts}} added to your benefits",
|
"Gift(s) added to your benefits": "{amount, plural, one {Gift} other {Gifts}} added to your benefits",
|
||||||
"Go back to edit": "Go back to edit",
|
"Go back to edit": "Go back to edit",
|
||||||
@@ -171,6 +176,7 @@
|
|||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
"Hospital": "Hospital",
|
"Hospital": "Hospital",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
|
"Hotel details": "Hotel details",
|
||||||
"Hotel facilities": "Hotel facilities",
|
"Hotel facilities": "Hotel facilities",
|
||||||
"Hotel reservation": "Hotel reservation",
|
"Hotel reservation": "Hotel reservation",
|
||||||
"Hotel surroundings": "Hotel surroundings",
|
"Hotel surroundings": "Hotel surroundings",
|
||||||
@@ -213,8 +219,10 @@
|
|||||||
"Log in here": "Log in here",
|
"Log in here": "Log in here",
|
||||||
"Log in/Join": "Log in/Join",
|
"Log in/Join": "Log in/Join",
|
||||||
"Log out": "Log out",
|
"Log out": "Log out",
|
||||||
|
"Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}",
|
||||||
"Longitude": "Longitude {long}",
|
"Longitude": "Longitude {long}",
|
||||||
"MY SAVED CARDS": "MY SAVED CARDS",
|
"MY SAVED CARDS": "MY SAVED CARDS",
|
||||||
|
"Main guest": "Main guest",
|
||||||
"Main menu": "Main menu",
|
"Main menu": "Main menu",
|
||||||
"Manage booking": "Manage booking",
|
"Manage booking": "Manage booking",
|
||||||
"Manage preferences": "Manage preferences",
|
"Manage preferences": "Manage preferences",
|
||||||
@@ -228,6 +236,7 @@
|
|||||||
"Members": "Members",
|
"Members": "Members",
|
||||||
"Membership ID": "Membership ID",
|
"Membership ID": "Membership ID",
|
||||||
"Membership ID copied to clipboard": "Membership ID copied to clipboard",
|
"Membership ID copied to clipboard": "Membership ID copied to clipboard",
|
||||||
|
"Membership benefits applied": "Membership benefits applied",
|
||||||
"Membership cards": "Membership cards",
|
"Membership cards": "Membership cards",
|
||||||
"Membership no": "Membership no",
|
"Membership no": "Membership no",
|
||||||
"Membership terms and conditions": "Membership terms and conditions",
|
"Membership terms and conditions": "Membership terms and conditions",
|
||||||
@@ -253,6 +262,7 @@
|
|||||||
"No breakfast": "No breakfast",
|
"No breakfast": "No breakfast",
|
||||||
"No content published": "No content published",
|
"No content published": "No content published",
|
||||||
"No matching location found": "No matching location found",
|
"No matching location found": "No matching location found",
|
||||||
|
"No membership benefits applied": "No membership benefits applied",
|
||||||
"No prices available": "No prices available",
|
"No prices available": "No prices available",
|
||||||
"No results": "No results",
|
"No results": "No results",
|
||||||
"No transactions available": "No transactions available",
|
"No transactions available": "No transactions available",
|
||||||
@@ -285,6 +295,7 @@
|
|||||||
"Pay now": "Pay now",
|
"Pay now": "Pay now",
|
||||||
"Payment": "Payment",
|
"Payment": "Payment",
|
||||||
"Payment Guarantee": "Payment Guarantee",
|
"Payment Guarantee": "Payment Guarantee",
|
||||||
|
"Payment details": "Payment details",
|
||||||
"Payment info": "Payment info",
|
"Payment info": "Payment info",
|
||||||
"Payment method": "Payment method",
|
"Payment method": "Payment method",
|
||||||
"Payment received": "Payment received",
|
"Payment received": "Payment received",
|
||||||
@@ -331,6 +342,8 @@
|
|||||||
"Relax": "Relax",
|
"Relax": "Relax",
|
||||||
"Remove card from member profile": "Remove card from member profile",
|
"Remove card from member profile": "Remove card from member profile",
|
||||||
"Request bedtype": "Request bedtype",
|
"Request bedtype": "Request bedtype",
|
||||||
|
"Reservation number": "Reservation number",
|
||||||
|
"Reservation policy": "Reservation policy",
|
||||||
"Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}",
|
"Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}",
|
||||||
"Restaurant & Bar": "Restaurant & Bar",
|
"Restaurant & Bar": "Restaurant & Bar",
|
||||||
"Restaurants & Bars": "Restaurants & Bars",
|
"Restaurants & Bars": "Restaurants & Bars",
|
||||||
@@ -386,6 +399,7 @@
|
|||||||
"Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.",
|
"Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.",
|
||||||
"Something went wrong!": "Something went wrong!",
|
"Something went wrong!": "Something went wrong!",
|
||||||
"Sort by": "Sort by",
|
"Sort by": "Sort by",
|
||||||
|
"Spice things up": "Spice things up",
|
||||||
"Sports": "Sports",
|
"Sports": "Sports",
|
||||||
"Standard price": "Standard price",
|
"Standard price": "Standard price",
|
||||||
"Stay at HOTEL_NAME | Hotel in DESTINATION": "Stay at {hotelName} | Hotel in {destination}",
|
"Stay at HOTEL_NAME | Hotel in DESTINATION": "Stay at {hotelName} | Hotel in {destination}",
|
||||||
@@ -424,8 +438,10 @@
|
|||||||
"User information": "User information",
|
"User information": "User information",
|
||||||
"VAT": "VAT",
|
"VAT": "VAT",
|
||||||
"VAT amount": "VAT amount",
|
"VAT amount": "VAT amount",
|
||||||
|
"View and buy add-ons": "View and buy add-ons",
|
||||||
"View as list": "View as list",
|
"View as list": "View as list",
|
||||||
"View as map": "View as map",
|
"View as map": "View as map",
|
||||||
|
"View room details": "View room details",
|
||||||
"View terms": "View terms",
|
"View terms": "View terms",
|
||||||
"View your booking": "View your booking",
|
"View your booking": "View your booking",
|
||||||
"Visiting address": "Visiting address",
|
"Visiting address": "Visiting address",
|
||||||
@@ -498,6 +514,7 @@
|
|||||||
"guest": "guest",
|
"guest": "guest",
|
||||||
"guest.paid": "{amount} {currency} has been paid",
|
"guest.paid": "{amount} {currency} has been paid",
|
||||||
"guests": "guests",
|
"guests": "guests",
|
||||||
|
"has been paid": "has been paid",
|
||||||
"hotelPages.rooms.roomCard.persons": "{size} ({totalOccupancy, plural, one {# person} other {# persons}})",
|
"hotelPages.rooms.roomCard.persons": "{size} ({totalOccupancy, plural, one {# person} other {# persons}})",
|
||||||
"hotelPages.rooms.roomCard.seeRoomDetails": "See room details",
|
"hotelPages.rooms.roomCard.seeRoomDetails": "See room details",
|
||||||
"km to city center": "km to city center",
|
"km to city center": "km to city center",
|
||||||
|
|||||||
23
package-lock.json
generated
23
package-lock.json
generated
@@ -35,6 +35,7 @@
|
|||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
"downshift": "^9.0.8",
|
"downshift": "^9.0.8",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fetch-retry": "^6.0.0",
|
"fetch-retry": "^6.0.0",
|
||||||
"framer-motion": "^11.3.28",
|
"framer-motion": "^11.3.28",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
@@ -43,7 +44,6 @@
|
|||||||
"immer": "10.1.1",
|
"immer": "10.1.1",
|
||||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||||
"libphonenumber-js": "^1.10.60",
|
"libphonenumber-js": "^1.10.60",
|
||||||
"lodash.isequal": "^4.5.0",
|
|
||||||
"next": "^14.2.18",
|
"next": "^14.2.18",
|
||||||
"next-auth": "^5.0.0-beta.19",
|
"next-auth": "^5.0.0-beta.19",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
@@ -67,7 +67,6 @@
|
|||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/json-stable-stringify-without-jsonify": "^1.0.2",
|
"@types/json-stable-stringify-without-jsonify": "^1.0.2",
|
||||||
"@types/lodash.isequal": "^4.5.8",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
@@ -6866,21 +6865,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/lodash": {
|
|
||||||
"version": "4.17.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
|
|
||||||
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/lodash.isequal": {
|
|
||||||
"version": "4.5.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz",
|
|
||||||
"integrity": "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/lodash": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.12.7",
|
"version": "20.12.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
|
||||||
@@ -15001,11 +14985,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
||||||
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
|
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isequal": {
|
|
||||||
"version": "4.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
|
||||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isplainobject": {
|
"node_modules/lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
"downshift": "^9.0.8",
|
"downshift": "^9.0.8",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fetch-retry": "^6.0.0",
|
"fetch-retry": "^6.0.0",
|
||||||
"framer-motion": "^11.3.28",
|
"framer-motion": "^11.3.28",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
@@ -58,7 +59,6 @@
|
|||||||
"immer": "10.1.1",
|
"immer": "10.1.1",
|
||||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||||
"libphonenumber-js": "^1.10.60",
|
"libphonenumber-js": "^1.10.60",
|
||||||
"lodash.isequal": "^4.5.0",
|
|
||||||
"next": "^14.2.18",
|
"next": "^14.2.18",
|
||||||
"next-auth": "^5.0.0-beta.19",
|
"next-auth": "^5.0.0-beta.19",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
@@ -82,7 +82,6 @@
|
|||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/json-stable-stringify-without-jsonify": "^1.0.2",
|
"@types/json-stable-stringify-without-jsonify": "^1.0.2",
|
||||||
"@types/lodash.isequal": "^4.5.8",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import isEqual from "lodash.isequal"
|
import isEqual from "fast-deep-equal"
|
||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
import { breakfastPackageSchema } from "@/server/routers/hotels/output"
|
|
||||||
|
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
|
||||||
import { CurrencyEnum } from "@/types/enums/currency"
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
import { StepEnum } from "@/types/enums/step"
|
import { StepEnum } from "@/types/enums/step"
|
||||||
import type { DetailsState, RoomRate } from "@/types/stores/enter-details"
|
import type { DetailsState, RoomRate } from "@/types/stores/enter-details"
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface PromoProps {
|
||||||
|
buttonText: string
|
||||||
|
text: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { RouterOutput } from "@/lib/trpc/client"
|
||||||
|
|
||||||
|
export interface RoomProps {
|
||||||
|
booking: RouterOutput["booking"]["confirmation"]["booking"]
|
||||||
|
img: NonNullable<
|
||||||
|
RouterOutput["booking"]["confirmation"]["hotel"]["included"]
|
||||||
|
>[number]["images"][number]
|
||||||
|
roomName: NonNullable<
|
||||||
|
RouterOutput["booking"]["confirmation"]["hotel"]["included"]
|
||||||
|
>[number]["name"]
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import type { Packages } from "@/types/requests/packages"
|
|
||||||
import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability"
|
import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability"
|
||||||
|
|
||||||
export interface ClientSummaryProps
|
export interface ClientSummaryProps
|
||||||
|
|||||||
6
types/components/hotelReservation/sidePanel.ts
Normal file
6
types/components/hotelReservation/sidePanel.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { sidePanelVariants } from "@/components/HotelReservation/SidePanel/variants"
|
||||||
|
|
||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
export interface SidePanelProps
|
||||||
|
extends VariantProps<typeof sidePanelVariants> {}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { RoomPackageCodeEnum } from "./selectRate/roomFilter"
|
|
||||||
|
|
||||||
import type { Packages } from "@/types/requests/packages"
|
import type { Packages } from "@/types/requests/packages"
|
||||||
import type { DetailsState, Price } from "@/types/stores/enter-details"
|
import type { DetailsState, Price } from "@/types/stores/enter-details"
|
||||||
import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability"
|
import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability"
|
||||||
import type { BedTypeSchema } from "./enterDetails/bedType"
|
|
||||||
import type { BreakfastPackage } from "./enterDetails/breakfast"
|
|
||||||
import type { Child } from "./selectRate/selectRate"
|
import type { Child } from "./selectRate/selectRate"
|
||||||
|
|
||||||
export type RoomsData = Pick<DetailsState, "roomPrice"> &
|
export type RoomsData = Pick<DetailsState, "roomPrice"> &
|
||||||
@@ -15,25 +11,8 @@ export type RoomsData = Pick<DetailsState, "roomPrice"> &
|
|||||||
packages: Packages | null
|
packages: Packages | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SharedSummaryProps {
|
export interface SummaryProps
|
||||||
fromDate: string
|
extends Pick<RoomAvailability, "cancellationText" | "rateDetails">,
|
||||||
toDate: string
|
Pick<RoomAvailability["selectedRoom"], "roomType"> {
|
||||||
}
|
isMember: boolean
|
||||||
|
|
||||||
export interface SummaryProps extends SharedSummaryProps {
|
|
||||||
bedType: BedTypeSchema | undefined
|
|
||||||
breakfast: BreakfastPackage | false | undefined
|
|
||||||
showMemberPrice: boolean
|
|
||||||
room: RoomsData
|
|
||||||
toggleSummaryOpen?: () => void
|
|
||||||
totalPrice: Price
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SummaryPageProps extends SharedSummaryProps {
|
|
||||||
adults: number
|
|
||||||
hotelId: string
|
|
||||||
kids: Child[] | undefined
|
|
||||||
packageCodes: RoomPackageCodeEnum[] | undefined
|
|
||||||
rateCode: string
|
|
||||||
roomTypeCode: string
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
utils/getBookedHotelRoom.ts
Normal file
23
utils/getBookedHotelRoom.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { RouterOutput } from "@/lib/trpc/client"
|
||||||
|
|
||||||
|
export function getBookedHotelRoom(
|
||||||
|
hotel: RouterOutput["booking"]["confirmation"]["hotel"],
|
||||||
|
roomTypeCode: string
|
||||||
|
) {
|
||||||
|
const room = hotel.included?.find((include) => {
|
||||||
|
return include.roomTypes.find((roomType) => roomType.code === roomTypeCode)
|
||||||
|
})
|
||||||
|
if (!room) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const bedType = room.roomTypes.find(
|
||||||
|
(roomType) => roomType.code === roomTypeCode
|
||||||
|
)
|
||||||
|
if (!bedType) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
bedType,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user