diff --git a/.env.local.example b/.env.local.example index 36561372b..e939323ef 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,6 +1,7 @@ ADOBE_SCRIPT_SRC="" API_BASEURL="https://tstapi.scandichotels.com" CMS_ACCESS_TOKEN="" +CMS_BRANCH="development" CMS_API_KEY="" CMS_ENVIRONMENT="development" CMS_PREVIEW_TOKEN="" diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/loading.tsx b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/loading.tsx deleted file mode 100644 index c739b6635..000000000 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import LoadingSpinner from "@/components/LoadingSpinner" - -export default function Loading() { - return -} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.module.css index bb1cf59f0..bba79b2d0 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.module.css @@ -1,7 +1,42 @@ .main { + background-color: var(--Base-Surface-Primary-light-Normal); + display: grid; + gap: var(--Spacing-x5); + grid-template-areas: "header" "booking"; + margin: 0 auto; + min-height: 100dvh; + padding-top: var(--Spacing-x5); + width: var(--max-width-page); +} + +.booking { display: flex; flex-direction: column; gap: var(--Spacing-x5); - margin: 0 auto; - width: min(calc(100dvw - (var(--Spacing-x3) * 2)), 948px); + grid-area: booking; + padding-bottom: var(--Spacing-x9); +} + +.aside { + display: none; +} + +@media screen and (min-width: 1367px) { + .main { + grid-template-areas: + "header receipt" + "booking receipt"; + grid-template-columns: 1fr 340px; + grid-template-rows: auto 1fr; + padding-top: var(--Spacing-x9); + } + + .mobileReceipt { + display: none; + } + + .aside { + display: grid; + grid-area: receipt; + } } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx index ab15f040c..c8096f3e6 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx @@ -1,8 +1,16 @@ +import { Suspense } from "react" + import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" -import Details from "@/components/HotelReservation/BookingConfirmation/Details" import Header from "@/components/HotelReservation/BookingConfirmation/Header" -import TotalPrice from "@/components/HotelReservation/BookingConfirmation/TotalPrice" +import HotelDetails from "@/components/HotelReservation/BookingConfirmation/HotelDetails" +import PaymentDetails from "@/components/HotelReservation/BookingConfirmation/PaymentDetails" +import Promos from "@/components/HotelReservation/BookingConfirmation/Promos" +import Receipt from "@/components/HotelReservation/BookingConfirmation/Receipt" +import Rooms from "@/components/HotelReservation/BookingConfirmation/Rooms" +import SidePanel from "@/components/HotelReservation/SidePanel" +import LoadingSpinner from "@/components/LoadingSpinner" +import Divider from "@/components/TempDesignSystem/Divider" import { setLang } from "@/i18n/serverContext" import styles from "./page.module.css" @@ -18,10 +26,27 @@ export default async function BookingConfirmationPage({ const { confirmationNumber } = searchParams return ( -
-
-
- -
+
+ }> +
+
+ + + + + +
+ +
+
+ + +
) } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/layout.module.css b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/layout.module.css deleted file mode 100644 index 3f89e6f51..000000000 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/layout.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.layout { - background-color: var(--Base-Surface-Primary-light-Normal); - min-height: 100dvh; - padding: 80px 0 160px; -} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/layout.tsx deleted file mode 100644 index 5f59de245..000000000 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { notFound } from "next/navigation" - -import { env } from "@/env/server" - -import styles from "./layout.module.css" - -// route groups needed as layouts have different bgc -export default function ConfirmedBookingLayout({ - children, -}: React.PropsWithChildren) { - return
{children}
-} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/loading.tsx b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/loading.tsx deleted file mode 100644 index 92ff5739e..000000000 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import LoadingSpinner from "@/components/LoadingSpinner" - -export default function Loading() { - return -} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.tsx index f8fefdb5b..056db9936 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/layout.tsx @@ -1,7 +1,3 @@ -import { notFound } from "next/navigation" - -import { env } from "@/env/server" - import styles from "./layout.module.css" import { LangParams, LayoutArgs } from "@/types/params" diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/layout.tsx index b846b3ec8..66352fe80 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/layout.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/layout.tsx @@ -1,7 +1,3 @@ -import { notFound } from "next/navigation" - -import { env } from "@/env/server" - import styles from "./layout.module.css" import { LangParams, LayoutArgs } from "@/types/params" @@ -12,9 +8,6 @@ export default function HotelReservationLayout({ }: React.PropsWithChildren> & { sidePeek: React.ReactNode }) { - if (!env.ENABLE_BOOKING_FLOW) { - return notFound() - } return (
{children} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx index ca12f369a..980522754 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx @@ -17,6 +17,7 @@ import { setLang } from "@/i18n/serverContext" import { fetchAvailableHotels, getFiltersFromHotels } from "../../utils" +import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" import { TrackingChannelEnum, @@ -59,6 +60,10 @@ export default async function SelectHotelMapPage({ children, }) + const validHotels = hotels.filter( + (hotel): hotel is HotelData => hotel !== null + ) + const arrivalDate = new Date(searchParams.fromDate) const departureDate = new Date(searchParams.toDate) @@ -85,15 +90,15 @@ export default async function SelectHotelMapPage({ leadTime: differenceInCalendarDays(arrivalDate, new Date()), searchType: "destination", bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday", - country: hotels?.[0].hotelData.address.country, - region: hotels?.[0].hotelData.address.city, + country: validHotels?.[0].hotelData.address.country, + region: validHotels?.[0].hotelData.address.city, } - const hotelPins = getHotelPins(hotels) - const filterList = getFiltersFromHotels(hotels) + const hotelPins = getHotelPins(validHotels) + const filterList = getFiltersFromHotels(validHotels) const cityCoordinates = await getCityCoordinates({ city: city.name, - hotel: { address: hotels[0].hotelData.address.streetAddress }, + hotel: { address: hotels?.[0]?.hotelData?.address.streetAddress }, }) return ( @@ -102,7 +107,7 @@ export default async function SelectHotelMapPage({ apiKey={googleMapsApiKey} hotelPins={hotelPins} mapId={googleMapId} - hotels={hotels} + hotels={validHotels} filterList={filterList} cityCoordinates={cityCoordinates} /> diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index fbc596552..2f8f21e37 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -36,6 +36,7 @@ import { setLang } from "@/i18n/serverContext" import styles from "./page.module.css" +import type { HotelData } from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" import { TrackingChannelEnum, @@ -69,6 +70,14 @@ export default async function SelectHotelPage({ const selectHotelParams = new URLSearchParams(searchParams) const selectHotelParamsObject = getHotelReservationQueryParams(selectHotelParams) + + if ( + !selectHotelParamsObject.room || + selectHotelParamsObject.room.length === 0 + ) { + return notFound() + } + const adults = selectHotelParamsObject.room[0].adults // TODO: Handle multiple rooms const child = selectHotelParamsObject.room[0].child // TODO: Handle multiple rooms const children = child ? generateChildrenString(child) : undefined @@ -84,7 +93,11 @@ export default async function SelectHotelPage({ const arrivalDate = new Date(searchParams.fromDate) const departureDate = new Date(searchParams.toDate) - const filterList = getFiltersFromHotels(hotels) + const validHotels = hotels.filter( + (hotel): hotel is HotelData => hotel !== null + ) + + const filterList = getFiltersFromHotels(validHotels) const breadcrumbs = [ { title: intl.formatMessage({ id: "Home" }), @@ -132,8 +145,8 @@ export default async function SelectHotelPage({ leadTime: differenceInCalendarDays(arrivalDate, new Date()), searchType: "destination", bookingTypeofDay: isWeekend(arrivalDate) ? "weekend" : "weekday", - country: hotels?.[0].hotelData.address.country, - region: hotels?.[0].hotelData.address.city, + country: validHotels?.[0].hotelData.address.country, + region: validHotels?.[0].hotelData.address.city, } return ( @@ -206,7 +219,7 @@ export default async function SelectHotelPage({ })} /> )} - +
{ +): Promise { const availableHotels = await serverClient().hotel.availability.hotels(input) - if (!availableHotels) throw new Error() + if (!availableHotels) return [] const language = getLang() @@ -43,7 +46,7 @@ export async function fetchAvailableHotels( language, }) - if (!hotelData) throw new Error() + if (!hotelData) return { hotelData: null, price: hotel.productType } return { hotelData: hotelData.data.attributes, @@ -55,7 +58,13 @@ export async function fetchAvailableHotels( } export function getFiltersFromHotels(hotels: HotelData[]): CategorizedFilters { - const filters = hotels.flatMap((hotel) => hotel.hotelData.detailedFacilities) + if (hotels.length === 0) + return { facilityFilters: [], surroundingsFilters: [] } + + const filters = hotels.flatMap((hotel) => { + if (!hotel.hotelData) return [] + return hotel.hotelData.detailedFacilities + }) const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))] const filterList: Filter[] = uniqueFilterIds diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.module.css index 5c757de70..d807e2633 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.module.css @@ -4,7 +4,8 @@ } .content { - margin: var(--Spacing-x3) var(--Spacing-x2) 0; + width: var(--max-width-page); + margin: var(--Spacing-x3) auto 0; } .summary { @@ -16,14 +17,17 @@ @media screen and (min-width: 1367px) { .container { + width: var(--max-width-page); + grid-template-columns: 1fr 340px; grid-template-rows: auto 1fr; margin: var(--Spacing-x5) auto 0; /* simulates padding on viewport smaller than --max-width-navigation */ - width: min( - calc(100dvw - (var(--Spacing-x2) * 2)), - var(--max-width-navigation) - ); + } + + .content { + width: 100%; + margin: var(--Spacing-x3) 0 0; } .summary { diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx index 16e382f2a..d8a86d6ed 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx @@ -19,7 +19,8 @@ import HistoryStateManager from "@/components/HotelReservation/EnterDetails/Hist import Payment from "@/components/HotelReservation/EnterDetails/Payment" import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion" import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom" -import Summary from "@/components/HotelReservation/EnterDetails/Summary" +import DesktopSummary from "@/components/HotelReservation/EnterDetails/Summary/Desktop" +import MobileSummary from "@/components/HotelReservation/EnterDetails/Summary/Mobile" import { generateChildrenString, getQueryParamsForEnterDetails, @@ -182,6 +183,13 @@ export default async function StepPage({ //lowestRoomPrice: } + const summary = { + cancellationText: roomAvailability.cancellationText, + isMember: !!user, + rateDetails: roomAvailability.rateDetails, + roomType: roomAvailability.selectedRoom.roomType, + } + return ( diff --git a/app/[lang]/(live)/(public)/hotelreservation/layout.tsx b/app/[lang]/(live)/(public)/hotelreservation/layout.tsx new file mode 100644 index 000000000..a8fd03992 --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/layout.tsx @@ -0,0 +1,12 @@ +import { notFound } from "next/navigation" + +import { env } from "@/env/server" + +export default function HotelReservationLayout({ + children, +}: React.PropsWithChildren) { + if (!env.ENABLE_BOOKING_FLOW) { + return notFound() + } + return <>{children} +} diff --git a/app/[lang]/(live)/error.tsx b/app/[lang]/(live)/error.tsx index 1b1970475..a39495eb7 100644 --- a/app/[lang]/(live)/error.tsx +++ b/app/[lang]/(live)/error.tsx @@ -1,7 +1,12 @@ "use client" // Error components must be Client Components -import { useParams, usePathname } from "next/navigation" -import { useEffect } from "react" +import { + useParams, + usePathname, + useRouter, + useSearchParams, +} from "next/navigation" +import { startTransition, useEffect, useRef } from "react" import { useIntl } from "react-intl" import { login } from "@/constants/routes/handleAuth" @@ -15,11 +20,17 @@ import { LangParams } from "@/types/params" export default function Error({ error, + reset, }: { error: Error & { digest?: string } + reset: () => void }) { const intl = useIntl() const params = useParams() + const router = useRouter() + const searchParams = useSearchParams() + const currentSearchParamsRef = useRef() + const isFirstLoadRef = useRef(true) useEffect(() => { // Log the error to an error reporting service @@ -31,6 +42,23 @@ export default function Error({ } }, [error, params.lang]) + useEffect(() => { + // This is to reset the error and refresh the page when the search params change, to support the booking widget that is using router.push to navigate to the booking flow page + const currentSearchParams = searchParams.toString() + + if ( + currentSearchParamsRef.current !== currentSearchParams && + !isFirstLoadRef.current + ) { + startTransition(() => { + reset() + router.refresh() + }) + } + isFirstLoadRef.current = false + currentSearchParamsRef.current = currentSearchParams + }, [searchParams, reset, router]) + const pathname = usePathname() const lang = findLang(pathname) diff --git a/app/globals.css b/app/globals.css index a266e9f31..a2d667892 100644 --- a/app/globals.css +++ b/app/globals.css @@ -105,6 +105,12 @@ --current-mobile-site-header-height: 70.047px; --max-width-navigation: 89.5rem; + --max-width-spacing: calc(var(--Layout-Mobile-Margin-Margin-min) * 2); + --max-width-page: min( + calc(100dvw - var(--max-width-spacing)), + var(--max-width-navigation) + ); + --main-menu-mobile-height: 75px; --main-menu-desktop-height: 125px; --booking-widget-mobile-height: 75px; @@ -140,15 +146,26 @@ body { body.overflow-hidden { overflow: hidden; } -@media screen and (min-width: 768px) { - body.overflow-hidden { - overflow: auto; - overflow-x: hidden; - } -} ul { padding-inline-start: 0; margin-block-start: 0; margin-block-end: 0; } + +@media screen and (min-width: 768px) { + :root { + --max-width-spacing: calc(var(--Layout-Tablet-Margin-Margin-min) * 2); + } + + body.overflow-hidden { + overflow: auto; + overflow-x: hidden; + } +} + +@media screen and (min-width: 1367px) { + :root { + --max-width-spacing: calc(var(--Layout-Desktop-Margin-Margin-min) * 2); + } +} diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx index f4bf137a4..d7e4d8a19 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -10,7 +10,7 @@ import { getLang } from "@/i18n/serverContext" import styles from "./contactInformation.module.css" -import type { ContactInformationProps } from "@/types/components/hotelPage/sidepeek/contactInformation" +import type { ContactInformationProps } from "@/types/components/hotelPage/sidepeek/aboutTheHotel" export default async function ContactInformation({ hotelAddress, @@ -21,7 +21,6 @@ export default async function ContactInformation({ }: ContactInformationProps) { const intl = await getIntl() const lang = getLang() - const { latitude, longitude } = coordinates const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}` diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx index 436147a50..0537fda83 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx @@ -37,7 +37,7 @@ export default async function AboutTheHotelSidePeek({ socials={socials} ecoLabels={ecoLabels} /> - + {descriptions.descriptions.medium} {descriptions.facilityInformation} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Accessibility/accessibilityAmenity.module.css b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Accessibility/accessibilityAmenity.module.css new file mode 100644 index 000000000..00ab8aebe --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Accessibility/accessibilityAmenity.module.css @@ -0,0 +1,5 @@ +.wrapper { + display: flex; + flex-direction: column; + gap: var(--Spacing-x3); +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Accessibility/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Accessibility/index.tsx new file mode 100644 index 000000000..5313bb50a --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Accessibility/index.tsx @@ -0,0 +1,39 @@ +import { ArrowRightIcon } from "@/components/Icons" +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" +import { getIntl } from "@/i18n" + +import styles from "./accessibilityAmenity.module.css" + +import type { AccessibilityAmenityProps } from "@/types/components/hotelPage/sidepeek/accessibility" +import { IconName } from "@/types/components/icon" + +export default async function AccessibilityAmenity({ + accessibility, +}: AccessibilityAmenityProps) { + const intl = await getIntl() + return ( + +
+ {accessibility?.description && ( + {accessibility.description} + )} + {accessibility?.link && ( + + {intl.formatMessage({ id: "About accessibility" })} + + + )} +
+
+ ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Breakfast/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Breakfast/index.tsx new file mode 100644 index 000000000..c731bb86a --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Breakfast/index.tsx @@ -0,0 +1,16 @@ +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import { getIntl } from "@/i18n" + +import { IconName } from "@/types/components/icon" + +export default async function BreakfastAmenity() { + const intl = await getIntl() + return ( + + {/* TODO: breakfast to be implemented */} + + ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/CheckIn/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/CheckIn/index.tsx new file mode 100644 index 000000000..21cc2b964 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/CheckIn/index.tsx @@ -0,0 +1,23 @@ +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Body from "@/components/TempDesignSystem/Text/Body" +import { getIntl } from "@/i18n" + +import type { CheckInAmenityProps } from "@/types/components/hotelPage/sidepeek/checkIn" +import { IconName } from "@/types/components/icon" + +export default async function CheckInAmenity({ + checkInInformation, +}: CheckInAmenityProps) { + const intl = await getIntl() + const { checkInTime, checkOutTime } = checkInInformation + return ( + + {intl.formatMessage({ id: "Times" })} + {`${intl.formatMessage({ id: "Check in from" })}: ${checkInTime}`} + {`${intl.formatMessage({ id: "Check out at latest" })}: ${checkOutTime}`} + + ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingList/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingList/index.tsx new file mode 100644 index 000000000..dd858d9b7 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingList/index.tsx @@ -0,0 +1,50 @@ +import Body from "@/components/TempDesignSystem/Text/Body" +import { getIntl } from "@/i18n" + +import styles from "./parkingList.module.css" + +import type { ParkingListProps } from "@/types/components/hotelPage/sidepeek/parking" + +export default async function ParkingList({ + numberOfChargingSpaces, + canMakeReservation, + numberOfParkingSpots, + distanceToHotel, + address, +}: ParkingListProps) { + const intl = await getIntl() + return ( + +
    + {numberOfChargingSpaces ? ( +
  • + {intl.formatMessage( + { id: "Number of charging points for electric cars" }, + { number: numberOfChargingSpaces } + )} +
  • + ) : null} +
  • {`${intl.formatMessage({ id: "Parking can be reserved in advance" })}: ${canMakeReservation ? intl.formatMessage({ id: "Yes" }) : intl.formatMessage({ id: "No" })}`}
  • + {numberOfParkingSpots ? ( +
  • + {intl.formatMessage( + { id: "Number of parking spots" }, + { number: numberOfParkingSpots } + )} +
  • + ) : null} + {distanceToHotel ? ( +
  • + {intl.formatMessage( + { id: "Distance to hotel" }, + { distance: distanceToHotel } + )} +
  • + ) : null} + {address ? ( +
  • {`${intl.formatMessage({ id: "Address" })}: ${address}`}
  • + ) : null} +
+ + ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingList/parkingList.module.css b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingList/parkingList.module.css new file mode 100644 index 000000000..6e837a4fd --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingList/parkingList.module.css @@ -0,0 +1,11 @@ +.listStyling { + list-style-type: none; +} + +.listStyling > li::before { + content: url("/_static/icons/heart.svg"); + position: relative; + height: 8px; + top: 3px; + margin-right: var(--Spacing-x1); +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/index.tsx new file mode 100644 index 000000000..b524c9896 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/index.tsx @@ -0,0 +1,66 @@ +import Body from "@/components/TempDesignSystem/Text/Body" +import { getIntl } from "@/i18n" + +import styles from "./parkingPrices.module.css" + +import { + type ParkingPricesProps, + Periods, +} from "@/types/components/hotelPage/sidepeek/parking" + +export default async function ParkingPrices({ + data, + currency, + freeParking, +}: ParkingPricesProps) { + const intl = await getIntl() + const day = intl.formatMessage({ id: "Price per day" }) + const night = intl.formatMessage({ id: "Price per night" }) + const allDay = intl.formatMessage({ id: "Price per 24 hours" }) + + function getPeriod(period?: string) { + switch (period) { + case Periods.day: + return day + case Periods.night: + return night + case Periods.allDay: + return allDay + default: + return period + } + } + + const filteredPeriods = data?.filter((filter) => filter.period !== "Hour") + + return ( +
+ {filteredPeriods?.map((parking) => ( +
+
+ + {getPeriod(parking.period)} + + + {freeParking + ? intl.formatMessage({ id: "Free parking" }) + : `${parking.amount} ${currency}`} + +
+ {parking.startTime && + parking.endTime && + parking.period !== Periods.allDay && ( +
+ + {intl.formatMessage({ id: "From" })} + + + {parking.startTime}-{parking.endTime} + +
+ )} +
+ ))} +
+ ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/parkingPrices.module.css b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/parkingPrices.module.css new file mode 100644 index 000000000..436e883ec --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/ParkingPrices/parkingPrices.module.css @@ -0,0 +1,13 @@ +.wrapper { + display: grid; + row-gap: var(--Spacing-x1); +} + +.period { + display: flex; + gap: var(--Spacing-x5); +} + +.information { + flex: 1; +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/index.tsx new file mode 100644 index 000000000..d706f03a8 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/index.tsx @@ -0,0 +1,68 @@ +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Divider from "@/components/TempDesignSystem/Divider" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import { getIntl } from "@/i18n" + +import ParkingList from "./ParkingList" +import ParkingPrices from "./ParkingPrices" + +import styles from "./parkingAmenity.module.css" + +import type { ParkingAmenityProps } from "@/types/components/hotelPage/sidepeek/parking" +import { IconName } from "@/types/components/icon" + +export default async function ParkingAmenity({ parking }: ParkingAmenityProps) { + const intl = await getIntl() + + return ( + +
+ {parking.map((data) => ( +
+
+ {data.type} + +
+
+ + {intl.formatMessage({ id: "Prices" })} + +
+ + {intl.formatMessage({ id: "Weekday prices" })} + + + +
+
+ + {intl.formatMessage({ id: "Weekend prices" })} + + + +
+
+
+ ))} +
+
+ ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/parkingAmenity.module.css b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/parkingAmenity.module.css new file mode 100644 index 000000000..7535e3543 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/Parking/parkingAmenity.module.css @@ -0,0 +1,20 @@ +.wrapper { + display: grid; + gap: var(--Spacing-x3); +} + +.information, +.list, +.prices { + display: grid; + gap: var(--Spacing-x-one-and-half); +} + +.weekday, +.weekend { + background-color: var(--Base-Surface-Subtle-Normal); + border-radius: var(--Corner-radius-Medium); + padding: var(--Spacing-x2) var(--Spacing-x3); + display: grid; + gap: var(--Spacing-x1); +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/index.ts b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/index.ts new file mode 100644 index 000000000..58ecb1e2e --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/AccordionAmenities/index.ts @@ -0,0 +1,4 @@ +export { default as AccessibilityAmenity } from "./Accessibility" +export { default as BreakfastAmenity } from "./Breakfast" +export { default as CheckInAmenity } from "./CheckIn" +export { default as ParkingAmenity } from "./Parking" diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/FilteredAmenities/filteredAmenities.module.css b/components/ContentType/HotelPage/SidePeeks/Amenities/FilteredAmenities/filteredAmenities.module.css new file mode 100644 index 000000000..b6d3ba651 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/FilteredAmenities/filteredAmenities.module.css @@ -0,0 +1,10 @@ +.wrapper { + padding: var(--Spacing-x1); + border-bottom: 1px solid var(--Base-Border-Subtle); +} + +.amenity { + display: flex; + gap: var(--Spacing-x1); + padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/FilteredAmenities/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/FilteredAmenities/index.tsx new file mode 100644 index 000000000..f121893e5 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/FilteredAmenities/index.tsx @@ -0,0 +1,32 @@ +import { HeartIcon } from "@/components/Icons" +import Body from "@/components/TempDesignSystem/Text/Body" + +import { mapFacilityToIcon } from "../../../data" + +import styles from "./filteredAmenities.module.css" + +import type { FilteredAmenitiesProps } from "@/types/components/hotelPage/sidepeek/amenities" + +export default function FilteredAmenities({ + filteredAmenities, +}: FilteredAmenitiesProps) { + return ( + <> + {filteredAmenities?.map((amenity) => { + const Icon = mapFacilityToIcon(amenity.id) + return ( +
+
+ {Icon ? ( + + ) : ( + + )} + {amenity.name} +
+
+ ) + })} + + ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/Amenities/index.tsx b/components/ContentType/HotelPage/SidePeeks/Amenities/index.tsx new file mode 100644 index 000000000..4026621b1 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/Amenities/index.tsx @@ -0,0 +1,59 @@ +import { amenities } from "@/constants/routes/hotelPageParams" + +import Accordion from "@/components/TempDesignSystem/Accordion" +import SidePeek from "@/components/TempDesignSystem/SidePeek" +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" + +import { + AccessibilityAmenity, + BreakfastAmenity, + CheckInAmenity, + ParkingAmenity, +} from "./AccordionAmenities" +import FilteredAmenities from "./FilteredAmenities" + +import type { AmenitiesSidePeekProps } from "@/types/components/hotelPage/sidepeek/amenities" +import { FacilityEnum } from "@/types/enums/facilities" + +export default async function AmenitiesSidePeek({ + amenitiesList, + parking, + checkInInformation, + accessibility, +}: AmenitiesSidePeekProps) { + const lang = getLang() + const intl = await getIntl() + + const amenitiesToRemove = [ + FacilityEnum.ParkingAdditionalCost, + FacilityEnum.ParkingElectricCharging, + FacilityEnum.ParkingFreeParking, + FacilityEnum.ParkingGarage, + FacilityEnum.ParkingOutdoor, + FacilityEnum.MeetingArea, + FacilityEnum.ServesBreakfastAlwaysIncluded, + FacilityEnum.LateCheckOutUntil1400Guaranteed, + ] + + const filteredAmenities = amenitiesList.filter( + (amenity) => !amenitiesToRemove.includes(amenity.id) + ) + + return ( + + + {parking.length ? : null} + + + {accessibility && ( + + )} + + + + ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/WellnessAndExercise/Facility/index.tsx b/components/ContentType/HotelPage/SidePeeks/WellnessAndExercise/Facility/index.tsx index 3313a109b..3357765ad 100644 --- a/components/ContentType/HotelPage/SidePeeks/WellnessAndExercise/Facility/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/WellnessAndExercise/Facility/index.tsx @@ -31,7 +31,7 @@ export default async function Facility({ data }: FacilityProps) {
- {intl.formatMessage({ id: " Opening Hours" })} + {intl.formatMessage({ id: "Opening Hours" })}
diff --git a/components/ContentType/HotelPage/SidePeeks/index.ts b/components/ContentType/HotelPage/SidePeeks/index.ts index 5da71bbcd..7eff75db2 100644 --- a/components/ContentType/HotelPage/SidePeeks/index.ts +++ b/components/ContentType/HotelPage/SidePeeks/index.ts @@ -1,3 +1,4 @@ export { default as AboutTheHotelSidePeek } from "./AboutTheHotel" +export { default as AmenitiesSidePeek } from "./Amenities" export { default as RoomSidePeek } from "./Room" export { default as WellnessAndExerciseSidePeek } from "./WellnessAndExercise" diff --git a/components/ContentType/HotelPage/data.ts b/components/ContentType/HotelPage/data.ts index dd2a74f90..81ae2b438 100644 --- a/components/ContentType/HotelPage/data.ts +++ b/components/ContentType/HotelPage/data.ts @@ -14,6 +14,7 @@ const facilityToIconMap: Record = { [FacilityEnum.GymTrainingFacilities]: IconName.Fitness, [FacilityEnum.KeyAccessOnlyToHealthClubGym]: IconName.Fitness, [FacilityEnum.FreeWiFi]: IconName.Wifi, + [FacilityEnum.MeetingArea]: IconName.Business, [FacilityEnum.MeetingRooms]: IconName.Business, [FacilityEnum.MeetingConferenceFacilities]: IconName.Business, [FacilityEnum.PetFriendlyRooms]: IconName.Pets, @@ -27,6 +28,7 @@ const facilityToIconMap: Record = { [FacilityEnum.DisabledParking]: IconName.Parking, [FacilityEnum.OutdoorTerrace]: IconName.OutdoorFurniture, [FacilityEnum.RoomService]: IconName.RoomService, + [FacilityEnum.LateCheckOutUntil1400Guaranteed]: IconName.Business, [FacilityEnum.LaundryRoom]: IconName.LaundryMachine, [FacilityEnum.LaundryService]: IconName.LaundryMachine, [FacilityEnum.LaundryServiceExpress]: IconName.LaundryMachine, diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index 969d75936..8910a7cd8 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -2,7 +2,6 @@ import { notFound } from "next/navigation" import { activities, - amenities, meetingsAndConferences, restaurantAndBar, } from "@/constants/routes/hotelPageParams" @@ -30,6 +29,7 @@ import PreviewImages from "./PreviewImages" import { Rooms } from "./Rooms" import { AboutTheHotelSidePeek, + AmenitiesSidePeek, RoomSidePeek, WellnessAndExerciseSidePeek, } from "./SidePeeks" @@ -75,6 +75,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) { hotelFacts, location, ratings, + parking, } = hotelData.data.attributes const roomCategories = hotelData.included?.filter((item) => item.type === "roomcategories") || [] @@ -177,13 +178,12 @@ export default async function HotelPage({ hotelId }: HotelPageProps) { ) : null} - - {/* TODO: Render amenities as per the design. */} - Read more about the amenities here - + - +
    {!isThreeStaticPagesPathnames && !!user ? ( - <> -
  • - - {intl.formatMessage({ id: "My pages" })} - -
  • - +
  • + + {intl.formatMessage({ id: "My pages" })} + +
  • ) : ( <>
  • diff --git a/components/DatePicker/date-picker.module.css b/components/DatePicker/date-picker.module.css index 985c9d63c..cfd78c747 100644 --- a/components/DatePicker/date-picker.module.css +++ b/components/DatePicker/date-picker.module.css @@ -1,12 +1,3 @@ -.container { - overflow: hidden; - position: relative; - - &[data-isopen="true"] { - overflow: visible; - } -} - .btn { background: none; border: none; @@ -15,6 +6,12 @@ padding: 0; width: 100%; text-align: left; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + padding: 20px var(--Spacing-x-one-and-half) 0; } .body { @@ -33,6 +30,7 @@ @media screen and (max-width: 1366px) { .container { z-index: 10001; + height: 24px; } .hideWrapper { @@ -63,6 +61,6 @@ border-width + wanted space below booking widget */ - top: calc(100% + var(--Spacing-x2) + 1px + var(--Spacing-x4)); + top: calc(100% + var(--Spacing-x1) + 1px + var(--Spacing-x4)); } } diff --git a/components/Footer/Navigation/MainNav/index.tsx b/components/Footer/Navigation/MainNav/index.tsx index 11057af43..d9b499603 100644 --- a/components/Footer/Navigation/MainNav/index.tsx +++ b/components/Footer/Navigation/MainNav/index.tsx @@ -13,7 +13,7 @@ export default function FooterMainNav({ mainLinks }: FooterMainNavProps) {
      {mainLinks.map((link) => (
    • - + {items.map((x) => (
    • - + diff --git a/components/Forms/BookingWidget/FormContent/Search/index.tsx b/components/Forms/BookingWidget/FormContent/Search/index.tsx index 0d1ee36d4..860ecffde 100644 --- a/components/Forms/BookingWidget/FormContent/Search/index.tsx +++ b/components/Forms/BookingWidget/FormContent/Search/index.tsx @@ -169,25 +169,27 @@ export default function Search({ locations }: SearchProps) {
      - + + placeholder: intl.formatMessage({ + id: "Destinations & hotels", + }), + ...register(name, { + onBlur: function () { + closeMenu() + }, + onChange: handleOnChange, + }), + type: "search", + })} + /> +
      - {intl.formatMessage( - { id: "booking.nights" }, - { totalNights: nights > 0 ? nights : 0 } - )} + {nights > 0 + ? intl.formatMessage( + { id: "booking.nights" }, + { totalNights: nights } + ) + : intl.formatMessage({ + id: "Check in", + })}
      diff --git a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx index a74b52200..95bb9fc0c 100644 --- a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx @@ -33,7 +33,7 @@ export default function ChildInfoSelector({ const ageLabel = intl.formatMessage({ id: "Age" }) const bedLabel = intl.formatMessage({ id: "Bed" }) const errorMessage = intl.formatMessage({ id: "Child age is required" }) - const { setValue, formState, register } = useFormContext() + const { setValue, formState } = useFormContext() function updateSelectedBed(bed: number) { setValue(`rooms.${roomIndex}.child.${index}.bed`, bed) @@ -95,9 +95,7 @@ export default function ChildInfoSelector({ }} placeholder={ageLabel} maxHeight={180} - {...register(ageFieldName, { - required: true, - })} + name={ageFieldName} isNestedInModal={true} />
@@ -112,9 +110,7 @@ export default function ChildInfoSelector({ updateSelectedBed(key as number) }} placeholder={bedLabel} - {...register(bedFieldName, { - required: true, - })} + name={bedFieldName} isNestedInModal={true} /> ) : null} diff --git a/components/GuestsRoomsPicker/guests-rooms-picker.module.css b/components/GuestsRoomsPicker/guests-rooms-picker.module.css index d4a6dbec0..dd43b2c32 100644 --- a/components/GuestsRoomsPicker/guests-rooms-picker.module.css +++ b/components/GuestsRoomsPicker/guests-rooms-picker.module.css @@ -47,6 +47,12 @@ padding: 0; width: 100%; text-align: left; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + padding: 20px var(--Spacing-x-one-and-half) 0; } .footer { @@ -109,6 +115,9 @@ } @media screen and (min-width: 1367px) { + .container { + height: 24px; + } .pickerContainerMobile { display: none; } diff --git a/components/HotelReservation/BookingConfirmation/Details/details.module.css b/components/HotelReservation/BookingConfirmation/Details/details.module.css deleted file mode 100644 index 13460ac82..000000000 --- a/components/HotelReservation/BookingConfirmation/Details/details.module.css +++ /dev/null @@ -1,31 +0,0 @@ -.details { - background-color: var(--Base-Surface-Subtle-Normal); - border-radius: var(--Corner-radius-Medium); - display: flex; - flex-direction: column; - gap: var(--Spacing-x3); - grid-area: details; - padding: var(--Spacing-x2); -} - -.list { - display: flex; - flex-direction: column; - gap: var(--Spacing-x-one-and-half); - list-style: none; - margin: 0; - padding: 0; -} - -.listItem { - align-items: center; - display: flex; - gap: var(--Spacing-x1); - justify-content: space-between; -} - -@media screen and (min-width: 768px) { - .details { - padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x2); - } -} diff --git a/components/HotelReservation/BookingConfirmation/Details/index.tsx b/components/HotelReservation/BookingConfirmation/Details/index.tsx deleted file mode 100644 index 5d23e55a8..000000000 --- a/components/HotelReservation/BookingConfirmation/Details/index.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { dt } from "@/lib/dt" -import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" - -import Body from "@/components/TempDesignSystem/Text/Body" -import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" -import { getIntl } from "@/i18n" -import { getLang } from "@/i18n/serverContext" - -import styles from "./details.module.css" - -import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" - -export default async function Details({ - confirmationNumber, -}: BookingConfirmationProps) { - const intl = await getIntl() - const lang = getLang() - const { booking } = await getBookingConfirmation(confirmationNumber) - - const fromDate = dt(booking.checkInDate).locale(lang) - const toDate = dt(booking.checkOutDate).locale(lang) - - return ( -
-
- - {intl.formatMessage( - { id: "Reference #{bookingNr}" }, - { bookingNr: booking.confirmationNumber } - )} - -
-
    -
  • - {intl.formatMessage({ id: "Check-in" })} - - {`${fromDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${fromDate.format("HH:mm")}`} - -
  • -
  • - {intl.formatMessage({ id: "Check-out" })} - - {`${toDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${toDate.format("HH:mm")}`} - -
  • -
  • - {intl.formatMessage({ id: "Breakfast" })} - N/A -
  • -
  • - {intl.formatMessage({ id: "Cancellation policy" })} - {booking.rateDefinition.cancellationText} -
  • -
  • - {intl.formatMessage({ id: "Rebooking" })} - N/A -
  • -
-
- ) -} diff --git a/components/HotelReservation/BookingConfirmation/Header/Actions/actions.module.css b/components/HotelReservation/BookingConfirmation/Header/Actions/actions.module.css index 5e9865b30..c54e54d13 100644 --- a/components/HotelReservation/BookingConfirmation/Header/Actions/actions.module.css +++ b/components/HotelReservation/BookingConfirmation/Header/Actions/actions.module.css @@ -2,6 +2,7 @@ border-radius: var(--Corner-radius-Medium); display: grid; grid-area: actions; + justify-content: flex-start; } @media screen and (min-width: 768px) { @@ -10,6 +11,5 @@ grid-auto-columns: auto; grid-auto-flow: column; grid-template-columns: auto; - justify-content: flex-start; } } diff --git a/components/HotelReservation/BookingConfirmation/Header/header.module.css b/components/HotelReservation/BookingConfirmation/Header/header.module.css index 57d87e49d..5468c7dbf 100644 --- a/components/HotelReservation/BookingConfirmation/Header/header.module.css +++ b/components/HotelReservation/BookingConfirmation/Header/header.module.css @@ -16,3 +16,9 @@ .body { max-width: 720px; } + +@media screen and (min-width: 1367px) { + .header { + padding-bottom: var(--Spacing-x4); + } +} diff --git a/components/HotelReservation/BookingConfirmation/HotelDetails/hotelDetails.module.css b/components/HotelReservation/BookingConfirmation/HotelDetails/hotelDetails.module.css new file mode 100644 index 000000000..484ae03e2 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/HotelDetails/hotelDetails.module.css @@ -0,0 +1,37 @@ +.contact, +.container, +.details, +.hotel { + display: flex; + flex-direction: column; +} + +.container { + gap: var(--Spacing-x4); +} + +.details { + gap: var(--Spacing-x-one-and-half); +} + +.contact, +.hotel { + gap: var(--Spacing-x-half); +} + +.coordinates { + margin-top: var(--Spacing-x-half); +} + +.toast { + align-self: flex-start; + min-width: 300px; +} + +.list { + padding-left: var(--Spacing-x2); +} + +.link { + word-break: break-all; +} diff --git a/components/HotelReservation/BookingConfirmation/HotelDetails/index.tsx b/components/HotelReservation/BookingConfirmation/HotelDetails/index.tsx new file mode 100644 index 000000000..dcef584a5 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/HotelDetails/index.tsx @@ -0,0 +1,74 @@ +import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" + +import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import { Toast } from "@/components/TempDesignSystem/Toasts" +import { getIntl } from "@/i18n" + +import styles from "./hotelDetails.module.css" + +import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" + +export default async function HotelDetails({ + confirmationNumber, +}: BookingConfirmationProps) { + const intl = await getIntl() + const { hotel } = await getBookingConfirmation(confirmationNumber) + return ( +
+
+ + {intl.formatMessage({ id: "Hotel details" })} + +
+ {hotel.name} + + {hotel.address.streetAddress}, {hotel.address.zipCode}{" "} + {hotel.address.city} + + + + {hotel.contactInformation.phoneNumber} + + +
+ + {intl.formatMessage( + { id: "Long {long} ∙ Lat {lat}" }, + { + lat: hotel.location.latitude, + long: hotel.location.longitude, + } + )} + +
+
+ + {hotel.contactInformation.email} + + + {hotel.contactInformation.websiteUrl} + +
+
+ +
    +
  • N/A
  • +
+
+
+
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/HotelImage/image.module.css b/components/HotelReservation/BookingConfirmation/HotelImage/image.module.css deleted file mode 100644 index 34e5748bb..000000000 --- a/components/HotelReservation/BookingConfirmation/HotelImage/image.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.imageContainer { - align-items: center; - border-radius: var(--Corner-radius-Medium); - display: flex; - grid-area: image; - justify-content: center; -} diff --git a/components/HotelReservation/BookingConfirmation/HotelImage/index.tsx b/components/HotelReservation/BookingConfirmation/HotelImage/index.tsx deleted file mode 100644 index b6d99889e..000000000 --- a/components/HotelReservation/BookingConfirmation/HotelImage/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" - -import Image from "@/components/Image" - -import styles from "./image.module.css" - -import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" - -export default async function HotelImage({ - confirmationNumber, -}: BookingConfirmationProps) { - const { hotel } = await getBookingConfirmation(confirmationNumber) - return ( - - ) -} diff --git a/components/HotelReservation/BookingConfirmation/PaymentDetails/index.tsx b/components/HotelReservation/BookingConfirmation/PaymentDetails/index.tsx new file mode 100644 index 000000000..67f327455 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/PaymentDetails/index.tsx @@ -0,0 +1,59 @@ +import { dt } from "@/lib/dt" +import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" + +import { CreditCardAddIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" + +import styles from "./paymentDetails.module.css" + +import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" + +export default async function PaymentDetails({ + confirmationNumber, +}: BookingConfirmationProps) { + const intl = await getIntl() + const lang = getLang() + const { booking } = await getBookingConfirmation(confirmationNumber) + return ( +
+ + {intl.formatMessage({ id: "Payment details" })} + +
+ + {intl.formatNumber(booking.totalPrice, { + currency: booking.currencyCode, + style: "currency", + })}{" "} + {intl.formatMessage({ id: "has been paid" })} + + + {dt(booking.createDateTime) + .locale(lang) + .format("ddd D MMM YYYY, hh:mm")} + + + {intl.formatMessage( + { id: "{card} ending with {cardno}" }, + { card: "N/A", cardno: "N/A" } + )} + +
+ +
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/PaymentDetails/paymentDetails.module.css b/components/HotelReservation/BookingConfirmation/PaymentDetails/paymentDetails.module.css new file mode 100644 index 000000000..c8535da2f --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/PaymentDetails/paymentDetails.module.css @@ -0,0 +1,18 @@ +.details, +.payment { + display: flex; + flex-direction: column; +} + +.details { + gap: var(--Spacing-x-one-and-half); +} + +.payment { + gap: var(--Spacing-x-half); +} + +.details button.btn { + align-self: flex-start; + margin-top: var(--Spacing-x-half); +} diff --git a/components/HotelReservation/BookingConfirmation/Promos/Promo/index.tsx b/components/HotelReservation/BookingConfirmation/Promos/Promo/index.tsx new file mode 100644 index 000000000..f8319c644 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Promos/Promo/index.tsx @@ -0,0 +1,23 @@ +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Title from "@/components/TempDesignSystem/Text/Title" + +import styles from "./promo.module.css" + +import type { PromoProps } from "@/types/components/hotelReservation/bookingConfirmation/promo" + +export default function Promo({ buttonText, text, title }: PromoProps) { + return ( +
+ + {title} + + + {text} + + +
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/Promos/Promo/promo.module.css b/components/HotelReservation/BookingConfirmation/Promos/Promo/promo.module.css new file mode 100644 index 000000000..ed4714228 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Promos/Promo/promo.module.css @@ -0,0 +1,38 @@ +.promo { + align-items: center; + background-position: 50%; + background-repeat: no-repeat; + background-size: cover; + border-radius: var(--Medium, 8px); + display: flex; + flex: 1 0 320px; + flex-direction: column; + gap: var(--Spacing-x2); + height: 320px; + justify-content: center; + padding: var(--Spacing-x4) var(--Spacing-x3); +} + +.promo:nth-of-type(1) { + background-image: linear-gradient( + 180deg, + rgba(0, 0, 0, 0) 0%, + rgba(0, 0, 0, 0.36) 37.88%, + rgba(0, 0, 0, 0.75) 100% + ); + /* , url(""); uncomment and add image once we have it */ +} + +.promo:nth-of-type(2) { + background-image: linear-gradient( + 180deg, + rgba(0, 0, 0, 0) 0%, + rgba(0, 0, 0, 0.36) 37.88%, + rgba(0, 0, 0, 0.75) 100% + ); + /* , url(""); uncomment and add image once we have it */ +} + +.text { + max-width: 400px; +} diff --git a/components/HotelReservation/BookingConfirmation/Promos/index.tsx b/components/HotelReservation/BookingConfirmation/Promos/index.tsx new file mode 100644 index 000000000..d0692f917 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Promos/index.tsx @@ -0,0 +1,27 @@ +import { getIntl } from "@/i18n" + +import Promo from "./Promo" + +import styles from "./promos.module.css" + +export default async function Promos() { + const intl = await getIntl() + return ( +
+ + +
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/Promos/promos.module.css b/components/HotelReservation/BookingConfirmation/Promos/promos.module.css new file mode 100644 index 000000000..0092c6af1 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Promos/promos.module.css @@ -0,0 +1,12 @@ +.promos { + display: flex; + flex-direction: column; + gap: var(--Spacing-x2); + padding: var(--Spacing-x5) 0; +} + +@media screen and (min-width: 1367px) { + .promos { + flex-direction: row; + } +} diff --git a/components/HotelReservation/BookingConfirmation/Receipt/index.tsx b/components/HotelReservation/BookingConfirmation/Receipt/index.tsx new file mode 100644 index 000000000..d700474d2 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Receipt/index.tsx @@ -0,0 +1,143 @@ +import { notFound } from "next/navigation" + +import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" + +import { ChevronRightSmallIcon, InfoCircleIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" +import Divider from "@/components/TempDesignSystem/Divider" +import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import { getIntl } from "@/i18n" +import { getBookedHotelRoom } from "@/utils/getBookedHotelRoom" + +import styles from "./receipt.module.css" + +import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" +import { BreakfastPackageEnum } from "@/types/enums/breakfast" + +export default async function Receipt({ + confirmationNumber, +}: BookingConfirmationProps) { + const intl = await getIntl() + const { booking, hotel } = await getBookingConfirmation(confirmationNumber) + const roomAndBed = getBookedHotelRoom(hotel, booking.roomTypeCode ?? "") + if (!roomAndBed) { + return notFound() + } + + const breakfastPkgSelected = booking.packages.find( + (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST + ) + const breakfastPkgIncluded = booking.packages.find( + (pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST + ) + return ( +
+ {intl.formatMessage({ id: "Summary" })} +
+
+ {roomAndBed.name} + {booking.rateDefinition.isMemberRate ? ( +
+ + N/A + + + {intl.formatNumber(booking.roomPrice, { + currency: booking.currencyCode, + style: "currency", + })} + +
+ ) : ( + + {intl.formatNumber(booking.roomPrice, { + currency: booking.currencyCode, + style: "currency", + })} + + )} + + {intl.formatMessage( + { id: "booking.adults" }, + { + totalAdults: booking.adults, + } + )} + + + {booking.rateDefinition.cancellationText} + + + {intl.formatMessage({ id: "Reservation policy" })} + + +
+
+ + {roomAndBed.bedType.description} + + + {intl.formatNumber(0, { + currency: booking.currencyCode, + style: "currency", + })} + +
+
+ {intl.formatMessage({ id: "Breakfast buffet" })} + {booking.rateDefinition.breakfastIncluded ?? breakfastPkgIncluded ? ( + {intl.formatMessage({ id: "Included" })} + ) : null} + + {breakfastPkgSelected ? ( + + {intl.formatNumber(breakfastPkgSelected.totalPrice, { + currency: breakfastPkgSelected.currency, + style: "currency", + })} + + ) : null} +
+
+ +
+
+ + {intl.formatMessage({ id: "Total price" })} + + + {intl.formatNumber(booking.totalPrice, { + currency: booking.currencyCode, + style: "currency", + })} + +
+
+ + + {intl.formatMessage({ id: "Approx." })} N/A EUR + +
+
+
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/Receipt/receipt.module.css b/components/HotelReservation/BookingConfirmation/Receipt/receipt.module.css new file mode 100644 index 000000000..3625add0e --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Receipt/receipt.module.css @@ -0,0 +1,40 @@ +.receipt { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-one-and-half); +} + +.room { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-one-and-half); +} + +.roomHeader { + display: grid; + grid-template-columns: 1fr auto; +} + +.roomHeader :nth-child(n + 3) { + grid-column: 1/-1; +} + +.memberPrice { + display: flex; + gap: var(--Spacing-x1); +} + +.entry { + display: flex; + justify-content: space-between; +} + +.receipt .price button.btn { + padding: 0; +} + +@media screen and (min-width: 1367px) { + .receipt { + padding: var(--Spacing-x3); + } +} diff --git a/components/HotelReservation/BookingConfirmation/Rooms/Room/index.tsx b/components/HotelReservation/BookingConfirmation/Rooms/Room/index.tsx index 0dcb32216..e721c3d8c 100644 --- a/components/HotelReservation/BookingConfirmation/Rooms/Room/index.tsx +++ b/components/HotelReservation/BookingConfirmation/Rooms/Room/index.tsx @@ -1,5 +1,139 @@ +import { dt } from "@/lib/dt" + +import { + CheckCircleIcon, + ChevronRightSmallIcon, + CrossCircle, +} from "@/components/Icons" +import Image from "@/components/Image" +import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" + import styles from "./room.module.css" -export default function Room() { - return
+import type { RoomProps } from "@/types/components/hotelReservation/bookingConfirmation/room" + +export default async function Room({ booking, img, roomName }: RoomProps) { + const intl = await getIntl() + const lang = getLang() + + const fromDate = dt(booking.checkInDate).locale(lang) + const toDate = dt(booking.checkOutDate).locale(lang) + return ( +
+
+
+ {/* + {intl.formatMessage({ id: "Room" })} 1 + */} + + {`${intl.formatMessage({ id: "Reservation number" })} ${booking.confirmationNumber}`} + +
+
+ {booking.rateDefinition.isMemberRate ? ( + <> + + + {intl.formatMessage({ id: "Membership benefits applied" })} + + + ) : ( + <> + + + {intl.formatMessage({ id: "No membership benefits applied" })} + + + )} +
+
+
+ {img.metaData.altText} +
+
+ + {roomName} + + + {intl.formatMessage({ id: "View room details" })} + + +
+
    +
  • + + {intl.formatMessage({ id: "Check-in" })} + + + {`${fromDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${fromDate.format("HH:mm")}`} + +
  • +
  • + + {intl.formatMessage({ id: "Check-out" })} + + + {`${toDate.format("ddd, D MMM")} ${intl.formatMessage({ id: "from" })} ${toDate.format("HH:mm")}`} + +
  • +
  • + + {intl.formatMessage({ id: "Breakfast" })} + + N/A +
  • +
  • + + {intl.formatMessage({ id: "Cancellation policy" })} + + + {booking.rateDefinition.cancellationText} + +
  • +
  • + + {intl.formatMessage({ id: "Rebooking" })} + + N/A +
  • +
+
+ + {intl.formatMessage({ id: "Main guest" })} + + + {`${booking.guest.firstName} ${booking.guest.lastName}`} + + {booking.guest.membershipNumber ? ( + + {`${intl.formatMessage({ id: "Friend no." })} ${booking.guest.membershipNumber}`} + + ) : null} + {booking.guest.phoneNumber ? ( + + {booking.guest.phoneNumber} + + ) : null} + {booking.guest.email ? ( + {booking.guest.email} + ) : null} +
+
+
+
+ ) } diff --git a/components/HotelReservation/BookingConfirmation/Rooms/Room/room.module.css b/components/HotelReservation/BookingConfirmation/Rooms/Room/room.module.css index e69de29bb..d93ab691c 100644 --- a/components/HotelReservation/BookingConfirmation/Rooms/Room/room.module.css +++ b/components/HotelReservation/BookingConfirmation/Rooms/Room/room.module.css @@ -0,0 +1,96 @@ +.room { + display: flex; + flex-direction: column; + gap: var(--Spacing-x2); +} + +.header { + align-items: flex-end; + display: grid; + gap: var(--Spacing-x2); + grid-template-columns: 1fr; +} + +.benefits { + align-items: center; + border: 1px solid var(--Base-Border-Subtle); + border-radius: var(--Corner-radius-Medium); + display: flex; + gap: var(--Spacing-x1); + padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); + width: max-content; +} + +.booking { + background-color: var(--Base-Background-Primary-Normal); + border-radius: var(--Corner-radius-Large); + display: grid; + gap: var(--Spacing-x2); + padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x3) + var(--Spacing-x2); +} + +.img { + width: 100%; +} + +.roomDetails { + display: grid; + gap: var(--Spacing-x2); +} + +.roomName { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); + grid-column: 1/-1; +} + +.details { + display: grid; + gap: var(--Spacing-x-half) var(--Spacing-x3); + list-style: none; +} + +.listItem { + display: flex; + justify-content: space-between; +} + +.guest { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); +} + +@media screen and (max-width: 1366px) { + .details { + padding-bottom: var(--Spacing-x1); + } + + .details p:nth-of-type(even) { + text-align: right; + } +} + +@media screen and (min-width: 1367px) { + .header { + grid-template-columns: 1fr auto; + } + + .booking { + gap: var(--Spacing-x3); + grid-template-columns: auto 1fr; + padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x2) + var(--Spacing-x2); + } + + .roomDetails { + grid-template-columns: 1fr 1fr; + } + + .guest { + align-items: flex-end; + align-self: flex-end; + } +} diff --git a/components/HotelReservation/BookingConfirmation/Rooms/index.tsx b/components/HotelReservation/BookingConfirmation/Rooms/index.tsx index 871dbc4e4..fdc973971 100644 --- a/components/HotelReservation/BookingConfirmation/Rooms/index.tsx +++ b/components/HotelReservation/BookingConfirmation/Rooms/index.tsx @@ -1,5 +1,30 @@ +import { notFound } from "next/navigation" + +import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" + +import { getBookedHotelRoom } from "@/utils/getBookedHotelRoom" + +import Room from "./Room" + import styles from "./rooms.module.css" -export default function Rooms() { - return
+import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" + +export default async function Rooms({ + confirmationNumber, +}: BookingConfirmationProps) { + const { booking, hotel } = await getBookingConfirmation(confirmationNumber) + const roomAndBed = getBookedHotelRoom(hotel, booking.roomTypeCode ?? "") + if (!roomAndBed) { + return notFound() + } + return ( +
+ +
+ ) } diff --git a/components/HotelReservation/BookingConfirmation/Rooms/rooms.module.css b/components/HotelReservation/BookingConfirmation/Rooms/rooms.module.css index 9713547bf..b5b084c16 100644 --- a/components/HotelReservation/BookingConfirmation/Rooms/rooms.module.css +++ b/components/HotelReservation/BookingConfirmation/Rooms/rooms.module.css @@ -1,6 +1,5 @@ .rooms { display: flex; flex-direction: column; - gap: var(--Spacing-x9); - grid-area: booking; + gap: var(--Spacing-x5); } diff --git a/components/HotelReservation/BookingConfirmation/Summary/index.tsx b/components/HotelReservation/BookingConfirmation/Summary/index.tsx deleted file mode 100644 index ce95e08bb..000000000 --- a/components/HotelReservation/BookingConfirmation/Summary/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import styles from "./summary.module.css" - -export default function Summary() { - return -} diff --git a/components/HotelReservation/BookingConfirmation/Summary/summary.module.css b/components/HotelReservation/BookingConfirmation/Summary/summary.module.css deleted file mode 100644 index 8d8015b28..000000000 --- a/components/HotelReservation/BookingConfirmation/Summary/summary.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.summary { - background-color: hotpink; - grid-area: summary; -} diff --git a/components/HotelReservation/BookingConfirmation/TotalPrice/index.tsx b/components/HotelReservation/BookingConfirmation/TotalPrice/index.tsx deleted file mode 100644 index d7ee0ff11..000000000 --- a/components/HotelReservation/BookingConfirmation/TotalPrice/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" - -import { - CoffeeIcon, - DiscountIcon, - DoorClosedIcon, - PriceTagIcon, -} from "@/components/Icons" -import Divider from "@/components/TempDesignSystem/Divider" -import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" -import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" -import { getIntl } from "@/i18n" - -import styles from "./totalPrice.module.css" - -import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" -import { BreakfastPackageEnum } from "@/types/enums/breakfast" - -export default async function TotalPrice({ - confirmationNumber, -}: BookingConfirmationProps) { - const intl = await getIntl() - const { booking } = await getBookingConfirmation(confirmationNumber) - - const totalPrice = intl.formatNumber(booking.totalPrice, { - currency: booking.currencyCode, - style: "currency", - }) - const breakfastPackage = booking.packages.find( - (p) => p.code === BreakfastPackageEnum.REGULAR_BREAKFAST - ) - return ( -
-
- - {intl.formatMessage({ id: "Total price" })} - - - {totalPrice} (~ EUR) - -
-
-
- - - {`${intl.formatMessage({ id: "Room" })}, ${intl.formatMessage({ id: "booking.nights" }, { totalNights: 1 })}`} - - {totalPrice} -
-
- - - {intl.formatMessage({ id: "Breakfast" })} - - - {breakfastPackage - ? intl.formatNumber(breakfastPackage.totalPrice, { - currency: breakfastPackage.currency, - style: "currency", - }) - : intl.formatMessage({ id: "No breakfast" })} - -
-
- - - {intl.formatMessage({ id: "Member discount" })} - - N/A -
-
- - - {intl.formatMessage({ id: "Points used" })} - - N/A -
-
- -
-
- - {intl.formatMessage({ id: "Price excl VAT" })} - - - {intl.formatNumber(booking.totalPriceExVat, { - currency: booking.currencyCode, - style: "currency", - })} - -
-
- - {intl.formatMessage({ id: "VAT" })} - - {booking.vatPercentage}% -
-
- - {intl.formatMessage({ id: "VAT amount" })} - - - {intl.formatNumber(booking.vatAmount, { - currency: booking.currencyCode, - style: "currency", - })} - -
-
- - {intl.formatMessage({ id: "Price incl VAT" })} - - - {intl.formatNumber(booking.totalPrice, { - currency: booking.currencyCode, - style: "currency", - })} - -
-
- - {intl.formatMessage({ id: "Payment method" })} - - N/A -
-
- - {intl.formatMessage({ id: "Payment status" })} - - N/A -
-
-
- ) -} diff --git a/components/HotelReservation/BookingConfirmation/TotalPrice/totalPrice.module.css b/components/HotelReservation/BookingConfirmation/TotalPrice/totalPrice.module.css deleted file mode 100644 index 0bb8cf70a..000000000 --- a/components/HotelReservation/BookingConfirmation/TotalPrice/totalPrice.module.css +++ /dev/null @@ -1,14 +0,0 @@ -.container { - background-color: var(--Base-Background-Primary-Normal); - border-radius: var(--Corner-radius-Large); - display: flex; - flex-direction: column; - gap: var(--Spacing-x3); - padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x3); -} - -.items { - display: grid; - gap: var(--Spacing-x3) var(--Spacing-x1); - grid-template-columns: repeat(4, minmax(100px, 1fr)); -} diff --git a/components/HotelReservation/BookingConfirmation/_Summary/index.tsx b/components/HotelReservation/BookingConfirmation/_Summary/index.tsx deleted file mode 100644 index 05800f2d2..000000000 --- a/components/HotelReservation/BookingConfirmation/_Summary/index.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { profile } from "@/constants/routes/myPages" -import { dt } from "@/lib/dt" -import { - getBookingConfirmation, - getProfileSafely, -} from "@/lib/trpc/memoizedRequests" - -import { CreditCardAddIcon, EditIcon, PersonIcon } from "@/components/Icons" -import Divider from "@/components/TempDesignSystem/Divider" -import Link from "@/components/TempDesignSystem/Link" -import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" -import { getIntl } from "@/i18n" -import { getLang } from "@/i18n/serverContext" - -import styles from "./summary.module.css" - -import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" -import { BreakfastPackageEnum } from "@/types/enums/breakfast" - -export default async function Summary({ - confirmationNumber, -}: BookingConfirmationProps) { - const intl = await getIntl() - const lang = getLang() - const { booking, hotel } = await getBookingConfirmation(confirmationNumber) - const user = await getProfileSafely() - const { firstName, lastName } = booking.guest - const membershipNumber = user?.membership?.membershipNumber - const totalNights = dt(booking.checkOutDate.setHours(0, 0, 0)).diff( - dt(booking.checkInDate.setHours(0, 0, 0)), - "days" - ) - - const breakfastPackage = booking.packages.find( - (pkg) => pkg.code === BreakfastPackageEnum.REGULAR_BREAKFAST - ) - return ( -
-
-
- - {intl.formatMessage({ id: "Guest" })} - - {`${firstName} ${lastName}`} - {membershipNumber ? ( - - {intl.formatMessage( - { id: "membership.no" }, - { membershipNumber } - )} - - ) : null} - {booking.guest.email} - {booking.guest.phoneNumber} -
- {user ? ( - - - - {intl.formatMessage({ id: "Go to profile" })} - - - ) : null} -
- -
-
- - {intl.formatMessage({ id: "Payment" })} - - - {intl.formatMessage( - { id: "guest.paid" }, - { - amount: intl.formatNumber(booking.totalPrice), - currency: booking.currencyCode, - } - )} - - Date information N/A - Card information N/A -
- {/* # href until more info */} - {user ? ( - - - - {intl.formatMessage({ id: "Save card to profile" })} - - - ) : null} -
- -
-
- - {intl.formatMessage({ id: "Booking" })} - - - N/A, {intl.formatMessage({ id: "booking.nights" }, { totalNights })} - ,{" "} - {intl.formatMessage( - { id: "booking.adults" }, - { totalAdults: booking.adults } - )} - - {breakfastPackage ? ( - - {intl.formatMessage({ id: "Breakfast added" })} - - ) : null} - Bedtype N/A -
- {/* # href until more info */} - - - - {intl.formatMessage({ id: "Manage booking" })} - - -
- -
-
- - {intl.formatMessage({ id: "Hotel" })} - - {hotel.name} - - {`${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`} - - - {hotel.contactInformation.phoneNumber} - - - {`${intl.formatMessage({ id: "Longitude" }, { long: hotel.location.longitude })} ∙ ${intl.formatMessage({ id: "Latitude" }, { lat: hotel.location.latitude })}`} - -
-
- - {hotel.contactInformation.websiteUrl} - - - {hotel.contactInformation.email} - -
-
-
- ) -} diff --git a/components/HotelReservation/BookingConfirmation/_Summary/summary.module.css b/components/HotelReservation/BookingConfirmation/_Summary/summary.module.css deleted file mode 100644 index f7f4e6635..000000000 --- a/components/HotelReservation/BookingConfirmation/_Summary/summary.module.css +++ /dev/null @@ -1,31 +0,0 @@ -.summary { - display: grid; - gap: var(--Spacing-x3); -} - -.container, -.textContainer { - display: flex; - flex-direction: column; -} - -.container { - gap: var(--Spacing-x-one-and-half); -} - -.textContainer { - gap: var(--Spacing-x-half); -} - -.container .textContainer .latLong { - padding-top: var(--Spacing-x1); -} - -.hotelLinks { - display: flex; - flex-direction: column; -} - -.summary .container .link { - gap: var(--Spacing-x1); -} diff --git a/components/HotelReservation/Contact/contact.module.css b/components/HotelReservation/Contact/contact.module.css index b9fba3d9b..e35862fed 100644 --- a/components/HotelReservation/Contact/contact.module.css +++ b/components/HotelReservation/Contact/contact.module.css @@ -22,10 +22,6 @@ flex-direction: column; } -.heading { - font-weight: 500; -} - .soMeIcons { display: flex; gap: var(--Spacing-x-one-and-half); @@ -35,14 +31,20 @@ display: flex; align-items: center; column-gap: var(--Spacing-x-one-and-half); - grid-column: 2 / 3; - grid-row: 3 / 4; + grid-column: 1 / 3; + grid-row: 4 / 4; font-size: var(--typography-Footnote-Regular-fontSize); line-height: (); + margin-bottom: var(--Spacing-x1); +} + +.ecoLabel img { + flex-shrink: 0; } .ecoLabelText { display: flex; + color: var(--UI-Text-Medium-contrast); flex-direction: column; justify-content: center; } diff --git a/components/HotelReservation/Contact/index.tsx b/components/HotelReservation/Contact/index.tsx index 95fcb114a..360831c94 100644 --- a/components/HotelReservation/Contact/index.tsx +++ b/components/HotelReservation/Contact/index.tsx @@ -5,6 +5,7 @@ import { useIntl } from "react-intl" import { FacebookIcon, InstagramIcon } from "@/components/Icons" import Image from "@/components/Image" import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" import useLang from "@/hooks/useLang" import styles from "./contact.module.css" @@ -20,17 +21,17 @@ export default function Contact({ hotel }: ContactProps) {
- {hotel.hotelFacts.ecoLabels.nordicEcoLabel ? ( + {hotel.hotelFacts.ecoLabels?.nordicEcoLabel ? (
{intl.formatMessage({ diff --git a/components/HotelReservation/EnterDetails/Details/JoinScandicFriendsCard/index.tsx b/components/HotelReservation/EnterDetails/Details/JoinScandicFriendsCard/index.tsx index 2048f114e..830f882d3 100644 --- a/components/HotelReservation/EnterDetails/Details/JoinScandicFriendsCard/index.tsx +++ b/components/HotelReservation/EnterDetails/Details/JoinScandicFriendsCard/index.tsx @@ -81,27 +81,29 @@ export default function JoinScandicFriendsCard({ ))}
- - {intl.formatMessage( - { - id: "signup.terms", - }, - { - termsLink: (str) => ( - - {str} - - ), - } - )} - +
+ + {intl.formatMessage( + { + id: "signup.terms", + }, + { + termsLink: (str) => ( + + {str} + + ), + } + )} + +
) } diff --git a/components/HotelReservation/EnterDetails/Header/header.module.css b/components/HotelReservation/EnterDetails/Header/header.module.css index a2b8fbdcb..6e8a2b2ba 100644 --- a/components/HotelReservation/EnterDetails/Header/header.module.css +++ b/components/HotelReservation/EnterDetails/Header/header.module.css @@ -15,13 +15,19 @@ .wrapper { position: relative; - padding: var(--Spacing-x3) var(--Spacing-x2); background-color: rgba(57, 57, 57, 0.5); + width: 100dvw; +} + +.container { display: flex; flex-direction: column; justify-content: center; align-items: flex-start; + max-width: var(--max-width-page); gap: var(--Spacing-x2); + padding: var(--Spacing-x3) 0; + margin: 0 auto; } .titleContainer { @@ -30,9 +36,19 @@ gap: var(--Spacing-x-half); } +.title { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; /* number of lines to show */ + line-clamp: 2; + -webkit-box-orient: vertical; +} + .address { display: flex; - gap: var(--Spacing-x-one-and-half); + flex-wrap: wrap; + column-gap: var(--Spacing-x-one-and-half); font-style: normal; } @@ -41,13 +57,13 @@ } @media (min-width: 768px) { - .wrapper { - padding: var(--Spacing-x3) var(--Spacing-x3); + .container { + padding: var(--Spacing-x3) 0; } } @media screen and (min-width: 1367px) { - .wrapper { - padding: var(--Spacing-x6) var(--Spacing-x5); + .container { + padding: var(--Spacing-x6) 0; } } diff --git a/components/HotelReservation/EnterDetails/Header/index.tsx b/components/HotelReservation/EnterDetails/Header/index.tsx index 818b9c996..e1fbb96dd 100644 --- a/components/HotelReservation/EnterDetails/Header/index.tsx +++ b/components/HotelReservation/EnterDetails/Header/index.tsx @@ -25,28 +25,30 @@ export default async function HotelHeader({ hotelData }: HotelHeaderProps) { width={1196} />
-
- - {hotel.name} - -
- - {hotel.address.streetAddress}, {hotel.address.city} - - ∙ - - {intl.formatMessage( - { id: "Distance in km to city centre" }, - { - number: getSingleDecimal( - hotel.location.distanceToCentre / 1000 - ), - } - )} - -
+
+
+ + {hotel.name} + +
+ + {hotel.address.streetAddress}, {hotel.address.city} + + ∙ + + {intl.formatMessage( + { id: "Distance in km to city centre" }, + { + number: getSingleDecimal( + hotel.location.distanceToCentre / 1000 + ), + } + )} + +
+
+
-
) diff --git a/components/HotelReservation/EnterDetails/Summary/Client.tsx b/components/HotelReservation/EnterDetails/Summary/Client.tsx deleted file mode 100644 index 1fe83bc57..000000000 --- a/components/HotelReservation/EnterDetails/Summary/Client.tsx +++ /dev/null @@ -1,97 +0,0 @@ -"use client" - -import { useEnterDetailsStore } from "@/stores/enter-details" - -import Summary from "@/components/HotelReservation/Summary" -import { SummaryBottomSheet } from "@/components/HotelReservation/Summary/BottomSheet" - -import styles from "./summary.module.css" - -import type { ClientSummaryProps } from "@/types/components/hotelReservation/enterDetails/summary" -import type { DetailsState } from "@/types/stores/enter-details" - -function storeSelector(state: DetailsState) { - return { - bedType: state.bedType, - breakfast: state.breakfast, - fromDate: state.booking.fromDate, - join: state.guest.join, - membershipNo: state.guest.membershipNo, - packages: state.packages, - roomRate: state.roomRate, - roomPrice: state.roomPrice, - toDate: state.booking.toDate, - toggleSummaryOpen: state.actions.toggleSummaryOpen, - totalPrice: state.totalPrice, - } -} - -export default function ClientSummary({ - adults, - cancellationText, - isMember, - kids, - memberRate, - rateDetails, - roomType, -}: ClientSummaryProps) { - const { - bedType, - breakfast, - fromDate, - join, - membershipNo, - packages, - roomPrice, - toDate, - toggleSummaryOpen, - totalPrice, - } = useEnterDetailsStore(storeSelector) - - const showMemberPrice = !!(isMember && memberRate) || join || !!membershipNo - const room = { - adults, - cancellationText, - children: kids, - packages, - rateDetails, - roomPrice, - roomType, - } - - return ( - <> -
- -
- -
-
-
-
-
-
- -
-
-
- - ) -} diff --git a/components/HotelReservation/EnterDetails/Summary/Desktop.tsx b/components/HotelReservation/EnterDetails/Summary/Desktop.tsx new file mode 100644 index 000000000..3c90539b4 --- /dev/null +++ b/components/HotelReservation/EnterDetails/Summary/Desktop.tsx @@ -0,0 +1,13 @@ +import SidePanel from "@/components/HotelReservation/SidePanel" + +import SummaryUI from "./UI" + +import type { SummaryProps } from "@/types/components/hotelReservation/summary" + +export default function DesktopSummary(props: SummaryProps) { + return ( + + + + ) +} diff --git a/components/HotelReservation/Summary/BottomSheet/bottomSheet.module.css b/components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/bottomSheet.module.css similarity index 100% rename from components/HotelReservation/Summary/BottomSheet/bottomSheet.module.css rename to components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/bottomSheet.module.css diff --git a/components/HotelReservation/Summary/BottomSheet/index.tsx b/components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/index.tsx similarity index 96% rename from components/HotelReservation/Summary/BottomSheet/index.tsx rename to components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/index.tsx index 085325a13..4041b4fd8 100644 --- a/components/HotelReservation/Summary/BottomSheet/index.tsx +++ b/components/HotelReservation/EnterDetails/Summary/Mobile/BottomSheet/index.tsx @@ -12,7 +12,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import styles from "./bottomSheet.module.css" -export function SummaryBottomSheet({ children }: PropsWithChildren) { +export default function SummaryBottomSheet({ children }: PropsWithChildren) { const intl = useIntl() const { isSummaryOpen, toggleSummaryOpen, totalPrice, isSubmittingDisabled } = diff --git a/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx b/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx new file mode 100644 index 000000000..07f18807e --- /dev/null +++ b/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx @@ -0,0 +1,18 @@ +import SummaryUI from "../UI" +import SummaryBottomSheet from "./BottomSheet" + +import styles from "./mobile.module.css" + +import type { SummaryProps } from "@/types/components/hotelReservation/summary" + +export default function MobileSummary(props: SummaryProps) { + return ( +
+ +
+ +
+
+
+ ) +} diff --git a/components/HotelReservation/EnterDetails/Summary/Mobile/mobile.module.css b/components/HotelReservation/EnterDetails/Summary/Mobile/mobile.module.css new file mode 100644 index 000000000..30b077e92 --- /dev/null +++ b/components/HotelReservation/EnterDetails/Summary/Mobile/mobile.module.css @@ -0,0 +1,20 @@ +.mobileSummary { + display: block; +} + +@media screen and (max-width: 1366px) { + .wrapper { + background-color: var(--Main-Grey-White); + border-color: var(--Primary-Light-On-Surface-Divider-subtle); + border-style: solid; + border-width: 1px; + border-bottom: none; + z-index: 10; + } +} + +@media screen and (min-width: 1367px) { + .mobileSummary { + display: none; + } +} diff --git a/components/HotelReservation/Summary/index.tsx b/components/HotelReservation/EnterDetails/Summary/UI/index.tsx similarity index 64% rename from components/HotelReservation/Summary/index.tsx rename to components/HotelReservation/EnterDetails/Summary/UI/index.tsx index 1b309362a..5e61ef155 100644 --- a/components/HotelReservation/Summary/index.tsx +++ b/components/HotelReservation/EnterDetails/Summary/UI/index.tsx @@ -2,6 +2,7 @@ import { useIntl } from "react-intl" import { dt } from "@/lib/dt" +import { useEnterDetailsStore } from "@/stores/enter-details" import { ArrowRightIcon, ChevronDownSmallIcon } from "@/components/Icons" import Button from "@/components/TempDesignSystem/Button" @@ -13,25 +14,58 @@ import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import useLang from "@/hooks/useLang" -import styles from "./summary.module.css" +import styles from "./ui.module.css" import type { SummaryProps } from "@/types/components/hotelReservation/summary" import { CurrencyEnum } from "@/types/enums/currency" +import type { DetailsState } from "@/types/stores/enter-details" -export default function Summary({ - bedType, - breakfast, - fromDate, - showMemberPrice, - room, - toDate, - toggleSummaryOpen, - totalPrice, +export function storeSelector(state: DetailsState) { + return { + bedType: state.bedType, + booking: state.booking, + breakfast: state.breakfast, + join: state.guest.join, + membershipNo: state.guest.membershipNo, + packages: state.packages, + roomRate: state.roomRate, + roomPrice: state.roomPrice, + toggleSummaryOpen: state.actions.toggleSummaryOpen, + totalPrice: state.totalPrice, + } +} + +export default function SummaryUI({ + cancellationText, + isMember, + rateDetails, + roomType, }: SummaryProps) { const intl = useIntl() const lang = useLang() - const diff = dt(toDate).diff(fromDate, "days") + const { + bedType, + booking, + breakfast, + join, + membershipNo, + packages, + roomPrice, + roomRate, + toggleSummaryOpen, + totalPrice, + } = useEnterDetailsStore(storeSelector) + + const adults = booking.rooms[0].adults + const children = booking.rooms[0].children + + const showMemberPrice = !!( + (isMember || join || membershipNo) && + roomRate.memberRate + ) + + const diff = dt(booking.toDate).diff(booking.fromDate, "days") const nights = intl.formatMessage( { id: "booking.nights" }, @@ -51,9 +85,9 @@ export default function Summary({ {intl.formatMessage({ id: "Summary" })} - {dt(fromDate).locale(lang).format("ddd, D MMM")} + {dt(booking.fromDate).locale(lang).format("ddd, D MMM")} - {dt(toDate).locale(lang).format("ddd, D MMM")} ({nights}) + {dt(booking.toDate).locale(lang).format("ddd, D MMM")} ({nights})
- {intl.formatMessage( - { id: "{amount} {currency}" }, - { amount: "0", currency: room.roomPrice.local.currency } - )} + {intl.formatNumber(0, { + currency: roomPrice.local.currency, + style: "currency", + })}
) : null} @@ -159,10 +185,10 @@ export default function Summary({ {intl.formatMessage({ id: "No breakfast" })} - {intl.formatMessage( - { id: "{amount} {currency}" }, - { amount: "0", currency: room.roomPrice.local.currency } - )} + {intl.formatNumber(0, { + currency: roomPrice.local.currency, + style: "currency", + })}
) : null} @@ -172,13 +198,10 @@ export default function Summary({ {intl.formatMessage({ id: "Breakfast buffet" })} - {intl.formatMessage( - { id: "{amount} {currency}" }, - { - amount: breakfast.localPrice.totalPrice, - currency: breakfast.localPrice.currency, - } - )} + {intl.formatNumber(parseInt(breakfast.localPrice.totalPrice), { + currency: breakfast.localPrice.currency, + style: "currency", + })} ) : null} @@ -199,30 +222,18 @@ export default function Summary({
- {intl.formatMessage( - { id: "{amount} {currency}" }, - { - amount: intl.formatNumber(totalPrice.local.price, { - currency: totalPrice.local.currency, - style: "currency", - }), - currency: totalPrice.local.currency, - } - )} + {intl.formatNumber(totalPrice.local.price, { + currency: totalPrice.local.currency, + style: "currency", + })} {totalPrice.euro && ( {intl.formatMessage({ id: "Approx." })}{" "} - {intl.formatMessage( - { id: "{amount} {currency}" }, - { - amount: intl.formatNumber(totalPrice.euro.price, { - currency: CurrencyEnum.EUR, - style: "currency", - }), - currency: totalPrice.euro.currency, - } - )} + {intl.formatNumber(totalPrice.euro.price, { + currency: CurrencyEnum.EUR, + style: "currency", + })} )}
diff --git a/components/HotelReservation/Summary/summary.module.css b/components/HotelReservation/EnterDetails/Summary/UI/ui.module.css similarity index 100% rename from components/HotelReservation/Summary/summary.module.css rename to components/HotelReservation/EnterDetails/Summary/UI/ui.module.css diff --git a/components/HotelReservation/EnterDetails/Summary/index.tsx b/components/HotelReservation/EnterDetails/Summary/index.tsx deleted file mode 100644 index a649788ea..000000000 --- a/components/HotelReservation/EnterDetails/Summary/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { redirect } from "next/navigation" - -import { selectRate } from "@/constants/routes/hotelReservation" -import { - getProfileSafely, - getSelectedRoomAvailability, -} from "@/lib/trpc/memoizedRequests" - -import { generateChildrenString } from "@/components/HotelReservation/SelectRate/RoomSelection/utils" -import { getLang } from "@/i18n/serverContext" - -import ClientSummary from "./Client" - -import type { SummaryPageProps } from "@/types/components/hotelReservation/summary" - -export default async function Summary({ - adults, - fromDate, - hotelId, - kids, - packageCodes, - rateCode, - roomTypeCode, - toDate, -}: SummaryPageProps) { - const lang = getLang() - - const availability = await getSelectedRoomAvailability({ - adults, - children: kids ? generateChildrenString(kids) : undefined, - hotelId, - packageCodes, - rateCode, - roomStayStartDate: fromDate, - roomStayEndDate: toDate, - roomTypeCode, - }) - const user = await getProfileSafely() - - if (!availability || !availability.selectedRoom) { - console.error("No hotel or availability data", availability) - // TODO: handle this case - redirect(selectRate(lang)) - } - - return ( - - ) -} diff --git a/components/HotelReservation/HotelCard/index.tsx b/components/HotelReservation/HotelCard/index.tsx index f6fbbcf12..5c2f788ee 100644 --- a/components/HotelReservation/HotelCard/index.tsx +++ b/components/HotelReservation/HotelCard/index.tsx @@ -41,18 +41,11 @@ function HotelCard({ const { hotelData } = hotel const { price } = hotel - const amenities = hotelData.detailedFacilities.slice(0, 5) - - const classNames = hotelCardVariants({ - type, - state, - }) - const handleMouseEnter = useCallback(() => { - if (onHotelCardHover) { + if (onHotelCardHover && hotelData) { onHotelCardHover(hotelData.name) } - }, [onHotelCardHover, hotelData.name]) + }, [onHotelCardHover, hotelData]) const handleMouseLeave = useCallback(() => { if (onHotelCardHover) { @@ -60,6 +53,15 @@ function HotelCard({ } }, [onHotelCardHover]) + if (!hotel || !hotelData) return null + + const amenities = hotelData.detailedFacilities.slice(0, 5) + + const classNames = hotelCardVariants({ + type, + state, + }) + return (
{hotelData.name} diff --git a/components/HotelReservation/HotelCardDialogListing/index.tsx b/components/HotelReservation/HotelCardDialogListing/index.tsx index 123cc4ced..4f9e83b36 100644 --- a/components/HotelReservation/HotelCardDialogListing/index.tsx +++ b/components/HotelReservation/HotelCardDialogListing/index.tsx @@ -17,7 +17,7 @@ export default function HotelCardDialogListing({ activeCard, onActiveCardChange, }: HotelCardDialogListingProps) { - const hotelsPinData = getHotelPins(hotels) + const hotelsPinData = hotels ? getHotelPins(hotels) : [] const activeCardRef = useRef(null) const observerRef = useRef(null) const dialogRef = useRef(null) @@ -77,7 +77,7 @@ export default function HotelCardDialogListing({ return (
- {hotelsPinData?.length && + {!!hotelsPinData?.length && hotelsPinData.map((data) => { const isActive = data.name === activeCard return ( diff --git a/components/HotelReservation/HotelCardDialogListing/utils.ts b/components/HotelReservation/HotelCardDialogListing/utils.ts index 2299b3f35..1a0e05ad8 100644 --- a/components/HotelReservation/HotelCardDialogListing/utils.ts +++ b/components/HotelReservation/HotelCardDialogListing/utils.ts @@ -2,6 +2,8 @@ import type { HotelData } from "@/types/components/hotelReservation/selectHotel/ import type { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" export function getHotelPins(hotels: HotelData[]): HotelPin[] { + if (hotels.length === 0) return [] + return hotels.map((hotel) => ({ coordinates: { lat: hotel.hotelData.location.latitude, diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index f2ae9d57f..c800c3cc9 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -4,8 +4,10 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useCallback, useEffect, useMemo } from "react" import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" +import { useMediaQuery } from "usehooks-ts" import { z } from "zod" +import { InfoCircleIcon } from "@/components/Icons" import CheckboxChip from "@/components/TempDesignSystem/Form/FilterChip/Checkbox" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" @@ -25,6 +27,8 @@ export default function RoomFilter({ onFilter, filterOptions, }: RoomFilterProps) { + const isAboveMobile = useMediaQuery("(min-width: 768px)") + const initialFilterValues = useMemo( () => filterOptions.reduce( @@ -55,6 +59,8 @@ export default function RoomFilter({ id: "Pet-friendly rooms have an additional fee of 20 EUR per stay", }) + const showTooltip = isAboveMobile && petFriendly + const submitFilter = useCallback(() => { const data = getValues() onFilter(data) @@ -77,19 +83,50 @@ export default function RoomFilter({
- - {intl.formatMessage({ id: "Filter" })} - - - {Object.entries(selectedFilters) - .filter(([_, value]) => value) - .map(([key]) => intl.formatMessage({ id: key })) - .join(", ")} - + {!isAboveMobile ? ( + + + + + {intl.formatMessage({ id: "Filter" })} + + + {Object.entries(selectedFilters) + .filter(([_, value]) => value) + .map(([key]) => intl.formatMessage({ id: key })) + .join(", ")} + + + ) : ( + <> + + {intl.formatMessage({ id: "Filter" })} + + + {Object.entries(selectedFilters) + .filter(([_, value]) => value) + .map(([key]) => intl.formatMessage({ id: key })) + .join(", ")} + + + )}
{intl.formatMessage( @@ -118,11 +155,11 @@ export default function RoomFilter({ disabled={isDisabled} selected={getValues(code)} Icon={getIconForFeatureCode(code)} - hasTooltip={isPetRoom} + hasTooltip={isPetRoom && isAboveMobile} /> ) - return isPetRoom ? ( + return showTooltip ? ( ) { + const classNames = sidePanelVariants({ variant }) + return ( +
+
+
{children}
+
+
+ ) +} diff --git a/components/HotelReservation/EnterDetails/Summary/summary.module.css b/components/HotelReservation/SidePanel/sidePanel.module.css similarity index 71% rename from components/HotelReservation/EnterDetails/Summary/summary.module.css rename to components/HotelReservation/SidePanel/sidePanel.module.css index 5cc412082..6485aa89d 100644 --- a/components/HotelReservation/EnterDetails/Summary/summary.module.css +++ b/components/HotelReservation/SidePanel/sidePanel.module.css @@ -1,68 +1,62 @@ -.mobileSummary { - display: block; -} - -.desktopSummary { - display: none; -} - -.summary { - background-color: var(--Main-Grey-White); - - border-color: var(--Primary-Light-On-Surface-Divider-subtle); - border-style: solid; - border-width: 1px; - border-bottom: none; - z-index: 10; -} - -.hider { - display: none; -} - +.sidePanel, +.hider, .shadow { display: none; } @media screen and (min-width: 1367px) { - .mobileSummary { - display: none; - } - - .desktopSummary { + .sidePanel { display: grid; grid-template-rows: auto auto 1fr; - margin-top: calc(0px - var(--Spacing-x9)); } .summary { + margin-top: calc(0px - var(--Spacing-x9)); + } + + .hider { + display: block; + position: sticky; + } + + .receipt .hider { + background-color: var(--Main-Grey-White); + height: 150px; + margin-top: -78px; + top: -40px; + } + + .summary .hider { + background-color: var(--Scandic-Brand-Warm-White); + height: 40px; + margin-top: var(--Spacing-x4); + top: calc(var(--booking-widget-desktop-height) - 6px); + } + + .wrapper { + background-color: var(--Main-Grey-White); + border-color: var(--Primary-Light-On-Surface-Divider-subtle); + border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0; + border-style: solid; + border-width: 1px; + border-bottom: none; + margin-top: calc(0px - var(--Spacing-x9)); position: sticky; top: calc( var(--booking-widget-desktop-height) + var(--Spacing-x2) + var(--Spacing-x-half) ); z-index: 9; - border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0; - margin-top: calc(0px - var(--Spacing-x9)); } .shadow { - display: block; background-color: var(--Main-Grey-White); border-color: var(--Primary-Light-On-Surface-Divider-subtle); - border-style: solid; border-left-width: 1px; border-right-width: 1px; - border-top: none; + border-style: solid; border-bottom: none; - } - - .hider { + border-top: none; display: block; - background-color: var(--Scandic-Brand-Warm-White); - position: sticky; - top: calc(var(--booking-widget-desktop-height) - 6px); - margin-top: var(--Spacing-x4); - height: 40px; } } diff --git a/components/HotelReservation/SidePanel/variants.ts b/components/HotelReservation/SidePanel/variants.ts new file mode 100644 index 000000000..a6a3a3825 --- /dev/null +++ b/components/HotelReservation/SidePanel/variants.ts @@ -0,0 +1,15 @@ +import { cva } from "class-variance-authority" + +import styles from "./sidePanel.module.css" + +export const sidePanelVariants = cva(styles.sidePanel, { + variants: { + variant: { + receipt: styles.receipt, + summary: styles.summary, + }, + }, + defaultVariants: { + variant: "summary", + }, +}) diff --git a/components/Icons/FilledHeart.tsx b/components/Icons/FilledHeart.tsx new file mode 100644 index 000000000..f254dade7 --- /dev/null +++ b/components/Icons/FilledHeart.tsx @@ -0,0 +1,27 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function FilledHeartIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + ) +} diff --git a/components/JsonToHtml/renderOptions.tsx b/components/JsonToHtml/renderOptions.tsx index 845813508..5d16a3bc6 100644 --- a/components/JsonToHtml/renderOptions.tsx +++ b/components/JsonToHtml/renderOptions.tsx @@ -309,6 +309,29 @@ export const renderOptions: RenderOptions = { ) }, + [RTETypeEnum.span]: ( + node: RTEDefaultNode, + embeds: EmbedByUid, + next: RTENext, + fullRenderOptions: RenderOptions + ) => { + let props = extractPossibleAttributes(node.attrs) + const className = props.className + + if (className) { + if (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 + props.className = styles[className] + } + } + + return ( + {next(node.children, embeds, fullRenderOptions)} + ) + }, + [RTETypeEnum.reference]: ( node: RTENode, embeds: EmbedByUid, diff --git a/components/LanguageSwitcher/LanguageSwitcherContent/index.tsx b/components/LanguageSwitcher/LanguageSwitcherContent/index.tsx index 19cc69961..2f684d96e 100644 --- a/components/LanguageSwitcher/LanguageSwitcherContent/index.tsx +++ b/components/LanguageSwitcher/LanguageSwitcherContent/index.tsx @@ -1,5 +1,6 @@ "use client" +import { usePathname } from "next/navigation" import { useIntl } from "react-intl" import { Lang, languages } from "@/constants/languages" @@ -10,6 +11,8 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import useLang from "@/hooks/useLang" import { useTrapFocus } from "@/hooks/useTrapFocus" +import { replaceUrlPart } from "./utils" + import styles from "./languageSwitcherContent.module.css" import type { LanguageSwitcherContentProps } from "@/types/components/languageSwitcher/languageSwitcher" @@ -24,6 +27,8 @@ export default function LanguageSwitcherContent({ const languageSwitcherRef = useTrapFocus() const urlKeys = Object.keys(urls) as Lang[] + const pathname = usePathname() + return (
@@ -39,8 +44,9 @@ export default function LanguageSwitcherContent({
  • {languages[key]} {isActive ? : null} diff --git a/components/LanguageSwitcher/LanguageSwitcherContent/utils.ts b/components/LanguageSwitcher/LanguageSwitcherContent/utils.ts new file mode 100644 index 000000000..ee9c27a1d --- /dev/null +++ b/components/LanguageSwitcher/LanguageSwitcherContent/utils.ts @@ -0,0 +1,19 @@ +export function replaceUrlPart(currentPath: string, newPart: string): string { + const pathSegments = currentPath.split("/").filter((segment) => segment) + + const newPathSegments = newPart + .replace(/\/$/, "") + .split("/") + .filter((segment) => segment) + + const isFullPathReplacement = newPathSegments.length > 1 + + if (isFullPathReplacement) { + return `/${newPathSegments.join("/")}` + } + + const updatedPathSegments = pathSegments.slice(1) + const updatedPath = `/${newPathSegments.concat(updatedPathSegments).join("/")}` + + return updatedPath +} diff --git a/components/LanguageSwitcher/index.tsx b/components/LanguageSwitcher/index.tsx index f087502c0..7eb47c90c 100644 --- a/components/LanguageSwitcher/index.tsx +++ b/components/LanguageSwitcher/index.tsx @@ -36,6 +36,7 @@ export default function LanguageSwitcher({ isHeaderLanguageSwitcherMobileOpen, isHeaderLanguageSwitcherOpen, } = useDropdownStore() + const languageSwitcherRef = useRef(null) const isFooter = type === LanguageSwitcherTypesEnum.Footer const isHeader = !isFooter diff --git a/components/Maps/StaticMap/index.tsx b/components/Maps/StaticMap/index.tsx index 7c80aa01f..6db5eada3 100644 --- a/components/Maps/StaticMap/index.tsx +++ b/components/Maps/StaticMap/index.tsx @@ -5,6 +5,25 @@ import { getUrlWithSignature } from "@/utils/map" import { StaticMapProps } from "@/types/components/maps/staticMap" +function getCenter({ + coordinates, + city, + country, +}: { + coordinates?: { lat: number; lng: number } + city?: string + country?: string +}): string | undefined { + switch (true) { + case !!coordinates: + return `${coordinates.lat},${coordinates.lng}` + case !!country: + return `${city}, ${country}` + default: + return city + } +} + export default function StaticMap({ city, country, @@ -19,9 +38,7 @@ export default function StaticMap({ const key = env.GOOGLE_STATIC_MAP_KEY const secret = env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET const baseUrl = "https://maps.googleapis.com/maps/api/staticmap" - const center = coordinates - ? `${coordinates.lat},${coordinates.lng}` - : `${city}, ${country}` + const center = getCenter({ coordinates, city, country }) if (!center) { return null diff --git a/components/SidePeeks/HotelSidePeek/Accordions/Accessibility.tsx b/components/SidePeeks/HotelSidePeek/Accordions/Accessibility.tsx new file mode 100644 index 000000000..be0fac660 --- /dev/null +++ b/components/SidePeeks/HotelSidePeek/Accordions/Accessibility.tsx @@ -0,0 +1,21 @@ +import { useIntl } from "react-intl" + +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Body from "@/components/TempDesignSystem/Text/Body" + +import { AccessibilityProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" +import { IconName } from "@/types/components/icon" + +export default function Accessibility({ + accessibilityElevatorPitchText, +}: AccessibilityProps) { + const intl = useIntl() + return ( + + {accessibilityElevatorPitchText} + + ) +} diff --git a/components/SidePeeks/HotelSidePeek/Accordions/CheckInCheckOut.tsx b/components/SidePeeks/HotelSidePeek/Accordions/CheckInCheckOut.tsx new file mode 100644 index 000000000..d005e6688 --- /dev/null +++ b/components/SidePeeks/HotelSidePeek/Accordions/CheckInCheckOut.tsx @@ -0,0 +1,22 @@ +import { useIntl } from "react-intl" + +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Body from "@/components/TempDesignSystem/Text/Body" + +import { CheckInCheckOutProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" +import { IconName } from "@/types/components/icon" + +export default function CheckinCheckOut({ checkin }: CheckInCheckOutProps) { + const intl = useIntl() + + return ( + + {intl.formatMessage({ id: "Hours" })} + {`${intl.formatMessage({ id: "Check in from" })}: ${checkin.checkInTime}`} + {`${intl.formatMessage({ id: "Check out at latest" })}: ${checkin.checkOutTime}`} + + ) +} diff --git a/components/SidePeeks/HotelSidePeek/Accordions/MeetingsAndConferences.tsx b/components/SidePeeks/HotelSidePeek/Accordions/MeetingsAndConferences.tsx new file mode 100644 index 000000000..558046d4b --- /dev/null +++ b/components/SidePeeks/HotelSidePeek/Accordions/MeetingsAndConferences.tsx @@ -0,0 +1,21 @@ +import { useIntl } from "react-intl" + +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Body from "@/components/TempDesignSystem/Text/Body" + +import { MeetingsAndConferencesProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" +import { IconName } from "@/types/components/icon" + +export default function MeetingsAndConferences({ + meetingDescription, +}: MeetingsAndConferencesProps) { + const intl = useIntl() + return ( + + {meetingDescription} + + ) +} diff --git a/components/SidePeeks/HotelSidePeek/Accordions/Parking.tsx b/components/SidePeeks/HotelSidePeek/Accordions/Parking.tsx new file mode 100644 index 000000000..0e5ffcec0 --- /dev/null +++ b/components/SidePeeks/HotelSidePeek/Accordions/Parking.tsx @@ -0,0 +1,55 @@ +import { useIntl } from "react-intl" + +import FilledHeartIcon from "@/components/Icons/FilledHeart" +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" + +import styles from "./sidePeekAccordion.module.css" + +import { ParkingProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" +import { IconName } from "@/types/components/icon" + +export default function Parking({ parking }: ParkingProps) { + const intl = useIntl() + return ( + + {parking.map((p) => ( +
    + + {`${intl.formatMessage({ id: p.type })} ${p?.name ? ` (${p.name})` : ""}`} + +
      + {p?.address && ( +
    • + + {`${intl.formatMessage({ id: "Address" })}: ${p.address}`} +
    • + )} + {p?.numberOfParkingSpots !== undefined && ( +
    • + + {intl.formatMessage( + { id: "Number of parking spots" }, + { number: p.numberOfParkingSpots } + )} +
    • + )} + {p?.numberOfChargingSpaces !== undefined && ( +
    • + + {intl.formatMessage( + { id: "Number of charging points for electric cars" }, + { number: p.numberOfChargingSpaces } + )} +
    • + )} +
    +
    + ))} +
    + ) +} diff --git a/components/SidePeeks/HotelSidePeek/Accordions/Restaurant.tsx b/components/SidePeeks/HotelSidePeek/Accordions/Restaurant.tsx new file mode 100644 index 000000000..a8d1cd047 --- /dev/null +++ b/components/SidePeeks/HotelSidePeek/Accordions/Restaurant.tsx @@ -0,0 +1,22 @@ +import { useIntl } from "react-intl" + +import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem" +import Body from "@/components/TempDesignSystem/Text/Body" + +import { RestaurantProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" +import { IconName } from "@/types/components/icon" + +export default function Restaurant({ + restaurantsContentDescriptionMedium, +}: RestaurantProps) { + const intl = useIntl() + + return ( + + {restaurantsContentDescriptionMedium} + + ) +} diff --git a/components/SidePeeks/HotelSidePeek/Accordions/sidePeekAccordion.module.css b/components/SidePeeks/HotelSidePeek/Accordions/sidePeekAccordion.module.css new file mode 100644 index 000000000..bd2ae8933 --- /dev/null +++ b/components/SidePeeks/HotelSidePeek/Accordions/sidePeekAccordion.module.css @@ -0,0 +1,23 @@ +.list { + font-family: var(--typography-Body-Regular-fontFamily); + list-style-position: inside; + list-style-type: none; + margin-top: var(--Spacing-x-one-and-half); +} + +.list li { + display: flex; + align-items: center; + gap: var(--Spacing-x1); + padding-left: var(--Spacing-x1); +} + +.list li svg { + flex-shrink: 0; +} + +.parking details > div { + display: flex; + flex-direction: column; + gap: var(--Spacing-x2); +} diff --git a/components/SidePeeks/HotelSidePeek/hotelSidePeek.module.css b/components/SidePeeks/HotelSidePeek/hotelSidePeek.module.css index 37fb3aeab..23b92b792 100644 --- a/components/SidePeeks/HotelSidePeek/hotelSidePeek.module.css +++ b/components/SidePeeks/HotelSidePeek/hotelSidePeek.module.css @@ -22,8 +22,3 @@ .noIcon { margin-left: var(--Spacing-x4); } - -.list { - font-family: var(--typography-Body-Regular-fontFamily); - list-style: inside; -} diff --git a/components/SidePeeks/HotelSidePeek/index.tsx b/components/SidePeeks/HotelSidePeek/index.tsx index e1e0d55ca..4fc5bab8c 100644 --- a/components/SidePeeks/HotelSidePeek/index.tsx +++ b/components/SidePeeks/HotelSidePeek/index.tsx @@ -2,29 +2,21 @@ import { useIntl } from "react-intl" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" import Contact from "@/components/HotelReservation/Contact" -import { AccessibilityIcon } 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 Accessibility from "./Accordions/Accessibility" +import CheckinCheckOut from "./Accordions/CheckInCheckOut" +import MeetingsAndConferences from "./Accordions/MeetingsAndConferences" +import Parking from "./Accordions/Parking" +import Restaurant from "./Accordions/Restaurant" + import styles from "./hotelSidePeek.module.css" import type { HotelSidePeekProps } from "@/types/components/hotelReservation/hotelSidePeek" -import type { ParkingProps } from "@/types/components/hotelReservation/selectHotel/selectHotel" import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek" -import { IconName } from "@/types/components/icon" -import type { Amenities, Hotel } from "@/types/hotel" - -function getAmenitiesList(hotel: Hotel) { - const detailedAmenities: Amenities = hotel.detailedFacilities.filter( - // Remove Parking facilities since parking accordion is based on hotel.parking - (facility) => !facility.name.startsWith("Parking") && facility.public - ) - return detailedAmenities -} export default function HotelSidePeek({ hotel, @@ -33,7 +25,12 @@ export default function HotelSidePeek({ showCTA, }: HotelSidePeekProps) { const intl = useIntl() - const amenitiesList = getAmenitiesList(hotel) + const amenitiesList = hotel.detailedFacilities.filter( + (facility) => facility.public + ) + const parking = hotel.parking.filter( + (p) => p?.numberOfParkingSpots || p?.numberOfChargingSpaces || p?.address + ) return (
    - + {intl.formatMessage({ id: "Practical information" })} - {/* parking */} - {hotel.parking.length ? ( - - {hotel.parking.map((p) => ( - - ))} - - ) : null} -
    - 0 && } + {hotel.hotelContent?.restaurantsOverviewPage + ?.restaurantsContentDescriptionMedium && ( + - {intl.formatMessage({ id: "Accessibility" })} -
    + )} + {hotel?.accessibilityElevatorPitchText && ( + + )} + {hotel.hotelFacts?.checkin && ( + + )} + {hotel.hotelContent.texts?.meetingDescription?.medium && ( + + )} {amenitiesList.map((amenity) => { const Icon = mapFacilityToIcon(amenity.id) return ( @@ -84,54 +89,13 @@ export default function HotelSidePeek({ ) })}
    - {showCTA && ( - /* TODO: handle linking to Hotel Page */ - - )} + {/* TODO: handle linking to Hotel Page */} + {/* {showCTA && ( + + )} */}
    ) } - -function Parking({ parking }: ParkingProps) { - const intl = useIntl() - return ( -
    - - {`${intl.formatMessage({ id: parking.type })}${parking?.name ? ` (${parking.name})` : ""}`} - -
      - {parking?.numberOfChargingSpaces !== undefined && ( -
    • - {intl.formatMessage( - { id: "Number of charging points for electric cars" }, - { number: parking.numberOfChargingSpaces } - )} -
    • - )} - {parking?.canMakeReservation && ( -
    • {`${intl.formatMessage({ id: "Parking can be reserved in advance" })}: ${parking.canMakeReservation ? intl.formatMessage({ id: "Yes" }) : intl.formatMessage({ id: "No" })}`}
    • - )} - {parking?.numberOfParkingSpots !== undefined && ( -
    • - {intl.formatMessage( - { id: "Number of parking spots" }, - { number: parking.numberOfParkingSpots } - )} -
    • - )} - {parking?.distanceToHotel !== undefined && ( -
    • - {intl.formatMessage( - { id: "Distance to hotel" }, - { distance: parking.distanceToHotel } - )} -
    • - )} - {parking?.address && ( -
    • {`${intl.formatMessage({ id: "Address" })}: ${parking.address}`}
    • - )} -
    -
    - ) -} diff --git a/components/TempDesignSystem/Accordion/AccordionItem/index.tsx b/components/TempDesignSystem/Accordion/AccordionItem/index.tsx index be877dd9e..5831be33e 100644 --- a/components/TempDesignSystem/Accordion/AccordionItem/index.tsx +++ b/components/TempDesignSystem/Accordion/AccordionItem/index.tsx @@ -5,6 +5,8 @@ import { useRef } from "react" import { ChevronDownIcon } from "@/components/Icons" import { getIconByIconName } from "@/components/Icons/get-icon-by-icon-name" +import Body from "../../Text/Body" +import Subtitle from "../../Text/Subtitle" import { accordionItemVariants } from "./variants" import styles from "./accordionItem.module.css" @@ -49,7 +51,23 @@ export default function AccordionItem({
    {IconComp && } - {title} + {variant === "card" ? ( + + {title} + + ) : ( + + {title} + + )} { + if (formState.isSubmitting) return + + if (!(day && month && year) && dateValue) { + setValue(DateName.day, Number(dateValue.day)) + setValue(DateName.month, Number(dateValue.month)) + setValue(DateName.year, Number(dateValue.year)) + } + }, [setValue, formState.isSubmitting, dateValue, day, month, year]) + return ( ) } + +function getDefaultCountryFromLang(lang: Lang): LowerCaseCountryCode { + const countryMap: Record = { + sv: "se", + da: "dk", + fi: "fi", + no: "no", + de: "de", + en: "se", // Default to Sweden for English + } + return countryMap[lang] || "se" +} diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css index 3a997bddb..da8dc802a 100644 --- a/components/TempDesignSystem/Link/link.module.css +++ b/components/TempDesignSystem/Link/link.module.css @@ -16,7 +16,8 @@ .breadcrumb { font-family: var(--typography-Footnote-Bold-fontFamily); font-size: var(--typography-Footnote-Bold-fontSize); - font-weight: 500; /* var(--typography-Footnote-Bold-fontWeight); */ + font-weight: 500; + /* var(--typography-Footnote-Bold-fontWeight); */ letter-spacing: var(--typography-Footnote-Bold-letterSpacing); line-height: var(--typography-Footnote-Bold-lineHeight); } @@ -24,7 +25,8 @@ .link.breadcrumb { font-family: var(--typography-Footnote-Bold-fontFamily); font-size: var(--typography-Footnote-Bold-fontSize); - font-weight: 500; /* var(--typography-Footnote-Bold-fontWeight); */ + font-weight: 500; + /* var(--typography-Footnote-Bold-fontWeight); */ letter-spacing: var(--typography-Footnote-Bold-letterSpacing); line-height: var(--typography-Footnote-Bold-lineHeight); } @@ -159,12 +161,15 @@ color: var(--Scandic-Peach-50); } -.peach80 { +.peach80, +.baseTextMediumContrast { color: var(--Base-Text-Medium-contrast); } .peach80:hover, -.peach80:active { +.peach80:active, +.baseTextMediumContrast:hover, +.baseTextMediumContrast:active { color: var(--Base-Text-High-contrast); } @@ -235,6 +240,7 @@ letter-spacing: var(--typography-Caption-Bold-letterSpacing); line-height: var(--typography-Caption-Bold-lineHeight); } + .bold { font-family: var(--typography-Body-Bold-fontFamily); font-size: var(--typography-Body-Bold-fontSize); diff --git a/components/TempDesignSystem/Link/variants.ts b/components/TempDesignSystem/Link/variants.ts index 0a0c1dd6d..c93b93189 100644 --- a/components/TempDesignSystem/Link/variants.ts +++ b/components/TempDesignSystem/Link/variants.ts @@ -9,6 +9,7 @@ export const linkVariants = cva(styles.link, { }, color: { baseButtonTextOnFillNormal: styles.baseButtonTextOnFillNormal, + baseTextMediumContrast: styles.baseTextMediumContrast, black: styles.black, burgundy: styles.burgundy, none: "", diff --git a/components/TempDesignSystem/Text/Subtitle/subtitle.module.css b/components/TempDesignSystem/Text/Subtitle/subtitle.module.css index b15623491..10d3773dd 100644 --- a/components/TempDesignSystem/Text/Subtitle/subtitle.module.css +++ b/components/TempDesignSystem/Text/Subtitle/subtitle.module.css @@ -63,7 +63,7 @@ color: var(--Scandic-Brand-Pale-Peach); } -.baseTextMediumContrast { +.baseTextHighContrast { color: var(--Base-Text-High-contrast); } @@ -86,3 +86,7 @@ .baseTextDisabled { color: var(--Base-Text-Disabled); } + +.mainGrey60 { + color: var(--Main-Grey-60); +} diff --git a/components/TempDesignSystem/Text/Subtitle/variants.ts b/components/TempDesignSystem/Text/Subtitle/variants.ts index f2edbb9f5..8f5bc72dd 100644 --- a/components/TempDesignSystem/Text/Subtitle/variants.ts +++ b/components/TempDesignSystem/Text/Subtitle/variants.ts @@ -9,11 +9,12 @@ const config = { burgundy: styles.burgundy, baseTextDisabled: styles.baseTextDisabled, pale: styles.pale, - baseTextMediumContrast: styles.baseTextMediumContrast, + baseTextHighContrast: styles.baseTextHighContrast, uiTextHighContrast: styles.uiTextHighContrast, uiTextMediumContrast: styles.uiTextMediumContrast, uiTextPlaceholder: styles.uiTextPlaceholder, red: styles.red, + mainGrey60: styles.mainGrey60, }, textAlign: { center: styles.center, diff --git a/components/TempDesignSystem/Toasts/index.tsx b/components/TempDesignSystem/Toasts/index.tsx index 407eef5a1..899b97672 100644 --- a/components/TempDesignSystem/Toasts/index.tsx +++ b/components/TempDesignSystem/Toasts/index.tsx @@ -32,7 +32,7 @@ function getIcon(variant: ToastsProps["variant"]) { } } -export function Toast({ message, onClose, variant }: ToastsProps) { +export function Toast({ children, message, onClose, variant }: ToastsProps) { const className = toastVariants({ variant }) const Icon = getIcon(variant) return ( @@ -40,10 +40,16 @@ export function Toast({ message, onClose, variant }: ToastsProps) {
    {Icon && }
    - {message} - + {message ? ( + {message} + ) : ( +
    {children}
    + )} + {onClose ? ( + + ) : null}
  • ) } diff --git a/components/TempDesignSystem/Toasts/toasts.module.css b/components/TempDesignSystem/Toasts/toasts.module.css index d49b8d57e..e5f811cb4 100644 --- a/components/TempDesignSystem/Toasts/toasts.module.css +++ b/components/TempDesignSystem/Toasts/toasts.module.css @@ -8,6 +8,11 @@ align-items: center; } +.content { + padding: var(--Spacing-x-one-and-half) var(--Spacing-x3) + var(--Spacing-x-one-and-half) var(--Spacing-x2); +} + @media screen and (min-width: 768px) { .toast { width: var(--width); diff --git a/components/TempDesignSystem/Toasts/toasts.ts b/components/TempDesignSystem/Toasts/toasts.ts index 573f50a1f..1bf37d114 100644 --- a/components/TempDesignSystem/Toasts/toasts.ts +++ b/components/TempDesignSystem/Toasts/toasts.ts @@ -2,9 +2,16 @@ import { toastVariants } from "./variants" import type { VariantProps } from "class-variance-authority" -export interface ToastsProps - extends Omit, "color">, - VariantProps { - message: React.ReactNode - onClose: () => void -} +export type ToastsProps = Omit, "color"> & + VariantProps & { + onClose?: () => void + } & ( + | { + children: React.ReactNode + message?: never + } + | { + children?: never + message: React.ReactNode + } + ) diff --git a/components/TempDesignSystem/Tooltip/index.tsx b/components/TempDesignSystem/Tooltip/index.tsx index d73252161..855bd1237 100644 --- a/components/TempDesignSystem/Tooltip/index.tsx +++ b/components/TempDesignSystem/Tooltip/index.tsx @@ -14,12 +14,20 @@ export function Tooltip

    ({ position, arrow, children, + isTouchable = false, }: PropsWithChildren>) { const className = tooltipVariants({ position, arrow }) const [isActive, setIsActive] = useState(false) function handleToggle() { - setIsActive(!isActive) + setIsActive((prevState) => !prevState) + } + + function handleKeyDown(event: React.KeyboardEvent) { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault() + handleToggle() + } } return ( @@ -27,8 +35,10 @@ export function Tooltip

    ({ className={styles.tooltipContainer} role="tooltip" aria-label={text} - onClick={handleToggle} - onTouchStart={handleToggle} + tabIndex={0} + onClick={isTouchable ? undefined : handleToggle} + onTouchStart={isTouchable ? handleToggle : undefined} + onKeyDown={handleKeyDown} data-active={isActive} >

    diff --git a/constants/routes/signup.ts b/constants/routes/signup.ts index 4c63cc47d..3779c6e82 100644 --- a/constants/routes/signup.ts +++ b/constants/routes/signup.ts @@ -1,12 +1,12 @@ import { LangRoute } from "@/types/routes" export const signup: LangRoute = { - en: "/en/scandic-friends/join-scandic-friends", + en: "/en/scandic-friends/join", sv: "/sv/scandic-friends/bli-medlem", - no: "/no/scandic-friends/registrer-deg-for-scandic-friends", - fi: "/fi/scandic-friends/liity-scandic-friends-ohjelmaan", - da: "/da/scandic-friends/tilmeld-dig-scandic-friends", - de: "/de/scandic-friends/werden-sie-mitglied-von-scandic-friends", + no: "/no/scandic-friends/registrer-deg", + fi: "/fi/scandic-friends/liity-jaseneksi", + da: "/da/scandic-friends/tilmeld-dig", + de: "/de/scandic-friends/mitglied-werden", } export const signupVerify: LangRoute = { diff --git a/env/server.ts b/env/server.ts index c2dd9843d..6b0e89b07 100644 --- a/env/server.ts +++ b/env/server.ts @@ -27,6 +27,7 @@ export const env = createEnv({ CMS_PREVIEW_TOKEN: z.string(), CMS_PREVIEW_URL: z.string(), CMS_URL: z.string(), + CMS_BRANCH: z.enum(["development", "production"]), CURITY_CLIENT_ID_USER: z.string(), CURITY_CLIENT_ID_SERVICE: z.string(), CURITY_CLIENT_SECRET_SERVICE: z.string(), @@ -137,6 +138,7 @@ export const env = createEnv({ CMS_PREVIEW_TOKEN: process.env.CMS_PREVIEW_TOKEN, CMS_PREVIEW_URL: process.env.CMS_PREVIEW_URL, CMS_URL: process.env.CMS_URL, + CMS_BRANCH: process.env.CMS_BRANCH, CURITY_CLIENT_ID_USER: process.env.CURITY_CLIENT_ID_USER, CURITY_CLIENT_ID_SERVICE: process.env.CURITY_CLIENT_ID_SERVICE, CURITY_CLIENT_SECRET_SERVICE: process.env.CURITY_CLIENT_SECRET_SERVICE, diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 9bfc58fad..02062f2b3 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -6,6 +6,7 @@ "A photo of the room": "Et foto af værelset", "ACCE": "Tilgængelighed", "ALLG": "Allergi", + "About accessibility": "Om tilgængelighed", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", "Accept new price": "Accepter ny pris", @@ -17,6 +18,7 @@ "Add new card": "Tilføj nyt kort", "Address": "Adresse", "Adults": "voksne", + "Age": "Alder", "Airport": "Lufthavn", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle vores morgenmadsbuffeter tilbyder glutenfrie, veganske og allergivenlige muligheder.", "Allergy Room": "Allergirum", @@ -41,6 +43,7 @@ "Back to top": "Tilbage til top", "Bar": "Bar", "Based on availability": "Baseret på tilgængelighed", + "Bed": "Seng type", "Bed type": "Seng type", "Birth date": "Fødselsdato", "Book": "Book", @@ -59,8 +62,11 @@ "Cancel": "Afbestille", "Change room": "Skift værelse", "Check in": "Check ind", + "Check in from": "Indtjekning fra", "Check out": "Check ud", + "Check out at latest": "Udtjekning senest", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Tjek de kreditkort, der er gemt på din profil. Betal med et gemt kort, når du er logget ind for en mere jævn weboplevelse.", + "Check-in/Check-out": "Indtjekning/Udtjekning", "Children": "børn", "Choose room": "Vælg rum", "Cities": "Byer", @@ -137,6 +143,7 @@ "Food options": "Madvalg", "Former Scandic Hotel": "Tidligere Scandic Hotel", "Free cancellation": "Gratis afbestilling", + "Free parking": "Gratis parkering", "Free rebooking": "Gratis ombooking", "From": "Fra", "Garage": "Garage", @@ -160,6 +167,7 @@ "Hotel surroundings": "Hotel omgivelser", "Hotel(s)": "{amount} {amount, plural, one {hotel} other {hoteller}}", "Hotels": "Hoteller", + "Hours": "Tider", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det virker", "Hurry up and use them before they expire!": "Skynd dig og brug dem, før de udløber!", @@ -253,6 +261,7 @@ "Open menu": "Åbn menuen", "Open my pages menu": "Åbn mine sider menuen", "Opening Hours": "Åbningstider", + "Outdoor": "Udendørs", "OutdoorPool": "Udendørs pool", "Overview": "Oversigt", "PETR": "Kæledyr", @@ -283,6 +292,10 @@ "Previous victories": "Tidligere sejre", "Price": "Pris", "Price details": "Prisoplysninger", + "Price per 24 hours": "Pris per 24 timer", + "Price per day": "Pris per dag", + "Price per night": "Pris per nat", + "Prices": "Priser", "Proceed to login": "Fortsæt til login", "Proceed to payment method": "Fortsæt til betalingsmetode", "Provide a payment card in the next step": "Giv os dine betalingsoplysninger i næste skridt", @@ -294,10 +307,11 @@ "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Læs mere om hotellet", "Read more about wellness & exercise": "Read more about wellness & exercise", + "Reference #{bookingNr}": "Reference #{bookingNr}", "Relax": "Slap af", "Remove card from member profile": "Fjern kortet fra medlemsprofilen", "Request bedtype": "Anmod om sengetype", - "Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}", + "Restaurant": "{count, plural, one {Restaurant} other {Restauranter}}", "Restaurant & Bar": "Restaurant & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Gentag den nye adgangskode", @@ -368,6 +382,7 @@ "There are no transactions to display": "Der er ingen transaktioner at vise", "Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}", "This room is not available": "Dette værelse er ikke tilgængeligt", + "Times": "Tider", "To get the member price {amount} {currency}, log in or join when completing the booking.": "For at få medlemsprisen {amount} {currency}, log ind eller tilmeld dig, når du udfylder bookingen.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For at sikre din reservation, beder vi om at du giver os dine betalingsoplysninger. Du kan så være sikker på, at ingen gebyrer vil blive opkrævet på dette tidspunkt.", "Total": "Total", @@ -394,7 +409,11 @@ "We have a special gift waiting for you!": "Vi har en speciel gave, der venter på dig!", "We have sent a detailed confirmation of your booking to your email:": "Vi har sendt en detaljeret bekræftelse af din booking til din email:", "We look forward to your visit!": "Vi ser frem til dit besøg!", + "Weekday": "Ugedag", + "Weekday prices": "Ugedagspriser", "Weekdays": "Hverdage", + "Weekend": "Weekend", + "Weekend prices": "Weekendpriser", "Weekends": "Weekender", "Welcome": "Velkommen", "Welcome to": "Velkommen til", @@ -434,6 +453,8 @@ "booking.basedOnAvailability": "Baseret på tilgængelighed", "booking.bedOptions": "Sengemuligheder", "booking.children": "{totalChildren, plural, one {# barn} other {# børn}}", + "booking.confirmation.text": "Tak fordi du bookede hos os! Vi glæder os til at byde dig velkommen og håber du får et behageligt ophold. Hvis du har spørgsmål eller har brug for at foretage ændringer i din reservation, bedes du kontakte os.", + "booking.confirmation.title": "Booking bekræftelse", "booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}", "booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}", "booking.rooms": "{totalRooms, plural, one {# værelse} other {# værelser}}", @@ -444,6 +465,9 @@ "breakfast.price.free": "{amount} {currency} 0 {currency}/nat", "by": "inden", "characters": "tegn", + "filters.nohotel.heading": "Ingen rum matchede dine filtre.", + "filters.nohotel.text": "Det ser ud til, at ingen hoteller matcher dine filtre. Prøv at justere din søgning for at finde det perfekte ophold.", + "from": "fra", "guaranteeing": "garanti", "guest": "gæst", "guests": "gæster", @@ -464,11 +488,14 @@ "points": "Point", "room type": "værelsestype", "room types": "værelsestyper", + "signup.terms": "Ved at tilmelde dig accepterer du Scandic Friends vilkår og betingelser. Dit medlemskab er gyldigt indtil videre, og du kan til enhver tid opsige dit medlemskab ved at sende en e-mail til Scandics kundeservice", "signupPage.terms": "Ja, jeg accepterer vilkårene og betingelserne for Scandic Friends og forstår, at Scandic vil behandle mine personlige data i overensstemmelse med Scandic's integritetspolicy.", "special character": "speciel karakter", "spendable points expiring by": "{points} Brugbare point udløber den {date}", "to": "til", "uppercase letter": "stort bogstav", "{amount} out of {total}": "{amount} ud af {total}", - "{amount} {currency}": "{amount} {currency}" + "{amount} {currency}": "{amount} {currency}", + "{card} ending with {cardno}": "{card} slutter med {cardno}", + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 69c8c8956..5cdfe1a31 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -6,6 +6,7 @@ "A photo of the room": "Ein Foto des Zimmers", "ACCE": "Zugänglichkeit", "ALLG": "Allergie", + "About accessibility": "Über Barrierefreiheit", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Über das Hotel", "Accept new price": "Neuen Preis akzeptieren", @@ -17,6 +18,7 @@ "Add new card": "Neue Karte hinzufügen", "Address": "Adresse", "Adults": "Erwachsene", + "Age": "Alter", "Airport": "Flughafen", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle unsere Frühstücksbuffets bieten glutenfreie, vegane und allergikerfreundliche Speisen.", "Allergy Room": "Allergikerzimmer", @@ -41,6 +43,7 @@ "Back to top": "Zurück zur Spitze", "Bar": "Bar", "Based on availability": "Je nach Verfügbarkeit", + "Bed": "Bettentyp", "Bed type": "Bettentyp", "Birth date": "Geburtsdatum", "Book": "Buchen", @@ -59,8 +62,11 @@ "Cancel": "Stornieren", "Change room": "Zimmer ändern", "Check in": "Einchecken", + "Check in from": "Check-in ab", "Check out": "Auschecken", + "Check out at latest": "Check-out spätestens", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Sehen Sie sich die in Ihrem Profil gespeicherten Kreditkarten an. Bezahlen Sie mit einer gespeicherten Karte, wenn Sie angemeldet sind, für ein reibungsloseres Web-Erlebnis.", + "Check-in/Check-out": "Einchecken/Auschecken", "Children": "Kinder", "Choose room": "Zimmer wählen", "Cities": "Städte", @@ -137,6 +143,7 @@ "Food options": "Speisen & Getränke", "Former Scandic Hotel": "Ehemaliges Scandic Hotel", "Free cancellation": "Kostenlose Stornierung", + "Free parking": "Kostenloses Parken", "Free rebooking": "Kostenlose Umbuchung", "From": "Fromm", "Garage": "Garage", @@ -160,6 +167,7 @@ "Hotel surroundings": "Umgebung des Hotels", "Hotel(s)": "{amount} {amount, plural, one {hotel} other {hotels}}", "Hotels": "Hotels", + "Hours": "Zeiten", "How do you want to sleep?": "Wie möchtest du schlafen?", "How it works": "Wie es funktioniert", "Hurry up and use them before they expire!": "Beeilen Sie sich und nutzen Sie sie, bevor sie ablaufen!", @@ -252,6 +260,7 @@ "Open menu": "Menü öffnen", "Open my pages menu": "Meine Seiten Menü öffnen", "Opening Hours": "Öffnungszeiten", + "Outdoor": "Im Freien", "OutdoorPool": "Außenpool", "Overview": "Übersicht", "PETR": "Haustier", @@ -282,6 +291,10 @@ "Previous victories": "Bisherige Siege", "Price": "Preis", "Price details": "Preisdetails", + "Price per 24 hours": "Preis pro 24 Stunden", + "Price per day": "Preis pro Tag", + "Price per night": "Preis pro Nacht", + "Prices": "Preise", "Proceed to login": "Weiter zum Login", "Proceed to payment method": "Weiter zur Zahlungsmethode", "Provide a payment card in the next step": "Geben Sie Ihre Zahlungskarteninformationen im nächsten Schritt an", @@ -293,10 +306,11 @@ "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Lesen Sie mehr über das Hotel", "Read more about wellness & exercise": "Read more about wellness & exercise", + "Reference #{bookingNr}": "Referenz #{bookingNr}", "Relax": "Entspannen", "Remove card from member profile": "Karte aus dem Mitgliedsprofil entfernen", "Request bedtype": "Bettentyp anfragen", - "Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}", + "Restaurant": "{count, plural, one {Restaurant} other {Restaurants}}", "Restaurant & Bar": "Restaurant & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Neues Passwort erneut eingeben", @@ -367,6 +381,7 @@ "There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden", "Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}", "This room is not available": "Dieses Zimmer ist nicht verfügbar", + "Times": "Zeiten", "To get the member price {amount} {currency}, log in or join when completing the booking.": "Um den Mitgliederpreis von {amount} {currency} zu erhalten, loggen Sie sich ein oder treten Sie Scandic Friends bei, wenn Sie die Buchung abschließen.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Um Ihre Reservierung zu sichern, bitten wir Sie, Ihre Zahlungskarteninformationen zu geben. Sie können sicher sein, dass keine Gebühren zu diesem Zeitpunkt erhoben werden.", "Total": "Gesamt", @@ -393,7 +408,11 @@ "We have a special gift waiting for you!": "Wir haben ein besonderes Geschenk für Sie!", "We have sent a detailed confirmation of your booking to your email:": "Wir haben eine detaillierte Bestätigung Ihrer Buchung an Ihre E-Mail gesendet:", "We look forward to your visit!": "Wir freuen uns auf Ihren Besuch!", + "Weekday": "Wochentag", + "Weekday prices": "Wochentagspreise", "Weekdays": "Wochentage", + "Weekend": "Wochenende", + "Weekend prices": "Wochenendpreise", "Weekends": "Wochenenden", "Welcome": "Willkommen", "Welcome to": "Willkommen zu", @@ -433,6 +452,8 @@ "booking.basedOnAvailability": "Abhängig von der Verfügbarkeit", "booking.bedOptions": "Bettoptionen", "booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}", + "booking.confirmation.text": "Vielen Dank, dass Sie bei uns gebucht haben! Wir freuen uns, Sie bei uns begrüßen zu dürfen und wünschen Ihnen einen angenehmen Aufenthalt. Wenn Sie Fragen haben oder Änderungen an Ihrer Buchung vornehmen müssen, kontaktieren Sie uns bitte..", + "booking.confirmation.title": "Buchungsbestätigung", "booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}", "booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}", "booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}", @@ -443,6 +464,9 @@ "breakfast.price.free": "{amount} {currency} 0 {currency}/Nacht", "by": "bis", "characters": "figuren", + "filters.nohotel.heading": "Kein Zimmer entspricht Ihren Filtern.", + "filters.nohotel.text": "Es scheint, dass keine Hotels Ihren Filtern entsprechen. Versuchen Sie, Ihre Suche anzupassen, um den perfekten Aufenthalt zu finden.", + "from": "aus", "guaranteeing": "garantiert", "guest": "gast", "guests": "gäste", @@ -463,11 +487,14 @@ "points": "Punkte", "room type": "zimmerart", "room types": "zimmerarten", + "signup.terms": "Mit Ihrer Anmeldung akzeptieren Sie die Allgemeinen Geschäftsbedingungen von Scandic Friends. Ihre Mitgliedschaft ist bis auf Weiteres gültig und Sie können sie jederzeit kündigen, indem Sie eine E-Mail an den Kundenservice von Scandic senden.", "signupPage.terms": "Ja, ich akzeptiere die Allgemeinen Geschäftsbedingungen für Scandic Friends und verstehe, dass Scandic meine persönlichen Daten gemäß Scandics Datenschutzrichtlinie.", "special character": "sonderzeichen", "spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}", "to": "zu", "uppercase letter": "großbuchstabe", "{amount} out of {total}": "{amount} von {total}", - "{amount} {currency}": "{amount} {currency}" + "{amount} {currency}": "{amount} {currency}", + "{card} ending with {cardno}": "{card} endet mit {cardno}", + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 2bc086e28..1930a6824 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -6,6 +6,7 @@ "A photo of the room": "A photo of the room", "ACCE": "Accessibility", "ALLG": "Allergy", + "About accessibility": "About accessibility", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", "Accept new price": "Accept new price", @@ -47,7 +48,9 @@ "Bed type": "Bed type", "Birth date": "Birth date", "Book": "Book", + "Book another stay": "Book another stay", "Book reward night": "Book reward night", + "Book your next stay": "Book your next stay", "Booking": "Booking", "Booking number": "Booking number", "Breakfast": "Breakfast", @@ -64,9 +67,12 @@ "Cancellation policy": "Cancellation policy", "Change room": "Change room", "Check in": "Check in", + "Check in from": "Check in from", "Check out": "Check out", + "Check out at latest": "Check out at latest", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.", "Check-in": "Check-in", + "Check-in/Check-out": "Check-in/Check-out", "Check-out": "Check-out", "Child age is required": "Child age is required", "Children": "Children", @@ -111,6 +117,7 @@ "Disabled booking options text": "Codes, cheques and reward nights aren't available on the new website yet.", "Discard changes": "Discard changes", "Discard unsaved changes?": "Discard unsaved changes?", + "Discover the little extra touches to make your upcoming stay even more unforgettable.": "Discover the little extra touches to make your upcoming stay even more unforgettable.", "Distance in km to city centre": "{number} km to city centre", "Distance to city centre": "Distance to city centre", "Distance to hotel": "Distance to hotel: {distance} m", @@ -146,11 +153,14 @@ "Food options": "Food options", "Former Scandic Hotel": "Former Scandic Hotel", "Free cancellation": "Free cancellation", + "Free parking": "Free parking", "Free rebooking": "Free rebooking", "Free until": "Free until", + "Friend no.": "Friend no.", "From": "From", "Garage": "Garage", "Get inspired": "Get inspired", + "Get inspired and start dreaming beyond your next trip. Explore more Scandic destinations.": "Get inspired and start dreaming beyond your next trip. Explore more Scandic destinations.", "Get member benefits & offers": "Get member benefits & offers", "Gift(s) added to your benefits": "{amount, plural, one {Gift} other {Gifts}} added to your benefits", "Go back to edit": "Go back to edit", @@ -167,11 +177,13 @@ "Home": "Home", "Hospital": "Hospital", "Hotel": "Hotel", + "Hotel details": "Hotel details", "Hotel facilities": "Hotel facilities", "Hotel reservation": "Hotel reservation", "Hotel surroundings": "Hotel surroundings", "Hotel(s)": "{amount} {amount, plural, one {hotel} other {hotels}}", "Hotels": "Hotels", + "Hours": "Hours", "How do you want to sleep?": "How do you want to sleep?", "How it works": "How it works", "Hurry up and use them before they expire!": "Hurry up and use them before they expire!", @@ -209,8 +221,10 @@ "Log in here": "Log in here", "Log in/Join": "Log in/Join", "Log out": "Log out", + "Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}", "Longitude": "Longitude {long}", "MY SAVED CARDS": "MY SAVED CARDS", + "Main guest": "Main guest", "Main menu": "Main menu", "Manage booking": "Manage booking", "Manage preferences": "Manage preferences", @@ -224,6 +238,7 @@ "Members": "Members", "Membership ID": "Membership ID", "Membership ID copied to clipboard": "Membership ID copied to clipboard", + "Membership benefits applied": "Membership benefits applied", "Membership cards": "Membership cards", "Membership no": "Membership no", "Membership terms and conditions": "Membership terms and conditions", @@ -249,6 +264,7 @@ "No breakfast": "No breakfast", "No content published": "No content published", "No matching location found": "No matching location found", + "No membership benefits applied": "No membership benefits applied", "No prices available": "No prices available", "No results": "No results", "No transactions available": "No transactions available", @@ -270,6 +286,7 @@ "Open menu": "Open menu", "Open my pages menu": "Open my pages menu", "Opening Hours": "Opening Hours", + "Outdoor": "Outdoor", "OutdoorPool": "Outdoor pool", "Overview": "Overview", "PETR": "Pet", @@ -281,6 +298,7 @@ "Pay now": "Pay now", "Payment": "Payment", "Payment Guarantee": "Payment Guarantee", + "Payment details": "Payment details", "Payment info": "Payment info", "Payment method": "Payment method", "Payment received": "Payment received", @@ -306,6 +324,10 @@ "Price details": "Price details", "Price excl VAT": "Price excl VAT", "Price incl VAT": "Price incl VAT", + "Price per 24 hours": "Price per 24 hours", + "Price per day": "Price per day", + "Price per night": "Price per night", + "Prices": "Prices", "Print confirmation": "Print confirmation", "Proceed to login": "Proceed to login", "Proceed to payment method": "Proceed to payment method", @@ -323,7 +345,9 @@ "Relax": "Relax", "Remove card from member profile": "Remove card from member profile", "Request bedtype": "Request bedtype", - "Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}", + "Reservation number": "Reservation number", + "Reservation policy": "Reservation policy", + "Restaurant": "{count, plural, one {Restaurant} other {Restaurants}}", "Restaurant & Bar": "Restaurant & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Retype new password", @@ -378,6 +402,7 @@ "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!", "Sort by": "Sort by", + "Spice things up": "Spice things up", "Sports": "Sports", "Standard price": "Standard price", "Stay at HOTEL_NAME | Hotel in DESTINATION": "Stay at {hotelName} | Hotel in {destination}", @@ -396,6 +421,7 @@ "There are no transactions to display": "There are no transactions to display", "Things nearby HOTEL_NAME": "Things nearby {hotelName}", "This room is not available": "This room is not available", + "Times": "Times", "To get the member price {amount} {currency}, log in or join when completing the booking.": "To get the member price {amount} {currency}, log in or join when completing the booking.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.", "Total": "Total", @@ -415,8 +441,10 @@ "User information": "User information", "VAT": "VAT", "VAT amount": "VAT amount", + "View and buy add-ons": "View and buy add-ons", "View as list": "View as list", "View as map": "View as map", + "View room details": "View room details", "View terms": "View terms", "View your booking": "View your booking", "Visiting address": "Visiting address", @@ -425,7 +453,11 @@ "We have a special gift waiting for you!": "We have a special gift waiting for you!", "We have sent a detailed confirmation of your booking to your email:": "We have sent a detailed confirmation of your booking to your email: ", "We look forward to your visit!": "We look forward to your visit!", + "Weekday": "Weekday", + "Weekday prices": "Weekday prices", "Weekdays": "Weekdays", + "Weekend": "Weekend", + "Weekend prices": "Weekend prices", "Weekends": "Weekends", "Welcome": "Welcome", "Welcome to": "Welcome to", @@ -485,6 +517,7 @@ "guest": "guest", "guest.paid": "{amount} {currency} has been paid", "guests": "guests", + "has been paid": "has been paid", "hotelPages.rooms.roomCard.persons": "{size} ({totalOccupancy, plural, one {# person} other {# persons}})", "hotelPages.rooms.roomCard.seeRoomDetails": "See room details", "km to city center": "km to city center", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 0f536a5ff..edbeb7c00 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -6,6 +6,7 @@ "A photo of the room": "Kuva huoneesta", "ACCE": "Saavutettavuus", "ALLG": "Allergia", + "About accessibility": "Tietoja saavutettavuudesta", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Tietoja hotellista", "Accept new price": "Hyväksy uusi hinta", @@ -17,6 +18,7 @@ "Add new card": "Lisää uusi kortti", "Address": "Osoite", "Adults": "Aikuista", + "Age": "Ikä", "Airport": "Lentokenttä", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Kaikki aamiaisbuffettimme tarjoavat gluteenittomia, vegaanisia ja allergiaystävällisiä vaihtoehtoja.", "Allergy Room": "Allergiahuone", @@ -41,6 +43,7 @@ "Back to top": "Takaisin ylös", "Bar": "Bar", "Based on availability": "Saatavuuden mukaan", + "Bed": "Vuodetyyppi", "Bed type": "Vuodetyyppi", "Birth date": "Syntymäaika", "Book": "Varaa", @@ -59,8 +62,11 @@ "Cancel": "Peruuttaa", "Change room": "Vaihda huonetta", "Check in": "Sisäänkirjautuminen", + "Check in from": "Sisäänkirjautuminen alkaen", "Check out": "Uloskirjautuminen", + "Check out at latest": "Uloskirjautuminen viimeistään", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Tarkista profiiliisi tallennetut luottokortit. Maksa tallennetulla kortilla kirjautuneena, jotta verkkokokemus on sujuvampi.", + "Check-in/Check-out": "Sisäänkirjautuminen/Uloskirjautuminen", "Children": "Lasta", "Choose room": "Valitse huone", "Cities": "Kaupungit", @@ -137,6 +143,7 @@ "Food options": "Ruokavalio", "Former Scandic Hotel": "Entinen Scandic-hotelli", "Free cancellation": "Ilmainen peruutus", + "Free parking": "Ilmainen pysäköinti", "Free rebooking": "Ilmainen uudelleenvaraus", "From": "From", "Garage": "Autotalli", @@ -160,6 +167,7 @@ "Hotel surroundings": "Hotellin ympäristö", "Hotel(s)": "{amount} {amount, plural, one {hotelli} other {hotellit}}", "Hotels": "Hotellit", + "Hours": "Ajat", "How do you want to sleep?": "Kuinka haluat nukkua?", "How it works": "Kuinka se toimii", "Hurry up and use them before they expire!": "Ole nopea ja käytä ne ennen kuin ne vanhenevat!", @@ -253,6 +261,7 @@ "Open menu": "Avaa valikko", "Open my pages menu": "Avaa omat sivut -valikko", "Opening Hours": "Aukioloajat", + "Outdoor": "Ulkona", "OutdoorPool": "Ulkouima-allas", "Overview": "Yleiskatsaus", "PETR": "Lemmikki", @@ -283,6 +292,10 @@ "Previous victories": "Edelliset voitot", "Price": "Hinta", "Price details": "Hintatiedot", + "Price per 24 hours": "Hinta per 24 tuntia", + "Price per day": "Hinta per päivä", + "Price per night": "Hinta per yö", + "Prices": "Hinnat", "Proceed to login": "Jatka kirjautumiseen", "Proceed to payment method": "Siirry maksutavalle", "Provide a payment card in the next step": "Anna maksukortin tiedot seuraavassa vaiheessa", @@ -294,10 +307,11 @@ "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Lue lisää hotellista", "Read more about wellness & exercise": "Read more about wellness & exercise", + "Reference #{bookingNr}": "Referenssi #{bookingNr}", "Relax": "Rentoutua", "Remove card from member profile": "Poista kortti jäsenprofiilista", "Request bedtype": "Pyydä sänkytyyppiä", - "Restaurant": "{count, plural, one {#Ravintola} other {#Restaurants}}", + "Restaurant": "{count, plural, one {Ravintola} other {Ravintolat}}", "Restaurant & Bar": "Ravintola & Baari", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Kirjoita uusi salasana uudelleen", @@ -368,6 +382,7 @@ "There are no transactions to display": "Näytettäviä tapahtumia ei ole", "Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}", "This room is not available": "Tämä huone ei ole käytettävissä", + "Times": "Ajat", "To get the member price {amount} {currency}, log in or join when completing the booking.": "Jäsenhintaan saavat sisäänkirjautuneet tai liittyneet jäsenet.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Varmistaaksesi varauksen, pyydämme sinua antamaan meille maksukortin tiedot. Varmista, että ei veloiteta maksusi tällä hetkellä.", "Total": "Kokonais", @@ -394,7 +409,11 @@ "We have a special gift waiting for you!": "Meillä on erityinen lahja odottamassa sinua!", "We have sent a detailed confirmation of your booking to your email:": "Olemme lähettäneet yksityiskohtaisen varausvahvistuksen sähköpostiisi:", "We look forward to your visit!": "Odotamme innolla vierailuasi!", + "Weekday": "Arkipäivä", + "Weekday prices": "Arkisin hinnat", "Weekdays": "Arkisin", + "Weekend": "Viikonloppu", + "Weekend prices": "Viikonlopun hinnat", "Weekends": "Viikonloppuisin", "Welcome": "Tervetuloa", "Welcome to": "Tervetuloa", @@ -432,6 +451,8 @@ "booking.basedOnAvailability": "Saatavuuden mukaan", "booking.bedOptions": "Vuodevaihtoehdot", "booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}", + "booking.confirmation.text": "Kiitos, että teit varauksen meiltä! Toivotamme sinut tervetulleeksi ja toivomme sinulle miellyttävää oleskelua. Jos sinulla on kysyttävää tai haluat tehdä muutoksia varaukseesi, ota meihin yhteyttä.", + "booking.confirmation.title": "Varausvahvistus", "booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}", "booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}", "booking.rooms": "{totalRooms, plural, one {# huone} other {# sviitti}}", @@ -442,6 +463,9 @@ "breakfast.price.free": "{amount} {currency} 0 {currency}/yö", "by": "mennessä", "characters": "hahmoja", + "filters.nohotel.heading": "Yksikään huone ei vastannut suodattimiasi", + "filters.nohotel.text": "Näyttää siltä, ​​että mikään hotelli ei vastaa suodattimiasi. Yritä muokata hakuasi löytääksesi täydellisen oleskelun.", + "from": "alkaen", "guaranteeing": "varmistetaan", "guest": "Vieras", "guests": "Vieraita", @@ -462,11 +486,14 @@ "points": "pistettä", "room type": "huonetyyppi", "room types": "huonetyypit", + "signup.terms": "Rekisteröitymällä hyväksyt Scandic Friendsin käyttöehdot. Jäsenyytesi on voimassa toistaiseksi ja voit lopettaa jäsenyytesi milloin tahansa lähettämällä sähköpostia Scandicin asiakaspalveluun", "signupPage.terms": "Kyllä, hyväksyn Scandic Friends -käyttöehdot ja ymmärrän, että Scandic käsittelee henkilötietojani Scandicin tietosuojakäytännön mukaisesti.", "special character": "erikoishahmo", "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", "to": "to", "uppercase letter": "iso kirjain", "{amount} out of {total}": "{amount}/{total}", - "{amount} {currency}": "{amount} {currency}" + "{amount} {currency}": "{amount} {currency}", + "{card} ending with {cardno}": "{card} päättyen {cardno}", + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 443b83bab..24a86d528 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -6,6 +6,7 @@ "A photo of the room": "Et bilde av rommet", "ACCE": "Tilgjengelighet", "ALLG": "Allergi", + "About accessibility": "Om tilgjengelighet", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", "Accept new price": "Aksepterer ny pris", @@ -17,6 +18,7 @@ "Add new card": "Legg til nytt kort", "Address": "Adresse", "Adults": "Voksne", + "Age": "Alder", "Airport": "Flyplass", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle våre frokostbufféer tilbyr glutenfrie, veganske og allergivennlige alternativer.", "Allergy Room": "Allergirom", @@ -41,6 +43,7 @@ "Back to top": "Tilbake til toppen", "Bar": "Bar", "Based on availability": "Basert på tilgjengelighet", + "Bed": "Seng type", "Bed type": "Seng type", "Birth date": "Fødselsdato", "Book": "Bestill", @@ -59,8 +62,11 @@ "Cancel": "Avbryt", "Change room": "Endre rom", "Check in": "Sjekk inn", + "Check in from": "Innsjekking fra", "Check out": "Sjekk ut", + "Check out at latest": "Utsjekking senest", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Sjekk ut kredittkortene som er lagret på profilen din. Betal med et lagret kort når du er pålogget for en jevnere nettopplevelse.", + "Check-in/Check-out": "Innsjekking/Utsjekking", "Children": "Barn", "Choose room": "Velg rom", "Cities": "Byer", @@ -136,6 +142,7 @@ "Food options": "Matvalg", "Former Scandic Hotel": "Tidligere Scandic-hotell", "Free cancellation": "Gratis avbestilling", + "Free parking": "Gratis parkering", "Free rebooking": "Gratis ombooking", "From": "Fra", "Garage": "Garasje", @@ -159,6 +166,7 @@ "Hotel surroundings": "Hotellomgivelser", "Hotel(s)": "{amount} {amount, plural, one {hotell} other {hoteller}}", "Hotels": "Hoteller", + "Hours": "Tider", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det fungerer", "Hurry up and use them before they expire!": "Skynd deg og bruk dem før de utløper!", @@ -251,6 +259,7 @@ "Open menu": "Åpne menyen", "Open my pages menu": "Åpne mine sider menyen", "Opening Hours": "Åpningstider", + "Outdoor": "Utendørs", "OutdoorPool": "Utendørs basseng", "Overview": "Oversikt", "PETR": "Kjæledyr", @@ -281,6 +290,10 @@ "Previous victories": "Tidligere seire", "Price": "Pris", "Price details": "Prisdetaljer", + "Price per 24 hours": "Pris per 24 timer", + "Price per day": "Pris per dag", + "Price per night": "Pris per natt", + "Prices": "Priser", "Proceed to login": "Fortsett til innlogging", "Proceed to payment method": "Fortsett til betalingsmetode", "Provide a payment card in the next step": "Gi oss dine betalingskortdetaljer i neste steg", @@ -292,10 +305,11 @@ "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Les mer om hotellet", "Read more about wellness & exercise": "Read more about wellness & exercise", + "Reference #{bookingNr}": "Referanse #{bookingNr}", "Relax": "Slappe av", "Remove card from member profile": "Fjern kortet fra medlemsprofilen", "Request bedtype": "Be om sengetype", - "Restaurant": "{count, plural, one {#Restaurant} other {#Restaurants}}", + "Restaurant": "{count, plural, one {Restaurant} other {Restauranter}}", "Restaurant & Bar": "Restaurant & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Skriv inn nytt passord på nytt", @@ -365,6 +379,7 @@ "There are no transactions to display": "Det er ingen transaksjoner å vise", "Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}", "This room is not available": "Dette rommet er ikke tilgjengelig", + "Times": "Tider", "To get the member price {amount} {currency}, log in or join when completing the booking.": "For å få medlemsprisen {amount} {currency}, logg inn eller bli med når du fullfører bestillingen.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For å sikre din reservasjon, ber vi om at du gir oss dine betalingskortdetaljer. Vær sikker på at ingen gebyrer vil bli belastet på dette tidspunktet.", "Total": "Total", @@ -391,7 +406,11 @@ "We have a special gift waiting for you!": "Vi har en spesiell gave som venter på deg!", "We have sent a detailed confirmation of your booking to your email:": "Vi har sendt en detaljert bekreftelse av din bestilling til din e-post:", "We look forward to your visit!": "Vi ser frem til ditt besøk!", + "Weekday": "Ukedag", + "Weekday prices": "Ukedagspriser", "Weekdays": "Hverdager", + "Weekend": "Helg", + "Weekend prices": "Helgepriser", "Weekends": "Helger", "Welcome": "Velkommen", "Welcome to": "Velkommen til", @@ -431,15 +450,21 @@ "booking.basedOnAvailability": "Basert på tilgjengelighet", "booking.bedOptions": "Sengemuligheter", "booking.children": "{totalChildren, plural, one {# barn} other {# barn}}", + "booking.confirmation.text": "Takk for at du booket hos oss! Vi ser frem til å ønske deg velkommen og håper du får et hyggelig opphold. Hvis du har spørsmål eller trenger å gjøre endringer i bestillingen din, vennligst kontakt oss.", + "booking.confirmation.title": "Bestillingsbekreftelse", "booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}", "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", "booking.rooms": "{totalRooms, plural, one {# rom} other {# rom}}", "booking.selectRoom": "Velg rom", + "booking.terms": "Ved å betale med en av de tilgjengelige betalingsmetodene godtar jeg vilkårene og betingelsene for denne bestillingen og de generelle vilkårene, og forstår at Scandic vil behandle mine personopplysninger i forbindelse med denne bestillingen i henhold til Scandics personvernpolicy. Jeg aksepterer at Scandic krever et gyldig kredittkort under mitt besøk i tilfelle noe blir refundert.", "booking.thisRoomIsEquippedWith": "Dette rommet er utstyrt med", "breakfast.price": "{amount} {currency}/natt", "breakfast.price.free": "{amount} {currency} 0 {currency}/natt", "by": "innen", "characters": "tegn", + "filters.nohotel.heading": "Ingen rom samsvarte med filtrene dine", + "filters.nohotel.text": "Det ser ut til at ingen hoteller samsvarer med filtrene dine. Prøv å justere søket for å finne det perfekte oppholdet.", + "from": "fra", "guaranteeing": "garantiert", "guest": "gjest", "guests": "gjester", @@ -460,11 +485,14 @@ "points": "poeng", "room type": "romtype", "room types": "romtyper", + "signup.terms": "Ved å registrere deg godtar du Scandic Friends vilkår og betingelser. Medlemskapet ditt er gyldig inntil videre, og du kan si opp medlemskapet ditt når som helst ved å sende en e-post til Scandics kundeservice", "signupPage.terms": "Ja, jeg godtar vilkårene og betingelsene for Scandic Friends og forstår at Scandic vil behandle mine personopplysninger i henhold til Scandics integritetspolicy.", "special character": "spesiell karakter", "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", "to": "til", "uppercase letter": "stor bokstav", "{amount} out of {total}": "{amount} av {total}", - "{amount} {currency}": "{amount} {currency}" + "{amount} {currency}": "{amount} {currency}", + "{card} ending with {cardno}": "{card} slutter med {cardno}", + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 94c6bd602..a67214de5 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -6,6 +6,7 @@ "A photo of the room": "Ett foto av rummet", "ACCE": "Tillgänglighet", "ALLG": "Allergi", + "About accessibility": "Om tillgänglighet", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", "Accept new price": "Accepter ny pris", @@ -17,6 +18,7 @@ "Add new card": "Lägg till nytt kort", "Address": "Adress", "Adults": "Vuxna", + "Age": "Ålder", "Airport": "Flygplats", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alla våra frukostbufféer erbjuder glutenfria, veganska och allergivänliga alternativ.", "Allergy Room": "Allergirum", @@ -41,6 +43,7 @@ "Back to top": "Tillbaka till toppen", "Bar": "Bar", "Based on availability": "Baserat på tillgänglighet", + "Bed": "Sängtyp", "Bed type": "Sängtyp", "Birth date": "Födelsedatum", "Book": "Boka", @@ -59,8 +62,11 @@ "Cancel": "Avbryt", "Change room": "Ändra rum", "Check in": "Checka in", + "Check in from": "Incheckning från", "Check out": "Checka ut", + "Check out at latest": "Utcheckning senast", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Kolla in kreditkorten som sparats i din profil. Betala med ett sparat kort när du är inloggad för en smidigare webbupplevelse.", + "Check-in/Check-out": "Inchecking/Utcheckning", "Children": "Barn", "Choose room": "Välj rum", "Cities": "Städer", @@ -136,6 +142,7 @@ "Food options": "Matval", "Former Scandic Hotel": "Tidigare Scandichotell", "Free cancellation": "Fri avbokning", + "Free parking": "Gratis parkering", "Free rebooking": "Fri ombokning", "From": "Från", "Garage": "Garage", @@ -159,6 +166,7 @@ "Hotel surroundings": "Hotellomgivning", "Hotel(s)": "{amount} hotell", "Hotels": "Hotell", + "Hours": "Tider", "How do you want to sleep?": "Hur vill du sova?", "How it works": "Hur det fungerar", "Hurry up and use them before they expire!": "Skynda dig och använd dem innan de går ut!", @@ -251,6 +259,7 @@ "Open menu": "Öppna menyn", "Open my pages menu": "Öppna mina sidor menyn", "Opening Hours": "Öppettider", + "Outdoor": "Utomhus", "OutdoorPool": "Utomhuspool", "Overview": "Översikt", "PETR": "Husdjur", @@ -281,6 +290,10 @@ "Previous victories": "Tidigare segrar", "Price": "Pris", "Price details": "Prisdetaljer", + "Price per 24 hours": "Pris per 24 timmar", + "Price per day": "Pris per dag", + "Price per night": "Pris per natt", + "Prices": "Priser", "Proceed to login": "Fortsätt till inloggning", "Proceed to payment method": "Gå vidare till betalningsmetod", "Provide a payment card in the next step": "Ge oss dina betalkortdetaljer i nästa steg", @@ -292,10 +305,11 @@ "Read more & book a table": "Read more & book a table", "Read more about the hotel": "Läs mer om hotellet", "Read more about wellness & exercise": "Read more about wellness & exercise", + "Reference #{bookingNr}": "Referens #{bookingNr}", "Relax": "Koppla av", "Remove card from member profile": "Ta bort kortet från medlemsprofilen", "Request bedtype": "Request bedtype", - "Restaurant": "{count, plural, one {#Restaurang} other {#Restauranger}}", + "Restaurant": "{count, plural, one {Restaurang} other {Restauranger}}", "Restaurant & Bar": "Restaurang & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Upprepa nytt lösenord", @@ -365,6 +379,7 @@ "There are no transactions to display": "Det finns inga transaktioner att visa", "Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}", "This room is not available": "Detta rum är inte tillgängligt", + "Times": "Tider", "To get the member price {amount} {currency}, log in or join when completing the booking.": "För att få medlemsprisen {amount} {currency}, logga in eller bli medlem när du slutför bokningen.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "För att säkra din bokning ber vi om att du ger oss dina betalkortdetaljer. Välj säker på att ingen avgifter kommer att debiteras just nu.", "Total": "Totalt", @@ -391,7 +406,11 @@ "We have a special gift waiting for you!": "Vi har en speciell present som väntar på dig!", "We have sent a detailed confirmation of your booking to your email:": "Vi har skickat en detaljerad bekräftelse av din bokning till din e-post:", "We look forward to your visit!": "Vi ser fram emot ditt besök!", + "Weekday": "Vardag", + "Weekday prices": "Vardagspriser", "Weekdays": "Vardagar", + "Weekend": "Helg", + "Weekend prices": "Helgpriser", "Weekends": "Helger", "Welcome": "Välkommen", "Welcome to": "Välkommen till", @@ -431,6 +450,8 @@ "booking.basedOnAvailability": "Baserat på tillgänglighet", "booking.bedOptions": "Sängalternativ", "booking.children": "{totalChildren, plural, one {# barn} other {# barn}}", + "booking.confirmation.text": "Tack för att du bokar hos oss! Vi ser fram emot att välkomna dig och hoppas att du får en trevlig vistelse. Om du har några frågor eller behöver göra ändringar i din bokning, vänligen kontakta oss.", + "booking.confirmation.title": "Bokningsbekräftelse", "booking.guests": "Max {nrOfGuests, plural, one {# gäst} other {# gäster}}", "booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}", "booking.rooms": "{totalRooms, plural, one {# rum} other {# rum}}", @@ -441,6 +462,9 @@ "breakfast.price.free": "{amount} {currency} 0 {currency}/natt", "by": "innan", "characters": "tecken", + "filters.nohotel.heading": "Inga rum matchade dina filter", + "filters.nohotel.text": "Det verkar som att inga hotell matchar dina filter. Prova att justera din sökning för att hitta den perfekta vistelsen.", + "from": "från", "guaranteeing": "garanterar", "guest": "gäst", "guests": "gäster", @@ -461,6 +485,7 @@ "points": "poäng", "room type": "rumtyp", "room types": "rumstyper", + "signup.terms": "Genom att registrera dig accepterar du Scandic Friends Användarvillkor. Ditt medlemskap gäller tills vidare och du kan när som helst säga upp ditt medlemskap genom att skicka ett mejl till Scandics kundtjänst", "signupPage.terms": "Ja, jag accepterar villkoren för Scandic Friends och förstår att Scandic kommer att behandla mina personuppgifter i enlighet med Scandics integritetspolicy.", "special character": "speciell karaktär", "spendable points expiring by": "{points} poäng förfaller {date}", @@ -469,5 +494,7 @@ "types": "typer", "uppercase letter": "stor bokstav", "{amount} out of {total}": "{amount} av {total}", - "{amount} {currency}": "{amount} {currency}" + "{amount} {currency}": "{amount} {currency}", + "{card} ending with {cardno}": "{card} som slutar på {cardno}", + "{difference}{amount} {currency}": "{difference}{amount} {currency}" } diff --git a/lib/graphql/_request.ts b/lib/graphql/_request.ts index 42920c293..e2ffef5c4 100644 --- a/lib/graphql/_request.ts +++ b/lib/graphql/_request.ts @@ -18,6 +18,7 @@ export async function request( try { client.setHeaders({ access_token: env.CMS_ACCESS_TOKEN, + branch: env.CMS_BRANCH, "Content-Type": "application/json", ...params?.headers, }) diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index 7db2e66da..2bf35c264 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -144,7 +144,7 @@ export const getBookingConfirmation = cache( export const getCityCoordinates = cache( async function getMemoizedCityCoordinates(input: { city: string - hotel: { address: string } + hotel: { address: string | undefined } }) { return serverClient().hotel.map.city(input) } diff --git a/package-lock.json b/package-lock.json index 2a2f3b61a..b4509615e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "dayjs": "^1.11.10", "deepmerge": "^4.3.1", "downshift": "^9.0.8", + "fast-deep-equal": "^3.1.3", "fetch-retry": "^6.0.0", "framer-motion": "^11.3.28", "graphql": "^16.8.1", @@ -43,7 +44,6 @@ "immer": "10.1.1", "json-stable-stringify-without-jsonify": "^1.0.1", "libphonenumber-js": "^1.10.60", - "lodash.isequal": "^4.5.0", "next": "^14.2.18", "next-auth": "^5.0.0-beta.19", "react": "^18", @@ -67,7 +67,6 @@ "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", "@types/json-stable-stringify-without-jsonify": "^1.0.2", - "@types/lodash.isequal": "^4.5.8", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -6866,21 +6865,6 @@ "@types/node": "*" } }, - "node_modules/@types/lodash": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", - "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", - "dev": true - }, - "node_modules/@types/lodash.isequal": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz", - "integrity": "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/node": { "version": "20.12.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", @@ -15001,11 +14985,6 @@ "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" - }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", diff --git a/package.json b/package.json index b6e68c685..efb824197 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dayjs": "^1.11.10", "deepmerge": "^4.3.1", "downshift": "^9.0.8", + "fast-deep-equal": "^3.1.3", "fetch-retry": "^6.0.0", "framer-motion": "^11.3.28", "graphql": "^16.8.1", @@ -58,7 +59,6 @@ "immer": "10.1.1", "json-stable-stringify-without-jsonify": "^1.0.1", "libphonenumber-js": "^1.10.60", - "lodash.isequal": "^4.5.0", "next": "^14.2.18", "next-auth": "^5.0.0-beta.19", "react": "^18", @@ -82,7 +82,6 @@ "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", "@types/json-stable-stringify-without-jsonify": "^1.0.2", - "@types/lodash.isequal": "^4.5.8", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/public/_static/img/icons/swan-eco/swan_eco_dark_dk.png b/public/_static/img/icons/swan-eco/swan_eco_dark_da.png similarity index 100% rename from public/_static/img/icons/swan-eco/swan_eco_dark_dk.png rename to public/_static/img/icons/swan-eco/swan_eco_dark_da.png diff --git a/public/_static/img/icons/swan-eco/swan_eco_dark_de.png b/public/_static/img/icons/swan-eco/swan_eco_dark_de.png new file mode 100644 index 000000000..2b7ef7340 Binary files /dev/null and b/public/_static/img/icons/swan-eco/swan_eco_dark_de.png differ diff --git a/public/_static/img/icons/swan-eco/swan_eco_dark_se.png b/public/_static/img/icons/swan-eco/swan_eco_dark_sv.png similarity index 100% rename from public/_static/img/icons/swan-eco/swan_eco_dark_se.png rename to public/_static/img/icons/swan-eco/swan_eco_dark_sv.png diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts index a4e47caf3..4a66009b3 100644 --- a/server/routers/hotels/input.ts +++ b/server/routers/hotels/input.ts @@ -77,6 +77,6 @@ export const getRoomPackagesInputSchema = z.object({ export const getCityCoordinatesInputSchema = z.object({ city: z.string(), hotel: z.object({ - address: z.string(), + address: z.string().optional(), }), }) diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 14bea40c1..76f29349a 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -56,7 +56,7 @@ const contactInformationSchema = z.object({ websiteUrl: z.string(), }) -const checkinSchema = z.object({ +export const checkinSchema = z.object({ checkInTime: z.string(), checkOutTime: z.string(), onlineCheckOutAvailableFrom: z.string().nullable().optional(), @@ -110,6 +110,12 @@ const hotelContentSchema = z.object({ short: z.string(), medium: z.string(), }), + meetingDescription: z + .object({ + short: z.string().optional(), + medium: z.string().optional(), + }) + .optional(), }), restaurantsOverviewPage: z.object({ restaurantsOverviewPageLinkText: z.string().optional(), @@ -129,7 +135,7 @@ const detailedFacilitySchema = z.object({ }) export const facilitySchema = z.object({ - headingText: z.string(), + headingText: z.string().default(""), heroImages: z.array( z.object({ metaData: imageMetaDataSchema, diff --git a/stores/enter-details/helpers.ts b/stores/enter-details/helpers.ts index 567e96334..496b75ba6 100644 --- a/stores/enter-details/helpers.ts +++ b/stores/enter-details/helpers.ts @@ -1,13 +1,10 @@ -import isEqual from "lodash.isequal" -import { z } from "zod" +import isEqual from "fast-deep-equal" import { Lang } from "@/constants/languages" -import { breakfastPackageSchema } from "@/server/routers/hotels/output" import { getLang } from "@/i18n/serverContext" import type { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData" -import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { CurrencyEnum } from "@/types/enums/currency" import { StepEnum } from "@/types/enums/step" import type { DetailsState, RoomRate } from "@/types/stores/enter-details" diff --git a/components/TempDesignSystem/Form/Phone/phone.ts b/types/components/form/phone.ts similarity index 61% rename from components/TempDesignSystem/Form/Phone/phone.ts rename to types/components/form/phone.ts index f7dbd0e15..537c17cc1 100644 --- a/components/TempDesignSystem/Form/Phone/phone.ts +++ b/types/components/form/phone.ts @@ -1,6 +1,9 @@ +import type { CountryCode } from "libphonenumber-js/min" import type { RegisterOptions } from "react-hook-form" -export type PhoneProps = { +export type LowerCaseCountryCode = Lowercase + +export interface PhoneProps { ariaLabel?: string className?: string disabled?: boolean diff --git a/types/components/hotelPage/sidepeek/aboutTheHotel.ts b/types/components/hotelPage/sidepeek/aboutTheHotel.ts index bb9633b60..d2ceae322 100644 --- a/types/components/hotelPage/sidepeek/aboutTheHotel.ts +++ b/types/components/hotelPage/sidepeek/aboutTheHotel.ts @@ -8,3 +8,8 @@ export type AboutTheHotelSidePeekProps = { ecoLabels: Hotel["hotelFacts"]["ecoLabels"] descriptions: Hotel["hotelContent"]["texts"] } + +export type ContactInformationProps = Omit< + AboutTheHotelSidePeekProps, + "descriptions" +> diff --git a/types/components/hotelPage/sidepeek/accessibility.ts b/types/components/hotelPage/sidepeek/accessibility.ts new file mode 100644 index 000000000..335971419 --- /dev/null +++ b/types/components/hotelPage/sidepeek/accessibility.ts @@ -0,0 +1,5 @@ +import type { Hotel } from "@/types/hotel" + +export type AccessibilityAmenityProps = { + accessibility: Hotel["hotelFacts"]["hotelInformation"]["accessibility"] +} diff --git a/types/components/hotelPage/sidepeek/amenities.ts b/types/components/hotelPage/sidepeek/amenities.ts new file mode 100644 index 000000000..8ded22722 --- /dev/null +++ b/types/components/hotelPage/sidepeek/amenities.ts @@ -0,0 +1,12 @@ +import type { Hotel } from "@/types/hotel" + +export type AmenitiesSidePeekProps = { + amenitiesList: Hotel["detailedFacilities"] + parking: Hotel["parking"] + checkInInformation: Hotel["hotelFacts"]["checkin"] + accessibility: Hotel["hotelFacts"]["hotelInformation"]["accessibility"] +} + +export type FilteredAmenitiesProps = { + filteredAmenities: Hotel["detailedFacilities"] +} diff --git a/types/components/hotelPage/sidepeek/checkIn.ts b/types/components/hotelPage/sidepeek/checkIn.ts new file mode 100644 index 000000000..e3a8d929b --- /dev/null +++ b/types/components/hotelPage/sidepeek/checkIn.ts @@ -0,0 +1,5 @@ +import type { Hotel } from "@/types/hotel" + +export type CheckInAmenityProps = { + checkInInformation: Hotel["hotelFacts"]["checkin"] +} diff --git a/types/components/hotelPage/sidepeek/contactInformation.ts b/types/components/hotelPage/sidepeek/contactInformation.ts deleted file mode 100644 index f1eb73196..000000000 --- a/types/components/hotelPage/sidepeek/contactInformation.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { AboutTheHotelSidePeekProps } from "./aboutTheHotel" - -export type ContactInformationProps = Omit< - AboutTheHotelSidePeekProps, - "descriptions" -> diff --git a/types/components/hotelPage/sidepeek/parking.ts b/types/components/hotelPage/sidepeek/parking.ts new file mode 100644 index 000000000..67c1653e8 --- /dev/null +++ b/types/components/hotelPage/sidepeek/parking.ts @@ -0,0 +1,25 @@ +import type { Hotel } from "@/types/hotel" + +export enum Periods { + day = "Day", + night = "Night", + allDay = "AllDay", +} + +export type ParkingAmenityProps = { + parking: Hotel["parking"] +} + +export type ParkingListProps = { + numberOfChargingSpaces: Hotel["parking"][number]["numberOfChargingSpaces"] + canMakeReservation: Hotel["parking"][number]["canMakeReservation"] + numberOfParkingSpots: Hotel["parking"][number]["numberOfParkingSpots"] + distanceToHotel: Hotel["parking"][number]["distanceToHotel"] + address: Hotel["parking"][number]["address"] +} + +export type ParkingPricesProps = { + data: Hotel["parking"][number]["pricing"]["localCurrency"]["ordinary"] + currency: Hotel["parking"][number]["pricing"]["localCurrency"]["currency"] + freeParking: Hotel["parking"][number]["pricing"]["freeParking"] +} diff --git a/types/components/hotelReservation/bookingConfirmation/promo.ts b/types/components/hotelReservation/bookingConfirmation/promo.ts new file mode 100644 index 000000000..22cf695d8 --- /dev/null +++ b/types/components/hotelReservation/bookingConfirmation/promo.ts @@ -0,0 +1,5 @@ +export interface PromoProps { + buttonText: string + text: string + title: string +} diff --git a/types/components/hotelReservation/bookingConfirmation/room.ts b/types/components/hotelReservation/bookingConfirmation/room.ts new file mode 100644 index 000000000..5f379c4ec --- /dev/null +++ b/types/components/hotelReservation/bookingConfirmation/room.ts @@ -0,0 +1,11 @@ +import { RouterOutput } from "@/lib/trpc/client" + +export interface RoomProps { + booking: RouterOutput["booking"]["confirmation"]["booking"] + img: NonNullable< + RouterOutput["booking"]["confirmation"]["hotel"]["included"] + >[number]["images"][number] + roomName: NonNullable< + RouterOutput["booking"]["confirmation"]["hotel"]["included"] + >[number]["name"] +} diff --git a/types/components/hotelReservation/enterDetails/summary.ts b/types/components/hotelReservation/enterDetails/summary.ts index 5b6f82754..54d896d92 100644 --- a/types/components/hotelReservation/enterDetails/summary.ts +++ b/types/components/hotelReservation/enterDetails/summary.ts @@ -1,5 +1,4 @@ import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate" -import type { Packages } from "@/types/requests/packages" import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability" export interface ClientSummaryProps diff --git a/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts b/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts index 68a6174ed..eb84b6977 100644 --- a/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts +++ b/types/components/hotelReservation/selectHotel/hotelCardListingProps.ts @@ -18,3 +18,7 @@ export type HotelData = { hotelData: Hotel price: ProductType } + +export interface NullableHotelData extends Omit { + hotelData: HotelData["hotelData"] | null +} diff --git a/types/components/hotelReservation/selectHotel/map.ts b/types/components/hotelReservation/selectHotel/map.ts index 4ec21f86f..19490464d 100644 --- a/types/components/hotelReservation/selectHotel/map.ts +++ b/types/components/hotelReservation/selectHotel/map.ts @@ -56,7 +56,7 @@ export interface HotelCardDialogProps { } export interface HotelCardDialogListingProps { - hotels: HotelData[] + hotels: HotelData[] | null activeCard: string | null | undefined onActiveCardChange: (hotelName: string | null) => void } diff --git a/types/components/hotelReservation/selectHotel/selectHotel.ts b/types/components/hotelReservation/selectHotel/selectHotel.ts index 233cf0076..d5f8807bd 100644 --- a/types/components/hotelReservation/selectHotel/selectHotel.ts +++ b/types/components/hotelReservation/selectHotel/selectHotel.ts @@ -1,4 +1,4 @@ -import { Hotel, ParkingData } from "@/types/hotel" +import { CheckInData, Hotel, ParkingData } from "@/types/hotel" export enum AvailabilityEnum { Available = "Available", @@ -17,5 +17,21 @@ export interface ContactProps { } export interface ParkingProps { - parking: ParkingData + parking: ParkingData[] +} + +export interface AccessibilityProps { + accessibilityElevatorPitchText: string +} + +export interface RestaurantProps { + restaurantsContentDescriptionMedium: string +} + +export interface CheckInCheckOutProps { + checkin: CheckInData +} + +export interface MeetingsAndConferencesProps { + meetingDescription: string } diff --git a/types/components/hotelReservation/sidePanel.ts b/types/components/hotelReservation/sidePanel.ts new file mode 100644 index 000000000..5a7f1910a --- /dev/null +++ b/types/components/hotelReservation/sidePanel.ts @@ -0,0 +1,6 @@ +import { sidePanelVariants } from "@/components/HotelReservation/SidePanel/variants" + +import type { VariantProps } from "class-variance-authority" + +export interface SidePanelProps + extends VariantProps {} diff --git a/types/components/hotelReservation/summary.ts b/types/components/hotelReservation/summary.ts index de8e20c4c..e705f3751 100644 --- a/types/components/hotelReservation/summary.ts +++ b/types/components/hotelReservation/summary.ts @@ -1,10 +1,6 @@ -import { RoomPackageCodeEnum } from "./selectRate/roomFilter" - import type { Packages } from "@/types/requests/packages" import type { DetailsState, Price } from "@/types/stores/enter-details" import type { RoomAvailability } from "@/types/trpc/routers/hotel/availability" -import type { BedTypeSchema } from "./enterDetails/bedType" -import type { BreakfastPackage } from "./enterDetails/breakfast" import type { Child } from "./selectRate/selectRate" export type RoomsData = Pick & @@ -15,25 +11,8 @@ export type RoomsData = Pick & packages: Packages | null } -interface SharedSummaryProps { - fromDate: string - toDate: string -} - -export interface SummaryProps extends SharedSummaryProps { - bedType: BedTypeSchema | undefined - breakfast: BreakfastPackage | false | undefined - showMemberPrice: boolean - room: RoomsData - toggleSummaryOpen?: () => void - totalPrice: Price -} - -export interface SummaryPageProps extends SharedSummaryProps { - adults: number - hotelId: string - kids: Child[] | undefined - packageCodes: RoomPackageCodeEnum[] | undefined - rateCode: string - roomTypeCode: string +export interface SummaryProps + extends Pick, + Pick { + isMember: boolean } diff --git a/types/components/tooltip.ts b/types/components/tooltip.ts index ff7ed6ecc..622db4c2c 100644 --- a/types/components/tooltip.ts +++ b/types/components/tooltip.ts @@ -18,4 +18,5 @@ export interface TooltipProps

    { text?: string position: P arrow: ValidArrow

    + isTouchable?: boolean } diff --git a/types/enums/facilities.ts b/types/enums/facilities.ts index 169aad6c0..2e9d0ae4d 100644 --- a/types/enums/facilities.ts +++ b/types/enums/facilities.ts @@ -148,6 +148,7 @@ export enum FacilityEnum { Lake0To1Km = 1865, LakeOrSea0To1Km = 245437, LaptopSafe = 5283, + LateCheckOutUntil1400Guaranteed = 324101, LaundryRoom = 326031, LaundryService = 1834, LaundryServiceExpress = 162583, @@ -155,6 +156,7 @@ export enum FacilityEnum { LifestyleConcierge = 162584, LuggageLockers = 324098, Massage = 348859, + MeetingArea = 1692, MeetingConferenceFacilities = 5806, MeetingRooms = 1017, MinibarInRoom = 5768, diff --git a/types/hotel.ts b/types/hotel.ts index aadaab740..def1e2ebe 100644 --- a/types/hotel.ts +++ b/types/hotel.ts @@ -1,6 +1,7 @@ import { z } from "zod" import { + checkinSchema, facilitySchema, getHotelDataSchema, imageSchema, @@ -23,6 +24,7 @@ export type HotelTripAdvisor = export type RoomData = z.infer export type GalleryImage = z.infer +export type CheckInData = z.infer export type PointOfInterest = z.output diff --git a/types/transitionTypes/rte/enums.ts b/types/transitionTypes/rte/enums.ts index f4d6bd86c..29a76bf0d 100644 --- a/types/transitionTypes/rte/enums.ts +++ b/types/transitionTypes/rte/enums.ts @@ -27,6 +27,7 @@ export enum RTETypeEnum { ol = "ol", p = "p", reference = "reference", + span = "span", table = "table", tbody = "tbody", td = "td", diff --git a/utils/getBookedHotelRoom.ts b/utils/getBookedHotelRoom.ts new file mode 100644 index 000000000..45b5d65b8 --- /dev/null +++ b/utils/getBookedHotelRoom.ts @@ -0,0 +1,23 @@ +import type { RouterOutput } from "@/lib/trpc/client" + +export function getBookedHotelRoom( + hotel: RouterOutput["booking"]["confirmation"]["hotel"], + roomTypeCode: string +) { + const room = hotel.included?.find((include) => { + return include.roomTypes.find((roomType) => roomType.code === roomTypeCode) + }) + if (!room) { + return null + } + const bedType = room.roomTypes.find( + (roomType) => roomType.code === roomTypeCode + ) + if (!bedType) { + return null + } + return { + ...room, + bedType, + } +}