feat: add Price details modal
This commit is contained in:
@@ -114,7 +114,7 @@ export default function EnterDetailsTracking(props: Props) {
|
||||
roomTypeName: selectedRoom.roomType,
|
||||
bedType: bedType?.description,
|
||||
roomTypeCode: bedType?.roomTypeCode,
|
||||
roomPrice: roomPrice.local.price,
|
||||
roomPrice: roomPrice.perStay.local.price,
|
||||
//discount: public - member rates?.
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export default function EnterDetailsTracking(props: Props) {
|
||||
selectedRoom.roomType,
|
||||
bedType?.description,
|
||||
bedType?.roomTypeCode,
|
||||
roomPrice.local.price,
|
||||
roomPrice.perStay.local.price,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -206,6 +206,7 @@ export default async function StepPage({
|
||||
searchParamsStr={selectRoomParams.toString()}
|
||||
step={searchParams.step}
|
||||
user={user}
|
||||
vat={hotelData.data.attributes.vat}
|
||||
>
|
||||
<main>
|
||||
<HotelHeader hotelData={hotelData} />
|
||||
|
||||
@@ -30,7 +30,7 @@ export default async function Friend({
|
||||
<Body color="white" textTransform="bold" textAlign="center">
|
||||
{intl.formatMessage(
|
||||
isHighestLevel
|
||||
? { id: "Highest level" }
|
||||
? { id: "Highest floor" }
|
||||
: { id: `Level ${membershipLevels[membership.membershipLevel]}` }
|
||||
)}
|
||||
</Body>
|
||||
|
||||
@@ -21,4 +21,4 @@
|
||||
.modalContent {
|
||||
width: 352px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,11 +50,11 @@ export default function SpecialRequests() {
|
||||
noPreferenceItem,
|
||||
{
|
||||
value: FloorPreference.HIGH,
|
||||
label: intl.formatMessage({ id: "High level" }),
|
||||
label: intl.formatMessage({ id: "High floor" }),
|
||||
},
|
||||
{
|
||||
value: FloorPreference.LOW,
|
||||
label: intl.formatMessage({ id: "Low level" }),
|
||||
label: intl.formatMessage({ id: "Low floor" }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -9,8 +9,8 @@ const stringMatcher =
|
||||
const isValidString = (key: string) => stringMatcher.test(key)
|
||||
|
||||
export enum FloorPreference {
|
||||
LOW = "Low level",
|
||||
HIGH = "High level",
|
||||
LOW = "Low floor",
|
||||
HIGH = "High floor",
|
||||
}
|
||||
|
||||
export enum ElevatorPreference {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: var(--Spacing-x3);
|
||||
|
||||
/* for supporting animations within content */
|
||||
position: relative;
|
||||
@@ -37,8 +36,7 @@
|
||||
align-items: center;
|
||||
height: var(--button-dimension);
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2) 0;
|
||||
padding: var(--Spacing-x2) var(--Spacing-x3) 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -74,4 +72,4 @@
|
||||
width: auto;
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { getNights } from "@/utils/dateFormatting"
|
||||
|
||||
import styles from "./priceDetailsTable.module.css"
|
||||
|
||||
import { type PriceDetailsTableProps } from "@/types/components/hotelReservation/enterDetails/priceDetailsTable"
|
||||
import type { DetailsState } from "@/types/stores/enter-details"
|
||||
|
||||
function Row({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<tr className={styles.row}>
|
||||
<td>
|
||||
<Caption>{label}</Caption>
|
||||
</td>
|
||||
<td className={styles.price}>
|
||||
<Caption>{value}</Caption>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
function TableSection({ children }: React.PropsWithChildren) {
|
||||
return <tbody className={styles.tableSection}>{children}</tbody>
|
||||
}
|
||||
|
||||
function TableSectionHeader({ title }: { title: string }) {
|
||||
return (
|
||||
<tr>
|
||||
<th colSpan={2}>
|
||||
<Body>{title}</Body>
|
||||
</th>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
export function storeSelector(state: DetailsState) {
|
||||
return {
|
||||
bedType: state.bedType,
|
||||
booking: state.booking,
|
||||
breakfast: state.breakfast,
|
||||
join: state.guest.join,
|
||||
membershipNo: state.guest.membershipNo,
|
||||
packages: state.packages,
|
||||
roomRate: state.roomRate,
|
||||
roomPrice: state.roomPrice,
|
||||
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
||||
togglePriceDetailsModalOpen: state.actions.togglePriceDetailsModalOpen,
|
||||
totalPrice: state.totalPrice,
|
||||
vat: state.vat,
|
||||
}
|
||||
}
|
||||
|
||||
export default function PriceDetailsTable({
|
||||
roomType,
|
||||
}: PriceDetailsTableProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
|
||||
const {
|
||||
bedType,
|
||||
booking,
|
||||
breakfast,
|
||||
join,
|
||||
membershipNo,
|
||||
packages,
|
||||
roomPrice,
|
||||
roomRate,
|
||||
toggleSummaryOpen,
|
||||
togglePriceDetailsModalOpen,
|
||||
totalPrice,
|
||||
vat,
|
||||
} = useEnterDetailsStore(storeSelector)
|
||||
|
||||
// TODO: Update for Multiroom later
|
||||
const { adults, children } = booking.rooms[0]
|
||||
const nights = getNights(booking.fromDate, booking.toDate)
|
||||
|
||||
const vatPercentage = vat / 100
|
||||
const vatAmount = totalPrice.local.price * vatPercentage
|
||||
|
||||
const priceExclVat = totalPrice.local.price - vatAmount
|
||||
|
||||
return (
|
||||
<table className={styles.priceDetailsTable}>
|
||||
<TableSection>
|
||||
<TableSectionHeader title={roomType} />
|
||||
{nights.map((night) => {
|
||||
return (
|
||||
<Row
|
||||
key={night.format("YYMMDD")}
|
||||
label={dt(night).locale(lang).format("ddd, D MMM YYYY")}
|
||||
value={intl.formatNumber(roomPrice.perNight.local.price, {
|
||||
currency: roomPrice.perNight.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</TableSection>
|
||||
{bedType ? (
|
||||
<TableSection>
|
||||
<Row
|
||||
label={bedType.description}
|
||||
value={intl.formatNumber(0, {
|
||||
currency: roomPrice.perStay.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
/>
|
||||
</TableSection>
|
||||
) : null}
|
||||
{breakfast ? (
|
||||
<TableSection>
|
||||
<TableSectionHeader title={intl.formatMessage({ id: "Breakfast" })} />
|
||||
<Row
|
||||
label={intl.formatMessage(
|
||||
{ id: "booking.adults.breakfasts" },
|
||||
{ totalAdults: adults, totalBreakfasts: nights.length }
|
||||
)}
|
||||
value={intl.formatNumber(
|
||||
parseInt(breakfast.localPrice.totalPrice),
|
||||
{
|
||||
currency: breakfast.localPrice.currency,
|
||||
style: "currency",
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{children?.length ? (
|
||||
<Row
|
||||
label={intl.formatMessage(
|
||||
{ id: "booking.children.breakfasts" },
|
||||
{
|
||||
totalChildren: children.length,
|
||||
totalBreakfasts: nights.length,
|
||||
}
|
||||
)}
|
||||
value={intl.formatNumber(0, {
|
||||
currency: breakfast.localPrice.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</TableSection>
|
||||
) : null}
|
||||
<TableSection>
|
||||
<TableSectionHeader title={intl.formatMessage({ id: "Total" })} />
|
||||
<Row
|
||||
label={intl.formatMessage({ id: "booking.vat.excl" })}
|
||||
value={intl.formatNumber(priceExclVat, {
|
||||
currency: totalPrice.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
/>
|
||||
<Row
|
||||
label={intl.formatMessage({ id: "booking.vat" }, { vat })}
|
||||
value={intl.formatNumber(vatAmount, {
|
||||
currency: totalPrice.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
/>
|
||||
<tr className={styles.row}>
|
||||
<td>
|
||||
<Body textTransform="bold">
|
||||
{intl.formatMessage({ id: "booking.vat.incl" })}
|
||||
</Body>
|
||||
</td>
|
||||
<td className={styles.price}>
|
||||
<Body textTransform="bold">
|
||||
{intl.formatNumber(totalPrice.local.price, {
|
||||
currency: totalPrice.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
</Body>
|
||||
</td>
|
||||
</tr>
|
||||
</TableSection>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
.priceDetailsTable {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.price {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.tableSection {
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-half);
|
||||
flex-direction: column;
|
||||
padding-bottom: var(--Spacing-x2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tableSection:has(tr > th) {
|
||||
padding-top: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.tableSection:has(tr > th):not(:first-of-type) {
|
||||
border-top: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.priceDetailsTable {
|
||||
min-width: 512px;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
@@ -17,8 +18,10 @@ import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { getNights } from "@/utils/dateFormatting"
|
||||
|
||||
import Modal from "../../Modal"
|
||||
import PriceDetailsTable from "../PriceDetailsTable"
|
||||
|
||||
import styles from "./ui.module.css"
|
||||
|
||||
@@ -39,6 +42,7 @@ export function storeSelector(state: DetailsState) {
|
||||
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
||||
togglePriceDetailsModalOpen: state.actions.togglePriceDetailsModalOpen,
|
||||
totalPrice: state.totalPrice,
|
||||
vat: state.vat,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +67,10 @@ export default function SummaryUI({
|
||||
toggleSummaryOpen,
|
||||
togglePriceDetailsModalOpen,
|
||||
totalPrice,
|
||||
vat,
|
||||
} = useEnterDetailsStore(storeSelector)
|
||||
|
||||
// TODO: Update for Multiroom later
|
||||
const adults = booking.rooms[0].adults
|
||||
const children = booking.rooms[0].children
|
||||
|
||||
@@ -90,6 +96,7 @@ export default function SummaryUI({
|
||||
const memberPrice = roomRate.memberRate
|
||||
? {
|
||||
currency: roomRate.memberRate.localPrice.currency,
|
||||
pricePerNight: roomRate.memberRate.localPrice.pricePerNight,
|
||||
amount: roomRate.memberRate.localPrice.pricePerStay,
|
||||
}
|
||||
: null
|
||||
@@ -141,8 +148,8 @@ export default function SummaryUI({
|
||||
<div className={styles.entry}>
|
||||
<Body color="uiTextHighContrast">{roomType}</Body>
|
||||
<Body color={showMemberPrice ? "red" : "uiTextHighContrast"}>
|
||||
{intl.formatNumber(roomPrice.local.price, {
|
||||
currency: roomPrice.local.currency,
|
||||
{intl.formatNumber(roomPrice.perStay.local.price, {
|
||||
currency: roomPrice.perStay.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
</Body>
|
||||
@@ -203,7 +210,7 @@ export default function SummaryUI({
|
||||
|
||||
<Body color="uiTextHighContrast">
|
||||
{intl.formatNumber(0, {
|
||||
currency: roomPrice.local.currency,
|
||||
currency: roomPrice.perStay.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
</Body>
|
||||
@@ -221,7 +228,7 @@ export default function SummaryUI({
|
||||
</div>
|
||||
<Body color="uiTextHighContrast">
|
||||
{intl.formatNumber(0, {
|
||||
currency: roomPrice.local.currency,
|
||||
currency: roomPrice.perStay.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
</Body>
|
||||
@@ -236,7 +243,7 @@ export default function SummaryUI({
|
||||
</div>
|
||||
<Body color="uiTextHighContrast">
|
||||
{intl.formatNumber(0, {
|
||||
currency: roomPrice.local.currency,
|
||||
currency: roomPrice.perStay.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
</Body>
|
||||
@@ -249,7 +256,7 @@ export default function SummaryUI({
|
||||
</Body>
|
||||
<Body color="uiTextHighContrast">
|
||||
{intl.formatNumber(0, {
|
||||
currency: roomPrice.local.currency,
|
||||
currency: roomPrice.perStay.local.currency,
|
||||
style: "currency",
|
||||
})}
|
||||
</Body>
|
||||
@@ -303,7 +310,9 @@ export default function SummaryUI({
|
||||
{ b: (str) => <b>{str}</b> }
|
||||
)}
|
||||
</Body>
|
||||
|
||||
<Modal
|
||||
title={intl.formatMessage({ id: "Price details" })}
|
||||
trigger={
|
||||
<Button intent="text" onPress={handleTogglePriceDetailsModal}>
|
||||
<Caption color="burgundy">
|
||||
@@ -317,13 +326,7 @@ export default function SummaryUI({
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className={styles.modalContent}>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu
|
||||
risus quis varius quam quisque id diam vel. Rhoncus urna neque
|
||||
viverra justo. Mattis aliquam faucibus purus in massa. Id cursus
|
||||
metus aliquam eleifend mi in nulla posuere.
|
||||
</div>
|
||||
<PriceDetailsTable roomType={roomType} />
|
||||
</Modal>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -179,8 +179,8 @@
|
||||
"Guests & Rooms": "Gæster & værelser",
|
||||
"Gym": "Fitnesscenter",
|
||||
"Hi": "Hei",
|
||||
"High level": "Højt niveau",
|
||||
"Highest level": "Højeste niveau",
|
||||
"High floor": "Højt niveau",
|
||||
"Highest floor": "Højeste niveau",
|
||||
"Home": "Hjem",
|
||||
"Hospital": "Hospital",
|
||||
"Hotel": "Hotel",
|
||||
@@ -497,9 +497,11 @@
|
||||
"as of today": "pr. dags dato",
|
||||
"booking.accommodatesUpTo": "Plads til {nrOfGuests, plural, one {# person} other {op til # personer}}",
|
||||
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
|
||||
"booking.adults.breakfasts": "{totalAdults, plural, one {# voksen} other {# voksne}}, {totalBreakfasts, plural, one {# morgenmad} other {# morgenmad}}",
|
||||
"booking.basedOnAvailability": "Baseret på tilgængelighed",
|
||||
"booking.bedOptions": "Sengemuligheder",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# børn}}",
|
||||
"booking.children.breakfasts": "{totalChildren, plural, one {# barn} other {# børn}}, {totalBreakfasts, plural, one {# morgenmad} other {# morgenmad}}",
|
||||
"booking.confirmation.text": "Tak fordi du bookede hos os! Vi glæder os til at byde dig velkommen og håber du får et behageligt ophold. Hvis du har spørgsmål eller har brug for at foretage ændringer i din reservation, bedes du <emailLink>kontakte os.</emailLink>",
|
||||
"booking.confirmation.title": "Booking bekræftelse",
|
||||
"booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}",
|
||||
@@ -508,6 +510,9 @@
|
||||
"booking.selectRoom": "Zimmer auswählen",
|
||||
"booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.",
|
||||
"booking.thisRoomIsEquippedWith": "Dette værelse er udstyret med",
|
||||
"booking.vat": "Moms {vat}%",
|
||||
"booking.vat.excl": "Pris ekskl. moms",
|
||||
"booking.vat.incl": "Pris inkl. moms",
|
||||
"breakfast.price": "{amount} {currency}/nat",
|
||||
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/nat",
|
||||
"by": "inden",
|
||||
|
||||
@@ -179,8 +179,8 @@
|
||||
"Guests & Rooms": "Gäste & Zimmer",
|
||||
"Gym": "Fitnessstudio",
|
||||
"Hi": "Hallo",
|
||||
"High level": "Hohes Level",
|
||||
"Highest level": "Höchstes Level",
|
||||
"High floor": "Hohes Level",
|
||||
"Highest floor": "Höchstes Level",
|
||||
"Home": "Heim",
|
||||
"Hospital": "Krankenhaus",
|
||||
"Hotel": "Hotel",
|
||||
@@ -496,9 +496,11 @@
|
||||
"as of today": "Stand heute",
|
||||
"booking.accommodatesUpTo": "Bietet Platz für {nrOfGuests, plural, one {# Person } other {bis zu # Personen}}",
|
||||
"booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}",
|
||||
"booking.adults.breakfasts": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}, {totalBreakfasts, plural, one {# frühstück} other {# frühstücke}}",
|
||||
"booking.basedOnAvailability": "Abhängig von der Verfügbarkeit",
|
||||
"booking.bedOptions": "Bettoptionen",
|
||||
"booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}",
|
||||
"booking.children.breakfasts": "{totalChildren, plural, one {# kind} other {# kinder}}, {totalBreakfasts, plural, one {# frühstück} other {# frühstücke}}",
|
||||
"booking.confirmation.text": "Vielen Dank, dass Sie bei uns gebucht haben! Wir freuen uns, Sie bei uns begrüßen zu dürfen und wünschen Ihnen einen angenehmen Aufenthalt. Wenn Sie Fragen haben oder Änderungen an Ihrer Buchung vornehmen müssen, <emailLink>kontaktieren Sie uns bitte.</emailLink>.",
|
||||
"booking.confirmation.title": "Buchungsbestätigung",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}",
|
||||
@@ -507,6 +509,9 @@
|
||||
"booking.selectRoom": "Vælg værelse",
|
||||
"booking.terms": "Mit der Zahlung über eine der verfügbaren Zahlungsmethoden akzeptiere ich die Buchungsbedingungen und die allgemeinen <termsLink>Geschäftsbedingungen</termsLink> und verstehe, dass Scandic meine personenbezogenen Daten im Zusammenhang mit dieser Buchung gemäß der <privacyLink>Scandic Datenschutzrichtlinie</privacyLink> verarbeitet. Ich akzeptiere, dass Scandic während meines Aufenthalts eine gültige Kreditkarte für eventuelle Rückerstattungen benötigt.",
|
||||
"booking.thisRoomIsEquippedWith": "Dieses Zimmer ist ausgestattet mit",
|
||||
"booking.vat": "MwSt. {vat}%",
|
||||
"booking.vat.excl": "Preis ohne MwSt.",
|
||||
"booking.vat.incl": "Preis inkl. MwSt.",
|
||||
"breakfast.price": "{amount} {currency}/Nacht",
|
||||
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/Nacht",
|
||||
"by": "bis",
|
||||
|
||||
@@ -193,8 +193,8 @@
|
||||
"Guests & Rooms": "Guests & Rooms",
|
||||
"Gym": "Gym",
|
||||
"Hi": "Hi",
|
||||
"High level": "High level",
|
||||
"Highest level": "Highest level",
|
||||
"High floor": "High floor",
|
||||
"Highest floor": "Highest floor",
|
||||
"Home": "Home",
|
||||
"Hospital": "Hospital",
|
||||
"Hotel": "Hotel",
|
||||
@@ -540,9 +540,11 @@
|
||||
"as of today": "as of today",
|
||||
"booking.accommodatesUpTo": "Accommodates up to {nrOfGuests, plural, one {# person} other {# people}}",
|
||||
"booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}",
|
||||
"booking.adults.breakfasts": "{totalAdults, plural, one {# adult} other {# adults}}, {totalBreakfasts, plural, one {# breakfast} other {# breakfasts}}",
|
||||
"booking.basedOnAvailability": "Based on availability",
|
||||
"booking.bedOptions": "Bed options",
|
||||
"booking.children": "{totalChildren, plural, one {# child} other {# children}}",
|
||||
"booking.children.breakfasts": "{totalChildren, plural, one {# child} other {# children}}, {totalBreakfasts, plural, one {# breakfast} other {# breakfasts}}",
|
||||
"booking.confirmation.text": "Thank you for booking with us! We look forward to welcoming you and hope you have a pleasant stay. If you have any questions or need to make changes to your reservation, please <emailLink>contact us.</emailLink>",
|
||||
"booking.confirmation.title": "Booking confirmation",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# guest} other {# guests}}",
|
||||
@@ -551,6 +553,9 @@
|
||||
"booking.selectRoom": "Select room",
|
||||
"booking.terms": "By paying with any of the payment methods available, I accept the terms for this booking and the general <termsLink>Terms & Conditions</termsLink>, and understand that Scandic will process my personal data for this booking in accordance with <privacyLink>Scandic's Privacy policy</privacyLink>. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.",
|
||||
"booking.thisRoomIsEquippedWith": "This room is equipped with",
|
||||
"booking.vat": "VAT {vat}%",
|
||||
"booking.vat.excl": "Price excluding VAT",
|
||||
"booking.vat.incl": "Price including VAT",
|
||||
"breakfast.price": "{amount} {currency}/night",
|
||||
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/night",
|
||||
"by": "by",
|
||||
|
||||
@@ -179,8 +179,8 @@
|
||||
"Guests & Rooms": "Vieraat & Huoneet",
|
||||
"Gym": "Kuntosali",
|
||||
"Hi": "Hi",
|
||||
"High level": "Korkea taso",
|
||||
"Highest level": "Korkein taso",
|
||||
"High floor": "Korkea taso",
|
||||
"Highest floor": "Korkein taso",
|
||||
"Home": "Kotiin",
|
||||
"Hospital": "Sairaala",
|
||||
"Hotel": "Hotelli",
|
||||
@@ -495,9 +495,11 @@
|
||||
"as of today": "tänään",
|
||||
"booking.accommodatesUpTo": "Huoneeseen {nrOfGuests, plural, one {# person} other {mahtuu 2 henkilöä}}",
|
||||
"booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}",
|
||||
"booking.adults.breakfasts": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}, {totalBreakfasts, plural, one {# aamiainen} other {# aamiaista}}",
|
||||
"booking.basedOnAvailability": "Saatavuuden mukaan",
|
||||
"booking.bedOptions": "Vuodevaihtoehdot",
|
||||
"booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}",
|
||||
"booking.children.breakfasts": "{totalChildren, plural, one {# lapsi} other {# lasta}}, {totalBreakfasts, plural, one {# aamiainen} other {# aamiaista}}",
|
||||
"booking.confirmation.text": "Kiitos, että teit varauksen meiltä! Toivotamme sinut tervetulleeksi ja toivomme sinulle miellyttävää oleskelua. Jos sinulla on kysyttävää tai haluat tehdä muutoksia varaukseesi, <emailLink>ota meihin yhteyttä.</emailLink>",
|
||||
"booking.confirmation.title": "Varausvahvistus",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}",
|
||||
@@ -506,6 +508,9 @@
|
||||
"booking.selectRoom": "Valitse huone",
|
||||
"booking.terms": "Maksamalla minkä tahansa saatavilla olevan maksutavan avulla hyväksyn tämän varauksen ehdot ja yleiset <termsLink>ehdot ja ehtoja</termsLink>, ja ymmärrän, että Scandic käsittelee minun henkilötietoni tässä varauksessa mukaisesti <privacyLink>Scandicin tietosuojavaltuuden</privacyLink> mukaisesti. Hyväksyn myös, että Scandic vaatii validin luottokortin majoituksen ajan, jos jokin jää maksamatta.",
|
||||
"booking.thisRoomIsEquippedWith": "Tämä huone on varustettu",
|
||||
"booking.vat": "ALV {vat}%",
|
||||
"booking.vat.excl": "ALV ei sisälly hintaan",
|
||||
"booking.vat.incl": "ALV sisältyy hintaan",
|
||||
"breakfast.price": "{amount} {currency}/yö",
|
||||
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/yö",
|
||||
"by": "mennessä",
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
"Guests & Rooms": "Gjester & rom",
|
||||
"Gym": "Treningsstudio",
|
||||
"Hi": "Hei",
|
||||
"High level": "Høy nivå",
|
||||
"Highest level": "Høyeste nivå",
|
||||
"High floor": "Høy nivå",
|
||||
"Highest floor": "Høyeste nivå",
|
||||
"Home": "Hjem",
|
||||
"Hospital": "Sykehus",
|
||||
"Hotel": "Hotel",
|
||||
@@ -495,9 +495,11 @@
|
||||
"as of today": "per i dag",
|
||||
"booking.accommodatesUpTo": "Plass til {nrOfGuests, plural, one {# person} other {opptil # personer}}",
|
||||
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
|
||||
"booking.adults.breakfasts": "{totalAdults, plural, one {# voksen} other {# voksne}}, {totalBreakfasts, plural, one {# frokost} other {# frokoster}}",
|
||||
"booking.basedOnAvailability": "Basert på tilgjengelighet",
|
||||
"booking.bedOptions": "Sengemuligheter",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}",
|
||||
"booking.children.breakfasts": "{totalChildren, plural, one {# barn} other {# barn}}, {totalBreakfasts, plural, one {# frokost} other {# frokoster}}",
|
||||
"booking.confirmation.text": "Takk for at du booket hos oss! Vi ser frem til å ønske deg velkommen og håper du får et hyggelig opphold. Hvis du har spørsmål eller trenger å gjøre endringer i bestillingen din, vennligst <emailLink>kontakt oss.</emailLink>",
|
||||
"booking.confirmation.title": "Bestillingsbekreftelse",
|
||||
"booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}",
|
||||
@@ -506,6 +508,9 @@
|
||||
"booking.selectRoom": "Velg rom",
|
||||
"booking.terms": "Ved å betale med en av de tilgjengelige betalingsmetodene godtar jeg vilkårene og betingelsene for denne bestillingen og de generelle <termsLink>vilkårene</termsLink>, og forstår at Scandic vil behandle mine personopplysninger i forbindelse med denne bestillingen i henhold til <privacyLink> Scandics personvernpolicy</privacyLink>. Jeg aksepterer at Scandic krever et gyldig kredittkort under mitt besøk i tilfelle noe blir refundert.",
|
||||
"booking.thisRoomIsEquippedWith": "Dette rommet er utstyrt med",
|
||||
"booking.vat": "mva {vat}%",
|
||||
"booking.vat.excl": "Pris exkludert mva",
|
||||
"booking.vat.incl": "Pris inkludert mva",
|
||||
"breakfast.price": "{amount} {currency}/natt",
|
||||
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/natt",
|
||||
"by": "innen",
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
"Guests & Rooms": "Gäster & rum",
|
||||
"Gym": "Gym",
|
||||
"Hi": "Hej",
|
||||
"High level": "Högt upp",
|
||||
"Highest level": "Högsta nivå",
|
||||
"High floor": "Högt upp",
|
||||
"Highest floor": "Högsta nivå",
|
||||
"Home": "Hem",
|
||||
"Hospital": "Sjukhus",
|
||||
"Hotel": "Hotell",
|
||||
@@ -495,9 +495,11 @@
|
||||
"as of today": "från och med idag",
|
||||
"booking.accommodatesUpTo": "Rymmer {nrOfGuests, plural, one {# person} other {upp till # personer}}",
|
||||
"booking.adults": "{totalAdults, plural, one {# vuxen} other {# vuxna}}",
|
||||
"booking.adults.breakfasts": "{totalAdults, plural, one {# vuxen} other {# vuxna}}, {totalBreakfasts, plural, one {# frukost} other {# frukostar}}",
|
||||
"booking.basedOnAvailability": "Baserat på tillgänglighet",
|
||||
"booking.bedOptions": "Sängalternativ",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}",
|
||||
"booking.children.breakfasts": "{totalChildren, plural, one {# barn} other {# barn}}, {totalBreakfasts, plural, one {# frukost} other {# frukostar}}",
|
||||
"booking.confirmation.text": "Tack för att du bokar hos oss! Vi ser fram emot att välkomna dig och hoppas att du får en trevlig vistelse. Om du har några frågor eller behöver göra ändringar i din bokning, vänligen <emailLink>kontakta oss.</emailLink>",
|
||||
"booking.confirmation.title": "Bokningsbekräftelse",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# gäst} other {# gäster}}",
|
||||
@@ -506,6 +508,9 @@
|
||||
"booking.selectRoom": "Välj rum",
|
||||
"booking.terms": "Genom att betala med någon av de tillgängliga betalningsmetoderna accepterar jag villkoren för denna bokning och de generella <termsLink>Villkoren och villkoren</termsLink>, och förstår att Scandic kommer att behandla min personliga data i samband med denna bokning i enlighet med <privacyLink>Scandics integritetspolicy</privacyLink>. Jag accepterar att Scandic kräver ett giltigt kreditkort under min besök i fall att något är tillbaka betalt.",
|
||||
"booking.thisRoomIsEquippedWith": "Detta rum är utrustat med",
|
||||
"booking.vat": "Moms {vat}%",
|
||||
"booking.vat.excl": "Pris exkl. VAT",
|
||||
"booking.vat.incl": "Pris inkl. VAT",
|
||||
"breakfast.price": "{amount} {currency}/natt",
|
||||
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/natt",
|
||||
"by": "innan",
|
||||
|
||||
@@ -32,11 +32,12 @@ export default function EnterDetailsProvider({
|
||||
searchParamsStr,
|
||||
step,
|
||||
user,
|
||||
vat,
|
||||
}: DetailsProviderProps) {
|
||||
const storeRef = useRef<DetailsStore>()
|
||||
|
||||
if (!storeRef.current) {
|
||||
const initialData: InitialState = { booking, packages, roomRate }
|
||||
const initialData: InitialState = { booking, packages, roomRate, vat }
|
||||
if (bedTypes.length === 1) {
|
||||
initialData.bedType = {
|
||||
description: bedTypes[0].description,
|
||||
|
||||
@@ -435,6 +435,7 @@ export const hotelAttributesSchema = z.object({
|
||||
socialMedia: socialMediaSchema,
|
||||
specialAlerts: specialAlertsSchema,
|
||||
specialNeedGroups: z.array(specialNeedGroupSchema),
|
||||
vat: z.number(),
|
||||
})
|
||||
|
||||
const includedSchema = z
|
||||
|
||||
@@ -15,6 +15,8 @@ import type {
|
||||
DetailsState,
|
||||
PersistedState,
|
||||
PersistedStatePart,
|
||||
Price,
|
||||
RoomPrice,
|
||||
RoomRate,
|
||||
} from "@/types/stores/enter-details"
|
||||
import type { SafeUser } from "@/types/user"
|
||||
@@ -106,25 +108,49 @@ export function subtract(...nums: (number | string | undefined)[]) {
|
||||
export function getInitialRoomPrice(roomRate: RoomRate, isMember: boolean) {
|
||||
if (isMember && roomRate.memberRate) {
|
||||
return {
|
||||
requested: roomRate.memberRate.requestedPrice && {
|
||||
currency: roomRate.memberRate.requestedPrice.currency,
|
||||
price: roomRate.memberRate.requestedPrice.pricePerStay,
|
||||
perNight: {
|
||||
requested: roomRate.memberRate.requestedPrice && {
|
||||
currency: roomRate.memberRate.requestedPrice.currency,
|
||||
price: roomRate.memberRate.requestedPrice.pricePerNight,
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.memberRate.localPrice.currency,
|
||||
price: roomRate.memberRate.localPrice.pricePerNight,
|
||||
},
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.memberRate.localPrice.currency,
|
||||
price: roomRate.memberRate.localPrice.pricePerStay,
|
||||
perStay: {
|
||||
requested: roomRate.memberRate.requestedPrice && {
|
||||
currency: roomRate.memberRate.requestedPrice.currency,
|
||||
price: roomRate.memberRate.requestedPrice.pricePerStay,
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.memberRate.localPrice.currency,
|
||||
price: roomRate.memberRate.localPrice.pricePerStay,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
requested: roomRate.publicRate.requestedPrice && {
|
||||
currency: roomRate.publicRate.requestedPrice.currency,
|
||||
price: roomRate.publicRate.requestedPrice.pricePerStay,
|
||||
perNight: {
|
||||
requested: roomRate.publicRate.requestedPrice && {
|
||||
currency: roomRate.publicRate.requestedPrice.currency,
|
||||
price: roomRate.publicRate.requestedPrice.pricePerNight,
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.publicRate.localPrice.currency,
|
||||
price: roomRate.publicRate.localPrice.pricePerNight,
|
||||
},
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.publicRate.localPrice.currency,
|
||||
price: roomRate.publicRate.localPrice.pricePerStay,
|
||||
perStay: {
|
||||
requested: roomRate.publicRate.requestedPrice && {
|
||||
currency: roomRate.publicRate.requestedPrice.currency,
|
||||
price: roomRate.publicRate.requestedPrice.pricePerStay,
|
||||
},
|
||||
local: {
|
||||
currency: roomRate.publicRate.localPrice.currency,
|
||||
price: roomRate.publicRate.localPrice.pricePerStay,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -190,13 +216,31 @@ export function calcTotalPrice(
|
||||
DetailsState["roomRate"]["publicRate"]
|
||||
) {
|
||||
// state is sometimes read-only, thus we
|
||||
// need to create a copy of the values
|
||||
// need to create a deep copy of the values
|
||||
const roomAndTotalPrice = {
|
||||
roomPrice: { ...state.roomPrice },
|
||||
totalPrice: { ...state.totalPrice },
|
||||
roomPrice: {
|
||||
perNight: {
|
||||
local: { ...state.roomPrice.perNight.local },
|
||||
requested: state.roomPrice.perNight.requested
|
||||
? { ...state.roomPrice.perNight.requested }
|
||||
: state.roomPrice.perNight.requested,
|
||||
},
|
||||
perStay: {
|
||||
local: { ...state.roomPrice.perStay.local },
|
||||
requested: state.roomPrice.perStay.requested
|
||||
? { ...state.roomPrice.perStay.requested }
|
||||
: state.roomPrice.perStay.requested,
|
||||
},
|
||||
},
|
||||
totalPrice: {
|
||||
local: { ...state.totalPrice.local },
|
||||
requested: state.totalPrice.requested
|
||||
? { ...state.totalPrice.requested }
|
||||
: state.totalPrice.requested,
|
||||
},
|
||||
}
|
||||
if (state.requestedPrice?.pricePerStay) {
|
||||
roomAndTotalPrice.roomPrice.requested = {
|
||||
roomAndTotalPrice.roomPrice.perStay.requested = {
|
||||
currency: state.requestedPrice.currency,
|
||||
price: state.requestedPrice.pricePerStay,
|
||||
}
|
||||
@@ -225,7 +269,7 @@ export function calcTotalPrice(
|
||||
}
|
||||
|
||||
const roomPriceLocal = state.localPrice
|
||||
roomAndTotalPrice.roomPrice.local = {
|
||||
roomAndTotalPrice.roomPrice.perStay.local = {
|
||||
currency: roomPriceLocal.currency,
|
||||
price: roomPriceLocal.pricePerStay,
|
||||
}
|
||||
|
||||
@@ -347,6 +347,7 @@ export function createDetailsStore(
|
||||
roomRate: initialState.roomRate,
|
||||
steps,
|
||||
totalPrice: initialTotalPrice,
|
||||
vat: initialState.vat,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface PriceDetailsTableProps {
|
||||
roomType: string
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||
import { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||
import type { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast"
|
||||
import { StepEnum } from "@/types/enums/step"
|
||||
import type { StepEnum } from "@/types/enums/step"
|
||||
import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability"
|
||||
import type { SafeUser } from "@/types/user"
|
||||
import type { Packages } from "../requests/packages"
|
||||
@@ -15,4 +15,5 @@ export interface DetailsProviderProps extends React.PropsWithChildren {
|
||||
searchParamsStr: string
|
||||
step: StepEnum
|
||||
user: SafeUser
|
||||
vat: number
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ interface TPrice {
|
||||
price: number
|
||||
}
|
||||
|
||||
export interface RoomPrice {
|
||||
perNight: Price
|
||||
perStay: Price
|
||||
}
|
||||
|
||||
export interface Price {
|
||||
requested: TPrice | undefined
|
||||
local: TPrice
|
||||
@@ -52,14 +57,15 @@ export interface DetailsState {
|
||||
isValid: Record<StepEnum, boolean>
|
||||
packages: Packages | null
|
||||
roomRate: DetailsProviderProps["roomRate"]
|
||||
roomPrice: Price
|
||||
roomPrice: RoomPrice
|
||||
steps: StepEnum[]
|
||||
totalPrice: Price
|
||||
searchParamString: string
|
||||
vat: number
|
||||
}
|
||||
|
||||
export type InitialState = Pick<DetailsState, "booking" | "packages"> &
|
||||
Pick<DetailsProviderProps, "roomRate"> & {
|
||||
Pick<DetailsProviderProps, "roomRate" | "vat"> & {
|
||||
bedType?: BedTypeSchema
|
||||
breakfast?: false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
import d from "dayjs"
|
||||
|
||||
import type { Lang } from "@/constants/languages"
|
||||
|
||||
/**
|
||||
* Get the localized month name for a given month index and language
|
||||
@@ -13,3 +15,13 @@ export function getLocalizedMonthName(monthIndex: number, lang: Lang) {
|
||||
|
||||
return monthName.charAt(0).toUpperCase() + monthName.slice(1)
|
||||
}
|
||||
|
||||
export function getNights(start: string, end: string) {
|
||||
const range = []
|
||||
let current = d(start)
|
||||
while (current.isBefore(end)) {
|
||||
range.push(current)
|
||||
current = current.add(1, "days")
|
||||
}
|
||||
return range
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user