feat(SW-325): added pois to the list and dynamic map

This commit is contained in:
Erik Tiekstra
2024-09-17 16:13:22 +02:00
parent 1729f4b9c7
commit e79f413003
44 changed files with 1078 additions and 318 deletions

View File

@@ -1,147 +0,0 @@
"use client"
import {
AdvancedMarker,
Map,
type MapProps,
useMap,
} from "@vis.gl/react-google-maps"
import { useState } from "react"
import { useIntl } from "react-intl"
import useHotelPageStore from "@/stores/hotel-page"
import { CloseIcon, MinusIcon, PlusIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Title from "@/components/TempDesignSystem/Text/Title"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import useLang from "@/hooks/useLang"
import ScandicMarker from "../Markers/Scandic"
import styles from "./dynamicMap.module.css"
import type { DynamicMapContentProps } from "@/types/components/hotelPage/map/dynamicMapContent"
export default function DynamicMapContent({
hotelName,
coordinates,
}: DynamicMapContentProps) {
const intl = useIntl()
const lang = useLang()
const { isDynamicMapOpen, closeDynamicMap } = useHotelPageStore()
const [isFullScreenSidebar, setIsFullScreenSidebar] = useState(false)
const map = useMap()
const mapOptions: MapProps = {
defaultZoom: 15,
defaultCenter: coordinates,
disableDefaultUI: true,
clickableIcons: false,
mapId: `${hotelName}-${lang}-map`,
// As reference for future styles when adding POIs
// styles: [
// {
// featureType: "poi",
// elementType: "all",
// stylers: [{ visibility: "off" }],
// },
// ],
}
useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && isDynamicMapOpen) {
closeDynamicMap()
}
})
function zoomIn() {
const currentZoom = map && map.getZoom()
if (currentZoom) {
map.setZoom(currentZoom + 1)
}
}
function zoomOut() {
const currentZoom = map && map.getZoom()
if (currentZoom) {
map.setZoom(currentZoom - 1)
}
}
function toggleFullScreenSidebar() {
setIsFullScreenSidebar((prev) => !prev)
}
return (
<>
<div className={styles.ctaButtons}>
<Button
theme="base"
intent="inverted"
variant="icon"
size="small"
className={styles.closeButton}
onClick={closeDynamicMap}
>
<CloseIcon color="burgundy" width={24} height={24} />
<span>{intl.formatMessage({ id: "Close the map" })}</span>
</Button>
<div className={styles.zoomButtons}>
<Button
theme="base"
intent="inverted"
variant="icon"
size="small"
className={styles.zoomButton}
onClick={zoomOut}
aria-label={intl.formatMessage({ id: "Zoom in" })}
>
<MinusIcon color="burgundy" width={20} height={20} />
</Button>
<Button
theme="base"
intent="inverted"
variant="icon"
size="small"
className={styles.zoomButton}
onClick={zoomIn}
aria-label={intl.formatMessage({ id: "Zoom out" })}
>
<PlusIcon color="burgundy" width={20} height={20} />
</Button>
</div>
</div>
<aside
className={`${styles.sidebar} ${isFullScreenSidebar ? styles.fullscreen : ""}`}
>
<div className={styles.sidebarToggle}>
<span className={styles.rectangle} />
<button
className={styles.toggleButton}
onClick={toggleFullScreenSidebar}
>
{intl.formatMessage({
id: isFullScreenSidebar ? "View as map" : "View as list",
})}
</button>
</div>
<div className={styles.sidebarContent}>
<Title as="h4" level="h2" textTransform="regular">
{intl.formatMessage(
{ id: "Things nearby HOTEL_NAME" },
{ hotelName }
)}
</Title>
<Divider color="subtle" />
</div>
</aside>
<div className={styles.mapContainer}>
<Map {...mapOptions}>
<AdvancedMarker position={coordinates}>
<ScandicMarker />
</AdvancedMarker>
</Map>
</div>
</>
)
}

View File

@@ -0,0 +1,137 @@
"use client"
import {
AdvancedMarker,
AdvancedMarkerAnchorPoint,
Map,
type MapProps,
useMap,
} from "@vis.gl/react-google-maps"
import { useIntl } from "react-intl"
import useHotelPageStore from "@/stores/hotel-page"
import { MinusIcon, PlusIcon } from "@/components/Icons"
import CloseLargeIcon from "@/components/Icons/CloseLarge"
import PoiMarker from "@/components/Maps/Markers/Poi"
import ScandicMarker from "@/components/Maps/Markers/Scandic"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./map.module.css"
import type { MapContentProps } from "@/types/components/hotelPage/map/mapContent"
export default function MapContent({
coordinates,
pointsOfInterest,
activePoi,
onActivePoiChange,
}: MapContentProps) {
const intl = useIntl()
const { closeDynamicMap } = useHotelPageStore()
const map = useMap()
const mapOptions: MapProps = {
defaultZoom: 14,
defaultCenter: coordinates,
disableDefaultUI: true,
clickableIcons: false,
mapId: "6b48ef228325ae84",
}
function zoomIn() {
const currentZoom = map && map.getZoom()
if (currentZoom) {
map.setZoom(currentZoom + 1)
}
}
function zoomOut() {
const currentZoom = map && map.getZoom()
if (currentZoom) {
map.setZoom(currentZoom - 1)
}
}
function toggleActivePoi(poiName: string) {
onActivePoiChange(activePoi === poiName ? null : poiName)
}
return (
<div className={styles.mapContainer}>
<Map {...mapOptions}>
<AdvancedMarker position={coordinates} zIndex={1}>
<ScandicMarker />
</AdvancedMarker>
{pointsOfInterest.map((poi) => (
<AdvancedMarker
key={poi.name}
className={styles.advancedMarker}
position={poi.coordinates}
anchorPoint={AdvancedMarkerAnchorPoint.CENTER}
zIndex={activePoi === poi.name ? 2 : 0}
onMouseEnter={() => onActivePoiChange(poi.name)}
onMouseLeave={() => onActivePoiChange(null)}
onClick={() => toggleActivePoi(poi.name)}
>
<span
className={`${styles.poi} ${activePoi === poi.name ? styles.active : ""}`}
>
<PoiMarker
category={poi.category}
className={styles.poiMarker}
size={activePoi === poi.name ? 20 : 16}
/>
<Body className={styles.poiLabel} asChild>
<span>
{poi.name}
<Caption asChild>
<span>{poi.distance} km</span>
</Caption>
</span>
</Body>
</span>
</AdvancedMarker>
))}
</Map>
<div className={styles.ctaButtons}>
<Button
theme="base"
intent="inverted"
variant="icon"
size="small"
className={styles.closeButton}
onClick={closeDynamicMap}
>
<CloseLargeIcon color="burgundy" />
<span>{intl.formatMessage({ id: "Close the map" })}</span>
</Button>
<div className={styles.zoomButtons}>
<Button
theme="base"
intent="inverted"
variant="icon"
size="small"
className={styles.zoomButton}
onClick={zoomOut}
aria-label={intl.formatMessage({ id: "Zoom in" })}
>
<MinusIcon color="burgundy" width={20} height={20} />
</Button>
<Button
theme="base"
intent="inverted"
variant="icon"
size="small"
className={styles.zoomButton}
onClick={zoomIn}
aria-label={intl.formatMessage({ id: "Zoom out" })}
>
<PlusIcon color="burgundy" width={20} height={20} />
</Button>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,108 @@
.mapContainer {
--button-box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
width: 100%;
position: relative;
z-index: 0;
}
.mapContainer::after {
content: "";
position: absolute;
top: 0;
right: 0;
background: linear-gradient(
43deg,
rgba(172, 172, 172, 0) 57.66%,
rgba(0, 0, 0, 0.25) 92.45%
);
width: 100%;
height: 100%;
pointer-events: none;
}
.ctaButtons {
position: absolute;
top: var(--Spacing-x2);
right: var(--Spacing-x2);
z-index: 1;
display: flex;
flex-direction: column;
gap: var(--Spacing-x7);
align-items: flex-end;
pointer-events: none;
}
.zoomButtons {
display: grid;
gap: var(--Spacing-x1);
}
.closeButton {
pointer-events: initial;
box-shadow: var(--button-box-shadow);
gap: var(--Spacing-x-half);
}
.zoomButton {
width: var(--Spacing-x5);
height: var(--Spacing-x5);
padding: 0;
pointer-events: initial;
box-shadow: var(--button-box-shadow);
}
.advancedMarker {
height: var(--Spacing-x4);
width: var(
--Spacing-x4
) !important; /* Overwriting the default width of the @vis.gl/react-google-maps AdvancedMarker */
}
.advancedMarker.active {
height: var(--Spacing-x5);
width: var(
--Spacing-x5
) !important; /* Overwriting the default width of the @vis.gl/react-google-maps AdvancedMarker */
}
.poi {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
padding: var(--Spacing-x-half);
border-radius: var(--Corner-radius-Rounded);
background-color: var(--Base-Surface-Primary-light-Normal);
box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.1);
gap: var(--Spacing-x1);
}
.poi.active {
padding-right: var(--Spacing-x-one-and-half);
}
.poiLabel {
display: none;
}
.poi.active .poiLabel {
display: flex;
align-items: center;
gap: var(--Spacing-x2);
text-wrap: nowrap;
}
@media screen and (min-width: 768px) {
.ctaButtons {
top: var(--Spacing-x4);
right: var(--Spacing-x4);
bottom: var(--Spacing-x4);
justify-content: space-between;
}
.zoomButtons {
display: flex;
}
}

View File

@@ -0,0 +1,102 @@
"use client"
import { useState } from "react"
import { useIntl } from "react-intl"
import PoiMarker from "@/components/Maps/Markers/Poi"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import styles from "./sidebar.module.css"
import type { SidebarProps } from "@/types/components/hotelPage/map/sidebar"
export default function Sidebar({
activePoi,
hotelName,
pointsOfInterest,
onActivePoiChange,
}: SidebarProps) {
const intl = useIntl()
const [isFullScreenSidebar, setIsFullScreenSidebar] = useState(false)
const poiCategories = new Set(
pointsOfInterest.map(({ category }) => category)
)
const poisInCategories = Array.from(poiCategories).map((category) => ({
category,
pois: pointsOfInterest.filter((poi) => poi.category === category),
}))
function toggleFullScreenSidebar() {
setIsFullScreenSidebar((prev) => !prev)
}
return (
<aside
className={`${styles.sidebar} ${
isFullScreenSidebar ? styles.fullscreen : ""
}`}
>
<Button
theme="base"
intent="text"
className={styles.sidebarToggle}
onClick={toggleFullScreenSidebar}
>
<Body textTransform="bold" color="textMediumContrast" asChild>
<span>
{intl.formatMessage({
id: isFullScreenSidebar ? "View as map" : "View as list",
})}
</span>
</Body>
</Button>
<div className={styles.sidebarContent}>
<Title as="h4" level="h2" textTransform="regular">
{intl.formatMessage(
{ id: "Things nearby HOTEL_NAME" },
{ hotelName }
)}
</Title>
{poisInCategories.map(({ category, pois }) =>
pois.length ? (
<div key={category} className={styles.poiGroup}>
<Body
color="black"
textTransform="bold"
className={styles.poiHeading}
asChild
>
<h3>
<PoiMarker category={category} />
{intl.formatMessage({ id: category })}
</h3>
</Body>
<ul className={styles.poiList}>
{pois.map((poi) => (
<li key={poi.name} className={styles.poiItem}>
<button
className={`${styles.poiButton} ${activePoi === poi.name ? styles.active : ""}`}
onMouseEnter={() => onActivePoiChange(poi.name)}
onMouseLeave={() => onActivePoiChange(null)}
onClick={() =>
onActivePoiChange(
activePoi === poi.name ? null : poi.name
)
}
>
<span>{poi.name}</span>
<span>{poi.distance} km</span>
</button>
</li>
))}
</ul>
</div>
) : null
)}
</div>
</aside>
)
}

View File

@@ -0,0 +1,111 @@
.sidebar {
--sidebar-max-width: 26.25rem;
--sidebar-mobile-toggle-height: 91px;
--sidebar-mobile-fullscreen-height: calc(
100vh - var(--main-menu-mobile-height) - var(--sidebar-mobile-toggle-height)
);
position: absolute;
top: var(--sidebar-mobile-fullscreen-height);
height: 100%;
right: 0;
left: 0;
background-color: var(--Base-Surface-Primary-light-Normal);
z-index: 1;
transition: top 0.3s;
}
.sidebar:not(.fullscreen) {
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
}
.sidebar.fullscreen {
top: 0;
}
.sidebarToggle {
position: relative;
margin: var(--Spacing-x4) 0 var(--Spacing-x2);
width: 100%;
}
.sidebarToggle::before {
content: "";
position: absolute;
display: block;
top: -0.5rem;
width: 100px;
height: 3px;
background-color: var(--UI-Text-High-contrast);
}
.sidebarContent {
display: grid;
gap: var(--Spacing-x5);
align-content: start;
padding: var(--Spacing-x3) var(--Spacing-x2);
height: var(--sidebar-mobile-fullscreen-height);
overflow-y: auto;
}
.poiGroup {
display: grid;
gap: var(--Spacing-x2);
}
.poiHeading {
display: flex;
align-items: center;
gap: var(--Spacing-x1);
}
.poiList {
list-style: none;
}
.poiItem {
padding: var(--Spacing-x1) 0;
border-bottom: 1px solid var(--Base-Border-Subtle);
}
.poiButton {
background-color: var(--Base-Surface-Primary-light-Normal);
border-width: 0;
font-family: var(--typography-Body-Regular-fontFamily);
font-size: var(--typography-Body-Regular-fontSize);
font-weight: var(--typography-Body-Regular-fontWeight);
color: var(--UI-Text-High-contrast);
width: 100%;
display: grid;
grid-template-columns: 1fr max-content;
gap: var(--Spacing-x2);
align-items: center;
padding: var(--Spacing-x-half) var(--Spacing-x1);
border-radius: var(--Corner-radius-Medium);
cursor: pointer;
text-align: left;
transition: background-color 0.3s;
}
.poiButton.active {
background-color: var(--Base-Surface-Primary-light-Hover);
}
@media screen and (min-width: 768px) {
.sidebar {
position: static;
width: 40vw;
min-width: 10rem;
max-width: var(--sidebar-max-width);
background-color: var(--Base-Surface-Primary-light-Normal);
}
.sidebarToggle {
display: none;
}
.sidebarContent {
padding: var(--Spacing-x4) var(--Spacing-x5);
height: 100%;
position: relative;
}
}

View File

@@ -1,5 +1,4 @@
.dynamicMap {
--sidebar-height: 88px;
position: fixed;
top: var(--main-menu-mobile-height);
right: 0;
@@ -10,143 +9,8 @@
background-color: var(--Base-Surface-Primary-light-Normal);
}
.sidebar {
position: absolute;
top: calc(100vh - var(--main-menu-mobile-height) - var(--sidebar-height));
height: 100%;
right: 0;
left: 0;
background-color: var(--Base-Surface-Primary-light-Normal);
z-index: 1;
transition: top 0.3s;
}
.sidebar:not(.fullscreen) {
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
}
.sidebar.fullscreen {
top: 0;
}
.sidebarToggle {
display: grid;
gap: var(--Spacing-x1);
padding: var(--Spacing-x2);
height: var(--sidebar-height);
}
.toggleButton {
position: relative;
display: flex;
justify-content: center;
background-color: transparent;
border-width: 0;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
font-family: var(--typography-Body-Bold-fontFamily);
font-size: var(--typography-Body-Bold-fontSize);
font-weight: var(--typography-Body-Bold-fontWeight);
color: var(--UI-Text-Medium-contrast);
width: 100%;
}
.toggleButton::before {
content: "";
position: absolute;
display: block;
top: -0.5rem;
width: 100px;
height: 3px;
background-color: var(--UI-Text-High-contrast);
}
.sidebarContent {
display: grid;
gap: var(--Spacing-x3);
align-content: start;
padding: var(--Spacing-x3) var(--Spacing-x2);
height: 100%;
overflow-y: auto;
}
.mapContainer {
flex: 1;
position: relative;
}
.mapContainer::after {
content: "";
display: block;
position: absolute;
top: 0;
right: 0;
background: linear-gradient(
43deg,
rgba(172, 172, 172, 0) 57.66%,
rgba(0, 0, 0, 0.25) 92.45%
);
width: 100%;
height: 100%;
pointer-events: none;
}
.ctaButtons {
position: absolute;
top: var(--Spacing-x2);
right: var(--Spacing-x2);
z-index: 1;
display: flex;
flex-direction: column;
gap: var(--Spacing-x7);
align-items: flex-end;
pointer-events: none;
}
.zoomButtons {
display: grid;
gap: var(--Spacing-x1);
}
.closeButton {
pointer-events: initial;
box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
}
.zoomButton {
width: var(--Spacing-x5);
height: var(--Spacing-x5);
padding: 0;
pointer-events: initial;
box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
}
@media screen and (min-width: 768px) {
.dynamicMap {
top: var(--main-menu-desktop-height);
}
.sidebar {
position: static;
width: 40vw;
min-width: 10rem;
max-width: 26.25rem;
background-color: var(--Base-Surface-Primary-light-Normal);
}
.sidebarToggle {
display: none;
}
.sidebarContent {
padding: var(--Spacing-x4) var(--Spacing-x5);
}
.ctaButtons {
top: var(--Spacing-x4);
right: var(--Spacing-x4);
bottom: var(--Spacing-x4);
justify-content: space-between;
}
.zoomButtons {
display: flex;
}
}

View File

@@ -8,7 +8,8 @@ import useHotelPageStore from "@/stores/hotel-page"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import DynamicMapContent from "./Content"
import MapContent from "./Map"
import Sidebar from "./Sidebar"
import styles from "./dynamicMap.module.css"
@@ -18,11 +19,13 @@ export default function DynamicMap({
apiKey,
hotelName,
coordinates,
pointsOfInterest,
}: DynamicMapProps) {
const intl = useIntl()
const { isDynamicMapOpen, closeDynamicMap } = useHotelPageStore()
const [scrollHeightWhenOpened, setScrollHeightWhenOpened] = useState(0)
const hasMounted = useRef(false)
const [activePoi, setActivePoi] = useState<string | null>(null)
useHandleKeyUp((event: KeyboardEvent) => {
if (event.key === "Escape" && isDynamicMapOpen) {
@@ -58,7 +61,18 @@ export default function DynamicMap({
{ hotelName }
)}
>
<DynamicMapContent hotelName={hotelName} coordinates={coordinates} />
<Sidebar
activePoi={activePoi}
hotelName={hotelName}
pointsOfInterest={pointsOfInterest}
onActivePoiChange={setActivePoi}
/>
<MapContent
coordinates={coordinates}
pointsOfInterest={pointsOfInterest}
activePoi={activePoi}
onActivePoiChange={setActivePoi}
/>
</Dialog>
</Modal>
</APIProvider>

View File

@@ -4,7 +4,9 @@ import { useIntl } from "react-intl"
import useHotelPageStore from "@/stores/hotel-page"
import PoiMarker from "@/components/Maps/Markers/Poi"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
@@ -12,7 +14,7 @@ import styles from "./mapCard.module.css"
import type { MapCardProps } from "@/types/components/hotelPage/map/mapCard"
export default function MapCard({ hotelName }: MapCardProps) {
export default function MapCard({ hotelName, pois }: MapCardProps) {
const intl = useIntl()
const { openDynamicMap } = useHotelPageStore()
@@ -34,6 +36,15 @@ export default function MapCard({ hotelName }: MapCardProps) {
>
{hotelName}
</Title>
<ul className={styles.poiList}>
{pois.map((poi) => (
<li key={poi.name} className={styles.poiItem}>
<PoiMarker category={poi.category} skipBackground size={20} />
<Body color="black">{poi.name}</Body>
<Caption>{poi.distance} km</Caption>
</li>
))}
</ul>
<Button
theme="base"

View File

@@ -13,3 +13,17 @@
.ctaButton {
margin-top: var(--Spacing-x2);
}
.poiList {
list-style: none;
margin-top: var(--Spacing-x1);
}
.poiItem {
display: grid;
grid-template-columns: max-content 1fr max-content;
gap: var(--Spacing-x-half);
align-items: center;
padding: var(--Spacing-x2) 0;
border-bottom: 1px solid var(--Base-Border-Subtle);
}

View File

@@ -11,7 +11,7 @@
border-radius: 4rem;
background-color: var(--Base-Surface-Primary-light-Normal);
box-shadow: 0 0 30px 2px rgba(0, 0, 0, 0.15);
padding: 0.375rem;
padding: var(--Spacing-x-half);
}
.button {

View File

@@ -1,11 +1,10 @@
/* eslint-disable @next/next/no-img-element */
import ScandicMarker from "@/components/Maps/Markers/Scandic"
import StaticMapComp from "@/components/Maps/StaticMap"
import { getIntl } from "@/i18n"
import { calculateLatWithOffset } from "@/utils/map"
import ScandicMarker from "../Markers/Scandic"
import styles from "./staticMap.module.css"
import type { StaticMapProps } from "@/types/components/hotelPage/map/staticMap"

View File

@@ -43,10 +43,12 @@ export default async function HotelPage() {
hotelImages,
roomCategories,
activitiesCard,
pointsOfInterest,
} = hotelData
const facilities = [...MOCK_FACILITIES]
activitiesCard && facilities.push(setActivityCard(activitiesCard))
const topThreePois = pointsOfInterest.slice(0, 3)
const coordinates = {
lat: hotelLocation.latitude,
@@ -122,13 +124,14 @@ export default async function HotelPage() {
<>
<aside className={styles.mapContainer}>
<StaticMap coordinates={coordinates} hotelName={hotelName} />
<MapCard hotelName={hotelName} />
<MapCard hotelName={hotelName} pois={topThreePois} />
</aside>
<MobileMapToggle />
<DynamicMap
apiKey={googleMapsApiKey}
hotelName={hotelName}
coordinates={coordinates}
pointsOfInterest={pointsOfInterest}
/>
</>
) : null}

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function CulturalIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<mask
id="mask0_71_1002"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_71_1002)">
<path
d="M18.8424 7.51255C19.1058 7.51255 19.3292 7.4226 19.5125 7.2427C19.6959 7.0628 19.7875 6.83988 19.7875 6.57395C19.7875 6.30802 19.6965 6.08547 19.5144 5.9063C19.3323 5.72713 19.1067 5.63755 18.8375 5.63755C18.5755 5.63755 18.3558 5.72922 18.1785 5.91255C18.0012 6.09588 17.9125 6.31672 17.9125 6.57505C17.9125 6.83338 18.0017 7.05422 18.1799 7.23755C18.3581 7.42088 18.5789 7.51255 18.8424 7.51255ZM14.9 7.51255C15.1584 7.51255 15.3792 7.42088 15.5625 7.23755C15.7459 7.05422 15.8375 6.83338 15.8375 6.57505C15.8375 6.31672 15.7459 6.09588 15.5625 5.91255C15.3792 5.72922 15.1584 5.63755 14.9 5.63755C14.6417 5.63755 14.4209 5.72922 14.2375 5.91255C14.0542 6.09588 13.9625 6.31672 13.9625 6.57505C13.9625 6.83338 14.0542 7.05422 14.2375 7.23755C14.4209 7.42088 14.6417 7.51255 14.9 7.51255ZM16.875 9.06255C16.4212 9.06255 15.9869 9.1438 15.5721 9.3063C15.1574 9.4688 14.8417 9.74172 14.625 10.125C14.5167 10.3 14.5292 10.4709 14.6625 10.6375C14.7959 10.8042 14.975 10.8875 15.2 10.8875H18.55C18.775 10.8875 18.9542 10.8042 19.0875 10.6375C19.2209 10.4709 19.2334 10.3 19.125 10.125C18.9084 9.74172 18.5927 9.4688 18.1779 9.3063C17.7632 9.1438 17.3289 9.06255 16.875 9.06255ZM7.11035 21.75C5.49515 21.75 4.12297 21.1849 2.9938 20.0547C1.86463 18.9245 1.30005 17.5521 1.30005 15.9375V11.0125C1.30005 10.4969 1.48364 10.0555 1.85082 9.68832C2.21801 9.32114 2.65942 9.13755 3.17505 9.13755H11.05C11.5657 9.13755 12.0071 9.32114 12.3743 9.68832C12.7415 10.0555 12.925 10.4969 12.925 11.0125V15.9375C12.925 17.5521 12.3597 18.9245 11.2291 20.0547C10.0985 21.1849 8.72555 21.75 7.11035 21.75ZM7.11255 19.875C8.20422 19.875 9.13338 19.4917 9.90005 18.725C10.6667 17.9584 11.05 17.0292 11.05 15.9375V11.0125H3.17505V15.9375C3.17505 17.0292 3.55838 17.9584 4.32505 18.725C5.09172 19.4917 6.02088 19.875 7.11255 19.875ZM16.875 14.85C16.4417 14.85 16.0084 14.8021 15.575 14.7063C15.1417 14.6105 14.7334 14.4667 14.35 14.275V12.0625C14.7167 12.3542 15.1153 12.5792 15.5457 12.7375C15.9762 12.8959 16.4193 12.975 16.875 12.975C17.9667 12.975 18.8959 12.5917 19.6625 11.825C20.4292 11.0584 20.8125 10.1292 20.8125 9.03755V4.11255H12.925V7.71255H11.05V4.11255C11.05 3.59692 11.2336 3.15551 11.6008 2.78832C11.968 2.42114 12.4094 2.23755 12.925 2.23755H20.8125C21.3282 2.23755 21.7696 2.42114 22.1368 2.78832C22.504 3.15551 22.6875 3.59692 22.6875 4.11255V9.03755C22.6875 10.6521 22.1224 12.0245 20.9922 13.1547C19.862 14.2849 18.4896 14.85 16.875 14.85ZM5.16255 14.4C5.42463 14.4 5.64432 14.3109 5.82162 14.1327C5.99891 13.9545 6.08755 13.7337 6.08755 13.4702C6.08755 13.2068 5.99844 12.9834 5.82022 12.8C5.64201 12.6167 5.42117 12.525 5.15772 12.525C4.89427 12.525 4.67088 12.6161 4.48755 12.7982C4.30422 12.9803 4.21255 13.2059 4.21255 13.475C4.21255 13.7371 4.30359 13.9568 4.48567 14.1341C4.66776 14.3114 4.89338 14.4 5.16255 14.4ZM9.08755 14.4C9.34588 14.4 9.56672 14.3109 9.75005 14.1327C9.93338 13.9545 10.025 13.7337 10.025 13.4702C10.025 13.2068 9.9351 12.9834 9.7552 12.8C9.5753 12.6167 9.35238 12.525 9.08645 12.525C8.82052 12.525 8.59797 12.6161 8.4188 12.7982C8.23963 12.9803 8.15005 13.2059 8.15005 13.475C8.15005 13.7371 8.24172 13.9568 8.42505 14.1341C8.60838 14.3114 8.82922 14.4 9.08755 14.4ZM7.12317 17.7875C7.57442 17.7875 8.00213 17.7042 8.4063 17.5375C8.81047 17.3709 9.12922 17.1042 9.36255 16.7375C9.47088 16.5542 9.46418 16.375 9.34245 16.2C9.2207 16.025 9.04823 15.9375 8.82505 15.9375H5.42505C5.20187 15.9375 5.0294 16.025 4.90765 16.2C4.78592 16.375 4.77922 16.5542 4.88755 16.7375C5.12088 17.1042 5.43901 17.3709 5.84192 17.5375C6.24482 17.7042 6.67191 17.7875 7.12317 17.7875Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function MuseumIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<mask
id="mask0_3958_408"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_3958_408)">
<path
d="M2.5 21V19.1H4.4V10.55H2.5V8.65L12 2L21.5 8.65V10.55H19.6V19.1H21.5V21H2.5ZM8.2 17.2H10.1V13.4L12 16.25L13.9 13.4V17.2H15.8V10.55H13.9L12 13.4L10.1 10.55H8.2V17.2ZM17.7 19.1V8.3175L12 4.3275L6.3 8.3175V19.1H17.7Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function ShoppingIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<mask
id="mask0_69_3482"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3482)">
<path
d="M6.125 21.75C5.60937 21.75 5.16796 21.5664 4.80078 21.1992C4.43359 20.832 4.25 20.3906 4.25 19.875V8.05C4.25 7.53437 4.43359 7.09296 4.80078 6.72578C5.16796 6.35859 5.60937 6.175 6.125 6.175H8.0625C8.0625 5.09167 8.4481 4.16667 9.2193 3.4C9.9905 2.63333 10.9176 2.25 12.0006 2.25C13.0835 2.25 14.0104 2.63433 14.7812 3.40298C15.5521 4.17161 15.9375 5.09562 15.9375 6.175H17.875C18.3906 6.175 18.832 6.35859 19.1992 6.72578C19.5664 7.09296 19.75 7.53437 19.75 8.05V19.875C19.75 20.3906 19.5664 20.832 19.1992 21.1992C18.832 21.5664 18.3906 21.75 17.875 21.75H6.125ZM6.125 19.875H17.875V8.05H15.9375V10.1125C15.9375 10.3708 15.8458 10.5917 15.6625 10.775C15.4792 10.9583 15.2583 11.05 15 11.05C14.7417 11.05 14.5208 10.9583 14.3375 10.775C14.1542 10.5917 14.0625 10.3708 14.0625 10.1125V8.05H9.9375V10.1125C9.9375 10.3708 9.84583 10.5917 9.6625 10.775C9.47917 10.9583 9.25833 11.05 9 11.05C8.74167 11.05 8.52083 10.9583 8.3375 10.775C8.15417 10.5917 8.0625 10.3708 8.0625 10.1125V8.05H6.125V19.875ZM9.9375 6.175H14.0625C14.0625 5.60833 13.8606 5.125 13.4568 4.725C13.053 4.325 12.5676 4.125 12.0006 4.125C11.4335 4.125 10.9479 4.32573 10.5438 4.7272C10.1396 5.12865 9.9375 5.61125 9.9375 6.175Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function StarFilledIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<mask
id="mask0_69_3249"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_69_3249)">
<path
d="M11.9999 17.5625L7.81244 20.0875C7.63744 20.1958 7.45619 20.2417 7.26869 20.225C7.08119 20.2083 6.91661 20.1458 6.77494 20.0375C6.63328 19.9292 6.52494 19.7917 6.44994 19.625C6.37494 19.4583 6.36244 19.275 6.41244 19.075L7.52494 14.3125L3.82494 11.1125C3.66661 10.9708 3.56661 10.8104 3.52494 10.6312C3.48328 10.4521 3.49161 10.2792 3.54994 10.1125C3.60828 9.94583 3.70619 9.80417 3.84369 9.6875C3.98119 9.57083 4.15411 9.5 4.36244 9.475L9.23744 9.05L11.1374 4.5625C11.2208 4.37083 11.3416 4.22917 11.4999 4.1375C11.6583 4.04583 11.8249 4 11.9999 4C12.1749 4 12.3416 4.04583 12.4999 4.1375C12.6583 4.22917 12.7791 4.37083 12.8624 4.5625L14.7624 9.05L19.6374 9.475C19.8458 9.5 20.0187 9.57083 20.1562 9.6875C20.2937 9.80417 20.3916 9.94583 20.4499 10.1125C20.5083 10.2792 20.5166 10.4521 20.4749 10.6312C20.4333 10.8104 20.3333 10.9708 20.1749 11.1125L16.4749 14.3125L17.5874 19.075C17.6374 19.275 17.6249 19.4583 17.5499 19.625C17.4749 19.7917 17.3666 19.9292 17.2249 20.0375C17.0833 20.1458 16.9187 20.2083 16.7312 20.225C16.5437 20.2417 16.3624 20.1958 16.1874 20.0875L11.9999 17.5625Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function TrainIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<mask
id="mask0_71_1020"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_71_1020)">
<path
d="M4.22498 15.425V6.1C4.22498 5.25 4.43956 4.57292 4.86873 4.06875C5.29789 3.56458 5.87081 3.17917 6.58748 2.9125C7.30414 2.64583 8.13123 2.46875 9.06873 2.38125C10.0062 2.29375 10.9833 2.25 12 2.25C13.0666 2.25 14.0729 2.29375 15.0187 2.38125C15.9646 2.46875 16.7896 2.64583 17.4937 2.9125C18.1979 3.17917 18.7541 3.56458 19.1625 4.06875C19.5708 4.57292 19.775 5.25 19.775 6.1V15.425C19.775 16.3833 19.4521 17.1854 18.8062 17.8313C18.1604 18.4771 17.3583 18.8 16.4 18.8L16.9375 19.3375C17.1958 19.5958 17.2541 19.8917 17.1125 20.225C16.9708 20.5583 16.7166 20.725 16.35 20.725C16.2416 20.725 16.1396 20.7063 16.0437 20.6688C15.9479 20.6313 15.8583 20.5708 15.775 20.4875L14.0875 18.8H9.91248L8.22498 20.4875C8.14164 20.5708 8.05206 20.6313 7.95623 20.6688C7.86039 20.7063 7.75831 20.725 7.64998 20.725C7.29164 20.725 7.03956 20.5583 6.89373 20.225C6.74789 19.8917 6.80414 19.5958 7.06248 19.3375L7.59998 18.8C6.64164 18.8 5.83956 18.4771 5.19373 17.8313C4.54789 17.1854 4.22498 16.3833 4.22498 15.425ZM12 4.125C10.2583 4.125 8.96456 4.22708 8.11873 4.43125C7.27289 4.63542 6.71664 4.875 6.44998 5.15H17.6C17.3583 4.85833 16.8125 4.61458 15.9625 4.41875C15.1125 4.22292 13.7916 4.125 12 4.125ZM6.09998 10.125H11.075V7.025H6.09998V10.125ZM12.95 10.125H17.9V7.025H12.95V10.125ZM8.54998 15.875C8.96664 15.875 9.30831 15.7417 9.57498 15.475C9.84164 15.2083 9.97498 14.8667 9.97498 14.45C9.97498 14.0333 9.84164 13.6917 9.57498 13.425C9.30831 13.1583 8.96664 13.025 8.54998 13.025C8.13331 13.025 7.79164 13.1583 7.52498 13.425C7.25831 13.6917 7.12498 14.0333 7.12498 14.45C7.12498 14.8667 7.25831 15.2083 7.52498 15.475C7.79164 15.7417 8.13331 15.875 8.54998 15.875ZM15.45 15.875C15.8666 15.875 16.2083 15.7417 16.475 15.475C16.7416 15.2083 16.875 14.8667 16.875 14.45C16.875 14.0333 16.7416 13.6917 16.475 13.425C16.2083 13.1583 15.8666 13.025 15.45 13.025C15.0333 13.025 14.6916 13.1583 14.425 13.425C14.1583 13.6917 14.025 14.0333 14.025 14.45C14.025 14.8667 14.1583 15.2083 14.425 15.475C14.6916 15.7417 15.0333 15.875 15.45 15.875ZM7.59998 16.925H16.4C16.8333 16.925 17.1916 16.7833 17.475 16.5C17.7583 16.2167 17.9 15.8583 17.9 15.425V12H6.09998V15.425C6.09998 15.8583 6.24164 16.2167 6.52498 16.5C6.80831 16.7833 7.16664 16.925 7.59998 16.925Z"
fill="#26201E"
/>
</g>
</svg>
)
}

View File

@@ -22,6 +22,7 @@ import {
CoffeeIcon,
ConciergeIcon,
CrossCircle,
CulturalIcon,
DoorOpenIcon,
ElectricBikeIcon,
EmailIcon,
@@ -35,6 +36,7 @@ import {
LockIcon,
MapIcon,
MinusIcon,
MuseumIcon,
ParkingIcon,
People2Icon,
PersonIcon,
@@ -46,6 +48,9 @@ import {
SaunaIcon,
SearchIcon,
ServiceIcon,
ShoppingIcon,
StarFilledIcon,
TrainIcon,
TshirtWashIcon,
WarningTriangle,
WifiIcon,
@@ -91,6 +96,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
return CoffeeIcon
case IconName.Concierge:
return ConciergeIcon
case IconName.Cultural:
return CulturalIcon
case IconName.DoorOpen:
return DoorOpenIcon
case IconName.ElectricBike:
@@ -121,6 +128,8 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
return MapIcon
case IconName.Minus:
return MinusIcon
case IconName.Museum:
return MuseumIcon
case IconName.Parking:
return ParkingIcon
case IconName.Person:
@@ -143,6 +152,12 @@ export function getIconByIconName(icon?: IconName): FC<IconProps> | null {
return SearchIcon
case IconName.Service:
return ServiceIcon
case IconName.Shopping:
return ShoppingIcon
case IconName.StarFilled:
return StarFilledIcon
case IconName.Train:
return TrainIcon
case IconName.Tripadvisor:
return TripAdvisorIcon
case IconName.TshirtWash:

View File

@@ -17,6 +17,7 @@ export { default as CoffeeIcon } from "./Coffee"
export { default as ConciergeIcon } from "./Concierge"
export { default as CreditCard } from "./CreditCard"
export { default as CrossCircle } from "./CrossCircle"
export { default as CulturalIcon } from "./Cultural"
export { default as Delete } from "./Delete"
export { default as DoorOpenIcon } from "./DoorOpen"
export { default as ElectricBikeIcon } from "./ElectricBike"
@@ -31,6 +32,7 @@ export { default as LocationIcon } from "./Location"
export { default as LockIcon } from "./Lock"
export { default as MapIcon } from "./Map"
export { default as MinusIcon } from "./Minus"
export { default as MuseumIcon } from "./Museum"
export { default as ParkingIcon } from "./Parking"
export { default as People2Icon } from "./People2"
export { default as PersonIcon } from "./Person"
@@ -44,6 +46,9 @@ export { default as SaunaIcon } from "./Sauna"
export { default as ScandicLogoIcon } from "./ScandicLogo"
export { default as SearchIcon } from "./Search"
export { default as ServiceIcon } from "./Service"
export { default as ShoppingIcon } from "./Shopping"
export { default as StarFilledIcon } from "./StarFilled"
export { default as TrainIcon } from "./Train"
export { default as TshirtWashIcon } from "./TshirtWash"
export { default as WarningTriangle } from "./WarningTriangle"
export { default as WifiIcon } from "./Wifi"

View File

@@ -0,0 +1,27 @@
import { getIconByIconName } from "@/components/Icons/get-icon-by-icon-name"
import { getCategoryIconName } from "../utils"
import { poiVariants } from "./variants"
import { PoiMarkerProps } from "@/types/components/maps/poiMarker"
export default function PoiMarker({
category,
skipBackground,
size = 16,
className = "",
}: PoiMarkerProps) {
const iconName = getCategoryIconName(category)
const Icon = iconName ? getIconByIconName(iconName) : null
const classNames = poiVariants({ category, skipBackground, className })
return Icon ? (
<span className={classNames}>
<Icon
color={skipBackground ? "grey80" : "white"}
width={size}
height={size}
/>
</span>
) : null
}

View File

@@ -0,0 +1,52 @@
/* 2024-09-18: At the moment, the background-colors for the poi marker is unknown.
This will be handled later. */
.icon {
display: flex;
justify-content: center;
align-items: center;
padding: var(--Spacing-x-half);
border-radius: var(--Corner-radius-Rounded);
background-color: var(--Scandic-Beige-90);
}
.airport,
.amusementPark,
.busTerminal,
.fair,
.hospital,
.hotel,
.marketingCity {
}
.museum {
background: var(--Base-Interactive-Surface-Secondary-normal);
}
.nearbyCompanies,
.parkingGarage {
}
.restaurant {
background: var(--Scandic-Peach-50);
}
.shopping {
background: var(--Base-Interactive-Surface-Primary-normal);
}
.sports,
.theatre {
}
.tourist {
background: var(--Scandic-Yellow-60);
}
.transportations {
background: var(--Base-Interactive-Surface-Tertiary-normal);
}
.zoo {
}
.icon.transparent {
background: transparent;
padding: 0;
}

View File

@@ -0,0 +1,34 @@
import { cva } from "class-variance-authority"
import styles from "./poi.module.css"
export const poiVariants = cva(styles.icon, {
variants: {
category: {
Airport: styles.airport,
"Amusement park": styles.amusementPark,
"Bus terminal": styles.busTerminal,
Fair: styles.fair,
Hospital: styles.hospital,
Hotel: styles.hotel,
"Marketing city": styles.marketingCity,
Museum: styles.museum,
"Nearby companies": styles.nearbyCompanies,
"Parking / Garage": styles.parkingGarage,
Restaurant: styles.restaurant,
Shopping: styles.shopping,
Sports: styles.sports,
Theatre: styles.theatre,
Tourist: styles.tourist,
Transportations: styles.transportations,
Zoo: styles.zoo,
},
skipBackground: {
true: styles.transparent,
false: "",
},
},
defaultVariants: {
skipBackground: false,
},
})

View File

@@ -0,0 +1,21 @@
import { IconName } from "@/types/components/icon"
import { PointOfInterestCategory } from "@/types/hotel"
/* 2024-09-18: At the moment, the icons for the different categories is unknown.
This will be handled later. */
export function getCategoryIconName(category?: PointOfInterestCategory | null) {
switch (category) {
case "Transportations":
return IconName.Train
case "Shopping":
return IconName.Shopping
case "Museum":
return IconName.Museum
case "Tourist":
return IconName.Cultural
case "Restaurant":
return IconName.Restaurant
default:
return IconName.StarFilled
}
}

View File

@@ -0,0 +1,19 @@
export enum PoiCategories {
"Airport" = "airport",
"Amusement park" = "amusementPark",
"Bus terminal" = "busTerminal",
"Fair" = "fair",
"Hospital" = "hospital",
"Hotel" = "hotel",
"Marketing city" = "marketingCity",
"Museum" = "museum",
"Nearby companies" = "nearbyCompanies",
"Parking / Garage" = "parkingGarage",
"Restaurant" = "restaurant",
"Shopping" = "shopping",
"Sports" = "sports",
"Theatre" = "theatre",
"Tourist" = "tourist",
"Transportations" = "transportations",
"Zoo" = "zoo",
}

View File

@@ -4,8 +4,10 @@
"Add code": "Tilføj kode",
"Add new card": "Tilføj nyt kort",
"Address": "Adresse",
"Airport": "Lufthavn",
"Already a friend?": "Allerede en ven?",
"Amenities": "Faciliteter",
"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 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.",
@@ -26,6 +28,7 @@
"Breakfast": "Morgenmad",
"Breakfast excluded": "Morgenmad ikke inkluderet",
"Breakfast included": "Morgenmad inkluderet",
"Bus terminal": "Busstation",
"by": "inden",
"Cancel": "Afbestille",
"characters": "tegn",
@@ -66,6 +69,7 @@
"Explore nearby": "Udforsk i nærheden",
"Extras to your booking": "Tillæg til din booking",
"Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.",
"Fair": "Messe",
"Find booking": "Find booking",
"Find hotels": "Find hotel",
"Flexibility": "Fleksibilitet",
@@ -78,6 +82,7 @@
"Go back to overview": "Gå tilbage til oversigten",
"Hi": "Hei",
"Highest level": "Højeste niveau",
"Hospital": "Hospital",
"Hotel": "Hotel",
"Hotel facilities": "Hotel faciliteter",
"Hotel surroundings": "Hotel omgivelser",
@@ -104,6 +109,7 @@
"Manage preferences": "Administrer præferencer",
"Map": "Kort",
"Map of HOTEL_NAME": "Map of {hotelName}",
"Marketing city": "Marketing by",
"Meetings & Conferences": "Møder & Konferencer",
"Member price": "Medlemspris",
"Member price from": "Medlemspris fra",
@@ -114,6 +120,7 @@
"Menu": "Menu",
"Modify": "Ændre",
"Month": "Måned",
"Museum": "Museum",
"My communication preferences": "Mine kommunikationspræferencer",
"My membership cards": "Mine medlemskort",
"My pages": "Mine sider",
@@ -121,6 +128,7 @@
"My payment cards": "Mine betalingskort",
"My wishes": "Mine ønsker",
"Nearby": "I nærheden",
"Nearby companies": "Nærliggende virksomheder",
"New password": "Nyt kodeord",
"Next": "Næste",
"next level:": "Næste niveau:",
@@ -142,6 +150,7 @@
"Open my pages menu": "Åbn mine sider menuen",
"or": "eller",
"Overview": "Oversigt",
"Parking / Garage": "Parkering / Garage",
"Password": "Adgangskode",
"Pay later": "Betal senere",
"Pay now": "Betal nu",
@@ -150,8 +159,8 @@
"Phone is required": "Telefonnummer er påkrævet",
"Phone number": "Telefonnummer",
"Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer",
"points": "Point",
"Points": "Point",
"points": "Point",
"Points being calculated": "Point udregnes",
"Points earned prior to May 1, 2021": "Point optjent inden 1. maj 2021",
"Points may take up to 10 days to be displayed.": "Det kan tage op til 10 dage at få vist point.",
@@ -162,6 +171,7 @@
"Read more": "Læs mere",
"Read more about the hotel": "Læs mere om hotellet",
"Remove card from member profile": "Fjern kortet fra medlemsprofilen",
"Restaurant": "Restaurant",
"Restaurant & Bar": "Restaurant & Bar",
"Retype new password": "Gentag den nye adgangskode",
"Room & Terms": "Værelse & Vilkår",
@@ -179,6 +189,7 @@
"Select date of birth": "Vælg fødselsdato",
"Select language": "Vælg sprog",
"Select your language": "Vælg dit sprog",
"Shopping": "Shopping",
"Show all amenities": "Vis alle faciliteter",
"Show less": "Vis mindre",
"Show map": "Vis kort",
@@ -190,18 +201,22 @@
"Something went wrong!": "Noget gik galt!",
"special character": "speciel karakter",
"spendable points expiring by": "{points} Brugbare point udløber den {date}",
"Sports": "Sport",
"Standard price": "Standardpris",
"Street": "Gade",
"Successfully updated profile!": "Profilen er opdateret med succes!",
"Summary": "Opsummering",
"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",
"Theatre": "Teater",
"There are no transactions to display": "Der er ingen transaktioner at vise",
"Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}",
"to": "til",
"Total Points": "Samlet antal point",
"Tourist": "Turist",
"Transaction date": "Overførselsdato",
"Transactions": "Transaktioner",
"Transportations": "Transport",
"Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Sengtype",
@@ -239,6 +254,7 @@
"Your level": "Dit niveau",
"Your points to spend": "Dine brugbare point",
"Zip code": "Postnummer",
"Zoo": "Zoo",
"Zoom in": "Zoom ind",
"Zoom out": "Zoom ud"
}

View File

@@ -3,8 +3,10 @@
"Add code": "Code hinzufügen",
"Add new card": "Neue Karte hinzufügen",
"Address": "Adresse",
"Airport": "Flughafen",
"Already a friend?": "Sind wir schon Freunde?",
"Amenities": "Annehmlichkeiten",
"Amusement park": "Vergnügungspark",
"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.",
"Any changes you've made will be lost.": "Alle Änderungen, die Sie vorgenommen haben, gehen verloren.",
@@ -24,6 +26,7 @@
"Breakfast": "Frühstück",
"Breakfast excluded": "Frühstück nicht inbegriffen",
"Breakfast included": "Frühstück inbegriffen",
"Bus terminal": "Busbahnhof",
"by": "bis",
"Cancel": "Stornieren",
"characters": "figuren",
@@ -64,6 +67,7 @@
"Explore nearby": "Erkunden Sie die Umgebung",
"Extras to your booking": "Extras zu Ihrer Buchung",
"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",
"Find booking": "Buchung finden",
"Find hotels": "Hotels finden",
"Flexibility": "Flexibilität",
@@ -76,6 +80,7 @@
"Go back to overview": "Zurück zur Übersicht",
"Hi": "Hallo",
"Highest level": "Höchstes Level",
"Hospital": "Krankenhaus",
"Hotel": "Hotel",
"Hotel facilities": "Hotel-Infos",
"Hotel surroundings": "Umgebung des Hotels",
@@ -102,6 +107,7 @@
"Manage preferences": "Verwalten von Voreinstellungen",
"Map": "Karte",
"Map of HOTEL_NAME": "Map of {hotelName}",
"Marketing city": "Marketingstadt",
"Member price": "Mitgliederpreis",
"Member price from": "Mitgliederpreis ab",
"Members": "Mitglieder",
@@ -111,6 +117,7 @@
"Menu": "Menu",
"Modify": "Ändern",
"Month": "Monat",
"Museum": "Museum",
"My communication preferences": "Meine Kommunikationseinstellungen",
"My membership cards": "Meine Mitgliedskarten",
"My pages": "Meine Seiten",
@@ -118,6 +125,7 @@
"My payment cards": "Meine Zahlungskarten",
"My wishes": "Meine Wünsche",
"Nearby": "In der Nähe",
"Nearby companies": "Nahe gelegene Unternehmen",
"New password": "Neues Kennwort",
"Next": "Nächste",
"next level:": "Nächstes Level:",
@@ -138,6 +146,7 @@
"Open menu": "Menü öffnen",
"Open my pages menu": "Meine Seiten Menü öffnen",
"or": "oder",
"Parking / Garage": "Parken / Garage",
"Password": "Passwort",
"Pay later": "Später bezahlen",
"Pay now": "Jetzt bezahlen",
@@ -146,8 +155,8 @@
"Phone is required": "Telefon ist erforderlich",
"Phone number": "Telefonnummer",
"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 earned prior to May 1, 2021": "Zusammengeführte Punkte vor dem 1. Mai 2021",
"Points may take up to 10 days to be displayed.": "Es kann bis zu 10 Tage dauern, bis Punkte angezeigt werden.",
@@ -158,6 +167,7 @@
"Read more": "Mehr lesen",
"Read more about the hotel": "Lesen Sie mehr über das Hotel",
"Remove card from member profile": "Karte aus dem Mitgliedsprofil entfernen",
"Restaurant": "Restaurant",
"Retype new password": "Neues Passwort erneut eingeben",
"Room & Terms": "Zimmer & Bedingungen",
"Room facilities": "Zimmerausstattung",
@@ -173,6 +183,7 @@
"Select date of birth": "Geburtsdatum auswählen",
"Select language": "Sprache auswählen",
"Select your language": "Wählen Sie Ihre Sprache",
"Shopping": "Einkaufen",
"Show all amenities": "Alle Annehmlichkeiten anzeigen",
"Show less": "Weniger anzeigen",
"Show map": "Karte anzeigen",
@@ -184,18 +195,22 @@
"Something went wrong!": "Etwas ist schief gelaufen!",
"special character": "sonderzeichen",
"spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}",
"Sports": "Sport",
"Standard price": "Standardpreis",
"Street": "Straße",
"Successfully updated profile!": "Profil erfolgreich aktualisiert!",
"Summary": "Zusammenfassung",
"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",
"Theatre": "Theater",
"There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden",
"Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}",
"to": "zu",
"Total Points": "Gesamtpunktzahl",
"Tourist": "Tourist",
"Transaction date": "Transaktionsdatum",
"Transactions": "Transaktionen",
"Transportations": "Transportmittel",
"Tripadvisor reviews": "{rating} ({count} Bewertungen auf Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Bettentyp",
@@ -232,6 +247,7 @@
"Your level": "Dein level",
"Your points to spend": "Meine Punkte",
"Zip code": "PLZ",
"Zoo": "Zoo",
"Zoom in": "Vergrößern",
"Zoom out": "Verkleinern"
}

View File

@@ -4,8 +4,10 @@
"Add code": "Add code",
"Add new card": "Add new card",
"Address": "Address",
"Airport": "Airport",
"Already a friend?": "Already a friend?",
"Amenities": "Amenities",
"Amusement park": "Amusement park",
"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.",
"Any changes you've made will be lost.": "Any changes you've made will be lost.",
@@ -25,6 +27,7 @@
"Breakfast": "Breakfast",
"Breakfast excluded": "Breakfast excluded",
"Breakfast included": "Breakfast included",
"Bus terminal": "Bus terminal",
"by": "by",
"Cancel": "Cancel",
"characters": "characters",
@@ -66,6 +69,7 @@
"Explore nearby": "Explore nearby",
"Extras to your booking": "Extras to your booking",
"Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.",
"Fair": "Fair",
"FAQ": "FAQ",
"Find booking": "Find booking",
"Find hotels": "Find hotels",
@@ -79,6 +83,7 @@
"Go back to overview": "Go back to overview",
"Hi": "Hi",
"Highest level": "Highest level",
"Hospital": "Hospital",
"Hotel": "Hotel",
"Hotel facilities": "Hotel facilities",
"Hotel surroundings": "Hotel surroundings",
@@ -108,6 +113,7 @@
"Manage preferences": "Manage preferences",
"Map": "Map",
"Map of HOTEL_NAME": "Map of {hotelName}",
"Marketing city": "Marketing city",
"Meetings & Conferences": "Meetings & Conferences",
"Member price": "Member price",
"Member price from": "Member price from",
@@ -118,6 +124,7 @@
"Menu": "Menu",
"Modify": "Modify",
"Month": "Month",
"Museum": "Museum",
"My communication preferences": "My communication preferences",
"My membership cards": "My membership cards",
"My pages": "My pages",
@@ -125,6 +132,7 @@
"My payment cards": "My payment cards",
"My wishes": "My wishes",
"Nearby": "Nearby",
"Nearby companies": "Nearby companies",
"New password": "New password",
"Next": "Next",
"next level:": "next level:",
@@ -146,6 +154,7 @@
"Open my pages menu": "Open my pages menu",
"or": "or",
"Overview": "Overview",
"Parking / Garage": "Parking / Garage",
"Password": "Password",
"Pay later": "Pay later",
"Pay now": "Pay now",
@@ -154,8 +163,8 @@
"Phone is required": "Phone is required",
"Phone number": "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 earned prior to May 1, 2021": "Points earned prior to May 1, 2021",
"Points may take up to 10 days to be displayed.": "Points may take up to 10 days to be displayed.",
@@ -166,6 +175,7 @@
"Read more": "Read more",
"Read more about the hotel": "Read more about the hotel",
"Remove card from member profile": "Remove card from member profile",
"Restaurant": "Restaurant",
"Restaurant & Bar": "Restaurant & Bar",
"Retype new password": "Retype new password",
"Room & Terms": "Room & Terms",
@@ -184,6 +194,7 @@
"Select date of birth": "Select date of birth",
"Select language": "Select language",
"Select your language": "Select your language",
"Shopping": "Shopping",
"Show all amenities": "Show all amenities",
"Show less": "Show less",
"Show map": "Show map",
@@ -195,18 +206,22 @@
"Something went wrong!": "Something went wrong!",
"special character": "special character",
"spendable points expiring by": "{points} spendable points expiring by {date}",
"Sports": "Sports",
"Standard price": "Standard price",
"Street": "Street",
"Successfully updated profile!": "Successfully updated profile!",
"Summary": "Summary",
"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",
"Theatre": "Theatre",
"There are no transactions to display": "There are no transactions to display",
"Things nearby HOTEL_NAME": "Things nearby {hotelName}",
"to": "to",
"Total Points": "Total Points",
"Tourist": "Tourist",
"Transaction date": "Transaction date",
"Transactions": "Transactions",
"Transportations": "Transportations",
"Tripadvisor reviews": "{rating} ({count} reviews on Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Type of bed",
@@ -244,6 +259,7 @@
"Your level": "Your level",
"Your points to spend": "Your points to spend",
"Zip code": "Zip code",
"Zoo": "Zoo",
"Zoom in": "Zoom in",
"Zoom out": "Zoom out"
}

View File

@@ -4,8 +4,10 @@
"Add code": "Lisää koodi",
"Add new card": "Lisää uusi kortti",
"Address": "Osoite",
"Airport": "Lentokenttä",
"Already a friend?": "Oletko jo ystävä?",
"Amenities": "Mukavuudet",
"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 trying to update profile.": "Profiilia päivitettäessä tapahtui virhe.",
"Any changes you've made will be lost.": "Kaikki tekemäsi muutokset menetetään.",
@@ -25,6 +27,7 @@
"Breakfast": "Aamiainen",
"Breakfast excluded": "Aamiainen ei sisälly",
"Breakfast included": "Aamiainen sisältyy",
"Bus terminal": "Bussiasema",
"by": "mennessä",
"Cancel": "Peruuttaa",
"characters": "hahmoja",
@@ -65,6 +68,7 @@
"Explore nearby": "Tutustu lähialueeseen",
"Extras to your booking": "Varauksessa lisäpalveluita",
"Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.",
"Fair": "Messukeskus",
"Find booking": "Etsi varaus",
"Find hotels": "Etsi hotelleja",
"Flexibility": "Joustavuus",
@@ -77,6 +81,7 @@
"Go back to overview": "Palaa yleiskatsaukseen",
"Hi": "Hi",
"Highest level": "Korkein taso",
"Hospital": "Sairaala",
"Hotel": "Hotelli",
"Hotel facilities": "Hotellin palvelut",
"Hotel surroundings": "Hotellin ympäristö",
@@ -103,6 +108,7 @@
"Manage preferences": "Asetusten hallinta",
"Map": "Kartta",
"Map of HOTEL_NAME": "Map of {hotelName}",
"Marketing city": "Markkinointikaupunki",
"Meetings & Conferences": "Kokoukset & Konferenssit",
"Member price": "Jäsenhinta",
"Member price from": "Jäsenhinta alkaen",
@@ -113,6 +119,7 @@
"Menu": "Valikko",
"Modify": "Muokkaa",
"Month": "Kuukausi",
"Museum": "Museo",
"My communication preferences": "Viestintämieltymykseni",
"My membership cards": "Jäsenkorttini",
"My pages": "Omat sivut",
@@ -120,6 +127,7 @@
"My payment cards": "Minun maksukortit",
"My wishes": "Toiveeni",
"Nearby": "Lähistöllä",
"Nearby companies": "Läheiset yritykset",
"New password": "Uusi salasana",
"Next": "Seuraava",
"next level:": "pistettä tasolle:",
@@ -141,6 +149,7 @@
"Open my pages menu": "Avaa omat sivut -valikko",
"or": "tai",
"Overview": "Yleiskatsaus",
"Parking / Garage": "Pysäköinti / Autotalli",
"Password": "Salasana",
"Pay later": "Maksa myöhemmin",
"Pay now": "Maksa nyt",
@@ -149,8 +158,8 @@
"Phone is required": "Puhelin vaaditaan",
"Phone number": "Puhelinnumero",
"Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero",
"points": "pistettä",
"Points": "Pisteet",
"points": "pistettä",
"Points being calculated": "Pisteitä lasketaan",
"Points earned prior to May 1, 2021": "Pisteet, jotka ansaittu ennen 1.5.2021",
"Points may take up to 10 days to be displayed.": "Pisteiden näyttäminen voi kestää jopa 10 päivää.",
@@ -161,6 +170,7 @@
"Read more": "Lue lisää",
"Read more about the hotel": "Lue lisää hotellista",
"Remove card from member profile": "Poista kortti jäsenprofiilista",
"Restaurant": "Ravintola",
"Restaurant & Bar": "Ravintola & Baari",
"Retype new password": "Kirjoita uusi salasana uudelleen",
"Room & Terms": "Huone & Ehdot",
@@ -178,6 +188,7 @@
"Select date of birth": "Valitse syntymäaika",
"Select language": "Valitse kieli",
"Select your language": "Valitse kieli",
"Shopping": "Ostokset",
"Show all amenities": "Näytä kaikki mukavuudet",
"Show less": "Näytä vähemmän",
"Show map": "Näytä kartta",
@@ -189,18 +200,22 @@
"Something went wrong!": "Jotain meni pieleen!",
"special character": "erikoishahmo",
"spendable points expiring by": "{points} pistettä vanhenee {date} mennessä",
"Sports": "Urheilu",
"Standard price": "Normaali hinta",
"Street": "Katu",
"Successfully updated profile!": "Profiilin päivitys onnistui!",
"Summary": "Yhteenveto",
"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",
"Theatre": "Teatteri",
"There are no transactions to display": "Näytettäviä tapahtumia ei ole",
"Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}",
"to": "to",
"Total Points": "Kokonaispisteet",
"Tourist": "Turisti",
"Transaction date": "Tapahtuman päivämäärä",
"Transactions": "Tapahtumat",
"Transportations": "Kuljetukset",
"Tripadvisor reviews": "{rating} ({count} arvostelua TripAdvisorissa)",
"TUI Points": "TUI Points",
"Type of bed": "Vuodetyyppi",
@@ -238,6 +253,7 @@
"Your level": "Tasosi",
"Your points to spend": "Käytettävissä olevat pisteesi",
"Zip code": "Postinumero",
"Zoo": "Eläintarha",
"Zoom in": "Lähennä",
"Zoom out": "Loitonna"
}

View File

@@ -4,8 +4,10 @@
"Add code": "Legg til kode",
"Add new card": "Legg til nytt kort",
"Address": "Adresse",
"Airport": "Flyplass",
"Already a friend?": "Allerede Friend?",
"Amenities": "Fasiliteter",
"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 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.",
@@ -25,6 +27,7 @@
"Breakfast": "Frokost",
"Breakfast excluded": "Frokost ekskludert",
"Breakfast included": "Frokost inkludert",
"Bus terminal": "Bussterminal",
"by": "innen",
"Cancel": "Avbryt",
"characters": "tegn",
@@ -65,6 +68,7 @@
"Explore nearby": "Utforsk i nærheten",
"Extras to your booking": "Tilvalg til bestillingen din",
"Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.",
"Fair": "Messe",
"Find booking": "Finn booking",
"Find hotels": "Finn hotell",
"Flexibility": "Fleksibilitet",
@@ -77,6 +81,7 @@
"Go back to overview": "Gå tilbake til oversikten",
"Hi": "Hei",
"Highest level": "Høyeste nivå",
"Hospital": "Sykehus",
"Hotel": "Hotel",
"Hotel facilities": "Hotelfaciliteter",
"Hotel surroundings": "Hotellomgivelser",
@@ -103,6 +108,7 @@
"Manage preferences": "Administrer preferanser",
"Map": "Kart",
"Map of HOTEL_NAME": "Map of {hotelName}",
"Marketing city": "Markedsføringsby",
"Meetings & Conferences": "Møter & Konferanser",
"Member price": "Medlemspris",
"Member price from": "Medlemspris fra",
@@ -113,6 +119,7 @@
"Menu": "Menu",
"Modify": "Endre",
"Month": "Måned",
"Museum": "Museum",
"My communication preferences": "Mine kommunikasjonspreferanser",
"My membership cards": "Mine medlemskort",
"My pages": "Mine sider",
@@ -120,6 +127,7 @@
"My payment cards": "Mine betalingskort",
"My wishes": "Mine ønsker",
"Nearby": "I nærheten",
"Nearby companies": "Nærliggende selskaper",
"New password": "Nytt passord",
"Next": "Neste",
"next level:": "Neste nivå:",
@@ -141,6 +149,7 @@
"Open my pages menu": "Åpne mine sider menyen",
"or": "eller",
"Overview": "Oversikt",
"Parking / Garage": "Parkering / Garasje",
"Password": "Passord",
"Pay later": "Betal senere",
"Pay now": "Betal nå",
@@ -149,8 +158,8 @@
"Phone is required": "Telefon kreves",
"Phone number": "Telefonnummer",
"Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer",
"Points": "Poeng",
"points": "poeng",
"Points": "Poeng",
"Points being calculated": "Poeng beregnes",
"Points earned prior to May 1, 2021": "Opptjente poeng før 1. mai 2021",
"Points may take up to 10 days to be displayed.": "Det kan ta opptil 10 dager før poeng vises.",
@@ -161,6 +170,7 @@
"Read more": "Les mer",
"Read more about the hotel": "Les mer om hotellet",
"Remove card from member profile": "Fjern kortet fra medlemsprofilen",
"Restaurant": "Restaurant",
"Restaurant & Bar": "Restaurant & Bar",
"Retype new password": "Skriv inn nytt passord på nytt",
"Room & Terms": "Rom & Vilkår",
@@ -178,6 +188,7 @@
"Select date of birth": "Velg fødselsdato",
"Select language": "Velg språk",
"Select your language": "Velg språk",
"Shopping": "Shopping",
"Show all amenities": "Vis alle fasiliteter",
"Show less": "Vis mindre",
"Show map": "Vis kart",
@@ -189,18 +200,22 @@
"Something went wrong!": "Noe gikk galt!",
"special character": "spesiell karakter",
"spendable points expiring by": "{points} Brukbare poeng utløper innen {date}",
"Sports": "Sport",
"Standard price": "Standardpris",
"Street": "Gate",
"Successfully updated profile!": "Vellykket oppdatert profil!",
"Summary": "Sammendrag",
"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",
"Theatre": "Teater",
"There are no transactions to display": "Det er ingen transaksjoner å vise",
"Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}",
"to": "til",
"Total Points": "Totale poeng",
"Tourist": "Turist",
"Transaction date": "Transaksjonsdato",
"Transactions": "Transaksjoner",
"Transportations": "Transport",
"Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Sengtype",
@@ -238,6 +253,7 @@
"Your level": "Ditt nivå",
"Your points to spend": "Dine brukbare poeng",
"Zip code": "Post kode",
"Zoo": "Dyrehage",
"Zoom in": "Zoom inn",
"Zoom out": "Zoom ut"
}

View File

@@ -4,8 +4,10 @@
"Add code": "Lägg till kod",
"Add new card": "Lägg till nytt kort",
"Address": "Adress",
"Airport": "Flygplats",
"Already a friend?": "Är du redan en vän?",
"Amenities": "Bekvämligheter",
"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 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.",
@@ -25,6 +27,7 @@
"Breakfast": "Frukost",
"Breakfast excluded": "Frukost ingår ej",
"Breakfast included": "Frukost ingår",
"Bus terminal": "Bussterminal",
"by": "innan",
"Cancel": "Avbryt",
"characters": "tecken",
@@ -65,6 +68,7 @@
"Explore nearby": "Utforska i närheten",
"Extras to your booking": "Extra tillval till din bokning",
"Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.",
"Fair": "Mässa",
"Find booking": "Hitta bokning",
"Find hotels": "Hitta hotell",
"Flexibility": "Flexibilitet",
@@ -77,6 +81,7 @@
"Go back to overview": "Gå tillbaka till översikten",
"Hi": "Hej",
"Highest level": "Högsta nivå",
"Hospital": "Sjukhus",
"Hotel": "Hotell",
"Hotel facilities": "Hotellfaciliteter",
"Hotel surroundings": "Hotellomgivning",
@@ -105,6 +110,7 @@
"Manage preferences": "Hantera inställningar",
"Map": "Karta",
"Map of HOTEL_NAME": "Map of {hotelName}",
"Marketing city": "Marknadsföringsstad",
"Meetings & Conferences": "Möten & Konferenser",
"Member price": "Medlemspris",
"Member price from": "Medlemspris från",
@@ -115,6 +121,7 @@
"Menu": "Meny",
"Modify": "Ändra",
"Month": "Månad",
"Museum": "Museum",
"My communication preferences": "Mina kommunikationspreferenser",
"My membership cards": "Mina medlemskort",
"My pages": "Mina sidor",
@@ -122,6 +129,7 @@
"My payment cards": "Mina betalningskort",
"My wishes": "Mina önskningar",
"Nearby": "I närheten",
"Nearby companies": "Närliggande företag",
"New password": "Nytt lösenord",
"Next": "Nästa",
"next level:": "Nästa nivå:",
@@ -143,6 +151,7 @@
"Open my pages menu": "Öppna mina sidor menyn",
"or": "eller",
"Overview": "Översikt",
"Parking / Garage": "Parkering / Garage",
"Password": "Lösenord",
"Pay later": "Betala senare",
"Pay now": "Betala nu",
@@ -151,8 +160,8 @@
"Phone is required": "Telefonnummer är obligatorisk",
"Phone number": "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 earned prior to May 1, 2021": "Intjänade poäng före den 1 maj 2021",
"Points may take up to 10 days to be displayed.": "Det kan ta upp till 10 dagar innan poäng visas.",
@@ -163,6 +172,7 @@
"Read more": "Läs mer",
"Read more about the hotel": "Läs mer om hotellet",
"Remove card from member profile": "Ta bort kortet från medlemsprofilen",
"Restaurant": "Restaurang",
"Restaurant & Bar": "Restaurang & Bar",
"Retype new password": "Upprepa nytt lösenord",
"Room & Terms": "Rum & Villkor",
@@ -181,6 +191,7 @@
"Select date of birth": "Välj födelsedatum",
"Select language": "Välj språk",
"Select your language": "Välj ditt språk",
"Shopping": "Shopping",
"Show all amenities": "Visa alla bekvämligheter",
"Show less": "Visa mindre",
"Show map": "Visa karta",
@@ -192,18 +203,22 @@
"Something went wrong!": "Något gick fel!",
"special character": "speciell karaktär",
"spendable points expiring by": "{points} poäng förfaller {date}",
"Sports": "Sport",
"Standard price": "Standardpris",
"Street": "Gata",
"Successfully updated profile!": "Profilen har uppdaterats framgångsrikt!",
"Summary": "Sammanfattning",
"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",
"Theatre": "Teater",
"There are no transactions to display": "Det finns inga transaktioner att visa",
"Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}",
"to": "till",
"Total Points": "Poäng totalt",
"Tourist": "Turist",
"Transaction date": "Transaktionsdatum",
"Transactions": "Transaktioner",
"Transportations": "Transport",
"Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)",
"TUI Points": "TUI Points",
"Type of bed": "Sängtyp",
@@ -240,6 +255,7 @@
"Your level": "Din nivå",
"Your points to spend": "Dina spenderbara poäng",
"Zip code": "Postnummer",
"Zoo": "Djurpark",
"Zoom in": "Zooma in",
"Zoom out": "Zooma ut"
}

8
package-lock.json generated
View File

@@ -27,7 +27,7 @@
"@trpc/react-query": "^11.0.0-rc.467",
"@trpc/server": "^11.0.0-rc.467",
"@vercel/otel": "^1.9.1",
"@vis.gl/react-google-maps": "^1.1.3",
"@vis.gl/react-google-maps": "^1.2.0",
"class-variance-authority": "^0.7.0",
"clean-deep": "^3.4.0",
"dayjs": "^1.11.10",
@@ -6656,9 +6656,9 @@
}
},
"node_modules/@vis.gl/react-google-maps": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@vis.gl/react-google-maps/-/react-google-maps-1.1.3.tgz",
"integrity": "sha512-jsjj5OpL5AyQDSfzBHU+r5UGAuZxv/7mw9gM2dP4EhqRXkXBpwDKKlml47/4l0m1WL4fSEXLzv85Mrh1VhPiFA==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@vis.gl/react-google-maps/-/react-google-maps-1.2.0.tgz",
"integrity": "sha512-gKVE1Jb+FT+F8RGzFrsgB4GWbRq/vLJm2U5nMHiLJmRyaO6HcSfZJue8mUEDJCShsXE0ASphcoJxTQNrBhbFJg==",
"dependencies": {
"@types/google.maps": "^3.54.10",
"fast-deep-equal": "^3.1.3"

View File

@@ -43,7 +43,7 @@
"@trpc/react-query": "^11.0.0-rc.467",
"@trpc/server": "^11.0.0-rc.467",
"@vercel/otel": "^1.9.1",
"@vis.gl/react-google-maps": "^1.1.3",
"@vis.gl/react-google-maps": "^1.2.0",
"class-variance-authority": "^0.7.0",
"clean-deep": "^3.4.0",
"dayjs": "^1.11.10",

View File

@@ -216,16 +216,46 @@ const rewardNightSchema = z.object({
}),
})
const pointsOfInterestSchema = z.object({
name: z.string(),
distance: z.number(),
category: z.object({
const poiCategories = z.enum([
"Airport",
"Amusement park",
"Bus terminal",
"Fair",
"Hospital",
"Hotel",
"Marketing city",
"Museum",
"Nearby companies",
"Parking / Garage",
"Restaurant",
"Shopping",
"Sports",
"Theatre",
"Tourist",
"Transportations",
"Zoo",
])
export const pointOfInterestSchema = z
.object({
name: z.string(),
group: z.string(),
}),
location: locationSchema,
isHighlighted: z.boolean(),
})
distance: z.number(),
category: z.object({
name: poiCategories,
group: z.string(),
}),
location: locationSchema,
isHighlighted: z.boolean(),
})
.transform((poi) => ({
name: poi.name,
distance: poi.distance,
category: poi.category.name,
coordinates: {
lat: poi.location.latitude,
lng: poi.location.longitude,
},
}))
const parkingPricingSchema = z.object({
freeParking: z.boolean(),
@@ -454,7 +484,9 @@ export const getHotelDataSchema = z.object({
detailedFacilities: z.array(detailedFacilitySchema),
healthFacilities: z.array(healthFacilitySchema),
rewardNight: rewardNightSchema,
pointsOfInterest: z.array(pointsOfInterestSchema),
pointsOfInterest: z
.array(pointOfInterestSchema)
.transform((pois) => pois.sort((a, b) => a.distance - b.distance)),
parking: z.array(parkingSchema),
specialNeedGroups: z.array(specialNeedGroupSchema),
socialMedia: socialMediaSchema,

View File

@@ -251,6 +251,7 @@ export const hotelQueryRouter = router({
hotelRatings: hotelAttributes.ratings,
hotelDetailedFacilities: hotelAttributes.detailedFacilities,
hotelImages: images,
pointsOfInterest: hotelAttributes.pointsOfInterest,
roomCategories,
activitiesCard: activities,
}

View File

@@ -0,0 +1,18 @@
import { IconName } from "@/types/components/icon"
export function getIconByPoiCategory(category: string) {
switch (category) {
case "Transportations":
return IconName.Train
case "Shopping":
return IconName.Shopping
case "Museum":
return IconName.Museum
case "Tourist":
return IconName.Cultural
case "Restaurant":
return IconName.Restaurant
default:
return null
}
}

View File

@@ -1,7 +1,9 @@
import type { PointOfInterest } from "@/types/hotel"
import type { Coordinates } from "../../maps/coordinates"
export interface DynamicMapProps {
apiKey: string
hotelName: string
coordinates: Coordinates
pointsOfInterest: PointOfInterest[]
}

View File

@@ -1,6 +0,0 @@
import type { Coordinates } from "../../maps/coordinates"
export interface DynamicMapContentProps {
hotelName: string
coordinates: Coordinates
}

View File

@@ -1,3 +1,6 @@
import { PointOfInterest } from "@/types/hotel"
export interface MapCardProps {
hotelName: string
pois: PointOfInterest[]
}

View File

@@ -0,0 +1,9 @@
import { PointOfInterest } from "@/types/hotel"
import type { Coordinates } from "../../maps/coordinates"
export interface MapContentProps {
coordinates: Coordinates
pointsOfInterest: PointOfInterest[]
activePoi: PointOfInterest["name"] | null
onActivePoiChange: (poi: PointOfInterest["name"] | null) => void
}

View File

@@ -0,0 +1,8 @@
import { PointOfInterest } from "@/types/hotel"
export interface SidebarProps {
hotelName: string
pointsOfInterest: PointOfInterest[]
activePoi: PointOfInterest["name"] | null
onActivePoiChange: (poi: PointOfInterest["name"] | null) => void
}

View File

@@ -25,6 +25,7 @@ export enum IconName {
CloseLarge = "CloseLarge",
Coffee = "Coffee",
Concierge = "Concierge",
Cultural = "Cultural",
DoorOpen = "DoorOpen",
ElectricBike = "ElectricBike",
Email = "Email",
@@ -40,6 +41,7 @@ export enum IconName {
Lock = "Lock",
Map = "Map",
Minus = "Minus",
Museum = "Museum",
Parking = "Parking",
Person = "Person",
People2 = "People2",
@@ -51,6 +53,9 @@ export enum IconName {
Sauna = "Sauna",
Search = "Search",
Service = "Service",
Shopping = "Shopping",
StarFilled = "StarFilled",
Train = "Train",
Tripadvisor = "Tripadvisor",
TshirtWash = "TshirtWash",
Wifi = "Wifi",

View File

@@ -0,0 +1,8 @@
import { VariantProps } from "class-variance-authority"
import { poiVariants } from "@/components/Maps/Markers/Poi/variants"
export interface PoiMarkerProps extends VariantProps<typeof poiVariants> {
size?: number
className?: string
}

View File

@@ -1,8 +1,8 @@
import { z } from "zod"
import {
getAvailabilitySchema,
getHotelDataSchema,
pointOfInterestSchema,
roomSchema,
} from "@/server/routers/hotels/output"
@@ -18,3 +18,6 @@ export type HotelTripAdvisor =
| undefined
export type RoomData = z.infer<typeof roomSchema>
export type PointOfInterest = z.output<typeof pointOfInterestSchema>
export type PointOfInterestCategory = PointOfInterest["category"]