feat(SW-1063): add member price modal
This commit is contained in:
committed by
Christel Westerberg
parent
a1a36e80d5
commit
42bb71cf2f
@@ -125,6 +125,8 @@
|
||||
--dialog-z-index: 9;
|
||||
--sidepeek-z-index: 100;
|
||||
--lightbox-z-index: 150;
|
||||
--default-modal-overlay-z-index: 100;
|
||||
--default-modal-z-index: 101;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useCallback } from "react"
|
||||
import { useCallback, useState } from "react"
|
||||
import { FormProvider, useForm } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
@@ -13,6 +13,7 @@ import Phone from "@/components/TempDesignSystem/Form/Phone"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
|
||||
import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
|
||||
import MemberPriceModal from "./MemberPriceModal"
|
||||
import { guestDetailsSchema, signedInDetailsSchema } from "./schema"
|
||||
import Signup from "./Signup"
|
||||
import SpecialRequests from "./SpecialRequests"
|
||||
@@ -27,6 +28,8 @@ import type {
|
||||
const formID = "enter-details"
|
||||
export default function Details({ user, memberPrice }: DetailsProps) {
|
||||
const intl = useIntl()
|
||||
const [isMemberPriceModalOpen, setIsMemberPriceModalOpen] = useState(false)
|
||||
|
||||
const initialData = useEnterDetailsStore((state) => state.guest)
|
||||
const updateDetails = useEnterDetailsStore(
|
||||
(state) => state.actions.updateDetails
|
||||
@@ -53,9 +56,12 @@ export default function Details({ user, memberPrice }: DetailsProps) {
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(values: DetailsSchema) => {
|
||||
if ((values.join || values.membershipNo) && memberPrice) {
|
||||
setIsMemberPriceModalOpen(true)
|
||||
}
|
||||
updateDetails(values)
|
||||
},
|
||||
[updateDetails]
|
||||
[updateDetails, setIsMemberPriceModalOpen, memberPrice]
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -130,6 +136,10 @@ export default function Details({ user, memberPrice }: DetailsProps) {
|
||||
{intl.formatMessage({ id: "Proceed to payment method" })}
|
||||
</Button>
|
||||
</footer>
|
||||
<MemberPriceModal
|
||||
isOpen={isMemberPriceModalOpen}
|
||||
setIsOpen={setIsMemberPriceModalOpen}
|
||||
/>
|
||||
</form>
|
||||
</FormProvider>
|
||||
)
|
||||
|
||||
150
components/HotelReservation/EnterDetails/Modal/index.tsx
Normal file
150
components/HotelReservation/EnterDetails/Modal/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
26
components/HotelReservation/EnterDetails/Modal/modal.ts
Normal file
26
components/HotelReservation/EnterDetails/Modal/modal.ts
Normal 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>>
|
||||
}
|
||||
@@ -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" },
|
||||
},
|
||||
}
|
||||
@@ -5,16 +5,21 @@ import { dt } from "@/lib/dt"
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
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 Divider from "@/components/TempDesignSystem/Divider"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Popover from "@/components/TempDesignSystem/Popover"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import Modal from "../../Modal"
|
||||
|
||||
import styles from "./ui.module.css"
|
||||
|
||||
import type { SummaryProps } from "@/types/components/hotelReservation/summary"
|
||||
@@ -31,6 +36,7 @@ export function storeSelector(state: DetailsState) {
|
||||
roomRate: state.roomRate,
|
||||
roomPrice: state.roomPrice,
|
||||
toggleSummaryOpen: state.actions.toggleSummaryOpen,
|
||||
togglePriceDetailsModalOpen: state.actions.togglePriceDetailsModalOpen,
|
||||
totalPrice: state.totalPrice,
|
||||
}
|
||||
}
|
||||
@@ -54,6 +60,7 @@ export default function SummaryUI({
|
||||
roomPrice,
|
||||
roomRate,
|
||||
toggleSummaryOpen,
|
||||
togglePriceDetailsModalOpen,
|
||||
totalPrice,
|
||||
} = useEnterDetailsStore(storeSelector)
|
||||
|
||||
@@ -82,6 +89,12 @@ export default function SummaryUI({
|
||||
}
|
||||
}
|
||||
|
||||
function handleTogglePriceDetailsModal() {
|
||||
if (togglePriceDetailsModalOpen) {
|
||||
togglePriceDetailsModalOpen()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.summary}>
|
||||
<header className={styles.header}>
|
||||
@@ -243,9 +256,28 @@ export default function SummaryUI({
|
||||
{ b: (str) => <b>{str}</b> }
|
||||
)}
|
||||
</Body>
|
||||
<Link color="burgundy" href="" variant="underscored" size="small">
|
||||
{intl.formatMessage({ id: "Price details" })}
|
||||
</Link>
|
||||
<Modal
|
||||
trigger={
|
||||
<Button intent="text" onPress={handleTogglePriceDetailsModal}>
|
||||
<Caption color="burgundy">
|
||||
{intl.formatMessage({ id: "Price details" })}
|
||||
</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>
|
||||
<Body textTransform="bold">
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
width: 560px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.bottomDivider {
|
||||
display: block;
|
||||
|
||||
@@ -55,7 +55,10 @@ a.text {
|
||||
border: 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 */
|
||||
.default,
|
||||
a.default {
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
outline: none;
|
||||
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
|
||||
text-align: left;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.input :global(.react-aria-SelectValue) {
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"Marketing city": "Marketing by",
|
||||
"Meetings & Conferences": "Møder & Konferencer",
|
||||
"Member price": "Medlemspris",
|
||||
"Member price activated": "Medlemspris aktiveret",
|
||||
"Member price from": "Medlemspris fra",
|
||||
"Members": "Medlemmer",
|
||||
"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.",
|
||||
"Terms and conditions": "Vilkår og betingelser",
|
||||
"Thank you": "Tak",
|
||||
"The new price is": "Nyprisen er",
|
||||
"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.",
|
||||
"Theatre": "Teater",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"Marketing city": "Marketingstadt",
|
||||
"Meetings & Conferences": "Tagungen & Konferenzen",
|
||||
"Member price": "Mitgliederpreis",
|
||||
"Member price activated": "Mitgliederpreis aktiviert",
|
||||
"Member price from": "Mitgliederpreis ab",
|
||||
"Members": "Mitglieder",
|
||||
"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.",
|
||||
"Terms and conditions": "Geschäftsbedingungen",
|
||||
"Thank you": "Danke",
|
||||
"The new price is": "Der neue Preis beträgt",
|
||||
"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.",
|
||||
"Theatre": "Theater",
|
||||
|
||||
@@ -254,6 +254,7 @@
|
||||
"Meetings & Conferences": "Meetings & Conferences",
|
||||
"Member discount": "Member discount",
|
||||
"Member price": "Member price",
|
||||
"Member price activated": "Member price activated",
|
||||
"Member price from": "Member price from",
|
||||
"Members": "Members",
|
||||
"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.",
|
||||
"Terms and conditions": "Terms and conditions",
|
||||
"Thank you": "Thank you",
|
||||
"The new price is": "The new price is",
|
||||
"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.",
|
||||
"Theatre": "Theatre",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"Marketing city": "Markkinointikaupunki",
|
||||
"Meetings & Conferences": "Kokoukset & Konferenssit",
|
||||
"Member price": "Jäsenhinta",
|
||||
"Member price activated": "Jäsenhinta aktivoitu",
|
||||
"Member price from": "Jäsenhinta alkaen",
|
||||
"Members": "Jäsenet",
|
||||
"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ä.",
|
||||
"Terms and conditions": "Käyttöehdot",
|
||||
"Thank you": "Kiitos",
|
||||
"The new price is": "Uusi hinta on",
|
||||
"The price has increased": "Hinta on noussut",
|
||||
"The price has increased since you selected your room.": "Hinta on noussut, koska valitsit huoneen.",
|
||||
"Theatre": "Teatteri",
|
||||
|
||||
@@ -235,6 +235,7 @@
|
||||
"Marketing city": "Markedsføringsby",
|
||||
"Meetings & Conferences": "Møter & Konferanser",
|
||||
"Member price": "Medlemspris",
|
||||
"Member price activated": "Medlemspris aktivert",
|
||||
"Member price from": "Medlemspris fra",
|
||||
"Members": "Medlemmer",
|
||||
"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.",
|
||||
"Terms and conditions": "Vilkår og betingelser",
|
||||
"Thank you": "Takk",
|
||||
"The new price is": "Den nye prisen er",
|
||||
"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.",
|
||||
"Theatre": "Teater",
|
||||
|
||||
@@ -235,6 +235,7 @@
|
||||
"Marketing city": "Marknadsföringsstad",
|
||||
"Meetings & Conferences": "Möten & Konferenser",
|
||||
"Member price": "Medlemspris",
|
||||
"Member price activated": "Medlemspris aktiverat",
|
||||
"Member price from": "Medlemspris från",
|
||||
"Members": "Medlemmar",
|
||||
"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.",
|
||||
"Terms and conditions": "Allmänna villkor",
|
||||
"Thank you": "Tack",
|
||||
"The new price is": "Det nya priset är",
|
||||
"The price has increased": "Priset har ökat",
|
||||
"The price has increased since you selected your room.": "Priset har ökat sedan du valde ditt rum.",
|
||||
"Theatre": "Teater",
|
||||
|
||||
@@ -174,6 +174,13 @@ export function createDetailsStore(
|
||||
})
|
||||
)
|
||||
},
|
||||
togglePriceDetailsModalOpen() {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
state.isPriceDetailsModalOpen = !state.isPriceDetailsModalOpen
|
||||
})
|
||||
)
|
||||
},
|
||||
updateBedType(bedType) {
|
||||
return set(
|
||||
produce((state: DetailsState) => {
|
||||
@@ -328,6 +335,7 @@ export function createDetailsStore(
|
||||
: defaultGuestState,
|
||||
isSubmittingDisabled: false,
|
||||
isSummaryOpen: false,
|
||||
isPriceDetailsModalOpen: false,
|
||||
isValid: {
|
||||
[StepEnum.selectBed]: false,
|
||||
[StepEnum.breakfast]: false,
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface DetailsState {
|
||||
setStep: (step: StepEnum) => void
|
||||
setTotalPrice: (totalPrice: Price) => void
|
||||
toggleSummaryOpen: () => void
|
||||
togglePriceDetailsModalOpen: () => void
|
||||
updateBedType: (data: BedTypeSchema) => void
|
||||
updateBreakfast: (data: BreakfastPackage | false) => void
|
||||
updateDetails: (data: DetailsSchema) => void
|
||||
@@ -47,6 +48,7 @@ export interface DetailsState {
|
||||
guest: DetailsSchema
|
||||
isSubmittingDisabled: boolean
|
||||
isSummaryOpen: boolean
|
||||
isPriceDetailsModalOpen: boolean
|
||||
isValid: Record<StepEnum, boolean>
|
||||
packages: Packages | null
|
||||
roomRate: DetailsProviderProps["roomRate"]
|
||||
|
||||
Reference in New Issue
Block a user