feat: save search params from select-rate to store

This commit is contained in:
Christel Westerberg
2024-10-22 16:19:15 +02:00
parent b5dce01fd3
commit 85fdefb5ac
28 changed files with 332 additions and 195 deletions

View File

@@ -14,18 +14,17 @@ import { bedTypeSchema } from "./schema"
import styles from "./bedOptions.module.css"
import type { BedTypeSchema } from "@/types/components/enterDetails/bedType"
import { BedTypeEnum } from "@/types/enums/bedType"
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
export default function BedType() {
const intl = useIntl()
const bedType = useEnterDetailsStore((state) => state.data.bedType)
const bedType = useEnterDetailsStore((state) => state.userData.bedType)
const methods = useForm<BedTypeSchema>({
defaultValues: bedType
? {
bedType,
}
bedType,
}
: undefined,
criteriaMode: "all",
mode: "all",

View File

@@ -17,13 +17,13 @@ import styles from "./breakfast.module.css"
import type {
BreakfastFormSchema,
BreakfastProps,
} from "@/types/components/enterDetails/breakfast"
} from "@/types/components/hotelReservation/enterDetails/breakfast"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
export default function Breakfast({ packages }: BreakfastProps) {
const intl = useIntl()
const breakfast = useEnterDetailsStore((state) => state.data.breakfast)
const breakfast = useEnterDetailsStore((state) => state.userData.breakfast)
let defaultValues = undefined
if (breakfast === BreakfastPackageEnum.NO_BREAKFAST) {
@@ -76,21 +76,21 @@ export default function Breakfast({ packages }: BreakfastProps) {
subtitle={
pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
? intl.formatMessage<React.ReactNode>(
{ id: "breakfast.price.free" },
{
amount: pkg.originalPrice,
currency: pkg.currency,
free: (str) => <Highlight>{str}</Highlight>,
strikethrough: (str) => <s>{str}</s>,
}
)
{ id: "breakfast.price.free" },
{
amount: pkg.originalPrice,
currency: pkg.currency,
free: (str) => <Highlight>{str}</Highlight>,
strikethrough: (str) => <s>{str}</s>,
}
)
: intl.formatMessage(
{ id: "breakfast.price" },
{
amount: pkg.packagePrice,
currency: pkg.currency,
}
)
{ id: "breakfast.price" },
{
amount: pkg.packagePrice,
currency: pkg.currency,
}
)
}
text={intl.formatMessage({
id: "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.",

View File

@@ -22,21 +22,21 @@ import styles from "./details.module.css"
import type {
DetailsProps,
DetailsSchema,
} from "@/types/components/enterDetails/details"
} from "@/types/components/hotelReservation/enterDetails/details"
const formID = "enter-details"
export default function Details({ user }: DetailsProps) {
const intl = useIntl()
const initialData = useEnterDetailsStore((state) => ({
countryCode: state.data.countryCode,
email: state.data.email,
firstName: state.data.firstName,
lastName: state.data.lastName,
phoneNumber: state.data.phoneNumber,
join: state.data.join,
dateOfBirth: state.data.dateOfBirth,
zipCode: state.data.zipCode,
termsAccepted: state.data.termsAccepted,
countryCode: state.userData.countryCode,
email: state.userData.email,
firstName: state.userData.firstName,
lastName: state.userData.lastName,
phoneNumber: state.userData.phoneNumber,
join: state.userData.join,
dateOfBirth: state.userData.dateOfBirth,
zipCode: state.userData.zipCode,
termsAccepted: state.userData.termsAccepted,
}))
const methods = useForm<DetailsSchema>({

View File

@@ -1,4 +1,5 @@
"use client"
import { useSearchParams } from "next/navigation"
import { PropsWithChildren, useRef } from "react"
import {
@@ -7,15 +8,16 @@ import {
initEditDetailsState,
} from "@/stores/enter-details"
import { StepEnum } from "@/types/components/enterDetails/step"
import { StepEnum } from "@/types/components/hotelReservation/enterDetails/step"
export default function EnterDetailsProvider({
step,
children,
}: PropsWithChildren<{ step: StepEnum }>) {
const searchParams = useSearchParams()
const initialStore = useRef<EnterDetailsStore>()
if (!initialStore.current) {
initialStore.current = initEditDetailsState(step)
initialStore.current = initEditDetailsState(step, searchParams)
}
return (

View File

@@ -14,7 +14,7 @@ import styles from "./enterDetailsSidePeek.module.css"
import {
SidePeekEnum,
SidePeekProps,
} from "@/types/components/enterDetails/sidePeek"
} from "@/types/components/hotelReservation/enterDetails/sidePeek"
export default function EnterDetailsSidePeek({ hotel }: SidePeekProps) {
const activeSidePeek = useEnterDetailsStore((state) => state.activeSidePeek)

View File

@@ -7,7 +7,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details"
import { ChevronRightSmallIcon } from "@/components/Icons"
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() {
const intl = useIntl()

View File

@@ -1,154 +1,206 @@
"use client"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import { trpc } from "@/lib/trpc/client"
import { useEnterDetailsStore } from "@/stores/enter-details"
import { ArrowRightIcon } from "@/components/Icons"
import LoadingSpinner from "@/components/LoadingSpinner"
import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import ToggleSidePeek from "./ToggleSidePeek"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang"
import { formatNumber } from "@/utils/format"
import styles from "./summary.module.css"
// TEMP
const rooms = [
{
adults: 1,
type: "Cozy cabin",
},
]
import { RoomsData } from "@/types/components/hotelReservation/enterDetails/bookingData"
export default async function Summary() {
const intl = await getIntl()
const lang = getLang()
const fromDate = dt().locale(lang).format("ddd, D MMM")
const toDate = dt().add(1, "day").locale(lang).format("ddd, D MMM")
const diff = dt(toDate).diff(fromDate, "days")
export default function Summary({ isMember }: { isMember: boolean }) {
const intl = useIntl()
const lang = useLang()
const { fromDate, toDate, rooms, hotel, bedType, breakfast } =
useEnterDetailsStore((state) => ({
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 adults = intl.formatMessage(
{ id: "booking.adults" },
{ totalAdults: totalAdults }
const {
data: availabilityData,
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(
{ id: "booking.nights" },
{ totalNights: diff }
)
const addOns = [
{
price: intl.formatMessage({ id: "Included" }),
title: intl.formatMessage({ id: "King bed" }),
},
{
price: intl.formatMessage({ id: "Included" }),
title: intl.formatMessage({ id: "Breakfast buffet" }),
},
]
if (isLoading) {
return <LoadingSpinner />
}
const populatedRooms = rooms
.map((room) => {
const chosenRoom = availabilityData?.roomConfigurations.find(
(availRoom) => room.roomtypecode === availRoom.roomTypeCode
)
const cancellationText = availabilityData?.rateDefinitions.find(
(rate) => rate.rateCode === room.ratecode
)?.cancellationText
const mappedRooms = Array.from(
rooms
.reduce((acc, room) => {
const currentRoom = acc.get(room.type)
acc.set(room.type, {
total: currentRoom ? currentRoom.total + 1 : 1,
type: room.type,
})
return acc
}, new Map())
.values()
)
if (chosenRoom) {
const memberPrice = chosenRoom.products.find(
(rate) => rate.productType.member?.rateCode === room.ratecode
)?.productType.member?.localPrice.pricePerStay
const publicPrice = chosenRoom.products.find(
(rate) => rate.productType.public?.rateCode === room.ratecode
)?.productType.public?.localPrice.pricePerStay
return {
roomType: chosenRoom.roomType,
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 (
<section className={styles.summary}>
<header>
<Body textTransform="bold">
{mappedRooms.map(
(room, idx) =>
`${room.total} x ${room.type}${mappedRooms.length > 1 && idx + 1 !== mappedRooms.length ? ", " : ""}`
)}
<Subtitle type="two">{intl.formatMessage({ id: "Summary" })}</Subtitle>
<Body className={styles.date} color="baseTextMediumContrast">
{dt(fromDate).locale(lang).format("ddd, D MMM")}
<ArrowRightIcon color="peach80" height={15} width={15} />
{dt(toDate).locale(lang).format("ddd, D MMM")} ({nights})
</Body>
<Body className={styles.date} color="textMediumContrast">
{fromDate}
<ArrowRightIcon color="uiTextMediumContrast" height={15} width={15} />
{toDate}
</Body>
<ToggleSidePeek />
</header>
<Divider color="primaryLightSubtle" />
<div className={styles.addOns}>
<div className={styles.entry}>
<Caption color="uiTextMediumContrast">
{`${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>
{populatedRooms.map((room, idx) => (
<RoomBreakdown key={idx} room={room} isMember={isMember} />
))}
{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>
<Divider color="primaryLightSubtle" />
<div className={styles.total}>
<div>
<div className={styles.entry}>
<Body textTransform="bold">
{intl.formatMessage({ id: "Total incl VAT" })}
</Body>
<Body textTransform="bold">
{intl.formatMessage(
{ id: "{amount} {currency}" },
{ amount: "4686", currency: "SEK" }
)}
</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 className={styles.entry}>
<Body textTransform="bold">
{intl.formatMessage({ id: "Total price (incl VAT)" })}
</Body>
<Body textTransform="bold">
{intl.formatMessage(
{ id: "{amount} {currency}" },
{ amount: "4686", currency: "SEK" }
)}
</Body>
</div>
<div>
<div className={styles.entry}>
<Body color="red" textTransform="bold">
{intl.formatMessage({ id: "Member price" })}
</Body>
<Body color="red" textTransform="bold">
{intl.formatMessage(
{ id: "{amount} {currency}" },
{ amount: "4219", currency: "SEK" }
)}
</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 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>
</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>
)
}

View File

@@ -22,7 +22,7 @@
.addOns {
display: flex;
flex-direction: column;
gap: var(--Spacing-x1);
gap: var(--Spacing-x-one-and-half);
}
.entry {