Files
web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx
Hrishikesh Vaipurkar eabe45b73c Merged in feat/SW-1557-implement-booking-code-select (pull request #1304)
feat: SW-1577 Implemented booking code city search

* feat: SW-1577 Implemented booking code city search

* feat: SW-1557 Strict comparison

* feat: SW-1557 Review comments fix


Approved-by: Michael Zetterberg
Approved-by: Pontus Dreij
2025-02-13 09:24:47 +00:00

263 lines
8.0 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react"
import { Dialog, DialogTrigger, Popover } from "react-aria-components"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
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 Switch from "@/components/TempDesignSystem/Form/Switch"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Input from "../Input"
import TabletCodeInput from "./TabletCodeInput"
import styles from "./booking-code.module.css"
import type {
BookingCodeSchema,
BookingWidgetSchema,
} from "@/types/components/bookingWidget"
export default function BookingCode() {
const intl = useIntl()
const checkIsTablet = useMediaQuery(
"(min-width: 768px) and (max-width: 1367px)"
)
const checkIsDesktop = useMediaQuery("(min-width: 1367px)")
const [isTablet, setIsTablet] = useState(false)
const [isDesktop, setIsDesktop] = useState(false)
const {
setValue,
formState: { errors },
getValues,
register,
} = useFormContext<BookingWidgetSchema>()
const bookingCode: BookingCodeSchema = getValues("bookingCode")
const [isOpen, setIsOpen] = useState(!!bookingCode?.value)
const [showRemember, setShowRemember] = useState(false)
const [showRememberMobile, setShowRememberMobile] = useState(false)
const codeError = errors["bookingCode"]?.value
const codeVoucher = intl.formatMessage({ id: "Code / Voucher" })
const addCode = intl.formatMessage({ id: "Add code" })
const ref = useRef<HTMLDivElement | null>(null)
function updateBookingCodeFormValue(value: string) {
setValue("bookingCode.value", value, { shouldValidate: true })
}
function toggleModal(isOpen: boolean) {
if (!isOpen && !bookingCode?.value) {
setValue("bookingCode.flag", false)
setIsOpen(isOpen)
} else if (!codeError || isOpen) {
setIsOpen(isOpen)
if (isOpen || bookingCode?.value) {
setValue("bookingCode.flag", true)
}
}
}
const closeIfOutside = useCallback(
(target: HTMLElement) => {
if (ref.current && target && !ref.current.contains(target)) {
setShowRemember(false)
}
},
[setShowRemember, ref]
)
function showRememberCheck() {
setShowRemember(true)
}
useEffect(() => {
setIsTablet(checkIsTablet)
}, [checkIsTablet])
useEffect(() => {
setIsDesktop(checkIsDesktop)
}, [checkIsDesktop])
const isRememberMobileVisible =
!isDesktop && (showRemember || !!bookingCode?.remember)
useEffect(() => {
setShowRememberMobile(isRememberMobileVisible)
}, [isRememberMobileVisible])
useEffect(() => {
function handleClickOutside(evt: Event) {
if (showRemember) {
const target = evt.target as HTMLElement
closeIfOutside(target)
}
}
document.body.addEventListener("click", handleClickOutside)
return () => {
document.body.removeEventListener("click", handleClickOutside)
}
}, [closeIfOutside, showRemember])
return isTablet ? (
<div className={styles.container}>
<DialogTrigger isOpen={isOpen} onOpenChange={toggleModal}>
<Button type="button" intent="text">
<Checkbox
checked={!!bookingCode?.value}
{...register("bookingCode.flag", {
onChange: function (e) {
if (bookingCode?.value || isOpen) {
setValue("bookingCode.flag", true)
}
},
})}
>
<Caption color="red" type="bold" asChild>
<span>{codeVoucher}</span>
</Caption>
</Checkbox>
</Button>
<Popover
className={styles.codePopover}
placement="bottom start"
offset={36}
>
<Dialog>
{({ close }) => (
<div className={styles.popover}>
<TabletCodeInput
updateValue={updateBookingCodeFormValue}
defaultValue={bookingCode?.value}
/>
<div className={styles.bookingCodeRememberVisible}>
<CodeRemember
bookingCodeValue={bookingCode?.value}
onApplyClick={close}
/>
</div>
</div>
)}
</Dialog>
</Popover>
</DialogTrigger>
<CodeRulesModal />
</div>
) : (
<div
className={styles.container}
ref={ref}
onFocus={showRememberCheck}
onBlur={(e) => closeIfOutside(e.nativeEvent.relatedTarget as HTMLElement)}
>
<div className={styles.bookingCodeLabel}>
<Caption
color={showRemember ? "uiTextActive" : "red"}
type="bold"
asChild
>
<span>{codeVoucher}</span>
</Caption>
<CodeRulesModal />
</div>
<Input
className="input"
type="search"
placeholder={addCode}
name="bookingCode.value"
id="booking-code"
onChange={(event) => updateBookingCodeFormValue(event.target.value)}
defaultValue={bookingCode?.value}
autoComplete="off"
/>
{codeError?.message ? (
<Caption color="red" className={styles.error}>
<ErrorCircleIcon color="red" className={styles.errorIcon} />
{intl.formatMessage({ id: codeError.message })}
</Caption>
) : null}
{isDesktop ? (
<div
className={
showRemember
? styles.bookingCodeRememberVisible
: styles.bookingCodeRemember
}
>
<CodeRemember
bookingCodeValue={bookingCode?.value}
onApplyClick={() => setShowRemember(false)}
/>
</div>
) : (
<div
className={
showRememberMobile
? styles.bookingCodeRememberVisible
: styles.bookingCodeRemember
}
>
<Switch name="bookingCode.remember" className="mobile-switch">
<Caption asChild>
<span>{intl.formatMessage({ id: "Remember code" })}</span>
</Caption>
</Switch>
</div>
)}
</div>
)
}
type CodeRememberProps = {
bookingCodeValue: string | undefined
onApplyClick: () => void
}
function CodeRulesModal() {
const intl = useIntl()
const codeVoucher = intl.formatMessage({ id: "Code / Voucher" })
const bookingCodeTooltipText = intl.formatMessage({
id: "If you're booking a promotional offer or a Corporate negotiated rate you'll need a special booking code. Don't use any special characters such as (.) (,) (-) (:). If you would like to make a booking with code VOF, please call us +46 8 517 517 20.Save your booking code for the next time you visit the page by ticking the box “Remember”. Don't tick the box if you're using a public computer to avoid unauthorized access to your booking code.",
})
return (
<Modal
trigger={
<Button intent="text">
<InfoCircleIcon color="uiTextPlaceholder" height={20} width={20} />
</Button>
}
title={codeVoucher}
>
<Body color="uiTextHighContrast">{bookingCodeTooltipText}</Body>
</Modal>
)
}
function CodeRemember({ bookingCodeValue, onApplyClick }: CodeRememberProps) {
const intl = useIntl()
return (
<>
<Checkbox name="bookingCode.remember">
<Caption asChild>
<span>{intl.formatMessage({ id: "Remember code" })}</span>
</Caption>
</Checkbox>
{bookingCodeValue ? (
<Button
size="small"
className={styles.hideOnMobile}
intent="secondary"
type="button"
onClick={onApplyClick}
>
{intl.formatMessage({ id: "Apply" })}
</Button>
) : null}
</>
)
}