feat: SW-1583 Used constants for strings
This commit is contained in:
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)!
|
||||||
|
|||||||
Reference in New Issue
Block a user