feat: get breakfast package from API

This commit is contained in:
Simon Emanuelsson
2024-10-28 10:12:03 +01:00
parent fc8844eb96
commit 62f549e85d
47 changed files with 718 additions and 210 deletions

View File

@@ -15,7 +15,7 @@ import { bedTypeSchema } from "./schema"
import styles from "./bedOptions.module.css"
import type { BedTypeSchema } from "@/types/components/enterDetails/bedType"
import { bedTypeEnum } from "@/types/enums/bedType"
import { BedTypeEnum } from "@/types/enums/bedType"
export default function BedType() {
const intl = useIntl()
@@ -61,7 +61,7 @@ export default function BedType() {
<RadioCard
Icon={KingBedIcon}
iconWidth={46}
id={bedTypeEnum.KING}
id={BedTypeEnum.KING}
name="bedType"
subtitle={intl.formatMessage(
{ id: "{width} cm × {length} cm" },
@@ -72,12 +72,12 @@ export default function BedType() {
)}
text={text}
title={intl.formatMessage({ id: "King bed" })}
value={bedTypeEnum.KING}
value={BedTypeEnum.KING}
/>
<RadioCard
Icon={KingBedIcon}
iconWidth={46}
id={bedTypeEnum.QUEEN}
id={BedTypeEnum.QUEEN}
name="bedType"
subtitle={intl.formatMessage(
{ id: "{width} cm × {length} cm" },
@@ -88,7 +88,7 @@ export default function BedType() {
)}
text={text}
title={intl.formatMessage({ id: "Queen bed" })}
value={bedTypeEnum.QUEEN}
value={BedTypeEnum.QUEEN}
/>
</form>
</FormProvider>

View File

@@ -1,7 +1,7 @@
import { z } from "zod"
import { bedTypeEnum } from "@/types/enums/bedType"
import { BedTypeEnum } from "@/types/enums/bedType"
export const bedTypeSchema = z.object({
bedType: z.nativeEnum(bedTypeEnum),
bedType: z.nativeEnum(BedTypeEnum),
})

View File

@@ -7,36 +7,50 @@ import { useIntl } from "react-intl"
import { useEnterDetailsStore } from "@/stores/enter-details"
import { BreakfastIcon, NoBreakfastIcon } from "@/components/Icons"
import { Highlight } from "@/components/TempDesignSystem/Form/ChoiceCard/_Card"
import RadioCard from "@/components/TempDesignSystem/Form/ChoiceCard/Radio"
import { breakfastSchema } from "./schema"
import { breakfastFormSchema } from "./schema"
import styles from "./breakfast.module.css"
import type { BreakfastSchema } from "@/types/components/enterDetails/breakfast"
import { breakfastEnum } from "@/types/enums/breakfast"
import type {
BreakfastFormSchema,
BreakfastProps,
} from "@/types/components/enterDetails/breakfast"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
export default function Breakfast() {
export default function Breakfast({ packages }: BreakfastProps) {
const intl = useIntl()
const breakfast = useEnterDetailsStore((state) => state.data.breakfast)
const methods = useForm<BreakfastSchema>({
defaultValues: breakfast ? { breakfast } : undefined,
let defaultValues = undefined
if (breakfast === BreakfastPackageEnum.NO_BREAKFAST) {
defaultValues = { breakfast: BreakfastPackageEnum.NO_BREAKFAST }
} else if (breakfast?.code) {
defaultValues = { breakfast: breakfast.code }
}
const methods = useForm<BreakfastFormSchema>({
defaultValues,
criteriaMode: "all",
mode: "all",
resolver: zodResolver(breakfastSchema),
resolver: zodResolver(breakfastFormSchema),
reValidateMode: "onChange",
})
const completeStep = useEnterDetailsStore((state) => state.completeStep)
const onSubmit = useCallback(
(values: BreakfastSchema) => {
completeStep(values)
(values: BreakfastFormSchema) => {
const pkg = packages?.find((p) => p.code === values.breakfast)
if (pkg) {
completeStep({ breakfast: pkg })
} else {
completeStep({ breakfast: BreakfastPackageEnum.NO_BREAKFAST })
}
},
[completeStep]
[completeStep, packages]
)
useEffect(() => {
@@ -47,30 +61,46 @@ export default function Breakfast() {
return () => subscription.unsubscribe()
}, [methods, onSubmit])
if (!packages) {
return null
}
return (
<FormProvider {...methods}>
<form className={styles.form} onSubmit={methods.handleSubmit(onSubmit)}>
<RadioCard
Icon={BreakfastIcon}
id={breakfastEnum.BREAKFAST}
name="breakfast"
subtitle={intl.formatMessage<React.ReactNode>(
{ id: "<b>{amount} {currency}</b>/night per adult" },
{
amount: "150",
b: (str) => <b>{str}</b>,
currency: "SEK",
{packages.map((pkg) => (
<RadioCard
key={pkg.code}
id={pkg.code}
name="breakfast"
subtitle={
pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
? intl.formatMessage<React.ReactNode>(
{ id: "breakfast.price.free" },
{
amount: pkg.originalPrice,
currency: pkg.currency,
free: (str) => <Highlight>{str}</Highlight>,
strikethrough: (str) => <s>{str}</s>,
}
)
: intl.formatMessage(
{ id: "breakfast.price" },
{
amount: pkg.packagePrice,
currency: pkg.currency,
}
)
}
)}
text={intl.formatMessage({
id: "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.",
})}
title={intl.formatMessage({ id: "Breakfast buffet" })}
value={breakfastEnum.BREAKFAST}
/>
text={intl.formatMessage({
id: "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.",
})}
title={intl.formatMessage({ id: "Breakfast buffet" })}
value={pkg.code}
/>
))}
<RadioCard
Icon={NoBreakfastIcon}
id={breakfastEnum.NO_BREAKFAST}
id={BreakfastPackageEnum.NO_BREAKFAST}
name="breakfast"
subtitle={intl.formatMessage(
{ id: "{amount} {currency}" },
@@ -83,7 +113,7 @@ export default function Breakfast() {
id: "You can always change your mind later and add breakfast at the hotel.",
})}
title={intl.formatMessage({ id: "No breakfast" })}
value={breakfastEnum.NO_BREAKFAST}
value={BreakfastPackageEnum.NO_BREAKFAST}
/>
</form>
</FormProvider>

View File

@@ -1,7 +1,15 @@
import { z } from "zod"
import { breakfastEnum } from "@/types/enums/breakfast"
import { breakfastPackageSchema } from "@/server/routers/hotels/output"
export const breakfastSchema = z.object({
breakfast: z.nativeEnum(breakfastEnum),
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
export const breakfastStoreSchema = z.object({
breakfast: breakfastPackageSchema.or(
z.literal(BreakfastPackageEnum.NO_BREAKFAST)
),
})
export const breakfastFormSchema = z.object({
breakfast: z.string().or(z.literal(BreakfastPackageEnum.NO_BREAKFAST)),
})

View File

@@ -70,3 +70,7 @@
.listItem:nth-of-type(n + 2) {
margin-top: var(--Spacing-x-quarter);
}
.highlight {
color: var(--Scandic-Brand-Scandic-Red);
}

View File

@@ -34,3 +34,12 @@ export type CheckboxProps =
export type RadioProps =
| Omit<ListCardProps, "type">
| Omit<TextCardProps, "type">
export interface ListProps extends Pick<ListCardProps, "declined"> {
list?: ListCardProps["list"]
}
export interface SubtitleProps
extends Pick<BaseCardProps, "highlightSubtitle" | "subtitle"> {}
export interface TextProps extends Pick<TextCardProps, "text"> {}

View File

@@ -2,16 +2,16 @@
import { useFormContext } from "react-hook-form"
import { CheckIcon, CloseIcon, HeartIcon } from "@/components/Icons"
import { CheckIcon, CloseIcon } from "@/components/Icons"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import styles from "./card.module.css"
import type { CardProps } from "./card"
import type { CardProps, ListProps, SubtitleProps, TextProps } from "./card"
export default function Card({
Icon = HeartIcon,
Icon,
iconHeight = 32,
iconWidth = 32,
declined = false,
@@ -26,56 +26,79 @@ export default function Card({
value,
}: CardProps) {
const { register } = useFormContext()
return (
<label className={styles.label} data-declined={declined} tabIndex={0}>
<Caption className={styles.title} type="label" uppercase>
<Caption className={styles.title} color="burgundy" type="label" uppercase>
{title}
</Caption>
{subtitle ? (
<Caption
className={styles.subtitle}
color={highlightSubtitle ? "baseTextAccent" : "uiTextHighContrast"}
type="regular"
>
{subtitle}
</Caption>
) : null}
<Icon
className={styles.icon}
color="uiTextHighContrast"
height={iconHeight}
width={iconWidth}
/>
{list
? list.map((listItem) => (
<span key={listItem.title} className={styles.listItem}>
{declined ? (
<CloseIcon
color="uiTextMediumContrast"
height={20}
width={20}
/>
) : (
<CheckIcon color="baseIconLowContrast" height={20} width={20} />
)}
<Footnote color="uiTextMediumContrast">{listItem.title}</Footnote>
</span>
))
: null}
{text ? (
<Footnote className={styles.text} color="uiTextMediumContrast">
{text}
</Footnote>
<Subtitle highlightSubtitle={highlightSubtitle} subtitle={subtitle} />
{Icon ? (
<Icon
className={styles.icon}
color="uiTextHighContrast"
height={iconHeight}
width={iconWidth}
/>
) : null}
<List declined={declined} list={list} />
<Text text={text} />
<input
{...register(name)}
aria-hidden
id={id || name}
hidden
type={type}
value={value}
{...register(name)}
/>
</label>
)
}
function List({ declined, list }: ListProps) {
if (!list) {
return null
}
return list.map((listItem) => (
<span key={listItem.title} className={styles.listItem}>
{declined ? (
<CloseIcon color="uiTextMediumContrast" height={20} width={20} />
) : (
<CheckIcon color="baseIconLowContrast" height={20} width={20} />
)}
<Footnote color="uiTextMediumContrast">{listItem.title}</Footnote>
</span>
))
}
function Subtitle({ highlightSubtitle, subtitle }: SubtitleProps) {
if (!subtitle) {
return null
}
return (
<Caption
className={styles.subtitle}
color={highlightSubtitle ? "baseTextAccent" : "uiTextMediumContrast"}
type="label"
uppercase
>
{subtitle}
</Caption>
)
}
function Text({ text }: TextProps) {
if (!text) {
return null
}
return (
<Footnote className={styles.text} color="uiTextMediumContrast">
{text}
</Footnote>
)
}
export function Highlight({ children }: React.PropsWithChildren) {
return <span className={styles.highlight}>{children}</span>
}