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
263 lines
8.0 KiB
TypeScript
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}
|
|
</>
|
|
)
|
|
}
|