diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.tsx index 3308028c0..17be92f63 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/my-stay/page.tsx @@ -6,6 +6,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import { env } from "@/env/server" import { dt } from "@/lib/dt" import { + findBooking, getAncillaryPackages, getBookingConfirmation, getLinkedReservations, @@ -15,6 +16,7 @@ import { } from "@/lib/trpc/memoizedRequests" import { decrypt } from "@/server/routers/utils/encryption" +import { auth } from "@/auth" import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm" import accessBooking, { ACCESS_GRANTED, @@ -32,6 +34,7 @@ import Image from "@/components/Image" import { getIntl } from "@/i18n" import { setLang } from "@/i18n/serverContext" import MyStayProvider from "@/providers/MyStay" +import { isValidSession } from "@/utils/session" import { getCurrentWebUrl } from "@/utils/url" import styles from "./page.module.css" @@ -55,8 +58,43 @@ export default async function MyStay({ return notFound() } + const session = await auth() + const isLoggedIn = isValidSession(session) + const [confirmationNumber, lastName] = value.split(",") - const bookingConfirmation = await getBookingConfirmation(confirmationNumber) + const bv = cookies().get("bv")?.value + let bookingConfirmation + if (isLoggedIn) { + bookingConfirmation = await getBookingConfirmation(confirmationNumber) + } else if (bv) { + const params = new URLSearchParams(bv) + const firstName = params.get("firstName") + const email = params.get("email") + + if (firstName && email) { + bookingConfirmation = await findBooking( + confirmationNumber, + lastName, + firstName, + email + ) + } else { + return ( + + ) + } + } else { + return ( + + ) + } + if (!bookingConfirmation) { return notFound() } @@ -64,7 +102,7 @@ export default async function MyStay({ const { additionalData, booking, hotel, roomCategories } = bookingConfirmation const user = await getProfileSafely() - const bv = cookies().get("bv")?.value + const intl = await getIntl() const access = accessBooking(booking.guest, lastName, user, bv) @@ -232,3 +270,22 @@ export default async function MyStay({ return notFound() } + +function RenderAdditionalInfoForm({ + confirmationNumber, + lastName, +}: { + confirmationNumber: string + lastName: string +}) { + return ( +
+
+ +
+
+ ) +} diff --git a/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.tsx b/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.tsx index e083c1cde..c87a6a567 100644 --- a/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.tsx +++ b/apps/scandic-web/app/[lang]/webview/hotelreservation/my-stay/page.tsx @@ -6,6 +6,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import { env } from "@/env/server" import { dt } from "@/lib/dt" import { + findBooking, getAncillaryPackages, getBookingConfirmation, getLinkedReservations, @@ -15,6 +16,7 @@ import { } from "@/lib/trpc/memoizedRequests" import { decrypt } from "@/server/routers/utils/encryption" +import { auth } from "@/auth" import AdditionalInfoForm from "@/components/HotelReservation/FindMyBooking/AdditionalInfoForm" import accessBooking, { ACCESS_GRANTED, @@ -32,6 +34,7 @@ import Image from "@/components/Image" import { getIntl } from "@/i18n" import { setLang } from "@/i18n/serverContext" import MyStayProvider from "@/providers/MyStay" +import { isValidSession } from "@/utils/session" import { getCurrentWebUrl } from "@/utils/url" import styles from "./page.module.css" @@ -54,9 +57,42 @@ export default async function MyStay({ if (!value) { return notFound() } + const session = await auth() + const isLoggedIn = isValidSession(session) const [confirmationNumber, lastName] = value.split(",") - const bookingConfirmation = await getBookingConfirmation(confirmationNumber) + const bv = cookies().get("bv")?.value + let bookingConfirmation + if (isLoggedIn) { + bookingConfirmation = await getBookingConfirmation(confirmationNumber) + } else if (bv) { + const params = new URLSearchParams(bv) + const firstName = params.get("firstName") + const email = params.get("email") + + if (firstName && email) { + bookingConfirmation = await findBooking( + confirmationNumber, + lastName, + firstName, + email + ) + } else { + return ( + + ) + } + } else { + return ( + + ) + } if (!bookingConfirmation) { return notFound() } @@ -64,7 +100,6 @@ export default async function MyStay({ const { additionalData, booking, hotel, roomCategories } = bookingConfirmation const user = await getProfileSafely() - const bv = cookies().get("bv")?.value const intl = await getIntl() const access = accessBooking(booking.guest, lastName, user, bv) @@ -232,3 +267,22 @@ export default async function MyStay({ return notFound() } + +function RenderAdditionalInfoForm({ + confirmationNumber, + lastName, +}: { + confirmationNumber: string + lastName: string +}) { + return ( +
+
+ +
+
+ ) +} diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx index b7f5923c3..420648db7 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/Receipt/index.tsx @@ -6,13 +6,16 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import { dt } from "@/lib/dt" import { + findBooking, getAncillaryPackages, getBookingConfirmation, getProfileSafely, } from "@/lib/trpc/memoizedRequests" import { decrypt } from "@/server/routers/utils/encryption" +import { auth } from "@/auth" import { getIntl } from "@/i18n" +import { isValidSession } from "@/utils/session" import AdditionalInfoForm from "../../FindMyBooking/AdditionalInfoForm" import accessBooking, { @@ -33,15 +36,48 @@ export async function Receipt({ refId }: { refId: string }) { if (!value) { return notFound() } + const session = await auth() + const isLoggedIn = isValidSession(session) + const [confirmationNumber, lastName] = value.split(",") - const bookingConfirmation = await getBookingConfirmation(confirmationNumber) + const bv = cookies().get("bv")?.value + let bookingConfirmation + if (isLoggedIn) { + bookingConfirmation = await getBookingConfirmation(confirmationNumber) + } else if (bv) { + const params = new URLSearchParams(bv) + const firstName = params.get("firstName") + const email = params.get("email") + + if (firstName && email) { + bookingConfirmation = await findBooking( + confirmationNumber, + lastName, + firstName, + email + ) + } else { + return ( + + ) + } + } else { + return ( + + ) + } if (!bookingConfirmation) { return notFound() } const { booking, hotel, room } = bookingConfirmation const user = await getProfileSafely() - const bv = cookies().get("bv")?.value const intl = await getIntl() const access = accessBooking(booking.guest, lastName, user, bv) @@ -166,3 +202,22 @@ export async function Receipt({ refId }: { refId: string }) { return notFound() } + +function RenderAdditionalInfoForm({ + confirmationNumber, + lastName, +}: { + confirmationNumber: string + lastName: string +}) { + return ( +
+
+ +
+
+ ) +} diff --git a/apps/scandic-web/lib/api/endpoints.ts b/apps/scandic-web/lib/api/endpoints.ts index d505fa9ec..07a3f5a38 100644 --- a/apps/scandic-web/lib/api/endpoints.ts +++ b/apps/scandic-web/lib/api/endpoints.ts @@ -60,6 +60,9 @@ export namespace endpoints { export function booking(confirmationNumber: string) { return `${bookings}/${confirmationNumber}` } + export function find(confirmationNumber: string) { + return `${bookings}/${confirmationNumber}/find` + } export function cancel(confirmationNumber: string) { return `${bookings}/${confirmationNumber}/cancel` } diff --git a/apps/scandic-web/lib/trpc/memoizedRequests/index.ts b/apps/scandic-web/lib/trpc/memoizedRequests/index.ts index 9dd7eb0ba..05040e27c 100644 --- a/apps/scandic-web/lib/trpc/memoizedRequests/index.ts +++ b/apps/scandic-web/lib/trpc/memoizedRequests/index.ts @@ -141,6 +141,20 @@ export const getBookingConfirmation = cache( } ) +export const findBooking = cache(async function getMemoizedFindBooking( + confirmationNumber: string, + lastName: string, + firstName: string, + email: string +) { + return serverClient().booking.findBooking({ + confirmationNumber, + lastName, + firstName, + email, + }) +}) + export const getLinkedReservations = cache( async function getMemoizedLinkedReservations(input: LinkedReservationsInput) { return serverClient().booking.linkedReservations(input) diff --git a/apps/scandic-web/server/routers/booking/input.ts b/apps/scandic-web/server/routers/booking/input.ts index bbf444181..50bcdec28 100644 --- a/apps/scandic-web/server/routers/booking/input.ts +++ b/apps/scandic-web/server/routers/booking/input.ts @@ -184,6 +184,14 @@ export const getLinkedReservationsInput = z.object({ ), }) +export const findBookingInput = z.object({ + confirmationNumber: z.string(), + firstName: z.string(), + lastName: z.string(), + email: z.string(), + lang: z.nativeEnum(Lang).optional(), +}) + export type LinkedReservationsInput = z.input export const getBookingStatusInput = confirmationNumberInput diff --git a/apps/scandic-web/server/routers/booking/query.ts b/apps/scandic-web/server/routers/booking/query.ts index 280a49769..2c8a518d3 100644 --- a/apps/scandic-web/server/routers/booking/query.ts +++ b/apps/scandic-web/server/routers/booking/query.ts @@ -11,12 +11,13 @@ import { getHotel } from "../hotels/utils" import { encrypt } from "../utils/encryption" import { createRefIdInput, + findBookingInput, getBookingInput, getBookingStatusInput, getLinkedReservationsInput, } from "./input" import { createBookingSchema } from "./output" -import { getBookedHotelRoom, getBooking } from "./utils" +import { findBooking, getBookedHotelRoom, getBooking } from "./utils" export const bookingQueryRouter = router({ get: safeProtectedServiceProcedure @@ -69,6 +70,66 @@ export const bookingQueryRouter = router({ metricsGetBooking.success() + return { + ...hotelData, + booking, + room: getBookedHotelRoom( + hotelData.roomCategories, + booking.roomTypeCode + ), + } + }), + findBooking: safeProtectedServiceProcedure + .input(findBookingInput) + + .query(async function ({ + ctx, + input: { confirmationNumber, lastName, firstName, email }, + }) { + const findBookingCounter = createCounter("trpc.booking", "findBooking") + const metricsFindBooking = findBookingCounter.init({ confirmationNumber }) + + metricsFindBooking.start() + + const booking = await findBooking( + confirmationNumber, + ctx.lang, + ctx.serviceToken, + lastName, + firstName, + email + ) + + if (!booking) { + metricsFindBooking.dataError( + `Fail to find booking data for ${confirmationNumber}`, + { confirmationNumber } + ) + return null + } + + const hotelData = await getHotel( + { + hotelId: booking.hotelId, + isCardOnlyPayment: false, + language: ctx.lang, + }, + ctx.serviceToken + ) + + if (!hotelData) { + metricsFindBooking.dataError( + `Failed to find hotel data for ${booking.hotelId}`, + { + hotelId: booking.hotelId, + } + ) + + throw serverErrorByStatus(404) + } + + metricsFindBooking.success() + return { ...hotelData, booking, diff --git a/apps/scandic-web/server/routers/booking/utils.ts b/apps/scandic-web/server/routers/booking/utils.ts index c983407b4..0941bc76b 100644 --- a/apps/scandic-web/server/routers/booking/utils.ts +++ b/apps/scandic-web/server/routers/booking/utils.ts @@ -78,6 +78,63 @@ export async function getBooking( return booking.data } +export async function findBooking( + confirmationNumber: string, + lang: Lang, + token: string, + lastName?: string, + firstName?: string, + email?: string +) { + const findBookingCounter = createCounter("booking", "find") + const metricsGetBooking = findBookingCounter.init({ + confirmationNumber, + lastName, + firstName, + email, + }) + + metricsGetBooking.start() + + const apiResponse = await api.post( + api.endpoints.v1.Booking.find(confirmationNumber), + { + headers: { + Authorization: `Bearer ${token}`, + }, + body: { + lastName, + firstName, + email, + }, + }, + { language: toApiLang(lang) } + ) + + if (!apiResponse.ok) { + await metricsGetBooking.httpError(apiResponse) + + // If the booking is not found, return null. + // This scenario is expected to happen when a logged in user trying to access a booking that doesn't belong to them. + if (apiResponse.status === 400) { + return null + } + + throw serverErrorByStatus(apiResponse.status, apiResponse) + } + + const apiJson = await apiResponse.json() + const booking = bookingConfirmationSchema.safeParse(apiJson) + if (!booking.success) { + metricsGetBooking.validationError(booking.error) + throw badRequestError() + } + + metricsGetBooking.success() + + return booking.data +} + export async function cancelBooking( confirmationNumber: string, language: Lang,