Merged in feat/enter-details-multiroom (pull request #1280)

feat(SW-1259): Enter details multiroom

* refactor: remove per-step URLs

* WIP: map multiroom data

* fix: lint errors in details page

* fix: made useEnterDetailsStore tests pass

* fix: WIP refactor enter details store

* fix: WIP enter details store update

* fix: added room index to select correct room

* fix: added logic for navigating between steps and rooms

* fix: update summary to work with store changes

* fix: added room and total price calculation

* fix: removed unused code and added test for breakfast included

* refactor: move store selectors into helpers

* refactor: session storage state for multiroom booking

* feat: update enter details accordion navigation

* fix: added room index to each form component so they select correct room

* fix: added unique id to input to handle case when multiple inputs have same name

* fix: update payment step with store changes

* fix: rebase issues

* fix: now you should only be able to go to a step if previous room is completed

* refactor: cleanup

* fix: if no availability just skip that room for now

* fix: add select-rate Summary and adjust typings


Approved-by: Arvid Norlin
This commit is contained in:
Tobias Johansson
2025-02-11 14:24:24 +00:00
committed by Arvid Norlin
parent f43ee4a0e6
commit b394d54c3f
48 changed files with 1870 additions and 1150 deletions

View File

@@ -3,8 +3,14 @@ import { useEffect, useState } from "react"
import { useIntl } from "react-intl"
import { useEnterDetailsStore } from "@/stores/enter-details"
import {
selectBookingProgress,
selectNextStep,
selectRoom,
selectRoomStatus,
} from "@/stores/enter-details/helpers"
import { CheckIcon, ChevronDownIcon } from "@/components/Icons"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "@/components/Icons"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useScrollToActiveSection from "@/hooks/booking/useScrollToActiveSection"
@@ -19,24 +25,31 @@ export default function SectionAccordion({
header,
label,
step,
roomIndex,
}: React.PropsWithChildren<SectionAccordionProps>) {
const intl = useIntl()
const currentStep = useEnterDetailsStore((state) => state.currentStep)
const steps = useEnterDetailsStore((state) => state.steps)
const roomStatus = useEnterDetailsStore((state) =>
selectRoomStatus(state, roomIndex)
)
const setStep = useEnterDetailsStore((state) => state.actions.setStep)
const { bedType, breakfast } = useEnterDetailsStore((state) =>
selectRoom(state, roomIndex)
)
const { roomStatuses, currentRoomIndex } = useEnterDetailsStore((state) =>
selectBookingProgress(state)
)
const [isComplete, setIsComplete] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const isValid = useEnterDetailsStore((state) => state.isValid[step])
const navigate = useEnterDetailsStore((state) => state.actions.navigate)
const { bedType, breakfast } = useEnterDetailsStore((state) => ({
bedType: state.bedType,
breakfast: state.breakfast,
}))
const isValid = roomStatus.steps[step]?.isValid ?? 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)
// useScrollToActiveSection(step, steps, roomStatus.currentStep === step)
useEffect(() => {
if (step === StepEnum.selectBed && bedType) {
@@ -57,11 +70,29 @@ export default function SectionAccordion({
}, [isValid, setIsComplete])
useEffect(() => {
setIsOpen(currentStep === step)
}, [currentStep, setIsOpen, step])
setIsOpen(roomStatus.currentStep === step && currentRoomIndex === roomIndex)
}, [currentRoomIndex, roomIndex, roomStatus.currentStep, setIsOpen, step])
function onModify() {
navigate(step)
setStep(step, roomIndex)
}
function close() {
setIsOpen(false)
const isLastStep = step === StepEnum.details
const hasNextRoom = roomIndex + 1 <= roomStatuses.length
if (!isLastStep) {
const nextStep = selectNextStep(roomStatus)
if (nextStep) {
setStep(nextStep, roomIndex)
}
} else if (isLastStep && hasNextRoom) {
setStep(StepEnum.selectBed, roomIndex + 1)
} else {
// Time for payment, collapse any open step
setStep(null)
}
}
const textColor =
@@ -81,7 +112,7 @@ export default function SectionAccordion({
</div>
<header className={styles.header}>
<button
onClick={onModify}
onClick={isOpen ? close : onModify}
disabled={!isComplete}
className={styles.modifyButton}
>
@@ -97,9 +128,11 @@ export default function SectionAccordion({
<Subtitle className={styles.selection} type="two" color={textColor}>
{title}
</Subtitle>
{isComplete && !isOpen && (
<ChevronDownIcon className={styles.button} color="burgundy" />
{isComplete && (
<ChevronDownIcon
className={`${styles.button} ${isOpen ? styles.buttonOpen : ""}`}
color="burgundy"
/>
)}
</button>
</header>

View File

@@ -16,10 +16,6 @@
transform-origin: top;
}
.accordion:last-child {
border-bottom: none;
}
.header {
grid-area: header;
}
@@ -46,8 +42,13 @@
.button {
grid-area: button;
justify-self: flex-end;
transform-origin: 50% 50%;
transition: transform 0.3s;
}
.buttonOpen {
transform: rotate(180deg);
}
.selection {
grid-area: selection;
}
@@ -85,22 +86,21 @@
}
.contentWrapper {
opacity: 0;
padding-bottom: var(--Spacing-x3);
}
.accordion[data-section-open="true"] .contentWrapper {
opacity: 1;
}
.content {
overflow: hidden;
grid-area: content;
opacity: 0;
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 {
opacity: 1;
}
.content:has([data-section-open="true"]) {
overflow: visible;
}