Merged in feat/sw-1602-preliminary-receipt (pull request #1595)
feat/sw-1602 preliminary receipt * feat(sw-1602): create page for preliminary receipt * Add link to my stay page Approved-by: Pontus Dreij
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { notFound } from "next/navigation"
|
||||
import { Suspense } from "react"
|
||||
|
||||
import { MyStaySkeleton } from "@/components/HotelReservation/MyStay/myStaySkeleton"
|
||||
import { Receipt } from "@/components/HotelReservation/MyStay/Receipt"
|
||||
|
||||
import type { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
export default async function ReceiptPage({
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, { RefId?: string }>) {
|
||||
if (!searchParams.RefId) {
|
||||
notFound()
|
||||
}
|
||||
return (
|
||||
<Suspense fallback={<MyStaySkeleton />}>
|
||||
<Receipt refId={searchParams.RefId} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
63
apps/scandic-web/app/[lang]/(no-layout)/layout.tsx
Normal file
63
apps/scandic-web/app/[lang]/(no-layout)/layout.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import "@scandic-hotels/design-system/fonts.css"
|
||||
import "@/app/globals.css"
|
||||
import "@/public/_static/css/design-system-new-deprecated.css"
|
||||
import "@scandic-hotels/design-system/style.css"
|
||||
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
|
||||
import Script from "next/script"
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
|
||||
import TrpcProvider from "@/lib/trpc/Provider"
|
||||
|
||||
import { SessionRefresher } from "@/components/Auth/TokenRefresher"
|
||||
import CookieBotConsent from "@/components/CookieBot"
|
||||
import StorageCleaner from "@/components/HotelReservation/EnterDetails/StorageCleaner"
|
||||
import { ToastHandler } from "@/components/TempDesignSystem/Toasts"
|
||||
import AdobeSDKScript from "@/components/TrackingSDK/AdobeSDKScript"
|
||||
import GTMScript from "@/components/TrackingSDK/GTMScript"
|
||||
import RouterTracking from "@/components/TrackingSDK/RouterTracking"
|
||||
import { getIntl } from "@/i18n"
|
||||
import ServerIntlProvider from "@/i18n/Provider"
|
||||
|
||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
||||
const { defaultLocale, locale, messages } = await getIntl()
|
||||
|
||||
return (
|
||||
<>
|
||||
<head>
|
||||
<AdobeSDKScript />
|
||||
<GTMScript />
|
||||
<Script
|
||||
strategy="beforeInteractive"
|
||||
data-blockingmode="auto"
|
||||
data-cbid="6d539de8-3e67-4f0f-a0df-8cef9070f712"
|
||||
data-culture="@cultureCode"
|
||||
id="Cookiebot"
|
||||
src="https://consent.cookiebot.com/uc.js"
|
||||
/>
|
||||
<Script id="ensure-adobeDataLayer">{`
|
||||
window.adobeDataLayer = window.adobeDataLayer || []
|
||||
`}</Script>
|
||||
</head>
|
||||
<body className="scandic">
|
||||
<SessionProvider basePath="/api/web/auth">
|
||||
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
|
||||
<TrpcProvider>
|
||||
<RouterTracking />
|
||||
{children}
|
||||
<ToastHandler />
|
||||
<SessionRefresher />
|
||||
<StorageCleaner />
|
||||
<CookieBotConsent />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</TrpcProvider>
|
||||
</ServerIntlProvider>
|
||||
</SessionProvider>
|
||||
</body>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -25,13 +25,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.actionPanel .menu .button {
|
||||
.actionPanel .menu .button,
|
||||
.actionLink {
|
||||
width: 100%;
|
||||
color: var(--Scandic-Brand-Burgundy);
|
||||
justify-content: space-between !important;
|
||||
padding: var(--Spacing-x1) 0 !important;
|
||||
}
|
||||
|
||||
.actionLink {
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 100%;
|
||||
background-color: var(--Base-Background-Primary-Normal);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useIntl } from "react-intl"
|
||||
|
||||
import { BookingStatusEnum } from "@/constants/booking"
|
||||
import { customerService } from "@/constants/currentWebHrefs"
|
||||
import { preliminaryReceipt } from "@/constants/routes/myStay"
|
||||
|
||||
import AddToCalendar from "@/components/HotelReservation/AddToCalendar"
|
||||
import { generateDateTime } from "@/components/HotelReservation/BookingConfirmation/Header/Actions/helpers"
|
||||
@@ -128,15 +129,16 @@ export default function ActionPanel({
|
||||
hotelName={hotel.name}
|
||||
renderButton={(onPress) => <AddToCalendarButton onPress={onPress} />}
|
||||
/>
|
||||
<Button
|
||||
variant="icon"
|
||||
<Link
|
||||
href={preliminaryReceipt[lang]}
|
||||
target="_blank"
|
||||
keepSearchParams
|
||||
className={styles.actionLink}
|
||||
onClick={handleDownloadInvoice}
|
||||
intent="text"
|
||||
className={styles.button}
|
||||
>
|
||||
{intl.formatMessage({ id: "Download invoice" })}
|
||||
<DownloadIcon width={24} height={24} color="burgundy" />
|
||||
</Button>
|
||||
</Link>
|
||||
{showCancelStayButton && (
|
||||
<Button
|
||||
variant="icon"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
.dl {
|
||||
padding: 0 var(--Spacing-x2);
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dl > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.dl dt {
|
||||
color: var(--Text-Tertiary);
|
||||
}
|
||||
|
||||
.rightColumn {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
import { getNumberOfNights } from "@/utils/dateFormatting"
|
||||
|
||||
import styles from "./footer.module.css"
|
||||
|
||||
import type { FooterProps } from "@/types/components/hotelReservation/myStay/receipt"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
|
||||
export default async function Footer({ booking, room }: FooterProps) {
|
||||
const intl = await getIntl()
|
||||
const lang = getLang()
|
||||
|
||||
const petRoomPackage = booking.packages.find(
|
||||
(p) => p.code === RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
|
||||
return (
|
||||
<dl className={styles.dl}>
|
||||
<div>
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Reference number" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>{booking.confirmationNumber}</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Room" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>{room?.name}</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Rate" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>{booking.rateDefinition.title}</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Check-in" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>
|
||||
{dt(booking.checkInDate).locale(lang).format("ddd, D MMM YYYY")}
|
||||
</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Check-out" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>
|
||||
{dt(booking.checkOutDate).locale(lang).format("ddd, D MMM YYYY")}
|
||||
</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.rightColumn}>
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Number of nights" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>
|
||||
{getNumberOfNights(booking.checkInDate, booking.checkOutDate)}
|
||||
</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Number of guests" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>
|
||||
{intl.formatMessage(
|
||||
{ id: "{adults, plural, one {# adult} other {# adults}}" },
|
||||
{ adults: booking.adults }
|
||||
)}
|
||||
{booking.childrenAges.length > 0 && (
|
||||
<>
|
||||
{", "}
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "{children, plural, one {# child} other {# children}}",
|
||||
},
|
||||
{ children: booking.childrenAges.length }
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Bed type" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>{room?.bedType.mainBed.description}</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
{petRoomPackage && (
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Room classification" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>{intl.formatMessage({ id: "Pet-friendly" })}</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Breakfast" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>
|
||||
{booking.rateDefinition.breakfastIncluded
|
||||
? intl.formatMessage({ id: "Included" })
|
||||
: intl.formatMessage({ id: "Not included" })}
|
||||
</dd>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</dl>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./specification.module.css"
|
||||
|
||||
import type { SpecificationProps } from "@/types/components/hotelReservation/myStay/receipt"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
|
||||
export default async function Specification({
|
||||
ancillaryPackages,
|
||||
booking,
|
||||
currency,
|
||||
}: SpecificationProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
const breakfastPackages = booking.packages.filter(
|
||||
(p) => p.type === "Breakfast"
|
||||
)
|
||||
const breakfastTotalPriceInMoney = breakfastPackages
|
||||
.filter((p) => p.currency !== "Points")
|
||||
.reduce((acc, curr) => acc + curr.totalPrice, 0)
|
||||
const breakfastTotalPriceInPoints = breakfastPackages
|
||||
.filter((p) => p.currency === "Points")
|
||||
.reduce((acc, curr) => acc + curr.totalPrice, 0)
|
||||
|
||||
const breakfastCount = breakfastPackages.reduce(
|
||||
(acc, curr) => acc + curr.unit,
|
||||
0
|
||||
)
|
||||
|
||||
const breakfastMoneyString =
|
||||
breakfastTotalPriceInMoney > 0
|
||||
? `${breakfastTotalPriceInMoney} ${currency}`
|
||||
: ""
|
||||
const breakfastPointsString =
|
||||
breakfastTotalPriceInPoints > 0
|
||||
? `${breakfastTotalPriceInPoints} ${intl.formatMessage({ id: "Points" })}`
|
||||
: ""
|
||||
const breakfastPlusString =
|
||||
breakfastMoneyString && breakfastPointsString ? " + " : ""
|
||||
|
||||
const petRoomPackage = booking.packages.find(
|
||||
(p) => p.code === RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
|
||||
const roomPriceExclVat =
|
||||
booking.roomPrice - (booking.roomPrice * booking.vatPercentage) / 100
|
||||
const roomPriceVat = booking.roomPrice - roomPriceExclVat
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
{/****** The room ********/}
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>
|
||||
{intl.formatMessage({ id: "Accommodation" })}
|
||||
{!booking.rateDefinition.mustBeGuaranteed && (
|
||||
<>
|
||||
{" - "}
|
||||
{intl.formatMessage({ id: "Room is prepaid" })}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</Typography>
|
||||
<dl className={styles.dl}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>{intl.formatMessage({ id: "Price including VAT" })}</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>
|
||||
{!booking.rateDefinition.mustBeGuaranteed &&
|
||||
`(${intl.formatMessage({ id: "PREPAID" })}) `}
|
||||
{`${booking.roomPrice} ${currency}`}
|
||||
</dd>
|
||||
</Typography>
|
||||
|
||||
{petRoomPackage && (
|
||||
<>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>
|
||||
{intl.formatMessage({ id: "Pet room charge including VAT" })}
|
||||
</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>{`${petRoomPackage.totalPrice} ${petRoomPackage.currency}`}</dd>
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt className={styles.tertiary}>
|
||||
{intl.formatMessage({ id: "Price excluding VAT" })}
|
||||
</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd
|
||||
className={styles.tertiary}
|
||||
>{`${roomPriceExclVat.toFixed(2)} ${currency}`}</dd>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt className={styles.tertiary}>
|
||||
{intl.formatMessage({ id: "VAT" })} {booking.vatPercentage} %
|
||||
</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd
|
||||
className={styles.tertiary}
|
||||
>{`${roomPriceVat.toFixed(2)} ${currency}`}</dd>
|
||||
</Typography>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{/****** Ancillaries ********/}
|
||||
{booking.ancillaries.map((ancillary) => (
|
||||
<>
|
||||
<Divider color="subtle" />
|
||||
<div>
|
||||
<div className={styles.quantifyingHeader}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>
|
||||
{ancillaryPackages?.flatMap((a) =>
|
||||
a.ancillaryContent.filter(
|
||||
(aa) =>
|
||||
aa.id === ancillary.code ||
|
||||
aa.loyaltyCode === ancillary.code
|
||||
)
|
||||
)[0]?.title ?? intl.formatMessage({ id: "Unknown item" })}
|
||||
</span>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{`x ${ancillary.unit}`}</span>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<dl className={styles.dl}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>
|
||||
{ancillary.currency !== "Points"
|
||||
? intl.formatMessage({ id: "Price including VAT" })
|
||||
: intl.formatMessage({ id: "Price" })}
|
||||
</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>{`${ancillary.totalPrice} ${ancillary.currency}`}</dd>
|
||||
</Typography>
|
||||
</dl>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
|
||||
{/****** Breakfast ********/}
|
||||
{breakfastCount > 0 && (
|
||||
<>
|
||||
<Divider color="subtle" />
|
||||
<div>
|
||||
<div className={styles.quantifyingHeader}>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{intl.formatMessage({ id: "Breakfast" })}</span>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<span>{`x ${breakfastCount}`}</span>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<dl className={styles.dl}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dt>
|
||||
{breakfastTotalPriceInMoney > 0
|
||||
? intl.formatMessage({ id: "Price including VAT" })
|
||||
: intl.formatMessage({ id: "Price" })}
|
||||
</dt>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<dd>{`${breakfastMoneyString}${breakfastPlusString}${breakfastPointsString}`}</dd>
|
||||
</Typography>
|
||||
</dl>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
.container {
|
||||
display: flex;
|
||||
padding: 0 var(--Spacing-x2);
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.tertiary {
|
||||
color: var(--Text-Tertiary);
|
||||
}
|
||||
|
||||
.dl {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
gap: var(--Spacing-x-half);
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.dl dd {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.quantifyingHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./total.module.css"
|
||||
|
||||
import type { TotalProps } from "@/types/components/hotelReservation/myStay/receipt"
|
||||
|
||||
export default async function Total({ booking, currency }: TotalProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
const totalPriceInMoney = booking.totalPrice
|
||||
const totalPriceInMoneyExclVat = booking.totalPriceExVat
|
||||
const totalVat = booking.vatAmount
|
||||
const totalPriceInPoints = booking.ancillaries
|
||||
.filter((a) => a.currency === "Points")
|
||||
.reduce((acc, curr) => acc + curr.totalPrice, 0)
|
||||
|
||||
const moneyString =
|
||||
totalPriceInMoney > 0 ? `${totalPriceInMoney} ${currency}` : ""
|
||||
const pointsString =
|
||||
totalPriceInPoints > 0
|
||||
? `${totalPriceInPoints} ${intl.formatMessage({ id: "Points" })}`
|
||||
: ""
|
||||
const plusString = moneyString && pointsString ? " + " : ""
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/****** Total ********/}
|
||||
<div className={styles.totalContainer}>
|
||||
<Typography>
|
||||
<div>
|
||||
<span className={styles.title}>
|
||||
{intl.formatMessage({ id: "Preliminary receipt" })}
|
||||
</span>
|
||||
<span className={styles.titleSubtext}>
|
||||
{intl.formatMessage({
|
||||
id: "Final VAT breakdown will be provided at check-out.",
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</Typography>
|
||||
|
||||
<Divider color="subtle" />
|
||||
<dl className={styles.dl}>
|
||||
<Typography>
|
||||
<dt>{intl.formatMessage({ id: "Total including VAT" })}</dt>
|
||||
</Typography>
|
||||
<Typography>
|
||||
<dd>{`${moneyString}${plusString}${pointsString}`}</dd>
|
||||
</Typography>
|
||||
|
||||
{totalPriceInMoney > 0 && (
|
||||
<>
|
||||
<Typography>
|
||||
<dt className={styles.tertiary}>
|
||||
{intl.formatMessage({ id: "Total excluding VAT" })}
|
||||
</dt>
|
||||
</Typography>
|
||||
<Typography>
|
||||
<dd
|
||||
className={styles.tertiary}
|
||||
>{`${totalPriceInMoneyExclVat} ${currency}`}</dd>
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
|
||||
{totalPriceInMoney > 0 && (
|
||||
<>
|
||||
<Typography>
|
||||
<dt className={styles.tertiary}>
|
||||
{intl.formatMessage({ id: "VAT" })}
|
||||
</dt>
|
||||
</Typography>
|
||||
<Typography>
|
||||
<dd className={styles.tertiary}>{`${totalVat} ${currency}`}</dd>
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
.tertiary {
|
||||
color: var(--Text-Tertiary);
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--Text-Tertiary);
|
||||
font-weight: var(--Body-Supporting-text-Font-weight-2);
|
||||
font-size: var(--Body-Supporting-text-Size);
|
||||
margin-right: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.titleSubtext {
|
||||
color: var(--Text-Tertiary);
|
||||
font-size: var(--Body-Supporting-text-Size);
|
||||
}
|
||||
|
||||
.totalContainer {
|
||||
border: 1px solid var(--Border-Divider-Subtle);
|
||||
border-radius: var(--Spacing-x1);
|
||||
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2) var(--Spacing-x2)
|
||||
var(--Spacing-x2);
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dl {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
gap: var(--Spacing-x-half);
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.dl dd {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { cookies } from "next/headers"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
import {
|
||||
getAncillaryPackages,
|
||||
getBookingConfirmation,
|
||||
getProfileSafely,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
import { decrypt } from "@/server/routers/utils/encryption"
|
||||
|
||||
import { ScandicLogoIcon } from "@/components/Icons"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import AdditionalInfoForm from "../../FindMyBooking/AdditionalInfoForm"
|
||||
import accessBooking, {
|
||||
ACCESS_GRANTED,
|
||||
ERROR_BAD_REQUEST,
|
||||
ERROR_UNAUTHORIZED,
|
||||
} from "../accessBooking"
|
||||
import Footer from "./Footer"
|
||||
import Specification from "./Specification"
|
||||
import Total from "./Total"
|
||||
|
||||
import styles from "./receipt.module.css"
|
||||
|
||||
export async function Receipt({ refId }: { refId: string }) {
|
||||
const value = decrypt(refId)
|
||||
if (!value) {
|
||||
return notFound()
|
||||
}
|
||||
const [confirmationNumber, lastName] = value.split(",")
|
||||
const bookingConfirmation = await getBookingConfirmation(confirmationNumber)
|
||||
if (!bookingConfirmation) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const { booking, hotel, room } = bookingConfirmation
|
||||
const user = await getProfileSafely()
|
||||
const bv = cookies().get("bv")?.value
|
||||
const intl = await getIntl()
|
||||
|
||||
const access = accessBooking(booking.guest, lastName, user, bv)
|
||||
|
||||
if (access === ACCESS_GRANTED) {
|
||||
const ancillaryPackages = await getAncillaryPackages({
|
||||
fromDate: dt(booking.checkInDate).format("YYYY-MM-DD"),
|
||||
hotelId: hotel.operaId,
|
||||
toDate: dt(booking.checkOutDate).format("YYYY-MM-DD"),
|
||||
})
|
||||
|
||||
const currency =
|
||||
booking.currencyCode !== "Points"
|
||||
? booking.currencyCode
|
||||
: (booking.ancillaries.find((a) => a.currency !== "Points")?.currency ??
|
||||
booking.packages.find((p) => p.currency !== "Points")?.currency)
|
||||
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div>
|
||||
<ScandicLogoIcon width="89px" height="19px" color="red" />
|
||||
<div className={styles.addresses}>
|
||||
<div>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div>{hotel.name}</div>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div>
|
||||
{`${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`}
|
||||
</div>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div className={`${styles.tertiary} ${styles.addressMargin}`}>
|
||||
{hotel.contactInformation.email}
|
||||
</div>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div className={styles.tertiary}>
|
||||
{hotel.contactInformation.phoneNumber}
|
||||
</div>
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.rightColumn}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div>{`${booking.guest.firstName} ${booking.guest.lastName}`}</div>
|
||||
</Typography>
|
||||
{booking.guest.membershipNumber && (
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div>{`${intl.formatMessage({ id: "Member" })} ${booking.guest.membershipNumber}`}</div>
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div className={`${styles.tertiary} ${styles.addressMargin}`}>
|
||||
{booking.guest.email}
|
||||
</div>
|
||||
</Typography>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<div className={styles.tertiary}>
|
||||
{booking.guest.phoneNumber}
|
||||
</div>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Total booking={booking} currency={currency} />
|
||||
<Specification
|
||||
ancillaryPackages={ancillaryPackages}
|
||||
booking={booking}
|
||||
currency={currency}
|
||||
/>
|
||||
|
||||
<hr className={styles.divider} />
|
||||
|
||||
<Footer booking={booking} room={room} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
if (access === ERROR_BAD_REQUEST) {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.form}>
|
||||
<AdditionalInfoForm
|
||||
confirmationNumber={confirmationNumber}
|
||||
lastName={lastName}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
if (access === ERROR_UNAUTHORIZED) {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.logIn}>
|
||||
<Body textAlign="center">
|
||||
{intl.formatMessage({
|
||||
id: "In order to view your booking, please log in.",
|
||||
})}
|
||||
</Body>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
return notFound()
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
.main {
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
display: flex;
|
||||
padding: var(--Spacing-x5) var(--Spacing-x4);
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x5);
|
||||
}
|
||||
|
||||
.addresses {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.rightColumn {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.addressMargin {
|
||||
margin-top: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.tertiary {
|
||||
color: var(--Text-Tertiary);
|
||||
}
|
||||
|
||||
.divider {
|
||||
color: var(--Border-Divider-Accent);
|
||||
}
|
||||
|
||||
.form {
|
||||
max-width: 640px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: var(--Spacing-x5) 0;
|
||||
}
|
||||
|
||||
.logIn {
|
||||
padding: var(--Spacing-x5) var(--Spacing-x2);
|
||||
}
|
||||
@@ -12,3 +12,13 @@ export const myStay = {
|
||||
no: "/no/hotelreservation/min-ophold",
|
||||
sv: "/sv/hotelreservation/min-vistelse",
|
||||
}
|
||||
|
||||
/** @type {import('@/types/routes').LangRoute} */
|
||||
export const preliminaryReceipt = {
|
||||
da: `${myStay.da}/receipt`,
|
||||
de: `${myStay.de}/receipt`,
|
||||
en: `${myStay.en}/receipt`,
|
||||
fi: `${myStay.fi}/receipt`,
|
||||
no: `${myStay.no}/receipt`,
|
||||
sv: `${myStay.sv}/receipt`,
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"Accessibility": "Tilgængelighed",
|
||||
"Accessibility at {hotel}": "Tilgængelighed på {hotel}",
|
||||
"Accessibility room": "Tilgængelighedsrum",
|
||||
"Accommodation": "Indkvartering",
|
||||
"Account unlinked, reloading": "Account unlinked, reloading",
|
||||
"Accounts are already linked": "Accounts are already linked",
|
||||
"Active": "Aktiv",
|
||||
@@ -277,6 +278,7 @@
|
||||
"Filter": "Filter",
|
||||
"Filter and sort": "Filtrer og sorter",
|
||||
"Filter by": "Filtrer efter",
|
||||
"Final VAT breakdown will be provided at check-out.": "Endelig momsopgørelse gives ved check-out.",
|
||||
"Find": "Finde",
|
||||
"Find booking": "Find booking",
|
||||
"Find hotels": "Find hotel",
|
||||
@@ -440,6 +442,7 @@
|
||||
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
|
||||
"Meetings & Conferences": "Møder & Konferencer",
|
||||
"Meetings, Conferences & Events": "Møder, Konferencer & Arrangementer",
|
||||
"Member": "Medlem",
|
||||
"Member Since: {value}": "Member Since: {value}",
|
||||
"Member discount": "Member discount",
|
||||
"Member no. {nr}": "Medlemsnummer {nr}",
|
||||
@@ -515,7 +518,10 @@
|
||||
"Non-refundable": "Ikke-refunderbart",
|
||||
"Nordic Swan Ecolabel": "Svanemærket",
|
||||
"Not found": "Ikke fundet",
|
||||
"Not included": "Ikke inkluderet",
|
||||
"Number of charging points for electric cars: {number}": "Antal ladepunkter til elbiler: {number}",
|
||||
"Number of guests": "Antal gæster",
|
||||
"Number of nights": "Antal nætter",
|
||||
"Number of parking spots: {number}": "Antal parkeringspladser: {number}",
|
||||
"Number: {membershipNumber}": "Number: {membershipNumber}",
|
||||
"OK": "OK",
|
||||
@@ -540,6 +546,7 @@
|
||||
"Outdoor pool": "Udendørs pool",
|
||||
"Overview": "Oversigt",
|
||||
"PETR": "Kæledyr",
|
||||
"PREPAID": "FORUDBETALT",
|
||||
"Paid": "Betalt",
|
||||
"Parking": "Parkering",
|
||||
"Parking / Garage": "Parkering / Garage",
|
||||
@@ -564,6 +571,8 @@
|
||||
"Payment will be made on check-in. The card will be only used to guarantee the ancillary in case of no-show.": "Betaling vil ske ved check-in. Kortet vil kun blive brugt til at garantere tillægget i tilfælde af en no-show.",
|
||||
"Per night from": "Per nat fra",
|
||||
"Pet room": "Kæledyrsrum",
|
||||
"Pet room charge including VAT": "Gebyr for kæledyrsværelse inkl. moms",
|
||||
"Pet-friendly": "Kæledyrsvenlig",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Kæledyrsrum har en ekstra gebyr på 20 EUR per ophold",
|
||||
"Phone": "Telefon",
|
||||
"Phone is required": "Telefonnummer er påkrævet",
|
||||
@@ -586,6 +595,7 @@
|
||||
"Points needed to stay on level": "Point nødvendige for at holde sig på niveau",
|
||||
"Points used": "Points used",
|
||||
"Practical information": "Praktisk information",
|
||||
"Preliminary receipt": "Foreløbig kvittering",
|
||||
"Previous": "Forudgående",
|
||||
"Previous victories": "Tidligere sejre",
|
||||
"Price": "Pris",
|
||||
@@ -613,6 +623,7 @@
|
||||
"Public price from": "Offentlig pris fra",
|
||||
"Public transport": "Offentlig transport",
|
||||
"Queen bed": "Queensize-seng",
|
||||
"Rate": "Pris",
|
||||
"Rate details": "Oplysninger om værelsespris",
|
||||
"Read more": "Læs mere",
|
||||
"Rebooking": "Rebooking",
|
||||
@@ -641,8 +652,10 @@
|
||||
"Room & Terms": "Værelse & Vilkår",
|
||||
"Room amenities": "Værelsesfaciliteter",
|
||||
"Room charge": "Værelsesafgift",
|
||||
"Room classification": "Værelsesklassifikation",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Værelsesfaciliteter",
|
||||
"Room is prepaid": "Værelset er forudbetalt",
|
||||
"Room sold out": "Værelse solgt ud",
|
||||
"Room total": "Værelse total",
|
||||
"Room type": "Værelsestype",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"Accessibility": "Zugänglichkeit",
|
||||
"Accessibility at {hotel}": "Barrierefreiheit im {hotel}",
|
||||
"Accessibility room": "Barrierefreies Zimmer",
|
||||
"Accommodation": "Unterkunft",
|
||||
"Account unlinked, reloading": "Account unlinked, reloading",
|
||||
"Accounts are already linked": "Accounts are already linked",
|
||||
"Active": "Aktiv",
|
||||
@@ -278,6 +279,7 @@
|
||||
"Filter": "Filter",
|
||||
"Filter and sort": "Filtern und sortieren",
|
||||
"Filter by": "Filtern nach",
|
||||
"Final VAT breakdown will be provided at check-out.": "Die endgültige Mehrwertsteueraufstellung wird beim Check-out bereitgestellt.",
|
||||
"Find": "Finden",
|
||||
"Find booking": "Buchung finden",
|
||||
"Find hotels": "Hotels finden",
|
||||
@@ -441,6 +443,7 @@
|
||||
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
|
||||
"Meetings & Conferences": "Tagungen & Konferenzen",
|
||||
"Meetings, Conferences & Events": "Møder, Konferencer & Arrangementer",
|
||||
"Member": "Mitglied",
|
||||
"Member Since: {value}": "Member Since: {value}",
|
||||
"Member discount": "Member discount",
|
||||
"Member no. {nr}": "Mitgliedsnummer {nr}",
|
||||
@@ -516,7 +519,10 @@
|
||||
"Non-refundable": "Nicht erstattungsfähig",
|
||||
"Nordic Swan Ecolabel": "Nordic Swan Ecolabel",
|
||||
"Not found": "Nicht gefunden",
|
||||
"Not included": "Nicht inbegriffen",
|
||||
"Number of charging points for electric cars: {number}": "Anzahl der Ladestationen für Elektroautos: {number}",
|
||||
"Number of guests": "Anzahl der Gäste",
|
||||
"Number of nights": "Anzahl der Nächte",
|
||||
"Number of parking spots: {number}": "Anzahl der Parkplätze: {number}",
|
||||
"Number: {membershipNumber}": "Number: {membershipNumber}",
|
||||
"OK": "OK",
|
||||
@@ -540,6 +546,7 @@
|
||||
"Outdoor pool": "Außenpool",
|
||||
"Overview": "Übersicht",
|
||||
"PETR": "Haustier",
|
||||
"PREPAID": "VORAUSBEZAHLT",
|
||||
"Paid": "Bezahlt",
|
||||
"Parking": "Parken",
|
||||
"Parking / Garage": "Parken / Garage",
|
||||
@@ -563,6 +570,8 @@
|
||||
"Payment will be made on check-in. The card will be only used to guarantee the ancillary in case of no-show.": "Die Zahlung erfolgt beim Check-in. Die Karte wird nur zur Garantie der Nebenkosten im Falle eines No-Shows verwendet.",
|
||||
"Per night from": "Pro Nacht ab",
|
||||
"Pet room": "Haustierzimmer",
|
||||
"Pet room charge including VAT": "Haustierzimmergebühr inkl. MwSt.",
|
||||
"Pet-friendly": "Haustierfreundlich",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Haustierzimmer haben einen zusätzlichen Preis von 20 EUR pro Aufenthalt",
|
||||
"Phone": "Telefon",
|
||||
"Phone is required": "Telefon ist erforderlich",
|
||||
@@ -585,6 +594,7 @@
|
||||
"Points needed to stay on level": "Erforderliche Punkte, um auf diesem Level zu bleiben",
|
||||
"Points used": "Points used",
|
||||
"Practical information": "Praktische Informationen",
|
||||
"Preliminary receipt": "Vorläufige Quittung",
|
||||
"Previous": "Früher",
|
||||
"Previous victories": "Bisherige Siege",
|
||||
"Price": "Preis",
|
||||
@@ -612,6 +622,7 @@
|
||||
"Public price from": "Öffentlicher Preis ab",
|
||||
"Public transport": "Öffentliche Verkehrsmittel",
|
||||
"Queen bed": "Queensize-Bett",
|
||||
"Rate": "Preis",
|
||||
"Rate details": "Preisdetails",
|
||||
"Read more": "Lesen Sie mehr und reservieren Sie einen Tisch",
|
||||
"Rebooking": "Rebooking",
|
||||
@@ -640,8 +651,10 @@
|
||||
"Room & Terms": "Zimmer & Bedingungen",
|
||||
"Room amenities": "Zimmerausstattung",
|
||||
"Room charge": "Zimmerpreis",
|
||||
"Room classification": "Zimmerkategorie",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Zimmerausstattung",
|
||||
"Room is prepaid": "Zimmer ist vorausbezahlt",
|
||||
"Room sold out": "Zimmer verkauft",
|
||||
"Room total": "Zimmer total",
|
||||
"Room type": "Zimmertyp",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"Accessibility": "Accessibility",
|
||||
"Accessibility at {hotel}": "Accessibility at {hotel}",
|
||||
"Accessibility room": "Accessibility room",
|
||||
"Accommodation": "Accommodation",
|
||||
"Account unlinked, reloading": "Account unlinked, reloading",
|
||||
"Accounts are already linked": "Accounts are already linked",
|
||||
"Active": "Active",
|
||||
@@ -276,6 +277,7 @@
|
||||
"Filter": "Filter",
|
||||
"Filter and sort": "Filter and sort",
|
||||
"Filter by": "Filter by",
|
||||
"Final VAT breakdown will be provided at check-out.": "Final VAT breakdown will be provided at check-out.",
|
||||
"Find": "Find",
|
||||
"Find booking": "Find booking",
|
||||
"Find hotels": "Find hotels",
|
||||
@@ -439,6 +441,7 @@
|
||||
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
|
||||
"Meetings & Conferences": "Meetings & Conferences",
|
||||
"Meetings, Conferences & Events": "Meetings, Conferences & Events",
|
||||
"Member": "Member",
|
||||
"Member Since: {value}": "Member Since: {value}",
|
||||
"Member discount": "Member discount",
|
||||
"Member no. {nr}": "Member no. {nr}",
|
||||
@@ -514,7 +517,10 @@
|
||||
"Non-refundable": "Non-refundable",
|
||||
"Nordic Swan Ecolabel": "Nordic Swan Ecolabel",
|
||||
"Not found": "Not found",
|
||||
"Not included": "Not included",
|
||||
"Number of charging points for electric cars: {number}": "Number of charging points for electric cars: {number}",
|
||||
"Number of guests": "Number of guests",
|
||||
"Number of nights": "Number of nights",
|
||||
"Number of parking spots: {number}": "Number of parking spots: {number}",
|
||||
"Number: {membershipNumber}": "Number: {membershipNumber}",
|
||||
"OK": "OK",
|
||||
@@ -539,6 +545,7 @@
|
||||
"Outdoor pool": "Outdoor pool",
|
||||
"Overview": "Overview",
|
||||
"PETR": "Pet",
|
||||
"PREPAID": "PREPAID",
|
||||
"Paid": "Paid",
|
||||
"Parking": "Parking",
|
||||
"Parking / Garage": "Parking / Garage",
|
||||
@@ -562,6 +569,8 @@
|
||||
"Payment will be made on check-in. The card will be only used to guarantee the ancillary in case of no-show.": "Payment will be made on check-in. The card will be only used to guarantee the ancillary in case of no-show.",
|
||||
"Per night from": "Per night from",
|
||||
"Pet room": "Pet room",
|
||||
"Pet room charge including VAT": "Pet room charge including VAT",
|
||||
"Pet-friendly": "Pet-friendly",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Pet-friendly rooms have an additional fee of 20 EUR per stay",
|
||||
"Phone": "Phone",
|
||||
"Phone is required": "Phone is required",
|
||||
@@ -584,6 +593,7 @@
|
||||
"Points needed to stay on level": "Points needed to stay on level",
|
||||
"Points used": "Points used",
|
||||
"Practical information": "Practical information",
|
||||
"Preliminary receipt": "Preliminary receipt",
|
||||
"Previous": "Previous",
|
||||
"Previous victories": "Previous victories",
|
||||
"Price": "Price",
|
||||
@@ -611,6 +621,7 @@
|
||||
"Public price from": "Public price from",
|
||||
"Public transport": "Public transport",
|
||||
"Queen bed": "Queen bed",
|
||||
"Rate": "Rate",
|
||||
"Rate details": "Rate details",
|
||||
"Read more": "Read more",
|
||||
"Rebooking": "Rebooking",
|
||||
@@ -639,8 +650,10 @@
|
||||
"Room & Terms": "Room & Terms",
|
||||
"Room amenities": "Room amenities",
|
||||
"Room charge": "Room charge",
|
||||
"Room classification": "Room classification",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Room facilities",
|
||||
"Room is prepaid": "Room is prepaid",
|
||||
"Room sold out": "Room sold out",
|
||||
"Room total": "Room total",
|
||||
"Room type": "Room type",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"Accessibility": "Saavutettavuus",
|
||||
"Accessibility at {hotel}": "Esteettömyys {hotel}",
|
||||
"Accessibility room": "Esteetön huone",
|
||||
"Accommodation": "Majoitus",
|
||||
"Account unlinked, reloading": "Account unlinked, reloading",
|
||||
"Accounts are already linked": "Accounts are already linked",
|
||||
"Active": "Aktiivinen",
|
||||
@@ -277,6 +278,7 @@
|
||||
"Filter": "Suodatin",
|
||||
"Filter and sort": "Suodata ja lajittele",
|
||||
"Filter by": "Suodatusperuste",
|
||||
"Final VAT breakdown will be provided at check-out.": "Lopullinen ALV-erittely annetaan uloskirjautumisen yhteydessä.",
|
||||
"Find": "Löytää",
|
||||
"Find booking": "Etsi varaus",
|
||||
"Find hotels": "Etsi hotelleja",
|
||||
@@ -440,6 +442,7 @@
|
||||
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
|
||||
"Meetings & Conferences": "Kokoukset & Konferenssit",
|
||||
"Meetings, Conferences & Events": "Tagungen, Konferenzen & Veranstaltungen",
|
||||
"Member": "Jäsen",
|
||||
"Member Since: {value}": "Member Since: {value}",
|
||||
"Member discount": "Member discount",
|
||||
"Member no. {nr}": "Jäsenyysnumero {nr}",
|
||||
@@ -515,7 +518,10 @@
|
||||
"Non-refundable": "Ei palautettavissa",
|
||||
"Nordic Swan Ecolabel": "Ympäristömerkki Miljömärkt",
|
||||
"Not found": "Ei löydetty",
|
||||
"Not included": "Ei sisälly",
|
||||
"Number of charging points for electric cars: {number}": "Sähköautojen latauspisteiden määrä: {number}",
|
||||
"Number of guests": "Vieraiden määrä",
|
||||
"Number of nights": "Yöpymisten määrä",
|
||||
"Number of parking spots: {number}": "Pysäköintipaikkojen määrä: {number}",
|
||||
"Number: {membershipNumber}": "Number: {membershipNumber}",
|
||||
"OK": "OK",
|
||||
@@ -539,6 +545,7 @@
|
||||
"Outdoor pool": "Ulkouima-allas",
|
||||
"Overview": "Yleiskatsaus",
|
||||
"PETR": "Lemmikki",
|
||||
"PREPAID": "ETUKÄTEEN MAKSETTU",
|
||||
"Paid": "Maksettu",
|
||||
"Parking": "Pysäköinti",
|
||||
"Parking / Garage": "Pysäköinti / Autotalli",
|
||||
@@ -562,6 +569,8 @@
|
||||
"Payment will be made on check-in. The card will be only used to guarantee the ancillary in case of no-show.": "Maksu suoritetaan sisäänkirjautumisen yhteydessä. Korttia käytetään vain lisäpalvelun varmistamiseen, jos varausmyyntiä ei tapahdu.",
|
||||
"Per night from": "Per yö alkaen",
|
||||
"Pet room": "Lemmikkihuone",
|
||||
"Pet room charge including VAT": "Lemmikkihuoneen maksu sis. ALV",
|
||||
"Pet-friendly": "Lemmikkiystävällinen",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Lemmikkihuoneen lisäkustannus on 20 EUR per majoitus",
|
||||
"Phone": "Puhelin",
|
||||
"Phone is required": "Puhelin vaaditaan",
|
||||
@@ -584,6 +593,7 @@
|
||||
"Points needed to stay on level": "Tällä tasolla pysymiseen tarvittavat pisteet",
|
||||
"Points used": "Points used",
|
||||
"Practical information": "Käytännön tietoa",
|
||||
"Preliminary receipt": "Alustava kuitti",
|
||||
"Previous": "Aikaisempi",
|
||||
"Previous victories": "Edelliset voitot",
|
||||
"Price": "Hinta",
|
||||
@@ -611,6 +621,7 @@
|
||||
"Public price from": "Julkinen hinta alkaen",
|
||||
"Public transport": "Julkinen liikenne",
|
||||
"Queen bed": "Queen-vuode",
|
||||
"Rate": "Hinta",
|
||||
"Rate details": "Hintatiedot",
|
||||
"Read more": "Lue lisää",
|
||||
"Rebooking": "Rebooking",
|
||||
@@ -639,8 +650,10 @@
|
||||
"Room & Terms": "Huone & Ehdot",
|
||||
"Room amenities": "Huoneen mukavuudet",
|
||||
"Room charge": "Huonemaksu",
|
||||
"Room classification": "Huoneluokitus",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Huoneen varustelu",
|
||||
"Room is prepaid": "Huone on maksettu etukäteen",
|
||||
"Room sold out": "Huone slutsattu",
|
||||
"Room total": "Huoneen kokonaishinta",
|
||||
"Room type": "Huonetyyppi",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"Accessibility": "Tilgjengelighet",
|
||||
"Accessibility at {hotel}": "Tilgjengelighet på {hotel}",
|
||||
"Accessibility room": "Tilgjengelighetsrom",
|
||||
"Accommodation": "Overnatting",
|
||||
"Account unlinked, reloading": "Account unlinked, reloading",
|
||||
"Accounts are already linked": "Accounts are already linked",
|
||||
"Active": "Aktiv",
|
||||
@@ -276,6 +277,7 @@
|
||||
"Filter": "Filter",
|
||||
"Filter and sort": "Filtrer og sorter",
|
||||
"Filter by": "Filtrer etter",
|
||||
"Final VAT breakdown will be provided at check-out.": "Endelig MVA-oversikt gis ved utsjekking.",
|
||||
"Find": "Finne",
|
||||
"Find booking": "Finn booking",
|
||||
"Find hotels": "Finn hotell",
|
||||
@@ -439,6 +441,7 @@
|
||||
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
|
||||
"Meetings & Conferences": "Møter & Konferanser",
|
||||
"Meetings, Conferences & Events": "Møter, Konferanser & Arrangementer",
|
||||
"Member": "Medlem",
|
||||
"Member Since: {value}": "Member Since: {value}",
|
||||
"Member discount": "Member discount",
|
||||
"Member no. {nr}": "Medlemsnummer {nr}",
|
||||
@@ -514,7 +517,10 @@
|
||||
"Non-refundable": "Ikke-refunderbart",
|
||||
"Nordic Swan Ecolabel": "Svanemerket",
|
||||
"Not found": "Ikke funnet",
|
||||
"Not included": "Ikke inkludert",
|
||||
"Number of charging points for electric cars: {number}": "Antall ladepunkter for elbiler: {number}",
|
||||
"Number of guests": "Antall gjester",
|
||||
"Number of nights": "Antall netter",
|
||||
"Number of parking spots: {number}": "Antall parkeringsplasser: {number}",
|
||||
"Number: {membershipNumber}": "Number: {membershipNumber}",
|
||||
"OK": "OK",
|
||||
@@ -538,6 +544,7 @@
|
||||
"Outdoor pool": "Utendørs basseng",
|
||||
"Overview": "Oversikt",
|
||||
"PETR": "Kjæledyr",
|
||||
"PREPAID": "FORHÅNDSBETALT",
|
||||
"Paid": "Betalt",
|
||||
"Parking": "Parkering",
|
||||
"Parking / Garage": "Parkering / Garasje",
|
||||
@@ -561,6 +568,8 @@
|
||||
"Payment will be made on check-in. The card will be only used to guarantee the ancillary in case of no-show.": "Betaling vil skje ved innsjekking. Kortet vil kun bli brukt til å garantere tillegget i tilfelle av en no-show.",
|
||||
"Per night from": "Per nat fra",
|
||||
"Pet room": "Kjæledyrsrom",
|
||||
"Pet room charge including VAT": "Kjæledyrromsgebyr inkl. MVA",
|
||||
"Pet-friendly": "Dyrevennlig",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Kjæledyrsrom har en tilleggsavgift på 20 EUR per opphold",
|
||||
"Phone": "Telefon",
|
||||
"Phone is required": "Telefon kreves",
|
||||
@@ -583,6 +592,7 @@
|
||||
"Points needed to stay on level": "Poeng som trengs for å holde seg på nivå",
|
||||
"Points used": "Points used",
|
||||
"Practical information": "Praktisk informasjon",
|
||||
"Preliminary receipt": "Foreløpig kvittering",
|
||||
"Previous": "Tidligere",
|
||||
"Previous victories": "Tidligere seire",
|
||||
"Price": "Pris",
|
||||
@@ -610,6 +620,7 @@
|
||||
"Public price from": "Offentlig pris fra",
|
||||
"Public transport": "Offentlig transport",
|
||||
"Queen bed": "Queen-size-seng",
|
||||
"Rate": "Pris",
|
||||
"Rate details": "Prisdetaljer",
|
||||
"Read more": "Les mer",
|
||||
"Rebooking": "Rebooking",
|
||||
@@ -638,8 +649,10 @@
|
||||
"Room & Terms": "Rom & Vilkår",
|
||||
"Room amenities": "Romfasiliteter",
|
||||
"Room charge": "Pris for rom",
|
||||
"Room classification": "Romklassifisering",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Romfasiliteter",
|
||||
"Room is prepaid": "Rommet er forhåndsbetalt",
|
||||
"Room total": "Rom total",
|
||||
"Room type": "Romtype",
|
||||
"Room {roomIndex}": "Rom {roomIndex}",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"Accessibility": "Tillgänglighet",
|
||||
"Accessibility at {hotel}": "Tillgänglighet på {hotel}",
|
||||
"Accessibility room": "Tillgänglighetsrum",
|
||||
"Accommodation": "Boende",
|
||||
"Account unlinked, reloading": "Account unlinked, reloading",
|
||||
"Accounts are already linked": "Accounts are already linked",
|
||||
"Active": "Aktiv",
|
||||
@@ -276,6 +277,7 @@
|
||||
"Filter": "Filter",
|
||||
"Filter and sort": "Filtrera och sortera",
|
||||
"Filter by": "Filtrera på",
|
||||
"Final VAT breakdown will be provided at check-out.": "Slutlig momsuppdelning tillhandahålls vid utcheckning.",
|
||||
"Find": "Hitta",
|
||||
"Find booking": "Hitta bokning",
|
||||
"Find hotels": "Hitta hotell",
|
||||
@@ -439,6 +441,7 @@
|
||||
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
|
||||
"Meetings & Conferences": "Möten & Konferenser",
|
||||
"Meetings, Conferences & Events": "Möten, Konferenser & Evenemang",
|
||||
"Member": "Medlem",
|
||||
"Member Since: {value}": "Member Since: {value}",
|
||||
"Member discount": "Member discount",
|
||||
"Member no. {nr}": "Medlemsnummer {nr}",
|
||||
@@ -514,7 +517,10 @@
|
||||
"Non-refundable": "Ej återbetalningsbar",
|
||||
"Nordic Swan Ecolabel": "Svanenmärkt",
|
||||
"Not found": "Hittades inte",
|
||||
"Not included": "Ej inkluderat",
|
||||
"Number of charging points for electric cars: {number}": "Antal laddplatser för elbilar: {number}",
|
||||
"Number of guests": "Antal gäster",
|
||||
"Number of nights": "Antal nätter",
|
||||
"Number of parking spots: {number}": "Antal parkeringsplatser: {number}",
|
||||
"Number: {membershipNumber}": "Number: {membershipNumber}",
|
||||
"OK": "OK",
|
||||
@@ -538,6 +544,7 @@
|
||||
"Outdoor pool": "Utomhuspool",
|
||||
"Overview": "Översikt",
|
||||
"PETR": "Husdjur",
|
||||
"PREPAID": "FÖRBETALT",
|
||||
"Paid": "Betalt",
|
||||
"Parking": "Parkering",
|
||||
"Parking / Garage": "Parkering / Garage",
|
||||
@@ -561,6 +568,8 @@
|
||||
"Payment will be made on check-in. The card will be only used to guarantee the ancillary in case of no-show.": "Betalning kommer att ske vid incheckning. Kortet kommer endast att användas för att garantera tillägget i händelse av no-show.",
|
||||
"Per night from": "Per natt från",
|
||||
"Pet room": "Husdjursrum",
|
||||
"Pet room charge including VAT": "Avgift för husdjursrum inkl. moms",
|
||||
"Pet-friendly": "Husdjursvänlig",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Husdjursrum har en extra avgift på 20 EUR per vistelse",
|
||||
"Phone": "Telefon",
|
||||
"Phone is required": "Telefonnummer är obligatorisk",
|
||||
@@ -583,6 +592,7 @@
|
||||
"Points needed to stay on level": "Poäng som behövs för att hålla sig på nivå",
|
||||
"Points used": "Points used",
|
||||
"Practical information": "Praktisk information",
|
||||
"Preliminary receipt": "Preliminärt kvitto",
|
||||
"Previous": "Föregående",
|
||||
"Previous victories": "Tidigare segrar",
|
||||
"Price": "Pris",
|
||||
@@ -610,6 +620,7 @@
|
||||
"Public price from": "Offentligt pris från",
|
||||
"Public transport": "Kollektivtrafik",
|
||||
"Queen bed": "Queen size-säng",
|
||||
"Rate": "Pris",
|
||||
"Rate details": "Detaljer om rumspriset",
|
||||
"Read more": "Läs mer",
|
||||
"Rebooking": "Rebooking",
|
||||
@@ -638,8 +649,10 @@
|
||||
"Room & Terms": "Rum & Villkor",
|
||||
"Room amenities": "Bekvämligheter på rummet",
|
||||
"Room charge": "Rumspris",
|
||||
"Room classification": "Rumsklassificering",
|
||||
"Room details": "Room details",
|
||||
"Room facilities": "Rumfaciliteter",
|
||||
"Room is prepaid": "Rummet är förbetalt",
|
||||
"Room sold out": "Rum slutsålt",
|
||||
"Room total": "Rum total",
|
||||
"Room type": "Rumstyp",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Room } from "@/types/hotel"
|
||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||
import type { Ancillaries } from "../../myPages/myStay/ancillaries"
|
||||
|
||||
export interface SpecificationProps {
|
||||
ancillaryPackages: Ancillaries | null
|
||||
booking: BookingConfirmation["booking"]
|
||||
currency?: string
|
||||
}
|
||||
|
||||
export interface TotalProps {
|
||||
booking: BookingConfirmation["booking"]
|
||||
currency?: string
|
||||
}
|
||||
|
||||
export interface FooterProps {
|
||||
booking: BookingConfirmation["booking"]
|
||||
room:
|
||||
| (Room & {
|
||||
bedType: Room["roomTypes"][number]
|
||||
})
|
||||
| null
|
||||
}
|
||||
@@ -25,3 +25,7 @@ export function getNights(start: string, end: string) {
|
||||
}
|
||||
return range
|
||||
}
|
||||
|
||||
export function getNumberOfNights(startDate: Date, endDate: Date) {
|
||||
return d(endDate).diff(startDate, "day")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user