feat: add Price details modal

This commit is contained in:
Arvid Norlin
2024-12-04 16:40:45 +01:00
parent df1e4da001
commit 0c7c6ea21a
24 changed files with 382 additions and 60 deletions

View File

@@ -114,7 +114,7 @@ export default function EnterDetailsTracking(props: Props) {
roomTypeName: selectedRoom.roomType, roomTypeName: selectedRoom.roomType,
bedType: bedType?.description, bedType: bedType?.description,
roomTypeCode: bedType?.roomTypeCode, roomTypeCode: bedType?.roomTypeCode,
roomPrice: roomPrice.local.price, roomPrice: roomPrice.perStay.local.price,
//discount: public - member rates?. //discount: public - member rates?.
} }
@@ -131,7 +131,7 @@ export default function EnterDetailsTracking(props: Props) {
selectedRoom.roomType, selectedRoom.roomType,
bedType?.description, bedType?.description,
bedType?.roomTypeCode, bedType?.roomTypeCode,
roomPrice.local.price, roomPrice.perStay.local.price,
]) ])
useEffect(() => { useEffect(() => {

View File

@@ -206,6 +206,7 @@ export default async function StepPage({
searchParamsStr={selectRoomParams.toString()} searchParamsStr={selectRoomParams.toString()}
step={searchParams.step} step={searchParams.step}
user={user} user={user}
vat={hotelData.data.attributes.vat}
> >
<main> <main>
<HotelHeader hotelData={hotelData} /> <HotelHeader hotelData={hotelData} />

View File

@@ -30,7 +30,7 @@ export default async function Friend({
<Body color="white" textTransform="bold" textAlign="center"> <Body color="white" textTransform="bold" textAlign="center">
{intl.formatMessage( {intl.formatMessage(
isHighestLevel isHighestLevel
? { id: "Highest level" } ? { id: "Highest floor" }
: { id: `Level ${membershipLevels[membership.membershipLevel]}` } : { id: `Level ${membershipLevels[membership.membershipLevel]}` }
)} )}
</Body> </Body>

View File

@@ -50,11 +50,11 @@ export default function SpecialRequests() {
noPreferenceItem, noPreferenceItem,
{ {
value: FloorPreference.HIGH, value: FloorPreference.HIGH,
label: intl.formatMessage({ id: "High level" }), label: intl.formatMessage({ id: "High floor" }),
}, },
{ {
value: FloorPreference.LOW, value: FloorPreference.LOW,
label: intl.formatMessage({ id: "Low level" }), label: intl.formatMessage({ id: "Low floor" }),
}, },
]} ]}
/> />

View File

@@ -9,8 +9,8 @@ const stringMatcher =
const isValidString = (key: string) => stringMatcher.test(key) const isValidString = (key: string) => stringMatcher.test(key)
export enum FloorPreference { export enum FloorPreference {
LOW = "Low level", LOW = "Low floor",
HIGH = "High level", HIGH = "High floor",
} }
export enum ElevatorPreference { export enum ElevatorPreference {

View File

@@ -22,7 +22,6 @@
.dialog { .dialog {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-bottom: var(--Spacing-x3);
/* for supporting animations within content */ /* for supporting animations within content */
position: relative; position: relative;
@@ -37,8 +36,7 @@
align-items: center; align-items: center;
height: var(--button-dimension); height: var(--button-dimension);
position: relative; position: relative;
justify-content: center; padding: var(--Spacing-x2) var(--Spacing-x3) 0;
padding: var(--Spacing-x3) var(--Spacing-x2) 0;
} }
.content { .content {

View File

@@ -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>
)
}

View File

@@ -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;
}
}

View File

@@ -1,4 +1,5 @@
"use client" "use client"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
@@ -17,8 +18,10 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" 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 { getNights } from "@/utils/dateFormatting"
import Modal from "../../Modal" import Modal from "../../Modal"
import PriceDetailsTable from "../PriceDetailsTable"
import styles from "./ui.module.css" import styles from "./ui.module.css"
@@ -39,6 +42,7 @@ export function storeSelector(state: DetailsState) {
toggleSummaryOpen: state.actions.toggleSummaryOpen, toggleSummaryOpen: state.actions.toggleSummaryOpen,
togglePriceDetailsModalOpen: state.actions.togglePriceDetailsModalOpen, togglePriceDetailsModalOpen: state.actions.togglePriceDetailsModalOpen,
totalPrice: state.totalPrice, totalPrice: state.totalPrice,
vat: state.vat,
} }
} }
@@ -63,8 +67,10 @@ export default function SummaryUI({
toggleSummaryOpen, toggleSummaryOpen,
togglePriceDetailsModalOpen, togglePriceDetailsModalOpen,
totalPrice, totalPrice,
vat,
} = useEnterDetailsStore(storeSelector) } = useEnterDetailsStore(storeSelector)
// TODO: Update for Multiroom later
const adults = booking.rooms[0].adults const adults = booking.rooms[0].adults
const children = booking.rooms[0].children const children = booking.rooms[0].children
@@ -90,6 +96,7 @@ export default function SummaryUI({
const memberPrice = roomRate.memberRate const memberPrice = roomRate.memberRate
? { ? {
currency: roomRate.memberRate.localPrice.currency, currency: roomRate.memberRate.localPrice.currency,
pricePerNight: roomRate.memberRate.localPrice.pricePerNight,
amount: roomRate.memberRate.localPrice.pricePerStay, amount: roomRate.memberRate.localPrice.pricePerStay,
} }
: null : null
@@ -141,8 +148,8 @@ export default function SummaryUI({
<div className={styles.entry}> <div className={styles.entry}>
<Body color="uiTextHighContrast">{roomType}</Body> <Body color="uiTextHighContrast">{roomType}</Body>
<Body color={showMemberPrice ? "red" : "uiTextHighContrast"}> <Body color={showMemberPrice ? "red" : "uiTextHighContrast"}>
{intl.formatNumber(roomPrice.local.price, { {intl.formatNumber(roomPrice.perStay.local.price, {
currency: roomPrice.local.currency, currency: roomPrice.perStay.local.currency,
style: "currency", style: "currency",
})} })}
</Body> </Body>
@@ -203,7 +210,7 @@ export default function SummaryUI({
<Body color="uiTextHighContrast"> <Body color="uiTextHighContrast">
{intl.formatNumber(0, { {intl.formatNumber(0, {
currency: roomPrice.local.currency, currency: roomPrice.perStay.local.currency,
style: "currency", style: "currency",
})} })}
</Body> </Body>
@@ -221,7 +228,7 @@ export default function SummaryUI({
</div> </div>
<Body color="uiTextHighContrast"> <Body color="uiTextHighContrast">
{intl.formatNumber(0, { {intl.formatNumber(0, {
currency: roomPrice.local.currency, currency: roomPrice.perStay.local.currency,
style: "currency", style: "currency",
})} })}
</Body> </Body>
@@ -236,7 +243,7 @@ export default function SummaryUI({
</div> </div>
<Body color="uiTextHighContrast"> <Body color="uiTextHighContrast">
{intl.formatNumber(0, { {intl.formatNumber(0, {
currency: roomPrice.local.currency, currency: roomPrice.perStay.local.currency,
style: "currency", style: "currency",
})} })}
</Body> </Body>
@@ -249,7 +256,7 @@ export default function SummaryUI({
</Body> </Body>
<Body color="uiTextHighContrast"> <Body color="uiTextHighContrast">
{intl.formatNumber(0, { {intl.formatNumber(0, {
currency: roomPrice.local.currency, currency: roomPrice.perStay.local.currency,
style: "currency", style: "currency",
})} })}
</Body> </Body>
@@ -303,7 +310,9 @@ export default function SummaryUI({
{ b: (str) => <b>{str}</b> } { b: (str) => <b>{str}</b> }
)} )}
</Body> </Body>
<Modal <Modal
title={intl.formatMessage({ id: "Price details" })}
trigger={ trigger={
<Button intent="text" onPress={handleTogglePriceDetailsModal}> <Button intent="text" onPress={handleTogglePriceDetailsModal}>
<Caption color="burgundy"> <Caption color="burgundy">
@@ -317,13 +326,7 @@ export default function SummaryUI({
</Button> </Button>
} }
> >
<div className={styles.modalContent}> <PriceDetailsTable roomType={roomType} />
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>
</Modal> </Modal>
</div> </div>
<div> <div>

View File

@@ -179,8 +179,8 @@
"Guests & Rooms": "Gæster & værelser", "Guests & Rooms": "Gæster & værelser",
"Gym": "Fitnesscenter", "Gym": "Fitnesscenter",
"Hi": "Hei", "Hi": "Hei",
"High level": "Højt niveau", "High floor": "Højt niveau",
"Highest level": "Højeste niveau", "Highest floor": "Højeste niveau",
"Home": "Hjem", "Home": "Hjem",
"Hospital": "Hospital", "Hospital": "Hospital",
"Hotel": "Hotel", "Hotel": "Hotel",
@@ -497,9 +497,11 @@
"as of today": "pr. dags dato", "as of today": "pr. dags dato",
"booking.accommodatesUpTo": "Plads til {nrOfGuests, plural, one {# person} other {op til # personer}}", "booking.accommodatesUpTo": "Plads til {nrOfGuests, plural, one {# person} other {op til # personer}}",
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}", "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.basedOnAvailability": "Baseret på tilgængelighed",
"booking.bedOptions": "Sengemuligheder", "booking.bedOptions": "Sengemuligheder",
"booking.children": "{totalChildren, plural, one {# barn} other {# børn}}", "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.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.confirmation.title": "Booking bekræftelse",
"booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}", "booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}",
@@ -508,6 +510,9 @@
"booking.selectRoom": "Zimmer auswählen", "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.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.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": "{amount} {currency}/nat",
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/nat", "breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/nat",
"by": "inden", "by": "inden",

View File

@@ -179,8 +179,8 @@
"Guests & Rooms": "Gäste & Zimmer", "Guests & Rooms": "Gäste & Zimmer",
"Gym": "Fitnessstudio", "Gym": "Fitnessstudio",
"Hi": "Hallo", "Hi": "Hallo",
"High level": "Hohes Level", "High floor": "Hohes Level",
"Highest level": "Höchstes Level", "Highest floor": "Höchstes Level",
"Home": "Heim", "Home": "Heim",
"Hospital": "Krankenhaus", "Hospital": "Krankenhaus",
"Hotel": "Hotel", "Hotel": "Hotel",
@@ -496,9 +496,11 @@
"as of today": "Stand heute", "as of today": "Stand heute",
"booking.accommodatesUpTo": "Bietet Platz für {nrOfGuests, plural, one {# Person } other {bis zu # Personen}}", "booking.accommodatesUpTo": "Bietet Platz für {nrOfGuests, plural, one {# Person } other {bis zu # Personen}}",
"booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}", "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.basedOnAvailability": "Abhängig von der Verfügbarkeit",
"booking.bedOptions": "Bettoptionen", "booking.bedOptions": "Bettoptionen",
"booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}", "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.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.confirmation.title": "Buchungsbestätigung",
"booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}", "booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}",
@@ -507,6 +509,9 @@
"booking.selectRoom": "Vælg værelse", "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.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.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": "{amount} {currency}/Nacht",
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/Nacht", "breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/Nacht",
"by": "bis", "by": "bis",

View File

@@ -193,8 +193,8 @@
"Guests & Rooms": "Guests & Rooms", "Guests & Rooms": "Guests & Rooms",
"Gym": "Gym", "Gym": "Gym",
"Hi": "Hi", "Hi": "Hi",
"High level": "High level", "High floor": "High floor",
"Highest level": "Highest level", "Highest floor": "Highest floor",
"Home": "Home", "Home": "Home",
"Hospital": "Hospital", "Hospital": "Hospital",
"Hotel": "Hotel", "Hotel": "Hotel",
@@ -540,9 +540,11 @@
"as of today": "as of today", "as of today": "as of today",
"booking.accommodatesUpTo": "Accommodates up to {nrOfGuests, plural, one {# person} other {# people}}", "booking.accommodatesUpTo": "Accommodates up to {nrOfGuests, plural, one {# person} other {# people}}",
"booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}", "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.basedOnAvailability": "Based on availability",
"booking.bedOptions": "Bed options", "booking.bedOptions": "Bed options",
"booking.children": "{totalChildren, plural, one {# child} other {# children}}", "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.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.confirmation.title": "Booking confirmation",
"booking.guests": "Max {nrOfGuests, plural, one {# guest} other {# guests}}", "booking.guests": "Max {nrOfGuests, plural, one {# guest} other {# guests}}",
@@ -551,6 +553,9 @@
"booking.selectRoom": "Select room", "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.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.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": "{amount} {currency}/night",
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/night", "breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/night",
"by": "by", "by": "by",

View File

@@ -179,8 +179,8 @@
"Guests & Rooms": "Vieraat & Huoneet", "Guests & Rooms": "Vieraat & Huoneet",
"Gym": "Kuntosali", "Gym": "Kuntosali",
"Hi": "Hi", "Hi": "Hi",
"High level": "Korkea taso", "High floor": "Korkea taso",
"Highest level": "Korkein taso", "Highest floor": "Korkein taso",
"Home": "Kotiin", "Home": "Kotiin",
"Hospital": "Sairaala", "Hospital": "Sairaala",
"Hotel": "Hotelli", "Hotel": "Hotelli",
@@ -495,9 +495,11 @@
"as of today": "tänään", "as of today": "tänään",
"booking.accommodatesUpTo": "Huoneeseen {nrOfGuests, plural, one {# person} other {mahtuu 2 henkilöä}}", "booking.accommodatesUpTo": "Huoneeseen {nrOfGuests, plural, one {# person} other {mahtuu 2 henkilöä}}",
"booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}", "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.basedOnAvailability": "Saatavuuden mukaan",
"booking.bedOptions": "Vuodevaihtoehdot", "booking.bedOptions": "Vuodevaihtoehdot",
"booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}", "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.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.confirmation.title": "Varausvahvistus",
"booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}", "booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}",
@@ -506,6 +508,9 @@
"booking.selectRoom": "Valitse huone", "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.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.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": "{amount} {currency}/yö",
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/yö", "breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/yö",
"by": "mennessä", "by": "mennessä",

View File

@@ -178,8 +178,8 @@
"Guests & Rooms": "Gjester & rom", "Guests & Rooms": "Gjester & rom",
"Gym": "Treningsstudio", "Gym": "Treningsstudio",
"Hi": "Hei", "Hi": "Hei",
"High level": "Høy nivå", "High floor": "Høy nivå",
"Highest level": "Høyeste nivå", "Highest floor": "Høyeste nivå",
"Home": "Hjem", "Home": "Hjem",
"Hospital": "Sykehus", "Hospital": "Sykehus",
"Hotel": "Hotel", "Hotel": "Hotel",
@@ -495,9 +495,11 @@
"as of today": "per i dag", "as of today": "per i dag",
"booking.accommodatesUpTo": "Plass til {nrOfGuests, plural, one {# person} other {opptil # personer}}", "booking.accommodatesUpTo": "Plass til {nrOfGuests, plural, one {# person} other {opptil # personer}}",
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}", "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.basedOnAvailability": "Basert på tilgjengelighet",
"booking.bedOptions": "Sengemuligheter", "booking.bedOptions": "Sengemuligheter",
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}", "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.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.confirmation.title": "Bestillingsbekreftelse",
"booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}", "booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}",
@@ -506,6 +508,9 @@
"booking.selectRoom": "Velg rom", "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.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.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": "{amount} {currency}/natt",
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/natt", "breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/natt",
"by": "innen", "by": "innen",

View File

@@ -178,8 +178,8 @@
"Guests & Rooms": "Gäster & rum", "Guests & Rooms": "Gäster & rum",
"Gym": "Gym", "Gym": "Gym",
"Hi": "Hej", "Hi": "Hej",
"High level": "Högt upp", "High floor": "Högt upp",
"Highest level": "Högsta nivå", "Highest floor": "Högsta nivå",
"Home": "Hem", "Home": "Hem",
"Hospital": "Sjukhus", "Hospital": "Sjukhus",
"Hotel": "Hotell", "Hotel": "Hotell",
@@ -495,9 +495,11 @@
"as of today": "från och med idag", "as of today": "från och med idag",
"booking.accommodatesUpTo": "Rymmer {nrOfGuests, plural, one {# person} other {upp till # personer}}", "booking.accommodatesUpTo": "Rymmer {nrOfGuests, plural, one {# person} other {upp till # personer}}",
"booking.adults": "{totalAdults, plural, one {# vuxen} other {# vuxna}}", "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.basedOnAvailability": "Baserat på tillgänglighet",
"booking.bedOptions": "Sängalternativ", "booking.bedOptions": "Sängalternativ",
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}", "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.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.confirmation.title": "Bokningsbekräftelse",
"booking.guests": "Max {nrOfGuests, plural, one {# gäst} other {# gäster}}", "booking.guests": "Max {nrOfGuests, plural, one {# gäst} other {# gäster}}",
@@ -506,6 +508,9 @@
"booking.selectRoom": "Välj rum", "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.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.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": "{amount} {currency}/natt",
"breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/natt", "breakfast.price.free": "<strikethrough>{amount} {currency}</strikethrough> <free>0 {currency}</free>/natt",
"by": "innan", "by": "innan",

View File

@@ -32,11 +32,12 @@ export default function EnterDetailsProvider({
searchParamsStr, searchParamsStr,
step, step,
user, user,
vat,
}: DetailsProviderProps) { }: DetailsProviderProps) {
const storeRef = useRef<DetailsStore>() const storeRef = useRef<DetailsStore>()
if (!storeRef.current) { if (!storeRef.current) {
const initialData: InitialState = { booking, packages, roomRate } const initialData: InitialState = { booking, packages, roomRate, vat }
if (bedTypes.length === 1) { if (bedTypes.length === 1) {
initialData.bedType = { initialData.bedType = {
description: bedTypes[0].description, description: bedTypes[0].description,

View File

@@ -435,6 +435,7 @@ export const hotelAttributesSchema = z.object({
socialMedia: socialMediaSchema, socialMedia: socialMediaSchema,
specialAlerts: specialAlertsSchema, specialAlerts: specialAlertsSchema,
specialNeedGroups: z.array(specialNeedGroupSchema), specialNeedGroups: z.array(specialNeedGroupSchema),
vat: z.number(),
}) })
const includedSchema = z const includedSchema = z

View File

@@ -15,6 +15,8 @@ import type {
DetailsState, DetailsState,
PersistedState, PersistedState,
PersistedStatePart, PersistedStatePart,
Price,
RoomPrice,
RoomRate, RoomRate,
} from "@/types/stores/enter-details" } from "@/types/stores/enter-details"
import type { SafeUser } from "@/types/user" import type { SafeUser } from "@/types/user"
@@ -106,25 +108,49 @@ export function subtract(...nums: (number | string | undefined)[]) {
export function getInitialRoomPrice(roomRate: RoomRate, isMember: boolean) { export function getInitialRoomPrice(roomRate: RoomRate, isMember: boolean) {
if (isMember && roomRate.memberRate) { if (isMember && roomRate.memberRate) {
return { return {
requested: roomRate.memberRate.requestedPrice && { perNight: {
currency: roomRate.memberRate.requestedPrice.currency, requested: roomRate.memberRate.requestedPrice && {
price: roomRate.memberRate.requestedPrice.pricePerStay, currency: roomRate.memberRate.requestedPrice.currency,
price: roomRate.memberRate.requestedPrice.pricePerNight,
},
local: {
currency: roomRate.memberRate.localPrice.currency,
price: roomRate.memberRate.localPrice.pricePerNight,
},
}, },
local: { perStay: {
currency: roomRate.memberRate.localPrice.currency, requested: roomRate.memberRate.requestedPrice && {
price: roomRate.memberRate.localPrice.pricePerStay, currency: roomRate.memberRate.requestedPrice.currency,
price: roomRate.memberRate.requestedPrice.pricePerStay,
},
local: {
currency: roomRate.memberRate.localPrice.currency,
price: roomRate.memberRate.localPrice.pricePerStay,
},
}, },
} }
} }
return { return {
requested: roomRate.publicRate.requestedPrice && { perNight: {
currency: roomRate.publicRate.requestedPrice.currency, requested: roomRate.publicRate.requestedPrice && {
price: roomRate.publicRate.requestedPrice.pricePerStay, currency: roomRate.publicRate.requestedPrice.currency,
price: roomRate.publicRate.requestedPrice.pricePerNight,
},
local: {
currency: roomRate.publicRate.localPrice.currency,
price: roomRate.publicRate.localPrice.pricePerNight,
},
}, },
local: { perStay: {
currency: roomRate.publicRate.localPrice.currency, requested: roomRate.publicRate.requestedPrice && {
price: roomRate.publicRate.localPrice.pricePerStay, 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"] DetailsState["roomRate"]["publicRate"]
) { ) {
// state is sometimes read-only, thus we // 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 = { const roomAndTotalPrice = {
roomPrice: { ...state.roomPrice }, roomPrice: {
totalPrice: { ...state.totalPrice }, 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) { if (state.requestedPrice?.pricePerStay) {
roomAndTotalPrice.roomPrice.requested = { roomAndTotalPrice.roomPrice.perStay.requested = {
currency: state.requestedPrice.currency, currency: state.requestedPrice.currency,
price: state.requestedPrice.pricePerStay, price: state.requestedPrice.pricePerStay,
} }
@@ -225,7 +269,7 @@ export function calcTotalPrice(
} }
const roomPriceLocal = state.localPrice const roomPriceLocal = state.localPrice
roomAndTotalPrice.roomPrice.local = { roomAndTotalPrice.roomPrice.perStay.local = {
currency: roomPriceLocal.currency, currency: roomPriceLocal.currency,
price: roomPriceLocal.pricePerStay, price: roomPriceLocal.pricePerStay,
} }

View File

@@ -347,6 +347,7 @@ export function createDetailsStore(
roomRate: initialState.roomRate, roomRate: initialState.roomRate,
steps, steps,
totalPrice: initialTotalPrice, totalPrice: initialTotalPrice,
vat: initialState.vat,
})) }))
} }

View File

@@ -0,0 +1,3 @@
export interface PriceDetailsTableProps {
roomType: string
}

View File

@@ -1,7 +1,7 @@
import type { BedTypeSelection } from "@/types/components/hotelReservation/enterDetails/bedType" 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 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 { RoomAvailability } from "@/types/trpc/routers/hotel/availability"
import type { SafeUser } from "@/types/user" import type { SafeUser } from "@/types/user"
import type { Packages } from "../requests/packages" import type { Packages } from "../requests/packages"
@@ -15,4 +15,5 @@ export interface DetailsProviderProps extends React.PropsWithChildren {
searchParamsStr: string searchParamsStr: string
step: StepEnum step: StepEnum
user: SafeUser user: SafeUser
vat: number
} }

View File

@@ -14,6 +14,11 @@ interface TPrice {
price: number price: number
} }
export interface RoomPrice {
perNight: Price
perStay: Price
}
export interface Price { export interface Price {
requested: TPrice | undefined requested: TPrice | undefined
local: TPrice local: TPrice
@@ -52,14 +57,15 @@ export interface DetailsState {
isValid: Record<StepEnum, boolean> isValid: Record<StepEnum, boolean>
packages: Packages | null packages: Packages | null
roomRate: DetailsProviderProps["roomRate"] roomRate: DetailsProviderProps["roomRate"]
roomPrice: Price roomPrice: RoomPrice
steps: StepEnum[] steps: StepEnum[]
totalPrice: Price totalPrice: Price
searchParamString: string searchParamString: string
vat: number
} }
export type InitialState = Pick<DetailsState, "booking" | "packages"> & export type InitialState = Pick<DetailsState, "booking" | "packages"> &
Pick<DetailsProviderProps, "roomRate"> & { Pick<DetailsProviderProps, "roomRate" | "vat"> & {
bedType?: BedTypeSchema bedType?: BedTypeSchema
breakfast?: false breakfast?: false
} }

View File

@@ -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 * 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) 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
}