Merge branch 'develop' into feature/sw-561-design-fixes
This commit is contained in:
@@ -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<LangParams & { section: string }, SectionPageProps>) {
|
||||
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 (
|
||||
<div>
|
||||
<HotelSelectionHeader hotel={hotel.data.attributes} />
|
||||
|
||||
<div className={styles.content}>
|
||||
<div className={styles.main}>
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Room & Terms" })}
|
||||
selection={
|
||||
selectedRoom
|
||||
? [
|
||||
selectedRoom,
|
||||
intl.formatMessage({ id: selectedFlexibility }),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
path={`select-rate?${currentSearchParams}`}
|
||||
></SectionAccordion>
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Bed type" })}
|
||||
selection={selectedBed}
|
||||
path={`select-bed?${currentSearchParams}`}
|
||||
>
|
||||
{params.section === "select-bed" ? <BedType /> : null}
|
||||
</SectionAccordion>
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Breakfast" })}
|
||||
selection={selectedBreakfast}
|
||||
path={`breakfast?${currentSearchParams}`}
|
||||
>
|
||||
{params.section === "breakfast" ? <Breakfast /> : null}
|
||||
</SectionAccordion>
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Your details" })}
|
||||
path={`details?${currentSearchParams}`}
|
||||
>
|
||||
{params.section === "details" ? <Details user={user} /> : null}
|
||||
</SectionAccordion>
|
||||
<SectionAccordion
|
||||
header={intl.formatMessage({ id: "Payment info" })}
|
||||
path={`payment?${currentSearchParams}`}
|
||||
>
|
||||
{params.section === "payment" && (
|
||||
<Payment hotel={hotel.data.attributes} />
|
||||
)}
|
||||
</SectionAccordion>
|
||||
</div>
|
||||
<div className={styles.summary}>
|
||||
<Summary />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -16,10 +16,15 @@
|
||||
gap: var(--Spacing-x7);
|
||||
}
|
||||
|
||||
.main {
|
||||
.section {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.summary {
|
||||
max-width: 340px;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
125
app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx
Normal file
125
app/[lang]/(live)/(public)/hotelreservation/[step]/page.tsx
Normal file
@@ -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<LangParams & { step: StepEnum }>) {
|
||||
const { step } = params
|
||||
const [activeStep, setActiveStep] = useState<StepEnum>(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 <LoadingSpinner />
|
||||
}
|
||||
|
||||
if (!hotel) {
|
||||
// TODO: handle case with hotel missing
|
||||
return notFound()
|
||||
}
|
||||
|
||||
switch (activeStep) {
|
||||
case StepEnum.breakfast:
|
||||
//return <div>Select BREAKFAST</div>
|
||||
case StepEnum.details:
|
||||
//return <div>Select DETAILS</div>
|
||||
case StepEnum.payment:
|
||||
//return <div>Select PAYMENT</div>
|
||||
case StepEnum.selectBed:
|
||||
// return <div>Select BED</div>
|
||||
}
|
||||
|
||||
function onNav(step: StepEnum) {
|
||||
setActiveStep(step)
|
||||
if (typeof window !== "undefined") {
|
||||
window.history.pushState({}, "", step)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main className={styles.page}>
|
||||
<HotelSelectionHeader hotel={hotel.data.attributes} />
|
||||
<div className={styles.content}>
|
||||
<section className={styles.section}>
|
||||
<SectionAccordion
|
||||
header="Select bed"
|
||||
isCompleted={true}
|
||||
isOpen={activeStep === StepEnum.selectBed}
|
||||
label={intl.formatMessage({ id: "Request bedtype" })}
|
||||
path="/select-bed"
|
||||
>
|
||||
<BedType />
|
||||
</SectionAccordion>
|
||||
<SectionAccordion
|
||||
header="Food options"
|
||||
isCompleted={true}
|
||||
isOpen={activeStep === StepEnum.breakfast}
|
||||
label={intl.formatMessage({ id: "Select breakfast options" })}
|
||||
path="/breakfast"
|
||||
>
|
||||
<Breakfast />
|
||||
</SectionAccordion>
|
||||
<SectionAccordion
|
||||
header="Details"
|
||||
isCompleted={false}
|
||||
isOpen={activeStep === StepEnum.details}
|
||||
label={intl.formatMessage({ id: "Enter your details" })}
|
||||
path="/details"
|
||||
>
|
||||
<Details user={null} />
|
||||
</SectionAccordion>
|
||||
<SectionAccordion
|
||||
header="Payment"
|
||||
isCompleted={false}
|
||||
isOpen={activeStep === StepEnum.payment}
|
||||
label={intl.formatMessage({ id: "Select payment method" })}
|
||||
path="/hotelreservation/select-bed"
|
||||
>
|
||||
<Payment hotel={hotel.data.attributes} />
|
||||
</SectionAccordion>
|
||||
</section>
|
||||
<aside className={styles.summary}>
|
||||
<Summary />
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
min-height: 100dvh;
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
background-color: var(--Base-Background-Primary-Normal);
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
>
|
||||
<CloseLarge />
|
||||
</button>
|
||||
<Form locations={locations} />
|
||||
<Form locations={locations} type={type} />
|
||||
</section>
|
||||
<div className={styles.backdrop} onClick={closeMobileSearch} />
|
||||
<MobileToggleButton openMobileSearch={openMobileSearch} />
|
||||
</FormProvider>
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 <BookingWidgetClient locations={locations.data} />
|
||||
return <BookingWidgetClient locations={locations.data} type={type} />
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import { DayPicker } from "react-day-picker"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
components/Forms/BookingWidget/FormContent/Input/index.tsx
Normal file
18
components/Forms/BookingWidget/FormContent/Input/index.tsx
Normal file
@@ -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<HTMLInputElement>
|
||||
>(function InputComponent(props, ref) {
|
||||
return (
|
||||
<Body asChild>
|
||||
<input {...props} ref={ref} className={styles.input} />
|
||||
</Body>
|
||||
)
|
||||
})
|
||||
|
||||
export default Input
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
</Caption>
|
||||
</label>
|
||||
<div {...getRootProps({}, { suppressRefError: true })}>
|
||||
<Body asChild>
|
||||
<input
|
||||
{...getInputProps({
|
||||
className: styles.input,
|
||||
id: name,
|
||||
onFocus(evt) {
|
||||
handleOnFocus(evt)
|
||||
openMenu()
|
||||
<Input
|
||||
{...getInputProps({
|
||||
id: name,
|
||||
onFocus(evt) {
|
||||
handleOnFocus(evt)
|
||||
openMenu()
|
||||
},
|
||||
placeholder: intl.formatMessage({
|
||||
id: "Destinations & hotels",
|
||||
}),
|
||||
...register(name, {
|
||||
onBlur: function () {
|
||||
handleOnBlur()
|
||||
closeMenu()
|
||||
},
|
||||
placeholder: intl.formatMessage({
|
||||
id: "Destinations & hotels",
|
||||
}),
|
||||
...register(name, {
|
||||
onBlur: function () {
|
||||
handleOnBlur()
|
||||
closeMenu()
|
||||
},
|
||||
onChange: handleOnChange,
|
||||
}),
|
||||
type: "search",
|
||||
})}
|
||||
/>
|
||||
</Body>
|
||||
onChange: handleOnChange,
|
||||
}),
|
||||
type: "search",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<SearchList
|
||||
getItemProps={getItemProps}
|
||||
|
||||
@@ -7,42 +7,24 @@
|
||||
}
|
||||
|
||||
.container:hover,
|
||||
.container:has(.input:active, .input:focus, .input:focus-within) {
|
||||
.container:has(input:active, input:focus, input:focus-within) {
|
||||
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
||||
}
|
||||
|
||||
.container:has(.input:active, .input:focus, .input:focus-within) {
|
||||
.container:has(input:active, input:focus, input:focus-within) {
|
||||
border-color: 1px solid var(--UI-Input-Controls-Border-Focus);
|
||||
}
|
||||
|
||||
.label:has(
|
||||
~ .inputContainer .input:active,
|
||||
~ .inputContainer .input:focus,
|
||||
~ .inputContainer .input:focus-within
|
||||
~ .inputContainer input:active,
|
||||
~ .inputContainer input:focus,
|
||||
~ .inputContainer input:focus-within
|
||||
)
|
||||
p {
|
||||
color: var(--UI-Text-Active);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.container:hover:has(.input:not(:active, :focus, :focus-within))
|
||||
.input::-webkit-search-cancel-button {
|
||||
.container:hover:has(input:not(:active, :focus, :focus-within))
|
||||
input::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
71
components/Forms/BookingWidget/FormContent/Voucher/index.tsx
Normal file
71
components/Forms/BookingWidget/FormContent/Voucher/index.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import { Tooltip } from "@/components/TempDesignSystem/Tooltip"
|
||||
|
||||
import Input from "../Input"
|
||||
|
||||
import styles from "./voucher.module.css"
|
||||
|
||||
export default function Voucher() {
|
||||
const intl = useIntl()
|
||||
|
||||
const vouchers = intl.formatMessage({ id: "Code / Voucher" })
|
||||
const useVouchers = intl.formatMessage({ id: "Use code/voucher" })
|
||||
const addVouchers = intl.formatMessage({ id: "Add code" })
|
||||
const bonus = intl.formatMessage({ id: "Use bonus cheque" })
|
||||
const reward = intl.formatMessage({ id: "Book reward night" })
|
||||
const disabledBookingOptionsHeader = intl.formatMessage({
|
||||
id: "Disabled booking options header",
|
||||
})
|
||||
const disabledBookingOptionsText = intl.formatMessage({
|
||||
id: "Disabled booking options text",
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={styles.optionsContainer}>
|
||||
<Tooltip
|
||||
heading={disabledBookingOptionsHeader}
|
||||
text={disabledBookingOptionsText}
|
||||
position="bottom"
|
||||
arrow="left"
|
||||
>
|
||||
<div className={styles.vouchers}>
|
||||
<label>
|
||||
<Caption color="disabled" textTransform="bold">
|
||||
{vouchers}
|
||||
</Caption>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</label>
|
||||
<Input type="text" placeholder={addVouchers} disabled />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
heading={disabledBookingOptionsHeader}
|
||||
text={disabledBookingOptionsText}
|
||||
position="bottom"
|
||||
arrow="left"
|
||||
>
|
||||
<div className={styles.options}>
|
||||
<label className={`${styles.option} ${styles.checkboxVoucher}`}>
|
||||
<input type="checkbox" disabled className={styles.checkbox} />
|
||||
<Caption color="disabled">{useVouchers}</Caption>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</label>
|
||||
<label className={styles.option}>
|
||||
<input type="checkbox" disabled className={styles.checkbox} />
|
||||
<Caption color="disabled">{bonus}</Caption>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</label>
|
||||
<label className={styles.option}>
|
||||
<input type="checkbox" disabled className={styles.checkbox} />
|
||||
<Caption color="disabled">{reward}</Caption>
|
||||
{/* <InfoCircleIcon color="white" className={styles.infoIcon} /> Out of scope for this release */}
|
||||
</label>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className={styles.input}>
|
||||
<div className={styles.where}>
|
||||
<Search locations={locations} />
|
||||
<>
|
||||
<div className={styles.input}>
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.where}>
|
||||
<Search locations={locations} />
|
||||
</div>
|
||||
<div className={styles.when}>
|
||||
<Caption color="red" textTransform="bold">
|
||||
{intl.formatMessage(
|
||||
{ id: "booking.nights" },
|
||||
{ totalNights: nights }
|
||||
)}
|
||||
</Caption>
|
||||
<DatePicker />
|
||||
</div>
|
||||
<div className={styles.rooms}>
|
||||
<label>
|
||||
<Caption color="red" textTransform="bold">
|
||||
{rooms}
|
||||
</Caption>
|
||||
</label>
|
||||
<Input type="text" placeholder={rooms} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.voucherContainer}>
|
||||
<Voucher />
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
<Button
|
||||
className={styles.button}
|
||||
disabled={!formState.isValid}
|
||||
form={formId}
|
||||
intent="primary"
|
||||
theme="base"
|
||||
type="submit"
|
||||
>
|
||||
<Caption
|
||||
color="white"
|
||||
textTransform="bold"
|
||||
className={styles.buttonText}
|
||||
>
|
||||
{intl.formatMessage({ id: "Search" })}
|
||||
</Caption>
|
||||
<div className={styles.icon}>
|
||||
<SearchIcon color="white" width={28} height={28} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.when}>
|
||||
<Caption color="red" textTransform="bold">
|
||||
{intl.formatMessage(
|
||||
{ id: "booking.nights" },
|
||||
{ totalNights: nights }
|
||||
)}
|
||||
</Caption>
|
||||
<DatePicker />
|
||||
<div className={styles.voucherRow}>
|
||||
<Voucher />
|
||||
</div>
|
||||
<div className={styles.rooms}>
|
||||
<Caption color="red" textTransform="bold">
|
||||
{rooms}
|
||||
</Caption>
|
||||
<input type="text" placeholder={rooms} />
|
||||
</div>
|
||||
<div className={styles.vouchers}>
|
||||
<Caption color="uiTextMediumContrast" textTransform="bold">
|
||||
{vouchers}
|
||||
</Caption>
|
||||
<input type="text" placeholder={vouchers} />
|
||||
</div>
|
||||
<div className={styles.options}>
|
||||
<label className={styles.option}>
|
||||
<input type="checkbox" />
|
||||
<Caption color="textMediumContrast">{bonus}</Caption>
|
||||
</label>
|
||||
<label className={styles.option}>
|
||||
<input type="checkbox" />
|
||||
<Caption color="textMediumContrast">{reward}</Caption>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BookingWidgetSchema>()
|
||||
|
||||
@@ -31,28 +31,19 @@ export default function Form({ locations }: BookingWidgetFormProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<section className={classNames}>
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className={styles.form}
|
||||
id={formId}
|
||||
>
|
||||
<input {...register("location")} type="hidden" />
|
||||
<FormContent locations={locations} />
|
||||
<FormContent
|
||||
locations={locations}
|
||||
formId={formId}
|
||||
formState={formState}
|
||||
/>
|
||||
</form>
|
||||
<Button
|
||||
className={styles.button}
|
||||
disabled={!formState.isValid}
|
||||
form={formId}
|
||||
intent="primary"
|
||||
size="small"
|
||||
theme="base"
|
||||
type="submit"
|
||||
>
|
||||
<Caption color="white" textTransform="bold">
|
||||
{intl.formatMessage({ id: "Search" })}
|
||||
</Caption>
|
||||
</Button>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
15
components/Forms/BookingWidget/variants.ts
Normal file
15
components/Forms/BookingWidget/variants.ts
Normal file
@@ -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",
|
||||
},
|
||||
})
|
||||
@@ -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 (
|
||||
<header className={styles.hotelSelectionHeader}>
|
||||
|
||||
@@ -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<SectionAccordionProps>) {
|
||||
const intl = await getIntl()
|
||||
const intl = useIntl()
|
||||
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const circleRef = useRef<HTMLDivElement>(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 (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.top}>
|
||||
<div>
|
||||
<CheckCircleIcon color={selection ? "green" : "pale"} />
|
||||
</div>
|
||||
<div className={styles.header}>
|
||||
<Caption color={"burgundy"} asChild>
|
||||
<h2>{header}</h2>
|
||||
</Caption>
|
||||
{(Array.isArray(selection) ? selection : [selection]).map((s) => (
|
||||
<Body key={s} className={styles.selection} color={"burgundy"}>
|
||||
{s}
|
||||
</Body>
|
||||
))}
|
||||
</div>
|
||||
{selection && (
|
||||
<Button intent="secondary" size="small" asChild>
|
||||
<Link href={path}>{intl.formatMessage({ id: "Modify" })}</Link>
|
||||
</Button>
|
||||
)}
|
||||
<div>
|
||||
<ChevronDownIcon />
|
||||
<section className={styles.wrapper} data-open={isOpen}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<div
|
||||
className={styles.circle}
|
||||
data-checked={isCompleted}
|
||||
ref={circleRef}
|
||||
>
|
||||
{isCompleted ? (
|
||||
<CheckIcon color="white" height="16" width="16" />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
<div className={styles.main}>
|
||||
<header className={styles.headerContainer}>
|
||||
<div>
|
||||
<Footnote
|
||||
asChild
|
||||
textTransform="uppercase"
|
||||
color="uiTextPlaceholder"
|
||||
>
|
||||
<h2>{header}</h2>
|
||||
</Footnote>
|
||||
<Subtitle
|
||||
type="two"
|
||||
className={styles.selection}
|
||||
color="uiTextHighContrast"
|
||||
>
|
||||
{label}
|
||||
</Subtitle>
|
||||
</div>
|
||||
{isCompleted && !isOpen && (
|
||||
<Link href={path} color="burgundy" variant="icon">
|
||||
{intl.formatMessage({ id: "Modify" })}{" "}
|
||||
<ChevronDownIcon color="burgundy" />
|
||||
</Link>
|
||||
)}
|
||||
</header>
|
||||
<div className={styles.content} ref={contentRef}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -61,3 +61,8 @@
|
||||
.uiTextMediumContrast * {
|
||||
fill: var(--UI-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
.blue,
|
||||
.blue * {
|
||||
fill: var(--UI-Input-Controls-Fill-Selected);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ const config = {
|
||||
white: styles.white,
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||
blue: styles.blue,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react"
|
||||
|
||||
import { ChevronRightIcon } from "@/components/Icons"
|
||||
import Image from "@/components/Image"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const config = {
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
uiTextActive: styles.uiTextActive,
|
||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||
disabled: styles.disabled,
|
||||
},
|
||||
textTransform: {
|
||||
bold: styles.bold,
|
||||
|
||||
@@ -58,3 +58,7 @@
|
||||
.pale {
|
||||
color: var(--Scandic-Brand-Pale-Peach);
|
||||
}
|
||||
|
||||
.uiTextHighContrast {
|
||||
color: var(--UI-Text-High-contrast);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const config = {
|
||||
black: styles.black,
|
||||
burgundy: styles.burgundy,
|
||||
pale: styles.pale,
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
},
|
||||
textAlign: {
|
||||
center: styles.center,
|
||||
|
||||
32
components/TempDesignSystem/Tooltip/index.tsx
Normal file
32
components/TempDesignSystem/Tooltip/index.tsx
Normal file
@@ -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<P extends TooltipPosition>({
|
||||
heading,
|
||||
text,
|
||||
position,
|
||||
arrow,
|
||||
children,
|
||||
}: PropsWithChildren<TooltipProps<P>>) {
|
||||
const className = tooltipVariants({ position, arrow })
|
||||
return (
|
||||
<div className={styles.tooltipContainer} role="tooltip" aria-label={text}>
|
||||
<div className={className}>
|
||||
{heading && (
|
||||
<Caption textTransform="bold" color="white">
|
||||
{heading}
|
||||
</Caption>
|
||||
)}
|
||||
{text && <Caption color="white">{text}</Caption>}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
137
components/TempDesignSystem/Tooltip/tooltip.module.css
Normal file
137
components/TempDesignSystem/Tooltip/tooltip.module.css
Normal file
@@ -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;
|
||||
}
|
||||
21
components/TempDesignSystem/Tooltip/variants.ts
Normal file
21
components/TempDesignSystem/Tooltip/variants.ts
Normal file
@@ -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,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -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),
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<typeof bookingWidgetSchema>
|
||||
|
||||
export type BookingWidgetType = VariantProps<
|
||||
typeof bookingWidgetVariants
|
||||
>["type"]
|
||||
|
||||
export interface BookingWidgetProps {
|
||||
type?: BookingWidgetType
|
||||
}
|
||||
|
||||
export interface BookingWidgetClientProps {
|
||||
locations: Locations
|
||||
type?: BookingWidgetType
|
||||
}
|
||||
|
||||
export interface BookingWidgetToggleButtonProps {
|
||||
|
||||
@@ -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<BookingWidgetSchema>
|
||||
}
|
||||
|
||||
export enum ActionType {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface SectionAccordionProps {
|
||||
header: string
|
||||
selection?: string | string[]
|
||||
isOpen: boolean
|
||||
isCompleted: boolean
|
||||
label: string
|
||||
path: string
|
||||
}
|
||||
|
||||
@@ -39,14 +39,14 @@ export interface ListItemProps
|
||||
|
||||
export interface DialogProps
|
||||
extends React.PropsWithChildren,
|
||||
VariantProps<typeof dialogVariants>,
|
||||
Pick<SearchListProps, "getMenuProps"> {
|
||||
VariantProps<typeof dialogVariants>,
|
||||
Pick<SearchListProps, "getMenuProps"> {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface ErrorDialogProps
|
||||
extends React.PropsWithChildren,
|
||||
Pick<SearchListProps, "getMenuProps"> { }
|
||||
Pick<SearchListProps, "getMenuProps"> {}
|
||||
|
||||
export interface ClearSearchButtonProps
|
||||
extends Pick<
|
||||
|
||||
21
types/components/tooltip.ts
Normal file
21
types/components/tooltip.ts
Normal file
@@ -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 TooltipPosition> = P extends keyof ValidArrowMap
|
||||
? ValidArrowMap[P]
|
||||
: never
|
||||
|
||||
export interface TooltipProps<P extends TooltipPosition = TooltipPosition> {
|
||||
heading?: string
|
||||
text?: string
|
||||
position: P
|
||||
arrow: ValidArrow<P>
|
||||
}
|
||||
Reference in New Issue
Block a user