diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/MemberPriceModal/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/MemberPriceModal/index.tsx index 0bad80707..097496df0 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/MemberPriceModal/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/MemberPriceModal/index.tsx @@ -1,5 +1,7 @@ "use client" +import { useEffect, useState } from "react" +import { useFormContext, useWatch } from "react-hook-form" import { useIntl } from "react-intl" import MagicWandIcon from "@scandic-hotels/design-system/Icons/MagicWandIcon" @@ -14,20 +16,35 @@ import { formatPrice } from "@/utils/numberFormatting" import styles from "./modal.module.css" -import type { Dispatch, SetStateAction } from "react" - import { CurrencyEnum } from "@/types/enums/currency" -export default function MemberPriceModal({ - isOpen, - setIsOpen, -}: { - isOpen: boolean - setIsOpen: Dispatch> -}) { - const { room } = useRoomContext() +export default function MemberPriceModal() { + const { + actions: { updatePriceForMembershipNo }, + room, + } = useRoomContext() const memberRate = "member" in room.roomRate ? room.roomRate.member : null const intl = useIntl() + const [isOpen, setIsOpen] = useState(false) + const { getFieldState, trigger } = useFormContext() + + const [join, membershipNo] = useWatch({ name: ["join", "membershipNo"] }) + + useEffect(() => { + if (join) { + setIsOpen(true) + } + }, [join]) + + useEffect(() => { + trigger("membershipNo").then((isValid) => { + const { isDirty } = getFieldState("membershipNo") + updatePriceForMembershipNo(membershipNo, isValid) + if (isValid && isDirty) { + setIsOpen(true) + } + }) + }, [getFieldState, membershipNo, trigger, updatePriceForMembershipNo]) if (!memberRate) { return null @@ -36,7 +53,7 @@ export default function MemberPriceModal({ const memberPrice = memberRate?.localPrice ?? memberRate?.requestedPrice return ( - + setIsOpen(false)}>
diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/JoinScandicFriendsCard/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/JoinScandicFriendsCard/index.tsx index 3983e6b1f..a85ba9350 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/JoinScandicFriendsCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/JoinScandicFriendsCard/index.tsx @@ -1,6 +1,5 @@ "use client" -import { useState } from "react" import { useIntl } from "react-intl" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" @@ -10,8 +9,6 @@ import Caption from "@/components/TempDesignSystem/Text/Caption" import { useRoomContext } from "@/contexts/Details/Room" import { formatPrice } from "@/utils/numberFormatting" -import MemberPriceModal from "../../MemberPriceModal" - import styles from "./joinScandicFriendsCard.module.css" import type { JoinScandicFriendsCardProps } from "@/types/components/hotelReservation/enterDetails/details" @@ -26,13 +23,9 @@ export default function JoinScandicFriendsCard({ roomNr, actions: { updateJoin }, } = useRoomContext() - const [isMemberPriceModalOpen, setIsMemberPriceModalOpen] = useState(false) function onChange(event: { target: { value: boolean } }) { updateJoin(event.target.value) - if (event.target.value) { - setIsMemberPriceModalOpen(true) - } } if (!("member" in room.roomRate) || !room.roomRate.member) { @@ -106,10 +99,6 @@ export default function JoinScandicFriendsCard({ ))}
-
) } 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 6012464c7..650036461 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/Multiroom/index.tsx @@ -13,6 +13,7 @@ import Phone from "@/components/TempDesignSystem/Form/Phone" import Footnote from "@/components/TempDesignSystem/Text/Footnote" import { useRoomContext } from "@/contexts/Details/Room" +import MemberPriceModal from "../MemberPriceModal" import JoinScandicFriendsCard from "./JoinScandicFriendsCard" import { getMultiroomDetailsSchema } from "./schema" @@ -52,11 +53,7 @@ export default function Details() { ) const methods = useForm({ - criteriaMode: "all", - mode: "all", - resolver: zodResolver(getMultiroomDetailsSchema(crossValidationData)), - reValidateMode: "onChange", - values: { + defaultValues: { countryCode: initialData.countryCode, email: initialData.email, firstName: initialData.firstName, @@ -68,6 +65,10 @@ export default function Details() { comment: room.specialRequest.comment, }, }, + criteriaMode: "all", + mode: "all", + resolver: zodResolver(getMultiroomDetailsSchema(crossValidationData)), + reValidateMode: "onChange", }) const { @@ -189,6 +190,7 @@ export default function Details() { )} + ) diff --git a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/JoinScandicFriendsCard/index.tsx b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/JoinScandicFriendsCard/index.tsx index 06348d850..5207f6970 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/JoinScandicFriendsCard/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/JoinScandicFriendsCard/index.tsx @@ -1,6 +1,4 @@ "use client" - -import { useState } from "react" import { useIntl } from "react-intl" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" @@ -16,8 +14,6 @@ import { useRoomContext } from "@/contexts/Details/Room" import useLang from "@/hooks/useLang" import { formatPrice } from "@/utils/numberFormatting" -import MemberPriceModal from "../../MemberPriceModal" - import styles from "./joinScandicFriendsCard.module.css" import type { JoinScandicFriendsCardProps } from "@/types/components/hotelReservation/enterDetails/details" @@ -32,13 +28,9 @@ export default function JoinScandicFriendsCard({ room, actions: { updateJoin }, } = useRoomContext() - const [isMemberPriceModalOpen, setIsMemberPriceModalOpen] = useState(false) function onChange(event: { target: { value: boolean } }) { updateJoin(event.target.value) - if (event.target.value) { - setIsMemberPriceModalOpen(true) - } } if (!("member" in room.roomRate) || !room.roomRate.member) { @@ -156,10 +148,6 @@ export default function JoinScandicFriendsCard({ )} - ) } 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 367bbe0bf..37bebc659 100644 --- a/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx +++ b/apps/scandic-web/components/HotelReservation/EnterDetails/Details/RoomOne/index.tsx @@ -13,6 +13,7 @@ import Phone from "@/components/TempDesignSystem/Form/Phone" import Footnote from "@/components/TempDesignSystem/Text/Footnote" import { useRoomContext } from "@/contexts/Details/Room" +import MemberPriceModal from "../MemberPriceModal" import JoinScandicFriendsCard from "./JoinScandicFriendsCard" import { guestDetailsSchema, signedInDetailsSchema } from "./schema" import Signup from "./Signup" @@ -43,25 +44,25 @@ export default function Details({ user }: DetailsProps) { const memberRate = "member" in room.roomRate ? room.roomRate.member : null const methods = useForm({ - criteriaMode: "all", - mode: "all", - resolver: zodResolver(user ? signedInDetailsSchema : guestDetailsSchema), - reValidateMode: "onChange", - values: { - countryCode: user?.address?.countryCode ?? initialData.countryCode, + defaultValues: { + countryCode: user?.address?.countryCode || initialData.countryCode, dateOfBirth: "dateOfBirth" in initialData ? initialData.dateOfBirth : undefined, - email: user?.email ?? initialData.email, - firstName: user?.firstName ?? initialData.firstName, + email: user?.email || initialData.email, + firstName: user?.firstName || initialData.firstName, join: initialData.join, - lastName: user?.lastName ?? initialData.lastName, + lastName: user?.lastName || initialData.lastName, membershipNo: initialData.membershipNo, - phoneNumber: user?.phoneNumber ?? initialData.phoneNumber, + phoneNumber: user?.phoneNumber || initialData.phoneNumber, zipCode: "zipCode" in initialData ? initialData.zipCode : undefined, specialRequest: { comment: room.specialRequest.comment, }, }, + criteriaMode: "all", + mode: "all", + resolver: zodResolver(user ? signedInDetailsSchema : guestDetailsSchema), + reValidateMode: "onChange", }) const { @@ -168,6 +169,7 @@ export default function Details({ user }: DetailsProps) { )} + ) diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Phone/index.tsx b/apps/scandic-web/components/TempDesignSystem/Form/Phone/index.tsx index cb7e18f8b..f6021b64c 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Phone/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Form/Phone/index.tsx @@ -57,7 +57,7 @@ export default function Phone({ rules: registerOptions, }) - const defaultPhoneNumber = formState.defaultValues?.phoneNumber + const defaultPhoneNumber = formState.defaultValues?.phoneNumber ?? "" // If defaultPhoneNumber exists and is valid, parse it to get the country code, // otherwise set the default country from the lang. diff --git a/apps/scandic-web/providers/EnterDetailsProvider.tsx b/apps/scandic-web/providers/EnterDetailsProvider.tsx index 009e1b08a..f86b27773 100644 --- a/apps/scandic-web/providers/EnterDetailsProvider.tsx +++ b/apps/scandic-web/providers/EnterDetailsProvider.tsx @@ -1,6 +1,6 @@ "use client" import deepmerge from "deepmerge" -import { useEffect, useRef } from "react" +import { useEffect, useRef, useState } from "react" import { dt } from "@/lib/dt" import { createDetailsStore } from "@/stores/enter-details" @@ -14,6 +14,7 @@ import { import { getMultiroomDetailsSchema } from "@/components/HotelReservation/EnterDetails/Details/Multiroom/schema" import { guestDetailsSchema } from "@/components/HotelReservation/EnterDetails/Details/RoomOne/schema" +import LoadingSpinner from "@/components/LoadingSpinner" import { DetailsContext } from "@/contexts/Details" import type { DetailsStore } from "@/types/contexts/enter-details" @@ -31,6 +32,11 @@ export default function EnterDetailsProvider({ user, vat, }: DetailsProviderProps) { + // This state is needed to be able to use defaultValues for + // react-hook-form since values needs to be there on mount + // and since we read from SessionStorage we need to delay + // rendering the form until that has been done. + const [hasInitializedStore, setHasInitializedStore] = useState(false) const storeRef = useRef() if (!storeRef.current) { const initialData: InitialState = { @@ -74,16 +80,19 @@ export default function EnterDetailsProvider({ useEffect(() => { const storedValues = readFromSessionStorage() if (!storedValues) { + setHasInitializedStore(true) return } const isSameBooking = checkIsSameBooking(storedValues.booking, booking) if (!isSameBooking) { clearSessionStorage() + setHasInitializedStore(true) return } const store = storeRef.current?.getState() if (!store) { + setHasInitializedStore(true) return } @@ -209,11 +218,13 @@ export default function EnterDetailsProvider({ rooms: filteredOutMissingRooms, totalPrice, }) + + setHasInitializedStore(true) }, [booking, rooms, user]) return ( - {children} + {hasInitializedStore ? children : } ) } diff --git a/apps/scandic-web/stores/enter-details/index.ts b/apps/scandic-web/stores/enter-details/index.ts index 14e9efaee..921fda158 100644 --- a/apps/scandic-web/stores/enter-details/index.ts +++ b/apps/scandic-web/stores/enter-details/index.ts @@ -253,24 +253,18 @@ export function createDetailsStore( }) ) }, - updateJoin(join) { + updatePriceForMembershipNo(membershipNo, isValid) { return set( produce((state: DetailsState) => { const currentRoom = state.rooms[idx].room - currentRoom.guest.join = join - - if (join) { - currentRoom.guest.membershipNo = undefined - } + currentRoom.guest.join = false + currentRoom.guest.membershipNo = isValid ? membershipNo : "" + const isValidMembershipNo = isValid && !!membershipNo currentRoom.roomPrice = getRoomPrice( currentRoom.roomRate, - Boolean( - join || - currentRoom.guest.membershipNo || - (idx === 0 && isMember) - ) + isValidMembershipNo ) const nights = dt(state.booking.toDate).diff( @@ -284,6 +278,43 @@ export function createDetailsStore( isMember, nights ) + + writeToSessionStorage({ + booking: state.booking, + rooms: state.rooms, + }) + }) + ) + }, + updateJoin(join) { + return set( + produce((state: DetailsState) => { + const currentRoom = state.rooms[idx].room + + currentRoom.guest.join = join + + if (join) { + currentRoom.guest.membershipNo = "" + } + + currentRoom.roomPrice = getRoomPrice(currentRoom.roomRate, join) + + const nights = dt(state.booking.toDate).diff( + state.booking.fromDate, + "days" + ) + + state.totalPrice = calcTotalPrice( + state.rooms, + state.totalPrice.local.currency, + isMember, + nights + ) + + writeToSessionStorage({ + booking: state.booking, + rooms: state.rooms, + }) }) ) }, diff --git a/apps/scandic-web/types/contexts/details/room.ts b/apps/scandic-web/types/contexts/details/room.ts index f0cb273fe..b016baba3 100644 --- a/apps/scandic-web/types/contexts/details/room.ts +++ b/apps/scandic-web/types/contexts/details/room.ts @@ -1,16 +1,7 @@ -import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast" -import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType" -import type { DetailsSchema } from "@/types/components/hotelReservation/enterDetails/details" 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 - updateDetails: (data: DetailsSchema) => void - } + actions: RoomState["actions"] isComplete: RoomState["isComplete"] idx: number room: RoomState["room"] diff --git a/apps/scandic-web/types/stores/enter-details.ts b/apps/scandic-web/types/stores/enter-details.ts index 897638d11..cf16821c4 100644 --- a/apps/scandic-web/types/stores/enter-details.ts +++ b/apps/scandic-web/types/stores/enter-details.ts @@ -65,6 +65,7 @@ export interface RoomState { updateBedType: (data: BedTypeSchema) => void updateBreakfast: (data: BreakfastPackage | false) => void updateJoin: (join: boolean) => void + updatePriceForMembershipNo: (membershipNo: string, isValid: boolean) => void updateDetails: (data: DetailsSchema) => void } isComplete: boolean