Merged in feat/SW-1889 (pull request #1670)
Feat/SW-1889 * fix: remove download invoice from confirmation page * feat: remove EnterDetails Accordions Approved-by: Simon.Emanuelsson
This commit is contained in:
@@ -11,7 +11,7 @@ import useLang from "@/hooks/useLang"
|
|||||||
|
|
||||||
import AddToCalendar from "../../AddToCalendar"
|
import AddToCalendar from "../../AddToCalendar"
|
||||||
import AddToCalendarButton from "./Actions/AddToCalendarButton"
|
import AddToCalendarButton from "./Actions/AddToCalendarButton"
|
||||||
import DownloadInvoice from "./Actions/DownloadInvoice"
|
// import DownloadInvoice from "./Actions/DownloadInvoice"
|
||||||
import { generateDateTime } from "./Actions/helpers"
|
import { generateDateTime } from "./Actions/helpers"
|
||||||
import ManageBooking from "./Actions/ManageBooking"
|
import ManageBooking from "./Actions/ManageBooking"
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ import type { BookingConfirmationHeaderProps } from "@/types/components/hotelRes
|
|||||||
export default function Header({
|
export default function Header({
|
||||||
booking,
|
booking,
|
||||||
hotel,
|
hotel,
|
||||||
mainRef,
|
// mainRef,
|
||||||
refId,
|
refId,
|
||||||
}: BookingConfirmationHeaderProps) {
|
}: BookingConfirmationHeaderProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
@@ -91,7 +91,8 @@ export default function Header({
|
|||||||
renderButton={(onPress) => <AddToCalendarButton onPress={onPress} />}
|
renderButton={(onPress) => <AddToCalendarButton onPress={onPress} />}
|
||||||
/>
|
/>
|
||||||
<ManageBooking bookingUrl={bookingUrlPath} />
|
<ManageBooking bookingUrl={bookingUrlPath} />
|
||||||
<DownloadInvoice mainRef={mainRef} />
|
{/* Download Invoice will be added later (currently available on My Stay) */}
|
||||||
|
{/* <DownloadInvoice mainRef={mainRef} /> */}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ export default function BedType() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscription = methods.watch(() => methods.handleSubmit(onSubmit)())
|
methods.watch(() => methods.handleSubmit(onSubmit)())
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}, [methods, onSubmit])
|
}, [methods, onSubmit])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ export default function Breakfast() {
|
|||||||
if (methods.formState.isSubmitting) {
|
if (methods.formState.isSubmitting) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const subscription = methods.watch(() => methods.handleSubmit(onSubmit)())
|
methods.watch(() => methods.handleSubmit(onSubmit)())
|
||||||
return () => subscription.unsubscribe()
|
|
||||||
}, [methods, onSubmit])
|
}, [methods, onSubmit])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -24,23 +24,20 @@ const formID = "enter-details"
|
|||||||
export default function Details() {
|
export default function Details() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const { activeRoom, canProceedToPayment, lastRoom } = useEnterDetailsStore(
|
const { canProceedToPayment, lastRoom } = useEnterDetailsStore((state) => ({
|
||||||
(state) => ({
|
canProceedToPayment: state.canProceedToPayment,
|
||||||
activeRoom: state.activeRoom,
|
lastRoom: state.lastRoom,
|
||||||
canProceedToPayment: state.canProceedToPayment,
|
}))
|
||||||
lastRoom: state.lastRoom,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { updateDetails },
|
actions: { updateDetails },
|
||||||
|
idx,
|
||||||
room,
|
room,
|
||||||
roomNr,
|
roomNr,
|
||||||
} = useRoomContext()
|
} = useRoomContext()
|
||||||
const initialData = room.guest
|
const initialData = room.guest
|
||||||
|
|
||||||
const isPaymentNext = activeRoom === lastRoom
|
const isPaymentNext = idx === lastRoom
|
||||||
|
|
||||||
const methods = useForm<MultiroomDetailsSchema>({
|
const methods = useForm<MultiroomDetailsSchema>({
|
||||||
criteriaMode: "all",
|
criteriaMode: "all",
|
||||||
mode: "all",
|
mode: "all",
|
||||||
|
|||||||
@@ -31,22 +31,20 @@ export default function Details({ user }: DetailsProps) {
|
|||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const [isMemberPriceModalOpen, setIsMemberPriceModalOpen] = useState(false)
|
const [isMemberPriceModalOpen, setIsMemberPriceModalOpen] = useState(false)
|
||||||
|
|
||||||
const { activeRoom, canProceedToPayment, lastRoom } = useEnterDetailsStore(
|
const { canProceedToPayment, lastRoom } = useEnterDetailsStore((state) => ({
|
||||||
(state) => ({
|
canProceedToPayment: state.canProceedToPayment,
|
||||||
activeRoom: state.activeRoom,
|
lastRoom: state.lastRoom,
|
||||||
canProceedToPayment: state.canProceedToPayment,
|
}))
|
||||||
lastRoom: state.lastRoom,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const {
|
const {
|
||||||
actions: { updateDetails },
|
actions: { updateDetails },
|
||||||
|
idx,
|
||||||
room,
|
room,
|
||||||
roomNr,
|
roomNr,
|
||||||
} = useRoomContext()
|
} = useRoomContext()
|
||||||
const initialData = room.guest
|
const initialData = room.guest
|
||||||
const memberRate = "member" in room.roomRate ? room.roomRate.member : null
|
|
||||||
|
|
||||||
const isPaymentNext = activeRoom === lastRoom
|
const memberRate = "member" in room.roomRate ? room.roomRate.member : null
|
||||||
|
const isPaymentNext = idx === lastRoom
|
||||||
|
|
||||||
const methods = useForm<DetailsSchema>({
|
const methods = useForm<DetailsSchema>({
|
||||||
criteriaMode: "all",
|
criteriaMode: "all",
|
||||||
|
|||||||
@@ -67,13 +67,13 @@ export default function PaymentClient({
|
|||||||
|
|
||||||
const [showPaymentAlert, setShowPaymentAlert] = useState(false)
|
const [showPaymentAlert, setShowPaymentAlert] = useState(false)
|
||||||
|
|
||||||
const { booking, canProceedToPayment, rooms, totalPrice } =
|
const { booking, rooms, totalPrice } = useEnterDetailsStore((state) => ({
|
||||||
useEnterDetailsStore((state) => ({
|
booking: state.booking,
|
||||||
booking: state.booking,
|
rooms: state.rooms,
|
||||||
canProceedToPayment: state.canProceedToPayment,
|
totalPrice: state.totalPrice,
|
||||||
rooms: state.rooms,
|
}))
|
||||||
totalPrice: state.totalPrice,
|
|
||||||
}))
|
const allRoomsComplete = rooms.every((r) => r.isComplete)
|
||||||
|
|
||||||
const bookingMustBeGuaranteed = rooms.some(({ room }, idx) => {
|
const bookingMustBeGuaranteed = rooms.some(({ room }, idx) => {
|
||||||
if (idx === 0 && isUserLoggedIn && room.memberMustBeGuaranteed) {
|
if (idx === 0 && isUserLoggedIn && room.memberMustBeGuaranteed) {
|
||||||
@@ -390,7 +390,7 @@ export default function PaymentClient({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={`${styles.paymentSection} ${canProceedToPayment ? "" : styles.disabled}`}
|
className={`${styles.paymentSection} ${allRoomsComplete ? "" : styles.disabled}`}
|
||||||
>
|
>
|
||||||
<header>
|
<header>
|
||||||
<Title level="h2" as="h4">
|
<Title level="h2" as="h4">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import BedType from "@/components/HotelReservation/EnterDetails/BedType"
|
|||||||
import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast"
|
import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast"
|
||||||
import Details from "@/components/HotelReservation/EnterDetails/Details/Multiroom"
|
import Details from "@/components/HotelReservation/EnterDetails/Details/Multiroom"
|
||||||
import Header from "@/components/HotelReservation/EnterDetails/Room/Header"
|
import Header from "@/components/HotelReservation/EnterDetails/Room/Header"
|
||||||
import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion"
|
import Section from "@/components/HotelReservation/EnterDetails/Section"
|
||||||
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
import { useRoomContext } from "@/contexts/Details/Room"
|
import { useRoomContext } from "@/contexts/Details/Room"
|
||||||
@@ -16,12 +16,31 @@ import { StepEnum } from "@/types/enums/step"
|
|||||||
|
|
||||||
export default function Multiroom() {
|
export default function Multiroom() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { room, roomNr } = useRoomContext()
|
const { idx, room, roomNr, steps } = useRoomContext()
|
||||||
const breakfastPackages = useEnterDetailsStore(
|
const { breakfastPackages, rooms } = useEnterDetailsStore((state) => ({
|
||||||
(state) => state.breakfastPackages
|
breakfastPackages: state.breakfastPackages,
|
||||||
)
|
rooms: state.rooms,
|
||||||
|
}))
|
||||||
|
|
||||||
const showBreakfastStep =
|
const showBreakfastStep =
|
||||||
!room.breakfastIncluded && !!breakfastPackages?.length
|
!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
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<Header>
|
<Header>
|
||||||
@@ -38,34 +57,37 @@ export default function Multiroom() {
|
|||||||
<SelectedRoom />
|
<SelectedRoom />
|
||||||
|
|
||||||
{room.bedTypes ? (
|
{room.bedTypes ? (
|
||||||
<SectionAccordion
|
<Section
|
||||||
header={intl.formatMessage({ id: "Select bed" })}
|
header={intl.formatMessage({ id: "Select bed" })}
|
||||||
label={intl.formatMessage({ id: "Request bedtype" })}
|
label={intl.formatMessage({ id: "Request bedtype" })}
|
||||||
step={StepEnum.selectBed}
|
step={StepEnum.selectBed}
|
||||||
|
disabled={!arePreviousRoomsValid}
|
||||||
>
|
>
|
||||||
<BedType />
|
<BedType />
|
||||||
</SectionAccordion>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{showBreakfastStep ? (
|
{showBreakfastStep ? (
|
||||||
<SectionAccordion
|
<Section
|
||||||
header={intl.formatMessage({ id: "Food options" })}
|
header={intl.formatMessage({ id: "Food options" })}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: "Select breakfast options",
|
id: "Select breakfast options",
|
||||||
})}
|
})}
|
||||||
step={StepEnum.breakfast}
|
step={StepEnum.breakfast}
|
||||||
|
disabled={isBreakfastDisabled}
|
||||||
>
|
>
|
||||||
<Breakfast />
|
<Breakfast />
|
||||||
</SectionAccordion>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<SectionAccordion
|
<Section
|
||||||
header={intl.formatMessage({ id: "Details" })}
|
header={intl.formatMessage({ id: "Details" })}
|
||||||
step={StepEnum.details}
|
step={StepEnum.details}
|
||||||
label={intl.formatMessage({ id: "Enter your details" })}
|
label={intl.formatMessage({ id: "Enter your details" })}
|
||||||
|
disabled={isDetailsDisabled}
|
||||||
>
|
>
|
||||||
<Details />
|
<Details />
|
||||||
</SectionAccordion>
|
</Section>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import BedType from "@/components/HotelReservation/EnterDetails/BedType"
|
|||||||
import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast"
|
import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast"
|
||||||
import Details from "@/components/HotelReservation/EnterDetails/Details/RoomOne"
|
import Details from "@/components/HotelReservation/EnterDetails/Details/RoomOne"
|
||||||
import Header from "@/components/HotelReservation/EnterDetails/Room/Header"
|
import Header from "@/components/HotelReservation/EnterDetails/Room/Header"
|
||||||
import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion"
|
import Section from "@/components/HotelReservation/EnterDetails/Section"
|
||||||
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
import { useRoomContext } from "@/contexts/Details/Room"
|
import { useRoomContext } from "@/contexts/Details/Room"
|
||||||
@@ -17,13 +17,12 @@ import type { SafeUser } from "@/types/user"
|
|||||||
|
|
||||||
export default function RoomOne({ user }: { user: SafeUser }) {
|
export default function RoomOne({ user }: { user: SafeUser }) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { room } = useRoomContext()
|
const { room, steps } = useRoomContext()
|
||||||
const { breakfastPackages, rooms } = useEnterDetailsStore((state) => ({
|
const { breakfastPackages, isMultiroom } = useEnterDetailsStore((state) => ({
|
||||||
breakfastPackages: state.breakfastPackages,
|
breakfastPackages: state.breakfastPackages,
|
||||||
rooms: state.rooms,
|
isMultiroom: state.rooms.length > 1,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const isMultiroom = rooms.length > 1
|
|
||||||
const showBreakfastStep =
|
const showBreakfastStep =
|
||||||
!room.breakfastIncluded && !!breakfastPackages?.length
|
!room.breakfastIncluded && !!breakfastPackages?.length
|
||||||
return (
|
return (
|
||||||
@@ -44,34 +43,41 @@ export default function RoomOne({ user }: { user: SafeUser }) {
|
|||||||
<SelectedRoom />
|
<SelectedRoom />
|
||||||
|
|
||||||
{room.bedTypes ? (
|
{room.bedTypes ? (
|
||||||
<SectionAccordion
|
<Section
|
||||||
header={intl.formatMessage({ id: "Select bed" })}
|
header={intl.formatMessage({ id: "Select bed" })}
|
||||||
label={intl.formatMessage({ id: "Request bedtype" })}
|
label={intl.formatMessage({ id: "Request bedtype" })}
|
||||||
step={StepEnum.selectBed}
|
step={StepEnum.selectBed}
|
||||||
>
|
>
|
||||||
<BedType />
|
<BedType />
|
||||||
</SectionAccordion>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{showBreakfastStep ? (
|
{showBreakfastStep ? (
|
||||||
<SectionAccordion
|
<Section
|
||||||
header={intl.formatMessage({ id: "Food options" })}
|
header={intl.formatMessage({ id: "Food options" })}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
id: "Select breakfast options",
|
id: "Select breakfast options",
|
||||||
})}
|
})}
|
||||||
step={StepEnum.breakfast}
|
step={StepEnum.breakfast}
|
||||||
|
disabled={!steps[StepEnum.selectBed].isValid}
|
||||||
>
|
>
|
||||||
<Breakfast />
|
<Breakfast />
|
||||||
</SectionAccordion>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<SectionAccordion
|
<Section
|
||||||
header={intl.formatMessage({ id: "Details" })}
|
header={intl.formatMessage({ id: "Details" })}
|
||||||
step={StepEnum.details}
|
step={StepEnum.details}
|
||||||
label={intl.formatMessage({ id: "Enter your details" })}
|
label={intl.formatMessage({ id: "Enter your details" })}
|
||||||
|
disabled={
|
||||||
|
!(
|
||||||
|
steps[StepEnum.selectBed].isValid &&
|
||||||
|
steps[StepEnum.breakfast]?.isValid !== false
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Details user={user} />
|
<Details user={user} />
|
||||||
</SectionAccordion>
|
</Section>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
"use client"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import { useRoomContext } from "@/contexts/Details/Room"
|
||||||
|
|
||||||
|
import styles from "./section.module.css"
|
||||||
|
|
||||||
|
import type { SectionProps } from "@/types/components/hotelReservation/enterDetails/section"
|
||||||
|
import { StepEnum } from "@/types/enums/step"
|
||||||
|
|
||||||
|
export default function Section({
|
||||||
|
children,
|
||||||
|
header,
|
||||||
|
label,
|
||||||
|
step,
|
||||||
|
disabled,
|
||||||
|
}: React.PropsWithChildren<SectionProps>) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const {
|
||||||
|
room: { bedType, breakfast },
|
||||||
|
} = useRoomContext()
|
||||||
|
|
||||||
|
const [title, setTitle] = useState(label)
|
||||||
|
|
||||||
|
const noBreakfastTitle = intl.formatMessage({ id: "No breakfast" })
|
||||||
|
const breakfastTitle = intl.formatMessage({ id: "Breakfast buffet" })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (step === StepEnum.selectBed && bedType) {
|
||||||
|
setTitle(bedType.description)
|
||||||
|
}
|
||||||
|
// If breakfast step, check if an option has been selected
|
||||||
|
if (step === StepEnum.breakfast && breakfast !== undefined) {
|
||||||
|
if (breakfast === false) {
|
||||||
|
setTitle(noBreakfastTitle)
|
||||||
|
} else {
|
||||||
|
setTitle(breakfastTitle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [bedType, breakfast, setTitle, step, breakfastTitle, noBreakfastTitle])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styles.accordion} ${disabled ? styles.disabled : ""}`}
|
||||||
|
data-step={step}
|
||||||
|
>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<Footnote
|
||||||
|
className={styles.title}
|
||||||
|
asChild
|
||||||
|
textTransform="uppercase"
|
||||||
|
type="label"
|
||||||
|
>
|
||||||
|
<h2>{header}</h2>
|
||||||
|
</Footnote>
|
||||||
|
<Subtitle className={styles.selection} type="two">
|
||||||
|
{title}
|
||||||
|
</Subtitle>
|
||||||
|
</header>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.contentWrapper}>{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
.accordion {
|
||||||
|
--header-height: 2.4em;
|
||||||
|
--circle-height: 24px;
|
||||||
|
gap: var(--Spacing-x3);
|
||||||
|
width: 100%;
|
||||||
|
padding-top: var(--Spacing-x3);
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "header" "content";
|
||||||
|
grid-template-rows: var(--header-height) 1fr;
|
||||||
|
column-gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
grid-area: header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
grid-area: title;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection {
|
||||||
|
grid-area: selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentWrapper {
|
||||||
|
padding-bottom: var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
grid-area: content;
|
||||||
|
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.accordion {
|
||||||
|
column-gap: var(--Spacing-x3);
|
||||||
|
grid-template-areas: "header" "content";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import { useEffect, useRef, useState } from "react"
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
|
||||||
|
|
||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
|
||||||
import { useRoomContext } from "@/contexts/Details/Room"
|
|
||||||
import useStickyPosition from "@/hooks/useStickyPosition"
|
|
||||||
|
|
||||||
import styles from "./sectionAccordion.module.css"
|
|
||||||
|
|
||||||
import type { SectionAccordionProps } from "@/types/components/hotelReservation/selectRate/sectionAccordion"
|
|
||||||
import { StepEnum } from "@/types/enums/step"
|
|
||||||
|
|
||||||
export default function SectionAccordion({
|
|
||||||
children,
|
|
||||||
header,
|
|
||||||
label,
|
|
||||||
step,
|
|
||||||
}: React.PropsWithChildren<SectionAccordionProps>) {
|
|
||||||
const intl = useIntl()
|
|
||||||
const stickyPosition = useStickyPosition({})
|
|
||||||
const {
|
|
||||||
actions: { setStep },
|
|
||||||
currentStep,
|
|
||||||
isActiveRoom,
|
|
||||||
room: { bedType, breakfast, isAvailable },
|
|
||||||
steps,
|
|
||||||
} = useRoomContext()
|
|
||||||
|
|
||||||
const isStepComplete = !!(steps[step]?.isValid && isAvailable)
|
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
|
||||||
const [title, setTitle] = useState(label)
|
|
||||||
|
|
||||||
const noBreakfastTitle = intl.formatMessage({ id: "No breakfast" })
|
|
||||||
const breakfastTitle = intl.formatMessage({ id: "Breakfast buffet" })
|
|
||||||
|
|
||||||
// useScrollToActiveSection(step, steps, currentStep === step)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (step === StepEnum.selectBed && bedType) {
|
|
||||||
setTitle(bedType.description)
|
|
||||||
}
|
|
||||||
// If breakfast step, check if an option has been selected
|
|
||||||
if (step === StepEnum.breakfast && breakfast !== undefined) {
|
|
||||||
if (breakfast === false) {
|
|
||||||
setTitle(noBreakfastTitle)
|
|
||||||
} else {
|
|
||||||
setTitle(breakfastTitle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [bedType, breakfast, setTitle, step, breakfastTitle, noBreakfastTitle])
|
|
||||||
|
|
||||||
const accordionRef = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const shouldBeOpen = currentStep === step && isActiveRoom && isAvailable
|
|
||||||
setIsOpen(shouldBeOpen)
|
|
||||||
|
|
||||||
// Scroll to this section when it is opened,
|
|
||||||
// but wait for the accordion animations to finish,
|
|
||||||
// else the height calculations will not be correct and
|
|
||||||
// the scroll position will be off.
|
|
||||||
if (shouldBeOpen) {
|
|
||||||
const handleTransitionEnd = () => {
|
|
||||||
if (accordionRef.current) {
|
|
||||||
window.scrollTo({
|
|
||||||
top: accordionRef.current.offsetTop - stickyPosition.getTopOffset(),
|
|
||||||
behavior: "smooth",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
accordionRef.current?.removeEventListener(
|
|
||||||
"transitionend",
|
|
||||||
handleTransitionEnd
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accordionRef.current) {
|
|
||||||
accordionRef.current.addEventListener(
|
|
||||||
"transitionend",
|
|
||||||
handleTransitionEnd,
|
|
||||||
{ once: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [currentStep, isActiveRoom, isAvailable, setIsOpen, step])
|
|
||||||
|
|
||||||
function goToStep() {
|
|
||||||
setStep(step)
|
|
||||||
}
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
setIsOpen(false)
|
|
||||||
goToStep()
|
|
||||||
}
|
|
||||||
|
|
||||||
const textColor =
|
|
||||||
isStepComplete || isOpen ? "uiTextHighContrast" : "baseTextDisabled"
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.accordion}
|
|
||||||
data-section-open={isOpen}
|
|
||||||
data-step={step}
|
|
||||||
ref={accordionRef}
|
|
||||||
>
|
|
||||||
<div className={styles.iconWrapper}>
|
|
||||||
<div className={styles.circle} data-checked={isStepComplete}>
|
|
||||||
{isStepComplete ? (
|
|
||||||
<MaterialIcon icon="check" color="Icon/Inverted" size={16} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<header className={styles.header}>
|
|
||||||
<button
|
|
||||||
onClick={isOpen ? close : goToStep}
|
|
||||||
disabled={!isStepComplete}
|
|
||||||
className={styles.modifyButton}
|
|
||||||
>
|
|
||||||
<Footnote
|
|
||||||
className={styles.title}
|
|
||||||
asChild
|
|
||||||
textTransform="uppercase"
|
|
||||||
type="label"
|
|
||||||
color={textColor}
|
|
||||||
>
|
|
||||||
<h2>{header}</h2>
|
|
||||||
</Footnote>
|
|
||||||
<Subtitle className={styles.selection} type="two" color={textColor}>
|
|
||||||
{title}
|
|
||||||
</Subtitle>
|
|
||||||
{isStepComplete && (
|
|
||||||
<MaterialIcon
|
|
||||||
icon="keyboard_arrow_down"
|
|
||||||
className={`${styles.button} ${isOpen ? styles.buttonOpen : ""}`}
|
|
||||||
color="Icon/Interactive/Default"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</header>
|
|
||||||
<div className={styles.content}>
|
|
||||||
<div className={styles.contentWrapper}>{children}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
.accordion {
|
|
||||||
--header-height: 2.4em;
|
|
||||||
--circle-height: 24px;
|
|
||||||
|
|
||||||
gap: var(--Spacing-x3);
|
|
||||||
width: 100%;
|
|
||||||
padding-top: var(--Spacing-x3);
|
|
||||||
transition: 0.3s ease-out;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas: "circle header" "content content";
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
grid-template-rows: var(--header-height) 0fr;
|
|
||||||
|
|
||||||
column-gap: var(--Spacing-x-one-and-half);
|
|
||||||
transform-origin: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
grid-area: header;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modifyButton {
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas: "title button" "selection button";
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modifyButton:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
grid-area: title;
|
|
||||||
text-align: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
grid-area: button;
|
|
||||||
justify-self: flex-end;
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
transition: transform 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonOpen {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
.selection {
|
|
||||||
grid-area: selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconWrapper {
|
|
||||||
position: relative;
|
|
||||||
grid-area: circle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle {
|
|
||||||
width: var(--circle-height);
|
|
||||||
height: var(--circle-height);
|
|
||||||
border-radius: 100px;
|
|
||||||
transition: background-color 0.4s;
|
|
||||||
border: 2px solid var(--Base-Border-Inverted);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle[data-checked="true"] {
|
|
||||||
background-color: var(--UI-Input-Controls-Fill-Selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion[data-section-open="true"] .circle[data-checked="false"] {
|
|
||||||
background-color: var(--UI-Text-Placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion[data-section-open="false"] .circle[data-checked="false"] {
|
|
||||||
background-color: var(--Base-Surface-Subtle-Hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion[data-section-open="true"] {
|
|
||||||
grid-template-rows: var(--header-height) 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentWrapper {
|
|
||||||
opacity: 0;
|
|
||||||
padding-bottom: var(--Spacing-x3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion[data-section-open="true"] .contentWrapper {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
overflow: hidden;
|
|
||||||
grid-area: content;
|
|
||||||
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
|
||||||
transform-origin: top;
|
|
||||||
transition: opacity 0.2s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion[data-section-open="true"] .content {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.accordion {
|
|
||||||
column-gap: var(--Spacing-x3);
|
|
||||||
grid-template-areas: "circle header" "circle content";
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconWrapper {
|
|
||||||
top: var(--Spacing-x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.accordion:not(:last-child) .iconWrapper::after {
|
|
||||||
position: absolute;
|
|
||||||
left: 12px;
|
|
||||||
bottom: calc(0px - var(--Spacing-x5));
|
|
||||||
top: var(--circle-height);
|
|
||||||
|
|
||||||
content: "";
|
|
||||||
border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,7 @@ export default function SelectedRoom() {
|
|||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [isPending, startTransition] = useTransition()
|
const [isPending, startTransition] = useTransition()
|
||||||
const { room, roomNr } = useRoomContext()
|
const { room, idx } = useRoomContext()
|
||||||
const { hotelId, searchParamsStr } = useEnterDetailsStore((state) => ({
|
const { hotelId, searchParamsStr } = useEnterDetailsStore((state) => ({
|
||||||
hotelId: state.booking.hotelId,
|
hotelId: state.booking.hotelId,
|
||||||
searchParamsStr: state.searchParamString,
|
searchParamsStr: state.searchParamString,
|
||||||
@@ -33,8 +33,8 @@ export default function SelectedRoom() {
|
|||||||
|
|
||||||
function changeRoom() {
|
function changeRoom() {
|
||||||
const searchParams = new URLSearchParams(searchParamsStr)
|
const searchParams = new URLSearchParams(searchParamsStr)
|
||||||
// rooms are index based, thus need for subtraction
|
|
||||||
searchParams.set("modifyRateIndex", `${roomNr - 1}`)
|
searchParams.set("modifyRateIndex", `${idx}`)
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
router.push(`${selectRate(lang)}?${searchParams.toString()}`)
|
router.push(`${selectRate(lang)}?${searchParams.toString()}`)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,24 +41,6 @@
|
|||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconWrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 100px;
|
|
||||||
border: 2px solid var(--Base-Border-Inverted);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle {
|
|
||||||
background-color: var(--UI-Input-Controls-Fill-Selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper[data-available="false"] .circle {
|
.wrapper[data-available="false"] .circle {
|
||||||
background-color: var(--Base-Surface-Subtle-Hover);
|
background-color: var(--Base-Surface-Subtle-Hover);
|
||||||
}
|
}
|
||||||
@@ -92,14 +74,4 @@
|
|||||||
.rate::after {
|
.rate::after {
|
||||||
content: ")";
|
content: ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper:not(:last-child)::after {
|
|
||||||
position: absolute;
|
|
||||||
left: 12px;
|
|
||||||
bottom: 0;
|
|
||||||
top: var(--Spacing-x7);
|
|
||||||
height: 100%;
|
|
||||||
content: "";
|
|
||||||
border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,21 @@ import { RoomContext } from "@/contexts/Details/Room"
|
|||||||
import type { RoomProviderProps } from "@/types/providers/details/room"
|
import type { RoomProviderProps } from "@/types/providers/details/room"
|
||||||
|
|
||||||
export default function RoomProvider({ children, idx }: RoomProviderProps) {
|
export default function RoomProvider({ children, idx }: RoomProviderProps) {
|
||||||
const { actions, activeRoom, currentStep, isComplete, room, steps } =
|
const { actions, isComplete, room, steps } = useEnterDetailsStore(
|
||||||
useEnterDetailsStore((state) => ({
|
(state) => ({
|
||||||
actions: state.rooms[idx].actions,
|
actions: state.rooms[idx].actions,
|
||||||
activeRoom: state.activeRoom,
|
|
||||||
currentStep: state.rooms[idx].currentStep,
|
|
||||||
isComplete: state.rooms[idx].isComplete,
|
isComplete: state.rooms[idx].isComplete,
|
||||||
room: state.rooms[idx].room,
|
room: state.rooms[idx].room,
|
||||||
steps: state.rooms[idx].steps,
|
steps: state.rooms[idx].steps,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomContext.Provider
|
<RoomContext.Provider
|
||||||
value={{
|
value={{
|
||||||
actions,
|
actions,
|
||||||
currentStep,
|
idx,
|
||||||
isComplete,
|
isComplete,
|
||||||
isActiveRoom: activeRoom === idx,
|
|
||||||
room,
|
room,
|
||||||
roomNr: idx + 1,
|
roomNr: idx + 1,
|
||||||
steps,
|
steps,
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ export default function EnterDetailsProvider({
|
|||||||
)
|
)
|
||||||
|
|
||||||
currentRoom.isComplete = !invalidStep
|
currentRoom.isComplete = !invalidStep
|
||||||
currentRoom.currentStep = invalidStep ? invalidStep.step : null
|
|
||||||
|
|
||||||
return currentRoom
|
return currentRoom
|
||||||
})
|
})
|
||||||
@@ -185,18 +184,12 @@ export default function EnterDetailsProvider({
|
|||||||
nights
|
nights
|
||||||
)
|
)
|
||||||
|
|
||||||
const activeRoom = filteredOutMissingRooms.findIndex(
|
|
||||||
(room) => !room.isComplete
|
|
||||||
)
|
|
||||||
|
|
||||||
writeToSessionStorage({
|
writeToSessionStorage({
|
||||||
activeRoom,
|
|
||||||
booking,
|
booking,
|
||||||
rooms: filteredOutMissingRooms,
|
rooms: filteredOutMissingRooms,
|
||||||
})
|
})
|
||||||
|
|
||||||
storeRef.current?.setState({
|
storeRef.current?.setState({
|
||||||
activeRoom: storedValues.activeRoom,
|
|
||||||
canProceedToPayment,
|
canProceedToPayment,
|
||||||
rooms: filteredOutMissingRooms,
|
rooms: filteredOutMissingRooms,
|
||||||
totalPrice,
|
totalPrice,
|
||||||
|
|||||||
@@ -7,11 +7,7 @@ import type { Price } from "@/types/components/hotelReservation/price"
|
|||||||
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
import { CurrencyEnum } from "@/types/enums/currency"
|
import { CurrencyEnum } from "@/types/enums/currency"
|
||||||
import { StepEnum } from "@/types/enums/step"
|
import { StepEnum } from "@/types/enums/step"
|
||||||
import type {
|
import type { PersistedState, RoomState } from "@/types/stores/enter-details"
|
||||||
DetailsState,
|
|
||||||
PersistedState,
|
|
||||||
RoomState,
|
|
||||||
} from "@/types/stores/enter-details"
|
|
||||||
import type { SafeUser } from "@/types/user"
|
import type { SafeUser } from "@/types/user"
|
||||||
|
|
||||||
export function extractGuestFromUser(user: NonNullable<SafeUser>) {
|
export function extractGuestFromUser(user: NonNullable<SafeUser>) {
|
||||||
@@ -513,54 +509,12 @@ export function findNextInvalidStep(roomState: RoomState) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const selectNextStep = (room: RoomState) => {
|
|
||||||
if (room.currentStep === null) {
|
|
||||||
throw new Error("getNextStep: currentStep is null")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!room.steps[room.currentStep]?.isValid) {
|
|
||||||
return room.currentStep
|
|
||||||
}
|
|
||||||
|
|
||||||
const stepsArray = Object.values(room.steps)
|
|
||||||
const currentIndex = stepsArray.findIndex(
|
|
||||||
(step) => step?.step === room.currentStep
|
|
||||||
)
|
|
||||||
if (currentIndex === stepsArray.length - 1) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextInvalidStep = stepsArray
|
|
||||||
.slice(currentIndex + 1)
|
|
||||||
.find((step) => !step.isValid)
|
|
||||||
|
|
||||||
return nextInvalidStep?.step ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const checkRoomProgress = (steps: RoomState["steps"]) => {
|
export const checkRoomProgress = (steps: RoomState["steps"]) => {
|
||||||
return Object.values(steps)
|
return Object.values(steps)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.every((step) => step.isValid)
|
.every((step) => step.isValid)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleStepProgression(room: RoomState, state: DetailsState) {
|
|
||||||
const isAllRoomsCompleted = state.rooms.every((r) => r.isComplete)
|
|
||||||
if (isAllRoomsCompleted) {
|
|
||||||
room.currentStep = null
|
|
||||||
state.canProceedToPayment = true
|
|
||||||
} else if (room.isComplete) {
|
|
||||||
room.currentStep = null
|
|
||||||
const nextRoomIndex = state.rooms.findIndex((r) => !r.isComplete)
|
|
||||||
state.activeRoom = nextRoomIndex
|
|
||||||
|
|
||||||
const nextRoom = state.rooms[nextRoomIndex]
|
|
||||||
const nextStep = selectNextStep(nextRoom)
|
|
||||||
nextRoom.currentStep = nextStep
|
|
||||||
} else if (selectNextStep(room)) {
|
|
||||||
room.currentStep = selectNextStep(room)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readFromSessionStorage(): PersistedState | undefined {
|
export function readFromSessionStorage(): PersistedState | undefined {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return undefined
|
return undefined
|
||||||
|
|||||||
@@ -15,10 +15,8 @@ import {
|
|||||||
calculateVoucherPrice,
|
calculateVoucherPrice,
|
||||||
checkRoomProgress,
|
checkRoomProgress,
|
||||||
extractGuestFromUser,
|
extractGuestFromUser,
|
||||||
findNextInvalidStep,
|
|
||||||
getRoomPrice,
|
getRoomPrice,
|
||||||
getTotalPrice,
|
getTotalPrice,
|
||||||
handleStepProgression,
|
|
||||||
writeToSessionStorage,
|
writeToSessionStorage,
|
||||||
} from "./helpers"
|
} from "./helpers"
|
||||||
|
|
||||||
@@ -114,7 +112,6 @@ export function createDetailsStore(
|
|||||||
})
|
})
|
||||||
|
|
||||||
return create<DetailsState>()((set) => ({
|
return create<DetailsState>()((set) => ({
|
||||||
activeRoom: 0,
|
|
||||||
booking: initialState.booking,
|
booking: initialState.booking,
|
||||||
breakfastPackages,
|
breakfastPackages,
|
||||||
canProceedToPayment: false,
|
canProceedToPayment: false,
|
||||||
@@ -141,72 +138,8 @@ export function createDetailsStore(
|
|||||||
delete steps[StepEnum.breakfast]
|
delete steps[StepEnum.breakfast]
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentStep =
|
|
||||||
Object.values(steps).find((step) => !step.isValid)?.step ??
|
|
||||||
StepEnum.selectBed
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actions: {
|
actions: {
|
||||||
setStep(step) {
|
|
||||||
return set(
|
|
||||||
produce((state: DetailsState) => {
|
|
||||||
const isSameRoom = idx === state.activeRoom
|
|
||||||
const room = state.rooms[idx]
|
|
||||||
if (isSameRoom) {
|
|
||||||
// Closed same accordion as was open
|
|
||||||
if (step === room.currentStep) {
|
|
||||||
if (room.isComplete) {
|
|
||||||
// Room is complete, move to next room or payment
|
|
||||||
const nextRoomIdx = state.rooms.findIndex(
|
|
||||||
(r) => !r.isComplete
|
|
||||||
)
|
|
||||||
state.activeRoom = nextRoomIdx
|
|
||||||
// Done, proceed to payment
|
|
||||||
if (nextRoomIdx === -1) {
|
|
||||||
room.currentStep = null
|
|
||||||
} else {
|
|
||||||
const nextRoom = state.rooms[nextRoomIdx]
|
|
||||||
const nextInvalidStep = findNextInvalidStep(nextRoom)
|
|
||||||
nextRoom.currentStep = nextInvalidStep
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
room.currentStep = findNextInvalidStep(room)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (room.steps[step]?.isValid) {
|
|
||||||
room.currentStep = step
|
|
||||||
} else {
|
|
||||||
room.currentStep = findNextInvalidStep(room)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const arePreviousRoomsCompleted = state.rooms
|
|
||||||
.slice(0, idx)
|
|
||||||
.every((room) => room.isComplete)
|
|
||||||
if (arePreviousRoomsCompleted) {
|
|
||||||
state.activeRoom = idx
|
|
||||||
if (room.steps[step]?.isValid) {
|
|
||||||
room.currentStep = step
|
|
||||||
} else {
|
|
||||||
room.currentStep = findNextInvalidStep(room)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const firstIncompleteRoom = state.rooms.findIndex(
|
|
||||||
(r) => !r.isComplete
|
|
||||||
)
|
|
||||||
state.activeRoom = firstIncompleteRoom
|
|
||||||
if (firstIncompleteRoom === -1) {
|
|
||||||
// All rooms are done, proceed to payment
|
|
||||||
room.currentStep = null
|
|
||||||
} else {
|
|
||||||
const nextRoom = state.rooms[firstIncompleteRoom]
|
|
||||||
nextRoom.currentStep = findNextInvalidStep(nextRoom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
|
||||||
updateBedType(bedType) {
|
updateBedType(bedType) {
|
||||||
return set(
|
return set(
|
||||||
produce((state: DetailsState) => {
|
produce((state: DetailsState) => {
|
||||||
@@ -220,10 +153,7 @@ export function createDetailsStore(
|
|||||||
state.rooms[idx].isComplete = true
|
state.rooms[idx].isComplete = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStepProgression(state.rooms[idx], state)
|
|
||||||
|
|
||||||
writeToSessionStorage({
|
writeToSessionStorage({
|
||||||
activeRoom: state.activeRoom,
|
|
||||||
booking: state.booking,
|
booking: state.booking,
|
||||||
rooms: state.rooms,
|
rooms: state.rooms,
|
||||||
})
|
})
|
||||||
@@ -340,10 +270,7 @@ export function createDetailsStore(
|
|||||||
state.rooms[idx].isComplete = true
|
state.rooms[idx].isComplete = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStepProgression(currentRoom, state)
|
|
||||||
|
|
||||||
writeToSessionStorage({
|
writeToSessionStorage({
|
||||||
activeRoom: state.activeRoom,
|
|
||||||
booking: state.booking,
|
booking: state.booking,
|
||||||
rooms: state.rooms,
|
rooms: state.rooms,
|
||||||
})
|
})
|
||||||
@@ -408,10 +335,7 @@ export function createDetailsStore(
|
|||||||
state.rooms[idx].isComplete = true
|
state.rooms[idx].isComplete = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStepProgression(state.rooms[idx], state)
|
|
||||||
|
|
||||||
writeToSessionStorage({
|
writeToSessionStorage({
|
||||||
activeRoom: state.activeRoom,
|
|
||||||
booking: state.booking,
|
booking: state.booking,
|
||||||
rooms: state.rooms,
|
rooms: state.rooms,
|
||||||
})
|
})
|
||||||
@@ -461,10 +385,7 @@ export function createDetailsStore(
|
|||||||
state.rooms[idx].isComplete = true
|
state.rooms[idx].isComplete = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStepProgression(state.rooms[idx], state)
|
|
||||||
|
|
||||||
writeToSessionStorage({
|
writeToSessionStorage({
|
||||||
activeRoom: state.activeRoom,
|
|
||||||
booking: state.booking,
|
booking: state.booking,
|
||||||
rooms: state.rooms,
|
rooms: state.rooms,
|
||||||
})
|
})
|
||||||
@@ -490,8 +411,6 @@ export function createDetailsStore(
|
|||||||
comment: "",
|
comment: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
currentStep,
|
|
||||||
isComplete: false,
|
isComplete: false,
|
||||||
steps,
|
steps,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { StepEnum } from "@/types/enums/step"
|
import type { StepEnum } from "@/types/enums/step"
|
||||||
|
|
||||||
export interface SectionAccordionProps {
|
export interface SectionProps {
|
||||||
header: string
|
header: string
|
||||||
label: string
|
label: string
|
||||||
step: StepEnum
|
step: StepEnum
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
|
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
|
||||||
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
|
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
|
||||||
import type { DetailsSchema } from "@/types/components/hotelReservation/enterDetails/details"
|
import type { DetailsSchema } from "@/types/components/hotelReservation/enterDetails/details"
|
||||||
import type { StepEnum } from "@/types/enums/step"
|
|
||||||
import type { RoomState } from "@/types/stores/enter-details"
|
import type { RoomState } from "@/types/stores/enter-details"
|
||||||
|
|
||||||
export interface RoomContextValue {
|
export interface RoomContextValue {
|
||||||
actions: {
|
actions: {
|
||||||
setStep: (step: StepEnum) => void
|
|
||||||
updateBedType: (data: BedTypeSchema) => void
|
updateBedType: (data: BedTypeSchema) => void
|
||||||
updateBreakfast: (data: BreakfastPackage | false) => void
|
updateBreakfast: (data: BreakfastPackage | false) => void
|
||||||
updateDetails: (data: DetailsSchema) => void
|
updateDetails: (data: DetailsSchema) => void
|
||||||
}
|
}
|
||||||
currentStep: RoomState["currentStep"]
|
|
||||||
isComplete: RoomState["isComplete"]
|
isComplete: RoomState["isComplete"]
|
||||||
isActiveRoom: boolean
|
idx: number
|
||||||
room: RoomState["room"]
|
room: RoomState["room"]
|
||||||
roomNr: number
|
roomNr: number
|
||||||
steps: RoomState["steps"]
|
steps: RoomState["steps"]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import { createDetailsStore } from "@/stores/enter-details"
|
import type { createDetailsStore } from "@/stores/enter-details"
|
||||||
|
|
||||||
export type DetailsStore = ReturnType<typeof createDetailsStore>
|
export type DetailsStore = ReturnType<typeof createDetailsStore>
|
||||||
|
|||||||
@@ -59,13 +59,11 @@ export interface Room extends InitialRoomData {
|
|||||||
|
|
||||||
export interface RoomState {
|
export interface RoomState {
|
||||||
actions: {
|
actions: {
|
||||||
setStep: (step: StepEnum) => void
|
|
||||||
updateBedType: (data: BedTypeSchema) => void
|
updateBedType: (data: BedTypeSchema) => void
|
||||||
updateBreakfast: (data: BreakfastPackage | false) => void
|
updateBreakfast: (data: BreakfastPackage | false) => void
|
||||||
updateDetails: (data: DetailsSchema) => void
|
updateDetails: (data: DetailsSchema) => void
|
||||||
updateMultiroomDetails: (data: MultiroomDetailsSchema) => void
|
updateMultiroomDetails: (data: MultiroomDetailsSchema) => void
|
||||||
}
|
}
|
||||||
currentStep: StepEnum | null
|
|
||||||
isComplete: boolean
|
isComplete: boolean
|
||||||
room: Room
|
room: Room
|
||||||
steps: {
|
steps: {
|
||||||
@@ -88,7 +86,6 @@ export interface DetailsState {
|
|||||||
toggleSummaryOpen: () => void
|
toggleSummaryOpen: () => void
|
||||||
updateSeachParamString: (searchParamString: string) => void
|
updateSeachParamString: (searchParamString: string) => void
|
||||||
}
|
}
|
||||||
activeRoom: number
|
|
||||||
booking: SelectRateSearchParams
|
booking: SelectRateSearchParams
|
||||||
breakfastPackages: BreakfastPackages | null
|
breakfastPackages: BreakfastPackages | null
|
||||||
canProceedToPayment: boolean
|
canProceedToPayment: boolean
|
||||||
@@ -102,7 +99,6 @@ export interface DetailsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PersistedState = {
|
export type PersistedState = {
|
||||||
activeRoom: number
|
|
||||||
booking: SelectRateSearchParams
|
booking: SelectRateSearchParams
|
||||||
rooms: RoomState[]
|
rooms: RoomState[]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user