fix(i18n): prepare for Lokalise
This commit is contained in:
@@ -5,11 +5,13 @@ import { overview } from "@/constants/routes/myPages"
|
|||||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import { auth } from "@/auth"
|
import { auth } from "@/auth"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
export default async function ProtectedLayout({
|
export default async function ProtectedLayout({
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren) {
|
}: React.PropsWithChildren) {
|
||||||
|
const intl = await getIntl()
|
||||||
const session = await auth()
|
const session = await auth()
|
||||||
/**
|
/**
|
||||||
* Fallback to make sure every route nested in the
|
* Fallback to make sure every route nested in the
|
||||||
@@ -54,7 +56,7 @@ export default async function ProtectedLayout({
|
|||||||
console.error(`[layout:protected] unhandled user loading error`)
|
console.error(`[layout:protected] unhandled user loading error`)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return <p>Something went wrong!</p>
|
return <p>{intl.formatMessage({ id: "Something went wrong!" })}</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs"
|
import * as Sentry from "@sentry/nextjs"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
export default function Error({
|
export default function Error({
|
||||||
error,
|
error,
|
||||||
}: {
|
}: {
|
||||||
error: Error & { digest?: string }
|
error: Error & { digest?: string }
|
||||||
}) {
|
}) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!error) return
|
if (!error) return
|
||||||
|
|
||||||
@@ -17,7 +21,12 @@ export default function Error({
|
|||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
<strong>
|
<strong>
|
||||||
Breadcrumbs failed for this page ({error.digest}@{Date.now()})
|
{intl.formatMessage(
|
||||||
|
{ id: "Breadcrumbs failed for this page ({errorId})" },
|
||||||
|
{
|
||||||
|
errorId: `${error.digest}@${Date.now()}`,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs"
|
import * as Sentry from "@sentry/nextjs"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
export default function Error({
|
export default function Error({
|
||||||
error,
|
error,
|
||||||
}: {
|
}: {
|
||||||
error: Error & { digest?: string }
|
error: Error & { digest?: string }
|
||||||
}) {
|
}) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!error) return
|
if (!error) return
|
||||||
|
|
||||||
@@ -18,7 +21,12 @@ export default function Error({
|
|||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
<strong>
|
<strong>
|
||||||
Error loading this page ({error.digest}@{Date.now()})
|
{intl.formatMessage(
|
||||||
|
{ id: "Error loading this page ({errorId})" },
|
||||||
|
{
|
||||||
|
errorId: `${error.digest}@${Date.now()}`,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,12 +30,43 @@ export default async function MembershipCardSlot({
|
|||||||
membershipCards.map((card, idx) => (
|
membershipCards.map((card, idx) => (
|
||||||
<div className={styles.card} key={idx}>
|
<div className={styles.card} key={idx}>
|
||||||
<Subtitle className={styles.subTitle}>
|
<Subtitle className={styles.subTitle}>
|
||||||
Name: {card.membershipType}
|
{intl.formatMessage(
|
||||||
|
{ id: "Name: {cardMembershipType}" },
|
||||||
|
{
|
||||||
|
cardMembershipType: card.membershipType,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<span> Current Points: {card.currentPoints} </span>
|
<span>
|
||||||
<span> Member Since: {card.memberSince}</span>
|
{intl.formatMessage(
|
||||||
<span> Number: {card.membershipNumber}</span>
|
{ id: "Current Points {points, number}" },
|
||||||
<span>Expiration Date: {card.expirationDate.split("T")[0]}</span>
|
{ points: card.currentPoints }
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "Member Since: {value}" },
|
||||||
|
{
|
||||||
|
value: card.memberSince,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "Number: {membershipNumber}" },
|
||||||
|
{
|
||||||
|
membershipNumber: card.membershipNumber,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "Expiration Date: {expirationDate}" },
|
||||||
|
{
|
||||||
|
expirationDate: card.expirationDate.split("T")[0],
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Link href="#" variant="icon">
|
<Link href="#" variant="icon">
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs"
|
import * as Sentry from "@sentry/nextjs"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import type { ErrorPage } from "@/types/next/error"
|
import type { ErrorPage } from "@/types/next/error"
|
||||||
|
|
||||||
export default function ProfileError({ error }: ErrorPage) {
|
export default function ProfileError({ error }: ErrorPage) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!error) return
|
if (!error) return
|
||||||
|
|
||||||
@@ -13,5 +16,5 @@ export default function ProfileError({ error }: ErrorPage) {
|
|||||||
Sentry.captureException(error)
|
Sentry.captureException(error)
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
return <h1>Error happened, Profile</h1>
|
return <h1>{intl.formatMessage({ id: "Error happened, Profile" })}</h1>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,24 @@ export default async function Profile({ params }: PageArgs<LangParams>) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addressParts = []
|
||||||
|
if (user.address.streetAddress) {
|
||||||
|
addressParts.push(user.address.streetAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.address.city) {
|
||||||
|
addressParts.push(user.address.city)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.address.country) {
|
||||||
|
addressParts.push(user.address.country)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addressOutput =
|
||||||
|
addressParts.length > 0
|
||||||
|
? addressParts.join(", ")
|
||||||
|
: intl.formatMessage({ id: "N/A" })
|
||||||
|
|
||||||
const defaultLanguage = languages[params.lang]
|
const defaultLanguage = languages[params.lang]
|
||||||
const language = languageSelect.find((l) => l.value === user.language)
|
const language = languageSelect.find((l) => l.value === user.language)
|
||||||
return (
|
return (
|
||||||
@@ -90,18 +108,7 @@ export default async function Profile({ params }: PageArgs<LangParams>) {
|
|||||||
<Body color="burgundy" textTransform="bold">
|
<Body color="burgundy" textTransform="bold">
|
||||||
{intl.formatMessage({ id: "Address" })}
|
{intl.formatMessage({ id: "Address" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="burgundy">
|
<Body color="burgundy">{addressOutput}</Body>
|
||||||
{user.address.streetAddress
|
|
||||||
? `${user.address.streetAddress}, `
|
|
||||||
: ""}
|
|
||||||
{user.address.city ? `${user.address.city}, ` : ""}
|
|
||||||
{user.address.country ? `${user.address.country}` : ""}
|
|
||||||
{!user.address.streetAddress &&
|
|
||||||
!user.address.city &&
|
|
||||||
!user.address.country
|
|
||||||
? "N/A"
|
|
||||||
: null}
|
|
||||||
</Body>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<LockIcon color="burgundy" />
|
<LockIcon color="burgundy" />
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
"use client" // Error components must be Client Components
|
"use client"
|
||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs"
|
import * as Sentry from "@sentry/nextjs"
|
||||||
import {
|
import { useParams, useRouter, useSearchParams } from "next/navigation"
|
||||||
useParams,
|
|
||||||
usePathname,
|
|
||||||
useRouter,
|
|
||||||
useSearchParams,
|
|
||||||
} from "next/navigation"
|
|
||||||
import { startTransition, useEffect, useRef } from "react"
|
import { startTransition, useEffect, useRef } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { login } from "@/constants/routes/handleAuth"
|
import { login } from "@/constants/routes/handleAuth"
|
||||||
import { SESSION_EXPIRED } from "@/server/errors/trpc"
|
import { SESSION_EXPIRED } from "@/server/errors/trpc"
|
||||||
|
|
||||||
import { findLang } from "@/utils/languages"
|
|
||||||
|
|
||||||
import styles from "./error.module.css"
|
import styles from "./error.module.css"
|
||||||
|
|
||||||
import type { LangParams } from "@/types/params"
|
import type { LangParams } from "@/types/params"
|
||||||
@@ -63,13 +57,10 @@ export default function Error({
|
|||||||
currentSearchParamsRef.current = currentSearchParams
|
currentSearchParamsRef.current = currentSearchParams
|
||||||
}, [searchParams, reset, router])
|
}, [searchParams, reset, router])
|
||||||
|
|
||||||
const pathname = usePathname()
|
|
||||||
const lang = findLang(pathname)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.layout}>
|
<section className={styles.layout}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{lang}: {intl.formatMessage({ id: "Something went wrong!" })}
|
{intl.formatMessage({ id: "Something went wrong!" })}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
|
import { getIntl } from "@/i18n"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
import type { LangParams, LayoutArgs, StatusParams } from "@/types/params"
|
import type { LangParams, LayoutArgs, StatusParams } from "@/types/params"
|
||||||
|
|
||||||
export default function MiddlewareError({
|
export default async function MiddlewareError({
|
||||||
params,
|
params,
|
||||||
}: LayoutArgs<LangParams & StatusParams>) {
|
}: LayoutArgs<LangParams & StatusParams>) {
|
||||||
setLang(params.lang)
|
setLang(params.lang)
|
||||||
|
|
||||||
|
const intl = await getIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.layout}>
|
<div className={styles.layout}>
|
||||||
Middleware Error {params.lang}: {params.status}
|
{intl.formatMessage(
|
||||||
|
{ id: "Middleware error {lang}: {status}" },
|
||||||
|
{
|
||||||
|
lang: params.lang,
|
||||||
|
status: params.status,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getProfile } from "@/lib/trpc/memoizedRequests"
|
|||||||
|
|
||||||
import AccountPage from "@/components/Webviews/AccountPage"
|
import AccountPage from "@/components/Webviews/AccountPage"
|
||||||
import LoyaltyPage from "@/components/Webviews/LoyaltyPage"
|
import LoyaltyPage from "@/components/Webviews/LoyaltyPage"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -18,11 +19,12 @@ export default async function ContentTypePage({
|
|||||||
params,
|
params,
|
||||||
}: PageArgs<LangParams & ContentTypeWebviewParams & UIDParams, {}>) {
|
}: PageArgs<LangParams & ContentTypeWebviewParams & UIDParams, {}>) {
|
||||||
setLang(params.lang)
|
setLang(params.lang)
|
||||||
|
const intl = await getIntl()
|
||||||
const user = await getProfile()
|
const user = await getProfile()
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.log(`[webview:page] unable to load user`)
|
console.log(`[webview:page] unable to load user`)
|
||||||
return <p>Error: No user could be loaded</p>
|
return <p>{intl.formatMessage({ id: "Error: No user could be loaded" })}</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("error" in user) {
|
if ("error" in user) {
|
||||||
@@ -36,9 +38,13 @@ export default async function ContentTypePage({
|
|||||||
console.log(`[webview:page] user error, redirecting to: ${redirectURL}`)
|
console.log(`[webview:page] user error, redirecting to: ${redirectURL}`)
|
||||||
redirect(redirectURL)
|
redirect(redirectURL)
|
||||||
case "notfound":
|
case "notfound":
|
||||||
return <p>Error: user not found</p>
|
return <p>{intl.formatMessage({ id: "Error: user not found" })}</p>
|
||||||
case "unknown":
|
case "unknown":
|
||||||
return <p>Unknown error occurred loading user</p>
|
return (
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({ id: "Unknown error occurred loading user" })}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
const u: never = user
|
const u: never = user
|
||||||
console.log(`[webview:page] unhandled user loading error`)
|
console.log(`[webview:page] unhandled user loading error`)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs"
|
import * as Sentry from "@sentry/nextjs"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import styles from "./global-error.module.css"
|
import styles from "./global-error.module.css"
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ export default function GlobalError({
|
|||||||
}) {
|
}) {
|
||||||
console.log({ global_error: error })
|
console.log({ global_error: error })
|
||||||
|
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Sentry.captureException(error)
|
Sentry.captureException(error)
|
||||||
}, [error])
|
}, [error])
|
||||||
@@ -20,7 +23,7 @@ export default function GlobalError({
|
|||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<div className={styles.layout}>
|
<div className={styles.layout}>
|
||||||
<h1>Something went really wrong!</h1>
|
<h1>{intl.formatMessage({ id: "Something went really wrong!" })}</h1>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import JsonToHtml from "@/components/JsonToHtml"
|
import JsonToHtml from "@/components/JsonToHtml"
|
||||||
import SectionContainer from "@/components/Section/Container"
|
import SectionContainer from "@/components/Section/Container"
|
||||||
@@ -14,6 +16,8 @@ import type { AccordionProps } from "@/types/components/blocks/Accordion"
|
|||||||
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation"
|
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation"
|
||||||
|
|
||||||
export default function AccordionSection({ accordion, title }: AccordionProps) {
|
export default function AccordionSection({ accordion, title }: AccordionProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const showToggleButton = accordion.length > 5
|
const showToggleButton = accordion.length > 5
|
||||||
const [allAccordionsVisible, setAllAccordionsVisible] =
|
const [allAccordionsVisible, setAllAccordionsVisible] =
|
||||||
useState(!showToggleButton)
|
useState(!showToggleButton)
|
||||||
@@ -44,8 +48,12 @@ export default function AccordionSection({ accordion, title }: AccordionProps) {
|
|||||||
<ShowMoreButton
|
<ShowMoreButton
|
||||||
loadMoreData={toggleAccordions}
|
loadMoreData={toggleAccordions}
|
||||||
showLess={allAccordionsVisible}
|
showLess={allAccordionsVisible}
|
||||||
textShowMore="See all FAQ"
|
textShowMore={intl.formatMessage({
|
||||||
textShowLess="See less FAQ"
|
id: "See all FAQ",
|
||||||
|
})}
|
||||||
|
textShowLess={intl.formatMessage({
|
||||||
|
id: "See less FAQ",
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</SectionContainer>
|
</SectionContainer>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
|||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
|
|
||||||
import SectionWrapper from "../SectionWrapper"
|
import SectionWrapper from "../SectionWrapper"
|
||||||
|
|
||||||
@@ -35,9 +34,25 @@ export default async function LoyaltyLevels({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function LevelCard({ level }: LevelCardProps) {
|
async function LevelCard({ level }: LevelCardProps) {
|
||||||
const lang = getLang()
|
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
const pointsString = `${level.required_points.toLocaleString(lang)} ${intl.formatMessage({ id: "points" })} `
|
|
||||||
|
let pointsMsg: React.ReactNode = intl.formatMessage(
|
||||||
|
{ id: "{pointsAmount, number} points" },
|
||||||
|
{ pointsAmount: level.required_points }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (level.required_nights) {
|
||||||
|
pointsMsg = intl.formatMessage<React.ReactNode>(
|
||||||
|
{
|
||||||
|
id: "{pointsAmount, number} points <highlight>or {nightsAmount, number} nights</highlight>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pointsAmount: level.required_points,
|
||||||
|
nightsAmount: level.required_nights,
|
||||||
|
highlight: (str) => <span className={styles.redText}>{str}</span>,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={styles.card}>
|
<article className={styles.card}>
|
||||||
@@ -47,18 +62,15 @@ async function LevelCard({ level }: LevelCardProps) {
|
|||||||
color="primaryLightOnSurfaceAccent"
|
color="primaryLightOnSurfaceAccent"
|
||||||
tilted="large"
|
tilted="large"
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: "Level" })} {level.user_facing_tag}
|
{intl.formatMessage(
|
||||||
|
{ id: "Level {level}" },
|
||||||
|
{ level: level.user_facing_tag }
|
||||||
|
)}
|
||||||
</BiroScript>
|
</BiroScript>
|
||||||
<MembershipLevelIcon level={level.level_id} color="red" />
|
<MembershipLevelIcon level={level.level_id} color="red" />
|
||||||
</header>
|
</header>
|
||||||
<Title textAlign="center" level="h5">
|
<Title textAlign="center" level="h5">
|
||||||
{pointsString}
|
{pointsMsg}
|
||||||
{level.required_nights ? (
|
|
||||||
<span className={styles.redText}>
|
|
||||||
{intl.formatMessage({ id: "or" })} {level.required_nights}{" "}
|
|
||||||
{intl.formatMessage({ id: "nights" })}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</Title>
|
</Title>
|
||||||
<div className={styles.textContainer}>
|
<div className={styles.textContainer}>
|
||||||
{level.rewards.map((reward) => (
|
{level.rewards.map((reward) => (
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ export default async function MembershipNumber({
|
|||||||
return (
|
return (
|
||||||
<div className={classNames}>
|
<div className={classNames}>
|
||||||
<Caption color="pale">
|
<Caption color="pale">
|
||||||
{intl.formatMessage({ id: "Membership ID" })}
|
{intl.formatMessage({ id: "Membership ID: " })}
|
||||||
{": "}
|
|
||||||
</Caption>
|
</Caption>
|
||||||
<span className={styles.icon}>
|
<span className={styles.icon}>
|
||||||
<Caption className={styles.icon} color="pale" asChild>
|
<Caption className={styles.icon} color="pale" asChild>
|
||||||
<code>{membership?.membershipNumber ?? "N/A"}</code>
|
<code>
|
||||||
|
{membership?.membershipNumber ?? intl.formatMessage({ id: "N/A" })}
|
||||||
|
</code>
|
||||||
</Caption>
|
</Caption>
|
||||||
{membership && (
|
{membership?.membershipNumber && (
|
||||||
<CopyButton membershipNumber={membership.membershipNumber} />
|
<CopyButton membershipNumber={membership.membershipNumber} />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -24,15 +24,18 @@ export default async function Friend({
|
|||||||
}
|
}
|
||||||
const isHighestLevel = isHighestMembership(membership.membershipLevel)
|
const isHighestLevel = isHighestMembership(membership.membershipLevel)
|
||||||
|
|
||||||
|
const lvlMessageHighest = intl.formatMessage({ id: "Highest level" })
|
||||||
|
|
||||||
|
const lvlMessageLevel = intl.formatMessage(
|
||||||
|
{ id: "Level {level}" },
|
||||||
|
{ level: membershipLevels[membership.membershipLevel] }
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.friend}>
|
<section className={styles.friend}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<Body color="white" textTransform="bold" textAlign="center">
|
<Body color="white" textTransform="bold" textAlign="center">
|
||||||
{intl.formatMessage(
|
{isHighestLevel ? lvlMessageHighest : lvlMessageLevel}
|
||||||
isHighestLevel
|
|
||||||
? { id: "Highest floor" }
|
|
||||||
: { id: `Level ${membershipLevels[membership.membershipLevel]}` }
|
|
||||||
)}
|
|
||||||
</Body>
|
</Body>
|
||||||
<MembershipLevelIcon
|
<MembershipLevelIcon
|
||||||
level={MembershipLevelEnum[membership.membershipLevel]}
|
level={MembershipLevelEnum[membership.membershipLevel]}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default async function ExpiringPoints({ user }: UserProps) {
|
|||||||
<section>
|
<section>
|
||||||
<Body color="white" textTransform="bold" textAlign="center">
|
<Body color="white" textTransform="bold" textAlign="center">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "spendable points expiring by" },
|
{ id: "{points} spendable points expiring by {date}" },
|
||||||
{
|
{
|
||||||
points: intl.formatNumber(membership.pointsToExpire),
|
points: intl.formatNumber(membership.pointsToExpire),
|
||||||
date: d.format(dateFormat),
|
date: d.format(dateFormat),
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
|
||||||
|
|
||||||
import styles from "./levelSummary.module.css"
|
import styles from "./levelSummary.module.css"
|
||||||
|
|
||||||
import type { LevelSummaryProps } from "@/types/components/overviewTable"
|
import type { LevelSummaryProps } from "@/types/components/overviewTable"
|
||||||
@@ -10,17 +8,29 @@ export default function LevelSummary({
|
|||||||
level,
|
level,
|
||||||
showDescription = true,
|
showDescription = true,
|
||||||
}: LevelSummaryProps) {
|
}: LevelSummaryProps) {
|
||||||
const lang = useLang()
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
let pointsMsg: React.ReactNode = intl.formatMessage(
|
||||||
|
{ id: "{pointsAmount, number} points" },
|
||||||
|
{ pointsAmount: level.required_points }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (level.required_nights) {
|
||||||
|
pointsMsg = intl.formatMessage<React.ReactNode>(
|
||||||
|
{
|
||||||
|
id: "{pointsAmount, number} points or {nightsAmount, number} nights",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pointsAmount: level.required_points,
|
||||||
|
nightsAmount: level.required_nights,
|
||||||
|
highlight: (str) => <span className={styles.redText}>{str}</span>,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.levelSummary}>
|
<div className={styles.levelSummary}>
|
||||||
<span className={styles.levelRequirements}>
|
<span className={styles.levelRequirements}>{pointsMsg}</span>
|
||||||
{level.required_points.toLocaleString(lang)}
|
|
||||||
{"p "}
|
|
||||||
{level.required_nights
|
|
||||||
? `${intl.formatMessage({ id: "or" })} ${level.required_nights} ${intl.formatMessage({ id: "nights" })}`
|
|
||||||
: ""}
|
|
||||||
</span>
|
|
||||||
{showDescription && (
|
{showDescription && (
|
||||||
<p className={styles.levelSummaryText}>{level.description}</p>
|
<p className={styles.levelSummaryText}>{level.description}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -22,12 +22,19 @@ export default function Row({ transaction }: RowProps) {
|
|||||||
const pathName = usePathname()
|
const pathName = usePathname()
|
||||||
const isWebview = webviews.includes(pathName)
|
const isWebview = webviews.includes(pathName)
|
||||||
|
|
||||||
const nightString = `${transaction.nights} ${transaction.nights === 1 ? intl.formatMessage({ id: "night" }) : intl.formatMessage({ id: "nights" })}`
|
const nightsMsg = intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{nightsAmount, plural, one {# night} other {# nights}}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nightsAmount: transaction.nights,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
let description =
|
let description =
|
||||||
transaction.hotelName && transaction.city
|
transaction.hotelName && transaction.city
|
||||||
? `${transaction.hotelName}, ${transaction.city} ${nightString}`
|
? `${transaction.hotelName}, ${transaction.city} ${nightsMsg}`
|
||||||
: `${nightString}`
|
: `${nightsMsg}`
|
||||||
|
|
||||||
switch (transaction.type) {
|
switch (transaction.type) {
|
||||||
case Transactions.rewardType.stay:
|
case Transactions.rewardType.stay:
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ import styles from "./clientTable.module.css"
|
|||||||
|
|
||||||
import type { ClientTableProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
import type { ClientTableProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||||
|
|
||||||
const tableHeadings = [
|
|
||||||
"Points",
|
|
||||||
"Description",
|
|
||||||
"Booking number",
|
|
||||||
"Arrival date",
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function ClientTable({ transactions }: ClientTableProps) {
|
export default function ClientTable({ transactions }: ClientTableProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const tableHeadings = [
|
||||||
|
intl.formatMessage({ id: "Points" }),
|
||||||
|
intl.formatMessage({ id: "Description" }),
|
||||||
|
intl.formatMessage({ id: "Booking number" }),
|
||||||
|
intl.formatMessage({ id: "Arrival date" }),
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Table>
|
<Table>
|
||||||
@@ -28,9 +28,7 @@ export default function ClientTable({ transactions }: ClientTableProps) {
|
|||||||
<Table.TR>
|
<Table.TR>
|
||||||
{tableHeadings.map((heading) => (
|
{tableHeadings.map((heading) => (
|
||||||
<Table.TH key={heading}>
|
<Table.TH key={heading}>
|
||||||
<Body textTransform="bold">
|
<Body textTransform="bold">{heading}</Body>
|
||||||
{intl.formatMessage({ id: heading })}
|
|
||||||
</Body>
|
|
||||||
</Table.TH>
|
</Table.TH>
|
||||||
))}
|
))}
|
||||||
</Table.TR>
|
</Table.TR>
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import useLang from "@/hooks/useLang"
|
|||||||
|
|
||||||
import AwardPoints from "../../EarnAndBurn/AwardPoints"
|
import AwardPoints from "../../EarnAndBurn/AwardPoints"
|
||||||
|
|
||||||
const tableHeadings = ["Points", "Expiration Date"]
|
|
||||||
|
|
||||||
export default function ExpiringPointsTable({
|
export default function ExpiringPointsTable({
|
||||||
points,
|
points,
|
||||||
expirationDate,
|
expirationDate,
|
||||||
@@ -23,15 +21,18 @@ export default function ExpiringPointsTable({
|
|||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const expiration = dt(expirationDate).locale(lang).format("DD MMM YYYY")
|
const expiration = dt(expirationDate).locale(lang).format("DD MMM YYYY")
|
||||||
|
|
||||||
|
const tableHeadings = [
|
||||||
|
intl.formatMessage({ id: "Points" }),
|
||||||
|
intl.formatMessage({ id: "Expiration Date" }),
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<Table.THead>
|
<Table.THead>
|
||||||
<Table.TR>
|
<Table.TR>
|
||||||
{tableHeadings.map((heading) => (
|
{tableHeadings.map((heading) => (
|
||||||
<Table.TH key={heading}>
|
<Table.TH key={heading}>
|
||||||
<Body textTransform="bold">
|
<Body textTransform="bold">{heading}</Body>
|
||||||
{intl.formatMessage({ id: heading })}
|
|
||||||
</Body>
|
|
||||||
</Table.TH>
|
</Table.TH>
|
||||||
))}
|
))}
|
||||||
</Table.TR>
|
</Table.TR>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default async function NextLevelRewardsBlock({
|
|||||||
<div className={styles.textContainer}>
|
<div className={styles.textContainer}>
|
||||||
<Body color="peach50" textAlign="center">
|
<Body color="peach50" textAlign="center">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "As our" },
|
{ id: "As our {level}" },
|
||||||
{ level: nextLevelRewards.level?.name }
|
{ level: nextLevelRewards.level?.name }
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ export default function MembershipNumberBadge({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.rewardBadge}>
|
<div className={styles.rewardBadge}>
|
||||||
<Caption textAlign="center" color="uiTextHighContrast">
|
<Caption textAlign="center" color="uiTextHighContrast">
|
||||||
{intl.formatMessage({ id: "Membership ID" })}: {membershipNumber}
|
{intl.formatMessage(
|
||||||
|
{ id: "Membership ID: {id}" },
|
||||||
|
{ id: membershipNumber }
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ import type { StayCardProps } from "@/types/components/myPages/stays/stayCard"
|
|||||||
|
|
||||||
export default function StayCard({ stay }: StayCardProps) {
|
export default function StayCard({ stay }: StayCardProps) {
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
// TODO: Temporary loading. Remove when current web is deleted.
|
// TODO: Temporary loading. Remove when current web is deleted.
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@@ -55,7 +57,7 @@ export default function StayCard({ stay }: StayCardProps) {
|
|||||||
<Caption asChild>
|
<Caption asChild>
|
||||||
<time dateTime={arrivalDateTime}>{arrivalDate}</time>
|
<time dateTime={arrivalDateTime}>{arrivalDate}</time>
|
||||||
</Caption>
|
</Caption>
|
||||||
{" - "}
|
{intl.formatMessage({ id: " - " })}
|
||||||
<Caption asChild>
|
<Caption asChild>
|
||||||
<time dateTime={departDateTime}>{departDate}</time>
|
<time dateTime={departDateTime}>{departDate}</time>
|
||||||
</Caption>
|
</Caption>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default async function HotelListingItem({
|
|||||||
</div>
|
</div>
|
||||||
<Caption color="uiTextPlaceholder">
|
<Caption color="uiTextPlaceholder">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Distance in km to city centre" },
|
{ id: "{number} km to city centre" },
|
||||||
{
|
{
|
||||||
number: getSingleDecimal(
|
number: getSingleDecimal(
|
||||||
hotel.location.distanceToCentre / 1000
|
hotel.location.distanceToCentre / 1000
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useWatch } from "react-hook-form"
|
import { useWatch } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -34,13 +35,12 @@ export default function MobileToggleButton({
|
|||||||
? JSON.parse(decodeURIComponent(location))
|
? JSON.parse(decodeURIComponent(location))
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const nights = dt(d.toDate).diff(dt(d.fromDate), "days")
|
|
||||||
|
|
||||||
const selectedFromDate = dt(d.fromDate).locale(lang).format("D MMM")
|
const selectedFromDate = dt(d.fromDate).locale(lang).format("D MMM")
|
||||||
const selectedToDate = dt(d.toDate).locale(lang).format("D MMM")
|
const selectedToDate = dt(d.toDate).locale(lang).format("D MMM")
|
||||||
|
|
||||||
const locationAndDateIsSet = parsedLocation && d
|
const locationAndDateIsSet = parsedLocation && d
|
||||||
|
|
||||||
|
const totalNights = dt(d.toDate).diff(dt(d.fromDate), "days")
|
||||||
const totalRooms = rooms.length
|
const totalRooms = rooms.length
|
||||||
const totalAdults = rooms.reduce((acc, room) => {
|
const totalAdults = rooms.reduce((acc, room) => {
|
||||||
if (room.adults) {
|
if (room.adults) {
|
||||||
@@ -55,6 +55,32 @@ export default function MobileToggleButton({
|
|||||||
return acc
|
return acc
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
|
const totalNightsMsg = intl.formatMessage(
|
||||||
|
{ id: "{totalNights, plural, one {# night} other {# nights}}" },
|
||||||
|
{ totalNights }
|
||||||
|
)
|
||||||
|
|
||||||
|
const totalAdultsMsg = intl.formatMessage(
|
||||||
|
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||||
|
{ totalAdults }
|
||||||
|
)
|
||||||
|
|
||||||
|
const totalChildrenMsg = intl.formatMessage(
|
||||||
|
{ id: "{totalChildren, plural, one {# child} other {# children}}" },
|
||||||
|
{ totalChildren }
|
||||||
|
)
|
||||||
|
|
||||||
|
const totalRoomsMsg = intl.formatMessage(
|
||||||
|
{ id: "{totalRooms, plural, one {# room} other {# rooms}}" },
|
||||||
|
{ totalRooms }
|
||||||
|
)
|
||||||
|
|
||||||
|
const totalDetails = [totalAdultsMsg]
|
||||||
|
if (totalChildren > 0) {
|
||||||
|
totalDetails.push(totalChildrenMsg)
|
||||||
|
}
|
||||||
|
totalDetails.push(totalRoomsMsg)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={locationAndDateIsSet ? styles.complete : styles.partial}
|
className={locationAndDateIsSet ? styles.complete : styles.partial}
|
||||||
@@ -76,13 +102,16 @@ export default function MobileToggleButton({
|
|||||||
<Divider color="baseSurfaceSubtleNormal" variant="vertical" />
|
<Divider color="baseSurfaceSubtleNormal" variant="vertical" />
|
||||||
<div>
|
<div>
|
||||||
<Caption type="bold" color="red">
|
<Caption type="bold" color="red">
|
||||||
{intl.formatMessage(
|
{totalNightsMsg}
|
||||||
{ id: "booking.nights" },
|
|
||||||
{ totalNights: nights }
|
|
||||||
)}
|
|
||||||
</Caption>
|
</Caption>
|
||||||
<Body>
|
<Body>
|
||||||
{selectedFromDate} - {selectedToDate}
|
{intl.formatMessage(
|
||||||
|
{ id: "{selectedFromDate} - {selectedToDate}" },
|
||||||
|
{
|
||||||
|
selectedFromDate,
|
||||||
|
selectedToDate,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.icon}>
|
<div className={styles.icon}>
|
||||||
@@ -96,17 +125,17 @@ export default function MobileToggleButton({
|
|||||||
<div>
|
<div>
|
||||||
<Caption color="red">{parsedLocation?.name}</Caption>
|
<Caption color="red">{parsedLocation?.name}</Caption>
|
||||||
<Caption>
|
<Caption>
|
||||||
{`${selectedFromDate} - ${selectedToDate} (${intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "booking.nights" },
|
{
|
||||||
{ totalNights: nights }
|
id: "{selectedFromDate} - {selectedToDate} ({totalNights}) {details}",
|
||||||
)}) ${intl.formatMessage({ id: "booking.adults" }, { totalAdults })}, ${
|
},
|
||||||
totalChildren > 0
|
{
|
||||||
? intl.formatMessage(
|
selectedFromDate,
|
||||||
{ id: "booking.children" },
|
selectedToDate,
|
||||||
{ totalChildren }
|
totalNights: totalNightsMsg,
|
||||||
) + ", "
|
details: totalDetails.join(", "),
|
||||||
: ""
|
}
|
||||||
}${intl.formatMessage({ id: "booking.rooms" }, { totalRooms })}`}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.icon}>
|
<div className={styles.icon}>
|
||||||
@@ -132,7 +161,10 @@ export function MobileToggleButtonSkeleton() {
|
|||||||
<Divider color="baseSurfaceSubtleNormal" variant="vertical" />
|
<Divider color="baseSurfaceSubtleNormal" variant="vertical" />
|
||||||
<div>
|
<div>
|
||||||
<Caption type="bold" color="red">
|
<Caption type="bold" color="red">
|
||||||
{intl.formatMessage({ id: "booking.nights" }, { totalNights: 0 })}
|
{intl.formatMessage(
|
||||||
|
{ id: "{totalNights, plural, one {# night} other {# nights}}" },
|
||||||
|
{ totalNights: 0 }
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
<SkeletonShimmer height="24px" />
|
<SkeletonShimmer height="24px" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default async function IntroSection({
|
|||||||
const { streetAddress, city } = address
|
const { streetAddress, city } = address
|
||||||
const { distanceToCentre } = location
|
const { distanceToCentre } = location
|
||||||
const formattedDistanceText = intl.formatMessage(
|
const formattedDistanceText = intl.formatMessage(
|
||||||
{ id: "Distance in km to city centre" },
|
{ id: "{number} km to city centre" },
|
||||||
{ number: getSingleDecimal(distanceToCentre / 1000) }
|
{ number: getSingleDecimal(distanceToCentre / 1000) }
|
||||||
)
|
)
|
||||||
const lang = getLang()
|
const lang = getLang()
|
||||||
@@ -37,7 +37,7 @@ export default async function IntroSection({
|
|||||||
)
|
)
|
||||||
const formattedTripAdvisorText = hasTripAdvisorData
|
const formattedTripAdvisorText = hasTripAdvisorData
|
||||||
? intl.formatMessage(
|
? intl.formatMessage(
|
||||||
{ id: "Tripadvisor reviews" },
|
{ id: "{rating} ({count} reviews on Tripadvisor)" },
|
||||||
{ rating: tripAdvisor.rating, count: tripAdvisor.numberOfReviews }
|
{ rating: tripAdvisor.rating, count: tripAdvisor.numberOfReviews }
|
||||||
)
|
)
|
||||||
: ""
|
: ""
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ export default function Sidebar({
|
|||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viewAsMapMsg = intl.formatMessage({ id: "View as map" })
|
||||||
|
const viewAsListMsg = intl.formatMessage({ id: "View as list" })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<aside
|
<aside
|
||||||
@@ -98,17 +101,13 @@ export default function Sidebar({
|
|||||||
onClick={toggleFullScreenSidebar}
|
onClick={toggleFullScreenSidebar}
|
||||||
>
|
>
|
||||||
<Body textTransform="bold" color="textMediumContrast" asChild>
|
<Body textTransform="bold" color="textMediumContrast" asChild>
|
||||||
<span>
|
<span>{isFullScreenSidebar ? viewAsMapMsg : viewAsListMsg}</span>
|
||||||
{intl.formatMessage({
|
|
||||||
id: isFullScreenSidebar ? "View as map" : "View as list",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</Body>
|
</Body>
|
||||||
</Button>
|
</Button>
|
||||||
<div className={styles.sidebarContent}>
|
<div className={styles.sidebarContent}>
|
||||||
<Title as="h4" level="h2" textTransform="regular">
|
<Title as="h4" level="h2" textTransform="regular">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Things nearby HOTEL_NAME" },
|
{ id: "Things nearby {hotelName}" },
|
||||||
{ hotelName }
|
{ hotelName }
|
||||||
)}
|
)}
|
||||||
</Title>
|
</Title>
|
||||||
@@ -138,7 +137,14 @@ export default function Sidebar({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span>{poi.name}</span>
|
<span>{poi.name}</span>
|
||||||
<span>{poi.distance} km</span>
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{distanceInKm} km" },
|
||||||
|
{
|
||||||
|
distanceInKm: poi.distance,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export default function DynamicMap({
|
|||||||
className={styles.dynamicMap}
|
className={styles.dynamicMap}
|
||||||
style={{ "--hotel-map-height": mapHeight } as React.CSSProperties}
|
style={{ "--hotel-map-height": mapHeight } as React.CSSProperties}
|
||||||
aria-label={intl.formatMessage(
|
aria-label={intl.formatMessage(
|
||||||
{ id: "Things nearby HOTEL_NAME" },
|
{ id: "Things nearby {hotelName}" },
|
||||||
{ hotelName }
|
{ hotelName }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -52,7 +52,12 @@ export default function MapCard({ hotelName, pois }: MapCardProps) {
|
|||||||
size={20}
|
size={20}
|
||||||
/>
|
/>
|
||||||
<Body color="black">{poi.name}</Body>
|
<Body color="black">{poi.name}</Body>
|
||||||
<Caption>{poi.distance} km</Caption>
|
<Caption>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{distanceInKm} km" },
|
||||||
|
{ distanceInKm: poi.distance }
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ export default async function StaticMap({
|
|||||||
width={380}
|
width={380}
|
||||||
height={mapHeight}
|
height={mapHeight}
|
||||||
zoomLevel={zoomLevel}
|
zoomLevel={zoomLevel}
|
||||||
altText={intl.formatMessage({ id: "Map of HOTEL_NAME" }, { hotelName })}
|
altText={intl.formatMessage(
|
||||||
|
{ id: "Map of {hotelName}" },
|
||||||
|
{ hotelName }
|
||||||
|
)}
|
||||||
mapId={mapId}
|
mapId={mapId}
|
||||||
/>
|
/>
|
||||||
<ScandicMarker
|
<ScandicMarker
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export default function PreviewImages({
|
|||||||
<Lightbox
|
<Lightbox
|
||||||
images={images}
|
images={images}
|
||||||
dialogTitle={intl.formatMessage(
|
dialogTitle={intl.formatMessage(
|
||||||
{ id: "Image gallery" },
|
{ id: "{title} - Image gallery" },
|
||||||
{ name: hotelName }
|
{ title: hotelName }
|
||||||
)}
|
)}
|
||||||
isOpen={lightboxIsOpen}
|
isOpen={lightboxIsOpen}
|
||||||
onClose={() => setLightboxIsOpen(false)}
|
onClose={() => setLightboxIsOpen(false)}
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ export function RoomCard({ room }: RoomCardProps) {
|
|||||||
<div className={styles.imageContainer}>
|
<div className={styles.imageContainer}>
|
||||||
<ImageGallery
|
<ImageGallery
|
||||||
images={images}
|
images={images}
|
||||||
title={intl.formatMessage({ id: "Image gallery" }, { name })}
|
title={intl.formatMessage(
|
||||||
|
{ id: "{title} - Image gallery" },
|
||||||
|
{ title: name }
|
||||||
|
)}
|
||||||
height={200}
|
height={200}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +48,9 @@ export function RoomCard({ room }: RoomCardProps) {
|
|||||||
</Subtitle>
|
</Subtitle>
|
||||||
<Body color="grey">
|
<Body color="grey">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "hotelPages.rooms.roomCard.persons" },
|
{
|
||||||
|
id: "{size} ({max, plural, one {{range} person} other {{range} persons}})",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
size,
|
size,
|
||||||
max: totalOccupancy.max,
|
max: totalOccupancy.max,
|
||||||
|
|||||||
@@ -54,8 +54,12 @@ export function Rooms({ rooms }: RoomsProps) {
|
|||||||
<ShowMoreButton
|
<ShowMoreButton
|
||||||
loadMoreData={handleShowMore}
|
loadMoreData={handleShowMore}
|
||||||
showLess={allRoomsVisible}
|
showLess={allRoomsVisible}
|
||||||
textShowMore="Show more rooms"
|
textShowMore={intl.formatMessage({
|
||||||
textShowLess="Show less rooms"
|
id: "Show more rooms",
|
||||||
|
})}
|
||||||
|
textShowLess={intl.formatMessage({
|
||||||
|
id: "Show less rooms",
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</SectionContainer>
|
</SectionContainer>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default async function ContactInformation({
|
|||||||
color="peach80"
|
color="peach80"
|
||||||
textDecoration="underline"
|
textDecoration="underline"
|
||||||
>
|
>
|
||||||
Google Maps
|
{intl.formatMessage({ id: "Google Maps" })}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.contact}>
|
<div className={styles.contact}>
|
||||||
|
|||||||
@@ -18,8 +18,18 @@ export default async function CheckInAmenity({
|
|||||||
trackingId="amenities:check-in"
|
trackingId="amenities:check-in"
|
||||||
>
|
>
|
||||||
<Body textTransform="bold">{intl.formatMessage({ id: "Times" })}</Body>
|
<Body textTransform="bold">{intl.formatMessage({ id: "Times" })}</Body>
|
||||||
<Body color="uiTextHighContrast">{`${intl.formatMessage({ id: "Check in from" })}: ${checkInTime}`}</Body>
|
<Body color="uiTextHighContrast">
|
||||||
<Body color="uiTextHighContrast">{`${intl.formatMessage({ id: "Check out at latest" })}: ${checkOutTime}`}</Body>
|
{intl.formatMessage(
|
||||||
|
{ id: "Check in from: {checkInTime}" },
|
||||||
|
{ checkInTime }
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "Check out at latest: {checkOutTime}" },
|
||||||
|
{ checkOutTime }
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,22 +13,34 @@ export default async function ParkingList({
|
|||||||
address,
|
address,
|
||||||
}: ParkingListProps) {
|
}: ParkingListProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
|
|
||||||
|
const canMakeReservationYesMsg = intl.formatMessage({
|
||||||
|
id: "Parking can be reserved in advance: Yes",
|
||||||
|
})
|
||||||
|
const canMakeReservationNoMsg = intl.formatMessage({
|
||||||
|
id: "Parking can be reserved in advance: No",
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Body color="uiTextHighContrast" asChild>
|
<Body color="uiTextHighContrast" asChild>
|
||||||
<ul className={styles.listStyling}>
|
<ul className={styles.listStyling}>
|
||||||
{numberOfChargingSpaces ? (
|
{numberOfChargingSpaces ? (
|
||||||
<li>
|
<li>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Number of charging points for electric cars" },
|
{ id: "Number of charging points for electric cars: {number}" },
|
||||||
{ number: numberOfChargingSpaces }
|
{ number: numberOfChargingSpaces }
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
<li>{`${intl.formatMessage({ id: "Parking can be reserved in advance" })}: ${canMakeReservation ? intl.formatMessage({ id: "Yes" }) : intl.formatMessage({ id: "No" })}`}</li>
|
<li>
|
||||||
|
{canMakeReservation
|
||||||
|
? canMakeReservationYesMsg
|
||||||
|
: canMakeReservationNoMsg}
|
||||||
|
</li>
|
||||||
{numberOfParkingSpots ? (
|
{numberOfParkingSpots ? (
|
||||||
<li>
|
<li>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Number of parking spots" },
|
{ id: "Number of parking spots: {number}" },
|
||||||
{ number: numberOfParkingSpots }
|
{ number: numberOfParkingSpots }
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
@@ -36,13 +48,15 @@ export default async function ParkingList({
|
|||||||
{distanceToHotel ? (
|
{distanceToHotel ? (
|
||||||
<li>
|
<li>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Distance to hotel" },
|
{ id: "Distance to hotel: {distanceInM} m" },
|
||||||
{ distance: distanceToHotel }
|
{ distanceInM: distanceToHotel }
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
{address ? (
|
{address ? (
|
||||||
<li>{`${intl.formatMessage({ id: "Address" })}: ${address}`}</li>
|
<li>
|
||||||
|
{intl.formatMessage({ id: "Address: {address}" }, { address })}
|
||||||
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
</ul>
|
</ul>
|
||||||
</Body>
|
</Body>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default async function ParkingPrices({
|
|||||||
? freeParking
|
? freeParking
|
||||||
? intl.formatMessage({ id: "Free parking" })
|
? intl.formatMessage({ id: "Free parking" })
|
||||||
: formatPrice(intl, parking.amount, currency)
|
: formatPrice(intl, parking.amount, currency)
|
||||||
: "N/A"}
|
: intl.formatMessage({ id: "N/A" })}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
{parking.startTime &&
|
{parking.startTime &&
|
||||||
@@ -58,7 +58,13 @@ export default async function ParkingPrices({
|
|||||||
{intl.formatMessage({ id: "From" })}
|
{intl.formatMessage({ id: "From" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{parking.startTime}-{parking.endTime}
|
{intl.formatMessage(
|
||||||
|
{ id: "{parkingStartTime}-{parkingEndTime}" },
|
||||||
|
{
|
||||||
|
parkingStartTime: parking.startTime,
|
||||||
|
parkingEndTime: parking.endTime,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -29,14 +29,14 @@ export async function getRoomText(roomSizes: number[]) {
|
|||||||
let roomText
|
let roomText
|
||||||
if (largestRoom === smallestRoom) {
|
if (largestRoom === smallestRoom) {
|
||||||
roomText = intl.formatMessage(
|
roomText = intl.formatMessage(
|
||||||
{ id: "{number} square meters" },
|
{ id: "{value} square meters" },
|
||||||
{ number: largestRoom }
|
{ value: largestRoom }
|
||||||
)
|
)
|
||||||
} else if (smallestRoom != null && largestRoom) {
|
} else if (smallestRoom != null && largestRoom) {
|
||||||
{
|
{
|
||||||
roomText = intl.formatMessage(
|
roomText = intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "{number} to {number} square meters",
|
id: "{smallest} to {largest} square meters",
|
||||||
},
|
},
|
||||||
{ largest: largestRoom, smallest: smallestRoom }
|
{ largest: largestRoom, smallest: smallestRoom }
|
||||||
)
|
)
|
||||||
@@ -53,14 +53,14 @@ export async function getSeatingText(roomSeating: number[]) {
|
|||||||
let seatingText
|
let seatingText
|
||||||
if (biggestSeating === smallestSeating) {
|
if (biggestSeating === smallestSeating) {
|
||||||
seatingText = intl.formatMessage(
|
seatingText = intl.formatMessage(
|
||||||
{ id: "{number} persons" },
|
{ id: "{value} persons" },
|
||||||
{ number: biggestSeating }
|
{ number: biggestSeating }
|
||||||
)
|
)
|
||||||
} else if (smallestSeating != null && biggestSeating) {
|
} else if (smallestSeating != null && biggestSeating) {
|
||||||
{
|
{
|
||||||
seatingText = intl.formatMessage(
|
seatingText = intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "{number} to {number} persons",
|
id: "{lowest} to {highest} persons",
|
||||||
},
|
},
|
||||||
{ highest: biggestSeating, lowest: smallestSeating }
|
{ highest: biggestSeating, lowest: smallestSeating }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import { getIntl } from "@/i18n"
|
|||||||
import styles from "./openingHours.module.css"
|
import styles from "./openingHours.module.css"
|
||||||
|
|
||||||
import type { OpeningHoursProps } from "@/types/components/hotelPage/sidepeek/openingHours"
|
import type { OpeningHoursProps } from "@/types/components/hotelPage/sidepeek/openingHours"
|
||||||
import { DaysEnum } from "@/types/components/hotelPage/sidepeek/restaurantBar"
|
|
||||||
import type { RestaurantOpeningHoursDay } from "@/types/hotel"
|
|
||||||
|
|
||||||
export default async function OpeningHours({
|
export default async function OpeningHours({
|
||||||
openingHours,
|
openingHours,
|
||||||
@@ -16,45 +14,83 @@ export default async function OpeningHours({
|
|||||||
}: OpeningHoursProps) {
|
}: OpeningHoursProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
|
|
||||||
const closed = intl.formatMessage({ id: "Closed" })
|
const closedMsg = intl.formatMessage({ id: "Closed" })
|
||||||
const alwaysOpen = intl.formatMessage({ id: "Always open" })
|
const alwaysOpenMsg = intl.formatMessage({ id: "Always open" })
|
||||||
|
|
||||||
const days: (keyof typeof openingHours)[] = [
|
// In order
|
||||||
DaysEnum.Monday,
|
const weekdayDefinitions = [
|
||||||
DaysEnum.Tuesday,
|
{
|
||||||
DaysEnum.Wednesday,
|
key: "monday",
|
||||||
DaysEnum.Thursday,
|
label: intl.formatMessage({ id: "monday" }),
|
||||||
DaysEnum.Friday,
|
},
|
||||||
DaysEnum.Saturday,
|
{
|
||||||
DaysEnum.Sunday,
|
key: "tuesday",
|
||||||
]
|
label: intl.formatMessage({ id: "tuesday" }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "wednesday",
|
||||||
|
label: intl.formatMessage({ id: "wednesday" }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "thursday",
|
||||||
|
label: intl.formatMessage({ id: "thursday" }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "friday",
|
||||||
|
label: intl.formatMessage({ id: "friday" }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "saturday",
|
||||||
|
label: intl.formatMessage({ id: "saturday" }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "sunday",
|
||||||
|
label: intl.formatMessage({ id: "sunday" }),
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
const groupedOpeningHours: { [key: string]: string[] } = {}
|
const groupedOpeningHours: string[] = []
|
||||||
|
|
||||||
days.forEach((day) => {
|
let rangeWeekdays: string[] = []
|
||||||
const today = openingHours[day] as RestaurantOpeningHoursDay
|
let rangeValue = ""
|
||||||
let key: string
|
for (let i = 0, n = weekdayDefinitions.length; i < n; ++i) {
|
||||||
|
const weekdayDefinition = weekdayDefinitions[i]
|
||||||
|
const weekday = openingHours[weekdayDefinition.key]
|
||||||
|
const label = weekdayDefinition.label
|
||||||
|
if (weekday) {
|
||||||
|
let newValue = null
|
||||||
|
|
||||||
if (today.isClosed) {
|
if (weekday.alwaysOpen) {
|
||||||
key = closed
|
newValue = alwaysOpenMsg
|
||||||
} else if (today.alwaysOpen) {
|
} else if (weekday.isClosed) {
|
||||||
key = alwaysOpen
|
newValue = closedMsg
|
||||||
} else {
|
} else if (weekday.openingTime && weekday.closingTime) {
|
||||||
key = `${today.openingTime}-${today.closingTime}`
|
newValue = `${weekday.openingTime}-${weekday.closingTime}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue !== null) {
|
||||||
|
if (rangeValue === newValue) {
|
||||||
|
if (rangeWeekdays.length > 1) {
|
||||||
|
rangeWeekdays.splice(-1, 1, label) // Replace last element
|
||||||
|
} else {
|
||||||
|
rangeWeekdays.push(label)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rangeValue) {
|
||||||
|
groupedOpeningHours.push(
|
||||||
|
`${rangeWeekdays.join("-")}: ${rangeValue}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rangeValue = newValue
|
||||||
|
rangeWeekdays = [label]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeValue && i === n - 1) {
|
||||||
|
// Flush everything at the end
|
||||||
|
groupedOpeningHours.push(`${rangeWeekdays.join("-")}: ${rangeValue}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!groupedOpeningHours[key]) {
|
|
||||||
groupedOpeningHours[key] = []
|
|
||||||
}
|
|
||||||
const formattedDay = day.charAt(0).toUpperCase() + day.slice(1)
|
|
||||||
groupedOpeningHours[key].push(intl.formatMessage({ id: formattedDay }))
|
|
||||||
})
|
|
||||||
|
|
||||||
function formatDayInterval(days: string[]) {
|
|
||||||
if (days.length === 1) {
|
|
||||||
return days[0]
|
|
||||||
}
|
|
||||||
return `${days[0]}-${days[days.length - 1]}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -62,10 +98,10 @@ export default async function OpeningHours({
|
|||||||
<Body textTransform="bold" asChild>
|
<Body textTransform="bold" asChild>
|
||||||
<h5>{heading ?? openingHours.name}</h5>
|
<h5>{heading ?? openingHours.name}</h5>
|
||||||
</Body>
|
</Body>
|
||||||
{Object.entries(groupedOpeningHours).map(([time, days]) => {
|
{groupedOpeningHours.map((groupedOpeningHour) => {
|
||||||
return (
|
return (
|
||||||
<Body color="uiTextHighContrast" key={time}>
|
<Body color="uiTextHighContrast" key={groupedOpeningHour}>
|
||||||
{`${formatDayInterval(days)}: ${time}`}
|
{groupedOpeningHour}
|
||||||
</Body>
|
</Body>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export default async function RestaurantBarItem({
|
|||||||
) : null}
|
) : null}
|
||||||
{ctaUrl ? (
|
{ctaUrl ? (
|
||||||
<ButtonLink fullWidth theme="base" intent="secondary" href={ctaUrl}>
|
<ButtonLink fullWidth theme="base" intent="secondary" href={ctaUrl}>
|
||||||
{`${intl.formatMessage({ id: "Discover" })} ${name}`}
|
{intl.formatMessage({ id: "Discover {name}" }, { name })}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,13 +28,27 @@ export default async function RoomSidePeek({ room }: RoomSidePeekProps) {
|
|||||||
<div className={styles.innerContent}>
|
<div className={styles.innerContent}>
|
||||||
<Body color="baseTextMediumContrast">
|
<Body color="baseTextMediumContrast">
|
||||||
{roomSize.min === roomSize.max
|
{roomSize.min === roomSize.max
|
||||||
? roomSize.min
|
? intl.formatMessage(
|
||||||
: `${roomSize.min} - ${roomSize.max}`}
|
{
|
||||||
m².{" "}
|
id: "{roomSize} m². Accommodates up to {max, plural, one {{range} person} other {{range} people}}",
|
||||||
{intl.formatMessage(
|
},
|
||||||
{ id: "booking.accommodatesUpTo" },
|
{
|
||||||
{ range: totalOccupancy.range, max: totalOccupancy.max }
|
roomSize: roomSize.min,
|
||||||
)}
|
max: totalOccupancy.max,
|
||||||
|
range: totalOccupancy.range,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{roomSizeMin} - {roomSizeMax} m². Accommodates up to {max, plural, one {{range} person} other {{range} people}}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roomSizeMin: roomSize.min,
|
||||||
|
roomSizeMax: roomSize.max,
|
||||||
|
max: totalOccupancy.max,
|
||||||
|
range: totalOccupancy.range,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<div className={styles.imageContainer}>
|
<div className={styles.imageContainer}>
|
||||||
<ImageGallery images={images} title={room.name} height={280} />
|
<ImageGallery images={images} title={room.name} height={280} />
|
||||||
@@ -44,9 +58,7 @@ export default async function RoomSidePeek({ room }: RoomSidePeekProps) {
|
|||||||
|
|
||||||
<div className={styles.innerContent}>
|
<div className={styles.innerContent}>
|
||||||
<Subtitle type="two" color="uiTextHighContrast" asChild>
|
<Subtitle type="two" color="uiTextHighContrast" asChild>
|
||||||
<h3>
|
<h3>{intl.formatMessage({ id: "This room is equipped with" })}</h3>
|
||||||
{intl.formatMessage({ id: "booking.thisRoomIsEquippedWith" })}
|
|
||||||
</h3>
|
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<ul className={styles.facilityList}>
|
<ul className={styles.facilityList}>
|
||||||
{room.roomFacilities
|
{room.roomFacilities
|
||||||
@@ -77,10 +89,10 @@ export default async function RoomSidePeek({ room }: RoomSidePeekProps) {
|
|||||||
|
|
||||||
<div className={styles.innerContent}>
|
<div className={styles.innerContent}>
|
||||||
<Subtitle type="two" color="uiTextHighContrast" asChild>
|
<Subtitle type="two" color="uiTextHighContrast" asChild>
|
||||||
<h3>{intl.formatMessage({ id: "booking.bedOptions" })}</h3>
|
<h3>{intl.formatMessage({ id: "Bed options" })}</h3>
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<Body color="grey">
|
<Body color="grey">
|
||||||
{intl.formatMessage({ id: "booking.basedOnAvailability" })}
|
{intl.formatMessage({ id: "Based on availability" })}
|
||||||
</Body>
|
</Body>
|
||||||
<ul className={styles.bedOptions}>
|
<ul className={styles.bedOptions}>
|
||||||
{room.roomTypes.map((roomType) => {
|
{room.roomTypes.map((roomType) => {
|
||||||
@@ -107,7 +119,7 @@ export default async function RoomSidePeek({ room }: RoomSidePeekProps) {
|
|||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
<Button fullWidth theme="base" intent="primary" asChild>
|
<Button fullWidth theme="base" intent="primary" asChild>
|
||||||
<Link href={ctaUrl}>
|
<Link href={ctaUrl}>
|
||||||
{intl.formatMessage({ id: "booking.selectRoom" })}
|
{intl.formatMessage({ id: "Select room" })}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default async function Facility({ data }: FacilityProps) {
|
|||||||
)}
|
)}
|
||||||
<div className={styles.information}>
|
<div className={styles.information}>
|
||||||
<Subtitle color="burgundy" asChild type="one">
|
<Subtitle color="burgundy" asChild type="one">
|
||||||
<Title level="h3">{intl.formatMessage({ id: `${data.type}` })}</Title>
|
<Title level="h3">{intl.formatMessage({ id: data.type })}</Title>
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<div>
|
<div>
|
||||||
<Subtitle type="two" color="uiTextHighContrast">
|
<Subtitle type="two" color="uiTextHighContrast">
|
||||||
@@ -36,13 +36,25 @@ export default async function Facility({ data }: FacilityProps) {
|
|||||||
<div className={styles.openingHours}>
|
<div className={styles.openingHours}>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{ordinaryOpeningTimes.alwaysOpen
|
{ordinaryOpeningTimes.alwaysOpen
|
||||||
? `${intl.formatMessage({ id: "Mon-Fri" })} ${intl.formatMessage({ id: "Always open" })}`
|
? intl.formatMessage({ id: "Mon-Fri Always open" })
|
||||||
: `${intl.formatMessage({ id: "Mon-Fri" })} ${ordinaryOpeningTimes.openingTime}-${ordinaryOpeningTimes.closingTime}`}
|
: intl.formatMessage(
|
||||||
|
{ id: "Mon-Fri {openingTime}-${closingTime}" },
|
||||||
|
{
|
||||||
|
openingTime: ordinaryOpeningTimes.openingTime,
|
||||||
|
closingTime: ordinaryOpeningTimes.closingTime,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{weekendOpeningTimes.alwaysOpen
|
{weekendOpeningTimes.alwaysOpen
|
||||||
? `${intl.formatMessage({ id: "Sat-Sun" })} ${intl.formatMessage({ id: "Always open" })}`
|
? intl.formatMessage({ id: "Sat-Sun Always open" })
|
||||||
: `${intl.formatMessage({ id: "Sat-Sun" })} ${weekendOpeningTimes.openingTime}-${weekendOpeningTimes.closingTime}`}
|
: intl.formatMessage(
|
||||||
|
{ id: "Sat-Sun {openingTime}-${closingTime}" },
|
||||||
|
{
|
||||||
|
openingTime: weekendOpeningTimes.openingTime,
|
||||||
|
closingTime: weekendOpeningTimes.closingTime,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ export default function TabNavigation({
|
|||||||
hash: HotelHashValues.overview,
|
hash: HotelHashValues.overview,
|
||||||
text: intl.formatMessage({ id: "Overview" }),
|
text: intl.formatMessage({ id: "Overview" }),
|
||||||
},
|
},
|
||||||
{ hash: HotelHashValues.rooms, text: intl.formatMessage({ id: "Rooms" }) },
|
{
|
||||||
|
hash: HotelHashValues.rooms,
|
||||||
|
text: intl.formatMessage({ id: "Rooms" }),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
hash: HotelHashValues.restaurant,
|
hash: HotelHashValues.restaurant,
|
||||||
text: intl.formatMessage({ id: restaurantTitle }, { count: 1 }),
|
text: intl.formatMessage({ id: restaurantTitle }, { count: 1 }),
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { da, de, fi, nb, sv } from "date-fns/locale"
|
import { da, de, fi, nb, sv } from "date-fns/locale"
|
||||||
import { useCallback, useEffect, useRef, useState } from "react"
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
import { useFormContext, useWatch } from "react-hook-form"
|
import { useFormContext, useWatch } from "react-hook-form"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
@@ -26,6 +28,8 @@ const locales = {
|
|||||||
|
|
||||||
export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
|
export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const selectedDate = useWatch({ name })
|
const selectedDate = useWatch({ name })
|
||||||
const { register, setValue } = useFormContext()
|
const { register, setValue } = useFormContext()
|
||||||
@@ -131,7 +135,13 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
|
|||||||
>
|
>
|
||||||
<Body className={styles.body} asChild color="uiTextHighContrast">
|
<Body className={styles.body} asChild color="uiTextHighContrast">
|
||||||
<span>
|
<span>
|
||||||
{selectedFromDate} - {selectedToDate}
|
{intl.formatMessage(
|
||||||
|
{ id: "{selectedFromDate} - {selectedToDate}" },
|
||||||
|
{
|
||||||
|
selectedFromDate,
|
||||||
|
selectedToDate,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -40,8 +40,10 @@ export default async function FooterDetails() {
|
|||||||
<div className={styles.bottomContainer}>
|
<div className={styles.bottomContainer}>
|
||||||
<div className={styles.copyrightContainer}>
|
<div className={styles.copyrightContainer}>
|
||||||
<Footnote type="label" textTransform="uppercase">
|
<Footnote type="label" textTransform="uppercase">
|
||||||
© {currentYear}{" "}
|
{intl.formatMessage(
|
||||||
{intl.formatMessage({ id: "Copyright all rights reserved" })}
|
{ id: "© {currentYear} Scandic AB All rights reserved" },
|
||||||
|
{ currentYear }
|
||||||
|
)}
|
||||||
</Footnote>
|
</Footnote>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.navigationContainer}>
|
<div className={styles.navigationContainer}>
|
||||||
@@ -99,8 +101,10 @@ export async function FooterDetailsSkeleton() {
|
|||||||
<div className={styles.bottomContainer}>
|
<div className={styles.bottomContainer}>
|
||||||
<div className={styles.copyrightContainer}>
|
<div className={styles.copyrightContainer}>
|
||||||
<Footnote type="label" textTransform="uppercase">
|
<Footnote type="label" textTransform="uppercase">
|
||||||
© {currentYear}{" "}
|
{intl.formatMessage(
|
||||||
{intl.formatMessage({ id: "Copyright all rights reserved" })}
|
{ id: "© {currentYear} Scandic AB All rights reserved" },
|
||||||
|
{ currentYear }
|
||||||
|
)}
|
||||||
</Footnote>
|
</Footnote>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.navigationContainer}>
|
<div className={styles.navigationContainer}>
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ export default function Voucher() {
|
|||||||
const bonus = intl.formatMessage({ id: "Use bonus cheque" })
|
const bonus = intl.formatMessage({ id: "Use bonus cheque" })
|
||||||
const reward = intl.formatMessage({ id: "Book reward night" })
|
const reward = intl.formatMessage({ id: "Book reward night" })
|
||||||
const disabledBookingOptionsHeader = intl.formatMessage({
|
const disabledBookingOptionsHeader = intl.formatMessage({
|
||||||
id: "Disabled booking options header",
|
id: "We're sorry",
|
||||||
})
|
})
|
||||||
const disabledBookingOptionsText = intl.formatMessage({
|
const disabledBookingOptionsText = intl.formatMessage({
|
||||||
id: "Disabled booking options text",
|
id: "Codes, cheques and reward nights aren't available on the new website yet.",
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export default function FormContent({
|
|||||||
<Caption color="red" type="bold">
|
<Caption color="red" type="bold">
|
||||||
{nights > 0
|
{nights > 0
|
||||||
? intl.formatMessage(
|
? intl.formatMessage(
|
||||||
{ id: "booking.nights" },
|
{
|
||||||
|
id: "{totalNights, plural, one {# night} other {# nights}}",
|
||||||
|
},
|
||||||
{ totalNights: nights }
|
{ totalNights: nights }
|
||||||
)
|
)
|
||||||
: intl.formatMessage({
|
: intl.formatMessage({
|
||||||
@@ -102,7 +104,10 @@ export function BookingWidgetFormContentSkeleton() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.when}>
|
<div className={styles.when}>
|
||||||
<Caption color="red" type="bold">
|
<Caption color="red" type="bold">
|
||||||
{intl.formatMessage({ id: "booking.nights" }, { totalNights: 0 })}
|
{intl.formatMessage(
|
||||||
|
{ id: "{totalNights, plural, one {# night} other {# nights}}" },
|
||||||
|
{ totalNights: 0 }
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
<SkeletonShimmer width={"100%"} />
|
<SkeletonShimmer width={"100%"} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -164,9 +164,11 @@ export default function SignupForm({ link, subtitle, title }: SignUpFormProps) {
|
|||||||
{/* TODO: Update copy once ready */}
|
{/* TODO: Update copy once ready */}
|
||||||
<Body>
|
<Body>
|
||||||
{intl.formatMessage<React.ReactNode>(
|
{intl.formatMessage<React.ReactNode>(
|
||||||
{ id: "signupPage.terms" },
|
|
||||||
{
|
{
|
||||||
termsAndConditions: (str) => (
|
id: "By accepting the <termsAndConditionsLink>Terms and Conditions for Scandic Friends</termsAndConditionsLink> I understand that my personal data will be processed in accordance with <privacyPolicy>Scandic's Privacy Policy</privacyPolicy>.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
termsAndConditionsLink: (str) => (
|
||||||
<Link
|
<Link
|
||||||
variant="underscored"
|
variant="underscored"
|
||||||
color="peach80"
|
color="peach80"
|
||||||
|
|||||||
@@ -33,13 +33,13 @@ export default function GuestsRoomsPickerDialog({
|
|||||||
const { getFieldState, trigger, setValue } =
|
const { getFieldState, trigger, setValue } =
|
||||||
useFormContext<BookingWidgetSchema>()
|
useFormContext<BookingWidgetSchema>()
|
||||||
const roomsValue = useWatch<BookingWidgetSchema, "rooms">({ name: "rooms" })
|
const roomsValue = useWatch<BookingWidgetSchema, "rooms">({ name: "rooms" })
|
||||||
const addRoomLabel = intl.formatMessage({ id: "Add Room" })
|
const addRoomLabel = intl.formatMessage({ id: "Add room" })
|
||||||
const doneLabel = intl.formatMessage({ id: "Done" })
|
const doneLabel = intl.formatMessage({ id: "Done" })
|
||||||
const disabledBookingOptionsHeader = intl.formatMessage({
|
const disabledBookingOptionsHeader = intl.formatMessage({
|
||||||
id: "Disabled booking options header",
|
id: "We're sorry",
|
||||||
})
|
})
|
||||||
const disabledBookingOptionsText = intl.formatMessage({
|
const disabledBookingOptionsText = intl.formatMessage({
|
||||||
id: "Disabled adding room",
|
id: "Adding room is not available on the new website yet.",
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClose = useCallback(async () => {
|
const handleClose = useCallback(async () => {
|
||||||
|
|||||||
@@ -137,6 +137,38 @@ function Trigger({
|
|||||||
}) {
|
}) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const parts = []
|
||||||
|
|
||||||
|
parts.push(
|
||||||
|
intl.formatMessage(
|
||||||
|
{ id: "{totalRooms, plural, one {# room} other {# rooms}}" },
|
||||||
|
{ totalRooms: rooms.length }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
parts.push(
|
||||||
|
intl.formatMessage(
|
||||||
|
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||||
|
{ totalAdults: rooms.reduce((acc, room) => acc + room.adults, 0) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (rooms.some((room) => room.child.length > 0)) {
|
||||||
|
parts.push(
|
||||||
|
intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{totalChildren, plural, one {# child} other {# children}}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
totalChildren: rooms.reduce(
|
||||||
|
(acc, room) => acc + room.child.length,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={`${className} ${styles.btn}`}
|
className={`${className} ${styles.btn}`}
|
||||||
@@ -144,29 +176,7 @@ function Trigger({
|
|||||||
onPress={triggerFn}
|
onPress={triggerFn}
|
||||||
>
|
>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
<span>
|
<span>{parts.join(", ")}</span>
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "booking.rooms" },
|
|
||||||
{ totalRooms: rooms.length }
|
|
||||||
)}
|
|
||||||
{", "}
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "booking.adults" },
|
|
||||||
{ totalAdults: rooms.reduce((acc, room) => acc + room.adults, 0) }
|
|
||||||
)}
|
|
||||||
{rooms.some((room) => room.child.length > 0)
|
|
||||||
? ", " +
|
|
||||||
intl.formatMessage(
|
|
||||||
{ id: "booking.children" },
|
|
||||||
{
|
|
||||||
totalChildren: rooms.reduce(
|
|
||||||
(acc, room) => acc + room.child.length,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
</span>
|
|
||||||
</Body>
|
</Body>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -60,14 +60,19 @@ export default function MobileMenu({
|
|||||||
}
|
}
|
||||||
}, [isHamburgerMenuOpen])
|
}, [isHamburgerMenuOpen])
|
||||||
|
|
||||||
|
const closeMsg = intl.formatMessage({
|
||||||
|
id: "Close menu",
|
||||||
|
})
|
||||||
|
const openMsg = intl.formatMessage({
|
||||||
|
id: "Open menu",
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`${styles.hamburger} ${isHamburgerExtended ? styles.isExpanded : ""}`}
|
className={`${styles.hamburger} ${isHamburgerExtended ? styles.isExpanded : ""}`}
|
||||||
aria-label={intl.formatMessage({
|
aria-label={isHamburgerExtended ? closeMsg : openMsg}
|
||||||
id: isHamburgerExtended ? "Close menu" : "Open menu",
|
|
||||||
})}
|
|
||||||
onClick={() => toggleDropdown(DropdownTypeEnum.HamburgerMenu)}
|
onClick={() => toggleDropdown(DropdownTypeEnum.HamburgerMenu)}
|
||||||
>
|
>
|
||||||
<span className={styles.bar} />
|
<span className={styles.bar} />
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ export default function MyPagesMenu({
|
|||||||
<Avatar initials={getInitials(user.firstName, user.lastName)} />
|
<Avatar initials={getInitials(user.firstName, user.lastName)} />
|
||||||
<Body textTransform="bold" color="baseTextHighContrast" asChild>
|
<Body textTransform="bold" color="baseTextHighContrast" asChild>
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage({ id: "Hi" })} {user.firstName}!
|
{intl.formatMessage(
|
||||||
|
{ id: "Hi {firstName}!" },
|
||||||
|
{ firstName: user.firstName }
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
<ChevronDownSmallIcon
|
<ChevronDownSmallIcon
|
||||||
|
|||||||
@@ -40,13 +40,19 @@ export default function MyPagesMenuContent({
|
|||||||
<nav className={styles.myPagesMenuContent} ref={myPagesMenuContentRef}>
|
<nav className={styles.myPagesMenuContent} ref={myPagesMenuContentRef}>
|
||||||
<div className={introClassName}>
|
<div className={introClassName}>
|
||||||
<Subtitle type="two" className={styles.userName}>
|
<Subtitle type="two" className={styles.userName}>
|
||||||
{intl.formatMessage({ id: "Hi" })} {user.firstName}!
|
{intl.formatMessage(
|
||||||
|
{ id: "Hi {firstName}!" },
|
||||||
|
{ firstName: user.firstName }
|
||||||
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
{membershipLevel && membershipPoints ? (
|
{membershipLevel && membershipPoints ? (
|
||||||
<Caption className={styles.friendTypeWrapper}>
|
<Caption className={styles.friendTypeWrapper}>
|
||||||
<span className={styles.friendType}>{membershipLevel.name}</span>
|
<span className={styles.friendType}>{membershipLevel.name}</span>
|
||||||
<span>
|
<span>
|
||||||
{membershipPoints} {intl.formatMessage({ id: "points" })}
|
{intl.formatMessage(
|
||||||
|
{ id: "{pointsAmount, number} points" },
|
||||||
|
{ pointsAmount: membershipPoints }
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Caption>
|
</Caption>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ export default function Confirmation({
|
|||||||
<Alert
|
<Alert
|
||||||
type={AlertTypeEnum.Info}
|
type={AlertTypeEnum.Info}
|
||||||
heading={intl.formatMessage({
|
heading={intl.formatMessage({
|
||||||
id: "booking.confirmation.membershipInfo.heading",
|
id: "Failed to verify membership",
|
||||||
})}
|
})}
|
||||||
text={intl.formatMessage({
|
text={intl.formatMessage({
|
||||||
id: "booking.confirmation.membershipInfo.text",
|
id: "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.",
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ export default function Header({
|
|||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const text = intl.formatMessage<React.ReactNode>(
|
const text = intl.formatMessage<React.ReactNode>(
|
||||||
{ id: "booking.confirmation.text" },
|
{
|
||||||
|
id: "Thank you for booking with us! We look forward to welcoming you and hope you have a pleasant stay. If you have any questions or need to make changes to your reservation, please <emailLink>contact us.</emailLink>",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
emailLink: (str) => (
|
emailLink: (str) => (
|
||||||
<Link color="burgundy" href="#" textDecoration="underline">
|
<Link color="burgundy" href="#" textDecoration="underline">
|
||||||
@@ -57,7 +59,7 @@ export default function Header({
|
|||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<hgroup className={styles.hgroup}>
|
<hgroup className={styles.hgroup}>
|
||||||
<Title as="h2" color="red" textTransform="uppercase" type="h2">
|
<Title as="h2" color="red" textTransform="uppercase" type="h2">
|
||||||
{intl.formatMessage({ id: "booking.confirmation.title" })}
|
{intl.formatMessage({ id: "Booking confirmation" })}
|
||||||
</Title>
|
</Title>
|
||||||
<Title as="h2" color="burgundy" textTransform="uppercase" type="h1">
|
<Title as="h2" color="burgundy" textTransform="uppercase" type="h1">
|
||||||
{hotel.name}
|
{hotel.name}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
@@ -23,8 +24,14 @@ export default function HotelDetails({
|
|||||||
<div className={styles.hotel}>
|
<div className={styles.hotel}>
|
||||||
<Body color="uiTextHighContrast">{hotel.name}</Body>
|
<Body color="uiTextHighContrast">{hotel.name}</Body>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{hotel.address.streetAddress}, {hotel.address.zipCode}{" "}
|
{intl.formatMessage(
|
||||||
{hotel.address.city}
|
{ id: "{streetAddress}, {zipCode} {city}" },
|
||||||
|
{
|
||||||
|
streetAddress: hotel.address.streetAddress,
|
||||||
|
zipCode: hotel.address.zipCode,
|
||||||
|
city: hotel.address.city,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<Body asChild color="uiTextHighContrast">
|
<Body asChild color="uiTextHighContrast">
|
||||||
<Link
|
<Link
|
||||||
@@ -64,7 +71,7 @@ export default function HotelDetails({
|
|||||||
<div className={styles.toast}>
|
<div className={styles.toast}>
|
||||||
<Toast variant="info">
|
<Toast variant="info">
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
<li>N/A</li>
|
<li>{intl.formatMessage({ id: "N/A" })}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Toast>
|
</Toast>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,8 +26,16 @@ export default function PaymentDetails({
|
|||||||
</Subtitle>
|
</Subtitle>
|
||||||
<div className={styles.payment}>
|
<div className={styles.payment}>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{formatPrice(intl, booking.totalPrice, booking.currencyCode)}{" "}
|
{intl.formatMessage(
|
||||||
{intl.formatMessage({ id: "has been paid" })}
|
{ id: "{amount} has been paid" },
|
||||||
|
{
|
||||||
|
amount: formatPrice(
|
||||||
|
intl,
|
||||||
|
booking.totalPrice,
|
||||||
|
booking.currencyCode
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{dt(booking.createDateTime)
|
{dt(booking.createDateTime)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ export default function Receipt({
|
|||||||
{booking.rateDefinition.isMemberRate ? (
|
{booking.rateDefinition.isMemberRate ? (
|
||||||
<div className={styles.memberPrice}>
|
<div className={styles.memberPrice}>
|
||||||
<Body color="uiTextPlaceholder">
|
<Body color="uiTextPlaceholder">
|
||||||
<s>N/A</s>
|
<s>{intl.formatMessage({ id: "N/A" })}</s>
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="red">
|
<Body color="red">
|
||||||
{formatPrice(intl, booking.roomPrice, booking.currencyCode)}
|
{formatPrice(intl, booking.roomPrice, booking.currencyCode)}
|
||||||
@@ -60,7 +61,7 @@ export default function Receipt({
|
|||||||
)}
|
)}
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "booking.adults" },
|
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||||
{
|
{
|
||||||
totalAdults: booking.adults,
|
totalAdults: booking.adults,
|
||||||
}
|
}
|
||||||
@@ -156,7 +157,12 @@ export default function Receipt({
|
|||||||
<ChevronRightSmallIcon />
|
<ChevronRightSmallIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage({ id: "Approx." })} N/A EUR
|
{intl.formatMessage(
|
||||||
|
{ id: "Approx. {value}" },
|
||||||
|
{
|
||||||
|
value: "N/A EUR",
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
@@ -23,6 +24,7 @@ export default function Room({ booking, img, roomName }: RoomProps) {
|
|||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
|
const guestName = `${booking.guest.firstName} ${booking.guest.lastName}`
|
||||||
const fromDate = dt(booking.checkInDate).locale(lang)
|
const fromDate = dt(booking.checkInDate).locale(lang)
|
||||||
const toDate = dt(booking.checkOutDate).locale(lang)
|
const toDate = dt(booking.checkOutDate).locale(lang)
|
||||||
return (
|
return (
|
||||||
@@ -33,7 +35,12 @@ export default function Room({ booking, img, roomName }: RoomProps) {
|
|||||||
{intl.formatMessage({ id: "Room" })} 1
|
{intl.formatMessage({ id: "Room" })} 1
|
||||||
</Subtitle> */}
|
</Subtitle> */}
|
||||||
<Subtitle color="uiTextHighContrast" type="two">
|
<Subtitle color="uiTextHighContrast" type="two">
|
||||||
{`${intl.formatMessage({ id: "Reservation number" })} ${booking.confirmationNumber}`}
|
{intl.formatMessage(
|
||||||
|
{ id: "Reservation number {value}" },
|
||||||
|
{
|
||||||
|
value: booking.confirmationNumber,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.benefits}>
|
<div className={styles.benefits}>
|
||||||
@@ -81,7 +88,13 @@ export default function Room({ booking, img, roomName }: RoomProps) {
|
|||||||
{intl.formatMessage({ id: "Check-in" })}
|
{intl.formatMessage({ id: "Check-in" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{`${fromDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${fromDate.format("HH:mm")}`}
|
{intl.formatMessage(
|
||||||
|
{ id: "{checkInDate} from {checkInTime}" },
|
||||||
|
{
|
||||||
|
checkInDate: fromDate.format("ddd, D MMM"),
|
||||||
|
checkInTime: fromDate.format("HH:mm"),
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.listItem}>
|
<li className={styles.listItem}>
|
||||||
@@ -89,14 +102,22 @@ export default function Room({ booking, img, roomName }: RoomProps) {
|
|||||||
{intl.formatMessage({ id: "Check-out" })}
|
{intl.formatMessage({ id: "Check-out" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{`${toDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${toDate.format("HH:mm")}`}
|
{intl.formatMessage(
|
||||||
|
{ id: "{checkOutDate} from {checkOutTime}" },
|
||||||
|
{
|
||||||
|
checkOutDate: toDate.format("ddd, D MMM"),
|
||||||
|
checkOutTime: toDate.format("HH:mm"),
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.listItem}>
|
<li className={styles.listItem}>
|
||||||
<Body color="uiTextPlaceholder">
|
<Body color="uiTextPlaceholder">
|
||||||
{intl.formatMessage({ id: "Breakfast" })}
|
{intl.formatMessage({ id: "Breakfast" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">N/A</Body>
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatMessage({ id: "N/A" })}
|
||||||
|
</Body>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.listItem}>
|
<li className={styles.listItem}>
|
||||||
<Body color="uiTextPlaceholder">
|
<Body color="uiTextPlaceholder">
|
||||||
@@ -110,19 +131,24 @@ export default function Room({ booking, img, roomName }: RoomProps) {
|
|||||||
<Body color="uiTextPlaceholder">
|
<Body color="uiTextPlaceholder">
|
||||||
{intl.formatMessage({ id: "Rebooking" })}
|
{intl.formatMessage({ id: "Rebooking" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">N/A</Body>
|
<Body color="uiTextHighContrast">
|
||||||
|
{intl.formatMessage({ id: "N/A" })}
|
||||||
|
</Body>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className={styles.guest}>
|
<div className={styles.guest}>
|
||||||
<Body color="uiTextPlaceholder">
|
<Body color="uiTextPlaceholder">
|
||||||
{intl.formatMessage({ id: "Main guest" })}
|
{intl.formatMessage({ id: "Main guest" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">{guestName}</Body>
|
||||||
{`${booking.guest.firstName} ${booking.guest.lastName}`}
|
|
||||||
</Body>
|
|
||||||
{booking.guest.membershipNumber ? (
|
{booking.guest.membershipNumber ? (
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{`${intl.formatMessage({ id: "Friend no." })} ${booking.guest.membershipNumber}`}
|
{intl.formatMessage(
|
||||||
|
{ id: "Friend no. {value}" },
|
||||||
|
{
|
||||||
|
value: booking.guest.membershipNumber,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
) : null}
|
) : null}
|
||||||
{booking.guest.phoneNumber ? (
|
{booking.guest.phoneNumber ? (
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export default function Contact({ hotel }: ContactProps) {
|
|||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const addressStr = `${hotel.address.streetAddress}, `
|
||||||
|
const cityStr = hotel.address.city
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.wrapper}>
|
<section className={styles.wrapper}>
|
||||||
<address className={styles.address}>
|
<address className={styles.address}>
|
||||||
@@ -24,8 +27,11 @@ export default function Contact({ hotel }: ContactProps) {
|
|||||||
<Body textTransform="bold">
|
<Body textTransform="bold">
|
||||||
{intl.formatMessage({ id: "Address" })}
|
{intl.formatMessage({ id: "Address" })}
|
||||||
</Body>
|
</Body>
|
||||||
<Body>{`${hotel.address.streetAddress}, `}</Body>
|
<Body>
|
||||||
<Body>{hotel.address.city}</Body>
|
{addressStr}
|
||||||
|
<br />
|
||||||
|
{cityStr}
|
||||||
|
</Body>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Body textTransform="bold">
|
<Body textTransform="bold">
|
||||||
@@ -34,7 +40,9 @@ export default function Contact({ hotel }: ContactProps) {
|
|||||||
<Link
|
<Link
|
||||||
href={`https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`}
|
href={`https://www.google.com/maps/dir/?api=1&destination=${hotel.location.latitude},${hotel.location.longitude}`}
|
||||||
>
|
>
|
||||||
<span className={styles.link}>Google Maps</span>
|
<span className={styles.link}>
|
||||||
|
{intl.formatMessage({ id: "Google Maps" })}
|
||||||
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ export default function BedTypeInfo({ hasMultipleBedTypes }: BedTypeInfoProps) {
|
|||||||
id: "Extra bed will be provided additionally",
|
id: "Extra bed will be provided additionally",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const combinedStr = `${availabilityText}. ${extraBedText}`
|
||||||
|
|
||||||
if (hasMultipleBedTypes && hasChildWithExtraBed) {
|
if (hasMultipleBedTypes && hasChildWithExtraBed) {
|
||||||
return (
|
return <Body>{combinedStr}</Body>
|
||||||
<Body>
|
|
||||||
{availabilityText}. {extraBedText}
|
|
||||||
</Body>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasMultipleBedTypes) {
|
if (hasMultipleBedTypes) {
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ export default function Breakfast({ packages }: BreakfastProps) {
|
|||||||
subtitle={
|
subtitle={
|
||||||
pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||||
? intl.formatMessage<React.ReactNode>(
|
? intl.formatMessage<React.ReactNode>(
|
||||||
{ id: "breakfast.price.free" },
|
{
|
||||||
|
id: "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
amount: formatPrice(
|
amount: formatPrice(
|
||||||
intl,
|
intl,
|
||||||
@@ -103,7 +105,7 @@ export default function Breakfast({ packages }: BreakfastProps) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
: intl.formatMessage(
|
: intl.formatMessage(
|
||||||
{ id: "breakfast.price" },
|
{ id: "{amount}/night per adult" },
|
||||||
{
|
{
|
||||||
amount: formatPrice(
|
amount: formatPrice(
|
||||||
intl,
|
intl,
|
||||||
|
|||||||
@@ -89,10 +89,10 @@ export default function JoinScandicFriendsCard({
|
|||||||
<Footnote color="uiTextPlaceholder">
|
<Footnote color="uiTextPlaceholder">
|
||||||
{intl.formatMessage<React.ReactNode>(
|
{intl.formatMessage<React.ReactNode>(
|
||||||
{
|
{
|
||||||
id: "signup.terms",
|
id: "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
termsLink: (str) => (
|
termsAndConditionsLink: (str) => (
|
||||||
<Link
|
<Link
|
||||||
variant="default"
|
variant="default"
|
||||||
textDecoration="underline"
|
textDecoration="underline"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function MemberPriceModal({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button intent="primary" theme="base" onClick={() => setIsOpen(false)}>
|
<Button intent="primary" theme="base" onClick={() => setIsOpen(false)}>
|
||||||
OK
|
{intl.formatMessage({ id: "OK" })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import useLang from "@/hooks/useLang"
|
|||||||
import styles from "./signup.module.css"
|
import styles from "./signup.module.css"
|
||||||
|
|
||||||
export default function Signup({ name }: { name: string }) {
|
export default function Signup({ name }: { name: string }) {
|
||||||
const lang = useLang()
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const [isJoinChecked, setIsJoinChecked] = useState(false)
|
const [isJoinChecked, setIsJoinChecked] = useState(false)
|
||||||
@@ -35,7 +34,9 @@ export default function Signup({ name }: { name: string }) {
|
|||||||
<div className={styles.dateField}>
|
<div className={styles.dateField}>
|
||||||
<header>
|
<header>
|
||||||
<Caption type="bold">
|
<Caption type="bold">
|
||||||
{intl.formatMessage({ id: "Birth date" })} *
|
<span className={styles.required}>
|
||||||
|
{intl.formatMessage({ id: "Birth date" })}
|
||||||
|
</span>
|
||||||
</Caption>
|
</Caption>
|
||||||
</header>
|
</header>
|
||||||
<DateSelect name="dateOfBirth" registerOptions={{ required: true }} />
|
<DateSelect name="dateOfBirth" registerOptions={{ required: true }} />
|
||||||
|
|||||||
@@ -13,3 +13,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.required:after {
|
||||||
|
content: " *";
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ export default async function HotelHeader({ hotelData }: HotelHeaderProps) {
|
|||||||
const hotel = hotelData.data.attributes
|
const hotel = hotelData.data.attributes
|
||||||
|
|
||||||
const image = hotel.hotelContent?.images
|
const image = hotel.hotelContent?.images
|
||||||
|
|
||||||
|
const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<Image
|
<Image
|
||||||
@@ -30,14 +33,12 @@ export default async function HotelHeader({ hotelData }: HotelHeaderProps) {
|
|||||||
<Title as="h1" level="h1" color="white" className={styles.title}>
|
<Title as="h1" level="h1" color="white" className={styles.title}>
|
||||||
{hotel.name}
|
{hotel.name}
|
||||||
</Title>
|
</Title>
|
||||||
<address className={styles.address}>
|
<div className={styles.address}>
|
||||||
<Caption color="white">
|
<Caption color="white">{addressStr}</Caption>
|
||||||
{hotel.address.streetAddress}, {hotel.address.city}
|
|
||||||
</Caption>
|
|
||||||
<Caption color="white">∙</Caption>
|
<Caption color="white">∙</Caption>
|
||||||
<Caption color="white">
|
<Caption color="white">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Distance in km to city centre" },
|
{ id: "{number} km to city centre" },
|
||||||
{
|
{
|
||||||
number: getSingleDecimal(
|
number: getSingleDecimal(
|
||||||
hotel.location.distanceToCentre / 1000
|
hotel.location.distanceToCentre / 1000
|
||||||
@@ -45,7 +46,7 @@ export default async function HotelHeader({ hotelData }: HotelHeaderProps) {
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</address>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ToggleSidePeek hotelId={hotel.operaId} />
|
<ToggleSidePeek hotelId={hotel.operaId} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export default function PaymentClient({
|
|||||||
(errorMessage: string) => {
|
(errorMessage: string) => {
|
||||||
toast.error(
|
toast.error(
|
||||||
intl.formatMessage({
|
intl.formatMessage({
|
||||||
id: "payment.error.failed",
|
id: "We had an issue processing your booking. Please try again. No charges have been made.",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const currentPaymentMethod = methods.getValues("paymentMethod")
|
const currentPaymentMethod = methods.getValues("paymentMethod")
|
||||||
@@ -312,10 +312,6 @@ export default function PaymentClient({
|
|||||||
return <LoadingSpinner />
|
return <LoadingSpinner />
|
||||||
}
|
}
|
||||||
|
|
||||||
const guaranteeing = intl.formatMessage({ id: "guaranteeing" })
|
|
||||||
const paying = intl.formatMessage({ id: "paying" })
|
|
||||||
const paymentVerb = mustBeGuaranteed ? guaranteeing : paying
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
@@ -387,11 +383,10 @@ export default function PaymentClient({
|
|||||||
<Caption>
|
<Caption>
|
||||||
{intl.formatMessage<React.ReactNode>(
|
{intl.formatMessage<React.ReactNode>(
|
||||||
{
|
{
|
||||||
id: "booking.terms",
|
id: "By paying with any of the payment methods available, I accept the terms for this booking and the general <termsAndConditionsLink>Terms & Conditions</termsAndConditionsLink>, and understand that Scandic will process my personal data for this booking in accordance with <privacyPolicyLink>Scandic's Privacy policy</privacyPolicyLink>. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
paymentVerb,
|
termsAndConditionsLink: (str) => (
|
||||||
termsLink: (str) => (
|
|
||||||
<Link
|
<Link
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
variant="underscored"
|
variant="underscored"
|
||||||
@@ -401,7 +396,7 @@ export default function PaymentClient({
|
|||||||
{str}
|
{str}
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
privacyLink: (str) => (
|
privacyPolicyLink: (str) => (
|
||||||
<Link
|
<Link
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
variant="underscored"
|
variant="underscored"
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function PriceChangeDialog({
|
|||||||
<br />
|
<br />
|
||||||
<span className={styles.oldPrice}>
|
<span className={styles.oldPrice}>
|
||||||
{formatPrice(intl, oldPrice, currency)}
|
{formatPrice(intl, oldPrice, currency)}
|
||||||
</span>{" "}
|
</span>
|
||||||
<strong className={styles.newPrice}>
|
<strong className={styles.newPrice}>
|
||||||
{formatPrice(intl, newPrice, currency)}
|
{formatPrice(intl, newPrice, currency)}
|
||||||
</strong>
|
</strong>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default function ToggleSidePeek({
|
|||||||
intent="text"
|
intent="text"
|
||||||
wrapping
|
wrapping
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: "See room details" })}{" "}
|
{intl.formatMessage({ id: "See room details" })}
|
||||||
<ChevronRight height="14" />
|
<ChevronRight height="14" />
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,8 +47,16 @@ export default function SelectedRoom({
|
|||||||
className={styles.description}
|
className={styles.description}
|
||||||
color="uiTextHighContrast"
|
color="uiTextHighContrast"
|
||||||
>
|
>
|
||||||
{room.roomType}{" "}
|
{intl.formatMessage<React.ReactNode>(
|
||||||
<span className={styles.rate}>{rateDescription}</span>
|
{ id: "{roomType} <rate>{rateDescription}</rate>" },
|
||||||
|
{
|
||||||
|
roomType: room.roomType,
|
||||||
|
rateDescription,
|
||||||
|
rate: (str) => {
|
||||||
|
return <span className={styles.rate}>{str}</span>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<Link
|
<Link
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function SummaryBottomSheet({ children }: PropsWithChildren) {
|
|||||||
onClick={toggleSummaryOpen}
|
onClick={toggleSummaryOpen}
|
||||||
className={styles.priceDetailsButton}
|
className={styles.priceDetailsButton}
|
||||||
>
|
>
|
||||||
<Caption>{intl.formatMessage({ id: "Total price" })}:</Caption>
|
<Caption>{intl.formatMessage({ id: "Total price" })}</Caption>
|
||||||
<Subtitle>
|
<Subtitle>
|
||||||
{formatPrice(
|
{formatPrice(
|
||||||
intl,
|
intl,
|
||||||
|
|||||||
@@ -104,10 +104,17 @@ export default function PriceDetailsTable({
|
|||||||
<TableSection>
|
<TableSection>
|
||||||
<TableSectionHeader title={intl.formatMessage({ id: "Breakfast" })} />
|
<TableSectionHeader title={intl.formatMessage({ id: "Breakfast" })} />
|
||||||
<Row
|
<Row
|
||||||
label={intl.formatMessage(
|
label={`${intl.formatMessage(
|
||||||
{ id: "booking.adults.breakfasts" },
|
{
|
||||||
{ totalAdults: adults, totalBreakfasts: nights.length }
|
id: "{totalAdults, plural, one {# voksen} other {# voksne}}",
|
||||||
)}
|
},
|
||||||
|
{ totalAdults: adults }
|
||||||
|
)}, ${intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{totalBreakfasts, plural, one {# morgenmad} other {# morgenmad}}",
|
||||||
|
},
|
||||||
|
{ totalBreakfasts: nights.length }
|
||||||
|
)}`}
|
||||||
value={formatPrice(
|
value={formatPrice(
|
||||||
intl,
|
intl,
|
||||||
parseInt(breakfast.localPrice.totalPrice),
|
parseInt(breakfast.localPrice.totalPrice),
|
||||||
@@ -116,13 +123,17 @@ export default function PriceDetailsTable({
|
|||||||
/>
|
/>
|
||||||
{children?.length ? (
|
{children?.length ? (
|
||||||
<Row
|
<Row
|
||||||
label={intl.formatMessage(
|
label={`${intl.formatMessage(
|
||||||
{ id: "booking.children.breakfasts" },
|
|
||||||
{
|
{
|
||||||
totalChildren: children.length,
|
id: "{totalChildren, plural, one {# child} other {# children}}",
|
||||||
totalBreakfasts: nights.length,
|
},
|
||||||
}
|
{ totalChildren: children.length }
|
||||||
)}
|
)}, ${intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{totalBreakfasts, plural, one {# breakfast} other {# breakfasts}}",
|
||||||
|
},
|
||||||
|
{ totalBreakfasts: nights.length }
|
||||||
|
)}`}
|
||||||
value={formatPrice(intl, 0, breakfast.localPrice.currency)}
|
value={formatPrice(intl, 0, breakfast.localPrice.currency)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -131,17 +142,17 @@ export default function PriceDetailsTable({
|
|||||||
<TableSection>
|
<TableSection>
|
||||||
<TableSectionHeader title={intl.formatMessage({ id: "Total" })} />
|
<TableSectionHeader title={intl.formatMessage({ id: "Total" })} />
|
||||||
<Row
|
<Row
|
||||||
label={intl.formatMessage({ id: "booking.vat.excl" })}
|
label={intl.formatMessage({ id: "Price excluding VAT" })}
|
||||||
value={formatPrice(intl, priceExclVat, totalPrice.local.currency)}
|
value={formatPrice(intl, priceExclVat, totalPrice.local.currency)}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
label={intl.formatMessage({ id: "booking.vat" }, { vat })}
|
label={intl.formatMessage({ id: "VAT {vat}%" }, { vat })}
|
||||||
value={formatPrice(intl, vatAmount, totalPrice.local.currency)}
|
value={formatPrice(intl, vatAmount, totalPrice.local.currency)}
|
||||||
/>
|
/>
|
||||||
<tr className={styles.row}>
|
<tr className={styles.row}>
|
||||||
<td>
|
<td>
|
||||||
<Body textTransform="bold">
|
<Body textTransform="bold">
|
||||||
{intl.formatMessage({ id: "booking.vat.incl" })}
|
{intl.formatMessage({ id: "Price including VAT" })}
|
||||||
</Body>
|
</Body>
|
||||||
</td>
|
</td>
|
||||||
<td className={styles.price}>
|
<td className={styles.price}>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export default function SummaryUI({
|
|||||||
const diff = dt(booking.toDate).diff(booking.fromDate, "days")
|
const diff = dt(booking.toDate).diff(booking.fromDate, "days")
|
||||||
|
|
||||||
const nights = intl.formatMessage(
|
const nights = intl.formatMessage(
|
||||||
{ id: "booking.nights" },
|
{ id: "{totalNights, plural, one {# night} other {# nights}}" },
|
||||||
{ totalNights: diff }
|
{ totalNights: diff }
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,6 +123,22 @@ export default function SummaryUI({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const adultsMsg = intl.formatMessage(
|
||||||
|
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||||
|
{ totalAdults: adults }
|
||||||
|
)
|
||||||
|
|
||||||
|
const guestsParts = [adultsMsg]
|
||||||
|
if (children?.length) {
|
||||||
|
const childrenMsg = intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{totalChildren, plural, one {# child} other {# children}}",
|
||||||
|
},
|
||||||
|
{ totalChildren: children.length }
|
||||||
|
)
|
||||||
|
guestsParts.push(childrenMsg)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.summary}>
|
<section className={styles.summary}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
@@ -157,17 +173,7 @@ export default function SummaryUI({
|
|||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{`${intl.formatMessage(
|
{guestsParts.join(", ")}
|
||||||
{ id: "booking.adults" },
|
|
||||||
{ totalAdults: adults }
|
|
||||||
)}${
|
|
||||||
children?.length
|
|
||||||
? `, ${intl.formatMessage(
|
|
||||||
{ id: "booking.children" },
|
|
||||||
{ totalChildren: children.length }
|
|
||||||
)}`
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
</Caption>
|
</Caption>
|
||||||
<Caption color="uiTextMediumContrast">{cancellationText}</Caption>
|
<Caption color="uiTextMediumContrast">{cancellationText}</Caption>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -231,7 +237,10 @@ export default function SummaryUI({
|
|||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<div>
|
<div>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{`${intl.formatMessage({ id: "Crib (child)" })} × ${childBedCrib}`}
|
{intl.formatMessage(
|
||||||
|
{ id: "Crib (child) × {count}" },
|
||||||
|
{ count: childBedCrib }
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage({ id: "Based on availability" })}
|
{intl.formatMessage({ id: "Based on availability" })}
|
||||||
@@ -246,7 +255,12 @@ export default function SummaryUI({
|
|||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<div>
|
<div>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{`${intl.formatMessage({ id: "Extra bed (child)" })} × ${childBedExtraBed}`}
|
{intl.formatMessage(
|
||||||
|
{ id: "Extra bed (child) × {count}" },
|
||||||
|
{
|
||||||
|
count: childBedExtraBed,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
@@ -278,7 +292,9 @@ export default function SummaryUI({
|
|||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "booking.adults" },
|
{
|
||||||
|
id: "{totalAdults, plural, one {# adult} other {# adults}}",
|
||||||
|
},
|
||||||
{ totalAdults: adults }
|
{ totalAdults: adults }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
@@ -294,7 +310,9 @@ export default function SummaryUI({
|
|||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "booking.children" },
|
{
|
||||||
|
id: "{totalChildren, plural, one {# child} other {# children}}",
|
||||||
|
},
|
||||||
{ totalChildren: children.length }
|
{ totalChildren: children.length }
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
@@ -345,11 +363,15 @@ export default function SummaryUI({
|
|||||||
</Body>
|
</Body>
|
||||||
{totalPrice.requested && (
|
{totalPrice.requested && (
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage({ id: "Approx." })}{" "}
|
{intl.formatMessage(
|
||||||
{formatPrice(
|
{ id: "Approx. {value}" },
|
||||||
intl,
|
{
|
||||||
totalPrice.requested.price,
|
value: formatPrice(
|
||||||
totalPrice.requested.currency
|
intl,
|
||||||
|
totalPrice.requested.price,
|
||||||
|
totalPrice.requested.currency
|
||||||
|
),
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useParams } from "next/dist/client/components/navigation"
|
import { useParams } from "next/dist/client/components/navigation"
|
||||||
import { memo, useCallback } from "react"
|
import { memo, useCallback } from "react"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
@@ -64,6 +65,8 @@ function HotelCard({
|
|||||||
state,
|
state,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const addressStr = `${hotelData.address.streetAddress}, ${hotelData.address.city}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
className={classNames}
|
className={classNames}
|
||||||
@@ -94,9 +97,7 @@ function HotelCard({
|
|||||||
</Subtitle>
|
</Subtitle>
|
||||||
<div className={styles.addressContainer}>
|
<div className={styles.addressContainer}>
|
||||||
<address className={styles.address}>
|
<address className={styles.address}>
|
||||||
<Caption color="uiTextPlaceholder">
|
<Caption color="uiTextPlaceholder">{addressStr}</Caption>
|
||||||
{hotelData.address.streetAddress}, {hotelData.address.city}
|
|
||||||
</Caption>
|
|
||||||
</address>
|
</address>
|
||||||
<address className={styles.addressMobile}>
|
<address className={styles.addressMobile}>
|
||||||
<Caption color="burgundy" type="underline" asChild>
|
<Caption color="burgundy" type="underline" asChild>
|
||||||
@@ -112,7 +113,7 @@ function HotelCard({
|
|||||||
color="burgundy"
|
color="burgundy"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
{hotelData.address.streetAddress}, {hotelData.address.city}
|
{addressStr}
|
||||||
</Link>
|
</Link>
|
||||||
</Caption>
|
</Caption>
|
||||||
</address>
|
</address>
|
||||||
@@ -121,7 +122,7 @@ function HotelCard({
|
|||||||
</div>
|
</div>
|
||||||
<Caption color="uiTextPlaceholder">
|
<Caption color="uiTextPlaceholder">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Distance in km to city centre" },
|
{ id: "{number} km to city centre" },
|
||||||
{
|
{
|
||||||
number: getSingleDecimal(
|
number: getSingleDecimal(
|
||||||
hotelData.location.distanceToCentre / 1000
|
hotelData.location.distanceToCentre / 1000
|
||||||
|
|||||||
@@ -93,7 +93,13 @@ export default function ListingHotelCardDialog({
|
|||||||
{publicPrice && memberPrice && <Caption>/</Caption>}
|
{publicPrice && memberPrice && <Caption>/</Caption>}
|
||||||
{memberPrice && (
|
{memberPrice && (
|
||||||
<Subtitle type="two" color="red" className={styles.memberPrice}>
|
<Subtitle type="two" color="red" className={styles.memberPrice}>
|
||||||
{memberPrice} {currency}
|
{intl.formatMessage(
|
||||||
|
{ id: "{price} {currency}" },
|
||||||
|
{
|
||||||
|
price: memberPrice,
|
||||||
|
currency,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -88,7 +88,13 @@ export default function StandaloneHotelCardDialog({
|
|||||||
</Caption>
|
</Caption>
|
||||||
{publicPrice && (
|
{publicPrice && (
|
||||||
<Subtitle type="two">
|
<Subtitle type="two">
|
||||||
{publicPrice} {currency}
|
{intl.formatMessage(
|
||||||
|
{ id: "{price} {currency}" },
|
||||||
|
{
|
||||||
|
price: publicPrice,
|
||||||
|
currency,
|
||||||
|
}
|
||||||
|
)}
|
||||||
<Body asChild>
|
<Body asChild>
|
||||||
<span>/{intl.formatMessage({ id: "night" })}</span>
|
<span>/{intl.formatMessage({ id: "night" })}</span>
|
||||||
</Body>
|
</Body>
|
||||||
@@ -100,7 +106,13 @@ export default function StandaloneHotelCardDialog({
|
|||||||
color="red"
|
color="red"
|
||||||
className={styles.memberPrice}
|
className={styles.memberPrice}
|
||||||
>
|
>
|
||||||
{memberPrice} {currency}
|
{intl.formatMessage(
|
||||||
|
{ id: "{price} {currency}" },
|
||||||
|
{
|
||||||
|
price: memberPrice,
|
||||||
|
currency,
|
||||||
|
}
|
||||||
|
)}
|
||||||
<Body asChild color="red">
|
<Body asChild color="red">
|
||||||
<span>/{intl.formatMessage({ id: "night" })}</span>
|
<span>/{intl.formatMessage({ id: "night" })}</span>
|
||||||
</Body>
|
</Body>
|
||||||
|
|||||||
@@ -81,8 +81,10 @@ export default function HotelCardListing({
|
|||||||
) : activeFilters ? (
|
) : activeFilters ? (
|
||||||
<Alert
|
<Alert
|
||||||
type={AlertTypeEnum.Info}
|
type={AlertTypeEnum.Info}
|
||||||
heading={intl.formatMessage({ id: "filters.nohotel.heading" })}
|
heading={intl.formatMessage({ id: "No hotels match your filters" })}
|
||||||
text={intl.formatMessage({ id: "filters.nohotel.text" })}
|
text={intl.formatMessage({
|
||||||
|
id: "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showBackToTop && (
|
{showBackToTop && (
|
||||||
|
|||||||
@@ -139,8 +139,10 @@ export default function FilterAndSortModal({
|
|||||||
onClick={() => handleApplyFiltersAndSorting(close)}
|
onClick={() => handleApplyFiltersAndSorting(close)}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "See results" },
|
{ id: "See results ({ count })" },
|
||||||
{ count: resultCount }
|
{
|
||||||
|
count: resultCount,
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
import { useHotelFilterStore } from "@/stores/hotel-filters"
|
||||||
@@ -13,7 +14,7 @@ export default function HotelCount() {
|
|||||||
<Preamble>
|
<Preamble>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "Hotel(s)",
|
id: "{amount, plural, one {# hotel} other {# hotels}}",
|
||||||
},
|
},
|
||||||
{ amount: resultCount }
|
{ amount: resultCount }
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation"
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import SelectionCard from "../SelectionCard"
|
import SelectionCard from "../SelectionCard"
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ export default function BedSelection({
|
|||||||
alternatives,
|
alternatives,
|
||||||
nextPath,
|
nextPath,
|
||||||
}: BedSelectionProps) {
|
}: BedSelectionProps) {
|
||||||
|
const intl = useIntl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ export default function BedSelection({
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button type="submit" hidden>
|
<button type="submit" hidden>
|
||||||
Submit
|
{intl.formatMessage({ id: "Submit" })}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation"
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import SelectionCard from "../SelectionCard"
|
import SelectionCard from "../SelectionCard"
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ export default function BreakfastSelection({
|
|||||||
alternatives,
|
alternatives,
|
||||||
nextPath,
|
nextPath,
|
||||||
}: BreakfastSelectionProps) {
|
}: BreakfastSelectionProps) {
|
||||||
|
const intl = useIntl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
@@ -49,7 +52,7 @@ export default function BreakfastSelection({
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button type="submit" hidden>
|
<button type="submit" hidden>
|
||||||
Submit
|
{intl.formatMessage({ id: "Submit" })}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
|
||||||
@@ -8,12 +10,13 @@ import styles from "./details.module.css"
|
|||||||
import type { DetailsProps } from "@/types/components/hotelReservation/selectRate/section"
|
import type { DetailsProps } from "@/types/components/hotelReservation/selectRate/section"
|
||||||
|
|
||||||
export default function Details({ nextPath }: DetailsProps) {
|
export default function Details({ nextPath }: DetailsProps) {
|
||||||
|
const intl = useIntl()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<form method="GET" action={`${nextPath}?${searchParams}`}>
|
<form method="GET" action={`${nextPath}?${searchParams}`}>
|
||||||
<Button type="submit">Submit</Button>
|
<Button type="submit">{intl.formatMessage({ id: "Submit" })}</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -70,7 +70,18 @@ export default async function HotelInfoCard({
|
|||||||
</Title>
|
</Title>
|
||||||
<div className={styles.hotelAddressDescription}>
|
<div className={styles.hotelAddressDescription}>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{`${hotelAttributes.address.streetAddress}, ${hotelAttributes.address.city} ∙ ${getSingleDecimal(hotelAttributes.location.distanceToCentre / 1000)} ${intl.formatMessage({ id: "km to city center" })}`}
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{address}, {city} ∙ {distanceToCityCentreInKm} km to city center",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: hotelAttributes.address.streetAddress,
|
||||||
|
city: hotelAttributes.address.city,
|
||||||
|
distanceToCityCentreInKm: getSingleDecimal(
|
||||||
|
hotelAttributes.location.distanceToCentre / 1000
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{hotelAttributes.hotelContent.texts.descriptions.medium}
|
{hotelAttributes.hotelContent.texts.descriptions.medium}
|
||||||
|
|||||||
@@ -84,8 +84,12 @@ export default function RoomFilter({
|
|||||||
<div className={styles.infoDesktop}>
|
<div className={styles.infoDesktop}>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Room types available" },
|
{
|
||||||
{ numberOfRooms }
|
id: "{numberOfRooms, plural, one {# room type} other {# room types}} available",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
numberOfRooms,
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,8 +121,12 @@ export default function RoomFilter({
|
|||||||
</div>
|
</div>
|
||||||
<Caption color="uiTextHighContrast">
|
<Caption color="uiTextHighContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Room types available" },
|
{
|
||||||
{ numberOfRooms }
|
id: "{numberOfRooms, plural, one {# room type} other {# room types}} available",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
numberOfRooms,
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,7 +146,7 @@ export default function RoomFilter({
|
|||||||
<CheckboxChip
|
<CheckboxChip
|
||||||
name={code}
|
name={code}
|
||||||
key={code}
|
key={code}
|
||||||
label={intl.formatMessage({ id: description })}
|
label={description}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
selected={getValues(code)}
|
selected={getValues(code)}
|
||||||
Icon={getIconForFeatureCode(code)}
|
Icon={getIconForFeatureCode(code)}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default function PriceList({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Subtitle type="two" color="baseTextDisabled">
|
<Subtitle type="two" color="baseTextDisabled">
|
||||||
{intl.formatMessage({ id: "n/a" })}
|
{intl.formatMessage({ id: "N/A" })}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
)}
|
)}
|
||||||
</dd>
|
</dd>
|
||||||
@@ -112,7 +112,7 @@ export default function PriceList({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Body textTransform="bold" color="disabled">
|
<Body textTransform="bold" color="disabled">
|
||||||
- {intl.formatMessage({ id: "Currency Code" })}
|
-
|
||||||
</Body>
|
</Body>
|
||||||
)}
|
)}
|
||||||
</dd>
|
</dd>
|
||||||
@@ -126,9 +126,14 @@ export default function PriceList({
|
|||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{totalPublicRequestedPricePerNight}/
|
{intl.formatMessage(
|
||||||
{totalMemberRequestedPricePerNight}{" "}
|
{ id: "{publicPrice}/{memberPrice} {currency}" },
|
||||||
{publicRequestedPrice.currency}
|
{
|
||||||
|
publicPrice: totalPublicRequestedPricePerNight,
|
||||||
|
memberPrice: totalMemberRequestedPricePerNight,
|
||||||
|
currency: publicRequestedPrice.currency,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -80,15 +80,15 @@ export default function RateSummary({
|
|||||||
const showMemberDiscountBanner = member && !isUserLoggedIn
|
const showMemberDiscountBanner = member && !isUserLoggedIn
|
||||||
|
|
||||||
const summaryPriceText = `${intl.formatMessage(
|
const summaryPriceText = `${intl.formatMessage(
|
||||||
{ id: "booking.nights" },
|
{ id: "{totalNights, plural, one {# night} other {# nights}}" },
|
||||||
{ totalNights: nights }
|
{ totalNights: nights }
|
||||||
)}, ${intl.formatMessage(
|
)}, ${intl.formatMessage(
|
||||||
{ id: "booking.adults" },
|
{ id: "{totalAdults, plural, one {# adult} other {# adults}}" },
|
||||||
{ totalAdults: roomsAvailability.occupancy?.adults }
|
{ totalAdults: roomsAvailability.occupancy?.adults }
|
||||||
)}${
|
)}${
|
||||||
roomsAvailability.occupancy?.children?.length
|
roomsAvailability.occupancy?.children?.length
|
||||||
? `, ${intl.formatMessage(
|
? `, ${intl.formatMessage(
|
||||||
{ id: "booking.children" },
|
{ id: "{totalChildren, plural, one {# child} other {# children}}" },
|
||||||
{ totalChildren: roomsAvailability.occupancy.children.length }
|
{ totalChildren: roomsAvailability.occupancy.children.length }
|
||||||
)}`
|
)}`
|
||||||
: ""
|
: ""
|
||||||
@@ -136,11 +136,15 @@ export default function RateSummary({
|
|||||||
</Subtitle>
|
</Subtitle>
|
||||||
{totalPriceToShow?.requestedPrice ? (
|
{totalPriceToShow?.requestedPrice ? (
|
||||||
<Body color="uiTextMediumContrast">
|
<Body color="uiTextMediumContrast">
|
||||||
{intl.formatMessage({ id: "Approx." })}{" "}
|
{intl.formatMessage(
|
||||||
{formatPrice(
|
{ id: "Approx. {value}" },
|
||||||
intl,
|
{
|
||||||
totalPriceToShow.requestedPrice.price,
|
value: formatPrice(
|
||||||
totalPriceToShow.requestedPrice.currency
|
intl,
|
||||||
|
totalPriceToShow.requestedPrice.price,
|
||||||
|
totalPriceToShow.requestedPrice.currency
|
||||||
|
),
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -118,10 +118,12 @@ export default function RoomCard({
|
|||||||
{roomConfiguration.roomsLeft > 0 &&
|
{roomConfiguration.roomsLeft > 0 &&
|
||||||
roomConfiguration.roomsLeft < 5 && (
|
roomConfiguration.roomsLeft < 5 && (
|
||||||
<span className={styles.chip}>
|
<span className={styles.chip}>
|
||||||
<Footnote
|
<Footnote color="burgundy" textTransform="uppercase">
|
||||||
color="burgundy"
|
{intl.formatMessage(
|
||||||
textTransform="uppercase"
|
{ id: "{amount, number} left" },
|
||||||
>{`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`}</Footnote>
|
{ amount: roomConfiguration.roomsLeft }
|
||||||
|
)}
|
||||||
|
</Footnote>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{roomConfiguration.features
|
{roomConfiguration.features
|
||||||
@@ -150,7 +152,7 @@ export default function RoomCard({
|
|||||||
<Caption color="uiTextMediumContrast" className={styles.guests}>
|
<Caption color="uiTextMediumContrast" className={styles.guests}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "booking.guests",
|
id: "Max {max, plural, one {{range} guest} other {{range} guests}}",
|
||||||
},
|
},
|
||||||
{ max: totalOccupancy.max, range: totalOccupancy.range }
|
{ max: totalOccupancy.max, range: totalOccupancy.range }
|
||||||
)}
|
)}
|
||||||
@@ -159,9 +161,21 @@ export default function RoomCard({
|
|||||||
{roomSize && (
|
{roomSize && (
|
||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{roomSize.min === roomSize.max
|
{roomSize.min === roomSize.max
|
||||||
? roomSize.min
|
? intl.formatMessage(
|
||||||
: `${roomSize.min}-${roomSize.max}`}
|
{ id: "{roomSize} m²" },
|
||||||
m²
|
{
|
||||||
|
roomSize: roomSize.min,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{roomSizeMin} - {roomSizeMax} m²",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roomSizeMin: roomSize.min,
|
||||||
|
roomSizeMax: roomSize.max,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
)}
|
)}
|
||||||
<div className={styles.toggleSidePeek}>
|
<div className={styles.toggleSidePeek}>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import RoomFilter from "../RoomFilter"
|
import RoomFilter from "../RoomFilter"
|
||||||
import RoomSelection from "../RoomSelection"
|
import RoomSelection from "../RoomSelection"
|
||||||
@@ -27,6 +28,8 @@ export default function Rooms({
|
|||||||
hotelType,
|
hotelType,
|
||||||
isUserLoggedIn,
|
isUserLoggedIn,
|
||||||
}: SelectRateProps) {
|
}: SelectRateProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const visibleRooms: RoomConfiguration[] = useMemo(() => {
|
const visibleRooms: RoomConfiguration[] = useMemo(() => {
|
||||||
const deduped = filterDuplicateRoomTypesByLowestPrice(
|
const deduped = filterDuplicateRoomTypesByLowestPrice(
|
||||||
roomsAvailability.roomConfigurations
|
roomsAvailability.roomConfigurations
|
||||||
@@ -59,27 +62,27 @@ export default function Rooms({
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
||||||
description: "Accessible Room",
|
description: intl.formatMessage({ id: "Accessible Room" }),
|
||||||
itemCode: availablePackages.find(
|
itemCode: availablePackages.find(
|
||||||
(pkg) => pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
|
(pkg) => pkg.code === RoomPackageCodeEnum.ACCESSIBILITY_ROOM
|
||||||
)?.itemCode,
|
)?.itemCode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: RoomPackageCodeEnum.ALLERGY_ROOM,
|
code: RoomPackageCodeEnum.ALLERGY_ROOM,
|
||||||
description: "Allergy Room",
|
description: intl.formatMessage({ id: "Allergy Room" }),
|
||||||
itemCode: availablePackages.find(
|
itemCode: availablePackages.find(
|
||||||
(pkg) => pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM
|
(pkg) => pkg.code === RoomPackageCodeEnum.ALLERGY_ROOM
|
||||||
)?.itemCode,
|
)?.itemCode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: RoomPackageCodeEnum.PET_ROOM,
|
code: RoomPackageCodeEnum.PET_ROOM,
|
||||||
description: "Pet Room",
|
description: intl.formatMessage({ id: "Pet Room" }),
|
||||||
itemCode: availablePackages.find(
|
itemCode: availablePackages.find(
|
||||||
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
||||||
)?.itemCode,
|
)?.itemCode,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[availablePackages]
|
[availablePackages, intl]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleFilter = useCallback(
|
const handleFilter = useCallback(
|
||||||
|
|||||||
@@ -30,15 +30,20 @@ export default function SelectionCard({
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Caption color="burgundy" className={styles.price}>
|
<Caption color="burgundy" className={styles.price}>
|
||||||
{formatPrice(intl, price, currency)}/
|
{intl.formatMessage(
|
||||||
{intl.formatMessage({ id: "night" })}
|
{ id: "{price}/night" },
|
||||||
|
{
|
||||||
|
price: formatPrice(intl, price, currency),
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
|
|
||||||
{membersPrice && (
|
{membersPrice && (
|
||||||
<Caption color="burgundy" className={styles.membersPrice}>
|
<Caption color="burgundy" className={styles.membersPrice}>
|
||||||
{intl.formatMessage({ id: "Members" })}{" "}
|
{intl.formatMessage(
|
||||||
{formatPrice(intl, membersPrice, currency)}/
|
{ id: "Members {price}/night" },
|
||||||
{intl.formatMessage({ id: "night" })}
|
{ price: formatPrice(intl, membersPrice, currency) }
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -77,16 +77,15 @@ export default function LanguageSwitcher({
|
|||||||
|
|
||||||
const classNames = languageSwitcherVariants({ position })
|
const classNames = languageSwitcherVariants({ position })
|
||||||
|
|
||||||
|
const closeMsg = intl.formatMessage({ id: "Close language menu" })
|
||||||
|
const openMsg = intl.formatMessage({ id: "Open language menu" })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames} ref={languageSwitcherRef}>
|
<div className={classNames} ref={languageSwitcherRef}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
aria-label={intl.formatMessage({
|
aria-label={isLanguageSwitcherOpen ? closeMsg : openMsg}
|
||||||
id: isLanguageSwitcherOpen
|
|
||||||
? "Close language menu"
|
|
||||||
: "Open language menu",
|
|
||||||
})}
|
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<GlobeIcon width={globeIconSize} height={globeIconSize} />
|
<GlobeIcon width={globeIconSize} height={globeIconSize} />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { AnimatePresence, motion } from "framer-motion"
|
import { AnimatePresence, motion } from "framer-motion"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ function HotelListingMapContent({ hotelPins }: HotelListingMapContentProps) {
|
|||||||
{/* TODO: Handle when no price is available */}
|
{/* TODO: Handle when no price is available */}
|
||||||
{hotelPrice
|
{hotelPrice
|
||||||
? formatPrice(intl, hotelPrice, pin.currency)
|
? formatPrice(intl, hotelPrice, pin.currency)
|
||||||
: "N/A"}
|
: intl.formatMessage({ id: "N/A" })}
|
||||||
</span>
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
AdvancedMarker,
|
AdvancedMarker,
|
||||||
AdvancedMarkerAnchorPoint,
|
AdvancedMarkerAnchorPoint,
|
||||||
} from "@vis.gl/react-google-maps"
|
} from "@vis.gl/react-google-maps"
|
||||||
|
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 Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
@@ -19,6 +20,8 @@ export default function HotelMapContent({
|
|||||||
onActivePoiChange,
|
onActivePoiChange,
|
||||||
activePoi,
|
activePoi,
|
||||||
}: HotelMapContentProps) {
|
}: HotelMapContentProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
function toggleActivePoi(poiName: string) {
|
function toggleActivePoi(poiName: string) {
|
||||||
onActivePoiChange?.(activePoi === poiName ? null : poiName)
|
onActivePoiChange?.(activePoi === poiName ? null : poiName)
|
||||||
}
|
}
|
||||||
@@ -51,7 +54,14 @@ export default function HotelMapContent({
|
|||||||
<span>
|
<span>
|
||||||
{poi.name}
|
{poi.name}
|
||||||
<Caption asChild>
|
<Caption asChild>
|
||||||
<span>{poi.distance} km</span>
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "{distanceInKm} km" },
|
||||||
|
{
|
||||||
|
distanceInKm: poi.distance,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</Caption>
|
</Caption>
|
||||||
</span>
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ function InnerModal({
|
|||||||
subtitle,
|
subtitle,
|
||||||
}: PropsWithChildren<InnerModalProps>) {
|
}: PropsWithChildren<InnerModalProps>) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
function modalStateHandler(newAnimationState: AnimationState) {
|
function modalStateHandler(newAnimationState: AnimationState) {
|
||||||
setAnimation((currentAnimationState) =>
|
setAnimation((currentAnimationState) =>
|
||||||
newAnimationState === AnimationStateEnum.hidden &&
|
newAnimationState === AnimationStateEnum.hidden &&
|
||||||
@@ -72,7 +73,10 @@ function InnerModal({
|
|||||||
animate={animation}
|
animate={animation}
|
||||||
initial={"hidden"}
|
initial={"hidden"}
|
||||||
>
|
>
|
||||||
<Dialog className={styles.dialog} aria-label="Dialog">
|
<Dialog
|
||||||
|
className={styles.dialog}
|
||||||
|
aria-label={intl.formatMessage({ id: "Dialog" })}
|
||||||
|
>
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<>
|
<>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ export default function SurprisesNotification({
|
|||||||
toast.success(
|
toast.success(
|
||||||
<>
|
<>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Gift(s) added to your benefits" },
|
{
|
||||||
|
id: "{amount, plural, one {Gift} other {Gifts}} added to your benefits",
|
||||||
|
},
|
||||||
{ amount: surprises.length }
|
{ amount: surprises.length }
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
@@ -117,7 +119,10 @@ export default function SurprisesNotification({
|
|||||||
}}
|
}}
|
||||||
onAnimationComplete={confetti}
|
onAnimationComplete={confetti}
|
||||||
>
|
>
|
||||||
<Dialog aria-label="Surprises" className={styles.dialog}>
|
<Dialog
|
||||||
|
aria-label={intl.formatMessage({ id: "Surprises" })}
|
||||||
|
className={styles.dialog}
|
||||||
|
>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default function Initial({ totalSurprises, onOpen }: InitialProps) {
|
|||||||
<>
|
<>
|
||||||
{intl.formatMessage<React.ReactNode>(
|
{intl.formatMessage<React.ReactNode>(
|
||||||
{
|
{
|
||||||
id: "You have <b>#</b> gifts waiting for you!",
|
id: "You have <b>{amount}</b> gifts waiting for you!",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
amount: totalSurprises,
|
amount: totalSurprises,
|
||||||
@@ -52,7 +52,7 @@ export default function Initial({ totalSurprises, onOpen }: InitialProps) {
|
|||||||
>
|
>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "Open gift(s)",
|
id: "Open {amount, plural, one {gift} other {gifts}}",
|
||||||
},
|
},
|
||||||
{ amount: totalSurprises }
|
{ amount: totalSurprises }
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,25 +23,30 @@ export default function Slide({ surprise, membershipNumber }: SlideProps) {
|
|||||||
},
|
},
|
||||||
dt()
|
dt()
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={surprise.label}>
|
<Card title={surprise.label}>
|
||||||
<Body textAlign="center">{surprise.description}</Body>
|
<Body textAlign="center">{surprise.description}</Body>
|
||||||
<div className={styles.badge}>
|
<div className={styles.badge}>
|
||||||
<Caption>
|
<Caption>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Expires at the earliest" },
|
{ id: "Expires at the earliest {expirationDate}" },
|
||||||
{
|
{
|
||||||
date: dt(earliestExpirationDate)
|
expirationDate: dt(earliestExpirationDate)
|
||||||
.locale(lang)
|
.locale(lang)
|
||||||
.format("D MMM YYYY"),
|
.format("D MMM YYYY"),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
<Caption>
|
<Caption>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage(
|
||||||
id: "Membership ID",
|
{
|
||||||
})}{" "}
|
id: "Membership ID {id}",
|
||||||
{membershipNumber}
|
},
|
||||||
|
{
|
||||||
|
id: membershipNumber,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -16,8 +16,22 @@ export default function CheckinCheckOut({ checkin }: CheckInCheckOutProps) {
|
|||||||
variant="sidepeek"
|
variant="sidepeek"
|
||||||
>
|
>
|
||||||
<Body textTransform="bold">{intl.formatMessage({ id: "Hours" })}</Body>
|
<Body textTransform="bold">{intl.formatMessage({ id: "Hours" })}</Body>
|
||||||
<Body>{`${intl.formatMessage({ id: "Check in from" })}: ${checkin.checkInTime}`}</Body>
|
<Body>
|
||||||
<Body>{`${intl.formatMessage({ id: "Check out at latest" })}: ${checkin.checkOutTime}`}</Body>
|
{intl.formatMessage(
|
||||||
|
{ id: "Check in from: {checkInTime}" },
|
||||||
|
{
|
||||||
|
checkInTime: checkin.checkInTime,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
|
<Body>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "Check out at latest: {checkOutTime}" },
|
||||||
|
{
|
||||||
|
checkOutTime: checkin.checkOutTime,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { IconName } from "@/types/components/icon"
|
|||||||
|
|
||||||
export default function Parking({ parking }: ParkingProps) {
|
export default function Parking({ parking }: ParkingProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
title={intl.formatMessage({ id: "Parking" })}
|
title={intl.formatMessage({ id: "Parking" })}
|
||||||
@@ -18,39 +19,48 @@ export default function Parking({ parking }: ParkingProps) {
|
|||||||
className={styles.parking}
|
className={styles.parking}
|
||||||
variant="sidepeek"
|
variant="sidepeek"
|
||||||
>
|
>
|
||||||
{parking.map((p) => (
|
{parking.map((p) => {
|
||||||
<div key={p.name}>
|
const title = `${p.type}${p.name ? ` (${p.name})` : ""}`
|
||||||
<Subtitle type="two">
|
|
||||||
{`${intl.formatMessage({ id: p.type })} ${p?.name ? ` (${p.name})` : ""}`}
|
return (
|
||||||
</Subtitle>
|
<div key={p.name}>
|
||||||
<ul className={styles.list}>
|
<Subtitle type="two">{title}</Subtitle>
|
||||||
{p?.address && (
|
<ul className={styles.list}>
|
||||||
<li>
|
{p.address !== undefined && (
|
||||||
<FilledHeartIcon color="baseIconLowContrast" />
|
<li>
|
||||||
{`${intl.formatMessage({ id: "Address" })}: ${p.address}`}
|
<FilledHeartIcon color="baseIconLowContrast" />$
|
||||||
</li>
|
{intl.formatMessage(
|
||||||
)}
|
{ id: "Address: {address}" },
|
||||||
{p?.numberOfParkingSpots !== undefined && (
|
{
|
||||||
<li>
|
address: p.address,
|
||||||
<FilledHeartIcon color="baseIconLowContrast" />
|
}
|
||||||
{intl.formatMessage(
|
)}
|
||||||
{ id: "Number of parking spots" },
|
</li>
|
||||||
{ number: p.numberOfParkingSpots }
|
)}
|
||||||
)}
|
{p.numberOfParkingSpots !== undefined && (
|
||||||
</li>
|
<li>
|
||||||
)}
|
<FilledHeartIcon color="baseIconLowContrast" />
|
||||||
{p?.numberOfChargingSpaces !== undefined && (
|
{intl.formatMessage(
|
||||||
<li>
|
{ id: "Number of parking spots: {number}" },
|
||||||
<FilledHeartIcon color="baseIconLowContrast" />
|
{ number: p.numberOfParkingSpots }
|
||||||
{intl.formatMessage(
|
)}
|
||||||
{ id: "Number of charging points for electric cars" },
|
</li>
|
||||||
{ number: p.numberOfChargingSpaces }
|
)}
|
||||||
)}
|
{p.numberOfChargingSpaces !== undefined && (
|
||||||
</li>
|
<li>
|
||||||
)}
|
<FilledHeartIcon color="baseIconLowContrast" />
|
||||||
</ul>
|
{intl.formatMessage(
|
||||||
</div>
|
{
|
||||||
))}
|
id: "Number of charging points for electric cars: {number}",
|
||||||
|
},
|
||||||
|
{ number: p.numberOfChargingSpaces }
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ export default function Restaurant({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
title={intl.formatMessage({ id: "Restaurant" }, { count: 1 })}
|
title={intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "{totalRestaurants, plural, one {Restaurant} other {Restaurants}}",
|
||||||
|
},
|
||||||
|
{ totalRestaurants: 1 }
|
||||||
|
)}
|
||||||
icon={IconName.Restaurant}
|
icon={IconName.Restaurant}
|
||||||
variant="sidepeek"
|
variant="sidepeek"
|
||||||
>
|
>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user