feat(SW-453): Fixed new filter buttons and updated price in summary
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
import { RoomPackageCode } from "@/server/routers/hotels/schemas/packages"
|
||||
|
||||
import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard"
|
||||
import Rooms from "@/components/HotelReservation/SelectRate/Rooms"
|
||||
import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
import { setLang } from "@/i18n/serverContext"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||
import { LangParams, PageArgs } from "@/types/params"
|
||||
|
||||
@@ -42,9 +42,9 @@ export default async function SelectRatePage({
|
||||
adults: adults,
|
||||
children: children,
|
||||
packageCodes: [
|
||||
RoomPackageCode.ACCE,
|
||||
RoomPackageCode.PETR,
|
||||
RoomPackageCode.ALLG,
|
||||
RoomPackageCodeEnum.ACCE,
|
||||
RoomPackageCodeEnum.PETR,
|
||||
RoomPackageCodeEnum.ALLG,
|
||||
],
|
||||
}),
|
||||
getProfileSafely(),
|
||||
|
||||
@@ -6,16 +6,19 @@ import { FormProvider, useForm } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
import { z } from "zod"
|
||||
|
||||
import { RoomPackageCode } from "@/server/routers/hotels/schemas/packages"
|
||||
|
||||
import Chip from "@/components/TempDesignSystem/Chip"
|
||||
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
|
||||
import { InfoCircleIcon } from "@/components/Icons"
|
||||
import CheckboxChip from "@/components/TempDesignSystem/Form/FilterChip/Checkbox"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import { Tooltip } from "@/components/TempDesignSystem/Tooltip"
|
||||
|
||||
import { getIconForFeatureCode } from "../utils"
|
||||
|
||||
import styles from "./roomFilter.module.css"
|
||||
|
||||
import { RoomFilterProps } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import {
|
||||
RoomFilterProps,
|
||||
RoomPackageCodeEnum,
|
||||
} from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
|
||||
export default function RoomFilter({
|
||||
numberOfRooms,
|
||||
@@ -43,8 +46,8 @@ export default function RoomFilter({
|
||||
})
|
||||
|
||||
const { watch, getValues, handleSubmit } = methods
|
||||
const petFriendly = watch(RoomPackageCode.PETR)
|
||||
const allergyFriendly = watch(RoomPackageCode.ALLG)
|
||||
const petFriendly = watch(RoomPackageCodeEnum.PETR)
|
||||
const allergyFriendly = watch(RoomPackageCodeEnum.ALLG)
|
||||
|
||||
const submitFilter = useCallback(() => {
|
||||
const data = getValues()
|
||||
@@ -65,21 +68,27 @@ export default function RoomFilter({
|
||||
<form onSubmit={handleSubmit(submitFilter)}>
|
||||
<div className={styles.roomsFilter}>
|
||||
{filterOptions.map((option) => (
|
||||
<Checkbox
|
||||
<CheckboxChip
|
||||
name={option.code}
|
||||
key={option.code}
|
||||
registerOptions={{
|
||||
required: false,
|
||||
disabled:
|
||||
(option.code === RoomPackageCode.PETR && allergyFriendly) ||
|
||||
(option.code === RoomPackageCode.ALLG && petFriendly),
|
||||
}}
|
||||
>
|
||||
<Caption color="uiTextHighContrast">
|
||||
{intl.formatMessage({ id: option.description })}
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
label={intl.formatMessage({ id: option.description })}
|
||||
disabled={
|
||||
(option.code === RoomPackageCodeEnum.ALLG && petFriendly) ||
|
||||
(option.code === RoomPackageCodeEnum.PETR && allergyFriendly)
|
||||
}
|
||||
selected={getValues(option.code)}
|
||||
Icon={getIconForFeatureCode(option.code)}
|
||||
/>
|
||||
))}
|
||||
<Tooltip
|
||||
text={intl.formatMessage({
|
||||
id: "Pet-friendly rooms have an additional fee of 20 EUR per stay",
|
||||
})}
|
||||
position="bottom"
|
||||
arrow="right"
|
||||
>
|
||||
<InfoCircleIcon className={styles.infoIcon} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
@@ -2,10 +2,18 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.roomsFilter {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x3);
|
||||
gap: var(--Spacing-x1);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.roomsFilter .infoIcon,
|
||||
.roomsFilter .infoIcon path {
|
||||
stroke: var(--UI-Text-Medium-contrast);
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function FlexibilityOption({
|
||||
priceInformation,
|
||||
roomType,
|
||||
roomTypeCode,
|
||||
features,
|
||||
handleSelectRate,
|
||||
}: FlexibilityOptionProps) {
|
||||
const [rootDiv, setRootDiv] = useState<Element | undefined>(undefined)
|
||||
@@ -52,6 +53,7 @@ export default function FlexibilityOption({
|
||||
priceName: name,
|
||||
public: publicPrice,
|
||||
member: memberPrice,
|
||||
features,
|
||||
}
|
||||
handleSelectRate(rate)
|
||||
}
|
||||
|
||||
@@ -7,15 +7,28 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import styles from "./rateSummary.module.css"
|
||||
|
||||
import { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
|
||||
export default function RateSummary({
|
||||
rateSummary,
|
||||
isUserLoggedIn,
|
||||
packages,
|
||||
}: RateSummaryProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
const priceToShow = isUserLoggedIn ? rateSummary.member : rateSummary.public
|
||||
|
||||
const isPetRoomSelect = rateSummary.features.some(
|
||||
(feature) => feature.code === RoomPackageCodeEnum.PETR
|
||||
)
|
||||
|
||||
const petRoomPackage = packages.find(
|
||||
(pkg) => pkg.code === RoomPackageCodeEnum.PETR
|
||||
)
|
||||
|
||||
const petRoomPrice = petRoomPackage ? petRoomPackage.calculatedPrice : null
|
||||
const petRoomCurrency = petRoomPackage ? petRoomPackage.currency : null
|
||||
|
||||
return (
|
||||
<div className={styles.summary}>
|
||||
<div className={styles.summaryText}>
|
||||
@@ -34,6 +47,16 @@ export default function RateSummary({
|
||||
{priceToShow?.requestedPrice?.currency}
|
||||
</Body>
|
||||
</div>
|
||||
{isPetRoomSelect && (
|
||||
<div className={styles.petInfo}>
|
||||
<Body color="uiTextHighContrast" textTransform="bold">
|
||||
+ {petRoomPrice} {petRoomCurrency}
|
||||
</Body>
|
||||
<Body color="uiTextMediumContrast">
|
||||
{intl.formatMessage({ id: "Pet charge" })}
|
||||
</Body>
|
||||
</div>
|
||||
)}
|
||||
<Button type="submit" theme="base">
|
||||
{intl.formatMessage({ id: "Continue" })}
|
||||
</Button>
|
||||
|
||||
@@ -15,3 +15,8 @@
|
||||
display: flex;
|
||||
gap: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
.petInfo {
|
||||
border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
padding-left: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { createElement } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { RateDefinition } from "@/server/routers/hotels/output"
|
||||
@@ -12,6 +13,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
|
||||
import ImageGallery from "../../ImageGallery"
|
||||
import RoomSidePeek from "../RoomSidePeek"
|
||||
import { getIconForFeatureCode } from "../../utils"
|
||||
|
||||
import styles from "./roomCard.module.css"
|
||||
|
||||
@@ -47,16 +49,16 @@ export default function RoomCard({
|
||||
: undefined
|
||||
}
|
||||
|
||||
function getPriceForRate(
|
||||
function getPriceInformationForRate(
|
||||
rate: typeof saveRate | typeof changeRate | typeof flexRate
|
||||
) {
|
||||
return rateDefinitions.find((def) => def.rateCode === rate?.rateCode)
|
||||
?.generalTerms
|
||||
}
|
||||
|
||||
const selectedRoom = roomCategories.find(
|
||||
(room) => room.name === roomConfiguration.roomType
|
||||
)
|
||||
|
||||
const roomSize = selectedRoom?.roomSize
|
||||
const occupancy = selectedRoom?.occupancy.total
|
||||
const roomDescription = selectedRoom?.descriptions.short
|
||||
@@ -68,7 +70,6 @@ export default function RoomCard({
|
||||
<div className={styles.cardBody}>
|
||||
<div className={styles.specification}>
|
||||
<Caption color="uiTextMediumContrast" className={styles.guests}>
|
||||
{/*TODO: Handle pluralisation*/}
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "booking.guests",
|
||||
@@ -105,44 +106,58 @@ export default function RoomCard({
|
||||
value="non-refundable"
|
||||
paymentTerm={intl.formatMessage({ id: "Pay now" })}
|
||||
product={findProductForRate(saveRate)}
|
||||
priceInformation={getPriceForRate(saveRate)}
|
||||
priceInformation={getPriceInformationForRate(saveRate)}
|
||||
handleSelectRate={handleSelectRate}
|
||||
roomType={roomConfiguration.roomType}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
features={roomConfiguration.features}
|
||||
/>
|
||||
<FlexibilityOption
|
||||
name={intl.formatMessage({ id: "Free rebooking" })}
|
||||
value="free-rebooking"
|
||||
paymentTerm={intl.formatMessage({ id: "Pay now" })}
|
||||
product={findProductForRate(changeRate)}
|
||||
priceInformation={getPriceForRate(changeRate)}
|
||||
priceInformation={getPriceInformationForRate(changeRate)}
|
||||
handleSelectRate={handleSelectRate}
|
||||
roomType={roomConfiguration.roomType}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
features={roomConfiguration.features}
|
||||
/>
|
||||
<FlexibilityOption
|
||||
name={intl.formatMessage({ id: "Free cancellation" })}
|
||||
value="free-cancellation"
|
||||
paymentTerm={intl.formatMessage({ id: "Pay later" })}
|
||||
product={findProductForRate(flexRate)}
|
||||
priceInformation={getPriceForRate(flexRate)}
|
||||
priceInformation={getPriceInformationForRate(flexRate)}
|
||||
handleSelectRate={handleSelectRate}
|
||||
roomType={roomConfiguration.roomType}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
features={roomConfiguration.features}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{mainImage && (
|
||||
<div className={styles.imageContainer}>
|
||||
{roomConfiguration.roomsLeft < 5 && (
|
||||
<span className={styles.roomsLeft}>
|
||||
<Footnote
|
||||
color="burgundy"
|
||||
textTransform="uppercase"
|
||||
>{`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`}</Footnote>
|
||||
</span>
|
||||
)}
|
||||
<div className={styles.chipContainer}>
|
||||
{roomConfiguration.roomsLeft < 5 && (
|
||||
<span className={styles.chip}>
|
||||
<Footnote
|
||||
color="burgundy"
|
||||
textTransform="uppercase"
|
||||
>{`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`}</Footnote>
|
||||
</span>
|
||||
)}
|
||||
{roomConfiguration.features.map((feature) => (
|
||||
<span className={styles.chip} key={feature.code}>
|
||||
{createElement(getIconForFeatureCode(feature.code), {
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: "burgundy",
|
||||
})}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{/*NOTE: images from the test API are hosted on test3.scandichotels.com,
|
||||
which can't be accessed unless on Scandic's Wifi or using Citrix. */}
|
||||
{images && (
|
||||
|
||||
@@ -64,10 +64,17 @@
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.roomsLeft {
|
||||
.chipContainer {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.chip {
|
||||
background-color: var(--Main-Grey-White);
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
|
||||
@@ -15,6 +15,7 @@ export default function RoomSelection({
|
||||
roomsAvailability,
|
||||
roomCategories,
|
||||
user,
|
||||
packages,
|
||||
}: RoomSelectionProps) {
|
||||
const [rateSummary, setRateSummary] = useState<Rate | null>(null)
|
||||
|
||||
@@ -69,6 +70,7 @@ export default function RoomSelection({
|
||||
<RateSummary
|
||||
rateSummary={rateSummary}
|
||||
isUserLoggedIn={isUserLoggedIn}
|
||||
packages={packages}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
}
|
||||
|
||||
.roomList {
|
||||
margin-top: var(--Spacing-x4);
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -9,15 +9,15 @@ import RoomSelection from "../RoomSelection"
|
||||
|
||||
import styles from "./rooms.module.css"
|
||||
|
||||
import { RoomProps } from "@/types/components/hotelReservation/selectRate/room"
|
||||
import { RoomPackageCodes } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
|
||||
|
||||
export default function Rooms({
|
||||
roomsAvailability,
|
||||
roomCategories = [],
|
||||
user,
|
||||
packages,
|
||||
}: RoomProps) {
|
||||
}: RoomSelectionProps) {
|
||||
const [rooms, setRooms] = useState<RoomsAvailability>(roomsAvailability)
|
||||
|
||||
function handleFilter(filter: Record<string, boolean | undefined>) {
|
||||
@@ -47,6 +47,7 @@ export default function Rooms({
|
||||
roomsAvailability={rooms}
|
||||
roomCategories={roomCategories}
|
||||
user={user}
|
||||
packages={packages}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x7);
|
||||
gap: var(--Spacing-x2);
|
||||
padding: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
19
components/HotelReservation/SelectRate/utils.ts
Normal file
19
components/HotelReservation/SelectRate/utils.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { AllergyIcon,PetsIcon, WheelchairIcon } from "@/components/Icons"
|
||||
|
||||
import {
|
||||
RoomPackageCodeEnum,
|
||||
RoomPackageCodes,
|
||||
} from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
|
||||
export function getIconForFeatureCode(featureCode: RoomPackageCodes) {
|
||||
switch (featureCode) {
|
||||
case RoomPackageCodeEnum.ACCE:
|
||||
return WheelchairIcon
|
||||
case RoomPackageCodeEnum.ALLG:
|
||||
return AllergyIcon
|
||||
case RoomPackageCodeEnum.PETR:
|
||||
return PetsIcon
|
||||
default:
|
||||
return PetsIcon
|
||||
}
|
||||
}
|
||||
36
components/Icons/Allergy.tsx
Normal file
36
components/Icons/Allergy.tsx
Normal file
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ export default function PetsIcon({ className, color, ...props }: IconProps) {
|
||||
>
|
||||
<path
|
||||
d="M4.6251 12.05C3.95843 12.05 3.39176 11.8166 2.9251 11.35C2.45843 10.8833 2.2251 10.3166 2.2251 9.64995C2.2251 8.98328 2.45843 8.41662 2.9251 7.94995C3.39176 7.48328 3.95843 7.24995 4.6251 7.24995C5.29176 7.24995 5.85843 7.48328 6.3251 7.94995C6.79176 8.41662 7.0251 8.98328 7.0251 9.64995C7.0251 10.3166 6.79176 10.8833 6.3251 11.35C5.85843 11.8166 5.29176 12.05 4.6251 12.05ZM9.0501 8.12495C8.38343 8.12495 7.81676 7.89162 7.3501 7.42495C6.88343 6.95828 6.6501 6.39162 6.6501 5.72495C6.6501 5.05828 6.88343 4.49162 7.3501 4.02495C7.81676 3.55828 8.38343 3.32495 9.0501 3.32495C9.71676 3.32495 10.2834 3.55828 10.7501 4.02495C11.2168 4.49162 11.4501 5.05828 11.4501 5.72495C11.4501 6.39162 11.2168 6.95828 10.7501 7.42495C10.2834 7.89162 9.71676 8.12495 9.0501 8.12495ZM14.9751 8.12495C14.3084 8.12495 13.7418 7.89162 13.2751 7.42495C12.8084 6.95828 12.5751 6.39162 12.5751 5.72495C12.5751 5.05828 12.8084 4.49162 13.2751 4.02495C13.7418 3.55828 14.3084 3.32495 14.9751 3.32495C15.6418 3.32495 16.2084 3.55828 16.6751 4.02495C17.1418 4.49162 17.3751 5.05828 17.3751 5.72495C17.3751 6.39162 17.1418 6.95828 16.6751 7.42495C16.2084 7.89162 15.6418 8.12495 14.9751 8.12495ZM19.4001 12.05C18.7334 12.05 18.1668 11.8166 17.7001 11.35C17.2334 10.8833 17.0001 10.3166 17.0001 9.64995C17.0001 8.98328 17.2334 8.41662 17.7001 7.94995C18.1668 7.48328 18.7334 7.24995 19.4001 7.24995C20.0668 7.24995 20.6334 7.48328 21.1001 7.94995C21.5668 8.41662 21.8001 8.98328 21.8001 9.64995C21.8001 10.3166 21.5668 10.8833 21.1001 11.35C20.6334 11.8166 20.0668 12.05 19.4001 12.05ZM6.7559 21.925C6.0187 21.925 5.40426 21.6474 4.9126 21.0922C4.42093 20.537 4.1751 19.8813 4.1751 19.125C4.1751 18.2666 4.4626 17.5187 5.0376 16.8812C5.6126 16.2437 6.19176 15.615 6.7751 14.995C7.25843 14.4893 7.6751 13.9387 8.0251 13.3432C8.3751 12.7477 8.78343 12.1833 9.2501 11.65C9.60843 11.2416 10.0188 10.8979 10.4813 10.6187C10.9438 10.3395 11.4506 10.2 12.0015 10.2C12.5524 10.2 13.0628 10.3333 13.5327 10.6C14.0026 10.8666 14.4168 11.2083 14.7751 11.625C15.2418 12.15 15.6522 12.7125 16.0063 13.3125C16.3605 13.9125 16.7755 14.4753 17.2513 15.001C17.8255 15.6253 18.4022 16.2541 18.9813 16.8875C19.5605 17.5208 19.8501 18.2666 19.8501 19.125C19.8501 19.8813 19.6043 20.537 19.1126 21.0922C18.6209 21.6474 18.0073 21.925 17.2718 21.925C16.3892 21.925 15.5147 21.85 14.6484 21.7C13.7822 21.55 12.9077 21.475 12.0251 21.475C11.1334 21.475 10.2535 21.55 9.38522 21.7C8.51697 21.85 7.64053 21.925 6.7559 21.925Z"
|
||||
fill="#4D001B"
|
||||
fill="#787472"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
40
components/Icons/Wheelchair.tsx
Normal file
40
components/Icons/Wheelchair.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { iconVariants } from "./variants"
|
||||
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
export default function WheelchairIcon({
|
||||
className,
|
||||
color,
|
||||
...props
|
||||
}: IconProps) {
|
||||
const classNames = iconVariants({ className, color })
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<mask
|
||||
id="mask0_7488_25219"
|
||||
style={{ maskType: "alpha" }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<rect width="16" height="16" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_7488_25219)">
|
||||
<path
|
||||
d="M5.41602 14.5C4.51046 14.5 3.7424 14.1847 3.11185 13.5541C2.48129 12.9236 2.16602 12.1555 2.16602 11.25C2.16602 10.3444 2.48129 9.57636 3.11185 8.94581C3.7424 8.31525 4.51324 7.99998 5.42435 7.99998V9.24998C4.86324 9.24998 4.38824 9.44442 3.99935 9.83331C3.61046 10.2222 3.41602 10.6944 3.41602 11.25C3.41602 11.8055 3.61046 12.2778 3.99935 12.6666C4.38824 13.0555 4.86046 13.25 5.41602 13.25C5.97157 13.25 6.44379 13.0541 6.83268 12.6625C7.22157 12.2708 7.41602 11.7916 7.41602 11.225H8.66602C8.66602 12.1416 8.35074 12.9166 7.72018 13.55C7.08963 14.1833 6.32157 14.5 5.41602 14.5ZM7.38268 10.6C6.89379 10.6 6.52018 10.3944 6.26185 9.98331C6.00352 9.5722 5.97157 9.14442 6.16602 8.69998L7.36602 6.03331H5.84935L5.64935 6.54998C5.59379 6.70553 5.49102 6.82081 5.34102 6.89581C5.19102 6.97081 5.03546 6.98053 4.87435 6.92498C4.70213 6.86942 4.57574 6.76109 4.49518 6.59998C4.41463 6.43886 4.40768 6.2722 4.47435 6.09998L4.68268 5.54998C4.77713 5.30553 4.92852 5.11664 5.13685 4.98331C5.34518 4.84998 5.5799 4.78331 5.84102 4.78331H9.21601C9.69379 4.78331 10.0618 4.97914 10.3202 5.37081C10.5785 5.76248 10.6105 6.17775 10.416 6.61664L9.31602 9.03331H11.2827C11.6438 9.03331 11.9507 9.1597 12.2035 9.41248C12.4563 9.66525 12.5827 9.9722 12.5827 10.3333V13.2083C12.5827 13.3805 12.5216 13.5278 12.3993 13.65C12.2771 13.7722 12.1299 13.8333 11.9577 13.8333C11.7855 13.8333 11.6382 13.7722 11.516 13.65C11.3938 13.5278 11.3327 13.3805 11.3327 13.2083V10.6H7.38268ZM10.5493 4.44998C10.1882 4.44998 9.88129 4.32359 9.62852 4.07081C9.37574 3.81803 9.24935 3.51109 9.24935 3.14998C9.24935 2.78886 9.37574 2.48192 9.62852 2.22914C9.88129 1.97636 10.1882 1.84998 10.5493 1.84998C10.9105 1.84998 11.2174 1.97636 11.4702 2.22914C11.723 2.48192 11.8493 2.78886 11.8493 3.14998C11.8493 3.51109 11.723 3.81803 11.4702 4.07081C11.2174 4.32359 10.9105 4.44998 10.5493 4.44998Z"
|
||||
fill="#787472"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -76,3 +76,8 @@
|
||||
.baseButtonTextOnFillNormal * {
|
||||
fill: var(--Base-Button-Text-On-Fill-Normal);
|
||||
}
|
||||
|
||||
.disabled,
|
||||
.disabled * {
|
||||
fill: var(--Base-Text-Disabled);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export { default as AccessibilityIcon } from "./Accessibility"
|
||||
export { default as AccountCircleIcon } from "./AccountCircle"
|
||||
export { default as AirIcon } from "./Air"
|
||||
export { default as AirplaneIcon } from "./Airplane"
|
||||
export { default as AllergyIcon } from "./Allergy"
|
||||
export { default as ArrowRightIcon } from "./ArrowRight"
|
||||
export { default as BarIcon } from "./Bar"
|
||||
export { default as BathtubIcon } from "./Bathtub"
|
||||
@@ -111,6 +112,7 @@ export { default as TshirtIcon } from "./Tshirt"
|
||||
export { default as TshirtWashIcon } from "./TshirtWash"
|
||||
export { default as TvCastingIcon } from "./TvCasting"
|
||||
export { default as WarningTriangle } from "./WarningTriangle"
|
||||
export { default as WheelchairIcon } from "./Wheelchair"
|
||||
export { default as WifiIcon } from "./Wifi"
|
||||
export { default as WindowCurtainsAltIcon } from "./WindowCurtainsAlt"
|
||||
export { default as WindowNotAvailableIcon } from "./WindowNotAvailable"
|
||||
|
||||
@@ -20,6 +20,7 @@ const config = {
|
||||
white: styles.white,
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||
disabled: styles.disabled,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
7
components/TempDesignSystem/Form/FilterChip/Checkbox.tsx
Normal file
7
components/TempDesignSystem/Form/FilterChip/Checkbox.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import Chip from "./_Chip"
|
||||
|
||||
import type { FilterChipCheckboxProps } from "@/types/components/form/filterChip"
|
||||
|
||||
export default function CheckboxChip(props: FilterChipCheckboxProps) {
|
||||
return <Chip {...props} type="checkbox" />
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x-half);
|
||||
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.label[data-selected="true"],
|
||||
.label[data-selected="true"]:hover {
|
||||
background-color: var(--Primary-Light-Surface-Normal);
|
||||
border-color: var(--Base-Border-Hover);
|
||||
}
|
||||
|
||||
.label:hover {
|
||||
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
||||
border-color: var(--Base-Border-Subtle);
|
||||
}
|
||||
|
||||
.label[data-disabled="true"] {
|
||||
background-color: var(--Base-Button-Primary-Fill-Disabled);
|
||||
border-color: var(--Base-Button-Primary-Fill-Disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
57
components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx
Normal file
57
components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { useMemo } from "react"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
|
||||
import { HeartIcon } from "@/components/Icons"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import styles from "./chip.module.css"
|
||||
|
||||
import { FilterChipProps } from "@/types/components/form/filterChip"
|
||||
|
||||
export default function FilterChip({
|
||||
Icon = HeartIcon,
|
||||
iconHeight = 20,
|
||||
iconWidth = 20,
|
||||
id,
|
||||
name,
|
||||
label,
|
||||
type,
|
||||
value,
|
||||
selected,
|
||||
disabled,
|
||||
}: FilterChipProps) {
|
||||
const { register } = useFormContext()
|
||||
|
||||
const color = useMemo(() => {
|
||||
if (selected) return "burgundy"
|
||||
if (disabled) return "disabled"
|
||||
return "uiTextPlaceholder"
|
||||
}, [selected, disabled])
|
||||
|
||||
return (
|
||||
<label
|
||||
className={styles.label}
|
||||
data-selected={selected}
|
||||
data-disabled={disabled}
|
||||
>
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
color={color}
|
||||
height={iconHeight}
|
||||
width={iconWidth}
|
||||
/>
|
||||
<Caption type="bold" color={color}>
|
||||
{label}
|
||||
</Caption>
|
||||
<input
|
||||
aria-hidden
|
||||
id={id || name}
|
||||
hidden
|
||||
type={type}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
{...register(name)}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
max-width: 200px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.tooltipContainer:hover .tooltip {
|
||||
@@ -31,11 +32,15 @@
|
||||
}
|
||||
|
||||
.top {
|
||||
bottom: 100%;
|
||||
bottom: calc(100% + 8px);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
top: 100%;
|
||||
top: calc(100% + 8px);
|
||||
}
|
||||
|
||||
.bottom.arrowRight {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.tooltip::before {
|
||||
|
||||
@@ -218,6 +218,7 @@
|
||||
"Pay now": "Betal nu",
|
||||
"Payment info": "Betalingsoplysninger",
|
||||
"Pet Room": "Kæledyrsrum",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Kæledyrsrum har en ekstra gebyr på 20 EUR per ophold",
|
||||
"Phone": "Telefon",
|
||||
"Phone is required": "Telefonnummer er påkrævet",
|
||||
"Phone number": "Telefonnummer",
|
||||
|
||||
@@ -218,6 +218,7 @@
|
||||
"Pay now": "Jetzt bezahlen",
|
||||
"Payment info": "Zahlungsinformationen",
|
||||
"Pet Room": "Haustierzimmer",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Haustierzimmer haben einen zusätzlichen Preis von 20 EUR pro Aufenthalt",
|
||||
"Phone": "Telefon",
|
||||
"Phone is required": "Telefon ist erforderlich",
|
||||
"Phone number": "Telefonnummer",
|
||||
|
||||
@@ -228,6 +228,7 @@
|
||||
"Payment info": "Payment info",
|
||||
"Payment received": "Payment received",
|
||||
"Pet Room": "Pet room",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Pet-friendly rooms have an additional fee of 20 EUR per stay",
|
||||
"Phone": "Phone",
|
||||
"Phone is required": "Phone is required",
|
||||
"Phone number": "Phone number",
|
||||
|
||||
@@ -218,6 +218,7 @@
|
||||
"Pay now": "Maksa nyt",
|
||||
"Payment info": "Maksutiedot",
|
||||
"Pet Room": "Lemmikkihuone",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Lemmikkihuoneen lisäkustannus on 20 EUR per majoitus",
|
||||
"Phone": "Puhelin",
|
||||
"Phone is required": "Puhelin vaaditaan",
|
||||
"Phone number": "Puhelinnumero",
|
||||
|
||||
@@ -216,6 +216,7 @@
|
||||
"Pay now": "Betal nå",
|
||||
"Payment info": "Betalingsinformasjon",
|
||||
"Pet Room": "Kjæledyrsrom",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Kjæledyrsrom har en tilleggsavgift på 20 EUR per opphold",
|
||||
"Phone": "Telefon",
|
||||
"Phone is required": "Telefon kreves",
|
||||
"Phone number": "Telefonnummer",
|
||||
|
||||
@@ -216,6 +216,7 @@
|
||||
"Pay now": "Betala nu",
|
||||
"Payment info": "Betalningsinformation",
|
||||
"Pet Room": "Husdjursrum",
|
||||
"Pet-friendly rooms have an additional fee of 20 EUR per stay": "Husdjursrum har en extra avgift på 20 EUR per vistelse",
|
||||
"Phone": "Telefon",
|
||||
"Phone is required": "Telefonnummer är obligatorisk",
|
||||
"Phone number": "Telefonnummer",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { imageMetaDataSchema, imageSizesSchema } from "./schemas/image"
|
||||
import { roomSchema } from "./schemas/room"
|
||||
import { getPoiGroupByCategoryName } from "./utils"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import { FacilityEnum } from "@/types/enums/facilities"
|
||||
import { PointOfInterestCategoryNameEnum } from "@/types/hotel"
|
||||
@@ -545,7 +546,16 @@ const roomConfigurationSchema = z.object({
|
||||
roomTypeCode: z.string().optional(),
|
||||
roomType: z.string(),
|
||||
roomsLeft: z.number(),
|
||||
features: z.array(z.object({ inventory: z.number(), code: z.string() })),
|
||||
features: z.array(
|
||||
z.object({
|
||||
inventory: z.number(),
|
||||
code: z.enum([
|
||||
RoomPackageCodeEnum.PETR,
|
||||
RoomPackageCodeEnum.ALLG,
|
||||
RoomPackageCodeEnum.ACCE,
|
||||
]),
|
||||
})
|
||||
),
|
||||
products: z.array(productSchema),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export enum RoomPackageCode {
|
||||
PETR = "PETR",
|
||||
ALLG = "ALLG",
|
||||
ACCE = "ACCE",
|
||||
}
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
|
||||
export const getRoomPackagesInputSchema = z.object({
|
||||
hotelId: z.string(),
|
||||
@@ -18,9 +14,9 @@ export const getRoomPackagesInputSchema = z.object({
|
||||
const packagesSchema = z.array(
|
||||
z.object({
|
||||
code: z.enum([
|
||||
RoomPackageCode.PETR,
|
||||
RoomPackageCode.ALLG,
|
||||
RoomPackageCode.ACCE,
|
||||
RoomPackageCodeEnum.PETR,
|
||||
RoomPackageCodeEnum.ALLG,
|
||||
RoomPackageCodeEnum.ACCE,
|
||||
]),
|
||||
itemCode: z.string(),
|
||||
description: z.string(),
|
||||
|
||||
16
types/components/form/filterChip.ts
Normal file
16
types/components/form/filterChip.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
type FilterChipType = "checkbox" | "radio"
|
||||
|
||||
export interface FilterChipProps {
|
||||
Icon?: React.ElementType
|
||||
iconHeight?: number
|
||||
iconWidth?: number
|
||||
id?: string
|
||||
label: string
|
||||
name: string
|
||||
type: FilterChipType
|
||||
value?: string
|
||||
selected?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export type FilterChipCheckboxProps = Omit<FilterChipProps, "type">
|
||||
@@ -18,6 +18,7 @@ export type FlexibilityOptionProps = {
|
||||
priceInformation?: Array<string>
|
||||
roomType: RoomConfiguration["roomType"]
|
||||
roomTypeCode: RoomConfiguration["roomTypeCode"]
|
||||
features: RoomConfiguration["features"]
|
||||
handleSelectRate: (rate: Rate) => void
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { RoomPackageData } from "./roomFilter"
|
||||
import { Rate } from "./selectRate"
|
||||
|
||||
export interface RateSummaryProps {
|
||||
rateSummary: Rate
|
||||
isUserLoggedIn: boolean
|
||||
packages: RoomPackageData
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { RoomPackageData } from "./roomFilter"
|
||||
import { RoomSelectionProps } from "./roomSelection"
|
||||
|
||||
export interface RoomProps extends RoomSelectionProps {
|
||||
packages: RoomPackageData
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
RoomConfiguration,
|
||||
} from "@/server/routers/hotels/output"
|
||||
|
||||
import { RoomPackageCodes } from "./roomFilter"
|
||||
import { Rate } from "./selectRate"
|
||||
|
||||
import { RoomData } from "@/types/hotel"
|
||||
|
||||
@@ -2,6 +2,11 @@ import { z } from "zod"
|
||||
|
||||
import { getRoomPackagesSchema } from "@/server/routers/hotels/schemas/packages"
|
||||
|
||||
export enum RoomPackageCodeEnum {
|
||||
PETR = "PETR",
|
||||
ALLG = "ALLG",
|
||||
ACCE = "ACCE",
|
||||
}
|
||||
export interface RoomFilterProps {
|
||||
numberOfRooms: number
|
||||
onFilter: (filter: Record<string, boolean | undefined>) => void
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { RoomsAvailability } from "@/server/routers/hotels/output"
|
||||
|
||||
import { RoomPackageData } from "./roomFilter"
|
||||
|
||||
import { RoomData } from "@/types/hotel"
|
||||
import { SafeUser } from "@/types/user"
|
||||
|
||||
@@ -7,4 +9,5 @@ export interface RoomSelectionProps {
|
||||
roomsAvailability: RoomsAvailability
|
||||
roomCategories: RoomData[]
|
||||
user: SafeUser
|
||||
packages: RoomPackageData
|
||||
}
|
||||
|
||||
@@ -26,4 +26,5 @@ export interface Rate {
|
||||
priceName: string
|
||||
public: Product["productType"]["public"]
|
||||
member: Product["productType"]["member"]
|
||||
features: RoomConfiguration["features"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user