Merged in feat/SW-1676-modify-contact-details-my-stay-anonymous (pull request #1468)
Feat/SW-1676 modify contact details my stay anonymous * feat(SW-1676): Modify guest details step 1 * feat(SW-1676) Integration to api to update guest details * feat(SW-1676) Reuse of old modal * feat(SW-1676) updated modify guest * feat(SW-1676) cleanup * feat(SW-1676) updated myStayReturnRoute to sessionStorage Approved-by: Niclas Edenvin
This commit is contained in:
@@ -94,10 +94,10 @@ export default function Form({ user }: EditFormProps) {
|
|||||||
// Kept logout out of Next router forcing browser to navigate on logout url
|
// Kept logout out of Next router forcing browser to navigate on logout url
|
||||||
window.location.href = logout[lang]
|
window.location.href = logout[lang]
|
||||||
} else {
|
} else {
|
||||||
const myStayReturnRoute = localStorage.getItem("myStayReturnRoute")
|
const myStayReturnRoute = sessionStorage.getItem("myStayReturnRoute")
|
||||||
if (myStayReturnRoute) {
|
if (myStayReturnRoute) {
|
||||||
const returnRoute = JSON.parse(myStayReturnRoute)
|
const returnRoute = JSON.parse(myStayReturnRoute)
|
||||||
localStorage.removeItem("myStayReturnRoute")
|
sessionStorage.removeItem("myStayReturnRoute")
|
||||||
router.push(returnRoute.path)
|
router.push(returnRoute.path)
|
||||||
} else {
|
} else {
|
||||||
router.push(profile[lang])
|
router.push(profile[lang])
|
||||||
|
|||||||
@@ -26,17 +26,19 @@ import SummaryCard from "./SummaryCard"
|
|||||||
|
|
||||||
import styles from "./bookingSummary.module.css"
|
import styles from "./bookingSummary.module.css"
|
||||||
|
|
||||||
import type { Hotel } from "@/types/hotel"
|
import type { Hotel, Room } from "@/types/hotel"
|
||||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
interface BookingSummaryProps {
|
interface BookingSummaryProps {
|
||||||
booking: BookingConfirmation["booking"]
|
booking: BookingConfirmation["booking"]
|
||||||
hotel: Hotel
|
hotel: Hotel
|
||||||
|
room: Room | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BookingSummary({
|
export default function BookingSummary({
|
||||||
booking,
|
booking,
|
||||||
hotel,
|
hotel,
|
||||||
|
room,
|
||||||
}: BookingSummaryProps) {
|
}: BookingSummaryProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
@@ -55,12 +57,10 @@ export default function BookingSummary({
|
|||||||
// Add room details
|
// Add room details
|
||||||
addRoomDetails({
|
addRoomDetails({
|
||||||
id: booking.confirmationNumber ?? "",
|
id: booking.confirmationNumber ?? "",
|
||||||
roomName: booking.roomTypeCode || "Main Room",
|
roomName: room?.name ?? booking.roomTypeCode ?? "",
|
||||||
roomTypeCode: booking.roomTypeCode || "",
|
isCancelable: booking.isCancelable,
|
||||||
rateDefinition: booking.rateDefinition,
|
|
||||||
isMainBooking: true,
|
|
||||||
})
|
})
|
||||||
}, [booking, addRoomPrice, addRoomDetails])
|
}, [booking, room, addRoomPrice, addRoomDetails])
|
||||||
|
|
||||||
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
||||||
const isPaid =
|
const isPaid =
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
|
|||||||
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 { useMyStayRoomDetailsStore } from "../../stores/myStayRoomDetailsStore"
|
||||||
import PriceContainer from "../Pricecontainer"
|
import PriceContainer from "../Pricecontainer"
|
||||||
|
|
||||||
import styles from "../cancelStay.module.css"
|
import styles from "../cancelStay.module.css"
|
||||||
@@ -20,10 +21,10 @@ export function CancelStayConfirmation({
|
|||||||
hotel,
|
hotel,
|
||||||
booking,
|
booking,
|
||||||
stayDetails,
|
stayDetails,
|
||||||
roomDetails = [],
|
|
||||||
}: CancelStayConfirmationProps) {
|
}: CancelStayConfirmationProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { getValues } = useFormContext<FormValues>()
|
const { getValues } = useFormContext<FormValues>()
|
||||||
|
const { rooms: roomDetails } = useMyStayRoomDetailsStore()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -62,9 +63,7 @@ export function CancelStayConfirmation({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
name={`rooms.${index}.checked`}
|
name={`rooms.${index}.checked`}
|
||||||
registerOptions={{
|
registerOptions={{
|
||||||
disabled:
|
disabled: !roomDetail?.isCancelable,
|
||||||
roomDetail?.rateDefinition.cancellationRule !==
|
|
||||||
"CancellableBefore6PM",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles.roomInfo}>
|
<div className={styles.roomInfo}>
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
|||||||
import { FormProvider, useForm } from "react-hook-form"
|
import { FormProvider, useForm } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
|
||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
import { ModalContent } from "../ManageStay/ModalContent"
|
|
||||||
import { useMyStayRoomDetailsStore } from "../stores/myStayRoomDetailsStore"
|
|
||||||
import useCancelStay from "./hooks/useCancelStay"
|
import useCancelStay from "./hooks/useCancelStay"
|
||||||
import { CancelStayConfirmation } from "./Confirmation"
|
import { CancelStayConfirmation } from "./Confirmation"
|
||||||
import { FinalConfirmation } from "./FinalConfirmation"
|
import { FinalConfirmation } from "./FinalConfirmation"
|
||||||
@@ -19,13 +18,9 @@ import {
|
|||||||
cancelStaySchema,
|
cancelStaySchema,
|
||||||
type FormValues,
|
type FormValues,
|
||||||
} from "@/types/components/hotelReservation/myStay/cancelStay"
|
} from "@/types/components/hotelReservation/myStay/cancelStay"
|
||||||
|
import { MODAL_STEPS } from "@/types/components/hotelReservation/myStay/myStay"
|
||||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
const MODAL_STEPS = {
|
|
||||||
INITIAL: 1,
|
|
||||||
CONFIRMATION: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CancelStay({
|
export default function CancelStay({
|
||||||
booking,
|
booking,
|
||||||
hotel,
|
hotel,
|
||||||
@@ -35,7 +30,6 @@ export default function CancelStay({
|
|||||||
}: CancelStayProps) {
|
}: CancelStayProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const { rooms: roomDetails } = useMyStayRoomDetailsStore()
|
|
||||||
|
|
||||||
const { mainRoom } = booking
|
const { mainRoom } = booking
|
||||||
|
|
||||||
@@ -86,7 +80,6 @@ export default function CancelStay({
|
|||||||
hotel={hotel}
|
hotel={hotel}
|
||||||
booking={booking}
|
booking={booking}
|
||||||
stayDetails={stayDetails}
|
stayDetails={stayDetails}
|
||||||
roomDetails={roomDetails}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -112,10 +105,10 @@ export default function CancelStay({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<ModalContent
|
<ModalContentWithActions
|
||||||
title={getModalCopy().title}
|
title={getModalCopy().title}
|
||||||
onClose={handleCloseModal}
|
|
||||||
content={getModalContent()}
|
content={getModalContent()}
|
||||||
|
onClose={handleCloseModal}
|
||||||
primaryAction={
|
primaryAction={
|
||||||
mainRoom
|
mainRoom
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ export default function LinkedReservation({
|
|||||||
// Add room details to the store
|
// Add room details to the store
|
||||||
addRoomDetails({
|
addRoomDetails({
|
||||||
id: booking.confirmationNumber ?? "",
|
id: booking.confirmationNumber ?? "",
|
||||||
roomName: room?.name || booking.roomTypeCode || "Room",
|
roomName: room?.name ?? booking.roomTypeCode ?? "",
|
||||||
roomTypeCode: booking.roomTypeCode || "",
|
isCancelable: booking.isCancelable,
|
||||||
rateDefinition: booking.rateDefinition,
|
|
||||||
isMainBooking: false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [booking, room, addRoomPrice, addRoomDetails])
|
}, [booking, room, addRoomPrice, addRoomDetails])
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmat
|
|||||||
interface ActionPanelProps {
|
interface ActionPanelProps {
|
||||||
booking: BookingConfirmation["booking"]
|
booking: BookingConfirmation["booking"]
|
||||||
hotel: Hotel
|
hotel: Hotel
|
||||||
showCancelButton: boolean
|
showCancelStayButton: boolean
|
||||||
onCancelClick: () => void
|
onCancelClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ActionPanel({
|
export default function ActionPanel({
|
||||||
booking,
|
booking,
|
||||||
hotel,
|
hotel,
|
||||||
showCancelButton,
|
showCancelStayButton,
|
||||||
onCancelClick,
|
onCancelClick,
|
||||||
}: ActionPanelProps) {
|
}: ActionPanelProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
@@ -67,7 +67,7 @@ export default function ActionPanel({
|
|||||||
<div className={styles.menu}>
|
<div className={styles.menu}>
|
||||||
<Button
|
<Button
|
||||||
variant="icon"
|
variant="icon"
|
||||||
onClick={onCancelClick}
|
onClick={() => {}}
|
||||||
intent="text"
|
intent="text"
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
>
|
>
|
||||||
@@ -76,7 +76,7 @@ export default function ActionPanel({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="icon"
|
variant="icon"
|
||||||
onClick={onCancelClick}
|
onClick={() => {}}
|
||||||
intent="text"
|
intent="text"
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
>
|
>
|
||||||
@@ -91,14 +91,14 @@ export default function ActionPanel({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="icon"
|
variant="icon"
|
||||||
onClick={onCancelClick}
|
onClick={() => {}}
|
||||||
intent="text"
|
intent="text"
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: "Download invoice" })}
|
{intl.formatMessage({ id: "Download invoice" })}
|
||||||
<DownloadIcon width={24} height={24} color="burgundy" />
|
<DownloadIcon width={24} height={24} color="burgundy" />
|
||||||
</Button>
|
</Button>
|
||||||
{showCancelButton && (
|
{showCancelStayButton && (
|
||||||
<Button
|
<Button
|
||||||
variant="icon"
|
variant="icon"
|
||||||
onClick={onCancelClick}
|
onClick={onCancelClick}
|
||||||
@@ -119,7 +119,7 @@ export default function ActionPanel({
|
|||||||
{booking.confirmationNumber}
|
{booking.confirmationNumber}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.hotel}>
|
<div>
|
||||||
<Body color="uiTextHighContrast" textAlign="right">
|
<Body color="uiTextHighContrast" textAlign="right">
|
||||||
{hotel.name}
|
{hotel.name}
|
||||||
</Body>
|
</Body>
|
||||||
|
|||||||
@@ -1,24 +1,17 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { motion } from "framer-motion"
|
|
||||||
import { useEffect, useState } from "react"
|
import { useState } from "react"
|
||||||
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
|
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { BookingStatusEnum } from "@/constants/booking"
|
import { BookingStatusEnum } from "@/constants/booking"
|
||||||
|
|
||||||
import { ChevronDownIcon } from "@/components/Icons"
|
import { ChevronDownIcon } from "@/components/Icons"
|
||||||
import {
|
import Modal from "@/components/Modal"
|
||||||
type AnimationState,
|
|
||||||
AnimationStateEnum,
|
|
||||||
} from "@/components/Modal/modal"
|
|
||||||
import { slideFromTop } from "@/components/Modal/motionVariants"
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
|
||||||
import CancelStay from "../CancelStay"
|
import CancelStay from "../CancelStay"
|
||||||
import ActionPanel from "./ActionPanel"
|
import ActionPanel from "./ActionPanel"
|
||||||
|
|
||||||
import styles from "./modifyModal.module.css"
|
|
||||||
|
|
||||||
import type { Hotel } from "@/types/hotel"
|
import type { Hotel } from "@/types/hotel"
|
||||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
@@ -38,39 +31,14 @@ export default function ManageStay({
|
|||||||
bookingStatus,
|
bookingStatus,
|
||||||
}: ManageStayProps) {
|
}: ManageStayProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [animation, setAnimation] = useState<AnimationState>(
|
|
||||||
AnimationStateEnum.visible
|
|
||||||
)
|
|
||||||
const [activeView, setActiveView] = useState<ActiveView>("actionPanel")
|
const [activeView, setActiveView] = useState<ActiveView>("actionPanel")
|
||||||
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const MotionOverlay = motion(ModalOverlay)
|
const showCancelStayButton =
|
||||||
const MotionModal = motion(Modal)
|
|
||||||
|
|
||||||
const showCancelButton =
|
|
||||||
bookingStatus !== BookingStatusEnum.Cancelled && booking.isCancelable
|
bookingStatus !== BookingStatusEnum.Cancelled && booking.isCancelable
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof isOpen === "boolean") {
|
|
||||||
setAnimation(
|
|
||||||
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isOpen === undefined) {
|
|
||||||
setAnimation(AnimationStateEnum.unmounted)
|
|
||||||
}
|
|
||||||
}, [isOpen])
|
|
||||||
|
|
||||||
function modalStateHandler(newAnimationState: AnimationState) {
|
|
||||||
setAnimation((currentAnimationState) =>
|
|
||||||
newAnimationState === AnimationStateEnum.hidden &&
|
|
||||||
currentAnimationState === AnimationStateEnum.hidden
|
|
||||||
? AnimationStateEnum.unmounted
|
|
||||||
: currentAnimationState
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
setActiveView("actionPanel")
|
setActiveView("actionPanel")
|
||||||
@@ -99,7 +67,7 @@ export default function ManageStay({
|
|||||||
booking={booking}
|
booking={booking}
|
||||||
hotel={hotel}
|
hotel={hotel}
|
||||||
onCancelClick={() => setActiveView("cancelStay")}
|
onCancelClick={() => setActiveView("cancelStay")}
|
||||||
showCancelButton={showCancelButton}
|
showCancelStayButton={showCancelStayButton}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -111,28 +79,9 @@ export default function ManageStay({
|
|||||||
{intl.formatMessage({ id: "Manage stay" })}
|
{intl.formatMessage({ id: "Manage stay" })}
|
||||||
<ChevronDownIcon width={24} height={24} color="burgundy" />
|
<ChevronDownIcon width={24} height={24} color="burgundy" />
|
||||||
</Button>
|
</Button>
|
||||||
<MotionOverlay
|
<Modal isOpen={isOpen} onToggle={handleClose} withActions hideHeader>
|
||||||
isOpen={isOpen}
|
{renderContent()}
|
||||||
className={styles.overlay}
|
</Modal>
|
||||||
initial={"hidden"}
|
|
||||||
onAnimationComplete={modalStateHandler}
|
|
||||||
onOpenChange={handleClose}
|
|
||||||
isDismissable
|
|
||||||
>
|
|
||||||
<MotionModal
|
|
||||||
className={styles.modal}
|
|
||||||
initial={"hidden"}
|
|
||||||
animate={animation}
|
|
||||||
variants={slideFromTop}
|
|
||||||
>
|
|
||||||
<Dialog
|
|
||||||
className={styles.dialog}
|
|
||||||
aria-label={intl.formatMessage({ id: "Dialog" })}
|
|
||||||
>
|
|
||||||
{renderContent()}
|
|
||||||
</Dialog>
|
|
||||||
</MotionModal>
|
|
||||||
</MotionOverlay>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
.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: var(--modal-box-shadow);
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: var(--default-modal-z-index);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
/* For removing focus outline when modal opens first time */
|
|
||||||
outline: 0 none;
|
|
||||||
|
|
||||||
/* for supporting animations within content */
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
padding: 0;
|
|
||||||
justify-content: 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);
|
|
||||||
max-width: var(--max-width-page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
"use client"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
import { useFormContext } from "react-hook-form"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import CountrySelect from "@/components/TempDesignSystem/Form/Country"
|
||||||
|
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||||
|
import Phone from "@/components/TempDesignSystem/Form/Phone"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
|
||||||
|
import styles from "./modifyContact.module.css"
|
||||||
|
|
||||||
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
|
|
||||||
|
interface ModifyContactProps {
|
||||||
|
guest: BookingConfirmation["booking"]["guest"]
|
||||||
|
isFirstStep: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ModifyContact({
|
||||||
|
guest,
|
||||||
|
isFirstStep,
|
||||||
|
}: ModifyContactProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const { getValues, setValue } = useFormContext()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue("firstName", guest.firstName ?? "")
|
||||||
|
setValue("lastName", guest.lastName ?? "")
|
||||||
|
setValue("email", guest.email ?? "")
|
||||||
|
setValue("phoneNumber", guest.phoneNumber ?? "")
|
||||||
|
setValue("countryCode", guest.countryCode ?? "")
|
||||||
|
}, [guest, setValue])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isFirstStep ? (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={`${styles.row} ${styles.gridEqual}`}>
|
||||||
|
<Input
|
||||||
|
label={intl.formatMessage({ id: "First name" })}
|
||||||
|
maxLength={30}
|
||||||
|
name="firstName"
|
||||||
|
disabled={!!guest.firstName}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label={intl.formatMessage({ id: "Last name" })}
|
||||||
|
maxLength={30}
|
||||||
|
name="lastName"
|
||||||
|
disabled={!!guest.lastName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<CountrySelect
|
||||||
|
label={intl.formatMessage({ id: "Country" })}
|
||||||
|
name="countryCode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Input
|
||||||
|
label={intl.formatMessage({ id: "Email" })}
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Phone
|
||||||
|
label={intl.formatMessage({ id: "Phone number" })}
|
||||||
|
name="phoneNumber"
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "Are you sure you want to change your guest details?",
|
||||||
|
})}
|
||||||
|
</Body>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Body color="uiTextHighContrast" textTransform="bold">
|
||||||
|
{getValues("firstName")} {getValues("lastName")}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">{getValues("email")}</Body>
|
||||||
|
<Body color="uiTextHighContrast">{getValues("phoneNumber")}</Body>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
.container {
|
||||||
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
|
padding: var(--Spacing-x2) var(--Spacing-x1) var(--Spacing-x3);
|
||||||
|
border-radius: var(--Corner-radius-Medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
margin-bottom: var(--Spacing-x2);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.container {
|
||||||
|
width: 700px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
.gridEqual {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,8 +41,6 @@ export function ReferenceCard({ booking, hotel }: ReferenceCardProps) {
|
|||||||
|
|
||||||
const isCancelled = bookingStatus === BookingStatusEnum.Cancelled
|
const isCancelled = bookingStatus === BookingStatusEnum.Cancelled
|
||||||
|
|
||||||
const showCancelButton = !isCancelled && booking.isCancelable
|
|
||||||
|
|
||||||
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`
|
||||||
|
|
||||||
const adults =
|
const adults =
|
||||||
@@ -149,7 +147,7 @@ export function ReferenceCard({ booking, hotel }: ReferenceCardProps) {
|
|||||||
</IconChip>
|
</IconChip>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!showCancelButton && (
|
{isCancelled && (
|
||||||
<div className={styles.referenceRow}>
|
<div className={styles.referenceRow}>
|
||||||
<IconChip
|
<IconChip
|
||||||
color={"red"}
|
color={"red"}
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Dialog } from "react-aria-components"
|
||||||
|
import { FormProvider, useForm } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { trpc } from "@/lib/trpc/client"
|
||||||
|
|
||||||
import { DiamondIcon, EditIcon } from "@/components/Icons"
|
import { DiamondIcon, EditIcon } from "@/components/Icons"
|
||||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||||
|
import Modal from "@/components/Modal"
|
||||||
|
import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
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 { toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
import ModifyContact from "../ModifyContact"
|
||||||
|
|
||||||
import styles from "./room.module.css"
|
import styles from "./room.module.css"
|
||||||
|
|
||||||
|
import {
|
||||||
|
type ModifyContactSchema,
|
||||||
|
modifyContactSchema,
|
||||||
|
} from "@/types/components/hotelReservation/myStay/modifyContact"
|
||||||
|
import { MODAL_STEPS } from "@/types/components/hotelReservation/myStay/myStay"
|
||||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||||
import type { User } from "@/types/user"
|
import type { User } from "@/types/user"
|
||||||
|
|
||||||
@@ -28,6 +44,26 @@ export default function GuestDetails({
|
|||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const [currentStep, setCurrentStep] = useState(MODAL_STEPS.INITIAL)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [guestDetails, setGuestDetails] = useState<
|
||||||
|
BookingConfirmation["booking"]["guest"]
|
||||||
|
>(booking.guest)
|
||||||
|
const [isModifyGuestDetailsOpen, setIsModifyGuestDetailsOpen] =
|
||||||
|
useState(false)
|
||||||
|
|
||||||
|
const isFirstStep = currentStep === MODAL_STEPS.INITIAL
|
||||||
|
|
||||||
|
const form = useForm<ModifyContactSchema>({
|
||||||
|
resolver: zodResolver(modifyContactSchema),
|
||||||
|
defaultValues: {
|
||||||
|
firstName: booking.guest.firstName ?? "",
|
||||||
|
lastName: booking.guest.lastName ?? "",
|
||||||
|
email: booking.guest.email ?? "",
|
||||||
|
phoneNumber: booking.guest.phoneNumber ?? "",
|
||||||
|
countryCode: booking.guest.countryCode ?? "",
|
||||||
|
},
|
||||||
|
})
|
||||||
const containerClass = isMobile
|
const containerClass = isMobile
|
||||||
? styles.guestDetailsMobile
|
? styles.guestDetailsMobile
|
||||||
: styles.guestDetailsDesktop
|
: styles.guestDetailsDesktop
|
||||||
@@ -35,22 +71,45 @@ export default function GuestDetails({
|
|||||||
const isMemberBooking =
|
const isMemberBooking =
|
||||||
booking.guest.membershipNumber === user?.membership?.membershipNumber
|
booking.guest.membershipNumber === user?.membership?.membershipNumber
|
||||||
|
|
||||||
function handleModifyGuestDetails() {
|
const updateGuest = trpc.booking.update.useMutation({
|
||||||
if (isMemberBooking) {
|
onMutate: () => setIsLoading(true),
|
||||||
const expirationTime = Date.now() + 10 * 60 * 1000
|
onSuccess: () => {
|
||||||
localStorage.setItem(
|
setIsLoading(false)
|
||||||
"myStayReturnRoute",
|
toast.success(intl.formatMessage({ id: "Guest details updated" }))
|
||||||
JSON.stringify({
|
setIsModifyGuestDetailsOpen(false)
|
||||||
path: window.location.pathname,
|
},
|
||||||
expiry: expirationTime,
|
onError: () => {
|
||||||
})
|
setIsLoading(false)
|
||||||
)
|
toast.error(intl.formatMessage({ id: "Failed to update guest details" }))
|
||||||
router.push(`/${lang}/scandic-friends/my-pages/profile/edit`)
|
},
|
||||||
} else {
|
})
|
||||||
console.log("not a member booking") // TODO: Implement non-member booking
|
|
||||||
|
async function onSubmit(data: ModifyContactSchema) {
|
||||||
|
if (booking.confirmationNumber) {
|
||||||
|
updateGuest.mutate({
|
||||||
|
confirmationNumber: booking.confirmationNumber,
|
||||||
|
guest: {
|
||||||
|
email: data.email,
|
||||||
|
phoneNumber: data.phoneNumber,
|
||||||
|
countryCode: data.countryCode,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setGuestDetails({ ...guestDetails, ...data })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleModifyMemberDetails() {
|
||||||
|
const expirationTime = Date.now() + 10 * 60 * 1000
|
||||||
|
sessionStorage.setItem(
|
||||||
|
"myStayReturnRoute",
|
||||||
|
JSON.stringify({
|
||||||
|
path: window.location.pathname,
|
||||||
|
expiry: expirationTime,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
router.push(`/${lang}/scandic-friends/my-pages/profile/edit`)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClass}>
|
<div className={containerClass}>
|
||||||
{isMemberBooking && (
|
{isMemberBooking && (
|
||||||
@@ -75,7 +134,7 @@ export default function GuestDetails({
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.totalPoints}>
|
<div className={styles.totalPoints}>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<div className={styles.totalPointsIcon}>
|
<div>
|
||||||
<DiamondIcon color="uiTextHighContrast" />
|
<DiamondIcon color="uiTextHighContrast" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -95,7 +154,7 @@ export default function GuestDetails({
|
|||||||
)}
|
)}
|
||||||
<div className={styles.guest}>
|
<div className={styles.guest}>
|
||||||
<Body textTransform="bold" color="uiTextHighContrast">
|
<Body textTransform="bold" color="uiTextHighContrast">
|
||||||
{booking.guest.firstName} {booking.guest.lastName}
|
{guestDetails.firstName} {guestDetails.lastName}
|
||||||
</Body>
|
</Body>
|
||||||
{isMemberBooking && (
|
{isMemberBooking && (
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
@@ -103,22 +162,82 @@ export default function GuestDetails({
|
|||||||
{user.membership!.membershipNumber}
|
{user.membership!.membershipNumber}
|
||||||
</Body>
|
</Body>
|
||||||
)}
|
)}
|
||||||
<Caption color="uiTextHighContrast">{booking.guest.email}</Caption>
|
<Caption color="uiTextHighContrast">{guestDetails.email}</Caption>
|
||||||
<Caption color="uiTextHighContrast">
|
<Caption color="uiTextHighContrast">{guestDetails.phoneNumber}</Caption>
|
||||||
{booking.guest.phoneNumber}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{isMemberBooking ? (
|
||||||
variant="icon"
|
<Button
|
||||||
color="burgundy"
|
variant="icon"
|
||||||
intent={isMobile ? "secondary" : "text"}
|
color="burgundy"
|
||||||
onClick={handleModifyGuestDetails}
|
intent={isMobile ? "secondary" : "text"}
|
||||||
>
|
onClick={handleModifyMemberDetails}
|
||||||
<EditIcon color="burgundy" width={20} height={20} />
|
>
|
||||||
<Caption color="burgundy">
|
<EditIcon color="burgundy" width={20} height={20} />
|
||||||
{intl.formatMessage({ id: "Modify guest details" })}
|
<Caption color="burgundy">
|
||||||
</Caption>
|
{intl.formatMessage({ id: "Modify guest details" })}
|
||||||
</Button>
|
</Caption>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="icon"
|
||||||
|
color="burgundy"
|
||||||
|
intent="text"
|
||||||
|
onClick={() =>
|
||||||
|
setIsModifyGuestDetailsOpen(!isModifyGuestDetailsOpen)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<EditIcon color="burgundy" width={20} height={20} />
|
||||||
|
<Caption color="burgundy">
|
||||||
|
{intl.formatMessage({ id: "Modify guest details" })}
|
||||||
|
</Caption>
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
withActions
|
||||||
|
hideHeader
|
||||||
|
isOpen={isModifyGuestDetailsOpen}
|
||||||
|
onToggle={setIsModifyGuestDetailsOpen}
|
||||||
|
>
|
||||||
|
<Dialog>
|
||||||
|
{({ close }) => (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<ModalContentWithActions
|
||||||
|
title={intl.formatMessage({ id: "Modify guest details" })}
|
||||||
|
onClose={() => setIsModifyGuestDetailsOpen(false)}
|
||||||
|
content={
|
||||||
|
<ModifyContact
|
||||||
|
guest={booking.guest}
|
||||||
|
isFirstStep={isFirstStep}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
primaryAction={{
|
||||||
|
label: isFirstStep
|
||||||
|
? intl.formatMessage({ id: "Save updates" })
|
||||||
|
: intl.formatMessage({ id: "Confirm" }),
|
||||||
|
onClick: isFirstStep
|
||||||
|
? () => setCurrentStep(MODAL_STEPS.CONFIRMATION)
|
||||||
|
: () => {
|
||||||
|
form.handleSubmit(onSubmit)()
|
||||||
|
},
|
||||||
|
disabled: !form.formState.isValid || isLoading,
|
||||||
|
intent: isFirstStep ? "secondary" : "primary",
|
||||||
|
}}
|
||||||
|
secondaryAction={{
|
||||||
|
label: isFirstStep
|
||||||
|
? intl.formatMessage({ id: "Back" })
|
||||||
|
: intl.formatMessage({ id: "Cancel" }),
|
||||||
|
onClick: () => {
|
||||||
|
close()
|
||||||
|
setCurrentStep(MODAL_STEPS.INITIAL)
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormProvider>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,11 +67,7 @@ function RoomHeader({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.roomHeader}>
|
<div className={styles.roomHeader}>
|
||||||
<Subtitle
|
<Subtitle textTransform="uppercase" color="burgundy">
|
||||||
textTransform="uppercase"
|
|
||||||
color="burgundy"
|
|
||||||
className={styles.roomName}
|
|
||||||
>
|
|
||||||
{room.name}
|
{room.name}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<ToggleSidePeek
|
<ToggleSidePeek
|
||||||
@@ -92,7 +88,7 @@ export function Room({ booking, room, hotel, user }: RoomProps) {
|
|||||||
const fromDate = dt(booking.checkInDate).locale(lang)
|
const fromDate = dt(booking.checkInDate).locale(lang)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.roomContainer}>
|
<div>
|
||||||
<article className={styles.room}>
|
<article className={styles.room}>
|
||||||
<RoomHeader room={room} hotel={hotel} />
|
<RoomHeader room={room} hotel={hotel} />
|
||||||
<div className={styles.booking}>
|
<div className={styles.booking}>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export async function MyStay({ reservationId }: { reservationId: string }) {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<BookingSummary booking={booking} hotel={hotel} />
|
<BookingSummary booking={booking} hotel={hotel} room={room} />
|
||||||
<Promo
|
<Promo
|
||||||
buttonText={intl.formatMessage({ id: "Book another stay" })}
|
buttonText={intl.formatMessage({ id: "Book another stay" })}
|
||||||
href={`${homeUrl}?hotel=${hotel.operaId}`}
|
href={`${homeUrl}?hotel=${hotel.operaId}`}
|
||||||
|
|||||||
@@ -3,18 +3,7 @@ import { create } from "zustand"
|
|||||||
interface RoomDetails {
|
interface RoomDetails {
|
||||||
id: string
|
id: string
|
||||||
roomName: string
|
roomName: string
|
||||||
roomTypeCode: string
|
isCancelable: boolean
|
||||||
rateDefinition: {
|
|
||||||
breakfastIncluded: boolean
|
|
||||||
cancellationRule: string | null
|
|
||||||
cancellationText: string | null
|
|
||||||
generalTerms: string[]
|
|
||||||
isMemberRate: boolean
|
|
||||||
mustBeGuaranteed: boolean
|
|
||||||
rateCode: string | null
|
|
||||||
title: string | null
|
|
||||||
}
|
|
||||||
isMainBooking?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MyStayRoomDetailsState {
|
interface MyStayRoomDetailsState {
|
||||||
@@ -22,13 +11,10 @@ interface MyStayRoomDetailsState {
|
|||||||
|
|
||||||
// Add a single room's details
|
// Add a single room's details
|
||||||
addRoomDetails: (room: RoomDetails) => void
|
addRoomDetails: (room: RoomDetails) => void
|
||||||
|
|
||||||
// Get room details by confirmationNumber
|
|
||||||
getRoomDetails: (confirmationNumber: string) => RoomDetails | undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMyStayRoomDetailsStore = create<MyStayRoomDetailsState>(
|
export const useMyStayRoomDetailsStore = create<MyStayRoomDetailsState>(
|
||||||
(set, get) => ({
|
(set) => ({
|
||||||
rooms: [],
|
rooms: [],
|
||||||
|
|
||||||
addRoomDetails: (room) => {
|
addRoomDetails: (room) => {
|
||||||
@@ -50,9 +36,5 @@ export const useMyStayRoomDetailsStore = create<MyStayRoomDetailsState>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getRoomDetails: (confirmationNumber) => {
|
|
||||||
return get().rooms.find((room) => room.id === confirmationNumber)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import styles from "./modalContent.module.css"
|
|||||||
import type { ReactNode } from "react"
|
import type { ReactNode } from "react"
|
||||||
|
|
||||||
interface ModalContentProps {
|
interface ModalContentProps {
|
||||||
title: string
|
title?: string
|
||||||
content: ReactNode
|
content: ReactNode
|
||||||
primaryAction: {
|
primaryAction: {
|
||||||
label: string
|
label: string
|
||||||
@@ -21,10 +21,10 @@ interface ModalContentProps {
|
|||||||
onClick: () => void
|
onClick: () => void
|
||||||
intent?: "primary" | "secondary" | "text"
|
intent?: "primary" | "secondary" | "text"
|
||||||
} | null
|
} | null
|
||||||
onClose: () => void
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ModalContent({
|
export function ModalContentWithActions({
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
primaryAction,
|
primaryAction,
|
||||||
@@ -33,12 +33,14 @@ export function ModalContent({
|
|||||||
}: ModalContentProps) {
|
}: ModalContentProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className={styles.header}>
|
{title && (
|
||||||
<Subtitle color="uiTextHighContrast">{title}</Subtitle>
|
<header className={styles.header}>
|
||||||
<button onClick={onClose} type="button" className={styles.close}>
|
<Subtitle>{title}</Subtitle>
|
||||||
<CloseLargeIcon color="uiTextMediumContrast" />
|
<button onClick={onClose} type="button" className={styles.close}>
|
||||||
</button>
|
<CloseLargeIcon color="uiTextMediumContrast" />
|
||||||
</header>
|
</button>
|
||||||
|
</header>
|
||||||
|
)}
|
||||||
<div className={styles.content}>{content}</div>
|
<div className={styles.content}>{content}</div>
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
{secondaryAction && (
|
{secondaryAction && (
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
.content {
|
.content {
|
||||||
width: 640px;
|
|
||||||
max-width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x3);
|
gap: var(--Spacing-x3);
|
||||||
padding: var(--Spacing-x1) var(--Spacing-x3) var(--Spacing-x4);
|
padding: var(--Spacing-x1) var(--Spacing-x3) var(--Spacing-x4);
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
position: relative;
|
display: flex;
|
||||||
padding: var(--Spacing-x3) var(--Spacing-x3) 0;
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x1)
|
||||||
|
var(--Spacing-x3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
@@ -34,3 +36,10 @@
|
|||||||
top: 20px;
|
top: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.content {
|
||||||
|
width: 640px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
type ModalProps,
|
type ModalProps,
|
||||||
} from "./modal"
|
} from "./modal"
|
||||||
import { fade, slideInOut } from "./motionVariants"
|
import { fade, slideInOut } from "./motionVariants"
|
||||||
|
import { modalContentVariants } from "./variants"
|
||||||
|
|
||||||
import styles from "./modal.module.css"
|
import styles from "./modal.module.css"
|
||||||
|
|
||||||
@@ -36,9 +37,15 @@ function InnerModal({
|
|||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
|
withActions,
|
||||||
|
hideHeader,
|
||||||
}: PropsWithChildren<InnerModalProps>) {
|
}: PropsWithChildren<InnerModalProps>) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const contentClassNames = modalContentVariants({
|
||||||
|
withActions: withActions,
|
||||||
|
})
|
||||||
|
|
||||||
function modalStateHandler(newAnimationState: AnimationState) {
|
function modalStateHandler(newAnimationState: AnimationState) {
|
||||||
setAnimation((currentAnimationState) =>
|
setAnimation((currentAnimationState) =>
|
||||||
newAnimationState === AnimationStateEnum.hidden &&
|
newAnimationState === AnimationStateEnum.hidden &&
|
||||||
@@ -79,27 +86,34 @@ function InnerModal({
|
|||||||
>
|
>
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<>
|
<>
|
||||||
<header
|
{!hideHeader && (
|
||||||
className={`${styles.header} ${!subtitle ? styles.verticalCenter : ""}`}
|
<header
|
||||||
>
|
className={`${styles.header} ${!subtitle ? styles.verticalCenter : ""}`}
|
||||||
<div>
|
>
|
||||||
{title && (
|
<div>
|
||||||
<Subtitle type="one" color="uiTextHighContrast">
|
{title && (
|
||||||
{title}
|
<Subtitle type="one" color="uiTextHighContrast">
|
||||||
</Subtitle>
|
{title}
|
||||||
)}
|
</Subtitle>
|
||||||
{subtitle && (
|
)}
|
||||||
<Preamble asChild>
|
{subtitle && (
|
||||||
<span>{subtitle}</span>
|
<Preamble asChild>
|
||||||
</Preamble>
|
<span>{subtitle}</span>
|
||||||
)}
|
</Preamble>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<button onClick={close} type="button" className={styles.close}>
|
<button
|
||||||
<CloseLargeIcon color="uiTextMediumContrast" />
|
onClick={close}
|
||||||
</button>
|
type="button"
|
||||||
</header>
|
className={styles.close}
|
||||||
<section className={styles.content}>{children}</section>
|
>
|
||||||
|
<CloseLargeIcon color="uiTextMediumContrast" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<section className={contentClassNames}>{children}</section>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -116,6 +130,8 @@ export default function Modal({
|
|||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
children,
|
children,
|
||||||
|
withActions = false,
|
||||||
|
hideHeader = false,
|
||||||
}: PropsWithChildren<ModalProps>) {
|
}: PropsWithChildren<ModalProps>) {
|
||||||
const [animation, setAnimation] = useState<AnimationState>(
|
const [animation, setAnimation] = useState<AnimationState>(
|
||||||
AnimationStateEnum.visible
|
AnimationStateEnum.visible
|
||||||
@@ -142,6 +158,8 @@ export default function Modal({
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title={title}
|
title={title}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
|
withActions={withActions}
|
||||||
|
hideHeader={hideHeader}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</InnerModal>
|
</InnerModal>
|
||||||
@@ -163,6 +181,7 @@ export default function Modal({
|
|||||||
setAnimation={setAnimation}
|
setAnimation={setAnimation}
|
||||||
title={title}
|
title={title}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
|
withActions={withActions}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</InnerModal>
|
</InnerModal>
|
||||||
|
|||||||
@@ -49,10 +49,17 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x2);
|
||||||
padding: 0 var(--Spacing-x3) var(--Spacing-x4);
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contentWithActions {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentWithoutActions {
|
||||||
|
padding: 0 var(--Spacing-x3) var(--Spacing-x4);
|
||||||
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export type ModalProps = {
|
|||||||
onAnimationComplete?: VoidFunction
|
onAnimationComplete?: VoidFunction
|
||||||
title?: string
|
title?: string
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
|
withActions?: boolean
|
||||||
|
hideHeader?: boolean
|
||||||
} & (
|
} & (
|
||||||
| { trigger: JSX.Element; isOpen?: never; onToggle?: never }
|
| { trigger: JSX.Element; isOpen?: never; onToggle?: never }
|
||||||
| {
|
| {
|
||||||
|
|||||||
17
apps/scandic-web/components/Modal/variants.ts
Normal file
17
apps/scandic-web/components/Modal/variants.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
import styles from "./modal.module.css"
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
variants: {
|
||||||
|
withActions: {
|
||||||
|
true: styles.contentWithActions,
|
||||||
|
false: styles.contentWithoutActions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
withActions: false,
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const modalContentVariants = cva(styles.content, config)
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
"Approx.": "Ca.",
|
"Approx.": "Ca.",
|
||||||
"Approx. {value}": "Approx. {value}",
|
"Approx. {value}": "Approx. {value}",
|
||||||
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Er du sikker på, at du vil annullere dit ophold hos {hotel} fra {checkInDate} til {checkOutDate}? Dette kan ikke gendannes.",
|
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Er du sikker på, at du vil annullere dit ophold hos {hotel} fra {checkInDate} til {checkOutDate}? Dette kan ikke gendannes.",
|
||||||
|
"Are you sure you want to change your guest details?": "Er du sikker på, at du vil ændre dine gæstdetaljer?",
|
||||||
"Are you sure you want to continue with the cancellation?": "Er du sikker på, at du vil fortsætte med annullereringen?",
|
"Are you sure you want to continue with the cancellation?": "Er du sikker på, at du vil fortsætte med annullereringen?",
|
||||||
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på, at du vil fjerne kortet, der slutter me {lastFourDigits} fra din medlemsprofil?",
|
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på, at du vil fjerne kortet, der slutter me {lastFourDigits} fra din medlemsprofil?",
|
||||||
"Are you sure you want to remove this product?": "Er du sikker på, at du vil fjerne dette produkt?",
|
"Are you sure you want to remove this product?": "Er du sikker på, at du vil fjerne dette produkt?",
|
||||||
@@ -233,6 +234,7 @@
|
|||||||
"Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.",
|
"Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.",
|
||||||
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
||||||
"Failed to unlink account": "Failed to unlink account",
|
"Failed to unlink account": "Failed to unlink account",
|
||||||
|
"Failed to update guest details": "Fejl ved opdatering af gæstdetaljer",
|
||||||
"Failed to upgrade level": "Failed to upgrade level",
|
"Failed to upgrade level": "Failed to upgrade level",
|
||||||
"Failed to verify membership": "Medlemskab ikke verificeret",
|
"Failed to verify membership": "Medlemskab ikke verificeret",
|
||||||
"Fair": "Messe",
|
"Fair": "Messe",
|
||||||
@@ -280,6 +282,7 @@
|
|||||||
"Great minds meet here": "Great minds meet here",
|
"Great minds meet here": "Great minds meet here",
|
||||||
"Guarantee booking with credit card": "Garantere booking med kreditkort",
|
"Guarantee booking with credit card": "Garantere booking med kreditkort",
|
||||||
"Guarantee late arrival": "Garanter sen ankomst",
|
"Guarantee late arrival": "Garanter sen ankomst",
|
||||||
|
"Guest details updated": "Gæstdetaljer opdateret",
|
||||||
"Guest information": "Gæsteinformation",
|
"Guest information": "Gæsteinformation",
|
||||||
"Guests": "Gæster",
|
"Guests": "Gæster",
|
||||||
"Guests & Rooms": "Gæster & værelser",
|
"Guests & Rooms": "Gæster & værelser",
|
||||||
@@ -574,6 +577,7 @@
|
|||||||
"Sauna and gym": "Sauna and gym",
|
"Sauna and gym": "Sauna and gym",
|
||||||
"Save": "Gemme",
|
"Save": "Gemme",
|
||||||
"Save card to profile": "Save card to profile",
|
"Save card to profile": "Save card to profile",
|
||||||
|
"Save updates": "Gem ændringer",
|
||||||
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
||||||
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
||||||
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
"Approx.": "Ca.",
|
"Approx.": "Ca.",
|
||||||
"Approx. {value}": "Approx. {value}",
|
"Approx. {value}": "Approx. {value}",
|
||||||
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Sind Sie sicher, dass Sie Ihren Aufenthalt bei {hotel} vom {checkInDate} bis {checkOutDate} stornieren möchten? Dies kann nicht rückgängig gemacht werden.",
|
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Sind Sie sicher, dass Sie Ihren Aufenthalt bei {hotel} vom {checkInDate} bis {checkOutDate} stornieren möchten? Dies kann nicht rückgängig gemacht werden.",
|
||||||
|
"Are you sure you want to change your guest details?": "Sind Sie sicher, dass Sie Ihre Gästedaten ändern möchten?",
|
||||||
"Are you sure you want to continue with the cancellation?": "Sind Sie sicher, dass Sie mit der Stornierung fortfahren möchten?",
|
"Are you sure you want to continue with the cancellation?": "Sind Sie sicher, dass Sie mit der Stornierung fortfahren möchten?",
|
||||||
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Möchten Sie die Karte mit der Endung {lastFourDigits} wirklich aus Ihrem Mitgliedsprofil entfernen?",
|
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Möchten Sie die Karte mit der Endung {lastFourDigits} wirklich aus Ihrem Mitgliedsprofil entfernen?",
|
||||||
"Are you sure you want to remove this product?": "Möchten Sie dieses Produkt wirklich entfernen?",
|
"Are you sure you want to remove this product?": "Möchten Sie dieses Produkt wirklich entfernen?",
|
||||||
@@ -234,6 +235,7 @@
|
|||||||
"Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.",
|
"Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.",
|
||||||
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
||||||
"Failed to unlink account": "Failed to unlink account",
|
"Failed to unlink account": "Failed to unlink account",
|
||||||
|
"Failed to update guest details": "Fehler beim Aktualisieren der Gästedaten",
|
||||||
"Failed to upgrade level": "Failed to upgrade level",
|
"Failed to upgrade level": "Failed to upgrade level",
|
||||||
"Failed to verify membership": "Medlemskab nicht verifiziert",
|
"Failed to verify membership": "Medlemskab nicht verifiziert",
|
||||||
"Fair": "Messe",
|
"Fair": "Messe",
|
||||||
@@ -281,6 +283,7 @@
|
|||||||
"Great minds meet here": "Great minds meet here",
|
"Great minds meet here": "Great minds meet here",
|
||||||
"Guarantee booking with credit card": "Buchung mit Kreditkarte garantieren",
|
"Guarantee booking with credit card": "Buchung mit Kreditkarte garantieren",
|
||||||
"Guarantee late arrival": "Garantere sen ankomst",
|
"Guarantee late arrival": "Garantere sen ankomst",
|
||||||
|
"Guest details updated": "Gästedaten aktualisiert",
|
||||||
"Guest information": "Informationen für Gäste",
|
"Guest information": "Informationen für Gäste",
|
||||||
"Guests": "Gäste",
|
"Guests": "Gäste",
|
||||||
"Guests & Rooms": "Gäste & Zimmer",
|
"Guests & Rooms": "Gäste & Zimmer",
|
||||||
@@ -573,6 +576,7 @@
|
|||||||
"Sauna and gym": "Sauna and gym",
|
"Sauna and gym": "Sauna and gym",
|
||||||
"Save": "Speichern",
|
"Save": "Speichern",
|
||||||
"Save card to profile": "Save card to profile",
|
"Save card to profile": "Save card to profile",
|
||||||
|
"Save updates": "Updates speichern",
|
||||||
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
||||||
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
||||||
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"Approx.": "Approx.",
|
"Approx.": "Approx.",
|
||||||
"Approx. {value}": "Approx. {value}",
|
"Approx. {value}": "Approx. {value}",
|
||||||
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.",
|
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.",
|
||||||
|
"Are you sure you want to change your guest details?": "Are you sure you want to change your guest details?",
|
||||||
"Are you sure you want to continue with the cancellation?": "Are you sure you want to continue with the cancellation?",
|
"Are you sure you want to continue with the cancellation?": "Are you sure you want to continue with the cancellation?",
|
||||||
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?",
|
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?",
|
||||||
"Are you sure you want to remove this product?": "Are you sure you want to remove this product?",
|
"Are you sure you want to remove this product?": "Are you sure you want to remove this product?",
|
||||||
@@ -238,6 +239,7 @@
|
|||||||
"Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.",
|
"Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.",
|
||||||
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
||||||
"Failed to unlink account": "Failed to unlink account",
|
"Failed to unlink account": "Failed to unlink account",
|
||||||
|
"Failed to update guest details": "Failed to update guest details",
|
||||||
"Failed to upgrade level": "Failed to upgrade level",
|
"Failed to upgrade level": "Failed to upgrade level",
|
||||||
"Failed to verify membership": "Failed to verify membership",
|
"Failed to verify membership": "Failed to verify membership",
|
||||||
"Fair": "Fair",
|
"Fair": "Fair",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"Great minds meet here": "Great minds meet here",
|
"Great minds meet here": "Great minds meet here",
|
||||||
"Guarantee booking with credit card": "Guarantee booking with credit card",
|
"Guarantee booking with credit card": "Guarantee booking with credit card",
|
||||||
"Guarantee late arrival": "Guarantee late arrival",
|
"Guarantee late arrival": "Guarantee late arrival",
|
||||||
|
"Guest details updated": "Guest details updated",
|
||||||
"Guest information": "Guest information",
|
"Guest information": "Guest information",
|
||||||
"Guests": "Guests",
|
"Guests": "Guests",
|
||||||
"Guests & Rooms": "Guests & Rooms",
|
"Guests & Rooms": "Guests & Rooms",
|
||||||
@@ -580,6 +583,7 @@
|
|||||||
"Sauna and gym": "Sauna and gym",
|
"Sauna and gym": "Sauna and gym",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Save card to profile": "Save card to profile",
|
"Save card to profile": "Save card to profile",
|
||||||
|
"Save updates": "Save updates",
|
||||||
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
||||||
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
||||||
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"Approx.": "N.",
|
"Approx.": "N.",
|
||||||
"Approx. {value}": "N. {value}",
|
"Approx. {value}": "N. {value}",
|
||||||
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Oletko varmasti haluamassa peruuttaa majoituksesi hoteleissa {hotel} alkaen {checkInDate} asti {checkOutDate}? Tätä ei voi kumota.",
|
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Oletko varmasti haluamassa peruuttaa majoituksesi hoteleissa {hotel} alkaen {checkInDate} asti {checkOutDate}? Tätä ei voi kumota.",
|
||||||
|
"Are you sure you want to change your guest details?": "Haluatko varmasti muuttaa gästien tiedoistasi?",
|
||||||
"Are you sure you want to continue with the cancellation?": "Oletko varmasti haluamassa jatkaa peruuttamista?",
|
"Are you sure you want to continue with the cancellation?": "Oletko varmasti haluamassa jatkaa peruuttamista?",
|
||||||
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Haluatko varmasti poistaa kortin, joka päättyy numeroon {lastFourDigits} jäsenprofiilistasi?",
|
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Haluatko varmasti poistaa kortin, joka päättyy numeroon {lastFourDigits} jäsenprofiilistasi?",
|
||||||
"Are you sure you want to remove this product?": "Haluatko varmasti poistaa tämän tuotteen?",
|
"Are you sure you want to remove this product?": "Haluatko varmasti poistaa tämän tuotteen?",
|
||||||
@@ -233,6 +234,7 @@
|
|||||||
"Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.",
|
"Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.",
|
||||||
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
||||||
"Failed to unlink account": "Failed to unlink account",
|
"Failed to unlink account": "Failed to unlink account",
|
||||||
|
"Failed to update guest details": "Gästien tiedojen päivitys epäonnistui",
|
||||||
"Failed to upgrade level": "Failed to upgrade level",
|
"Failed to upgrade level": "Failed to upgrade level",
|
||||||
"Failed to verify membership": "Jäsenyys ei verifioitu",
|
"Failed to verify membership": "Jäsenyys ei verifioitu",
|
||||||
"Fair": "Messukeskus",
|
"Fair": "Messukeskus",
|
||||||
@@ -280,6 +282,7 @@
|
|||||||
"Great minds meet here": "Great minds meet here",
|
"Great minds meet here": "Great minds meet here",
|
||||||
"Guarantee booking with credit card": "Varmista varaus luottokortilla",
|
"Guarantee booking with credit card": "Varmista varaus luottokortilla",
|
||||||
"Guarantee late arrival": "Varmista myöhäisempi tulema",
|
"Guarantee late arrival": "Varmista myöhäisempi tulema",
|
||||||
|
"Guest details updated": "Gästien tiedot päivitetty",
|
||||||
"Guest information": "Vieraan tiedot",
|
"Guest information": "Vieraan tiedot",
|
||||||
"Guests": "Vierailijat",
|
"Guests": "Vierailijat",
|
||||||
"Guests & Rooms": "Vieraat & Huoneet",
|
"Guests & Rooms": "Vieraat & Huoneet",
|
||||||
@@ -573,6 +576,7 @@
|
|||||||
"Sauna and gym": "Sauna and gym",
|
"Sauna and gym": "Sauna and gym",
|
||||||
"Save": "Tallenna",
|
"Save": "Tallenna",
|
||||||
"Save card to profile": "Save card to profile",
|
"Save card to profile": "Save card to profile",
|
||||||
|
"Save updates": "Tallenna muutokset",
|
||||||
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
||||||
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
||||||
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"Approx.": "Ca.",
|
"Approx.": "Ca.",
|
||||||
"Approx. {value}": "Ca. {value}",
|
"Approx. {value}": "Ca. {value}",
|
||||||
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Er du sikker på, at du vil annullere dit ophold hos {hotel} fra {checkInDate} til {checkOutDate}? Dette kan ikke gendannes.",
|
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Er du sikker på, at du vil annullere dit ophold hos {hotel} fra {checkInDate} til {checkOutDate}? Dette kan ikke gendannes.",
|
||||||
|
"Are you sure you want to change your guest details?": "Er du sikker på at du vil endre gjestens detaljer?",
|
||||||
"Are you sure you want to continue with the cancellation?": "Er du sikker på, at du vil fortsætte med annullereringen?",
|
"Are you sure you want to continue with the cancellation?": "Er du sikker på, at du vil fortsætte med annullereringen?",
|
||||||
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på at du vil fjerne kortet som slutter på {lastFourDigits} fra medlemsprofilen din?",
|
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på at du vil fjerne kortet som slutter på {lastFourDigits} fra medlemsprofilen din?",
|
||||||
"Are you sure you want to remove this product?": "Er du sikker på at du vil fjerne dette produktet?",
|
"Are you sure you want to remove this product?": "Er du sikker på at du vil fjerne dette produktet?",
|
||||||
@@ -232,6 +233,7 @@
|
|||||||
"Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.",
|
"Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.",
|
||||||
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
||||||
"Failed to unlink account": "Failed to unlink account",
|
"Failed to unlink account": "Failed to unlink account",
|
||||||
|
"Failed to update guest details": "Feil ved oppdatering av gjestens detaljer",
|
||||||
"Failed to upgrade level": "Failed to upgrade level",
|
"Failed to upgrade level": "Failed to upgrade level",
|
||||||
"Failed to verify membership": "Medlemskap ikke verifisert",
|
"Failed to verify membership": "Medlemskap ikke verifisert",
|
||||||
"Fair": "Messe",
|
"Fair": "Messe",
|
||||||
@@ -279,6 +281,7 @@
|
|||||||
"Great minds meet here": "Great minds meet here",
|
"Great minds meet here": "Great minds meet here",
|
||||||
"Guarantee booking with credit card": "Garantere booking med kredittkort",
|
"Guarantee booking with credit card": "Garantere booking med kredittkort",
|
||||||
"Guarantee late arrival": "Garantere sen ankomst",
|
"Guarantee late arrival": "Garantere sen ankomst",
|
||||||
|
"Guest details updated": "Gjestens detaljer oppdatert",
|
||||||
"Guest information": "Informasjon til gjester",
|
"Guest information": "Informasjon til gjester",
|
||||||
"Guests": "Gjester",
|
"Guests": "Gjester",
|
||||||
"Guests & Rooms": "Gjester & rom",
|
"Guests & Rooms": "Gjester & rom",
|
||||||
@@ -571,6 +574,7 @@
|
|||||||
"Sauna and gym": "Sauna and gym",
|
"Sauna and gym": "Sauna and gym",
|
||||||
"Save": "Lagre",
|
"Save": "Lagre",
|
||||||
"Save card to profile": "Save card to profile",
|
"Save card to profile": "Save card to profile",
|
||||||
|
"Save updates": "Lagre endringer",
|
||||||
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
||||||
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
||||||
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"Approx.": "Ca.",
|
"Approx.": "Ca.",
|
||||||
"Approx. {value}": "Ca. {value}",
|
"Approx. {value}": "Ca. {value}",
|
||||||
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Är du säker på att du vill avboka din vistelse hos {hotel} från {checkInDate} till {checkOutDate}? Detta kan inte ångras.",
|
"Are you sure you want to cancel your stay at {hotel} from {checkInDate} to {checkOutDate}? This can't be reversed.": "Är du säker på att du vill avboka din vistelse hos {hotel} från {checkInDate} till {checkOutDate}? Detta kan inte ångras.",
|
||||||
|
"Are you sure you want to change your guest details?": "Är du säker på att du vill ändra dina gästdetaljer?",
|
||||||
"Are you sure you want to continue with the cancellation?": "Är du säker på att du vill fortsätta med avbokningen?",
|
"Are you sure you want to continue with the cancellation?": "Är du säker på att du vill fortsätta med avbokningen?",
|
||||||
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Är du säker på att du vill ta bort kortet som slutar med {lastFourDigits} från din medlemsprofil?",
|
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Är du säker på att du vill ta bort kortet som slutar med {lastFourDigits} från din medlemsprofil?",
|
||||||
"Are you sure you want to remove this product?": "Är du säker på att du vill ta bort den här produkten?",
|
"Are you sure you want to remove this product?": "Är du säker på att du vill ta bort den här produkten?",
|
||||||
@@ -232,6 +233,7 @@
|
|||||||
"Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.",
|
"Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.",
|
||||||
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
|
||||||
"Failed to unlink account": "Failed to unlink account",
|
"Failed to unlink account": "Failed to unlink account",
|
||||||
|
"Failed to update guest details": "Misslyckades att uppdatera gästdetaljer",
|
||||||
"Failed to upgrade level": "Failed to upgrade level",
|
"Failed to upgrade level": "Failed to upgrade level",
|
||||||
"Failed to verify membership": "Medlemskap inte verifierat",
|
"Failed to verify membership": "Medlemskap inte verifierat",
|
||||||
"Fair": "Mässa",
|
"Fair": "Mässa",
|
||||||
@@ -279,6 +281,7 @@
|
|||||||
"Great minds meet here": "Great minds meet here",
|
"Great minds meet here": "Great minds meet here",
|
||||||
"Guarantee booking with credit card": "Garantera bokning med kreditkort",
|
"Guarantee booking with credit card": "Garantera bokning med kreditkort",
|
||||||
"Guarantee late arrival": "Garantera sen ankomst",
|
"Guarantee late arrival": "Garantera sen ankomst",
|
||||||
|
"Guest details updated": "Gästdetaljer uppdaterade",
|
||||||
"Guest information": "Information till gästerna",
|
"Guest information": "Information till gästerna",
|
||||||
"Guests": "Gäster",
|
"Guests": "Gäster",
|
||||||
"Guests & Rooms": "Gäster & rum",
|
"Guests & Rooms": "Gäster & rum",
|
||||||
@@ -571,6 +574,7 @@
|
|||||||
"Sauna and gym": "Sauna and gym",
|
"Sauna and gym": "Sauna and gym",
|
||||||
"Save": "Spara",
|
"Save": "Spara",
|
||||||
"Save card to profile": "Save card to profile",
|
"Save card to profile": "Save card to profile",
|
||||||
|
"Save updates": "Spara uppdateringar",
|
||||||
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
|
||||||
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
|
||||||
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
"Scandic ♥ SAS": "Scandic ♥ SAS",
|
||||||
|
|||||||
@@ -120,6 +120,19 @@ export const createRefIdInput = z.object({
|
|||||||
lastName: z.string().trim().max(250).min(1),
|
lastName: z.string().trim().max(250).min(1),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const updateBookingInput = z.object({
|
||||||
|
confirmationNumber: z.string(),
|
||||||
|
checkInDate: z.string().optional(),
|
||||||
|
checkOutDate: z.string().optional(),
|
||||||
|
guest: z
|
||||||
|
.object({
|
||||||
|
email: z.string().optional(),
|
||||||
|
phoneNumber: z.string().optional(),
|
||||||
|
countryCode: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
|
||||||
// Query
|
// Query
|
||||||
const confirmationNumberInput = z.object({
|
const confirmationNumberInput = z.object({
|
||||||
confirmationNumber: z.string(),
|
confirmationNumber: z.string(),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
cancelBookingInput,
|
cancelBookingInput,
|
||||||
createBookingInput,
|
createBookingInput,
|
||||||
priceChangeInput,
|
priceChangeInput,
|
||||||
|
updateBookingInput,
|
||||||
removePackageInput,
|
removePackageInput,
|
||||||
} from "./input"
|
} from "./input"
|
||||||
import { createBookingSchema } from "./output"
|
import { createBookingSchema } from "./output"
|
||||||
@@ -49,6 +50,14 @@ const addPackageFailCounter = meter.createCounter(
|
|||||||
"trpc.bookings.add-package-fail"
|
"trpc.bookings.add-package-fail"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const updateGuestCounter = meter.createCounter("trpc.bookings.update-guest")
|
||||||
|
const updateGuestSuccessCounter = meter.createCounter(
|
||||||
|
"trpc.bookings.update-guest-success"
|
||||||
|
)
|
||||||
|
const updateGuestFailCounter = meter.createCounter(
|
||||||
|
"trpc.bookings.update-guest-fail"
|
||||||
|
)
|
||||||
|
|
||||||
const removePackageCounter = meter.createCounter("trpc.bookings.remove-package")
|
const removePackageCounter = meter.createCounter("trpc.bookings.remove-package")
|
||||||
const removePackageSuccessCounter = meter.createCounter(
|
const removePackageSuccessCounter = meter.createCounter(
|
||||||
"trpc.bookings.remove-package-success"
|
"trpc.bookings.remove-package-success"
|
||||||
@@ -386,6 +395,70 @@ export const bookingMutationRouter = router({
|
|||||||
|
|
||||||
addPackageSuccessCounter.add(1, { confirmationNumber })
|
addPackageSuccessCounter.add(1, { confirmationNumber })
|
||||||
|
|
||||||
|
return verifiedData.data
|
||||||
|
}),
|
||||||
|
update: safeProtectedServiceProcedure
|
||||||
|
.input(updateBookingInput)
|
||||||
|
.mutation(async function ({ ctx, input }) {
|
||||||
|
const accessToken = ctx.serviceToken
|
||||||
|
const { confirmationNumber, ...body } = input
|
||||||
|
|
||||||
|
updateGuestCounter.add(1, { confirmationNumber })
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiResponse = await api.put(
|
||||||
|
api.endpoints.v1.Booking.booking(confirmationNumber),
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
body: body,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!apiResponse.ok) {
|
||||||
|
const text = await apiResponse.text()
|
||||||
|
updateGuestFailCounter.add(1, {
|
||||||
|
confirmationNumber,
|
||||||
|
error_type: "http_error",
|
||||||
|
error: JSON.stringify({
|
||||||
|
status: apiResponse.status,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
console.error(
|
||||||
|
"api.booking.updateGuest error",
|
||||||
|
JSON.stringify({
|
||||||
|
query: { confirmationNumber },
|
||||||
|
error: {
|
||||||
|
status: apiResponse.status,
|
||||||
|
statusText: apiResponse.statusText,
|
||||||
|
error: text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiJson = await apiResponse.json()
|
||||||
|
const verifiedData = createBookingSchema.safeParse(apiJson)
|
||||||
|
if (!verifiedData.success) {
|
||||||
|
updateGuestFailCounter.add(1, {
|
||||||
|
confirmationNumber,
|
||||||
|
error_type: "validation_error",
|
||||||
|
})
|
||||||
|
console.error(
|
||||||
|
"api.booking.updateGuest validation error",
|
||||||
|
JSON.stringify({
|
||||||
|
query: { confirmationNumber },
|
||||||
|
error: verifiedData.error,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGuestSuccessCounter.add(1, { confirmationNumber })
|
||||||
|
|
||||||
return verifiedData.data
|
return verifiedData.data
|
||||||
}),
|
}),
|
||||||
removePackage: safeProtectedServiceProcedure
|
removePackage: safeProtectedServiceProcedure
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ const guestSchema = z.object({
|
|||||||
lastName: z.string().nullable().default(""),
|
lastName: z.string().nullable().default(""),
|
||||||
membershipNumber: z.string().nullable().default(""),
|
membershipNumber: z.string().nullable().default(""),
|
||||||
phoneNumber: phoneValidator().nullable().default(""),
|
phoneNumber: phoneValidator().nullable().default(""),
|
||||||
|
countryCode: z.string().nullable().default(""),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const packageSchema = z
|
export const packageSchema = z
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export interface CancelStayConfirmationProps {
|
|||||||
hotel: Hotel
|
hotel: Hotel
|
||||||
booking: BookingConfirmation["booking"]
|
booking: BookingConfirmation["booking"]
|
||||||
stayDetails: StayDetails
|
stayDetails: StayDetails
|
||||||
roomDetails?: RoomDetails[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FinalConfirmationProps {
|
export interface FinalConfirmationProps {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const modifyContactSchema = z.object({
|
||||||
|
firstName: z.string(),
|
||||||
|
lastName: z.string(),
|
||||||
|
email: z.string().email(),
|
||||||
|
phoneNumber: z.string(),
|
||||||
|
countryCode: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type ModifyContactSchema = z.infer<typeof modifyContactSchema>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export enum MODAL_STEPS {
|
||||||
|
INITIAL = 1,
|
||||||
|
CONFIRMATION = 2,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user