Merge branch 'develop' into feature/sw-561-design-fixes

This commit is contained in:
Linus Flood
2024-10-08 13:20:40 +02:00
128 changed files with 2289 additions and 913 deletions

View File

@@ -19,6 +19,8 @@ DESIGN_SYSTEM_ACCESS_TOKEN=""
NEXTAUTH_REDIRECT_PROXY_URL="http://localhost:3000/api/web/auth" NEXTAUTH_REDIRECT_PROXY_URL="http://localhost:3000/api/web/auth"
NEXTAUTH_SECRET="" NEXTAUTH_SECRET=""
REVALIDATE_SECRET="" REVALIDATE_SECRET=""
SALESFORCE_PREFERENCE_BASE_URL="https://cloud.emails.scandichotels.com/preference-center"
SEAMLESS_LOGIN_DA="http://www.example.dk/updatelogin" SEAMLESS_LOGIN_DA="http://www.example.dk/updatelogin"
SEAMLESS_LOGIN_DE="http://www.example.de/updatelogin" SEAMLESS_LOGIN_DE="http://www.example.de/updatelogin"
SEAMLESS_LOGIN_EN="http://www.example.com/updatelogin" SEAMLESS_LOGIN_EN="http://www.example.com/updatelogin"

View File

@@ -1,4 +1,5 @@
import { ArrowRightIcon } from "@/components/Icons" import { ArrowRightIcon } from "@/components/Icons"
import ManagePreferencesButton from "@/components/Profile/ManagePreferencesButton"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
@@ -27,12 +28,7 @@ export default async function CommunicationSlot({
})} })}
</Body> </Body>
</article> </article>
<Link href="#" variant="icon"> <ManagePreferencesButton />
<ArrowRightIcon color="burgundy" />
<Body color="burgundy" textTransform="underlined">
{formatMessage({ id: "Manage preferences" })}
</Body>
</Link>
</section> </section>
) )
} }

View File

@@ -1,4 +1,3 @@
import { env } from "@/env/server"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import AddCreditCardButton from "@/components/Profile/AddCreditCardButton" import AddCreditCardButton from "@/components/Profile/AddCreditCardButton"
@@ -17,8 +16,6 @@ export default async function CreditCardSlot({ params }: PageArgs<LangParams>) {
const { formatMessage } = await getIntl() const { formatMessage } = await getIntl()
const creditCards = await serverClient().user.creditCards() const creditCards = await serverClient().user.creditCards()
const { lang } = params
return ( return (
<section className={styles.container}> <section className={styles.container}>
<article className={styles.content}> <article className={styles.content}>

View File

@@ -15,8 +15,7 @@ export default function ProfileLayout({
{profile} {profile}
<Divider color="burgundy" opacity={8} /> <Divider color="burgundy" opacity={8} />
{creditCards} {creditCards}
{/* TODO: Implement communication preferences flow. Hidden until decided on where to send user. */} {communication}
{/* {communication} */}
</section> </section>
</main> </main>
) )

View File

@@ -1,13 +1,13 @@
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import BedType from "@/components/HotelReservation/EnterDetails/BedType"
import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast"
import Details from "@/components/HotelReservation/EnterDetails/Details"
import HotelSelectionHeader from "@/components/HotelReservation/HotelSelectionHeader" import HotelSelectionHeader from "@/components/HotelReservation/HotelSelectionHeader"
import BedSelection from "@/components/HotelReservation/SelectRate/BedSelection"
import BreakfastSelection from "@/components/HotelReservation/SelectRate/BreakfastSelection"
import Details from "@/components/HotelReservation/SelectRate/Details"
import Payment from "@/components/HotelReservation/SelectRate/Payment" import Payment from "@/components/HotelReservation/SelectRate/Payment"
import RoomSelection from "@/components/HotelReservation/SelectRate/RoomSelection"
import SectionAccordion from "@/components/HotelReservation/SelectRate/SectionAccordion" import SectionAccordion from "@/components/HotelReservation/SelectRate/SectionAccordion"
import Summary from "@/components/HotelReservation/SelectRate/Summary" import Summary from "@/components/HotelReservation/SelectRate/Summary"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
@@ -79,6 +79,7 @@ export default async function SectionsPage({
searchParams, searchParams,
}: PageArgs<LangParams & { section: string }, SectionPageProps>) { }: PageArgs<LangParams & { section: string }, SectionPageProps>) {
setLang(params.lang) setLang(params.lang)
const profile = await getProfileSafely()
const hotel = await serverClient().hotel.hotelData.get({ const hotel = await serverClient().hotel.hotelData.get({
hotelId: "811", hotelId: "811",
@@ -114,6 +115,11 @@ export default async function SectionsPage({
const currentSearchParams = new URLSearchParams(searchParams).toString() const currentSearchParams = new URLSearchParams(searchParams).toString()
let user = null
if (profile && !("error" in profile)) {
user = profile
}
return ( return (
<div> <div>
<HotelSelectionHeader hotel={hotel.data.attributes} /> <HotelSelectionHeader hotel={hotel.data.attributes} />
@@ -131,47 +137,26 @@ export default async function SectionsPage({
: undefined : undefined
} }
path={`select-rate?${currentSearchParams}`} path={`select-rate?${currentSearchParams}`}
> ></SectionAccordion>
{params.section === "select-rate" && (
<RoomSelection
alternatives={rooms}
nextPath="select-bed"
// TODO: Get real value
nrOfNights={1}
// TODO: Get real value
nrOfAdults={1}
/>
)}
</SectionAccordion>
<SectionAccordion <SectionAccordion
header={intl.formatMessage({ id: "Bed type" })} header={intl.formatMessage({ id: "Bed type" })}
selection={selectedBed} selection={selectedBed}
path={`select-bed?${currentSearchParams}`} path={`select-bed?${currentSearchParams}`}
> >
{params.section === "select-bed" && ( {params.section === "select-bed" ? <BedType /> : null}
<BedSelection
nextPath="breakfast"
alternatives={bedAlternatives}
/>
)}
</SectionAccordion> </SectionAccordion>
<SectionAccordion <SectionAccordion
header={intl.formatMessage({ id: "Breakfast" })} header={intl.formatMessage({ id: "Breakfast" })}
selection={selectedBreakfast} selection={selectedBreakfast}
path={`breakfast?${currentSearchParams}`} path={`breakfast?${currentSearchParams}`}
> >
{params.section === "breakfast" && ( {params.section === "breakfast" ? <Breakfast /> : null}
<BreakfastSelection
alternatives={breakfastAlternatives}
nextPath="details"
/>
)}
</SectionAccordion> </SectionAccordion>
<SectionAccordion <SectionAccordion
header={intl.formatMessage({ id: "Your details" })} header={intl.formatMessage({ id: "Your details" })}
path={`details?${currentSearchParams}`} path={`details?${currentSearchParams}`}
> >
{params.section === "details" && <Details nextPath="payment" />} {params.section === "details" ? <Details user={user} /> : null}
</SectionAccordion> </SectionAccordion>
<SectionAccordion <SectionAccordion
header={intl.formatMessage({ id: "Payment info" })} header={intl.formatMessage({ id: "Payment info" })}

View File

@@ -9,7 +9,7 @@ import { Filter } from "@/types/components/hotelReservation/selectHotel/hotelFil
export async function fetchAvailableHotels( export async function fetchAvailableHotels(
input: AvailabilityInput input: AvailabilityInput
): Promise<HotelData[]> { ): Promise<HotelData[]> {
const availableHotels = await serverClient().hotel.availability.get(input) const availableHotels = await serverClient().hotel.availability.hotels(input)
if (!availableHotels) throw new Error() if (!availableHotels) throw new Error()

View File

@@ -0,0 +1,25 @@
.page {
min-height: 100dvh;
padding-top: var(--Spacing-x6);
padding-left: var(--Spacing-x2);
padding-right: var(--Spacing-x2);
background-color: var(--Scandic-Brand-Warm-White);
}
.content {
max-width: 1134px;
margin-top: var(--Spacing-x5);
margin-left: auto;
margin-right: auto;
display: flex;
justify-content: space-between;
gap: var(--Spacing-x7);
}
.main {
flex-grow: 1;
}
.summary {
max-width: 340px;
}

View File

@@ -0,0 +1,53 @@
import { serverClient } from "@/lib/trpc/server"
import tempHotelData from "@/server/routers/hotels/tempHotelData.json"
import RoomSelection from "@/components/HotelReservation/SelectRate/RoomSelection"
import { getIntl } from "@/i18n"
import { setLang } from "@/i18n/serverContext"
import styles from "./page.module.css"
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
import { LangParams, PageArgs } from "@/types/params"
export default async function SelectRatePage({
params,
searchParams,
}: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) {
setLang(params.lang)
// TODO: Use real endpoint.
const hotel = tempHotelData.data.attributes
const rates = await serverClient().hotel.rates.get({
// TODO: pass the correct hotel ID and all other parameters that should be included in the search
hotelId: searchParams.hotel,
})
// const rates = await serverClient().hotel.availability.getForHotel({
// hotelId: 811,
// roomStayStartDate: "2024-11-02",
// roomStayEndDate: "2024-11-03",
// adults: 1,
// })
const intl = await getIntl()
return (
<div>
{/* TODO: Add Hotel Listing Card */}
<div>Hotel Listing Card TBI</div>
<div className={styles.content}>
<div className={styles.main}>
<RoomSelection
rates={rates}
// TODO: Get real value
nrOfNights={1}
// TODO: Get real value
nrOfAdults={1}
/>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,4 @@
.container {
height: 76px;
width: 100%;
}

View File

@@ -0,0 +1,11 @@
import LoadingSpinner from "@/components/LoadingSpinner"
import styles from "./loading.module.css"
export default function LoadingBookingWidget() {
return (
<div className={styles.container}>
<LoadingSpinner />
</div>
)
}

View File

@@ -10,13 +10,11 @@ import { getLang } from "@/i18n/serverContext"
import styles from "./amenitiesList.module.css" import styles from "./amenitiesList.module.css"
import { HotelData } from "@/types/hotel" import type { AmenitiesListProps } from "@/types/components/hotelPage/amenities"
export default async function AmenitiesList({ export default async function AmenitiesList({
detailedFacilities, detailedFacilities,
}: { }: AmenitiesListProps) {
detailedFacilities: HotelData["data"]["attributes"]["detailedFacilities"]
}) {
const intl = await getIntl() const intl = await getIntl()
const sortedAmenities = detailedFacilities const sortedAmenities = detailedFacilities
.sort((a, b) => b.sortOrder - a.sortOrder) .sort((a, b) => b.sortOrder - a.sortOrder)

View File

@@ -0,0 +1,46 @@
import { activities } from "@/constants/routes/hotelPageParams"
import Card from "@/components/TempDesignSystem/Card"
import CardImage from "@/components/TempDesignSystem/Card/CardImage"
import Grids from "@/components/TempDesignSystem/Grids"
import { getLang } from "@/i18n/serverContext"
import styles from "./cardGrid.module.css"
import type { ActivityCard } from "@/types/trpc/routers/contentstack/hotelPage"
import type { CardProps } from "@/components/TempDesignSystem/Card/card"
export default function ActivitiesCardGrid(activitiesCard: ActivityCard) {
const lang = getLang()
const hasImage = activitiesCard.backgroundImage
const updatedCard: CardProps = {
...activitiesCard,
id: activities[lang],
theme: hasImage ? "image" : "primaryDark",
primaryButton: hasImage
? {
href: activitiesCard.contentPage.href,
title: activitiesCard.ctaText,
isExternal: false,
}
: undefined,
secondaryButton: hasImage
? undefined
: {
href: activitiesCard.contentPage.href,
title: activitiesCard.ctaText,
isExternal: false,
},
}
return (
<section id={updatedCard.id}>
<Grids.Stackable className={styles.desktopGrid}>
<Card {...updatedCard} className={styles.spanThree} />
</Grids.Stackable>
<Grids.Stackable className={styles.mobileGrid}>
<CardImage card={updatedCard} />
</Grids.Stackable>
</section>
)
}

View File

@@ -1,31 +1,32 @@
.one { .spanOne {
grid-column: span 1; grid-column: span 1;
} }
.two { .spanTwo {
grid-column: span 2; grid-column: span 2;
} }
.three { .spanThree {
grid-column: 1/-1; grid-column: span 3;
} }
.desktopGrid { section .desktopGrid {
display: none; display: none;
} }
.mobileGrid { section .mobileGrid {
display: grid; display: grid;
gap: var(--Spacing-x-quarter); gap: var(--Spacing-x-quarter);
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.desktopGrid { section .desktopGrid {
display: grid; display: grid;
gap: var(--Spacing-x1); gap: var(--Spacing-x1);
grid-template-columns: repeat(3, 1fr);
} }
.mobileGrid { section .mobileGrid {
display: none; display: none;
} }
} }

View File

@@ -1,29 +1,35 @@
import Card from "@/components/TempDesignSystem/Card" import Card from "@/components/TempDesignSystem/Card"
import CardImage from "@/components/TempDesignSystem/Card/CardImage" import CardImage from "@/components/TempDesignSystem/Card/CardImage"
import Grids from "@/components/TempDesignSystem/Grids" import Grids from "@/components/TempDesignSystem/Grids"
import { sortCards } from "@/utils/imageCard" import { filterFacilityCards, isFacilityCard } from "@/utils/facilityCards"
import styles from "./cardGrid.module.css" import styles from "./cardGrid.module.css"
import type { CardGridProps } from "@/types/components/hotelPage/facilities" import type {
CardGridProps,
FacilityCardType,
} from "@/types/components/hotelPage/facilities"
export default function FacilitiesCardGrid({
facilitiesCardGrid,
}: CardGridProps) {
const imageCard = filterFacilityCards(facilitiesCardGrid)
const nrCards = facilitiesCardGrid.length
function getCardClassName(card: FacilityCardType): string {
if (nrCards === 1) {
return styles.spanThree
} else if (nrCards === 2 && !isFacilityCard(card)) {
return styles.spanTwo
}
return styles.spanOne
}
export default async function CardGrid({ facility }: CardGridProps) {
const imageCard = sortCards(facility)
return ( return (
<section id={imageCard.card?.id}> <section id={imageCard.card.id}>
<Grids.Stackable className={styles.desktopGrid}> <Grids.Stackable className={styles.desktopGrid}>
{facility.map((card: any, idx: number) => ( {facilitiesCardGrid.map((card: FacilityCardType) => (
<Card <Card {...card} key={card.id} className={getCardClassName(card)} />
theme={card.theme || "primaryDark"}
key={idx}
scriptedTopTitle={card.scriptedTopTitle}
heading={card.heading}
bodyText={card.bodyText}
secondaryButton={card.secondaryButton}
primaryButton={card.primaryButton}
backgroundImage={card.backgroundImage}
className={styles[card.columnSpan]}
/>
))} ))}
</Grids.Stackable> </Grids.Stackable>
<Grids.Stackable className={styles.mobileGrid}> <Grids.Stackable className={styles.mobileGrid}>

View File

@@ -1,17 +1,56 @@
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
import { getIntl } from "@/i18n"
import { isFacilityCard, setFacilityCardGrids } from "@/utils/facilityCards"
import CardGrid from "./CardGrid" import ActivitiesCardGrid from "./CardGrid/ActivitiesCardGrid"
import FacilitiesCardGrid from "./CardGrid"
import styles from "./facilities.module.css" import styles from "./facilities.module.css"
import type { FacilityProps } from "@/types/components/hotelPage/facilities" import type {
Facilities,
FacilitiesProps,
FacilityCardType,
FacilityGrid,
} from "@/types/components/hotelPage/facilities"
export default async function Facilities({
facilities,
activitiesCard,
}: FacilitiesProps) {
const intl = await getIntl()
const facilityCardGrids = setFacilityCardGrids(facilities)
const translatedFacilityGrids: Facilities = facilityCardGrids.map(
(cardGrid: FacilityGrid) => {
return cardGrid.map((card: FacilityCardType) => {
if (isFacilityCard(card)) {
return {
...card,
heading: intl.formatMessage({ id: card.heading }),
secondaryButton: {
...card.secondaryButton,
title: intl.formatMessage({
id: card.secondaryButton.title,
}),
},
}
}
return card
})
}
)
export default async function Facilities({ facilities }: FacilityProps) {
return ( return (
<SectionContainer className={styles.grid}> <SectionContainer className={styles.grid}>
{facilities.map((facility: any, idx: number) => ( {translatedFacilityGrids.map((cardGrid: FacilityGrid) => (
<CardGrid key={`grid_${idx}`} facility={facility} /> <FacilitiesCardGrid
key={cardGrid[0].id}
facilitiesCardGrid={cardGrid}
/>
))} ))}
{activitiesCard && <ActivitiesCardGrid {...activitiesCard} />}
</SectionContainer> </SectionContainer>
) )
} }

View File

@@ -1,144 +0,0 @@
import {
activities,
meetingsAndConferences,
restaurantAndBar,
wellnessAndExercise,
} from "@/constants/routes/hotelPageParams"
import { getLang } from "@/i18n/serverContext"
import type { Facilities } from "@/types/components/hotelPage/facilities"
const lang = getLang()
/*
Most of this will be available from the api. Some will need to come from Contentstack. "Activities" will most likely come from Contentstack, which is prepped for.
*/
export const MOCK_FACILITIES: Facilities = [
[
{
id: "restaurant-and-bar",
theme: "primaryDark",
scriptedTopTitle: "Restaurant & Bar",
heading: "Enjoy relaxed restaurant experience",
secondaryButton: {
href: `?s=${restaurantAndBar[lang]}`,
title: "Read more & book a table",
isExternal: false,
},
columnSpan: "one",
},
{
backgroundImage: {
url: "https://imagevault.scandichotels.com/publishedmedia/79xttlmnum0kjbwhyh18/scandic-helsinki-hub-restaurant-food-tuna.jpg",
title: "scandic-helsinki-hub-restaurant-food-tuna.jpg",
meta: {
alt: "food in restaurant at scandic helsinki hub",
caption: "food in restaurant at scandic helsinki hub",
},
id: 81751,
dimensions: {
width: 5935,
height: 3957,
aspectRatio: 1.499873641647713,
},
},
columnSpan: "one",
},
{
backgroundImage: {
url: "https://imagevault.scandichotels.com/publishedmedia/48sb3eyhhzj727l2j1af/Scandic-helsinki-hub-II-centro-41.jpg",
meta: {
alt: "restaurant il centro at scandic helsinki hu",
caption: "restaurant il centro at scandic helsinki hub",
},
id: 82457,
title: "Scandic-helsinki-hub-II-centro-41.jpg",
dimensions: {
width: 4200,
height: 2800,
aspectRatio: 1.5,
},
},
columnSpan: "one",
},
],
[
{
backgroundImage: {
url: "https://imagevault.scandichotels.com/publishedmedia/csef06n329hjfiet1avj/Scandic-spectrum-8.jpg",
meta: {
alt: "man with a laptop",
caption: "man with a laptop",
},
id: 82713,
title: "Scandic-spectrum-8.jpg",
dimensions: {
width: 7499,
height: 4999,
aspectRatio: 1.500100020004001,
},
},
columnSpan: "two",
},
{
id: "meetings-and-conferences",
theme: "primaryDim",
scriptedTopTitle: "Meetings & Conferences",
heading: "Events that make an impression",
secondaryButton: {
href: `?s=${meetingsAndConferences[lang]}`,
title: "About meetings & conferences",
isExternal: false,
},
columnSpan: "one",
},
],
[
{
id: "wellness-and-exercise",
theme: "one",
scriptedTopTitle: "Wellness & Exercise",
heading: "Sauna and gym",
secondaryButton: {
href: `?s=${wellnessAndExercise[lang]}`,
title: "Read more about wellness & exercise",
isExternal: false,
},
columnSpan: "one",
},
{
backgroundImage: {
url: "https://imagevault.scandichotels.com/publishedmedia/69acct5i3pk5be7d6ub0/scandic-helsinki-hub-sauna.jpg",
meta: {
alt: "sauna at scandic helsinki hub",
caption: "sauna at scandic helsinki hub",
},
id: 81814,
title: "scandic-helsinki-hub-sauna.jpg",
dimensions: {
width: 4000,
height: 2667,
aspectRatio: 1.4998125234345707,
},
},
columnSpan: "one",
},
{
backgroundImage: {
url: "https://imagevault.scandichotels.com/publishedmedia/eu70o6z85idy24r92ysf/Scandic-Helsinki-Hub-gym-22.jpg",
meta: {
alt: "Gym at hotel Scandic Helsinki Hub",
caption: "Gym at hotel Scandic Helsinki Hub",
},
id: 81867,
title: "Scandic-Helsinki-Hub-gym-22.jpg",
dimensions: {
width: 4000,
height: 2667,
aspectRatio: 1.4998125234345707,
},
},
columnSpan: "one",
},
],
]

View File

@@ -1,35 +0,0 @@
import type { Facility } from "@/types/components/hotelPage/facilities"
import type { ActivityCard } from "@/types/trpc/routers/contentstack/hotelPage"
export function setActivityCard(activitiesCard: ActivityCard): Facility {
const hasImage = !!activitiesCard.background_image
return [
{
id: "activities",
theme: hasImage ? "image" : "primaryDark",
scriptedTopTitle: activitiesCard.scripted_title,
heading: activitiesCard.heading,
bodyText: activitiesCard.body_text,
backgroundImage: hasImage ? activitiesCard.background_image : undefined,
primaryButton: hasImage
? {
href: activitiesCard.contentPage.href,
title: activitiesCard.cta_text,
isExternal: false,
}
: undefined,
secondaryButton: hasImage
? undefined
: {
href: activitiesCard.contentPage.href,
title: activitiesCard.cta_text,
isExternal: false,
},
columnSpan: "three",
},
]
}
export function getCardTheme() {
// TODO
}

View File

@@ -10,7 +10,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./roomCard.module.css" import styles from "./roomCard.module.css"
import { RoomCardProps } from "@/types/components/hotelPage/roomCard" import type { RoomCardProps } from "@/types/components/hotelPage/roomCard"
export function RoomCard({ export function RoomCard({
badgeTextTransKey, badgeTextTransKey,

View File

@@ -10,10 +10,11 @@ import Button from "@/components/TempDesignSystem/Button"
import Grids from "@/components/TempDesignSystem/Grids" import Grids from "@/components/TempDesignSystem/Grids"
import { RoomCard } from "./RoomCard" import { RoomCard } from "./RoomCard"
import { RoomsProps } from "./types"
import styles from "./rooms.module.css" import styles from "./rooms.module.css"
import type { RoomsProps } from "./types"
export function Rooms({ rooms }: RoomsProps) { export function Rooms({ rooms }: RoomsProps) {
const intl = useIntl() const intl = useIntl()
const [allRoomsVisible, setAllRoomsVisible] = useState(false) const [allRoomsVisible, setAllRoomsVisible] = useState(false)

View File

@@ -6,21 +6,38 @@ import useHash from "@/hooks/useHash"
import styles from "./tabNavigation.module.css" import styles from "./tabNavigation.module.css"
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation" import {
HotelHashValues,
type TabNavigationProps,
} from "@/types/components/hotelPage/tabNavigation"
export default function TabNavigation() { export default function TabNavigation({ restaurantTitle }: TabNavigationProps) {
const hash = useHash() const hash = useHash()
const intl = useIntl() const intl = useIntl()
const hotelTabLinks: { href: HotelHashValues; text: string }[] = [ const hotelTabLinks: { href: HotelHashValues | string; text: string }[] = [
// TODO these titles will need to reflect the facility card titles, which will vary between hotels {
{ href: HotelHashValues.overview, text: "Overview" }, href: HotelHashValues.overview,
{ href: HotelHashValues.rooms, text: "Rooms" }, text: intl.formatMessage({ id: "Overview" }),
{ href: HotelHashValues.restaurant, text: "Restaurant & Bar" }, },
{ href: HotelHashValues.meetings, text: "Meetings & Conferences" }, { href: HotelHashValues.rooms, text: intl.formatMessage({ id: "Rooms" }) },
{ href: HotelHashValues.wellness, text: "Wellness & Exercise" }, {
{ href: HotelHashValues.activities, text: "Activities" }, href: HotelHashValues.restaurant,
{ href: HotelHashValues.faq, text: "FAQ" }, text: intl.formatMessage({ id: restaurantTitle }, { count: 1 }),
},
{
href: HotelHashValues.meetings,
text: intl.formatMessage({ id: "Meetings & Conferences" }),
},
{
href: HotelHashValues.wellness,
text: intl.formatMessage({ id: "Wellness & Exercise" }),
},
{
href: HotelHashValues.activities,
text: intl.formatMessage({ id: "Activities" }),
},
{ href: HotelHashValues.faq, text: intl.formatMessage({ id: "FAQ" }) },
] ]
return ( return (

View File

@@ -6,9 +6,8 @@ import SidePeekProvider from "@/components/SidePeekProvider"
import SidePeek from "@/components/TempDesignSystem/SidePeek" import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import { getRestaurantHeading } from "@/utils/facilityCards"
import { MOCK_FACILITIES } from "./Facilities/mockData"
import { setActivityCard } from "./Facilities/utils"
import DynamicMap from "./Map/DynamicMap" import DynamicMap from "./Map/DynamicMap"
import MapCard from "./Map/MapCard" import MapCard from "./Map/MapCard"
import MobileMapToggle from "./Map/MobileMapToggle" import MobileMapToggle from "./Map/MobileMapToggle"
@@ -45,10 +44,9 @@ export default async function HotelPage() {
roomCategories, roomCategories,
activitiesCard, activitiesCard,
pointsOfInterest, pointsOfInterest,
facilities,
} = hotelData } = hotelData
const facilities = [...MOCK_FACILITIES]
activitiesCard && facilities.push(setActivityCard(activitiesCard))
const topThreePois = pointsOfInterest.slice(0, 3) const topThreePois = pointsOfInterest.slice(0, 3)
const coordinates = { const coordinates = {
@@ -61,7 +59,9 @@ export default async function HotelPage() {
<div className={styles.hotelImages}> <div className={styles.hotelImages}>
<PreviewImages images={hotelImages} hotelName={hotelName} /> <PreviewImages images={hotelImages} hotelName={hotelName} />
</div> </div>
<TabNavigation /> <TabNavigation
restaurantTitle={getRestaurantHeading(hotelDetailedFacilities)}
/>
<main className={styles.mainSection}> <main className={styles.mainSection}>
<div className={styles.introContainer}> <div className={styles.introContainer}>
<IntroSection <IntroSection
@@ -119,7 +119,7 @@ export default async function HotelPage() {
<AmenitiesList detailedFacilities={hotelDetailedFacilities} /> <AmenitiesList detailedFacilities={hotelDetailedFacilities} />
</div> </div>
<Rooms rooms={roomCategories} /> <Rooms rooms={roomCategories} />
<Facilities facilities={facilities} /> <Facilities facilities={facilities} activitiesCard={activitiesCard} />
</main> </main>
{googleMapsApiKey ? ( {googleMapsApiKey ? (
<> <>

View File

@@ -10,6 +10,7 @@ import type { ClearSearchButtonProps } from "@/types/components/search"
export default function ClearSearchButton({ export default function ClearSearchButton({
getItemProps, getItemProps,
handleClearSearchHistory,
highlightedIndex, highlightedIndex,
index, index,
}: ClearSearchButtonProps) { }: ClearSearchButtonProps) {
@@ -18,13 +19,6 @@ export default function ClearSearchButton({
variant: index === highlightedIndex ? "active" : "default", variant: index === highlightedIndex ? "active" : "default",
}) })
function handleClick() {
// noop
// the click bubbles to handleOnSelect
// where selectedItem = "clear-search"
// which is the value for item below
}
return ( return (
<button <button
{...getItemProps({ {...getItemProps({
@@ -34,7 +28,7 @@ export default function ClearSearchButton({
item: "clear-search", item: "clear-search",
role: "button", role: "button",
})} })}
onClick={handleClick} onClick={handleClearSearchHistory}
tabIndex={0} tabIndex={0}
type="button" type="button"
> >

View File

@@ -20,6 +20,7 @@ import type { SearchListProps } from "@/types/components/search"
export default function SearchList({ export default function SearchList({
getItemProps, getItemProps,
getMenuProps, getMenuProps,
handleClearSearchHistory,
highlightedIndex, highlightedIndex,
isOpen, isOpen,
locations, locations,
@@ -125,6 +126,7 @@ export default function SearchList({
<Divider className={styles.divider} color="beige" /> <Divider className={styles.divider} color="beige" />
<ClearSearchButton <ClearSearchButton
getItemProps={getItemProps} getItemProps={getItemProps}
handleClearSearchHistory={handleClearSearchHistory}
highlightedIndex={highlightedIndex} highlightedIndex={highlightedIndex}
index={searchHistory.length} index={searchHistory.length}
/> />
@@ -161,6 +163,7 @@ export default function SearchList({
<Divider className={styles.divider} color="beige" /> <Divider className={styles.divider} color="beige" />
<ClearSearchButton <ClearSearchButton
getItemProps={getItemProps} getItemProps={getItemProps}
handleClearSearchHistory={handleClearSearchHistory}
highlightedIndex={highlightedIndex} highlightedIndex={highlightedIndex}
index={searchHistory.length} index={searchHistory.length}
/> />

View File

@@ -43,6 +43,11 @@ export default function Search({ locations }: SearchProps) {
[locations] [locations]
) )
function handleClearSearchHistory() {
localStorage.removeItem(localStorageKey)
dispatch({ type: ActionType.CLEAR_HISTORY_LOCATIONS })
}
function handleOnBlur() { function handleOnBlur() {
if (!value && state.searchData?.name) { if (!value && state.searchData?.name) {
setValue(name, state.searchData.name) setValue(name, state.searchData.name)
@@ -79,11 +84,8 @@ export default function Search({ locations }: SearchProps) {
} }
} }
function handleOnSelect(selectedItem: Location | null | "clear-search") { function handleOnSelect(selectedItem: Location | null) {
if (selectedItem === "clear-search") { if (selectedItem) {
localStorage.removeItem(localStorageKey)
dispatch({ type: ActionType.CLEAR_HISTORY_LOCATIONS })
} else if (selectedItem) {
const stringified = JSON.stringify(selectedItem) const stringified = JSON.stringify(selectedItem)
setValue("location", encodeURIComponent(stringified)) setValue("location", encodeURIComponent(stringified))
sessionStorage.setItem(sessionStorageKey, stringified) sessionStorage.setItem(sessionStorageKey, stringified)
@@ -167,6 +169,7 @@ export default function Search({ locations }: SearchProps) {
<SearchList <SearchList
getItemProps={getItemProps} getItemProps={getItemProps}
getMenuProps={getMenuProps} getMenuProps={getMenuProps}
handleClearSearchHistory={handleClearSearchHistory}
highlightedIndex={highlightedIndex} highlightedIndex={highlightedIndex}
isOpen={isOpen} isOpen={isOpen}
locations={state.locations} locations={state.locations}

View File

@@ -9,6 +9,7 @@ import useDropdownStore from "@/stores/main-menu"
import { GiftIcon, SearchIcon, ServiceIcon } from "@/components/Icons" import { GiftIcon, SearchIcon, ServiceIcon } from "@/components/Icons"
import LanguageSwitcher from "@/components/LanguageSwitcher" import LanguageSwitcher from "@/components/LanguageSwitcher"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import useMediaQuery from "@/hooks/useMediaQuery"
import HeaderLink from "../../HeaderLink" import HeaderLink from "../../HeaderLink"
@@ -37,6 +38,13 @@ export default function MobileMenu({
isHeaderLanguageSwitcherMobileOpen || isHeaderLanguageSwitcherMobileOpen ||
isFooterLanguageSwitcherOpen isFooterLanguageSwitcherOpen
const isAboveMobile = useMediaQuery("(min-width: 768px)")
useEffect(() => {
if (isAboveMobile && isHamburgerMenuOpen) {
toggleDropdown(DropdownTypeEnum.HamburgerMenu)
}
}, [isAboveMobile, isHamburgerMenuOpen, toggleDropdown])
useHandleKeyUp((event: KeyboardEvent) => { useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && isHamburgerMenuOpen) { if (event.key === "Escape" && isHamburgerMenuOpen) {
toggleDropdown(DropdownTypeEnum.HamburgerMenu) toggleDropdown(DropdownTypeEnum.HamburgerMenu)

View File

@@ -97,7 +97,8 @@
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.hamburger { .hamburger,
.modal {
display: none; display: none;
} }
} }

View File

@@ -1,11 +1,13 @@
"use client" "use client"
import { useRef } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import useDropdownStore from "@/stores/main-menu" import useDropdownStore from "@/stores/main-menu"
import { ChevronDownIcon } from "@/components/Icons" import { ChevronDownIcon } from "@/components/Icons"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useClickOutside from "@/hooks/useClickOutside"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import { getInitials } from "@/utils/user" import { getInitials } from "@/utils/user"
@@ -22,8 +24,10 @@ export default function MyPagesMenu({
membership, membership,
navigation, navigation,
user, user,
membershipLevel,
}: MyPagesMenuProps) { }: MyPagesMenuProps) {
const intl = useIntl() const intl = useIntl()
const myPagesMenuRef = useRef<HTMLDivElement>(null)
const { toggleDropdown, isMyPagesMenuOpen } = useDropdownStore() const { toggleDropdown, isMyPagesMenuOpen } = useDropdownStore()
@@ -33,8 +37,12 @@ export default function MyPagesMenu({
} }
}) })
useClickOutside(myPagesMenuRef, isMyPagesMenuOpen, () => {
toggleDropdown(DropdownTypeEnum.MyPagesMenu)
})
return ( return (
<div className={styles.myPagesMenu}> <div className={styles.myPagesMenu} ref={myPagesMenuRef}>
<MainMenuButton <MainMenuButton
onClick={() => toggleDropdown(DropdownTypeEnum.MyPagesMenu)} onClick={() => toggleDropdown(DropdownTypeEnum.MyPagesMenu)}
> >
@@ -50,6 +58,7 @@ export default function MyPagesMenu({
{isMyPagesMenuOpen ? ( {isMyPagesMenuOpen ? (
<div className={styles.dropdown}> <div className={styles.dropdown}>
<MyPagesMenuContent <MyPagesMenuContent
membershipLevel={membershipLevel}
navigation={navigation} navigation={navigation}
user={user} user={user}
membership={membership} membership={membership}

View File

@@ -2,9 +2,7 @@
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { MembershipLevelEnum } from "@/constants/membershipLevels"
import { logout } from "@/constants/routes/handleAuth" import { logout } from "@/constants/routes/handleAuth"
import { trpc } from "@/lib/trpc/client"
import { ArrowRightIcon } from "@/components/Icons" import { ArrowRightIcon } from "@/components/Icons"
import Divider from "@/components/TempDesignSystem/Divider" import Divider from "@/components/TempDesignSystem/Divider"
@@ -23,18 +21,12 @@ export default function MyPagesMenuContent({
navigation, navigation,
toggleOpenStateFn, toggleOpenStateFn,
user, user,
membershipLevel,
}: MyPagesMenuContentProps) { }: MyPagesMenuContentProps) {
const intl = useIntl() const intl = useIntl()
const lang = useLang() const lang = useLang()
const myPagesMenuContentRef = useTrapFocus() const myPagesMenuContentRef = useTrapFocus()
const membershipLevel = trpc.contentstack.loyaltyLevels.byLevel.useQuery(
{
level: MembershipLevelEnum[membership?.membershipLevel!],
},
{ enabled: !!membership?.membershipLevel }
).data
const membershipPoints = membership?.currentPoints const membershipPoints = membership?.currentPoints
const introClassName = const introClassName =
membershipLevel && membershipPoints membershipLevel && membershipPoints

View File

@@ -1,3 +1,4 @@
import { MembershipLevelEnum } from "@/constants/membershipLevels"
import { myPages } from "@/constants/routes/myPages" import { myPages } from "@/constants/routes/myPages"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
@@ -20,16 +21,24 @@ export default async function MyPagesMenuWrapper() {
serverClient().user.safeMembershipLevel(), serverClient().user.safeMembershipLevel(),
]) ])
const membershipLevel = membership?.membershipLevel
? await serverClient().contentstack.loyaltyLevels.byLevel({
level: MembershipLevelEnum[membership.membershipLevel],
})
: null
return ( return (
<> <>
{user ? ( {user ? (
<> <>
<MyPagesMenu <MyPagesMenu
membershipLevel={membershipLevel}
membership={membership} membership={membership}
navigation={myPagesNavigation} navigation={myPagesNavigation}
user={user} user={user}
/> />
<MyPagesMobileMenu <MyPagesMobileMenu
membershipLevel={membershipLevel}
membership={membership} membership={membership}
navigation={myPagesNavigation} navigation={myPagesNavigation}
user={user} user={user}

View File

@@ -7,6 +7,7 @@ import { useIntl } from "react-intl"
import useDropdownStore from "@/stores/main-menu" import useDropdownStore from "@/stores/main-menu"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import useMediaQuery from "@/hooks/useMediaQuery"
import { getInitials } from "@/utils/user" import { getInitials } from "@/utils/user"
import Avatar from "../Avatar" import Avatar from "../Avatar"
@@ -19,6 +20,7 @@ import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu" import type { MyPagesMenuProps } from "@/types/components/header/myPagesMenu"
export default function MyPagesMobileMenu({ export default function MyPagesMobileMenu({
membershipLevel,
membership, membership,
navigation, navigation,
user, user,
@@ -32,6 +34,13 @@ export default function MyPagesMobileMenu({
} }
}) })
const isAboveMobile = useMediaQuery("(min-width: 768px)")
useEffect(() => {
if (isAboveMobile && isMyPagesMobileMenuOpen) {
toggleDropdown(DropdownTypeEnum.MyPagesMobileMenu)
}
}, [isAboveMobile, isMyPagesMobileMenuOpen, toggleDropdown])
// Making sure the menu is always opened at the top of the page, just below the header. // Making sure the menu is always opened at the top of the page, just below the header.
useEffect(() => { useEffect(() => {
if (isMyPagesMobileMenuOpen) { if (isMyPagesMobileMenuOpen) {
@@ -54,6 +63,7 @@ export default function MyPagesMobileMenu({
aria-label={intl.formatMessage({ id: "My pages menu" })} aria-label={intl.formatMessage({ id: "My pages menu" })}
> >
<MyPagesMenuContent <MyPagesMenuContent
membershipLevel={membershipLevel}
membership={membership} membership={membership}
navigation={navigation} navigation={navigation}
user={user} user={user}

View File

@@ -103,6 +103,7 @@ export default function MegaMenu({
scriptedTopTitle={card.scripted_top_title} scriptedTopTitle={card.scripted_top_title}
onPrimaryButtonClick={handleNavigate} onPrimaryButtonClick={handleNavigate}
onSecondaryButtonClick={handleNavigate} onSecondaryButtonClick={handleNavigate}
imageGradient
theme="image" theme="image"
/> />
</div> </div>

View File

@@ -1,9 +1,12 @@
"use client" "use client"
import { useRef } from "react"
import useDropdownStore from "@/stores/main-menu" import useDropdownStore from "@/stores/main-menu"
import { ChevronDownIcon, ChevronRightIcon } from "@/components/Icons" import { ChevronDownIcon, ChevronRightIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
import useClickOutside from "@/hooks/useClickOutside"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import MainMenuButton from "../../MainMenuButton" import MainMenuButton from "../../MainMenuButton"
@@ -15,8 +18,10 @@ import type { NavigationMenuItemProps } from "@/types/components/header/navigati
export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) { export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
const { openMegaMenu, toggleMegaMenu } = useDropdownStore() const { openMegaMenu, toggleMegaMenu } = useDropdownStore()
const megaMenuRef = useRef<HTMLDivElement>(null)
const { submenu, title, link, seeAllLink, card } = item const { submenu, title, link, seeAllLink, card } = item
const isMegaMenuOpen = openMegaMenu === title const megaMenuTitle = `${title}-${isMobile ? "mobile" : "desktop"}`
const isMegaMenuOpen = openMegaMenu === megaMenuTitle
useHandleKeyUp((event: KeyboardEvent) => { useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && isMegaMenuOpen) { if (event.key === "Escape" && isMegaMenuOpen) {
@@ -24,10 +29,14 @@ export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
} }
}) })
useClickOutside(megaMenuRef, isMegaMenuOpen && !isMobile, () => {
toggleMegaMenu(false)
})
return submenu.length ? ( return submenu.length ? (
<> <>
<MainMenuButton <MainMenuButton
onClick={() => toggleMegaMenu(title)} onClick={() => toggleMegaMenu(megaMenuTitle)}
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : styles.desktop}`} className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : styles.desktop}`}
> >
{title} {title}
@@ -41,6 +50,7 @@ export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
)} )}
</MainMenuButton> </MainMenuButton>
<div <div
ref={megaMenuRef}
className={`${styles.dropdown} ${isMegaMenuOpen ? styles.isExpanded : ""}`} className={`${styles.dropdown} ${isMegaMenuOpen ? styles.isExpanded : ""}`}
> >
{isMegaMenuOpen ? ( {isMegaMenuOpen ? (

View File

@@ -0,0 +1,7 @@
.form {
display: grid;
gap: var(--Spacing-x2);
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
padding-bottom: var(--Spacing-x3);
width: min(600px, 100%);
}

View File

@@ -0,0 +1,72 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { KingBedIcon } from "@/components/Icons"
import RadioCard from "@/components/TempDesignSystem/Form/Card/Radio"
import { bedTypeSchema } from "./schema"
import styles from "./bedOptions.module.css"
import type { BedTypeSchema } from "@/types/components/enterDetails/bedType"
import { bedTypeEnum } from "@/types/enums/bedType"
export default function BedType() {
const intl = useIntl()
const methods = useForm<BedTypeSchema>({
criteriaMode: "all",
mode: "all",
resolver: zodResolver(bedTypeSchema),
reValidateMode: "onChange",
})
// @ts-expect-error - Types mismatch docs as this is
// a pattern that is allowed https://formatjs.io/docs/react-intl/api#usage
const text = intl.formatMessage(
{ id: "<b>Included</b> (based on availability)" },
{ b: (str) => <b>{str}</b> }
)
return (
<FormProvider {...methods}>
<form className={styles.form}>
<RadioCard
Icon={KingBedIcon}
iconWidth={46}
id={bedTypeEnum.KING}
name="bed"
subtitle={intl.formatMessage(
{ id: "{width} cm × {length} cm" },
{
length: "210",
width: "180",
}
)}
text={text}
title={intl.formatMessage({ id: "King bed" })}
value={bedTypeEnum.KING}
/>
<RadioCard
Icon={KingBedIcon}
iconWidth={46}
id={bedTypeEnum.QUEEN}
name="bed"
subtitle={intl.formatMessage(
{ id: "{width} cm × {length} cm" },
{
length: "200",
width: "160",
}
)}
text={text}
title={intl.formatMessage({ id: "Queen bed" })}
value={bedTypeEnum.QUEEN}
/>
</form>
</FormProvider>
)
}

View File

@@ -0,0 +1,7 @@
import { z } from "zod"
import { bedTypeEnum } from "@/types/enums/bedType"
export const bedTypeSchema = z.object({
bed: z.nativeEnum(bedTypeEnum),
})

View File

@@ -0,0 +1,7 @@
.form {
display: grid;
gap: var(--Spacing-x2);
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
padding-bottom: var(--Spacing-x3);
width: min(600px, 100%);
}

View File

@@ -0,0 +1,70 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { BreakfastIcon, NoBreakfastIcon } from "@/components/Icons"
import RadioCard from "@/components/TempDesignSystem/Form/Card/Radio"
import { breakfastSchema } from "./schema"
import styles from "./breakfast.module.css"
import type { BreakfastSchema } from "@/types/components/enterDetails/breakfast"
import { breakfastEnum } from "@/types/enums/breakfast"
export default function Breakfast() {
const intl = useIntl()
const methods = useForm<BreakfastSchema>({
criteriaMode: "all",
mode: "all",
resolver: zodResolver(breakfastSchema),
reValidateMode: "onChange",
})
return (
<FormProvider {...methods}>
<form className={styles.form}>
<RadioCard
Icon={BreakfastIcon}
id={breakfastEnum.BREAKFAST}
name="breakfast"
// @ts-expect-error - Types mismatch docs as this is
// a pattern that is allowed https://formatjs.io/docs/react-intl/api#usage
subtitle={intl.formatMessage(
{ id: "<b>{amount} {currency}</b>/night per adult" },
{
amount: "150",
b: (str) => <b>{str}</b>,
currency: "SEK",
}
)}
text={intl.formatMessage({
id: "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.",
})}
title={intl.formatMessage({ id: "Breakfast buffet" })}
value={breakfastEnum.BREAKFAST}
/>
<RadioCard
Icon={NoBreakfastIcon}
id={breakfastEnum.NO_BREAKFAST}
name="breakfast"
subtitle={intl.formatMessage(
{ id: "{amount} {currency}" },
{
amount: "0",
currency: "SEK",
}
)}
text={intl.formatMessage({
id: "You can always change your mind later and add breakfast at the hotel.",
})}
title={intl.formatMessage({ id: "No breakfast" })}
value={breakfastEnum.NO_BREAKFAST}
/>
</form>
</FormProvider>
)
}

View File

@@ -0,0 +1,7 @@
import { z } from "zod"
import { breakfastEnum } from "@/types/enums/breakfast"
export const breakfastSchema = z.object({
breakfast: z.nativeEnum(breakfastEnum),
})

View File

@@ -0,0 +1,25 @@
.container {
display: grid;
gap: var(--Spacing-x2);
padding: var(--Spacing-x3) 0px;
}
.form {
display: grid;
gap: var(--Spacing-x2);
grid-template-columns: 1fr 1fr;
width: min(100%, 600px);
}
.country,
.email,
.phone {
grid-column: 1/-1;
}
.footer {
display: grid;
gap: var(--Spacing-x3);
justify-items: flex-start;
margin-top: var(--Spacing-x1);
}

View File

@@ -0,0 +1,118 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import Button from "@/components/TempDesignSystem/Button"
import CheckboxCard from "@/components/TempDesignSystem/Form/Card/Checkbox"
import CountrySelect from "@/components/TempDesignSystem/Form/Country"
import Input from "@/components/TempDesignSystem/Form/Input"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import Body from "@/components/TempDesignSystem/Text/Body"
import { detailsSchema, signedInDetailsSchema } from "./schema"
import styles from "./details.module.css"
import type {
DetailsProps,
DetailsSchema,
} from "@/types/components/enterDetails/details"
export default function Details({ user }: DetailsProps) {
const intl = useIntl()
const list = [
{ title: intl.formatMessage({ id: "Earn bonus nights & points" }) },
{ title: intl.formatMessage({ id: "Get member benefits & offers" }) },
{ title: intl.formatMessage({ id: "Join at no cost" }) },
]
const methods = useForm<DetailsSchema>({
defaultValues: {
countryCode: user?.address?.countryCode ?? "",
email: user?.email ?? "",
firstname: user?.firstName ?? "",
lastname: user?.lastName ?? "",
phoneNumber: user?.phoneNumber ?? "",
},
criteriaMode: "all",
mode: "all",
resolver: zodResolver(user ? signedInDetailsSchema : detailsSchema),
reValidateMode: "onChange",
})
return (
<FormProvider {...methods}>
<section className={styles.container}>
<header>
<Body color="uiTextHighContrast" textTransform="bold">
{intl.formatMessage({ id: "Guest information" })}
</Body>
</header>
<form className={styles.form}>
<Input
label={intl.formatMessage({ id: "Firstname" })}
name="firstname"
readOnly={!!user}
registerOptions={{ required: true }}
/>
<Input
label={intl.formatMessage({ id: "Lastname" })}
name="lastname"
readOnly={!!user}
registerOptions={{ required: true }}
/>
<CountrySelect
className={styles.country}
label={intl.formatMessage({ id: "Country" })}
name="countryCode"
readOnly={!!user}
registerOptions={{ required: true }}
/>
<Input
className={styles.email}
label={intl.formatMessage({ id: "Email address" })}
name="email"
readOnly={!!user}
registerOptions={{ required: true }}
/>
<Phone
className={styles.phone}
label={intl.formatMessage({ id: "Phone number" })}
name="phoneNumber"
readOnly={!!user}
registerOptions={{ required: true }}
/>
</form>
<footer className={styles.footer}>
{user ? null : (
<CheckboxCard
list={list}
saving
subtitle={intl.formatMessage(
{
id: "{difference}{amount} {currency}",
},
{
amount: "491",
currency: "SEK",
difference: "-",
}
)}
title={intl.formatMessage({ id: "Join Scandic Friends" })}
/>
)}
<Button
disabled={!methods.formState.isValid}
intent="secondary"
size="small"
theme="base"
>
{intl.formatMessage({ id: "Proceed to payment method" })}
</Button>
</footer>
</section>
</FormProvider>
)
}

View File

@@ -0,0 +1,19 @@
import { z } from "zod"
import { phoneValidator } from "@/utils/phoneValidator"
export const detailsSchema = z.object({
countryCode: z.string(),
email: z.string().email(),
firstname: z.string(),
lastname: z.string(),
phoneNumber: phoneValidator(),
})
export const signedInDetailsSchema = z.object({
countryCode: z.string().optional(),
email: z.string().email().optional(),
firstname: z.string().optional(),
lastname: z.string().optional(),
phoneNumber: phoneValidator().optional(),
})

View File

@@ -48,10 +48,10 @@ export default async function HotelCard({ hotel }: HotelCardProps) {
<Title as="h4" textTransform="capitalize"> <Title as="h4" textTransform="capitalize">
{hotelData.name} {hotelData.name}
</Title> </Title>
<Footnote color="textMediumContrast"> <Footnote color="uiTextMediumContrast">
{`${hotelData.address.streetAddress}, ${hotelData.address.city}`} {`${hotelData.address.streetAddress}, ${hotelData.address.city}`}
</Footnote> </Footnote>
<Footnote color="textMediumContrast"> <Footnote color="uiTextMediumContrast">
{`${hotelData.location.distanceToCentre} ${intl.formatMessage({ id: "km to city center" })}`} {`${hotelData.location.distanceToCentre} ${intl.formatMessage({ id: "km to city center" })}`}
</Footnote> </Footnote>
</section> </section>
@@ -79,7 +79,7 @@ export default async function HotelCard({ hotel }: HotelCardProps) {
{price?.regularAmount} {price?.currency} / {price?.regularAmount} {price?.currency} /
{intl.formatMessage({ id: "night" })} {intl.formatMessage({ id: "night" })}
</Caption> </Caption>
<Footnote color="textMediumContrast">approx 280 eur</Footnote> <Footnote color="uiTextMediumContrast">approx 280 eur</Footnote>
</div> </div>
<div> <div>
<Chip intent="primary" className={styles.member}> <Chip intent="primary" className={styles.member}>
@@ -90,7 +90,7 @@ export default async function HotelCard({ hotel }: HotelCardProps) {
{price?.memberAmount} {price?.currency} / {price?.memberAmount} {price?.currency} /
{intl.formatMessage({ id: "night" })} {intl.formatMessage({ id: "night" })}
</Caption> </Caption>
<Footnote color="textMediumContrast">approx 280 eur</Footnote> <Footnote color="uiTextMediumContrast">approx 280 eur</Footnote>
</div> </div>
<Button <Button
asChild asChild

View File

@@ -5,11 +5,10 @@ import RoomCard from "./RoomCard"
import styles from "./roomSelection.module.css" import styles from "./roomSelection.module.css"
import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/section" import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
export default function RoomSelection({ export default function RoomSelection({
alternatives, rates,
nextPath,
nrOfNights, nrOfNights,
nrOfAdults, nrOfAdults,
}: RoomSelectionProps) { }: RoomSelectionProps) {
@@ -21,17 +20,17 @@ export default function RoomSelection({
const queryParams = new URLSearchParams(searchParams) const queryParams = new URLSearchParams(searchParams)
queryParams.set("roomClass", e.currentTarget.roomClass?.value) queryParams.set("roomClass", e.currentTarget.roomClass?.value)
queryParams.set("flexibility", e.currentTarget.flexibility?.value) queryParams.set("flexibility", e.currentTarget.flexibility?.value)
router.push(`${nextPath}?${queryParams}`) router.push(`select-bed?${queryParams}`)
} }
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<ul className={styles.roomList}> <ul className={styles.roomList}>
{alternatives.map((room) => ( {rates.map((room) => (
<li key={room.id}> <li key={room.id}>
<form <form
method="GET" method="GET"
action={`${nextPath}?${searchParams}`} action={`select-bed?${searchParams}`}
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
<input <input
@@ -50,6 +49,7 @@ export default function RoomSelection({
</li> </li>
))} ))}
</ul> </ul>
<div className={styles.summary}>This is summary</div>
</div> </div>
) )
} }

View File

@@ -21,3 +21,10 @@
position: fixed; position: fixed;
width: 0; width: 0;
} }
.summary {
position: fixed;
bottom: 0;
left: 0;
right: 0;
}

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function BreakfastIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="33"
viewBox="0 0 32 33"
width="32"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
height="33"
id="mask0_5171_13483"
maskUnits="userSpaceOnUse"
style={{ maskType: "alpha" }}
width="32"
x="0"
y="0"
>
<rect y="0.5" width="32" height="32" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_5171_13483)">
<path
d="M3 27H21.2167V28.25C21.2167 28.5944 21.0944 28.8889 20.85 29.1333C20.6056 29.3778 20.3111 29.5 19.9667 29.5H4.25C3.90556 29.5 3.61111 29.3778 3.36667 29.1333C3.12222 28.8889 3 28.5944 3 28.25V27ZM3 24.4333V23.1833C3 22.8389 3.12222 22.5444 3.36667 22.3C3.61111 22.0556 3.90556 21.9333 4.25 21.9333H9.53333V20.75C9.53333 20.4056 9.65556 20.1111 9.9 19.8667C10.1444 19.6222 10.4389 19.5 10.7833 19.5H13.4333C13.7778 19.5 14.0722 19.6222 14.3167 19.8667C14.5611 20.1111 14.6833 20.4056 14.6833 20.75V21.9333H19.9667C20.3111 21.9333 20.6056 22.0556 20.85 22.3C21.0944 22.5444 21.2167 22.8389 21.2167 23.1833V24.4333H3ZM23.9167 21.4667C23.15 20.6444 22.5278 19.7889 22.05 18.9C21.5722 18.0111 21.3333 16.9556 21.3333 15.7333V4.75C21.3333 4.40556 21.4556 4.11111 21.7 3.86667C21.9444 3.62222 22.2389 3.5 22.5833 3.5H27.75C28.0944 3.5 28.3889 3.62222 28.6333 3.86667C28.8778 4.11111 29 4.40556 29 4.75V15.7333C29 16.9556 28.7639 18.0139 28.2917 18.9083C27.8194 19.8028 27.1944 20.6556 26.4167 21.4667V27H27.7167C28.0611 27 28.3556 27.1222 28.6 27.3667C28.8444 27.6111 28.9667 27.9056 28.9667 28.25C28.9667 28.5944 28.8444 28.8889 28.6 29.1333C28.3556 29.3778 28.0611 29.5 27.7167 29.5H25.1667C24.8222 29.5 24.5278 29.3778 24.2833 29.1333C24.0389 28.8889 23.9167 28.5944 23.9167 28.25V21.4667ZM23.8333 11.3H26.5V6H23.8333V11.3ZM25.1667 19.1667C25.5667 18.7111 25.8889 18.1806 26.1333 17.575C26.3778 16.9694 26.5 16.3556 26.5 15.7333V13.8H23.8333V15.7333C23.8333 16.3556 23.95 16.9694 24.1833 17.575C24.4167 18.1806 24.7444 18.7111 25.1667 19.1667Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function HeartIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
height="24"
id="mask0_69_3298"
maskUnits="userSpaceOnUse"
style={{ maskType: "alpha" }}
width="24"
x="0"
y="0"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3298)">
<path
d="M12 20.0875C11.775 20.0875 11.5521 20.0479 11.3313 19.9687C11.1104 19.8896 10.9125 19.7666 10.7375 19.6L9.1625 18.1625C7.3625 16.5208 5.76042 14.9125 4.35625 13.3375C2.95208 11.7625 2.25 10.0416 2.25 8.17498C2.25 6.65816 2.75865 5.39145 3.77595 4.37485C4.79327 3.35827 6.06087 2.84998 7.57875 2.84998C8.43458 2.84998 9.24792 3.03539 10.0188 3.40623C10.7896 3.77706 11.45 4.29998 12 4.97498C12.5667 4.29998 13.231 3.77706 13.993 3.40623C14.755 3.03539 15.5657 2.84998 16.425 2.84998C17.9418 2.84998 19.2085 3.35827 20.2251 4.37485C21.2417 5.39145 21.75 6.65816 21.75 8.17498C21.75 10.0416 21.05 11.7646 19.65 13.3437C18.25 14.9229 16.6458 16.5291 14.8375 18.1625L13.2625 19.6C13.0875 19.7666 12.8896 19.8896 12.6687 19.9687C12.4479 20.0479 12.225 20.0875 12 20.0875ZM11.107 6.89753C10.6773 6.20749 10.1729 5.67289 9.59375 5.29373C9.01458 4.91456 8.33962 4.72498 7.56885 4.72498C6.5849 4.72498 5.76493 5.05206 5.10895 5.70623C4.45298 6.36039 4.125 7.18202 4.125 8.1711C4.125 9.0283 4.42917 9.93908 5.0375 10.9035C5.64583 11.8678 6.37083 12.8041 7.2125 13.7125C8.05417 14.6208 8.92083 15.4687 9.8125 16.2562C10.7042 17.0437 11.4333 17.6916 12 18.2C12.5667 17.6916 13.2958 17.0437 14.1875 16.2562C15.0792 15.4687 15.9458 14.6208 16.7875 13.7125C17.6292 12.8041 18.3542 11.8678 18.9625 10.9035C19.5708 9.93908 19.875 9.0283 19.875 8.1711C19.875 7.18202 19.547 6.36039 18.8911 5.70623C18.2351 5.05206 17.4151 4.72498 16.4311 4.72498C15.6604 4.72498 14.9833 4.91456 14.4 5.29373C13.8167 5.67289 13.3102 6.20749 12.8805 6.89753C12.7768 7.06583 12.6466 7.18956 12.4899 7.26873C12.3331 7.34789 12.1685 7.38748 11.9961 7.38748C11.8237 7.38748 11.6583 7.34789 11.5 7.26873C11.3417 7.18956 11.2107 7.06583 11.107 6.89753Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,27 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function KingBedIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="33"
viewBox="0 0 46 33"
width="46"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g id="bed king">
<path
clipRule="evenodd"
fillRule="evenodd"
id="Shape"
d="M43.4073 15.8263C44.9964 17.3421 46 19.4474 46 22.2263V22.3947V22.4789V30.9C46 31.7421 45.2473 32.5 44.3273 32.5H42.2364C41.3164 32.5 40.5636 31.7421 40.5636 30.9V27.1105H5.52V30.9C5.52 31.7421 4.76727 32.5 3.84727 32.5H1.67273C0.752727 32.5 0 31.7421 0 30.9V22.3947C0 20.2053 0.501818 18.5211 1.25455 17.0895V17.0053V1.34211C1.25455 0.921053 1.67273 0.5 2.09091 0.5H42.5709C43.0727 0.5 43.4073 0.836842 43.4073 1.34211V15.8263ZM1.67273 21.5526H44.3273C44.0764 18.1842 42.0691 16.1632 39.0582 14.8158C38.9745 14.8158 38.8909 14.8158 38.8073 14.7316C34.4582 13.0474 28.1018 13.0474 22.1636 13.0474C10.5382 13.0474 2.17455 13.7211 1.67273 21.5526ZM5.93818 13.3V11.9526C5.93818 6.81579 11.2909 6.22632 13.6327 6.22632C15.8909 6.22632 20.9927 6.73158 21.3273 11.4474C15.8909 11.4474 10.12 11.5316 5.93818 13.3ZM23 11.3632V11.4474C28.1018 11.4474 33.8727 11.5316 38.3891 13.0474V11.3632C38.3891 6.22632 33.0364 5.63684 30.6945 5.63684C28.3527 5.63684 23 6.22632 23 11.3632ZM41.7345 2.18421V14.4789C41.2327 14.1421 40.6473 13.8895 40.0618 13.6368V11.2789C40.0618 6.73158 36.5491 3.95263 30.6945 3.95263C26.3455 3.95263 23.2509 5.46842 21.9964 8.16316C20.5745 5.97368 17.7309 4.71053 13.7164 4.71053C7.86182 4.71053 4.34909 7.40526 4.34909 12.0368V14.1421C3.84727 14.4789 3.42909 14.8158 3.01091 15.1526V2.18421H41.7345ZM1.67273 30.9H3.84727V27.1105H1.67273V30.9ZM1.67273 25.5105V23.1526H44.3273V25.5105H1.67273ZM42.1527 27.1105V30.9H44.3273V27.1105H42.1527Z"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function NoBreakfastIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="33"
viewBox="0 0 32 33"
width="32"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
id="mask0_5171_21315"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="32"
height="33"
>
<rect y="0.5" width="32" height="32" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_5171_21315)">
<path
d="M28.4824 25.3833L26.1824 23.0833L27.4991 10H15.0324L14.8991 8.9C14.8546 8.52223 14.9546 8.19445 15.1991 7.91667C15.4435 7.63889 15.7546 7.5 16.1324 7.5H21.2324V3.48334C21.2324 3.13889 21.3546 2.84445 21.5991 2.6C21.8435 2.35556 22.138 2.23334 22.4824 2.23334C22.8269 2.23334 23.1213 2.35556 23.3658 2.6C23.6102 2.84445 23.7324 3.13889 23.7324 3.48334V7.5H28.8824C29.2602 7.5 29.5741 7.63334 29.8241 7.9C30.0741 8.16667 30.1769 8.48889 30.1324 8.86667L28.4824 25.3833ZM26.3324 30.35L2.18242 6.21667C1.92687 5.96111 1.79909 5.66389 1.79909 5.325C1.79909 4.98612 1.92687 4.68889 2.18242 4.43334C2.43798 4.17778 2.7352 4.05 3.07409 4.05C3.41298 4.05 3.7102 4.17778 3.96576 4.43334L28.1158 28.5833C28.3713 28.8389 28.4991 29.1333 28.4991 29.4667C28.4991 29.8 28.3713 30.0944 28.1158 30.35C27.8602 30.6056 27.563 30.7333 27.2241 30.7333C26.8852 30.7333 26.588 30.6056 26.3324 30.35ZM2.98242 25.75C2.63798 25.75 2.34353 25.6278 2.09909 25.3833C1.85464 25.1389 1.73242 24.8444 1.73242 24.5C1.73242 24.1556 1.85464 23.8611 2.09909 23.6167C2.34353 23.3722 2.63798 23.25 2.98242 23.25H19.9491C20.2935 23.25 20.588 23.3722 20.8324 23.6167C21.0769 23.8611 21.1991 24.1556 21.1991 24.5C21.1991 24.8444 21.0769 25.1389 20.8324 25.3833C20.588 25.6278 20.2935 25.75 19.9491 25.75H2.98242ZM2.98242 30.8333C2.63798 30.8333 2.34353 30.7111 2.09909 30.4667C1.85464 30.2222 1.73242 29.9278 1.73242 29.5833C1.73242 29.2389 1.85464 28.9444 2.09909 28.7C2.34353 28.4556 2.63798 28.3333 2.98242 28.3333H19.9491C20.2935 28.3333 20.588 28.4556 20.8324 28.7C21.0769 28.9444 21.1991 29.2389 21.1991 29.5833C21.1991 29.9278 21.0769 30.2222 20.8324 30.4667C20.588 30.7111 20.2935 30.8333 19.9491 30.8333H2.98242ZM12.7491 13.2167V15.7167C12.538 15.6944 12.3241 15.675 12.1074 15.6583C11.8908 15.6417 11.6769 15.6333 11.4658 15.6333C10.1546 15.6333 8.91853 15.8444 7.75742 16.2667C6.59631 16.6889 5.69909 17.3333 5.06575 18.2H17.7324L20.2324 20.7H3.26576C2.86576 20.7 2.54076 20.55 2.29076 20.25C2.04076 19.95 1.95464 19.6 2.03242 19.2C2.25464 18.1111 2.68242 17.1806 3.31576 16.4083C3.94909 15.6361 4.69909 15.0056 5.56575 14.5167C6.43242 14.0278 7.37964 13.675 8.40742 13.4583C9.4352 13.2417 10.4546 13.1333 11.4658 13.1333C11.6769 13.1333 11.8908 13.1417 12.1074 13.1583C12.3241 13.175 12.538 13.1944 12.7491 13.2167Z"
fill="#787472"
/>
</g>
</svg>
)
}

View File

@@ -2,6 +2,11 @@
margin: 0; margin: 0;
} }
.baseIconLowContrast,
.baseIconLowContrast * {
fill: var(--Base-Icon-Low-contrast);
}
.black, .black,
.black * { .black * {
fill: #000; fill: #000;
@@ -46,3 +51,13 @@
.white * { .white * {
fill: var(--UI-Opacity-White-100); fill: var(--UI-Opacity-White-100);
} }
.uiTextHighContrast,
.uiTextHighContrast * {
fill: var(--UI-Text-High-contrast);
}
.uiTextMediumContrast,
.uiTextMediumContrast * {
fill: var(--UI-Text-Medium-contrast);
}

View File

@@ -4,6 +4,7 @@ export { default as AirplaneIcon } from "./Airplane"
export { default as ArrowRightIcon } from "./ArrowRight" export { default as ArrowRightIcon } from "./ArrowRight"
export { default as BarIcon } from "./Bar" export { default as BarIcon } from "./Bar"
export { default as BikingIcon } from "./Biking" export { default as BikingIcon } from "./Biking"
export { default as BreakfastIcon } from "./Breakfast"
export { default as BusinessIcon } from "./Business" export { default as BusinessIcon } from "./Business"
export { default as CalendarIcon } from "./Calendar" export { default as CalendarIcon } from "./Calendar"
export { default as CameraIcon } from "./Camera" export { default as CameraIcon } from "./Camera"
@@ -30,14 +31,17 @@ export { default as ErrorCircleIcon } from "./ErrorCircle"
export { default as FitnessIcon } from "./Fitness" export { default as FitnessIcon } from "./Fitness"
export { default as GiftIcon } from "./Gift" export { default as GiftIcon } from "./Gift"
export { default as GlobeIcon } from "./Globe" export { default as GlobeIcon } from "./Globe"
export { default as HeartIcon } from "./Heart"
export { default as HouseIcon } from "./House" export { default as HouseIcon } from "./House"
export { default as ImageIcon } from "./Image" export { default as ImageIcon } from "./Image"
export { default as InfoCircleIcon } from "./InfoCircle" export { default as InfoCircleIcon } from "./InfoCircle"
export { default as KingBedIcon } from "./KingBed"
export { default as LocationIcon } from "./Location" export { default as LocationIcon } from "./Location"
export { default as LockIcon } from "./Lock" export { default as LockIcon } from "./Lock"
export { default as MapIcon } from "./Map" export { default as MapIcon } from "./Map"
export { default as MinusIcon } from "./Minus" export { default as MinusIcon } from "./Minus"
export { default as MuseumIcon } from "./Museum" export { default as MuseumIcon } from "./Museum"
export { default as NoBreakfastIcon } from "./NoBreakfast"
export { default as ParkingIcon } from "./Parking" export { default as ParkingIcon } from "./Parking"
export { default as People2Icon } from "./People2" export { default as People2Icon } from "./People2"
export { default as PersonIcon } from "./Person" export { default as PersonIcon } from "./Person"

View File

@@ -5,6 +5,7 @@ import styles from "./icon.module.css"
const config = { const config = {
variants: { variants: {
color: { color: {
baseIconLowContrast: styles.baseIconLowContrast,
black: styles.black, black: styles.black,
burgundy: styles.burgundy, burgundy: styles.burgundy,
grey80: styles.grey80, grey80: styles.grey80,
@@ -14,6 +15,8 @@ const config = {
red: styles.red, red: styles.red,
green: styles.green, green: styles.green,
white: styles.white, white: styles.white,
uiTextHighContrast: styles.uiTextHighContrast,
uiTextMediumContrast: styles.uiTextMediumContrast,
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@@ -1,12 +1,13 @@
"use client" "use client"
import { useEffect, useRef } from "react" import { useRef } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { languages } from "@/constants/languages" import { languages } from "@/constants/languages"
import useDropdownStore from "@/stores/main-menu" import useDropdownStore from "@/stores/main-menu"
import { ChevronDownIcon, GlobeIcon } from "@/components/Icons" import { ChevronDownIcon, GlobeIcon } from "@/components/Icons"
import useClickOutside from "@/hooks/useClickOutside"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
@@ -28,18 +29,13 @@ export default function LanguageSwitcher({
}: LanguageSwitcherProps) { }: LanguageSwitcherProps) {
const intl = useIntl() const intl = useIntl()
const currentLanguage = useLang() const currentLanguage = useLang()
const toggleDropdown = useDropdownStore((state) => state.toggleDropdown) const {
toggleDropdown,
isFooterLanguageSwitcherOpen,
isHeaderLanguageSwitcherMobileOpen,
isHeaderLanguageSwitcherOpen,
} = useDropdownStore()
const languageSwitcherRef = useRef<HTMLDivElement>(null) const languageSwitcherRef = useRef<HTMLDivElement>(null)
const isFooterLanguageSwitcherOpen = useDropdownStore(
(state) => state.isFooterLanguageSwitcherOpen
)
const isHeaderLanguageSwitcherOpen = useDropdownStore(
(state) => state.isHeaderLanguageSwitcherOpen
)
const isHeaderLanguageSwitcherMobileOpen = useDropdownStore(
(state) => state.isHeaderLanguageSwitcherMobileOpen
)
const isFooter = type === LanguageSwitcherTypesEnum.Footer const isFooter = type === LanguageSwitcherTypesEnum.Footer
const isHeader = !isFooter const isHeader = !isFooter
@@ -71,33 +67,11 @@ export default function LanguageSwitcher({
window.scrollTo(0, scrollPosition) window.scrollTo(0, scrollPosition)
}) })
} }
useClickOutside(
useEffect(() => { languageSwitcherRef,
function handleClickOutside(evt: Event) { isLanguageSwitcherOpen && !isHeaderLanguageSwitcherMobileOpen,
const target = evt.target as HTMLElement () => toggleDropdown(dropdownType)
if ( )
languageSwitcherRef.current &&
target &&
!languageSwitcherRef.current.contains(target) &&
isLanguageSwitcherOpen &&
!isHeaderLanguageSwitcherMobileOpen
) {
toggleDropdown(dropdownType)
}
}
if (languageSwitcherRef.current) {
document.addEventListener("click", handleClickOutside)
}
return () => {
document.removeEventListener("click", handleClickOutside)
}
}, [
dropdownType,
toggleDropdown,
isLanguageSwitcherOpen,
isHeaderLanguageSwitcherMobileOpen,
])
const classNames = languageSwitcherVariants({ color, position }) const classNames = languageSwitcherVariants({ color, position })

View File

@@ -0,0 +1,51 @@
"use client"
import { useIntl } from "react-intl"
import { trpc } from "@/lib/trpc/client"
import ArrowRight from "@/components/Icons/ArrowRight"
import Button from "@/components/TempDesignSystem/Button"
import { toast } from "@/components/TempDesignSystem/Toasts"
import styles from "./managePreferencesButton.module.css"
export default function ManagePreferencesButton() {
const intl = useIntl()
const generatePreferencesLink = trpc.user.generatePreferencesLink.useMutation(
{
onSuccess: (preferencesLink) => {
if (preferencesLink) {
window.open(preferencesLink, "_blank")
} else {
toast.error(
intl.formatMessage({
id: "It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.",
})
)
}
},
onError: (e) => {
toast.error(
intl.formatMessage({
id: "An error occurred trying to manage your preferences, please try again later.",
})
)
},
}
)
return (
<Button
className={styles.managePreferencesButton}
variant="icon"
theme="base"
intent="text"
onClick={() => generatePreferencesLink.mutate()}
wrapping
>
<ArrowRight color="burgundy" />
{intl.formatMessage({ id: "Manage preferences" })}
</Button>
)
}

View File

@@ -0,0 +1,3 @@
.managePreferencesButton {
justify-self: flex-start;
}

View File

@@ -14,7 +14,7 @@ export default function CardImage({
return ( return (
<article className={`${styles.container} ${className}`}> <article className={`${styles.container} ${className}`}>
<div className={styles.imageContainer}> <div className={styles.imageContainer}>
{imageCards.map( {imageCards?.map(
({ backgroundImage }) => ({ backgroundImage }) =>
backgroundImage && ( backgroundImage && (
<Image <Image

View File

@@ -2,6 +2,7 @@ import { cardVariants } from "./variants"
import type { VariantProps } from "class-variance-authority" import type { VariantProps } from "class-variance-authority"
import type { ApiImage } from "@/types/components/image"
import type { ImageVaultAsset } from "@/types/components/imageVault" import type { ImageVaultAsset } from "@/types/components/imageVault"
export interface CardProps export interface CardProps
@@ -22,9 +23,10 @@ export interface CardProps
scriptedTopTitle?: string | null scriptedTopTitle?: string | null
heading?: string | null heading?: string | null
bodyText?: string | null bodyText?: string | null
backgroundImage?: ImageVaultAsset
imageHeight?: number imageHeight?: number
imageWidth?: number imageWidth?: number
imageGradient?: boolean
onPrimaryButtonClick?: () => void onPrimaryButtonClick?: () => void
onSecondaryButtonClick?: () => void onSecondaryButtonClick?: () => void
backgroundImage?: ImageVaultAsset | ApiImage
} }

View File

@@ -24,15 +24,17 @@ export default function Card({
backgroundImage, backgroundImage,
imageHeight, imageHeight,
imageWidth, imageWidth,
imageGradient,
onPrimaryButtonClick, onPrimaryButtonClick,
onSecondaryButtonClick, onSecondaryButtonClick,
}: CardProps) { }: CardProps) {
const buttonTheme = getTheme(theme) const buttonTheme = getTheme(theme)
imageHeight = imageHeight || 320 imageHeight = imageHeight || 320
imageWidth = imageWidth =
imageWidth || imageWidth ||
(backgroundImage (backgroundImage && "dimensions" in backgroundImage
? backgroundImage.dimensions.aspectRatio * imageHeight ? backgroundImage.dimensions.aspectRatio * imageHeight
: 420) : 420)
@@ -44,7 +46,7 @@ export default function Card({
})} })}
> >
{backgroundImage && ( {backgroundImage && (
<div className={styles.imageWrapper}> <div className={imageGradient ? styles.imageWrapper : ""}>
<Image <Image
src={backgroundImage.url} src={backgroundImage.url}
className={styles.image} className={styles.image}

View File

@@ -0,0 +1,7 @@
import Card from "."
import type { CheckboxProps } from "./card"
export default function CheckboxCard(props: CheckboxProps) {
return <Card {...props} type="checkbox" />
}

View File

@@ -0,0 +1,7 @@
import Card from "."
import type { RadioProps } from "./card"
export default function RadioCard(props: RadioProps) {
return <Card {...props} type="radio" />
}

View File

@@ -0,0 +1,72 @@
.label {
align-self: flex-start;
background-color: var(--Base-Surface-Primary-light-Normal);
border: 1px solid var(--Base-Border-Subtle);
border-radius: var(--Corner-radius-Large);
cursor: pointer;
display: grid;
grid-template-columns: 1fr auto;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
transition: all 200ms ease;
width: min(100%, 600px);
}
.label:hover {
background-color: var(--Base-Surface-Secondary-light-Hover);
}
.label:has(:checked) {
background-color: var(--Primary-Light-Surface-Normal);
border-color: var(--Base-Border-Hover);
}
.icon {
align-self: center;
grid-column: 2/3;
grid-row: 1/3;
justify-self: flex-end;
transition: fill 200ms ease;
}
.label:hover .icon,
.label:hover .icon *,
.label:has(:checked) .icon,
.label:has(:checked) .icon * {
fill: var(--Base-Text-Medium-contrast);
}
.label[data-declined="true"]:hover .icon,
.label[data-declined="true"]:hover .icon *,
.label[data-declined="true"]:has(:checked) .icon,
.label[data-declined="true"]:has(:checked) .icon * {
fill: var(--Base-Text-Disabled);
}
.subtitle {
grid-column: 1 / 2;
grid-row: 2;
}
.title {
grid-column: 1 / 2;
}
.label .text {
margin-top: var(--Spacing-x1);
grid-column: 1/-1;
}
.listItem {
align-items: center;
display: flex;
gap: var(--Spacing-x-quarter);
grid-column: 1/-1;
}
.listItem:first-of-type {
margin-top: var(--Spacing-x1);
}
.listItem:nth-of-type(n + 2) {
margin-top: var(--Spacing-x-quarter);
}

View File

@@ -0,0 +1,35 @@
import type { IconProps } from "@/types/components/icon"
interface BaseCardProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
Icon?: (props: IconProps) => JSX.Element
declined?: boolean
iconHeight?: number
iconWidth?: number
name?: string
saving?: boolean
subtitle?: string
title: string
type: "checkbox" | "radio"
value?: string
}
interface ListCardProps extends BaseCardProps {
list: {
title: string
}[]
text?: never
}
interface TextCardProps extends BaseCardProps {
list?: never
text: string
}
export type CardProps = ListCardProps | TextCardProps
export type CheckboxProps =
| Omit<ListCardProps, "type">
| Omit<TextCardProps, "type">
export type RadioProps =
| Omit<ListCardProps, "type">
| Omit<TextCardProps, "type">

View File

@@ -0,0 +1,77 @@
"use client"
import { CheckIcon, CloseIcon, HeartIcon } from "@/components/Icons"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import styles from "./card.module.css"
import type { CardProps } from "./card"
export default function Card({
Icon = HeartIcon,
iconHeight = 32,
iconWidth = 32,
declined = false,
id,
list,
name = "join",
saving = false,
subtitle,
text,
title,
type,
value,
}: CardProps) {
return (
<label className={styles.label} data-declined={declined}>
<Caption className={styles.title} textTransform="bold" uppercase>
{title}
</Caption>
{subtitle ? (
<Caption
className={styles.subtitle}
color={saving ? "baseTextAccent" : "uiTextHighContrast"}
textTransform="bold"
>
{subtitle}
</Caption>
) : null}
<Icon
className={styles.icon}
color="uiTextHighContrast"
height={iconHeight}
width={iconWidth}
/>
{list
? list.map((listItem) => (
<span key={listItem.title} className={styles.listItem}>
{declined ? (
<CloseIcon
color="uiTextMediumContrast"
height={20}
width={20}
/>
) : (
<CheckIcon color="baseIconLowContrast" height={20} width={20} />
)}
<Footnote color="uiTextMediumContrast">{listItem.title}</Footnote>
</span>
))
: null}
{text ? (
<Footnote className={styles.text} color="uiTextMediumContrast">
{text}
</Footnote>
) : null}
<input
aria-hidden
id={id || name}
hidden
name={name}
type={type}
value={value}
/>
</label>
)
}

View File

@@ -1,9 +1,11 @@
import type { RegisterOptions } from "react-hook-form" import type { RegisterOptions } from "react-hook-form"
export type CountryProps = { export type CountryProps = {
className?: string
label: string label: string
name?: string name?: string
placeholder?: string placeholder?: string
readOnly?: boolean
registerOptions?: RegisterOptions registerOptions?: RegisterOptions
} }

View File

@@ -28,8 +28,10 @@ import type {
} from "./country" } from "./country"
export default function CountrySelect({ export default function CountrySelect({
className = "",
label, label,
name = "country", name = "country",
readOnly = false,
registerOptions = {}, registerOptions = {},
}: CountryProps) { }: CountryProps) {
const { formatMessage } = useIntl() const { formatMessage } = useIntl()
@@ -54,12 +56,13 @@ export default function CountrySelect({
const selectCountryLabel = formatMessage({ id: "Select a country" }) const selectCountryLabel = formatMessage({ id: "Select a country" })
return ( return (
<div className={styles.container} ref={setRef}> <div className={`${styles.container} ${className}`} ref={setRef}>
<ComboBox <ComboBox
aria-label={formatMessage({ id: "Select country of residence" })} aria-label={formatMessage({ id: "Select country of residence" })}
className={styles.select} className={styles.select}
isRequired={!!registerOptions?.required}
isInvalid={fieldState.invalid} isInvalid={fieldState.invalid}
isReadOnly={readOnly}
isRequired={!!registerOptions?.required}
name={field.name} name={field.name}
onBlur={field.onBlur} onBlur={field.onBlur}
onSelectionChange={handleChange} onSelectionChange={handleChange}

View File

@@ -0,0 +1,25 @@
import { type ForwardedRef,forwardRef } from "react"
import { Input as AriaInput, Label as AriaLabel } from "react-aria-components"
import Label from "@/components/TempDesignSystem/Form/Label"
import Body from "@/components/TempDesignSystem/Text/Body"
import styles from "./input.module.css"
import type { AriaInputWithLabelProps } from "./input"
const AriaInputWithLabel = forwardRef(function AriaInputWithLabelComponent(
{ label, ...props }: AriaInputWithLabelProps,
ref: ForwardedRef<HTMLInputElement>
) {
return (
<AriaLabel className={styles.container} htmlFor={props.name}>
<Body asChild fontOnly>
<AriaInput {...props} className={styles.input} ref={ref} />
</Body>
<Label required={!!props.required}>{label}</Label>
</AriaLabel>
)
})
export default AriaInputWithLabel

View File

@@ -0,0 +1,55 @@
.container {
align-content: center;
background-color: var(--Main-Grey-White);
border-color: var(--Scandic-Beige-40);
border-style: solid;
border-width: 1px;
border-radius: var(--Corner-radius-Medium);
display: grid;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
transition: border-color 200ms ease;
}
.container:has(.input:active, .input:focus) {
border-color: var(--Scandic-Blue-90);
}
.container:has(.input:disabled) {
background-color: var(--Main-Grey-10);
border: none;
color: var(--Main-Grey-40);
}
.container:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
border-color: var(--Scandic-Red-60);
}
.input {
background: none;
border: none;
color: var(--Main-Grey-100);
height: 18px;
margin: 0;
order: 2;
overflow: visible;
padding: 0;
}
.input:not(:active, :focus):placeholder-shown {
height: 0px;
transition: height 150ms ease;
}
.input:focus,
.input:focus:placeholder-shown,
.input:active,
.input:active:placeholder-shown {
height: 18px;
transition: height 150ms ease;
outline: none;
}
.input:disabled {
color: var(--Main-Grey-40);
}

View File

@@ -0,0 +1,4 @@
export interface AriaInputWithLabelProps
extends React.InputHTMLAttributes<HTMLInputElement> {
label: string
}

View File

@@ -1,15 +1,9 @@
"use client" "use client"
import { import { Text, TextField } from "react-aria-components"
Input as AriaInput,
Label as AriaLabel,
Text,
TextField,
} from "react-aria-components"
import { Controller, useFormContext } from "react-hook-form" import { Controller, useFormContext } from "react-hook-form"
import { CheckIcon, InfoCircleIcon } from "@/components/Icons" import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
import Label from "@/components/TempDesignSystem/Form/Label" import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./input.module.css" import styles from "./input.module.css"
@@ -20,11 +14,13 @@ import type { InputProps } from "./input"
export default function Input({ export default function Input({
"aria-label": ariaLabel, "aria-label": ariaLabel,
className = "",
disabled = false, disabled = false,
helpText = "", helpText = "",
label, label,
name, name,
placeholder = "", placeholder = "",
readOnly = false,
registerOptions = {}, registerOptions = {},
type = "text", type = "text",
}: InputProps) { }: InputProps) {
@@ -44,6 +40,7 @@ export default function Input({
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
aria-label={ariaLabel} aria-label={ariaLabel}
className={className}
isDisabled={field.disabled} isDisabled={field.disabled}
isInvalid={fieldState.invalid} isInvalid={fieldState.invalid}
isRequired={!!registerOptions.required} isRequired={!!registerOptions.required}
@@ -53,19 +50,16 @@ export default function Input({
validationBehavior="aria" validationBehavior="aria"
value={field.value} value={field.value}
> >
<AriaLabel className={styles.container} htmlFor={field.name}> <AriaInputWithLabel
<Body asChild fontOnly> {...field}
<AriaInput aria-labelledby={field.name}
{...numberAttributes} id={field.name}
aria-labelledby={field.name} label={label}
className={styles.input} placeholder={placeholder}
id={field.name} readOnly={readOnly}
placeholder={placeholder} required={!!registerOptions.required}
type={type} type={type}
/> />
</Body>
<Label required={!!registerOptions.required}>{label}</Label>
</AriaLabel>
{helpText && !fieldState.error ? ( {helpText && !fieldState.error ? (
<Caption asChild color="black"> <Caption asChild color="black">
<Text className={styles.helpText} slot="description"> <Text className={styles.helpText} slot="description">

View File

@@ -1,59 +1,3 @@
.container {
align-content: center;
background-color: var(--Main-Grey-White);
border-color: var(--Scandic-Beige-40);
border-style: solid;
border-width: 1px;
border-radius: var(--Corner-radius-Medium);
display: grid;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
transition: border-color 200ms ease;
}
.container:has(.input:active, .input:focus) {
border-color: var(--Scandic-Blue-90);
}
.container:has(.input:disabled) {
background-color: var(--Main-Grey-10);
border: none;
color: var(--Main-Grey-40);
}
.container:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
border-color: var(--Scandic-Red-60);
}
.input {
background: none;
border: none;
color: var(--Main-Grey-100);
height: 18px;
margin: 0;
order: 2;
overflow: visible;
padding: 0;
}
.input:not(:active, :focus):placeholder-shown {
height: 0px;
transition: height 150ms ease;
}
.input:focus,
.input:focus:placeholder-shown,
.input:active,
.input:active:placeholder-shown {
height: 18px;
transition: height 150ms ease;
outline: none;
}
.input:disabled {
color: var(--Main-Grey-40);
}
.helpText { .helpText {
align-items: flex-start; align-items: flex-start;
display: flex; display: flex;

View File

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

View File

@@ -5,6 +5,7 @@
letter-spacing: 0.03px; letter-spacing: 0.03px;
line-height: 120%; line-height: 120%;
text-align: left; text-align: left;
transition: font-size 100ms ease;
} }
span.small { span.small {
@@ -21,7 +22,6 @@ input:active ~ .label,
input:not(:placeholder-shown) ~ .label { input:not(:placeholder-shown) ~ .label {
display: block; display: block;
font-size: 12px; font-size: 12px;
transition: font-size 100ms ease;
} }
input:focus ~ .label { input:focus ~ .label {

View File

@@ -1,17 +1,11 @@
"use client" "use client"
import { import { Text, TextField } from "react-aria-components"
Input as AriaInput,
Label as AriaLabel,
Text,
TextField,
} from "react-aria-components"
import { Controller, useFormContext } from "react-hook-form" import { Controller, useFormContext } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { CheckIcon, CloseIcon } from "@/components/Icons" import { CheckIcon, CloseIcon } from "@/components/Icons"
import Error from "@/components/TempDesignSystem/Form/ErrorMessage/Error" import Error from "@/components/TempDesignSystem/Form/ErrorMessage/Error"
import Label from "@/components/TempDesignSystem/Form/Label" import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import { type IconProps, Key, type NewPasswordProps } from "./newPassword" import { type IconProps, Key, type NewPasswordProps } from "./newPassword"
@@ -47,20 +41,14 @@ export default function NewPassword({
value={field.value} value={field.value}
type="password" type="password"
> >
<AriaLabel className={styles.container} htmlFor={field.name}> <AriaInputWithLabel
<Body asChild fontOnly> {...field}
<AriaInput aria-labelledby={field.name}
aria-labelledby={field.name} id={field.name}
className={styles.input} label={formatMessage({ id: "New password" })}
id={field.name} placeholder={placeholder}
placeholder={placeholder} type="password"
type="password" />
/>
</Body>
<Label required={!!registerOptions.required}>
{formatMessage({ id: "New password" })}
</Label>
</AriaLabel>
{field.value ? ( {field.value ? (
<div className={styles.errors}> <div className={styles.errors}>
<Caption asChild color="black"> <Caption asChild color="black">

View File

@@ -1,59 +1,3 @@
.container {
align-content: center;
background-color: var(--Main-Grey-White);
border-color: var(--Scandic-Beige-40);
border-style: solid;
border-width: 1px;
border-radius: var(--Corner-radius-Medium);
display: grid;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
transition: border-color 200ms ease;
}
.container:has(.input:active, .input:focus) {
border-color: var(--Scandic-Blue-90);
}
.container:has(.input:disabled) {
background-color: var(--Main-Grey-10);
border: none;
color: var(--Main-Grey-40);
}
.container:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
border-color: var(--Scandic-Red-60);
}
.input {
background: none;
border: none;
color: var(--Main-Grey-100);
height: 18px;
margin: 0;
order: 2;
overflow: visible;
padding: 0;
}
.input:not(:active, :focus):placeholder-shown {
height: 0px;
transition: height 150ms ease;
}
.input:focus,
.input:focus:placeholder-shown,
.input:active,
.input:active:placeholder-shown {
height: 18px;
transition: height 150ms ease;
outline: none;
}
.input:disabled {
color: var(--Main-Grey-40);
}
.helpText { .helpText {
align-items: flex-start; align-items: flex-start;
display: flex; display: flex;

View File

@@ -2,11 +2,7 @@
import "react-international-phone/style.css" import "react-international-phone/style.css"
import { isValidPhoneNumber, parsePhoneNumber } from "libphonenumber-js" import { isValidPhoneNumber, parsePhoneNumber } from "libphonenumber-js"
import { import { TextField } from "react-aria-components"
Input as AriaInput,
Label as AriaLabel,
TextField,
} from "react-aria-components"
import { useController, useFormContext, useWatch } from "react-hook-form" import { useController, useFormContext, useWatch } from "react-hook-form"
import { import {
CountrySelector, CountrySelector,
@@ -18,6 +14,7 @@ import { useIntl } from "react-intl"
import { ChevronDownIcon } from "@/components/Icons" import { ChevronDownIcon } from "@/components/Icons"
import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage" import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage"
import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
import Label from "@/components/TempDesignSystem/Form/Label" import Label from "@/components/TempDesignSystem/Form/Label"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
@@ -29,10 +26,12 @@ import type { PhoneProps } from "./phone"
export default function Phone({ export default function Phone({
ariaLabel = "Phone number input", ariaLabel = "Phone number input",
className = "",
disabled = false, disabled = false,
label, label,
name = "phoneNumber", name = "phoneNumber",
placeholder = "", placeholder = "",
readOnly = false,
registerOptions = { registerOptions = {
required: true, required: true,
}, },
@@ -72,8 +71,9 @@ export default function Phone({
} }
return ( return (
<div className={styles.phone}> <div className={`${styles.phone} ${className}`}>
<CountrySelector <CountrySelector
disabled={readOnly}
dropdownArrowClassName={styles.arrow} dropdownArrowClassName={styles.arrow}
flagClassName={styles.flag} flagClassName={styles.flag}
onSelect={handleSelectCountry} onSelect={handleSelectCountry}
@@ -114,25 +114,21 @@ export default function Phone({
isDisabled={disabled ?? field.disabled} isDisabled={disabled ?? field.disabled}
isInvalid={fieldState.invalid} isInvalid={fieldState.invalid}
isRequired={!!registerOptions?.required} isRequired={!!registerOptions?.required}
isReadOnly={readOnly}
name={field.name} name={field.name}
type="tel" type="tel"
> >
<AriaLabel className={styles.inputContainer} htmlFor={field.name}> <AriaInputWithLabel
<Body asChild fontOnly> {...field}
<AriaInput id={field.name}
className={styles.input} label={label}
id={field.name} onChange={handleChange}
name={field.name} placeholder={placeholder}
onBlur={field.onBlur} readOnly={readOnly}
onChange={handleChange} required={!!registerOptions.required}
placeholder={placeholder} type="tel"
ref={field.ref} value={inputValue}
required={!!registerOptions.required} />
value={inputValue}
/>
</Body>
<Label required={!!registerOptions.required}>{label}</Label>
</AriaLabel>
<ErrorMessage errors={formState.errors} name={field.name} /> <ErrorMessage errors={formState.errors} name={field.name} />
</TextField> </TextField>
</div> </div>

View File

@@ -19,6 +19,9 @@
--react-international-phone-dropdown-top: calc( --react-international-phone-dropdown-top: calc(
var(--react-international-phone-height) + var(--Spacing-x1) var(--react-international-phone-height) + var(--Spacing-x1)
); );
--react-international-phone-dial-code-preview-font-size: var(
--typography-Body-Regular-fontSize
);
} }
.phone:has(.input:active, .input:focus) { .phone:has(.input:active, .input:focus) {
@@ -46,7 +49,6 @@
align-self: center; align-self: center;
} }
.inputContainer,
.select { .select {
align-content: center; align-content: center;
background-color: var(--Main-Grey-White); background-color: var(--Main-Grey-White);
@@ -93,42 +95,8 @@
.select .dialCode { .select .dialCode {
border: none; border: none;
color: var(--Main-Grey-100); color: var(--UI-Text-High-contrast);
line-height: 1; line-height: 1;
justify-self: flex-start; justify-self: flex-start;
padding: 0; padding: 0;
} }
.inputContainer:has(.input:not(:focus):placeholder-shown) {
gap: 0;
grid-template-rows: 1fr;
}
.inputContainer:has(.input:active, .input:focus) {
border-color: var(--Scandic-Blue-90);
}
.inputContainer:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
border-color: var(--Scandic-Red-60);
}
.input {
background: none;
border: none;
color: var(--Main-Grey-100);
height: 18px;
margin: 0;
order: 2;
overflow: visible;
padding: 0;
}
.input:not(:active, :focus):placeholder-shown {
height: 0px;
}
.input:focus,
.input:focus:placeholder-shown {
height: 18px;
outline: none;
}

View File

@@ -2,9 +2,11 @@ import type { RegisterOptions } from "react-hook-form"
export type PhoneProps = { export type PhoneProps = {
ariaLabel?: string ariaLabel?: string
className?: string
disabled?: boolean disabled?: boolean
label: string label: string
name?: string name?: string
placeholder?: string placeholder?: string
readOnly?: boolean
registerOptions?: RegisterOptions registerOptions?: RegisterOptions
} }

View File

@@ -92,6 +92,10 @@
color: var(--Base-Text-Medium-contrast); color: var(--Base-Text-Medium-contrast);
} }
.uiTextHighContrast {
color: var(--UI-Text-High-contrast);
}
.uiTextPlaceholder { .uiTextPlaceholder {
color: var(--UI-Text-Placeholder); color: var(--UI-Text-Placeholder);
} }

View File

@@ -15,6 +15,7 @@ const config = {
white: styles.white, white: styles.white,
peach50: styles.peach50, peach50: styles.peach50,
peach80: styles.peach80, peach80: styles.peach80,
uiTextHighContrast: styles.uiTextHighContrast,
uiTextPlaceholder: styles.uiTextPlaceholder, uiTextPlaceholder: styles.uiTextPlaceholder,
}, },
textAlign: { textAlign: {

View File

@@ -35,6 +35,10 @@ p.caption {
text-decoration: var(--typography-Caption-Regular-textDecoration); text-decoration: var(--typography-Caption-Regular-textDecoration);
} }
.baseTextAccent {
color: var(--Base-Text-Accent);
}
.black { .black {
color: var(--Main-Grey-100); color: var(--Main-Grey-100);
} }
@@ -67,10 +71,14 @@ p.caption {
color: var(--UI-Text-Medium-contrast); color: var(--UI-Text-Medium-contrast);
} }
.uiTextHighContrast {
color: var(--UI-Text-High-contrast);
}
.center { .center {
text-align: center; text-align: center;
} }
.left { .left {
text-align: left; text-align: left;
} }

View File

@@ -11,6 +11,7 @@ export default function Caption({
fontOnly = false, fontOnly = false,
textAlign, textAlign,
textTransform, textTransform,
uppercase,
...props ...props
}: CaptionProps) { }: CaptionProps) {
const Comp = asChild ? Slot : "p" const Comp = asChild ? Slot : "p"
@@ -18,12 +19,14 @@ export default function Caption({
? fontOnlycaptionVariants({ ? fontOnlycaptionVariants({
className, className,
textTransform, textTransform,
uppercase,
}) })
: captionVariants({ : captionVariants({
className, className,
color, color,
textTransform, textTransform,
textAlign, textAlign,
uppercase,
}) })
return <Comp className={classNames} {...props} /> return <Comp className={classNames} {...props} />
} }

View File

@@ -5,12 +5,14 @@ import styles from "./caption.module.css"
const config = { const config = {
variants: { variants: {
color: { color: {
baseTextAccent: styles.baseTextAccent,
black: styles.black, black: styles.black,
burgundy: styles.burgundy, burgundy: styles.burgundy,
pale: styles.pale, pale: styles.pale,
textMediumContrast: styles.textMediumContrast, textMediumContrast: styles.textMediumContrast,
red: styles.red, red: styles.red,
white: styles.white, white: styles.white,
uiTextHighContrast: styles.uiTextHighContrast,
uiTextActive: styles.uiTextActive, uiTextActive: styles.uiTextActive,
uiTextMediumContrast: styles.uiTextMediumContrast, uiTextMediumContrast: styles.uiTextMediumContrast,
}, },
@@ -23,6 +25,9 @@ const config = {
center: styles.center, center: styles.center,
left: styles.left, left: styles.left,
}, },
uppercase: {
true: styles.uppercase,
},
}, },
defaultVariants: { defaultVariants: {
color: "black", color: "black",
@@ -39,6 +44,9 @@ const fontOnlyConfig = {
regular: styles.regular, regular: styles.regular,
uppercase: styles.uppercase, uppercase: styles.uppercase,
}, },
uppercase: {
true: styles.uppercase,
},
}, },
defaultVariants: { defaultVariants: {
textTransform: "regular", textTransform: "regular",

View File

@@ -59,7 +59,7 @@
color: var(--Scandic-Peach-50); color: var(--Scandic-Peach-50);
} }
.textMediumContrast { .uiTextMediumContrast {
color: var(--UI-Text-Medium-contrast); color: var(--UI-Text-Medium-contrast);
} }

View File

@@ -9,7 +9,7 @@ const config = {
burgundy: styles.burgundy, burgundy: styles.burgundy,
pale: styles.pale, pale: styles.pale,
peach50: styles.peach50, peach50: styles.peach50,
textMediumContrast: styles.textMediumContrast, uiTextMediumContrast: styles.uiTextMediumContrast,
uiTextPlaceholder: styles.uiTextPlaceholder, uiTextPlaceholder: styles.uiTextPlaceholder,
}, },
textAlign: { textAlign: {

View File

@@ -45,13 +45,40 @@ export const meetingsAndConferences = {
export const restaurantAndBar = { export const restaurantAndBar = {
en: "restaurant-and-bar", en: "restaurant-and-bar",
sv: "restaurant-och-bar", sv: "restaurang-och-bar",
no: "restaurant-og-bar", no: "restaurant-og-bar",
da: "restaurant-og-bar", da: "restaurant-og-bar",
fi: "ravintola-ja-baari", fi: "ravintola-ja-baari",
de: "Restaurant-und-Bar", de: "Restaurant-und-Bar",
} }
/*export const restaurant = {
en: "restaurant",
sv: "restaurang",
no: "restaurant",
da: "restaurant",
fi: "ravintola",
de: "Restaurant",
}
export const bar = {
en: "bar",
sv: "bar",
no: "bar",
da: "bar",
fi: "baari",
de: "Bar",
}
export const breakfastRestaurant = {
en: "breakfast-restaurant",
sv: "frukostrestaurang",
no: "frokostrestaurant",
da: "morgenmadsrestaurant",
fi: "aamiaisravintola",
de: "Frühstücksrestaurant",
}
*/
const params = { const params = {
about, about,
amenities, amenities,
@@ -59,6 +86,9 @@ const params = {
activities, activities,
meetingsAndConferences, meetingsAndConferences,
restaurantAndBar, restaurantAndBar,
/*bar,
restaurant,
breakfastRestaurant,*/
} }
export default params export default params

2
env/server.ts vendored
View File

@@ -47,6 +47,7 @@ export const env = createEnv({
.default("false"), .default("false"),
PUBLIC_URL: z.string().optional(), PUBLIC_URL: z.string().optional(),
REVALIDATE_SECRET: z.string(), REVALIDATE_SECRET: z.string(),
SALESFORCE_PREFERENCE_BASE_URL: z.string(),
SEAMLESS_LOGIN_DA: z.string(), SEAMLESS_LOGIN_DA: z.string(),
SEAMLESS_LOGIN_DE: z.string(), SEAMLESS_LOGIN_DE: z.string(),
SEAMLESS_LOGIN_EN: z.string(), SEAMLESS_LOGIN_EN: z.string(),
@@ -104,6 +105,7 @@ export const env = createEnv({
PRINT_QUERY: process.env.PRINT_QUERY, PRINT_QUERY: process.env.PRINT_QUERY,
PUBLIC_URL: process.env.PUBLIC_URL, PUBLIC_URL: process.env.PUBLIC_URL,
REVALIDATE_SECRET: process.env.REVALIDATE_SECRET, REVALIDATE_SECRET: process.env.REVALIDATE_SECRET,
SALESFORCE_PREFERENCE_BASE_URL: process.env.SALESFORCE_PREFERENCE_BASE_URL,
SEAMLESS_LOGIN_DA: process.env.SEAMLESS_LOGIN_DA, SEAMLESS_LOGIN_DA: process.env.SEAMLESS_LOGIN_DA,
SEAMLESS_LOGIN_DE: process.env.SEAMLESS_LOGIN_DE, SEAMLESS_LOGIN_DE: process.env.SEAMLESS_LOGIN_DE,
SEAMLESS_LOGIN_EN: process.env.SEAMLESS_LOGIN_EN, SEAMLESS_LOGIN_EN: process.env.SEAMLESS_LOGIN_EN,

24
hooks/useClickOutside.ts Normal file
View File

@@ -0,0 +1,24 @@
import { useEffect } from "react"
export default function useClickOutside(
ref: React.RefObject<HTMLElement>,
isOpen: boolean,
callback: () => void
) {
useEffect(() => {
function handleClickOutside(evt: Event) {
const target = evt.target as HTMLElement
if (ref.current && target && !ref.current.contains(target) && isOpen) {
callback()
}
}
if (isOpen) {
document.addEventListener("click", handleClickOutside)
}
return () => {
document.removeEventListener("click", handleClickOutside)
}
}, [ref, isOpen, callback])
}

21
hooks/useMediaQuery.ts Normal file
View File

@@ -0,0 +1,21 @@
import { useEffect, useState } from "react"
function useMediaQuery(query: string) {
const [isMatch, setIsMatch] = useState(false)
useEffect(() => {
const media = window.matchMedia(query)
if (media.matches !== isMatch) {
setIsMatch(media.matches)
}
const listener = () => setIsMatch(media.matches)
media.addEventListener("change", listener)
return () => media.removeEventListener("change", listener)
}, [isMatch, query])
return isMatch
}
export default useMediaQuery

View File

@@ -1,39 +1,43 @@
{ {
"<b>Included</b> (based on availability)": "<b>Inkluderet</b> (baseret på tilgængelighed)",
"<b>{amount} {currency}</b>/night per adult": "<b>{amount} {currency}</b>/nat pr. voksen",
"A destination or hotel name is needed to be able to search for a hotel room.": "Et destinations- eller hotelnavn er nødvendigt for at kunne søge efter et hotelværelse.", "A destination or hotel name is needed to be able to search for a hotel room.": "Et destinations- eller hotelnavn er nødvendigt for at kunne søge efter et hotelværelse.",
"A photo of the room": "Et foto af værelset", "A photo of the room": "Et foto af værelset",
"About meetings & conferences": "About meetings & conferences",
"Activities": "Aktiviteter", "Activities": "Aktiviteter",
"Add code": "Tilføj kode", "Add code": "Tilføj kode",
"Add new card": "Tilføj nyt kort", "Add new card": "Tilføj nyt kort",
"Address": "Adresse", "Address": "Adresse",
"Airport": "Lufthavn", "Airport": "Lufthavn",
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle vores morgenmadsbuffeter tilbyder glutenfrie, veganske og allergivenlige muligheder.",
"Already a friend?": "Allerede en ven?", "Already a friend?": "Allerede en ven?",
"Amenities": "Faciliteter", "Amenities": "Faciliteter",
"Amusement park": "Forlystelsespark", "Amusement park": "Forlystelsespark",
"An error occurred when adding a credit card, please try again later.": "Der opstod en fejl under tilføjelse af et kreditkort. Prøv venligst igen senere.", "An error occurred when adding a credit card, please try again later.": "Der opstod en fejl under tilføjelse af et kreditkort. Prøv venligst igen senere.",
"An error occurred trying to manage your preferences, please try again later.": "Der opstod en fejl under forsøget på at administrere dine præferencer. Prøv venligst igen senere.",
"An error occurred when trying to update profile.": "Der opstod en fejl under forsøg på at opdatere profilen.", "An error occurred when trying to update profile.": "Der opstod en fejl under forsøg på at opdatere profilen.",
"Any changes you've made will be lost.": "Alle ændringer, du har foretaget, går tabt.", "Any changes you've made will be lost.": "Alle ændringer, du har foretaget, går tabt.",
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på, at du vil fjerne kortet, der slutter me {lastFourDigits} fra din medlemsprofil?", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på, at du vil fjerne kortet, der slutter me {lastFourDigits} fra din medlemsprofil?",
"Arrival date": "Ankomstdato", "Arrival date": "Ankomstdato",
"as of today": "fra idag",
"As our": "Som vores {level}", "As our": "Som vores {level}",
"As our Close Friend": "Som vores nære ven", "As our Close Friend": "Som vores nære ven",
"At latest": "Senest", "At latest": "Senest",
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Attraktioner", "Attractions": "Attraktioner",
"Back to scandichotels.com": "Tilbage til scandichotels.com", "Back to scandichotels.com": "Tilbage til scandichotels.com",
"Bar": "Bar",
"Bed type": "Seng type", "Bed type": "Seng type",
"Book": "Book", "Book": "Book",
"Book reward night": "Book bonusnat", "Book reward night": "Book bonusnat",
"Booking number": "Bookingnummer", "Booking number": "Bookingnummer",
"booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}",
"Breakfast": "Morgenmad", "Breakfast": "Morgenmad",
"Breakfast buffet": "Morgenbuffet",
"Breakfast excluded": "Morgenmad ikke inkluderet", "Breakfast excluded": "Morgenmad ikke inkluderet",
"Breakfast included": "Morgenmad inkluderet", "Breakfast included": "Morgenmad inkluderet",
"Bus terminal": "Busstation", "Bus terminal": "Busstation",
"Business": "Forretning", "Business": "Forretning",
"by": "inden", "Breakfast restaurant": "Breakfast restaurant",
"Cancel": "Afbestille", "Cancel": "Afbestille",
"characters": "tegn",
"Check in": "Check ind", "Check in": "Check ind",
"Check out": "Check ud", "Check out": "Check ud",
"Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Tjek de kreditkort, der er gemt på din profil. Betal med et gemt kort, når du er logget ind for en mere jævn weboplevelse.", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Tjek de kreditkort, der er gemt på din profil. Betal med et gemt kort, når du er logget ind for en mere jævn weboplevelse.",
@@ -64,32 +68,40 @@
"Date of Birth": "Fødselsdato", "Date of Birth": "Fødselsdato",
"Day": "Dag", "Day": "Dag",
"Description": "Beskrivelse", "Description": "Beskrivelse",
"Destination": "Destination",
"Destinations & hotels": "Destinationer & hoteller", "Destinations & hotels": "Destinationer & hoteller",
"Discard changes": "Kassér ændringer", "Discard changes": "Kassér ændringer",
"Discard unsaved changes?": "Slette ændringer, der ikke er gemt?", "Discard unsaved changes?": "Slette ændringer, der ikke er gemt?",
"Distance to city centre": "{number}km til centrum", "Distance to city centre": "{number}km til centrum",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vil du starte dagen med Scandics berømte morgenbuffet?", "Do you want to start the day with Scandics famous breakfast buffé?": "Vil du starte dagen med Scandics berømte morgenbuffet?",
"Download the Scandic app": "Download Scandic-appen", "Download the Scandic app": "Download Scandic-appen",
"Earn bonus nights & points": "Optjen bonusnætter og point",
"Edit": "Redigere", "Edit": "Redigere",
"Edit profile": "Rediger profil", "Edit profile": "Rediger profil",
"Email": "E-mail", "Email": "E-mail",
"Email address": "E-mailadresse",
"Enter destination or hotel": "Indtast destination eller hotel", "Enter destination or hotel": "Indtast destination eller hotel",
"Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences",
"Events that make an impression": "Events that make an impression",
"Explore all levels and benefits": "Udforsk alle niveauer og fordele", "Explore all levels and benefits": "Udforsk alle niveauer og fordele",
"Explore nearby": "Udforsk i nærheden", "Explore nearby": "Udforsk i nærheden",
"Extras to your booking": "Tillæg til din booking", "Extras to your booking": "Tillæg til din booking",
"FAQ": "Ofte stillede spørgsmål",
"Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.", "Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.",
"Fair": "Messe", "Fair": "Messe",
"FAQ": "Ofte stillede spørgsmål",
"Find booking": "Find booking", "Find booking": "Find booking",
"Find hotels": "Find hotel", "Find hotels": "Find hotel",
"Firstname": "Fornavn",
"Flexibility": "Fleksibilitet", "Flexibility": "Fleksibilitet",
"Former Scandic Hotel": "Tidligere Scandic Hotel", "Former Scandic Hotel": "Tidligere Scandic Hotel",
"Free cancellation": "Gratis afbestilling", "Free cancellation": "Gratis afbestilling",
"Free rebooking": "Gratis ombooking", "Free rebooking": "Gratis ombooking",
"From": "Fra", "From": "Fra",
"Get inspired": "Bliv inspireret", "Get inspired": "Bliv inspireret",
"Get member benefits & offers": "Få medlemsfordele og tilbud",
"Go back to edit": "Gå tilbage til redigering", "Go back to edit": "Gå tilbage til redigering",
"Go back to overview": "Gå tilbage til oversigten", "Go back to overview": "Gå tilbage til oversigten",
"Guest information": "Gæsteinformation",
"Guests & Rooms": "Gæster & værelser", "Guests & Rooms": "Gæster & værelser",
"Hi": "Hei", "Hi": "Hei",
"Highest level": "Højeste niveau", "Highest level": "Højeste niveau",
@@ -97,16 +109,16 @@
"Hotel": "Hotel", "Hotel": "Hotel",
"Hotel facilities": "Hotel faciliteter", "Hotel facilities": "Hotel faciliteter",
"Hotel surroundings": "Hotel omgivelser", "Hotel surroundings": "Hotel omgivelser",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personer",
"hotelPages.rooms.roomCard.seeRoomDetails": "Se værelsesdetaljer",
"Hotels": "Hoteller", "Hotels": "Hoteller",
"How do you want to sleep?": "Hvordan vil du sove?", "How do you want to sleep?": "Hvordan vil du sove?",
"How it works": "Hvordan det virker", "How it works": "Hvordan det virker",
"Image gallery": "Billedgalleri", "Image gallery": "Billedgalleri",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke muligt at administrere dine kommunikationspræferencer lige nu, prøv venligst igen senere eller kontakt support, hvis problemet fortsætter.",
"Join Scandic Friends": "Tilmeld dig Scandic Friends", "Join Scandic Friends": "Tilmeld dig Scandic Friends",
"km to city center": "km til byens centrum", "Join at no cost": "Tilmeld dig uden omkostninger",
"King bed": "Kingsize-seng",
"Language": "Sprog", "Language": "Sprog",
"Lastname": "Efternavn",
"Latest searches": "Seneste søgninger", "Latest searches": "Seneste søgninger",
"Level": "Niveau", "Level": "Niveau",
"Level 1": "Niveau 1", "Level 1": "Niveau 1",
@@ -132,9 +144,9 @@
"Member price": "Medlemspris", "Member price": "Medlemspris",
"Member price from": "Medlemspris fra", "Member price from": "Medlemspris fra",
"Members": "Medlemmer", "Members": "Medlemmer",
"Membership cards": "Medlemskort",
"Membership ID": "Medlems-id", "Membership ID": "Medlems-id",
"Membership ID copied to clipboard": "Medlems-ID kopieret til udklipsholder", "Membership ID copied to clipboard": "Medlems-ID kopieret til udklipsholder",
"Membership cards": "Medlemskort",
"Menu": "Menu", "Menu": "Menu",
"Modify": "Ændre", "Modify": "Ændre",
"Month": "Måned", "Month": "Måned",
@@ -149,10 +161,8 @@
"Nearby companies": "Nærliggende virksomheder", "Nearby companies": "Nærliggende virksomheder",
"New password": "Nyt kodeord", "New password": "Nyt kodeord",
"Next": "Næste", "Next": "Næste",
"next level:": "Næste niveau:",
"night": "nat",
"nights": "nætter",
"Nights needed to level up": "Nætter nødvendige for at komme i niveau", "Nights needed to level up": "Nætter nødvendige for at komme i niveau",
"No breakfast": "Ingen morgenmad",
"No content published": "Intet indhold offentliggjort", "No content published": "Intet indhold offentliggjort",
"No matching location found": "Der blev ikke fundet nogen matchende placering", "No matching location found": "Der blev ikke fundet nogen matchende placering",
"No results": "Ingen resultater", "No results": "Ingen resultater",
@@ -162,13 +172,11 @@
"Non-refundable": "Ikke-refunderbart", "Non-refundable": "Ikke-refunderbart",
"Not found": "Ikke fundet", "Not found": "Ikke fundet",
"Nr night, nr adult": "{nights, number} nat, {adults, number} voksen", "Nr night, nr adult": "{nights, number} nat, {adults, number} voksen",
"number": "nummer",
"On your journey": "På din rejse", "On your journey": "På din rejse",
"Open": "Åben", "Open": "Åben",
"Open language menu": "Åbn sprogmenuen", "Open language menu": "Åbn sprogmenuen",
"Open menu": "Åbn menuen", "Open menu": "Åbn menuen",
"Open my pages menu": "Åbn mine sider menuen", "Open my pages menu": "Åbn mine sider menuen",
"or": "eller",
"Overview": "Oversigt", "Overview": "Oversigt",
"Parking": "Parkering", "Parking": "Parkering",
"Parking / Garage": "Parkering / Garage", "Parking / Garage": "Parkering / Garage",
@@ -180,7 +188,6 @@
"Phone is required": "Telefonnummer er påkrævet", "Phone is required": "Telefonnummer er påkrævet",
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer", "Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer",
"points": "Point",
"Points": "Point", "Points": "Point",
"Points being calculated": "Point udregnes", "Points being calculated": "Point udregnes",
"Points earned prior to May 1, 2021": "Point optjent inden 1. maj 2021", "Points earned prior to May 1, 2021": "Point optjent inden 1. maj 2021",
@@ -188,17 +195,24 @@
"Points needed to level up": "Point nødvendige for at stige i niveau", "Points needed to level up": "Point nødvendige for at stige i niveau",
"Points needed to stay on level": "Point nødvendige for at holde sig på niveau", "Points needed to stay on level": "Point nødvendige for at holde sig på niveau",
"Previous victories": "Tidligere sejre", "Previous victories": "Tidligere sejre",
"Proceed to payment method": "Fortsæt til betalingsmetode",
"Public price from": "Offentlig pris fra", "Public price from": "Offentlig pris fra",
"Public transport": "Offentlig transport", "Public transport": "Offentlig transport",
"Queen bed": "Queensize-seng",
"Read more": "Læs mere", "Read more": "Læs mere",
"Read more & book a table": "Read more & book a table",
"Read more about the hotel": "Læs mere om hotellet", "Read more about the hotel": "Læs mere om hotellet",
"Read more about wellness & exercise": "Read more about wellness & exercise",
"Remove card from member profile": "Fjern kortet fra medlemsprofilen", "Remove card from member profile": "Fjern kortet fra medlemsprofilen",
"Restaurant": "Restaurant", "Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}",
"Restaurant & Bar": "Restaurant & Bar", "Restaurant & Bar": "Restaurant & Bar",
"Restaurants & Bars": "Restaurants & Bars",
"Retype new password": "Gentag den nye adgangskode", "Retype new password": "Gentag den nye adgangskode",
"Room & Terms": "Værelse & Vilkår", "Room & Terms": "Værelse & Vilkår",
"Room facilities": "Værelsesfaciliteter", "Room facilities": "Værelsesfaciliteter",
"Rooms": "Værelser", "Rooms": "Værelser",
"Rooms & Guests": "Værelser & gæster",
"Sauna and gym": "Sauna and gym",
"Save": "Gemme", "Save": "Gemme",
"Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop", "Scandic Friends Point Shop": "Scandic Friends Point Shop",
@@ -210,6 +224,7 @@
"Select a country": "Vælg et land", "Select a country": "Vælg et land",
"Select country of residence": "Vælg bopælsland", "Select country of residence": "Vælg bopælsland",
"Select date of birth": "Vælg fødselsdato", "Select date of birth": "Vælg fødselsdato",
"Select dates": "Vælg datoer",
"Select language": "Vælg sprog", "Select language": "Vælg sprog",
"Select your language": "Vælg dit sprog", "Select your language": "Vælg dit sprog",
"Shopping": "Shopping", "Shopping": "Shopping",
@@ -223,29 +238,25 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.", "Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.", "Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.",
"Something went wrong!": "Noget gik galt!", "Something went wrong!": "Noget gik galt!",
"special character": "speciel karakter",
"spendable points expiring by": "{points} Brugbare point udløber den {date}",
"Sports": "Sport", "Sports": "Sport",
"Standard price": "Standardpris", "Standard price": "Standardpris",
"Street": "Gade", "Street": "Gade",
"Successfully updated profile!": "Profilen er opdateret med succes!", "Successfully updated profile!": "Profilen er opdateret med succes!",
"Summary": "Opsummering", "Summary": "Opsummering",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.",
"Thank you": "Tak", "Thank you": "Tak",
"Theatre": "Teater", "Theatre": "Teater",
"There are no transactions to display": "Der er ingen transaktioner at vise", "There are no transactions to display": "Der er ingen transaktioner at vise",
"Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}", "Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}",
"to": "til",
"Total Points": "Samlet antal point", "Total Points": "Samlet antal point",
"Tourist": "Turist", "Tourist": "Turist",
"Transaction date": "Overførselsdato", "Transaction date": "Overførselsdato",
"Transactions": "Transaktioner", "Transactions": "Transaktioner",
"Transportations": "Transport", "Transportations": "Transport",
"Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)", "Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Sengtype", "Type of bed": "Sengtype",
"Type of room": "Værelsestype", "Type of room": "Værelsestype",
"uppercase letter": "stort bogstav",
"Use bonus cheque": "Brug Bonus Cheque", "Use bonus cheque": "Brug Bonus Cheque",
"User information": "Brugeroplysninger", "User information": "Brugeroplysninger",
"View as list": "Vis som liste", "View as list": "Vis som liste",
@@ -268,12 +279,13 @@
"Year": "År", "Year": "År",
"Yes, discard changes": "Ja, kasser ændringer", "Yes, discard changes": "Ja, kasser ændringer",
"Yes, remove my card": "Ja, fjern mit kort", "Yes, remove my card": "Ja, fjern mit kort",
"You can always change your mind later and add breakfast at the hotel.": "Du kan altid ombestemme dig senere og tilføje morgenmad på hotellet.",
"You canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.", "You canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.",
"You have no previous stays.": "Du har ingen tidligere ophold.", "You have no previous stays.": "Du har ingen tidligere ophold.",
"You have no upcoming stays.": "Du har ingen kommende ophold.", "You have no upcoming stays.": "Du har ingen kommende ophold.",
"Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!",
"Your card was successfully removed!": "Dit kort blev fjernet!", "Your card was successfully removed!": "Dit kort blev fjernet!",
"Your card was successfully saved!": "Dit kort blev gemt!", "Your card was successfully saved!": "Dit kort blev gemt!",
"Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!",
"Your current level": "Dit nuværende niveau", "Your current level": "Dit nuværende niveau",
"Your details": "Dine oplysninger", "Your details": "Dine oplysninger",
"Your level": "Dit niveau", "Your level": "Dit niveau",
@@ -281,5 +293,28 @@
"Zip code": "Postnummer", "Zip code": "Postnummer",
"Zoo": "Zoo", "Zoo": "Zoo",
"Zoom in": "Zoom ind", "Zoom in": "Zoom ind",
"Zoom out": "Zoom ud" "Zoom out": "Zoom ud",
"as of today": "fra idag",
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
"booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}",
"booking.rooms": "{totalRooms, plural, one {# værelse} other {# værelser}}",
"by": "inden",
"characters": "tegn",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personer",
"hotelPages.rooms.roomCard.seeRoomDetails": "Se værelsesdetaljer",
"km to city center": "km til byens centrum",
"next level:": "Næste niveau:",
"night": "nat",
"nights": "nætter",
"number": "nummer",
"or": "eller",
"points": "Point",
"special character": "speciel karakter",
"spendable points expiring by": "{points} Brugbare point udløber den {date}",
"to": "til",
"uppercase letter": "stort bogstav",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm"
} }

View File

@@ -1,39 +1,43 @@
{ {
"<b>Included</b> (based on availability)": "<b>Inbegriffen</b> (je nach Verfügbarkeit)",
"<b>{amount} {currency}</b>/night per adult": "<b>{amount} {currency}</b>/Nacht pro Erwachsener",
"A destination or hotel name is needed to be able to search for a hotel room.": "Ein Reiseziel oder Hotelname wird benötigt, um nach einem Hotelzimmer suchen zu können.", "A destination or hotel name is needed to be able to search for a hotel room.": "Ein Reiseziel oder Hotelname wird benötigt, um nach einem Hotelzimmer suchen zu können.",
"A photo of the room": "Ein Foto des Zimmers", "A photo of the room": "Ein Foto des Zimmers",
"About meetings & conferences": "About meetings & conferences",
"Activities": "Aktivitäten", "Activities": "Aktivitäten",
"Add code": "Code hinzufügen", "Add code": "Code hinzufügen",
"Add new card": "Neue Karte hinzufügen", "Add new card": "Neue Karte hinzufügen",
"Address": "Adresse", "Address": "Adresse",
"Airport": "Flughafen", "Airport": "Flughafen",
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle unsere Frühstücksbuffets bieten glutenfreie, vegane und allergikerfreundliche Speisen.",
"Already a friend?": "Sind wir schon Freunde?", "Already a friend?": "Sind wir schon Freunde?",
"Amenities": "Annehmlichkeiten", "Amenities": "Annehmlichkeiten",
"Amusement park": "Vergnügungspark", "Amusement park": "Vergnügungspark",
"An error occurred trying to manage your preferences, please try again later.": "Beim Versuch, Ihre Einstellungen zu verwalten, ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
"An error occurred when adding a credit card, please try again later.": "Beim Hinzufügen einer Kreditkarte ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.", "An error occurred when adding a credit card, please try again later.": "Beim Hinzufügen einer Kreditkarte ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
"An error occurred when trying to update profile.": "Beim Versuch, das Profil zu aktualisieren, ist ein Fehler aufgetreten.", "An error occurred when trying to update profile.": "Beim Versuch, das Profil zu aktualisieren, ist ein Fehler aufgetreten.",
"Any changes you've made will be lost.": "Alle Änderungen, die Sie vorgenommen haben, gehen verloren.", "Any changes you've made will be lost.": "Alle Änderungen, die Sie vorgenommen haben, gehen verloren.",
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Möchten Sie die Karte mit der Endung {lastFourDigits} wirklich aus Ihrem Mitgliedsprofil entfernen?", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Möchten Sie die Karte mit der Endung {lastFourDigits} wirklich aus Ihrem Mitgliedsprofil entfernen?",
"Arrival date": "Ankunftsdatum", "Arrival date": "Ankunftsdatum",
"as of today": "Stand heute",
"As our": "Als unser {level}", "As our": "Als unser {level}",
"As our Close Friend": "Als unser enger Freund", "As our Close Friend": "Als unser enger Freund",
"At latest": "Spätestens", "At latest": "Spätestens",
"At the hotel": "Im Hotel", "At the hotel": "Im Hotel",
"Attractions": "Attraktionen", "Attractions": "Attraktionen",
"Back to scandichotels.com": "Zurück zu scandichotels.com", "Back to scandichotels.com": "Zurück zu scandichotels.com",
"Bar": "Bar",
"Bed type": "Bettentyp", "Bed type": "Bettentyp",
"Book": "Buchen", "Book": "Buchen",
"Book reward night": "Bonusnacht buchen", "Book reward night": "Bonusnacht buchen",
"Booking number": "Buchungsnummer", "Booking number": "Buchungsnummer",
"booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}",
"Breakfast": "Frühstück", "Breakfast": "Frühstück",
"Breakfast buffet": "Frühstücksbuffet",
"Breakfast excluded": "Frühstück nicht inbegriffen", "Breakfast excluded": "Frühstück nicht inbegriffen",
"Breakfast included": "Frühstück inbegriffen", "Breakfast included": "Frühstück inbegriffen",
"Breakfast restaurant": "Breakfast restaurant",
"Bus terminal": "Busbahnhof", "Bus terminal": "Busbahnhof",
"Business": "Geschäft", "Business": "Geschäft",
"by": "bis",
"Cancel": "Stornieren", "Cancel": "Stornieren",
"characters": "figuren",
"Check in": "Einchecken", "Check in": "Einchecken",
"Check out": "Auschecken", "Check out": "Auschecken",
"Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Sehen Sie sich die in Ihrem Profil gespeicherten Kreditkarten an. Bezahlen Sie mit einer gespeicherten Karte, wenn Sie angemeldet sind, für ein reibungsloseres Web-Erlebnis.", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Sehen Sie sich die in Ihrem Profil gespeicherten Kreditkarten an. Bezahlen Sie mit einer gespeicherten Karte, wenn Sie angemeldet sind, für ein reibungsloseres Web-Erlebnis.",
@@ -64,32 +68,40 @@
"Date of Birth": "Geburtsdatum", "Date of Birth": "Geburtsdatum",
"Day": "Tag", "Day": "Tag",
"Description": "Beschreibung", "Description": "Beschreibung",
"Destination": "Bestimmungsort",
"Destinations & hotels": "Reiseziele & Hotels", "Destinations & hotels": "Reiseziele & Hotels",
"Discard changes": "Änderungen verwerfen", "Discard changes": "Änderungen verwerfen",
"Discard unsaved changes?": "Nicht gespeicherte Änderungen verwerfen?", "Discard unsaved changes?": "Nicht gespeicherte Änderungen verwerfen?",
"Distance to city centre": "{number}km zum Stadtzentrum", "Distance to city centre": "{number}km zum Stadtzentrum",
"Do you want to start the day with Scandics famous breakfast buffé?": "Möchten Sie den Tag mit Scandics berühmtem Frühstücksbuffet beginnen?", "Do you want to start the day with Scandics famous breakfast buffé?": "Möchten Sie den Tag mit Scandics berühmtem Frühstücksbuffet beginnen?",
"Download the Scandic app": "Laden Sie die Scandic-App herunter", "Download the Scandic app": "Laden Sie die Scandic-App herunter",
"Earn bonus nights & points": "Sammeln Sie Bonusnächte und -punkte",
"Edit": "Bearbeiten", "Edit": "Bearbeiten",
"Edit profile": "Profil bearbeiten", "Edit profile": "Profil bearbeiten",
"Email": "Email", "Email": "Email",
"Email address": "E-Mail-Adresse",
"Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences",
"Enter destination or hotel": "Reiseziel oder Hotel eingeben", "Enter destination or hotel": "Reiseziel oder Hotel eingeben",
"Events that make an impression": "Events that make an impression",
"Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile", "Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile",
"Explore nearby": "Erkunden Sie die Umgebung", "Explore nearby": "Erkunden Sie die Umgebung",
"Extras to your booking": "Extras zu Ihrer Buchung", "Extras to your booking": "Extras zu Ihrer Buchung",
"FAQ": "Häufig gestellte Fragen",
"Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.", "Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.",
"Fair": "Messe", "Fair": "Messe",
"FAQ": "Häufig gestellte Fragen",
"Find booking": "Buchung finden", "Find booking": "Buchung finden",
"Find hotels": "Hotels finden", "Find hotels": "Hotels finden",
"Firstname": "Vorname",
"Flexibility": "Flexibilität", "Flexibility": "Flexibilität",
"Former Scandic Hotel": "Ehemaliges Scandic Hotel", "Former Scandic Hotel": "Ehemaliges Scandic Hotel",
"Free cancellation": "Kostenlose Stornierung", "Free cancellation": "Kostenlose Stornierung",
"Free rebooking": "Kostenlose Umbuchung", "Free rebooking": "Kostenlose Umbuchung",
"From": "Fromm", "From": "Fromm",
"Get inspired": "Lassen Sie sich inspieren", "Get inspired": "Lassen Sie sich inspieren",
"Get member benefits & offers": "Holen Sie sich Vorteile und Angebote für Mitglieder",
"Go back to edit": "Zurück zum Bearbeiten", "Go back to edit": "Zurück zum Bearbeiten",
"Go back to overview": "Zurück zur Übersicht", "Go back to overview": "Zurück zur Übersicht",
"Guest information": "Informationen für Gäste",
"Guests & Rooms": "Gäste & Zimmer", "Guests & Rooms": "Gäste & Zimmer",
"Hi": "Hallo", "Hi": "Hallo",
"Highest level": "Höchstes Level", "Highest level": "Höchstes Level",
@@ -97,16 +109,16 @@
"Hotel": "Hotel", "Hotel": "Hotel",
"Hotel facilities": "Hotel-Infos", "Hotel facilities": "Hotel-Infos",
"Hotel surroundings": "Umgebung des Hotels", "Hotel surroundings": "Umgebung des Hotels",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personen",
"hotelPages.rooms.roomCard.seeRoomDetails": "Zimmerdetails ansehen",
"Hotels": "Hotels", "Hotels": "Hotels",
"How do you want to sleep?": "Wie möchtest du schlafen?", "How do you want to sleep?": "Wie möchtest du schlafen?",
"How it works": "Wie es funktioniert", "How it works": "Wie es funktioniert",
"Image gallery": "Bildergalerie", "Image gallery": "Bildergalerie",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Es ist derzeit nicht möglich, Ihre Kommunikationseinstellungen zu verwalten. Bitte versuchen Sie es später erneut oder wenden Sie sich an den Support, wenn das Problem weiterhin besteht.",
"Join Scandic Friends": "Treten Sie Scandic Friends bei", "Join Scandic Friends": "Treten Sie Scandic Friends bei",
"km to city center": "km bis zum Stadtzentrum", "Join at no cost": "Kostenlos beitreten",
"King bed": "Kingsize-Bett",
"Language": "Sprache", "Language": "Sprache",
"Lastname": "Nachname",
"Latest searches": "Letzte Suchanfragen", "Latest searches": "Letzte Suchanfragen",
"Level": "Level", "Level": "Level",
"Level 1": "Level 1", "Level 1": "Level 1",
@@ -132,9 +144,9 @@
"Member price": "Mitgliederpreis", "Member price": "Mitgliederpreis",
"Member price from": "Mitgliederpreis ab", "Member price from": "Mitgliederpreis ab",
"Members": "Mitglieder", "Members": "Mitglieder",
"Membership cards": "Mitgliedskarten",
"Membership ID": "Mitglieds-ID", "Membership ID": "Mitglieds-ID",
"Membership ID copied to clipboard": "Mitglieds-ID in die Zwischenablage kopiert", "Membership ID copied to clipboard": "Mitglieds-ID in die Zwischenablage kopiert",
"Membership cards": "Mitgliedskarten",
"Menu": "Menu", "Menu": "Menu",
"Modify": "Ändern", "Modify": "Ändern",
"Month": "Monat", "Month": "Monat",
@@ -149,10 +161,8 @@
"Nearby companies": "Nahe gelegene Unternehmen", "Nearby companies": "Nahe gelegene Unternehmen",
"New password": "Neues Kennwort", "New password": "Neues Kennwort",
"Next": "Nächste", "Next": "Nächste",
"next level:": "Nächstes Level:",
"night": "nacht",
"nights": "Nächte",
"Nights needed to level up": "Nächte, die zum Levelaufstieg benötigt werden", "Nights needed to level up": "Nächte, die zum Levelaufstieg benötigt werden",
"No breakfast": "Kein Frühstück",
"No content published": "Kein Inhalt veröffentlicht", "No content published": "Kein Inhalt veröffentlicht",
"No matching location found": "Kein passender Standort gefunden", "No matching location found": "Kein passender Standort gefunden",
"No results": "Keine Ergebnisse", "No results": "Keine Ergebnisse",
@@ -162,16 +172,15 @@
"Non-refundable": "Nicht erstattungsfähig", "Non-refundable": "Nicht erstattungsfähig",
"Not found": "Nicht gefunden", "Not found": "Nicht gefunden",
"Nr night, nr adult": "{nights, number} Nacht, {adults, number} Erwachsener", "Nr night, nr adult": "{nights, number} Nacht, {adults, number} Erwachsener",
"number": "nummer",
"On your journey": "Auf deiner Reise", "On your journey": "Auf deiner Reise",
"Open": "Offen", "Open": "Offen",
"Open language menu": "Sprachmenü öffnen", "Open language menu": "Sprachmenü öffnen",
"Open menu": "Menü öffnen", "Open menu": "Menü öffnen",
"Open my pages menu": "Meine Seiten Menü öffnen", "Open my pages menu": "Meine Seiten Menü öffnen",
"or": "oder",
"Overview": "Übersicht", "Overview": "Übersicht",
"Parking": "Parken", "Parking": "Parken",
"Parking / Garage": "Parken / Garage", "Parking / Garage": "Parken / Garage",
"Password": "Passwort",
"Pay later": "Später bezahlen", "Pay later": "Später bezahlen",
"Pay now": "Jetzt bezahlen", "Pay now": "Jetzt bezahlen",
"Payment info": "Zahlungsinformationen", "Payment info": "Zahlungsinformationen",
@@ -179,7 +188,6 @@
"Phone is required": "Telefon ist erforderlich", "Phone is required": "Telefon ist erforderlich",
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein", "Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein",
"points": "Punkte",
"Points": "Punkte", "Points": "Punkte",
"Points being calculated": "Punkte werden berechnet", "Points being calculated": "Punkte werden berechnet",
"Points earned prior to May 1, 2021": "Zusammengeführte Punkte vor dem 1. Mai 2021", "Points earned prior to May 1, 2021": "Zusammengeführte Punkte vor dem 1. Mai 2021",
@@ -187,17 +195,24 @@
"Points needed to level up": "Punkte, die zum Levelaufstieg benötigt werden", "Points needed to level up": "Punkte, die zum Levelaufstieg benötigt werden",
"Points needed to stay on level": "Erforderliche Punkte, um auf diesem Level zu bleiben", "Points needed to stay on level": "Erforderliche Punkte, um auf diesem Level zu bleiben",
"Previous victories": "Bisherige Siege", "Previous victories": "Bisherige Siege",
"Proceed to payment method": "Weiter zur Zahlungsmethode",
"Public price from": "Öffentlicher Preis ab", "Public price from": "Öffentlicher Preis ab",
"Public transport": "Öffentliche Verkehrsmittel", "Public transport": "Öffentliche Verkehrsmittel",
"Queen bed": "Queensize-Bett",
"Read more": "Mehr lesen", "Read more": "Mehr lesen",
"Read more & book a table": "Read more & book a table",
"Read more about the hotel": "Lesen Sie mehr über das Hotel", "Read more about the hotel": "Lesen Sie mehr über das Hotel",
"Read more about wellness & exercise": "Read more about wellness & exercise",
"Remove card from member profile": "Karte aus dem Mitgliedsprofil entfernen", "Remove card from member profile": "Karte aus dem Mitgliedsprofil entfernen",
"Restaurant": "Restaurant", "Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}",
"Restaurant & Bar": "Restaurant & Bar", "Restaurant & Bar": "Restaurant & Bar",
"Restaurants & Bars": "Restaurants & Bars",
"Retype new password": "Neues Passwort erneut eingeben", "Retype new password": "Neues Passwort erneut eingeben",
"Room & Terms": "Zimmer & Bedingungen", "Room & Terms": "Zimmer & Bedingungen",
"Room facilities": "Zimmerausstattung", "Room facilities": "Zimmerausstattung",
"Rooms": "Räume", "Rooms": "Räume",
"Rooms & Guests": "Zimmer & Gäste",
"Sauna and gym": "Sauna and gym",
"Save": "Speichern", "Save": "Speichern",
"Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop", "Scandic Friends Point Shop": "Scandic Friends Point Shop",
@@ -209,6 +224,7 @@
"Select a country": "Wähle ein Land", "Select a country": "Wähle ein Land",
"Select country of residence": "Wählen Sie das Land Ihres Wohnsitzes aus", "Select country of residence": "Wählen Sie das Land Ihres Wohnsitzes aus",
"Select date of birth": "Geburtsdatum auswählen", "Select date of birth": "Geburtsdatum auswählen",
"Select dates": "Datum auswählen",
"Select language": "Sprache auswählen", "Select language": "Sprache auswählen",
"Select your language": "Wählen Sie Ihre Sprache", "Select your language": "Wählen Sie Ihre Sprache",
"Shopping": "Einkaufen", "Shopping": "Einkaufen",
@@ -222,29 +238,25 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.", "Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.", "Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.",
"Something went wrong!": "Etwas ist schief gelaufen!", "Something went wrong!": "Etwas ist schief gelaufen!",
"special character": "sonderzeichen",
"spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}",
"Sports": "Sport", "Sports": "Sport",
"Standard price": "Standardpreis", "Standard price": "Standardpreis",
"Street": "Straße", "Street": "Straße",
"Successfully updated profile!": "Profil erfolgreich aktualisiert!", "Successfully updated profile!": "Profil erfolgreich aktualisiert!",
"Summary": "Zusammenfassung", "Summary": "Zusammenfassung",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.",
"Thank you": "Danke", "Thank you": "Danke",
"Theatre": "Theater", "Theatre": "Theater",
"There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden", "There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden",
"Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}", "Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}",
"to": "zu",
"Total Points": "Gesamtpunktzahl", "Total Points": "Gesamtpunktzahl",
"Tourist": "Tourist", "Tourist": "Tourist",
"Transaction date": "Transaktionsdatum", "Transaction date": "Transaktionsdatum",
"Transactions": "Transaktionen", "Transactions": "Transaktionen",
"Transportations": "Transportmittel", "Transportations": "Transportmittel",
"Tripadvisor reviews": "{rating} ({count} Bewertungen auf Tripadvisor)", "Tripadvisor reviews": "{rating} ({count} Bewertungen auf Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Bettentyp", "Type of bed": "Bettentyp",
"Type of room": "Zimmerart", "Type of room": "Zimmerart",
"uppercase letter": "großbuchstabe",
"Use bonus cheque": "Bonusscheck nutzen", "Use bonus cheque": "Bonusscheck nutzen",
"User information": "Nutzerinformation", "User information": "Nutzerinformation",
"View as list": "Als Liste anzeigen", "View as list": "Als Liste anzeigen",
@@ -267,12 +279,13 @@
"Year": "Jahr", "Year": "Jahr",
"Yes, discard changes": "Ja, Änderungen verwerfen", "Yes, discard changes": "Ja, Änderungen verwerfen",
"Yes, remove my card": "Ja, meine Karte entfernen", "Yes, remove my card": "Ja, meine Karte entfernen",
"You can always change your mind later and add breakfast at the hotel.": "Sie können es sich später jederzeit anders überlegen und das Frühstück im Hotel hinzufügen.",
"You canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.", "You canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.",
"You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.", "You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.",
"You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.", "You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.",
"Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!",
"Your card was successfully removed!": "Ihre Karte wurde erfolgreich entfernt!", "Your card was successfully removed!": "Ihre Karte wurde erfolgreich entfernt!",
"Your card was successfully saved!": "Ihre Karte wurde erfolgreich gespeichert!", "Your card was successfully saved!": "Ihre Karte wurde erfolgreich gespeichert!",
"Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!",
"Your current level": "Ihr aktuelles Level", "Your current level": "Ihr aktuelles Level",
"Your details": "Ihre Angaben", "Your details": "Ihre Angaben",
"Your level": "Dein level", "Your level": "Dein level",
@@ -280,5 +293,28 @@
"Zip code": "PLZ", "Zip code": "PLZ",
"Zoo": "Zoo", "Zoo": "Zoo",
"Zoom in": "Vergrößern", "Zoom in": "Vergrößern",
"Zoom out": "Verkleinern" "Zoom out": "Verkleinern",
"as of today": "Stand heute",
"booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}",
"booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}",
"booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}",
"by": "bis",
"characters": "figuren",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personen",
"hotelPages.rooms.roomCard.seeRoomDetails": "Zimmerdetails ansehen",
"km to city center": "km bis zum Stadtzentrum",
"next level:": "Nächstes Level:",
"night": "nacht",
"nights": "Nächte",
"number": "nummer",
"or": "oder",
"points": "Punkte",
"special character": "sonderzeichen",
"spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}",
"to": "zu",
"uppercase letter": "großbuchstabe",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm"
} }

View File

@@ -1,41 +1,43 @@
{ {
"<b>Included</b> (based on availability)": "<b>Included</b> (based on availability)",
"<b>{amount} {currency}</b>/night per adult": "<b>{amount} {currency}</b>/night per adult",
"A destination or hotel name is needed to be able to search for a hotel room.": "A destination or hotel name is needed to be able to search for a hotel room.", "A destination or hotel name is needed to be able to search for a hotel room.": "A destination or hotel name is needed to be able to search for a hotel room.",
"A photo of the room": "A photo of the room", "A photo of the room": "A photo of the room",
"About meetings & conferences": "About meetings & conferences",
"Activities": "Activities", "Activities": "Activities",
"Add code": "Add code", "Add code": "Add code",
"Add new card": "Add new card", "Add new card": "Add new card",
"Address": "Address", "Address": "Address",
"Airport": "Airport", "Airport": "Airport",
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.",
"Already a friend?": "Already a friend?", "Already a friend?": "Already a friend?",
"Amenities": "Amenities", "Amenities": "Amenities",
"Amusement park": "Amusement park", "Amusement park": "Amusement park",
"An error occurred trying to manage your preferences, please try again later.": "An error occurred trying to manage your preferences, please try again later.",
"An error occurred when adding a credit card, please try again later.": "An error occurred when adding a credit card, please try again later.", "An error occurred when adding a credit card, please try again later.": "An error occurred when adding a credit card, please try again later.",
"An error occurred when trying to update profile.": "An error occurred when trying to update profile.", "An error occurred when trying to update profile.": "An error occurred when trying to update profile.",
"Any changes you've made will be lost.": "Any changes you've made will be lost.", "Any changes you've made will be lost.": "Any changes you've made will be lost.",
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?",
"Arrival date": "Arrival date", "Arrival date": "Arrival date",
"as of today": "as of today",
"As our": "As our {level}", "As our": "As our {level}",
"As our Close Friend": "As our Close Friend", "As our Close Friend": "As our Close Friend",
"At latest": "At latest", "At latest": "At latest",
"At the hotel": "At the hotel", "At the hotel": "At the hotel",
"Attractions": "Attractions", "Attractions": "Attractions",
"Back to scandichotels.com": "Back to scandichotels.com", "Back to scandichotels.com": "Back to scandichotels.com",
"Bar": "Bar",
"Bed type": "Bed type", "Bed type": "Bed type",
"Book": "Book", "Book": "Book",
"Book reward night": "Book reward night", "Book reward night": "Book reward night",
"Booking number": "Booking number", "Booking number": "Booking number",
"booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}",
"booking.nights": "{totalNights, plural, one {# night} other {# nights}}",
"booking.rooms": "{totalRooms, plural, one {# room} other {# rooms}}",
"Breakfast": "Breakfast", "Breakfast": "Breakfast",
"Breakfast buffet": "Breakfast buffet",
"Breakfast excluded": "Breakfast excluded", "Breakfast excluded": "Breakfast excluded",
"Breakfast included": "Breakfast included", "Breakfast included": "Breakfast included",
"Breakfast restaurant": "Breakfast restaurant",
"Bus terminal": "Bus terminal", "Bus terminal": "Bus terminal",
"Business": "Business", "Business": "Business",
"by": "by",
"Cancel": "Cancel", "Cancel": "Cancel",
"characters": "characters",
"Check in": "Check in", "Check in": "Check in",
"Check out": "Check out", "Check out": "Check out",
"Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.",
@@ -73,26 +75,33 @@
"Distance to city centre": "{number}km to city centre", "Distance to city centre": "{number}km to city centre",
"Do you want to start the day with Scandics famous breakfast buffé?": "Do you want to start the day with Scandics famous breakfast buffé?", "Do you want to start the day with Scandics famous breakfast buffé?": "Do you want to start the day with Scandics famous breakfast buffé?",
"Download the Scandic app": "Download the Scandic app", "Download the Scandic app": "Download the Scandic app",
"Earn bonus nights & points": "Earn bonus nights & points",
"Edit": "Edit", "Edit": "Edit",
"Edit profile": "Edit profile", "Edit profile": "Edit profile",
"Email": "Email", "Email": "Email",
"Email address": "Email address",
"Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences",
"Enter destination or hotel": "Enter destination or hotel", "Enter destination or hotel": "Enter destination or hotel",
"Events that make an impression": "Events that make an impression",
"Explore all levels and benefits": "Explore all levels and benefits", "Explore all levels and benefits": "Explore all levels and benefits",
"Explore nearby": "Explore nearby", "Explore nearby": "Explore nearby",
"Extras to your booking": "Extras to your booking", "Extras to your booking": "Extras to your booking",
"FAQ": "FAQ",
"Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.", "Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.",
"Fair": "Fair", "Fair": "Fair",
"FAQ": "FAQ",
"Find booking": "Find booking", "Find booking": "Find booking",
"Find hotels": "Find hotels", "Find hotels": "Find hotels",
"Firstname": "Firstname",
"Flexibility": "Flexibility", "Flexibility": "Flexibility",
"Former Scandic Hotel": "Former Scandic Hotel", "Former Scandic Hotel": "Former Scandic Hotel",
"Free cancellation": "Free cancellation", "Free cancellation": "Free cancellation",
"Free rebooking": "Free rebooking", "Free rebooking": "Free rebooking",
"From": "From", "From": "From",
"Get inspired": "Get inspired", "Get inspired": "Get inspired",
"Get member benefits & offers": "Get member benefits & offers",
"Go back to edit": "Go back to edit", "Go back to edit": "Go back to edit",
"Go back to overview": "Go back to overview", "Go back to overview": "Go back to overview",
"Guest information": "Guest information",
"Guests & Rooms": "Guests & Rooms", "Guests & Rooms": "Guests & Rooms",
"Hi": "Hi", "Hi": "Hi",
"Highest level": "Highest level", "Highest level": "Highest level",
@@ -100,16 +109,16 @@
"Hotel": "Hotel", "Hotel": "Hotel",
"Hotel facilities": "Hotel facilities", "Hotel facilities": "Hotel facilities",
"Hotel surroundings": "Hotel surroundings", "Hotel surroundings": "Hotel surroundings",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "persons",
"hotelPages.rooms.roomCard.seeRoomDetails": "See room details",
"Hotels": "Hotels", "Hotels": "Hotels",
"How do you want to sleep?": "How do you want to sleep?", "How do you want to sleep?": "How do you want to sleep?",
"How it works": "How it works", "How it works": "How it works",
"Image gallery": "Image gallery", "Image gallery": "Image gallery",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.",
"Join Scandic Friends": "Join Scandic Friends", "Join Scandic Friends": "Join Scandic Friends",
"km to city center": "km to city center", "Join at no cost": "Join at no cost",
"King bed": "King bed",
"Language": "Language", "Language": "Language",
"Lastname": "Lastname",
"Latest searches": "Latest searches", "Latest searches": "Latest searches",
"Level": "Level", "Level": "Level",
"Level 1": "Level 1", "Level 1": "Level 1",
@@ -135,9 +144,9 @@
"Member price": "Member price", "Member price": "Member price",
"Member price from": "Member price from", "Member price from": "Member price from",
"Members": "Members", "Members": "Members",
"Membership cards": "Membership cards",
"Membership ID": "Membership ID", "Membership ID": "Membership ID",
"Membership ID copied to clipboard": "Membership ID copied to clipboard", "Membership ID copied to clipboard": "Membership ID copied to clipboard",
"Membership cards": "Membership cards",
"Menu": "Menu", "Menu": "Menu",
"Modify": "Modify", "Modify": "Modify",
"Month": "Month", "Month": "Month",
@@ -152,10 +161,8 @@
"Nearby companies": "Nearby companies", "Nearby companies": "Nearby companies",
"New password": "New password", "New password": "New password",
"Next": "Next", "Next": "Next",
"next level:": "next level:",
"night": "night",
"nights": "nights",
"Nights needed to level up": "Nights needed to level up", "Nights needed to level up": "Nights needed to level up",
"No breakfast": "No breakfast",
"No content published": "No content published", "No content published": "No content published",
"No matching location found": "No matching location found", "No matching location found": "No matching location found",
"No results": "No results", "No results": "No results",
@@ -165,13 +172,11 @@
"Non-refundable": "Non-refundable", "Non-refundable": "Non-refundable",
"Not found": "Not found", "Not found": "Not found",
"Nr night, nr adult": "{nights, number} night, {adults, number} adult", "Nr night, nr adult": "{nights, number} night, {adults, number} adult",
"number": "number",
"On your journey": "On your journey", "On your journey": "On your journey",
"Open": "Open", "Open": "Open",
"Open language menu": "Open language menu", "Open language menu": "Open language menu",
"Open menu": "Open menu", "Open menu": "Open menu",
"Open my pages menu": "Open my pages menu", "Open my pages menu": "Open my pages menu",
"or": "or",
"Overview": "Overview", "Overview": "Overview",
"Parking": "Parking", "Parking": "Parking",
"Parking / Garage": "Parking / Garage", "Parking / Garage": "Parking / Garage",
@@ -183,7 +188,6 @@
"Phone is required": "Phone is required", "Phone is required": "Phone is required",
"Phone number": "Phone number", "Phone number": "Phone number",
"Please enter a valid phone number": "Please enter a valid phone number", "Please enter a valid phone number": "Please enter a valid phone number",
"points": "Points",
"Points": "Points", "Points": "Points",
"Points being calculated": "Points being calculated", "Points being calculated": "Points being calculated",
"Points earned prior to May 1, 2021": "Points earned prior to May 1, 2021", "Points earned prior to May 1, 2021": "Points earned prior to May 1, 2021",
@@ -191,17 +195,24 @@
"Points needed to level up": "Points needed to level up", "Points needed to level up": "Points needed to level up",
"Points needed to stay on level": "Points needed to stay on level", "Points needed to stay on level": "Points needed to stay on level",
"Previous victories": "Previous victories", "Previous victories": "Previous victories",
"Proceed to payment method": "Proceed to payment method",
"Public price from": "Public price from", "Public price from": "Public price from",
"Public transport": "Public transport", "Public transport": "Public transport",
"Queen bed": "Queen bed",
"Read more": "Read more", "Read more": "Read more",
"Read more & book a table": "Read more & book a table",
"Read more about the hotel": "Read more about the hotel", "Read more about the hotel": "Read more about the hotel",
"Read more about wellness & exercise": "Read more about wellness & exercise",
"Remove card from member profile": "Remove card from member profile", "Remove card from member profile": "Remove card from member profile",
"Restaurant": "Restaurant", "Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}",
"Restaurant & Bar": "Restaurant & Bar", "Restaurant & Bar": "Restaurant & Bar",
"Restaurants & Bars": "Restaurants & Bars",
"Retype new password": "Retype new password", "Retype new password": "Retype new password",
"Room & Terms": "Room & Terms", "Room & Terms": "Room & Terms",
"Room facilities": "Room facilities", "Room facilities": "Room facilities",
"Rooms": "Rooms", "Rooms": "Rooms",
"Rooms & Guests": "Rooms & Guests",
"Sauna and gym": "Sauna and gym",
"Save": "Save", "Save": "Save",
"Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop", "Scandic Friends Point Shop": "Scandic Friends Point Shop",
@@ -227,29 +238,25 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.", "Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.", "Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.",
"Something went wrong!": "Something went wrong!", "Something went wrong!": "Something went wrong!",
"special character": "special character",
"spendable points expiring by": "{points} spendable points expiring by {date}",
"Sports": "Sports", "Sports": "Sports",
"Standard price": "Standard price", "Standard price": "Standard price",
"Street": "Street", "Street": "Street",
"Successfully updated profile!": "Successfully updated profile!", "Successfully updated profile!": "Successfully updated profile!",
"Summary": "Summary", "Summary": "Summary",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.",
"Thank you": "Thank you", "Thank you": "Thank you",
"Theatre": "Theatre", "Theatre": "Theatre",
"There are no transactions to display": "There are no transactions to display", "There are no transactions to display": "There are no transactions to display",
"Things nearby HOTEL_NAME": "Things nearby {hotelName}", "Things nearby HOTEL_NAME": "Things nearby {hotelName}",
"to": "to",
"Total Points": "Total Points", "Total Points": "Total Points",
"Tourist": "Tourist", "Tourist": "Tourist",
"Transaction date": "Transaction date", "Transaction date": "Transaction date",
"Transactions": "Transactions", "Transactions": "Transactions",
"Transportations": "Transportations", "Transportations": "Transportations",
"Tripadvisor reviews": "{rating} ({count} reviews on Tripadvisor)", "Tripadvisor reviews": "{rating} ({count} reviews on Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Type of bed", "Type of bed": "Type of bed",
"Type of room": "Type of room", "Type of room": "Type of room",
"uppercase letter": "uppercase letter",
"Use bonus cheque": "Use bonus cheque", "Use bonus cheque": "Use bonus cheque",
"User information": "User information", "User information": "User information",
"View as list": "View as list", "View as list": "View as list",
@@ -258,7 +265,7 @@
"Visiting address": "Visiting address", "Visiting address": "Visiting address",
"We could not add a card right now, please try again later.": "We could not add a card right now, please try again later.", "We could not add a card right now, please try again later.": "We could not add a card right now, please try again later.",
"We couldn't find a matching location for your search.": "We couldn't find a matching location for your search.", "We couldn't find a matching location for your search.": "We couldn't find a matching location for your search.",
"We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", "We have sent a detailed confirmation of your booking to your email:": "We have sent a detailed confirmation of your booking to your email: ",
"We look forward to your visit!": "We look forward to your visit!", "We look forward to your visit!": "We look forward to your visit!",
"Weekdays": "Weekdays", "Weekdays": "Weekdays",
"Weekends": "Weekends", "Weekends": "Weekends",
@@ -272,12 +279,13 @@
"Year": "Year", "Year": "Year",
"Yes, discard changes": "Yes, discard changes", "Yes, discard changes": "Yes, discard changes",
"Yes, remove my card": "Yes, remove my card", "Yes, remove my card": "Yes, remove my card",
"You can always change your mind later and add breakfast at the hotel.": "You can always change your mind later and add breakfast at the hotel.",
"You canceled adding a new credit card.": "You canceled adding a new credit card.", "You canceled adding a new credit card.": "You canceled adding a new credit card.",
"You have no previous stays.": "You have no previous stays.", "You have no previous stays.": "You have no previous stays.",
"You have no upcoming stays.": "You have no upcoming stays.", "You have no upcoming stays.": "You have no upcoming stays.",
"Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!",
"Your card was successfully removed!": "Your card was successfully removed!", "Your card was successfully removed!": "Your card was successfully removed!",
"Your card was successfully saved!": "Your card was successfully saved!", "Your card was successfully saved!": "Your card was successfully saved!",
"Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!",
"Your current level": "Your current level", "Your current level": "Your current level",
"Your details": "Your details", "Your details": "Your details",
"Your level": "Your level", "Your level": "Your level",
@@ -285,5 +293,28 @@
"Zip code": "Zip code", "Zip code": "Zip code",
"Zoo": "Zoo", "Zoo": "Zoo",
"Zoom in": "Zoom in", "Zoom in": "Zoom in",
"Zoom out": "Zoom out" "Zoom out": "Zoom out",
"as of today": "as of today",
"booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}",
"booking.nights": "{totalNights, plural, one {# night} other {# nights}}",
"booking.rooms": "{totalRooms, plural, one {# room} other {# rooms}}",
"by": "by",
"characters": "characters",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "persons",
"hotelPages.rooms.roomCard.seeRoomDetails": "See room details",
"km to city center": "km to city center",
"next level:": "next level:",
"night": "night",
"nights": "nights",
"number": "number",
"or": "or",
"points": "Points",
"special character": "special character",
"spendable points expiring by": "{points} spendable points expiring by {date}",
"to": "to",
"uppercase letter": "uppercase letter",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm"
} }

View File

@@ -1,39 +1,43 @@
{ {
"<b>Included</b> (based on availability)": "<b>Sisältyy</b> (saatavuuden mukaan)",
"<b>{amount} {currency}</b>/night per adult": "<b>{amount} {currency}</b>/yö per aikuinen",
"A destination or hotel name is needed to be able to search for a hotel room.": "Kohteen tai hotellin nimi tarvitaan, jotta hotellihuonetta voidaan hakea.", "A destination or hotel name is needed to be able to search for a hotel room.": "Kohteen tai hotellin nimi tarvitaan, jotta hotellihuonetta voidaan hakea.",
"A photo of the room": "Kuva huoneesta", "A photo of the room": "Kuva huoneesta",
"About meetings & conferences": "About meetings & conferences",
"Activities": "Aktiviteetit", "Activities": "Aktiviteetit",
"Add code": "Lisää koodi", "Add code": "Lisää koodi",
"Add new card": "Lisää uusi kortti", "Add new card": "Lisää uusi kortti",
"Address": "Osoite", "Address": "Osoite",
"Airport": "Lentokenttä", "Airport": "Lentokenttä",
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Kaikki aamiaisbuffettimme tarjoavat gluteenittomia, vegaanisia ja allergiaystävällisiä vaihtoehtoja.",
"Already a friend?": "Oletko jo ystävä?", "Already a friend?": "Oletko jo ystävä?",
"Amenities": "Mukavuudet", "Amenities": "Mukavuudet",
"Amusement park": "Huvipuisto", "Amusement park": "Huvipuisto",
"An error occurred when adding a credit card, please try again later.": "Luottokorttia lisättäessä tapahtui virhe. Yritä myöhemmin uudelleen.", "An error occurred when adding a credit card, please try again later.": "Luottokorttia lisättäessä tapahtui virhe. Yritä myöhemmin uudelleen.",
"An error occurred trying to manage your preferences, please try again later.": "Asetusten hallinnassa tapahtui virhe. Yritä myöhemmin uudelleen.",
"An error occurred when trying to update profile.": "Profiilia päivitettäessä tapahtui virhe.", "An error occurred when trying to update profile.": "Profiilia päivitettäessä tapahtui virhe.",
"Any changes you've made will be lost.": "Kaikki tekemäsi muutokset menetetään.", "Any changes you've made will be lost.": "Kaikki tekemäsi muutokset menetetään.",
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Haluatko varmasti poistaa kortin, joka päättyy numeroon {lastFourDigits} jäsenprofiilistasi?", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Haluatko varmasti poistaa kortin, joka päättyy numeroon {lastFourDigits} jäsenprofiilistasi?",
"Arrival date": "Saapumispäivä", "Arrival date": "Saapumispäivä",
"as of today": "tänään",
"As our": "{level}-etu", "As our": "{level}-etu",
"As our Close Friend": "Läheisenä ystävänämme", "As our Close Friend": "Läheisenä ystävänämme",
"At latest": "Viimeistään", "At latest": "Viimeistään",
"At the hotel": "Hotellissa", "At the hotel": "Hotellissa",
"Attractions": "Nähtävyydet", "Attractions": "Nähtävyydet",
"Back to scandichotels.com": "Takaisin scandichotels.com", "Back to scandichotels.com": "Takaisin scandichotels.com",
"Bar": "Bar",
"Bed type": "Vuodetyyppi", "Bed type": "Vuodetyyppi",
"Book": "Varaa", "Book": "Varaa",
"Book reward night": "Kirjapalkinto-ilta", "Book reward night": "Kirjapalkinto-ilta",
"Booking number": "Varausnumero", "Booking number": "Varausnumero",
"booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}",
"Breakfast": "Aamiainen", "Breakfast": "Aamiainen",
"Breakfast buffet": "Aamiaisbuffet",
"Breakfast excluded": "Aamiainen ei sisälly", "Breakfast excluded": "Aamiainen ei sisälly",
"Breakfast included": "Aamiainen sisältyy", "Breakfast included": "Aamiainen sisältyy",
"Bus terminal": "Bussiasema", "Bus terminal": "Bussiasema",
"Business": "Business", "Business": "Business",
"by": "mennessä", "Breakfast restaurant": "Breakfast restaurant",
"Cancel": "Peruuttaa", "Cancel": "Peruuttaa",
"characters": "hahmoja",
"Check in": "Sisäänkirjautuminen", "Check in": "Sisäänkirjautuminen",
"Check out": "Uloskirjautuminen", "Check out": "Uloskirjautuminen",
"Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Tarkista profiiliisi tallennetut luottokortit. Maksa tallennetulla kortilla kirjautuneena, jotta verkkokokemus on sujuvampi.", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Tarkista profiiliisi tallennetut luottokortit. Maksa tallennetulla kortilla kirjautuneena, jotta verkkokokemus on sujuvampi.",
@@ -64,32 +68,40 @@
"Date of Birth": "Syntymäaika", "Date of Birth": "Syntymäaika",
"Day": "Päivä", "Day": "Päivä",
"Description": "Kuvaus", "Description": "Kuvaus",
"Destination": "Kohde",
"Destinations & hotels": "Kohteet ja hotellit", "Destinations & hotels": "Kohteet ja hotellit",
"Discard changes": "Hylkää muutokset", "Discard changes": "Hylkää muutokset",
"Discard unsaved changes?": "Hylkäätkö tallentamattomat muutokset?", "Discard unsaved changes?": "Hylkäätkö tallentamattomat muutokset?",
"Distance to city centre": "{number}km Etäisyys kaupunkiin", "Distance to city centre": "{number}km Etäisyys kaupunkiin",
"Do you want to start the day with Scandics famous breakfast buffé?": "Haluatko aloittaa päiväsi Scandicsin kuuluisalla aamiaisbuffella?", "Do you want to start the day with Scandics famous breakfast buffé?": "Haluatko aloittaa päiväsi Scandicsin kuuluisalla aamiaisbuffella?",
"Download the Scandic app": "Lataa Scandic-sovellus", "Download the Scandic app": "Lataa Scandic-sovellus",
"Earn bonus nights & points": "Ansaitse bonusöitä ja -pisteitä",
"Edit": "Muokata", "Edit": "Muokata",
"Edit profile": "Muokkaa profiilia", "Edit profile": "Muokkaa profiilia",
"Email": "Sähköposti", "Email": "Sähköposti",
"Email address": "Sähköpostiosoite",
"Enter destination or hotel": "Anna kohde tai hotelli", "Enter destination or hotel": "Anna kohde tai hotelli",
"Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences",
"Events that make an impression": "Events that make an impression",
"Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin", "Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin",
"Explore nearby": "Tutustu lähialueeseen", "Explore nearby": "Tutustu lähialueeseen",
"Extras to your booking": "Varauksessa lisäpalveluita", "Extras to your booking": "Varauksessa lisäpalveluita",
"FAQ": "UKK",
"Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.", "Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.",
"Fair": "Messukeskus", "Fair": "Messukeskus",
"FAQ": "UKK",
"Find booking": "Etsi varaus", "Find booking": "Etsi varaus",
"Find hotels": "Etsi hotelleja", "Find hotels": "Etsi hotelleja",
"Firstname": "Etunimi",
"Flexibility": "Joustavuus", "Flexibility": "Joustavuus",
"Former Scandic Hotel": "Entinen Scandic-hotelli", "Former Scandic Hotel": "Entinen Scandic-hotelli",
"Free cancellation": "Ilmainen peruutus", "Free cancellation": "Ilmainen peruutus",
"Free rebooking": "Ilmainen uudelleenvaraus", "Free rebooking": "Ilmainen uudelleenvaraus",
"From": "From", "From": "From",
"Get inspired": "Inspiroidu", "Get inspired": "Inspiroidu",
"Get member benefits & offers": "Hanki jäsenetuja ja -tarjouksia",
"Go back to edit": "Palaa muokkaamaan", "Go back to edit": "Palaa muokkaamaan",
"Go back to overview": "Palaa yleiskatsaukseen", "Go back to overview": "Palaa yleiskatsaukseen",
"Guest information": "Vieraan tiedot",
"Guests & Rooms": "Vieraat & Huoneet", "Guests & Rooms": "Vieraat & Huoneet",
"Hi": "Hi", "Hi": "Hi",
"Highest level": "Korkein taso", "Highest level": "Korkein taso",
@@ -97,16 +109,16 @@
"Hotel": "Hotelli", "Hotel": "Hotelli",
"Hotel facilities": "Hotellin palvelut", "Hotel facilities": "Hotellin palvelut",
"Hotel surroundings": "Hotellin ympäristö", "Hotel surroundings": "Hotellin ympäristö",
"hotelPages.rooms.roomCard.person": "henkilö",
"hotelPages.rooms.roomCard.persons": "Henkilöä",
"hotelPages.rooms.roomCard.seeRoomDetails": "Katso huoneen tiedot",
"Hotels": "Hotellit", "Hotels": "Hotellit",
"How do you want to sleep?": "Kuinka haluat nukkua?", "How do you want to sleep?": "Kuinka haluat nukkua?",
"How it works": "Kuinka se toimii", "How it works": "Kuinka se toimii",
"Image gallery": "Kuvagalleria", "Image gallery": "Kuvagalleria",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Viestintäasetuksiasi ei voi hallita juuri nyt. Yritä myöhemmin uudelleen tai ota yhteyttä tukeen, jos ongelma jatkuu.",
"Join Scandic Friends": "Liity jäseneksi", "Join Scandic Friends": "Liity jäseneksi",
"km to city center": "km keskustaan", "Join at no cost": "Liity maksutta",
"King bed": "King-vuode",
"Language": "Kieli", "Language": "Kieli",
"Lastname": "Sukunimi",
"Latest searches": "Viimeisimmät haut", "Latest searches": "Viimeisimmät haut",
"Level": "Level", "Level": "Level",
"Level 1": "Taso 1", "Level 1": "Taso 1",
@@ -132,9 +144,9 @@
"Member price": "Jäsenhinta", "Member price": "Jäsenhinta",
"Member price from": "Jäsenhinta alkaen", "Member price from": "Jäsenhinta alkaen",
"Members": "Jäsenet", "Members": "Jäsenet",
"Membership cards": "Jäsenkortit",
"Membership ID": "Jäsentunnus", "Membership ID": "Jäsentunnus",
"Membership ID copied to clipboard": "Jäsenyystunnus kopioitu leikepöydälle", "Membership ID copied to clipboard": "Jäsenyystunnus kopioitu leikepöydälle",
"Membership cards": "Jäsenkortit",
"Menu": "Valikko", "Menu": "Valikko",
"Modify": "Muokkaa", "Modify": "Muokkaa",
"Month": "Kuukausi", "Month": "Kuukausi",
@@ -149,10 +161,8 @@
"Nearby companies": "Läheiset yritykset", "Nearby companies": "Läheiset yritykset",
"New password": "Uusi salasana", "New password": "Uusi salasana",
"Next": "Seuraava", "Next": "Seuraava",
"next level:": "pistettä tasolle:",
"night": "yö",
"nights": "yötä",
"Nights needed to level up": "Yöt, joita tarvitaan tasolle", "Nights needed to level up": "Yöt, joita tarvitaan tasolle",
"No breakfast": "Ei aamiaista",
"No content published": "Ei julkaistua sisältöä", "No content published": "Ei julkaistua sisältöä",
"No matching location found": "Vastaavaa sijaintia ei löytynyt", "No matching location found": "Vastaavaa sijaintia ei löytynyt",
"No results": "Ei tuloksia", "No results": "Ei tuloksia",
@@ -162,13 +172,11 @@
"Non-refundable": "Ei palautettavissa", "Non-refundable": "Ei palautettavissa",
"Not found": "Ei löydetty", "Not found": "Ei löydetty",
"Nr night, nr adult": "{nights, number} yö, {adults, number} aikuinen", "Nr night, nr adult": "{nights, number} yö, {adults, number} aikuinen",
"number": "määrä",
"On your journey": "Matkallasi", "On your journey": "Matkallasi",
"Open": "Avata", "Open": "Avata",
"Open language menu": "Avaa kielivalikko", "Open language menu": "Avaa kielivalikko",
"Open menu": "Avaa valikko", "Open menu": "Avaa valikko",
"Open my pages menu": "Avaa omat sivut -valikko", "Open my pages menu": "Avaa omat sivut -valikko",
"or": "tai",
"Overview": "Yleiskatsaus", "Overview": "Yleiskatsaus",
"Parking": "Pysäköinti", "Parking": "Pysäköinti",
"Parking / Garage": "Pysäköinti / Autotalli", "Parking / Garage": "Pysäköinti / Autotalli",
@@ -180,7 +188,6 @@
"Phone is required": "Puhelin vaaditaan", "Phone is required": "Puhelin vaaditaan",
"Phone number": "Puhelinnumero", "Phone number": "Puhelinnumero",
"Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero", "Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero",
"points": "pistettä",
"Points": "Pisteet", "Points": "Pisteet",
"Points being calculated": "Pisteitä lasketaan", "Points being calculated": "Pisteitä lasketaan",
"Points earned prior to May 1, 2021": "Pisteet, jotka ansaittu ennen 1.5.2021", "Points earned prior to May 1, 2021": "Pisteet, jotka ansaittu ennen 1.5.2021",
@@ -188,17 +195,25 @@
"Points needed to level up": "Tarvitset vielä", "Points needed to level up": "Tarvitset vielä",
"Points needed to stay on level": "Tällä tasolla pysymiseen tarvittavat pisteet", "Points needed to stay on level": "Tällä tasolla pysymiseen tarvittavat pisteet",
"Previous victories": "Edelliset voitot", "Previous victories": "Edelliset voitot",
"Proceed to payment method": "Siirry maksutavalle",
"Public price from": "Julkinen hinta alkaen", "Public price from": "Julkinen hinta alkaen",
"Public transport": "Julkinen liikenne", "Public transport": "Julkinen liikenne",
"Queen bed": "Queen-vuode",
"Read more": "Lue lisää", "Read more": "Lue lisää",
"Read more & book a table": "Read more & book a table",
"Read more about the hotel": "Lue lisää hotellista", "Read more about the hotel": "Lue lisää hotellista",
"Read more about wellness & exercise": "Read more about wellness & exercise",
"Remove card from member profile": "Poista kortti jäsenprofiilista", "Remove card from member profile": "Poista kortti jäsenprofiilista",
"Restaurant": "Ravintola", "Restaurant": "{count, plural, one {#Ravintola} other {#Restaurants}}",
"Restaurant & Bar": "Ravintola & Baari", "Restaurant & Bar": "Ravintola & Baari",
"Restaurants & Bars": "Restaurants & Bars",
"Retype new password": "Kirjoita uusi salasana uudelleen", "Retype new password": "Kirjoita uusi salasana uudelleen",
"Room & Terms": "Huone & Ehdot", "Room & Terms": "Huone & Ehdot",
"Room facilities": "Huoneen varustelu", "Room facilities": "Huoneen varustelu",
"Rooms": "Huoneet", "Rooms": "Huoneet",
"Rooms & Guests": "Huoneet & Vieraat",
"Rooms & Guestss": "Huoneet & Vieraat",
"Sauna and gym": "Sauna and gym",
"Save": "Tallenna", "Save": "Tallenna",
"Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop", "Scandic Friends Point Shop": "Scandic Friends Point Shop",
@@ -210,6 +225,7 @@
"Select a country": "Valitse maa", "Select a country": "Valitse maa",
"Select country of residence": "Valitse asuinmaa", "Select country of residence": "Valitse asuinmaa",
"Select date of birth": "Valitse syntymäaika", "Select date of birth": "Valitse syntymäaika",
"Select dates": "Valitse päivämäärät",
"Select language": "Valitse kieli", "Select language": "Valitse kieli",
"Select your language": "Valitse kieli", "Select your language": "Valitse kieli",
"Shopping": "Ostokset", "Shopping": "Ostokset",
@@ -223,29 +239,25 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.", "Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.", "Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.",
"Something went wrong!": "Jotain meni pieleen!", "Something went wrong!": "Jotain meni pieleen!",
"special character": "erikoishahmo",
"spendable points expiring by": "{points} pistettä vanhenee {date} mennessä",
"Sports": "Urheilu", "Sports": "Urheilu",
"Standard price": "Normaali hinta", "Standard price": "Normaali hinta",
"Street": "Katu", "Street": "Katu",
"Successfully updated profile!": "Profiilin päivitys onnistui!", "Successfully updated profile!": "Profiilin päivitys onnistui!",
"Summary": "Yhteenveto", "Summary": "Yhteenveto",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.",
"Thank you": "Kiitos", "Thank you": "Kiitos",
"Theatre": "Teatteri", "Theatre": "Teatteri",
"There are no transactions to display": "Näytettäviä tapahtumia ei ole", "There are no transactions to display": "Näytettäviä tapahtumia ei ole",
"Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}", "Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}",
"to": "to",
"Total Points": "Kokonaispisteet", "Total Points": "Kokonaispisteet",
"Tourist": "Turisti", "Tourist": "Turisti",
"Transaction date": "Tapahtuman päivämäärä", "Transaction date": "Tapahtuman päivämäärä",
"Transactions": "Tapahtumat", "Transactions": "Tapahtumat",
"Transportations": "Kuljetukset", "Transportations": "Kuljetukset",
"Tripadvisor reviews": "{rating} ({count} arvostelua TripAdvisorissa)", "Tripadvisor reviews": "{rating} ({count} arvostelua TripAdvisorissa)",
"TUI Points": "TUI Points",
"Type of bed": "Vuodetyyppi", "Type of bed": "Vuodetyyppi",
"Type of room": "Huonetyyppi", "Type of room": "Huonetyyppi",
"uppercase letter": "iso kirjain",
"Use bonus cheque": "Käytä bonussekkiä", "Use bonus cheque": "Käytä bonussekkiä",
"User information": "Käyttäjän tiedot", "User information": "Käyttäjän tiedot",
"View as list": "Näytä listana", "View as list": "Näytä listana",
@@ -268,12 +280,13 @@
"Year": "Vuosi", "Year": "Vuosi",
"Yes, discard changes": "Kyllä, hylkää muutokset", "Yes, discard changes": "Kyllä, hylkää muutokset",
"Yes, remove my card": "Kyllä, poista korttini", "Yes, remove my card": "Kyllä, poista korttini",
"You can always change your mind later and add breakfast at the hotel.": "Voit aina muuttaa mieltäsi myöhemmin ja lisätä aamiaisen hotelliin.",
"You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.", "You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.",
"You have no previous stays.": "Sinulla ei ole aiempia majoituksia.", "You have no previous stays.": "Sinulla ei ole aiempia majoituksia.",
"You have no upcoming stays.": "Sinulla ei ole tulevia majoituksia.", "You have no upcoming stays.": "Sinulla ei ole tulevia majoituksia.",
"Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!",
"Your card was successfully removed!": "Korttisi poistettiin onnistuneesti!", "Your card was successfully removed!": "Korttisi poistettiin onnistuneesti!",
"Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!", "Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!",
"Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!",
"Your current level": "Nykyinen tasosi", "Your current level": "Nykyinen tasosi",
"Your details": "Tietosi", "Your details": "Tietosi",
"Your level": "Tasosi", "Your level": "Tasosi",
@@ -281,5 +294,28 @@
"Zip code": "Postinumero", "Zip code": "Postinumero",
"Zoo": "Eläintarha", "Zoo": "Eläintarha",
"Zoom in": "Lähennä", "Zoom in": "Lähennä",
"Zoom out": "Loitonna" "Zoom out": "Loitonna",
"as of today": "tänään",
"booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}",
"booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}",
"booking.rooms": "{totalRooms, plural, one {# huone} other {# sviitti}}",
"by": "mennessä",
"characters": "hahmoja",
"hotelPages.rooms.roomCard.person": "henkilö",
"hotelPages.rooms.roomCard.persons": "Henkilöä",
"hotelPages.rooms.roomCard.seeRoomDetails": "Katso huoneen tiedot",
"km to city center": "km keskustaan",
"next level:": "pistettä tasolle:",
"night": "yö",
"nights": "yötä",
"number": "määrä",
"or": "tai",
"points": "pistettä",
"special character": "erikoishahmo",
"spendable points expiring by": "{points} pistettä vanhenee {date} mennessä",
"to": "to",
"uppercase letter": "iso kirjain",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm"
} }

View File

@@ -1,39 +1,42 @@
{ {
"<b>Included</b> (based on availability)": "<b>Inkludert</b> (basert på tilgjengelighet)",
"<b>{amount} {currency}</b>/night per adult": "<b>{amount} {currency}</b>/natt per voksen",
"A destination or hotel name is needed to be able to search for a hotel room.": "Et reisemål eller hotellnavn er nødvendig for å kunne søke etter et hotellrom.", "A destination or hotel name is needed to be able to search for a hotel room.": "Et reisemål eller hotellnavn er nødvendig for å kunne søke etter et hotellrom.",
"A photo of the room": "Et bilde av rommet", "A photo of the room": "Et bilde av rommet",
"About meetings & conferences": "About meetings & conferences",
"Activities": "Aktiviteter", "Activities": "Aktiviteter",
"Add code": "Legg til kode", "Add code": "Legg til kode",
"Add new card": "Legg til nytt kort", "Add new card": "Legg til nytt kort",
"Address": "Adresse", "Address": "Adresse",
"Airport": "Flyplass", "Airport": "Flyplass",
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle våre frokostbufféer tilbyr glutenfrie, veganske og allergivennlige alternativer.",
"Already a friend?": "Allerede Friend?", "Already a friend?": "Allerede Friend?",
"Amenities": "Fasiliteter", "Amenities": "Fasiliteter",
"Amusement park": "Tivoli", "Amusement park": "Tivoli",
"An error occurred when adding a credit card, please try again later.": "Det oppstod en feil ved å legge til et kredittkort. Prøv igjen senere.", "An error occurred when adding a credit card, please try again later.": "Det oppstod en feil ved å legge til et kredittkort. Prøv igjen senere.",
"An error occurred trying to manage your preferences, please try again later.": "Det oppstod en feil under forsøket på å administrere innstillingene dine. Prøv igjen senere.",
"An error occurred when trying to update profile.": "Det oppstod en feil under forsøk på å oppdatere profilen.", "An error occurred when trying to update profile.": "Det oppstod en feil under forsøk på å oppdatere profilen.",
"Any changes you've made will be lost.": "Eventuelle endringer du har gjort, går tapt.", "Any changes you've made will be lost.": "Eventuelle endringer du har gjort, går tapt.",
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på at du vil fjerne kortet som slutter på {lastFourDigits} fra medlemsprofilen din?", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på at du vil fjerne kortet som slutter på {lastFourDigits} fra medlemsprofilen din?",
"Arrival date": "Ankomstdato", "Arrival date": "Ankomstdato",
"as of today": "per idag",
"As our": "Som vår {level}", "As our": "Som vår {level}",
"As our Close Friend": "Som vår nære venn", "As our Close Friend": "Som vår nære venn",
"At latest": "Senest", "At latest": "Senest",
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Attraksjoner", "Attractions": "Attraksjoner",
"Back to scandichotels.com": "Tilbake til scandichotels.com", "Back to scandichotels.com": "Tilbake til scandichotels.com",
"Bar": "Bar",
"Bed type": "Seng type", "Bed type": "Seng type",
"Book": "Bestill", "Book": "Bestill",
"Book reward night": "Bestill belønningskveld", "Book reward night": "Bestill belønningskveld",
"Booking number": "Bestillingsnummer", "Booking number": "Bestillingsnummer",
"booking.nights": "{totalNights, plural, one {# natt} other {# netter}}",
"Breakfast": "Frokost", "Breakfast": "Frokost",
"Breakfast buffet": "Breakfast buffet",
"Breakfast excluded": "Frokost ekskludert", "Breakfast excluded": "Frokost ekskludert",
"Breakfast included": "Frokost inkludert", "Breakfast included": "Frokost inkludert",
"Bus terminal": "Bussterminal", "Bus terminal": "Bussterminal",
"Business": "Forretnings", "Business": "Forretnings",
"by": "innen",
"Cancel": "Avbryt", "Cancel": "Avbryt",
"characters": "tegn",
"Check in": "Sjekk inn", "Check in": "Sjekk inn",
"Check out": "Sjekk ut", "Check out": "Sjekk ut",
"Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Sjekk ut kredittkortene som er lagret på profilen din. Betal med et lagret kort når du er pålogget for en jevnere nettopplevelse.", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Sjekk ut kredittkortene som er lagret på profilen din. Betal med et lagret kort når du er pålogget for en jevnere nettopplevelse.",
@@ -64,16 +67,21 @@
"Date of Birth": "Fødselsdato", "Date of Birth": "Fødselsdato",
"Day": "Dag", "Day": "Dag",
"Description": "Beskrivelse", "Description": "Beskrivelse",
"Destination": "Destinasjon",
"Destinations & hotels": "Destinasjoner og hoteller", "Destinations & hotels": "Destinasjoner og hoteller",
"Discard changes": "Forkaste endringer", "Discard changes": "Forkaste endringer",
"Discard unsaved changes?": "Forkaste endringer som ikke er lagret?", "Discard unsaved changes?": "Forkaste endringer som ikke er lagret?",
"Distance to city centre": "{number}km til sentrum", "Distance to city centre": "{number}km til sentrum",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vil du starte dagen med Scandics berømte frokostbuffé?", "Do you want to start the day with Scandics famous breakfast buffé?": "Vil du starte dagen med Scandics berømte frokostbuffé?",
"Download the Scandic app": "Last ned Scandic-appen", "Download the Scandic app": "Last ned Scandic-appen",
"Earn bonus nights & points": "Tjen bonusnetter og poeng",
"Edit": "Redigere", "Edit": "Redigere",
"Edit profile": "Rediger profil", "Edit profile": "Rediger profil",
"Email": "E-post", "Email": "E-post",
"Email address": "E-postadresse",
"Enter destination or hotel": "Skriv inn destinasjon eller hotell", "Enter destination or hotel": "Skriv inn destinasjon eller hotell",
"Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences",
"Events that make an impression": "Events that make an impression",
"Explore all levels and benefits": "Utforsk alle nivåer og fordeler", "Explore all levels and benefits": "Utforsk alle nivåer og fordeler",
"Explore nearby": "Utforsk i nærheten", "Explore nearby": "Utforsk i nærheten",
"Extras to your booking": "Tilvalg til bestillingen din", "Extras to your booking": "Tilvalg til bestillingen din",
@@ -82,14 +90,17 @@
"FAQ": "FAQ", "FAQ": "FAQ",
"Find booking": "Finn booking", "Find booking": "Finn booking",
"Find hotels": "Finn hotell", "Find hotels": "Finn hotell",
"Firstname": "Fornavn",
"Flexibility": "Fleksibilitet", "Flexibility": "Fleksibilitet",
"Former Scandic Hotel": "Tidligere Scandic-hotell", "Former Scandic Hotel": "Tidligere Scandic-hotell",
"Free cancellation": "Gratis avbestilling", "Free cancellation": "Gratis avbestilling",
"Free rebooking": "Gratis ombooking", "Free rebooking": "Gratis ombooking",
"From": "Fra", "From": "Fra",
"Get inspired": "Bli inspirert", "Get inspired": "Bli inspirert",
"Get member benefits & offers": "Få medlemsfordeler og tilbud",
"Go back to edit": "Gå tilbake til redigering", "Go back to edit": "Gå tilbake til redigering",
"Go back to overview": "Gå tilbake til oversikten", "Go back to overview": "Gå tilbake til oversikten",
"Guest information": "Informasjon til gjester",
"Guests & Rooms": "Gjester & rom", "Guests & Rooms": "Gjester & rom",
"Hi": "Hei", "Hi": "Hei",
"Highest level": "Høyeste nivå", "Highest level": "Høyeste nivå",
@@ -97,16 +108,16 @@
"Hotel": "Hotel", "Hotel": "Hotel",
"Hotel facilities": "Hotelfaciliteter", "Hotel facilities": "Hotelfaciliteter",
"Hotel surroundings": "Hotellomgivelser", "Hotel surroundings": "Hotellomgivelser",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personer",
"hotelPages.rooms.roomCard.seeRoomDetails": "Se detaljer om rommet",
"Hotels": "Hoteller", "Hotels": "Hoteller",
"How do you want to sleep?": "Hvordan vil du sove?", "How do you want to sleep?": "Hvordan vil du sove?",
"How it works": "Hvordan det fungerer", "How it works": "Hvordan det fungerer",
"Image gallery": "Bildegalleri", "Image gallery": "Bildegalleri",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke mulig å administrere kommunikasjonspreferansene dine akkurat nå, prøv igjen senere eller kontakt support hvis problemet vedvarer.",
"Join Scandic Friends": "Bli med i Scandic Friends", "Join Scandic Friends": "Bli med i Scandic Friends",
"km to city center": "km til sentrum", "Join at no cost": "Bli med uten kostnad",
"King bed": "King-size-seng",
"Language": "Språk", "Language": "Språk",
"Lastname": "Etternavn",
"Latest searches": "Siste søk", "Latest searches": "Siste søk",
"Level": "Nivå", "Level": "Nivå",
"Level 1": "Nivå 1", "Level 1": "Nivå 1",
@@ -132,9 +143,9 @@
"Member price": "Medlemspris", "Member price": "Medlemspris",
"Member price from": "Medlemspris fra", "Member price from": "Medlemspris fra",
"Members": "Medlemmer", "Members": "Medlemmer",
"Membership cards": "Medlemskort",
"Membership ID": "Medlems-ID", "Membership ID": "Medlems-ID",
"Membership ID copied to clipboard": "Medlems-ID kopiert til utklippstavlen", "Membership ID copied to clipboard": "Medlems-ID kopiert til utklippstavlen",
"Membership cards": "Medlemskort",
"Menu": "Menu", "Menu": "Menu",
"Modify": "Endre", "Modify": "Endre",
"Month": "Måned", "Month": "Måned",
@@ -149,10 +160,8 @@
"Nearby companies": "Nærliggende selskaper", "Nearby companies": "Nærliggende selskaper",
"New password": "Nytt passord", "New password": "Nytt passord",
"Next": "Neste", "Next": "Neste",
"next level:": "Neste nivå:",
"night": "natt",
"nights": "netter",
"Nights needed to level up": "Netter som trengs for å komme opp i nivå", "Nights needed to level up": "Netter som trengs for å komme opp i nivå",
"No breakfast": "Ingen frokost",
"No content published": "Ingen innhold publisert", "No content published": "Ingen innhold publisert",
"No matching location found": "Fant ingen samsvarende plassering", "No matching location found": "Fant ingen samsvarende plassering",
"No results": "Ingen resultater", "No results": "Ingen resultater",
@@ -162,13 +171,11 @@
"Non-refundable": "Ikke-refunderbart", "Non-refundable": "Ikke-refunderbart",
"Not found": "Ikke funnet", "Not found": "Ikke funnet",
"Nr night, nr adult": "{nights, number} natt, {adults, number} voksen", "Nr night, nr adult": "{nights, number} natt, {adults, number} voksen",
"number": "antall",
"On your journey": "På reisen din", "On your journey": "På reisen din",
"Open": "Åpen", "Open": "Åpen",
"Open language menu": "Åpne språkmenyen", "Open language menu": "Åpne språkmenyen",
"Open menu": "Åpne menyen", "Open menu": "Åpne menyen",
"Open my pages menu": "Åpne mine sider menyen", "Open my pages menu": "Åpne mine sider menyen",
"or": "eller",
"Overview": "Oversikt", "Overview": "Oversikt",
"Parking": "Parkering", "Parking": "Parkering",
"Parking / Garage": "Parkering / Garasje", "Parking / Garage": "Parkering / Garasje",
@@ -180,7 +187,6 @@
"Phone is required": "Telefon kreves", "Phone is required": "Telefon kreves",
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer", "Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer",
"points": "poeng",
"Points": "Poeng", "Points": "Poeng",
"Points being calculated": "Poeng beregnes", "Points being calculated": "Poeng beregnes",
"Points earned prior to May 1, 2021": "Opptjente poeng før 1. mai 2021", "Points earned prior to May 1, 2021": "Opptjente poeng før 1. mai 2021",
@@ -188,17 +194,24 @@
"Points needed to level up": "Poeng som trengs for å komme opp i nivå", "Points needed to level up": "Poeng som trengs for å komme opp i nivå",
"Points needed to stay on level": "Poeng som trengs for å holde seg på nivå", "Points needed to stay on level": "Poeng som trengs for å holde seg på nivå",
"Previous victories": "Tidligere seire", "Previous victories": "Tidligere seire",
"Proceed to payment method": "Fortsett til betalingsmetode",
"Public price from": "Offentlig pris fra", "Public price from": "Offentlig pris fra",
"Public transport": "Offentlig transport", "Public transport": "Offentlig transport",
"Queen bed": "Queen-size-seng",
"Read more": "Les mer", "Read more": "Les mer",
"Read more & book a table": "Read more & book a table",
"Read more about the hotel": "Les mer om hotellet", "Read more about the hotel": "Les mer om hotellet",
"Read more about wellness & exercise": "Read more about wellness & exercise",
"Remove card from member profile": "Fjern kortet fra medlemsprofilen", "Remove card from member profile": "Fjern kortet fra medlemsprofilen",
"Restaurant": "Restaurant", "Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}",
"Restaurant & Bar": "Restaurant & Bar", "Restaurant & Bar": "Restaurant & Bar",
"Restaurants & Bars": "Restaurants & Bars",
"Retype new password": "Skriv inn nytt passord på nytt", "Retype new password": "Skriv inn nytt passord på nytt",
"Room & Terms": "Rom & Vilkår", "Room & Terms": "Rom & Vilkår",
"Room facilities": "Romfasiliteter", "Room facilities": "Romfasiliteter",
"Rooms": "Rom", "Rooms": "Rom",
"Rooms & Guests": "Rom og gjester",
"Sauna and gym": "Sauna and gym",
"Save": "Lagre", "Save": "Lagre",
"Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop", "Scandic Friends Point Shop": "Scandic Friends Point Shop",
@@ -210,6 +223,7 @@
"Select a country": "Velg et land", "Select a country": "Velg et land",
"Select country of residence": "Velg bostedsland", "Select country of residence": "Velg bostedsland",
"Select date of birth": "Velg fødselsdato", "Select date of birth": "Velg fødselsdato",
"Select dates": "Velg datoer",
"Select language": "Velg språk", "Select language": "Velg språk",
"Select your language": "Velg språk", "Select your language": "Velg språk",
"Shopping": "Shopping", "Shopping": "Shopping",
@@ -223,29 +237,25 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.", "Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.", "Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.",
"Something went wrong!": "Noe gikk galt!", "Something went wrong!": "Noe gikk galt!",
"special character": "spesiell karakter",
"spendable points expiring by": "{points} Brukbare poeng utløper innen {date}",
"Sports": "Sport", "Sports": "Sport",
"Standard price": "Standardpris", "Standard price": "Standardpris",
"Street": "Gate", "Street": "Gate",
"Successfully updated profile!": "Vellykket oppdatert profil!", "Successfully updated profile!": "Vellykket oppdatert profil!",
"Summary": "Sammendrag", "Summary": "Sammendrag",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.",
"Thank you": "Takk", "Thank you": "Takk",
"Theatre": "Teater", "Theatre": "Teater",
"There are no transactions to display": "Det er ingen transaksjoner å vise", "There are no transactions to display": "Det er ingen transaksjoner å vise",
"Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}", "Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}",
"to": "til",
"Total Points": "Totale poeng", "Total Points": "Totale poeng",
"Tourist": "Turist", "Tourist": "Turist",
"Transaction date": "Transaksjonsdato", "Transaction date": "Transaksjonsdato",
"Transactions": "Transaksjoner", "Transactions": "Transaksjoner",
"Transportations": "Transport", "Transportations": "Transport",
"Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)", "Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Sengtype", "Type of bed": "Sengtype",
"Type of room": "Romtype", "Type of room": "Romtype",
"uppercase letter": "stor bokstav",
"Use bonus cheque": "Bruk bonussjekk", "Use bonus cheque": "Bruk bonussjekk",
"User information": "Brukerinformasjon", "User information": "Brukerinformasjon",
"View as list": "Vis som liste", "View as list": "Vis som liste",
@@ -268,12 +278,13 @@
"Year": "År", "Year": "År",
"Yes, discard changes": "Ja, forkast endringer", "Yes, discard changes": "Ja, forkast endringer",
"Yes, remove my card": "Ja, fjern kortet mitt", "Yes, remove my card": "Ja, fjern kortet mitt",
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ombestemme deg senere og legge til frokost på hotellet.",
"You canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.", "You canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.",
"You have no previous stays.": "Du har ingen tidligere opphold.", "You have no previous stays.": "Du har ingen tidligere opphold.",
"You have no upcoming stays.": "Du har ingen kommende opphold.", "You have no upcoming stays.": "Du har ingen kommende opphold.",
"Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!",
"Your card was successfully removed!": "Kortet ditt ble fjernet!", "Your card was successfully removed!": "Kortet ditt ble fjernet!",
"Your card was successfully saved!": "Kortet ditt ble lagret!", "Your card was successfully saved!": "Kortet ditt ble lagret!",
"Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!",
"Your current level": "Ditt nåværende nivå", "Your current level": "Ditt nåværende nivå",
"Your details": "Dine detaljer", "Your details": "Dine detaljer",
"Your level": "Ditt nivå", "Your level": "Ditt nivå",
@@ -281,5 +292,28 @@
"Zip code": "Post kode", "Zip code": "Post kode",
"Zoo": "Dyrehage", "Zoo": "Dyrehage",
"Zoom in": "Zoom inn", "Zoom in": "Zoom inn",
"Zoom out": "Zoom ut" "Zoom out": "Zoom ut",
"as of today": "per idag",
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
"booking.nights": "{totalNights, plural, one {# natt} other {# netter}}",
"booking.rooms": "{totalRooms, plural, one {# rom} other {# rom}}",
"by": "innen",
"characters": "tegn",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personer",
"hotelPages.rooms.roomCard.seeRoomDetails": "Se detaljer om rommet",
"km to city center": "km til sentrum",
"next level:": "Neste nivå:",
"night": "natt",
"nights": "netter",
"number": "antall",
"or": "eller",
"points": "poeng",
"special character": "spesiell karakter",
"spendable points expiring by": "{points} Brukbare poeng utløper innen {date}",
"to": "til",
"uppercase letter": "stor bokstav",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm"
} }

View File

@@ -1,39 +1,43 @@
{ {
"<b>Included</b> (based on availability)": "<b>Ingår</b> (baserat på tillgänglighet)",
"<b>{amount} {currency}</b>/night per adult": "<b>{amount} {currency}</b>/natt per vuxen",
"A destination or hotel name is needed to be able to search for a hotel room.": "Ett destinations- eller hotellnamn behövs för att kunna söka efter ett hotellrum.", "A destination or hotel name is needed to be able to search for a hotel room.": "Ett destinations- eller hotellnamn behövs för att kunna söka efter ett hotellrum.",
"A photo of the room": "Ett foto av rummet", "A photo of the room": "Ett foto av rummet",
"About meetings & conferences": "About meetings & conferences",
"Activities": "Aktiviteter", "Activities": "Aktiviteter",
"Add code": "Lägg till kod", "Add code": "Lägg till kod",
"Add new card": "Lägg till nytt kort", "Add new card": "Lägg till nytt kort",
"Address": "Adress", "Address": "Adress",
"Airport": "Flygplats", "Airport": "Flygplats",
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alla våra frukostbufféer erbjuder glutenfria, veganska och allergivänliga alternativ.",
"Already a friend?": "Är du redan en vän?", "Already a friend?": "Är du redan en vän?",
"Amenities": "Bekvämligheter", "Amenities": "Bekvämligheter",
"Amusement park": "Nöjespark", "Amusement park": "Nöjespark",
"An error occurred when adding a credit card, please try again later.": "Ett fel uppstod när ett kreditkort lades till, försök igen senare.", "An error occurred when adding a credit card, please try again later.": "Ett fel uppstod när ett kreditkort lades till, försök igen senare.",
"An error occurred trying to manage your preferences, please try again later.": "Ett fel uppstod när du försökte hantera dina inställningar, försök igen senare.",
"An error occurred when trying to update profile.": "Ett fel uppstod när du försökte uppdatera profilen.", "An error occurred when trying to update profile.": "Ett fel uppstod när du försökte uppdatera profilen.",
"Any changes you've made will be lost.": "Alla ändringar du har gjort kommer att gå förlorade.", "Any changes you've made will be lost.": "Alla ändringar du har gjort kommer att gå förlorade.",
"Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Är du säker på att du vill ta bort kortet som slutar med {lastFourDigits} från din medlemsprofil?", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Är du säker på att du vill ta bort kortet som slutar med {lastFourDigits} från din medlemsprofil?",
"Arrival date": "Ankomstdatum", "Arrival date": "Ankomstdatum",
"as of today": "från och med idag",
"As our": "Som vår {level}", "As our": "Som vår {level}",
"As our Close Friend": "Som vår nära vän", "As our Close Friend": "Som vår nära vän",
"At latest": "Senast", "At latest": "Senast",
"At the hotel": "På hotellet", "At the hotel": "På hotellet",
"Attractions": "Sevärdheter", "Attractions": "Sevärdheter",
"Back to scandichotels.com": "Tillbaka till scandichotels.com", "Back to scandichotels.com": "Tillbaka till scandichotels.com",
"Bar": "Bar",
"Bed type": "Sängtyp", "Bed type": "Sängtyp",
"Book": "Boka", "Book": "Boka",
"Book reward night": "Boka frinatt", "Book reward night": "Boka frinatt",
"Booking number": "Bokningsnummer", "Booking number": "Bokningsnummer",
"booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}",
"Breakfast": "Frukost", "Breakfast": "Frukost",
"Breakfast buffet": "Frukostbuffé",
"Breakfast excluded": "Frukost ingår ej", "Breakfast excluded": "Frukost ingår ej",
"Breakfast included": "Frukost ingår", "Breakfast included": "Frukost ingår",
"Bus terminal": "Bussterminal", "Bus terminal": "Bussterminal",
"Business": "Business", "Business": "Business",
"by": "innan", "Breakfast restaurant": "Breakfast restaurant",
"Cancel": "Avbryt", "Cancel": "Avbryt",
"characters": "tecken",
"Check in": "Checka in", "Check in": "Checka in",
"Check out": "Checka ut", "Check out": "Checka ut",
"Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Kolla in kreditkorten som sparats i din profil. Betala med ett sparat kort när du är inloggad för en smidigare webbupplevelse.", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Kolla in kreditkorten som sparats i din profil. Betala med ett sparat kort när du är inloggad för en smidigare webbupplevelse.",
@@ -64,32 +68,40 @@
"Date of Birth": "Födelsedatum", "Date of Birth": "Födelsedatum",
"Day": "Dag", "Day": "Dag",
"Description": "Beskrivning", "Description": "Beskrivning",
"Destination": "Destination",
"Destinations & hotels": "Destinationer & hotell", "Destinations & hotels": "Destinationer & hotell",
"Discard changes": "Ignorera ändringar", "Discard changes": "Ignorera ändringar",
"Discard unsaved changes?": "Vill du ignorera ändringar som inte har sparats?", "Discard unsaved changes?": "Vill du ignorera ändringar som inte har sparats?",
"Distance to city centre": "{number}km till centrum", "Distance to city centre": "{number}km till centrum",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vill du starta dagen med Scandics berömda frukostbuffé?", "Do you want to start the day with Scandics famous breakfast buffé?": "Vill du starta dagen med Scandics berömda frukostbuffé?",
"Download the Scandic app": "Ladda ner Scandic-appen", "Download the Scandic app": "Ladda ner Scandic-appen",
"Earn bonus nights & points": "Tjäna bonusnätter och poäng",
"Edit": "Redigera", "Edit": "Redigera",
"Edit profile": "Redigera profil", "Edit profile": "Redigera profil",
"Email": "E-post", "Email": "E-post",
"Email address": "E-postadress",
"Enter destination or hotel": "Ange destination eller hotell", "Enter destination or hotel": "Ange destination eller hotell",
"Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences",
"Events that make an impression": "Events that make an impression",
"Explore all levels and benefits": "Utforska alla nivåer och fördelar", "Explore all levels and benefits": "Utforska alla nivåer och fördelar",
"Explore nearby": "Utforska i närheten", "Explore nearby": "Utforska i närheten",
"Extras to your booking": "Extra tillval till din bokning", "Extras to your booking": "Extra tillval till din bokning",
"FAQ": "FAQ",
"Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.", "Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.",
"Fair": "Mässa", "Fair": "Mässa",
"FAQ": "FAQ",
"Find booking": "Hitta bokning", "Find booking": "Hitta bokning",
"Find hotels": "Hitta hotell", "Find hotels": "Hitta hotell",
"Firstname": "Förnamn",
"Flexibility": "Flexibilitet", "Flexibility": "Flexibilitet",
"Former Scandic Hotel": "Tidigare Scandichotell", "Former Scandic Hotel": "Tidigare Scandichotell",
"Free cancellation": "Fri avbokning", "Free cancellation": "Fri avbokning",
"Free rebooking": "Fri ombokning", "Free rebooking": "Fri ombokning",
"From": "Från", "From": "Från",
"Get inspired": "Bli inspirerad", "Get inspired": "Bli inspirerad",
"Get member benefits & offers": "Ta del av medlemsförmåner och erbjudanden",
"Go back to edit": "Gå tillbaka till redigeringen", "Go back to edit": "Gå tillbaka till redigeringen",
"Go back to overview": "Gå tillbaka till översikten", "Go back to overview": "Gå tillbaka till översikten",
"Guest information": "Information till gästerna",
"Guests & Rooms": "Gäster & rum", "Guests & Rooms": "Gäster & rum",
"Hi": "Hej", "Hi": "Hej",
"Highest level": "Högsta nivå", "Highest level": "Högsta nivå",
@@ -97,16 +109,17 @@
"Hotel": "Hotell", "Hotel": "Hotell",
"Hotel facilities": "Hotellfaciliteter", "Hotel facilities": "Hotellfaciliteter",
"Hotel surroundings": "Hotellomgivning", "Hotel surroundings": "Hotellomgivning",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personer",
"hotelPages.rooms.roomCard.seeRoomDetails": "Se information om rummet",
"Hotels": "Hotell", "Hotels": "Hotell",
"How do you want to sleep?": "Hur vill du sova?", "How do you want to sleep?": "Hur vill du sova?",
"How it works": "Hur det fungerar", "How it works": "Hur det fungerar",
"Image gallery": "Bildgalleri", "Image gallery": "Bildgalleri",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det gick inte att hantera dina kommunikationsinställningar just nu, försök igen senare eller kontakta supporten om problemet kvarstår.",
"Join Scandic Friends": "Gå med i Scandic Friends", "Join Scandic Friends": "Gå med i Scandic Friends",
"km to city center": "km till stadens centrum", "Join at no cost": "Gå med utan kostnad",
"King bed": "King size-säng",
"Language": "Språk", "Language": "Språk",
"Lastname": "Efternamn",
"Latest searches": "Senaste sökningarna", "Latest searches": "Senaste sökningarna",
"Level": "Nivå", "Level": "Nivå",
"Level 1": "Nivå 1", "Level 1": "Nivå 1",
@@ -132,9 +145,9 @@
"Member price": "Medlemspris", "Member price": "Medlemspris",
"Member price from": "Medlemspris från", "Member price from": "Medlemspris från",
"Members": "Medlemmar", "Members": "Medlemmar",
"Membership cards": "Medlemskort",
"Membership ID": "Medlems-ID", "Membership ID": "Medlems-ID",
"Membership ID copied to clipboard": "Medlems-ID kopierat till urklipp", "Membership ID copied to clipboard": "Medlems-ID kopierat till urklipp",
"Membership cards": "Medlemskort",
"Menu": "Meny", "Menu": "Meny",
"Modify": "Ändra", "Modify": "Ändra",
"Month": "Månad", "Month": "Månad",
@@ -149,10 +162,8 @@
"Nearby companies": "Närliggande företag", "Nearby companies": "Närliggande företag",
"New password": "Nytt lösenord", "New password": "Nytt lösenord",
"Next": "Nästa", "Next": "Nästa",
"next level:": "Nästa nivå:",
"night": "natt",
"nights": "nätter",
"Nights needed to level up": "Nätter som behövs för att gå upp i nivå", "Nights needed to level up": "Nätter som behövs för att gå upp i nivå",
"No breakfast": "Ingen frukost",
"No content published": "Inget innehåll publicerat", "No content published": "Inget innehåll publicerat",
"No matching location found": "Ingen matchande plats hittades", "No matching location found": "Ingen matchande plats hittades",
"No results": "Inga resultat", "No results": "Inga resultat",
@@ -162,13 +173,11 @@
"Non-refundable": "Ej återbetalningsbar", "Non-refundable": "Ej återbetalningsbar",
"Not found": "Hittades inte", "Not found": "Hittades inte",
"Nr night, nr adult": "{nights, number} natt, {adults, number} vuxen", "Nr night, nr adult": "{nights, number} natt, {adults, number} vuxen",
"number": "nummer",
"On your journey": "På din resa", "On your journey": "På din resa",
"Open": "Öppna", "Open": "Öppna",
"Open language menu": "Öppna språkmenyn", "Open language menu": "Öppna språkmenyn",
"Open menu": "Öppna menyn", "Open menu": "Öppna menyn",
"Open my pages menu": "Öppna mina sidor menyn", "Open my pages menu": "Öppna mina sidor menyn",
"or": "eller",
"Overview": "Översikt", "Overview": "Översikt",
"Parking": "Parkering", "Parking": "Parkering",
"Parking / Garage": "Parkering / Garage", "Parking / Garage": "Parkering / Garage",
@@ -180,7 +189,6 @@
"Phone is required": "Telefonnummer är obligatorisk", "Phone is required": "Telefonnummer är obligatorisk",
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer", "Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer",
"points": "poäng",
"Points": "Poäng", "Points": "Poäng",
"Points being calculated": "Poäng beräknas", "Points being calculated": "Poäng beräknas",
"Points earned prior to May 1, 2021": "Intjänade poäng före den 1 maj 2021", "Points earned prior to May 1, 2021": "Intjänade poäng före den 1 maj 2021",
@@ -188,17 +196,24 @@
"Points needed to level up": "Poäng som behövs för att gå upp i nivå", "Points needed to level up": "Poäng som behövs för att gå upp i nivå",
"Points needed to stay on level": "Poäng som behövs för att hålla sig på nivå", "Points needed to stay on level": "Poäng som behövs för att hålla sig på nivå",
"Previous victories": "Tidigare segrar", "Previous victories": "Tidigare segrar",
"Proceed to payment method": "Gå vidare till betalningsmetod",
"Public price from": "Offentligt pris från", "Public price from": "Offentligt pris från",
"Public transport": "Kollektivtrafik", "Public transport": "Kollektivtrafik",
"Queen bed": "Queen size-säng",
"Read more": "Läs mer", "Read more": "Läs mer",
"Read more & book a table": "Read more & book a table",
"Read more about the hotel": "Läs mer om hotellet", "Read more about the hotel": "Läs mer om hotellet",
"Read more about wellness & exercise": "Read more about wellness & exercise",
"Remove card from member profile": "Ta bort kortet från medlemsprofilen", "Remove card from member profile": "Ta bort kortet från medlemsprofilen",
"Restaurant": "Restaurang", "Restaurant": "{count, plural, one {#Restaurang} other {#Restauranger}}",
"Restaurant & Bar": "Restaurang & Bar", "Restaurant & Bar": "Restaurang & Bar",
"Restaurants & Bars": "Restaurants & Bars",
"Retype new password": "Upprepa nytt lösenord", "Retype new password": "Upprepa nytt lösenord",
"Room & Terms": "Rum & Villkor", "Room & Terms": "Rum & Villkor",
"Room facilities": "Rumfaciliteter", "Room facilities": "Rumfaciliteter",
"Rooms": "Rum", "Rooms": "Rum",
"Rooms & Guests": "Rum och gäster",
"Sauna and gym": "Sauna and gym",
"Save": "Spara", "Save": "Spara",
"Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop", "Scandic Friends Point Shop": "Scandic Friends Point Shop",
@@ -210,6 +225,7 @@
"Select a country": "Välj ett land", "Select a country": "Välj ett land",
"Select country of residence": "Välj bosättningsland", "Select country of residence": "Välj bosättningsland",
"Select date of birth": "Välj födelsedatum", "Select date of birth": "Välj födelsedatum",
"Select dates": "Välj datum",
"Select language": "Välj språk", "Select language": "Välj språk",
"Select your language": "Välj ditt språk", "Select your language": "Välj ditt språk",
"Shopping": "Shopping", "Shopping": "Shopping",
@@ -223,29 +239,25 @@
"Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.", "Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.", "Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.",
"Something went wrong!": "Något gick fel!", "Something went wrong!": "Något gick fel!",
"special character": "speciell karaktär",
"spendable points expiring by": "{points} poäng förfaller {date}",
"Sports": "Sport", "Sports": "Sport",
"Standard price": "Standardpris", "Standard price": "Standardpris",
"Street": "Gata", "Street": "Gata",
"Successfully updated profile!": "Profilen har uppdaterats framgångsrikt!", "Successfully updated profile!": "Profilen har uppdaterats framgångsrikt!",
"Summary": "Sammanfattning", "Summary": "Sammanfattning",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.",
"Thank you": "Tack", "Thank you": "Tack",
"Theatre": "Teater", "Theatre": "Teater",
"There are no transactions to display": "Det finns inga transaktioner att visa", "There are no transactions to display": "Det finns inga transaktioner att visa",
"Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}", "Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}",
"to": "till",
"Total Points": "Poäng totalt", "Total Points": "Poäng totalt",
"Tourist": "Turist", "Tourist": "Turist",
"Transaction date": "Transaktionsdatum", "Transaction date": "Transaktionsdatum",
"Transactions": "Transaktioner", "Transactions": "Transaktioner",
"Transportations": "Transport", "Transportations": "Transport",
"Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)", "Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Sängtyp", "Type of bed": "Sängtyp",
"Type of room": "Rumstyp", "Type of room": "Rumstyp",
"uppercase letter": "stor bokstav",
"Use bonus cheque": "Use bonus cheque", "Use bonus cheque": "Use bonus cheque",
"User information": "Användarinformation", "User information": "Användarinformation",
"View as list": "Visa som lista", "View as list": "Visa som lista",
@@ -268,12 +280,13 @@
"Year": "År", "Year": "År",
"Yes, discard changes": "Ja, ignorera ändringar", "Yes, discard changes": "Ja, ignorera ändringar",
"Yes, remove my card": "Ja, ta bort mitt kort", "Yes, remove my card": "Ja, ta bort mitt kort",
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ändra dig senare och lägga till frukost på hotellet.",
"You canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.", "You canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.",
"You have no previous stays.": "Du har inga tidigare vistelser.", "You have no previous stays.": "Du har inga tidigare vistelser.",
"You have no upcoming stays.": "Du har inga planerade resor.", "You have no upcoming stays.": "Du har inga planerade resor.",
"Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!",
"Your card was successfully removed!": "Ditt kort har tagits bort!", "Your card was successfully removed!": "Ditt kort har tagits bort!",
"Your card was successfully saved!": "Ditt kort har sparats!", "Your card was successfully saved!": "Ditt kort har sparats!",
"Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!",
"Your current level": "Din nuvarande nivå", "Your current level": "Din nuvarande nivå",
"Your details": "Dina uppgifter", "Your details": "Dina uppgifter",
"Your level": "Din nivå", "Your level": "Din nivå",
@@ -281,5 +294,28 @@
"Zip code": "Postnummer", "Zip code": "Postnummer",
"Zoo": "Djurpark", "Zoo": "Djurpark",
"Zoom in": "Zooma in", "Zoom in": "Zooma in",
"Zoom out": "Zooma ut" "Zoom out": "Zooma ut",
"as of today": "från och med idag",
"booking.adults": "{totalAdults, plural, one {# vuxen} other {# vuxna}}",
"booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}",
"booking.rooms": "{totalRooms, plural, one {# rum} other {# rum}}",
"by": "innan",
"characters": "tecken",
"hotelPages.rooms.roomCard.person": "person",
"hotelPages.rooms.roomCard.persons": "personer",
"hotelPages.rooms.roomCard.seeRoomDetails": "Se information om rummet",
"km to city center": "km till stadens centrum",
"next level:": "Nästa nivå:",
"night": "natt",
"nights": "nätter",
"number": "nummer",
"or": "eller",
"points": "poäng",
"special character": "speciell karaktär",
"spendable points expiring by": "{points} poäng förfaller {date}",
"to": "till",
"uppercase letter": "stor bokstav",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm"
} }

View File

@@ -6,7 +6,7 @@ export namespace endpoints {
profile = "profile/v0/Profile", profile = "profile/v0/Profile",
} }
export const enum v1 { export const enum v1 {
availability = "availability/v1/availabilities/city", hotelsAvailability = "availability/v1/availabilities/city",
profile = "profile/v1/Profile", profile = "profile/v1/Profile",
booking = "booking/v1/Bookings", booking = "booking/v1/Bookings",
creditCards = `${profile}/creditCards`, creditCards = `${profile}/creditCards`,
@@ -21,6 +21,7 @@ export namespace endpoints {
upcomingStays = "booking/v1/Stays/future", upcomingStays = "booking/v1/Stays/future",
rewards = `${profile}/reward`, rewards = `${profile}/reward`,
tierRewards = `${profile}/TierRewards`, tierRewards = `${profile}/TierRewards`,
subscriberId = `${profile}/SubscriberId`,
} }
} }

View File

@@ -21,7 +21,7 @@ import type {
* is an Interface e.g). * is an Interface e.g).
*/ */
export function discriminatedUnion<T extends Option>(options: T[]) { export function discriminatedUnion<R>(options: Option[]) {
return z return z
.discriminatedUnion("__typename", [ .discriminatedUnion("__typename", [
z.object({ __typename: z.literal(undefined) }), z.object({ __typename: z.literal(undefined) }),
@@ -37,6 +37,12 @@ export function discriminatedUnion<T extends Option>(options: T[]) {
} }
throw new Error(error.message) throw new Error(error.message)
}) })
.transform((data) => {
if (data.__typename === "undefined" || data.__typename === undefined) {
return null
}
return data as R
})
} }
export function discriminatedUnionArray<T extends Option>(options: T[]) { export function discriminatedUnionArray<T extends Option>(options: T[]) {

View File

@@ -1,11 +1,13 @@
#import "../../Fragments/PageLink/ContentPageLink.graphql"
query GetHotelPage($locale: String!, $uid: String!) { query GetHotelPage($locale: String!, $uid: String!) {
hotel_page(locale: $locale, uid: $uid) { hotel_page(locale: $locale, uid: $uid) {
hotel_page_id hotel_page_id
title title
url url
content { content {
__typename
... on HotelPageContentUpcomingActivitiesCard { ... on HotelPageContentUpcomingActivitiesCard {
__typename
upcoming_activities_card { upcoming_activities_card {
background_image background_image
cta_text cta_text
@@ -16,15 +18,8 @@ query GetHotelPage($locale: String!, $uid: String!) {
hotel_page_activities_content_pageConnection { hotel_page_activities_content_pageConnection {
edges { edges {
node { node {
... on ContentPage { __typename
url ...ContentPageLink
web {
original_url
}
system {
locale
}
}
} }
} }
} }

View File

@@ -10,6 +10,12 @@ export const getProfile = cache(async function getMemoizedProfile() {
return serverClient().user.get() return serverClient().user.get()
}) })
export const getProfileSafely = cache(
async function getMemoizedProfileSafely() {
return serverClient().user.getSafely()
}
)
export const getFooter = cache(async function getMemoizedFooter() { export const getFooter = cache(async function getMemoizedFooter() {
return serverClient().contentstack.base.footer() return serverClient().contentstack.base.footer()
}) })

View File

@@ -0,0 +1,3 @@
<svg width="46" height="33" viewBox="0 0 46 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.4073 15.8263C44.9964 17.3421 46 19.4474 46 22.2263V22.3947V22.4789V30.9C46 31.7421 45.2473 32.5 44.3273 32.5H42.2364C41.3164 32.5 40.5636 31.7421 40.5636 30.9V27.1105H5.52V30.9C5.52 31.7421 4.76727 32.5 3.84727 32.5H1.67273C0.752727 32.5 0 31.7421 0 30.9V22.3947C0 20.2053 0.501818 18.5211 1.25455 17.0895V17.0053V1.34211C1.25455 0.921053 1.67273 0.5 2.09091 0.5H42.5709C43.0727 0.5 43.4073 0.836842 43.4073 1.34211V15.8263ZM1.67273 21.5526H44.3273C44.0764 18.1842 42.0691 16.1632 39.0582 14.8158C38.9745 14.8158 38.8909 14.8158 38.8073 14.7316C34.4582 13.0474 28.1018 13.0474 22.1636 13.0474C10.5382 13.0474 2.17455 13.7211 1.67273 21.5526ZM5.93818 13.3V11.9526C5.93818 6.81579 11.2909 6.22632 13.6327 6.22632C15.8909 6.22632 20.9927 6.73158 21.3273 11.4474C15.8909 11.4474 10.12 11.5316 5.93818 13.3ZM23 11.3632V11.4474C28.1018 11.4474 33.8727 11.5316 38.3891 13.0474V11.3632C38.3891 6.22632 33.0364 5.63684 30.6945 5.63684C28.3527 5.63684 23 6.22632 23 11.3632ZM41.7345 2.18421V14.4789C41.2327 14.1421 40.6473 13.8895 40.0618 13.6368V11.2789C40.0618 6.73158 36.5491 3.95263 30.6945 3.95263C26.3455 3.95263 23.2509 5.46842 21.9964 8.16316C20.5745 5.97368 17.7309 4.71053 13.7164 4.71053C7.86182 4.71053 4.34909 7.40526 4.34909 12.0368V14.1421C3.84727 14.4789 3.42909 14.8158 3.01091 15.1526V2.18421H41.7345ZM1.67273 30.9H3.84727V27.1105H1.67273V30.9ZM1.67273 25.5105V23.1526H44.3273V25.5105H1.67273ZM42.1527 27.1105V30.9H44.3273V27.1105H42.1527Z" fill="#57514E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -551,10 +551,13 @@ const linkSchema = z
}) })
.transform((data) => { .transform((data) => {
if (data.linkConnection.edges.length) { if (data.linkConnection.edges.length) {
const link = pageLinks.transform(data.linkConnection.edges[0].node) const linkNode = data.linkConnection.edges[0].node
if (link) { if (linkNode) {
return { const link = pageLinks.transform(linkNode)
link, if (link) {
return {
link,
}
} }
} }
} }

View File

@@ -31,6 +31,17 @@ const getAllLoyaltyLevelFailCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.all-fail" "trpc.contentstack.loyaltyLevel.all-fail"
) )
const getByLevelLoyaltyLevelCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.byLevel"
)
const getByLevelLoyaltyLevelSuccessCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.byLevel-success"
)
const getByLevelLoyaltyLevelFailCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.byLevel-fail"
)
export async function getAllLoyaltyLevels(ctx: Context) { export async function getAllLoyaltyLevels(ctx: Context) {
getAllLoyaltyLevelCounter.add(1) getAllLoyaltyLevelCounter.add(1)
@@ -87,7 +98,9 @@ export async function getAllLoyaltyLevels(ctx: Context) {
} }
export async function getLoyaltyLevel(ctx: Context, level_id: MembershipLevel) { export async function getLoyaltyLevel(ctx: Context, level_id: MembershipLevel) {
getAllLoyaltyLevelCounter.add(1) getByLevelLoyaltyLevelCounter.add(1, {
query: JSON.stringify({ lang: ctx.lang, level_id }),
})
const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>( const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
GetLoyaltyLevel, GetLoyaltyLevel,
@@ -103,10 +116,10 @@ export async function getLoyaltyLevel(ctx: Context, level_id: MembershipLevel) {
!loyaltyLevelsConfigResponse.data || !loyaltyLevelsConfigResponse.data ||
!loyaltyLevelsConfigResponse.data.all_loyalty_level.items.length !loyaltyLevelsConfigResponse.data.all_loyalty_level.items.length
) { ) {
getAllLoyaltyLevelFailCounter.add(1) getByLevelLoyaltyLevelFailCounter.add(1)
const notFoundError = notFound(loyaltyLevelsConfigResponse) const notFoundError = notFound(loyaltyLevelsConfigResponse)
console.error( console.error(
"contentstack.loyaltyLevels not found error", "contentstack.loyaltyLevel not found error",
JSON.stringify({ JSON.stringify({
query: { lang: ctx.lang, level_id }, query: { lang: ctx.lang, level_id },
error: { code: notFoundError.code }, error: { code: notFoundError.code },
@@ -119,10 +132,10 @@ export async function getLoyaltyLevel(ctx: Context, level_id: MembershipLevel) {
loyaltyLevelsConfigResponse.data loyaltyLevelsConfigResponse.data
) )
if (!validatedLoyaltyLevels.success) { if (!validatedLoyaltyLevels.success) {
getAllLoyaltyLevelFailCounter.add(1) getByLevelLoyaltyLevelFailCounter.add(1)
console.error(validatedLoyaltyLevels.error) console.error(validatedLoyaltyLevels.error)
console.error( console.error(
"contentstack.rewards validation error", "contentstack.loyaltyLevel validation error",
JSON.stringify({ JSON.stringify({
query: { lang: ctx.lang, level_id }, query: { lang: ctx.lang, level_id },
error: validatedLoyaltyLevels.error, error: validatedLoyaltyLevels.error,
@@ -131,7 +144,7 @@ export async function getLoyaltyLevel(ctx: Context, level_id: MembershipLevel) {
return null return null
} }
getAllLoyaltyLevelSuccessCounter.add(1) getByLevelLoyaltyLevelSuccessCounter.add(1)
return validatedLoyaltyLevels.data[0] return validatedLoyaltyLevels.data[0]
} }

Some files were not shown because too many files have changed in this diff Show More