feat: save search params from select-rate to store
This commit is contained in:
@@ -7,7 +7,7 @@ import { preload } from "./page"
|
|||||||
|
|
||||||
import styles from "./layout.module.css"
|
import styles from "./layout.module.css"
|
||||||
|
|
||||||
import { StepEnum } from "@/types/components/enterDetails/step"
|
import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step"
|
||||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||||
|
|
||||||
export default async function StepLayout({
|
export default async function StepLayout({
|
||||||
@@ -19,19 +19,18 @@ export default async function StepLayout({
|
|||||||
LayoutArgs<LangParams & { step: StepEnum }> & {
|
LayoutArgs<LangParams & { step: StepEnum }> & {
|
||||||
hotelHeader: React.ReactNode
|
hotelHeader: React.ReactNode
|
||||||
sidePeek: React.ReactNode
|
sidePeek: React.ReactNode
|
||||||
}
|
}>) {
|
||||||
>) {
|
|
||||||
setLang(params.lang)
|
setLang(params.lang)
|
||||||
preload()
|
preload()
|
||||||
return (
|
return (
|
||||||
<EnterDetailsProvider step={params.step}>
|
<EnterDetailsProvider step={params.step} >
|
||||||
<main className={styles.layout}>
|
<main className={styles.layout}>
|
||||||
{hotelHeader}
|
{hotelHeader}
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<SelectedRoom />
|
<SelectedRoom />
|
||||||
{children}
|
{children}
|
||||||
<aside className={styles.summary}>
|
<aside className={styles.summary}>
|
||||||
<Summary />
|
<Summary isMember={false} />
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
{sidePeek}
|
{sidePeek}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import SectionAccordion from "@/components/HotelReservation/EnterDetails/Section
|
|||||||
import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import { StepEnum } from "@/types/components/enterDetails/step"
|
import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step"
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
export function preload() {
|
export function preload() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { dt } from "@/lib/dt"
|
|||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
import { formatNumber } from "@/utils/format"
|
||||||
import { getMembership } from "@/utils/user"
|
import { getMembership } from "@/utils/user"
|
||||||
|
|
||||||
import type { UserProps } from "@/types/components/myPages/user"
|
import type { UserProps } from "@/types/components/myPages/user"
|
||||||
@@ -16,9 +17,6 @@ export default async function ExpiringPoints({ user }: UserProps) {
|
|||||||
// TODO: handle this case?
|
// TODO: handle this case?
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// sv hardcoded to force space on thousands
|
|
||||||
const formatter = new Intl.NumberFormat(Lang.sv)
|
|
||||||
const d = dt(membership.pointsExpiryDate)
|
const d = dt(membership.pointsExpiryDate)
|
||||||
|
|
||||||
const dateFormat = getLang() == Lang.fi ? "DD.MM.YYYY" : "YYYY-MM-DD"
|
const dateFormat = getLang() == Lang.fi ? "DD.MM.YYYY" : "YYYY-MM-DD"
|
||||||
@@ -29,7 +27,7 @@ export default async function ExpiringPoints({ user }: UserProps) {
|
|||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "spendable points expiring by" },
|
{ id: "spendable points expiring by" },
|
||||||
{
|
{
|
||||||
points: formatter.format(membership.pointsToExpire),
|
points: formatNumber(membership.pointsToExpire),
|
||||||
date: d.format(dateFormat),
|
date: d.format(dateFormat),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
|
||||||
|
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import { formatNumber } from "@/utils/format"
|
||||||
|
|
||||||
import { awardPointsVariants } from "./awardPointsVariants"
|
import { awardPointsVariants } from "./awardPointsVariants"
|
||||||
|
|
||||||
@@ -32,12 +31,10 @@ export default function AwardPoints({
|
|||||||
variant,
|
variant,
|
||||||
})
|
})
|
||||||
|
|
||||||
// sv hardcoded to force space on thousands
|
|
||||||
const formatter = new Intl.NumberFormat(Lang.sv)
|
|
||||||
return (
|
return (
|
||||||
<Body textTransform="bold" className={classNames}>
|
<Body textTransform="bold" className={classNames}>
|
||||||
{isCalculated
|
{isCalculated
|
||||||
? formatter.format(awardPoints)
|
? formatNumber(awardPoints)
|
||||||
: intl.formatMessage({ id: "Points being calculated" })}
|
: intl.formatMessage({ id: "Points being calculated" })}
|
||||||
</Body>
|
</Body>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default function FooterSecondaryNav({
|
|||||||
<div className={styles.secondaryNavigation}>
|
<div className={styles.secondaryNavigation}>
|
||||||
{appDownloads && (
|
{appDownloads && (
|
||||||
<nav className={styles.secondaryNavigationGroup}>
|
<nav className={styles.secondaryNavigationGroup}>
|
||||||
<Body color="peach80" textTransform="uppercase">
|
<Body color="baseTextMediumContrast" textTransform="uppercase">
|
||||||
{appDownloads.title}
|
{appDownloads.title}
|
||||||
</Body>
|
</Body>
|
||||||
<ul className={styles.secondaryNavigationList}>
|
<ul className={styles.secondaryNavigationList}>
|
||||||
@@ -50,7 +50,7 @@ export default function FooterSecondaryNav({
|
|||||||
)}
|
)}
|
||||||
{secondaryLinks.map((link) => (
|
{secondaryLinks.map((link) => (
|
||||||
<nav className={styles.secondaryNavigationGroup} key={link.title}>
|
<nav className={styles.secondaryNavigationGroup} key={link.title}>
|
||||||
<Body color="peach80" textTransform="uppercase">
|
<Body color="baseTextMediumContrast" textTransform="uppercase">
|
||||||
{link.title}
|
{link.title}
|
||||||
</Body>
|
</Body>
|
||||||
<ul className={styles.secondaryNavigationList}>
|
<ul className={styles.secondaryNavigationList}>
|
||||||
|
|||||||
@@ -14,18 +14,17 @@ import { bedTypeSchema } from "./schema"
|
|||||||
|
|
||||||
import styles from "./bedOptions.module.css"
|
import styles from "./bedOptions.module.css"
|
||||||
|
|
||||||
import type { BedTypeSchema } from "@/types/components/enterDetails/bedType"
|
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||||
import { BedTypeEnum } from "@/types/enums/bedType"
|
|
||||||
|
|
||||||
export default function BedType() {
|
export default function BedType() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const bedType = useEnterDetailsStore((state) => state.data.bedType)
|
const bedType = useEnterDetailsStore((state) => state.userData.bedType)
|
||||||
|
|
||||||
const methods = useForm<BedTypeSchema>({
|
const methods = useForm<BedTypeSchema>({
|
||||||
defaultValues: bedType
|
defaultValues: bedType
|
||||||
? {
|
? {
|
||||||
bedType,
|
bedType,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
criteriaMode: "all",
|
criteriaMode: "all",
|
||||||
mode: "all",
|
mode: "all",
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ import styles from "./breakfast.module.css"
|
|||||||
import type {
|
import type {
|
||||||
BreakfastFormSchema,
|
BreakfastFormSchema,
|
||||||
BreakfastProps,
|
BreakfastProps,
|
||||||
} from "@/types/components/enterDetails/breakfast"
|
} from "@/types/components/hotelReservation/enterDetails/breakfast"
|
||||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||||
|
|
||||||
export default function Breakfast({ packages }: BreakfastProps) {
|
export default function Breakfast({ packages }: BreakfastProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const breakfast = useEnterDetailsStore((state) => state.data.breakfast)
|
const breakfast = useEnterDetailsStore((state) => state.userData.breakfast)
|
||||||
|
|
||||||
let defaultValues = undefined
|
let defaultValues = undefined
|
||||||
if (breakfast === BreakfastPackageEnum.NO_BREAKFAST) {
|
if (breakfast === BreakfastPackageEnum.NO_BREAKFAST) {
|
||||||
@@ -76,21 +76,21 @@ export default function Breakfast({ packages }: BreakfastProps) {
|
|||||||
subtitle={
|
subtitle={
|
||||||
pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||||
? intl.formatMessage<React.ReactNode>(
|
? intl.formatMessage<React.ReactNode>(
|
||||||
{ id: "breakfast.price.free" },
|
{ id: "breakfast.price.free" },
|
||||||
{
|
{
|
||||||
amount: pkg.originalPrice,
|
amount: pkg.originalPrice,
|
||||||
currency: pkg.currency,
|
currency: pkg.currency,
|
||||||
free: (str) => <Highlight>{str}</Highlight>,
|
free: (str) => <Highlight>{str}</Highlight>,
|
||||||
strikethrough: (str) => <s>{str}</s>,
|
strikethrough: (str) => <s>{str}</s>,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: intl.formatMessage(
|
: intl.formatMessage(
|
||||||
{ id: "breakfast.price" },
|
{ id: "breakfast.price" },
|
||||||
{
|
{
|
||||||
amount: pkg.packagePrice,
|
amount: pkg.packagePrice,
|
||||||
currency: pkg.currency,
|
currency: pkg.currency,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
text={intl.formatMessage({
|
text={intl.formatMessage({
|
||||||
id: "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.",
|
id: "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.",
|
||||||
|
|||||||
@@ -22,21 +22,21 @@ import styles from "./details.module.css"
|
|||||||
import type {
|
import type {
|
||||||
DetailsProps,
|
DetailsProps,
|
||||||
DetailsSchema,
|
DetailsSchema,
|
||||||
} from "@/types/components/enterDetails/details"
|
} from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
|
|
||||||
const formID = "enter-details"
|
const formID = "enter-details"
|
||||||
export default function Details({ user }: DetailsProps) {
|
export default function Details({ user }: DetailsProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const initialData = useEnterDetailsStore((state) => ({
|
const initialData = useEnterDetailsStore((state) => ({
|
||||||
countryCode: state.data.countryCode,
|
countryCode: state.userData.countryCode,
|
||||||
email: state.data.email,
|
email: state.userData.email,
|
||||||
firstName: state.data.firstName,
|
firstName: state.userData.firstName,
|
||||||
lastName: state.data.lastName,
|
lastName: state.userData.lastName,
|
||||||
phoneNumber: state.data.phoneNumber,
|
phoneNumber: state.userData.phoneNumber,
|
||||||
join: state.data.join,
|
join: state.userData.join,
|
||||||
dateOfBirth: state.data.dateOfBirth,
|
dateOfBirth: state.userData.dateOfBirth,
|
||||||
zipCode: state.data.zipCode,
|
zipCode: state.userData.zipCode,
|
||||||
termsAccepted: state.data.termsAccepted,
|
termsAccepted: state.userData.termsAccepted,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const methods = useForm<DetailsSchema>({
|
const methods = useForm<DetailsSchema>({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
import { useSearchParams } from "next/navigation"
|
||||||
import { PropsWithChildren, useRef } from "react"
|
import { PropsWithChildren, useRef } from "react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -7,15 +8,16 @@ import {
|
|||||||
initEditDetailsState,
|
initEditDetailsState,
|
||||||
} from "@/stores/enter-details"
|
} from "@/stores/enter-details"
|
||||||
|
|
||||||
import { StepEnum } from "@/types/components/enterDetails/step"
|
import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step"
|
||||||
|
|
||||||
export default function EnterDetailsProvider({
|
export default function EnterDetailsProvider({
|
||||||
step,
|
step,
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<{ step: StepEnum }>) {
|
}: PropsWithChildren<{ step: StepEnum }>) {
|
||||||
|
const searchParams = useSearchParams()
|
||||||
const initialStore = useRef<EnterDetailsStore>()
|
const initialStore = useRef<EnterDetailsStore>()
|
||||||
if (!initialStore.current) {
|
if (!initialStore.current) {
|
||||||
initialStore.current = initEditDetailsState(step)
|
initialStore.current = initEditDetailsState(step, searchParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import styles from "./enterDetailsSidePeek.module.css"
|
|||||||
import {
|
import {
|
||||||
SidePeekEnum,
|
SidePeekEnum,
|
||||||
SidePeekProps,
|
SidePeekProps,
|
||||||
} from "@/types/components/enterDetails/sidePeek"
|
} from "@/types/components/hotelReservation/enterDetails/sidePeek"
|
||||||
|
|
||||||
export default function EnterDetailsSidePeek({ hotel }: SidePeekProps) {
|
export default function EnterDetailsSidePeek({ hotel }: SidePeekProps) {
|
||||||
const activeSidePeek = useEnterDetailsStore((state) => state.activeSidePeek)
|
const activeSidePeek = useEnterDetailsStore((state) => state.activeSidePeek)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details"
|
|||||||
import { ChevronRightSmallIcon } from "@/components/Icons"
|
import { ChevronRightSmallIcon } from "@/components/Icons"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
|
||||||
import { SidePeekEnum } from "@/types/components/enterDetails/sidePeek"
|
import { SidePeekEnum } from "@/types/components/hotelReservation/enterDetails/sidePeek"
|
||||||
|
|
||||||
export default function ToggleSidePeek() {
|
export default function ToggleSidePeek() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|||||||
@@ -1,154 +1,206 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
import { trpc } from "@/lib/trpc/client"
|
||||||
|
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||||
|
|
||||||
import { ArrowRightIcon } from "@/components/Icons"
|
import { ArrowRightIcon } from "@/components/Icons"
|
||||||
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
import Divider from "@/components/TempDesignSystem/Divider"
|
import Divider from "@/components/TempDesignSystem/Divider"
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import { getIntl } from "@/i18n"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import useLang from "@/hooks/useLang"
|
||||||
|
import { formatNumber } from "@/utils/format"
|
||||||
import ToggleSidePeek from "./ToggleSidePeek"
|
|
||||||
|
|
||||||
import styles from "./summary.module.css"
|
import styles from "./summary.module.css"
|
||||||
|
|
||||||
// TEMP
|
import { RoomsData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||||
const rooms = [
|
|
||||||
{
|
|
||||||
adults: 1,
|
|
||||||
type: "Cozy cabin",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default async function Summary() {
|
export default function Summary({ isMember }: { isMember: boolean }) {
|
||||||
const intl = await getIntl()
|
const intl = useIntl()
|
||||||
const lang = getLang()
|
const lang = useLang()
|
||||||
const fromDate = dt().locale(lang).format("ddd, D MMM")
|
const { fromDate, toDate, rooms, hotel, bedType, breakfast } =
|
||||||
const toDate = dt().add(1, "day").locale(lang).format("ddd, D MMM")
|
useEnterDetailsStore((state) => ({
|
||||||
const diff = dt(toDate).diff(fromDate, "days")
|
fromDate: state.roomData.fromdate,
|
||||||
|
toDate: state.roomData.todate,
|
||||||
|
rooms: state.roomData.room,
|
||||||
|
hotel: state.roomData.hotel,
|
||||||
|
bedType: state.userData.bedType,
|
||||||
|
breakfast: state.userData.breakfast,
|
||||||
|
}))
|
||||||
|
|
||||||
const totalAdults = rooms.reduce((total, room) => total + room.adults, 0)
|
const totalAdults = rooms.reduce((total, room) => total + room.adults, 0)
|
||||||
|
|
||||||
const adults = intl.formatMessage(
|
const {
|
||||||
{ id: "booking.adults" },
|
data: availabilityData,
|
||||||
{ totalAdults: totalAdults }
|
isLoading,
|
||||||
|
error,
|
||||||
|
} = trpc.hotel.availability.rooms.useQuery(
|
||||||
|
{
|
||||||
|
hotelId: parseInt(hotel),
|
||||||
|
adults: totalAdults,
|
||||||
|
roomStayStartDate: dt(fromDate).format("YYYY-MM-DD"),
|
||||||
|
roomStayEndDate: dt(toDate).format("YYYY-MM-DD"),
|
||||||
|
},
|
||||||
|
{ enabled: !!hotel && !!fromDate && !!toDate }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const diff = dt(toDate).diff(fromDate, "days")
|
||||||
|
|
||||||
const nights = intl.formatMessage(
|
const nights = intl.formatMessage(
|
||||||
{ id: "booking.nights" },
|
{ id: "booking.nights" },
|
||||||
{ totalNights: diff }
|
{ totalNights: diff }
|
||||||
)
|
)
|
||||||
|
|
||||||
const addOns = [
|
if (isLoading) {
|
||||||
{
|
return <LoadingSpinner />
|
||||||
price: intl.formatMessage({ id: "Included" }),
|
}
|
||||||
title: intl.formatMessage({ id: "King bed" }),
|
const populatedRooms = rooms
|
||||||
},
|
.map((room) => {
|
||||||
{
|
const chosenRoom = availabilityData?.roomConfigurations.find(
|
||||||
price: intl.formatMessage({ id: "Included" }),
|
(availRoom) => room.roomtypecode === availRoom.roomTypeCode
|
||||||
title: intl.formatMessage({ id: "Breakfast buffet" }),
|
)
|
||||||
},
|
const cancellationText = availabilityData?.rateDefinitions.find(
|
||||||
]
|
(rate) => rate.rateCode === room.ratecode
|
||||||
|
)?.cancellationText
|
||||||
|
|
||||||
const mappedRooms = Array.from(
|
if (chosenRoom) {
|
||||||
rooms
|
const memberPrice = chosenRoom.products.find(
|
||||||
.reduce((acc, room) => {
|
(rate) => rate.productType.member?.rateCode === room.ratecode
|
||||||
const currentRoom = acc.get(room.type)
|
)?.productType.member?.localPrice.pricePerStay
|
||||||
acc.set(room.type, {
|
const publicPrice = chosenRoom.products.find(
|
||||||
total: currentRoom ? currentRoom.total + 1 : 1,
|
(rate) => rate.productType.public?.rateCode === room.ratecode
|
||||||
type: room.type,
|
)?.productType.public?.localPrice.pricePerStay
|
||||||
})
|
|
||||||
return acc
|
return {
|
||||||
}, new Map())
|
roomType: chosenRoom.roomType,
|
||||||
.values()
|
memberPrice: memberPrice && formatNumber(parseInt(memberPrice)),
|
||||||
)
|
publicPrice: publicPrice && formatNumber(parseInt(publicPrice)),
|
||||||
|
adults: room.adults,
|
||||||
|
children: room.child,
|
||||||
|
cancellationText,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((room): room is RoomsData => room !== undefined)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.summary}>
|
<section className={styles.summary}>
|
||||||
<header>
|
<header>
|
||||||
<Body textTransform="bold">
|
<Subtitle type="two">{intl.formatMessage({ id: "Summary" })}</Subtitle>
|
||||||
{mappedRooms.map(
|
<Body className={styles.date} color="baseTextMediumContrast">
|
||||||
(room, idx) =>
|
{dt(fromDate).locale(lang).format("ddd, D MMM")}
|
||||||
`${room.total} x ${room.type}${mappedRooms.length > 1 && idx + 1 !== mappedRooms.length ? ", " : ""}`
|
<ArrowRightIcon color="peach80" height={15} width={15} />
|
||||||
)}
|
{dt(toDate).locale(lang).format("ddd, D MMM")} ({nights})
|
||||||
</Body>
|
</Body>
|
||||||
<Body className={styles.date} color="textMediumContrast">
|
|
||||||
{fromDate}
|
|
||||||
<ArrowRightIcon color="uiTextMediumContrast" height={15} width={15} />
|
|
||||||
{toDate}
|
|
||||||
</Body>
|
|
||||||
|
|
||||||
<ToggleSidePeek />
|
|
||||||
</header>
|
</header>
|
||||||
<Divider color="primaryLightSubtle" />
|
<Divider color="primaryLightSubtle" />
|
||||||
<div className={styles.addOns}>
|
<div className={styles.addOns}>
|
||||||
<div className={styles.entry}>
|
{populatedRooms.map((room, idx) => (
|
||||||
<Caption color="uiTextMediumContrast">
|
<RoomBreakdown key={idx} room={room} isMember={isMember} />
|
||||||
{`${nights}, ${adults}`}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextHighContrast">
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "{amount} {currency}" },
|
|
||||||
{ amount: "4536", currency: "SEK" }
|
|
||||||
)}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
|
||||||
{addOns.map((addOn) => (
|
|
||||||
<div className={styles.entry} key={addOn.title}>
|
|
||||||
<Caption color="uiTextMediumContrast">{addOn.title}</Caption>
|
|
||||||
<Caption color="uiTextHighContrast">{addOn.price}</Caption>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{bedType ? (
|
||||||
|
<div className={styles.entry}>
|
||||||
|
<Body color="textHighContrast">{bedType}</Body>
|
||||||
|
<Caption color="red">
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{amount} {currency}" },
|
||||||
|
{ amount: "0", currency: "SEK" }
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{breakfast ? (
|
||||||
|
<div className={styles.entry}>
|
||||||
|
<Body color="textHighContrast">{breakfast}</Body>
|
||||||
|
<Caption color="red">
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{amount} {currency}" },
|
||||||
|
{ amount: "0", currency: "SEK" }
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Divider color="primaryLightSubtle" />
|
<Divider color="primaryLightSubtle" />
|
||||||
<div className={styles.total}>
|
<div className={styles.total}>
|
||||||
<div>
|
<div className={styles.entry}>
|
||||||
<div className={styles.entry}>
|
<Body textTransform="bold">
|
||||||
<Body textTransform="bold">
|
{intl.formatMessage({ id: "Total price (incl VAT)" })}
|
||||||
{intl.formatMessage({ id: "Total incl VAT" })}
|
</Body>
|
||||||
</Body>
|
<Body textTransform="bold">
|
||||||
<Body textTransform="bold">
|
{intl.formatMessage(
|
||||||
{intl.formatMessage(
|
{ id: "{amount} {currency}" },
|
||||||
{ id: "{amount} {currency}" },
|
{ amount: "4686", currency: "SEK" }
|
||||||
{ amount: "4686", currency: "SEK" }
|
)}
|
||||||
)}
|
</Body>
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
<div className={styles.entry}>
|
|
||||||
<Caption color="uiTextMediumContrast">
|
|
||||||
{intl.formatMessage({ id: "Approx." })}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextMediumContrast">
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "{amount} {currency}" },
|
|
||||||
{ amount: "455", currency: "EUR" }
|
|
||||||
)}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className={styles.entry}>
|
||||||
<div className={styles.entry}>
|
<Caption color="uiTextMediumContrast">
|
||||||
<Body color="red" textTransform="bold">
|
{intl.formatMessage({ id: "Approx." })}
|
||||||
{intl.formatMessage({ id: "Member price" })}
|
</Caption>
|
||||||
</Body>
|
<Caption color="uiTextMediumContrast">
|
||||||
<Body color="red" textTransform="bold">
|
{intl.formatMessage(
|
||||||
{intl.formatMessage(
|
{ id: "{amount} {currency}" },
|
||||||
{ id: "{amount} {currency}" },
|
{ amount: "455", currency: "EUR" }
|
||||||
{ amount: "4219", currency: "SEK" }
|
)}
|
||||||
)}
|
</Caption>
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
<div className={styles.entry}>
|
|
||||||
<Caption color="uiTextMediumContrast">
|
|
||||||
{intl.formatMessage({ id: "Approx." })}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="uiTextMediumContrast">
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "{amount} {currency}" },
|
|
||||||
{ amount: "412", currency: "EUR" }
|
|
||||||
)}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RoomBreakdown({
|
||||||
|
room,
|
||||||
|
isMember,
|
||||||
|
}: {
|
||||||
|
room: RoomsData
|
||||||
|
isMember: boolean
|
||||||
|
}) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
let color: "uiTextHighContrast" | "red" = "uiTextHighContrast"
|
||||||
|
let price = room.publicPrice
|
||||||
|
if (isMember) {
|
||||||
|
color = "red"
|
||||||
|
price = room.memberPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.entry}>
|
||||||
|
<Body color="textHighContrast">{room.roomType}</Body>
|
||||||
|
<Caption color={color}>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{amount} {currency}" },
|
||||||
|
{ amount: price, currency: "SEK" }
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
<Caption color="uiTextMediumContrast">
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "booking.adults" },
|
||||||
|
{ totalAdults: room.adults }
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
{room.children?.length ? (
|
||||||
|
<Caption color="uiTextMediumContrast">
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "booking.children" },
|
||||||
|
{ totalChildren: room.children.length }
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
) : null}
|
||||||
|
<Caption color="uiTextMediumContrast">{room.cancellationText}</Caption>
|
||||||
|
<Link color="burgundy" href="#" variant="underscored" size="small">
|
||||||
|
{intl.formatMessage({ id: "Rate details" })}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
.addOns {
|
.addOns {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x-one-and-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
color: var(--Primary-Dark-On-Surface-Accent);
|
color: var(--Primary-Dark-On-Surface-Accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.peach80 {
|
.baseTextMediumContrast {
|
||||||
color: var(--Base-Text-Medium-contrast);
|
color: var(--Base-Text-Medium-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const config = {
|
|||||||
textHighContrast: styles.textHighContrast,
|
textHighContrast: styles.textHighContrast,
|
||||||
white: styles.white,
|
white: styles.white,
|
||||||
peach50: styles.peach50,
|
peach50: styles.peach50,
|
||||||
peach80: styles.peach80,
|
baseTextMediumContrast: styles.baseTextMediumContrast,
|
||||||
uiTextHighContrast: styles.uiTextHighContrast,
|
uiTextHighContrast: styles.uiTextHighContrast,
|
||||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||||
|
|||||||
@@ -313,8 +313,8 @@
|
|||||||
"Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}",
|
"Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}",
|
||||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For at sikre din reservation, beder vi om at du giver os dine betalingsoplysninger. Du kan så være sikker på, at ingen gebyrer vil blive opkrævet på dette tidspunkt.",
|
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For at sikre din reservation, beder vi om at du giver os dine betalingsoplysninger. Du kan så være sikker på, at ingen gebyrer vil blive opkrævet på dette tidspunkt.",
|
||||||
"Total Points": "Samlet antal point",
|
"Total Points": "Samlet antal point",
|
||||||
"Total incl VAT": "Inkl. moms",
|
|
||||||
"Total price": "Samlet pris",
|
"Total price": "Samlet pris",
|
||||||
|
"Total price (incl VAT)": "Samlet pris (inkl. moms)",
|
||||||
"Tourist": "Turist",
|
"Tourist": "Turist",
|
||||||
"Transaction date": "Overførselsdato",
|
"Transaction date": "Overførselsdato",
|
||||||
"Transactions": "Transaktioner",
|
"Transactions": "Transaktioner",
|
||||||
|
|||||||
@@ -312,8 +312,8 @@
|
|||||||
"Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}",
|
"Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}",
|
||||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Um Ihre Reservierung zu sichern, bitten wir Sie, Ihre Zahlungskarteninformationen zu geben. Sie können sicher sein, dass keine Gebühren zu diesem Zeitpunkt erhoben werden.",
|
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Um Ihre Reservierung zu sichern, bitten wir Sie, Ihre Zahlungskarteninformationen zu geben. Sie können sicher sein, dass keine Gebühren zu diesem Zeitpunkt erhoben werden.",
|
||||||
"Total Points": "Gesamtpunktzahl",
|
"Total Points": "Gesamtpunktzahl",
|
||||||
"Total incl VAT": "Gesamt inkl. MwSt.",
|
|
||||||
"Total price": "Gesamtpreis",
|
"Total price": "Gesamtpreis",
|
||||||
|
"Total price (incl VAT)": "Gesamtpreis (inkl. MwSt.)",
|
||||||
"Tourist": "Tourist",
|
"Tourist": "Tourist",
|
||||||
"Transaction date": "Transaktionsdatum",
|
"Transaction date": "Transaktionsdatum",
|
||||||
"Transactions": "Transaktionen",
|
"Transactions": "Transaktionen",
|
||||||
|
|||||||
@@ -326,10 +326,10 @@
|
|||||||
"There are no transactions to display": "There are no transactions to display",
|
"There are no transactions to display": "There are no transactions to display",
|
||||||
"Things nearby HOTEL_NAME": "Things nearby {hotelName}",
|
"Things nearby HOTEL_NAME": "Things nearby {hotelName}",
|
||||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.",
|
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.",
|
||||||
"Total Points": "Total Points",
|
|
||||||
"Total cost": "Total cost",
|
"Total cost": "Total cost",
|
||||||
"Total incl VAT": "Total incl VAT",
|
|
||||||
"Total price": "Total price",
|
"Total price": "Total price",
|
||||||
|
"Total Points": "Total Points",
|
||||||
|
"Total price (incl VAT)": "Total price (incl VAT)",
|
||||||
"Tourist": "Tourist",
|
"Tourist": "Tourist",
|
||||||
"Transaction date": "Transaction date",
|
"Transaction date": "Transaction date",
|
||||||
"Transactions": "Transactions",
|
"Transactions": "Transactions",
|
||||||
|
|||||||
@@ -314,8 +314,8 @@
|
|||||||
"Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}",
|
"Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}",
|
||||||
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Varmistaaksesi varauksen, pyydämme sinua antamaan meille maksukortin tiedot. Varmista, että ei veloiteta maksusi tällä hetkellä.",
|
"To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Varmistaaksesi varauksen, pyydämme sinua antamaan meille maksukortin tiedot. Varmista, että ei veloiteta maksusi tällä hetkellä.",
|
||||||
"Total Points": "Kokonaispisteet",
|
"Total Points": "Kokonaispisteet",
|
||||||
"Total incl VAT": "Yhteensä sis. alv",
|
|
||||||
"Total price": "Kokonaishinta",
|
"Total price": "Kokonaishinta",
|
||||||
|
"Total price (incl VAT)": "Kokonaishinta (sis. ALV)",
|
||||||
"Tourist": "Turisti",
|
"Tourist": "Turisti",
|
||||||
"Transaction date": "Tapahtuman päivämäärä",
|
"Transaction date": "Tapahtuman päivämäärä",
|
||||||
"Transactions": "Tapahtumat",
|
"Transactions": "Tapahtumat",
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
import { produce } from "immer"
|
import { produce } from "immer"
|
||||||
|
import { ReadonlyURLSearchParams } from "next/navigation"
|
||||||
import { createContext, useContext } from "react"
|
import { createContext, useContext } from "react"
|
||||||
import { create, useStore } from "zustand"
|
import { create, useStore } from "zustand"
|
||||||
|
|
||||||
import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema"
|
import { bedTypeSchema } from "@/components/HotelReservation/EnterDetails/BedType/schema"
|
||||||
import { breakfastStoreSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema"
|
import { breakfastStoreSchema } from "@/components/HotelReservation/EnterDetails/Breakfast/schema"
|
||||||
import { detailsSchema } from "@/components/HotelReservation/EnterDetails/Details/schema"
|
import { detailsSchema } from "@/components/HotelReservation/EnterDetails/Details/schema"
|
||||||
|
import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||||
|
|
||||||
import { BreakfastPackage } from "@/types/components/enterDetails/breakfast"
|
import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast"
|
||||||
import { DetailsSchema } from "@/types/components/enterDetails/details"
|
import { SidePeekEnum } from "@/types/components/hotelReservation/enterDetails/sidePeek"
|
||||||
import { SidePeekEnum } from "@/types/components/enterDetails/sidePeek"
|
import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step"
|
||||||
import { StepEnum } from "@/types/components/enterDetails/step"
|
|
||||||
import { BedTypeEnum } from "@/types/enums/bedType"
|
import { BedTypeEnum } from "@/types/enums/bedType"
|
||||||
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
|
||||||
|
import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||||
|
import type { DetailsSchema } from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
|
|
||||||
const SESSION_STORAGE_KEY = "enterDetails"
|
const SESSION_STORAGE_KEY = "enterDetails"
|
||||||
|
|
||||||
interface EnterDetailsState {
|
interface EnterDetailsState {
|
||||||
data: {
|
userData: {
|
||||||
bedType: BedTypeEnum | undefined
|
bedType: BedTypeEnum | undefined
|
||||||
breakfast: BreakfastPackage | BreakfastPackageEnum.NO_BREAKFAST | undefined
|
breakfast: BreakfastPackage | BreakfastPackageEnum.NO_BREAKFAST | undefined
|
||||||
} & DetailsSchema
|
} & DetailsSchema
|
||||||
|
roomData: BookingData
|
||||||
steps: StepEnum[]
|
steps: StepEnum[]
|
||||||
currentStep: StepEnum
|
currentStep: StepEnum
|
||||||
activeSidePeek: SidePeekEnum | null
|
activeSidePeek: SidePeekEnum | null
|
||||||
isValid: Record<StepEnum, boolean>
|
isValid: Record<StepEnum, boolean>
|
||||||
completeStep: (updatedData: Partial<EnterDetailsState["data"]>) => void
|
completeStep: (updatedData: Partial<EnterDetailsState["userData"]>) => void
|
||||||
navigate: (
|
navigate: (
|
||||||
step: StepEnum,
|
step: StepEnum,
|
||||||
updatedData?: Record<string, string | boolean | BreakfastPackage>
|
updatedData?: Record<string, string | boolean | BreakfastPackage>
|
||||||
@@ -34,13 +38,60 @@ interface EnterDetailsState {
|
|||||||
closeSidePeek: () => void
|
closeSidePeek: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initEditDetailsState(currentStep: StepEnum) {
|
function getUpdatedValue<T>(
|
||||||
|
searchParams: URLSearchParams,
|
||||||
|
key: string,
|
||||||
|
defaultValue: T
|
||||||
|
): T {
|
||||||
|
const value = searchParams.get(key)
|
||||||
|
if (value === null) return defaultValue
|
||||||
|
if (typeof defaultValue === "number")
|
||||||
|
return parseInt(value, 10) as unknown as T
|
||||||
|
if (typeof defaultValue === "boolean")
|
||||||
|
return (value === "true") as unknown as T
|
||||||
|
if (defaultValue instanceof Date) return new Date(value) as unknown as T
|
||||||
|
|
||||||
|
return value as unknown as T
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initEditDetailsState(
|
||||||
|
currentStep: StepEnum,
|
||||||
|
searchParams: ReadonlyURLSearchParams
|
||||||
|
) {
|
||||||
const isBrowser = typeof window !== "undefined"
|
const isBrowser = typeof window !== "undefined"
|
||||||
const sessionData = isBrowser
|
const sessionData = isBrowser
|
||||||
? sessionStorage.getItem(SESSION_STORAGE_KEY)
|
? sessionStorage.getItem(SESSION_STORAGE_KEY)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const defaultData: EnterDetailsState["data"] = {
|
const today = new Date()
|
||||||
|
const tomorrow = new Date()
|
||||||
|
tomorrow.setDate(today.getDate() + 1)
|
||||||
|
|
||||||
|
let roomData: BookingData
|
||||||
|
if (searchParams?.size) {
|
||||||
|
roomData = getHotelReservationQueryParams(searchParams)
|
||||||
|
|
||||||
|
roomData.room = roomData.room.map((room, index) => ({
|
||||||
|
...room,
|
||||||
|
adults: getUpdatedValue(
|
||||||
|
searchParams,
|
||||||
|
`room[${index}].adults`,
|
||||||
|
room.adults
|
||||||
|
),
|
||||||
|
roomtypecode: getUpdatedValue(
|
||||||
|
searchParams,
|
||||||
|
`room[${index}].roomtypecode`,
|
||||||
|
room.roomtypecode
|
||||||
|
),
|
||||||
|
ratecode: getUpdatedValue(
|
||||||
|
searchParams,
|
||||||
|
`room[${index}].ratecode`,
|
||||||
|
room.ratecode
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultUserData: EnterDetailsState["userData"] = {
|
||||||
bedType: undefined,
|
bedType: undefined,
|
||||||
breakfast: undefined,
|
breakfast: undefined,
|
||||||
countryCode: "",
|
countryCode: "",
|
||||||
@@ -54,14 +105,14 @@ export function initEditDetailsState(currentStep: StepEnum) {
|
|||||||
termsAccepted: false,
|
termsAccepted: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputData = {}
|
let inputUserData = {}
|
||||||
if (sessionData) {
|
if (sessionData) {
|
||||||
inputData = JSON.parse(sessionData)
|
inputUserData = JSON.parse(sessionData)
|
||||||
}
|
}
|
||||||
|
|
||||||
const validPaths = [StepEnum.selectBed]
|
const validPaths = [StepEnum.selectBed]
|
||||||
|
|
||||||
let initialData: EnterDetailsState["data"] = defaultData
|
let initialData: EnterDetailsState["userData"] = defaultUserData
|
||||||
|
|
||||||
const isValid = {
|
const isValid = {
|
||||||
[StepEnum.selectBed]: false,
|
[StepEnum.selectBed]: false,
|
||||||
@@ -70,19 +121,19 @@ export function initEditDetailsState(currentStep: StepEnum) {
|
|||||||
[StepEnum.payment]: false,
|
[StepEnum.payment]: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedBedType = bedTypeSchema.safeParse(inputData)
|
const validatedBedType = bedTypeSchema.safeParse(inputUserData)
|
||||||
if (validatedBedType.success) {
|
if (validatedBedType.success) {
|
||||||
validPaths.push(StepEnum.breakfast)
|
validPaths.push(StepEnum.breakfast)
|
||||||
initialData = { ...initialData, ...validatedBedType.data }
|
initialData = { ...initialData, ...validatedBedType.data }
|
||||||
isValid[StepEnum.selectBed] = true
|
isValid[StepEnum.selectBed] = true
|
||||||
}
|
}
|
||||||
const validatedBreakfast = breakfastStoreSchema.safeParse(inputData)
|
const validatedBreakfast = breakfastStoreSchema.safeParse(inputUserData)
|
||||||
if (validatedBreakfast.success) {
|
if (validatedBreakfast.success) {
|
||||||
validPaths.push(StepEnum.details)
|
validPaths.push(StepEnum.details)
|
||||||
initialData = { ...initialData, ...validatedBreakfast.data }
|
initialData = { ...initialData, ...validatedBreakfast.data }
|
||||||
isValid[StepEnum.breakfast] = true
|
isValid[StepEnum.breakfast] = true
|
||||||
}
|
}
|
||||||
const validatedDetails = detailsSchema.safeParse(inputData)
|
const validatedDetails = detailsSchema.safeParse(inputUserData)
|
||||||
if (validatedDetails.success) {
|
if (validatedDetails.success) {
|
||||||
validPaths.push(StepEnum.payment)
|
validPaths.push(StepEnum.payment)
|
||||||
initialData = { ...initialData, ...validatedDetails.data }
|
initialData = { ...initialData, ...validatedDetails.data }
|
||||||
@@ -101,7 +152,8 @@ export function initEditDetailsState(currentStep: StepEnum) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return create<EnterDetailsState>()((set, get) => ({
|
return create<EnterDetailsState>()((set, get) => ({
|
||||||
data: initialData,
|
userData: initialData,
|
||||||
|
roomData,
|
||||||
steps: Object.values(StepEnum),
|
steps: Object.values(StepEnum),
|
||||||
setCurrentStep: (step) => set({ currentStep: step }),
|
setCurrentStep: (step) => set({ currentStep: step }),
|
||||||
navigate: (step, updatedData) =>
|
navigate: (step, updatedData) =>
|
||||||
@@ -129,14 +181,17 @@ export function initEditDetailsState(currentStep: StepEnum) {
|
|||||||
isValid,
|
isValid,
|
||||||
completeStep: (updatedData) =>
|
completeStep: (updatedData) =>
|
||||||
set(
|
set(
|
||||||
produce((state) => {
|
produce((state: EnterDetailsState) => {
|
||||||
state.isValid[state.currentStep] = true
|
state.isValid[state.currentStep] = true
|
||||||
|
|
||||||
const nextStep =
|
const nextStep =
|
||||||
state.steps[state.steps.indexOf(state.currentStep) + 1]
|
state.steps[state.steps.indexOf(state.currentStep) + 1]
|
||||||
|
|
||||||
state.data = { ...state.data, ...updatedData }
|
// @ts-expect-error: ts has a hard time understanding that "false | true" equals "boolean"
|
||||||
|
state.userData = {
|
||||||
|
...state.userData,
|
||||||
|
...updatedData,
|
||||||
|
}
|
||||||
state.currentStep = nextStep
|
state.currentStep = nextStep
|
||||||
get().navigate(nextStep, updatedData)
|
get().navigate(nextStep, updatedData)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
interface Child {
|
||||||
|
bed: string
|
||||||
|
age: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Room {
|
||||||
|
adults: number
|
||||||
|
roomtypecode: string
|
||||||
|
ratecode: string
|
||||||
|
child: Child[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BookingData {
|
||||||
|
hotel: string
|
||||||
|
fromdate: string
|
||||||
|
todate: string
|
||||||
|
room: Room[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RoomsData = {
|
||||||
|
roomType: string
|
||||||
|
memberPrice: string | undefined
|
||||||
|
publicPrice: string | undefined
|
||||||
|
adults: number
|
||||||
|
children: Child[]
|
||||||
|
cancellationText: string | undefined
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { StepEnum } from "../../enterDetails/step"
|
import { StepEnum } from "../enterDetails/step"
|
||||||
|
|
||||||
export interface SectionAccordionProps {
|
export interface SectionAccordionProps {
|
||||||
header: string
|
header: string
|
||||||
|
|||||||
8
utils/format.ts
Normal file
8
utils/format.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
|
/// Function to format numbers with space as thousands separator
|
||||||
|
export function formatNumber(num: number) {
|
||||||
|
// sv hardcoded to force space on thousands
|
||||||
|
const formatter = new Intl.NumberFormat(Lang.sv)
|
||||||
|
return formatter.format(num)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user