feat(SW-1063): add member price modal

This commit is contained in:
Christian Andolf
2024-12-04 16:40:45 +01:00
committed by Christel Westerberg
parent a1a36e80d5
commit 42bb71cf2f
20 changed files with 446 additions and 8 deletions

View File

@@ -125,6 +125,8 @@
--dialog-z-index: 9; --dialog-z-index: 9;
--sidepeek-z-index: 100; --sidepeek-z-index: 100;
--lightbox-z-index: 150; --lightbox-z-index: 150;
--default-modal-overlay-z-index: 100;
--default-modal-z-index: 101;
} }
* { * {

View File

@@ -0,0 +1,64 @@
"use client"
import { useIntl } from "react-intl"
import { useEnterDetailsStore } from "@/stores/enter-details"
import { MagicWandIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import Title from "@/components/TempDesignSystem/Text/Title"
import Modal from "../../Modal"
import styles from "./modal.module.css"
import type { Dispatch, SetStateAction } from "react"
export default function MemberPriceModal({
isOpen,
setIsOpen,
}: {
isOpen: boolean
setIsOpen: Dispatch<SetStateAction<boolean>>
}) {
const memberRate = useEnterDetailsStore((state) => state.roomRate.memberRate)
const intl = useIntl()
const memberPrice = memberRate?.localPrice ?? memberRate?.requestedPrice
return (
<Modal isOpen={isOpen} onToggle={setIsOpen}>
<div className={styles.modalContent}>
<div className={styles.innerModalContent}>
<MagicWandIcon width="265px" />
<Title as="h3" level="h1" textTransform="regular">
{intl.formatMessage({
id: "Member price activated",
})}
</Title>
{memberPrice && (
<span className={styles.newPrice}>
<Body>
{intl.formatMessage({
id: "The new price is",
})}
</Body>
<Subtitle type="two" color="red">
{intl.formatNumber(memberPrice.pricePerStay, {
currency: memberPrice.currency,
style: "currency",
})}
</Subtitle>
</span>
)}
</div>
<Button intent="primary" theme="base" onClick={() => setIsOpen(false)}>
OK
</Button>
</div>
</Modal>
)
}

View File

@@ -0,0 +1,24 @@
.modalContent {
display: grid;
gap: var(--Spacing-x3);
width: 100%;
}
.innerModalContent {
display: grid;
gap: var(--Spacing-x2);
align-items: center;
justify-items: center;
}
.newPrice {
display: flex;
gap: var(--Spacing-x1);
align-items: center;
}
@media screen and (min-width: 768px) {
.modalContent {
width: 352px;
}
}

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { useCallback } from "react" import { useCallback, useState } from "react"
import { FormProvider, useForm } from "react-hook-form" import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
@@ -13,6 +13,7 @@ import Phone from "@/components/TempDesignSystem/Form/Phone"
import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import JoinScandicFriendsCard from "./JoinScandicFriendsCard" import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
import MemberPriceModal from "./MemberPriceModal"
import { guestDetailsSchema, signedInDetailsSchema } from "./schema" import { guestDetailsSchema, signedInDetailsSchema } from "./schema"
import Signup from "./Signup" import Signup from "./Signup"
import SpecialRequests from "./SpecialRequests" import SpecialRequests from "./SpecialRequests"
@@ -27,6 +28,8 @@ import type {
const formID = "enter-details" const formID = "enter-details"
export default function Details({ user, memberPrice }: DetailsProps) { export default function Details({ user, memberPrice }: DetailsProps) {
const intl = useIntl() const intl = useIntl()
const [isMemberPriceModalOpen, setIsMemberPriceModalOpen] = useState(false)
const initialData = useEnterDetailsStore((state) => state.guest) const initialData = useEnterDetailsStore((state) => state.guest)
const updateDetails = useEnterDetailsStore( const updateDetails = useEnterDetailsStore(
(state) => state.actions.updateDetails (state) => state.actions.updateDetails
@@ -53,9 +56,12 @@ export default function Details({ user, memberPrice }: DetailsProps) {
const onSubmit = useCallback( const onSubmit = useCallback(
(values: DetailsSchema) => { (values: DetailsSchema) => {
if ((values.join || values.membershipNo) && memberPrice) {
setIsMemberPriceModalOpen(true)
}
updateDetails(values) updateDetails(values)
}, },
[updateDetails] [updateDetails, setIsMemberPriceModalOpen, memberPrice]
) )
return ( return (
@@ -130,6 +136,10 @@ export default function Details({ user, memberPrice }: DetailsProps) {
{intl.formatMessage({ id: "Proceed to payment method" })} {intl.formatMessage({ id: "Proceed to payment method" })}
</Button> </Button>
</footer> </footer>
<MemberPriceModal
isOpen={isMemberPriceModalOpen}
setIsOpen={setIsMemberPriceModalOpen}
/>
</form> </form>
</FormProvider> </FormProvider>
) )

View File

@@ -0,0 +1,150 @@
"use client"
import { motion } from "framer-motion"
import { type PropsWithChildren, useEffect, useState } from "react"
import {
Dialog,
DialogTrigger,
Modal as AriaModal,
ModalOverlay,
} from "react-aria-components"
import { useIntl } from "react-intl"
import { CloseLargeIcon } from "@/components/Icons"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import {
type AnimationState,
AnimationStateEnum,
type InnerModalProps,
type ModalProps,
} from "./modal"
import { fade, slideInOut } from "./motionVariants"
import styles from "./modal.module.css"
const MotionOverlay = motion(ModalOverlay)
const MotionModal = motion(AriaModal)
function InnerModal({
animation,
onAnimationComplete = () => undefined,
setAnimation,
onToggle,
isOpen,
children,
title,
}: PropsWithChildren<InnerModalProps>) {
const intl = useIntl()
function modalStateHandler(newAnimationState: AnimationState) {
setAnimation((currentAnimationState) =>
newAnimationState === AnimationStateEnum.hidden &&
currentAnimationState === AnimationStateEnum.hidden
? AnimationStateEnum.unmounted
: currentAnimationState
)
if (newAnimationState === AnimationStateEnum.visible) {
onAnimationComplete()
}
}
function onOpenChange(state: boolean) {
onToggle!(state)
}
return (
<MotionOverlay
animate={animation}
className={styles.overlay}
initial={"hidden"}
isDismissable
isExiting={animation === AnimationStateEnum.hidden}
onAnimationComplete={modalStateHandler}
variants={fade}
isOpen={isOpen}
onOpenChange={onOpenChange}
>
<MotionModal
className={styles.modal}
variants={slideInOut}
animate={animation}
initial={"hidden"}
>
<Dialog className={styles.dialog} aria-label="Dialog">
{({ close }) => (
<>
<header className={styles.header}>
{title && (
<Subtitle type="one" color="uiTextHighContrast">
{title}
</Subtitle>
)}
<button onClick={close} type="button" className={styles.close}>
<CloseLargeIcon color="uiTextMediumContrast" />
</button>
</header>
<section className={styles.content}>{children}</section>
</>
)}
</Dialog>
</MotionModal>
</MotionOverlay>
)
}
export default function Modal({
onAnimationComplete = () => undefined,
trigger,
isOpen,
onToggle,
title,
children,
}: PropsWithChildren<ModalProps>) {
const [animation, setAnimation] = useState<AnimationState>(
AnimationStateEnum.visible
)
useEffect(() => {
if (typeof isOpen === "boolean") {
setAnimation(
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
)
}
}, [isOpen])
if (!trigger) {
return (
<InnerModal
onAnimationComplete={onAnimationComplete}
animation={animation}
setAnimation={setAnimation}
onToggle={onToggle}
isOpen={isOpen}
title={title}
>
{children}
</InnerModal>
)
}
return (
<DialogTrigger
onOpenChange={(isOpen) =>
setAnimation(
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
)
}
>
{trigger}
<InnerModal
onAnimationComplete={onAnimationComplete}
animation={animation}
setAnimation={setAnimation}
title={title}
>
{children}
</InnerModal>
</DialogTrigger>
)
}

View File

@@ -0,0 +1,77 @@
.overlay {
background: rgba(0, 0, 0, 0.5);
height: var(--visual-viewport-height);
position: fixed;
top: 0;
left: 0;
width: 100vw;
z-index: var(--default-modal-overlay-z-index);
}
.modal {
background-color: var(--Base-Surface-Primary-light-Normal);
border-radius: var(--Corner-radius-Medium) var(--Corner-radius-Medium) 0 0;
box-shadow: 0px 4px 24px 0px rgba(38, 32, 30, 0.08);
width: 100%;
position: absolute;
left: 0;
bottom: 0;
z-index: var(--default-modal-z-index);
}
.dialog {
display: flex;
flex-direction: column;
padding-bottom: var(--Spacing-x3);
/* for supporting animations within content */
position: relative;
overflow: hidden;
}
.header {
--button-dimension: 32px;
box-sizing: content-box;
display: flex;
align-items: center;
height: var(--button-dimension);
position: relative;
justify-content: center;
padding: var(--Spacing-x3) var(--Spacing-x2) 0;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--Spacing-x2);
padding: 0 var(--Spacing-x3) var(--Spacing-x1);
}
.close {
background: none;
border: none;
cursor: pointer;
position: absolute;
right: var(--Spacing-x2);
width: var(--button-dimension);
height: var(--button-dimension);
display: flex;
align-items: center;
}
@media screen and (min-width: 768px) {
.overlay {
display: flex;
justify-content: center;
align-items: center;
}
.modal {
left: auto;
bottom: auto;
width: auto;
border-radius: var(--Corner-radius-Medium);
}
}

View File

@@ -0,0 +1,26 @@
import type { Dispatch, SetStateAction } from "react"
export enum AnimationStateEnum {
unmounted = "unmounted",
hidden = "hidden",
visible = "visible",
}
export type AnimationState = keyof typeof AnimationStateEnum
export type ModalProps = {
onAnimationComplete?: VoidFunction
title?: string
} & (
| { trigger: JSX.Element; isOpen?: never; onToggle?: never }
| {
trigger?: never
isOpen: boolean
onToggle: Dispatch<SetStateAction<boolean>>
}
)
export type InnerModalProps = Omit<ModalProps, "trigger"> & {
animation: AnimationState
setAnimation: Dispatch<SetStateAction<AnimationState>>
}

View File

@@ -0,0 +1,23 @@
export const fade = {
hidden: {
opacity: 0,
transition: { duration: 0.4, ease: "easeInOut" },
},
visible: {
opacity: 1,
transition: { duration: 0.4, ease: "easeInOut" },
},
}
export const slideInOut = {
hidden: {
opacity: 0,
y: 32,
transition: { duration: 0.4, ease: "easeInOut" },
},
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: "easeInOut" },
},
}

View File

@@ -5,16 +5,21 @@ import { dt } from "@/lib/dt"
import { useEnterDetailsStore } from "@/stores/enter-details" import { useEnterDetailsStore } from "@/stores/enter-details"
import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop"
import { ArrowRightIcon, ChevronDownSmallIcon } from "@/components/Icons" import {
ArrowRightIcon,
ChevronDownSmallIcon,
ChevronRightSmallIcon,
} from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider" import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
import Popover from "@/components/TempDesignSystem/Popover" import Popover from "@/components/TempDesignSystem/Popover"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import Modal from "../../Modal"
import styles from "./ui.module.css" import styles from "./ui.module.css"
import type { SummaryProps } from "@/types/components/hotelReservation/summary" import type { SummaryProps } from "@/types/components/hotelReservation/summary"
@@ -31,6 +36,7 @@ export function storeSelector(state: DetailsState) {
roomRate: state.roomRate, roomRate: state.roomRate,
roomPrice: state.roomPrice, roomPrice: state.roomPrice,
toggleSummaryOpen: state.actions.toggleSummaryOpen, toggleSummaryOpen: state.actions.toggleSummaryOpen,
togglePriceDetailsModalOpen: state.actions.togglePriceDetailsModalOpen,
totalPrice: state.totalPrice, totalPrice: state.totalPrice,
} }
} }
@@ -54,6 +60,7 @@ export default function SummaryUI({
roomPrice, roomPrice,
roomRate, roomRate,
toggleSummaryOpen, toggleSummaryOpen,
togglePriceDetailsModalOpen,
totalPrice, totalPrice,
} = useEnterDetailsStore(storeSelector) } = useEnterDetailsStore(storeSelector)
@@ -82,6 +89,12 @@ export default function SummaryUI({
} }
} }
function handleTogglePriceDetailsModal() {
if (togglePriceDetailsModalOpen) {
togglePriceDetailsModalOpen()
}
}
return ( return (
<section className={styles.summary}> <section className={styles.summary}>
<header className={styles.header}> <header className={styles.header}>
@@ -243,9 +256,28 @@ export default function SummaryUI({
{ b: (str) => <b>{str}</b> } { b: (str) => <b>{str}</b> }
)} )}
</Body> </Body>
<Link color="burgundy" href="" variant="underscored" size="small"> <Modal
trigger={
<Button intent="text" onPress={handleTogglePriceDetailsModal}>
<Caption color="burgundy">
{intl.formatMessage({ id: "Price details" })} {intl.formatMessage({ id: "Price details" })}
</Link> </Caption>
<ChevronRightSmallIcon
color="burgundy"
height="20px"
width="20px"
/>
</Button>
}
>
<div className={styles.modalContent}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Arcu
risus quis varius quam quisque id diam vel. Rhoncus urna neque
viverra justo. Mattis aliquam faucibus purus in massa. Id cursus
metus aliquam eleifend mi in nulla posuere.
</div>
</Modal>
</div> </div>
<div> <div>
<Body textTransform="bold"> <Body textTransform="bold">

View File

@@ -68,6 +68,10 @@
display: none; display: none;
} }
.modalContent {
width: 560px;
}
@media screen and (min-width: 1367px) { @media screen and (min-width: 1367px) {
.bottomDivider { .bottomDivider {
display: block; display: block;

View File

@@ -55,7 +55,10 @@ a.text {
border: none; border: none;
outline: none; outline: none;
} }
/* TODO: The variants for combinations of size/text/wrapping should be looked at and iterated on */
.text:not(.wrapping) {
padding: 0 !important;
}
/* VARIANTS */ /* VARIANTS */
.default, .default,
a.default { a.default {

View File

@@ -52,6 +52,7 @@
outline: none; outline: none;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
text-align: left; text-align: left;
justify-content: space-between;
} }
.input :global(.react-aria-SelectValue) { .input :global(.react-aria-SelectValue) {

View File

@@ -236,6 +236,7 @@
"Marketing city": "Marketing by", "Marketing city": "Marketing by",
"Meetings & Conferences": "Møder & Konferencer", "Meetings & Conferences": "Møder & Konferencer",
"Member price": "Medlemspris", "Member price": "Medlemspris",
"Member price activated": "Medlemspris aktiveret",
"Member price from": "Medlemspris fra", "Member price from": "Medlemspris fra",
"Members": "Medlemmer", "Members": "Medlemmer",
"Membership ID": "Medlems-id", "Membership ID": "Medlems-id",
@@ -403,6 +404,7 @@
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.",
"Terms and conditions": "Vilkår og betingelser", "Terms and conditions": "Vilkår og betingelser",
"Thank you": "Tak", "Thank you": "Tak",
"The new price is": "Nyprisen er",
"The price has increased": "Prisen er steget", "The price has increased": "Prisen er steget",
"The price has increased since you selected your room.": "Prisen er steget, efter at du har valgt dit værelse.", "The price has increased since you selected your room.": "Prisen er steget, efter at du har valgt dit værelse.",
"Theatre": "Teater", "Theatre": "Teater",

View File

@@ -236,6 +236,7 @@
"Marketing city": "Marketingstadt", "Marketing city": "Marketingstadt",
"Meetings & Conferences": "Tagungen & Konferenzen", "Meetings & Conferences": "Tagungen & Konferenzen",
"Member price": "Mitgliederpreis", "Member price": "Mitgliederpreis",
"Member price activated": "Mitgliederpreis aktiviert",
"Member price from": "Mitgliederpreis ab", "Member price from": "Mitgliederpreis ab",
"Members": "Mitglieder", "Members": "Mitglieder",
"Membership ID": "Mitglieds-ID", "Membership ID": "Mitglieds-ID",
@@ -403,6 +404,7 @@
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.",
"Terms and conditions": "Geschäftsbedingungen", "Terms and conditions": "Geschäftsbedingungen",
"Thank you": "Danke", "Thank you": "Danke",
"The new price is": "Der neue Preis beträgt",
"The price has increased": "Der Preis ist gestiegen", "The price has increased": "Der Preis ist gestiegen",
"The price has increased since you selected your room.": "Der Preis ist gestiegen, nachdem Sie Ihr Zimmer ausgewählt haben.", "The price has increased since you selected your room.": "Der Preis ist gestiegen, nachdem Sie Ihr Zimmer ausgewählt haben.",
"Theatre": "Theater", "Theatre": "Theater",

View File

@@ -254,6 +254,7 @@
"Meetings & Conferences": "Meetings & Conferences", "Meetings & Conferences": "Meetings & Conferences",
"Member discount": "Member discount", "Member discount": "Member discount",
"Member price": "Member price", "Member price": "Member price",
"Member price activated": "Member price activated",
"Member price from": "Member price from", "Member price from": "Member price from",
"Members": "Members", "Members": "Members",
"Membership ID": "Membership ID", "Membership ID": "Membership ID",
@@ -440,6 +441,7 @@
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.",
"Terms and conditions": "Terms and conditions", "Terms and conditions": "Terms and conditions",
"Thank you": "Thank you", "Thank you": "Thank you",
"The new price is": "The new price is",
"The price has increased": "The price has increased", "The price has increased": "The price has increased",
"The price has increased since you selected your room.": "The price has increased since you selected your room.", "The price has increased since you selected your room.": "The price has increased since you selected your room.",
"Theatre": "Theatre", "Theatre": "Theatre",

View File

@@ -236,6 +236,7 @@
"Marketing city": "Markkinointikaupunki", "Marketing city": "Markkinointikaupunki",
"Meetings & Conferences": "Kokoukset & Konferenssit", "Meetings & Conferences": "Kokoukset & Konferenssit",
"Member price": "Jäsenhinta", "Member price": "Jäsenhinta",
"Member price activated": "Jäsenhinta aktivoitu",
"Member price from": "Jäsenhinta alkaen", "Member price from": "Jäsenhinta alkaen",
"Members": "Jäsenet", "Members": "Jäsenet",
"Membership ID": "Jäsentunnus", "Membership ID": "Jäsentunnus",
@@ -404,6 +405,7 @@
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.",
"Terms and conditions": "Käyttöehdot", "Terms and conditions": "Käyttöehdot",
"Thank you": "Kiitos", "Thank you": "Kiitos",
"The new price is": "Uusi hinta on",
"The price has increased": "Hinta on noussut", "The price has increased": "Hinta on noussut",
"The price has increased since you selected your room.": "Hinta on noussut, koska valitsit huoneen.", "The price has increased since you selected your room.": "Hinta on noussut, koska valitsit huoneen.",
"Theatre": "Teatteri", "Theatre": "Teatteri",

View File

@@ -235,6 +235,7 @@
"Marketing city": "Markedsføringsby", "Marketing city": "Markedsføringsby",
"Meetings & Conferences": "Møter & Konferanser", "Meetings & Conferences": "Møter & Konferanser",
"Member price": "Medlemspris", "Member price": "Medlemspris",
"Member price activated": "Medlemspris aktivert",
"Member price from": "Medlemspris fra", "Member price from": "Medlemspris fra",
"Members": "Medlemmer", "Members": "Medlemmer",
"Membership ID": "Medlems-ID", "Membership ID": "Medlems-ID",
@@ -402,6 +403,7 @@
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.",
"Terms and conditions": "Vilkår og betingelser", "Terms and conditions": "Vilkår og betingelser",
"Thank you": "Takk", "Thank you": "Takk",
"The new price is": "Den nye prisen er",
"The price has increased": "Prisen er steget", "The price has increased": "Prisen er steget",
"The price has increased since you selected your room.": "Prisen er steget, etter at du har valgt rommet.", "The price has increased since you selected your room.": "Prisen er steget, etter at du har valgt rommet.",
"Theatre": "Teater", "Theatre": "Teater",

View File

@@ -235,6 +235,7 @@
"Marketing city": "Marknadsföringsstad", "Marketing city": "Marknadsföringsstad",
"Meetings & Conferences": "Möten & Konferenser", "Meetings & Conferences": "Möten & Konferenser",
"Member price": "Medlemspris", "Member price": "Medlemspris",
"Member price activated": "Medlemspris aktiverat",
"Member price from": "Medlemspris från", "Member price from": "Medlemspris från",
"Members": "Medlemmar", "Members": "Medlemmar",
"Membership ID": "Medlems-ID", "Membership ID": "Medlems-ID",
@@ -402,6 +403,7 @@
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.",
"Terms and conditions": "Allmänna villkor", "Terms and conditions": "Allmänna villkor",
"Thank you": "Tack", "Thank you": "Tack",
"The new price is": "Det nya priset är",
"The price has increased": "Priset har ökat", "The price has increased": "Priset har ökat",
"The price has increased since you selected your room.": "Priset har ökat sedan du valde ditt rum.", "The price has increased since you selected your room.": "Priset har ökat sedan du valde ditt rum.",
"Theatre": "Teater", "Theatre": "Teater",

View File

@@ -174,6 +174,13 @@ export function createDetailsStore(
}) })
) )
}, },
togglePriceDetailsModalOpen() {
return set(
produce((state: DetailsState) => {
state.isPriceDetailsModalOpen = !state.isPriceDetailsModalOpen
})
)
},
updateBedType(bedType) { updateBedType(bedType) {
return set( return set(
produce((state: DetailsState) => { produce((state: DetailsState) => {
@@ -328,6 +335,7 @@ export function createDetailsStore(
: defaultGuestState, : defaultGuestState,
isSubmittingDisabled: false, isSubmittingDisabled: false,
isSummaryOpen: false, isSummaryOpen: false,
isPriceDetailsModalOpen: false,
isValid: { isValid: {
[StepEnum.selectBed]: false, [StepEnum.selectBed]: false,
[StepEnum.breakfast]: false, [StepEnum.breakfast]: false,

View File

@@ -34,6 +34,7 @@ export interface DetailsState {
setStep: (step: StepEnum) => void setStep: (step: StepEnum) => void
setTotalPrice: (totalPrice: Price) => void setTotalPrice: (totalPrice: Price) => void
toggleSummaryOpen: () => void toggleSummaryOpen: () => void
togglePriceDetailsModalOpen: () => void
updateBedType: (data: BedTypeSchema) => void updateBedType: (data: BedTypeSchema) => void
updateBreakfast: (data: BreakfastPackage | false) => void updateBreakfast: (data: BreakfastPackage | false) => void
updateDetails: (data: DetailsSchema) => void updateDetails: (data: DetailsSchema) => void
@@ -47,6 +48,7 @@ export interface DetailsState {
guest: DetailsSchema guest: DetailsSchema
isSubmittingDisabled: boolean isSubmittingDisabled: boolean
isSummaryOpen: boolean isSummaryOpen: boolean
isPriceDetailsModalOpen: boolean
isValid: Record<StepEnum, boolean> isValid: Record<StepEnum, boolean>
packages: Packages | null packages: Packages | null
roomRate: DetailsProviderProps["roomRate"] roomRate: DetailsProviderProps["roomRate"]