diff --git a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx deleted file mode 100644 index fa2581a54..000000000 --- a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { notFound } from "next/navigation" - -import { getProfileSafely } from "@/lib/trpc/memoizedRequests" -import { serverClient } from "@/lib/trpc/server" - -import BedType from "@/components/HotelReservation/EnterDetails/BedType" -import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast" -import Details from "@/components/HotelReservation/EnterDetails/Details" -import HotelSelectionHeader from "@/components/HotelReservation/HotelSelectionHeader" -import Payment from "@/components/HotelReservation/SelectRate/Payment" -import SectionAccordion from "@/components/HotelReservation/SelectRate/SectionAccordion" -import Summary from "@/components/HotelReservation/SelectRate/Summary" -import { getIntl } from "@/i18n" -import { setLang } from "@/i18n/serverContext" - -import styles from "./page.module.css" - -import { SectionPageProps } from "@/types/components/hotelReservation/selectRate/section" -import { LangParams, PageArgs } from "@/types/params" - -const bedAlternatives = [ - { - value: "queen", - name: "Queen bed", - payment: "160 cm", - pricePerNight: 0, - membersPricePerNight: 0, - currency: "SEK", - }, - { - value: "king", - name: "King bed", - payment: "160 cm", - pricePerNight: 0, - membersPricePerNight: 0, - currency: "SEK", - }, - { - value: "twin", - name: "Twin bed", - payment: "90 cm + 90 cm", - pricePerNight: 82, - membersPricePerNight: 67, - currency: "SEK", - }, -] - -const breakfastAlternatives = [ - { - value: "no", - name: "No breakfast", - payment: "Always cheeper to get it online", - pricePerNight: 0, - currency: "SEK", - }, - { - value: "buffe", - name: "Breakfast buffé", - payment: "Always cheeper to get it online", - pricePerNight: 150, - currency: "SEK", - }, -] - -const getFlexibilityMessage = (value: string) => { - switch (value) { - case "non-refundable": - return "Non refundable" - case "free-rebooking": - return "Free rebooking" - case "free-cancellation": - return "Free cancellation" - } - return undefined -} - -export default async function SectionsPage({ - params, - searchParams, -}: PageArgs) { - setLang(params.lang) - const profile = await getProfileSafely() - - const hotel = await serverClient().hotel.hotelData.get({ - hotelId: "811", - language: params.lang, - }) - - if (!hotel) { - // TODO: handle case with hotel missing - return notFound() - } - - const rooms = await serverClient().hotel.rates.get({ - // TODO: pass the correct hotel ID and all other parameters that should be included in the search - hotelId: hotel.data.id, - }) - const intl = await getIntl() - - const selectedBed = searchParams.bed - ? bedAlternatives.find((a) => a.value === searchParams.bed)?.name - : undefined - - const selectedBreakfast = searchParams.breakfast - ? breakfastAlternatives.find((a) => a.value === searchParams.breakfast) - ?.name - : undefined - - const selectedRoom = searchParams.roomClass - ? rooms.find((room) => room.id.toString() === searchParams.roomClass)?.name - : undefined - const selectedFlexibility = searchParams.flexibility - ? getFlexibilityMessage(searchParams.flexibility) - : undefined - - const currentSearchParams = new URLSearchParams(searchParams).toString() - - let user = null - if (profile && !("error" in profile)) { - user = profile - } - - return ( -
- - -
-
- - - {params.section === "select-bed" ? : null} - - - {params.section === "breakfast" ? : null} - - - {params.section === "details" ?
: null} - - - {params.section === "payment" && ( - - )} - -
-
- -
-
-
- ) -} diff --git a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/[step]/page.module.css similarity index 86% rename from app/[lang]/(live)/(public)/hotelreservation/[section]/page.module.css rename to app/[lang]/(live)/(public)/hotelreservation/[step]/page.module.css index 3266c418d..8962fd5ee 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/[step]/page.module.css @@ -16,10 +16,15 @@ gap: var(--Spacing-x7); } -.main { +.section { flex-grow: 1; } .summary { max-width: 340px; } + +.form { + display: grid; + gap: var(--Spacing-x2); +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx new file mode 100644 index 000000000..050aa0280 --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx @@ -0,0 +1,125 @@ +"use client" + +import { notFound } from "next/navigation" +import { useState } from "react" +import { useIntl } from "react-intl" + +import { trpc } from "@/lib/trpc/client" + +import BedType from "@/components/HotelReservation/EnterDetails/BedType" +import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast" +import Details from "@/components/HotelReservation/EnterDetails/Details" +import HotelSelectionHeader from "@/components/HotelReservation/HotelSelectionHeader" +import Payment from "@/components/HotelReservation/SelectRate/Payment" +import SectionAccordion from "@/components/HotelReservation/SelectRate/SectionAccordion" +import Summary from "@/components/HotelReservation/SelectRate/Summary" +import LoadingSpinner from "@/components/LoadingSpinner" + +import styles from "./page.module.css" + +import { LangParams, PageArgs } from "@/types/params" + +enum StepEnum { + selectBed = "select-bed", + breakfast = "breakfast", + details = "details", + payment = "payment", +} + +function isValidStep(step: string): step is StepEnum { + return Object.values(StepEnum).includes(step as StepEnum) +} + +export default function StepPage({ + params, +}: PageArgs) { + const { step } = params + const [activeStep, setActiveStep] = useState(step) + const intl = useIntl() + + if (!isValidStep(activeStep)) { + return notFound() + } + + const { data: hotel, isLoading: loadingHotel } = + trpc.hotel.hotelData.get.useQuery({ + hotelId: "811", + language: params.lang, + }) + + if (loadingHotel) { + return + } + + if (!hotel) { + // TODO: handle case with hotel missing + return notFound() + } + + switch (activeStep) { + case StepEnum.breakfast: + //return
Select BREAKFAST
+ case StepEnum.details: + //return
Select DETAILS
+ case StepEnum.payment: + //return
Select PAYMENT
+ case StepEnum.selectBed: + // return
Select BED
+ } + + function onNav(step: StepEnum) { + setActiveStep(step) + if (typeof window !== "undefined") { + window.history.pushState({}, "", step) + } + } + + return ( +
+ +
+
+ + + + + + + +
+ + + + +
+ +
+
+ ) +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/layout.module.css b/app/[lang]/(live)/(public)/hotelreservation/layout.module.css index e12cfcba6..aaf8d1c3a 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/layout.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/layout.module.css @@ -2,4 +2,5 @@ min-height: 100dvh; max-width: var(--max-width); margin: 0 auto; + background-color: var(--Base-Background-Primary-Normal); } diff --git a/components/BookingWidget/Client.tsx b/components/BookingWidget/Client.tsx index ce8370bab..48f08f323 100644 --- a/components/BookingWidget/Client.tsx +++ b/components/BookingWidget/Client.tsx @@ -22,6 +22,7 @@ import type { Location } from "@/types/trpc/routers/hotel/locations" export default function BookingWidgetClient({ locations, + type, }: BookingWidgetClientProps) { const [isOpen, setIsOpen] = useState(false) @@ -99,8 +100,9 @@ export default function BookingWidgetClient({ > -
+ +
) diff --git a/components/BookingWidget/MobileToggleButton/button.module.css b/components/BookingWidget/MobileToggleButton/button.module.css index f4a3d80fc..9a0912b07 100644 --- a/components/BookingWidget/MobileToggleButton/button.module.css +++ b/components/BookingWidget/MobileToggleButton/button.module.css @@ -6,6 +6,10 @@ display: grid; gap: var(--Spacing-x-one-and-half); padding: var(--Spacing-x2); + position: sticky; + top: 0; + z-index: 1; + background-color: var(--Base-Surface-Primary-light-Normal); } .complete { @@ -13,7 +17,7 @@ } .partial { - grid-template-columns: min(1fr, 150px) min-content min(1fr, 150px) 1fr; + grid-template-columns: minmax(auto, 150px) min-content minmax(auto, 150px) auto; } .icon { diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index 5384dac60..2827ba72d 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -1,16 +1,17 @@ -@media screen and (max-width: 1366px) { +@media screen and (max-width: 767px) { .container { background-color: var(--UI-Input-Controls-Surface-Normal); bottom: -100%; display: grid; gap: var(--Spacing-x3); grid-template-rows: 36px 1fr; - height: 100dvh; + height: calc(100dvh - 20px); padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x7); position: fixed; transition: bottom 300ms ease; width: 100%; z-index: 10000; + border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0; } .container[data-open="true"] { @@ -23,13 +24,26 @@ cursor: pointer; justify-self: flex-end; } + + .container[data-open="true"] + .backdrop { + background-color: rgba(0, 0, 0, 0.4); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 1000; + } } -@media screen and (min-width: 1367px) { +@media screen and (min-width: 768px) { .container { - border-bottom: 1px solid var(--Base-Border-Subtle); - border-top: 1px solid var(--Base-Border-Subtle); display: block; + box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05); + position: sticky; + top: 0; + z-index: 10000; + background-color: var(--Base-Surface-Primary-light-Normal); } .close { diff --git a/components/BookingWidget/index.tsx b/components/BookingWidget/index.tsx index 1c8b02f8e..f7f220a0b 100644 --- a/components/BookingWidget/index.tsx +++ b/components/BookingWidget/index.tsx @@ -2,16 +2,18 @@ import { getLocations } from "@/lib/trpc/memoizedRequests" import BookingWidgetClient from "./Client" +import type { BookingWidgetProps } from "@/types/components/bookingWidget" + export function preload() { void getLocations() } -export default async function BookingWidget() { +export default async function BookingWidget({ type }: BookingWidgetProps) { const locations = await getLocations() if (!locations || "error" in locations) { return null } - return + return } diff --git a/components/DatePicker/Screen/Desktop.tsx b/components/DatePicker/Screen/Desktop.tsx index 8656fb8e5..db290137e 100644 --- a/components/DatePicker/Screen/Desktop.tsx +++ b/components/DatePicker/Screen/Desktop.tsx @@ -1,4 +1,5 @@ "use client" + import { DayPicker } from "react-day-picker" import { useIntl } from "react-intl" diff --git a/components/DatePicker/date-picker.module.css b/components/DatePicker/date-picker.module.css index faaac7dd0..b11ab3b77 100644 --- a/components/DatePicker/date-picker.module.css +++ b/components/DatePicker/date-picker.module.css @@ -41,7 +41,8 @@ } .container[data-isopen="true"] .hideWrapper { - top: 0; + border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0; + top: 20px; } } diff --git a/components/Forms/BookingWidget/FormContent/Input/index.tsx b/components/Forms/BookingWidget/FormContent/Input/index.tsx new file mode 100644 index 000000000..7a592f594 --- /dev/null +++ b/components/Forms/BookingWidget/FormContent/Input/index.tsx @@ -0,0 +1,18 @@ +import React, { forwardRef, InputHTMLAttributes } from "react" + +import Body from "@/components/TempDesignSystem/Text/Body" + +import styles from "./input.module.css" + +const Input = forwardRef< + HTMLInputElement, + InputHTMLAttributes +>(function InputComponent(props, ref) { + return ( + + + + ) +}) + +export default Input diff --git a/components/Forms/BookingWidget/FormContent/Input/input.module.css b/components/Forms/BookingWidget/FormContent/Input/input.module.css new file mode 100644 index 000000000..7a4f7b998 --- /dev/null +++ b/components/Forms/BookingWidget/FormContent/Input/input.module.css @@ -0,0 +1,22 @@ +.input { + background-color: transparent; + border: none; + height: 24px; + outline: none; + position: relative; + width: 100%; + z-index: 2; +} + +.input::-webkit-search-cancel-button { + -webkit-appearance: none; + appearance: none; + background-image: url("/_static/icons/close.svg"); + height: 20px; + width: 20px; +} + +.input:disabled, +.input:disabled::placeholder { + color: var(--Base-Text-Disabled); +} diff --git a/components/Forms/BookingWidget/FormContent/Search/index.tsx b/components/Forms/BookingWidget/FormContent/Search/index.tsx index 353c2807b..98c5b36e8 100644 --- a/components/Forms/BookingWidget/FormContent/Search/index.tsx +++ b/components/Forms/BookingWidget/FormContent/Search/index.tsx @@ -13,6 +13,7 @@ import { useIntl } from "react-intl" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" +import Input from "../Input" import { init, localStorageKey, reducer, sessionStorageKey } from "./reducer" import SearchList from "./SearchList" @@ -142,29 +143,26 @@ export default function Search({ locations }: SearchProps) {
- - - + onChange: handleOnChange, + }), + type: "search", + })} + />
+ +
+ + +
+
+ +
+ + + +
+
+
+ ) +} diff --git a/components/Forms/BookingWidget/FormContent/Voucher/voucher.module.css b/components/Forms/BookingWidget/FormContent/Voucher/voucher.module.css new file mode 100644 index 000000000..83f02c14b --- /dev/null +++ b/components/Forms/BookingWidget/FormContent/Voucher/voucher.module.css @@ -0,0 +1,79 @@ +.options { + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; +} + +.option { + display: flex; + gap: var(--Spacing-x2); + margin-top: var(--Spacing-x2); + align-items: center; +} +.vouchers { + width: 100%; + display: block; + padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); + border-radius: var(--Corner-radius-Small); +} + +.optionsContainer { + display: flex; + flex-direction: column; +} + +.checkbox { + width: 24px; + height: 24px; +} + +.checkboxVoucher { + display: none; +} + +@media screen and (min-width: 768px) { + .vouchers { + display: none; + } + .options { + flex-direction: row; + gap: var(--Spacing-x4); + } + .option { + margin-top: 0; + gap: var(--Spacing-x-one-and-half); + } + .checkboxVoucher { + display: flex; + } +} + +@media screen and (max-width: 1366px) { + .vouchers { + background-color: var(--Base-Background-Primary-Normal); + border-radius: var(--Corner-radius-Medium); + } +} + +@media screen and (min-width: 1367px) { + .vouchers { + display: block; + max-width: 200px; + } + .options { + flex-direction: column; + max-width: 190px; + gap: 0; + } + .vouchers:hover, + .option:hover { + cursor: not-allowed; + } + .optionsContainer { + flex-direction: row; + } + .checkboxVoucher { + display: none; + } +} diff --git a/components/Forms/BookingWidget/FormContent/formContent.module.css b/components/Forms/BookingWidget/FormContent/formContent.module.css index 4b62ecdcb..dfffecc96 100644 --- a/components/Forms/BookingWidget/FormContent/formContent.module.css +++ b/components/Forms/BookingWidget/FormContent/formContent.module.css @@ -1,16 +1,29 @@ -.options { - display: flex; - flex-direction: column; - justify-content: center; - width: 100%; +.infoIcon { + stroke: var(--Base-Text-Disabled); } -.option { +.vouchersHeader { display: flex; + gap: var(--Spacing-x-one-and-half); } -@media screen and (max-width: 1366px) { - .input { +.checkbox { + width: 24px; + height: 24px; +} +.icon, +.voucherRow { + display: none; +} + +@media screen and (max-width: 767px) { + .voucherContainer { + padding: var(--Spacing-x2) 0 var(--Spacing-x4); + } +} + +@media screen and (max-width: 1367px) { + .inputContainer { display: grid; gap: var(--Spacing-x2); } @@ -29,52 +42,85 @@ padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); } - .options { - gap: var(--Spacing-x2); - margin-top: var(--Spacing-x2); - } - - .option { - gap: var(--Spacing-x2); + .button { + align-self: flex-end; + justify-content: center; + width: 100%; } } -@media screen and (min-width: 1367px) { +@media screen and (min-width: 768px) { .input { display: flex; + align-items: center; + } + .inputContainer { + display: flex; + flex: 2; gap: var(--Spacing-x2); } + .voucherContainer { + flex: 1; + } .rooms, - .vouchers, .when, .where { - border-right: 1px solid var(--Base-Surface-Subtle-Normal); width: 100%; } - .input input[type="text"] { + .inputContainer input[type="text"] { border: none; height: 24px; } .rooms, .when { - max-width: 240px; padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); + border-radius: var(--Corner-radius-Small); } - - .vouchers { - max-width: 200px; - padding: var(--Spacing-x1) 0; + .when:hover, + .rooms:hover, + .rooms:has(.input:active, .input:focus, .input:focus-within) { + background-color: var(--Base-Surface-Primary-light-Hover-alt); } .where { - max-width: 280px; position: relative; } - .options { - max-width: 158px; + .button { + justify-content: center; + width: 118px; + } +} + +@media screen and (min-width: 768px) and (max-width: 1366px) { + .inputContainer { + padding: var(--Spacing-x2) var(--Spacing-x2); + } + .buttonContainer { + padding-right: var(--Spacing-x2); + } + .input .buttonContainer .button { + padding: var(--Spacing-x1); + width: 48px; + height: 48px; + } + .buttonText { + display: none; + } + .icon { + display: flex; + } + + .voucherRow { + display: flex; + background: var(--Base-Surface-Primary-light-Hover); + border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); + padding: var(--Spacing-x2); + } + .voucherContainer { + display: none; } } diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx index fcb5bb847..8f3e8cbc4 100644 --- a/components/Forms/BookingWidget/FormContent/index.tsx +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -5,9 +5,14 @@ import { useIntl } from "react-intl" import { dt } from "@/lib/dt" import DatePicker from "@/components/DatePicker" +import { SearchIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" +import Input from "./Input" import Search from "./Search" +import Voucher from "./Voucher" import styles from "./formContent.module.css" @@ -15,53 +20,69 @@ import type { BookingWidgetFormContentProps } from "@/types/components/form/book export default function FormContent({ locations, + formId, + formState, }: BookingWidgetFormContentProps) { const intl = useIntl() const selectedDate = useWatch({ name: "date" }) const rooms = intl.formatMessage({ id: "Guests & Rooms" }) - const vouchers = intl.formatMessage({ id: "Code / Voucher" }) - const bonus = intl.formatMessage({ id: "Use bonus cheque" }) - const reward = intl.formatMessage({ id: "Book reward night" }) const nights = dt(selectedDate.to).diff(dt(selectedDate.from), "days") return ( -
-
- + <> +
+
+
+ +
+
+ + {intl.formatMessage( + { id: "booking.nights" }, + { totalNights: nights } + )} + + +
+
+ + +
+
+
+ +
+
+ +
-
- - {intl.formatMessage( - { id: "booking.nights" }, - { totalNights: nights } - )} - - +
+
-
- - {rooms} - - -
-
- - {vouchers} - - -
-
- - -
-
+ ) } diff --git a/components/Forms/BookingWidget/form.module.css b/components/Forms/BookingWidget/form.module.css index 323d84037..8514dffd7 100644 --- a/components/Forms/BookingWidget/form.module.css +++ b/components/Forms/BookingWidget/form.module.css @@ -8,29 +8,31 @@ .form { display: grid; - gap: var(--Spacing-x2); width: 100%; } -@media screen and (max-width: 1366px) { +@media screen and (max-width: 767px) { .form { align-self: flex-start; } - - .button { - align-self: flex-end; - justify-content: center; - width: 100%; - } } -@media screen and (min-width: 1367px) { +@media screen and (min-width: 768px) { .section { display: flex; } - .button { - justify-content: center; - width: 118px; + .default { + border-radius: var(--Corner-radius-Medium); + } +} + +@media screen and (min-width: 1367px) { + .default { + padding: var(--Spacing-x-one-and-half) var(--Spacing-x2) + var(--Spacing-x-one-and-half) var(--Spacing-x1); + } + .full { + padding: var(--Spacing-x1) var(--Spacing-x5); } } diff --git a/components/Forms/BookingWidget/index.tsx b/components/Forms/BookingWidget/index.tsx index f78e38be0..80a822315 100644 --- a/components/Forms/BookingWidget/index.tsx +++ b/components/Forms/BookingWidget/index.tsx @@ -1,12 +1,9 @@ "use client" import { useRouter } from "next/navigation" import { useFormContext } from "react-hook-form" -import { useIntl } from "react-intl" - -import Button from "@/components/TempDesignSystem/Button" -import Caption from "@/components/TempDesignSystem/Text/Caption" import FormContent from "./FormContent" +import { bookingWidgetVariants } from "./variants" import styles from "./form.module.css" @@ -15,10 +12,13 @@ import type { BookingWidgetFormProps } from "@/types/components/form/bookingwidg const formId = "booking-widget" -export default function Form({ locations }: BookingWidgetFormProps) { - const intl = useIntl() +export default function Form({ locations, type }: BookingWidgetFormProps) { const router = useRouter() + const classNames = bookingWidgetVariants({ + type, + }) + const { formState, handleSubmit, register } = useFormContext() @@ -31,28 +31,19 @@ export default function Form({ locations }: BookingWidgetFormProps) { } return ( -
+
- + -
) } diff --git a/components/Forms/BookingWidget/variants.ts b/components/Forms/BookingWidget/variants.ts new file mode 100644 index 000000000..d31fd9643 --- /dev/null +++ b/components/Forms/BookingWidget/variants.ts @@ -0,0 +1,15 @@ +import { cva } from "class-variance-authority" + +import styles from "./form.module.css" + +export const bookingWidgetVariants = cva(styles.section, { + variants: { + type: { + default: styles.default, + full: styles.full, + }, + }, + defaultVariants: { + type: "full", + }, +}) diff --git a/components/HotelReservation/HotelSelectionHeader/index.tsx b/components/HotelReservation/HotelSelectionHeader/index.tsx index c7e119b0a..e34f05f37 100644 --- a/components/HotelReservation/HotelSelectionHeader/index.tsx +++ b/components/HotelReservation/HotelSelectionHeader/index.tsx @@ -1,8 +1,10 @@ +"use client" +import { useIntl } from "react-intl" + import Divider from "@/components/TempDesignSystem/Divider" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" -import { getIntl } from "@/i18n" import HotelDetailSidePeek from "./HotelDetailSidePeek" @@ -10,10 +12,10 @@ import styles from "./hotelSelectionHeader.module.css" import { HotelSelectionHeaderProps } from "@/types/components/hotelReservation/selectRate/hotelSelectionHeader" -export default async function HotelSelectionHeader({ +export default function HotelSelectionHeader({ hotel, }: HotelSelectionHeaderProps) { - const intl = await getIntl() + const intl = useIntl() return (
diff --git a/components/HotelReservation/SelectRate/SectionAccordion/index.tsx b/components/HotelReservation/SelectRate/SectionAccordion/index.tsx index 94ae62f21..a56902248 100644 --- a/components/HotelReservation/SelectRate/SectionAccordion/index.tsx +++ b/components/HotelReservation/SelectRate/SectionAccordion/index.tsx @@ -1,48 +1,91 @@ -import { CheckCircleIcon, ChevronDownIcon } from "@/components/Icons" -import Button from "@/components/TempDesignSystem/Button" +"use client" +import { useEffect, useRef } from "react" +import { useIntl } from "react-intl" + +import { CheckIcon, ChevronDownIcon } from "@/components/Icons" 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 Footnote from "@/components/TempDesignSystem/Text/Footnote" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import styles from "./sectionAccordion.module.css" import { SectionAccordionProps } from "@/types/components/hotelReservation/selectRate/sectionAccordion" -export default async function SectionAccordion({ +export default function SectionAccordion({ header, - selection, + isOpen, + isCompleted, + label, path, children, }: React.PropsWithChildren) { - const intl = await getIntl() + const intl = useIntl() + + const contentRef = useRef(null) + const circleRef = useRef(null) + + useEffect(() => { + const content = contentRef.current + const circle = circleRef.current + if (content) { + if (isOpen) { + content.style.maxHeight = `${content.scrollHeight}px` + } else { + content.style.maxHeight = "0" + } + } + + if (circle) { + if (isOpen) { + circle.style.backgroundColor = `var(--UI-Text-Placeholder);` + } else { + circle.style.backgroundColor = `var(--Base-Surface-Subtle-Hover);` + } + } + }, [isOpen]) return ( -
-
-
- -
-
- -

{header}

- - {(Array.isArray(selection) ? selection : [selection]).map((s) => ( - - {s} - - ))} -
- {selection && ( - - )} -
- +
+
+
+ {isCompleted ? ( + + ) : null}
- {children} -
+
+
+
+ +

{header}

+
+ + {label} + +
+ {isCompleted && !isOpen && ( + + {intl.formatMessage({ id: "Modify" })}{" "} + + + )} +
+
+ {children} +
+
+
) } diff --git a/components/HotelReservation/SelectRate/SectionAccordion/sectionAccordion.module.css b/components/HotelReservation/SelectRate/SectionAccordion/sectionAccordion.module.css index ce9dec013..8c1a05ba4 100644 --- a/components/HotelReservation/SelectRate/SectionAccordion/sectionAccordion.module.css +++ b/components/HotelReservation/SelectRate/SectionAccordion/sectionAccordion.module.css @@ -1,21 +1,73 @@ .wrapper { - border-bottom: 1px solid var(--Base-Border-Normal); + position: relative; + display: flex; + flex-direction: row; + gap: var(--Spacing-x3); + + padding-top: var(--Spacing-x3); } -.top { +.wrapper:not(:last-child)::after { + position: absolute; + left: 12px; + bottom: 0; + top: var(--Spacing-x5); + height: 100%; + content: ""; + border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); +} + +.main { + display: flex; + flex-direction: column; + gap: var(--Spacing-x3); + width: 100%; + border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); padding-bottom: var(--Spacing-x3); - padding-top: var(--Spacing-x3); +} + +.headerContainer { display: flex; justify-content: space-between; align-items: center; - gap: var(--Spacing-x2); -} - -.header { - flex-grow: 1; } .selection { font-weight: 450; font-size: var(--typography-Title-4-fontSize); } + +.iconWrapper { + position: relative; + top: var(--Spacing-x1); + z-index: 10; +} + +.circle { + width: 24px; + height: 24px; + border-radius: 100px; + transition: background-color 0.4s; + border: 2px solid var(--Base-Border-Inverted); + display: flex; + justify-content: center; + align-items: center; +} + +.circle[data-checked="true"] { + background-color: var(--UI-Input-Controls-Fill-Selected); +} + +.wrapper[data-open="true"] .circle[data-checked="false"] { + background-color: var(--UI-Text-Placeholder); +} + +.wrapper[data-open="false"] .circle[data-checked="false"] { + background-color: var(--Base-Surface-Subtle-Hover); +} + +.content { + overflow: hidden; + transition: max-height 0.4s ease-out; + max-height: 0; +} diff --git a/components/Icons/icon.module.css b/components/Icons/icon.module.css index 282c214cd..3fa235d32 100644 --- a/components/Icons/icon.module.css +++ b/components/Icons/icon.module.css @@ -61,3 +61,8 @@ .uiTextMediumContrast * { fill: var(--UI-Text-Medium-contrast); } + +.blue, +.blue * { + fill: var(--UI-Input-Controls-Fill-Selected); +} diff --git a/components/Icons/variants.ts b/components/Icons/variants.ts index cf2703bd4..12b9cb574 100644 --- a/components/Icons/variants.ts +++ b/components/Icons/variants.ts @@ -17,6 +17,7 @@ const config = { white: styles.white, uiTextHighContrast: styles.uiTextHighContrast, uiTextMediumContrast: styles.uiTextMediumContrast, + blue: styles.blue, }, }, defaultVariants: { diff --git a/components/TempDesignSystem/TeaserCard/index.tsx b/components/TempDesignSystem/TeaserCard/index.tsx index a51c04eed..a441a3322 100644 --- a/components/TempDesignSystem/TeaserCard/index.tsx +++ b/components/TempDesignSystem/TeaserCard/index.tsx @@ -1,5 +1,3 @@ -import React from "react" - import { ChevronRightIcon } from "@/components/Icons" import Image from "@/components/Image" import Button from "@/components/TempDesignSystem/Button" diff --git a/components/TempDesignSystem/Text/Caption/caption.module.css b/components/TempDesignSystem/Text/Caption/caption.module.css index a7b51bb52..a3ac8919c 100644 --- a/components/TempDesignSystem/Text/Caption/caption.module.css +++ b/components/TempDesignSystem/Text/Caption/caption.module.css @@ -75,10 +75,14 @@ p.caption { color: var(--UI-Text-High-contrast); } +.disabled { + color: var(--Base-Text-Disabled); +} + .center { text-align: center; } .left { text-align: left; -} \ No newline at end of file +} diff --git a/components/TempDesignSystem/Text/Caption/variants.ts b/components/TempDesignSystem/Text/Caption/variants.ts index 401893857..3b84c513e 100644 --- a/components/TempDesignSystem/Text/Caption/variants.ts +++ b/components/TempDesignSystem/Text/Caption/variants.ts @@ -15,6 +15,7 @@ const config = { uiTextHighContrast: styles.uiTextHighContrast, uiTextActive: styles.uiTextActive, uiTextMediumContrast: styles.uiTextMediumContrast, + disabled: styles.disabled, }, textTransform: { bold: styles.bold, diff --git a/components/TempDesignSystem/Text/Subtitle/subtitle.module.css b/components/TempDesignSystem/Text/Subtitle/subtitle.module.css index 07f795811..8fdac30c4 100644 --- a/components/TempDesignSystem/Text/Subtitle/subtitle.module.css +++ b/components/TempDesignSystem/Text/Subtitle/subtitle.module.css @@ -58,3 +58,7 @@ .pale { color: var(--Scandic-Brand-Pale-Peach); } + +.uiTextHighContrast { + color: var(--UI-Text-High-contrast); +} diff --git a/components/TempDesignSystem/Text/Subtitle/variants.ts b/components/TempDesignSystem/Text/Subtitle/variants.ts index afb33bde1..7a8faa54c 100644 --- a/components/TempDesignSystem/Text/Subtitle/variants.ts +++ b/components/TempDesignSystem/Text/Subtitle/variants.ts @@ -8,6 +8,7 @@ const config = { black: styles.black, burgundy: styles.burgundy, pale: styles.pale, + uiTextHighContrast: styles.uiTextHighContrast, }, textAlign: { center: styles.center, diff --git a/components/TempDesignSystem/Tooltip/index.tsx b/components/TempDesignSystem/Tooltip/index.tsx new file mode 100644 index 000000000..ba53c022d --- /dev/null +++ b/components/TempDesignSystem/Tooltip/index.tsx @@ -0,0 +1,32 @@ +import { PropsWithChildren } from "react" + +import Caption from "@/components/TempDesignSystem/Text/Caption" + +import { tooltipVariants } from "./variants" + +import styles from "./tooltip.module.css" + +import { TooltipPosition, TooltipProps } from "@/types/components/tooltip" + +export function Tooltip

({ + heading, + text, + position, + arrow, + children, +}: PropsWithChildren>) { + const className = tooltipVariants({ position, arrow }) + return ( +

+
+ {heading && ( + + {heading} + + )} + {text && {text}} +
+ {children} +
+ ) +} diff --git a/components/TempDesignSystem/Tooltip/tooltip.module.css b/components/TempDesignSystem/Tooltip/tooltip.module.css new file mode 100644 index 000000000..2a6b00b43 --- /dev/null +++ b/components/TempDesignSystem/Tooltip/tooltip.module.css @@ -0,0 +1,137 @@ +.tooltipContainer { + position: relative; + display: inline-block; +} + +.tooltip { + padding: var(--Spacing-x1); + background-color: var(--UI-Text-Active); + border: 0.5px solid var(--UI-Border-Active); + border-radius: var(--Corner-radius-Medium); + color: var(--Base-Text-Inverted); + position: absolute; + visibility: hidden; + z-index: 1000; + opacity: 0; + transition: opacity 0.3s; + max-width: 200px; +} + +.tooltipContainer:hover .tooltip { + visibility: visible; + opacity: 1; +} + +.left { + right: 100%; +} + +.right { + left: 100%; +} + +.top { + bottom: 100%; +} + +.bottom { + top: 100%; +} + +.tooltip::before { + content: ""; + position: absolute; + border-style: solid; +} + +.bottom.arrowLeft::before { + top: -8px; + left: 16px; + border-width: 0 7px 8px 7px; + border-color: transparent transparent var(--UI-Text-Active) transparent; +} + +.bottom.arrowCenter::before { + top: -8px; + left: 50%; + transform: translateX(-50%); + border-width: 0 7px 8px 7px; + border-color: transparent transparent var(--UI-Text-Active) transparent; +} + +.bottom.arrowRight::before { + top: -8px; + right: 16px; + border-width: 0 7px 8px 7px; + border-color: transparent transparent var(--UI-Text-Active) transparent; +} + +.top.arrowLeft::before { + bottom: -8px; + left: 16px; + border-width: 8px 7px 0 7px; + border-color: var(--UI-Text-Active) transparent transparent transparent; +} + +.top.arrowCenter::before { + bottom: -8px; + left: 50%; + transform: translateX(-50%); + border-width: 8px 7px 0 7px; + border-color: var(--UI-Text-Active) transparent transparent transparent; +} + +.top.arrowRight::before { + bottom: -8px; + right: 16px; + border-width: 8px 7px 0 7px; + border-color: var(--UI-Text-Active) transparent transparent transparent; +} + +.left.arrowTop::before { + top: 16px; + right: -8px; + transform: translateY(-50%); + border-width: 7px 0 7px 8px; + border-color: transparent transparent transparent var(--UI-Text-Active); +} + +.left.arrowCenter::before { + top: 50%; + right: -8px; + transform: translateY(-50%); + border-width: 7px 0 7px 8px; + border-color: transparent transparent transparent var(--UI-Text-Active); +} + +.left.arrowBottom::before { + bottom: 16px; + right: -8px; + transform: translateY(50%); + border-width: 7px 0 7px 8px; + border-color: transparent transparent transparent var(--UI-Text-Active); +} + +.right.arrowTop::before { + top: 16px; + left: -8px; + transform: translateY(-50%); + border-width: 7px 8px 7px 0; + border-color: transparent var(--UI-Text-Active) transparent transparent; +} + +.right.arrowCenter::before { + top: 50%; + left: -8px; + transform: translateY(-50%); + border-width: 7px 8px 7px 0; + border-color: transparent var(--UI-Text-Active) transparent transparent; +} + +.right.arrowBottom::before { + bottom: 16px; + left: -8px; + transform: translateY(50%); + border-width: 7px 8px 7px 0; + border-color: transparent var(--UI-Text-Active) transparent transparent; +} diff --git a/components/TempDesignSystem/Tooltip/variants.ts b/components/TempDesignSystem/Tooltip/variants.ts new file mode 100644 index 000000000..286466f92 --- /dev/null +++ b/components/TempDesignSystem/Tooltip/variants.ts @@ -0,0 +1,21 @@ +import { cva } from "class-variance-authority" + +import styles from "./tooltip.module.css" + +export const tooltipVariants = cva(styles.tooltip, { + variants: { + position: { + left: styles.left, + right: styles.right, + top: styles.top, + bottom: styles.bottom, + }, + arrow: { + left: styles.arrowLeft, + right: styles.arrowRight, + center: styles.arrowCenter, + top: styles.arrowTop, + bottom: styles.arrowBottom, + }, + }, +}) diff --git a/constants/routes/hotelReservation.js b/constants/routes/hotelReservation.js index 5fdb1b6b1..4f37fcb6c 100644 --- a/constants/routes/hotelReservation.js +++ b/constants/routes/hotelReservation.js @@ -18,6 +18,46 @@ export const selectHotel = { de: `${hotelReservation.de}/select-hotel`, } +// TODO: Translate paths +export const selectBed = { + en: `${hotelReservation.en}/select-bed`, + sv: `${hotelReservation.sv}/select-bed`, + no: `${hotelReservation.no}/select-bed`, + fi: `${hotelReservation.fi}/select-bed`, + da: `${hotelReservation.da}/select-bed`, + de: `${hotelReservation.de}/select-bed`, +} + +// TODO: Translate paths +export const breakfast = { + en: `${hotelReservation.en}/breakfast`, + sv: `${hotelReservation.sv}/breakfast`, + no: `${hotelReservation.no}/breakfast`, + fi: `${hotelReservation.fi}/breakfast`, + da: `${hotelReservation.da}/breakfast`, + de: `${hotelReservation.de}/breakfast`, +} + +// TODO: Translate paths +export const details = { + en: `${hotelReservation.en}/details`, + sv: `${hotelReservation.sv}/details`, + no: `${hotelReservation.no}/details`, + fi: `${hotelReservation.fi}/details`, + da: `${hotelReservation.da}/details`, + de: `${hotelReservation.de}/details`, +} + +// TODO: Translate paths +export const payments = { + en: `${hotelReservation.en}/payment`, + sv: `${hotelReservation.sv}/payment`, + no: `${hotelReservation.no}/payment`, + fi: `${hotelReservation.fi}/payment`, + da: `${hotelReservation.da}/payment`, + de: `${hotelReservation.de}/payment`, +} + // TODO: Translate paths export const selectHotelMap = { en: `${selectHotel.en}/map`, @@ -48,4 +88,11 @@ export const bookingConfirmation = { de: `${hotelReservation.de}/buchungsbesttigung`, } -export const bookingFlow = [...Object.values(hotelReservation)] +export const bookingFlow = [ + ...Object.values(selectHotel), + ...Object.values(selectBed), + ...Object.values(breakfast), + ...Object.values(details), + ...Object.values(payments), + ...Object.values(selectHotelMap), +] diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index e29d7cf6d..a260dd068 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -19,11 +19,12 @@ "Any changes you've made will be lost.": "Alle ændringer, du har foretaget, går tabt.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på, at du vil fjerne kortet, der slutter me {lastFourDigits} fra din medlemsprofil?", "Arrival date": "Ankomstdato", + "as of today": "pr. dags dato", "As our": "Som vores {level}", "As our Close Friend": "Som vores nære ven", "At latest": "Senest", "At the hotel": "På hotellet", - "Attractions": "Attraktioner", + "Attraction": "Attraktion", "Back to scandichotels.com": "Tilbage til scandichotels.com", "Bar": "Bar", "Bed type": "Seng type", @@ -70,6 +71,8 @@ "Description": "Beskrivelse", "Destination": "Destination", "Destinations & hotels": "Destinationer & hoteller", + "Disabled booking options header": "Vi beklager", + "Disabled booking options text": "Koder, checks og bonusnætter er endnu ikke tilgængelige på den nye hjemmeside.", "Discard changes": "Kassér ændringer", "Discard unsaved changes?": "Slette ændringer, der ikke er gemt?", "Distance to city centre": "{number}km til centrum", @@ -81,6 +84,7 @@ "Email": "E-mail", "Email address": "E-mailadresse", "Enter destination or hotel": "Indtast destination eller hotel", + "Enter your details": "Indtast dine oplysninger", "Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences", "Events that make an impression": "Events that make an impression", "Explore all levels and benefits": "Udforsk alle niveauer og fordele", @@ -117,6 +121,7 @@ "Join Scandic Friends": "Tilmeld dig Scandic Friends", "Join at no cost": "Tilmeld dig uden omkostninger", "King bed": "Kingsize-seng", + "km to city center": "km til byens centrum", "Language": "Sprog", "Lastname": "Efternavn", "Latest searches": "Seneste søgninger", @@ -208,6 +213,7 @@ "Restaurant & Bar": "Restaurant & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Gentag den nye adgangskode", + "Request bedtype": "Anmod om sengetype", "Room & Terms": "Værelse & Vilkår", "Room facilities": "Værelsesfaciliteter", "Rooms": "Værelser", @@ -222,10 +228,12 @@ "See room details": "Se værelsesdetaljer", "See rooms": "Se værelser", "Select a country": "Vælg et land", + "Select breakfast options": "Vælg morgenmadsmuligheder", "Select country of residence": "Vælg bopælsland", "Select date of birth": "Vælg fødselsdato", "Select dates": "Vælg datoer", "Select language": "Vælg sprog", + "Select payment method": "Vælg betalingsmetode", "Select your language": "Vælg dit sprog", "Shopping": "Shopping", "Shopping & Dining": "Shopping & Spisning", @@ -258,6 +266,7 @@ "Type of bed": "Sengtype", "Type of room": "Værelsestype", "Use bonus cheque": "Brug Bonus Cheque", + "Use code/voucher": "Brug kode/voucher", "User information": "Brugeroplysninger", "View as list": "Vis som liste", "View as map": "Vis som kort", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index ee4bccf96..ad755efdc 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -23,7 +23,7 @@ "As our Close Friend": "Als unser enger Freund", "At latest": "Spätestens", "At the hotel": "Im Hotel", - "Attractions": "Attraktionen", + "Attraction": "Attraktion", "Back to scandichotels.com": "Zurück zu scandichotels.com", "Bar": "Bar", "Bed type": "Bettentyp", @@ -70,6 +70,8 @@ "Description": "Beschreibung", "Destination": "Bestimmungsort", "Destinations & hotels": "Reiseziele & Hotels", + "Disabled booking options header": "Es tut uns leid", + "Disabled booking options text": "Codes, Schecks und Bonusnächte sind auf der neuen Website noch nicht verfügbar.", "Discard changes": "Änderungen verwerfen", "Discard unsaved changes?": "Nicht gespeicherte Änderungen verwerfen?", "Distance to city centre": "{number}km zum Stadtzentrum", @@ -82,6 +84,7 @@ "Email address": "E-Mail-Adresse", "Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences", "Enter destination or hotel": "Reiseziel oder Hotel eingeben", + "Enter your details": "Geben Sie Ihre Daten ein", "Events that make an impression": "Events that make an impression", "Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile", "Explore nearby": "Erkunden Sie die Umgebung", @@ -208,6 +211,7 @@ "Restaurant & Bar": "Restaurant & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Neues Passwort erneut eingeben", + "Request bedtype": "Bettentyp anfragen", "Room & Terms": "Zimmer & Bedingungen", "Room facilities": "Zimmerausstattung", "Rooms": "Räume", @@ -222,10 +226,12 @@ "See room details": "Zimmerdetails ansehen", "See rooms": "Zimmer ansehen", "Select a country": "Wähle ein Land", + "Select breakfast options": "Wählen Sie Frühstücksoptionen", "Select country of residence": "Wählen Sie das Land Ihres Wohnsitzes aus", "Select date of birth": "Geburtsdatum auswählen", "Select dates": "Datum auswählen", "Select language": "Sprache auswählen", + "Select payment method": "Zahlungsart auswählen", "Select your language": "Wählen Sie Ihre Sprache", "Shopping": "Einkaufen", "Shopping & Dining": "Einkaufen & Essen", @@ -258,6 +264,7 @@ "Type of bed": "Bettentyp", "Type of room": "Zimmerart", "Use bonus cheque": "Bonusscheck nutzen", + "Use code/voucher": "Code/Gutschein nutzen", "User information": "Nutzerinformation", "View as list": "Als Liste anzeigen", "View as map": "Als Karte anzeigen", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 1e51379f2..43903c4c2 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -70,6 +70,8 @@ "Description": "Description", "Destination": "Destination", "Destinations & hotels": "Destinations & hotels", + "Disabled booking options header": "We're sorry", + "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?", "Distance to city centre": "{number}km to city centre", @@ -82,6 +84,7 @@ "Email address": "Email address", "Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences", "Enter destination or hotel": "Enter destination or hotel", + "Enter your details": "Enter your details", "Events that make an impression": "Events that make an impression", "Explore all levels and benefits": "Explore all levels and benefits", "Explore nearby": "Explore nearby", @@ -117,6 +120,7 @@ "Join Scandic Friends": "Join Scandic Friends", "Join at no cost": "Join at no cost", "King bed": "King bed", + "km to city center": "km to city center", "Language": "Language", "Lastname": "Lastname", "Latest searches": "Latest searches", @@ -258,6 +262,7 @@ "Type of bed": "Type of bed", "Type of room": "Type of room", "Use bonus cheque": "Use bonus cheque", + "Use code/voucher": "Use code/voucher", "User information": "User information", "View as list": "View as list", "View as map": "View as map", @@ -310,6 +315,9 @@ "number": "number", "or": "or", "points": "Points", + "Request bedtype": "Request bedtype", + "Select breakfast options": "Select breakfast options", + "Select payment method": "Select payment method", "special character": "special character", "spendable points expiring by": "{points} spendable points expiring by {date}", "to": "to", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 5e3ab2fc4..558c99dcb 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -70,6 +70,8 @@ "Description": "Kuvaus", "Destination": "Kohde", "Destinations & hotels": "Kohteet ja hotellit", + "Disabled booking options header": "Olemme pahoillamme", + "Disabled booking options text": "Koodit, sekit ja palkintoillat eivät ole vielä saatavilla uudella verkkosivustolla.", "Discard changes": "Hylkää muutokset", "Discard unsaved changes?": "Hylkäätkö tallentamattomat muutokset?", "Distance to city centre": "{number}km Etäisyys kaupunkiin", @@ -81,6 +83,7 @@ "Email": "Sähköposti", "Email address": "Sähköpostiosoite", "Enter destination or hotel": "Anna kohde tai hotelli", + "Enter your details": "Anna tietosi", "Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences", "Events that make an impression": "Events that make an impression", "Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin", @@ -117,6 +120,7 @@ "Join Scandic Friends": "Liity jäseneksi", "Join at no cost": "Liity maksutta", "King bed": "King-vuode", + "km to city center": "km keskustaan", "Language": "Kieli", "Lastname": "Sukunimi", "Latest searches": "Viimeisimmät haut", @@ -213,6 +217,7 @@ "Rooms": "Huoneet", "Rooms & Guests": "Huoneet & Vieraat", "Rooms & Guestss": "Huoneet & Vieraat", + "Request bedtype": "Pyydä sänkytyyppiä", "Sauna and gym": "Sauna and gym", "Save": "Tallenna", "Scandic Friends Mastercard": "Scandic Friends Mastercard", @@ -223,10 +228,12 @@ "See room details": "Katso huoneen tiedot", "See rooms": "Katso huoneet", "Select a country": "Valitse maa", + "Select breakfast options": "Valitse aamiaisvaihtoehdot", "Select country of residence": "Valitse asuinmaa", "Select date of birth": "Valitse syntymäaika", "Select dates": "Valitse päivämäärät", "Select language": "Valitse kieli", + "Select payment method": "Valitse maksutapa", "Select your language": "Valitse kieli", "Shopping": "Ostokset", "Shopping & Dining": "Ostokset & Ravintolat", @@ -259,6 +266,7 @@ "Type of bed": "Vuodetyyppi", "Type of room": "Huonetyyppi", "Use bonus cheque": "Käytä bonussekkiä", + "Use code/voucher": "Käytä koodia/voucheria", "User information": "Käyttäjän tiedot", "View as list": "Näytä listana", "View as map": "Näytä kartalla", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 1dd97b8f2..5db6252f3 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -19,6 +19,7 @@ "Any changes you've made will be lost.": "Eventuelle endringer du har gjort, går tapt.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på at du vil fjerne kortet som slutter på {lastFourDigits} fra medlemsprofilen din?", "Arrival date": "Ankomstdato", + "as of today": "per i dag", "As our": "Som vår {level}", "As our Close Friend": "Som vår nære venn", "At latest": "Senest", @@ -69,6 +70,8 @@ "Description": "Beskrivelse", "Destination": "Destinasjon", "Destinations & hotels": "Destinasjoner og hoteller", + "Disabled booking options header": "Vi beklager", + "Disabled booking options text": "Koder, checks og belønningsnætter er enda ikke tilgjengelige på den nye nettsiden.", "Discard changes": "Forkaste endringer", "Discard unsaved changes?": "Forkaste endringer som ikke er lagret?", "Distance to city centre": "{number}km til sentrum", @@ -80,6 +83,7 @@ "Email": "E-post", "Email address": "E-postadresse", "Enter destination or hotel": "Skriv inn destinasjon eller hotell", + "Enter your details": "Skriv inn detaljene dine", "Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences", "Events that make an impression": "Events that make an impression", "Explore all levels and benefits": "Utforsk alle nivåer og fordeler", @@ -116,6 +120,7 @@ "Join Scandic Friends": "Bli med i Scandic Friends", "Join at no cost": "Bli med uten kostnad", "King bed": "King-size-seng", + "km to city center": "km til sentrum", "Language": "Språk", "Lastname": "Etternavn", "Latest searches": "Siste søk", @@ -207,6 +212,7 @@ "Restaurant & Bar": "Restaurant & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Skriv inn nytt passord på nytt", + "Request bedtype": "Be om sengetype", "Room & Terms": "Rom & Vilkår", "Room facilities": "Romfasiliteter", "Rooms": "Rom", @@ -221,10 +227,12 @@ "See room details": "Se detaljer om rommet", "See rooms": "Se rom", "Select a country": "Velg et land", + "Select breakfast options": "Velg frokostalternativer", "Select country of residence": "Velg bostedsland", "Select date of birth": "Velg fødselsdato", "Select dates": "Velg datoer", "Select language": "Velg språk", + "Select payment method": "Velg betalingsmetode", "Select your language": "Velg språk", "Shopping": "Shopping", "Shopping & Dining": "Shopping & Spisesteder", @@ -257,6 +265,7 @@ "Type of bed": "Sengtype", "Type of room": "Romtype", "Use bonus cheque": "Bruk bonussjekk", + "Use code/voucher": "Bruk kode/voucher", "User information": "Brukerinformasjon", "View as list": "Vis som liste", "View as map": "Vis som kart", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 6f5fcd37a..e7b55e6f6 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -19,6 +19,7 @@ "Any changes you've made will be lost.": "Alla ändringar du har gjort kommer att gå förlorade.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Är du säker på att du vill ta bort kortet som slutar med {lastFourDigits} från din medlemsprofil?", "Arrival date": "Ankomstdatum", + "as of today": "per idag", "As our": "Som vår {level}", "As our Close Friend": "Som vår nära vän", "At latest": "Senast", @@ -70,6 +71,8 @@ "Description": "Beskrivning", "Destination": "Destination", "Destinations & hotels": "Destinationer & hotell", + "Disabled booking options header": "Vi beklagar", + "Disabled booking options text": "Koder, bonuscheckar och belöningsnätter är inte tillgängliga på den nya webbplatsen än.", "Discard changes": "Ignorera ändringar", "Discard unsaved changes?": "Vill du ignorera ändringar som inte har sparats?", "Distance to city centre": "{number}km till centrum", @@ -81,6 +84,7 @@ "Email": "E-post", "Email address": "E-postadress", "Enter destination or hotel": "Ange destination eller hotell", + "Enter your details": "Ange dina uppgifter", "Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences", "Events that make an impression": "Events that make an impression", "Explore all levels and benefits": "Utforska alla nivåer och fördelar", @@ -118,6 +122,7 @@ "Join Scandic Friends": "Gå med i Scandic Friends", "Join at no cost": "Gå med utan kostnad", "King bed": "King size-säng", + "km to city center": "km till stadens centrum", "Language": "Språk", "Lastname": "Efternamn", "Latest searches": "Senaste sökningarna", @@ -209,6 +214,7 @@ "Restaurant & Bar": "Restaurang & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Upprepa nytt lösenord", + "Request bedtype": "Request bedtype", "Room & Terms": "Rum & Villkor", "Room facilities": "Rumfaciliteter", "Rooms": "Rum", @@ -223,10 +229,12 @@ "See room details": "Se rumsdetaljer", "See rooms": "Se rum", "Select a country": "Välj ett land", + "Select breakfast options": "Välj frukostalternativ", "Select country of residence": "Välj bosättningsland", "Select date of birth": "Välj födelsedatum", "Select dates": "Välj datum", "Select language": "Välj språk", + "Select payment method": "Välj betalningsmetod", "Select your language": "Välj ditt språk", "Shopping": "Shopping", "Shopping & Dining": "Shopping & Mat", @@ -258,7 +266,9 @@ "Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)", "Type of bed": "Sängtyp", "Type of room": "Rumstyp", - "Use bonus cheque": "Use bonus cheque", + "uppercase letter": "stor bokstav", + "Use bonus cheque": "Använd bonuscheck", + "Use code/voucher": "Använd kod/voucher", "User information": "Användarinformation", "View as list": "Visa som lista", "View as map": "Visa som karta", diff --git a/middlewares/bookingFlow.ts b/middlewares/bookingFlow.ts index 6c8826c8f..74c3818f1 100644 --- a/middlewares/bookingFlow.ts +++ b/middlewares/bookingFlow.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server" -import { hotelReservation } from "@/constants/routes/hotelReservation" +import { bookingFlow } from "@/constants/routes/hotelReservation" import { resolve as resolveEntry } from "@/utils/entry" import { findLang } from "@/utils/languages" @@ -14,19 +14,8 @@ import type { MiddlewareMatcher } from "@/types/middleware" export const middleware: NextMiddleware = async (request) => { const { nextUrl } = request - const lang = findLang(nextUrl.pathname)! - - const pathWithoutTrailingSlash = removeTrailingSlash(nextUrl.pathname) - const pathNameWithoutLang = pathWithoutTrailingSlash.replace(`/${lang}`, "") - const { contentType, uid } = await resolveEntry(pathNameWithoutLang, lang) const headers = getDefaultRequestHeaders(request) - if (uid) { - headers.set("x-uid", uid) - } - if (contentType) { - headers.set("x-contenttype", contentType) - } return NextResponse.next({ request: { headers, @@ -35,6 +24,5 @@ export const middleware: NextMiddleware = async (request) => { } export const matcher: MiddlewareMatcher = (request) => { - const lang = findLang(request.nextUrl.pathname)! - return request.nextUrl.pathname.startsWith(hotelReservation[lang]) + return bookingFlow.includes(request.nextUrl.pathname) } diff --git a/types/components/bookingWidget/index.ts b/types/components/bookingWidget/index.ts index ab4d194d9..fb511bc86 100644 --- a/types/components/bookingWidget/index.ts +++ b/types/components/bookingWidget/index.ts @@ -1,13 +1,24 @@ +import { VariantProps } from "class-variance-authority" import { z } from "zod" import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" +import { bookingWidgetVariants } from "@/components/Forms/BookingWidget/variants" import type { Locations } from "@/types/trpc/routers/hotel/locations" export type BookingWidgetSchema = z.output +export type BookingWidgetType = VariantProps< + typeof bookingWidgetVariants +>["type"] + +export interface BookingWidgetProps { + type?: BookingWidgetType +} + export interface BookingWidgetClientProps { locations: Locations + type?: BookingWidgetType } export interface BookingWidgetToggleButtonProps { diff --git a/types/components/form/bookingwidget.ts b/types/components/form/bookingwidget.ts index f9ef4d2e1..e655b8b59 100644 --- a/types/components/form/bookingwidget.ts +++ b/types/components/form/bookingwidget.ts @@ -1,11 +1,20 @@ +import { FormState, UseFormReturn } from "react-hook-form" + +import type { + BookingWidgetSchema, + BookingWidgetType, +} from "@/types/components/bookingWidget" import type { Location, Locations } from "@/types/trpc/routers/hotel/locations" export interface BookingWidgetFormProps { locations: Locations + type?: BookingWidgetType } export interface BookingWidgetFormContentProps { locations: Locations + formId: string + formState: FormState } export enum ActionType { diff --git a/types/components/hotelReservation/selectRate/sectionAccordion.ts b/types/components/hotelReservation/selectRate/sectionAccordion.ts index 19d13f836..802e471f9 100644 --- a/types/components/hotelReservation/selectRate/sectionAccordion.ts +++ b/types/components/hotelReservation/selectRate/sectionAccordion.ts @@ -1,5 +1,7 @@ export interface SectionAccordionProps { header: string - selection?: string | string[] + isOpen: boolean + isCompleted: boolean + label: string path: string } diff --git a/types/components/search.ts b/types/components/search.ts index 6f1017126..42401045f 100644 --- a/types/components/search.ts +++ b/types/components/search.ts @@ -39,14 +39,14 @@ export interface ListItemProps export interface DialogProps extends React.PropsWithChildren, - VariantProps, - Pick { + VariantProps, + Pick { className?: string } export interface ErrorDialogProps extends React.PropsWithChildren, - Pick { } + Pick {} export interface ClearSearchButtonProps extends Pick< diff --git a/types/components/tooltip.ts b/types/components/tooltip.ts new file mode 100644 index 000000000..ff7ed6ecc --- /dev/null +++ b/types/components/tooltip.ts @@ -0,0 +1,21 @@ +export type TooltipPosition = "left" | "right" | "top" | "bottom" +type VerticalArrow = "top" | "bottom" | "center" +type HorizontalArrow = "left" | "right" | "center" + +type ValidArrowMap = { + left: VerticalArrow + right: VerticalArrow + top: HorizontalArrow + bottom: HorizontalArrow +} + +type ValidArrow

= P extends keyof ValidArrowMap + ? ValidArrowMap[P] + : never + +export interface TooltipProps

{ + heading?: string + text?: string + position: P + arrow: ValidArrow

+}