feat: get breakfast package from API
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)),
|
||||
})
|
||||
|
||||
@@ -70,3 +70,7 @@
|
||||
.listItem:nth-of-type(n + 2) {
|
||||
margin-top: var(--Spacing-x-quarter);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--Scandic-Brand-Scandic-Red);
|
||||
}
|
||||
|
||||
@@ -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"> {}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user