Merged in feat(SW-1279)-mystay-multirum-cancelling (pull request #1443)

Feat(SW-1279) mystay multirum cancelling

* feat(SW-1279) Cancelation text if non-user on room 2-4

* feat(SW-1279) cancel mystay multiroom

* feat(SW-1279): Added cancellation for multiroom on mystay


Approved-by: Niclas Edenvin
This commit is contained in:
Pontus Dreij
2025-02-28 07:17:25 +00:00
parent bee6c6d83a
commit 69139c5230
24 changed files with 646 additions and 168 deletions

View File

@@ -20,6 +20,7 @@ import { Toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { formatPrice } from "@/utils/numberFormatting" import { formatPrice } from "@/utils/numberFormatting"
import { useMyStayRoomDetailsStore } from "../stores/myStayRoomDetailsStore"
import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice" import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice"
import SummaryCard from "./SummaryCard" import SummaryCard from "./SummaryCard"
@@ -40,15 +41,26 @@ export default function BookingSummary({
const intl = useIntl() const intl = useIntl()
const lang = useLang() const lang = useLang()
const { totalPrice, currencyCode, addRoomPrice } = useMyStayTotalPriceStore() const { totalPrice, currencyCode, addRoomPrice } = useMyStayTotalPriceStore()
const { addRoomDetails } = useMyStayRoomDetailsStore()
useEffect(() => { useEffect(() => {
// Add price information
addRoomPrice({ addRoomPrice({
id: booking.confirmationNumber ?? "", id: booking.confirmationNumber ?? "",
totalPrice: booking.totalPrice, totalPrice: booking.totalPrice,
currencyCode: booking.currencyCode, currencyCode: booking.currencyCode,
isMainBooking: true, isMainBooking: true,
}) })
}, [booking, addRoomPrice])
// Add room details
addRoomDetails({
id: booking.confirmationNumber ?? "",
roomName: booking.roomTypeCode || "Main Room",
roomTypeCode: booking.roomTypeCode || "",
rateDefinition: booking.rateDefinition,
isMainBooking: true,
})
}, [booking, 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 =
@@ -130,7 +142,7 @@ export default function BookingSummary({
{hotel.specialAlerts.length > 0 && ( {hotel.specialAlerts.length > 0 && (
<div className={styles.toast}> <div className={styles.toast}>
<Toast variant="info"> <Toast variant="info">
<ul className={styles.list}> <ul>
{hotel.specialAlerts.map((alert) => ( {hotel.specialAlerts.map((alert) => (
<li key={alert.id}> <li key={alert.id}>
<Body color="uiTextHighContrast">{alert.text}</Body> <Body color="uiTextHighContrast">{alert.text}</Body>

View File

@@ -1,32 +1,29 @@
"use client"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
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 Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import PriceContainer from "../Pricecontainer"
import styles from "../cancelStay.module.css" import styles from "../cancelStay.module.css"
import type { Hotel } from "@/types/hotel" import type {
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" CancelStayConfirmationProps,
FormValues,
interface CancelStayConfirmationProps { } from "@/types/components/hotelReservation/myStay/cancelStay"
hotel: Hotel
booking: BookingConfirmation["booking"]
stayDetails: {
checkInDate: string
checkOutDate: string
nightsText: string
adultsText: string
childrenText: string
}
}
export function CancelStayConfirmation({ export function CancelStayConfirmation({
hotel, hotel,
booking, booking,
stayDetails, stayDetails,
roomDetails = [],
}: CancelStayConfirmationProps) { }: CancelStayConfirmationProps) {
const intl = useIntl() const intl = useIntl()
const { getValues } = useFormContext<FormValues>()
return ( return (
<> <>
@@ -47,24 +44,51 @@ export function CancelStayConfirmation({
{intl.formatMessage({ id: "No charges were made." })} {intl.formatMessage({ id: "No charges were made." })}
</Caption> </Caption>
</div> </div>
<div className={styles.priceContainer}> {booking.multiRoom && (
<div className={styles.info}> <>
<Caption color="uiTextHighContrast" type="bold"> <Body color="uiTextHighContrast" textTransform="bold">
{intl.formatMessage({ id: "Cancellation cost" })} {intl.formatMessage({ id: "Select rooms" })}
</Caption> </Body>
<Caption color="uiTextHighContrast">
{stayDetails.nightsText}, {stayDetails.adultsText} <div className={styles.rooms}>
{booking.childrenAges?.length > 0 {getValues("rooms").map((room, index) => {
? `, ${stayDetails.childrenText}` // Find room details from store by confirmationNumber
: ""} const roomDetail = roomDetails.find(
</Caption> (detail) => detail.id === room.confirmationNumber
</div> )
<div className={styles.price}>
<Subtitle color="burgundy" type="one"> return (
0 {booking.currencyCode} <div key={room.id} className={styles.roomContainer}>
</Subtitle> <Checkbox
</div> name={`rooms.${index}.checked`}
</div> registerOptions={{
disabled:
roomDetail?.rateDefinition.cancellationRule !==
"CancellableBefore6PM",
}}
>
<div className={styles.roomInfo}>
<Caption color="uiTextHighContrast">
{intl.formatMessage({ id: "Room" })} {index + 1}
</Caption>
{roomDetail && (
<>
<Body color="uiTextHighContrast">
{roomDetail.roomName}
</Body>
</>
)}
</div>
</Checkbox>
</div>
)
})}
</div>
</>
)}
{getValues("rooms").some((room) => room.checked) && (
<PriceContainer booking={booking} stayDetails={stayDetails} />
)}
</> </>
) )
} }

View File

@@ -1,21 +1,12 @@
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import PriceContainer from "../Pricecontainer"
import styles from "../cancelStay.module.css" import styles from "../cancelStay.module.css"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { FinalConfirmationProps } from "@/types/components/hotelReservation/myStay/cancelStay"
interface FinalConfirmationProps {
booking: BookingConfirmation["booking"]
stayDetails: {
nightsText: string
adultsText: string
childrenText: string
}
}
export function FinalConfirmation({ export function FinalConfirmation({
booking, booking,
@@ -32,24 +23,7 @@ export function FinalConfirmation({
})} })}
</Body> </Body>
</div> </div>
<div className={styles.priceContainer}> <PriceContainer booking={booking} stayDetails={stayDetails} />
<div className={styles.info}>
<Caption color="uiTextHighContrast" type="bold">
{intl.formatMessage({ id: "Cancellation cost" })}
</Caption>
<Caption color="uiTextHighContrast">
{stayDetails.nightsText}, {stayDetails.adultsText}
{booking.childrenAges?.length > 0
? `, ${stayDetails.childrenText}`
: ""}
</Caption>
</div>
<div className={styles.price}>
<Subtitle color="burgundy" type="one">
0 {booking.currencyCode}
</Subtitle>
</div>
</div>
</> </>
) )
} }

View File

@@ -0,0 +1,45 @@
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getCheckedRoomsCounts } from "../utils"
import styles from "../cancelStay.module.css"
import type {
FormValues,
PriceContainerProps,
} from "@/types/components/hotelReservation/myStay/cancelStay"
export default function PriceContainer({
booking,
stayDetails,
}: PriceContainerProps) {
const intl = useIntl()
const { getValues } = useFormContext<FormValues>()
const checkedRoomsDetails = getCheckedRoomsCounts(booking, getValues, intl)
return (
<div className={styles.priceContainer}>
<div className={styles.info}>
<Caption color="uiTextHighContrast" type="bold">
{intl.formatMessage({ id: "Cancellation cost" })}
</Caption>
<Caption color="uiTextHighContrast">
{stayDetails.nightsText}, {checkedRoomsDetails.adultsText}
{checkedRoomsDetails.totalChildren > 0
? `, ${checkedRoomsDetails.childrenText}`
: ""}
</Caption>
</div>
<div className={styles.price}>
<Subtitle color="burgundy" type="one">
0 {booking.currencyCode}
</Subtitle>
</div>
</div>
)
}

View File

@@ -13,12 +13,36 @@
justify-content: flex-end; justify-content: flex-end;
} }
.rooms {
display: flex;
flex-direction: column;
gap: var(--Spacing-x1);
}
.roomContainer {
display: flex;
padding: var(--Spacing-x2);
background-color: var(--Base-Background-Primary-Normal);
border-radius: var(--Corner-radius-Medium);
align-items: center;
gap: var(--Spacing-x1);
}
.roomInfo {
display: flex;
flex-direction: column;
}
.info { .info {
border-right: 1px solid var(--Base-Border-Subtle); border-right: 1px solid var(--Base-Border-Subtle);
padding-right: var(--Spacing-x2); padding-right: var(--Spacing-x2);
text-align: right; text-align: right;
display: flex;
flex-direction: column;
} }
.price { .price {
padding-left: var(--Spacing-x2); padding-left: var(--Spacing-x2);
display: flex;
align-items: center;
} }

View File

@@ -6,14 +6,22 @@ import { trpc } from "@/lib/trpc/client"
import { toast } from "@/components/TempDesignSystem/Toasts" import { toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import type { CancelStayProps } from ".." import type {
CancelStayProps,
FormValues,
} from "@/types/components/hotelReservation/myStay/cancelStay"
interface UseCancelStayProps extends Omit<CancelStayProps, "hotel"> {
getFormValues: () => FormValues // Function to get form values
}
export default function useCancelStay({ export default function useCancelStay({
booking, booking,
setBookingStatus, setBookingStatus,
handleCloseModal, handleCloseModal,
handleBackToManageStay, handleBackToManageStay,
}: Omit<CancelStayProps, "hotel">) { getFormValues,
}: UseCancelStayProps) {
const intl = useIntl() const intl = useIntl()
const lang = useLang() const lang = useLang()
const [currentStep, setCurrentStep] = useState(1) const [currentStep, setCurrentStep] = useState(1)
@@ -21,39 +29,9 @@ export default function useCancelStay({
const cancelStay = trpc.booking.cancel.useMutation({ const cancelStay = trpc.booking.cancel.useMutation({
onMutate: () => setIsLoading(true), onMutate: () => setIsLoading(true),
onSuccess: (result) => {
if (!result) {
toast.error(
intl.formatMessage({
id: "Something went wrong. Please try again later.",
})
)
return
}
setBookingStatus()
toast.success(
intl.formatMessage(
{
id: "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out",
},
{ currency: booking.currencyCode }
)
)
},
onError: () => {
toast.error(
intl.formatMessage({
id: "Something went wrong. Please try again later.",
})
)
},
onSettled: () => {
handleCloseModal()
},
}) })
function handleCancelStay() { async function handleCancelStay() {
if (!booking.confirmationNumber) { if (!booking.confirmationNumber) {
toast.error( toast.error(
intl.formatMessage({ intl.formatMessage({
@@ -63,10 +41,86 @@ export default function useCancelStay({
return return
} }
cancelStay.mutate({ setIsLoading(true)
confirmationNumber: booking.confirmationNumber,
language: lang, try {
}) // Get form values using the provided getter function
const formValues = getFormValues()
const { rooms } = formValues
const checkedRooms = rooms.filter((room) => room.checked)
const results = []
const errors = []
// Process each checked room sequentially
for (const room of checkedRooms) {
const confirmationNumber =
room.confirmationNumber || booking.confirmationNumber
try {
const result = await cancelStay.mutateAsync({
confirmationNumber: confirmationNumber,
language: lang,
})
if (result) {
results.push(room.id)
} else {
errors.push(room.id)
}
} catch (error) {
console.error(
`Error cancelling room ${room.confirmationNumber}:`,
error
)
toast.error(
intl.formatMessage({
id: "Something went wrong. Please try again later.",
})
)
errors.push(room.id)
}
}
// Handle results
if (results.length > 0 && errors.length === 0) {
// All rooms were cancelled successfully
setBookingStatus()
toast.success(
intl.formatMessage(
{
id: "Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out",
},
{ currency: booking.currencyCode }
)
)
} else if (results.length > 0 && errors.length > 0) {
// Some rooms were cancelled, some failed
setBookingStatus()
toast.warning(
intl.formatMessage({
id: "Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.",
})
)
} else {
// All rooms failed to cancel
toast.error(
intl.formatMessage({
id: "Something went wrong. Please try again later.",
})
)
}
handleCloseModal()
} catch (error) {
console.error("Error in handleCancelStay:", error)
toast.error(
intl.formatMessage({
id: "Something went wrong. Please try again later.",
})
)
setIsLoading(false)
}
} }
function handleCloseCancelStay() { function handleCloseCancelStay() {

View File

@@ -1,24 +1,29 @@
"use client" "use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import Alert from "@/components/TempDesignSystem/Alert"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { ModalContent } from "../ManageStay/ModalContent" 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"
import { formatStayDetails } from "./utils" import { formatStayDetails, getDefaultRooms } from "./utils"
import type { Hotel } from "@/types/hotel" import {
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" type CancelStayProps,
cancelStaySchema,
type FormValues,
} from "@/types/components/hotelReservation/myStay/cancelStay"
import { AlertTypeEnum } from "@/types/enums/alert"
export interface CancelStayProps { const MODAL_STEPS = {
booking: BookingConfirmation["booking"] INITIAL: 1,
hotel: Hotel CONFIRMATION: 2,
setBookingStatus: () => void
handleCloseModal: () => void
handleBackToManageStay: () => void
} }
export default function CancelStay({ export default function CancelStay({
@@ -30,6 +35,17 @@ 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 form = useForm<FormValues>({
resolver: zodResolver(cancelStaySchema),
defaultValues: {
rooms: getDefaultRooms(booking),
},
})
const { const {
currentStep, currentStep,
isLoading, isLoading,
@@ -41,47 +57,82 @@ export default function CancelStay({
setBookingStatus, setBookingStatus,
handleCloseModal, handleCloseModal,
handleBackToManageStay, handleBackToManageStay,
getFormValues: form.getValues,
}) })
const stayDetails = formatStayDetails({ booking, lang, intl }) const stayDetails = formatStayDetails({ booking, lang, intl })
const isFirstStep = currentStep === 1 const isFirstStep = currentStep === MODAL_STEPS.INITIAL
function getModalCopy() {
if (isFirstStep) {
return {
title: intl.formatMessage({ id: "Cancel stay" }),
primaryLabel: intl.formatMessage({ id: "Cancel stay" }),
secondaryLabel: intl.formatMessage({ id: "Back" }),
}
} else {
return {
title: intl.formatMessage({ id: "Confirm cancellation" }),
primaryLabel: intl.formatMessage({ id: "Confirm cancellation" }),
secondaryLabel: intl.formatMessage({ id: "Don't cancel" }),
}
}
}
function getModalContent() {
if (mainRoom && isFirstStep)
return (
<CancelStayConfirmation
hotel={hotel}
booking={booking}
stayDetails={stayDetails}
roomDetails={roomDetails}
/>
)
if (mainRoom && !isFirstStep)
return <FinalConfirmation booking={booking} stayDetails={stayDetails} />
if (!mainRoom && isFirstStep)
return (
<Alert
type={AlertTypeEnum.Info}
heading={intl.formatMessage({
id: "Contact the person who booked the stay",
})}
text={intl.formatMessage({
id: "As this is a multiroom stay, the cancellation has to be done by the person who made the booking. Please call 08-517 517 00 to talk to our customer service if you would need further assistance.",
})}
/>
)
}
const { rooms } = form.watch()
const isFormValid = rooms?.some((room) => room.checked)
return ( return (
<> <FormProvider {...form}>
<ModalContent <ModalContent
title={ title={getModalCopy().title}
isFirstStep
? intl.formatMessage({ id: "Cancel stay" })
: intl.formatMessage({ id: "Confirm cancellation" })
}
onClose={handleCloseModal} onClose={handleCloseModal}
content={ content={getModalContent()}
isFirstStep ? ( primaryAction={
<CancelStayConfirmation mainRoom
hotel={hotel} ? {
booking={booking} label: getModalCopy().primaryLabel,
stayDetails={stayDetails} onClick: isFirstStep ? handleForward : handleCancelStay,
/> intent: isFirstStep ? "secondary" : "primary",
) : ( isLoading: isLoading,
<FinalConfirmation booking={booking} stayDetails={stayDetails} /> disabled: !isFormValid,
) }
: null
} }
primaryAction={{
label: isFirstStep
? intl.formatMessage({ id: "Cancel stay" })
: intl.formatMessage({ id: "Confirm cancellation" }),
onClick: isFirstStep ? handleForward : handleCancelStay,
intent: isFirstStep ? "secondary" : "primary",
isLoading: isLoading,
}}
secondaryAction={{ secondaryAction={{
label: isFirstStep label: getModalCopy().secondaryLabel,
? intl.formatMessage({ id: "Back" })
: intl.formatMessage({ id: "Don't cancel" }),
onClick: isFirstStep ? handleCloseCancelStay : handleCloseModal, onClick: isFirstStep ? handleCloseCancelStay : handleCloseModal,
intent: "text", intent: "text",
}} }}
/> />
</> </FormProvider>
) )
} }

View File

@@ -1,9 +1,28 @@
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import type { UseFormReturn } from "react-hook-form"
import type { IntlShape } from "react-intl" import type { IntlShape } from "react-intl"
import type { FormValues } from "@/types/components/hotelReservation/myStay/cancelStay"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
export function getDefaultRooms(booking: BookingConfirmation["booking"]) {
const { multiRoom, confirmationNumber, linkedReservations = [] } = booking
if (!multiRoom) {
return [{ id: "1", checked: true, confirmationNumber }]
}
const mainRoom = { id: "1", checked: false, confirmationNumber }
const linkedRooms = linkedReservations.map((reservation, index) => ({
id: `${index + 2}`,
checked: false,
confirmationNumber: reservation.confirmationNumber,
}))
return [mainRoom, ...linkedRooms]
}
export function formatStayDetails({ export function formatStayDetails({
booking, booking,
lang, lang,
@@ -13,6 +32,20 @@ export function formatStayDetails({
lang: string lang: string
intl: IntlShape intl: IntlShape
}) { }) {
const { multiRoom } = booking
const totalAdults = multiRoom
? (booking.adults ?? 0) +
(booking.linkedReservations ?? []).reduce((acc, reservation) => {
return acc + (reservation.adults ?? 0)
}, 0)
: (booking.adults ?? 0)
const totalChildren = multiRoom
? booking.childrenAges?.length +
(booking.linkedReservations ?? []).reduce((acc, reservation) => {
return acc + reservation.children
}, 0)
: booking.childrenAges?.length
const checkInDate = dt(booking.checkInDate) const checkInDate = dt(booking.checkInDate)
.locale(lang) .locale(lang)
.format("dddd D MMM YYYY") .format("dddd D MMM YYYY")
@@ -27,11 +60,11 @@ export function formatStayDetails({
) )
const adultsText = intl.formatMessage( const adultsText = intl.formatMessage(
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" }, { id: "{totalAdults, plural, one {# adult} other {# adults}}" },
{ totalAdults: booking.adults } { totalAdults: totalAdults }
) )
const childrenText = intl.formatMessage( const childrenText = intl.formatMessage(
{ id: "{totalChildren, plural, one {# child} other {# children}}" }, { id: "{totalChildren, plural, one {# child} other {# children}}" },
{ totalChildren: booking.childrenAges?.length } { totalChildren: totalChildren }
) )
return { return {
@@ -42,3 +75,72 @@ export function formatStayDetails({
childrenText, childrenText,
} }
} }
function getMatchedRooms(
booking: BookingConfirmation["booking"],
checkedConfirmationNumbers: string[]
) {
let matchedRooms = []
// Main booking
if (checkedConfirmationNumbers.includes(booking.confirmationNumber ?? "")) {
matchedRooms.push({
adults: booking.adults ?? 0,
children: booking.childrenAges?.length ?? 0,
})
}
// Linked reservations
if (booking.linkedReservations) {
const matchedLinkedRooms = booking.linkedReservations
.filter((reservation) =>
checkedConfirmationNumbers.includes(reservation.confirmationNumber)
)
.map((reservation) => ({
adults: reservation.adults ?? 0,
children: reservation.children ?? 0,
}))
matchedRooms = [...matchedRooms, ...matchedLinkedRooms]
}
return matchedRooms
}
function calculateTotals(matchedRooms: { adults: number; children: number }[]) {
const totalAdults = matchedRooms.reduce((sum, room) => sum + room.adults, 0)
const totalChildren = matchedRooms.reduce(
(sum, room) => sum + room.children,
0
)
return { totalAdults, totalChildren }
}
export const getCheckedRoomsCounts = (
booking: BookingConfirmation["booking"],
getValues: UseFormReturn<FormValues>["getValues"],
intl: IntlShape
) => {
const formRooms = getValues("rooms")
const checkedFormRooms = formRooms.filter((room) => room.checked)
const checkedConfirmationNumbers = checkedFormRooms
.map((room) => room.confirmationNumber)
.filter(
(confirmationNumber): confirmationNumber is string =>
confirmationNumber !== null && confirmationNumber !== undefined
)
const matchedRooms = getMatchedRooms(booking, checkedConfirmationNumbers)
const { totalAdults, totalChildren } = calculateTotals(matchedRooms)
const adultsText = intl.formatMessage(
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
{ totalAdults: totalAdults }
)
const childrenText = intl.formatMessage(
{ id: "{totalChildren, plural, one {# child} other {# children}}" },
{ totalChildren: totalChildren }
)
return { adultsText, childrenText, totalChildren }
}

View File

@@ -8,6 +8,7 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { useMyStayRoomDetailsStore } from "../stores/myStayRoomDetailsStore"
import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice" import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice"
import styles from "./linkedReservation.module.css" import styles from "./linkedReservation.module.css"
@@ -27,9 +28,10 @@ export default function LinkedReservation({
const lang = useLang() const lang = useLang()
const { addRoomPrice } = useMyStayTotalPriceStore() const { addRoomPrice } = useMyStayTotalPriceStore()
const { addRoomDetails } = useMyStayRoomDetailsStore()
const bookingConfirmation = use(bookingPromise) const bookingConfirmation = use(bookingPromise)
const { booking } = bookingConfirmation ?? {} const { booking, room } = bookingConfirmation ?? {}
useEffect(() => { useEffect(() => {
if (booking) { if (booking) {
@@ -39,8 +41,17 @@ export default function LinkedReservation({
currencyCode: booking.currencyCode, currencyCode: booking.currencyCode,
isMainBooking: false, isMainBooking: false,
}) })
// Add room details to the store
addRoomDetails({
id: booking.confirmationNumber ?? "",
roomName: room?.name || booking.roomTypeCode || "Room",
roomTypeCode: booking.roomTypeCode || "",
rateDefinition: booking.rateDefinition,
isMainBooking: false,
})
} }
}, [booking, addRoomPrice]) }, [booking, room, addRoomPrice, addRoomDetails])
if (!booking) return null if (!booking) return null
@@ -58,7 +69,7 @@ export default function LinkedReservation({
<Caption textTransform="uppercase" type="bold"> <Caption textTransform="uppercase" type="bold">
{intl.formatMessage({ id: "Reference" })} {booking.confirmationNumber} {intl.formatMessage({ id: "Reference" })} {booking.confirmationNumber}
</Caption> </Caption>
<div className={styles.guests}> <div>
<Caption color="uiTextHighContrast"> <Caption color="uiTextHighContrast">
{booking.childrenAges.length > 0 {booking.childrenAges.length > 0
? intl.formatMessage( ? intl.formatMessage(

View File

@@ -14,12 +14,13 @@ interface ModalContentProps {
onClick: () => void onClick: () => void
intent?: "primary" | "secondary" | "text" intent?: "primary" | "secondary" | "text"
isLoading?: boolean isLoading?: boolean
} disabled?: boolean
} | null
secondaryAction: { secondaryAction: {
label: string label: string
onClick: () => void onClick: () => void
intent?: "primary" | "secondary" | "text" intent?: "primary" | "secondary" | "text"
} } | null
onClose: () => void onClose: () => void
} }
@@ -40,22 +41,26 @@ export function ModalContent({
</header> </header>
<div className={styles.content}>{content}</div> <div className={styles.content}>{content}</div>
<footer className={styles.footer}> <footer className={styles.footer}>
<Button {secondaryAction && (
theme="base" <Button
intent={secondaryAction.intent ?? "text"} theme="base"
color="burgundy" intent={secondaryAction.intent ?? "text"}
onClick={secondaryAction.onClick} color="burgundy"
> onClick={secondaryAction.onClick}
{secondaryAction.label} >
</Button> {secondaryAction.label}
<Button </Button>
theme="base" )}
intent={primaryAction.intent ?? "secondary"} {primaryAction && (
onClick={primaryAction.onClick} <Button
disabled={primaryAction.isLoading} theme="base"
> intent={primaryAction.intent ?? "secondary"}
{primaryAction.label} onClick={primaryAction.onClick}
</Button> disabled={primaryAction.isLoading || primaryAction.disabled}
>
{primaryAction.label}
</Button>
)}
</footer> </footer>
</> </>
) )

View File

@@ -5,6 +5,8 @@
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;
overflow-y: auto;
} }
.header { .header {

View File

@@ -69,8 +69,9 @@ export function ReferenceCard({ booking, hotel }: ReferenceCardProps) {
: intl.formatMessage({ id: "Reference number" })} : intl.formatMessage({ id: "Reference number" })}
</Subtitle> </Subtitle>
<Subtitle color="uiTextHighContrast"> <Subtitle color="uiTextHighContrast">
{/* TODO: Implement this: https://scandichotels.atlassian.net/browse/API2-2883 to get correct cancellation number */} {isCancelled
{isCancelled ? "" : booking.confirmationNumber} ? booking.cancellationNumber
: booking.confirmationNumber}
</Subtitle> </Subtitle>
</div> </div>
<Divider color="primaryLightSubtle" className={styles.divider} /> <Divider color="primaryLightSubtle" className={styles.divider} />
@@ -83,7 +84,7 @@ export function ReferenceCard({ booking, hotel }: ReferenceCardProps) {
{intl.formatMessage({ id: "Guests" })} {intl.formatMessage({ id: "Guests" })}
</Caption> </Caption>
<Caption type="bold" color="uiTextHighContrast"> <Caption type="bold" color="uiTextHighContrast">
{booking.childrenAges.length > 0 {children > 0
? intl.formatMessage( ? intl.formatMessage(
{ id: "{adults} adults, {children} children" }, { id: "{adults} adults, {children} children" },
{ {

View File

@@ -98,6 +98,22 @@
} }
} }
.imagePlaceholder {
height: 100%;
width: 100%;
background-color: #fff;
background-image: linear-gradient(45deg, #000000 25%, transparent 25%),
linear-gradient(-45deg, #000000 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #000000 75%),
linear-gradient(-45deg, transparent 75%, #000000 75%);
background-size: 120px 120px;
background-position:
0 0,
0 60px,
60px -60px,
-60px 0;
}
.roomDetails { .roomDetails {
display: grid; display: grid;
gap: var(--Spacing-x5); gap: var(--Spacing-x5);

View File

@@ -59,7 +59,7 @@ export async function MyStay({ reservationId }: { reservationId: string }) {
className={styles.image} className={styles.image}
src={ src={
hotel.gallery?.heroImages[0]?.imageSizes.large ?? hotel.gallery?.heroImages[0]?.imageSizes.large ??
room?.images[0]?.imageSizes.large ?? hotel.galleryImages[0]?.imageSizes.large ??
"" ""
} }
alt={hotel.name} alt={hotel.name}

View File

@@ -0,0 +1,58 @@
import { create } from "zustand"
interface RoomDetails {
id: string
roomName: string
roomTypeCode: string
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 {
rooms: RoomDetails[]
// Add a single room's details
addRoomDetails: (room: RoomDetails) => void
// Get room details by confirmationNumber
getRoomDetails: (confirmationNumber: string) => RoomDetails | undefined
}
export const useMyStayRoomDetailsStore = create<MyStayRoomDetailsState>(
(set, get) => ({
rooms: [],
addRoomDetails: (room) => {
set((state) => {
// Check if room with this ID already exists
const existingIndex = state.rooms.findIndex((r) => r.id === room.id)
let newRooms = [...state.rooms]
if (existingIndex >= 0) {
// Update existing room
newRooms[existingIndex] = room
} else {
// Add new room
newRooms.push(room)
}
return {
rooms: newRooms,
}
})
},
getRoomDetails: (confirmationNumber) => {
return get().rooms.find((room) => room.id === confirmationNumber)
},
})
)

View File

@@ -58,6 +58,7 @@
"Arrival date": "Ankomstdato", "Arrival date": "Ankomstdato",
"As our Close Friend": "Som vores nære ven", "As our Close Friend": "Som vores nære ven",
"As our {level}": "Som vores {level}", "As our {level}": "Som vores {level}",
"As this is a multiroom stay, the cancellation has to be done by the person who made the booking. Please call 08-517 517 00 to talk to our customer service if you would need further assistance.": "Da dette er et ophold med flere værelser, skal annullereringen gennemføres af personen, der har booket opholdet. Bedes du ringe til vores kundeservice på 08-517 517 00, hvis du har brug for yderligere hjælp.",
"At latest": "Senest", "At latest": "Senest",
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Attraktioner", "Attractions": "Attraktioner",
@@ -155,6 +156,7 @@
"Confirm cancellation": "Bekræft annullerering", "Confirm cancellation": "Bekræft annullerering",
"Contact information": "Kontaktoplysninger", "Contact information": "Kontaktoplysninger",
"Contact our memberservice": "Contact our memberservice", "Contact our memberservice": "Contact our memberservice",
"Contact the person who booked the stay": "Kontakt personen, der bookede opholdet",
"Contact us": "Kontakt os", "Contact us": "Kontakt os",
"Continue": "Blive ved", "Continue": "Blive ved",
"Copied to clipboard": "Copied to clipboard", "Copied to clipboard": "Copied to clipboard",
@@ -579,6 +581,7 @@
"Select payment method": "Vælg betalingsmetode", "Select payment method": "Vælg betalingsmetode",
"Select quantity": "Vælg antal", "Select quantity": "Vælg antal",
"Select room": "Zimmer auswählen", "Select room": "Zimmer auswählen",
"Select rooms": "Vælg værelser",
"Select your language": "Vælg dit sprog", "Select your language": "Vælg dit sprog",
"Shopping": "Shopping", "Shopping": "Shopping",
"Shopping & Dining": "Shopping & Spisning", "Shopping & Dining": "Shopping & Spisning",
@@ -592,6 +595,7 @@
"Sign up to Scandic Friends": "Tilmeld dig Scandic Friends", "Sign up to Scandic Friends": "Tilmeld dig Scandic Friends",
"Signing up...": "Tilmelder...", "Signing up...": "Tilmelder...",
"Skip to main content": "Spring over og gå til hovedindhold", "Skip to main content": "Spring over og gå til hovedindhold",
"Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.": "Nogle værelser blev annulleret med succes, men vi stødte på problemer med andre. Bedes du kontakte kundeservice for hjælp.",
"Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.", "Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.", "Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.",
"Something went wrong!": "Noget gik galt!", "Something went wrong!": "Noget gik galt!",
@@ -731,6 +735,7 @@
"Your points to spend": "Dine brugbare point", "Your points to spend": "Dine brugbare point",
"Your room": "Dit værelse", "Your room": "Dit værelse",
"Your selected bed type will be provided based on availability": "Din valgte sengtype vil blive stillet til rådighed baseret på tilgængelighed", "Your selected bed type will be provided based on availability": "Din valgte sengtype vil blive stillet til rådighed baseret på tilgængelighed",
"Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out": "Dit ophold blev annulleret. Annullereringspris: 0 {currency}. Vi beklager, at planerne ikke fungerede ud",
"Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Dit ophold blev annulleret. Annullereringspris: 0 {currency}. Vi beklager, at planerne ikke fungerede ud", "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Dit ophold blev annulleret. Annullereringspris: 0 {currency}. Vi beklager, at planerne ikke fungerede ud",
"Zip code": "Postnummer", "Zip code": "Postnummer",
"Zoo": "Zoo", "Zoo": "Zoo",

View File

@@ -58,6 +58,7 @@
"Arrival date": "Ankunftsdatum", "Arrival date": "Ankunftsdatum",
"As our Close Friend": "Als unser enger Freund", "As our Close Friend": "Als unser enger Freund",
"As our {level}": "Als unser {level}", "As our {level}": "Als unser {level}",
"As this is a multiroom stay, the cancellation has to be done by the person who made the booking. Please call 08-517 517 00 to talk to our customer service if you would need further assistance.": "Da dies ein Mehrzimmer-Aufenthalt ist, muss die Stornierung von der Person, die die Buchung getätigt hat, durchgeführt werden. Bitte rufen Sie uns unter der Telefonnummer 08-517 517 00 an, wenn Sie weitere Hilfe benötigen.",
"At latest": "Spätestens", "At latest": "Spätestens",
"At the hotel": "Im Hotel", "At the hotel": "Im Hotel",
"Attraction": "Attraktion", "Attraction": "Attraktion",
@@ -156,6 +157,7 @@
"Confirm cancellation": "Stornierung bestätigen", "Confirm cancellation": "Stornierung bestätigen",
"Contact information": "Kontaktinformationen", "Contact information": "Kontaktinformationen",
"Contact our memberservice": "Contact our memberservice", "Contact our memberservice": "Contact our memberservice",
"Contact the person who booked the stay": "Kontakt personen, der Buchung getätigt hat",
"Contact us": "Kontaktieren Sie uns", "Contact us": "Kontaktieren Sie uns",
"Continue": "Weitermachen", "Continue": "Weitermachen",
"Copied to clipboard": "Copied to clipboard", "Copied to clipboard": "Copied to clipboard",
@@ -581,6 +583,7 @@
"Select payment method": "Zahlungsart auswählen", "Select payment method": "Zahlungsart auswählen",
"Select quantity": "Menge auswählen", "Select quantity": "Menge auswählen",
"Select room": "Vælg værelse", "Select room": "Vælg værelse",
"Select rooms": "Zimmer auswählen",
"Select your language": "Wählen Sie Ihre Sprache", "Select your language": "Wählen Sie Ihre Sprache",
"Shopping": "Einkaufen", "Shopping": "Einkaufen",
"Shopping & Dining": "Einkaufen & Essen", "Shopping & Dining": "Einkaufen & Essen",
@@ -594,6 +597,7 @@
"Sign up to Scandic Friends": "Treten Sie Scandic Friends bei", "Sign up to Scandic Friends": "Treten Sie Scandic Friends bei",
"Signing up...": "Registrierung läuft...", "Signing up...": "Registrierung läuft...",
"Skip to main content": "Direkt zum Inhalt", "Skip to main content": "Direkt zum Inhalt",
"Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.": "Einige Zimmer wurden erfolgreich storniert, aber wir stießen auf Probleme mit anderen. Bitte kontaktieren Sie unseren Kundensupport für weitere Hilfe.",
"Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.", "Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.", "Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.",
"Something went wrong!": "Etwas ist schief gelaufen!", "Something went wrong!": "Etwas ist schief gelaufen!",
@@ -732,6 +736,7 @@
"Your points to spend": "Meine Punkte", "Your points to spend": "Meine Punkte",
"Your room": "Ihr Zimmer", "Your room": "Ihr Zimmer",
"Your selected bed type will be provided based on availability": "Ihre ausgewählte Bettart wird basierend auf der Verfügbarkeit bereitgestellt", "Your selected bed type will be provided based on availability": "Ihre ausgewählte Bettart wird basierend auf der Verfügbarkeit bereitgestellt",
"Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out": "Ihr Aufenthalt wurde storniert. Stornierungskosten: 0 {currency}. Es tut uns leid, dass die Pläne nicht funktionierten",
"Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Ihr Aufenthalt wurde storniert. Stornierungskosten: 0 {currency}. Es tut uns leid, dass die Pläne nicht funktionierten", "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Ihr Aufenthalt wurde storniert. Stornierungskosten: 0 {currency}. Es tut uns leid, dass die Pläne nicht funktionierten",
"Zip code": "PLZ", "Zip code": "PLZ",
"Zoo": "Zoo", "Zoo": "Zoo",

View File

@@ -58,6 +58,7 @@
"Arrival date": "Arrival date", "Arrival date": "Arrival date",
"As our Close Friend": "As our Close Friend", "As our Close Friend": "As our Close Friend",
"As our {level}": "As our {level}", "As our {level}": "As our {level}",
"As this is a multiroom stay, the cancellation has to be done by the person who made the booking. Please call 08-517 517 00 to talk to our customer service if you would need further assistance.": "As this is a multiroom stay, the cancellation has to be done by the person who made the booking. Please call 08-517 517 00 to talk to our customer service if you would need further assistance.",
"At latest": "At latest", "At latest": "At latest",
"At the hotel": "At the hotel", "At the hotel": "At the hotel",
"Attractions": "Attractions", "Attractions": "Attractions",
@@ -156,6 +157,7 @@
"Confirm cancellation": "Confirm cancellation", "Confirm cancellation": "Confirm cancellation",
"Contact information": "Contact information", "Contact information": "Contact information",
"Contact our memberservice": "Contact our memberservice", "Contact our memberservice": "Contact our memberservice",
"Contact the person who booked the stay": "Contact the person who booked the stay",
"Contact us": "Contact us", "Contact us": "Contact us",
"Continue": "Continue", "Continue": "Continue",
"Continue to room {nextRoomNumber}": "Continue to room {nextRoomNumber}", "Continue to room {nextRoomNumber}": "Continue to room {nextRoomNumber}",
@@ -585,6 +587,7 @@
"Select payment method": "Select payment method", "Select payment method": "Select payment method",
"Select quantity": "Select quantity", "Select quantity": "Select quantity",
"Select room": "Select room", "Select room": "Select room",
"Select rooms": "Select rooms",
"Select your language": "Select your language", "Select your language": "Select your language",
"Shopping": "Shopping", "Shopping": "Shopping",
"Shopping & Dining": "Shopping & Dining", "Shopping & Dining": "Shopping & Dining",
@@ -598,6 +601,7 @@
"Sign up to Scandic Friends": "Sign up to Scandic Friends", "Sign up to Scandic Friends": "Sign up to Scandic Friends",
"Signing up...": "Signing up...", "Signing up...": "Signing up...",
"Skip to main content": "Skip to main content", "Skip to main content": "Skip to main content",
"Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.": "Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.",
"Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.", "Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.", "Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.",
"Something went wrong!": "Something went wrong!", "Something went wrong!": "Something went wrong!",
@@ -737,6 +741,7 @@
"Your points to spend": "Your points to spend", "Your points to spend": "Your points to spend",
"Your room": "Your room", "Your room": "Your room",
"Your selected bed type will be provided based on availability": "Your selected bed type will be provided based on availability", "Your selected bed type will be provided based on availability": "Your selected bed type will be provided based on availability",
"Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out": "Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out",
"Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out", "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out",
"Zip code": "Zip code", "Zip code": "Zip code",
"Zoo": "Zoo", "Zoo": "Zoo",

View File

@@ -57,6 +57,7 @@
"Arrival date": "Saapumispäivä", "Arrival date": "Saapumispäivä",
"As our Close Friend": "Läheisenä ystävänämme", "As our Close Friend": "Läheisenä ystävänämme",
"As our {level}": "{level}-etu", "As our {level}": "{level}-etu",
"As this is a multiroom stay, the cancellation has to be done by the person who made the booking. Please call 08-517 517 00 to talk to our customer service if you would need further assistance.": "Koska tämä on monihuoneinen majoitus, peruutus on tehtävä henkilölle, joka teki varauksen. Ota yhteyttä asiakaspalveluun apua varten, jos tarvitset lisää apua.",
"At latest": "Viimeistään", "At latest": "Viimeistään",
"At the hotel": "Hotellissa", "At the hotel": "Hotellissa",
"Attractions": "Nähtävyydet", "Attractions": "Nähtävyydet",
@@ -155,6 +156,7 @@
"Confirm cancellation": "Vahvista peruutus", "Confirm cancellation": "Vahvista peruutus",
"Contact information": "Yhteystiedot", "Contact information": "Yhteystiedot",
"Contact our memberservice": "Contact our memberservice", "Contact our memberservice": "Contact our memberservice",
"Contact the person who booked the stay": "Ota yhteyttä henkilölle, joka teki varauksen",
"Contact us": "Ota meihin yhteyttä", "Contact us": "Ota meihin yhteyttä",
"Continue": "Jatkaa", "Continue": "Jatkaa",
"Copied to clipboard": "Copied to clipboard", "Copied to clipboard": "Copied to clipboard",
@@ -581,6 +583,7 @@
"Select payment method": "Valitse maksutapa", "Select payment method": "Valitse maksutapa",
"Select quantity": "Valitse määrä", "Select quantity": "Valitse määrä",
"Select room": "Valitse huone", "Select room": "Valitse huone",
"Select rooms": "Valitse huoneet",
"Select your language": "Valitse kieli", "Select your language": "Valitse kieli",
"Shopping": "Ostokset", "Shopping": "Ostokset",
"Shopping & Dining": "Ostokset & Ravintolat", "Shopping & Dining": "Ostokset & Ravintolat",
@@ -594,6 +597,7 @@
"Sign up to Scandic Friends": "Liity Scandic Friends -jäseneksi", "Sign up to Scandic Friends": "Liity Scandic Friends -jäseneksi",
"Signing up...": "Rekisteröidytään...", "Signing up...": "Rekisteröidytään...",
"Skip to main content": "Siirry pääsisältöön", "Skip to main content": "Siirry pääsisältöön",
"Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.": "Joitakin huoneita peruutettiin onnistuneesti, mutta esiintyi ongelmia muiden kanssa. Ota yhteyttä asiakaspalveluun apua varten.",
"Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.", "Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.", "Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.",
"Something went wrong!": "Jotain meni pieleen!", "Something went wrong!": "Jotain meni pieleen!",
@@ -732,6 +736,7 @@
"Your points to spend": "Käytettävissä olevat pisteesi", "Your points to spend": "Käytettävissä olevat pisteesi",
"Your room": "Sinun huoneesi", "Your room": "Sinun huoneesi",
"Your selected bed type will be provided based on availability": "Valitun vuodetyypin toimitetaan saatavuuden mukaan", "Your selected bed type will be provided based on availability": "Valitun vuodetyypin toimitetaan saatavuuden mukaan",
"Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out": "Majoituksesi peruutettiin. Peruutusmaksu: 0 {currency}. Emme voi käyttää sitä, että suunnitellut majoitukset eivät toiminneet",
"Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Majoituksesi peruutettiin. Peruutusmaksu: 0 {currency}. Emme voi käyttää sitä, että suunnitellut majoitukset eivät toiminneet", "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Majoituksesi peruutettiin. Peruutusmaksu: 0 {currency}. Emme voi käyttää sitä, että suunnitellut majoitukset eivät toiminneet",
"Zip code": "Postinumero", "Zip code": "Postinumero",
"Zoo": "Eläintarha", "Zoo": "Eläintarha",

View File

@@ -57,6 +57,7 @@
"Arrival date": "Ankomstdato", "Arrival date": "Ankomstdato",
"As our Close Friend": "Som vår nære venn", "As our Close Friend": "Som vår nære venn",
"As our {level}": "Som vår {level}", "As our {level}": "Som vår {level}",
"As this is a multiroom stay, the cancellation has to be done by the person who made the booking. Please call 08-517 517 00 to talk to our customer service if you would need further assistance.": "Som dette er et ophold med flere rom, må annullereringen gjøres av personen som booket opholdet. Vennligst ring 08-517 517 00 til vår kundeservice hvis du trenger mer hjelp.",
"At latest": "Senest", "At latest": "Senest",
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Attraksjoner", "Attractions": "Attraksjoner",
@@ -154,6 +155,7 @@
"Confirm cancellation": "Bekræft annullerering", "Confirm cancellation": "Bekræft annullerering",
"Contact information": "Kontaktinformasjon", "Contact information": "Kontaktinformasjon",
"Contact our memberservice": "Contact our memberservice", "Contact our memberservice": "Contact our memberservice",
"Contact the person who booked the stay": "Kontakt personen som booket opholdet",
"Contact us": "Kontakt oss", "Contact us": "Kontakt oss",
"Continue": "Fortsette", "Continue": "Fortsette",
"Copied to clipboard": "Copied to clipboard", "Copied to clipboard": "Copied to clipboard",
@@ -577,6 +579,7 @@
"Select payment method": "Velg betalingsmetode", "Select payment method": "Velg betalingsmetode",
"Select quantity": "Velg antall", "Select quantity": "Velg antall",
"Select room": "Velg rom", "Select room": "Velg rom",
"Select rooms": "Velg rom",
"Select your language": "Velg språk", "Select your language": "Velg språk",
"Shopping": "Shopping", "Shopping": "Shopping",
"Shopping & Dining": "Shopping & Spisesteder", "Shopping & Dining": "Shopping & Spisesteder",
@@ -590,6 +593,7 @@
"Sign up to Scandic Friends": "Bli med i Scandic Friends", "Sign up to Scandic Friends": "Bli med i Scandic Friends",
"Signing up...": "Registrerer...", "Signing up...": "Registrerer...",
"Skip to main content": "Gå videre til hovedsiden", "Skip to main content": "Gå videre til hovedsiden",
"Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.": "Noen rom ble annulleret vellykket, men vi møtte problemer med andre. Vennligst kontakt kundeservice for hjelp.",
"Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.", "Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.", "Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.",
"Something went wrong!": "Noe gikk galt!", "Something went wrong!": "Noe gikk galt!",
@@ -728,6 +732,7 @@
"Your points to spend": "Dine brukbare poeng", "Your points to spend": "Dine brukbare poeng",
"Your room": "Rommet ditt", "Your room": "Rommet ditt",
"Your selected bed type will be provided based on availability": "Din valgte sengtype vil blive stillet til rådighed baseret på tilgængelighed", "Your selected bed type will be provided based on availability": "Din valgte sengtype vil blive stillet til rådighed baseret på tilgængelighed",
"Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out": "Ditt ophold ble annulleret. Annullereringspris: 0 {currency}. Vi beklager at planene ikke fungerte ut",
"Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Dit ophold blev annulleret. Annullereringspris: 0 {currency}. Vi beklager, at planerne ikke fungerede ud", "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Dit ophold blev annulleret. Annullereringspris: 0 {currency}. Vi beklager, at planerne ikke fungerede ud",
"Zip code": "Post kode", "Zip code": "Post kode",
"Zoo": "Dyrehage", "Zoo": "Dyrehage",

View File

@@ -57,6 +57,7 @@
"Arrival date": "Ankomstdatum", "Arrival date": "Ankomstdatum",
"As our Close Friend": "Som vår nära vän", "As our Close Friend": "Som vår nära vän",
"As our {level}": "Som vår {level}", "As our {level}": "Som vår {level}",
"As this is a multiroom stay, the cancellation has to be done by the person who made the booking. Please call 08-517 517 00 to talk to our customer service if you would need further assistance.": "Då detta är en vistelse med flera rum måste avbokningen göras av personen som bokade vistelsen. Kontakta vår kundsupport på 08-517 517 00 om du behöver mer hjälp.",
"At latest": "Senast", "At latest": "Senast",
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Sevärdheter", "Attractions": "Sevärdheter",
@@ -154,6 +155,7 @@
"Confirm cancellation": "Bekräfta avbokning", "Confirm cancellation": "Bekräfta avbokning",
"Contact information": "Kontaktinformation", "Contact information": "Kontaktinformation",
"Contact our memberservice": "Contact our memberservice", "Contact our memberservice": "Contact our memberservice",
"Contact the person who booked the stay": "Kontakta personen som bokade vistelsen",
"Contact us": "Kontakta oss", "Contact us": "Kontakta oss",
"Continue": "Fortsätt", "Continue": "Fortsätt",
"Copied to clipboard": "Copied to clipboard", "Copied to clipboard": "Copied to clipboard",
@@ -577,6 +579,7 @@
"Select payment method": "Välj betalningsmetod", "Select payment method": "Välj betalningsmetod",
"Select quantity": "Välj antal", "Select quantity": "Välj antal",
"Select room": "Välj rum", "Select room": "Välj rum",
"Select rooms": "Välj rum",
"Select your language": "Välj ditt språk", "Select your language": "Välj ditt språk",
"Shopping": "Shopping", "Shopping": "Shopping",
"Shopping & Dining": "Shopping & Mat", "Shopping & Dining": "Shopping & Mat",
@@ -590,6 +593,7 @@
"Sign up to Scandic Friends": "Bli medlem i Scandic Friends", "Sign up to Scandic Friends": "Bli medlem i Scandic Friends",
"Signing up...": "Registrerar...", "Signing up...": "Registrerar...",
"Skip to main content": "Fortsätt till huvudinnehåll", "Skip to main content": "Fortsätt till huvudinnehåll",
"Some rooms were cancelled successfully, but we encountered issues with others. Please contact customer service for assistance.": "Några rum blev avbokade, men vi stötte på problem med andra. Kontakta kundsupporten för hjälp.",
"Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.", "Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.", "Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.",
"Something went wrong!": "Något gick fel!", "Something went wrong!": "Något gick fel!",
@@ -728,6 +732,7 @@
"Your points to spend": "Dina spenderbara poäng", "Your points to spend": "Dina spenderbara poäng",
"Your room": "Ditt rum", "Your room": "Ditt rum",
"Your selected bed type will be provided based on availability": "Din valda sängtyp kommer att tillhandahållas baserat på tillgänglighet", "Your selected bed type will be provided based on availability": "Din valda sängtyp kommer att tillhandahållas baserat på tillgänglighet",
"Your stay was cancelled. Cancellation cost: 0 {currency}. We're sorry to see that the plans didn't work out": "Din vistelse blev avbokad. Avbokningskostnad: 0 {currency}. Vi beklagar att planerna inte fungerade.",
"Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Din vistelse blev avbokad. Avbokningskostnad: 0 {currency}. Vi beklagar att planerna inte fungerade ut", "Your stay was cancelled. Cancellation cost: 0 {currency}. Were sorry to see that the plans didnt work out": "Din vistelse blev avbokad. Avbokningskostnad: 0 {currency}. Vi beklagar att planerna inte fungerade ut",
"Zip code": "Postnummer", "Zip code": "Postnummer",
"Zoo": "Djurpark", "Zoo": "Djurpark",

View File

@@ -190,6 +190,7 @@ export const bookingConfirmationSchema = z
attributes: z.object({ attributes: z.object({
adults: z.number().int(), adults: z.number().int(),
ancillary: ancillarySchema, ancillary: ancillarySchema,
cancellationNumber: z.string().nullable().default(""),
checkInDate: z.date({ coerce: true }), checkInDate: z.date({ coerce: true }),
checkOutDate: z.date({ coerce: true }), checkOutDate: z.date({ coerce: true }),
childBedPreferences: z.array(childBedPreferencesSchema).default([]), childBedPreferences: z.array(childBedPreferencesSchema).default([]),
@@ -204,6 +205,8 @@ export const bookingConfirmationSchema = z
linkedReservationSchema linkedReservationSchema
), ),
hotelId: z.string(), hotelId: z.string(),
mainRoom: z.boolean(),
multiRoom: z.boolean(),
packages: z.array(packageSchema).default([]), packages: z.array(packageSchema).default([]),
rateDefinition: rateDefinitionSchema, rateDefinition: rateDefinitionSchema,
reservationStatus: z.string().nullable().default(""), reservationStatus: z.string().nullable().default(""),

View File

@@ -1,4 +1,4 @@
import { RegisterOptions } from "react-hook-form" import type { RegisterOptions } from "react-hook-form"
export interface CheckboxProps export interface CheckboxProps
extends React.InputHTMLAttributes<HTMLInputElement> { extends React.InputHTMLAttributes<HTMLInputElement> {

View File

@@ -0,0 +1,66 @@
import { z } from "zod"
import type { Hotel } from "@/types/hotel"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
export const cancelStaySchema = z.object({
rooms: z.array(
z.object({
id: z.string().optional(),
checked: z.boolean().optional(),
confirmationNumber: z.string().nullable().optional(),
})
),
})
export interface CancelStayProps {
booking: BookingConfirmation["booking"]
hotel: Hotel
setBookingStatus: () => void
handleCloseModal: () => void
handleBackToManageStay: () => void
}
export type FormValues = z.infer<typeof cancelStaySchema>
export interface RoomDetails {
id: string
roomName: string
roomTypeCode: string
rateDefinition: {
breakfastIncluded: boolean
cancellationRule: string | null
cancellationText: string | null
generalTerms: string[]
isMemberRate: boolean
mustBeGuaranteed: boolean
rateCode: string | null
title: string | null
}
isMainBooking?: boolean
}
export interface StayDetails {
checkInDate: string
checkOutDate: string
nightsText: string
adultsText: string
childrenText: string
}
export interface CancelStayConfirmationProps {
hotel: Hotel
booking: BookingConfirmation["booking"]
stayDetails: StayDetails
roomDetails?: RoomDetails[]
}
export interface FinalConfirmationProps {
booking: BookingConfirmation["booking"]
stayDetails: StayDetails
}
export interface PriceContainerProps {
booking: BookingConfirmation["booking"]
stayDetails: StayDetails
}