feat: SW-1583 Used constants for strings

This commit is contained in:
Hrishikesh Vaipurkar
2025-03-06 16:23:16 +01:00
parent 23eaa772ea
commit 24bf96df41
12 changed files with 55 additions and 35 deletions

View File

@@ -1,5 +1,6 @@
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { REDEMPTION } from "@/constants/booking"
import { getLocations } from "@/lib/trpc/memoizedRequests" import { getLocations } from "@/lib/trpc/memoizedRequests"
import { generateChildrenString } from "@/components/HotelReservation/utils" import { generateChildrenString } from "@/components/HotelReservation/utils"
@@ -106,6 +107,6 @@ export async function getHotelSearchDetails<
childrenInRoomString, childrenInRoomString,
childrenInRoom, childrenInRoom,
bookingCode: selectHotelParams.bookingCode ?? undefined, bookingCode: selectHotelParams.bookingCode ?? undefined,
redemption: selectHotelParams.searchType === "redemption", redemption: selectHotelParams.searchType === REDEMPTION,
} }
} }

View File

@@ -4,6 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
import { FormProvider, useForm } from "react-hook-form" import { FormProvider, useForm } from "react-hook-form"
import { REDEMPTION } from "@/constants/booking"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import { trpc } from "@/lib/trpc/client" import { trpc } from "@/lib/trpc/client"
import { StickyElementNameEnum } from "@/stores/sticky-position" import { StickyElementNameEnum } from "@/stores/sticky-position"
@@ -102,7 +103,7 @@ export default function BookingWidgetClient({
value: selectedBookingCode, value: selectedBookingCode,
remember: false, remember: false,
}, },
redemption: params?.searchType === "redemption", redemption: params?.searchType === REDEMPTION,
rooms: defaultRoomsData, rooms: defaultRoomsData,
}, },
shouldFocusError: false, shouldFocusError: false,

View File

@@ -4,6 +4,8 @@ import { type FieldError, useFormContext } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts" import { useMediaQuery } from "usehooks-ts"
import { REDEMPTION } from "@/constants/booking"
import { ErrorCircleIcon, InfoCircleIcon } from "@/components/Icons" import { ErrorCircleIcon, InfoCircleIcon } from "@/components/Icons"
import Modal from "@/components/Modal" import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
@@ -52,9 +54,14 @@ export default function BookingCode() {
}) })
function updateBookingCodeFormValue(value: string) { function updateBookingCodeFormValue(value: string) {
// Set value and show error if validation fails
setValue("bookingCode.value", value, { shouldValidate: true }) setValue("bookingCode.value", value, { shouldValidate: true })
if (getValues("redemption")) {
setValue("redemption", false) if (getValues(REDEMPTION)) {
// Remove the redemption as user types booking code and show notification for the same
setValue(REDEMPTION, false)
// Hide the above notification popup after 5 seconds by re-triggering validation
// This is kept consistent with location search field error notification timeout
setTimeout(function () { setTimeout(function () {
trigger("bookingCode.value") trigger("bookingCode.value")
}, 5000) }, 5000)

View File

@@ -4,6 +4,8 @@ import { useCallback, useEffect, useRef } from "react"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { REDEMPTION } from "@/constants/booking"
import { ErrorCircleIcon } from "@/components/Icons" import { ErrorCircleIcon } from "@/components/Icons"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
@@ -13,7 +15,7 @@ import styles from "./reward-night.module.css"
import type { BookingWidgetSchema } from "@/types/components/bookingWidget" import type { BookingWidgetSchema } from "@/types/components/bookingWidget"
export function RewardNight() { export default function RewardNight() {
const intl = useIntl() const intl = useIntl()
const { const {
setValue, setValue,
@@ -23,24 +25,27 @@ export function RewardNight() {
} = useFormContext<BookingWidgetSchema>() } = useFormContext<BookingWidgetSchema>()
const ref = useRef<HTMLDivElement | null>(null) const ref = useRef<HTMLDivElement | null>(null)
const reward = intl.formatMessage({ id: "Book Reward Night" }) const reward = intl.formatMessage({ id: "Book Reward Night" })
const redemptionErr = errors["redemption"] const redemptionErr = errors[REDEMPTION]
const bookingCode = getValues("bookingCode.value") const bookingCode = getValues("bookingCode.value")
const isMultiRoomError = redemptionErr?.message?.indexOf("Multi-room") === 0 const isMultiRoomError = redemptionErr?.message?.indexOf("Multi-room") === 0
const errorInfoColor = isMultiRoomError ? "red" : "blue" const errorInfoColor = isMultiRoomError ? "red" : "blue"
function validateBookingWidget(value: boolean) { function validateRedemption(value: boolean) {
trigger("redemption") // Validate redemption as per the rules defined in the schema
trigger(REDEMPTION)
if (value && bookingCode) { if (value && bookingCode) {
setValue("bookingCode.value", "", { shouldValidate: true }) setValue("bookingCode.value", "", { shouldValidate: true })
// Hide the notification popup after 5 seconds by re-triggering validation
// This is kept consistent with location search field error notification timeout
setTimeout(() => { setTimeout(() => {
trigger("redemption") trigger(REDEMPTION)
}, 5000) }, 5000)
} }
} }
const resetOnMultiroomError = useCallback(() => { const resetOnMultiroomError = useCallback(() => {
if (isMultiRoomError) { if (isMultiRoomError) {
setValue("redemption", false, { shouldValidate: true }) setValue(REDEMPTION, false, { shouldValidate: true })
} }
}, [isMultiRoomError, setValue]) }, [isMultiRoomError, setValue])
@@ -68,10 +73,10 @@ export function RewardNight() {
<div ref={ref} onBlur={(e) => closeOnBlur(e.nativeEvent)}> <div ref={ref} onBlur={(e) => closeOnBlur(e.nativeEvent)}>
<Checkbox <Checkbox
hideError hideError
name="redemption" name={REDEMPTION}
registerOptions={{ registerOptions={{
onChange: (e) => { onChange: (e) => {
validateBookingWidget(e.target.value) validateRedemption(e.target.value)
}, },
}} }}
> >

View File

@@ -10,7 +10,7 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
import { Tooltip } from "@/components/TempDesignSystem/Tooltip" import { Tooltip } from "@/components/TempDesignSystem/Tooltip"
import BookingCode from "../BookingCode" import BookingCode from "../BookingCode"
import { RewardNight } from "../RewardNight" import RewardNight from "../RewardNight"
import styles from "./voucher.module.css" import styles from "./voucher.module.css"

View File

@@ -4,6 +4,7 @@ import { useTransition } from "react"
import { Form as FormRAC } from "react-aria-components" import { Form as FormRAC } from "react-aria-components"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { REDEMPTION } from "@/constants/booking"
import { selectHotel, selectRate } from "@/constants/routes/hotelReservation" import { selectHotel, selectRate } from "@/constants/routes/hotelReservation"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
@@ -49,8 +50,8 @@ export default function Form({
...(data.bookingCode?.value ...(data.bookingCode?.value
? { bookingCode: data.bookingCode.value } ? { bookingCode: data.bookingCode.value }
: {}), : {}),
// Followed current url structure to keep searchType=redemption param incase of reward night // Followed current url structure to keep searchtype=redemption param incase of reward night
...(data.redemption ? { searchType: "redemption" } : {}), ...(data.redemption ? { searchType: REDEMPTION } : {}),
}) })
onClose() onClose()

View File

@@ -1,5 +1,7 @@
import { z } from "zod" import { z } from "zod"
import { REDEMPTION } from "@/constants/booking"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums" import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import type { Location } from "@/types/trpc/routers/hotel/locations" import type { Location } from "@/types/trpc/routers/hotel/locations"
@@ -133,7 +135,7 @@ export const bookingWidgetSchema = z
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "Multi-room booking is not available with reward night.", message: "Multi-room booking is not available with reward night.",
path: ["redemption"], path: [REDEMPTION],
}) })
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
@@ -145,7 +147,7 @@ export const bookingWidgetSchema = z
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "Code and voucher is not available with reward night.", message: "Code and voucher is not available with reward night.",
path: ["redemption"], path: [REDEMPTION],
}) })
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,

View File

@@ -4,14 +4,11 @@ import { useCallback, useEffect } from "react"
import { useFormContext, useWatch } from "react-hook-form" import { useFormContext, useWatch } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { import { REDEMPTION } from "@/constants/booking"
CloseLargeIcon,
ErrorCircleIcon, import { CloseLargeIcon, PlusCircleIcon, PlusIcon } from "../Icons"
PlusCircleIcon,
PlusIcon,
} from "../Icons"
import Button from "../TempDesignSystem/Button" import Button from "../TempDesignSystem/Button"
import Caption from "../TempDesignSystem/Text/Caption"
import { Tooltip } from "../TempDesignSystem/Tooltip" import { Tooltip } from "../TempDesignSystem/Tooltip"
import { GuestsRoom } from "./GuestsRoom" import { GuestsRoom } from "./GuestsRoom"
@@ -38,7 +35,7 @@ export default function GuestsRoomsPickerDialog({
const addRoomLabel = intl.formatMessage({ id: "Add room" }) const addRoomLabel = intl.formatMessage({ id: "Add room" })
const doneLabel = intl.formatMessage({ id: "Done" }) const doneLabel = intl.formatMessage({ id: "Done" })
// Disable add room if booking code is either voucher or corporate cheque, or reward night is enabled // Disable add room if booking code is either voucher or corporate cheque, or reward night is enabled
const addRoomDisabledTextForSpecialRate = getValues("redemption") const addRoomDisabledTextForSpecialRate = getValues(REDEMPTION)
? intl.formatMessage({ ? intl.formatMessage({
id: "Multi-room booking is not available with reward night.", id: "Multi-room booking is not available with reward night.",
}) })

View File

@@ -13,7 +13,7 @@ export default function HotelPointsCard({
}: PointsCardProps) { }: PointsCardProps) {
const intl = useIntl() const intl = useIntl()
const pointsPerStay = const pointsPerStay =
productTypePoints?.localPrice.pricePerStay ?? redemptionPrice productTypePoints?.localPrice.pointsPerStay ?? redemptionPrice
return ( return (
<div className={styles.poinstRow}> <div className={styles.poinstRow}>

View File

@@ -190,10 +190,13 @@ function HotelCard({
<Caption> <Caption>
{intl.formatMessage({ id: "Available rates" })} {intl.formatMessage({ id: "Available rates" })}
</Caption> </Caption>
{/* Display rate with full points option */}
<HotelPointsCard productTypePoints={price.redemption} /> <HotelPointsCard productTypePoints={price.redemption} />
{/* Display rate with partial points option A */}
{price.redemptionA && ( {price.redemptionA && (
<HotelPointsCard productTypePoints={price.redemptionA} /> <HotelPointsCard productTypePoints={price.redemptionA} />
)} )}
{/* Display rate with partial points option B */}
{price.redemptionB && ( {price.redemptionB && (
<HotelPointsCard productTypePoints={price.redemptionB} /> <HotelPointsCard productTypePoints={price.redemptionB} />
)} )}

View File

@@ -35,6 +35,9 @@ export enum ChildBedTypeEnum {
Unknown = "Unknown", Unknown = "Unknown",
} }
export const REDEMPTION = "redemption"
export const SEARCHTYPE = "searchtype"
export const BOOKING_CONFIRMATION_NUMBER = "confirmationNumber" export const BOOKING_CONFIRMATION_NUMBER = "confirmationNumber"
export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError" export const MEMBERSHIP_FAILED_ERROR = "MembershipFailedError"

View File

@@ -1,20 +1,20 @@
import { NextResponse } from "next/server" import { type NextMiddleware,NextResponse } from "next/server"
import { REDEMPTION, SEARCHTYPE } from "@/constants/booking"
import { login } from "@/constants/routes/handleAuth"
import { getPublicNextURL } from "@/server/utils"
import { auth } from "@/auth"
import { findLang } from "@/utils/languages"
import { getDefaultRequestHeaders } from "./utils" import { getDefaultRequestHeaders } from "./utils"
import type { NextMiddleware } from "next/server"
import type { MiddlewareMatcher } from "@/types/middleware" import type { MiddlewareMatcher } from "@/types/middleware"
import { auth } from "@/auth"
import { getPublicNextURL } from "@/server/utils"
import { login } from "@/constants/routes/handleAuth"
import { findLang } from "@/utils/languages"
export const middleware: NextMiddleware = async (request) => { export const middleware: NextMiddleware = async (request) => {
// Redirect user to login if reward nights search and not logged in // Redirect user to login if reward nights search and not logged in
const isRedemption = const isRedemption =
request.nextUrl.searchParams.get("searchtype") === "redemption" || request.nextUrl.searchParams.get(SEARCHTYPE) === REDEMPTION
request.nextUrl.searchParams.get("searchType") === "redemption"
const session = await auth() // Check for user session const session = await auth() // Check for user session
if (isRedemption && (!session || session?.error)) { if (isRedemption && (!session || session?.error)) {
const lang = findLang(request.nextUrl.pathname)! const lang = findLang(request.nextUrl.pathname)!