From 2cd1b6c72cb5a42dffb46a5768026268691c99c2 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Mon, 24 Feb 2025 17:06:20 +0100 Subject: [PATCH] feat: SW-963 Implemented error states and handling booking code and multiroom --- .../BookingCode/booking-code.module.css | 47 ++- .../FormContent/BookingCode/index.tsx | 287 ++++++++++++------ .../FormContent/Voucher/voucher.module.css | 28 -- .../FormContent/formContent.module.css | 17 +- .../Forms/BookingWidget/FormContent/index.tsx | 15 +- .../components/Forms/BookingWidget/schema.ts | 16 +- .../components/GuestsRoomsPicker/Form.tsx | 74 ++++- .../guests-rooms-picker.module.css | 15 +- .../TempDesignSystem/Form/Checkbox/index.tsx | 5 +- .../Form/Input/input.module.css | 4 + apps/scandic-web/i18n/dictionaries/da.json | 4 +- apps/scandic-web/i18n/dictionaries/de.json | 4 +- apps/scandic-web/i18n/dictionaries/en.json | 4 +- apps/scandic-web/i18n/dictionaries/fi.json | 4 +- apps/scandic-web/i18n/dictionaries/no.json | 4 +- apps/scandic-web/i18n/dictionaries/sv.json | 4 +- 16 files changed, 374 insertions(+), 158 deletions(-) diff --git a/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/booking-code.module.css b/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/booking-code.module.css index 91a44b1ca..191e74275 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/booking-code.module.css +++ b/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/booking-code.module.css @@ -1,5 +1,14 @@ .container { position: relative; + display: grid; + gap: var(--Spacing-x1); +} + +.bookingCode { + height: 60px; + background-color: var(--Base-Background-Primary-Normal); + border-radius: var(--Corner-radius-Medium); + padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); } .bookingCodeLabel { @@ -9,6 +18,11 @@ position: relative; } +.errorContainer { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); +} .error { display: flex; gap: var(--Spacing-x-half); @@ -26,8 +40,6 @@ .bookingCodeRememberVisible { display: flex; - position: absolute; - top: calc(100% + 16px); width: 100%; } @@ -36,6 +48,10 @@ margin-top: var(--Spacing-x2); } +.bookingCodeRememberVisible label { + align-items: center; +} + @media screen and (max-width: 767px) { .hideOnMobile { display: none; @@ -43,6 +59,10 @@ } @media screen and (min-width: 768px) { + .bookingCode { + height: auto; + background-color: transparent; + } .bookingCodeRememberVisible { align-items: center; background: var(--Base-Surface-Primary-light-Normal); @@ -54,7 +74,6 @@ @media screen and (min-width: 768px) and (max-width: 1367px) { .container { display: flex; - gap: var(--Spacing-x1); } .codePopover { background: var(--Base-Surface-Primary-light-Normal); @@ -67,16 +86,30 @@ display: grid; gap: var(--Spacing-x2); } - .bookingCodeRememberVisible { - position: static; + .overlayTrigger { + position: absolute; + top: 0; + bottom: 0; + display: block; + left: 0; + right: 24px; } } @media screen and (min-width: 1367px) { + .container:hover, + .container:focus-within, + .container:has([data-focused="true"], [data-pressed="true"]) { + background-color: var(--Base-Surface-Primary-light-Hover-alt); + border-radius: var(--Corner-radius-Medium); + } + .bookingCodeRememberVisible { padding: var(--Spacing-x2); - width: 320px; + position: absolute; top: calc(100% + 24px); - left: calc(0% - var(--Spacing-x-one-and-half)); + left: calc(0% - var(--Spacing-x-half)); + width: 360px; + box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); } } diff --git a/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx b/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx index 5fde16d5e..cd42c83cb 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx +++ b/apps/scandic-web/components/Forms/BookingWidget/FormContent/BookingCode/index.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useRef, useState } from "react" import { Dialog, DialogTrigger, Popover } from "react-aria-components" -import { useFormContext } from "react-hook-form" +import { type FieldError,useFormContext } from "react-hook-form" import { useIntl } from "react-intl" import { useMediaQuery } from "usehooks-ts" @@ -21,6 +21,7 @@ import type { BookingCodeSchema, BookingWidgetSchema, } from "@/types/components/bookingWidget" +import type { ButtonProps } from "@/components/TempDesignSystem/Button/button" export default function BookingCode() { const intl = useIntl() @@ -34,11 +35,9 @@ export default function BookingCode() { setValue, formState: { errors }, getValues, - register, } = useFormContext() 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 @@ -50,24 +49,21 @@ export default function BookingCode() { 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)) { + if ( + ref.current && + target && + !ref.current.contains(target) && + target.getAttribute("value") !== "Remove extra rooms" + ) { setShowRemember(false) + if (codeError) { + setValue("bookingCode.value", "", { shouldValidate: true }) + } } }, - [setShowRemember, ref] + [setShowRemember, setValue, ref, codeError] ) function showRememberCheck() { @@ -102,46 +98,10 @@ export default function BookingCode() { }, [closeIfOutside, showRemember]) return isTablet ? ( -
- - - - - {({ close }) => ( -
- -
- -
-
- )} -
-
-
- -
+ ) : (
closeIfOutside(e.nativeEvent.relatedTarget as HTMLElement)} > -
- - {codeVoucher} - - +
+
+ + {codeVoucher} + + +
+ updateBookingCodeFormValue(event.target.value)} + autoComplete="off" + value={bookingCode?.value} + />
- updateBookingCodeFormValue(event.target.value)} - defaultValue={bookingCode?.value} - autoComplete="off" - /> - {codeError?.message ? ( - - - {intl.formatMessage({ id: codeError.message })} - - ) : null} {isDesktop ? (
- setShowRemember(false)} - /> + {codeError?.message ? ( + + ) : ( + setShowRemember(false)} + /> + )}
) : ( -
- - - {intl.formatMessage({ id: "Remember code" })} - - -
+ <> + {codeError?.message ? ( + + ) : ( +
+ + + {intl.formatMessage({ id: "Remember code" })} + + +
+ )} + )}
) @@ -259,3 +225,128 @@ function CodeRemember({ bookingCodeValue, onApplyClick }: CodeRememberProps) { ) } + +function BookingCodeError({ codeError }: { codeError: FieldError }) { + const intl = useIntl() + const isMultiroomErr = codeError.message?.indexOf("Multi-room") !== -1 + + return ( +
+ + + {intl.formatMessage({ id: codeError.message })} + + {isMultiroomErr ? ( + + ) : null} +
+ ) +} + +export function RemoveExtraRooms({ ...props }: ButtonProps) { + const intl = useIntl() + const { getValues, setValue, trigger } = useFormContext() + function removeExtraRooms() { + // Timeout to delay the event scheduling issue with touch events on mobile + window.setTimeout(() => { + const rooms = getValues("rooms")[0] + setValue("rooms", [rooms], { shouldValidate: true }) + trigger("bookingCode.value") + }, 300) + } + + return ( + + ) +} + +function TabletBookingCode({ + bookingCode, + updateValue, +}: { + bookingCode: BookingCodeSchema + updateValue: (value: string) => void +}) { + const intl = useIntl() + const [isOpen, setIsOpen] = useState(!!bookingCode?.value) + const { + setValue, + register, + formState: { errors }, + } = useFormContext() + const codeError = errors["bookingCode"]?.value + const codeVoucher = intl.formatMessage({ id: "Code / Voucher" }) + + 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) + } + } + } + + return ( +
+ + + + + {({ close }) => ( +
+ +
+ {codeError?.message ? ( + + ) : ( + + )} +
+
+ )} +
+
+
+ +
+ ) +} diff --git a/apps/scandic-web/components/Forms/BookingWidget/FormContent/Voucher/voucher.module.css b/apps/scandic-web/components/Forms/BookingWidget/FormContent/Voucher/voucher.module.css index bb8a80ac1..4979a6f44 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/FormContent/Voucher/voucher.module.css +++ b/apps/scandic-web/components/Forms/BookingWidget/FormContent/Voucher/voucher.module.css @@ -11,12 +11,6 @@ margin-top: var(--Spacing-x2); align-items: center; } -.vouchers { - width: 100%; - display: block; - padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); - border-radius: var(--Corner-radius-Small); -} .optionsContainer { display: flex; @@ -28,12 +22,6 @@ height: 24px; } -@media screen and (max-width: 767px) { - .vouchers { - margin-bottom: var(--Spacing-x5); - } -} - @media screen and (min-width: 768px) { .options { flex-direction: row; @@ -48,25 +36,9 @@ grid-template-columns: auto auto; column-gap: var(--Spacing-x2); } - .vouchers:hover, - .vouchers:focus-within, - .vouchers:has([data-focused="true"], [data-pressed="true"]) { - background-color: var(--Base-Surface-Primary-light-Hover-alt); - } -} - -@media screen and (max-width: 1366px) { - .vouchers { - background-color: var(--Base-Background-Primary-Normal); - border-radius: var(--Corner-radius-Medium); - } } @media screen and (min-width: 1367px) { - .vouchers { - display: block; - max-width: 200px; - } .options { flex-direction: column; max-width: 190px; diff --git a/apps/scandic-web/components/Forms/BookingWidget/FormContent/formContent.module.css b/apps/scandic-web/components/Forms/BookingWidget/FormContent/formContent.module.css index e743a80bc..bc0d73873 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/FormContent/formContent.module.css +++ b/apps/scandic-web/components/Forms/BookingWidget/FormContent/formContent.module.css @@ -16,6 +16,11 @@ position: relative; } +.buttonContainer { + display: grid; + gap: var(--Spacing-x1); +} + .showOnTablet { display: none; } @@ -33,7 +38,6 @@ } .rooms, - .vouchers, .when, .where { background-color: var(--Base-Background-Primary-Normal); @@ -41,7 +45,6 @@ } .rooms, - .vouchers, .when { padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); } @@ -103,6 +106,10 @@ justify-content: center; width: 118px; } + + .showOnMobile { + display: none; + } } @media screen and (min-width: 768px) and (max-width: 1366px) { @@ -143,3 +150,9 @@ display: none; } } + +@media screen and (min-width: 1366px) { + .input { + gap: var(--Spacing-x2); + } +} diff --git a/apps/scandic-web/components/Forms/BookingWidget/FormContent/index.tsx b/apps/scandic-web/components/Forms/BookingWidget/FormContent/index.tsx index 0bf6eb6ca..3012fcd6e 100644 --- a/apps/scandic-web/components/Forms/BookingWidget/FormContent/index.tsx +++ b/apps/scandic-web/components/Forms/BookingWidget/FormContent/index.tsx @@ -1,5 +1,5 @@ "use client" -import { useWatch } from "react-hook-form" +import { useFormContext, useWatch } from "react-hook-form" import { useIntl } from "react-intl" import { dt } from "@/lib/dt" @@ -11,11 +11,13 @@ import SkeletonShimmer from "@/components/SkeletonShimmer" import Button from "@/components/TempDesignSystem/Button" import Caption from "@/components/TempDesignSystem/Text/Caption" +import { RemoveExtraRooms } from "./BookingCode" import Search, { SearchSkeleton } from "./Search" import Voucher, { VoucherSkeleton } from "./Voucher" import styles from "./formContent.module.css" +import type { BookingWidgetSchema } from "@/types/components/bookingWidget" import type { BookingWidgetFormContentProps } from "@/types/components/form/bookingwidget" export default function FormContent({ @@ -25,6 +27,10 @@ export default function FormContent({ isSearching, }: BookingWidgetFormContentProps) { const intl = useIntl() + const { + formState: { errors }, + } = useFormContext() + const bookingCodeError = errors["bookingCode"]?.value const selectedDate = useWatch({ name: "date" }) const roomsLabel = intl.formatMessage({ id: "Rooms & Guests" }) @@ -79,6 +85,13 @@ export default function FormContent({
+ {bookingCodeError?.message?.indexOf("Multi-room") === 0 ? ( + + ) : null} + {addRoomDisabledText ? ( + + + {addRoomDisabledText} + + ) : null}
)}