Merged in feat/sw-2879-booking-widget-to-booking-flow-package (pull request #2532)

feat(SW-2879): Move BookingWidget to booking-flow package

* Fix lockfile

* Fix styling

* a tiny little booking widget test

* Tiny fixes

* Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package

* Remove unused scripts

* lint:fix

* Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package

* Tiny lint fixes

* update test

* Update Input in booking-flow

* Clean up comments etc

* Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package

* Setup tracking context for booking-flow

* Add missing use client

* Fix temp tracking function

* Pass booking to booking-widget

* Remove comment

* Add use client to booking widget tracking provider

* Add use client to tracking functions

* Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package

* Move debug page

* Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package

* Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package

* Merge branch 'master' into feat/sw-2879-booking-widget-to-booking-flow-package


Approved-by: Bianca Widstam
This commit is contained in:
Anton Gunnarsson
2025-08-05 09:20:20 +00:00
parent 03c9244fdf
commit 1bd8fe6821
206 changed files with 1936 additions and 796 deletions

View File

@@ -0,0 +1,157 @@
"use client"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import Caption from "@scandic-hotels/design-system/Caption"
import DeprecatedSelect from "@scandic-hotels/design-system/DeprecatedSelect"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
import styles from "./child-selector.module.css"
import type { Child } from "@scandic-hotels/trpc/types/child"
const ageList = [...Array(13)].map((_, i) => ({
label: i.toString(),
value: i,
}))
const childDefaultValues = { age: -1, bed: -1 }
type ChildBed = {
label: string
value: number
}
type ChildInfoSelectorProps = {
child: Child
adults: number
index: number
roomIndex: number
childrenInAdultsBed: number
}
export default function ChildInfoSelector({
child,
childrenInAdultsBed,
adults,
index = 0,
roomIndex = 0,
}: ChildInfoSelectorProps) {
const ageFieldName = `rooms.${roomIndex}.childrenInRoom.${index}.age`
const bedFieldName = `rooms.${roomIndex}.childrenInRoom.${index}.bed`
const intl = useIntl()
const ageLabel = intl.formatMessage({
defaultMessage: "Age",
})
const bedLabel = intl.formatMessage({
defaultMessage: "Bed preference",
})
const errorMessage = intl.formatMessage({
defaultMessage: "Child age is required",
})
const { setValue, formState } = useFormContext()
function updateSelectedBed(bed: number) {
setValue(`rooms.${roomIndex}.childrenInRoom.${index}.bed`, bed)
}
function updateSelectedAge(age: number) {
setValue(`rooms.${roomIndex}.childrenInRoom.${index}.age`, age)
const availableBedTypes = getAvailableBeds(age)
updateSelectedBed(availableBedTypes[0].value)
}
const allBedTypes: ChildBed[] = [
{
label: intl.formatMessage({
defaultMessage: "In adult's bed",
}),
value: ChildBedMapEnum.IN_ADULTS_BED,
},
{
label: intl.formatMessage({
defaultMessage: "In crib",
}),
value: ChildBedMapEnum.IN_CRIB,
},
{
label: intl.formatMessage({
defaultMessage: "In extra bed",
}),
value: ChildBedMapEnum.IN_EXTRA_BED,
},
]
function getAvailableBeds(age: number) {
let availableBedTypes: ChildBed[] = []
if (age <= 5 && (adults > childrenInAdultsBed || child.bed === 0)) {
availableBedTypes.push(allBedTypes[0])
}
if (age < 3) {
availableBedTypes.push(allBedTypes[1])
}
if (age > 2) {
availableBedTypes.push(allBedTypes[2])
}
return availableBedTypes
}
const roomErrors =
//@ts-expect-error: formState is typed with FormValues
formState.errors.rooms?.[roomIndex]?.childrenInRoom?.[index]
const ageError = roomErrors?.age
const bedError = roomErrors?.bed
return (
<>
<div key={index} className={styles.childInfoContainer}>
<div>
<DeprecatedSelect
required={true}
items={ageList}
label={ageLabel}
aria-label={ageLabel}
value={child.age ?? childDefaultValues.age}
onSelect={(key) => {
updateSelectedAge(key as number)
}}
maxHeight={180}
name={ageFieldName}
isNestedInModal={true}
/>
</div>
<div>
{child.age >= 0 ? (
<DeprecatedSelect
items={getAvailableBeds(child.age)}
label={bedLabel}
aria-label={bedLabel}
value={child.bed ?? childDefaultValues.bed}
onSelect={(key) => {
updateSelectedBed(key as number)
}}
name={bedFieldName}
isNestedInModal={true}
/>
) : null}
</div>
</div>
{roomErrors && roomErrors.message ? (
<Caption color="red" className={styles.error}>
<MaterialIcon icon="error" color="Icon/Interactive/Accent" />
{roomErrors.message}
</Caption>
) : null}
{ageError || bedError ? (
<Caption color="red" className={styles.error}>
<MaterialIcon icon="error" color="Icon/Interactive/Accent" />
{errorMessage}
</Caption>
) : null}
</>
)
}

View File

@@ -0,0 +1,21 @@
.container {
display: flex;
justify-content: space-between;
align-items: center;
}
.captionBold {
font-weight: 600;
}
.childInfoContainer {
display: grid;
gap: var(--Spacing-x2);
grid-template-columns: 1fr 2fr;
}
.error {
display: flex;
align-items: center;
gap: var(--Spacing-x1);
}

View File

@@ -0,0 +1,84 @@
"use client"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import Caption from "@scandic-hotels/design-system/Caption"
import Counter from "../Counter"
import ChildInfoSelector from "./ChildInfoSelector"
import styles from "./child-selector.module.css"
import type { Child } from "@scandic-hotels/trpc/types/child"
type ChildSelectorProps = {
roomIndex: number
currentAdults: number
currentChildren: Child[]
childrenInAdultsBed: number
}
export default function ChildSelector({
roomIndex = 0,
currentAdults,
childrenInAdultsBed,
currentChildren,
}: ChildSelectorProps) {
const intl = useIntl()
const childrenLabel = intl.formatMessage({
defaultMessage: "Children",
})
const { setValue } = useFormContext()
function increaseChildrenCount(roomIndex: number) {
if (currentChildren.length < 5) {
setValue(
`rooms.${roomIndex}.childrenInRoom.${currentChildren.length}`,
{
age: undefined,
bed: undefined,
},
{ shouldDirty: true }
)
}
}
function decreaseChildrenCount(roomIndex: number) {
if (currentChildren.length > 0) {
currentChildren.pop()
setValue(`rooms.${roomIndex}.childrenInRoom`, currentChildren, {
shouldDirty: true,
})
}
}
return (
<>
<section className={styles.container}>
<Caption color="uiTextHighContrast" type="bold">
{childrenLabel}
</Caption>
<Counter
count={currentChildren.length}
handleOnDecrease={() => {
decreaseChildrenCount(roomIndex)
}}
handleOnIncrease={() => {
increaseChildrenCount(roomIndex)
}}
disableDecrease={currentChildren.length == 0}
disableIncrease={currentChildren.length == 5}
/>
</section>
{currentChildren.map((child, index) => (
<ChildInfoSelector
roomIndex={roomIndex}
index={index}
child={child}
adults={currentAdults}
key={"child_" + index}
childrenInAdultsBed={childrenInAdultsBed}
/>
))}
</>
)
}