feat: SW-1422 Enabled booking code in booking widget
This commit is contained in:
@@ -23,6 +23,7 @@ import MobileToggleButton, {
|
||||
import styles from "./bookingWidget.module.css"
|
||||
|
||||
import type {
|
||||
BookingCodeSchema,
|
||||
BookingWidgetClientProps,
|
||||
BookingWidgetSchema,
|
||||
BookingWidgetSearchData,
|
||||
@@ -82,6 +83,10 @@ export default function BookingWidgetClient({
|
||||
)
|
||||
: undefined
|
||||
|
||||
const selectedBookingCode = bookingWidgetSearchData
|
||||
? bookingWidgetSearchData.bookingCode
|
||||
: undefined
|
||||
|
||||
const defaultRoomsData: BookingWidgetSchema["rooms"] =
|
||||
bookingWidgetSearchData?.rooms?.map((room) => ({
|
||||
adults: room.adults,
|
||||
@@ -107,7 +112,10 @@ export default function BookingWidgetClient({
|
||||
? parsedToDate.format("YYYY-MM-DD")
|
||||
: now.utc().add(1, "day").format("YYYY-MM-DD"),
|
||||
},
|
||||
bookingCode: "",
|
||||
bookingCode: {
|
||||
value: selectedBookingCode ?? "",
|
||||
remember: false,
|
||||
},
|
||||
redemption: false,
|
||||
voucher: false,
|
||||
rooms: defaultRoomsData,
|
||||
@@ -164,7 +172,20 @@ export default function BookingWidgetClient({
|
||||
!selectedLocation &&
|
||||
sessionStorageSearchData &&
|
||||
methods.setValue("location", encodeURIComponent(sessionStorageSearchData))
|
||||
}, [methods, selectedLocation])
|
||||
|
||||
const storedBookingCode =
|
||||
typeof window !== "undefined"
|
||||
? localStorage.getItem("bookingCode")
|
||||
: undefined
|
||||
const initialBookingCode: BookingCodeSchema | undefined =
|
||||
storedBookingCode && isValidJson(storedBookingCode)
|
||||
? JSON.parse(storedBookingCode)
|
||||
: undefined
|
||||
!selectedBookingCode &&
|
||||
initialBookingCode &&
|
||||
initialBookingCode.remember &&
|
||||
methods.setValue("bookingCode", initialBookingCode)
|
||||
}, [methods, selectedLocation, selectedBookingCode])
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
.bookingCodeLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.error {
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.errorIcon {
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
.bookingCodeRemember {
|
||||
display: flex;
|
||||
gap: var(--Spacing-x1);
|
||||
|
||||
/* ToDo: Remove once remember checkbox design are ready */
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { useFormContext } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { ErrorCircleIcon, InfoCircleIcon } from "@/components/Icons"
|
||||
import Modal from "@/components/Modal"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import Input from "../Input"
|
||||
|
||||
import styles from "./booking-code.module.css"
|
||||
|
||||
import type {
|
||||
BookingCodeSchema,
|
||||
BookingWidgetSchema,
|
||||
} from "@/types/components/bookingWidget"
|
||||
|
||||
export default function BookingCode() {
|
||||
const intl = useIntl()
|
||||
const {
|
||||
setValue,
|
||||
formState: { errors },
|
||||
getValues,
|
||||
} = useFormContext<BookingWidgetSchema>()
|
||||
const codeError = errors["bookingCode"]?.value
|
||||
|
||||
const codeVoucher = intl.formatMessage({ id: "Code / Voucher" })
|
||||
const addCode = intl.formatMessage({ id: "Add code" })
|
||||
const bookingCodeTooltipText = intl.formatMessage({
|
||||
id: "booking.codes.information",
|
||||
})
|
||||
const bookingCode: BookingCodeSchema = getValues("bookingCode")
|
||||
|
||||
function updateBookingCodeFormValue(value: string) {
|
||||
setValue("bookingCode.value", value, { shouldValidate: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<label htmlFor="booking-code">
|
||||
<div className={styles.bookingCodeLabel}>
|
||||
<Caption color="uiTextMediumContrast" type="bold" asChild>
|
||||
<span>{codeVoucher}</span>
|
||||
</Caption>
|
||||
<Modal
|
||||
trigger={
|
||||
<Button intent="text">
|
||||
<InfoCircleIcon
|
||||
color="uiTextPlaceholder"
|
||||
height={20}
|
||||
width={20}
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
title={codeVoucher}
|
||||
>
|
||||
<Body color="uiTextHighContrast">{bookingCodeTooltipText}</Body>
|
||||
</Modal>
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={addCode}
|
||||
name="bookingCode"
|
||||
id="booking-code"
|
||||
onChange={(event) => updateBookingCodeFormValue(event.target.value)}
|
||||
defaultValue={bookingCode?.value}
|
||||
/>
|
||||
</label>
|
||||
{codeError && codeError.message ? (
|
||||
<Caption color="red" className={styles.error}>
|
||||
<ErrorCircleIcon color="red" className={styles.errorIcon} />
|
||||
{intl.formatMessage({ id: codeError.message })}
|
||||
</Caption>
|
||||
) : null}
|
||||
{/* ToDo: Update styles once designs are ready */}
|
||||
<div className={styles.bookingCodeRemember}>
|
||||
<Checkbox name="bookingCode.remember">
|
||||
<Caption asChild>
|
||||
<span>{intl.formatMessage({ id: "Remember code" })}</span>
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
<Button size="small" intent="primary" type="button">
|
||||
{intl.formatMessage({ id: "Apply" })}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import { Tooltip } from "@/components/TempDesignSystem/Tooltip"
|
||||
|
||||
import BookingCode from "../BookingCode"
|
||||
import Input from "../Input"
|
||||
|
||||
import styles from "./voucher.module.css"
|
||||
@@ -13,11 +14,11 @@ import styles from "./voucher.module.css"
|
||||
export default function Voucher() {
|
||||
const intl = useIntl()
|
||||
|
||||
const vouchers = intl.formatMessage({ id: "Code / Voucher" })
|
||||
const useVouchers = intl.formatMessage({ id: "Use code/voucher" })
|
||||
const addVouchers = intl.formatMessage({ id: "Add code" })
|
||||
const bonus = intl.formatMessage({ id: "Use bonus cheque" })
|
||||
const reward = intl.formatMessage({ id: "Book reward night" })
|
||||
|
||||
// ToDo: Remove this when all three options are enabled
|
||||
const disabledBookingOptionsHeader = intl.formatMessage({
|
||||
id: "We're sorry",
|
||||
})
|
||||
@@ -27,55 +28,49 @@ export default function Voucher() {
|
||||
|
||||
return (
|
||||
<div className={styles.optionsContainer}>
|
||||
<Tooltip
|
||||
heading={disabledBookingOptionsHeader}
|
||||
text={disabledBookingOptionsText}
|
||||
position="bottom"
|
||||
arrow="left"
|
||||
>
|
||||
<div className={styles.vouchers}>
|
||||
<label>
|
||||
<Caption color="disabled" type="bold" asChild>
|
||||
<span>{vouchers}</span>
|
||||
<div className={styles.vouchers}>
|
||||
<BookingCode />
|
||||
</div>
|
||||
<div className={styles.options}>
|
||||
<div className={`${styles.option} ${styles.checkboxVoucher}`}>
|
||||
<Checkbox name="useVouchers" registerOptions={{ disabled: true }}>
|
||||
<Caption color="disabled" asChild>
|
||||
<span>{useVouchers}</span>
|
||||
</Caption>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</label>
|
||||
<Input type="text" placeholder={addVouchers} disabled />
|
||||
</Checkbox>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
heading={disabledBookingOptionsHeader}
|
||||
text={disabledBookingOptionsText}
|
||||
position="bottom"
|
||||
arrow="left"
|
||||
>
|
||||
<div className={styles.options}>
|
||||
<div className={`${styles.option} ${styles.checkboxVoucher}`}>
|
||||
<Checkbox name="useVouchers" registerOptions={{ disabled: true }}>
|
||||
<Caption color="disabled" asChild>
|
||||
<span>{useVouchers}</span>
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</div>
|
||||
<div className={styles.option}>
|
||||
<div className={styles.option}>
|
||||
<Tooltip
|
||||
heading={disabledBookingOptionsHeader}
|
||||
text={disabledBookingOptionsText}
|
||||
position="bottom"
|
||||
arrow="left"
|
||||
>
|
||||
<Checkbox name="useBonus" registerOptions={{ disabled: true }}>
|
||||
<Caption color="disabled" asChild>
|
||||
<span>{bonus}</span>
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</div>
|
||||
<div className={styles.option}>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={styles.option}>
|
||||
<Tooltip
|
||||
heading={disabledBookingOptionsHeader}
|
||||
text={disabledBookingOptionsText}
|
||||
position="bottom"
|
||||
arrow="left"
|
||||
>
|
||||
<Checkbox name="useReward" registerOptions={{ disabled: true }}>
|
||||
<Caption color="disabled" asChild>
|
||||
<span>{reward}</span>
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,10 +32,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.infoIcon {
|
||||
stroke: var(--Base-Text-Disabled);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.vouchers {
|
||||
display: none;
|
||||
|
||||
@@ -86,6 +86,7 @@ export default function FormContent({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* ToDo: Remove below additional voucher element which is only used in tablet mode better to have submit button twice instead */}
|
||||
<div className={styles.voucherRow}>
|
||||
<Voucher />
|
||||
</div>
|
||||
|
||||
@@ -44,10 +44,16 @@ export default function Form({
|
||||
...(locationData.type == "cities"
|
||||
? { city: locationData.name }
|
||||
: { hotel: locationData.operaId || "" }),
|
||||
...(data.bookingCode && data.bookingCode.value
|
||||
? { bookingCode: data.bookingCode.value }
|
||||
: {}),
|
||||
})
|
||||
|
||||
onClose()
|
||||
router.push(`${bookingFlowPage}?${bookingWidgetParams.toString()}`)
|
||||
if (data.bookingCode?.remember) {
|
||||
localStorage.setItem("bookingCode", JSON.stringify(data.bookingCode))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,9 +35,30 @@ export const guestRoomSchema = z
|
||||
|
||||
export const guestRoomsSchema = z.array(guestRoomSchema)
|
||||
|
||||
export const bookingCodeSchema = z
|
||||
.object({
|
||||
value: z.string().refine(
|
||||
(value) => {
|
||||
if (
|
||||
!value ||
|
||||
/(^D\d*$)|(^DSH[0-9a-z]*$)|(^L\d*$)|(^LH[0-9a-z]*$)|(^B[a-z]{3}\d{6})|(^VO[0-9a-z]*$)|^[0-9a-z]*$/i.test(
|
||||
value
|
||||
)
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
{ message: "Invalid booking code" }
|
||||
),
|
||||
remember: z.boolean(),
|
||||
})
|
||||
.optional()
|
||||
|
||||
export const bookingWidgetSchema = z
|
||||
.object({
|
||||
bookingCode: z.string(), // Update this as required when working with booking codes component
|
||||
bookingCode: bookingCodeSchema,
|
||||
date: z.object({
|
||||
// Update this as required once started working with Date picker in Nights component
|
||||
fromDate: z.string(),
|
||||
@@ -70,7 +91,27 @@ export const bookingWidgetSchema = z
|
||||
hotel: z.number().optional(),
|
||||
city: z.string().optional(),
|
||||
})
|
||||
.refine((value) => value.hotel || value.city, {
|
||||
message: "Destination required",
|
||||
path: ["search"],
|
||||
.superRefine((value, ctx) => {
|
||||
if (!value.hotel && !value.city) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Destination required",
|
||||
path: ["search"],
|
||||
})
|
||||
}
|
||||
if (
|
||||
value.rooms.length > 1 &&
|
||||
value.bookingCode?.value.toLowerCase().startsWith("vo")
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Multiroom with voucher error",
|
||||
path: ["bookingCode.value"],
|
||||
})
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Multiroom with voucher error",
|
||||
path: ["rooms"],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -76,5 +76,6 @@
|
||||
bottom: auto;
|
||||
width: auto;
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
max-width: var(--max-width-page);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user