From 341be43a530a59c6a9ff244100abbf91e7e335e7 Mon Sep 17 00:00:00 2001 From: Niclas Edenvin Date: Wed, 30 Apr 2025 08:56:16 +0000 Subject: [PATCH] Merged in fix/sw-2501-remove-continue-button-light (pull request #1892) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(sw-2501): remove the continue buttons on enter details This removes the continue buttons on the enter details page. This is only that, not the refactoring of the whole enter details page with changing to one form etc. Since I just didn’t complete that refactor today I decided to do this light variant for now. A quick explanation is that the continue buttons are removed and instead the form is submitted (meaning saving the form data to the store) on blur on the input elements IF the form is valid. If it’s invalid we change the isComplete flag in the store to false. This will hopefully also fix a bug where you were able to submit old data if the new data is invalid. When clicking the submit button and a room is incomplete/invalid the browser scrolls to the first invalid room. Approved-by: Erik Tiekstra --- .../EnterDetails/Details/Multiroom/index.tsx | 63 ++++++---------- .../Details/RoomOne/Signup/index.tsx | 18 ++++- .../EnterDetails/Details/RoomOne/index.tsx | 74 ++++++------------- .../Details/SpecialRequests/index.tsx | 9 ++- .../EnterDetails/Payment/PaymentClient.tsx | 37 ++++++++-- .../EnterDetails/Room/Multiroom.tsx | 25 +------ .../EnterDetails/Room/One.tsx | 11 +-- .../scandic-web/stores/enter-details/index.ts | 7 ++ .../types/contexts/details/room.ts | 1 + .../scandic-web/types/stores/enter-details.ts | 1 + 10 files changed, 111 insertions(+), 135 deletions(-) diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx index 0006d8a66..6b678a1ea 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx @@ -1,11 +1,9 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" -import { useEffect, useMemo } from "react" +import { useCallback, useEffect, useMemo } from "react" import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" -import { Button } from "@scandic-hotels/design-system/Button" - import { useEnterDetailsStore } from "@/stores/enter-details" import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests" @@ -26,16 +24,12 @@ const formID = "enter-details" export default function Details() { const intl = useIntl() - const { canProceedToPayment, lastRoom, rooms } = useEnterDetailsStore( - (state) => ({ - canProceedToPayment: state.canProceedToPayment, - lastRoom: state.lastRoom, - rooms: state.rooms, - }) - ) + const { rooms } = useEnterDetailsStore((state) => ({ + rooms: state.rooms, + })) const { - actions: { updateDetails }, + actions: { updateDetails, setIncomplete }, idx, room, roomNr, @@ -58,7 +52,6 @@ export default function Details() { [idx, rooms] ) - const isPaymentNext = idx === lastRoom const methods = useForm({ criteriaMode: "all", mode: "all", @@ -78,6 +71,16 @@ export default function Details() { }, }) + const updateDetailsStore = useCallback(() => { + if (methods.formState.isValid) { + methods.handleSubmit(updateDetails)() + } else { + setIncomplete() + } + }, [methods, setIncomplete, updateDetails]) + + useEffect(updateDetailsStore, [methods.formState.isValid, updateDetailsStore]) + // Trigger validation of the room manually when another room changes its data. // Only do it if the field has a value, to avoid error states before the user // has filled anything in. @@ -125,6 +128,7 @@ export default function Details() { registerOptions={{ required: true, deps: "lastName", + onBlur: updateDetailsStore, }} /> {guestIsGoingToJoin ? null : ( )} - + -
- -
) diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/Signup/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/Signup/index.tsx index 47f438935..220c1bdc3 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/Signup/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/Signup/index.tsx @@ -1,7 +1,7 @@ "use client" import { useEffect, useState } from "react" -import { useWatch } from "react-hook-form" +import { type RegisterOptions, useWatch } from "react-hook-form" import { useIntl } from "react-intl" import DateSelect from "@/components/TempDesignSystem/Form/Date" @@ -10,7 +10,13 @@ import Caption from "@/components/TempDesignSystem/Text/Caption" import styles from "./signup.module.css" -export default function Signup({ name }: { name: string }) { +export default function Signup({ + name, + registerOptions, +}: { + name: string + registerOptions?: RegisterOptions +}) { const intl = useIntl() const [isJoinChecked, setIsJoinChecked] = useState(false) @@ -30,7 +36,7 @@ export default function Signup({ name }: { name: string }) { label={intl.formatMessage({ defaultMessage: "Zip code", })} - registerOptions={{ required: true }} + registerOptions={{ required: true, ...registerOptions }} />
@@ -42,7 +48,10 @@ export default function Signup({ name }: { name: string }) {
- +
) : ( @@ -52,6 +61,7 @@ export default function Signup({ name }: { name: string }) { })} name="membershipNo" type="tel" + registerOptions={registerOptions} /> ) } diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx index 6ae788128..88d1ebd0b 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx @@ -1,13 +1,9 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" -import { useCallback } from "react" +import { useCallback, useEffect } from "react" import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" -import { Button } from "@scandic-hotels/design-system/Button" - -import { useEnterDetailsStore } from "@/stores/enter-details" - import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests" import CountrySelect from "@/components/TempDesignSystem/Form/Country" import Input from "@/components/TempDesignSystem/Form/Input" @@ -30,24 +26,14 @@ const formID = "enter-details" export default function Details({ user }: DetailsProps) { const intl = useIntl() - const { canProceedToPayment, lastRoom, isMultiRoom } = useEnterDetailsStore( - (state) => ({ - canProceedToPayment: state.canProceedToPayment, - lastRoom: state.lastRoom, - isMultiRoom: state.rooms.length > 1, - }) - ) const { - actions: { updateDetails }, - idx, + actions: { updateDetails, setIncomplete }, room, roomNr, } = useRoomContext() const initialData = room.guest const memberRate = "member" in room.roomRate ? room.roomRate.member : null - const isPaymentNext = idx === lastRoom - const showContinueButton = isMultiRoom || !user const methods = useForm({ criteriaMode: "all", @@ -78,6 +64,16 @@ export default function Details({ user }: DetailsProps) { [updateDetails] ) + const updateDetailsStore = useCallback(() => { + if (methods.formState.isValid) { + methods.handleSubmit(onSubmit)() + } else { + setIncomplete() + } + }, [methods, onSubmit, setIncomplete]) + + useEffect(updateDetailsStore, [methods.formState.isValid, updateDetailsStore]) + return (
{user ? null : (
- +
)} - + - {showContinueButton ? ( -
- -
- ) : null}
) diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/SpecialRequests/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/SpecialRequests/index.tsx index 73a782b08..b70d6e0d6 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/SpecialRequests/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/SpecialRequests/index.tsx @@ -6,7 +6,13 @@ import TextArea from "@/components/TempDesignSystem/Form/TextArea" import styles from "./specialRequests.module.css" -export default function SpecialRequests() { +import type { RegisterOptions } from "react-hook-form" + +export default function SpecialRequests({ + registerOptions, +}: { + registerOptions?: RegisterOptions +}) { const intl = useIntl() return ( @@ -63,6 +69,7 @@ export default function SpecialRequests() { "Is there anything else you would like us to know before your arrival?", })} name="specialRequest.comment" + registerOptions={registerOptions} /> diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx index a7c9dd182..15431a792 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Payment/PaymentClient.tsx @@ -33,6 +33,7 @@ import Title from "@/components/TempDesignSystem/Text/Title" import { useAvailablePaymentOptions } from "@/hooks/booking/useAvailablePaymentOptions" import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus" import useLang from "@/hooks/useLang" +import useStickyPosition from "@/hooks/useStickyPosition" import { trackPaymentEvent } from "@/utils/tracking" import { trackEvent } from "@/utils/tracking/base" import { trackGlaSaveCardAttempt } from "@/utils/tracking/myStay" @@ -71,6 +72,7 @@ export default function PaymentClient({ const lang = useLang() const intl = useIntl() const searchParams = useSearchParams() + const { getTopOffset } = useStickyPosition({}) const [showPaymentAlert, setShowPaymentAlert] = useState(false) @@ -80,8 +82,6 @@ export default function PaymentClient({ totalPrice: state.totalPrice, })) - const allRoomsComplete = rooms.every((r) => r.isComplete) - const bookingMustBeGuaranteed = rooms.some(({ room }, idx) => { if (idx === 0 && isUserLoggedIn && room.memberMustBeGuaranteed) { return true @@ -279,6 +279,30 @@ export default function PaymentClient({ const handleSubmit = useCallback( (data: PaymentFormData) => { + const firstIncompleteRoomIndex = rooms.findIndex( + (room) => !room.isComplete + ) + + // If any room is not complete/valid, scroll to it + if (firstIncompleteRoomIndex !== -1) { + const roomElement = document.getElementById( + `room-${firstIncompleteRoomIndex + 1}` + ) + + if (!roomElement) { + return + } + const roomElementTop = + roomElement.getBoundingClientRect().top + window.scrollY + + window.scrollTo({ + top: roomElementTop - getTopOffset() - 20, + behavior: "smooth", + }) + + return + } + const paymentMethod = getPaymentMethod(data.paymentMethod) const savedCreditCard = savedCreditCards?.find( @@ -423,6 +447,7 @@ export default function PaymentClient({ hasOnlyFlexRates, bookingMustBeGuaranteed, isUserLoggedIn, + getTopOffset, ] ) @@ -446,9 +471,7 @@ export default function PaymentClient({ }) return ( -
+
{hasOnlyFlexRates && bookingMustBeGuaranteed @@ -573,9 +596,7 @@ export default function PaymentClient({ theme="base" size="small" type="submit" - disabled={ - !methods.formState.isValid || methods.formState.isSubmitting - } + disabled={methods.formState.isSubmitting} > {intl.formatMessage({ defaultMessage: "Complete booking", diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Room/Multiroom.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Room/Multiroom.tsx index 514f587c5..559ef27e1 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Room/Multiroom.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Room/Multiroom.tsx @@ -19,8 +19,8 @@ import { StepEnum } from "@/types/enums/step" export default function Multiroom() { const intl = useIntl() - const { idx, room, roomNr, steps } = useRoomContext() - const { breakfastPackages, rooms } = useEnterDetailsStore((state) => ({ + const { room, roomNr } = useRoomContext() + const { breakfastPackages } = useEnterDetailsStore((state) => ({ breakfastPackages: state.breakfastPackages, rooms: state.rooms, })) @@ -28,22 +28,6 @@ export default function Multiroom() { const showBreakfastStep = !room.breakfastIncluded && !!breakfastPackages.length - const arePreviousRoomsValid = rooms.slice(0, idx).every((r) => r.isComplete) - - const isBreakfastStepValid = showBreakfastStep - ? steps[StepEnum.breakfast]?.isValid - : true - - const isBreakfastDisabled = !( - arePreviousRoomsValid && steps[StepEnum.selectBed].isValid - ) - - const isDetailsDisabled = !( - arePreviousRoomsValid && - steps[StepEnum.selectBed].isValid && - isBreakfastStepValid - ) - const hasChildWithExtraBed = room.childrenInRoom?.some( (child) => Number(child.bed) === ChildBedMapEnum.IN_EXTRA_BED ) @@ -55,7 +39,7 @@ export default function Multiroom() { ) return ( - <section> + <section id={`room-${roomNr}`}> <Header> <Title level="h2" as="h4"> {intl.formatMessage( @@ -77,7 +61,6 @@ export default function Multiroom() { label={intl.formatMessage({ defaultMessage: "Request bedtype" })} additionalInfo={bedTypeInfoText} step={StepEnum.selectBed} - disabled={!arePreviousRoomsValid} > <BedType /> </Section> @@ -92,7 +75,6 @@ export default function Multiroom() { defaultMessage: "Select breakfast options", })} step={StepEnum.breakfast} - disabled={isBreakfastDisabled} > <Breakfast /> </Section> @@ -106,7 +88,6 @@ export default function Multiroom() { label={intl.formatMessage({ defaultMessage: "Enter your details", })} - disabled={isDetailsDisabled} > <Details /> </Section> diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Room/One.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Room/One.tsx index 529ce3dce..da09a992f 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Room/One.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Room/One.tsx @@ -20,7 +20,7 @@ import type { SafeUser } from "@/types/user" export default function RoomOne({ user }: { user: SafeUser }) { const intl = useIntl() - const { room, steps } = useRoomContext() + const { room } = useRoomContext() const { breakfastPackages, isMultiroom } = useEnterDetailsStore((state) => ({ breakfastPackages: state.breakfastPackages, isMultiroom: state.rooms.length > 1, @@ -43,7 +43,7 @@ export default function RoomOne({ user }: { user: SafeUser }) { !room.breakfastIncluded && !!breakfastPackages.length return ( - <section> + <section id="room-1"> {isMultiroom ? ( <Header> <Title level="h2" as="h4"> @@ -81,7 +81,6 @@ export default function RoomOne({ user }: { user: SafeUser }) { defaultMessage: "Select breakfast options", })} step={StepEnum.breakfast} - disabled={!steps[StepEnum.selectBed].isValid} > <Breakfast /> </Section> @@ -95,12 +94,6 @@ export default function RoomOne({ user }: { user: SafeUser }) { label={intl.formatMessage({ defaultMessage: "Enter your details", })} - disabled={ - !( - steps[StepEnum.selectBed].isValid && - steps[StepEnum.breakfast]?.isValid !== false - ) - } > <Details user={user} /> </Section> diff --git a/apps/scandic-web/stores/enter-details/index.ts b/apps/scandic-web/stores/enter-details/index.ts index 871c93e23..ff9d869e2 100644 --- a/apps/scandic-web/stores/enter-details/index.ts +++ b/apps/scandic-web/stores/enter-details/index.ts @@ -167,6 +167,13 @@ export function createDetailsStore( return { actions: { + setIncomplete() { + return set( + produce((state: DetailsState) => { + state.rooms[idx].isComplete = false + }) + ) + }, updateBedType(bedType) { return set( produce((state: DetailsState) => { diff --git a/apps/scandic-web/types/contexts/details/room.ts b/apps/scandic-web/types/contexts/details/room.ts index 5222b387c..f0cb273fe 100644 --- a/apps/scandic-web/types/contexts/details/room.ts +++ b/apps/scandic-web/types/contexts/details/room.ts @@ -5,6 +5,7 @@ import type { RoomState } from "@/types/stores/enter-details" export interface RoomContextValue { actions: { + setIncomplete: () => void updateBedType: (data: BedTypeSchema) => void updateBreakfast: (data: BreakfastPackage | false) => void updateJoin: (join: boolean) => void diff --git a/apps/scandic-web/types/stores/enter-details.ts b/apps/scandic-web/types/stores/enter-details.ts index 500bc6920..5fdd4e7fe 100644 --- a/apps/scandic-web/types/stores/enter-details.ts +++ b/apps/scandic-web/types/stores/enter-details.ts @@ -60,6 +60,7 @@ export interface Room extends InitialRoomData { export interface RoomState { actions: { + setIncomplete: () => void updateBedType: (data: BedTypeSchema) => void updateBreakfast: (data: BreakfastPackage | false) => void updateJoin: (join: boolean) => void