Merged in feat/SW-1076-no-room-availability (pull request #1467)

Feat/SW-1076 no room availability

* fix: update booking error codes

* feat(SW-1076): handle no room availabilty on enter-details

* fix: parse to json in api mutation instead of expecting json

* fix: remove 'isComplete' state from sectionAccordion because it was not needed


Approved-by: Simon.Emanuelsson
This commit is contained in:
Tobias Johansson
2025-03-10 12:13:15 +00:00
parent 131cbfcda3
commit 7c233ab846
23 changed files with 139 additions and 63 deletions

View File

@@ -7,6 +7,7 @@ import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import {
BookingErrorCodeEnum,
BookingStatusEnum,
PAYMENT_METHOD_TITLES,
PaymentMethodEnum,
@@ -107,6 +108,15 @@ export default function PaymentClient({
const initiateBooking = trpc.booking.create.useMutation({
onSuccess: (result) => {
if (result) {
if ("error" in result) {
if (result.cause === BookingErrorCodeEnum.AvailabilityError) {
window.location.reload() // reload to refetch room data because we dont know which room is unavailable
} else {
handlePaymentError(result.cause)
}
return
}
setBookingNumber(result.id)
const priceChange = result.rooms.find(

View File

@@ -25,14 +25,12 @@ export default function SectionAccordion({
actions: { setStep },
currentStep,
isActiveRoom,
room: { bedType, breakfast },
room: { bedType, breakfast, isAvailable },
steps,
} = useRoomContext()
const [isComplete, setIsComplete] = useState(false)
const isStepComplete = !!(steps[step]?.isValid && isAvailable)
const [isOpen, setIsOpen] = useState(false)
const isValid = steps[step]?.isValid ?? false
const [title, setTitle] = useState(label)
const noBreakfastTitle = intl.formatMessage({ id: "No breakfast" })
@@ -54,14 +52,10 @@ export default function SectionAccordion({
}
}, [bedType, breakfast, setTitle, step, breakfastTitle, noBreakfastTitle])
useEffect(() => {
setIsComplete(isValid)
}, [isValid, setIsComplete])
const accordionRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const shouldBeOpen = currentStep === step && isActiveRoom
const shouldBeOpen = currentStep === step && isActiveRoom && isAvailable
setIsOpen(shouldBeOpen)
// Scroll to this section when it is opened,
@@ -91,7 +85,7 @@ export default function SectionAccordion({
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentStep, isActiveRoom, setIsOpen, step])
}, [currentStep, isActiveRoom, isAvailable, setIsOpen, step])
function goToStep() {
setStep(step)
@@ -103,7 +97,7 @@ export default function SectionAccordion({
}
const textColor =
isComplete || isOpen ? "uiTextHighContrast" : "baseTextDisabled"
isStepComplete || isOpen ? "uiTextHighContrast" : "baseTextDisabled"
return (
<div
className={styles.accordion}
@@ -112,8 +106,8 @@ export default function SectionAccordion({
ref={accordionRef}
>
<div className={styles.iconWrapper}>
<div className={styles.circle} data-checked={isComplete}>
{isComplete ? (
<div className={styles.circle} data-checked={isStepComplete}>
{isStepComplete ? (
<CheckIcon color="white" height="16" width="16" />
) : null}
</div>
@@ -121,7 +115,7 @@ export default function SectionAccordion({
<header className={styles.header}>
<button
onClick={isOpen ? close : goToStep}
disabled={!isComplete}
disabled={!isStepComplete}
className={styles.modifyButton}
>
<Footnote
@@ -136,7 +130,7 @@ export default function SectionAccordion({
<Subtitle className={styles.selection} type="two" color={textColor}>
{title}
</Subtitle>
{isComplete && (
{isStepComplete && (
<ChevronDownIcon
className={`${styles.button} ${isOpen ? styles.buttonOpen : ""}`}
color="burgundy"

View File

@@ -9,6 +9,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details"
import { CheckIcon, EditIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { useRoomContext } from "@/contexts/Details/Room"
@@ -39,7 +40,7 @@ export default function SelectedRoom() {
}
return (
<div className={styles.wrapper}>
<div className={styles.wrapper} data-available={room.isAvailable}>
<div className={styles.iconWrapper}>
<div className={styles.circle}>
<CheckIcon color="white" height="16" width="16" />
@@ -74,13 +75,15 @@ export default function SelectedRoom() {
</Subtitle>
<Button
variant="icon"
intent="text"
size="small"
color="burgundy"
onClick={changeRoom}
disabled={isPending}
>
<EditIcon color="burgundy" />
{intl.formatMessage({ id: "Change room" })}
<Caption color="burgundy" type="bold">
{intl.formatMessage({ id: "Change room" })}
</Caption>
</Button>
</div>
{room.roomTypeCode && (

View File

@@ -5,6 +5,13 @@
gap: var(--Spacing-x-one-and-half);
}
.wrapper[data-available="false"] .title,
.wrapper[data-available="false"] .description,
.wrapper[data-available="false"] .details {
opacity: 0.5;
pointer-events: none;
}
.main {
width: 100%;
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
@@ -52,6 +59,10 @@
background-color: var(--UI-Input-Controls-Fill-Selected);
}
.wrapper[data-available="false"] .circle {
background-color: var(--Base-Surface-Subtle-Hover);
}
.rate {
color: var(--UI-Text-Placeholder);
display: block;

View File

@@ -381,7 +381,7 @@ export default function SummaryUI({
totalPrice.local.currency
)}
</Body>
{totalPrice.local.regularPrice && (
{totalPrice.local.regularPrice ? (
<Caption color="uiTextMediumContrast" striked={true}>
{formatPrice(
intl,
@@ -389,7 +389,7 @@ export default function SummaryUI({
totalPrice.local.currency
)}
</Caption>
)}
) : null}
{totalPrice.requested && (
<Caption color="uiTextMediumContrast">
{intl.formatMessage(

View File

@@ -63,6 +63,7 @@ const rooms: RoomState[] = [
roomRate: roomRate,
roomType: "Standard",
roomTypeCode: "QS",
isAvailable: true,
},
steps: {
[StepEnum.selectBed]: {
@@ -100,6 +101,7 @@ const rooms: RoomState[] = [
roomRate: roomRate,
roomType: "Standard",
roomTypeCode: "QS",
isAvailable: true,
},
steps: {
[StepEnum.selectBed]: {