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:
@@ -20,6 +20,7 @@ import { Toast } from "@/components/TempDesignSystem/Toasts"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { formatPrice } from "@/utils/numberFormatting"
|
||||
|
||||
import { useMyStayRoomDetailsStore } from "../stores/myStayRoomDetailsStore"
|
||||
import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice"
|
||||
import SummaryCard from "./SummaryCard"
|
||||
|
||||
@@ -40,15 +41,26 @@ export default function BookingSummary({
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const { totalPrice, currencyCode, addRoomPrice } = useMyStayTotalPriceStore()
|
||||
const { addRoomDetails } = useMyStayRoomDetailsStore()
|
||||
|
||||
useEffect(() => {
|
||||
// Add price information
|
||||
addRoomPrice({
|
||||
id: booking.confirmationNumber ?? "",
|
||||
totalPrice: booking.totalPrice,
|
||||
currencyCode: booking.currencyCode,
|
||||
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 isPaid =
|
||||
@@ -130,7 +142,7 @@ export default function BookingSummary({
|
||||
{hotel.specialAlerts.length > 0 && (
|
||||
<div className={styles.toast}>
|
||||
<Toast variant="info">
|
||||
<ul className={styles.list}>
|
||||
<ul>
|
||||
{hotel.specialAlerts.map((alert) => (
|
||||
<li key={alert.id}>
|
||||
<Body color="uiTextHighContrast">{alert.text}</Body>
|
||||
|
||||
@@ -1,32 +1,29 @@
|
||||
"use client"
|
||||
|
||||
import { useFormContext } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
|
||||
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 type { Hotel } from "@/types/hotel"
|
||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||
|
||||
interface CancelStayConfirmationProps {
|
||||
hotel: Hotel
|
||||
booking: BookingConfirmation["booking"]
|
||||
stayDetails: {
|
||||
checkInDate: string
|
||||
checkOutDate: string
|
||||
nightsText: string
|
||||
adultsText: string
|
||||
childrenText: string
|
||||
}
|
||||
}
|
||||
import type {
|
||||
CancelStayConfirmationProps,
|
||||
FormValues,
|
||||
} from "@/types/components/hotelReservation/myStay/cancelStay"
|
||||
|
||||
export function CancelStayConfirmation({
|
||||
hotel,
|
||||
booking,
|
||||
stayDetails,
|
||||
roomDetails = [],
|
||||
}: CancelStayConfirmationProps) {
|
||||
const intl = useIntl()
|
||||
const { getValues } = useFormContext<FormValues>()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -47,24 +44,51 @@ export function CancelStayConfirmation({
|
||||
{intl.formatMessage({ id: "No charges were made." })}
|
||||
</Caption>
|
||||
</div>
|
||||
<div className={styles.priceContainer}>
|
||||
<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>
|
||||
{booking.multiRoom && (
|
||||
<>
|
||||
<Body color="uiTextHighContrast" textTransform="bold">
|
||||
{intl.formatMessage({ id: "Select rooms" })}
|
||||
</Body>
|
||||
|
||||
<div className={styles.rooms}>
|
||||
{getValues("rooms").map((room, index) => {
|
||||
// Find room details from store by confirmationNumber
|
||||
const roomDetail = roomDetails.find(
|
||||
(detail) => detail.id === room.confirmationNumber
|
||||
)
|
||||
|
||||
return (
|
||||
<div key={room.id} className={styles.roomContainer}>
|
||||
<Checkbox
|
||||
name={`rooms.${index}.checked`}
|
||||
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} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
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 type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||
|
||||
interface FinalConfirmationProps {
|
||||
booking: BookingConfirmation["booking"]
|
||||
stayDetails: {
|
||||
nightsText: string
|
||||
adultsText: string
|
||||
childrenText: string
|
||||
}
|
||||
}
|
||||
import type { FinalConfirmationProps } from "@/types/components/hotelReservation/myStay/cancelStay"
|
||||
|
||||
export function FinalConfirmation({
|
||||
booking,
|
||||
@@ -32,24 +23,7 @@ export function FinalConfirmation({
|
||||
})}
|
||||
</Body>
|
||||
</div>
|
||||
<div className={styles.priceContainer}>
|
||||
<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>
|
||||
<PriceContainer booking={booking} stayDetails={stayDetails} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -13,12 +13,36 @@
|
||||
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 {
|
||||
border-right: 1px solid var(--Base-Border-Subtle);
|
||||
padding-right: var(--Spacing-x2);
|
||||
text-align: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.price {
|
||||
padding-left: var(--Spacing-x2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,22 @@ import { trpc } from "@/lib/trpc/client"
|
||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
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({
|
||||
booking,
|
||||
setBookingStatus,
|
||||
handleCloseModal,
|
||||
handleBackToManageStay,
|
||||
}: Omit<CancelStayProps, "hotel">) {
|
||||
getFormValues,
|
||||
}: UseCancelStayProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const [currentStep, setCurrentStep] = useState(1)
|
||||
@@ -21,39 +29,9 @@ export default function useCancelStay({
|
||||
|
||||
const cancelStay = trpc.booking.cancel.useMutation({
|
||||
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}. We’re sorry to see that the plans didn’t 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) {
|
||||
toast.error(
|
||||
intl.formatMessage({
|
||||
@@ -63,10 +41,86 @@ export default function useCancelStay({
|
||||
return
|
||||
}
|
||||
|
||||
cancelStay.mutate({
|
||||
confirmationNumber: booking.confirmationNumber,
|
||||
language: lang,
|
||||
})
|
||||
setIsLoading(true)
|
||||
|
||||
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() {
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
"use client"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { FormProvider, useForm } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Alert from "@/components/TempDesignSystem/Alert"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import { ModalContent } from "../ManageStay/ModalContent"
|
||||
import { useMyStayRoomDetailsStore } from "../stores/myStayRoomDetailsStore"
|
||||
import useCancelStay from "./hooks/useCancelStay"
|
||||
import { CancelStayConfirmation } from "./Confirmation"
|
||||
import { FinalConfirmation } from "./FinalConfirmation"
|
||||
import { formatStayDetails } from "./utils"
|
||||
import { formatStayDetails, getDefaultRooms } from "./utils"
|
||||
|
||||
import type { Hotel } from "@/types/hotel"
|
||||
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
|
||||
import {
|
||||
type CancelStayProps,
|
||||
cancelStaySchema,
|
||||
type FormValues,
|
||||
} from "@/types/components/hotelReservation/myStay/cancelStay"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
|
||||
export interface CancelStayProps {
|
||||
booking: BookingConfirmation["booking"]
|
||||
hotel: Hotel
|
||||
setBookingStatus: () => void
|
||||
handleCloseModal: () => void
|
||||
handleBackToManageStay: () => void
|
||||
const MODAL_STEPS = {
|
||||
INITIAL: 1,
|
||||
CONFIRMATION: 2,
|
||||
}
|
||||
|
||||
export default function CancelStay({
|
||||
@@ -30,6 +35,17 @@ export default function CancelStay({
|
||||
}: CancelStayProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
const { rooms: roomDetails } = useMyStayRoomDetailsStore()
|
||||
|
||||
const { mainRoom } = booking
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(cancelStaySchema),
|
||||
defaultValues: {
|
||||
rooms: getDefaultRooms(booking),
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
currentStep,
|
||||
isLoading,
|
||||
@@ -41,47 +57,82 @@ export default function CancelStay({
|
||||
setBookingStatus,
|
||||
handleCloseModal,
|
||||
handleBackToManageStay,
|
||||
getFormValues: form.getValues,
|
||||
})
|
||||
|
||||
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 (
|
||||
<>
|
||||
<FormProvider {...form}>
|
||||
<ModalContent
|
||||
title={
|
||||
isFirstStep
|
||||
? intl.formatMessage({ id: "Cancel stay" })
|
||||
: intl.formatMessage({ id: "Confirm cancellation" })
|
||||
}
|
||||
title={getModalCopy().title}
|
||||
onClose={handleCloseModal}
|
||||
content={
|
||||
isFirstStep ? (
|
||||
<CancelStayConfirmation
|
||||
hotel={hotel}
|
||||
booking={booking}
|
||||
stayDetails={stayDetails}
|
||||
/>
|
||||
) : (
|
||||
<FinalConfirmation booking={booking} stayDetails={stayDetails} />
|
||||
)
|
||||
content={getModalContent()}
|
||||
primaryAction={
|
||||
mainRoom
|
||||
? {
|
||||
label: getModalCopy().primaryLabel,
|
||||
onClick: isFirstStep ? handleForward : handleCancelStay,
|
||||
intent: isFirstStep ? "secondary" : "primary",
|
||||
isLoading: isLoading,
|
||||
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={{
|
||||
label: isFirstStep
|
||||
? intl.formatMessage({ id: "Back" })
|
||||
: intl.formatMessage({ id: "Don't cancel" }),
|
||||
label: getModalCopy().secondaryLabel,
|
||||
onClick: isFirstStep ? handleCloseCancelStay : handleCloseModal,
|
||||
intent: "text",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import type { UseFormReturn } from "react-hook-form"
|
||||
import type { IntlShape } from "react-intl"
|
||||
|
||||
import type { FormValues } from "@/types/components/hotelReservation/myStay/cancelStay"
|
||||
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({
|
||||
booking,
|
||||
lang,
|
||||
@@ -13,6 +32,20 @@ export function formatStayDetails({
|
||||
lang: string
|
||||
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)
|
||||
.locale(lang)
|
||||
.format("dddd D MMM YYYY")
|
||||
@@ -27,11 +60,11 @@ export function formatStayDetails({
|
||||
)
|
||||
const adultsText = intl.formatMessage(
|
||||
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||
{ totalAdults: booking.adults }
|
||||
{ totalAdults: totalAdults }
|
||||
)
|
||||
const childrenText = intl.formatMessage(
|
||||
{ id: "{totalChildren, plural, one {# child} other {# children}}" },
|
||||
{ totalChildren: booking.childrenAges?.length }
|
||||
{ totalChildren: totalChildren }
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -42,3 +75,72 @@ export function formatStayDetails({
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import { useMyStayRoomDetailsStore } from "../stores/myStayRoomDetailsStore"
|
||||
import { useMyStayTotalPriceStore } from "../stores/myStayTotalPrice"
|
||||
|
||||
import styles from "./linkedReservation.module.css"
|
||||
@@ -27,9 +28,10 @@ export default function LinkedReservation({
|
||||
const lang = useLang()
|
||||
|
||||
const { addRoomPrice } = useMyStayTotalPriceStore()
|
||||
const { addRoomDetails } = useMyStayRoomDetailsStore()
|
||||
|
||||
const bookingConfirmation = use(bookingPromise)
|
||||
const { booking } = bookingConfirmation ?? {}
|
||||
const { booking, room } = bookingConfirmation ?? {}
|
||||
|
||||
useEffect(() => {
|
||||
if (booking) {
|
||||
@@ -39,8 +41,17 @@ export default function LinkedReservation({
|
||||
currencyCode: booking.currencyCode,
|
||||
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
|
||||
|
||||
@@ -58,7 +69,7 @@ export default function LinkedReservation({
|
||||
<Caption textTransform="uppercase" type="bold">
|
||||
{intl.formatMessage({ id: "Reference" })} {booking.confirmationNumber}
|
||||
</Caption>
|
||||
<div className={styles.guests}>
|
||||
<div>
|
||||
<Caption color="uiTextHighContrast">
|
||||
{booking.childrenAges.length > 0
|
||||
? intl.formatMessage(
|
||||
|
||||
@@ -14,12 +14,13 @@ interface ModalContentProps {
|
||||
onClick: () => void
|
||||
intent?: "primary" | "secondary" | "text"
|
||||
isLoading?: boolean
|
||||
}
|
||||
disabled?: boolean
|
||||
} | null
|
||||
secondaryAction: {
|
||||
label: string
|
||||
onClick: () => void
|
||||
intent?: "primary" | "secondary" | "text"
|
||||
}
|
||||
} | null
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
@@ -40,22 +41,26 @@ export function ModalContent({
|
||||
</header>
|
||||
<div className={styles.content}>{content}</div>
|
||||
<footer className={styles.footer}>
|
||||
<Button
|
||||
theme="base"
|
||||
intent={secondaryAction.intent ?? "text"}
|
||||
color="burgundy"
|
||||
onClick={secondaryAction.onClick}
|
||||
>
|
||||
{secondaryAction.label}
|
||||
</Button>
|
||||
<Button
|
||||
theme="base"
|
||||
intent={primaryAction.intent ?? "secondary"}
|
||||
onClick={primaryAction.onClick}
|
||||
disabled={primaryAction.isLoading}
|
||||
>
|
||||
{primaryAction.label}
|
||||
</Button>
|
||||
{secondaryAction && (
|
||||
<Button
|
||||
theme="base"
|
||||
intent={secondaryAction.intent ?? "text"}
|
||||
color="burgundy"
|
||||
onClick={secondaryAction.onClick}
|
||||
>
|
||||
{secondaryAction.label}
|
||||
</Button>
|
||||
)}
|
||||
{primaryAction && (
|
||||
<Button
|
||||
theme="base"
|
||||
intent={primaryAction.intent ?? "secondary"}
|
||||
onClick={primaryAction.onClick}
|
||||
disabled={primaryAction.isLoading || primaryAction.disabled}
|
||||
>
|
||||
{primaryAction.label}
|
||||
</Button>
|
||||
)}
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
padding: var(--Spacing-x1) var(--Spacing-x3) var(--Spacing-x4);
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
||||
@@ -69,8 +69,9 @@ export function ReferenceCard({ booking, hotel }: ReferenceCardProps) {
|
||||
: intl.formatMessage({ id: "Reference number" })}
|
||||
</Subtitle>
|
||||
<Subtitle color="uiTextHighContrast">
|
||||
{/* TODO: Implement this: https://scandichotels.atlassian.net/browse/API2-2883 to get correct cancellation number */}
|
||||
{isCancelled ? "" : booking.confirmationNumber}
|
||||
{isCancelled
|
||||
? booking.cancellationNumber
|
||||
: booking.confirmationNumber}
|
||||
</Subtitle>
|
||||
</div>
|
||||
<Divider color="primaryLightSubtle" className={styles.divider} />
|
||||
@@ -83,7 +84,7 @@ export function ReferenceCard({ booking, hotel }: ReferenceCardProps) {
|
||||
{intl.formatMessage({ id: "Guests" })}
|
||||
</Caption>
|
||||
<Caption type="bold" color="uiTextHighContrast">
|
||||
{booking.childrenAges.length > 0
|
||||
{children > 0
|
||||
? intl.formatMessage(
|
||||
{ id: "{adults} adults, {children} children" },
|
||||
{
|
||||
|
||||
@@ -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 {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x5);
|
||||
|
||||
@@ -59,7 +59,7 @@ export async function MyStay({ reservationId }: { reservationId: string }) {
|
||||
className={styles.image}
|
||||
src={
|
||||
hotel.gallery?.heroImages[0]?.imageSizes.large ??
|
||||
room?.images[0]?.imageSizes.large ??
|
||||
hotel.galleryImages[0]?.imageSizes.large ??
|
||||
""
|
||||
}
|
||||
alt={hotel.name}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -58,6 +58,7 @@
|
||||
"Arrival date": "Ankomstdato",
|
||||
"As our Close Friend": "Som vores nære ven",
|
||||
"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 the hotel": "På hotellet",
|
||||
"Attractions": "Attraktioner",
|
||||
@@ -155,6 +156,7 @@
|
||||
"Confirm cancellation": "Bekræft annullerering",
|
||||
"Contact information": "Kontaktoplysninger",
|
||||
"Contact our memberservice": "Contact our memberservice",
|
||||
"Contact the person who booked the stay": "Kontakt personen, der bookede opholdet",
|
||||
"Contact us": "Kontakt os",
|
||||
"Continue": "Blive ved",
|
||||
"Copied to clipboard": "Copied to clipboard",
|
||||
@@ -579,6 +581,7 @@
|
||||
"Select payment method": "Vælg betalingsmetode",
|
||||
"Select quantity": "Vælg antal",
|
||||
"Select room": "Zimmer auswählen",
|
||||
"Select rooms": "Vælg værelser",
|
||||
"Select your language": "Vælg dit sprog",
|
||||
"Shopping": "Shopping",
|
||||
"Shopping & Dining": "Shopping & Spisning",
|
||||
@@ -592,6 +595,7 @@
|
||||
"Sign up to Scandic Friends": "Tilmeld dig Scandic Friends",
|
||||
"Signing up...": "Tilmelder...",
|
||||
"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 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!",
|
||||
@@ -731,6 +735,7 @@
|
||||
"Your points to spend": "Dine brugbare point",
|
||||
"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 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}. 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",
|
||||
"Zip code": "Postnummer",
|
||||
"Zoo": "Zoo",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"Arrival date": "Ankunftsdatum",
|
||||
"As our Close Friend": "Als unser enger Freund",
|
||||
"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 the hotel": "Im Hotel",
|
||||
"Attraction": "Attraktion",
|
||||
@@ -156,6 +157,7 @@
|
||||
"Confirm cancellation": "Stornierung bestätigen",
|
||||
"Contact information": "Kontaktinformationen",
|
||||
"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",
|
||||
"Continue": "Weitermachen",
|
||||
"Copied to clipboard": "Copied to clipboard",
|
||||
@@ -581,6 +583,7 @@
|
||||
"Select payment method": "Zahlungsart auswählen",
|
||||
"Select quantity": "Menge auswählen",
|
||||
"Select room": "Vælg værelse",
|
||||
"Select rooms": "Zimmer auswählen",
|
||||
"Select your language": "Wählen Sie Ihre Sprache",
|
||||
"Shopping": "Einkaufen",
|
||||
"Shopping & Dining": "Einkaufen & Essen",
|
||||
@@ -594,6 +597,7 @@
|
||||
"Sign up to Scandic Friends": "Treten Sie Scandic Friends bei",
|
||||
"Signing up...": "Registrierung läuft...",
|
||||
"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 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!",
|
||||
@@ -732,6 +736,7 @@
|
||||
"Your points to spend": "Meine Punkte",
|
||||
"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 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}. 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",
|
||||
"Zip code": "PLZ",
|
||||
"Zoo": "Zoo",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"Arrival date": "Arrival date",
|
||||
"As our Close Friend": "As our Close Friend",
|
||||
"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 the hotel": "At the hotel",
|
||||
"Attractions": "Attractions",
|
||||
@@ -156,6 +157,7 @@
|
||||
"Confirm cancellation": "Confirm cancellation",
|
||||
"Contact information": "Contact information",
|
||||
"Contact our memberservice": "Contact our memberservice",
|
||||
"Contact the person who booked the stay": "Contact the person who booked the stay",
|
||||
"Contact us": "Contact us",
|
||||
"Continue": "Continue",
|
||||
"Continue to room {nextRoomNumber}": "Continue to room {nextRoomNumber}",
|
||||
@@ -585,6 +587,7 @@
|
||||
"Select payment method": "Select payment method",
|
||||
"Select quantity": "Select quantity",
|
||||
"Select room": "Select room",
|
||||
"Select rooms": "Select rooms",
|
||||
"Select your language": "Select your language",
|
||||
"Shopping": "Shopping",
|
||||
"Shopping & Dining": "Shopping & Dining",
|
||||
@@ -598,6 +601,7 @@
|
||||
"Sign up to Scandic Friends": "Sign up to Scandic Friends",
|
||||
"Signing up...": "Signing up...",
|
||||
"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 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!",
|
||||
@@ -737,6 +741,7 @@
|
||||
"Your points to spend": "Your points to spend",
|
||||
"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 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}. 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",
|
||||
"Zip code": "Zip code",
|
||||
"Zoo": "Zoo",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"Arrival date": "Saapumispäivä",
|
||||
"As our Close Friend": "Läheisenä ystävänämme",
|
||||
"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 the hotel": "Hotellissa",
|
||||
"Attractions": "Nähtävyydet",
|
||||
@@ -155,6 +156,7 @@
|
||||
"Confirm cancellation": "Vahvista peruutus",
|
||||
"Contact information": "Yhteystiedot",
|
||||
"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ä",
|
||||
"Continue": "Jatkaa",
|
||||
"Copied to clipboard": "Copied to clipboard",
|
||||
@@ -581,6 +583,7 @@
|
||||
"Select payment method": "Valitse maksutapa",
|
||||
"Select quantity": "Valitse määrä",
|
||||
"Select room": "Valitse huone",
|
||||
"Select rooms": "Valitse huoneet",
|
||||
"Select your language": "Valitse kieli",
|
||||
"Shopping": "Ostokset",
|
||||
"Shopping & Dining": "Ostokset & Ravintolat",
|
||||
@@ -594,6 +597,7 @@
|
||||
"Sign up to Scandic Friends": "Liity Scandic Friends -jäseneksi",
|
||||
"Signing up...": "Rekisteröidytää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 remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.",
|
||||
"Something went wrong!": "Jotain meni pieleen!",
|
||||
@@ -732,6 +736,7 @@
|
||||
"Your points to spend": "Käytettävissä olevat pisteesi",
|
||||
"Your room": "Sinun huoneesi",
|
||||
"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}. 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",
|
||||
"Zip code": "Postinumero",
|
||||
"Zoo": "Eläintarha",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"Arrival date": "Ankomstdato",
|
||||
"As our Close Friend": "Som vår nære venn",
|
||||
"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 the hotel": "På hotellet",
|
||||
"Attractions": "Attraksjoner",
|
||||
@@ -154,6 +155,7 @@
|
||||
"Confirm cancellation": "Bekræft annullerering",
|
||||
"Contact information": "Kontaktinformasjon",
|
||||
"Contact our memberservice": "Contact our memberservice",
|
||||
"Contact the person who booked the stay": "Kontakt personen som booket opholdet",
|
||||
"Contact us": "Kontakt oss",
|
||||
"Continue": "Fortsette",
|
||||
"Copied to clipboard": "Copied to clipboard",
|
||||
@@ -577,6 +579,7 @@
|
||||
"Select payment method": "Velg betalingsmetode",
|
||||
"Select quantity": "Velg antall",
|
||||
"Select room": "Velg rom",
|
||||
"Select rooms": "Velg rom",
|
||||
"Select your language": "Velg språk",
|
||||
"Shopping": "Shopping",
|
||||
"Shopping & Dining": "Shopping & Spisesteder",
|
||||
@@ -590,6 +593,7 @@
|
||||
"Sign up to Scandic Friends": "Bli med i Scandic Friends",
|
||||
"Signing up...": "Registrerer...",
|
||||
"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 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!",
|
||||
@@ -728,6 +732,7 @@
|
||||
"Your points to spend": "Dine brukbare poeng",
|
||||
"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 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}. 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",
|
||||
"Zip code": "Post kode",
|
||||
"Zoo": "Dyrehage",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"Arrival date": "Ankomstdatum",
|
||||
"As our Close Friend": "Som vår nära vän",
|
||||
"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 the hotel": "På hotellet",
|
||||
"Attractions": "Sevärdheter",
|
||||
@@ -154,6 +155,7 @@
|
||||
"Confirm cancellation": "Bekräfta avbokning",
|
||||
"Contact information": "Kontaktinformation",
|
||||
"Contact our memberservice": "Contact our memberservice",
|
||||
"Contact the person who booked the stay": "Kontakta personen som bokade vistelsen",
|
||||
"Contact us": "Kontakta oss",
|
||||
"Continue": "Fortsätt",
|
||||
"Copied to clipboard": "Copied to clipboard",
|
||||
@@ -577,6 +579,7 @@
|
||||
"Select payment method": "Välj betalningsmetod",
|
||||
"Select quantity": "Välj antal",
|
||||
"Select room": "Välj rum",
|
||||
"Select rooms": "Välj rum",
|
||||
"Select your language": "Välj ditt språk",
|
||||
"Shopping": "Shopping",
|
||||
"Shopping & Dining": "Shopping & Mat",
|
||||
@@ -590,6 +593,7 @@
|
||||
"Sign up to Scandic Friends": "Bli medlem i Scandic Friends",
|
||||
"Signing up...": "Registrerar...",
|
||||
"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 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!",
|
||||
@@ -728,6 +732,7 @@
|
||||
"Your points to spend": "Dina spenderbara poäng",
|
||||
"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 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}. 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 ut",
|
||||
"Zip code": "Postnummer",
|
||||
"Zoo": "Djurpark",
|
||||
|
||||
@@ -190,6 +190,7 @@ export const bookingConfirmationSchema = z
|
||||
attributes: z.object({
|
||||
adults: z.number().int(),
|
||||
ancillary: ancillarySchema,
|
||||
cancellationNumber: z.string().nullable().default(""),
|
||||
checkInDate: z.date({ coerce: true }),
|
||||
checkOutDate: z.date({ coerce: true }),
|
||||
childBedPreferences: z.array(childBedPreferencesSchema).default([]),
|
||||
@@ -204,6 +205,8 @@ export const bookingConfirmationSchema = z
|
||||
linkedReservationSchema
|
||||
),
|
||||
hotelId: z.string(),
|
||||
mainRoom: z.boolean(),
|
||||
multiRoom: z.boolean(),
|
||||
packages: z.array(packageSchema).default([]),
|
||||
rateDefinition: rateDefinitionSchema,
|
||||
reservationStatus: z.string().nullable().default(""),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RegisterOptions } from "react-hook-form"
|
||||
import type { RegisterOptions } from "react-hook-form"
|
||||
|
||||
export interface CheckboxProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user