Merged in feat/SW-2308-family-friends-booking-rest (pull request #1916)

feat: SW-2308 FnF code restriction added

* feat: SW-2308 FnF code restriction added

* feat: 2308 Optimized code


Approved-by: Niclas Edenvin
This commit is contained in:
Hrishikesh Vaipurkar
2025-05-02 14:22:05 +00:00
parent fb44990777
commit 53b630b6d8
10 changed files with 157 additions and 7 deletions

View File

@@ -1,7 +1,9 @@
import { cookies } from "next/headers"
import { notFound, redirect } from "next/navigation" import { notFound, redirect } from "next/navigation"
import { Suspense } from "react" import { Suspense } from "react"
import { BookingErrorCodeEnum } from "@/constants/booking" import { BookingErrorCodeEnum } from "@/constants/booking"
import { FamilyAndFriendsCodes } from "@/constants/booking"
import { selectRate } from "@/constants/routes/hotelReservation" import { selectRate } from "@/constants/routes/hotelReservation"
import { import {
getBreakfastPackages, getBreakfastPackages,
@@ -17,6 +19,7 @@ import RoomOne from "@/components/HotelReservation/EnterDetails/Room/One"
import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop" import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop"
import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile" import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile"
import EnterDetailsTrackingWrapper from "@/components/HotelReservation/EnterDetails/Tracking" import EnterDetailsTrackingWrapper from "@/components/HotelReservation/EnterDetails/Tracking"
import FnFNotAllowedAlert from "@/components/HotelReservation/FnFNotAllowedAlert/FnFNotAllowedAlert"
import RoomProvider from "@/providers/Details/RoomProvider" import RoomProvider from "@/providers/Details/RoomProvider"
import EnterDetailsProvider from "@/providers/EnterDetailsProvider" import EnterDetailsProvider from "@/providers/EnterDetailsProvider"
import { convertSearchParamsToObj } from "@/utils/url" import { convertSearchParamsToObj } from "@/utils/url"
@@ -38,6 +41,18 @@ export default async function DetailsPage({
delete booking.modifyRateIndex delete booking.modifyRateIndex
} }
if (
booking.bookingCode &&
FamilyAndFriendsCodes.includes(booking.bookingCode)
) {
const cookieStore = cookies()
const isInValidFNF = cookieStore.get("sc")?.value !== "1"
if (isInValidFNF) {
return <FnFNotAllowedAlert />
}
}
const breakfastInput = { const breakfastInput = {
adults: 1, adults: 1,
fromDate: booking.fromDate, fromDate: booking.fromDate,

View File

@@ -0,0 +1,6 @@
.fnfMain {
max-width: var(--max-width-page);
margin: auto;
min-height: 30dvh;
padding: var(--Spacing-x5) 0;
}

View File

@@ -0,0 +1,22 @@
import Alert from "@/components/TempDesignSystem/Alert"
import { getIntl } from "@/i18n"
import styles from "./FnFNotAllowedAlert.module.css"
import { AlertTypeEnum } from "@/types/enums/alert"
export default async function FnFNotAllowedAlert() {
const intl = await getIntl()
return (
<div className={styles.fnfMain}>
<Alert
type={AlertTypeEnum.Warning}
text={intl.formatMessage({
defaultMessage:
"The Friends & Family rate can only be booked via FUSE.",
})}
/>
</div>
)
}

View File

@@ -1,9 +1,11 @@
import stringify from "json-stable-stringify-without-jsonify" import stringify from "json-stable-stringify-without-jsonify"
import { cookies } from "next/headers"
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { Suspense } from "react" import { Suspense } from "react"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { FamilyAndFriendsCodes } from "@/constants/booking"
import { import {
alternativeHotels, alternativeHotels,
alternativeHotelsMap, alternativeHotelsMap,
@@ -21,6 +23,7 @@ import { getIntl } from "@/i18n"
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails" import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
import { convertObjToSearchParams } from "@/utils/url" import { convertObjToSearchParams } from "@/utils/url"
import FnFNotAllowedAlert from "../FnFNotAllowedAlert/FnFNotAllowedAlert"
import HotelCardListing from "../HotelCardListing" import HotelCardListing from "../HotelCardListing"
import BookingCodeFilter from "./BookingCodeFilter" import BookingCodeFilter from "./BookingCodeFilter"
import { getFiltersFromHotels, getHotels } from "./helpers" import { getFiltersFromHotels, getHotels } from "./helpers"
@@ -67,6 +70,15 @@ export default async function SelectHotel({
if (!city) return notFound() if (!city) return notFound()
if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) {
const cookieStore = cookies()
const isInValidFNF = cookieStore.get("sc")?.value !== "1"
if (isInValidFNF) {
return <FnFNotAllowedAlert />
}
}
const hotels = await getHotels( const hotels = await getHotels(
selectHotelParams, selectHotelParams,
isAlternativeFor, isAlternativeFor,

View File

@@ -1,8 +1,9 @@
import stringify from "json-stable-stringify-without-jsonify" import stringify from "json-stable-stringify-without-jsonify"
import { cookies } from "next/headers"
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { Suspense } from "react" import { Suspense } from "react"
import { REDEMPTION } from "@/constants/booking" import { FamilyAndFriendsCodes, REDEMPTION } from "@/constants/booking"
import { getHotel } from "@/lib/trpc/memoizedRequests" import { getHotel } from "@/lib/trpc/memoizedRequests"
import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard"
@@ -13,6 +14,7 @@ import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
import { convertSearchParamsToObj } from "@/utils/url" import { convertSearchParamsToObj } from "@/utils/url"
import AvailabilityError from "./AvailabilityError" import AvailabilityError from "./AvailabilityError"
import FnFNotAllowedAlert from "../FnFNotAllowedAlert/FnFNotAllowedAlert"
import { getValidDates } from "./getValidDates" import { getValidDates } from "./getValidDates"
import { getTracking } from "./tracking" import { getTracking } from "./tracking"
@@ -73,17 +75,26 @@ export default async function SelectRatePage({
const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams) const booking = convertSearchParamsToObj<SelectRateSearchParams>(searchParams)
let isInValidFNF = false
if (bookingCode && FamilyAndFriendsCodes.includes(bookingCode)) {
const cookieStore = cookies()
isInValidFNF = cookieStore.get("sc")?.value !== "1"
}
const suspenseKey = stringify(searchParams) const suspenseKey = stringify(searchParams)
return ( return (
<> <>
<HotelInfoCard hotel={hotelData.hotel} /> <HotelInfoCard hotel={hotelData.hotel} />
<RoomsContainer {isInValidFNF ? (
booking={booking} <FnFNotAllowedAlert />
hotelType={hotelData.hotel.hotelType} ) : (
roomCategories={hotelData.roomCategories} <RoomsContainer
vat={hotelData.hotel.vat} booking={booking}
/> hotelType={hotelData.hotel.hotelType}
roomCategories={hotelData.roomCategories}
vat={hotelData.hotel.vat}
/>
)}
<Suspense key={`${suspenseKey}-tracking`} fallback={null}> <Suspense key={`${suspenseKey}-tracking`} fallback={null}>
<TrackingSDK <TrackingSDK

View File

@@ -34,6 +34,8 @@ export enum ChildBedTypeEnum {
Unknown = "Unknown", Unknown = "Unknown",
} }
export const FamilyAndFriendsCodes = ["D000029555", "D000029271", "D000029195"]
export const REDEMPTION = "redemption" export const REDEMPTION = "redemption"
export const SEARCHTYPE = "searchtype" export const SEARCHTYPE = "searchtype"

View File

@@ -10,6 +10,7 @@ import * as currentWebLogin from "@/middlewares/currentWebLogin"
import * as currentWebLoginEmail from "@/middlewares/currentWebLoginEmail" import * as currentWebLoginEmail from "@/middlewares/currentWebLoginEmail"
import * as currentWebLogout from "@/middlewares/currentWebLogout" import * as currentWebLogout from "@/middlewares/currentWebLogout"
import * as dateFormat from "@/middlewares/dateFormat" import * as dateFormat from "@/middlewares/dateFormat"
import * as familyAndFriends from "@/middlewares/familyAndFriends"
import * as handleAuth from "@/middlewares/handleAuth" import * as handleAuth from "@/middlewares/handleAuth"
import * as myPages from "@/middlewares/myPages" import * as myPages from "@/middlewares/myPages"
import * as redirect from "@/middlewares/redirect" import * as redirect from "@/middlewares/redirect"
@@ -57,6 +58,7 @@ export const middleware: NextMiddleware = async (request, event) => {
webView, webView,
dateFormat, dateFormat,
bookingFlow, bookingFlow,
familyAndFriends,
sasXScandic, sasXScandic,
cmsContent, cmsContent,
redirect, redirect,

View File

@@ -0,0 +1,46 @@
import { type NextMiddleware, NextResponse } from "next/server"
import { getPublicNextURL } from "@/server/utils"
import { findLang } from "@/utils/languages"
import { getDefaultRequestHeaders } from "./utils"
import type { MiddlewareMatcher } from "@/types/middleware"
export const middleware: NextMiddleware = async (request) => {
const lang = findLang(request.nextUrl.pathname)!
const nextUrlPublic = getPublicNextURL(request)
const redirectUrl = new URL(`/${lang}`, nextUrlPublic)
const key = request.nextUrl.searchParams.get("key")
const code = request.nextUrl.searchParams.get("code")
const md5 = require("md5")
if (
key &&
code &&
key === md5("scandic" + new Date().toISOString().substring(0, 10))
) {
const headers = new Headers()
// Set cookie for this device/user to be valid to use the FnF code for 3 days starting now
headers.append(
"set-cookie",
`sc=1; max-age=259200; Path=/; HttpOnly; SameSite=Lax`
)
const redirectOpts = {
headers,
}
redirectUrl.searchParams.set("bookingCode", code)
return NextResponse.redirect(redirectUrl, redirectOpts)
}
const headers = getDefaultRequestHeaders(request)
return NextResponse.redirect(redirectUrl, {
headers,
})
}
export const matcher: MiddlewareMatcher = (request) => {
return request.nextUrl.pathname === "/en/familyandfriends"
}

View File

@@ -91,6 +91,7 @@
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.10.60", "libphonenumber-js": "^1.10.60",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"md5": "^2.3.0",
"nanoid": "^5.0.9", "nanoid": "^5.0.9",
"next": "^14.2.25", "next": "^14.2.25",
"next-auth": "5.0.0-beta.19", "next-auth": "5.0.0-beta.19",

View File

@@ -6452,6 +6452,7 @@ __metadata:
lint-staged: "npm:^15.2.2" lint-staged: "npm:^15.2.2"
lodash-es: "npm:^4.17.21" lodash-es: "npm:^4.17.21"
material-symbols: "npm:^0.29.0" material-symbols: "npm:^0.29.0"
md5: "npm:^2.3.0"
nanoid: "npm:^5.0.9" nanoid: "npm:^5.0.9"
netlify-plugin-cypress: "npm:^2.2.1" netlify-plugin-cypress: "npm:^2.2.1"
next: "npm:^14.2.25" next: "npm:^14.2.25"
@@ -10282,6 +10283,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"charenc@npm:0.0.2":
version: 0.0.2
resolution: "charenc@npm:0.0.2"
checksum: 10c0/a45ec39363a16799d0f9365c8dd0c78e711415113c6f14787a22462ef451f5013efae8a28f1c058f81fc01f2a6a16955f7a5fd0cd56247ce94a45349c89877d8
languageName: node
linkType: hard
"check-error@npm:^2.1.1": "check-error@npm:^2.1.1":
version: 2.1.1 version: 2.1.1
resolution: "check-error@npm:2.1.1" resolution: "check-error@npm:2.1.1"
@@ -10938,6 +10946,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"crypt@npm:0.0.2":
version: 0.0.2
resolution: "crypt@npm:0.0.2"
checksum: 10c0/adbf263441dd801665d5425f044647533f39f4612544071b1471962209d235042fb703c27eea2795c7c53e1dfc242405173003f83cf4f4761a633d11f9653f18
languageName: node
linkType: hard
"css-select@npm:^5.1.0": "css-select@npm:^5.1.0":
version: 5.1.0 version: 5.1.0
resolution: "css-select@npm:5.1.0" resolution: "css-select@npm:5.1.0"
@@ -14151,6 +14166,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-buffer@npm:~1.1.6":
version: 1.1.6
resolution: "is-buffer@npm:1.1.6"
checksum: 10c0/ae18aa0b6e113d6c490ad1db5e8df9bdb57758382b313f5a22c9c61084875c6396d50bbf49315f5b1926d142d74dfb8d31b40d993a383e0a158b15fea7a82234
languageName: node
linkType: hard
"is-bun-module@npm:^1.0.2": "is-bun-module@npm:^1.0.2":
version: 1.3.0 version: 1.3.0
resolution: "is-bun-module@npm:1.3.0" resolution: "is-bun-module@npm:1.3.0"
@@ -16457,6 +16479,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"md5@npm:^2.3.0":
version: 2.3.0
resolution: "md5@npm:2.3.0"
dependencies:
charenc: "npm:0.0.2"
crypt: "npm:0.0.2"
is-buffer: "npm:~1.1.6"
checksum: 10c0/14a21d597d92e5b738255fbe7fe379905b8cb97e0a49d44a20b58526a646ec5518c337b817ce0094ca94d3e81a3313879c4c7b510d250c282d53afbbdede9110
languageName: node
linkType: hard
"mdn-data@npm:2.0.28": "mdn-data@npm:2.0.28":
version: 2.0.28 version: 2.0.28
resolution: "mdn-data@npm:2.0.28" resolution: "mdn-data@npm:2.0.28"