merge
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||||
import BreadcrumbsSkeleton from "@/components/Breadcrumbs/BreadcrumbsSkeleton"
|
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import { LangParams, PageArgs } from "@/types/params"
|
import { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||||
import BreadcrumbsSkeleton from "@/components/Breadcrumbs/BreadcrumbsSkeleton"
|
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import { LangParams, PageArgs } from "@/types/params"
|
import { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.layout {
|
||||||
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
|
import styles from "./layout.module.css"
|
||||||
|
|
||||||
|
import { LangParams, LayoutArgs } from "@/types/params"
|
||||||
|
|
||||||
|
export default function PaymentCallbackLayout({
|
||||||
|
children,
|
||||||
|
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
||||||
|
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
return <div className={styles.layout}>{children}</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
|
import {
|
||||||
|
BOOKING_CONFIRMATION_NUMBER,
|
||||||
|
PaymentErrorCodeEnum,
|
||||||
|
} from "@/constants/booking"
|
||||||
|
import { Lang } from "@/constants/languages"
|
||||||
|
import {
|
||||||
|
bookingConfirmation,
|
||||||
|
payment,
|
||||||
|
} from "@/constants/routes/hotelReservation"
|
||||||
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
|
import PaymentCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback"
|
||||||
|
|
||||||
|
import { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
|
export default async function PaymentCallbackPage({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: PageArgs<
|
||||||
|
LangParams,
|
||||||
|
{ status: "error" | "success" | "cancel"; confirmationNumber?: string }
|
||||||
|
>) {
|
||||||
|
console.log(`[payment-callback] callback started`)
|
||||||
|
const lang = params.lang
|
||||||
|
const status = searchParams.status
|
||||||
|
const confirmationNumber = searchParams.confirmationNumber
|
||||||
|
|
||||||
|
if (status === "success" && confirmationNumber) {
|
||||||
|
const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${confirmationNumber}`
|
||||||
|
|
||||||
|
console.log(`[payment-callback] redirecting to: ${confirmationUrl}`)
|
||||||
|
redirect(confirmationUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnUrl = payment(lang)
|
||||||
|
const searchObject = new URLSearchParams()
|
||||||
|
|
||||||
|
if (confirmationNumber) {
|
||||||
|
try {
|
||||||
|
const bookingStatus = await serverClient().booking.status({
|
||||||
|
confirmationNumber,
|
||||||
|
})
|
||||||
|
if (bookingStatus.metadata) {
|
||||||
|
searchObject.set(
|
||||||
|
"errorCode",
|
||||||
|
bookingStatus.metadata.errorCode?.toString() ?? ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}`
|
||||||
|
)
|
||||||
|
if (status === "cancel") {
|
||||||
|
searchObject.set("errorCode", PaymentErrorCodeEnum.Cancelled.toString())
|
||||||
|
}
|
||||||
|
if (status === "error") {
|
||||||
|
searchObject.set("errorCode", PaymentErrorCodeEnum.Failed.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PaymentCallback
|
||||||
|
returnUrl={returnUrl.toString()}
|
||||||
|
searchObject={searchObject}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -14,6 +14,10 @@
|
|||||||
padding: var(--Spacing-x3) var(--Spacing-x2) 0 var(--Spacing-x2);
|
padding: var(--Spacing-x3) var(--Spacing-x2) 0 var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.cityInformation {
|
.cityInformation {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -65,13 +69,19 @@
|
|||||||
var(--Spacing-x5);
|
var(--Spacing-x5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header nav {
|
||||||
|
display: block;
|
||||||
|
max-width: var(--max-width-navigation);
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sorter {
|
.sorter {
|
||||||
display: block;
|
display: block;
|
||||||
width: 339px;
|
width: 339px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 0 auto;
|
margin: var(--Spacing-x3) auto 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: var(--max-width-navigation);
|
max-width: var(--max-width-navigation);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
import { Suspense } from "react"
|
||||||
|
|
||||||
import { selectHotelMap } from "@/constants/routes/hotelReservation"
|
import {
|
||||||
|
selectHotel,
|
||||||
|
selectHotelMap,
|
||||||
|
} from "@/constants/routes/hotelReservation"
|
||||||
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
import { getLocations } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -19,6 +23,8 @@ import {
|
|||||||
import { ChevronRightIcon } from "@/components/Icons"
|
import { ChevronRightIcon } from "@/components/Icons"
|
||||||
import StaticMap from "@/components/Maps/StaticMap"
|
import StaticMap from "@/components/Maps/StaticMap"
|
||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
|
import Breadcrumbs from "@/components/TempDesignSystem/Breadcrumbs"
|
||||||
|
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
@@ -65,12 +71,36 @@ export default async function SelectHotelPage({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const filterList = getFiltersFromHotels(hotels)
|
const filterList = getFiltersFromHotels(hotels)
|
||||||
|
const breadcrumbs = [
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: "Home" }),
|
||||||
|
href: `/${params.lang}`,
|
||||||
|
uid: "home-page",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: "Hotel reservation" }),
|
||||||
|
href: `/${params.lang}/hotelreservation`,
|
||||||
|
uid: "hotel-reservation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: "Select hotel" }),
|
||||||
|
href: `${selectHotel(params.lang)}/?${selectHotelParams}`,
|
||||||
|
uid: "select-hotel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: city.name,
|
||||||
|
uid: city.id,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const isAllUnavailable = hotels.every((hotel) => hotel.price === undefined)
|
const isAllUnavailable = hotels.every((hotel) => hotel.price === undefined)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
|
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
||||||
|
<Breadcrumbs breadcrumbs={breadcrumbs} />
|
||||||
|
</Suspense>
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<div className={styles.cityInformation}>
|
<div className={styles.cityInformation}>
|
||||||
<Subtitle>{city.name}</Subtitle>
|
<Subtitle>{city.name}</Subtitle>
|
||||||
|
|||||||
@@ -64,32 +64,34 @@ export default async function SummaryPage({
|
|||||||
redirect(selectRate(params.lang))
|
redirect(selectRate(params.lang))
|
||||||
}
|
}
|
||||||
|
|
||||||
const prices =
|
const prices = {
|
||||||
user && availability.memberRate
|
public: {
|
||||||
|
local: {
|
||||||
|
amount: availability.publicRate.localPrice.pricePerStay,
|
||||||
|
currency: availability.publicRate.localPrice.currency,
|
||||||
|
},
|
||||||
|
euro: availability.publicRate?.requestedPrice
|
||||||
|
? {
|
||||||
|
amount: availability.publicRate?.requestedPrice.pricePerStay,
|
||||||
|
currency: availability.publicRate?.requestedPrice.currency,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
member: availability.memberRate
|
||||||
? {
|
? {
|
||||||
local: {
|
local: {
|
||||||
price: availability.memberRate.localPrice.pricePerStay,
|
amount: availability.memberRate.localPrice.pricePerStay,
|
||||||
currency: availability.memberRate.localPrice.currency,
|
currency: availability.memberRate.localPrice.currency,
|
||||||
},
|
},
|
||||||
euro: availability.memberRate.requestedPrice
|
euro: availability.memberRate.requestedPrice
|
||||||
? {
|
? {
|
||||||
price: availability.memberRate.requestedPrice.pricePerStay,
|
amount: availability.memberRate.requestedPrice.pricePerStay,
|
||||||
currency: availability.memberRate.requestedPrice.currency,
|
currency: availability.memberRate.requestedPrice.currency,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
}
|
}
|
||||||
: {
|
: undefined,
|
||||||
local: {
|
}
|
||||||
price: availability.publicRate.localPrice.pricePerStay,
|
|
||||||
currency: availability.publicRate.localPrice.currency,
|
|
||||||
},
|
|
||||||
euro: availability.publicRate?.requestedPrice
|
|
||||||
? {
|
|
||||||
price: availability.publicRate?.requestedPrice.pricePerStay,
|
|
||||||
currency: availability.publicRate?.requestedPrice.currency,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -100,8 +102,7 @@ export default async function SummaryPage({
|
|||||||
showMemberPrice={!!(user && availability.memberRate)}
|
showMemberPrice={!!(user && availability.memberRate)}
|
||||||
room={{
|
room={{
|
||||||
roomType: availability.selectedRoom.roomType,
|
roomType: availability.selectedRoom.roomType,
|
||||||
localPrice: prices.local,
|
prices,
|
||||||
euroPrice: prices.euro,
|
|
||||||
adults,
|
adults,
|
||||||
children,
|
children,
|
||||||
rateDetails: availability.rateDetails,
|
rateDetails: availability.rateDetails,
|
||||||
@@ -119,8 +120,7 @@ export default async function SummaryPage({
|
|||||||
showMemberPrice={!!(user && availability.memberRate)}
|
showMemberPrice={!!(user && availability.memberRate)}
|
||||||
room={{
|
room={{
|
||||||
roomType: availability.selectedRoom.roomType,
|
roomType: availability.selectedRoom.roomType,
|
||||||
localPrice: prices.local,
|
prices,
|
||||||
euroPrice: prices.euro,
|
|
||||||
adults,
|
adults,
|
||||||
children,
|
children,
|
||||||
rateDetails: availability.rateDetails,
|
rateDetails: availability.rateDetails,
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ export default async function StepPage({
|
|||||||
label={mustBeGuaranteed ? guaranteeWithCard : selectPaymentMethod}
|
label={mustBeGuaranteed ? guaranteeWithCard : selectPaymentMethod}
|
||||||
>
|
>
|
||||||
<Payment
|
<Payment
|
||||||
|
user={user}
|
||||||
roomPrice={roomPrice}
|
roomPrice={roomPrice}
|
||||||
otherPaymentOptions={
|
otherPaymentOptions={
|
||||||
hotelData.data.attributes.merchantInformationData
|
hotelData.data.attributes.merchantInformationData
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server"
|
|
||||||
|
|
||||||
import {
|
|
||||||
BOOKING_CONFIRMATION_NUMBER,
|
|
||||||
PaymentErrorCodeEnum,
|
|
||||||
} from "@/constants/booking"
|
|
||||||
import { Lang } from "@/constants/languages"
|
|
||||||
import {
|
|
||||||
bookingConfirmation,
|
|
||||||
payment,
|
|
||||||
} from "@/constants/routes/hotelReservation"
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
|
||||||
import { getPublicURL } from "@/server/utils"
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
request: NextRequest,
|
|
||||||
{ params }: { params: { lang: string; status: string } }
|
|
||||||
): Promise<NextResponse> {
|
|
||||||
const publicURL = getPublicURL(request)
|
|
||||||
|
|
||||||
console.log(`[payment-callback] callback started`)
|
|
||||||
const lang = params.lang as Lang
|
|
||||||
const status = params.status
|
|
||||||
|
|
||||||
const queryParams = request.nextUrl.searchParams
|
|
||||||
const confirmationNumber = queryParams.get(BOOKING_CONFIRMATION_NUMBER)
|
|
||||||
|
|
||||||
if (status === "success" && confirmationNumber) {
|
|
||||||
const confirmationUrl = new URL(`${publicURL}/${bookingConfirmation(lang)}`)
|
|
||||||
confirmationUrl.searchParams.set(
|
|
||||||
BOOKING_CONFIRMATION_NUMBER,
|
|
||||||
confirmationNumber
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(`[payment-callback] redirecting to: ${confirmationUrl}`)
|
|
||||||
return NextResponse.redirect(confirmationUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
const returnUrl = new URL(`${publicURL}/${payment(lang)}`)
|
|
||||||
returnUrl.search = queryParams.toString()
|
|
||||||
|
|
||||||
if (confirmationNumber) {
|
|
||||||
try {
|
|
||||||
const bookingStatus = await serverClient().booking.status({
|
|
||||||
confirmationNumber,
|
|
||||||
})
|
|
||||||
if (bookingStatus.metadata) {
|
|
||||||
returnUrl.searchParams.set(
|
|
||||||
"errorCode",
|
|
||||||
bookingStatus.metadata.errorCode?.toString() ?? ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (status === "cancel") {
|
|
||||||
returnUrl.searchParams.set(
|
|
||||||
"errorCode",
|
|
||||||
PaymentErrorCodeEnum.Cancelled.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (status === "error") {
|
|
||||||
returnUrl.searchParams.set(
|
|
||||||
"errorCode",
|
|
||||||
PaymentErrorCodeEnum.Failed.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[payment-callback] redirecting to: ${returnUrl}`)
|
|
||||||
return NextResponse.redirect(returnUrl)
|
|
||||||
}
|
|
||||||
@@ -1,60 +1,13 @@
|
|||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import { ChevronRightSmallIcon,HouseIcon } from "@/components/Icons"
|
import BreadcrumbsComp from "@/components/TempDesignSystem/Breadcrumbs"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
|
||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
|
||||||
|
|
||||||
import styles from "./breadcrumbs.module.css"
|
|
||||||
|
|
||||||
export default async function Breadcrumbs() {
|
export default async function Breadcrumbs() {
|
||||||
const breadcrumbs = await serverClient().contentstack.breadcrumbs.get()
|
const breadcrumbs = await serverClient().contentstack.breadcrumbs.get()
|
||||||
|
|
||||||
if (!breadcrumbs?.length) {
|
if (!breadcrumbs?.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const homeBreadcrumb = breadcrumbs.shift()
|
return <BreadcrumbsComp breadcrumbs={breadcrumbs} />
|
||||||
return (
|
|
||||||
<nav className={styles.breadcrumbs}>
|
|
||||||
<ul className={styles.list}>
|
|
||||||
{homeBreadcrumb ? (
|
|
||||||
<li className={styles.listItem}>
|
|
||||||
<Link
|
|
||||||
className={styles.homeLink}
|
|
||||||
color="peach80"
|
|
||||||
href={homeBreadcrumb.href!}
|
|
||||||
variant="breadcrumb"
|
|
||||||
>
|
|
||||||
<HouseIcon width={16} height={16} color="peach80" />
|
|
||||||
</Link>
|
|
||||||
<ChevronRightSmallIcon aria-hidden="true" color="peach80" />
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{breadcrumbs.map((breadcrumb) => {
|
|
||||||
if (breadcrumb.href) {
|
|
||||||
return (
|
|
||||||
<li key={breadcrumb.uid} className={styles.listItem}>
|
|
||||||
<Link
|
|
||||||
color="peach80"
|
|
||||||
href={breadcrumb.href}
|
|
||||||
variant="breadcrumb"
|
|
||||||
>
|
|
||||||
{breadcrumb.title}
|
|
||||||
</Link>
|
|
||||||
<ChevronRightSmallIcon aria-hidden="true" color="peach80" />
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={breadcrumb.uid} className={styles.listItem}>
|
|
||||||
<Footnote color="burgundy" type="bold">
|
|
||||||
{breadcrumb.title}
|
|
||||||
</Footnote>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,4 +55,8 @@ export const signedInDetailsSchema = z.object({
|
|||||||
firstName: z.string().optional(),
|
firstName: z.string().optional(),
|
||||||
lastName: z.string().optional(),
|
lastName: z.string().optional(),
|
||||||
phoneNumber: z.string().optional(),
|
phoneNumber: z.string().optional(),
|
||||||
|
join: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.transform((_) => false),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
import { detailsStorageName } from "@/stores/details"
|
||||||
|
|
||||||
|
import { createQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||||
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
|
||||||
|
import { DetailsState } from "@/types/stores/details"
|
||||||
|
|
||||||
|
export default function PaymentCallback({
|
||||||
|
returnUrl,
|
||||||
|
searchObject,
|
||||||
|
}: {
|
||||||
|
returnUrl: string
|
||||||
|
searchObject: URLSearchParams
|
||||||
|
}) {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const bookingData = window.sessionStorage.getItem(detailsStorageName)
|
||||||
|
|
||||||
|
if (bookingData) {
|
||||||
|
const detailsStorage: Record<
|
||||||
|
"state",
|
||||||
|
Pick<DetailsState, "data">
|
||||||
|
> = JSON.parse(bookingData)
|
||||||
|
const searchParams = createQueryParamsForEnterDetails(
|
||||||
|
detailsStorage.state.data.booking,
|
||||||
|
searchObject
|
||||||
|
)
|
||||||
|
|
||||||
|
if (searchParams.size > 0) {
|
||||||
|
router.replace(`${returnUrl}?${searchParams.toString()}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [returnUrl, router, searchObject])
|
||||||
|
|
||||||
|
return <LoadingSpinner />
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ function isPaymentMethodEnum(value: string): value is PaymentMethodEnum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Payment({
|
export default function Payment({
|
||||||
|
user,
|
||||||
roomPrice,
|
roomPrice,
|
||||||
otherPaymentOptions,
|
otherPaymentOptions,
|
||||||
savedCreditCards,
|
savedCreditCards,
|
||||||
@@ -59,7 +60,6 @@ export default function Payment({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const queryParams = useSearchParams()
|
|
||||||
const { booking, ...userData } = useDetailsStore((state) => state.data)
|
const { booking, ...userData } = useDetailsStore((state) => state.data)
|
||||||
const setIsSubmittingDisabled = useDetailsStore(
|
const setIsSubmittingDisabled = useDetailsStore(
|
||||||
(state) => state.actions.setIsSubmittingDisabled
|
(state) => state.actions.setIsSubmittingDisabled
|
||||||
@@ -163,9 +163,6 @@ export default function Payment({
|
|||||||
])
|
])
|
||||||
|
|
||||||
function handleSubmit(data: PaymentFormData) {
|
function handleSubmit(data: PaymentFormData) {
|
||||||
const allQueryParams =
|
|
||||||
queryParams.size > 0 ? `?${queryParams.toString()}` : ""
|
|
||||||
|
|
||||||
// set payment method to card if saved card is submitted
|
// set payment method to card if saved card is submitted
|
||||||
const paymentMethod = isPaymentMethodEnum(data.paymentMethod)
|
const paymentMethod = isPaymentMethodEnum(data.paymentMethod)
|
||||||
? data.paymentMethod
|
? data.paymentMethod
|
||||||
@@ -175,6 +172,8 @@ export default function Payment({
|
|||||||
(card) => card.id === data.paymentMethod
|
(card) => card.id === data.paymentMethod
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const paymentRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}/${lang}/hotelreservation/payment-callback`
|
||||||
|
|
||||||
initiateBooking.mutate({
|
initiateBooking.mutate({
|
||||||
hotelId: hotel,
|
hotelId: hotel,
|
||||||
checkInDate: fromDate,
|
checkInDate: fromDate,
|
||||||
@@ -185,7 +184,8 @@ export default function Payment({
|
|||||||
age: child.age,
|
age: child.age,
|
||||||
bedType: bedTypeMap[parseInt(child.bed.toString())],
|
bedType: bedTypeMap[parseInt(child.bed.toString())],
|
||||||
})),
|
})),
|
||||||
rateCode: room.rateCode,
|
rateCode:
|
||||||
|
user || join || membershipNo ? room.counterRateCode : room.rateCode,
|
||||||
roomTypeCode: bedType!.roomTypeCode, // A selection has been made in order to get to this step.
|
roomTypeCode: bedType!.roomTypeCode, // A selection has been made in order to get to this step.
|
||||||
guest: {
|
guest: {
|
||||||
title: "",
|
title: "",
|
||||||
@@ -222,9 +222,9 @@ export default function Payment({
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
success: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/success`,
|
success: `${paymentRedirectUrl}/success`,
|
||||||
error: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/error${allQueryParams}`,
|
error: `${paymentRedirectUrl}/error`,
|
||||||
cancel: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/cancel${allQueryParams}`,
|
cancel: `${paymentRedirectUrl}/cancel`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function SummaryBottomSheet({ children }: PropsWithChildren) {
|
|||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{
|
{
|
||||||
amount: intl.formatNumber(totalPrice.local.price),
|
amount: intl.formatNumber(totalPrice.local.amount),
|
||||||
currency: totalPrice.local.currency,
|
currency: totalPrice.local.currency,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
import { ChevronDown } from "react-feather"
|
import { ChevronDown } from "react-feather"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -33,6 +33,8 @@ function storeSelector(state: DetailsState) {
|
|||||||
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
||||||
setTotalPrice: state.actions.setTotalPrice,
|
setTotalPrice: state.actions.setTotalPrice,
|
||||||
totalPrice: state.totalPrice,
|
totalPrice: state.totalPrice,
|
||||||
|
join: state.data.join,
|
||||||
|
membershipNo: state.data.membershipNo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +53,8 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
toDate,
|
toDate,
|
||||||
toggleSummaryOpen,
|
toggleSummaryOpen,
|
||||||
totalPrice,
|
totalPrice,
|
||||||
|
join,
|
||||||
|
membershipNo,
|
||||||
} = useDetailsStore(storeSelector)
|
} = useDetailsStore(storeSelector)
|
||||||
|
|
||||||
const diff = dt(toDate).diff(fromDate, "days")
|
const diff = dt(toDate).diff(fromDate, "days")
|
||||||
@@ -60,10 +64,8 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
{ totalNights: diff }
|
{ totalNights: diff }
|
||||||
)
|
)
|
||||||
|
|
||||||
let color: "uiTextHighContrast" | "red" = "uiTextHighContrast"
|
const color = useRef<"uiTextHighContrast" | "red">("uiTextHighContrast")
|
||||||
if (showMemberPrice) {
|
const [price, setPrice] = useState(room.prices.public)
|
||||||
color = "red"
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalPackageCost = room.packages?.reduce(
|
const additionalPackageCost = room.packages?.reduce(
|
||||||
(acc, curr) => {
|
(acc, curr) => {
|
||||||
@@ -74,11 +76,23 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
{ local: 0, euro: 0 }
|
{ local: 0, euro: 0 }
|
||||||
) || { local: 0, euro: 0 }
|
) || { local: 0, euro: 0 }
|
||||||
|
|
||||||
const roomsPriceLocal = room.localPrice.price + additionalPackageCost.local
|
const roomsPriceLocal = price.local.amount + additionalPackageCost.local
|
||||||
const roomsPriceEuro = room.euroPrice
|
const roomsPriceEuro = price.euro
|
||||||
? room.euroPrice.price + additionalPackageCost.euro
|
? price.euro.amount + additionalPackageCost.euro
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showMemberPrice || join || membershipNo) {
|
||||||
|
color.current = "red"
|
||||||
|
if (room.prices.member) {
|
||||||
|
setPrice(room.prices.member)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
color.current = "uiTextHighContrast"
|
||||||
|
setPrice(room.prices.public)
|
||||||
|
}
|
||||||
|
}, [showMemberPrice, join, membershipNo, room.prices])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChosenBed(bedType)
|
setChosenBed(bedType)
|
||||||
|
|
||||||
@@ -87,30 +101,30 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
if (breakfast === false) {
|
if (breakfast === false) {
|
||||||
setTotalPrice({
|
setTotalPrice({
|
||||||
local: {
|
local: {
|
||||||
price: roomsPriceLocal,
|
amount: roomsPriceLocal,
|
||||||
currency: room.localPrice.currency,
|
currency: price.local.currency,
|
||||||
},
|
},
|
||||||
euro:
|
euro:
|
||||||
room.euroPrice && roomsPriceEuro
|
price.euro && roomsPriceEuro
|
||||||
? {
|
? {
|
||||||
price: roomsPriceEuro,
|
amount: roomsPriceEuro,
|
||||||
currency: room.euroPrice.currency,
|
currency: price.euro.currency,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
setTotalPrice({
|
setTotalPrice({
|
||||||
local: {
|
local: {
|
||||||
price: roomsPriceLocal + parseInt(breakfast.localPrice.totalPrice),
|
amount: roomsPriceLocal + parseInt(breakfast.localPrice.totalPrice),
|
||||||
currency: room.localPrice.currency,
|
currency: price.local.currency,
|
||||||
},
|
},
|
||||||
euro:
|
euro:
|
||||||
room.euroPrice && roomsPriceEuro
|
price.euro && roomsPriceEuro
|
||||||
? {
|
? {
|
||||||
price:
|
amount:
|
||||||
roomsPriceEuro +
|
roomsPriceEuro +
|
||||||
parseInt(breakfast.requestedPrice.totalPrice),
|
parseInt(breakfast.requestedPrice.totalPrice),
|
||||||
currency: room.euroPrice.currency,
|
currency: price.euro.currency,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
@@ -120,8 +134,8 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
bedType,
|
bedType,
|
||||||
breakfast,
|
breakfast,
|
||||||
roomsPriceLocal,
|
roomsPriceLocal,
|
||||||
room.localPrice.currency,
|
price.local.currency,
|
||||||
room.euroPrice,
|
price.euro,
|
||||||
roomsPriceEuro,
|
roomsPriceEuro,
|
||||||
setTotalPrice,
|
setTotalPrice,
|
||||||
])
|
])
|
||||||
@@ -151,12 +165,12 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
<div>
|
<div>
|
||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<Body color="uiTextHighContrast">{room.roomType}</Body>
|
<Body color="uiTextHighContrast">{room.roomType}</Body>
|
||||||
<Caption color={color}>
|
<Caption color={color.current}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{
|
{
|
||||||
amount: intl.formatNumber(room.localPrice.price),
|
amount: intl.formatNumber(price.local.amount),
|
||||||
currency: room.localPrice.currency,
|
currency: price.local.currency,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
@@ -229,7 +243,7 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
<Caption color="uiTextHighContrast">
|
<Caption color="uiTextHighContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{ amount: "0", currency: room.localPrice.currency }
|
{ amount: "0", currency: price.local.currency }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,7 +257,7 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{ amount: "0", currency: room.localPrice.currency }
|
{ amount: "0", currency: price.local.currency }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,22 +293,24 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Body textTransform="bold">
|
{totalPrice.local.amount > 0 && (
|
||||||
{intl.formatMessage(
|
<Body textTransform="bold">
|
||||||
{ id: "{amount} {currency}" },
|
{intl.formatMessage(
|
||||||
{
|
{ id: "{amount} {currency}" },
|
||||||
amount: intl.formatNumber(totalPrice.local.price),
|
{
|
||||||
currency: totalPrice.local.currency,
|
amount: intl.formatNumber(totalPrice.local.amount),
|
||||||
}
|
currency: totalPrice.local.currency,
|
||||||
)}
|
}
|
||||||
</Body>
|
)}
|
||||||
{totalPrice.euro && (
|
</Body>
|
||||||
|
)}
|
||||||
|
{totalPrice.euro && totalPrice.euro.amount > 0 && (
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage({ id: "Approx." })}{" "}
|
{intl.formatMessage({ id: "Approx." })}{" "}
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "{amount} {currency}" },
|
{ id: "{amount} {currency}" },
|
||||||
{
|
{
|
||||||
amount: intl.formatNumber(totalPrice.euro.price),
|
amount: intl.formatNumber(totalPrice.euro.amount),
|
||||||
currency: totalPrice.euro.currency,
|
currency: totalPrice.euro.currency,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
||||||
|
|
||||||
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton"
|
||||||
|
|
||||||
import HotelCard from "../HotelCard"
|
import HotelCard from "../HotelCard"
|
||||||
@@ -17,6 +19,7 @@ import {
|
|||||||
type HotelData,
|
type HotelData,
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||||
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
|
import { SortOrder } from "@/types/components/hotelReservation/selectHotel/hotelSorter"
|
||||||
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
export default function HotelCardListing({
|
export default function HotelCardListing({
|
||||||
hotelData,
|
hotelData,
|
||||||
@@ -28,6 +31,7 @@ export default function HotelCardListing({
|
|||||||
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
|
||||||
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
|
const setResultCount = useHotelFilterStore((state) => state.setResultCount)
|
||||||
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
|
const [showBackToTop, setShowBackToTop] = useState<boolean>(false)
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const sortBy = useMemo(
|
const sortBy = useMemo(
|
||||||
() => searchParams.get("sort") ?? DEFAULT_SORT,
|
() => searchParams.get("sort") ?? DEFAULT_SORT,
|
||||||
@@ -69,7 +73,6 @@ export default function HotelCardListing({
|
|||||||
|
|
||||||
const hotels = useMemo(() => {
|
const hotels = useMemo(() => {
|
||||||
if (activeFilters.length === 0) {
|
if (activeFilters.length === 0) {
|
||||||
setResultCount(sortedHotels.length)
|
|
||||||
return sortedHotels
|
return sortedHotels
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +84,8 @@ export default function HotelCardListing({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
setResultCount(filteredHotels.length)
|
|
||||||
return filteredHotels
|
return filteredHotels
|
||||||
}, [activeFilters, sortedHotels, setResultCount])
|
}, [activeFilters, sortedHotels])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
@@ -95,23 +97,33 @@ export default function HotelCardListing({
|
|||||||
return () => window.removeEventListener("scroll", handleScroll)
|
return () => window.removeEventListener("scroll", handleScroll)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setResultCount(hotels ? hotels.length : 0)
|
||||||
|
}, [hotels, setResultCount])
|
||||||
|
|
||||||
function scrollToTop() {
|
function scrollToTop() {
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" })
|
window.scrollTo({ top: 0, behavior: "smooth" })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.hotelCards}>
|
<section className={styles.hotelCards}>
|
||||||
{hotels?.length
|
{hotels?.length ? (
|
||||||
? hotels.map((hotel) => (
|
hotels.map((hotel) => (
|
||||||
<HotelCard
|
<HotelCard
|
||||||
key={hotel.hotelData.operaId}
|
key={hotel.hotelData.operaId}
|
||||||
hotel={hotel}
|
hotel={hotel}
|
||||||
type={type}
|
type={type}
|
||||||
state={hotel.hotelData.name === activeCard ? "active" : "default"}
|
state={hotel.hotelData.name === activeCard ? "active" : "default"}
|
||||||
onHotelCardHover={onHotelCardHover}
|
onHotelCardHover={onHotelCardHover}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: null}
|
) : activeFilters ? (
|
||||||
|
<Alert
|
||||||
|
type={AlertTypeEnum.Info}
|
||||||
|
heading={intl.formatMessage({ id: "filters.nohotel.heading" })}
|
||||||
|
text={intl.formatMessage({ id: "filters.nohotel.text" })}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{showBackToTop && <BackToTopButton onClick={scrollToTop} />}
|
{showBackToTop && <BackToTopButton onClick={scrollToTop} />}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -99,11 +99,13 @@ export default function RoomFilter({
|
|||||||
<form onSubmit={handleSubmit(submitFilter)}>
|
<form onSubmit={handleSubmit(submitFilter)}>
|
||||||
<div className={styles.roomsFilter}>
|
<div className={styles.roomsFilter}>
|
||||||
{filterOptions.map((option) => {
|
{filterOptions.map((option) => {
|
||||||
const { code, description } = option
|
const { code, description, itemCode } = option
|
||||||
const isPetRoom = code === RoomPackageCodeEnum.PET_ROOM
|
const isPetRoom = code === RoomPackageCodeEnum.PET_ROOM
|
||||||
const isAllergyRoom = code === RoomPackageCodeEnum.ALLERGY_ROOM
|
const isAllergyRoom = code === RoomPackageCodeEnum.ALLERGY_ROOM
|
||||||
const isDisabled =
|
const isDisabled =
|
||||||
(isAllergyRoom && petFriendly) || (isPetRoom && allergyFriendly)
|
(isAllergyRoom && petFriendly) ||
|
||||||
|
(isPetRoom && allergyFriendly) ||
|
||||||
|
!itemCode
|
||||||
|
|
||||||
const checkboxChip = (
|
const checkboxChip = (
|
||||||
<CheckboxChip
|
<CheckboxChip
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function FlexibilityOption({
|
|||||||
</div>
|
</div>
|
||||||
<Label size="regular" className={styles.noPricesLabel}>
|
<Label size="regular" className={styles.noPricesLabel}>
|
||||||
<Caption color="uiTextHighContrast" type="bold">
|
<Caption color="uiTextHighContrast" type="bold">
|
||||||
{intl.formatMessage({ id: "No Prices available" })}
|
{intl.formatMessage({ id: "No prices available" })}
|
||||||
</Caption>
|
</Caption>
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function RoomSelection({
|
|||||||
roomsAvailability,
|
roomsAvailability,
|
||||||
roomCategories,
|
roomCategories,
|
||||||
user,
|
user,
|
||||||
packages,
|
availablePackages,
|
||||||
selectedPackages,
|
selectedPackages,
|
||||||
setRateCode,
|
setRateCode,
|
||||||
rateSummary,
|
rateSummary,
|
||||||
@@ -72,7 +72,7 @@ export default function RoomSelection({
|
|||||||
roomCategories={roomCategories}
|
roomCategories={roomCategories}
|
||||||
handleSelectRate={setRateCode}
|
handleSelectRate={setRateCode}
|
||||||
selectedPackages={selectedPackages}
|
selectedPackages={selectedPackages}
|
||||||
packages={packages}
|
packages={availablePackages}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -81,7 +81,7 @@ export default function RoomSelection({
|
|||||||
<RateSummary
|
<RateSummary
|
||||||
rateSummary={rateSummary}
|
rateSummary={rateSummary}
|
||||||
isUserLoggedIn={isUserLoggedIn}
|
isUserLoggedIn={isUserLoggedIn}
|
||||||
packages={packages}
|
packages={availablePackages}
|
||||||
roomsAvailability={roomsAvailability}
|
roomsAvailability={roomsAvailability}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -50,6 +50,54 @@ export function getQueryParamsForEnterDetails(
|
|||||||
roomTypeCode: room.roomtype,
|
roomTypeCode: room.roomtype,
|
||||||
rateCode: room.ratecode,
|
rateCode: room.ratecode,
|
||||||
packages: room.packages?.split(",") as RoomPackageCodeEnum[],
|
packages: room.packages?.split(",") as RoomPackageCodeEnum[],
|
||||||
|
counterRateCode: room.counterratecode,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createQueryParamsForEnterDetails(
|
||||||
|
bookingData: BookingData,
|
||||||
|
intitalSearchParams: URLSearchParams
|
||||||
|
) {
|
||||||
|
const { hotel, fromDate, toDate, rooms } = bookingData
|
||||||
|
|
||||||
|
const bookingSearchParams = new URLSearchParams({ hotel, fromDate, toDate })
|
||||||
|
const searchParams = new URLSearchParams([
|
||||||
|
...intitalSearchParams,
|
||||||
|
...bookingSearchParams,
|
||||||
|
])
|
||||||
|
|
||||||
|
rooms.forEach((item, index) => {
|
||||||
|
if (item?.adults) {
|
||||||
|
searchParams.set(`room[${index}].adults`, item.adults.toString())
|
||||||
|
}
|
||||||
|
if (item?.children) {
|
||||||
|
item.children.forEach((child, childIndex) => {
|
||||||
|
searchParams.set(
|
||||||
|
`room[${index}].child[${childIndex}].age`,
|
||||||
|
child.age.toString()
|
||||||
|
)
|
||||||
|
searchParams.set(
|
||||||
|
`room[${index}].child[${childIndex}].bed`,
|
||||||
|
child.bed.toString()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (item?.roomTypeCode) {
|
||||||
|
searchParams.set(`room[${index}].roomtype`, item.roomTypeCode)
|
||||||
|
}
|
||||||
|
if (item?.rateCode) {
|
||||||
|
searchParams.set(`room[${index}].ratecode`, item.rateCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item?.counterRateCode) {
|
||||||
|
searchParams.set(`room[${index}].counterratecode`, item.counterRateCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.packages && item.packages.length > 0) {
|
||||||
|
searchParams.set(`room[${index}].packages`, item.packages.join(","))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return searchParams
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export async function RoomsContainer({
|
|||||||
return (
|
return (
|
||||||
<Rooms
|
<Rooms
|
||||||
user={user}
|
user={user}
|
||||||
packages={packages ?? []}
|
availablePackages={packages ?? []}
|
||||||
roomsAvailability={roomsAvailability}
|
roomsAvailability={roomsAvailability}
|
||||||
roomCategories={hotelData?.included ?? []}
|
roomCategories={hotelData?.included ?? []}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { filterDuplicateRoomTypesByLowestPrice } from "./utils"
|
|||||||
import styles from "./rooms.module.css"
|
import styles from "./rooms.module.css"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DefaultFilterOptions,
|
||||||
RoomPackageCodeEnum,
|
RoomPackageCodeEnum,
|
||||||
type RoomPackageCodes,
|
type RoomPackageCodes,
|
||||||
} from "@/types/components/hotelReservation/selectRate/roomFilter"
|
} from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||||
@@ -20,17 +21,39 @@ export default function Rooms({
|
|||||||
roomsAvailability,
|
roomsAvailability,
|
||||||
roomCategories = [],
|
roomCategories = [],
|
||||||
user,
|
user,
|
||||||
packages,
|
availablePackages,
|
||||||
}: SelectRateProps) {
|
}: SelectRateProps) {
|
||||||
const visibleRooms: RoomConfiguration[] =
|
const visibleRooms: RoomConfiguration[] =
|
||||||
filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations)
|
filterDuplicateRoomTypesByLowestPrice(roomsAvailability.roomConfigurations)
|
||||||
// const [internalRateSummary, setRateSummary] = useState<Rate | null>(null)
|
|
||||||
const [selectedRate, setSelectedRate] = useState<
|
const [selectedRate, setSelectedRate] = useState<
|
||||||
{ publicRateCode: string; roomTypeCode: string } | undefined
|
{ publicRateCode: string; roomTypeCode: string } | undefined
|
||||||
>(undefined)
|
>(undefined)
|
||||||
const [selectedPackages, setSelectedPackages] = useState<RoomPackageCodes[]>(
|
const [selectedPackages, setSelectedPackages] = useState<RoomPackageCodes[]>(
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
const defaultPackages: DefaultFilterOptions[] = [
|
||||||
|
{
|
||||||
|
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
||||||
|
description: "Accessible Room",
|
||||||
|
itemCode: availablePackages.find(
|
||||||
|
(pkg) => pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
|
||||||
|
)?.itemCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: RoomPackageCodeEnum.ALLERGY_ROOM,
|
||||||
|
description: "Allergy Room",
|
||||||
|
itemCode: availablePackages.find(
|
||||||
|
(pkg) => pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM
|
||||||
|
)?.itemCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: RoomPackageCodeEnum.PET_ROOM,
|
||||||
|
description: "Pet Room",
|
||||||
|
itemCode: availablePackages.find(
|
||||||
|
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
||||||
|
)?.itemCode,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const handleFilter = useCallback(
|
const handleFilter = useCallback(
|
||||||
(filter: Record<RoomPackageCodeEnum, boolean | undefined>) => {
|
(filter: Record<RoomPackageCodeEnum, boolean | undefined>) => {
|
||||||
@@ -39,7 +62,6 @@ export default function Rooms({
|
|||||||
) as RoomPackageCodeEnum[]
|
) as RoomPackageCodeEnum[]
|
||||||
|
|
||||||
setSelectedPackages(filteredPackages)
|
setSelectedPackages(filteredPackages)
|
||||||
// setRateSummary(null)
|
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
@@ -94,7 +116,9 @@ export default function Rooms({
|
|||||||
|
|
||||||
const petRoomPackage =
|
const petRoomPackage =
|
||||||
(selectedPackages.includes(RoomPackageCodeEnum.PET_ROOM) &&
|
(selectedPackages.includes(RoomPackageCodeEnum.PET_ROOM) &&
|
||||||
packages.find((pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM)) ||
|
availablePackages.find(
|
||||||
|
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
||||||
|
)) ||
|
||||||
undefined
|
undefined
|
||||||
|
|
||||||
const features = filteredRooms.find((room) =>
|
const features = filteredRooms.find((room) =>
|
||||||
@@ -113,7 +137,7 @@ export default function Rooms({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return rateSummary
|
return rateSummary
|
||||||
}, [filteredRooms, packages, selectedPackages, selectedRate])
|
}, [filteredRooms, availablePackages, selectedPackages, selectedRate])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rateSummary) return
|
if (rateSummary) return
|
||||||
@@ -127,13 +151,13 @@ export default function Rooms({
|
|||||||
<RoomFilter
|
<RoomFilter
|
||||||
numberOfRooms={rooms.roomConfigurations.length}
|
numberOfRooms={rooms.roomConfigurations.length}
|
||||||
onFilter={handleFilter}
|
onFilter={handleFilter}
|
||||||
filterOptions={packages}
|
filterOptions={defaultPackages}
|
||||||
/>
|
/>
|
||||||
<RoomSelection
|
<RoomSelection
|
||||||
roomsAvailability={rooms}
|
roomsAvailability={rooms}
|
||||||
roomCategories={roomCategories}
|
roomCategories={roomCategories}
|
||||||
user={user}
|
user={user}
|
||||||
packages={packages}
|
availablePackages={availablePackages}
|
||||||
selectedPackages={selectedPackages}
|
selectedPackages={selectedPackages}
|
||||||
setRateCode={setSelectedRate}
|
setRateCode={setSelectedRate}
|
||||||
rateSummary={rateSummary}
|
rateSummary={rateSummary}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@
|
|||||||
|
|
||||||
.galleryContent {
|
.galleryContent {
|
||||||
width: 1090px;
|
width: 1090px;
|
||||||
height: 725px;
|
height: min(725px, 85dvh);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullViewContent {
|
.fullViewContent {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { ChevronRightIcon, HouseIcon } from "@/components/Icons"
|
import { ChevronRightIcon, HouseIcon } from "@/components/Icons"
|
||||||
|
import styles from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css"
|
||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
|
|
||||||
import styles from "./breadcrumbs.module.css"
|
|
||||||
|
|
||||||
export default function BreadcrumbsSkeleton() {
|
export default function BreadcrumbsSkeleton() {
|
||||||
return (
|
return (
|
||||||
<nav className={styles.breadcrumbs}>
|
<nav className={styles.breadcrumbs}>
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
.breadcrumbs {
|
.breadcrumbs {
|
||||||
display: block;
|
display: block;
|
||||||
padding-left: var(--Spacing-x2);
|
padding: var(--Spacing-x2) var(--Spacing-x2) 0;
|
||||||
padding-right: var(--Spacing-x2);
|
|
||||||
padding-top: var(--Spacing-x2);
|
|
||||||
max-width: var(--max-width);
|
max-width: var(--max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
9
components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts
Normal file
9
components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
type Breadcrumb = {
|
||||||
|
title: string
|
||||||
|
uid: string
|
||||||
|
href?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BreadcrumbsProps {
|
||||||
|
breadcrumbs: Breadcrumb[]
|
||||||
|
}
|
||||||
61
components/TempDesignSystem/Breadcrumbs/index.tsx
Normal file
61
components/TempDesignSystem/Breadcrumbs/index.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { HouseIcon } from "@/components/Icons"
|
||||||
|
import ChevronRightSmallIcon from "@/components/Icons/ChevronRightSmall"
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
|
|
||||||
|
import styles from "./breadcrumbs.module.css"
|
||||||
|
|
||||||
|
import type { BreadcrumbsProps } from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs"
|
||||||
|
|
||||||
|
export default function Breadcrumbs({ breadcrumbs }: BreadcrumbsProps) {
|
||||||
|
if (!breadcrumbs?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeBreadcrumb = breadcrumbs.shift()
|
||||||
|
return (
|
||||||
|
<nav className={styles.breadcrumbs}>
|
||||||
|
<ul className={styles.list}>
|
||||||
|
{homeBreadcrumb ? (
|
||||||
|
<li className={styles.listItem}>
|
||||||
|
<Link
|
||||||
|
className={styles.homeLink}
|
||||||
|
color="peach80"
|
||||||
|
href={homeBreadcrumb.href!}
|
||||||
|
variant="breadcrumb"
|
||||||
|
aria-label={homeBreadcrumb.title}
|
||||||
|
>
|
||||||
|
<HouseIcon width={16} height={16} color="peach80" />
|
||||||
|
</Link>
|
||||||
|
<ChevronRightSmallIcon aria-hidden="true" color="peach80" />
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{breadcrumbs.map((breadcrumb) => {
|
||||||
|
if (breadcrumb.href) {
|
||||||
|
return (
|
||||||
|
<li key={breadcrumb.uid} className={styles.listItem}>
|
||||||
|
<Link
|
||||||
|
color="peach80"
|
||||||
|
href={breadcrumb.href}
|
||||||
|
variant="breadcrumb"
|
||||||
|
>
|
||||||
|
{breadcrumb.title}
|
||||||
|
</Link>
|
||||||
|
<ChevronRightSmallIcon aria-hidden="true" color="peach80" />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={breadcrumb.uid} className={styles.listItem}>
|
||||||
|
<Footnote color="burgundy" type="bold">
|
||||||
|
{breadcrumb.title}
|
||||||
|
</Footnote>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--Spacing-x-half);
|
gap: var(--Spacing-x-half);
|
||||||
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
|
padding: calc(var(--Spacing-x1) - 2px) var(--Spacing-x-one-and-half);
|
||||||
border: 1px solid var(--Base-Border-Subtle);
|
border: 1px solid var(--Base-Border-Subtle);
|
||||||
border-radius: var(--Corner-radius-Small);
|
border-radius: var(--Corner-radius-Small);
|
||||||
background-color: var(--Base-Surface-Secondary-light-Normal);
|
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
height: 32px;
|
||||||
|
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.label[data-selected="true"],
|
.label[data-selected="true"],
|
||||||
@@ -21,8 +23,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label[data-disabled="true"] {
|
.label[data-disabled="true"] {
|
||||||
background-color: var(--Base-Button-Primary-Fill-Disabled);
|
background-color: var(--UI-Input-Controls-Surface-Disabled);
|
||||||
border-color: var(--Base-Button-Primary-Fill-Disabled);
|
border-color: var(--UI-Input-Controls-Border-Disabled);
|
||||||
|
color: var(--Base-Text-Disabled);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
env/client.ts
vendored
4
env/client.ts
vendored
@@ -5,14 +5,10 @@ export const env = createEnv({
|
|||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_NODE_ENV: z.enum(["development", "test", "production"]),
|
NEXT_PUBLIC_NODE_ENV: z.enum(["development", "test", "production"]),
|
||||||
NEXT_PUBLIC_PORT: z.string().default("3000"),
|
NEXT_PUBLIC_PORT: z.string().default("3000"),
|
||||||
NEXT_PUBLIC_PAYMENT_CALLBACK_URL: z
|
|
||||||
.string()
|
|
||||||
.default("/api/web/payment-callback"),
|
|
||||||
},
|
},
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV,
|
NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV,
|
||||||
NEXT_PUBLIC_PORT: process.env.NEXT_PUBLIC_PORT,
|
NEXT_PUBLIC_PORT: process.env.NEXT_PUBLIC_PORT,
|
||||||
NEXT_PUBLIC_PAYMENT_CALLBACK_URL: `${process.env.NODE_ENV === "development" ? `http://localhost:${process.env.NEXT_PUBLIC_PORT}` : ""}/api/web/payment-callback`,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -150,9 +150,11 @@
|
|||||||
"Gym": "Fitnesscenter",
|
"Gym": "Fitnesscenter",
|
||||||
"Hi": "Hei",
|
"Hi": "Hei",
|
||||||
"Highest level": "Højeste niveau",
|
"Highest level": "Højeste niveau",
|
||||||
|
"Home": "Hjem",
|
||||||
"Hospital": "Hospital",
|
"Hospital": "Hospital",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
"Hotel facilities": "Hotel faciliteter",
|
"Hotel facilities": "Hotel faciliteter",
|
||||||
|
"Hotel reservation": "Hotel reservation",
|
||||||
"Hotel surroundings": "Hotel omgivelser",
|
"Hotel surroundings": "Hotel omgivelser",
|
||||||
"Hotel(s)": "{amount} {amount, plural, one {hotel} other {hoteller}}",
|
"Hotel(s)": "{amount} {amount, plural, one {hotel} other {hoteller}}",
|
||||||
"Hotels": "Hoteller",
|
"Hotels": "Hoteller",
|
||||||
@@ -227,6 +229,7 @@
|
|||||||
"No breakfast": "Ingen morgenmad",
|
"No breakfast": "Ingen morgenmad",
|
||||||
"No content published": "Intet indhold offentliggjort",
|
"No content published": "Intet indhold offentliggjort",
|
||||||
"No matching location found": "Der blev ikke fundet nogen matchende placering",
|
"No matching location found": "Der blev ikke fundet nogen matchende placering",
|
||||||
|
"No prices available": "Ingen tilgængelige priser",
|
||||||
"No results": "Ingen resultater",
|
"No results": "Ingen resultater",
|
||||||
"No transactions available": "Ingen tilgængelige transaktioner",
|
"No transactions available": "Ingen tilgængelige transaktioner",
|
||||||
"No, keep card": "Nej, behold kortet",
|
"No, keep card": "Nej, behold kortet",
|
||||||
@@ -324,6 +327,7 @@
|
|||||||
"Select country of residence": "Vælg bopælsland",
|
"Select country of residence": "Vælg bopælsland",
|
||||||
"Select date of birth": "Vælg fødselsdato",
|
"Select date of birth": "Vælg fødselsdato",
|
||||||
"Select dates": "Vælg datoer",
|
"Select dates": "Vælg datoer",
|
||||||
|
"Select hotel": "Vælg hotel",
|
||||||
"Select language": "Vælg sprog",
|
"Select language": "Vælg sprog",
|
||||||
"Select payment method": "Vælg betalingsmetode",
|
"Select payment method": "Vælg betalingsmetode",
|
||||||
"Select your language": "Vælg dit sprog",
|
"Select your language": "Vælg dit sprog",
|
||||||
|
|||||||
@@ -150,9 +150,11 @@
|
|||||||
"Gym": "Fitnessstudio",
|
"Gym": "Fitnessstudio",
|
||||||
"Hi": "Hallo",
|
"Hi": "Hallo",
|
||||||
"Highest level": "Höchstes Level",
|
"Highest level": "Höchstes Level",
|
||||||
|
"Home": "Heim",
|
||||||
"Hospital": "Krankenhaus",
|
"Hospital": "Krankenhaus",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
"Hotel facilities": "Hotel-Infos",
|
"Hotel facilities": "Hotel-Infos",
|
||||||
|
"Hotel reservation": "Hotelreservierung",
|
||||||
"Hotel surroundings": "Umgebung des Hotels",
|
"Hotel surroundings": "Umgebung des Hotels",
|
||||||
"Hotel(s)": "{amount} {amount, plural, one {hotel} other {hotels}}",
|
"Hotel(s)": "{amount} {amount, plural, one {hotel} other {hotels}}",
|
||||||
"Hotels": "Hotels",
|
"Hotels": "Hotels",
|
||||||
@@ -225,6 +227,7 @@
|
|||||||
"No breakfast": "Kein Frühstück",
|
"No breakfast": "Kein Frühstück",
|
||||||
"No content published": "Kein Inhalt veröffentlicht",
|
"No content published": "Kein Inhalt veröffentlicht",
|
||||||
"No matching location found": "Kein passender Standort gefunden",
|
"No matching location found": "Kein passender Standort gefunden",
|
||||||
|
"No prices available": "Keine Preise verfügbar",
|
||||||
"No results": "Keine Ergebnisse",
|
"No results": "Keine Ergebnisse",
|
||||||
"No transactions available": "Keine Transaktionen verfügbar",
|
"No transactions available": "Keine Transaktionen verfügbar",
|
||||||
"No, keep card": "Nein, Karte behalten",
|
"No, keep card": "Nein, Karte behalten",
|
||||||
@@ -323,6 +326,7 @@
|
|||||||
"Select country of residence": "Wählen Sie das Land Ihres Wohnsitzes aus",
|
"Select country of residence": "Wählen Sie das Land Ihres Wohnsitzes aus",
|
||||||
"Select date of birth": "Geburtsdatum auswählen",
|
"Select date of birth": "Geburtsdatum auswählen",
|
||||||
"Select dates": "Datum auswählen",
|
"Select dates": "Datum auswählen",
|
||||||
|
"Select hotel": "Hotel auswählen",
|
||||||
"Select language": "Sprache auswählen",
|
"Select language": "Sprache auswählen",
|
||||||
"Select payment method": "Zahlungsart auswählen",
|
"Select payment method": "Zahlungsart auswählen",
|
||||||
"Select your language": "Wählen Sie Ihre Sprache",
|
"Select your language": "Wählen Sie Ihre Sprache",
|
||||||
|
|||||||
@@ -162,9 +162,11 @@
|
|||||||
"Gym": "Gym",
|
"Gym": "Gym",
|
||||||
"Hi": "Hi",
|
"Hi": "Hi",
|
||||||
"Highest level": "Highest level",
|
"Highest level": "Highest level",
|
||||||
|
"Home": "Home",
|
||||||
"Hospital": "Hospital",
|
"Hospital": "Hospital",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
"Hotel facilities": "Hotel facilities",
|
"Hotel facilities": "Hotel facilities",
|
||||||
|
"Hotel reservation": "Hotel reservation",
|
||||||
"Hotel surroundings": "Hotel surroundings",
|
"Hotel surroundings": "Hotel surroundings",
|
||||||
"Hotel(s)": "{amount} {amount, plural, one {hotel} other {hotels}}",
|
"Hotel(s)": "{amount} {amount, plural, one {hotel} other {hotels}}",
|
||||||
"Hotels": "Hotels",
|
"Hotels": "Hotels",
|
||||||
@@ -244,6 +246,7 @@
|
|||||||
"No breakfast": "No breakfast",
|
"No breakfast": "No breakfast",
|
||||||
"No content published": "No content published",
|
"No content published": "No content published",
|
||||||
"No matching location found": "No matching location found",
|
"No matching location found": "No matching location found",
|
||||||
|
"No prices available": "No prices available",
|
||||||
"No results": "No results",
|
"No results": "No results",
|
||||||
"No transactions available": "No transactions available",
|
"No transactions available": "No transactions available",
|
||||||
"No, keep card": "No, keep card",
|
"No, keep card": "No, keep card",
|
||||||
@@ -353,6 +356,7 @@
|
|||||||
"Select country of residence": "Select country of residence",
|
"Select country of residence": "Select country of residence",
|
||||||
"Select date of birth": "Select date of birth",
|
"Select date of birth": "Select date of birth",
|
||||||
"Select dates": "Select dates",
|
"Select dates": "Select dates",
|
||||||
|
"Select hotel": "Select hotel",
|
||||||
"Select language": "Select language",
|
"Select language": "Select language",
|
||||||
"Select payment method": "Select payment method",
|
"Select payment method": "Select payment method",
|
||||||
"Select your language": "Select your language",
|
"Select your language": "Select your language",
|
||||||
@@ -469,6 +473,8 @@
|
|||||||
"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",
|
||||||
"characters": "characters",
|
"characters": "characters",
|
||||||
|
"filters.nohotel.heading": "No hotels match your filters",
|
||||||
|
"filters.nohotel.text": "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
"from": "from",
|
"from": "from",
|
||||||
"guaranteeing": "guaranteeing",
|
"guaranteeing": "guaranteeing",
|
||||||
"guest": "guest",
|
"guest": "guest",
|
||||||
|
|||||||
@@ -150,9 +150,11 @@
|
|||||||
"Gym": "Kuntosali",
|
"Gym": "Kuntosali",
|
||||||
"Hi": "Hi",
|
"Hi": "Hi",
|
||||||
"Highest level": "Korkein taso",
|
"Highest level": "Korkein taso",
|
||||||
|
"Home": "Kotiin",
|
||||||
"Hospital": "Sairaala",
|
"Hospital": "Sairaala",
|
||||||
"Hotel": "Hotelli",
|
"Hotel": "Hotelli",
|
||||||
"Hotel facilities": "Hotellin palvelut",
|
"Hotel facilities": "Hotellin palvelut",
|
||||||
|
"Hotel reservation": "Hotellivaraukset",
|
||||||
"Hotel surroundings": "Hotellin ympäristö",
|
"Hotel surroundings": "Hotellin ympäristö",
|
||||||
"Hotel(s)": "{amount} {amount, plural, one {hotelli} other {hotellit}}",
|
"Hotel(s)": "{amount} {amount, plural, one {hotelli} other {hotellit}}",
|
||||||
"Hotels": "Hotellit",
|
"Hotels": "Hotellit",
|
||||||
@@ -227,6 +229,7 @@
|
|||||||
"No breakfast": "Ei aamiaista",
|
"No breakfast": "Ei aamiaista",
|
||||||
"No content published": "Ei julkaistua sisältöä",
|
"No content published": "Ei julkaistua sisältöä",
|
||||||
"No matching location found": "Vastaavaa sijaintia ei löytynyt",
|
"No matching location found": "Vastaavaa sijaintia ei löytynyt",
|
||||||
|
"No prices available": "Hintoja ei ole saatavilla",
|
||||||
"No results": "Ei tuloksia",
|
"No results": "Ei tuloksia",
|
||||||
"No transactions available": "Ei tapahtumia saatavilla",
|
"No transactions available": "Ei tapahtumia saatavilla",
|
||||||
"No, keep card": "Ei, pidä kortti",
|
"No, keep card": "Ei, pidä kortti",
|
||||||
@@ -325,6 +328,7 @@
|
|||||||
"Select country of residence": "Valitse asuinmaa",
|
"Select country of residence": "Valitse asuinmaa",
|
||||||
"Select date of birth": "Valitse syntymäaika",
|
"Select date of birth": "Valitse syntymäaika",
|
||||||
"Select dates": "Valitse päivämäärät",
|
"Select dates": "Valitse päivämäärät",
|
||||||
|
"Select hotel": "Valitse hotelli",
|
||||||
"Select language": "Valitse kieli",
|
"Select language": "Valitse kieli",
|
||||||
"Select payment method": "Valitse maksutapa",
|
"Select payment method": "Valitse maksutapa",
|
||||||
"Select your language": "Valitse kieli",
|
"Select your language": "Valitse kieli",
|
||||||
|
|||||||
@@ -149,9 +149,11 @@
|
|||||||
"Gym": "Treningsstudio",
|
"Gym": "Treningsstudio",
|
||||||
"Hi": "Hei",
|
"Hi": "Hei",
|
||||||
"Highest level": "Høyeste nivå",
|
"Highest level": "Høyeste nivå",
|
||||||
|
"Home": "Hjem",
|
||||||
"Hospital": "Sykehus",
|
"Hospital": "Sykehus",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
"Hotel facilities": "Hotelfaciliteter",
|
"Hotel facilities": "Hotelfaciliteter",
|
||||||
|
"Hotel reservation": "Hotellreservasjon",
|
||||||
"Hotel surroundings": "Hotellomgivelser",
|
"Hotel surroundings": "Hotellomgivelser",
|
||||||
"Hotel(s)": "{amount} {amount, plural, one {hotell} other {hoteller}}",
|
"Hotel(s)": "{amount} {amount, plural, one {hotell} other {hoteller}}",
|
||||||
"Hotels": "Hoteller",
|
"Hotels": "Hoteller",
|
||||||
@@ -225,6 +227,7 @@
|
|||||||
"No breakfast": "Ingen frokost",
|
"No breakfast": "Ingen frokost",
|
||||||
"No content published": "Ingen innhold publisert",
|
"No content published": "Ingen innhold publisert",
|
||||||
"No matching location found": "Fant ingen samsvarende plassering",
|
"No matching location found": "Fant ingen samsvarende plassering",
|
||||||
|
"No prices available": "Ingen priser tilgjengelig",
|
||||||
"No results": "Ingen resultater",
|
"No results": "Ingen resultater",
|
||||||
"No transactions available": "Ingen transaksjoner tilgjengelig",
|
"No transactions available": "Ingen transaksjoner tilgjengelig",
|
||||||
"No, keep card": "Nei, behold kortet",
|
"No, keep card": "Nei, behold kortet",
|
||||||
@@ -322,6 +325,7 @@
|
|||||||
"Select country of residence": "Velg bostedsland",
|
"Select country of residence": "Velg bostedsland",
|
||||||
"Select date of birth": "Velg fødselsdato",
|
"Select date of birth": "Velg fødselsdato",
|
||||||
"Select dates": "Velg datoer",
|
"Select dates": "Velg datoer",
|
||||||
|
"Select hotel": "Velg hotell",
|
||||||
"Select language": "Velg språk",
|
"Select language": "Velg språk",
|
||||||
"Select payment method": "Velg betalingsmetode",
|
"Select payment method": "Velg betalingsmetode",
|
||||||
"Select your language": "Velg språk",
|
"Select your language": "Velg språk",
|
||||||
|
|||||||
@@ -149,9 +149,11 @@
|
|||||||
"Gym": "Gym",
|
"Gym": "Gym",
|
||||||
"Hi": "Hej",
|
"Hi": "Hej",
|
||||||
"Highest level": "Högsta nivå",
|
"Highest level": "Högsta nivå",
|
||||||
|
"Home": "Hem",
|
||||||
"Hospital": "Sjukhus",
|
"Hospital": "Sjukhus",
|
||||||
"Hotel": "Hotell",
|
"Hotel": "Hotell",
|
||||||
"Hotel facilities": "Hotellfaciliteter",
|
"Hotel facilities": "Hotellfaciliteter",
|
||||||
|
"Hotel reservation": "Hotellbokning",
|
||||||
"Hotel surroundings": "Hotellomgivning",
|
"Hotel surroundings": "Hotellomgivning",
|
||||||
"Hotel(s)": "{amount} hotell",
|
"Hotel(s)": "{amount} hotell",
|
||||||
"Hotels": "Hotell",
|
"Hotels": "Hotell",
|
||||||
@@ -225,6 +227,7 @@
|
|||||||
"No breakfast": "Ingen frukost",
|
"No breakfast": "Ingen frukost",
|
||||||
"No content published": "Inget innehåll publicerat",
|
"No content published": "Inget innehåll publicerat",
|
||||||
"No matching location found": "Ingen matchande plats hittades",
|
"No matching location found": "Ingen matchande plats hittades",
|
||||||
|
"No prices available": "Inga priser tillgängliga",
|
||||||
"No results": "Inga resultat",
|
"No results": "Inga resultat",
|
||||||
"No transactions available": "Inga transaktioner tillgängliga",
|
"No transactions available": "Inga transaktioner tillgängliga",
|
||||||
"No, keep card": "Nej, behåll kortet",
|
"No, keep card": "Nej, behåll kortet",
|
||||||
@@ -322,6 +325,7 @@
|
|||||||
"Select country of residence": "Välj bosättningsland",
|
"Select country of residence": "Välj bosättningsland",
|
||||||
"Select date of birth": "Välj födelsedatum",
|
"Select date of birth": "Välj födelsedatum",
|
||||||
"Select dates": "Välj datum",
|
"Select dates": "Välj datum",
|
||||||
|
"Select hotel": "Välj hotell",
|
||||||
"Select language": "Välj språk",
|
"Select language": "Välj språk",
|
||||||
"Select payment method": "Välj betalningsmetod",
|
"Select payment method": "Välj betalningsmetod",
|
||||||
"Select your language": "Välj ditt språk",
|
"Select your language": "Välj ditt språk",
|
||||||
|
|||||||
@@ -2,30 +2,14 @@ import "server-only"
|
|||||||
|
|
||||||
import deepmerge from "deepmerge"
|
import deepmerge from "deepmerge"
|
||||||
|
|
||||||
|
import { arrayMerge } from "@/utils/merge"
|
||||||
|
|
||||||
import { request } from "./request"
|
import { request } from "./request"
|
||||||
|
|
||||||
import type { BatchRequestDocument } from "graphql-request"
|
import type { BatchRequestDocument } from "graphql-request"
|
||||||
|
|
||||||
import type { Data } from "@/types/request"
|
import type { Data } from "@/types/request"
|
||||||
|
|
||||||
function arrayMerge(
|
|
||||||
target: any[],
|
|
||||||
source: any[],
|
|
||||||
options: deepmerge.ArrayMergeOptions | undefined
|
|
||||||
) {
|
|
||||||
const destination = target.slice()
|
|
||||||
source.forEach((item, index) => {
|
|
||||||
if (typeof destination[index] === "undefined") {
|
|
||||||
destination[index] = options?.cloneUnlessOtherwiseSpecified(item, options)
|
|
||||||
} else if (options?.isMergeableObject(item)) {
|
|
||||||
destination[index] = deepmerge(target[index], item, options)
|
|
||||||
} else if (target.indexOf(item) === -1) {
|
|
||||||
destination.push(item)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return destination
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function batchRequest<T>(
|
export async function batchRequest<T>(
|
||||||
queries: (BatchRequestDocument & { options?: RequestInit })[]
|
queries: (BatchRequestDocument & { options?: RequestInit })[]
|
||||||
): Promise<Data<T>> {
|
): Promise<Data<T>> {
|
||||||
|
|||||||
@@ -282,6 +282,11 @@ const nextConfig = {
|
|||||||
"/:lang/hotelreservation/:step(breakfast|details|payment|select-bed)",
|
"/:lang/hotelreservation/:step(breakfast|details|payment|select-bed)",
|
||||||
destination: "/:lang/hotelreservation/step?step=:step",
|
destination: "/:lang/hotelreservation/step?step=:step",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/:lang/hotelreservation/payment-callback/:status",
|
||||||
|
destination:
|
||||||
|
"/:lang/hotelreservation/payment-callback?status=:status",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ export const createBookingSchema = z
|
|||||||
paymentUrl: z.string().nullable(),
|
paymentUrl: z.string().nullable(),
|
||||||
metadata: z
|
metadata: z
|
||||||
.object({
|
.object({
|
||||||
errorCode: z.number().optional(),
|
errorCode: z.number().nullable().optional(),
|
||||||
errorMessage: z.string().optional(),
|
errorMessage: z.string().nullable().optional(),
|
||||||
priceChangedMetadata: z
|
priceChangedMetadata: z
|
||||||
.object({
|
.object({
|
||||||
roomPrice: z.number().optional(),
|
roomPrice: z.number().nullable().optional(),
|
||||||
totalPrice: z.number().optional(),
|
totalPrice: z.number().nullable().optional(),
|
||||||
})
|
})
|
||||||
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.nullable(),
|
.nullable(),
|
||||||
|
|||||||
@@ -112,10 +112,10 @@ const hotelContentSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
restaurantsOverviewPage: z.object({
|
restaurantsOverviewPage: z.object({
|
||||||
restaurantsOverviewPageLinkText: z.string(),
|
restaurantsOverviewPageLinkText: z.string().optional(),
|
||||||
restaurantsOverviewPageLink: z.string(),
|
restaurantsOverviewPageLink: z.string().optional(),
|
||||||
restaurantsContentDescriptionShort: z.string(),
|
restaurantsContentDescriptionShort: z.string().optional(),
|
||||||
restaurantsContentDescriptionMedium: z.string(),
|
restaurantsContentDescriptionMedium: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -864,22 +864,24 @@ export const packagesSchema = z.object({
|
|||||||
|
|
||||||
export const getRoomPackagesSchema = z
|
export const getRoomPackagesSchema = z
|
||||||
.object({
|
.object({
|
||||||
data: z.object({
|
data: z
|
||||||
attributes: z.object({
|
.object({
|
||||||
hotelId: z.number(),
|
attributes: z.object({
|
||||||
packages: z.array(packagesSchema).optional().default([]),
|
hotelId: z.number(),
|
||||||
}),
|
packages: z.array(packagesSchema).optional().default([]),
|
||||||
relationships: z
|
}),
|
||||||
.object({
|
relationships: z
|
||||||
links: z.array(
|
.object({
|
||||||
z.object({
|
links: z.array(
|
||||||
url: z.string(),
|
z.object({
|
||||||
type: z.string(),
|
url: z.string(),
|
||||||
})
|
type: z.string(),
|
||||||
),
|
})
|
||||||
})
|
),
|
||||||
.optional(),
|
})
|
||||||
type: z.string(),
|
.optional(),
|
||||||
}),
|
type: z.string(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
.transform((data) => data.data.attributes.packages)
|
.transform((data) => data.data?.attributes?.packages ?? [])
|
||||||
|
|||||||
@@ -939,12 +939,10 @@ export const hotelQueryRouter = router({
|
|||||||
"api.hotels.packages error",
|
"api.hotels.packages error",
|
||||||
JSON.stringify({ query: { hotelId, params } })
|
JSON.stringify({ query: { hotelId, params } })
|
||||||
)
|
)
|
||||||
throw serverErrorByStatus(apiResponse.status, apiResponse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiJson = await apiResponse.json()
|
const apiJson = await apiResponse.json()
|
||||||
const validatedPackagesData = getRoomPackagesSchema.safeParse(apiJson)
|
const validatedPackagesData = getRoomPackagesSchema.safeParse(apiJson)
|
||||||
|
|
||||||
if (!validatedPackagesData.success) {
|
if (!validatedPackagesData.success) {
|
||||||
getHotelFailCounter.add(1, {
|
getHotelFailCounter.add(1, {
|
||||||
hotelId,
|
hotelId,
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ import {
|
|||||||
signedInDetailsSchema,
|
signedInDetailsSchema,
|
||||||
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
||||||
import { DetailsContext } from "@/contexts/Details"
|
import { DetailsContext } from "@/contexts/Details"
|
||||||
|
import { arrayMerge } from "@/utils/merge"
|
||||||
|
|
||||||
import { StepEnum } from "@/types/enums/step"
|
import { StepEnum } from "@/types/enums/step"
|
||||||
import type { DetailsState, InitialState } from "@/types/stores/details"
|
import type { DetailsState, InitialState } from "@/types/stores/details"
|
||||||
|
|
||||||
export const storageName = "details-storage"
|
export const detailsStorageName = "details-storage"
|
||||||
export function createDetailsStore(
|
export function createDetailsStore(
|
||||||
initialState: InitialState,
|
initialState: InitialState,
|
||||||
isMember: boolean
|
isMember: boolean
|
||||||
@@ -27,13 +28,15 @@ export function createDetailsStore(
|
|||||||
* we cannot use the data as `defaultValues` for our forms.
|
* we cannot use the data as `defaultValues` for our forms.
|
||||||
* RHF caches defaultValues on mount.
|
* RHF caches defaultValues on mount.
|
||||||
*/
|
*/
|
||||||
const detailsStorageUnparsed = sessionStorage.getItem(storageName)
|
const detailsStorageUnparsed = sessionStorage.getItem(detailsStorageName)
|
||||||
if (detailsStorageUnparsed) {
|
if (detailsStorageUnparsed) {
|
||||||
const detailsStorage: Record<
|
const detailsStorage: Record<
|
||||||
"state",
|
"state",
|
||||||
Pick<DetailsState, "data">
|
Pick<DetailsState, "data">
|
||||||
> = JSON.parse(detailsStorageUnparsed)
|
> = JSON.parse(detailsStorageUnparsed)
|
||||||
initialState = merge(initialState, detailsStorage.state.data)
|
initialState = merge(detailsStorage.state.data, initialState, {
|
||||||
|
arrayMerge,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return create<DetailsState>()(
|
return create<DetailsState>()(
|
||||||
@@ -135,40 +138,39 @@ export function createDetailsStore(
|
|||||||
},
|
},
|
||||||
|
|
||||||
totalPrice: {
|
totalPrice: {
|
||||||
euro: { currency: "", price: 0 },
|
euro: { currency: "", amount: 0 },
|
||||||
local: { currency: "", price: 0 },
|
local: { currency: "", amount: 0 },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: storageName,
|
name: detailsStorageName,
|
||||||
onRehydrateStorage() {
|
onRehydrateStorage(prevState) {
|
||||||
return function (state) {
|
return function (state) {
|
||||||
if (state) {
|
if (state) {
|
||||||
const validatedBedType = bedTypeSchema.safeParse(state.data)
|
const validatedBedType = bedTypeSchema.safeParse(state.data)
|
||||||
if (validatedBedType.success) {
|
if (validatedBedType.success !== state.isValid["select-bed"]) {
|
||||||
state.actions.updateValidity(StepEnum.selectBed, true)
|
state.isValid["select-bed"] = validatedBedType.success
|
||||||
} else {
|
|
||||||
state.actions.updateValidity(StepEnum.selectBed, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedBreakfast = breakfastStoreSchema.safeParse(
|
const validatedBreakfast = breakfastStoreSchema.safeParse(
|
||||||
state.data
|
state.data
|
||||||
)
|
)
|
||||||
if (validatedBreakfast.success) {
|
if (validatedBreakfast.success !== state.isValid.breakfast) {
|
||||||
state.actions.updateValidity(StepEnum.breakfast, true)
|
state.isValid.breakfast = validatedBreakfast.success
|
||||||
} else {
|
|
||||||
state.actions.updateValidity(StepEnum.breakfast, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailsSchema = isMember
|
const detailsSchema = isMember
|
||||||
? signedInDetailsSchema
|
? signedInDetailsSchema
|
||||||
: guestDetailsSchema
|
: guestDetailsSchema
|
||||||
const validatedDetails = detailsSchema.safeParse(state.data)
|
const validatedDetails = detailsSchema.safeParse(state.data)
|
||||||
if (validatedDetails.success) {
|
if (validatedDetails.success !== state.isValid.details) {
|
||||||
state.actions.updateValidity(StepEnum.details, true)
|
state.isValid.details = validatedDetails.success
|
||||||
} else {
|
|
||||||
state.actions.updateValidity(StepEnum.details, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mergedState = merge(state.data, prevState.data, {
|
||||||
|
arrayMerge,
|
||||||
|
})
|
||||||
|
state.data = mergedState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
||||||
import { StepsContext } from "@/contexts/Steps"
|
import { StepsContext } from "@/contexts/Steps"
|
||||||
|
|
||||||
import { storageName as detailsStorageName } from "./details"
|
import { detailsStorageName as detailsStorageName } from "./details"
|
||||||
|
|
||||||
import { StepEnum } from "@/types/enums/step"
|
import { StepEnum } from "@/types/enums/step"
|
||||||
import type { DetailsState } from "@/types/stores/details"
|
import type { DetailsState } from "@/types/stores/details"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface Room {
|
|||||||
adults: number
|
adults: number
|
||||||
roomTypeCode: string
|
roomTypeCode: string
|
||||||
rateCode: string
|
rateCode: string
|
||||||
|
counterRateCode: string
|
||||||
children?: Child[]
|
children?: Child[]
|
||||||
packages?: RoomPackageCodeEnum[]
|
packages?: RoomPackageCodeEnum[]
|
||||||
}
|
}
|
||||||
@@ -18,14 +19,24 @@ export interface BookingData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Price = {
|
type Price = {
|
||||||
price: number
|
amount: number
|
||||||
currency: string
|
currency: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoomsData = {
|
export type RoomsData = {
|
||||||
roomType: string
|
roomType: string
|
||||||
localPrice: Price
|
prices: {
|
||||||
euroPrice: Price | undefined
|
public: {
|
||||||
|
local: Price
|
||||||
|
euro: Price | undefined
|
||||||
|
}
|
||||||
|
member:
|
||||||
|
| {
|
||||||
|
local: Price
|
||||||
|
euro: Price | undefined
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
}
|
||||||
adults: number
|
adults: number
|
||||||
children?: Child[]
|
children?: Child[]
|
||||||
rateDetails?: string[]
|
rateDetails?: string[]
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import {
|
import { packagesSchema } from "@/server/routers/hotels/output"
|
||||||
getRoomPackagesSchema,
|
|
||||||
packagesSchema,
|
|
||||||
} from "@/server/routers/hotels/output"
|
|
||||||
|
|
||||||
export enum RoomPackageCodeEnum {
|
export enum RoomPackageCodeEnum {
|
||||||
PET_ROOM = "PETR",
|
PET_ROOM = "PETR",
|
||||||
ALLERGY_ROOM = "ALLG",
|
ALLERGY_ROOM = "ALLG",
|
||||||
ACCESSIBILITY_ROOM = "ACCE",
|
ACCESSIBILITY_ROOM = "ACCE",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DefaultFilterOptions {
|
||||||
|
code: RoomPackageCodeEnum
|
||||||
|
description: string
|
||||||
|
itemCode: string | undefined
|
||||||
|
}
|
||||||
export interface RoomFilterProps {
|
export interface RoomFilterProps {
|
||||||
numberOfRooms: number
|
numberOfRooms: number
|
||||||
onFilter: (filter: Record<string, boolean | undefined>) => void
|
onFilter: (filter: Record<string, boolean | undefined>) => void
|
||||||
filterOptions: RoomPackageData
|
filterOptions: DefaultFilterOptions[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoomPackage = z.output<typeof packagesSchema>
|
export type RoomPackage = z.output<typeof packagesSchema>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export interface RoomSelectionProps {
|
|||||||
roomsAvailability: RoomsAvailability
|
roomsAvailability: RoomsAvailability
|
||||||
roomCategories: RoomData[]
|
roomCategories: RoomData[]
|
||||||
user: SafeUser
|
user: SafeUser
|
||||||
packages: RoomPackageData | undefined
|
availablePackages: RoomPackageData | undefined
|
||||||
selectedPackages: RoomPackageCodes[]
|
selectedPackages: RoomPackageCodes[]
|
||||||
setRateCode: (rateCode: {
|
setRateCode: (rateCode: {
|
||||||
publicRateCode: string
|
publicRateCode: string
|
||||||
@@ -21,5 +21,5 @@ export interface SelectRateProps {
|
|||||||
roomsAvailability: RoomsAvailability
|
roomsAvailability: RoomsAvailability
|
||||||
roomCategories: RoomData[]
|
roomCategories: RoomData[]
|
||||||
user: SafeUser
|
user: SafeUser
|
||||||
packages: RoomPackageData
|
availablePackages: RoomPackageData
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CreditCard } from "@/types/user"
|
import { CreditCard, SafeUser } from "@/types/user"
|
||||||
|
|
||||||
export interface SectionProps {
|
export interface SectionProps {
|
||||||
nextPath: string
|
nextPath: string
|
||||||
@@ -28,6 +28,7 @@ export interface BreakfastSelectionProps extends SectionProps {
|
|||||||
export interface DetailsProps extends SectionProps {}
|
export interface DetailsProps extends SectionProps {}
|
||||||
|
|
||||||
export interface PaymentProps {
|
export interface PaymentProps {
|
||||||
|
user: SafeUser
|
||||||
roomPrice: { publicPrice: number; memberPrice: number | undefined }
|
roomPrice: { publicPrice: number; memberPrice: number | undefined }
|
||||||
otherPaymentOptions: string[]
|
otherPaymentOptions: string[]
|
||||||
savedCreditCards: CreditCard[] | null
|
savedCreditCards: CreditCard[] | null
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface Room {
|
|||||||
adults: number
|
adults: number
|
||||||
roomtype: string
|
roomtype: string
|
||||||
ratecode: string
|
ratecode: string
|
||||||
counterratecode?: string
|
counterratecode: string
|
||||||
child?: Child[]
|
child?: Child[]
|
||||||
packages?: string
|
packages?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export interface DetailsState {
|
|||||||
actions: {
|
actions: {
|
||||||
setIsSubmittingDisabled: (isSubmittingDisabled: boolean) => void
|
setIsSubmittingDisabled: (isSubmittingDisabled: boolean) => void
|
||||||
setTotalPrice: (totalPrice: TotalPrice) => void
|
setTotalPrice: (totalPrice: TotalPrice) => void
|
||||||
toggleSummaryOpen: () => void,
|
toggleSummaryOpen: () => void
|
||||||
updateBedType: (data: BedTypeSchema) => void
|
updateBedType: (data: BedTypeSchema) => void
|
||||||
updateBreakfast: (data: BreakfastPackage | false) => void
|
updateBreakfast: (data: BreakfastPackage | false) => void
|
||||||
updateDetails: (data: DetailsSchema) => void
|
updateDetails: (data: DetailsSchema) => void
|
||||||
@@ -31,10 +31,10 @@ export interface InitialState extends Partial<DetailsState> {
|
|||||||
|
|
||||||
interface Price {
|
interface Price {
|
||||||
currency: string
|
currency: string
|
||||||
price: number
|
amount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TotalPrice {
|
export interface TotalPrice {
|
||||||
euro: Price | undefined
|
euro: Price | undefined
|
||||||
local: Price
|
local: Price
|
||||||
}
|
}
|
||||||
|
|||||||
19
utils/merge.ts
Normal file
19
utils/merge.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import merge from "deepmerge"
|
||||||
|
|
||||||
|
export function arrayMerge(
|
||||||
|
target: any[],
|
||||||
|
source: any[],
|
||||||
|
options: merge.ArrayMergeOptions
|
||||||
|
) {
|
||||||
|
const destination = target.slice()
|
||||||
|
source.forEach((item, index) => {
|
||||||
|
if (typeof destination[index] === "undefined") {
|
||||||
|
destination[index] = options.cloneUnlessOtherwiseSpecified(item, options)
|
||||||
|
} else if (options?.isMergeableObject(item)) {
|
||||||
|
destination[index] = merge(target[index], item, options)
|
||||||
|
} else if (target.indexOf(item) === -1) {
|
||||||
|
destination.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return destination
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user