feat: SW-963 Implemented error states and handling booking code and multiroom

This commit is contained in:
Hrishikesh Vaipurkar
2025-02-24 17:06:20 +01:00
parent 202d84218c
commit 2cd1b6c72c
16 changed files with 374 additions and 158 deletions

View File

@@ -1,5 +1,14 @@
.container {
position: relative;
display: grid;
gap: var(--Spacing-x1);
}
.bookingCode {
height: 60px;
background-color: var(--Base-Background-Primary-Normal);
border-radius: var(--Corner-radius-Medium);
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
}
.bookingCodeLabel {
@@ -9,6 +18,11 @@
position: relative;
}
.errorContainer {
display: flex;
flex-direction: column;
gap: var(--Spacing-x-half);
}
.error {
display: flex;
gap: var(--Spacing-x-half);
@@ -26,8 +40,6 @@
.bookingCodeRememberVisible {
display: flex;
position: absolute;
top: calc(100% + 16px);
width: 100%;
}
@@ -36,6 +48,10 @@
margin-top: var(--Spacing-x2);
}
.bookingCodeRememberVisible label {
align-items: center;
}
@media screen and (max-width: 767px) {
.hideOnMobile {
display: none;
@@ -43,6 +59,10 @@
}
@media screen and (min-width: 768px) {
.bookingCode {
height: auto;
background-color: transparent;
}
.bookingCodeRememberVisible {
align-items: center;
background: var(--Base-Surface-Primary-light-Normal);
@@ -54,7 +74,6 @@
@media screen and (min-width: 768px) and (max-width: 1367px) {
.container {
display: flex;
gap: var(--Spacing-x1);
}
.codePopover {
background: var(--Base-Surface-Primary-light-Normal);
@@ -67,16 +86,30 @@
display: grid;
gap: var(--Spacing-x2);
}
.bookingCodeRememberVisible {
position: static;
.overlayTrigger {
position: absolute;
top: 0;
bottom: 0;
display: block;
left: 0;
right: 24px;
}
}
@media screen and (min-width: 1367px) {
.container:hover,
.container:focus-within,
.container:has([data-focused="true"], [data-pressed="true"]) {
background-color: var(--Base-Surface-Primary-light-Hover-alt);
border-radius: var(--Corner-radius-Medium);
}
.bookingCodeRememberVisible {
padding: var(--Spacing-x2);
width: 320px;
position: absolute;
top: calc(100% + 24px);
left: calc(0% - var(--Spacing-x-one-and-half));
left: calc(0% - var(--Spacing-x-half));
width: 360px;
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
}
}

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react"
import { Dialog, DialogTrigger, Popover } from "react-aria-components"
import { useFormContext } from "react-hook-form"
import { type FieldError,useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts"
@@ -21,6 +21,7 @@ import type {
BookingCodeSchema,
BookingWidgetSchema,
} from "@/types/components/bookingWidget"
import type { ButtonProps } from "@/components/TempDesignSystem/Button/button"
export default function BookingCode() {
const intl = useIntl()
@@ -34,11 +35,9 @@ export default function BookingCode() {
setValue,
formState: { errors },
getValues,
register,
} = useFormContext<BookingWidgetSchema>()
const bookingCode: BookingCodeSchema = getValues("bookingCode")
const [isOpen, setIsOpen] = useState(!!bookingCode?.value)
const [showRemember, setShowRemember] = useState(false)
const [showRememberMobile, setShowRememberMobile] = useState(false)
const codeError = errors["bookingCode"]?.value
@@ -50,24 +49,21 @@ export default function BookingCode() {
setValue("bookingCode.value", value, { shouldValidate: true })
}
function toggleModal(isOpen: boolean) {
if (!isOpen && !bookingCode?.value) {
setValue("bookingCode.flag", false)
setIsOpen(isOpen)
} else if (!codeError || isOpen) {
setIsOpen(isOpen)
if (isOpen || bookingCode?.value) {
setValue("bookingCode.flag", true)
}
}
}
const closeIfOutside = useCallback(
(target: HTMLElement) => {
if (ref.current && target && !ref.current.contains(target)) {
if (
ref.current &&
target &&
!ref.current.contains(target) &&
target.getAttribute("value") !== "Remove extra rooms"
) {
setShowRemember(false)
if (codeError) {
setValue("bookingCode.value", "", { shouldValidate: true })
}
}
},
[setShowRemember, ref]
[setShowRemember, setValue, ref, codeError]
)
function showRememberCheck() {
@@ -102,46 +98,10 @@ export default function BookingCode() {
}, [closeIfOutside, showRemember])
return isTablet ? (
<div className={styles.container}>
<DialogTrigger isOpen={isOpen} onOpenChange={toggleModal}>
<Button type="button" intent="text">
<Checkbox
checked={!!bookingCode?.value}
{...register("bookingCode.flag", {
onChange: function () {
if (bookingCode?.value || isOpen) {
setValue("bookingCode.flag", true)
}
},
})}
>
<Caption color="uiTextMediumContrast" asChild>
<span>{codeVoucher}</span>
</Caption>
</Checkbox>
</Button>
<Popover
className={styles.codePopover}
placement="bottom start"
offset={36}
>
<Dialog>
{({ close }) => (
<div className={styles.popover}>
<TabletCodeInput updateValue={updateBookingCodeFormValue} />
<div className={styles.bookingCodeRememberVisible}>
<CodeRemember
bookingCodeValue={bookingCode?.value}
onApplyClick={close}
/>
</div>
</div>
)}
</Dialog>
</Popover>
</DialogTrigger>
<CodeRulesModal />
</div>
<TabletBookingCode
bookingCode={bookingCode}
updateValue={updateBookingCodeFormValue}
/>
) : (
<div
className={styles.container}
@@ -149,32 +109,28 @@ export default function BookingCode() {
onFocus={showRememberCheck}
onBlur={(e) => closeIfOutside(e.nativeEvent.relatedTarget as HTMLElement)}
>
<div className={styles.bookingCodeLabel}>
<Caption
color={showRemember ? "uiTextActive" : "red"}
type="bold"
asChild
>
<span>{codeVoucher}</span>
</Caption>
<CodeRulesModal />
<div className={styles.bookingCode}>
<div className={styles.bookingCodeLabel}>
<Caption
color={showRemember ? "uiTextActive" : "red"}
type="bold"
asChild
>
<span>{codeVoucher}</span>
</Caption>
<CodeRulesModal />
</div>
<Input
className="input"
type="search"
placeholder={addCode}
name="bookingCode.value"
id="booking-code"
onChange={(event) => updateBookingCodeFormValue(event.target.value)}
autoComplete="off"
value={bookingCode?.value}
/>
</div>
<Input
className="input"
type="search"
placeholder={addCode}
name="bookingCode.value"
id="booking-code"
onChange={(event) => updateBookingCodeFormValue(event.target.value)}
defaultValue={bookingCode?.value}
autoComplete="off"
/>
{codeError?.message ? (
<Caption color="red" className={styles.error}>
<ErrorCircleIcon color="red" className={styles.errorIcon} />
{intl.formatMessage({ id: codeError.message })}
</Caption>
) : null}
{isDesktop ? (
<div
className={
@@ -183,25 +139,35 @@ export default function BookingCode() {
: styles.bookingCodeRemember
}
>
<CodeRemember
bookingCodeValue={bookingCode?.value}
onApplyClick={() => setShowRemember(false)}
/>
{codeError?.message ? (
<BookingCodeError codeError={codeError} />
) : (
<CodeRemember
bookingCodeValue={bookingCode?.value}
onApplyClick={() => setShowRemember(false)}
/>
)}
</div>
) : (
<div
className={
showRememberMobile
? styles.bookingCodeRememberVisible
: styles.bookingCodeRemember
}
>
<Switch name="bookingCode.remember" className="mobile-switch">
<Caption asChild>
<span>{intl.formatMessage({ id: "Remember code" })}</span>
</Caption>
</Switch>
</div>
<>
{codeError?.message ? (
<BookingCodeError codeError={codeError} />
) : (
<div
className={
showRememberMobile
? styles.bookingCodeRememberVisible
: styles.bookingCodeRemember
}
>
<Switch name="bookingCode.remember" className="mobile-switch">
<Caption asChild>
<span>{intl.formatMessage({ id: "Remember code" })}</span>
</Caption>
</Switch>
</div>
)}
</>
)}
</div>
)
@@ -259,3 +225,128 @@ function CodeRemember({ bookingCodeValue, onApplyClick }: CodeRememberProps) {
</>
)
}
function BookingCodeError({ codeError }: { codeError: FieldError }) {
const intl = useIntl()
const isMultiroomErr = codeError.message?.indexOf("Multi-room") !== -1
return (
<div className={styles.errorContainer}>
<Caption color={isMultiroomErr ? "blue" : "red"} className={styles.error}>
<ErrorCircleIcon
color={isMultiroomErr ? "blue" : "red"}
className={styles.errorIcon}
/>
{intl.formatMessage({ id: codeError.message })}
</Caption>
{isMultiroomErr ? (
<RemoveExtraRooms className={styles.hideOnMobile} />
) : null}
</div>
)
}
export function RemoveExtraRooms({ ...props }: ButtonProps) {
const intl = useIntl()
const { getValues, setValue, trigger } = useFormContext<BookingWidgetSchema>()
function removeExtraRooms() {
// Timeout to delay the event scheduling issue with touch events on mobile
window.setTimeout(() => {
const rooms = getValues("rooms")[0]
setValue("rooms", [rooms], { shouldValidate: true })
trigger("bookingCode.value")
}, 300)
}
return (
<Button
value="Remove extra rooms"
type="button"
onClick={removeExtraRooms}
size="small"
intent="secondary"
{...props}
>
{intl.formatMessage({ id: "Remove extra rooms" })}
</Button>
)
}
function TabletBookingCode({
bookingCode,
updateValue,
}: {
bookingCode: BookingCodeSchema
updateValue: (value: string) => void
}) {
const intl = useIntl()
const [isOpen, setIsOpen] = useState(!!bookingCode?.value)
const {
setValue,
register,
formState: { errors },
} = useFormContext<BookingWidgetSchema>()
const codeError = errors["bookingCode"]?.value
const codeVoucher = intl.formatMessage({ id: "Code / Voucher" })
function toggleModal(isOpen: boolean) {
if (!isOpen && !bookingCode?.value) {
setValue("bookingCode.flag", false)
setIsOpen(isOpen)
} else if (!codeError || isOpen) {
setIsOpen(isOpen)
if (isOpen || bookingCode?.value) {
setValue("bookingCode.flag", true)
}
}
}
return (
<div className={styles.container}>
<DialogTrigger isOpen={isOpen} onOpenChange={toggleModal}>
<Button type="button" intent="text">
{/* For some reason Checkbox click triggers twice modal state change, which returns the modal back to old state. So we are using overlay as trigger for modal */}
<div className={styles.overlayTrigger}></div>
<Checkbox
checked={!!bookingCode?.value}
{...register("bookingCode.flag", {
onChange: function () {
if (bookingCode?.value || isOpen) {
setValue("bookingCode.flag", true)
}
},
})}
>
<Caption color="uiTextMediumContrast" type="bold" asChild>
<span>{codeVoucher}</span>
</Caption>
</Checkbox>
</Button>
<Popover
className={styles.codePopover}
placement="bottom start"
offset={36}
>
<Dialog>
{({ close }) => (
<div className={styles.popover}>
<TabletCodeInput updateValue={updateValue} />
<div className={styles.bookingCodeRememberVisible}>
{codeError?.message ? (
<RemoveExtraRooms />
) : (
<CodeRemember
bookingCodeValue={bookingCode?.value}
onApplyClick={close}
/>
)}
</div>
</div>
)}
</Dialog>
</Popover>
</DialogTrigger>
<CodeRulesModal />
</div>
)
}

View File

@@ -11,12 +11,6 @@
margin-top: var(--Spacing-x2);
align-items: center;
}
.vouchers {
width: 100%;
display: block;
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
border-radius: var(--Corner-radius-Small);
}
.optionsContainer {
display: flex;
@@ -28,12 +22,6 @@
height: 24px;
}
@media screen and (max-width: 767px) {
.vouchers {
margin-bottom: var(--Spacing-x5);
}
}
@media screen and (min-width: 768px) {
.options {
flex-direction: row;
@@ -48,25 +36,9 @@
grid-template-columns: auto auto;
column-gap: var(--Spacing-x2);
}
.vouchers:hover,
.vouchers:focus-within,
.vouchers:has([data-focused="true"], [data-pressed="true"]) {
background-color: var(--Base-Surface-Primary-light-Hover-alt);
}
}
@media screen and (max-width: 1366px) {
.vouchers {
background-color: var(--Base-Background-Primary-Normal);
border-radius: var(--Corner-radius-Medium);
}
}
@media screen and (min-width: 1367px) {
.vouchers {
display: block;
max-width: 200px;
}
.options {
flex-direction: column;
max-width: 190px;

View File

@@ -16,6 +16,11 @@
position: relative;
}
.buttonContainer {
display: grid;
gap: var(--Spacing-x1);
}
.showOnTablet {
display: none;
}
@@ -33,7 +38,6 @@
}
.rooms,
.vouchers,
.when,
.where {
background-color: var(--Base-Background-Primary-Normal);
@@ -41,7 +45,6 @@
}
.rooms,
.vouchers,
.when {
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
}
@@ -103,6 +106,10 @@
justify-content: center;
width: 118px;
}
.showOnMobile {
display: none;
}
}
@media screen and (min-width: 768px) and (max-width: 1366px) {
@@ -143,3 +150,9 @@
display: none;
}
}
@media screen and (min-width: 1366px) {
.input {
gap: var(--Spacing-x2);
}
}

View File

@@ -1,5 +1,5 @@
"use client"
import { useWatch } from "react-hook-form"
import { useFormContext, useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
@@ -11,11 +11,13 @@ import SkeletonShimmer from "@/components/SkeletonShimmer"
import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { RemoveExtraRooms } from "./BookingCode"
import Search, { SearchSkeleton } from "./Search"
import Voucher, { VoucherSkeleton } from "./Voucher"
import styles from "./formContent.module.css"
import type { BookingWidgetSchema } from "@/types/components/bookingWidget"
import type { BookingWidgetFormContentProps } from "@/types/components/form/bookingwidget"
export default function FormContent({
@@ -25,6 +27,10 @@ export default function FormContent({
isSearching,
}: BookingWidgetFormContentProps) {
const intl = useIntl()
const {
formState: { errors },
} = useFormContext<BookingWidgetSchema>()
const bookingCodeError = errors["bookingCode"]?.value
const selectedDate = useWatch({ name: "date" })
const roomsLabel = intl.formatMessage({ id: "Rooms & Guests" })
@@ -79,6 +85,13 @@ export default function FormContent({
<Voucher />
</div>
<div className={`${styles.buttonContainer} ${styles.hideOnTablet}`}>
{bookingCodeError?.message?.indexOf("Multi-room") === 0 ? (
<RemoveExtraRooms
size="medium"
fullWidth
className={styles.showOnMobile}
/>
) : null}
<Button
className={styles.button}
form={formId}

View File

@@ -109,12 +109,24 @@ export const bookingWidgetSchema = z
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Multiroom with voucher error",
message: "Multi-room booking is not available with this booking code.",
path: ["bookingCode.value"],
})
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Multiroom with voucher error",
message: "Multi-room booking is not available with this booking code.",
path: ["rooms"],
})
}
if (value.rooms.length > 1 && value.redemption) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Multi-room booking is not available with reward night.",
path: ["bookingCode.value"],
})
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Multi-room booking is not available with reward night.",
path: ["rooms"],
})
}

View File

@@ -4,8 +4,17 @@ import { useCallback, useEffect } from "react"
import { useFormContext, useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
import { CloseLargeIcon, PlusCircleIcon, PlusIcon } from "../Icons"
import { env } from "@/env/client"
import {
CloseLargeIcon,
ErrorCircleIcon,
PlusCircleIcon,
PlusIcon,
} from "../Icons"
import Button from "../TempDesignSystem/Button"
import Caption from "../TempDesignSystem/Text/Caption"
import { Tooltip } from "../TempDesignSystem/Tooltip"
import { GuestsRoom } from "./GuestsRoom"
import styles from "./guests-rooms-picker.module.css"
@@ -25,12 +34,22 @@ export default function GuestsRoomsPickerDialog({
onClose,
}: GuestsRoomsPickerDialogProps) {
const intl = useIntl()
const { getFieldState, trigger, setValue } =
const { getFieldState, trigger, setValue, getValues } =
useFormContext<BookingWidgetSchema>()
const roomsValue = useWatch<BookingWidgetSchema, "rooms">({ name: "rooms" })
const addRoomLabel = intl.formatMessage({ id: "Add room" })
const doneLabel = intl.formatMessage({ id: "Done" })
// Disable add room if booking code is voucher or reward night is enebaled
const addRoomDisabledText = getValues("redemption")
? intl.formatMessage({
id: "Multi-room booking is not available with reward night.",
})
: getValues("bookingCode.value")?.toLowerCase().startsWith("vo") &&
intl.formatMessage({
id: "Multi-room booking is not available with this booking code.",
})
const handleClose = useCallback(async () => {
const isValid = await trigger("rooms")
if (isValid) onClose()
@@ -94,28 +113,59 @@ export default function GuestsRoomsPickerDialog({
theme="base"
fullWidth
onPress={handleAddRoom}
disabled={addRoomDisabledText ? true : false}
>
<PlusIcon />
{addRoomLabel}
</Button>
{addRoomDisabledText ? (
<Caption
color="blue"
className={styles.addRoomMobileDisabledText}
>
<ErrorCircleIcon color="blue" width={20} />
{addRoomDisabledText}
</Caption>
) : null}
</div>
)}
</div>
</section>
<footer className={styles.footer}>
{canAddRooms && (
{addRoomDisabledText ? (
<div className={styles.hideOnMobile}>
<Button
intent="text"
variant="icon"
wrapping
theme="base"
onPress={handleAddRoom}
<Tooltip
text={addRoomDisabledText}
position={"bottom"}
arrow="left"
>
<PlusCircleIcon />
{addRoomLabel}
</Button>
<Button
intent="text"
variant="icon"
wrapping
theme="base"
disabled
>
<PlusCircleIcon />
{addRoomLabel}
</Button>
</Tooltip>
</div>
) : (
canAddRooms && (
<div className={styles.hideOnMobile}>
<Button
intent="text"
variant="icon"
wrapping
theme="base"
onPress={handleAddRoom}
>
<PlusCircleIcon />
{addRoomLabel}
</Button>
</div>
)
)}
<Button
onPress={handleClose}

View File

@@ -117,9 +117,22 @@
.addRoomMobileContainer {
display: grid;
padding-bottom: calc(var(--sticky-button-height) + 20px);
gap: var(--Spacing-x2);
}
.addRoomMobileContainer button {
width: 150px;
margin: 0 auto;
padding-bottom: calc(var(--sticky-button-height) + 20px);
}
.addRoomMobileContainer .addRoomMobileDisabledText {
padding: var(--Spacing-x1) var(--Spacing-x2);
background-color: var(--Base-Background-Primary-Normal);
margin: 0 var(--Spacing-x2);
border-radius: var(--Corner-radius-Medium);
display: flex;
gap: var(--Spacing-x1);
}
}

View File

@@ -36,7 +36,10 @@ export default function Checkbox({
{({ isSelected }) => (
<>
<span className={styles.checkboxContainer}>
<span className={styles.checkbox} tabIndex={0}>
<span
className={styles.checkbox}
tabIndex={registerOptions?.disabled ? undefined : 0}
>
{isSelected && <CheckIcon color="white" />}
</span>
{children}

View File

@@ -11,3 +11,7 @@
gap: var(--Spacing-x-half);
margin: var(--Spacing-x1) 0 0;
}
.error svg {
min-width: 20px;
}

View File

@@ -405,7 +405,8 @@
"Month": "Måned",
"MonFri: Always open": "ManFre: Altid åben",
"MonFri: {openingTime}{closingTime}": "ManFre: {openingTime}{closingTime}",
"Multiroom with voucher error": "Det er ikke muligt at booke flere værelser med gavekort.",
"Multi-room booking is not available with reward night.": "Multi-værelse booking er ikke tilgængelig med belønning nat.",
"Multi-room booking is not available with this booking code.": "Multi-værelse booking er ikke tilgængelig med denne reservationskode.",
"Museum": "Museum",
"Museums": "Museer",
"My Add-on's": "Mine tilføjelser",
@@ -546,6 +547,7 @@
"Remember code": "Husk kode",
"Remove": "Fjerne",
"Remove card from member profile": "Fjern kortet fra medlemsprofilen",
"Remove extra rooms": "Fjern ekstra rum",
"Remove room": "Remove room",
"Request bedtype": "Anmod om sengetype",
"Reservation policy": "Reservation policy",

View File

@@ -406,7 +406,8 @@
"Month": "Monat",
"MonFri: Always open": "MoFr: Immer geöffnet",
"MonFri: {openingTime}{closingTime}": "MoFr: {openingTime}{closingTime}",
"Multiroom with voucher error": "Die Buchung mehrerer Zimmer ist nicht mit einem Gutschein möglich",
"Multi-room booking is not available with reward night.": "Mehrzimmerbuchungen sind mit Prämiennächten nicht möglich.",
"Multi-room booking is not available with this booking code.": "Mehrzimmerbuchungen sind mit diesem Buchungscode nicht möglich.",
"Museum": "Museum",
"Museums": "Museen",
"My Add-on's": "Meine Add-ons",
@@ -545,6 +546,7 @@
"Remember code": "Code merken",
"Remove": "Entfernen",
"Remove card from member profile": "Karte aus dem Mitgliedsprofil entfernen",
"Remove extra rooms": "Zusätzliche Zimmer entfernen",
"Remove room": "Remove room",
"Request bedtype": "Bettentyp anfragen",
"Reservation policy": "Reservation policy",

View File

@@ -404,7 +404,8 @@
"Month": "Month",
"MonFri: Always open": "MonFri: Always open",
"MonFri: {openingTime}{closingTime}": "MonFri: {openingTime}{closingTime}",
"Multiroom with voucher error": "Multiroom booking is not compatible with a code or voucher",
"Multi-room booking is not available with reward night.": "Multi-room booking is not available with reward night.",
"Multi-room booking is not available with this booking code.": "Multi-room booking is not available with this booking code.",
"Museum": "Museum",
"Museums": "Museums",
"My Add-on's": "My Add-on's",
@@ -544,6 +545,7 @@
"Remember code": "Remember code",
"Remove": "Remove",
"Remove card from member profile": "Remove card from member profile",
"Remove extra rooms": "Remove extra rooms",
"Remove room": "Remove room",
"Request bedtype": "Request bedtype",
"Reservation policy": "Reservation policy",

View File

@@ -405,7 +405,8 @@
"Month": "Kuukausi",
"MonFri: Always open": "MaPe: Aina auki",
"MonFri: {openingTime}{closingTime}": "MaPe: {openingTime}-{closingTime}",
"Multiroom with voucher error": "Lahjakortilla ei voi varata monta huonetta",
"Multi-room booking is not available with reward night.": "Usean huoneen varaus ei ole saatavilla palkintoyönä.",
"Multi-room booking is not available with this booking code.": "Usean huoneen varaus ei ole käytettävissä tällä varauskoodilla.",
"Museum": "Museo",
"Museums": "Museot",
"My Add-on's": "Omat lisäosat",
@@ -544,6 +545,7 @@
"Remember code": "Muista koodi",
"Remove": "Poistaa",
"Remove card from member profile": "Poista kortti jäsenprofiilista",
"Remove extra rooms": "Poista ylimääräiset huoneet",
"Remove room": "Remove room",
"Request bedtype": "Pyydä sänkytyyppiä",
"Reservation policy": "Reservation policy",

View File

@@ -404,7 +404,8 @@
"Month": "Måned",
"MonFri: Always open": "ManFre: Alltid åpen",
"MonFri: {openingTime}{closingTime}": "ManFre: {openingTime}{closingTime}",
"Multiroom with voucher error": "Bestilling av flere rom er ikke mulig når du bestiller med voucher",
"Multi-room booking is not available with reward night.": "Multi-rom booking er ikke tilgjengelig med belønning natt.",
"Multi-room booking is not available with this booking code.": "Bestilling av flere rom er ikke tilgjengelig med denne bestillingskoden.",
"Museum": "Museum",
"Museums": "Museums",
"My Add-on's": "Mine tillegg",
@@ -543,6 +544,7 @@
"Remember code": "Husk kode",
"Remove": "Fjerne",
"Remove card from member profile": "Fjern kortet fra medlemsprofilen",
"Remove extra rooms": "Fjern ekstra rom",
"Remove room": "Remove room",
"Request bedtype": "Be om sengetype",
"Reservation policy": "Reservation policy",

View File

@@ -404,7 +404,8 @@
"Month": "Månad",
"MonFri: Always open": "MånFre: Alltid öppet",
"MonFri: {openingTime}{closingTime}": "MånFre: {openingTime}{closingTime}",
"Multiroom with voucher error": "Flera rum kan inte bokas samtidigt med presentkort",
"Multi-room booking is not available with reward night.": "Flerrumsbokning är inte tillgänglig med belöningsnatt.",
"Multi-room booking is not available with this booking code.": "Flerrumsbokning är inte tillgänglig med denna bokningskod.",
"Museum": "Museum",
"Museums": "Museer",
"My Add-on's": "Mina tillägg",
@@ -543,6 +544,7 @@
"Remember code": "Kom ihåg kod",
"Remove": "Ta bort",
"Remove card from member profile": "Ta bort kortet från medlemsprofilen",
"Remove extra rooms": "Ta bort extra rum",
"Remove room": "Remove room",
"Request bedtype": "Request bedtype",
"Reservation policy": "Reservation policy",