From 1440b4dbae40eccc2d823048dd7640d89d4015df Mon Sep 17 00:00:00 2001 From: Niclas Edenvin Date: Wed, 25 Sep 2024 17:44:53 +0200 Subject: [PATCH 01/30] Move map out to its own component --- components/ContentType/HotelPage/Map/DynamicMap/index.tsx | 2 +- .../Map/DynamicMap/Map => Maps/InteractiveMap}/index.tsx | 4 ++-- .../InteractiveMap/interactiveMap.module.css} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename components/{ContentType/HotelPage/Map/DynamicMap/Map => Maps/InteractiveMap}/index.tsx (97%) rename components/{ContentType/HotelPage/Map/DynamicMap/Map/map.module.css => Maps/InteractiveMap/interactiveMap.module.css} (100%) diff --git a/components/ContentType/HotelPage/Map/DynamicMap/index.tsx b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx index 785949be5..9fdddcf48 100644 --- a/components/ContentType/HotelPage/Map/DynamicMap/index.tsx +++ b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx @@ -6,9 +6,9 @@ import { useIntl } from "react-intl" import useHotelPageStore from "@/stores/hotel-page" +import MapContent from "@/components/Maps/InteractiveMap" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" -import MapContent from "./Map" import Sidebar from "./Sidebar" import styles from "./dynamicMap.module.css" diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Map/index.tsx b/components/Maps/InteractiveMap/index.tsx similarity index 97% rename from components/ContentType/HotelPage/Map/DynamicMap/Map/index.tsx rename to components/Maps/InteractiveMap/index.tsx index 60a30a9a3..ce18c2d77 100644 --- a/components/ContentType/HotelPage/Map/DynamicMap/Map/index.tsx +++ b/components/Maps/InteractiveMap/index.tsx @@ -18,11 +18,11 @@ 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 styles from "./interactiveMap.module.css" import type { MapContentProps } from "@/types/components/hotelPage/map/mapContent" -export default function MapContent({ +export default function InteractiveMap({ coordinates, pointsOfInterest, activePoi, diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Map/map.module.css b/components/Maps/InteractiveMap/interactiveMap.module.css similarity index 100% rename from components/ContentType/HotelPage/Map/DynamicMap/Map/map.module.css rename to components/Maps/InteractiveMap/interactiveMap.module.css From 1e7c24f875481550e182f1698cf075040f7ff6a1 Mon Sep 17 00:00:00 2001 From: Niclas Edenvin Date: Thu, 26 Sep 2024 15:27:24 +0200 Subject: [PATCH 02/30] Adapt the interactive map for reuse --- .../Map/DynamicMap/dynamicMap.module.css | 6 ++++++ .../HotelPage/Map/DynamicMap/index.tsx | 21 +++++++++++++++++-- components/Maps/InteractiveMap/index.tsx | 21 ++++--------------- .../map/{mapContent.ts => interactiveMap.ts} | 5 ++++- 4 files changed, 33 insertions(+), 20 deletions(-) rename types/components/hotelPage/map/{mapContent.ts => interactiveMap.ts} (75%) diff --git a/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css b/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css index 2a3e4ff78..32df5b502 100644 --- a/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css +++ b/components/ContentType/HotelPage/Map/DynamicMap/dynamicMap.module.css @@ -14,3 +14,9 @@ top: var(--main-menu-desktop-height); } } + +.closeButton { + pointer-events: initial; + box-shadow: var(--button-box-shadow); + gap: var(--Spacing-x-half); +} diff --git a/components/ContentType/HotelPage/Map/DynamicMap/index.tsx b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx index 9fdddcf48..2af575a69 100644 --- a/components/ContentType/HotelPage/Map/DynamicMap/index.tsx +++ b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx @@ -6,7 +6,9 @@ import { useIntl } from "react-intl" import useHotelPageStore from "@/stores/hotel-page" -import MapContent from "@/components/Maps/InteractiveMap" +import CloseLargeIcon from "@/components/Icons/CloseLarge" +import InteractiveMap from "@/components/Maps/InteractiveMap" +import Button from "@/components/TempDesignSystem/Button" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import Sidebar from "./Sidebar" @@ -52,6 +54,20 @@ export default function DynamicMap({ } }, [isDynamicMapOpen, scrollHeightWhenOpened]) + const closeButton = ( + + ) + return ( @@ -68,7 +84,8 @@ export default function DynamicMap({ pointsOfInterest={pointsOfInterest} onActivePoiChange={setActivePoi} /> -
- + {closeButton}
+ ) + return ( + + + + + ) +} diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/selectHotelMap.module.css b/components/HotelReservation/SelectHotel/SelectHotelMap/selectHotelMap.module.css new file mode 100644 index 000000000..54f685aa8 --- /dev/null +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/selectHotelMap.module.css @@ -0,0 +1,5 @@ +.closeButton { + pointer-events: initial; + box-shadow: var(--button-box-shadow); + gap: var(--Spacing-x-half); +} diff --git a/components/TempDesignSystem/Link/index.tsx b/components/TempDesignSystem/Link/index.tsx index 23cf15e46..2301ce932 100644 --- a/components/TempDesignSystem/Link/index.tsx +++ b/components/TempDesignSystem/Link/index.tsx @@ -1,7 +1,7 @@ "use client" import NextLink from "next/link" -import { usePathname, useRouter } from "next/navigation" -import { startTransition, useCallback } from "react" +import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { startTransition, useCallback, useMemo } from "react" import useRouterTransitionStore from "@/stores/router-transition" @@ -24,9 +24,14 @@ export default function Link({ variant, trackingId, onClick, + /** + * Decides if the link should include the current search params in the URL + */ + keepSearchParams, ...props }: LinkProps) { const currentPageSlug = usePathname() + const searchParams = useSearchParams() let isActive = active || currentPageSlug === href if (partialMatch && !isActive) { @@ -44,6 +49,12 @@ export default function Link({ const router = useRouter() + const fullUrl = useMemo(() => { + const search = + keepSearchParams && searchParams.size ? `?${searchParams}` : "" + return `${href}${search}` + }, [href, searchParams, keepSearchParams]) + const startRouterTransition = useRouterTransitionStore( (state) => state.startRouterTransition ) @@ -75,10 +86,10 @@ export default function Link({ trackPageViewStart() startTransition(() => { startRouterTransition() - router.push(href, { scroll }) + router.push(fullUrl, { scroll }) }) }} - href={href} + href={fullUrl} id={trackingId} {...props} /> diff --git a/components/TempDesignSystem/Link/link.ts b/components/TempDesignSystem/Link/link.ts index 85cb492c6..a68362457 100644 --- a/components/TempDesignSystem/Link/link.ts +++ b/components/TempDesignSystem/Link/link.ts @@ -10,4 +10,5 @@ export interface LinkProps partialMatch?: boolean prefetch?: boolean trackingId?: string + keepSearchParams?: boolean } diff --git a/constants/routes/hotelReservation.js b/constants/routes/hotelReservation.js index ba0d6d4b6..72d298965 100644 --- a/constants/routes/hotelReservation.js +++ b/constants/routes/hotelReservation.js @@ -8,4 +8,24 @@ export const hotelReservation = { de: "/de/hotelreservierung", } +// TODO: Translate paths +export const selectHotel = { + en: `${hotelReservation.en}/select-hotel`, + sv: `${hotelReservation.sv}/select-hotel`, + no: `${hotelReservation.no}/select-hotel`, + fi: `${hotelReservation.fi}/select-hotel`, + da: `${hotelReservation.da}/select-hotel`, + de: `${hotelReservation.de}/select-hotel`, +} + +// TODO: Translate paths +export const selectHotelMap = { + en: `${selectHotel.en}/map`, + sv: `${selectHotel.sv}/map`, + no: `${selectHotel.no}/map`, + fi: `${selectHotel.fi}/map`, + da: `${selectHotel.da}/map`, + de: `${selectHotel.de}/map`, +} + export const bookingFlow = [...Object.values(hotelReservation)] diff --git a/types/components/hotelReservation/selectHotel/hotelFiltersProps.ts b/types/components/hotelReservation/selectHotel/hotelFilters.ts similarity index 51% rename from types/components/hotelReservation/selectHotel/hotelFiltersProps.ts rename to types/components/hotelReservation/selectHotel/hotelFilters.ts index 1be84f63d..18177641f 100644 --- a/types/components/hotelReservation/selectHotel/hotelFiltersProps.ts +++ b/types/components/hotelReservation/selectHotel/hotelFilters.ts @@ -3,3 +3,11 @@ import { Hotel } from "@/types/hotel" export type HotelFiltersProps = { filters: Hotel["detailedFacilities"] } + +export type Filter = { + name: string + id: number + public: boolean + sortOrder: number + code?: string +} diff --git a/types/components/hotelReservation/selectHotel/map.ts b/types/components/hotelReservation/selectHotel/map.ts new file mode 100644 index 000000000..9d223d24d --- /dev/null +++ b/types/components/hotelReservation/selectHotel/map.ts @@ -0,0 +1,15 @@ +import { Coordinates } from "@/types/components/maps/coordinates" +import type { PointOfInterest } from "@/types/hotel" + +export interface HotelListingProps { + // pointsOfInterest: PointOfInterest[] + // activePoi: PointOfInterest["name"] | null + // onActivePoiChange: (poi: PointOfInterest["name"] | null) => void +} + +export interface SelectHotelMapProps { + apiKey: string + coordinates: Coordinates + pointsOfInterest: PointOfInterest[] + mapId: string +} From 2d25496d79511f3522dec8d50f39afdafb019f8e Mon Sep 17 00:00:00 2001 From: Niclas Edenvin Date: Thu, 26 Sep 2024 16:15:06 +0200 Subject: [PATCH 04/30] Update availability endpoint to v1 --- lib/api/endpoints.ts | 2 +- server/routers/hotels/query.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/endpoints.ts b/lib/api/endpoints.ts index 1fbb3af98..213e40b37 100644 --- a/lib/api/endpoints.ts +++ b/lib/api/endpoints.ts @@ -4,9 +4,9 @@ export namespace endpoints { export const enum v0 { profile = "profile/v0/Profile", - availability = "availability/v0/availabilities/city", } export const enum v1 { + availability = "availability/v1/availabilities/city", profile = "profile/v1/Profile", booking = "booking/v1/Bookings", creditCards = `${profile}/creditCards`, diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index a515ea420..9c1042dbd 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -271,7 +271,7 @@ export const hotelQueryRouter = router({ JSON.stringify({ query: { cityId, params } }) ) const apiResponse = await api.get( - `${api.endpoints.v0.availability}/${cityId}`, + `${api.endpoints.v1.availability}/${cityId}`, { headers: { Authorization: `Bearer ${ctx.serviceToken}`, From 4477adacdce42d0f9e9549f330532fc869ae976d Mon Sep 17 00:00:00 2001 From: Niclas Edenvin Date: Fri, 27 Sep 2024 14:30:03 +0200 Subject: [PATCH 05/30] Change to new enum format for point of interest --- .../(public)/hotelreservation/select-hotel/map/page.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.tsx index 1c66c16cb..89f4e62ca 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/map/page.tsx @@ -9,7 +9,11 @@ import { setLang } from "@/i18n/serverContext" import styles from "./page.module.css" -import { PointOfInterest } from "@/types/hotel" +import { + PointOfInterest, + PointOfInterestCategoryNameEnum, + PointOfInterestGroupEnum, +} from "@/types/hotel" import { LangParams, PageArgs } from "@/types/params" export default async function SelectHotelMapPage({ @@ -36,7 +40,8 @@ export default async function SelectHotelMapPage({ }, name: hotel.hotelData.name, distance: hotel.hotelData.location.distanceToCentre, - category: "Hotel", + categoryName: PointOfInterestCategoryNameEnum.HOTEL, + group: PointOfInterestGroupEnum.LOCATION, })) return ( From a5dff7b97d0e9dc3a28af654ab0ec95006eee3e4 Mon Sep 17 00:00:00 2001 From: Niclas Edenvin Date: Fri, 27 Sep 2024 14:42:40 +0200 Subject: [PATCH 06/30] New filter data format --- types/components/hotelReservation/selectHotel/hotelFilters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/components/hotelReservation/selectHotel/hotelFilters.ts b/types/components/hotelReservation/selectHotel/hotelFilters.ts index 18177641f..e298cb547 100644 --- a/types/components/hotelReservation/selectHotel/hotelFilters.ts +++ b/types/components/hotelReservation/selectHotel/hotelFilters.ts @@ -9,5 +9,5 @@ export type Filter = { id: number public: boolean sortOrder: number - code?: string + filter?: string } From 99b14304d47182193a4a2174a18c92d2b378beed Mon Sep 17 00:00:00 2001 From: Simon Emanuelsson Date: Fri, 23 Aug 2024 16:17:35 +0200 Subject: [PATCH 07/30] feat: add desktop ui to calendar --- .../BookingWidget/bookingWidget.module.css | 11 +- components/DatePicker/DatePicker.tsx | 99 ++++++++++++-- components/DatePicker/date-picker.module.css | 129 +++++++++++++++++- components/DatePicker/index.tsx | 5 + .../FormContent/Search/search.module.css | 1 + .../Forms/BookingWidget/FormContent/index.tsx | 8 +- components/Forms/BookingWidget/schema.ts | 7 +- .../TempDesignSystem/Button/button.module.css | 18 +-- .../Divider/divider.module.css | 4 + .../TempDesignSystem/Divider/variants.ts | 9 +- i18n/dictionaries/da.json | 47 +++---- i18n/dictionaries/de.json | 47 +++---- i18n/dictionaries/en.json | 48 +++---- i18n/dictionaries/fi.json | 47 +++---- i18n/dictionaries/no.json | 47 +++---- i18n/dictionaries/sv.json | 47 +++---- types/components/datepicker.ts | 8 ++ 17 files changed, 405 insertions(+), 177 deletions(-) diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index 5664279a2..7e1c1615e 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -1,12 +1,11 @@ .container { - background-color: var(--Base-Surface-Primary-light-Normal); - border-top: 1px solid var(--Base-Border-Subtle); - border-bottom: 1px solid var(--Base-Border-Subtle); - padding: var(--Spacing-x2) var(--Spacing-x5); + display: none; } -@media screen and (max-width: 1367px) { +@media screen and (min-width: 1367px) { .container { - display: none; + border-bottom: 1px solid var(--Base-Border-Subtle); + border-top: 1px solid var(--Base-Border-Subtle); + display: block; } } diff --git a/components/DatePicker/DatePicker.tsx b/components/DatePicker/DatePicker.tsx index a48bb3831..0e78ac59c 100644 --- a/components/DatePicker/DatePicker.tsx +++ b/components/DatePicker/DatePicker.tsx @@ -2,14 +2,24 @@ import { da, de, fi, nb, sv } from "date-fns/locale" import { useState } from "react" import { type DateRange, DayPicker } from "react-day-picker" +import { useIntl } from "react-intl" import { Lang } from "@/constants/languages" import { dt } from "@/lib/dt" +import Button from "@/components/TempDesignSystem/Button" +import Divider from "@/components/TempDesignSystem/Divider" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import useLang from "@/hooks/useLang" +import { ChevronLeftIcon } from "../Icons" + +import styles from "./date-picker.module.css" import classNames from "react-day-picker/style.module.css" +import type { DatePickerProps } from "@/types/components/datepicker" + const locales = { [Lang.da]: da, [Lang.de]: de, @@ -18,12 +28,8 @@ const locales = { [Lang.sv]: sv, } -export interface DatePickerProps { - handleOnSelect: (selected: DateRange) => void - initialSelected?: DateRange -} - export default function DatePicker({ + close, handleOnSelect, initialSelected = { from: undefined, @@ -31,6 +37,7 @@ export default function DatePicker({ }, }: DatePickerProps) { const lang = useLang() + const intl = useIntl() const [selectedDate, setSelectedDate] = useState(initialSelected) function handleSelectDate(selected: DateRange) { @@ -40,23 +47,99 @@ export default function DatePicker({ /** English is default language and doesn't need to be imported */ const locale = lang === Lang.en ? undefined : locales[lang] - const currentDate = dt().toDate() const startOfMonth = dt(currentDate).set("date", 1).toDate() const yesterday = dt(currentDate).subtract(1, "day").toDate() return ( + }, + Footer(props) { + return ( + <> + +
+ +
+ + ) + }, + MonthCaption(props) { + return ( +
+ + {props.children} + +
+ ) + }, + Nav(props) { + if (Array.isArray(props.children)) { + const prevButton = props.children?.[0] + const nextButton = props.children?.[1] + return ( + <> + {prevButton ? ( + + ) : null} + {nextButton ? ( + + ) : null} + + ) + } + return <> + }, + }} /> ) } diff --git a/components/DatePicker/date-picker.module.css b/components/DatePicker/date-picker.module.css index 926b9ad47..77cc0b140 100644 --- a/components/DatePicker/date-picker.module.css +++ b/components/DatePicker/date-picker.module.css @@ -9,12 +9,16 @@ .hideWrapper { background-color: var(--Main-Grey-White); - border-radius: var(--Corner-radius-Medium); - box-shadow: 0px 16px 24px 0px rgba(0, 0, 0, 0.08); - padding: var(--Spacing-x-one-and-half); + border-radius: var(--Corner-radius-Large); + box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); + padding: var(--Spacing-x2) var(--Spacing-x3); position: absolute; - /** BookingWidget padding + border-width */ - top: calc(100% + var(--Spacing-x2) + 1px); + /** + BookingWidget padding + + border-width + + wanted space below booking widget + */ + top: calc(100% + var(--Spacing-x2) + 1px + var(--Spacing-x4)); } .btn { @@ -29,3 +33,118 @@ .body { opacity: 0.8; } + +div.months { + flex-wrap: nowrap; +} + +.monthCaption { + justify-content: center; +} + +.captionLabel { + text-transform: capitalize; +} + +td.day, +td.rangeEnd, +td.rangeStart { + font-family: var(--typography-Body-Bold-fontFamily); + font-size: var(--typography-Body-Bold-fontSize); + font-weight: 500; + letter-spacing: var(--typography-Body-Bold-letterSpacing); + line-height: var(--typography-Body-Bold-lineHeight); + text-decoration: var(--typography-Body-Bold-textDecoration); +} + +td.rangeEnd, +td.rangeStart { + background: var(--Base-Background-Primary-Normal); +} + +td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) { + border-radius: 0 50% 50% 0; +} + +td.rangeStart[aria-selected="true"] { + border-radius: 50% 0 0 50%; +} + +td.rangeEnd[aria-selected="true"] button.dayButton:hover, +td.rangeStart[aria-selected="true"] button.dayButton:hover { + background: var(--Primary-Light-On-Surface-Accent); + border-radius: 50%; +} + +td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) button.dayButton, +td.rangeStart[aria-selected="true"]:not([data-outside="true"]) + button.dayButton { + background: var(--Primary-Light-On-Surface-Accent); + border: none; + color: var(--Base-Button-Inverted-Fill-Normal); +} + +td.day, +td.day[data-today="true"] { + color: var(--UI-Text-High-contrast); + height: 40px; + padding: var(--Spacing-x-half); + width: 40px; +} + +td.day button.dayButton:hover { + background: var(--Base-Surface-Secondary-light-Hover); +} + +td.day[data-outside="true"] button.dayButton { + border: none; +} + +td.day:not(td.rangeEnd, td.rangeStart)[aria-selected="true"], +td.rangeMiddle[aria-selected="true"] button.dayButton { + background: var(--Base-Background-Primary-Normal); + border: none; + border-radius: 0; +} + +td.day[data-disabled="true"], +td.day[data-disabled="true"] button.dayButton, +td.day[data-outside="true"] ~ td.day[data-disabled="true"], +td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton, +.week:has(td.day[data-outside="true"] ~ td.day[data-disabled="true"]) + td.day[data-outside="true"] + button.dayButton { + background: none; + color: var(--Base-Text-Disabled); + cursor: not-allowed; +} + +.weekDay { + color: var(--UI-Text-Placeholder); + font-family: var(--typography-Footnote-Labels-fontFamily); + font-size: var(--typography-Footnote-Labels-fontSize); + font-weight: var(--typography-Footnote-Labels-fontWeight); + letter-spacing: var(--typography-Footnote-Labels-letterSpacing); + line-height: var(--typography-Footnote-Labels-lineHeight); + text-decoration: var(--typography-Footnote-Labels-textDecoration); + text-transform: uppercase; +} + +.footer { + display: flex; + justify-content: flex-end; + margin-top: var(--Spacing-x2); +} + +.divider { + margin-top: var(--Spacing-x2); +} + +.nextButton { + transform: rotate(180deg); + right: 0; +} + +.previousButton { + left: 0; +} diff --git a/components/DatePicker/index.tsx b/components/DatePicker/index.tsx index f7de60e20..0da5bb263 100644 --- a/components/DatePicker/index.tsx +++ b/components/DatePicker/index.tsx @@ -22,6 +22,10 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) { const { register, setValue } = useFormContext() const ref = useRef(null) + function close() { + setIsOpen(false) + } + function handleOnClick() { setIsOpen((prevIsOpen) => !prevIsOpen) } @@ -64,6 +68,7 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
diff --git a/components/Forms/BookingWidget/FormContent/Search/search.module.css b/components/Forms/BookingWidget/FormContent/Search/search.module.css index 4b2d2047a..d2f40bda3 100644 --- a/components/Forms/BookingWidget/FormContent/Search/search.module.css +++ b/components/Forms/BookingWidget/FormContent/Search/search.module.css @@ -30,6 +30,7 @@ height: 24px; outline: none; position: relative; + width: 100%; z-index: 2; } diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx index e4e86450a..371140a7f 100644 --- a/components/Forms/BookingWidget/FormContent/index.tsx +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -33,10 +33,10 @@ export default function FormContent({
- {nights}{" "} - {nights > 1 - ? intl.formatMessage({ id: "nights" }) - : intl.formatMessage({ id: "night" })} + {intl.formatMessage( + { id: "booking.nights" }, + { totalNights: nights } + )}
diff --git a/components/Forms/BookingWidget/schema.ts b/components/Forms/BookingWidget/schema.ts index cccd51a71..1455f65a7 100644 --- a/components/Forms/BookingWidget/schema.ts +++ b/components/Forms/BookingWidget/schema.ts @@ -3,8 +3,9 @@ import { z } from "zod" import type { Location } from "@/types/trpc/routers/hotel/locations" export const bookingWidgetSchema = z.object({ - search: z.string({ coerce: true }).min(1, "Required"), + bookingCode: z.string(), // Update this as required when working with booking codes component date: z.object({ + // Update this as required once started working with Date picker in Nights component from: z.string(), to: z.string(), }), @@ -23,9 +24,7 @@ export const bookingWidgetSchema = z.object({ }, { message: "Required" } ), - bookingCode: z.string(), // Update this as required when working with booking codes component redemption: z.boolean().default(false), - voucher: z.boolean().default(false), rooms: z.array( // This will be updated when working in guests component z.object({ @@ -38,4 +37,6 @@ export const bookingWidgetSchema = z.object({ ), }) ), + search: z.string({ coerce: true }).min(1, "Required"), + voucher: z.boolean().default(false), }) diff --git a/components/TempDesignSystem/Button/button.module.css b/components/TempDesignSystem/Button/button.module.css index 1079b6e54..e44619deb 100644 --- a/components/TempDesignSystem/Button/button.module.css +++ b/components/TempDesignSystem/Button/button.module.css @@ -69,19 +69,19 @@ a.default { } /* SIZES */ -.small { +.btn.small { gap: var(--Spacing-x-quarter); height: 40px; padding: var(--Spacing-x1) var(--Spacing-x2); } -.medium { +.btn.medium { gap: var(--Spacing-x-half); height: 48px; padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); } -.large { +.btn.large { gap: var(--Spacing-x-half); height: 56px; padding: var(--Spacing-x2) var(--Spacing-x3); @@ -170,19 +170,19 @@ a.default { fill: var(--Base-Button-Secondary-On-Fill-Disabled); } -.baseTertiary { +.btn.baseTertiary { background-color: var(--Base-Button-Tertiary-Fill-Normal); color: var(--Base-Button-Tertiary-On-Fill-Normal); } -.baseTertiary:active, -.baseTertiary:focus, -.baseTertiary:hover { +.btn.baseTertiary:active, +.btn.baseTertiary:focus, +.btn.baseTertiary:hover { background-color: var(--Base-Button-Tertiary-Fill-Hover); color: var(--Base-Button-Tertiary-On-Fill-Hover); } -.baseTertiary:disabled { +.btn.baseTertiary:disabled { background-color: var(--Base-Button-Tertiary-Fill-Disabled); color: var(--Base-Button-Tertiary-On-Fill-Disabled); } @@ -800,4 +800,4 @@ a.default { .icon.tertiaryLightSecondary:disabled svg, .icon.tertiaryLightSecondary:disabled svg * { fill: var(--Tertiary-Light-Button-Secondary-On-Fill-Disabled); -} \ No newline at end of file +} diff --git a/components/TempDesignSystem/Divider/divider.module.css b/components/TempDesignSystem/Divider/divider.module.css index 80dac16d0..1896c546b 100644 --- a/components/TempDesignSystem/Divider/divider.module.css +++ b/components/TempDesignSystem/Divider/divider.module.css @@ -33,6 +33,10 @@ border-bottom-color: var(--Base-Border-Subtle); } +.primaryLightSubtle { + border-bottom-color: var(--Primary-Light-On-Surface-Divider-subtle); +} + .opacity100 { opacity: 1; } diff --git a/components/TempDesignSystem/Divider/variants.ts b/components/TempDesignSystem/Divider/variants.ts index 9c11e2c5a..8611474fa 100644 --- a/components/TempDesignSystem/Divider/variants.ts +++ b/components/TempDesignSystem/Divider/variants.ts @@ -5,12 +5,13 @@ import styles from "./divider.module.css" export const dividerVariants = cva(styles.divider, { variants: { color: { - burgundy: styles.burgundy, - peach: styles.peach, beige: styles.beige, - white: styles.white, - subtle: styles.subtle, + burgundy: styles.burgundy, pale: styles.pale, + peach: styles.peach, + primaryLightSubtle: styles.primaryLightSubtle, + subtle: styles.subtle, + white: styles.white, }, opacity: { 100: styles.opacity100, diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 76a453d7c..499f67cb3 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -14,7 +14,6 @@ "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?", "Arrival date": "Ankomstdato", - "as of today": "fra idag", "As our": "Som vores {level}", "As our Close Friend": "Som vores nære ven", "At latest": "Senest", @@ -31,9 +30,7 @@ "Breakfast included": "Morgenmad inkluderet", "Bus terminal": "Busstation", "Business": "Forretning", - "by": "inden", "Cancel": "Afbestille", - "characters": "tegn", "Check in": "Check ind", "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.", @@ -75,9 +72,9 @@ "Explore all levels and benefits": "Udforsk alle niveauer og fordele", "Explore nearby": "Udforsk i nærheden", "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.", "Fair": "Messe", - "FAQ": "Ofte stillede spørgsmål", "Find booking": "Find booking", "Find hotels": "Find hotel", "Flexibility": "Fleksibilitet", @@ -94,15 +91,11 @@ "Hotel": "Hotel", "Hotel facilities": "Hotel faciliteter", "Hotel surroundings": "Hotel omgivelser", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "personer", - "hotelPages.rooms.roomCard.seeRoomDetails": "Se værelsesdetaljer", "Hotels": "Hoteller", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det virker", "Image gallery": "Billedgalleri", "Join Scandic Friends": "Tilmeld dig Scandic Friends", - "km to city center": "km til byens centrum", "Language": "Sprog", "Latest searches": "Seneste søgninger", "Level": "Niveau", @@ -129,9 +122,9 @@ "Member price": "Medlemspris", "Member price from": "Medlemspris fra", "Members": "Medlemmer", - "Membership cards": "Medlemskort", "Membership ID": "Medlems-id", "Membership ID copied to clipboard": "Medlems-ID kopieret til udklipsholder", + "Membership cards": "Medlemskort", "Menu": "Menu", "Modify": "Ændre", "Month": "Måned", @@ -146,9 +139,6 @@ "Nearby companies": "Nærliggende virksomheder", "New password": "Nyt kodeord", "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", "No content published": "Intet indhold offentliggjort", "No matching location found": "Der blev ikke fundet nogen matchende placering", @@ -159,13 +149,11 @@ "Non-refundable": "Ikke-refunderbart", "Not found": "Ikke fundet", "Nr night, nr adult": "{nights, number} nat, {adults, number} voksen", - "number": "nummer", "On your journey": "På din rejse", "Open": "Åben", "Open language menu": "Åbn sprogmenuen", "Open menu": "Åbn menuen", "Open my pages menu": "Åbn mine sider menuen", - "or": "eller", "Overview": "Oversigt", "Parking": "Parkering", "Parking / Garage": "Parkering / Garage", @@ -177,7 +165,6 @@ "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 being calculated": "Point udregnes", "Points earned prior to May 1, 2021": "Point optjent inden 1. maj 2021", @@ -220,29 +207,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 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!", - "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", + "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.", "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", "Type of room": "Værelsestype", - "uppercase letter": "stort bogstav", "Use bonus cheque": "Brug Bonus Cheque", "User information": "Brugeroplysninger", "View as list": "Vis som liste", @@ -268,9 +251,9 @@ "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 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 saved!": "Dit kort blev gemt!", - "Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!", "Your current level": "Dit nuværende niveau", "Your details": "Dine oplysninger", "Your level": "Dit niveau", @@ -278,5 +261,23 @@ "Zip code": "Postnummer", "Zoo": "Zoo", "Zoom in": "Zoom ind", - "Zoom out": "Zoom ud" -} + "Zoom out": "Zoom ud", + "as of today": "fra idag", + "booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}", + "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" +} \ No newline at end of file diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 468d02143..a215a8263 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -14,7 +14,6 @@ "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?", "Arrival date": "Ankunftsdatum", - "as of today": "Stand heute", "As our": "Als unser {level}", "As our Close Friend": "Als unser enger Freund", "At latest": "Spätestens", @@ -31,9 +30,7 @@ "Breakfast included": "Frühstück inbegriffen", "Bus terminal": "Busbahnhof", "Business": "Geschäft", - "by": "bis", "Cancel": "Stornieren", - "characters": "figuren", "Check in": "Einchecken", "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.", @@ -75,9 +72,9 @@ "Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile", "Explore nearby": "Erkunden Sie die Umgebung", "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.", "Fair": "Messe", - "FAQ": "Häufig gestellte Fragen", "Find booking": "Buchung finden", "Find hotels": "Hotels finden", "Flexibility": "Flexibilität", @@ -94,15 +91,11 @@ "Hotel": "Hotel", "Hotel facilities": "Hotel-Infos", "Hotel surroundings": "Umgebung des Hotels", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "personen", - "hotelPages.rooms.roomCard.seeRoomDetails": "Zimmerdetails ansehen", "Hotels": "Hotels", "How do you want to sleep?": "Wie möchtest du schlafen?", "How it works": "Wie es funktioniert", "Image gallery": "Bildergalerie", "Join Scandic Friends": "Treten Sie Scandic Friends bei", - "km to city center": "km bis zum Stadtzentrum", "Language": "Sprache", "Latest searches": "Letzte Suchanfragen", "Level": "Level", @@ -129,9 +122,9 @@ "Member price": "Mitgliederpreis", "Member price from": "Mitgliederpreis ab", "Members": "Mitglieder", - "Membership cards": "Mitgliedskarten", "Membership ID": "Mitglieds-ID", "Membership ID copied to clipboard": "Mitglieds-ID in die Zwischenablage kopiert", + "Membership cards": "Mitgliedskarten", "Menu": "Menu", "Modify": "Ändern", "Month": "Monat", @@ -146,9 +139,6 @@ "Nearby companies": "Nahe gelegene Unternehmen", "New password": "Neues Kennwort", "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", "No content published": "Kein Inhalt veröffentlicht", "No matching location found": "Kein passender Standort gefunden", @@ -159,13 +149,11 @@ "Non-refundable": "Nicht erstattungsfähig", "Not found": "Nicht gefunden", "Nr night, nr adult": "{nights, number} Nacht, {adults, number} Erwachsener", - "number": "nummer", "On your journey": "Auf deiner Reise", "Open": "Offen", "Open language menu": "Sprachmenü öffnen", "Open menu": "Menü öffnen", "Open my pages menu": "Meine Seiten Menü öffnen", - "or": "oder", "Overview": "Übersicht", "Parking": "Parken", "Parking / Garage": "Parken / Garage", @@ -177,7 +165,6 @@ "Phone number": "Telefonnummer", "Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein", "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.", @@ -219,29 +206,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 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!", - "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", + "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.", "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", "Type of room": "Zimmerart", - "uppercase letter": "großbuchstabe", "Use bonus cheque": "Bonusscheck nutzen", "User information": "Nutzerinformation", "View as list": "Als Liste anzeigen", @@ -267,9 +250,9 @@ "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 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 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 details": "Ihre Angaben", "Your level": "Dein level", @@ -277,5 +260,23 @@ "Zip code": "PLZ", "Zoo": "Zoo", "Zoom in": "Vergrößern", - "Zoom out": "Verkleinern" -} + "Zoom out": "Verkleinern", + "as of today": "Stand heute", + "booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}", + "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" +} \ No newline at end of file diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 3bab2ea50..fa90f4f4e 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -14,7 +14,6 @@ "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?", "Arrival date": "Arrival date", - "as of today": "as of today", "As our": "As our {level}", "As our Close Friend": "As our Close Friend", "At latest": "At latest", @@ -31,9 +30,7 @@ "Breakfast included": "Breakfast included", "Bus terminal": "Bus terminal", "Business": "Business", - "by": "by", "Cancel": "Cancel", - "characters": "characters", "Check in": "Check in", "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.", @@ -75,9 +72,9 @@ "Explore all levels and benefits": "Explore all levels and benefits", "Explore nearby": "Explore nearby", "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.", "Fair": "Fair", - "FAQ": "FAQ", "Find booking": "Find booking", "Find hotels": "Find hotels", "Flexibility": "Flexibility", @@ -94,15 +91,11 @@ "Hotel": "Hotel", "Hotel facilities": "Hotel facilities", "Hotel surroundings": "Hotel surroundings", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "persons", - "hotelPages.rooms.roomCard.seeRoomDetails": "See room details", "Hotels": "Hotels", "How do you want to sleep?": "How do you want to sleep?", "How it works": "How it works", "Image gallery": "Image gallery", "Join Scandic Friends": "Join Scandic Friends", - "km to city center": "km to city center", "Language": "Language", "Latest searches": "Latest searches", "Level": "Level", @@ -129,9 +122,9 @@ "Member price": "Member price", "Member price from": "Member price from", "Members": "Members", - "Membership cards": "Membership cards", "Membership ID": "Membership ID", "Membership ID copied to clipboard": "Membership ID copied to clipboard", + "Membership cards": "Membership cards", "Menu": "Menu", "Modify": "Modify", "Month": "Month", @@ -146,9 +139,6 @@ "Nearby companies": "Nearby companies", "New password": "New password", "Next": "Next", - "next level:": "next level:", - "night": "night", - "nights": "nights", "Nights needed to level up": "Nights needed to level up", "No content published": "No content published", "No matching location found": "No matching location found", @@ -159,13 +149,11 @@ "Non-refundable": "Non-refundable", "Not found": "Not found", "Nr night, nr adult": "{nights, number} night, {adults, number} adult", - "number": "number", "On your journey": "On your journey", "Open": "Open", "Open language menu": "Open language menu", "Open menu": "Open menu", "Open my pages menu": "Open my pages menu", - "or": "or", "Overview": "Overview", "Parking": "Parking", "Parking / Garage": "Parking / Garage", @@ -178,7 +166,6 @@ "Phone number": "Phone number", "Please enter a valid phone number": "Please enter a valid phone number", "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.", @@ -207,6 +194,7 @@ "Select a country": "Select a country", "Select country of residence": "Select country of residence", "Select date of birth": "Select date of birth", + "Select dates": "Select dates", "Select language": "Select language", "Select your language": "Select your language", "Shopping": "Shopping", @@ -220,29 +208,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 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!", - "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", + "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.", "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", "Type of room": "Type of room", - "uppercase letter": "uppercase letter", "Use bonus cheque": "Use bonus cheque", "User information": "User information", "View as list": "View as list", @@ -268,9 +252,9 @@ "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 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 saved!": "Your card was successfully saved!", - "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your current level": "Your current level", "Your details": "Your details", "Your level": "Your level", @@ -278,5 +262,23 @@ "Zip code": "Zip code", "Zoo": "Zoo", "Zoom in": "Zoom in", - "Zoom out": "Zoom out" -} + "Zoom out": "Zoom out", + "as of today": "as of today", + "booking.nights": "{totalNights, plural, one {# night} other {# nights}}", + "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" +} \ No newline at end of file diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index a0c5f5207..673d108b5 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -14,7 +14,6 @@ "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?", "Arrival date": "Saapumispäivä", - "as of today": "tänään", "As our": "{level}-etu", "As our Close Friend": "Läheisenä ystävänämme", "At latest": "Viimeistään", @@ -31,9 +30,7 @@ "Breakfast included": "Aamiainen sisältyy", "Bus terminal": "Bussiasema", "Business": "Business", - "by": "mennessä", "Cancel": "Peruuttaa", - "characters": "hahmoja", "Check in": "Sisäänkirjautuminen", "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.", @@ -75,9 +72,9 @@ "Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin", "Explore nearby": "Tutustu lähialueeseen", "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.", "Fair": "Messukeskus", - "FAQ": "UKK", "Find booking": "Etsi varaus", "Find hotels": "Etsi hotelleja", "Flexibility": "Joustavuus", @@ -94,15 +91,11 @@ "Hotel": "Hotelli", "Hotel facilities": "Hotellin palvelut", "Hotel surroundings": "Hotellin ympäristö", - "hotelPages.rooms.roomCard.person": "henkilö", - "hotelPages.rooms.roomCard.persons": "Henkilöä", - "hotelPages.rooms.roomCard.seeRoomDetails": "Katso huoneen tiedot", "Hotels": "Hotellit", "How do you want to sleep?": "Kuinka haluat nukkua?", "How it works": "Kuinka se toimii", "Image gallery": "Kuvagalleria", "Join Scandic Friends": "Liity jäseneksi", - "km to city center": "km keskustaan", "Language": "Kieli", "Latest searches": "Viimeisimmät haut", "Level": "Level", @@ -129,9 +122,9 @@ "Member price": "Jäsenhinta", "Member price from": "Jäsenhinta alkaen", "Members": "Jäsenet", - "Membership cards": "Jäsenkortit", "Membership ID": "Jäsentunnus", "Membership ID copied to clipboard": "Jäsenyystunnus kopioitu leikepöydälle", + "Membership cards": "Jäsenkortit", "Menu": "Valikko", "Modify": "Muokkaa", "Month": "Kuukausi", @@ -146,9 +139,6 @@ "Nearby companies": "Läheiset yritykset", "New password": "Uusi salasana", "Next": "Seuraava", - "next level:": "pistettä tasolle:", - "night": "yö", - "nights": "yötä", "Nights needed to level up": "Yöt, joita tarvitaan tasolle", "No content published": "Ei julkaistua sisältöä", "No matching location found": "Vastaavaa sijaintia ei löytynyt", @@ -159,13 +149,11 @@ "Non-refundable": "Ei palautettavissa", "Not found": "Ei löydetty", "Nr night, nr adult": "{nights, number} yö, {adults, number} aikuinen", - "number": "määrä", "On your journey": "Matkallasi", "Open": "Avata", "Open language menu": "Avaa kielivalikko", "Open menu": "Avaa valikko", "Open my pages menu": "Avaa omat sivut -valikko", - "or": "tai", "Overview": "Yleiskatsaus", "Parking": "Pysäköinti", "Parking / Garage": "Pysäköinti / Autotalli", @@ -177,7 +165,6 @@ "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 being calculated": "Pisteitä lasketaan", "Points earned prior to May 1, 2021": "Pisteet, jotka ansaittu ennen 1.5.2021", @@ -221,29 +208,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 remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.", "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", + "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ä.", "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", "Type of room": "Huonetyyppi", - "uppercase letter": "iso kirjain", "Use bonus cheque": "Käytä bonussekkiä", "User information": "Käyttäjän tiedot", "View as list": "Näytä listana", @@ -269,9 +252,9 @@ "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 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 saved!": "Korttisi tallennettu onnistuneesti!", - "Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!", "Your current level": "Nykyinen tasosi", "Your details": "Tietosi", "Your level": "Tasosi", @@ -279,5 +262,23 @@ "Zip code": "Postinumero", "Zoo": "Eläintarha", "Zoom in": "Lähennä", - "Zoom out": "Loitonna" -} + "Zoom out": "Loitonna", + "as of today": "tänään", + "booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}", + "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" +} \ No newline at end of file diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 745240373..ea8aa4548 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -14,7 +14,6 @@ "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?", "Arrival date": "Ankomstdato", - "as of today": "per idag", "As our": "Som vår {level}", "As our Close Friend": "Som vår nære venn", "At latest": "Senest", @@ -31,9 +30,7 @@ "Breakfast included": "Frokost inkludert", "Bus terminal": "Bussterminal", "Business": "Forretnings", - "by": "innen", "Cancel": "Avbryt", - "characters": "tegn", "Check in": "Sjekk inn", "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.", @@ -75,9 +72,9 @@ "Explore all levels and benefits": "Utforsk alle nivåer og fordeler", "Explore nearby": "Utforsk i nærheten", "Extras to your booking": "Tilvalg til bestillingen din", + "FAQ": "FAQ", "Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.", "Fair": "Messe", - "FAQ": "FAQ", "Find booking": "Finn booking", "Find hotels": "Finn hotell", "Flexibility": "Fleksibilitet", @@ -94,15 +91,11 @@ "Hotel": "Hotel", "Hotel facilities": "Hotelfaciliteter", "Hotel surroundings": "Hotellomgivelser", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "personer", - "hotelPages.rooms.roomCard.seeRoomDetails": "Se detaljer om rommet", "Hotels": "Hoteller", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det fungerer", "Image gallery": "Bildegalleri", "Join Scandic Friends": "Bli med i Scandic Friends", - "km to city center": "km til sentrum", "Language": "Språk", "Latest searches": "Siste søk", "Level": "Nivå", @@ -129,9 +122,9 @@ "Member price": "Medlemspris", "Member price from": "Medlemspris fra", "Members": "Medlemmer", - "Membership cards": "Medlemskort", "Membership ID": "Medlems-ID", "Membership ID copied to clipboard": "Medlems-ID kopiert til utklippstavlen", + "Membership cards": "Medlemskort", "Menu": "Menu", "Modify": "Endre", "Month": "Måned", @@ -146,9 +139,6 @@ "Nearby companies": "Nærliggende selskaper", "New password": "Nytt passord", "Next": "Neste", - "next level:": "Neste nivå:", - "night": "natt", - "nights": "netter", "Nights needed to level up": "Netter som trengs for å komme opp i nivå", "No content published": "Ingen innhold publisert", "No matching location found": "Fant ingen samsvarende plassering", @@ -159,13 +149,11 @@ "Non-refundable": "Ikke-refunderbart", "Not found": "Ikke funnet", "Nr night, nr adult": "{nights, number} natt, {adults, number} voksen", - "number": "antall", "On your journey": "På reisen din", "Open": "Åpen", "Open language menu": "Åpne språkmenyen", "Open menu": "Åpne menyen", "Open my pages menu": "Åpne mine sider menyen", - "or": "eller", "Overview": "Oversikt", "Parking": "Parkering", "Parking / Garage": "Parkering / Garasje", @@ -177,7 +165,6 @@ "Phone is required": "Telefon kreves", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer", - "points": "poeng", "Points": "Poeng", "Points being calculated": "Poeng beregnes", "Points earned prior to May 1, 2021": "Opptjente poeng før 1. mai 2021", @@ -220,29 +207,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 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!", - "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", + "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.", "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", "Type of room": "Romtype", - "uppercase letter": "stor bokstav", "Use bonus cheque": "Bruk bonussjekk", "User information": "Brukerinformasjon", "View as list": "Vis som liste", @@ -268,9 +251,9 @@ "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 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 saved!": "Kortet ditt ble lagret!", - "Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!", "Your current level": "Ditt nåværende nivå", "Your details": "Dine detaljer", "Your level": "Ditt nivå", @@ -278,5 +261,23 @@ "Zip code": "Post kode", "Zoo": "Dyrehage", "Zoom in": "Zoom inn", - "Zoom out": "Zoom ut" -} + "Zoom out": "Zoom ut", + "as of today": "per idag", + "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", + "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" +} \ No newline at end of file diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 4aa850ebb..864b6e4ae 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -14,7 +14,6 @@ "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?", "Arrival date": "Ankomstdatum", - "as of today": "från och med idag", "As our": "Som vår {level}", "As our Close Friend": "Som vår nära vän", "At latest": "Senast", @@ -31,9 +30,7 @@ "Breakfast included": "Frukost ingår", "Bus terminal": "Bussterminal", "Business": "Business", - "by": "innan", "Cancel": "Avbryt", - "characters": "tecken", "Check in": "Checka in", "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.", @@ -75,9 +72,9 @@ "Explore all levels and benefits": "Utforska alla nivåer och fördelar", "Explore nearby": "Utforska i närheten", "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.", "Fair": "Mässa", - "FAQ": "FAQ", "Find booking": "Hitta bokning", "Find hotels": "Hitta hotell", "Flexibility": "Flexibilitet", @@ -94,15 +91,11 @@ "Hotel": "Hotell", "Hotel facilities": "Hotellfaciliteter", "Hotel surroundings": "Hotellomgivning", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "personer", - "hotelPages.rooms.roomCard.seeRoomDetails": "Se information om rummet", "Hotels": "Hotell", "How do you want to sleep?": "Hur vill du sova?", "How it works": "Hur det fungerar", "Image gallery": "Bildgalleri", "Join Scandic Friends": "Gå med i Scandic Friends", - "km to city center": "km till stadens centrum", "Language": "Språk", "Latest searches": "Senaste sökningarna", "Level": "Nivå", @@ -129,9 +122,9 @@ "Member price": "Medlemspris", "Member price from": "Medlemspris från", "Members": "Medlemmar", - "Membership cards": "Medlemskort", "Membership ID": "Medlems-ID", "Membership ID copied to clipboard": "Medlems-ID kopierat till urklipp", + "Membership cards": "Medlemskort", "Menu": "Meny", "Modify": "Ändra", "Month": "Månad", @@ -146,9 +139,6 @@ "Nearby companies": "Närliggande företag", "New password": "Nytt lösenord", "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å", "No content published": "Inget innehåll publicerat", "No matching location found": "Ingen matchande plats hittades", @@ -159,13 +149,11 @@ "Non-refundable": "Ej återbetalningsbar", "Not found": "Hittades inte", "Nr night, nr adult": "{nights, number} natt, {adults, number} vuxen", - "number": "nummer", "On your journey": "På din resa", "Open": "Öppna", "Open language menu": "Öppna språkmenyn", "Open menu": "Öppna menyn", "Open my pages menu": "Öppna mina sidor menyn", - "or": "eller", "Overview": "Översikt", "Parking": "Parkering", "Parking / Garage": "Parkering / Garage", @@ -177,7 +165,6 @@ "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 being calculated": "Poäng beräknas", "Points earned prior to May 1, 2021": "Intjänade poäng före den 1 maj 2021", @@ -220,29 +207,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 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!", - "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", + "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.", "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", "Type of room": "Rumstyp", - "uppercase letter": "stor bokstav", "Use bonus cheque": "Use bonus cheque", "User information": "Användarinformation", "View as list": "Visa som lista", @@ -268,9 +251,9 @@ "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 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 saved!": "Ditt kort har sparats!", - "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your current level": "Din nuvarande nivå", "Your details": "Dina uppgifter", "Your level": "Din nivå", @@ -278,5 +261,23 @@ "Zip code": "Postnummer", "Zoo": "Djurpark", "Zoom in": "Zooma in", - "Zoom out": "Zooma ut" -} + "Zoom out": "Zooma ut", + "as of today": "från och med idag", + "booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}", + "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" +} \ No newline at end of file diff --git a/types/components/datepicker.ts b/types/components/datepicker.ts index 976c792f6..2b82966c1 100644 --- a/types/components/datepicker.ts +++ b/types/components/datepicker.ts @@ -1,3 +1,11 @@ +import type { DateRange } from "react-day-picker" + export interface DatePickerFormProps { name?: string } + +export interface DatePickerProps { + close: () => void + handleOnSelect: (selected: DateRange) => void + initialSelected?: DateRange +} From d2121a3fedbc01971169f401940d8eda4ba65f74 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 27 Sep 2024 13:38:56 +0200 Subject: [PATCH 08/30] fix(SW-272): fixed issue with image sizes and aspect ratio on cards mainly inside mega menu --- components/TempDesignSystem/Card/card.ts | 2 ++ components/TempDesignSystem/Card/index.tsx | 13 +++++++++++-- server/routers/contentstack/schemas/imageVault.ts | 13 +++++++++---- utils/imageVault.ts | 14 ++++++++++---- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/components/TempDesignSystem/Card/card.ts b/components/TempDesignSystem/Card/card.ts index ba8a618a9..a2395c4f6 100644 --- a/components/TempDesignSystem/Card/card.ts +++ b/components/TempDesignSystem/Card/card.ts @@ -23,6 +23,8 @@ export interface CardProps heading?: string | null bodyText?: string | null backgroundImage?: ImageVaultAsset + imageHeight?: number + imageWidth?: number onPrimaryButtonClick?: () => void onSecondaryButtonClick?: () => void } diff --git a/components/TempDesignSystem/Card/index.tsx b/components/TempDesignSystem/Card/index.tsx index c4f414359..8d29226bf 100644 --- a/components/TempDesignSystem/Card/index.tsx +++ b/components/TempDesignSystem/Card/index.tsx @@ -21,11 +21,20 @@ export default function Card({ className, theme, backgroundImage, + imageHeight, + imageWidth, onPrimaryButtonClick, onSecondaryButtonClick, }: CardProps) { const { buttonTheme, primaryLinkColor, secondaryLinkColor } = getTheme(theme) + imageHeight = imageHeight || 320 + imageWidth = + imageWidth || + (backgroundImage + ? backgroundImage.dimensions.aspectRatio * imageHeight + : 420) + return (
)} diff --git a/server/routers/contentstack/schemas/imageVault.ts b/server/routers/contentstack/schemas/imageVault.ts index 6d8729c96..2e656a465 100644 --- a/server/routers/contentstack/schemas/imageVault.ts +++ b/server/routers/contentstack/schemas/imageVault.ts @@ -105,9 +105,14 @@ export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform( const caption = rawData.Metadata?.find((meta) => meta.Name.includes("Title_") )?.Value + const mediaConversion = rawData.MediaConversions[0] + const aspectRatio = + mediaConversion.FormatAspectRatio || + mediaConversion.AspectRatio || + mediaConversion.Width / mediaConversion.Height return { - url: rawData.MediaConversions[0].Url, + url: mediaConversion.Url, id: rawData.Id, meta: { alt, @@ -115,9 +120,9 @@ export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform( }, title: rawData.Name, dimensions: { - width: rawData.MediaConversions[0].Width, - height: rawData.MediaConversions[0].Height, - aspectRatio: rawData.MediaConversions[0].FormatAspectRatio, + width: mediaConversion.Width, + height: mediaConversion.Height, + aspectRatio, }, } } diff --git a/utils/imageVault.ts b/utils/imageVault.ts index d0dc7d086..f04b7fdf2 100644 --- a/utils/imageVault.ts +++ b/utils/imageVault.ts @@ -14,8 +14,14 @@ export function insertResponseToImageVaultAsset( meta.Name.includes("Title_") )?.Value + const mediaConversion = response.MediaConversions[0] + const aspectRatio = + mediaConversion.FormatAspectRatio || + mediaConversion.AspectRatio || + mediaConversion.Width / mediaConversion.Height + return { - url: response.MediaConversions[0].Url, + url: mediaConversion.Url, id: response.Id, meta: { alt, @@ -23,9 +29,9 @@ export function insertResponseToImageVaultAsset( }, title: response.Name, dimensions: { - width: response.MediaConversions[0].Width, - height: response.MediaConversions[0].Height, - aspectRatio: response.MediaConversions[0].FormatAspectRatio, + width: mediaConversion.Width, + height: mediaConversion.Height, + aspectRatio, }, } } From ea1a175c410af841d4c7ded7af31394bc4399c54 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 27 Sep 2024 11:34:14 +0200 Subject: [PATCH 09/30] feat(SW-440) implemented changes to buttons according to figma --- .../HotelPage/Map/MapCard/index.tsx | 1 + .../TempDesignSystem/Button/button.module.css | 29 ++++++++--- components/TempDesignSystem/Button/index.tsx | 13 ++++- .../TempDesignSystem/Button/variants.ts | 3 ++ components/TempDesignSystem/Card/index.tsx | 7 ++- utils/cardTheme.ts | 52 ++++--------------- 6 files changed, 50 insertions(+), 55 deletions(-) diff --git a/components/ContentType/HotelPage/Map/MapCard/index.tsx b/components/ContentType/HotelPage/Map/MapCard/index.tsx index 79e9759be..c305ff445 100644 --- a/components/ContentType/HotelPage/Map/MapCard/index.tsx +++ b/components/ContentType/HotelPage/Map/MapCard/index.tsx @@ -55,6 +55,7 @@ export default function MapCard({ hotelName, pois }: MapCardProps) { theme="base" intent="secondary" size="small" + fullWidth className={styles.ctaButton} onClick={openDynamicMap} > diff --git a/components/TempDesignSystem/Button/button.module.css b/components/TempDesignSystem/Button/button.module.css index e44619deb..f6499c5c1 100644 --- a/components/TempDesignSystem/Button/button.module.css +++ b/components/TempDesignSystem/Button/button.module.css @@ -1,7 +1,6 @@ .btn { background: none; - /* No variable yet for radius 50px */ - border-radius: 50px; + border-radius: var(--Corner-radius-Rounded); cursor: pointer; margin: 0; padding: 0; @@ -10,12 +9,12 @@ background-color 300ms ease, color 300ms ease; - /* TODO: Waiting for variables for buttons from Design team */ font-family: var(--typography-Body-Bold-fontFamily); font-size: var(--typography-Body-Bold-fontSize); font-weight: 500; - line-height: 24px; + line-height: var(--typography-Body-Bold-lineHeight); letter-spacing: 0.6%; + text-decoration: none; } .wrapping { @@ -23,6 +22,10 @@ padding-right: 0 !important; } +.fullWidth { + width: 100%; +} + /* INTENT */ .primary, a.primary { @@ -70,20 +73,32 @@ a.default { /* SIZES */ .btn.small { + font-size: var(--typography-Caption-Bold-fontSize); + line-height: var(--typography-Caption-Bold-lineHeight); gap: var(--Spacing-x-quarter); - height: 40px; + padding: calc(var(--Spacing-x1) + 2px) var(--Spacing-x2); /* Special case padding to adjust the missing border */ +} + +.btn.small.secondary { padding: var(--Spacing-x1) var(--Spacing-x2); } .btn.medium { gap: var(--Spacing-x-half); - height: 48px; + padding: calc(var(--Spacing-x-one-and-half) + 2px) var(--Spacing-x2); /* Special case padding to adjust the missing border */ +} + +.medium.secondary { padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); } .btn.large { gap: var(--Spacing-x-half); - height: 56px; + padding: calc(var(--Spacing-x2) + 2px) var(--Spacing-x3); /* Special case padding to adjust the missing border */ +} + +.large.secondary { + gap: var(--Spacing-x-half); padding: var(--Spacing-x2) var(--Spacing-x3); } diff --git a/components/TempDesignSystem/Button/index.tsx b/components/TempDesignSystem/Button/index.tsx index 38261e61a..20bfbd292 100644 --- a/components/TempDesignSystem/Button/index.tsx +++ b/components/TempDesignSystem/Button/index.tsx @@ -8,14 +8,23 @@ import { buttonVariants } from "./variants" import type { ButtonProps } from "./button" export default function Button(props: ButtonProps) { - const { className, intent, size, theme, wrapping, variant, ...restProps } = - props + const { + className, + intent, + size, + theme, + fullWidth, + wrapping, + variant, + ...restProps + } = props const classNames = buttonVariants({ className, intent, size, theme, + fullWidth, wrapping, variant, }) diff --git a/components/TempDesignSystem/Button/variants.ts b/components/TempDesignSystem/Button/variants.ts index 301613544..77abd40e4 100644 --- a/components/TempDesignSystem/Button/variants.ts +++ b/components/TempDesignSystem/Button/variants.ts @@ -33,6 +33,9 @@ export const buttonVariants = cva(styles.btn, { wrapping: { true: styles.wrapping, }, + fullWidth: { + true: styles.fullWidth, + }, }, defaultVariants: { intent: "primary", diff --git a/components/TempDesignSystem/Card/index.tsx b/components/TempDesignSystem/Card/index.tsx index 8d29226bf..94cc08773 100644 --- a/components/TempDesignSystem/Card/index.tsx +++ b/components/TempDesignSystem/Card/index.tsx @@ -1,6 +1,7 @@ +import Link from "next/link" + import Image from "@/components/Image" import Button from "@/components/TempDesignSystem/Button" -import Link from "@/components/TempDesignSystem/Link" import BiroScript from "@/components/TempDesignSystem/Text/BiroScript" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" @@ -26,7 +27,7 @@ export default function Card({ onPrimaryButtonClick, onSecondaryButtonClick, }: CardProps) { - const { buttonTheme, primaryLinkColor, secondaryLinkColor } = getTheme(theme) + const buttonTheme = getTheme(theme) imageHeight = imageHeight || 320 imageWidth = @@ -85,7 +86,6 @@ export default function Card({ {primaryButton.title} @@ -103,7 +103,6 @@ export default function Card({ {secondaryButton.title} diff --git a/utils/cardTheme.ts b/utils/cardTheme.ts index f49b7c677..335d5ed3a 100644 --- a/utils/cardTheme.ts +++ b/utils/cardTheme.ts @@ -1,53 +1,21 @@ import type { ButtonProps } from "@/components/TempDesignSystem/Button/button" import type { CardProps } from "@/components/TempDesignSystem/Card/card" -import type { LinkProps } from "@/components/TempDesignSystem/Link/link" - -export function getTheme(theme: CardProps["theme"]) { - let buttonTheme: ButtonProps["theme"] = "primaryLight" - let primaryLinkColor: LinkProps["color"] = "pale" - let secondaryLinkColor: LinkProps["color"] = "burgundy" +export function getTheme(theme: CardProps["theme"]): ButtonProps["theme"] { switch (theme) { - case "one": - buttonTheme = "primaryLight" - primaryLinkColor = "pale" - secondaryLinkColor = "burgundy" - break case "two": - buttonTheme = "secondaryLight" - primaryLinkColor = "pale" - secondaryLinkColor = "burgundy" - break + return "secondaryLight" case "three": - buttonTheme = "tertiaryLight" - primaryLinkColor = "pale" - secondaryLinkColor = "burgundy" - break + return "tertiaryLight" case "primaryDark": - buttonTheme = "primaryDark" - primaryLinkColor = "burgundy" - secondaryLinkColor = "pale" - break - case "primaryDim": - buttonTheme = "primaryLight" - primaryLinkColor = "pale" - secondaryLinkColor = "burgundy" - break - case "primaryInverted": - buttonTheme = "primaryLight" - primaryLinkColor = "pale" - secondaryLinkColor = "burgundy" - break + return "primaryDark" case "primaryStrong": case "image": - buttonTheme = "primaryStrong" - primaryLinkColor = "red" - secondaryLinkColor = "white" - } - - return { - buttonTheme: buttonTheme, - primaryLinkColor: primaryLinkColor, - secondaryLinkColor: secondaryLinkColor, + return "primaryStrong" + case "one": + case "primaryDim": + case "primaryInverted": + default: + return "primaryLight" } } From 1c0025a55a6910ea269a955a3a93047d6b991f13 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Mon, 30 Sep 2024 11:26:52 +0200 Subject: [PATCH 10/30] fix: separate scopes in service token --- server/tokenManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/tokenManager.ts b/server/tokenManager.ts index e60bb02e5..4007896d6 100644 --- a/server/tokenManager.ts +++ b/server/tokenManager.ts @@ -18,7 +18,7 @@ export async function fetchServiceToken( grant_type: "client_credentials", client_id: env.CURITY_CLIENT_ID_SERVICE, client_secret: env.CURITY_CLIENT_SECRET_SERVICE, - scope: scopes.join(","), + scope: scopes.join(" "), }), next: { revalidate: SERVICE_TOKEN_REVALIDATE_SECONDS, From 7d53b0b26aeab8c954e99f28cf97f56f389cfc3c Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Mon, 30 Sep 2024 11:46:22 +0200 Subject: [PATCH 11/30] fix: "typo" in revalidation logic --- app/api/web/revalidate/loyaltyConfig/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/web/revalidate/loyaltyConfig/route.ts b/app/api/web/revalidate/loyaltyConfig/route.ts index 5e8fd51c7..131b583d1 100644 --- a/app/api/web/revalidate/loyaltyConfig/route.ts +++ b/app/api/web/revalidate/loyaltyConfig/route.ts @@ -11,7 +11,7 @@ import { generateLoyaltyConfigTag } from "@/utils/generateTag" enum LoyaltyConfigContentTypes { loyalty_level = "loyalty_level", - rewards = "rewards", + reward = "reward", } const validateJsonBody = z.object({ @@ -65,7 +65,7 @@ export async function POST(request: NextRequest) { entry.level_id ) } else if ( - content_type.uid === LoyaltyConfigContentTypes.rewards && + content_type.uid === LoyaltyConfigContentTypes.reward && entry.reward_id ) { tag = generateLoyaltyConfigTag( From 5faeaa3d34ada0993091e71173c3a119c8ac0373 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Mon, 30 Sep 2024 11:18:57 +0200 Subject: [PATCH 12/30] fix(SW-519): Corrected import of 'Link' inisde the MyPagesMenuWrapper --- components/Header/MainMenu/MyPagesMenuWrapper/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/Header/MainMenu/MyPagesMenuWrapper/index.tsx b/components/Header/MainMenu/MyPagesMenuWrapper/index.tsx index 5845e8b8e..2e0e30fae 100644 --- a/components/Header/MainMenu/MyPagesMenuWrapper/index.tsx +++ b/components/Header/MainMenu/MyPagesMenuWrapper/index.tsx @@ -1,8 +1,7 @@ -import { Link } from "react-feather" - import { myPages } from "@/constants/routes/myPages" import { serverClient } from "@/lib/trpc/server" +import Link from "@/components/TempDesignSystem/Link" import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" From 2e26506b3994c7d51225999b032c472ef729404e Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Tue, 1 Oct 2024 08:58:19 +0000 Subject: [PATCH 13/30] Merged in feat/SW-517-language-switcher-dismiss (pull request #626) Feat/SW-517 language switcher dismiss * feat(SW-517): Close language switcher when clicking outside * feat(SW-517): Close language switcher on selection * feat(SW-517): Fix mobile header Approved-by: Niclas Edenvin Approved-by: Linus Flood --- .../LanguageSwitcherContent/index.tsx | 2 ++ components/LanguageSwitcher/index.tsx | 36 +++++++++++++++++-- .../languageSwitcher/languageSwitcher.ts | 1 + 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/components/LanguageSwitcher/LanguageSwitcherContent/index.tsx b/components/LanguageSwitcher/LanguageSwitcherContent/index.tsx index 3533ecb93..19cc69961 100644 --- a/components/LanguageSwitcher/LanguageSwitcherContent/index.tsx +++ b/components/LanguageSwitcher/LanguageSwitcherContent/index.tsx @@ -16,6 +16,7 @@ import type { LanguageSwitcherContentProps } from "@/types/components/languageSw export default function LanguageSwitcherContent({ urls, + onLanguageSwitch, }: LanguageSwitcherContentProps) { const intl = useIntl() const currentLanguage = useLang() @@ -39,6 +40,7 @@ export default function LanguageSwitcherContent({ {languages[key]} {isActive ? : null} diff --git a/components/LanguageSwitcher/index.tsx b/components/LanguageSwitcher/index.tsx index 3d0543051..b5cb2f25e 100644 --- a/components/LanguageSwitcher/index.tsx +++ b/components/LanguageSwitcher/index.tsx @@ -1,5 +1,6 @@ "use client" +import { useEffect, useRef } from "react" import { useIntl } from "react-intl" import { languages } from "@/constants/languages" @@ -28,6 +29,7 @@ export default function LanguageSwitcher({ const intl = useIntl() const currentLanguage = useLang() const toggleDropdown = useDropdownStore((state) => state.toggleDropdown) + const languageSwitcherRef = useRef(null) const isFooterLanguageSwitcherOpen = useDropdownStore( (state) => state.isFooterLanguageSwitcherOpen ) @@ -70,10 +72,37 @@ export default function LanguageSwitcher({ }) } + useEffect(() => { + function handleClickOutside(evt: Event) { + const target = evt.target as HTMLElement + 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 }) return ( -
+
diff --git a/types/components/languageSwitcher/languageSwitcher.ts b/types/components/languageSwitcher/languageSwitcher.ts index 312d28712..5201728cb 100644 --- a/types/components/languageSwitcher/languageSwitcher.ts +++ b/types/components/languageSwitcher/languageSwitcher.ts @@ -17,6 +17,7 @@ export interface LanguageSwitcherProps { export interface LanguageSwitcherContentProps { urls: LanguageSwitcherData + onLanguageSwitch: () => void } export interface LanguageSwitcherContainerProps { From 38452b049f3cc5fdd46e9b948b104364d7d1b256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20Landstr=C3=B6m?= Date: Tue, 1 Oct 2024 11:10:00 +0200 Subject: [PATCH 14/30] fix: Finnish typo --- i18n/dictionaries/fi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 673d108b5..117fcad58 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -185,7 +185,7 @@ "Rooms": "Huoneet", "Rooms & Guests": "Huoneet & Vieraat", "Rooms & Guestss": "Huoneet & Vieraat", - "Save": "Tallentaa", + "Save": "Tallenna", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", "See all photos": "Katso kaikki kuvat", @@ -281,4 +281,4 @@ "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", "to": "to", "uppercase letter": "iso kirjain" -} \ No newline at end of file +} From d1b54093f2d374ca677cce0cee43d50418a2ef06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20Landstr=C3=B6m?= Date: Tue, 1 Oct 2024 11:19:53 +0200 Subject: [PATCH 15/30] fix: add missing formatMessage --- .../DynamicContent/Overview/Stats/Points/PointsColumn/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Blocks/DynamicContent/Overview/Stats/Points/PointsColumn/index.tsx b/components/Blocks/DynamicContent/Overview/Stats/Points/PointsColumn/index.tsx index 29678b471..0d67a14e3 100644 --- a/components/Blocks/DynamicContent/Overview/Stats/Points/PointsColumn/index.tsx +++ b/components/Blocks/DynamicContent/Overview/Stats/Points/PointsColumn/index.tsx @@ -63,7 +63,7 @@ async function PointsColumn({ {subtitle ? ( - {subtitle} + {formatMessage({ id: subtitle })} ) : null} From a3bb1ed8a4ad1ba842f6be10958e4067e2740ca9 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Fri, 27 Sep 2024 10:02:46 +0200 Subject: [PATCH 16/30] fix: make my pages dynamic to opt out of full router cache --- app/[lang]/(live)/(protected)/(.)logout/page.tsx | 14 -------------- app/[lang]/(live)/(protected)/layout.tsx | 2 ++ 2 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 app/[lang]/(live)/(protected)/(.)logout/page.tsx diff --git a/app/[lang]/(live)/(protected)/(.)logout/page.tsx b/app/[lang]/(live)/(protected)/(.)logout/page.tsx deleted file mode 100644 index c4884586f..000000000 --- a/app/[lang]/(live)/(protected)/(.)logout/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client" - -import { useEffect } from "react" - -import LoadingSpinner from "@/components/LoadingSpinner" - -export default function LogoutInterceptedRoute() { - // Reload the browser on logout in order to flush router cache. This is to make sure we don't show stale user specific data. - useEffect(() => { - window.location.reload() - }, []) - - return -} diff --git a/app/[lang]/(live)/(protected)/layout.tsx b/app/[lang]/(live)/(protected)/layout.tsx index c6e4a0a51..c9ffe2046 100644 --- a/app/[lang]/(live)/(protected)/layout.tsx +++ b/app/[lang]/(live)/(protected)/layout.tsx @@ -7,6 +7,8 @@ import { getProfile } from "@/lib/trpc/memoizedRequests" import { auth } from "@/auth" import { getLang } from "@/i18n/serverContext" +export const revalidate = 0 + export default async function ProtectedLayout({ children, }: React.PropsWithChildren) { From dd4d6b46b1778eb61154f3275cad6bf0feb49c67 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Fri, 27 Sep 2024 13:46:55 +0200 Subject: [PATCH 17/30] fix: remove dynamic rendering --- app/[lang]/(live)/(protected)/layout.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/[lang]/(live)/(protected)/layout.tsx b/app/[lang]/(live)/(protected)/layout.tsx index c9ffe2046..c6e4a0a51 100644 --- a/app/[lang]/(live)/(protected)/layout.tsx +++ b/app/[lang]/(live)/(protected)/layout.tsx @@ -7,8 +7,6 @@ import { getProfile } from "@/lib/trpc/memoizedRequests" import { auth } from "@/auth" import { getLang } from "@/i18n/serverContext" -export const revalidate = 0 - export default async function ProtectedLayout({ children, }: React.PropsWithChildren) { From 724f4296965a7da2ad93057ce6317ed2e0779f6e Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Wed, 11 Sep 2024 12:27:54 +0200 Subject: [PATCH 18/30] feat(SW-341): add hotels listing sidepeeks --- .../HotelCard/hotelCard.module.css | 8 +- .../HotelReservation/HotelCard/index.tsx | 22 ++-- .../HotelCardListing/index.tsx | 2 - .../ReadMore/Contact/contact.module.css | 48 +++++++ .../ReadMore/Contact/index.tsx | 85 +++++++++++++ .../HotelReservation/ReadMore/index.tsx | 119 ++++++++++++++++++ .../ReadMore/readMore.module.css | 25 ++++ components/TempDesignSystem/Link/index.tsx | 5 + .../TempDesignSystem/SidePeek/index.tsx | 84 +++++++------ .../SidePeek/sidePeek.module.css | 5 +- server/routers/hotels/output.ts | 2 +- .../selectHotel/selectHotel.ts | 21 ++++ types/hotel.ts | 3 + 13 files changed, 365 insertions(+), 64 deletions(-) create mode 100644 components/HotelReservation/ReadMore/Contact/contact.module.css create mode 100644 components/HotelReservation/ReadMore/Contact/index.tsx create mode 100644 components/HotelReservation/ReadMore/index.tsx create mode 100644 components/HotelReservation/ReadMore/readMore.module.css diff --git a/components/HotelReservation/HotelCard/hotelCard.module.css b/components/HotelReservation/HotelCard/hotelCard.module.css index 3db576744..a6c4a892d 100644 --- a/components/HotelReservation/HotelCard/hotelCard.module.css +++ b/components/HotelReservation/HotelCard/hotelCard.module.css @@ -48,12 +48,6 @@ gap: var(--Spacing-x-half); } -.link { - display: flex; - padding: var(--Spacing-x2) var(--Spacing-x0); - border-bottom: 1px solid var(--Base-Border-Subtle); -} - .prices { display: flex; flex-direction: column; @@ -115,7 +109,7 @@ padding-bottom: var(--Spacing-x2); } - .link { + .detailsButton { border-bottom: none; } diff --git a/components/HotelReservation/HotelCard/index.tsx b/components/HotelReservation/HotelCard/index.tsx index 76d702b98..82b0c4b91 100644 --- a/components/HotelReservation/HotelCard/index.tsx +++ b/components/HotelReservation/HotelCard/index.tsx @@ -1,11 +1,5 @@ -import { useIntl } from "react-intl" - import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" -import { - ChevronRightIcon, - PriceTagIcon, - ScandicLogoIcon, -} from "@/components/Icons" +import { PriceTagIcon, ScandicLogoIcon } from "@/components/Icons" import TripAdvisorIcon from "@/components/Icons/TripAdvisor" import Image from "@/components/Image" import Button from "@/components/TempDesignSystem/Button" @@ -14,13 +8,16 @@ import Link from "@/components/TempDesignSystem/Link" import Caption from "@/components/TempDesignSystem/Text/Caption" import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Title from "@/components/TempDesignSystem/Text/Title" +import { getIntl } from "@/i18n" + +import ReadMore from "../ReadMore" import styles from "./hotelCard.module.css" import { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps" -export default function HotelCard({ hotel }: HotelCardProps) { - const intl = useIntl() +export default async function HotelCard({ hotel }: HotelCardProps) { + const intl = await getIntl() const { hotelData } = hotel const { price } = hotel @@ -51,7 +48,7 @@ export default function HotelCard({ hotel }: HotelCardProps) { {hotelData.name} - + {`${hotelData.address.streetAddress}, ${hotelData.address.city}`} @@ -70,10 +67,7 @@ export default function HotelCard({ hotel }: HotelCardProps) { ) })}
- - {intl.formatMessage({ id: "See hotel details" })} - - +
diff --git a/components/HotelReservation/HotelCardListing/index.tsx b/components/HotelReservation/HotelCardListing/index.tsx index bba0ce99d..fb17cb2a5 100644 --- a/components/HotelReservation/HotelCardListing/index.tsx +++ b/components/HotelReservation/HotelCardListing/index.tsx @@ -1,5 +1,3 @@ -"use client" - import Title from "@/components/TempDesignSystem/Text/Title" import HotelCard from "../HotelCard" diff --git a/components/HotelReservation/ReadMore/Contact/contact.module.css b/components/HotelReservation/ReadMore/Contact/contact.module.css new file mode 100644 index 000000000..53f6e01f3 --- /dev/null +++ b/components/HotelReservation/ReadMore/Contact/contact.module.css @@ -0,0 +1,48 @@ +.wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto; + gap: var(--Spacing-x2); + font-family: var(--typography-Body-Regular-fontFamily); +} + +.address, +.contactInfo { + display: grid; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + grid-column: 1 / 3; + grid-row: 1 / 4; +} + +.contactInfo > li { + font-style: normal; + list-style-type: none; + display: flex; + flex-direction: column; +} + +.heading { + font-weight: 500; +} + +.soMeIcons { + display: flex; + gap: var(--Spacing-x-one-and-half); +} + +.ecoLabel { + display: grid; + grid-template-columns: auto 1fr; + column-gap: var(--Spacing-x-one-and-half); + grid-column: 2 / 3; + grid-row: 3 / 4; + font-size: var(--typography-Footnote-Regular-fontSize); + line-height: (); +} + +.ecoLabelText { + display: flex; + flex-direction: column; + justify-content: center; +} diff --git a/components/HotelReservation/ReadMore/Contact/index.tsx b/components/HotelReservation/ReadMore/Contact/index.tsx new file mode 100644 index 000000000..46491fb3d --- /dev/null +++ b/components/HotelReservation/ReadMore/Contact/index.tsx @@ -0,0 +1,85 @@ +"use client" + +import { useIntl } from "react-intl" + +import FacebookIcon from "@/components/Icons/Facebook" +import InstagramIcon from "@/components/Icons/Instagram" +import Image from "@/components/Image" +import Link from "@/components/TempDesignSystem/Link" +import useLang from "@/hooks/useLang" + +import styles from "./contact.module.css" + +import { ContactProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" + +export default function Contact({ hotel }: ContactProps) { + const lang = useLang() + const intl = useIntl() + + return ( +
+
+
    +
  • + + {intl.formatMessage({ id: "Address" })} + + {hotel.address.streetAddress} + {hotel.address.city} +
  • +
  • + + {intl.formatMessage({ id: "Driving directions" })} + + {intl.formatMessage({ id: "Google Maps" })} +
  • +
  • + + {intl.formatMessage({ id: "Email" })} + + + {hotel.contactInformation.email} + +
  • +
  • + + {intl.formatMessage({ id: "Contact us" })} + + + {hotel.contactInformation.phoneNumber} + +
  • +
  • + + {intl.formatMessage({ id: "Follow us" })} + +
    + + + + + + +
    +
  • +
+
+ {hotel.hotelFacts.ecoLabels.nordicEcoLabel ? ( +
+ {intl.formatMessage({ +
+ {intl.formatMessage({ id: "Nordic Swan Ecolabel" })} + + {hotel.hotelFacts.ecoLabels.svanenEcoLabelCertificateNumber} + +
+
+ ) : null} +
+ ) +} diff --git a/components/HotelReservation/ReadMore/index.tsx b/components/HotelReservation/ReadMore/index.tsx new file mode 100644 index 000000000..e988ec40d --- /dev/null +++ b/components/HotelReservation/ReadMore/index.tsx @@ -0,0 +1,119 @@ +"use client" + +import { useState } from "react" +import { useIntl } from "react-intl" + +import { ChevronRightIcon } from "@/components/Icons" +import Accordion from "@/components/TempDesignSystem/Accordion" +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Button from "@/components/TempDesignSystem/Button" +import SidePeek from "@/components/TempDesignSystem/SidePeek" +import Body from "@/components/TempDesignSystem/Text/Body" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" + +import Contact from "./Contact" + +import styles from "./readMore.module.css" + +import { + DetailedAmenity, + ParkingProps, + ReadMoreProps, +} from "@/types/components/hotelReservation/selectHotel/selectHotel" +import { Hotel } from "@/types/hotel" + +function getAmenitiesList(hotel: Hotel) { + const detailedAmenities: DetailedAmenity[] = Object.entries( + hotel.hotelFacts.hotelFacilityDetail + ).map(([key, value]) => ({ name: key, ...value })) + + // Remove Parking facilities since parking accordion is based on hotel.parking + const simpleAmenities = hotel.detailedFacilities.filter( + (facility) => !facility.name.startsWith("Parking") + ) + return [...detailedAmenities, ...simpleAmenities] +} + +export default function ReadMore({ hotel, hotelId }: ReadMoreProps) { + const intl = useIntl() + + const [sidePeekOpen, setSidePeekOpen] = useState(false) + + const amenitiesList = getAmenitiesList(hotel) + return ( + <> + + { + setSidePeekOpen(false) + }} + > +
+ + {intl.formatMessage({ id: "Practical information" })} + + + + {/* parking */} + {hotel.parking.length ? ( + + {hotel.parking.map((p) => ( + + ))} + + ) : null} + + TODO: What content should be in the accessibility section? + + {amenitiesList.map((amenity) => { + return "description" in amenity ? ( + + {amenity.description} + + ) : ( +
+ {amenity.name} +
+ ) + })} +
+ {/* TODO: handle linking to Hotel Page */} + +
+
+ + ) +} + +function Parking({ parking }: ParkingProps) { + const intl = useIntl() + return ( +
+ {`${intl.formatMessage({ id: parking.type })} (${parking.name})`} +
    +
  • + {`${intl.formatMessage({ + id: "Number of charging points for electric cars", + })}: ${parking.numberOfChargingSpaces}`} +
  • +
  • {`${intl.formatMessage({ id: "Parking can be reserved in advance" })}: ${parking.canMakeReservation ? intl.formatMessage({ id: "Yes" }) : intl.formatMessage({ id: "No" })}`}
  • +
  • {`${intl.formatMessage({ id: "Number of parking spots" })}: ${parking.numberOfParkingSpots}`}
  • +
  • {`${intl.formatMessage({ id: "Distance to hotel" })}: ${parking.distanceToHotel}`}
  • +
  • {`${intl.formatMessage({ id: "Address" })}: ${parking.address}`}
  • +
+
+ ) +} diff --git a/components/HotelReservation/ReadMore/readMore.module.css b/components/HotelReservation/ReadMore/readMore.module.css new file mode 100644 index 000000000..f7859a8fc --- /dev/null +++ b/components/HotelReservation/ReadMore/readMore.module.css @@ -0,0 +1,25 @@ +.detailsButton { + align-self: start; + border-radius: 0; + height: auto; + padding-left: 0; + padding-right: 0; +} + +.content { + display: grid; + gap: var(--Spacing-x2); +} + +.amenity { + font-family: var(--typography-Body-Regular-fontFamily); + border-bottom: 1px solid var(--Base-Border-Subtle); + /* padding set to align with AccordionItem which has a different composition */ + padding: var(--Spacing-x2) + calc(var(--Spacing-x1) + var(--Spacing-x-one-and-half)); +} + +.list { + font-family: var(--typography-Body-Regular-fontFamily); + list-style: inside; +} diff --git a/components/TempDesignSystem/Link/index.tsx b/components/TempDesignSystem/Link/index.tsx index 2301ce932..771c60e82 100644 --- a/components/TempDesignSystem/Link/index.tsx +++ b/components/TempDesignSystem/Link/index.tsx @@ -82,6 +82,11 @@ export default function Link({ // track navigation nor start a router transition. return } + if (href.startsWith("tel:") || href.startsWith("mailto:")) { + // If href contains tel or mailto protocols we don't want to + // track navigation nor start a router transition. + return + } e.preventDefault() trackPageViewStart() startTransition(() => { diff --git a/components/TempDesignSystem/SidePeek/index.tsx b/components/TempDesignSystem/SidePeek/index.tsx index df0b8dc44..6df535b54 100644 --- a/components/TempDesignSystem/SidePeek/index.tsx +++ b/components/TempDesignSystem/SidePeek/index.tsx @@ -1,7 +1,7 @@ "use client" import { useIsSSR } from "@react-aria/ssr" -import { useContext } from "react" +import { useContext, useState } from "react" import { Dialog, DialogTrigger, @@ -29,6 +29,15 @@ function SidePeek({ }: React.PropsWithChildren) { const isSSR = useIsSSR() const intl = useIntl() + + const [rootDiv, setRootDiv] = useState(undefined) + + function setRef(node: HTMLDivElement | null) { + if (node) { + setRootDiv(node) + } + } + const context = useContext(SidePeekContext) function onClose() { const closeHandler = handleClose || context?.handleClose @@ -44,42 +53,45 @@ function SidePeek({ ) } return ( - - - - - - - - - + + + +
{children}
+ + + + + +
) } diff --git a/components/TempDesignSystem/SidePeek/sidePeek.module.css b/components/TempDesignSystem/SidePeek/sidePeek.module.css index 2d6de4f00..65ad886b1 100644 --- a/components/TempDesignSystem/SidePeek/sidePeek.module.css +++ b/components/TempDesignSystem/SidePeek/sidePeek.module.css @@ -78,6 +78,7 @@ .sidePeekContent { padding: var(--Spacing-x4); + overflow-y: auto; } @media screen and (min-width: 1367px) { .modal { @@ -94,8 +95,4 @@ .modal[data-exiting] { animation: slide-in 250ms reverse; } - - .overlay { - top: 0; - } } diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 7f57ba6cd..d0d95903a 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -298,7 +298,7 @@ const parkingPricingSchema = z.object({ .optional(), }) -const parkingSchema = z.object({ +export const parkingSchema = z.object({ type: z.string(), name: z.string(), address: z.string().optional(), diff --git a/types/components/hotelReservation/selectHotel/selectHotel.ts b/types/components/hotelReservation/selectHotel/selectHotel.ts index d4c40ac56..6d1cecfca 100644 --- a/types/components/hotelReservation/selectHotel/selectHotel.ts +++ b/types/components/hotelReservation/selectHotel/selectHotel.ts @@ -1,4 +1,25 @@ +import { Hotel, ParkingData } from "@/types/hotel" + export enum AvailabilityEnum { Available = "Available", NotAvailable = "NotAvailable", } + +export interface DetailedAmenity { + name: string + heading: string + description: string +} + +export interface ReadMoreProps { + hotelId: string + hotel: Hotel +} + +export interface ContactProps { + hotel: Hotel +} + +export interface ParkingProps { + parking: ParkingData +} diff --git a/types/hotel.ts b/types/hotel.ts index 54d9c8125..f4436fb6c 100644 --- a/types/hotel.ts +++ b/types/hotel.ts @@ -2,6 +2,7 @@ import { z } from "zod" import { getHotelDataSchema, + parkingSchema, pointOfInterestSchema, roomSchema, } from "@/server/routers/hotels/output" @@ -49,3 +50,5 @@ export enum PointOfInterestGroupEnum { PARKING = "Parking", SHOPPING_DINING = "Shopping & Dining", } + +export type ParkingData = z.infer From d40aa455de3b28fbf7616167647e46786fb2ad70 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Fri, 27 Sep 2024 08:49:27 +0200 Subject: [PATCH 19/30] feat: content page UI updates --- .../ContentPage/contentPage.module.css | 36 ++++++++++++++++--- components/ContentType/ContentPage/index.tsx | 29 ++++++++------- components/JsonToHtml/jsontohtml.module.css | 25 +++++++++---- components/JsonToHtml/renderOptions.tsx | 11 +++++- components/Sidebar/sidebar.module.css | 2 ++ 5 files changed, 79 insertions(+), 24 deletions(-) diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css index 9ba97d7f9..db1de0540 100644 --- a/components/ContentType/ContentPage/contentPage.module.css +++ b/components/ContentType/ContentPage/contentPage.module.css @@ -17,21 +17,37 @@ max-width: var(--max-width-content); margin: 0 auto; } + .headerIntro { display: grid; max-width: var(--max-width-text-block); gap: var(--Spacing-x3); } -.content { - padding: var(--Spacing-x4) var(--Spacing-x2); - display: grid; - justify-items: center; +.heroContainer { + width: 100%; + padding-bottom: var(--Spacing-x4); + padding-top: var(--Spacing-x4); +} + +.heroContainer img { + max-width: var(--max-width-content); + margin: 0 auto; + display: block; +} + +.contentContainer { + display: flex; + flex-direction: column; + padding-top: var(--Spacing-x4); +} + +.mainContent { + width: 100%; } .innerContent { width: 100%; - max-width: var(--max-width-content); } @media (min-width: 768px) { @@ -39,3 +55,13 @@ gap: var(--Spacing-x3); } } + +@media (min-width: 1367px) { + .contentContainer { + display: grid; + grid-template-columns: var(--max-width-text-block) 1fr; + gap: var(--Spacing-x9); + max-width: var(--max-width-content); + margin: 0 auto; + } +} diff --git a/components/ContentType/ContentPage/index.tsx b/components/ContentType/ContentPage/index.tsx index 3e7c59abb..5b9807d2e 100644 --- a/components/ContentType/ContentPage/index.tsx +++ b/components/ContentType/ContentPage/index.tsx @@ -23,8 +23,6 @@ export default async function ContentPage() { return ( <>
- {sidebar?.length ? : null} -
@@ -37,17 +35,24 @@ export default async function ContentPage() {
-
-
- {hero_image ? ( - - ) : null} - {blocks ? : null} + {hero_image && ( +
+
-
+ )} + +
+
+
+ {blocks ? : null} +
+
+ + {sidebar?.length ? : null} +
diff --git a/components/JsonToHtml/jsontohtml.module.css b/components/JsonToHtml/jsontohtml.module.css index 94e27d4b8..d55923c69 100644 --- a/components/JsonToHtml/jsontohtml.module.css +++ b/components/JsonToHtml/jsontohtml.module.css @@ -18,16 +18,12 @@ color: var(--Primary-Light-On-Surface-Accent); } -.li:has(.heart), -.li:has(.check) { - list-style: none; -} - .li { margin-left: var(--Spacing-x3); } -.li:has(.heart):before { +.heart > .li::before, +.li:has(.heart)::before { content: url("/_static/icons/heart.svg"); position: relative; height: 8px; @@ -36,6 +32,14 @@ margin-left: calc(var(--Spacing-x3) * -1); } +.heart > .li, +.check > .li, +.li:has(.check), +.li:has(.heart) { + list-style: none; +} + +.check > .li::before, .li:has(.check)::before { content: url("/_static/icons/check-ring.svg"); position: relative; @@ -59,6 +63,7 @@ max-width: 100%; overflow-x: auto; } + @media screen and (min-width: 768px) { .ol:has(li:nth-last-child(n + 5)), .ul:has(li:nth-last-child(n + 5)) { @@ -66,3 +71,11 @@ grid-auto-flow: column; } } + +@container sidebar (max-width: 390px) { + .ol, + .ul { + display: flex; + flex-direction: column; + } +} diff --git a/components/JsonToHtml/renderOptions.tsx b/components/JsonToHtml/renderOptions.tsx index 77f1b41f5..7f8420bf6 100644 --- a/components/JsonToHtml/renderOptions.tsx +++ b/components/JsonToHtml/renderOptions.tsx @@ -526,6 +526,15 @@ export const renderOptions: RenderOptions = { ) => { const props = extractPossibleAttributes(node.attrs) + if (props.className) { + if (hasAvailableULFormat(props.className)) { + // @ts-ignore: We want to set css modules classNames even if it does not correspond + // to an existing class in the module style sheet. Due to our css modules plugin for + // typescript, we cannot do this without the ts-ignore + props.className = styles[props.className] + } + } + // Set the number of rows dynamically to create even rows for each column. We want the li:s // to flow with the column, so therefore this is needed. let numberOfRows: number | undefined @@ -538,7 +547,7 @@ export const renderOptions: RenderOptions = {
    Date: Fri, 27 Sep 2024 08:53:06 +0200 Subject: [PATCH 20/30] fix: update value of container sidebar --- components/JsonToHtml/jsontohtml.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/JsonToHtml/jsontohtml.module.css b/components/JsonToHtml/jsontohtml.module.css index d55923c69..a85bf0e38 100644 --- a/components/JsonToHtml/jsontohtml.module.css +++ b/components/JsonToHtml/jsontohtml.module.css @@ -72,7 +72,7 @@ } } -@container sidebar (max-width: 390px) { +@container sidebar (max-width: 360px) { .ol, .ul { display: flex; From ff34ae3c0ffc45c098bfa35aba14b3e404bdc7ff Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Fri, 27 Sep 2024 10:51:07 +0200 Subject: [PATCH 21/30] fix: proper spacing on mobile --- .../ContentType/ContentPage/contentPage.module.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css index db1de0540..4535ecf56 100644 --- a/components/ContentType/ContentPage/contentPage.module.css +++ b/components/ContentType/ContentPage/contentPage.module.css @@ -26,8 +26,7 @@ .heroContainer { width: 100%; - padding-bottom: var(--Spacing-x4); - padding-top: var(--Spacing-x4); + padding: var(--Spacing-x4) var(--Spacing-x2); } .heroContainer img { @@ -39,7 +38,7 @@ .contentContainer { display: flex; flex-direction: column; - padding-top: var(--Spacing-x4); + padding: var(--Spacing-x4) var(--Spacing-x2) 0; } .mainContent { @@ -57,11 +56,15 @@ } @media (min-width: 1367px) { + .heroContainer { + padding: var(--Spacing-x4) 0; + } .contentContainer { display: grid; grid-template-columns: var(--max-width-text-block) 1fr; gap: var(--Spacing-x9); max-width: var(--max-width-content); margin: 0 auto; + padding: var(--Spacing-x4) 0 0; } } From 621a37cd5d4992b8ab565926fcf7930827b402f1 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Fri, 27 Sep 2024 11:50:58 +0200 Subject: [PATCH 22/30] fix: show sidebar in mobile viewports for content pages --- components/ContentType/ContentPage/contentPage.module.css | 2 ++ components/Sidebar/sidebar.module.css | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css index 4535ecf56..feeb3fb4c 100644 --- a/components/ContentType/ContentPage/contentPage.module.css +++ b/components/ContentType/ContentPage/contentPage.module.css @@ -4,6 +4,8 @@ .contentPage { padding-bottom: var(--Spacing-x9); + container-name: content-page; + container-type: inline-size; } .header { diff --git a/components/Sidebar/sidebar.module.css b/components/Sidebar/sidebar.module.css index b4ca2a6d5..695e35b46 100644 --- a/components/Sidebar/sidebar.module.css +++ b/components/Sidebar/sidebar.module.css @@ -8,10 +8,16 @@ padding: var(--Spacing-x0) var(--Spacing-x2); } -@media screen and (min-width: 1366px) { +@media screen and (min-width: 1367px) { .aside { align-content: flex-start; display: grid; gap: var(--Spacing-x4); } } + +@container content-page (max-width: 1366px) { + .aside { + display: grid; + } +} From 82654cd8e6aead258aac87c924467178cfd6cb87 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Fri, 27 Sep 2024 11:54:49 +0200 Subject: [PATCH 23/30] fix: gap for main content on mobile --- components/ContentType/ContentPage/contentPage.module.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css index feeb3fb4c..357376ad9 100644 --- a/components/ContentType/ContentPage/contentPage.module.css +++ b/components/ContentType/ContentPage/contentPage.module.css @@ -1,7 +1,3 @@ -/*nav:has(+ .contentPage) {*/ -/* background-color: var(--Base-Surface-Subtle-Normal);*/ -/*}*/ - .contentPage { padding-bottom: var(--Spacing-x9); container-name: content-page; @@ -41,6 +37,7 @@ display: flex; flex-direction: column; padding: var(--Spacing-x4) var(--Spacing-x2) 0; + gap: var(--Spacing-x4); } .mainContent { From 4cb57158f6bc790c4c1a637ae04063a7faafe439 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Tue, 1 Oct 2024 09:43:06 +0200 Subject: [PATCH 24/30] chore: cleaner prop classname value handling --- components/JsonToHtml/renderOptions.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/JsonToHtml/renderOptions.tsx b/components/JsonToHtml/renderOptions.tsx index 7f8420bf6..5b4eda0d7 100644 --- a/components/JsonToHtml/renderOptions.tsx +++ b/components/JsonToHtml/renderOptions.tsx @@ -526,6 +526,8 @@ export const renderOptions: RenderOptions = { ) => { const props = extractPossibleAttributes(node.attrs) + props.className = props.className || "" + if (props.className) { if (hasAvailableULFormat(props.className)) { // @ts-ignore: We want to set css modules classNames even if it does not correspond @@ -547,7 +549,7 @@ export const renderOptions: RenderOptions = {
      Date: Tue, 1 Oct 2024 12:02:02 +0200 Subject: [PATCH 25/30] fix: remove unecessary html --- .../ContentPage/contentPage.module.css | 8 +------ components/ContentType/ContentPage/index.tsx | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css index 357376ad9..026aa6065 100644 --- a/components/ContentType/ContentPage/contentPage.module.css +++ b/components/ContentType/ContentPage/contentPage.module.css @@ -34,8 +34,7 @@ } .contentContainer { - display: flex; - flex-direction: column; + display: grid; padding: var(--Spacing-x4) var(--Spacing-x2) 0; gap: var(--Spacing-x4); } @@ -44,10 +43,6 @@ width: 100%; } -.innerContent { - width: 100%; -} - @media (min-width: 768px) { .headerIntro { gap: var(--Spacing-x3); @@ -59,7 +54,6 @@ padding: var(--Spacing-x4) 0; } .contentContainer { - display: grid; grid-template-columns: var(--max-width-text-block) 1fr; gap: var(--Spacing-x9); max-width: var(--max-width-content); diff --git a/components/ContentType/ContentPage/index.tsx b/components/ContentType/ContentPage/index.tsx index 5b9807d2e..5c5c63893 100644 --- a/components/ContentType/ContentPage/index.tsx +++ b/components/ContentType/ContentPage/index.tsx @@ -25,30 +25,32 @@ export default async function ContentPage() {
      -
      - {header.heading} - {header.preamble} -
      - {header.navigation_links ? ( - + {header ? ( + <> +
      + {header.heading} + {header.preamble} +
      + {header.navigation_links ? ( + + ) : null} + ) : null}
      - {hero_image && ( + {hero_image ? (
      - )} + ) : null}
      -
      - {blocks ? : null} -
      + {blocks ? : null}
      {sidebar?.length ? : null} From 3a124f09a1f64c0c72ed861ea452cc3c443aaa51 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Tue, 1 Oct 2024 12:23:58 +0200 Subject: [PATCH 26/30] fix: better defaults for sidebar mobile --- components/ContentType/LoyaltyPage/loyaltyPage.module.css | 2 ++ components/Sidebar/sidebar.module.css | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/ContentType/LoyaltyPage/loyaltyPage.module.css b/components/ContentType/LoyaltyPage/loyaltyPage.module.css index 45470127c..8ceb15da3 100644 --- a/components/ContentType/LoyaltyPage/loyaltyPage.module.css +++ b/components/ContentType/LoyaltyPage/loyaltyPage.module.css @@ -6,6 +6,8 @@ position: relative; justify-content: center; align-items: flex-start; + container-name: loyalty-page; + container-type: inline-size; } .blocks { diff --git a/components/Sidebar/sidebar.module.css b/components/Sidebar/sidebar.module.css index 695e35b46..5ae1ce5d4 100644 --- a/components/Sidebar/sidebar.module.css +++ b/components/Sidebar/sidebar.module.css @@ -1,5 +1,5 @@ .aside { - display: none; + display: grid; container-name: sidebar; container-type: inline-size; } @@ -16,8 +16,8 @@ } } -@container content-page (max-width: 1366px) { +@container loyalty-page (max-width: 1366px) { .aside { - display: grid; + display: none; } } From 73eddcf4b723a2a60424e1373503b15b72595077 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Tue, 1 Oct 2024 13:07:59 +0200 Subject: [PATCH 27/30] refactor: reusable css modula compatible logic --- components/JsonToHtml/renderOptions.tsx | 33 ++++++++++++++----------- components/JsonToHtml/utils.tsx | 18 ++++++++++++++ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/components/JsonToHtml/renderOptions.tsx b/components/JsonToHtml/renderOptions.tsx index 5b4eda0d7..ce7a325b5 100644 --- a/components/JsonToHtml/renderOptions.tsx +++ b/components/JsonToHtml/renderOptions.tsx @@ -12,7 +12,11 @@ import Caption from "../TempDesignSystem/Text/Caption" import Footnote from "../TempDesignSystem/Text/Footnote" import Subtitle from "../TempDesignSystem/Text/Subtitle" import Title from "../TempDesignSystem/Text/Title" -import { hasAvailableParagraphFormat, hasAvailableULFormat } from "./utils" +import { + hasAvailableParagraphFormat, + hasAvailableULFormat, + makeCssModuleCompatibleClassName, +} from "./utils" import styles from "./jsontohtml.module.css" @@ -217,8 +221,16 @@ export const renderOptions: RenderOptions = { fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) + const compatibleClassName = makeCssModuleCompatibleClassName( + props.className, + "ul" + ) return ( -
    • +
    • {next(node.children, embeds, fullRenderOptions)}
    • ) @@ -525,17 +537,10 @@ export const renderOptions: RenderOptions = { fullRenderOptions: RenderOptions ) => { const props = extractPossibleAttributes(node.attrs) - - props.className = props.className || "" - - if (props.className) { - if (hasAvailableULFormat(props.className)) { - // @ts-ignore: We want to set css modules classNames even if it does not correspond - // to an existing class in the module style sheet. Due to our css modules plugin for - // typescript, we cannot do this without the ts-ignore - props.className = styles[props.className] - } - } + const compatibleClassName = makeCssModuleCompatibleClassName( + props.className, + "ul" + ) // Set the number of rows dynamically to create even rows for each column. We want the li:s // to flow with the column, so therefore this is needed. @@ -549,7 +554,7 @@ export const renderOptions: RenderOptions = {
        nodeToHtml(node, embeds, fullRenderOptions)) } + +export function makeCssModuleCompatibleClassName( + className: string | undefined, + formatType: "ul" +): string { + if (!className) return "" + + if (formatType === "ul" && hasAvailableULFormat(className)) { + // @ts-ignore: We want to set css modules classNames even if it does not correspond + // to an existing class in the module style sheet. Due to our css modules plugin for + // typescript, we cannot do this without the ts-ignore + return styles[className] || className + } + + return className +} From 1380dec6e37bd7bf3dfb0828eb8c7a6089f11e02 Mon Sep 17 00:00:00 2001 From: Simon Emanuelsson Date: Fri, 27 Sep 2024 17:44:36 +0200 Subject: [PATCH 28/30] feat: add mobile ui to calendar --- .../DynamicContent/Overview/Stats/index.tsx | 2 +- components/BookingWidget/Client.tsx | 107 +++++++++++ .../MobileToggleButton/button.module.css | 35 ++++ .../MobileToggleButton/index.tsx | 102 +++++++++++ .../BookingWidget/bookingWidget.module.css | 31 +++- components/BookingWidget/index.tsx | 10 +- .../{DatePicker.tsx => Screen/Desktop.tsx} | 34 +--- components/DatePicker/Screen/Mobile.tsx | 140 ++++++++++++++ .../DatePicker/Screen/desktop.module.css | 120 ++++++++++++ .../DatePicker/Screen/mobile.module.css | 173 ++++++++++++++++++ components/DatePicker/date-picker.module.css | 157 ++++------------ components/DatePicker/index.tsx | 30 ++- .../FormContent/formContent.module.css | 107 +++++++---- .../Forms/BookingWidget/FormContent/index.tsx | 14 +- .../Forms/BookingWidget/form.module.css | 35 +++- components/Forms/BookingWidget/index.tsx | 60 ++---- components/Forms/BookingWidget/schema.ts | 2 +- components/Icons/Edit.tsx | 36 ++++ components/Icons/index.tsx | 1 + .../Divider/divider.module.css | 29 +-- .../TempDesignSystem/Divider/variants.ts | 7 +- .../Text/Caption/caption.module.css | 4 + .../TempDesignSystem/Text/Caption/variants.ts | 1 + i18n/dictionaries/da.json | 5 +- i18n/dictionaries/de.json | 5 +- i18n/dictionaries/en.json | 8 +- i18n/dictionaries/fi.json | 8 +- i18n/dictionaries/no.json | 5 +- i18n/dictionaries/sv.json | 5 +- types/components/bookingWidget/index.ts | 10 + types/components/datepicker.ts | 8 +- utils/debounce.ts | 10 + 32 files changed, 1005 insertions(+), 296 deletions(-) create mode 100644 components/BookingWidget/Client.tsx create mode 100644 components/BookingWidget/MobileToggleButton/button.module.css create mode 100644 components/BookingWidget/MobileToggleButton/index.tsx rename components/DatePicker/{DatePicker.tsx => Screen/Desktop.tsx} (84%) create mode 100644 components/DatePicker/Screen/Mobile.tsx create mode 100644 components/DatePicker/Screen/desktop.module.css create mode 100644 components/DatePicker/Screen/mobile.module.css create mode 100644 components/Icons/Edit.tsx create mode 100644 utils/debounce.ts diff --git a/components/Blocks/DynamicContent/Overview/Stats/index.tsx b/components/Blocks/DynamicContent/Overview/Stats/index.tsx index 73443da1b..3786a2d86 100644 --- a/components/Blocks/DynamicContent/Overview/Stats/index.tsx +++ b/components/Blocks/DynamicContent/Overview/Stats/index.tsx @@ -11,7 +11,7 @@ export default function Stats({ user }: UserProps) { return (
        - +
        ) diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx new file mode 100644 index 000000000..ce8370bab --- /dev/null +++ b/components/BookingWidget/Client.tsx @@ -0,0 +1,107 @@ +"use client" +import { zodResolver } from "@hookform/resolvers/zod" +import { useEffect, useState } from "react" +import { FormProvider, useForm } from "react-hook-form" + +import { dt } from "@/lib/dt" + +import Form from "@/components/Forms/BookingWidget" +import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" +import { CloseLarge } from "@/components/Icons" +import { debounce } from "@/utils/debounce" + +import MobileToggleButton from "./MobileToggleButton" + +import styles from "./bookingWidget.module.css" + +import type { + BookingWidgetClientProps, + BookingWidgetSchema, +} from "@/types/components/bookingWidget" +import type { Location } from "@/types/trpc/routers/hotel/locations" + +export default function BookingWidgetClient({ + locations, +}: BookingWidgetClientProps) { + const [isOpen, setIsOpen] = useState(false) + + const sessionStorageSearchData = + typeof window !== "undefined" + ? sessionStorage.getItem("searchData") + : undefined + const initialSelectedLocation: Location | undefined = sessionStorageSearchData + ? JSON.parse(sessionStorageSearchData) + : undefined + const methods = useForm({ + defaultValues: { + search: initialSelectedLocation?.name ?? "", + location: sessionStorageSearchData + ? encodeURIComponent(sessionStorageSearchData) + : undefined, + date: { + // UTC is required to handle requests from far away timezones https://scandichotels.atlassian.net/browse/SWAP-6375 & PET-507 + // This is specifically to handle timezones falling in different dates. + from: dt().utc().format("YYYY-MM-DD"), + to: dt().utc().add(1, "day").format("YYYY-MM-DD"), + }, + bookingCode: "", + redemption: false, + voucher: false, + rooms: [ + { + adults: 1, + children: [], + }, + ], + }, + shouldFocusError: false, + mode: "all", + resolver: zodResolver(bookingWidgetSchema), + reValidateMode: "onChange", + }) + + function closeMobileSearch() { + setIsOpen(false) + document.body.style.overflowY = "visible" + } + + function openMobileSearch() { + setIsOpen(true) + document.body.style.overflowY = "hidden" + } + + useEffect(() => { + const debouncedResizeHandler = debounce(function ([ + entry, + ]: ResizeObserverEntry[]) { + if (entry.contentRect.width > 1366) { + closeMobileSearch() + } + }) + const observer = new ResizeObserver(debouncedResizeHandler) + + observer.observe(document.body) + + return () => { + if (observer) { + observer.unobserve(document.body) + } + } + }, []) + + return ( + +
        + +
        +
        + +
        + ) +} diff --git a/components/BookingWidget/MobileToggleButton/button.module.css b/components/BookingWidget/MobileToggleButton/button.module.css new file mode 100644 index 000000000..f4a3d80fc --- /dev/null +++ b/components/BookingWidget/MobileToggleButton/button.module.css @@ -0,0 +1,35 @@ +.complete, +.partial { + align-items: center; + box-shadow: 0px 8px 24px 0px rgba(0, 0, 0, 0.16); + cursor: pointer; + display: grid; + gap: var(--Spacing-x-one-and-half); + padding: var(--Spacing-x2); +} + +.complete { + grid-template-columns: 1fr 36px; +} + +.partial { + grid-template-columns: min(1fr, 150px) min-content min(1fr, 150px) 1fr; +} + +.icon { + align-items: center; + background-color: var(--Base-Button-Primary-Fill-Normal); + border-radius: 50%; + display: flex; + height: 36px; + justify-content: center; + justify-self: flex-end; + width: 36px; +} + +@media screen and (min-width: 768px) { + .complete, + .partial { + display: none; + } +} diff --git a/components/BookingWidget/MobileToggleButton/index.tsx b/components/BookingWidget/MobileToggleButton/index.tsx new file mode 100644 index 000000000..113d8b365 --- /dev/null +++ b/components/BookingWidget/MobileToggleButton/index.tsx @@ -0,0 +1,102 @@ +"use client" +import { useEffect, useState } from "react" +import { useWatch } from "react-hook-form" +import { useIntl } from "react-intl" + +import { dt } from "@/lib/dt" + +import { EditIcon, SearchIcon } from "@/components/Icons" +import Divider from "@/components/TempDesignSystem/Divider" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import useLang from "@/hooks/useLang" + +import styles from "./button.module.css" + +import type { + BookingWidgetSchema, + BookingWidgetToggleButtonProps, +} from "@/types/components/bookingWidget" +import type { Location } from "@/types/trpc/routers/hotel/locations" + +export default function MobileToggleButton({ + openMobileSearch, +}: BookingWidgetToggleButtonProps) { + const [hasMounted, setHasMounted] = useState(false) + const intl = useIntl() + const lang = useLang() + const d = useWatch({ name: "date" }) + const location = useWatch({ name: "location" }) + const rooms: BookingWidgetSchema["rooms"] = useWatch({ name: "rooms" }) + + const parsedLocation: Location | null = location + ? JSON.parse(decodeURIComponent(location)) + : null + + const nights = dt(d.to).diff(dt(d.from), "days") + + const selectedFromDate = dt(d.from).locale(lang).format("D MMM") + const selectedToDate = dt(d.to).locale(lang).format("D MMM") + + useEffect(() => { + setHasMounted(true) + }, []) + + if (!hasMounted) { + return null + } + + if (parsedLocation && d) { + const totalRooms = rooms.length + const totalAdults = rooms.reduce((acc, room) => { + if (room.adults) { + acc = acc + room.adults + } + return acc + }, 0) + return ( +
        +
        + {parsedLocation.name} + + {`${selectedFromDate} - ${selectedToDate} (${intl.formatMessage( + { id: "booking.nights" }, + { totalNights: nights } + )}) ${intl.formatMessage({ id: "booking.adults" }, { totalAdults })}, ${intl.formatMessage({ id: "booking.rooms" }, { totalRooms })}`} + +
        +
        + +
        +
        + ) + } + + return ( +
        +
        + {intl.formatMessage({ id: "Where to" })} + + {parsedLocation + ? parsedLocation.name + : intl.formatMessage({ id: "Destination" })} + +
        + +
        + + {intl.formatMessage( + { id: "booking.nights" }, + { totalNights: nights } + )} + + + {selectedFromDate} - {selectedToDate} + +
        +
        + +
        +
        + ) +} diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index 7e1c1615e..5384dac60 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -1,5 +1,28 @@ -.container { - display: none; +@media screen and (max-width: 1366px) { + .container { + background-color: var(--UI-Input-Controls-Surface-Normal); + bottom: -100%; + display: grid; + gap: var(--Spacing-x3); + grid-template-rows: 36px 1fr; + height: 100dvh; + padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x7); + position: fixed; + transition: bottom 300ms ease; + width: 100%; + z-index: 10000; + } + + .container[data-open="true"] { + bottom: 0; + } + + .close { + background: none; + border: none; + cursor: pointer; + justify-self: flex-end; + } } @media screen and (min-width: 1367px) { @@ -8,4 +31,8 @@ border-top: 1px solid var(--Base-Border-Subtle); display: block; } + + .close { + display: none; + } } diff --git a/components/BookingWidget/index.tsx b/components/BookingWidget/index.tsx index de5a972ae..1c8b02f8e 100644 --- a/components/BookingWidget/index.tsx +++ b/components/BookingWidget/index.tsx @@ -1,8 +1,6 @@ import { getLocations } from "@/lib/trpc/memoizedRequests" -import Form from "@/components/Forms/BookingWidget" - -import styles from "./bookingWidget.module.css" +import BookingWidgetClient from "./Client" export function preload() { void getLocations() @@ -15,9 +13,5 @@ export default async function BookingWidget() { return null } - return ( -
        - -
        - ) + return } diff --git a/components/DatePicker/DatePicker.tsx b/components/DatePicker/Screen/Desktop.tsx similarity index 84% rename from components/DatePicker/DatePicker.tsx rename to components/DatePicker/Screen/Desktop.tsx index 0e78ac59c..8656fb8e5 100644 --- a/components/DatePicker/DatePicker.tsx +++ b/components/DatePicker/Screen/Desktop.tsx @@ -1,49 +1,30 @@ "use client" -import { da, de, fi, nb, sv } from "date-fns/locale" -import { useState } from "react" -import { type DateRange, DayPicker } from "react-day-picker" +import { DayPicker } from "react-day-picker" import { useIntl } from "react-intl" import { Lang } from "@/constants/languages" import { dt } from "@/lib/dt" +import { ChevronLeftIcon } from "@/components/Icons" import Button from "@/components/TempDesignSystem/Button" import Divider from "@/components/TempDesignSystem/Divider" import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import useLang from "@/hooks/useLang" -import { ChevronLeftIcon } from "../Icons" - -import styles from "./date-picker.module.css" +import styles from "./desktop.module.css" import classNames from "react-day-picker/style.module.css" import type { DatePickerProps } from "@/types/components/datepicker" -const locales = { - [Lang.da]: da, - [Lang.de]: de, - [Lang.fi]: fi, - [Lang.no]: nb, - [Lang.sv]: sv, -} - -export default function DatePicker({ +export default function DatePickerDesktop({ close, handleOnSelect, - initialSelected = { - from: undefined, - to: undefined, - }, + locales, + selectedDate, }: DatePickerProps) { const lang = useLang() const intl = useIntl() - const [selectedDate, setSelectedDate] = useState(initialSelected) - - function handleSelectDate(selected: DateRange) { - handleOnSelect(selected) - setSelectedDate(selected) - } /** English is default language and doesn't need to be imported */ const locale = lang === Lang.en ? undefined : locales[lang] @@ -63,6 +44,7 @@ export default function DatePicker({ range_end: styles.rangeEnd, range_middle: styles.rangeMiddle, range_start: styles.rangeStart, + root: `${classNames.root} ${styles.container}`, week: styles.week, weekday: `${classNames.weekday} ${styles.weekDay}`, }} @@ -78,7 +60,7 @@ export default function DatePicker({ locale={locale} mode="range" numberOfMonths={2} - onSelect={handleSelectDate} + onSelect={handleOnSelect} pagedNavigation required selected={selectedDate} diff --git a/components/DatePicker/Screen/Mobile.tsx b/components/DatePicker/Screen/Mobile.tsx new file mode 100644 index 000000000..c56a79cd6 --- /dev/null +++ b/components/DatePicker/Screen/Mobile.tsx @@ -0,0 +1,140 @@ +"use client" +import { type ChangeEvent, useState } from "react" +import { DayPicker } from "react-day-picker" +import { useIntl } from "react-intl" + +import { Lang } from "@/constants/languages" +import { dt } from "@/lib/dt" + +import { CloseLarge } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import useLang from "@/hooks/useLang" + +import styles from "./mobile.module.css" +import classNames from "react-day-picker/style.module.css" + +import type { DatePickerProps } from "@/types/components/datepicker" + +function addOneYear(_: undefined, i: number) { + return new Date().getFullYear() + i +} + +const fiftyYearsAhead = Array.from({ length: 50 }, addOneYear) + +export default function DatePickerMobile({ + close, + handleOnSelect, + locales, + selectedDate, +}: DatePickerProps) { + const [selectedYear, setSelectedYear] = useState(() => dt().year()) + const lang = useLang() + const intl = useIntl() + + function handleSelectYear(evt: ChangeEvent) { + setSelectedYear(Number(evt.currentTarget.value)) + } + + /** English is default language and doesn't need to be imported */ + const locale = lang === Lang.en ? undefined : locales[lang] + const currentDate = dt().toDate() + const startOfCurrentMonth = dt(currentDate).set("date", 1).toDate() + const yesterday = dt(currentDate).subtract(1, "day").toDate() + + const startMonth = dt().set("year", selectedYear).startOf("year").toDate() + const decemberOfYear = dt().set("year", selectedYear).endOf("year").toDate() + return ( + + +
        + + ) + }, + MonthCaption(props) { + return ( +
        + + {props.children} + +
        + ) + }, + Root({ children, ...props }) { + return ( +
        +
        + + +
        + {children} +
        + ) + }, + }} + /> + ) +} diff --git a/components/DatePicker/Screen/desktop.module.css b/components/DatePicker/Screen/desktop.module.css new file mode 100644 index 000000000..afbb286a7 --- /dev/null +++ b/components/DatePicker/Screen/desktop.module.css @@ -0,0 +1,120 @@ +@media screen and (max-width: 1366px) { + .container { + display: none; + } +} + +div.months { + flex-wrap: nowrap; +} + +.monthCaption { + justify-content: center; +} + +.captionLabel { + text-transform: capitalize; +} + +td.day, +td.rangeEnd, +td.rangeStart { + font-family: var(--typography-Body-Bold-fontFamily); + font-size: var(--typography-Body-Bold-fontSize); + font-weight: 500; + letter-spacing: var(--typography-Body-Bold-letterSpacing); + line-height: var(--typography-Body-Bold-lineHeight); + text-decoration: var(--typography-Body-Bold-textDecoration); +} + +td.rangeEnd, +td.rangeStart { + background: var(--Base-Background-Primary-Normal); +} + +td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) { + border-radius: 0 50% 50% 0; +} + +td.rangeStart[aria-selected="true"] { + border-radius: 50% 0 0 50%; +} + +td.rangeEnd[aria-selected="true"] button.dayButton:hover, +td.rangeStart[aria-selected="true"] button.dayButton:hover { + background: var(--Primary-Light-On-Surface-Accent); + border-radius: 50%; +} + +td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) button.dayButton, +td.rangeStart[aria-selected="true"]:not([data-outside="true"]) + button.dayButton { + background: var(--Primary-Light-On-Surface-Accent); + border: none; + color: var(--Base-Button-Inverted-Fill-Normal); +} + +td.day, +td.day[data-today="true"] { + color: var(--UI-Text-High-contrast); + height: 40px; + padding: var(--Spacing-x-half); + width: 40px; +} + +td.day button.dayButton:hover { + background: var(--Base-Surface-Secondary-light-Hover); +} + +td.day[data-outside="true"] button.dayButton { + border: none; +} + +td.day:not(td.rangeEnd, td.rangeStart)[aria-selected="true"], +td.rangeMiddle[aria-selected="true"] button.dayButton { + background: var(--Base-Background-Primary-Normal); + border: none; + border-radius: 0; +} + +td.day[data-disabled="true"], +td.day[data-disabled="true"] button.dayButton, +td.day[data-outside="true"] ~ td.day[data-disabled="true"], +td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton, +.week:has(td.day[data-outside="true"] ~ td.day[data-disabled="true"]) + td.day[data-outside="true"] + button.dayButton { + background: none; + color: var(--Base-Text-Disabled); + cursor: not-allowed; +} + +.weekDay { + color: var(--UI-Text-Placeholder); + font-family: var(--typography-Footnote-Labels-fontFamily); + font-size: var(--typography-Footnote-Labels-fontSize); + font-weight: var(--typography-Footnote-Labels-fontWeight); + letter-spacing: var(--typography-Footnote-Labels-letterSpacing); + line-height: var(--typography-Footnote-Labels-lineHeight); + text-decoration: var(--typography-Footnote-Labels-textDecoration); + text-transform: uppercase; +} + +.footer { + display: flex; + justify-content: flex-end; + margin-top: var(--Spacing-x2); +} + +.divider { + margin-top: var(--Spacing-x2); +} + +.nextButton { + transform: rotate(180deg); + right: 0; +} + +.previousButton { + left: 0; +} diff --git a/components/DatePicker/Screen/mobile.module.css b/components/DatePicker/Screen/mobile.module.css new file mode 100644 index 000000000..4f820fd5f --- /dev/null +++ b/components/DatePicker/Screen/mobile.module.css @@ -0,0 +1,173 @@ +.container { + --header-height: 68px; + --sticky-button-height: 120px; + + display: grid; + grid-template-areas: + "header" + "content"; + grid-template-rows: var(--header-height) calc(100dvh - var(--header-height)); + position: relative; +} + +.header { + align-self: flex-start; + background-color: var(--Main-Grey-White); + display: grid; + grid-area: header; + grid-template-columns: 1fr 24px; + padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x2); + position: sticky; + top: 0; + z-index: 10; +} + +.select { + justify-self: center; + min-width: 100px; + transform: translateX(24px); +} + +.close { + align-items: center; + background: none; + border: none; + cursor: pointer; + display: flex; + justify-self: flex-end; +} + +div.months { + display: grid; + grid-area: content; + overflow-y: scroll; + scroll-snap-type: y mandatory; +} + +.month { + display: grid; + justify-items: center; + scroll-snap-align: start; +} + +.month:last-of-type { + padding-bottom: var(--sticky-button-height); +} + +.monthCaption { + justify-content: center; +} + +.captionLabel { + text-transform: capitalize; +} + +.footer { + align-self: flex-start; + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0) 7.5%, + #ffffff 82.5% + ); + display: flex; + grid-area: content; + padding: var(--Spacing-x1) var(--Spacing-x2) var(--Spacing-x7); + position: sticky; + top: calc(100vh - var(--sticky-button-height)); + width: 100%; + z-index: 10; +} + +.footer .button { + width: 100%; +} + +td.day, +td.rangeEnd, +td.rangeStart { + font-family: var(--typography-Body-Bold-fontFamily); + font-size: var(--typography-Body-Bold-fontSize); + font-weight: 500; + letter-spacing: var(--typography-Body-Bold-letterSpacing); + line-height: var(--typography-Body-Bold-lineHeight); + text-decoration: var(--typography-Body-Bold-textDecoration); +} + +td.rangeEnd, +td.rangeStart { + background: var(--Base-Background-Primary-Normal); +} + +td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) { + border-radius: 0 50% 50% 0; +} + +td.rangeStart[aria-selected="true"] { + border-radius: 50% 0 0 50%; +} + +td.rangeEnd[aria-selected="true"] button.dayButton:hover, +td.rangeStart[aria-selected="true"] button.dayButton:hover { + background: var(--Primary-Light-On-Surface-Accent); + border-radius: 50%; +} + +td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) button.dayButton, +td.rangeStart[aria-selected="true"]:not([data-outside="true"]) + button.dayButton { + background: var(--Primary-Light-On-Surface-Accent); + border: none; + color: var(--Base-Button-Inverted-Fill-Normal); +} + +td.day, +td.day[data-today="true"] { + color: var(--UI-Text-High-contrast); + height: 40px; + padding: var(--Spacing-x-half); + width: 40px; +} + +td.day button.dayButton:hover { + background: var(--Base-Surface-Secondary-light-Hover); +} + +td.day[data-outside="true"] button.dayButton { + border: none; +} + +td.day:not(td.rangeEnd, td.rangeStart)[aria-selected="true"], +td.rangeMiddle[aria-selected="true"] button.dayButton { + background: var(--Base-Background-Primary-Normal); + border: none; + border-radius: 0; +} + +td.day[data-disabled="true"], +td.day[data-disabled="true"] button.dayButton, +td.day[data-outside="true"] ~ td.day[data-disabled="true"], +td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton, +.week:has(td.day[data-outside="true"] ~ td.day[data-disabled="true"]) + td.day[data-outside="true"] + button.dayButton { + background: none; + color: var(--Base-Text-Disabled); + cursor: not-allowed; +} + +.weekDay { + color: var(--Base-Text-Medium-contrast); + font-family: var(--typography-Footnote-Labels-fontFamily); + font-size: var(--typography-Footnote-Labels-fontSize); + font-weight: var(--typography-Footnote-Labels-fontWeight); + letter-spacing: var(--typography-Footnote-Labels-letterSpacing); + line-height: var(--typography-Footnote-Labels-lineHeight); + text-decoration: var(--typography-Footnote-Labels-textDecoration); + text-transform: uppercase; +} + +@media screen and (min-width: 1367px) { + .container { + display: none; + } +} diff --git a/components/DatePicker/date-picker.module.css b/components/DatePicker/date-picker.module.css index 77cc0b140..faaac7dd0 100644 --- a/components/DatePicker/date-picker.module.css +++ b/components/DatePicker/date-picker.module.css @@ -7,20 +7,6 @@ } } -.hideWrapper { - background-color: var(--Main-Grey-White); - border-radius: var(--Corner-radius-Large); - box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); - padding: var(--Spacing-x2) var(--Spacing-x3); - position: absolute; - /** - BookingWidget padding + - border-width + - wanted space below booking widget - */ - top: calc(100% + var(--Spacing-x2) + 1px + var(--Spacing-x4)); -} - .btn { background: none; border: none; @@ -34,117 +20,42 @@ opacity: 0.8; } -div.months { - flex-wrap: nowrap; +.hideWrapper { + background-color: var(--Main-Grey-White); } -.monthCaption { - justify-content: center; +@media screen and (max-width: 1366px) { + .container { + z-index: 10001; + } + + .hideWrapper { + bottom: 0; + left: 0; + overflow: auto; + position: fixed; + right: 0; + top: 100%; + transition: top 300ms ease; + z-index: 10001; + } + + .container[data-isopen="true"] .hideWrapper { + top: 0; + } } -.captionLabel { - text-transform: capitalize; -} - -td.day, -td.rangeEnd, -td.rangeStart { - font-family: var(--typography-Body-Bold-fontFamily); - font-size: var(--typography-Body-Bold-fontSize); - font-weight: 500; - letter-spacing: var(--typography-Body-Bold-letterSpacing); - line-height: var(--typography-Body-Bold-lineHeight); - text-decoration: var(--typography-Body-Bold-textDecoration); -} - -td.rangeEnd, -td.rangeStart { - background: var(--Base-Background-Primary-Normal); -} - -td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) { - border-radius: 0 50% 50% 0; -} - -td.rangeStart[aria-selected="true"] { - border-radius: 50% 0 0 50%; -} - -td.rangeEnd[aria-selected="true"] button.dayButton:hover, -td.rangeStart[aria-selected="true"] button.dayButton:hover { - background: var(--Primary-Light-On-Surface-Accent); - border-radius: 50%; -} - -td.rangeEnd[aria-selected="true"]:not([data-outside="true"]) button.dayButton, -td.rangeStart[aria-selected="true"]:not([data-outside="true"]) - button.dayButton { - background: var(--Primary-Light-On-Surface-Accent); - border: none; - color: var(--Base-Button-Inverted-Fill-Normal); -} - -td.day, -td.day[data-today="true"] { - color: var(--UI-Text-High-contrast); - height: 40px; - padding: var(--Spacing-x-half); - width: 40px; -} - -td.day button.dayButton:hover { - background: var(--Base-Surface-Secondary-light-Hover); -} - -td.day[data-outside="true"] button.dayButton { - border: none; -} - -td.day:not(td.rangeEnd, td.rangeStart)[aria-selected="true"], -td.rangeMiddle[aria-selected="true"] button.dayButton { - background: var(--Base-Background-Primary-Normal); - border: none; - border-radius: 0; -} - -td.day[data-disabled="true"], -td.day[data-disabled="true"] button.dayButton, -td.day[data-outside="true"] ~ td.day[data-disabled="true"], -td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton, -.week:has(td.day[data-outside="true"] ~ td.day[data-disabled="true"]) - td.day[data-outside="true"] - button.dayButton { - background: none; - color: var(--Base-Text-Disabled); - cursor: not-allowed; -} - -.weekDay { - color: var(--UI-Text-Placeholder); - font-family: var(--typography-Footnote-Labels-fontFamily); - font-size: var(--typography-Footnote-Labels-fontSize); - font-weight: var(--typography-Footnote-Labels-fontWeight); - letter-spacing: var(--typography-Footnote-Labels-letterSpacing); - line-height: var(--typography-Footnote-Labels-lineHeight); - text-decoration: var(--typography-Footnote-Labels-textDecoration); - text-transform: uppercase; -} - -.footer { - display: flex; - justify-content: flex-end; - margin-top: var(--Spacing-x2); -} - -.divider { - margin-top: var(--Spacing-x2); -} - -.nextButton { - transform: rotate(180deg); - right: 0; -} - -.previousButton { - left: 0; +@media screen and (min-width: 1367px) { + .hideWrapper { + border-radius: var(--Corner-radius-Large); + box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1); + padding: var(--Spacing-x2) var(--Spacing-x3); + position: absolute; + /** + BookingWidget padding + + border-width + + wanted space below booking widget + */ + top: calc(100% + var(--Spacing-x2) + 1px + var(--Spacing-x4)); + } } diff --git a/components/DatePicker/index.tsx b/components/DatePicker/index.tsx index 0da5bb263..586da5208 100644 --- a/components/DatePicker/index.tsx +++ b/components/DatePicker/index.tsx @@ -1,13 +1,16 @@ "use client" +import { da, de, fi, nb, sv } from "date-fns/locale" import { useEffect, useRef, useState } from "react" import { useFormContext, useWatch } from "react-hook-form" +import { Lang } from "@/constants/languages" import { dt } from "@/lib/dt" import Body from "@/components/TempDesignSystem/Text/Body" import useLang from "@/hooks/useLang" -import DatePicker from "./DatePicker" +import DatePickerDesktop from "./Screen/Desktop" +import DatePickerMobile from "./Screen/Mobile" import styles from "./date-picker.module.css" @@ -15,6 +18,14 @@ import type { DateRange } from "react-day-picker" import type { DatePickerFormProps } from "@/types/components/datepicker" +const locales = { + [Lang.da]: da, + [Lang.de]: de, + [Lang.fi]: fi, + [Lang.no]: nb, + [Lang.sv]: sv, +} + export default function DatePickerForm({ name = "date" }: DatePickerFormProps) { const lang = useLang() const [isOpen, setIsOpen] = useState(false) @@ -44,11 +55,9 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) { setIsOpen(false) } } - - document.addEventListener("click", handleClickOutside) - + document.body.addEventListener("click", handleClickOutside) return () => { - document.removeEventListener("click", handleClickOutside) + document.body.removeEventListener("click", handleClickOutside) } }, [setIsOpen]) @@ -67,10 +76,17 @@ export default function DatePickerForm({ name = "date" }: DatePickerFormProps) {
        - +
        diff --git a/components/Forms/BookingWidget/FormContent/formContent.module.css b/components/Forms/BookingWidget/FormContent/formContent.module.css index eea85e3d3..4b62ecdcb 100644 --- a/components/Forms/BookingWidget/FormContent/formContent.module.css +++ b/components/Forms/BookingWidget/FormContent/formContent.module.css @@ -1,45 +1,80 @@ -.input { - display: flex; - gap: var(--Spacing-x2); -} - -.input input[type="text"] { - border: none; - height: 24px; -} - -.rooms, -.vouchers, -.when, -.where { - border-right: 1px solid var(--Base-Surface-Subtle-Normal); - width: 100%; -} - -.rooms, -.when { - max-width: 240px; - padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); -} - -.vouchers { - max-width: 200px; - padding: var(--Spacing-x1) 0; -} - -.where { - max-width: 280px; - position: relative; -} - .options { display: flex; flex-direction: column; justify-content: center; width: 100%; - max-width: 158px; } .option { display: flex; -} \ No newline at end of file +} + +@media screen and (max-width: 1366px) { + .input { + display: grid; + gap: var(--Spacing-x2); + } + + .rooms, + .vouchers, + .when, + .where { + background-color: var(--Base-Background-Primary-Normal); + border-radius: var(--Corner-radius-Medium); + } + + .rooms, + .vouchers, + .when { + padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); + } + + .options { + gap: var(--Spacing-x2); + margin-top: var(--Spacing-x2); + } + + .option { + gap: var(--Spacing-x2); + } +} + +@media screen and (min-width: 1367px) { + .input { + display: flex; + gap: var(--Spacing-x2); + } + + .rooms, + .vouchers, + .when, + .where { + border-right: 1px solid var(--Base-Surface-Subtle-Normal); + width: 100%; + } + + .input input[type="text"] { + border: none; + height: 24px; + } + + .rooms, + .when { + max-width: 240px; + padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); + } + + .vouchers { + max-width: 200px; + padding: var(--Spacing-x1) 0; + } + + .where { + max-width: 280px; + position: relative; + } + + .options { + max-width: 158px; + } +} diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx index 371140a7f..fcb5bb847 100644 --- a/components/Forms/BookingWidget/FormContent/index.tsx +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -19,8 +19,8 @@ export default function FormContent({ const intl = useIntl() const selectedDate = useWatch({ name: "date" }) - const rooms = intl.formatMessage({ id: "Rooms & Guests" }) - const vouchers = intl.formatMessage({ id: "Booking codes and vouchers" }) + const rooms = intl.formatMessage({ id: "Guests & Rooms" }) + const vouchers = intl.formatMessage({ id: "Code / Voucher" }) const bonus = intl.formatMessage({ id: "Use bonus cheque" }) const reward = intl.formatMessage({ id: "Book reward night" }) @@ -47,20 +47,20 @@ export default function FormContent({
      - + {vouchers}
      -
      +
      -
      + +
      +
) diff --git a/components/Forms/BookingWidget/form.module.css b/components/Forms/BookingWidget/form.module.css index e3c71df66..323d84037 100644 --- a/components/Forms/BookingWidget/form.module.css +++ b/components/Forms/BookingWidget/form.module.css @@ -1,15 +1,36 @@ .section { align-items: center; - display: flex; + display: grid; margin: 0 auto; max-width: var(--max-width-navigation); -} - -.form { width: 100%; } -.button { - width: 118px; - justify-content: center; +.form { + display: grid; + gap: var(--Spacing-x2); + width: 100%; +} + +@media screen and (max-width: 1366px) { + .form { + align-self: flex-start; + } + + .button { + align-self: flex-end; + justify-content: center; + width: 100%; + } +} + +@media screen and (min-width: 1367px) { + .section { + display: flex; + } + + .button { + justify-content: center; + width: 118px; + } } diff --git a/components/Forms/BookingWidget/index.tsx b/components/Forms/BookingWidget/index.tsx index 153d8417c..f78e38be0 100644 --- a/components/Forms/BookingWidget/index.tsx +++ b/components/Forms/BookingWidget/index.tsx @@ -1,22 +1,17 @@ "use client" -import { zodResolver } from "@hookform/resolvers/zod" import { useRouter } from "next/navigation" -import { FormProvider, useForm } from "react-hook-form" +import { useFormContext } from "react-hook-form" import { useIntl } from "react-intl" -import { dt } from "@/lib/dt" - import Button from "@/components/TempDesignSystem/Button" import Caption from "@/components/TempDesignSystem/Text/Caption" import FormContent from "./FormContent" -import { bookingWidgetSchema } from "./schema" import styles from "./form.module.css" import type { BookingWidgetSchema } from "@/types/components/bookingWidget" import type { BookingWidgetFormProps } from "@/types/components/form/bookingwidget" -import type { Location } from "@/types/trpc/routers/hotel/locations" const formId = "booking-widget" @@ -24,40 +19,8 @@ export default function Form({ locations }: BookingWidgetFormProps) { const intl = useIntl() const router = useRouter() - const sessionStorageSearchData = - typeof window !== "undefined" - ? sessionStorage.getItem("searchData") - : undefined - const initialSelectedLocation: Location | undefined = sessionStorageSearchData - ? JSON.parse(sessionStorageSearchData) - : undefined - const methods = useForm({ - defaultValues: { - search: initialSelectedLocation?.name ?? "", - location: sessionStorageSearchData - ? encodeURIComponent(sessionStorageSearchData) - : undefined, - date: { - // UTC is required to handle requests from far away timezones https://scandichotels.atlassian.net/browse/SWAP-6375 & PET-507 - // This is specifically to handle timezones falling in different dates. - from: dt().utc().format("YYYY-MM-DD"), - to: dt().utc().add(1, "day").format("YYYY-MM-DD"), - }, - bookingCode: "", - redemption: false, - voucher: false, - rooms: [ - { - adults: 1, - childs: [], - }, - ], - }, - shouldFocusError: false, - mode: "all", - resolver: zodResolver(bookingWidgetSchema), - reValidateMode: "onChange", - }) + const { formState, handleSubmit, register } = + useFormContext() function onSubmit(data: BookingWidgetSchema) { data.location = JSON.parse(decodeURIComponent(data.location)) @@ -70,25 +33,24 @@ export default function Form({ locations }: BookingWidgetFormProps) { return (
- - - - + +
diff --git a/components/Forms/BookingWidget/schema.ts b/components/Forms/BookingWidget/schema.ts index 1455f65a7..f474a72e1 100644 --- a/components/Forms/BookingWidget/schema.ts +++ b/components/Forms/BookingWidget/schema.ts @@ -29,7 +29,7 @@ export const bookingWidgetSchema = z.object({ // This will be updated when working in guests component z.object({ adults: z.number().default(1), - childs: z.array( + children: z.array( z.object({ age: z.number(), bed: z.number(), diff --git a/components/Icons/Edit.tsx b/components/Icons/Edit.tsx new file mode 100644 index 000000000..209e16883 --- /dev/null +++ b/components/Icons/Edit.tsx @@ -0,0 +1,36 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function EditIcon({ className, color, ...props }: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index db5e29bbb..78e3f8de1 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -23,6 +23,7 @@ export { default as CrossCircle } from "./CrossCircle" export { default as CulturalIcon } from "./Cultural" export { default as DeleteIcon } from "./Delete" export { default as DoorOpenIcon } from "./DoorOpen" +export { default as EditIcon } from "./Edit" export { default as ElectricBikeIcon } from "./ElectricBike" export { default as EmailIcon } from "./Email" export { default as ErrorCircleIcon } from "./ErrorCircle" diff --git a/components/TempDesignSystem/Divider/divider.module.css b/components/TempDesignSystem/Divider/divider.module.css index 1896c546b..6753e7533 100644 --- a/components/TempDesignSystem/Divider/divider.module.css +++ b/components/TempDesignSystem/Divider/divider.module.css @@ -1,40 +1,47 @@ .divider { - border-bottom-style: solid; - border-bottom-width: 1px; + pointer-events: none; +} + +.horizontal { height: 1px; width: 100%; } -.dotted { - border-bottom-style: dotted; +.vertical { + height: 100%; + width: 1px; } .burgundy { - border-bottom-color: var(--Scandic-Brand-Burgundy); + background-color: var(--Scandic-Brand-Burgundy); } .pale { - border-bottom-color: var(--Primary-Dark-On-Surface-Text); + background-color: var(--Primary-Dark-On-Surface-Text); } .peach { - border-bottom-color: var(--Primary-Light-On-Surface-Divider); + background-color: var(--Primary-Light-On-Surface-Divider); } .beige { - border-bottom-color: var(--Scandic-Beige-20); + background-color: var(--Scandic-Beige-20); } .white { - border-bottom-color: var(--UI-Opacity-White-100); + background-color: var(--UI-Opacity-White-100); } .subtle { - border-bottom-color: var(--Base-Border-Subtle); + background-color: var(--Base-Border-Subtle); } .primaryLightSubtle { - border-bottom-color: var(--Primary-Light-On-Surface-Divider-subtle); + background-color: var(--Primary-Light-On-Surface-Divider-subtle); +} + +.baseSurfaceSubtleNormal { + background-color: var(--Base-Surface-Subtle-Normal); } .opacity100 { diff --git a/components/TempDesignSystem/Divider/variants.ts b/components/TempDesignSystem/Divider/variants.ts index 8611474fa..43f7899e0 100644 --- a/components/TempDesignSystem/Divider/variants.ts +++ b/components/TempDesignSystem/Divider/variants.ts @@ -5,6 +5,7 @@ import styles from "./divider.module.css" export const dividerVariants = cva(styles.divider, { variants: { color: { + baseSurfaceSubtleNormal: styles.baseSurfaceSubtleNormal, beige: styles.beige, burgundy: styles.burgundy, pale: styles.pale, @@ -18,13 +19,13 @@ export const dividerVariants = cva(styles.divider, { 8: styles.opacity8, }, variant: { - default: styles.default, - dotted: styles.dotted, + horizontal: styles.horizontal, + vertical: styles.vertical, }, }, defaultVariants: { color: "burgundy", opacity: 100, - variant: "default", + variant: "horizontal", }, }) diff --git a/components/TempDesignSystem/Text/Caption/caption.module.css b/components/TempDesignSystem/Text/Caption/caption.module.css index 18bd252b6..627f7132a 100644 --- a/components/TempDesignSystem/Text/Caption/caption.module.css +++ b/components/TempDesignSystem/Text/Caption/caption.module.css @@ -63,6 +63,10 @@ p.caption { color: var(--UI-Text-Active); } +.uiTextMediumContrast { + color: var(--UI-Text-Medium-contrast); +} + .center { text-align: center; } diff --git a/components/TempDesignSystem/Text/Caption/variants.ts b/components/TempDesignSystem/Text/Caption/variants.ts index dc0437fff..4b0dd96af 100644 --- a/components/TempDesignSystem/Text/Caption/variants.ts +++ b/components/TempDesignSystem/Text/Caption/variants.ts @@ -12,6 +12,7 @@ const config = { red: styles.red, white: styles.white, uiTextActive: styles.uiTextActive, + uiTextMediumContrast: styles.uiTextMediumContrast, }, textTransform: { bold: styles.bold, diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 499f67cb3..35b5e759a 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -23,7 +23,7 @@ "Bed type": "Seng type", "Book": "Book", "Book reward night": "Book bonusnat", - "Booking codes and vouchers": "Bookingkoder og vouchers", + "Code / Voucher": "Bookingkoder / voucher", "Booking number": "Bookingnummer", "Breakfast": "Morgenmad", "Breakfast excluded": "Morgenmad ikke inkluderet", @@ -183,10 +183,11 @@ "Room & Terms": "Værelse & Vilkår", "Room facilities": "Værelsesfaciliteter", "Rooms": "Værelser", - "Rooms & Guests": "Værelser & gæster", + "Guests & Rooms": "Gæster & værelser", "Save": "Gemme", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", + "Search": "Søge", "See all photos": "Se alle billeder", "See hotel details": "Se hoteloplysninger", "See room details": "Se værelsesdetaljer", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index a215a8263..916804097 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -23,7 +23,7 @@ "Bed type": "Bettentyp", "Book": "Buchen", "Book reward night": "Bonusnacht buchen", - "Booking codes and vouchers": "Buchungscodes und Gutscheine", + "Code / Voucher": "Buchungscodes / Gutscheine", "Booking number": "Buchungsnummer", "Breakfast": "Frühstück", "Breakfast excluded": "Frühstück nicht inbegriffen", @@ -182,10 +182,11 @@ "Room & Terms": "Zimmer & Bedingungen", "Room facilities": "Zimmerausstattung", "Rooms": "Räume", - "Rooms & Guests": "Zimmer & Gäste", + "Guests & Rooms": "Gäste & Zimmer", "Save": "Speichern", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", + "Search": "Suchen", "See all photos": "Alle Fotos ansehen", "See hotel details": "Hotelinformationen ansehen", "See room details": "Zimmerdetails ansehen", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index fa90f4f4e..c663a0f80 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -23,7 +23,7 @@ "Bed type": "Bed type", "Book": "Book", "Book reward night": "Book reward night", - "Booking codes and vouchers": "Booking codes and vouchers", + "Code / Voucher": "Code / Voucher", "Booking number": "Booking number", "Breakfast": "Breakfast", "Breakfast excluded": "Breakfast excluded", @@ -60,6 +60,7 @@ "Day": "Day", "Description": "Description", "Destinations & hotels": "Destinations & hotels", + "Destination": "Destination", "Discard changes": "Discard changes", "Discard unsaved changes?": "Discard unsaved changes?", "Distance to city centre": "{number}km to city centre", @@ -183,11 +184,12 @@ "Room & Terms": "Room & Terms", "Room facilities": "Room facilities", "Rooms": "Rooms", - "Rooms & Guests": "Rooms & Guests", + "Guests & Rooms": "Guests & Rooms", "Save": "Save", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", "See all photos": "See all photos", + "Search": "Search", "See hotel details": "See hotel details", "See room details": "See room details", "See rooms": "See rooms", @@ -264,7 +266,9 @@ "Zoom in": "Zoom in", "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", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 117fcad58..aa12eccf0 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -23,7 +23,6 @@ "Bed type": "Vuodetyyppi", "Book": "Varaa", "Book reward night": "Kirjapalkinto-ilta", - "Booking codes and vouchers": "Varauskoodit ja kupongit", "Booking number": "Varausnumero", "Breakfast": "Aamiainen", "Breakfast excluded": "Aamiainen ei sisälly", @@ -45,6 +44,7 @@ "Close menu": "Sulje valikko", "Close my pages menu": "Sulje omat sivut -valikko", "Close the map": "Sulje kartta", + "Code / Voucher": "Varauskoodit / kupongit", "Coming up": "Tulossa", "Compare all levels": "Vertaa kaikkia tasoja", "Contact us": "Ota meihin yhteyttä", @@ -85,6 +85,7 @@ "Get inspired": "Inspiroidu", "Go back to edit": "Palaa muokkaamaan", "Go back to overview": "Palaa yleiskatsaukseen", + "Guests & Rooms": "Vieraat & Huoneet", "Hi": "Hi", "Highest level": "Korkein taso", "Hospital": "Sairaala", @@ -183,11 +184,10 @@ "Room & Terms": "Huone & Ehdot", "Room facilities": "Huoneen varustelu", "Rooms": "Huoneet", - "Rooms & Guests": "Huoneet & Vieraat", - "Rooms & Guestss": "Huoneet & Vieraat", "Save": "Tallenna", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", + "Search": "Haku", "See all photos": "Katso kaikki kuvat", "See hotel details": "Katso hotellin tiedot", "See room details": "Katso huoneen tiedot", @@ -281,4 +281,4 @@ "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", "to": "to", "uppercase letter": "iso kirjain" -} +} \ No newline at end of file diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index ea8aa4548..ed600ecb5 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -23,7 +23,7 @@ "Bed type": "Seng type", "Book": "Bestill", "Book reward night": "Bestill belønningskveld", - "Booking codes and vouchers": "Bestillingskoder og kuponger", + "Code / Voucher": "Bestillingskoder / kuponger", "Booking number": "Bestillingsnummer", "Breakfast": "Frokost", "Breakfast excluded": "Frokost ekskludert", @@ -183,10 +183,11 @@ "Room & Terms": "Rom & Vilkår", "Room facilities": "Romfasiliteter", "Rooms": "Rom", - "Rooms & Guests": "Rom og gjester", + "Guests & Rooms": "Gjester & rom", "Save": "Lagre", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", + "Search": "Søk", "See all photos": "Se alle bilder", "See hotel details": "Se hotellinformasjon", "See room details": "Se detaljer om rommet", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 864b6e4ae..9f3f71511 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -23,7 +23,7 @@ "Bed type": "Sängtyp", "Book": "Boka", "Book reward night": "Boka frinatt", - "Booking codes and vouchers": "Bokningskoder och kuponger", + "Code / Voucher": "Bokningskoder / kuponger", "Booking number": "Bokningsnummer", "Breakfast": "Frukost", "Breakfast excluded": "Frukost ingår ej", @@ -183,10 +183,11 @@ "Room & Terms": "Rum & Villkor", "Room facilities": "Rumfaciliteter", "Rooms": "Rum", - "Rooms & Guests": "Rum och gäster", + "Guests & Rooms": "Gäster & rum", "Save": "Spara", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", + "Search": "Sök", "See all photos": "Se alla foton", "See hotel details": "Se hotellinformation", "See room details": "Se rumsdetaljer", diff --git a/types/components/bookingWidget/index.ts b/types/components/bookingWidget/index.ts index 1998e3778..ab4d194d9 100644 --- a/types/components/bookingWidget/index.ts +++ b/types/components/bookingWidget/index.ts @@ -2,4 +2,14 @@ import { z } from "zod" import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" +import type { Locations } from "@/types/trpc/routers/hotel/locations" + export type BookingWidgetSchema = z.output + +export interface BookingWidgetClientProps { + locations: Locations +} + +export interface BookingWidgetToggleButtonProps { + openMobileSearch: () => void +} diff --git a/types/components/datepicker.ts b/types/components/datepicker.ts index 2b82966c1..1d30aa3ae 100644 --- a/types/components/datepicker.ts +++ b/types/components/datepicker.ts @@ -1,11 +1,17 @@ +import { Lang } from "@/constants/languages" + +import type { Locale } from "date-fns" import type { DateRange } from "react-day-picker" export interface DatePickerFormProps { name?: string } +type LangWithoutEn = Lang.da | Lang.de | Lang.fi | Lang.no | Lang.sv + export interface DatePickerProps { close: () => void handleOnSelect: (selected: DateRange) => void - initialSelected?: DateRange + locales: Record + selectedDate: DateRange } diff --git a/utils/debounce.ts b/utils/debounce.ts new file mode 100644 index 000000000..80eb13e96 --- /dev/null +++ b/utils/debounce.ts @@ -0,0 +1,10 @@ +export function debounce(func: Function, delay = 300) { + let debounceTimer: ReturnType + return function () { + // @ts-expect-error this in TypeScript + const context = this + const args = arguments + clearTimeout(debounceTimer) + debounceTimer = setTimeout(() => func.apply(context, args), delay) + } +} From 869c9b67b82e7ccf4129d60acc0127c59bb23aac Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Thu, 3 Oct 2024 07:42:26 +0000 Subject: [PATCH 29/30] Merged in feat/SW-475-enter-details-header (pull request #630) Feat/SW-475 enter details header * feat(SW-475): updated hotel mock data to reflect api * feat(SW-475): Added hotel selection header with sidepeek buttons * fix(SW-475): fixes from PR * fix(SW-475): changed intl string Approved-by: Arvid Norlin --- .../hotelreservation/[section]/page.tsx | 7 +- .../hotelDetailSidePeek.module.css | 4 + .../HotelDetailSidePeek/index.tsx | 58 ++ .../hotelSelectionHeader.module.css | 52 ++ .../HotelSelectionHeader/index.tsx | 52 ++ .../Text/Body/body.module.css | 4 + .../TempDesignSystem/Text/Body/variants.ts | 1 + server/routers/hotels/tempHotelData.json | 764 +++++++++++++++--- .../selectRate/hotelSelectionHeader.ts | 5 + 9 files changed, 840 insertions(+), 107 deletions(-) create mode 100644 components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/hotelDetailSidePeek.module.css create mode 100644 components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/index.tsx create mode 100644 components/HotelReservation/HotelSelectionHeader/hotelSelectionHeader.module.css create mode 100644 components/HotelReservation/HotelSelectionHeader/index.tsx create mode 100644 types/components/hotelReservation/selectRate/hotelSelectionHeader.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx index 45ca7d308..a0373645b 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx @@ -1,6 +1,8 @@ import { serverClient } from "@/lib/trpc/server" +import { getHotelDataSchema } from "@/server/routers/hotels/output" import tempHotelData from "@/server/routers/hotels/tempHotelData.json" +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" @@ -79,7 +81,7 @@ export default async function SectionsPage({ setLang(params.lang) // TODO: Use real endpoint. - const hotel = tempHotelData.data.attributes + const hotel = getHotelDataSchema.parse(tempHotelData) const rooms = await serverClient().hotel.rates.get({ // TODO: pass the correct hotel ID and all other parameters that should be included in the search @@ -107,8 +109,7 @@ export default async function SectionsPage({ return (
- {/* TODO: Add Hotel Listing Card */} -
Hotel Listing Card TBI
+
diff --git a/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/hotelDetailSidePeek.module.css b/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/hotelDetailSidePeek.module.css new file mode 100644 index 000000000..fb400465e --- /dev/null +++ b/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/hotelDetailSidePeek.module.css @@ -0,0 +1,4 @@ +.buttons { + display: flex; + gap: var(--Spacing-x3); +} diff --git a/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/index.tsx b/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/index.tsx new file mode 100644 index 000000000..d4e3eb829 --- /dev/null +++ b/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/index.tsx @@ -0,0 +1,58 @@ +"use client" + +import { useState } from "react" +import { useIntl } from "react-intl" + +import ChevronRightSmallIcon from "@/components/Icons/ChevronRightSmall" +import Button from "@/components/TempDesignSystem/Button" +import SidePeek from "@/components/TempDesignSystem/SidePeek" + +import styles from "./hotelDetailSidePeek.module.css" + +export default function HotelDetailSidePeek() { + const intl = useIntl() + const [isOpen, setIsOpen] = useState(false) + + function toggleSidePeek() { + setIsOpen(!isOpen) + } + + return ( + <> +
+ + +
+ setIsOpen(false)} + > +
TBD
+
+ + ) +} diff --git a/components/HotelReservation/HotelSelectionHeader/hotelSelectionHeader.module.css b/components/HotelReservation/HotelSelectionHeader/hotelSelectionHeader.module.css new file mode 100644 index 000000000..9646ec746 --- /dev/null +++ b/components/HotelReservation/HotelSelectionHeader/hotelSelectionHeader.module.css @@ -0,0 +1,52 @@ +.hotelSelectionHeader { + display: flex; + flex-direction: column; + background-color: var(--Base-Surface-Subtle-Normal); + padding: var(--Spacing-x3) var(--Spacing-x2); + justify-content: center; + gap: var(--Spacing-x3); +} + +.titleContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + gap: var(--Spacing-x1); +} + +.descriptionContainer { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-one-and-half); +} + +.address { + display: flex; + gap: var(--Spacing-x-one-and-half); + font-style: normal; +} + +.dividerContainer { + display: none; +} + +@media (min-width: 768px) { + .hotelSelectionHeader { + flex-direction: row; + padding: var(--Spacing-x4) var(--Spacing-x5); + gap: var(--Spacing-x6); + } + + .titleContainer > h1 { + white-space: nowrap; + } + + .dividerContainer { + display: block; + } + + .address { + gap: var(--Spacing-x3); + } +} diff --git a/components/HotelReservation/HotelSelectionHeader/index.tsx b/components/HotelReservation/HotelSelectionHeader/index.tsx new file mode 100644 index 000000000..c7e119b0a --- /dev/null +++ b/components/HotelReservation/HotelSelectionHeader/index.tsx @@ -0,0 +1,52 @@ +import Divider from "@/components/TempDesignSystem/Divider" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Title from "@/components/TempDesignSystem/Text/Title" +import { getIntl } from "@/i18n" + +import HotelDetailSidePeek from "./HotelDetailSidePeek" + +import styles from "./hotelSelectionHeader.module.css" + +import { HotelSelectionHeaderProps } from "@/types/components/hotelReservation/selectRate/hotelSelectionHeader" + +export default async function HotelSelectionHeader({ + hotel, +}: HotelSelectionHeaderProps) { + const intl = await getIntl() + + return ( +
+
+ + {hotel.name} + +
+ + {hotel.address.streetAddress}, {hotel.address.city} + +
+ +
+ + {intl.formatMessage( + { + id: "Distance to city centre", + }, + { number: hotel.location.distanceToCentre } + )} + +
+
+
+ +
+
+ + {hotel.hotelContent.texts.descriptions.short} + + +
+
+ ) +} diff --git a/components/TempDesignSystem/Text/Body/body.module.css b/components/TempDesignSystem/Text/Body/body.module.css index 238128edd..30939d217 100644 --- a/components/TempDesignSystem/Text/Body/body.module.css +++ b/components/TempDesignSystem/Text/Body/body.module.css @@ -76,6 +76,10 @@ color: var(--UI-Text-Medium-contrast); } +.textHighContrast { + color: var(--UI-Text-High-contrast); +} + .white { color: var(--UI-Opacity-White-100); } diff --git a/components/TempDesignSystem/Text/Body/variants.ts b/components/TempDesignSystem/Text/Body/variants.ts index e55bbc3e2..055710eb3 100644 --- a/components/TempDesignSystem/Text/Body/variants.ts +++ b/components/TempDesignSystem/Text/Body/variants.ts @@ -11,6 +11,7 @@ const config = { pale: styles.pale, red: styles.red, textMediumContrast: styles.textMediumContrast, + textHighContrast: styles.textHighContrast, white: styles.white, peach50: styles.peach50, peach80: styles.peach80, diff --git a/server/routers/hotels/tempHotelData.json b/server/routers/hotels/tempHotelData.json index d77cbf416..2ec39da33 100644 --- a/server/routers/hotels/tempHotelData.json +++ b/server/routers/hotels/tempHotelData.json @@ -9,19 +9,11 @@ "cityName": "Stockholm", "ratings": { "tripAdvisor": { - "numberOfReviews": 2776, - "rating": 4.0, - "ratingImageUrl": "https://www.tripadvisor.com/img/cdsi/img2/ratings/traveler/4.5-15458-5.svg", - "webUrl": "https://www.tripadvisor.com/Hotel_Review-g189852-d229383-Reviews-Scandic_Continental-Stockholm.html", + "numberOfReviews": 2793, + "rating": 4, + "ratingImageUrl": "https://www.tripadvisor.com/img/cdsi/img2/ratings/traveler/4.0-15458-5.svg", + "webUrl": "https://www.tripadvisor.com/Hotel_Review-g189852-d229383-Reviews-Scandic_Continental-Stockholm.html?m=15458", "awards": [ - { - "displayName": "Travelers Choice", - "images": { - "small": "https://static.tacdn.com/img2/travelers_choice/widgets/tchotel_2024_L.png", - "medium": "https://static.tacdn.com/img2/travelers_choice/widgets/tchotel_2024_L.png", - "large": "https://static.tacdn.com/img2/travelers_choice/widgets/tchotel_2024_L.png" - } - }, { "displayName": "Travelers Choice", "images": { @@ -30,12 +22,7 @@ "large": "https://static.tacdn.com/img2/travelers_choice/widgets/tchotel_2023_L.png" } } - ], - "reviews": { - "widgetHtmlTagId": "TA_cdspropertydetail", - "widgetScriptEmbedUrlIframe": "//www.tripadvisor.com/WidgetEmbed-cdspropertydetail?locationId=12441627&partnerId=FDF3F5CC73C349C0A5AB94C0DD86BB76&lang=en&display=true", - "widgetScriptEmbedUrlJavaScript": "//www.tripadvisor.com/WidgetEmbed-cdspropertydetail?locationId=12441627&partnerId=FDF3F5CC73C349C0A5AB94C0DD86BB76&lang=en&display=false" - } + ] } }, "address": { @@ -157,7 +144,7 @@ "facilityInformation": "Relax with your favorite mixed drink in the bar on our roof terrace. Work out in our gym or relax in our sauna. At Scandic Continental we offer meeting rooms for up to 900 participants.", "surroundingInformation": "Explore popular attractions such as the Old Town, the Opera, City Hall and Royal Palace. You are close to public transport, buses and trams and within walking distance of all that the city centre has to offer.", "descriptions": { - "short": "Scandic Continental enjoys a fantastic location in Stockholm city centre. Close to shopping areas, culture, attractions and restaurants. ", + "short": "Scandic Continental enjoys a fantastic location in Stockholm city centre. Close to shopping areas, culture, attractions and restaurants.", "medium": "Scandic Continental enjoys a fantastic location in Stockholm city centre. Close to shopping areas, culture, attractions and restaurants. Popular attractions such as the Old Town, the Opera, City Hall and Royal Palace are all close by." } }, @@ -172,190 +159,149 @@ { "id": 5550, "name": "Bikes for loan", - "applyToAllHotels": false, "public": true, - "icon": "BikesForLoan", - "sortOrder": 700 + "sortOrder": 700, + "filter": "None" }, { "id": 1829, "name": "Gym", - "code": "HEA - TRAI", - "applyToAllHotels": false, "public": true, - "icon": "Gym", - "iconName": "Gym", - "sortOrder": 1700 + "sortOrder": 1700, + "filter": "Hotel facilities" }, { "id": 1833, "name": "Free WiFi", - "code": "IHF", - "applyToAllHotels": false, "public": true, - "icon": "FreeWiFi", - "sortOrder": 1900 + "sortOrder": 1900, + "filter": "None" }, { "id": 1834, "name": "Laundry service", - "code": "LAU", - "applyToAllHotels": false, "public": true, - "icon": "LaundryService", - "sortOrder": 200 + "sortOrder": 200, + "filter": "None" }, { "id": 1406, "name": "Parking - additional cost", - "code": "PAR", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 2665, "name": "Parking - garage", - "code": "GAR", - "applyToAllHotels": false, "public": true, - "icon": "Parking", - "iconName": "Garage", - "sortOrder": 1400 + "sortOrder": 1400, + "filter": "Hotel facilities" }, { "id": 1835, "name": "Pet-friendly rooms", - "code": "PET", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "Hotel facilities" }, { "id": 1379, "name": "Sauna", - "code": "SAU - RELX", - "applyToAllHotels": false, "public": true, - "icon": "Sauna", - "sortOrder": 2000 + "sortOrder": 2000, + "filter": "Hotel facilities" }, { "id": 1017, "name": "Meeting rooms", - "code": "MEE", - "applyToAllHotels": false, "public": true, - "icon": "Meeting", - "sortOrder": 9000 + "sortOrder": 9000, + "filter": "None" }, { "id": 1382, "name": "Outdoor terrace", - "code": "-", - "applyToAllHotels": false, "public": true, - "icon": "OutdoorTerrace", - "sortOrder": 1000 + "sortOrder": 1000, + "filter": "Hotel facilities" }, { "id": 1408, "name": "Scandic Shop 24 hrs", - "code": "SHOP", - "applyToAllHotels": false, "public": true, - "icon": "Shop", - "sortOrder": 100 + "sortOrder": 100, + "filter": "None" }, { "id": 1606, "name": "Sky bar", - "code": "-", - "applyToAllHotels": false, "public": true, - "icon": "Skybar", - "iconName": "Sky Bar / Rooftop Bar", - "sortOrder": 1100 + "sortOrder": 1100, + "filter": "Hotel facilities" }, { "id": 5806, "name": "Meeting / conference facilities", - "code": "MEE - MEETING ", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 1500 + "sortOrder": 1500, + "filter": "None" }, { "id": 1607, "name": "Golf course (0-30 km)", - "code": "GOLF", - "applyToAllHotels": false, "public": false, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "Hotel surroundings" }, { "id": 971, "name": "Shopping", - "code": "-", - "applyToAllHotels": false, "public": false, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "Hotel surroundings" }, { "id": 1911, "name": "24 hours security", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 1913, "name": "Overnight security", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 162583, "name": "Laundry service - express", - "applyToAllHotels": false, "public": true, - "icon": "ExpressLaundryService", - "iconName": "Express dry cleaning", - "sortOrder": 300 + "sortOrder": 300, + "filter": "None" }, { "id": 229144, "name": "TV with Chromecast", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 1407, "name": "Serves breakfast (always included)", - "code": "-", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 1378, "name": "Room service", - "code": "ROO - R/S", - "applyToAllHotels": false, "public": true, - "icon": "RoomService", - "sortOrder": 400 + "sortOrder": 400, + "filter": "None" } ], "healthFacilities": [ @@ -1067,7 +1013,617 @@ "instagram": "https://www.instagram.com/scandiccontinental/", "facebook": "https://www.facebook.com/scandiccontinental/" }, - "isActive": true - } + "isActive": true, + "gallery": { + "heroImages": [ + { + "metaData": { + "title": "Superior plus, room", + "altText": "Superior plus, room", + "altText_En": "Superior plus, room", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/5rrxa1aq23taddapu11q/scandic-continental-room-superiorplus.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/66hti5xhqzmr8jhab213/scandic-continental-room-superiorplus.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/9lplgtij1oyjwaz93lrs/scandic-continental-room-superiorplus.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/f5bdchr9zeat67jri8kw/scandic-continental-room-superiorplus.jpg" + } + }, + { + "metaData": { + "title": "Entrance, restaurant Market", + "altText": "Entrance, restaurant Market", + "altText_En": "Entrance, restaurant Market", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/9qgydydjtavu128kv2xt/scandic-continental-entrance-themarket.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/0c0sz9g0r9hhejw62ez9/scandic-continental-entrance-themarket.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ka3ku5zqp5uuqpfjddot/scandic-continental-entrance-themarket.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/ksa5jgagck9sbj2uarz5/scandic-continental-entrance-themarket.jpg" + } + }, + { + "metaData": { + "title": "Terrace", + "altText": "Terrace", + "altText_En": "Terrace", + "copyRight": "Björn Enström" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/m9yj4g160snutij4sivk/Scandic_Continental_Capitol_Terrace.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/fqqakeunmamm3uetf47w/Scandic_Continental_Capitol_Terrace.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/6fp0avu8yjrh38zv1as8/Scandic_Continental_Capitol_Terrace.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/j8dveyubybb4f7or9qay/Scandic_Continental_Capitol_Terrace.jpg" + } + } + ], + "smallerImages": [ + { + "metaData": { + "title": "Entrance", + "altText": "Entrance from Vasagatan", + "altText_En": "Entrance from Vasagatan", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/jlnmjycubt2ks3oiaoee/scandic-continental-entrance-vasagatan.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/izgmvkaesxw9jynunuyo/scandic-continental-entrance-vasagatan.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/sfasl29ijndz2ez6lywn/scandic-continental-entrance-vasagatan.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/9vcvf9x9exykkl2yf0bg/scandic-continental-entrance-vasagatan.jpg" + } + }, + { + "metaData": { + "title": "Standard room", + "altText": "Standard room", + "altText_En": "Standard room", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/qj2en3cqvvadxe5y5b6y/scandic-continental-room-standard.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/16kz0cyxngrwe7880gjh/scandic-continental-room-standard.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/x8r0of336j2rsrhvwlqy/scandic-continental-room-standard.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/co3h6nr4p28j2tn641sz/scandic-continental-room-standard.jpg" + } + }, + { + "metaData": { + "title": "Junior suite", + "altText": "Junior suite, detail", + "altText_En": "Junior suite, detail", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/tijveokv1qmnmy964iv9/scandic-continental-room-juniorsuite-detail-1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/i7h3u2x6a9ta46jxb2tg/scandic-continental-room-juniorsuite-detail-1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/hv4on6ec8qlcxmcd0enl/scandic-continental-room-juniorsuite-detail-1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/3eakprn9n0zwyjpzpy6p/scandic-continental-room-juniorsuite-detail-1.jpg" + } + }, + { + "metaData": { + "title": "Junior Suite", + "altText": "Junior Suite", + "altText_En": "Junior Suite", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/7h4bonsezjsjz1lpg6hd/scandic-continental-room-juniorsuite.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/gge47e5ach2e0kuakggx/scandic-continental-room-juniorsuite.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/cts4y9u9w02ockyyoze7/scandic-continental-room-juniorsuite.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/f8aojtx38vb7ywv33cf6/scandic-continental-room-juniorsuite.jpg" + } + }, + { + "metaData": { + "title": "Superior plus, room", + "altText": "Superior plus, room", + "altText_En": "Superior plus, room", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/5rrxa1aq23taddapu11q/scandic-continental-room-superiorplus.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/66hti5xhqzmr8jhab213/scandic-continental-room-superiorplus.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/9lplgtij1oyjwaz93lrs/scandic-continental-room-superiorplus.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/f5bdchr9zeat67jri8kw/scandic-continental-room-superiorplus.jpg" + } + }, + { + "metaData": { + "title": "Superior room, detail", + "altText": "Superior room, detail", + "altText_En": "Superior room, detail", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/6jjitns74k9nutn9v9tz/scandic-continental-room-superior-detail.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ft3se6waag20lx6hxtn9/scandic-continental-room-superior-detail.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wsfl5usd8qbdm929dvxu/scandic-continental-room-superior-detail.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/duedba0io5xew6almv7l/scandic-continental-room-superior-detail.jpg" + } + }, + { + "metaData": { + "title": "Junior suite, bathroom", + "altText": "Junior suite, bathroom", + "altText_En": "Junior suite, bathroom", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/2cpdxrywepicqnp9eoob/scandic-continental-room-juniorsuite-bathroom-1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ynkzimfaldgef0ukj7q5/scandic-continental-room-juniorsuite-bathroom-1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/jrhqe8vbm91vegd02bjh/scandic-continental-room-juniorsuite-bathroom-1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/93ag11udgjefz6qwa8st/scandic-continental-room-juniorsuite-bathroom-1.jpg" + } + }, + { + "metaData": { + "title": "Entrance, restaurant Market", + "altText": "Entrance, restaurant Market", + "altText_En": "Entrance, restaurant Market", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/9qgydydjtavu128kv2xt/scandic-continental-entrance-themarket.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/0c0sz9g0r9hhejw62ez9/scandic-continental-entrance-themarket.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ka3ku5zqp5uuqpfjddot/scandic-continental-entrance-themarket.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/ksa5jgagck9sbj2uarz5/scandic-continental-entrance-themarket.jpg" + } + }, + { + "metaData": { + "title": "Dining room, the Market", + "altText": "Dining room, the Market", + "altText_En": "Dining room, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/bywlg4nwp11hmebfb1wg/scandic-continental-diningroom-themarket.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/fdvp429hm1rfpg2le6jo/scandic-continental-diningroom-themarket.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wm24ma9ngk8gijotm7va/scandic-continental-diningroom-themarket.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/7xiz9p0f254yl9oef7bj/scandic-continental-diningroom-themarket.jpg" + } + }, + { + "metaData": { + "title": "Dining room, the Market", + "altText": "Dining room, the Market", + "altText_En": "Dining room, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/zd9rkkm383ctblciw0np/scandic-continental-diningroom-themarket-2.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/i9giiq3ijihm172b5bbw/scandic-continental-diningroom-themarket-2.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/9baixkat8wjdsjn1lo34/scandic-continental-diningroom-themarket-2.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/kwemrr8fohnnwo0uzd3s/scandic-continental-diningroom-themarket-2.jpg" + } + }, + { + "metaData": { + "title": "Restaurant, the Market, detail", + "altText": "Restaurant, the Market, detail", + "altText_En": "Restaurant, the Market, detail", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/uo7e32pi5cmf06as55ad/scandic-continental-themarket-detail.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/1qhpesv8d407j00b1y5x/scandic-continental-themarket-detail.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ldenlssse8595b4iavjj/scandic-continental-themarket-detail.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/yhs5jgy8mgw9xvzazjze/scandic-continental-themarket-detail.jpg" + } + }, + { + "metaData": { + "title": "Breakfast, the Market", + "altText": "Breakfast, the Market", + "altText_En": "Breakfast, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/vsj1725jfae5eqx9urua/scandic-continental-breakfast-themarket-15.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ajqxe5obci19sw4g08ub/scandic-continental-breakfast-themarket-15.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wi3xa47bwtb5s5kbaxq8/scandic-continental-breakfast-themarket-15.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/1igemz65ffu4nw7bg64l/scandic-continental-breakfast-themarket-15.jpg" + } + }, + { + "metaData": { + "title": "Breakfast, the Market", + "altText": "Breakfast, the Market", + "altText_En": "Breakfast, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/1clcvo8e86itpus3xy99/scandic-continental-breakfast-themarket-9.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/on5ckcmwxvs47l11natn/scandic-continental-breakfast-themarket-9.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/l5g6dmumzh9duseaa9zw/scandic-continental-breakfast-themarket-9.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/pl45n94xmu19bo0fokwf/scandic-continental-breakfast-themarket-9.jpg" + } + }, + { + "metaData": { + "title": "Restaurant Caldo", + "altText": "Restaurant Caldo", + "altText_En": "Restaurant Caldo", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/q5cj5qehiq03ey38ppen/scandic-continental-caldo.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/na08m2sippkfzd3yf9le/scandic-continental-caldo.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wxh6w02ujd5wqyr9bvg8/scandic-continental-caldo.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/bem9c6dxappvlwxg7s1g/scandic-continental-caldo.jpg" + } + }, + { + "metaData": { + "title": "Roof top bar", + "altText": "Roof top bar at Scandic Continental", + "altText_En": "Roof top bar at Scandic Continental", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/cxf1z9sgr9152ls1kdmg/Scandic_Continental_Capitol_The_View_13.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/pd43w13z5dx8l7kzhsyg/Scandic_Continental_Capitol_The_View_13.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/7ryqztmdqg0mam62oyf5/Scandic_Continental_Capitol_The_View_13.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/hwpsq70dbln0l052cjqa/Scandic_Continental_Capitol_The_View_13.jpg" + } + }, + { + "metaData": { + "title": "Roof top bar", + "altText": "Roof top bar at Scandic Continental", + "altText_En": "Roof top bar at Scandic Continental", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/hd1wk1wvtwsz4fuvf0rb/Scandic_Continental_Capitol_The_View_6.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ioi4ir6ijpy0f5dvu56n/Scandic_Continental_Capitol_The_View_6.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ydbhbjj3ulju2bxh7ebj/Scandic_Continental_Capitol_The_View_6.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/kom0ttrq6xazxxuodrr1/Scandic_Continental_Capitol_The_View_6.jpg" + } + }, + { + "metaData": { + "title": "Terrace", + "altText": "Terrace", + "altText_En": "Terrace", + "copyRight": "Björn Enström" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/m9yj4g160snutij4sivk/Scandic_Continental_Capitol_Terrace.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/fqqakeunmamm3uetf47w/Scandic_Continental_Capitol_Terrace.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/6fp0avu8yjrh38zv1as8/Scandic_Continental_Capitol_Terrace.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/j8dveyubybb4f7or9qay/Scandic_Continental_Capitol_Terrace.jpg" + } + }, + { + "metaData": { + "title": "Terrace", + "altText": "Terrace", + "altText_En": "Terrace", + "copyRight": "Björn Enström" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/dsm1ief68fx4b6j5673x/Scandic_Continental_Capitol_Terrace.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/pm1lxtm8zf1ejd0qtko4/Scandic_Continental_Capitol_Terrace.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wwgkqa2fr5z223ybdemr/Scandic_Continental_Capitol_Terrace.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/aztwaanmwiwk5s0tc36w/Scandic_Continental_Capitol_Terrace.jpg" + } + }, + { + "metaData": { + "title": "Rooftop terrace", + "altText": "Rooftop terrace", + "altText_En": "Rooftop terrace", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/m4mhmwyynuf65ls1cybu/scandic-continental-rooftop-terrace-capital-4.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/phsnpd122oq7e6hszsc7/scandic-continental-rooftop-terrace-capital-4.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ty9gb8ogkq5iqszke7pe/scandic-continental-rooftop-terrace-capital-4.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/ug0co0ks7v25avze1vi3/scandic-continental-rooftop-terrace-capital-4.jpg" + } + }, + { + "metaData": { + "title": "View", + "altText": "View", + "altText_En": "View", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/gs1lqt13ptscg4audxgo/scandic-continental-theview-detail-1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/otr9i3f4q8084ggfwnm1/scandic-continental-theview-detail-1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/w8pkgf9igqibmlzp9egl/scandic-continental-theview-detail-1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/9yujqdk34x6juk6fstks/scandic-continental-theview-detail-1.jpg" + } + }, + { + "metaData": { + "title": "View", + "altText": "View", + "altText_En": "View", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/autuf074xvc2xr3zmxyf/scandic-continental-theview-detail-2.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/hyf3fjkreqw2rlx4wm4e/scandic-continental-theview-detail-2.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/3wezw0ftkli48oloekdb/scandic-continental-theview-detail-2.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/ygxz2q45b4gcgscqdzaw/scandic-continental-theview-detail-2.jpg" + } + }, + { + "metaData": { + "title": "Work out equipment at the terrace", + "altText": "Work out equipment at the terrace", + "altText_En": "Work out equipment at the terrace", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/oe32sgg1pudiwfd9hjpa/Scandic_Continental_Terrace_Workout_1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/1g9d62gqkyvfky85kgag/Scandic_Continental_Terrace_Workout_1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/eb9n7j8s9scwg89ac4lk/Scandic_Continental_Terrace_Workout_1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/cyrtt6bmy11y1wti2d9h/Scandic_Continental_Terrace_Workout_1.jpg" + } + }, + { + "metaData": { + "title": "Meeting room", + "altText": "", + "altText_En": "", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/zbc3bhdzs59sqsvm9v29/scandic-continental-conference-9.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/m2nh4f57ys3bcxuq27hb/scandic-continental-conference-9.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wk39k84g3nrivntzuww2/scandic-continental-conference-9.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/yn1mzw6nbyl90dreiz6b/scandic-continental-conference-9.jpg" + } + }, + { + "metaData": { + "title": "Meeting room", + "altText": "", + "altText_En": "", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/mfq1ayjplo7keqxm88ng/scandic-continental-conference-3.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/wzjl4n8qk4imfgla7r73/scandic-continental-conference-3.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wf6lg2u4bz1f9ntkszuz/scandic-continental-conference-3.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/uh83sqrope55u6q4hbhl/scandic-continental-conference-3.jpg" + } + }, + { + "metaData": { + "title": "Meeting room ", + "altText": "Meeting room ", + "altText_En": "Meeting room ", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/z00ei7j974hx7c9vp8a6/Scandic_Continental_meetingroom_15-16-17-18-0357.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/v64n5ne3ayfo8v3q9x2s/Scandic_Continental_meetingroom_15-16-17-18-0357.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/mtx54l82hr3o3m7k0vqi/Scandic_Continental_meetingroom_15-16-17-18-0357.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/3xr7ovgdjxq5taz8nzma/Scandic_Continental_meetingroom_15-16-17-18-0357.jpg" + } + }, + { + "metaData": { + "title": "Breakout area ", + "altText": "Breakout area ", + "altText_En": "Breakout area ", + "copyRight": "Karl Gabor " + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/e84l4tyafvmx6epe0c3d/Scandic_Continental_Breakout_3-0432.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/60im9a4yre1kg5me9riz/Scandic_Continental_Breakout_3-0432.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/nsfh80un5l7qv9y9i1iw/Scandic_Continental_Breakout_3-0432.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/pxe0dozr1joaqig3cbf2/Scandic_Continental_Breakout_3-0432.jpg" + } + }, + { + "metaData": { + "title": "Breakout area ", + "altText": "Breakout area ", + "altText_En": "Breakout area ", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/fpyspgc9op7wb4ay3083/Scandic_Continental_breakout2-0391.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/2ueu1qe77bdqkj682xvq/Scandic_Continental_breakout2-0391.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/xmaaxzi1qrlwkm2gbl0z/Scandic_Continental_breakout2-0391.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/px12tarpgqq31cuz512z/Scandic_Continental_breakout2-0391.jpg" + } + }, + { + "metaData": { + "title": "Meeting room ", + "altText": "Meeting room ", + "altText_En": "Meeting room ", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/ssrltn03vl7layrr5wgk/Scandic_Continental_Meetingroom_15_16_17_18.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/jozqcjuo1dpd9gyqjcyo/Scandic_Continental_Meetingroom_15_16_17_18.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/vdinjqa1j09av33z6wss/Scandic_Continental_Meetingroom_15_16_17_18.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/gq5lhbwrjvvo6otkgi9z/Scandic_Continental_Meetingroom_15_16_17_18.jpg" + } + }, + { + "metaData": { + "title": "Gym", + "altText": "Gym of Scandic Continental in Stockholm", + "altText_En": "Gym of Scandic Continental in Stockholm", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/ic6077yovsoxdmj9545r/scandic_continental_gym.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/uhhqrnb8wzy9xlr42zn8/scandic_continental_gym.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/iqor5zd7owsll42z59vh/scandic_continental_gym.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/5v5o0bbopf9t7r95sp2o/scandic_continental_gym.jpg" + } + } + ] + }, + "conferencesAndMeetings": { + "headingText": "Meetings, conferences & events", + "pageUrl": "https://test3.scandichotels.com/hotels/sweden/stockholm/scandic-continental/meetings-conferences-events", + "heroImages": [ + { + "metaData": { + "title": "Roof top bar", + "altText": "Roof bar of Scandic Continental in Stockholm", + "altText_En": "Roof bar of Scandic Continental in Stockholm", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/086xasvs4lq29x4oqvbq/Scandic_Continental_Capitol_The_View_2.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/rgrcj8ed9mhecp6hpcuq/Scandic_Continental_Capitol_The_View_2.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/gklysd2pkx9gi0nx1mqu/Scandic_Continental_Capitol_The_View_2.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/sjkd7r6mw6xxhwzkkqvk/Scandic_Continental_Capitol_The_View_2.jpg" + } + } + ] + }, + "healthAndWellness": { + "headingText": "Gym and health", + "heroImages": [ + { + "metaData": { + "title": "Gym", + "altText": "Gym of Scandic Continental in Stockholm", + "altText_En": "Gym of Scandic Continental in Stockholm", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/ic6077yovsoxdmj9545r/scandic_continental_gym.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/uhhqrnb8wzy9xlr42zn8/scandic_continental_gym.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/iqor5zd7owsll42z59vh/scandic_continental_gym.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/5v5o0bbopf9t7r95sp2o/scandic_continental_gym.jpg" + } + }, + { + "metaData": { + "title": "Work out equipment at the terrace", + "altText": "Work out equipment at the terrace", + "altText_En": "Work out equipment at the terrace", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/oe32sgg1pudiwfd9hjpa/Scandic_Continental_Terrace_Workout_1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/1g9d62gqkyvfky85kgag/Scandic_Continental_Terrace_Workout_1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/eb9n7j8s9scwg89ac4lk/Scandic_Continental_Terrace_Workout_1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/cyrtt6bmy11y1wti2d9h/Scandic_Continental_Terrace_Workout_1.jpg" + } + } + ] + }, + "accessibilityElevatorPitchText": "Find the information you might need, before visiting us. You are always welcome to our hotel - completely without barriers. Regardless of impairment, sight, hearing, allergies or wheelchair, we have made sure that you enjoy your stay.", + "merchantInformationData": { + "webMerchantId": "1110009031", + "cards": { + "americanExpress": true, + "dankort": false, + "dinersClub": true, + "jcb": true, + "masterCard": true, + "visa": true, + "maestro": false, + "chinaUnionPay": true, + "discover": true + }, + "alternatePaymentOptions": { + "swish": true, + "vipps": false, + "mobilePay": true, + "applePay": true, + "alipayPlus": false, + "googlePay": true, + "klarna": false, + "payPal": false, + "weChatPay": false + } + }, + "restaurantImages": { + "headingText": "Bar and breakfast", + "heroImages": [ + { + "metaData": { + "title": "Dining room, the Market", + "altText": "Dining room, the Market", + "altText_En": "Dining room, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/bywlg4nwp11hmebfb1wg/scandic-continental-diningroom-themarket.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/fdvp429hm1rfpg2le6jo/scandic-continental-diningroom-themarket.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wm24ma9ngk8gijotm7va/scandic-continental-diningroom-themarket.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/7xiz9p0f254yl9oef7bj/scandic-continental-diningroom-themarket.jpg" + } + }, + { + "metaData": { + "title": "Rooftop bar", + "altText": "Rooftop bar", + "altText_En": "Rooftop bar", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/ntwor0b4i90sp9sh5qbj/scandic-continental-rooftopbar-capital.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ivxowx9eunbyz68qkstd/scandic-continental-rooftopbar-capital.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/6pi2262z6lo0un7x7iik/scandic-continental-rooftopbar-capital.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/6rlwgo7lq198xg7ifii6/scandic-continental-rooftopbar-capital.jpg" + } + }, + { + "metaData": { + "title": "Terrace", + "altText": "Bar of Scandic Continental in Stockholm", + "altText_En": "Bar of Scandic Continental in Stockholm", + "copyRight": "Björn Enström" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/69jl573jtmw8g4jjtugo/Scandic_Continental_Capitol_Terrace.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/td5tz4ld37kwws6rpnye/Scandic_Continental_Capitol_Terrace.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/mlrdylyxf3m9fr5r9l7x/Scandic_Continental_Capitol_Terrace.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/dywcmnaamp0altjge9qx/Scandic_Continental_Capitol_Terrace.jpg" + } + } + ] + } + }, + "relationships": { + "restaurants": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/restaurants?language=En" + } + }, + "nearbyHotels": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/nearbyHotels?language=En" + } + }, + "roomCategories": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/roomCategories?language=En" + } + }, + "meetingRooms": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/meetingRooms?language=En" + } + }, + "merchantInformation": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/merchantInformation?language=En" + } + } + }, + "id": "811", + "language": "En", + "type": "hotels" } } diff --git a/types/components/hotelReservation/selectRate/hotelSelectionHeader.ts b/types/components/hotelReservation/selectRate/hotelSelectionHeader.ts new file mode 100644 index 000000000..9804e0424 --- /dev/null +++ b/types/components/hotelReservation/selectRate/hotelSelectionHeader.ts @@ -0,0 +1,5 @@ +import { Hotel } from "@/types/hotel" + +export type HotelSelectionHeaderProps = { + hotel: Hotel +} From 105f721dc953e062bbb02b140f99ca324408c4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20Landstr=C3=B6m?= Date: Wed, 2 Oct 2024 15:08:05 +0200 Subject: [PATCH 30/30] fix: "as of today" translation corrections --- i18n/dictionaries/da.json | 4 ++-- i18n/dictionaries/no.json | 4 ++-- i18n/dictionaries/sv.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 35b5e759a..50c95105a 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -263,7 +263,7 @@ "Zoo": "Zoo", "Zoom in": "Zoom ind", "Zoom out": "Zoom ud", - "as of today": "fra idag", + "as of today": "pr. dags dato", "booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}", "by": "inden", "characters": "tegn", @@ -281,4 +281,4 @@ "spendable points expiring by": "{points} Brugbare point udløber den {date}", "to": "til", "uppercase letter": "stort bogstav" -} \ No newline at end of file +} diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index ed600ecb5..39dda59c3 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -263,7 +263,7 @@ "Zoo": "Dyrehage", "Zoom in": "Zoom inn", "Zoom out": "Zoom ut", - "as of today": "per idag", + "as of today": "per i dag", "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", "by": "innen", "characters": "tegn", @@ -281,4 +281,4 @@ "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", "to": "til", "uppercase letter": "stor bokstav" -} \ No newline at end of file +} diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 9f3f71511..e2205b7b6 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -263,7 +263,7 @@ "Zoo": "Djurpark", "Zoom in": "Zooma in", "Zoom out": "Zooma ut", - "as of today": "från och med idag", + "as of today": "per idag", "booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}", "by": "innan", "characters": "tecken", @@ -281,4 +281,4 @@ "spendable points expiring by": "{points} poäng förfaller {date}", "to": "till", "uppercase letter": "stor bokstav" -} \ No newline at end of file +}