feat: adjust select rate ui to latest design
This commit is contained in:
@@ -11,10 +11,8 @@ import type {
|
|||||||
HotelData,
|
HotelData,
|
||||||
NullableHotelData,
|
NullableHotelData,
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
} from "@/types/components/hotelReservation/selectHotel/hotelCardListingProps"
|
||||||
import type {
|
import type { CategorizedFilters } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
||||||
CategorizedFilters,
|
import type { DetailedFacility } from "@/types/hotel"
|
||||||
Filter,
|
|
||||||
} from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
|
||||||
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
||||||
|
|
||||||
const hotelSurroundingsFilterNames = [
|
const hotelSurroundingsFilterNames = [
|
||||||
@@ -111,9 +109,9 @@ export function getFiltersFromHotels(hotels: HotelData[]): CategorizedFilters {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))]
|
const uniqueFilterIds = [...new Set(filters.map((filter) => filter.id))]
|
||||||
const filterList: Filter[] = uniqueFilterIds
|
const filterList: DetailedFacility[] = uniqueFilterIds
|
||||||
.map((filterId) => filters.find((filter) => filter.id === filterId))
|
.map((filterId) => filters.find((filter) => filter.id === filterId))
|
||||||
.filter((filter): filter is Filter => filter !== undefined)
|
.filter((filter): filter is DetailedFacility => filter !== undefined)
|
||||||
.sort((a, b) => b.sortOrder - a.sortOrder)
|
.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||||
|
|
||||||
return filterList.reduce<CategorizedFilters>(
|
return filterList.reduce<CategorizedFilters>(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function getTypeSpecificInformation(
|
|||||||
const { images } = hotel.hotelContent
|
const { images } = hotel.hotelContent
|
||||||
const { descriptions, meetingDescription } = hotel.hotelContent.texts
|
const { descriptions, meetingDescription } = hotel.hotelContent.texts
|
||||||
const hotelData = {
|
const hotelData = {
|
||||||
description: descriptions.short,
|
description: descriptions?.short,
|
||||||
imageSrc: images.imageSizes.small,
|
imageSrc: images.imageSizes.small,
|
||||||
altText: images.metaData.altText,
|
altText: images.metaData.altText,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export default function HotelListingItem({
|
|||||||
</Caption>
|
</Caption>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Body>{hotel.hotelContent.texts.descriptions.short}</Body>
|
<Body>{hotel.hotelContent.texts.descriptions?.short}</Body>
|
||||||
<ul className={styles.amenityList}>
|
<ul className={styles.amenityList}>
|
||||||
{amenities.map((amenity) => {
|
{amenities.map((amenity) => {
|
||||||
const IconComponent = mapFacilityToIcon(amenity.id)
|
const IconComponent = mapFacilityToIcon(amenity.id)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import type {
|
import type {
|
||||||
Hotel,
|
Hotel,
|
||||||
HotelAddress,
|
HotelAddress,
|
||||||
HotelContent,
|
|
||||||
HotelLocation,
|
HotelLocation,
|
||||||
HotelTripAdvisor,
|
HotelTripAdvisor,
|
||||||
} from "@/types/hotel"
|
} from "@/types/hotel"
|
||||||
|
|
||||||
export type IntroSectionProps = {
|
export type IntroSectionProps = {
|
||||||
address: HotelAddress
|
address: HotelAddress
|
||||||
hotelDescription: HotelContent["texts"]["descriptions"]["short"]
|
hotelDescription: string | undefined
|
||||||
hotelName: Hotel["name"]
|
hotelName: Hotel["name"]
|
||||||
location: HotelLocation
|
location: HotelLocation
|
||||||
tripAdvisor: HotelTripAdvisor
|
tripAdvisor: HotelTripAdvisor
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default async function AboutTheHotelSidePeek({
|
|||||||
ecoLabels={ecoLabels}
|
ecoLabels={ecoLabels}
|
||||||
/>
|
/>
|
||||||
<Divider color="baseSurfaceSubtleHover" />
|
<Divider color="baseSurfaceSubtleHover" />
|
||||||
<Preamble>{descriptions.descriptions.medium}</Preamble>
|
<Preamble>{descriptions.descriptions?.medium}</Preamble>
|
||||||
<Body>{descriptions.facilityInformation}</Body>
|
<Body>{descriptions.facilityInformation}</Body>
|
||||||
<Body>{descriptions.surroundingInformation}</Body>
|
<Body>{descriptions.surroundingInformation}</Body>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
|||||||
} = hotelData.additionalData
|
} = hotelData.additionalData
|
||||||
|
|
||||||
const images = gallery?.smallerImages
|
const images = gallery?.smallerImages
|
||||||
const description = hotelContent.texts.descriptions.medium
|
const description = hotelContent.texts.descriptions?.medium
|
||||||
|
|
||||||
const { spaPage, activitiesCards } = content
|
const { spaPage, activitiesCards } = content
|
||||||
|
|
||||||
@@ -107,9 +107,9 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
|||||||
const hasWellness = healthFacilities.length > 0
|
const hasWellness = healthFacilities.length > 0
|
||||||
|
|
||||||
const facilities = setFacilityCards(
|
const facilities = setFacilityCards(
|
||||||
restaurantImages,
|
restaurantImages ?? undefined,
|
||||||
conferencesAndMeetings,
|
conferencesAndMeetings ?? undefined,
|
||||||
healthAndWellness,
|
healthAndWellness ?? undefined,
|
||||||
hasRestaurants,
|
hasRestaurants,
|
||||||
hasMeetingRooms,
|
hasMeetingRooms,
|
||||||
hasWellness
|
hasWellness
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function Header({
|
|||||||
busyStatus: "FREE",
|
busyStatus: "FREE",
|
||||||
categories: ["booking", "hotel", "stay"],
|
categories: ["booking", "hotel", "stay"],
|
||||||
created: generateDateTime(booking.createDateTime),
|
created: generateDateTime(booking.createDateTime),
|
||||||
description: hotel.hotelContent.texts.descriptions.medium,
|
description: hotel.hotelContent.texts.descriptions?.medium,
|
||||||
end: generateDateTime(booking.checkOutDate),
|
end: generateDateTime(booking.checkOutDate),
|
||||||
endInputType: "utc",
|
endInputType: "utc",
|
||||||
geo: {
|
geo: {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default function ToggleSidePeek({
|
|||||||
hotelId,
|
hotelId,
|
||||||
roomTypeCode,
|
roomTypeCode,
|
||||||
intent = "textInverted",
|
intent = "textInverted",
|
||||||
|
title,
|
||||||
}: ToggleSidePeekProps) {
|
}: ToggleSidePeekProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
|
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
|
||||||
@@ -29,7 +30,7 @@ export default function ToggleSidePeek({
|
|||||||
intent={intent}
|
intent={intent}
|
||||||
wrapping
|
wrapping
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: "See room details" })}
|
{title ? title : intl.formatMessage({ id: "See room details" })}
|
||||||
<ChevronRight height="14" />
|
<ChevronRight height="14" />
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ function HotelCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Body className={styles.hotelDescription}>
|
<Body className={styles.hotelDescription}>
|
||||||
{hotelData.hotelContent.texts.descriptions.short}
|
{hotelData.hotelContent.texts.descriptions?.short}
|
||||||
</Body>
|
</Body>
|
||||||
<div className={styles.facilities}>
|
<div className={styles.facilities}>
|
||||||
{amenities.map((facility) => {
|
{amenities.map((facility) => {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function ActionPanel({
|
|||||||
busyStatus: "FREE",
|
busyStatus: "FREE",
|
||||||
categories: ["booking", "hotel", "stay"],
|
categories: ["booking", "hotel", "stay"],
|
||||||
created: generateDateTime(booking.createDateTime),
|
created: generateDateTime(booking.createDateTime),
|
||||||
description: hotel.hotelContent.texts.descriptions.medium,
|
description: hotel.hotelContent.texts.descriptions?.medium,
|
||||||
end: generateDateTime(booking.checkOutDate),
|
end: generateDateTime(booking.checkOutDate),
|
||||||
endInputType: "utc",
|
endInputType: "utc",
|
||||||
geo: {
|
geo: {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default async function HotelInfoCard({ hotelData }: HotelInfoCardProps) {
|
|||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
<Body color="uiTextHighContrast">
|
<Body color="uiTextHighContrast">
|
||||||
{hotel.hotelContent.texts.descriptions.medium}
|
{hotel.hotelContent.texts.descriptions?.medium}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { getRates } from "@/components/HotelReservation/SelectRate/utils"
|
|||||||
import { EditIcon } from "@/components/Icons"
|
import { EditIcon } from "@/components/Icons"
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import Chip from "@/components/TempDesignSystem/Chip"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
@@ -72,7 +73,7 @@ export default function SelectedRoomPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.selectedRoomPanel}>
|
<div className={styles.selectedRoomPanel}>
|
||||||
<div>
|
<div className={styles.content}>
|
||||||
<Caption color="uiTextHighContrast">
|
<Caption color="uiTextHighContrast">
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "Room {roomIndex}" },
|
{ id: "Room {roomIndex}" },
|
||||||
@@ -90,20 +91,22 @@ export default function SelectedRoomPanel() {
|
|||||||
{intl.formatMessage({ id: "night" })}
|
{intl.formatMessage({ id: "night" })}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.imageAndModifyButtonContainer}>
|
<div className={styles.imageContainer}>
|
||||||
{images?.[0]?.imageSizes?.tiny && (
|
{images?.[0]?.imageSizes?.tiny ? (
|
||||||
<div className={styles.imageContainer}>
|
<Image
|
||||||
<Image
|
alt={selectedRate?.roomType ?? images[0].metaData?.altText ?? ""}
|
||||||
alt={selectedRate?.roomType ?? images[0].metaData?.altText ?? ""}
|
className={styles.img}
|
||||||
fill
|
height={300}
|
||||||
src={images[0].imageSizes.tiny}
|
src={images[0].imageSizes.tiny}
|
||||||
/>
|
width={600}
|
||||||
</div>
|
/>
|
||||||
)}
|
) : null}
|
||||||
<div className={styles.modifyButtonContainer}>
|
<div className={styles.modifyButtonContainer}>
|
||||||
<Button variant="icon" size="small" onClick={modifyRate}>
|
<Button clean onClick={modifyRate}>
|
||||||
<EditIcon />
|
<Chip size="small" variant="uiTextHighContrast">
|
||||||
{intl.formatMessage({ id: "Modify" })}
|
<EditIcon />
|
||||||
|
{intl.formatMessage({ id: "Modify" })}
|
||||||
|
</Chip>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,50 +1,53 @@
|
|||||||
.selectedRoomPanel {
|
.selectedRoomPanel {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
grid-template-areas: "content image";
|
||||||
justify-content: space-between;
|
grid-template-columns: 1fr 190px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modifyButtonContainer {
|
.content {
|
||||||
position: absolute;
|
grid-area: content;
|
||||||
right: var(--Spacing-x2);
|
|
||||||
bottom: var(--Spacing-x2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.imageContainer {
|
.imageContainer {
|
||||||
width: 187px;
|
|
||||||
height: 105px;
|
|
||||||
position: relative;
|
|
||||||
border-radius: var(--Corner-radius-Small);
|
border-radius: var(--Corner-radius-Small);
|
||||||
overflow: hidden;
|
display: flex;
|
||||||
|
grid-area: image;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titleContainer {
|
.img {
|
||||||
display: flex;
|
border-radius: var(--Corner-radius-Small);
|
||||||
flex-direction: column;
|
height: auto;
|
||||||
gap: var(--Spacing-x1);
|
max-height: 105px;
|
||||||
|
object-fit: fill;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modifyButtonContainer {
|
||||||
|
bottom: var(--Spacing-x1);
|
||||||
|
position: absolute;
|
||||||
|
right: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.selectedRoomPanel p.subtitle {
|
div.selectedRoomPanel p.subtitle {
|
||||||
padding-bottom: var(--Spacing-x1);
|
padding-bottom: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.imageContainer {
|
.selectedRoomPanel {
|
||||||
width: 120px;
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imageAndModifyButtonContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
|
grid-template-areas: "image" "content";
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modifyButtonContainer {
|
.img {
|
||||||
position: relative;
|
max-height: 300px;
|
||||||
bottom: 0;
|
}
|
||||||
right: 0;
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
.img {
|
||||||
|
max-height: 190px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { useRatesStore } from "@/stores/select-rate"
|
import { useRatesStore } from "@/stores/select-rate"
|
||||||
|
|
||||||
|
import { ChevronUpIcon } from "@/components/Icons"
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
import { useRoomContext } from "@/contexts/Room"
|
import { useRoomContext } from "@/contexts/Room"
|
||||||
|
|
||||||
@@ -17,7 +19,13 @@ export default function MultiRoomWrapper({
|
|||||||
}: React.PropsWithChildren<{ isMultiRoom: boolean }>) {
|
}: React.PropsWithChildren<{ isMultiRoom: boolean }>) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const activeRoom = useRatesStore((state) => state.activeRoom)
|
const activeRoom = useRatesStore((state) => state.activeRoom)
|
||||||
const { bookingRoom, isActiveRoom, roomNr, selectedRate } = useRoomContext()
|
const {
|
||||||
|
actions: { closeSection },
|
||||||
|
bookingRoom,
|
||||||
|
isActiveRoom,
|
||||||
|
roomNr,
|
||||||
|
selectedRate,
|
||||||
|
} = useRoomContext()
|
||||||
|
|
||||||
const onlyAdultsMsg = intl.formatMessage(
|
const onlyAdultsMsg = intl.formatMessage(
|
||||||
{ id: "{adults} adults" },
|
{ id: "{adults} adults" },
|
||||||
@@ -56,19 +64,33 @@ export default function MultiRoomWrapper({
|
|||||||
selected: !!selectedRate && !isActiveRoom,
|
selected: !!selectedRate && !isActiveRoom,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<div className={styles.roomContainer}>
|
<div className={styles.roomContainer} data-multiroom="true">
|
||||||
{selectedRate && !isActiveRoom ? null : (
|
<div className={styles.header}>
|
||||||
<Subtitle className={styles.subtitle} color="uiTextHighContrast">
|
{selectedRate && !isActiveRoom ? null : (
|
||||||
{intl.formatMessage(
|
<Subtitle className={styles.subtitle} color="uiTextHighContrast">
|
||||||
{ id: "Room {roomIndex}" },
|
{intl.formatMessage(
|
||||||
{ roomIndex: roomNr }
|
{ id: "Room {roomIndex}" },
|
||||||
)}
|
{ roomIndex: roomNr }
|
||||||
,{" "}
|
)}
|
||||||
{bookingRoom.childrenInRoom?.length
|
,{" "}
|
||||||
? adultsAndChildrenMsg
|
{bookingRoom.childrenInRoom?.length
|
||||||
: onlyAdultsMsg}
|
? adultsAndChildrenMsg
|
||||||
</Subtitle>
|
: onlyAdultsMsg}
|
||||||
)}
|
</Subtitle>
|
||||||
|
)}
|
||||||
|
{selectedRate && isActiveRoom ? (
|
||||||
|
<Button
|
||||||
|
intent="text"
|
||||||
|
onClick={closeSection}
|
||||||
|
size="medium"
|
||||||
|
theme="base"
|
||||||
|
variant="icon"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "Close" })}
|
||||||
|
<ChevronUpIcon height={20} width={20} />
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
<div className={classNames}>
|
<div className={classNames}>
|
||||||
<div className={styles.roomPanel}>
|
<div className={styles.roomPanel}>
|
||||||
<SelectedRoomPanel />
|
<SelectedRoomPanel />
|
||||||
|
|||||||
@@ -4,7 +4,13 @@
|
|||||||
border-radius: var(--Corner-radius-Large);
|
border-radius: var(--Corner-radius-Large);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: var(--Spacing-x2);
|
padding: var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomPanel,
|
.roomPanel,
|
||||||
@@ -27,27 +33,21 @@
|
|||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.roomSelectionPanelContainer.active .roomSelectionPanel,
|
||||||
.roomSelectionPanelContainer.selected .roomPanel {
|
.roomSelectionPanelContainer.selected .roomPanel {
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
opacity: 1;
|
|
||||||
height: auto;
|
height: auto;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roomSelectionPanelContainer.active .roomPanel {
|
||||||
|
padding-top: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomSelectionPanelContainer.selected .roomSelectionPanel {
|
.roomSelectionPanelContainer.selected .roomSelectionPanel {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomSelectionPanelContainer.active .roomSelectionPanel {
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
opacity: 1;
|
|
||||||
height: auto;
|
|
||||||
padding-top: var(--Spacing-x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.roomContainer p.subtitle {
|
|
||||||
padding-bottom: var(--Spacing-x1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.roomContainer {
|
.roomContainer {
|
||||||
padding: var(--Spacing-x2);
|
padding: var(--Spacing-x2);
|
||||||
|
|||||||
@@ -138,20 +138,20 @@ export default function PriceList({
|
|||||||
<Caption color="uiTextMediumContrast">
|
<Caption color="uiTextMediumContrast">
|
||||||
{isUserLoggedIn
|
{isUserLoggedIn
|
||||||
? intl.formatMessage(
|
? intl.formatMessage(
|
||||||
{ id: "{memberPrice} {currency}" },
|
{ id: "{memberPrice} {currency}" },
|
||||||
{
|
{
|
||||||
memberPrice: totalMemberRequestedPricePerNight,
|
memberPrice: totalMemberRequestedPricePerNight,
|
||||||
currency: publicRequestedPrice.currency,
|
currency: publicRequestedPrice.currency,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: intl.formatMessage(
|
: intl.formatMessage(
|
||||||
{ id: "{publicPrice}/{memberPrice} {currency}" },
|
{ id: "{publicPrice}/{memberPrice} {currency}" },
|
||||||
{
|
{
|
||||||
publicPrice: totalPublicRequestedPricePerNight,
|
publicPrice: totalPublicRequestedPricePerNight,
|
||||||
memberPrice: totalMemberRequestedPricePerNight,
|
memberPrice: totalMemberRequestedPricePerNight,
|
||||||
currency: publicRequestedPrice.currency,
|
currency: publicRequestedPrice.currency,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Caption>
|
</Caption>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkIcon {
|
.checkIcon {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -33,10 +34,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="radio"].radio {
|
||||||
|
opacity: 0;
|
||||||
|
position: fixed;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="radio"]:checked + .card {
|
input[type="radio"]:checked + .card {
|
||||||
border: 1px solid var(--Primary-Dark-On-Surface-Divider);
|
border: 1px solid var(--Primary-Dark-On-Surface-Divider);
|
||||||
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="radio"]:checked + .card .checkIcon {
|
input[type="radio"]:checked + .card .checkIcon {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -81,11 +90,13 @@ input[type="radio"]:checked + .card .checkIcon {
|
|||||||
.terms {
|
.terms {
|
||||||
padding-top: var(--Spacing-x3);
|
padding-top: var(--Spacing-x3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.termsText:nth-child(n) {
|
.termsText:nth-child(n) {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-bottom: var(--Spacing-x1);
|
padding-bottom: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.termsIcon {
|
.termsIcon {
|
||||||
padding-right: var(--Spacing-x1);
|
padding-right: var(--Spacing-x1);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export default function FlexibilityOption({
|
|||||||
const {
|
const {
|
||||||
actions: { selectRate },
|
actions: { selectRate },
|
||||||
isMainRoom,
|
isMainRoom,
|
||||||
|
roomNr,
|
||||||
} = useRoomContext()
|
} = useRoomContext()
|
||||||
|
|
||||||
function handleSelect() {
|
function handleSelect() {
|
||||||
@@ -74,7 +75,8 @@ export default function FlexibilityOption({
|
|||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
name={`rateCode-${rate.rateCode}`}
|
className={styles.radio}
|
||||||
|
name={`rateCode-${roomNr}-${rate.rateCode}`}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
type="radio"
|
type="radio"
|
||||||
value={rate.rateCode}
|
value={rate.rateCode}
|
||||||
|
|||||||
@@ -14,23 +14,29 @@ export default function RoomSize({ roomSize }: RoomSizeProps) {
|
|||||||
|
|
||||||
if (roomSize.min === roomSize.max) {
|
if (roomSize.min === roomSize.max) {
|
||||||
return (
|
return (
|
||||||
<Caption color="uiTextMediumContrast">
|
<>
|
||||||
{intl.formatMessage(
|
<Caption color="uiTextMediumContrast">∙</Caption>
|
||||||
{ id: "{roomSize} m²" },
|
<Caption color="uiTextMediumContrast">
|
||||||
{ roomSize: roomSize.min }
|
{intl.formatMessage(
|
||||||
)}
|
{ id: "{roomSize} m²" },
|
||||||
</Caption>
|
{ roomSize: roomSize.min }
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Caption color="uiTextMediumContrast">
|
<>
|
||||||
{intl.formatMessage(
|
<Caption color="uiTextMediumContrast">∙</Caption>
|
||||||
{ id: "{roomSizeMin} - {roomSizeMax} m²" },
|
<Caption color="uiTextMediumContrast">
|
||||||
{
|
{intl.formatMessage(
|
||||||
roomSizeMin: roomSize.min,
|
{ id: "{roomSizeMin} - {roomSizeMax} m²" },
|
||||||
roomSizeMax: roomSize.max,
|
{
|
||||||
}
|
roomSizeMin: roomSize.min,
|
||||||
)}
|
roomSizeMax: roomSize.max,
|
||||||
</Caption>
|
}
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,13 +35,17 @@ function getBreakfastMessage(
|
|||||||
memberBreakfastIncluded: boolean,
|
memberBreakfastIncluded: boolean,
|
||||||
hotelType: string | undefined,
|
hotelType: string | undefined,
|
||||||
userIsLoggedIn: boolean,
|
userIsLoggedIn: boolean,
|
||||||
msgs: Record<"included" | "noSelection" | "scandicgo" | "notIncluded", string>
|
msgs: Record<
|
||||||
|
"included" | "noSelection" | "scandicgo" | "notIncluded",
|
||||||
|
string
|
||||||
|
>,
|
||||||
|
roomNr: number
|
||||||
) {
|
) {
|
||||||
if (hotelType === HotelTypeEnum.ScandicGo) {
|
if (hotelType === HotelTypeEnum.ScandicGo) {
|
||||||
return msgs.scandicgo
|
return msgs.scandicgo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userIsLoggedIn && memberBreakfastIncluded) {
|
if (userIsLoggedIn && memberBreakfastIncluded && roomNr === 1) {
|
||||||
return msgs.included
|
return msgs.included
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +85,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
|||||||
rateDefinitions: state.roomsAvailability?.rateDefinitions,
|
rateDefinitions: state.roomsAvailability?.rateDefinitions,
|
||||||
roomCategories: state.roomCategories,
|
roomCategories: state.roomCategories,
|
||||||
}))
|
}))
|
||||||
const { isMainRoom, selectedPackage, selectedRate } = useRoomContext()
|
const { isMainRoom, roomNr, selectedPackage, selectedRate } = useRoomContext()
|
||||||
|
|
||||||
const classNames = cardVariants({
|
const classNames = cardVariants({
|
||||||
availability:
|
availability:
|
||||||
@@ -105,7 +109,8 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
|||||||
roomConfiguration.breakfastIncludedInAllRatesMember,
|
roomConfiguration.breakfastIncludedInAllRatesMember,
|
||||||
hotelType,
|
hotelType,
|
||||||
isUserLoggedIn,
|
isUserLoggedIn,
|
||||||
breakfastMessages
|
breakfastMessages,
|
||||||
|
roomNr
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!rateDefinitions) {
|
if (!rateDefinitions) {
|
||||||
@@ -124,7 +129,7 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const { name, roomSize, totalOccupancy, images } = selectedRoom || {}
|
const { images, name, occupancy, roomSize } = selectedRoom || {}
|
||||||
const galleryImages = mapApiImagesToGalleryImages(images || [])
|
const galleryImages = mapApiImagesToGalleryImages(images || [])
|
||||||
|
|
||||||
const freeCancelation = intl.formatMessage({ id: "Free cancellation" })
|
const freeCancelation = intl.formatMessage({ id: "Free cancellation" })
|
||||||
@@ -203,86 +208,87 @@ export default function RoomCard({ roomConfiguration }: RoomCardProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={classNames}>
|
<li className={classNames}>
|
||||||
<div>
|
<div className={styles.imageContainer}>
|
||||||
<div className={styles.imageContainer}>
|
<div className={styles.chipContainer}>
|
||||||
<div className={styles.chipContainer}>
|
{lessThanFiveRoomsLeft ? (
|
||||||
{lessThanFiveRoomsLeft ? (
|
<span className={styles.chip}>
|
||||||
<span className={styles.chip}>
|
<Footnote color="burgundy" textTransform="uppercase">
|
||||||
<Footnote color="burgundy" textTransform="uppercase">
|
{intl.formatMessage(
|
||||||
{intl.formatMessage(
|
{ id: "{amount, number} left" },
|
||||||
{ id: "{amount, number} left" },
|
{ amount: roomConfiguration.roomsLeft }
|
||||||
{ amount: roomConfiguration.roomsLeft }
|
)}
|
||||||
)}
|
</Footnote>
|
||||||
</Footnote>
|
</span>
|
||||||
|
) : null}
|
||||||
|
{roomConfiguration.features
|
||||||
|
.filter((feature) => selectedPackage === feature.code)
|
||||||
|
.map((feature) => (
|
||||||
|
<span className={styles.chip} key={feature.code}>
|
||||||
|
{createElement(getIconForFeatureCode(feature.code), {
|
||||||
|
color: "burgundy",
|
||||||
|
height: 16,
|
||||||
|
width: 16,
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
))}
|
||||||
{roomConfiguration.features
|
|
||||||
.filter((feature) => selectedPackage === feature.code)
|
|
||||||
.map((feature) => (
|
|
||||||
<span className={styles.chip} key={feature.code}>
|
|
||||||
{createElement(getIconForFeatureCode(feature.code), {
|
|
||||||
color: "burgundy",
|
|
||||||
height: 16,
|
|
||||||
width: 16,
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<ImageGallery
|
|
||||||
images={galleryImages}
|
|
||||||
title={roomConfiguration.roomType}
|
|
||||||
fill
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ImageGallery
|
||||||
|
images={galleryImages}
|
||||||
|
title={roomConfiguration.roomType}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.specification}>
|
<div className={styles.specification}>
|
||||||
{totalOccupancy && (
|
{occupancy && (
|
||||||
<Caption color="uiTextMediumContrast" className={styles.guests}>
|
<Caption color="uiTextMediumContrast">
|
||||||
{intl.formatMessage(
|
{occupancy.max === occupancy.min
|
||||||
{
|
? intl.formatMessage(
|
||||||
id: "Max {max, plural, one {{range} guest} other {{range} guests}}",
|
{ id: "guests.plural" },
|
||||||
},
|
{ guests: occupancy.max }
|
||||||
{ max: totalOccupancy.max, range: totalOccupancy.range }
|
)
|
||||||
)}
|
: intl.formatMessage({ id: "guests.span" }, occupancy)}
|
||||||
</Caption>
|
</Caption>
|
||||||
|
)}
|
||||||
|
<RoomSize roomSize={roomSize} />
|
||||||
|
<div className={styles.toggleSidePeek}>
|
||||||
|
{roomConfiguration.roomTypeCode && (
|
||||||
|
<ToggleSidePeek
|
||||||
|
hotelId={hotelId.toString()}
|
||||||
|
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||||
|
title={intl.formatMessage({ id: "Room details" })}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<RoomSize roomSize={roomSize} />
|
|
||||||
<div className={styles.toggleSidePeek}>
|
|
||||||
{roomConfiguration.roomTypeCode && (
|
|
||||||
<ToggleSidePeek
|
|
||||||
hotelId={hotelId.toString()}
|
|
||||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.roomDetails}>
|
</div>
|
||||||
<Subtitle className={styles.name} type="two">
|
<div className={styles.roomDetails}>
|
||||||
{name}
|
<Subtitle className={styles.name} type="two">
|
||||||
</Subtitle>
|
{name}
|
||||||
{/* Out of scope for now
|
</Subtitle>
|
||||||
|
{/* Out of scope for now
|
||||||
<Body>{descriptions?.short}</Body>
|
<Body>{descriptions?.short}</Body>
|
||||||
*/}
|
*/}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{roomConfiguration.status === AvailabilityEnum.NotAvailable ? (
|
{roomConfiguration.status === AvailabilityEnum.NotAvailable ? (
|
||||||
<div className={styles.noRoomsContainer}>
|
<>
|
||||||
<div className={styles.noRooms}>
|
{/** The empty div is used to allow for subgrid to align rows */}
|
||||||
<ErrorCircleIcon color="red" width={16} />
|
<div></div>
|
||||||
<Caption color="uiTextHighContrast" type="bold">
|
<div className={styles.noRoomsContainer}>
|
||||||
{intl.formatMessage({
|
<div className={styles.noRooms}>
|
||||||
id: "This room is not available",
|
<ErrorCircleIcon color="red" width={16} />
|
||||||
})}
|
<Caption color="uiTextHighContrast" type="bold">
|
||||||
</Caption>
|
{intl.formatMessage({
|
||||||
|
id: "This room is not available",
|
||||||
|
})}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Caption color="uiTextHighContrast" type="bold">
|
<Caption color="uiTextHighContrast">{breakfastMessage}</Caption>
|
||||||
{breakfastMessage}
|
|
||||||
</Caption>
|
|
||||||
{roomConfiguration.products.map((product) => {
|
{roomConfiguration.products.map((product) => {
|
||||||
const rate = getRateInfo(product)
|
const rate = getRateInfo(product)
|
||||||
const isSelectedRateCode =
|
const isSelectedRateCode =
|
||||||
|
|||||||
@@ -1,112 +1,100 @@
|
|||||||
.card {
|
.card {
|
||||||
font-size: 14px;
|
background-color: #fff;
|
||||||
|
border: 1px solid var(--Base-Border-Subtle);
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
display: grid;
|
display: grid;
|
||||||
|
font-size: 14px;
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
grid-row: span 7;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: subgrid;
|
grid-template-rows: subgrid;
|
||||||
background-color: #fff;
|
|
||||||
border-radius: var(--Corner-radius-Large);
|
|
||||||
border: 1px solid var(--Base-Border-Subtle);
|
|
||||||
position: relative;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
grid-row: span 5;
|
padding: 0 var(--Spacing-x2) var(--Spacing-x2);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[data-multiroom="true"] .card {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.noAvailability {
|
.card.noAvailability {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.specification {
|
.imageContainer {
|
||||||
display: flex;
|
margin: 0 calc(-1 * var(--Spacing-x2));
|
||||||
flex-direction: row;
|
min-height: 190px;
|
||||||
align-items: center;
|
position: relative;
|
||||||
justify-content: space-between;
|
|
||||||
gap: var(--Spacing-x1);
|
|
||||||
padding: 0 var(--Spacing-x1) 0 var(--Spacing-x-one-and-half);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.specification .guests {
|
div[data-multiroom="true"] .imageContainer {
|
||||||
border-right: 1px solid var(--Base-Border-Subtle);
|
margin: 0;
|
||||||
padding-right: var(--Spacing-x1);
|
}
|
||||||
|
|
||||||
|
.chipContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
left: 12px;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
background-color: var(--Main-Grey-White);
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .imageContainer img {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
border-radius: var(--Corner-radius-Medium) var(--Corner-radius-Medium) 0 0;
|
||||||
|
max-width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specification {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggleSidePeek {
|
.toggleSidePeek {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggleSidePeek button {
|
.specification .toggleSidePeek button {
|
||||||
|
padding: 0;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: var(--Spacing-x1) var(--Spacing-x2) var(--Spacing-x2);
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: subgrid;
|
|
||||||
gap: var(--Spacing-x-one-and-half);
|
|
||||||
grid-row: span 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make sure rows with only unavailable rooms still has a min-height */
|
|
||||||
.container:has(.noRoomsContainer) {
|
|
||||||
min-height: 400px;
|
|
||||||
grid-template-rows: auto repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomDetails {
|
.roomDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
padding: var(--Spacing-x1) var(--Spacing-x2) 0;
|
padding-bottom: var(--Spacing-x-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card img {
|
.container {
|
||||||
max-width: 100%;
|
|
||||||
aspect-ratio: 16/9;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: var(--Corner-radius-Medium) var(--Corner-radius-Medium) 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexibilityOptions {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x2);
|
||||||
grid-template-rows: repeat(3, 1fr);
|
grid-row: span 4;
|
||||||
}
|
grid-template-rows: subgrid;
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
.imageContainer {
|
|
||||||
min-height: 190px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noRoomsContainer {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.noRooms {
|
.noRooms {
|
||||||
padding: var(--Spacing-x2);
|
|
||||||
background-color: var(--Base-Surface-Secondary-light-Normal);
|
background-color: var(--Base-Surface-Secondary-light-Normal);
|
||||||
border-radius: var(--Corner-radius-Medium);
|
border-radius: var(--Corner-radius-Medium);
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
}
|
margin: 0;
|
||||||
|
padding: var(--Spacing-x2);
|
||||||
|
}
|
||||||
@@ -41,16 +41,14 @@ export default function RoomSelectionPanel() {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<RoomTypeFilter />
|
<RoomTypeFilter />
|
||||||
<div className={styles.wrapper}>
|
<ul className={styles.roomList}>
|
||||||
<ul className={styles.roomList}>
|
{rooms.map((roomConfiguration) => (
|
||||||
{rooms.map((roomConfiguration) => (
|
<RoomCard
|
||||||
<RoomCard
|
key={roomConfiguration.roomTypeCode}
|
||||||
key={roomConfiguration.roomTypeCode}
|
roomConfiguration={roomConfiguration}
|
||||||
roomConfiguration={roomConfiguration}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
</ul>
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomList input[type="radio"] {
|
|
||||||
opacity: 0;
|
|
||||||
position: fixed;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hotelAlert {
|
.hotelAlert {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -47,22 +47,26 @@ export default async function ParkingInformation({
|
|||||||
{intl.formatMessage({ id: "Weekday prices" })}
|
{intl.formatMessage({ id: "Weekday prices" })}
|
||||||
</Caption>
|
</Caption>
|
||||||
<Divider color="baseSurfaceSubtleHover" />
|
<Divider color="baseSurfaceSubtleHover" />
|
||||||
<ParkingPrices
|
{parking.pricing.localCurrency ? (
|
||||||
pricing={parking.pricing.localCurrency?.ordinary}
|
<ParkingPrices
|
||||||
currency={parking.pricing.localCurrency?.currency}
|
currency={parking.pricing.localCurrency.currency}
|
||||||
freeParking={parking.pricing.freeParking}
|
freeParking={parking.pricing.freeParking}
|
||||||
/>
|
pricing={parking.pricing.localCurrency.ordinary}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.priceWrapper}>
|
<div className={styles.priceWrapper}>
|
||||||
<Caption color="uiTextMediumContrast" textTransform="uppercase">
|
<Caption color="uiTextMediumContrast" textTransform="uppercase">
|
||||||
{intl.formatMessage({ id: "Weekend prices" })}
|
{intl.formatMessage({ id: "Weekend prices" })}
|
||||||
</Caption>
|
</Caption>
|
||||||
<Divider color="baseSurfaceSubtleHover" />
|
<Divider color="baseSurfaceSubtleHover" />
|
||||||
<ParkingPrices
|
{parking.pricing.localCurrency ? (
|
||||||
pricing={parking.pricing.localCurrency?.weekend}
|
<ParkingPrices
|
||||||
currency={parking.pricing.localCurrency?.currency}
|
currency={parking.pricing.localCurrency.currency}
|
||||||
freeParking={parking.pricing.freeParking}
|
freeParking={parking.pricing.freeParking}
|
||||||
/>
|
pricing={parking.pricing.localCurrency.weekend}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{parking.externalParkingUrl && showExternalParkingButton && (
|
{parking.externalParkingUrl && showExternalParkingButton && (
|
||||||
|
|||||||
@@ -55,10 +55,12 @@ a.text {
|
|||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: The variants for combinations of size/text/wrapping should be looked at and iterated on */
|
/* TODO: The variants for combinations of size/text/wrapping should be looked at and iterated on */
|
||||||
.text:not(.wrapping) {
|
.text:not(.wrapping) {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* VARIANTS */
|
/* VARIANTS */
|
||||||
.default,
|
.default,
|
||||||
a.default {
|
a.default {
|
||||||
@@ -827,3 +829,15 @@ a.default {
|
|||||||
.icon.tertiaryLightSecondary:disabled svg * {
|
.icon.tertiaryLightSecondary:disabled svg * {
|
||||||
fill: var(--Tertiary-Light-Button-Secondary-On-Fill-Disabled);
|
fill: var(--Tertiary-Light-Button-Secondary-On-Fill-Disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.btn.clean {
|
||||||
|
background: none;
|
||||||
|
background-color: unset;
|
||||||
|
border: none;
|
||||||
|
border-color: unset;
|
||||||
|
border-radius: unset;
|
||||||
|
color: unset;
|
||||||
|
gap: unset;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { ButtonProps } from "./button"
|
|||||||
export default function Button(props: ButtonProps) {
|
export default function Button(props: ButtonProps) {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
|
clean,
|
||||||
intent,
|
intent,
|
||||||
size,
|
size,
|
||||||
theme,
|
theme,
|
||||||
@@ -21,6 +22,7 @@ export default function Button(props: ButtonProps) {
|
|||||||
|
|
||||||
const classNames = buttonVariants({
|
const classNames = buttonVariants({
|
||||||
className,
|
className,
|
||||||
|
clean,
|
||||||
intent,
|
intent,
|
||||||
size,
|
size,
|
||||||
theme,
|
theme,
|
||||||
|
|||||||
@@ -28,12 +28,16 @@ export const buttonVariants = cva(styles.btn, {
|
|||||||
tertiaryDark: "",
|
tertiaryDark: "",
|
||||||
},
|
},
|
||||||
variant: {
|
variant: {
|
||||||
|
clean: styles.clean,
|
||||||
default: styles.default,
|
default: styles.default,
|
||||||
icon: styles.icon,
|
icon: styles.icon,
|
||||||
},
|
},
|
||||||
wrapping: {
|
wrapping: {
|
||||||
true: styles.wrapping,
|
true: styles.wrapping,
|
||||||
},
|
},
|
||||||
|
clean: {
|
||||||
|
true: styles.clean,
|
||||||
|
},
|
||||||
fullWidth: {
|
fullWidth: {
|
||||||
true: styles.fullWidth,
|
true: styles.fullWidth,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
div.chip {
|
div.chip {
|
||||||
--chip-text-color: var(--Base-Text-High-contrast);
|
--chip-text-color: var(--Base-Text-High-contrast);
|
||||||
--chip-background-color: var(--Base-Surface-Primary-light-Normal);
|
--chip-background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
border-radius: var(--Corner-radius-Small);
|
|
||||||
color: var(--chip-text-color);
|
color: var(--chip-text-color);
|
||||||
background-color: var(--chip-background-color);
|
background-color: var(--chip-background-color);
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip.small {
|
||||||
|
padding: var(--Spacing-x-quarter) var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip.medium {
|
||||||
|
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chip *,
|
.chip *,
|
||||||
@@ -29,3 +36,8 @@ div.chip {
|
|||||||
.chip.tag {
|
.chip.tag {
|
||||||
--chip-background-color: var(--Base-Surface-Subtle-Hover);
|
--chip-background-color: var(--Base-Surface-Subtle-Hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chip.uiTextHighContrast {
|
||||||
|
--chip-background-color: var(--UI-Text-High-contrast);
|
||||||
|
--chip-text-color: var(--UI-Input-Controls-On-Fill-Normal);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,9 +4,15 @@ import { chipVariants } from "./variants"
|
|||||||
|
|
||||||
import type { ChipProps } from "./chip"
|
import type { ChipProps } from "./chip"
|
||||||
|
|
||||||
export default function Chip({ children, className, variant }: ChipProps) {
|
export default function Chip({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
size,
|
||||||
|
variant,
|
||||||
|
}: ChipProps) {
|
||||||
const classNames = chipVariants({
|
const classNames = chipVariants({
|
||||||
className,
|
className,
|
||||||
|
size,
|
||||||
variant,
|
variant,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,14 +4,20 @@ import styles from "./chip.module.css"
|
|||||||
|
|
||||||
export const chipVariants = cva(styles.chip, {
|
export const chipVariants = cva(styles.chip, {
|
||||||
variants: {
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: styles.small,
|
||||||
|
medium: styles.medium,
|
||||||
|
},
|
||||||
variant: {
|
variant: {
|
||||||
default: styles.default,
|
default: styles.default,
|
||||||
burgundy: styles.burgundy,
|
burgundy: styles.burgundy,
|
||||||
transparent: styles.transparent,
|
transparent: styles.transparent,
|
||||||
tag: styles.tag,
|
tag: styles.tag,
|
||||||
|
uiTextHighContrast: styles.uiTextHighContrast,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
size: "medium",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -716,6 +716,8 @@
|
|||||||
"booking.thisRoomIsEquippedWith": "Dette værelse er udstyret med",
|
"booking.thisRoomIsEquippedWith": "Dette værelse er udstyret med",
|
||||||
"friday": "fredag",
|
"friday": "fredag",
|
||||||
"from": "fra",
|
"from": "fra",
|
||||||
|
"guests.plural": "{guests, plural, one {# gæst} other {# gæster}}",
|
||||||
|
"guests.span": "{min}-{max} gæster",
|
||||||
"max {seatings} pers": "max {seatings} pers",
|
"max {seatings} pers": "max {seatings} pers",
|
||||||
"monday": "mandag",
|
"monday": "mandag",
|
||||||
"next level: {nextLevel}": "next level: {nextLevel}",
|
"next level: {nextLevel}": "next level: {nextLevel}",
|
||||||
@@ -782,4 +784,4 @@
|
|||||||
"{value} persons": "{number} Personen",
|
"{value} persons": "{number} Personen",
|
||||||
"{value} square meters": "{number} Quadratmeter",
|
"{value} square meters": "{number} Quadratmeter",
|
||||||
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Alle rettigheder forbeholdes"
|
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Alle rettigheder forbeholdes"
|
||||||
}
|
}
|
||||||
@@ -717,6 +717,8 @@
|
|||||||
"booking.thisRoomIsEquippedWith": "Dieses Zimmer ist ausgestattet mit",
|
"booking.thisRoomIsEquippedWith": "Dieses Zimmer ist ausgestattet mit",
|
||||||
"friday": "freitag",
|
"friday": "freitag",
|
||||||
"from": "von",
|
"from": "von",
|
||||||
|
"guests.plural": "{guests, plural, one {# gast} other {# gäste}}",
|
||||||
|
"guests.span": "{min}-{max} gäste",
|
||||||
"max {seatings} pers": "max {seatings} pers",
|
"max {seatings} pers": "max {seatings} pers",
|
||||||
"monday": "montag",
|
"monday": "montag",
|
||||||
"next level: {nextLevel}": "next level: {nextLevel}",
|
"next level: {nextLevel}": "next level: {nextLevel}",
|
||||||
@@ -783,4 +785,4 @@
|
|||||||
"{value} persons": "{number} personer",
|
"{value} persons": "{number} personer",
|
||||||
"{value} square meters": "{number} kvadratmeter",
|
"{value} square meters": "{number} kvadratmeter",
|
||||||
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Alle Rechte vorbehalten"
|
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Alle Rechte vorbehalten"
|
||||||
}
|
}
|
||||||
@@ -521,6 +521,7 @@
|
|||||||
"Room": "Room",
|
"Room": "Room",
|
||||||
"Room & Terms": "Room & Terms",
|
"Room & Terms": "Room & Terms",
|
||||||
"Room charge": "Room charge",
|
"Room charge": "Room charge",
|
||||||
|
"Room details": "Room details",
|
||||||
"Room facilities": "Room facilities",
|
"Room facilities": "Room facilities",
|
||||||
"Room total": "Room total",
|
"Room total": "Room total",
|
||||||
"Room {roomIndex}": "Room {roomIndex}",
|
"Room {roomIndex}": "Room {roomIndex}",
|
||||||
@@ -720,6 +721,8 @@
|
|||||||
"cm": "cm",
|
"cm": "cm",
|
||||||
"friday": "friday",
|
"friday": "friday",
|
||||||
"from": "from",
|
"from": "from",
|
||||||
|
"guests.plural": "{guests, plural, one {# guest} other {# guests}}",
|
||||||
|
"guests.span": "{min}-{max} guests",
|
||||||
"max {seatings} pers": "max {seatings} pers",
|
"max {seatings} pers": "max {seatings} pers",
|
||||||
"monday": "monday",
|
"monday": "monday",
|
||||||
"next level: {nextLevel}": "next level: {nextLevel}",
|
"next level: {nextLevel}": "next level: {nextLevel}",
|
||||||
@@ -788,4 +791,4 @@
|
|||||||
"{value} persons": "{value} persons",
|
"{value} persons": "{value} persons",
|
||||||
"{value} square meters": "{value} square meters",
|
"{value} square meters": "{value} square meters",
|
||||||
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB All rights reserved"
|
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB All rights reserved"
|
||||||
}
|
}
|
||||||
@@ -717,6 +717,8 @@
|
|||||||
"booking.thisRoomIsEquippedWith": "Tämä huone on varustettu",
|
"booking.thisRoomIsEquippedWith": "Tämä huone on varustettu",
|
||||||
"friday": "perjantai",
|
"friday": "perjantai",
|
||||||
"from": "alkaa",
|
"from": "alkaa",
|
||||||
|
"guests.plural": "{guests, plural, one {# vieras} other {# vieraita}}",
|
||||||
|
"guests.span": "{min}-{max} vieraita",
|
||||||
"max {seatings} pers": "max {seatings} pers",
|
"max {seatings} pers": "max {seatings} pers",
|
||||||
"monday": "maanantai",
|
"monday": "maanantai",
|
||||||
"next level: {nextLevel}": "next level: {nextLevel}",
|
"next level: {nextLevel}": "next level: {nextLevel}",
|
||||||
@@ -783,4 +785,4 @@
|
|||||||
"{value} persons": "{number} henkilöä",
|
"{value} persons": "{number} henkilöä",
|
||||||
"{value} square meters": "{number} neliömetriä",
|
"{value} square meters": "{number} neliömetriä",
|
||||||
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Kaikki oikeudet pidätetään"
|
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Kaikki oikeudet pidätetään"
|
||||||
}
|
}
|
||||||
@@ -715,6 +715,8 @@
|
|||||||
"booking.thisRoomIsEquippedWith": "Dette rommet er utstyrt med",
|
"booking.thisRoomIsEquippedWith": "Dette rommet er utstyrt med",
|
||||||
"friday": "fredag",
|
"friday": "fredag",
|
||||||
"from": "fra",
|
"from": "fra",
|
||||||
|
"guests.plural": "{guests, plural, one {# gjest} other {# gjester}}",
|
||||||
|
"guests.span": "{min}-{max} gjester",
|
||||||
"max {seatings} pers": "max {seatings} pers",
|
"max {seatings} pers": "max {seatings} pers",
|
||||||
"monday": "mandag",
|
"monday": "mandag",
|
||||||
"next level: {nextLevel}": "next level: {nextLevel}",
|
"next level: {nextLevel}": "next level: {nextLevel}",
|
||||||
@@ -781,4 +783,4 @@
|
|||||||
"{value} persons": "{number} personer",
|
"{value} persons": "{number} personer",
|
||||||
"{value} square meters": "{number} kvadratmeter",
|
"{value} square meters": "{number} kvadratmeter",
|
||||||
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Alle rettigheter forbeholdt"
|
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Alle rettigheter forbeholdt"
|
||||||
}
|
}
|
||||||
@@ -715,6 +715,8 @@
|
|||||||
"booking.thisRoomIsEquippedWith": "Detta rum är utrustat med",
|
"booking.thisRoomIsEquippedWith": "Detta rum är utrustat med",
|
||||||
"friday": "fredag",
|
"friday": "fredag",
|
||||||
"from": "från",
|
"from": "från",
|
||||||
|
"guests.plural": "{guests, plural, one {# gäst} other {# gäster}}",
|
||||||
|
"guests.span": "{min}-{max} gäster",
|
||||||
"max {seatings} pers": "max {seatings} pers",
|
"max {seatings} pers": "max {seatings} pers",
|
||||||
"monday": "måndag",
|
"monday": "måndag",
|
||||||
"next level: {nextLevel}": "next level: {nextLevel}",
|
"next level: {nextLevel}": "next level: {nextLevel}",
|
||||||
@@ -783,4 +785,4 @@
|
|||||||
"{value} persons": "{number} personer",
|
"{value} persons": "{number} personer",
|
||||||
"{value} square meters": "{number} kvadratmeter",
|
"{value} square meters": "{number} kvadratmeter",
|
||||||
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Alla rättigheter förbehålls"
|
"© {currentYear} Scandic AB All rights reserved": "© {currentYear} Scandic AB Alla rättigheter förbehålls"
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ export default function RoomProvider({
|
|||||||
room,
|
room,
|
||||||
}: RoomProviderProps) {
|
}: RoomProviderProps) {
|
||||||
const activeRoom = useRatesStore((state) => state.activeRoom)
|
const activeRoom = useRatesStore((state) => state.activeRoom)
|
||||||
|
const closeSection = useRatesStore((state) => state.actions.closeSection(idx))
|
||||||
const modifyRate = useRatesStore((state) => state.actions.modifyRate(idx))
|
const modifyRate = useRatesStore((state) => state.actions.modifyRate(idx))
|
||||||
const selectFilter = useRatesStore((state) => state.actions.selectFilter(idx))
|
const selectFilter = useRatesStore((state) => state.actions.selectFilter(idx))
|
||||||
const selectRate = useRatesStore((state) => state.actions.selectRate(idx))
|
const selectRate = useRatesStore((state) => state.actions.selectRate(idx))
|
||||||
@@ -21,6 +22,7 @@ export default function RoomProvider({
|
|||||||
value={{
|
value={{
|
||||||
...room,
|
...room,
|
||||||
actions: {
|
actions: {
|
||||||
|
closeSection,
|
||||||
modifyRate,
|
modifyRate,
|
||||||
selectFilter,
|
selectFilter,
|
||||||
selectRate,
|
selectRate,
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export function getDescription(data: RawMetadataSchema) {
|
|||||||
return metadata.description
|
return metadata.description
|
||||||
}
|
}
|
||||||
if (data.hotelData) {
|
if (data.hotelData) {
|
||||||
return data.hotelData.hotelContent.texts.descriptions.short
|
return data.hotelData.hotelContent.texts.descriptions?.short
|
||||||
}
|
}
|
||||||
if (data.preamble) {
|
if (data.preamble) {
|
||||||
return truncateTextAfterLastPeriod(data.preamble)
|
return truncateTextAfterLastPeriod(data.preamble)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { cache } from "@/utils/cache"
|
|||||||
|
|
||||||
import { getHotelPageUrl } from "../contentstack/hotelPage/utils"
|
import { getHotelPageUrl } from "../contentstack/hotelPage/utils"
|
||||||
import { getVerifiedUser, parsedUser } from "../user/query"
|
import { getVerifiedUser, parsedUser } from "../user/query"
|
||||||
import { additionalDataSchema } from "./schemas/additionalData"
|
import { additionalDataSchema } from "./schemas/hotel/include/additionalData"
|
||||||
import { meetingRoomsSchema } from "./schemas/meetingRoom"
|
import { meetingRoomsSchema } from "./schemas/meetingRoom"
|
||||||
import {
|
import {
|
||||||
ancillaryPackageInputSchema,
|
ancillaryPackageInputSchema,
|
||||||
@@ -510,8 +510,8 @@ export const hotelQueryRouter = router({
|
|||||||
adults: adultCount,
|
adults: adultCount,
|
||||||
...(childArray &&
|
...(childArray &&
|
||||||
childArray.length > 0 && {
|
childArray.length > 0 && {
|
||||||
children: childArray.join(","),
|
children: childArray.join(","),
|
||||||
}),
|
}),
|
||||||
...(bookingCode && { bookingCode }),
|
...(bookingCode && { bookingCode }),
|
||||||
language: apiLang,
|
language: apiLang,
|
||||||
}
|
}
|
||||||
@@ -754,9 +754,9 @@ export const hotelQueryRouter = router({
|
|||||||
type: matchingRoom.mainBed.type,
|
type: matchingRoom.mainBed.type,
|
||||||
extraBed: matchingRoom.fixedExtraBed
|
extraBed: matchingRoom.fixedExtraBed
|
||||||
? {
|
? {
|
||||||
type: matchingRoom.fixedExtraBed.type,
|
type: matchingRoom.fixedExtraBed.type,
|
||||||
description: matchingRoom.fixedExtraBed.description,
|
description: matchingRoom.fixedExtraBed.description,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -855,7 +855,7 @@ export const hotelQueryRouter = router({
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
rates: router({
|
rates: router({
|
||||||
get: publicProcedure.input(ratesInputSchema).query(async ({}) => {
|
get: publicProcedure.input(ratesInputSchema).query(async () => {
|
||||||
// TODO: Do a real API call when the endpoint is ready
|
// TODO: Do a real API call when the endpoint is ready
|
||||||
// const { hotelId } = input
|
// const { hotelId } = input
|
||||||
|
|
||||||
@@ -1112,9 +1112,9 @@ export const hotelQueryRouter = router({
|
|||||||
|
|
||||||
return hotelData
|
return hotelData
|
||||||
? {
|
? {
|
||||||
...hotelData,
|
...hotelData,
|
||||||
url,
|
url,
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
import { imageSchema } from "./image"
|
|
||||||
|
|
||||||
const specialNeedSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
details: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const specialNeedGroupSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
specialNeeds: z.array(specialNeedSchema),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const gallerySchema = z.object({
|
|
||||||
heroImages: z.array(imageSchema),
|
|
||||||
smallerImages: z.array(imageSchema),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const facilitySchema = z.object({
|
|
||||||
headingText: z.string().default(""),
|
|
||||||
heroImages: z.array(imageSchema),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const restaurantsOverviewPageSchema = z.object({
|
|
||||||
restaurantsOverviewPageLinkText: z.string().optional(),
|
|
||||||
restaurantsOverviewPageLink: z.string().optional(),
|
|
||||||
restaurantsContentDescriptionShort: z.string().optional(),
|
|
||||||
restaurantsContentDescriptionMedium: z.string().optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const extraPageSchema = z.object({
|
|
||||||
elevatorPitch: z.string().default(""),
|
|
||||||
mainBody: z.string().optional(),
|
|
||||||
nameInUrl: z.string().optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const accessibilitySchema = z.object({
|
|
||||||
headingText: z.string().default(""),
|
|
||||||
heroImages: z.array(imageSchema),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const additionalDataSchema = z.object({
|
|
||||||
attributes: z.object({
|
|
||||||
name: z.string(),
|
|
||||||
id: z.string(),
|
|
||||||
displayWebPage: z.object({
|
|
||||||
healthGym: z.boolean(),
|
|
||||||
meetingRoom: z.boolean(),
|
|
||||||
parking: z.boolean(),
|
|
||||||
specialNeeds: z.boolean(),
|
|
||||||
}),
|
|
||||||
specialNeedGroups: z.array(specialNeedGroupSchema),
|
|
||||||
gallery: gallerySchema.optional(),
|
|
||||||
conferencesAndMeetings: facilitySchema.optional(),
|
|
||||||
healthAndWellness: facilitySchema.optional(),
|
|
||||||
restaurantImages: facilitySchema.optional(),
|
|
||||||
parkingImages: facilitySchema.optional(),
|
|
||||||
restaurantsOverviewPage: restaurantsOverviewPageSchema,
|
|
||||||
meetingRooms: extraPageSchema,
|
|
||||||
healthAndFitness: extraPageSchema,
|
|
||||||
hotelParking: extraPageSchema,
|
|
||||||
hotelSpecialNeeds: extraPageSchema,
|
|
||||||
hotelRoomElevatorPitchText: z.string().optional(),
|
|
||||||
accessibility: accessibilitySchema.optional(),
|
|
||||||
}),
|
|
||||||
type: z.literal("additionalData"),
|
|
||||||
})
|
|
||||||
|
|
||||||
export function transformAdditionalData(
|
|
||||||
data: z.output<typeof additionalDataSchema>
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...data.attributes,
|
|
||||||
id: data.attributes.id,
|
|
||||||
type: data.type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import {
|
||||||
|
nullableArrayObjectValidator,
|
||||||
|
nullableArrayStringValidator,
|
||||||
|
} from "@/utils/zod/arrayValidator"
|
||||||
import { nullableNumberValidator } from "@/utils/zod/numberValidator"
|
import { nullableNumberValidator } from "@/utils/zod/numberValidator"
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
import { addressSchema } from "./hotel/address"
|
import { addressSchema } from "./hotel/address"
|
||||||
import { contactInformationSchema } from "./hotel/contactInformation"
|
import { contactInformationSchema } from "./hotel/contactInformation"
|
||||||
import { hotelContentSchema } from "./hotel/content"
|
import { hotelContentSchema } from "./hotel/content"
|
||||||
import { detailedFacilitiesSchema } from "./hotel/detailedFacility"
|
import { detailedFacilitiesSchema } from "./hotel/detailedFacility"
|
||||||
import { hotelFactsSchema } from "./hotel/facts"
|
import { hotelFactsSchema } from "./hotel/facts"
|
||||||
import { gallerySchema } from "./hotel/gallery"
|
import { healthFacilitiesSchema } from "./hotel/healthFacilities"
|
||||||
import { healthFacilitySchema } from "./hotel/healthFacilities"
|
import { displayWebPageSchema } from "./hotel/include/additionalData/displayWebPage"
|
||||||
|
import { facilitySchema } from "./hotel/include/additionalData/facility"
|
||||||
|
import { gallerySchema } from "./hotel/include/additionalData/gallery"
|
||||||
import { includeSchema } from "./hotel/include/include"
|
import { includeSchema } from "./hotel/include/include"
|
||||||
import { locationSchema } from "./hotel/location"
|
import { locationSchema } from "./hotel/location"
|
||||||
import { merchantInformationSchema } from "./hotel/merchantInformation"
|
import { merchantInformationSchema } from "./hotel/merchantInformation"
|
||||||
@@ -18,41 +25,41 @@ import { ratingsSchema } from "./hotel/rating"
|
|||||||
import { rewardNightSchema } from "./hotel/rewardNight"
|
import { rewardNightSchema } from "./hotel/rewardNight"
|
||||||
import { socialMediaSchema } from "./hotel/socialMedia"
|
import { socialMediaSchema } from "./hotel/socialMedia"
|
||||||
import { specialAlertsSchema } from "./hotel/specialAlerts"
|
import { specialAlertsSchema } from "./hotel/specialAlerts"
|
||||||
import { specialNeedGroupSchema } from "./hotel/specialNeedGroups"
|
|
||||||
import { facilitySchema } from "./additionalData"
|
|
||||||
import { imageSchema } from "./image"
|
import { imageSchema } from "./image"
|
||||||
|
|
||||||
export const attributesSchema = z.object({
|
export const attributesSchema = z.object({
|
||||||
id: z.string().optional(),
|
|
||||||
address: addressSchema,
|
address: addressSchema,
|
||||||
cityId: z.string(),
|
cityId: nullableStringValidator,
|
||||||
cityName: z.string(),
|
cityName: nullableStringValidator,
|
||||||
conferencesAndMeetings: facilitySchema.optional(),
|
conferencesAndMeetings: facilitySchema.nullish(),
|
||||||
contactInformation: contactInformationSchema,
|
contactInformation: contactInformationSchema,
|
||||||
|
countryCode: nullableStringValidator,
|
||||||
detailedFacilities: detailedFacilitiesSchema,
|
detailedFacilities: detailedFacilitiesSchema,
|
||||||
gallery: gallerySchema.optional(),
|
displayWebPage: displayWebPageSchema,
|
||||||
galleryImages: z.array(imageSchema).optional(),
|
gallery: gallerySchema.nullish(),
|
||||||
healthAndWellness: facilitySchema.optional(),
|
galleryImages: z
|
||||||
healthFacilities: z.array(healthFacilitySchema),
|
.array(imageSchema)
|
||||||
|
.nullish()
|
||||||
|
.transform((arr) => (arr ? arr.filter(Boolean) : [])),
|
||||||
|
healthAndWellness: facilitySchema.nullish(),
|
||||||
|
healthFacilities: healthFacilitiesSchema,
|
||||||
hotelContent: hotelContentSchema,
|
hotelContent: hotelContentSchema,
|
||||||
hotelFacts: hotelFactsSchema,
|
hotelFacts: hotelFactsSchema,
|
||||||
hotelRoomElevatorPitchText: z.string().optional(),
|
hotelType: nullableStringValidator,
|
||||||
hotelType: z.string().optional(),
|
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
isPublished: z.boolean(),
|
isPublished: z.boolean(),
|
||||||
keywords: z.array(z.string()),
|
keywords: nullableArrayStringValidator,
|
||||||
location: locationSchema,
|
location: locationSchema,
|
||||||
merchantInformationData: merchantInformationSchema,
|
merchantInformationData: merchantInformationSchema,
|
||||||
name: z.string(),
|
name: nullableStringValidator,
|
||||||
operaId: z.string(),
|
operaId: nullableStringValidator,
|
||||||
parking: z.array(parkingSchema),
|
parking: nullableArrayObjectValidator(parkingSchema),
|
||||||
pointsOfInterest: pointOfInterestsSchema,
|
pointsOfInterest: pointOfInterestsSchema,
|
||||||
ratings: ratingsSchema,
|
ratings: ratingsSchema,
|
||||||
|
restaurantImages: facilitySchema.nullish(),
|
||||||
rewardNight: rewardNightSchema,
|
rewardNight: rewardNightSchema,
|
||||||
restaurantImages: facilitySchema.optional(),
|
|
||||||
socialMedia: socialMediaSchema,
|
socialMedia: socialMediaSchema,
|
||||||
specialAlerts: specialAlertsSchema,
|
specialAlerts: specialAlertsSchema,
|
||||||
specialNeedGroups: z.array(specialNeedGroupSchema),
|
|
||||||
vat: nullableNumberValidator,
|
vat: nullableNumberValidator,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
export const addressSchema = z.object({
|
export const addressSchema = z.object({
|
||||||
city: z.string(),
|
city: nullableStringValidator,
|
||||||
country: z.string(),
|
country: nullableStringValidator,
|
||||||
streetAddress: z.string(),
|
streetAddress: nullableStringValidator,
|
||||||
zipCode: z.string(),
|
zipCode: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import {
|
||||||
|
nullableStringEmailValidator,
|
||||||
|
nullableStringValidator,
|
||||||
|
} from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
export const contactInformationSchema = z.object({
|
export const contactInformationSchema = z.object({
|
||||||
email: z.string(),
|
email: nullableStringEmailValidator,
|
||||||
faxNumber: z.string().optional(),
|
faxNumber: nullableStringValidator,
|
||||||
phoneNumber: z.string(),
|
phoneNumber: nullableStringValidator,
|
||||||
websiteUrl: z.string(),
|
websiteUrl: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,40 +1,26 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
import { imageSchema } from "../image"
|
import { imageSchema } from "../image"
|
||||||
|
import { restaurantsOverviewPageSchema } from "./include/additionalData/restaurantsOverviewPage"
|
||||||
|
|
||||||
|
const descriptionSchema = z
|
||||||
|
.object({
|
||||||
|
medium: nullableStringValidator,
|
||||||
|
short: nullableStringValidator,
|
||||||
|
})
|
||||||
|
.nullish()
|
||||||
|
|
||||||
|
const textsSchema = z.object({
|
||||||
|
descriptions: descriptionSchema,
|
||||||
|
facilityInformation: nullableStringValidator,
|
||||||
|
meetingDescription: descriptionSchema,
|
||||||
|
surroundingInformation: nullableStringValidator,
|
||||||
|
})
|
||||||
|
|
||||||
export const hotelContentSchema = z.object({
|
export const hotelContentSchema = z.object({
|
||||||
images: imageSchema.default({
|
images: imageSchema,
|
||||||
metaData: {
|
restaurantsOverviewPage: restaurantsOverviewPageSchema,
|
||||||
altText: "default image",
|
texts: textsSchema,
|
||||||
altText_En: "default image",
|
|
||||||
copyRight: "default image",
|
|
||||||
title: "default image",
|
|
||||||
},
|
|
||||||
imageSizes: {
|
|
||||||
large: "https://placehold.co/1280x720",
|
|
||||||
medium: "https://placehold.co/1280x720",
|
|
||||||
small: "https://placehold.co/1280x720",
|
|
||||||
tiny: "https://placehold.co/1280x720",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
restaurantsOverviewPage: z.object({
|
|
||||||
restaurantsContentDescriptionMedium: z.string().optional(),
|
|
||||||
restaurantsContentDescriptionShort: z.string().optional(),
|
|
||||||
restaurantsOverviewPageLink: z.string().optional(),
|
|
||||||
restaurantsOverviewPageLinkText: z.string().optional(),
|
|
||||||
}),
|
|
||||||
texts: z.object({
|
|
||||||
descriptions: z.object({
|
|
||||||
medium: z.string(),
|
|
||||||
short: z.string(),
|
|
||||||
}),
|
|
||||||
facilityInformation: z.string().optional(),
|
|
||||||
meetingDescription: z
|
|
||||||
.object({
|
|
||||||
medium: z.string().optional(),
|
|
||||||
short: z.string().optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
surroundingInformation: z.string(),
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
const detailedFacilitySchema = z.object({
|
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
||||||
filter: z.string().optional(),
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
icon: z.string().optional(),
|
|
||||||
id: z.number(),
|
import { FacilityEnum } from "@/types/enums/facilities"
|
||||||
name: z.string(),
|
|
||||||
|
export const detailedFacilitySchema = z.object({
|
||||||
|
filter: nullableStringValidator,
|
||||||
|
icon: nullableStringValidator,
|
||||||
|
id: z.nativeEnum(FacilityEnum),
|
||||||
|
name: nullableStringValidator,
|
||||||
public: z.boolean(),
|
public: z.boolean(),
|
||||||
sortOrder: z.number(),
|
sortOrder: z.number(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const detailedFacilitiesSchema = z
|
export const detailedFacilitiesSchema = nullableArrayObjectValidator(
|
||||||
.array(detailedFacilitySchema)
|
detailedFacilitySchema
|
||||||
.transform((facilities) =>
|
).transform((facilities) =>
|
||||||
facilities.sort((a, b) => b.sortOrder - a.sortOrder)
|
facilities.sort((a, b) => b.sortOrder - a.sortOrder)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,50 +1,19 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
|
export const checkinSchema = z.object({
|
||||||
|
checkInTime: nullableStringValidator,
|
||||||
|
checkOutTime: nullableStringValidator,
|
||||||
|
onlineCheckout: z.boolean(),
|
||||||
|
onlineCheckOutAvailableFrom: nullableStringValidator,
|
||||||
|
})
|
||||||
|
|
||||||
const ecoLabelsSchema = z.object({
|
const ecoLabelsSchema = z.object({
|
||||||
euEcoLabel: z.boolean(),
|
euEcoLabel: z.boolean(),
|
||||||
greenGlobeLabel: z.boolean(),
|
greenGlobeLabel: z.boolean(),
|
||||||
nordicEcoLabel: z.boolean(),
|
nordicEcoLabel: z.boolean(),
|
||||||
svanenEcoLabelCertificateNumber: z.string().optional(),
|
svanenEcoLabelCertificateNumber: nullableStringValidator,
|
||||||
})
|
|
||||||
|
|
||||||
export const checkinSchema = z.object({
|
|
||||||
checkInTime: z.string(),
|
|
||||||
checkOutTime: z.string(),
|
|
||||||
onlineCheckout: z.boolean(),
|
|
||||||
onlineCheckOutAvailableFrom: z.string().nullable().optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const hotelFacilityDetailSchema = z
|
|
||||||
.object({
|
|
||||||
description: z.string(),
|
|
||||||
heading: z.string(),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
|
|
||||||
/** Possibly more values */
|
|
||||||
const hotelFacilityDetailsSchema = z.object({
|
|
||||||
breakfast: hotelFacilityDetailSchema,
|
|
||||||
checkout: hotelFacilityDetailSchema,
|
|
||||||
gym: hotelFacilityDetailSchema,
|
|
||||||
internet: hotelFacilityDetailSchema,
|
|
||||||
laundry: hotelFacilityDetailSchema,
|
|
||||||
luggage: hotelFacilityDetailSchema,
|
|
||||||
shop: hotelFacilityDetailSchema,
|
|
||||||
telephone: hotelFacilityDetailSchema,
|
|
||||||
})
|
|
||||||
|
|
||||||
const hotelInformationSchema = z
|
|
||||||
.object({
|
|
||||||
description: z.string(),
|
|
||||||
heading: z.string(),
|
|
||||||
link: z.string().optional(),
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
|
|
||||||
const hotelInformationsSchema = z.object({
|
|
||||||
accessibility: hotelInformationSchema,
|
|
||||||
safety: hotelInformationSchema,
|
|
||||||
sustainability: hotelInformationSchema,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const interiorSchema = z.object({
|
const interiorSchema = z.object({
|
||||||
@@ -53,7 +22,7 @@ const interiorSchema = z.object({
|
|||||||
numberOfFloors: z.number(),
|
numberOfFloors: z.number(),
|
||||||
numberOfRooms: z.object({
|
numberOfRooms: z.object({
|
||||||
connected: z.number(),
|
connected: z.number(),
|
||||||
forAllergics: z.number().optional(),
|
forAllergics: z.number(),
|
||||||
forDisabled: z.number(),
|
forDisabled: z.number(),
|
||||||
nonSmoking: z.number(),
|
nonSmoking: z.number(),
|
||||||
pet: z.number(),
|
pet: z.number(),
|
||||||
@@ -64,17 +33,15 @@ const interiorSchema = z.object({
|
|||||||
|
|
||||||
const receptionHoursSchema = z.object({
|
const receptionHoursSchema = z.object({
|
||||||
alwaysOpen: z.boolean(),
|
alwaysOpen: z.boolean(),
|
||||||
|
closingTime: nullableStringValidator,
|
||||||
isClosed: z.boolean(),
|
isClosed: z.boolean(),
|
||||||
openingTime: z.string().optional(),
|
openingTime: nullableStringValidator,
|
||||||
closingTime: z.string().optional(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const hotelFactsSchema = z.object({
|
export const hotelFactsSchema = z.object({
|
||||||
checkin: checkinSchema,
|
checkin: checkinSchema,
|
||||||
ecoLabels: ecoLabelsSchema,
|
ecoLabels: ecoLabelsSchema,
|
||||||
hotelFacilityDetail: hotelFacilityDetailsSchema.default({}),
|
|
||||||
hotelInformation: hotelInformationsSchema.default({}),
|
|
||||||
interior: interiorSchema,
|
interior: interiorSchema,
|
||||||
receptionHours: receptionHoursSchema,
|
receptionHours: receptionHoursSchema,
|
||||||
yearBuilt: z.string(),
|
yearBuilt: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
import { imageSchema } from "../image"
|
|
||||||
|
|
||||||
export const gallerySchema = z.object({
|
|
||||||
heroImages: z.array(imageSchema),
|
|
||||||
smallerImages: z.array(imageSchema),
|
|
||||||
})
|
|
||||||
@@ -1,41 +1,61 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
||||||
|
import { nullableNumberValidator } from "@/utils/zod/numberValidator"
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
import { imageSchema } from "../image"
|
import { imageSchema } from "../image"
|
||||||
|
|
||||||
const healthFacilitiesOpenHoursSchema = z.object({
|
const healthFacilitiesOpenHoursSchema = z.object({
|
||||||
alwaysOpen: z.boolean(),
|
alwaysOpen: z.boolean(),
|
||||||
closingTime: z.string().optional(),
|
closingTime: nullableStringValidator,
|
||||||
isClosed: z.boolean(),
|
isClosed: z.boolean(),
|
||||||
openingTime: z.string().optional(),
|
openingTime: nullableStringValidator,
|
||||||
sortOrder: z.number().optional(),
|
sortOrder: nullableNumberValidator,
|
||||||
|
})
|
||||||
|
|
||||||
|
const descriptionSchema = z
|
||||||
|
.object({
|
||||||
|
medium: nullableStringValidator,
|
||||||
|
short: nullableStringValidator,
|
||||||
|
})
|
||||||
|
.nullish()
|
||||||
|
|
||||||
|
const detailsSchema = z.object({
|
||||||
|
name: nullableStringValidator,
|
||||||
|
type: nullableStringValidator,
|
||||||
|
value: nullableStringValidator,
|
||||||
|
})
|
||||||
|
|
||||||
|
const textsSchema = z.object({
|
||||||
|
descriptions: descriptionSchema,
|
||||||
|
facilityInformation: nullableStringValidator,
|
||||||
|
meetingDescription: descriptionSchema,
|
||||||
|
surroundingInformation: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const healthFacilitySchema = z.object({
|
export const healthFacilitySchema = z.object({
|
||||||
content: z.object({
|
content: z.object({
|
||||||
images: z.array(imageSchema),
|
images: z
|
||||||
texts: z.object({
|
.array(imageSchema)
|
||||||
descriptions: z.object({
|
.nullish()
|
||||||
short: z.string(),
|
.transform((arr) => (arr ? arr.filter(Boolean) : [])),
|
||||||
medium: z.string(),
|
texts: textsSchema,
|
||||||
}),
|
|
||||||
facilityInformation: z.string().optional(),
|
|
||||||
surroundingInformation: z.string().optional(),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
details: z.array(
|
details: nullableArrayObjectValidator(detailsSchema),
|
||||||
z.object({
|
|
||||||
name: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
value: z.string().optional(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
openingDetails: z.object({
|
openingDetails: z.object({
|
||||||
manualOpeningHours: z.string().optional(),
|
manualOpeningHours: nullableStringValidator,
|
||||||
openingHours: z.object({
|
openingHours: z.object({
|
||||||
ordinary: healthFacilitiesOpenHoursSchema,
|
ordinary: healthFacilitiesOpenHoursSchema,
|
||||||
weekends: healthFacilitiesOpenHoursSchema,
|
weekends: healthFacilitiesOpenHoursSchema,
|
||||||
}),
|
}),
|
||||||
useManualOpeningHours: z.boolean(),
|
useManualOpeningHours: z
|
||||||
|
.boolean()
|
||||||
|
.nullish()
|
||||||
|
.transform((b) => !!b),
|
||||||
}),
|
}),
|
||||||
type: z.string(),
|
type: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const healthFacilitiesSchema =
|
||||||
|
nullableArrayObjectValidator(healthFacilitySchema)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
|
import { displayWebPageSchema } from "./additionalData/displayWebPage"
|
||||||
|
import { facilitySchema } from "./additionalData/facility"
|
||||||
|
import { gallerySchema } from "./additionalData/gallery"
|
||||||
|
import { restaurantsOverviewPageSchema } from "./additionalData/restaurantsOverviewPage"
|
||||||
|
import { specialNeedGroupSchema } from "./additionalData/specialNeedGroups"
|
||||||
|
|
||||||
|
export const extraPageSchema = z.object({
|
||||||
|
elevatorPitch: nullableStringValidator,
|
||||||
|
mainBody: nullableStringValidator,
|
||||||
|
nameInUrl: nullableStringValidator,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const additionalDataSchema = z.object({
|
||||||
|
attributes: z.object({
|
||||||
|
accessibility: facilitySchema.nullish(),
|
||||||
|
conferencesAndMeetings: facilitySchema.nullish(),
|
||||||
|
displayWebPage: displayWebPageSchema,
|
||||||
|
gallery: gallerySchema.nullish(),
|
||||||
|
healthAndFitness: extraPageSchema,
|
||||||
|
healthAndWellness: facilitySchema.nullish(),
|
||||||
|
hotelParking: extraPageSchema,
|
||||||
|
hotelRoomElevatorPitchText: nullableStringValidator,
|
||||||
|
hotelSpecialNeeds: extraPageSchema,
|
||||||
|
id: nullableStringValidator,
|
||||||
|
meetingRooms: extraPageSchema,
|
||||||
|
name: nullableStringValidator,
|
||||||
|
parkingImages: facilitySchema.nullish(),
|
||||||
|
restaurantImages: facilitySchema.nullish(),
|
||||||
|
restaurantsOverviewPage: restaurantsOverviewPageSchema,
|
||||||
|
specialNeedGroups: nullableArrayObjectValidator(specialNeedGroupSchema),
|
||||||
|
}),
|
||||||
|
type: z.literal("additionalData"),
|
||||||
|
})
|
||||||
|
|
||||||
|
export function transformAdditionalData(
|
||||||
|
data: z.output<typeof additionalDataSchema>
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...data.attributes,
|
||||||
|
id: data.attributes.id,
|
||||||
|
type: data.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const displayWebPageSchema = z.object({
|
||||||
|
healthGym: z.boolean(),
|
||||||
|
meetingRoom: z.boolean(),
|
||||||
|
parking: z.boolean(),
|
||||||
|
specialNeeds: z.boolean(),
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { imageSchema } from "@/server/routers/hotels/schemas/image"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
|
export const facilitySchema = z.object({
|
||||||
|
headingText: nullableStringValidator,
|
||||||
|
heroImages: z
|
||||||
|
.array(imageSchema)
|
||||||
|
.nullish()
|
||||||
|
.transform((arr) => (arr ? arr.filter(Boolean) : [])),
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { imageSchema } from "@/server/routers/hotels/schemas/image"
|
||||||
|
|
||||||
|
const imagesSchema = z
|
||||||
|
.array(imageSchema)
|
||||||
|
.nullish()
|
||||||
|
.transform((arr) => (arr ? arr.filter(Boolean) : []))
|
||||||
|
|
||||||
|
export const gallerySchema = z.object({
|
||||||
|
heroImages: imagesSchema,
|
||||||
|
smallerImages: imagesSchema,
|
||||||
|
})
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
|
export const restaurantsOverviewPageSchema = z.object({
|
||||||
|
restaurantsContentDescriptionMedium: nullableStringValidator,
|
||||||
|
restaurantsContentDescriptionShort: nullableStringValidator,
|
||||||
|
restaurantsOverviewPageLink: nullableStringValidator,
|
||||||
|
restaurantsOverviewPageLinkText: nullableStringValidator,
|
||||||
|
})
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
|
const specialNeedSchema = z.object({
|
||||||
|
details: nullableStringValidator,
|
||||||
|
name: nullableStringValidator,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const specialNeedGroupSchema = z.object({
|
||||||
|
name: nullableStringValidator,
|
||||||
|
specialNeeds: nullableArrayObjectValidator(specialNeedSchema),
|
||||||
|
})
|
||||||
@@ -8,10 +8,7 @@ import {
|
|||||||
transformRoomCategories,
|
transformRoomCategories,
|
||||||
} from "@/server/routers/hotels/schemas/hotel/include/roomCategories"
|
} from "@/server/routers/hotels/schemas/hotel/include/roomCategories"
|
||||||
|
|
||||||
import {
|
import { additionalDataSchema, transformAdditionalData } from "./additionalData"
|
||||||
additionalDataSchema,
|
|
||||||
transformAdditionalData,
|
|
||||||
} from "../../additionalData"
|
|
||||||
|
|
||||||
export const includeSchema = z
|
export const includeSchema = z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { imageMetaDataSchema, imageSizesSchema } from "../../image"
|
import { imageSchema } from "@/server/routers/hotels/schemas/image"
|
||||||
|
|
||||||
|
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
const minMaxSchema = z.object({
|
const minMaxSchema = z.object({
|
||||||
max: z.number(),
|
max: z.number(),
|
||||||
@@ -8,8 +11,8 @@ const minMaxSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const bedTypeSchema = z.object({
|
const bedTypeSchema = z.object({
|
||||||
description: z.string().default(""),
|
description: nullableStringValidator,
|
||||||
type: z.string(),
|
type: nullableStringValidator,
|
||||||
widthRange: minMaxSchema,
|
widthRange: minMaxSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -20,28 +23,26 @@ const occupancySchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const roomContentSchema = z.object({
|
const roomContentSchema = z.object({
|
||||||
images: z.array(
|
images: z
|
||||||
z.object({
|
.array(imageSchema)
|
||||||
imageSizes: imageSizesSchema,
|
.nullish()
|
||||||
metaData: imageMetaDataSchema,
|
.transform((arr) => (arr ? arr.filter(Boolean) : [])),
|
||||||
})
|
|
||||||
),
|
|
||||||
texts: z.object({
|
texts: z.object({
|
||||||
descriptions: z.object({
|
descriptions: z.object({
|
||||||
medium: z.string().optional(),
|
medium: nullableStringValidator,
|
||||||
short: z.string().optional(),
|
short: nullableStringValidator,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const roomTypesSchema = z.object({
|
const roomTypesSchema = z.object({
|
||||||
code: z.string(),
|
code: nullableStringValidator,
|
||||||
description: z.string(),
|
description: nullableStringValidator,
|
||||||
fixedExtraBed: bedTypeSchema,
|
fixedExtraBed: bedTypeSchema,
|
||||||
isLackingCribs: z.boolean(),
|
isLackingCribs: z.boolean(),
|
||||||
isLackingExtraBeds: z.boolean(),
|
isLackingExtraBeds: z.boolean(),
|
||||||
mainBed: bedTypeSchema,
|
mainBed: bedTypeSchema,
|
||||||
name: z.string(),
|
name: nullableStringValidator,
|
||||||
occupancy: occupancySchema,
|
occupancy: occupancySchema,
|
||||||
roomCount: z.number(),
|
roomCount: z.number(),
|
||||||
roomSize: minMaxSchema,
|
roomSize: minMaxSchema,
|
||||||
@@ -58,11 +59,11 @@ const roomFacilitiesSchema = z.object({
|
|||||||
export const roomCategoriesSchema = z.object({
|
export const roomCategoriesSchema = z.object({
|
||||||
attributes: z.object({
|
attributes: z.object({
|
||||||
content: roomContentSchema,
|
content: roomContentSchema,
|
||||||
name: z.string(),
|
name: nullableStringValidator,
|
||||||
occupancy: minMaxSchema,
|
occupancy: minMaxSchema,
|
||||||
roomFacilities: z.array(roomFacilitiesSchema),
|
roomFacilities: nullableArrayObjectValidator(roomFacilitiesSchema),
|
||||||
roomSize: minMaxSchema,
|
roomSize: minMaxSchema,
|
||||||
roomTypes: z.array(roomTypesSchema),
|
roomTypes: nullableArrayObjectValidator(roomTypesSchema),
|
||||||
sortOrder: z.number(),
|
sortOrder: z.number(),
|
||||||
}),
|
}),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
import type { PaymentMethodEnum } from "@/constants/booking"
|
import type { PaymentMethodEnum } from "@/constants/booking"
|
||||||
|
|
||||||
export const merchantInformationSchema = z.object({
|
export const merchantInformationSchema = z.object({
|
||||||
@@ -17,5 +19,5 @@ export const merchantInformationSchema = z.object({
|
|||||||
.map(([key]) => key)
|
.map(([key]) => key)
|
||||||
.filter((key): key is PaymentMethodEnum => !!key)
|
.filter((key): key is PaymentMethodEnum => !!key)
|
||||||
}),
|
}),
|
||||||
webMerchantId: z.string().optional(),
|
webMerchantId: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,41 +1,45 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
||||||
|
import { nullableNumberValidator } from "@/utils/zod/numberValidator"
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
const periodSchema = z.object({
|
const periodSchema = z.object({
|
||||||
amount: z.number().optional(),
|
amount: nullableNumberValidator,
|
||||||
endTime: z.string().optional(),
|
endTime: nullableStringValidator,
|
||||||
period: z.string().optional(),
|
period: nullableStringValidator,
|
||||||
startTime: z.string().optional(),
|
startTime: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|
||||||
const currencySchema = z
|
const currencySchema = z
|
||||||
.object({
|
.object({
|
||||||
currency: z.string().optional(),
|
currency: nullableStringValidator,
|
||||||
ordinary: z.array(periodSchema).optional(),
|
ordinary: nullableArrayObjectValidator(periodSchema),
|
||||||
range: z
|
range: z
|
||||||
.object({
|
.object({
|
||||||
min: z.number().optional(),
|
min: nullableNumberValidator,
|
||||||
max: z.number().optional(),
|
max: nullableNumberValidator,
|
||||||
})
|
})
|
||||||
.optional(),
|
.nullish(),
|
||||||
weekend: z.array(periodSchema).optional(),
|
weekend: nullableArrayObjectValidator(periodSchema),
|
||||||
})
|
})
|
||||||
.optional()
|
.nullish()
|
||||||
|
|
||||||
const pricingSchema = z.object({
|
const pricingSchema = z.object({
|
||||||
freeParking: z.boolean(),
|
freeParking: z.boolean(),
|
||||||
localCurrency: currencySchema,
|
localCurrency: currencySchema,
|
||||||
paymentType: z.string().optional(),
|
paymentType: nullableStringValidator,
|
||||||
requestedCurrency: currencySchema,
|
requestedCurrency: currencySchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const parkingSchema = z.object({
|
export const parkingSchema = z.object({
|
||||||
address: z.string().optional(),
|
address: nullableStringValidator,
|
||||||
canMakeReservation: z.boolean(),
|
canMakeReservation: z.boolean(),
|
||||||
distanceToHotel: z.number().optional(),
|
distanceToHotel: nullableNumberValidator,
|
||||||
externalParkingUrl: z.string().optional(),
|
externalParkingUrl: nullableStringValidator,
|
||||||
name: z.string().optional(),
|
name: nullableStringValidator,
|
||||||
numberOfChargingSpaces: z.number().optional(),
|
numberOfChargingSpaces: nullableNumberValidator,
|
||||||
numberOfParkingSpots: z.number().optional(),
|
numberOfParkingSpots: nullableNumberValidator,
|
||||||
pricing: pricingSchema,
|
pricing: pricingSchema,
|
||||||
type: z.string().optional(),
|
type: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableNumberValidator } from "@/utils/zod/numberValidator"
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
import { getPoiGroupByCategoryName } from "../../utils"
|
import { getPoiGroupByCategoryName } from "../../utils"
|
||||||
import { locationSchema } from "./location"
|
import { locationSchema } from "./location"
|
||||||
|
|
||||||
export const pointOfInterestSchema = z
|
export const pointOfInterestSchema = z
|
||||||
.object({
|
.object({
|
||||||
category: z.object({
|
category: z.object({
|
||||||
name: z.string().optional(),
|
name: nullableStringValidator,
|
||||||
group: z.string().optional(),
|
|
||||||
}),
|
}),
|
||||||
distance: z.number().optional(),
|
distance: nullableNumberValidator,
|
||||||
isHighlighted: z.boolean().optional(),
|
location: locationSchema,
|
||||||
location: locationSchema.optional(),
|
name: nullableStringValidator,
|
||||||
name: z.string().optional(),
|
|
||||||
})
|
})
|
||||||
.transform((poi) => ({
|
.transform((poi) => ({
|
||||||
categoryName: poi.category.name,
|
categoryName: poi.category.name,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lat: poi.location?.latitude ?? 0,
|
lat: poi.location.latitude,
|
||||||
lng: poi.location?.longitude ?? 0,
|
lng: poi.location.longitude,
|
||||||
},
|
},
|
||||||
distance: poi.distance,
|
distance: poi.distance,
|
||||||
group: getPoiGroupByCategoryName(poi.category.name),
|
group: getPoiGroupByCategoryName(poi.category.name),
|
||||||
@@ -27,6 +28,8 @@ export const pointOfInterestSchema = z
|
|||||||
|
|
||||||
export const pointOfInterestsSchema = z
|
export const pointOfInterestsSchema = z
|
||||||
.array(pointOfInterestSchema)
|
.array(pointOfInterestSchema)
|
||||||
|
.nullish()
|
||||||
|
.transform((arr) => (arr ? arr.filter(Boolean) : []))
|
||||||
.transform((pois) =>
|
.transform((pois) =>
|
||||||
pois.sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0))
|
pois.sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,31 +1,57 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableArrayObjectValidator } from "@/utils/zod/arrayValidator"
|
||||||
|
import {
|
||||||
|
nullableStringUrlValidator,
|
||||||
|
nullableStringValidator,
|
||||||
|
} from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
const awardSchema = z.object({
|
const awardSchema = z.object({
|
||||||
displayName: z.string(),
|
displayName: nullableStringValidator,
|
||||||
images: z.object({
|
images: z
|
||||||
large: z.string(),
|
.object({
|
||||||
medium: z.string(),
|
large: nullableStringValidator,
|
||||||
small: z.string(),
|
medium: nullableStringValidator,
|
||||||
}),
|
small: nullableStringValidator,
|
||||||
|
})
|
||||||
|
.nullish()
|
||||||
|
.transform((obj) =>
|
||||||
|
obj
|
||||||
|
? obj
|
||||||
|
: {
|
||||||
|
small: "",
|
||||||
|
medium: "",
|
||||||
|
large: "",
|
||||||
|
}
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const reviewsSchema = z
|
const reviewsSchema = z
|
||||||
.object({
|
.object({
|
||||||
widgetHtmlTagId: z.string(),
|
widgetHtmlTagId: nullableStringValidator,
|
||||||
widgetScriptEmbedUrlIframe: z.string(),
|
widgetScriptEmbedUrlIframe: nullableStringValidator,
|
||||||
widgetScriptEmbedUrlJavaScript: z.string(),
|
widgetScriptEmbedUrlJavaScript: nullableStringValidator,
|
||||||
})
|
})
|
||||||
.optional()
|
.nullish()
|
||||||
|
.transform((obj) =>
|
||||||
|
obj
|
||||||
|
? obj
|
||||||
|
: {
|
||||||
|
widgetHtmlTagId: "",
|
||||||
|
widgetScriptEmbedUrlIframe: "",
|
||||||
|
widgetScriptEmbedUrlJavaScript: "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export const ratingsSchema = z
|
export const ratingsSchema = z
|
||||||
.object({
|
.object({
|
||||||
tripAdvisor: z.object({
|
tripAdvisor: z.object({
|
||||||
awards: z.array(awardSchema),
|
awards: nullableArrayObjectValidator(awardSchema),
|
||||||
numberOfReviews: z.number(),
|
numberOfReviews: z.number(),
|
||||||
rating: z.number(),
|
rating: z.number(),
|
||||||
ratingImageUrl: z.string(),
|
ratingImageUrl: nullableStringUrlValidator,
|
||||||
reviews: reviewsSchema,
|
reviews: reviewsSchema,
|
||||||
webUrl: z.string(),
|
webUrl: nullableStringUrlValidator,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
export const rewardNightSchema = z.object({
|
export const rewardNightSchema = z.object({
|
||||||
campaign: z.object({
|
campaign: z.object({
|
||||||
end: z.string(),
|
end: nullableStringValidator,
|
||||||
points: z.number(),
|
points: z.number(),
|
||||||
start: z.string(),
|
start: nullableStringValidator,
|
||||||
}),
|
}),
|
||||||
points: z.number(),
|
points: z.number(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
export const socialMediaSchema = z.object({
|
export const socialMediaSchema = z.object({
|
||||||
facebook: z.string().optional(),
|
facebook: nullableStringValidator,
|
||||||
instagram: z.string().optional(),
|
instagram: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const specialAlertSchema = z.object({
|
|||||||
|
|
||||||
export const specialAlertsSchema = z
|
export const specialAlertsSchema = z
|
||||||
.array(specialAlertSchema)
|
.array(specialAlertSchema)
|
||||||
|
.nullish()
|
||||||
|
.transform((arr) => (arr ? arr.filter(Boolean) : []))
|
||||||
.transform((data) => {
|
.transform((data) => {
|
||||||
const now = dt().utc().format("YYYY-MM-DD")
|
const now = dt().utc().format("YYYY-MM-DD")
|
||||||
const filteredAlerts = data.filter((alert) => {
|
const filteredAlerts = data.filter((alert) => {
|
||||||
@@ -35,4 +37,3 @@ export const specialAlertsSchema = z
|
|||||||
displayInBookingFlow: alert.displayInBookingFlow,
|
displayInBookingFlow: alert.displayInBookingFlow,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.default([])
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
const specialNeedSchema = z.object({
|
|
||||||
details: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const specialNeedGroupSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
specialNeeds: z.array(specialNeedSchema),
|
|
||||||
})
|
|
||||||
@@ -1,25 +1,27 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
export const imageSizesSchema = z.object({
|
export const imageSizesSchema = z.object({
|
||||||
large: z.string(),
|
large: nullableStringValidator,
|
||||||
medium: z.string(),
|
medium: nullableStringValidator,
|
||||||
small: z.string(),
|
small: nullableStringValidator,
|
||||||
tiny: z.string(),
|
tiny: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const imageMetaDataSchema = z.object({
|
export const imageMetaDataSchema = z.object({
|
||||||
altText: z.string(),
|
altText: nullableStringValidator,
|
||||||
altText_En: z.string(),
|
altText_En: nullableStringValidator,
|
||||||
copyRight: z.string(),
|
copyRight: nullableStringValidator,
|
||||||
title: z.string(),
|
title: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|
||||||
const DEFAULT_IMAGE_OBJ = {
|
const DEFAULT_IMAGE_OBJ = {
|
||||||
metaData: {
|
metaData: {
|
||||||
title: "Default image",
|
|
||||||
altText: "Default image",
|
altText: "Default image",
|
||||||
altText_En: "Default image",
|
altText_En: "Default image",
|
||||||
copyRight: "Default image",
|
copyRight: "Default image",
|
||||||
|
title: "Default image",
|
||||||
},
|
},
|
||||||
imageSizes: {
|
imageSizes: {
|
||||||
tiny: "https://placehold.co/1280x720",
|
tiny: "https://placehold.co/1280x720",
|
||||||
@@ -31,11 +33,10 @@ const DEFAULT_IMAGE_OBJ = {
|
|||||||
|
|
||||||
export const imageSchema = z
|
export const imageSchema = z
|
||||||
.object({
|
.object({
|
||||||
metaData: imageMetaDataSchema,
|
|
||||||
imageSizes: imageSizesSchema,
|
imageSizes: imageSizesSchema,
|
||||||
|
metaData: imageMetaDataSchema,
|
||||||
})
|
})
|
||||||
.default(DEFAULT_IMAGE_OBJ)
|
.nullish()
|
||||||
.nullable()
|
|
||||||
.transform((val) => {
|
.transform((val) => {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
return DEFAULT_IMAGE_OBJ
|
return DEFAULT_IMAGE_OBJ
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
} from "./output"
|
} from "./output"
|
||||||
import { getHotel } from "./query"
|
import { getHotel } from "./query"
|
||||||
|
|
||||||
import type { Country } from "@/types/enums/country"
|
|
||||||
import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
|
import { PointOfInterestGroupEnum } from "@/types/enums/pointOfInterest"
|
||||||
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
import type { RequestOptionsWithOutBody } from "@/types/fetch"
|
||||||
import type { HotelDataWithUrl } from "@/types/hotel"
|
import type { HotelDataWithUrl } from "@/types/hotel"
|
||||||
|
|||||||
@@ -120,6 +120,19 @@ export function createRatesStore({
|
|||||||
|
|
||||||
return create<RatesState>()((set) => ({
|
return create<RatesState>()((set) => ({
|
||||||
actions: {
|
actions: {
|
||||||
|
closeSection(idx) {
|
||||||
|
return function () {
|
||||||
|
return set(
|
||||||
|
produce((state: RatesState) => {
|
||||||
|
if (state.rateSummary.length === state.booking.rooms.length) {
|
||||||
|
state.activeRoom = -1
|
||||||
|
} else {
|
||||||
|
state.activeRoom = idx + 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifyRate(idx) {
|
modifyRate(idx) {
|
||||||
return function () {
|
return function () {
|
||||||
return set(
|
return set(
|
||||||
@@ -201,10 +214,11 @@ export function createRatesStore({
|
|||||||
selectedRate.roomTypeCode
|
selectedRate.roomTypeCode
|
||||||
)
|
)
|
||||||
|
|
||||||
state.activeRoom =
|
if (state.rateSummary.length === state.booking.rooms.length) {
|
||||||
idx + 1 < state.booking.rooms.length
|
state.activeRoom = -1
|
||||||
? idx + 1
|
} else {
|
||||||
: state.booking.rooms.length
|
state.activeRoom = idx + 1
|
||||||
|
}
|
||||||
|
|
||||||
state.searchParams = new ReadonlyURLSearchParams(searchParams)
|
state.searchParams = new ReadonlyURLSearchParams(searchParams)
|
||||||
window.history.pushState(
|
window.history.pushState(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Hotel } from "@/types/hotel"
|
import type { Hotel } from "@/types/hotel"
|
||||||
|
|
||||||
export type CategorizedFilters = {
|
export type CategorizedFilters = {
|
||||||
facilityFilters: Hotel["detailedFacilities"]
|
facilityFilters: Hotel["detailedFacilities"]
|
||||||
@@ -9,15 +9,6 @@ export type HotelFiltersProps = {
|
|||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Filter = {
|
|
||||||
name: string
|
|
||||||
id: number
|
|
||||||
public: boolean
|
|
||||||
sortOrder: number
|
|
||||||
filter?: string
|
|
||||||
icon: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HotelFilterModalProps = {
|
export type HotelFilterModalProps = {
|
||||||
filters: CategorizedFilters
|
filters: CategorizedFilters
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import type { z } from "zod"
|
import type { z } from "zod"
|
||||||
|
|
||||||
import type { Coordinates } from "@/types/components/maps/coordinates"
|
import type { Coordinates } from "@/types/components/maps/coordinates"
|
||||||
import type { Location } from "@/types/trpc/routers/hotel/locations"
|
import type { Amenities } from "@/types/hotel"
|
||||||
import type { imageSchema } from "@/server/routers/hotels/schemas/image"
|
import type { imageSchema } from "@/server/routers/hotels/schemas/image"
|
||||||
import type { Child } from "../selectRate/selectRate"
|
|
||||||
import type { HotelData } from "./hotelCardListingProps"
|
import type { HotelData } from "./hotelCardListingProps"
|
||||||
import type { CategorizedFilters, Filter } from "./hotelFilters"
|
import type { CategorizedFilters } from "./hotelFilters"
|
||||||
import type {
|
import type {
|
||||||
AlternativeHotelsSearchParams,
|
AlternativeHotelsSearchParams,
|
||||||
SelectHotelSearchParams,
|
SelectHotelSearchParams,
|
||||||
@@ -39,7 +38,7 @@ export type HotelPin = {
|
|||||||
imageSizes: ImageSizes
|
imageSizes: ImageSizes
|
||||||
metaData: ImageMetaData
|
metaData: ImageMetaData
|
||||||
}[]
|
}[]
|
||||||
amenities: Filter[]
|
amenities: Amenities
|
||||||
ratings: number | null
|
ratings: number | null
|
||||||
operaId: string
|
operaId: string
|
||||||
facilityIds: number[]
|
facilityIds: number[]
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export type ToggleSidePeekProps = {
|
|||||||
hotelId: string
|
hotelId: string
|
||||||
roomTypeCode?: string
|
roomTypeCode?: string
|
||||||
intent?: "text" | "textInverted"
|
intent?: "text" | "textInverted"
|
||||||
|
title?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import type { ImageProps as NextImageProps } from "next/image"
|
import type { ImageProps as NextImageProps } from "next/image"
|
||||||
|
|
||||||
import type { ImageVaultAsset } from "./imageVault"
|
|
||||||
|
|
||||||
export interface FocalPoint {
|
export interface FocalPoint {
|
||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { SelectedRate, SelectedRoom } from "@/types/stores/rates"
|
|||||||
|
|
||||||
export interface RoomContextValue extends SelectedRoom {
|
export interface RoomContextValue extends SelectedRoom {
|
||||||
actions: {
|
actions: {
|
||||||
|
closeSection: () => void
|
||||||
modifyRate: () => void
|
modifyRate: () => void
|
||||||
selectFilter: (code: RoomPackageCodeEnum | undefined) => void
|
selectFilter: (code: RoomPackageCodeEnum | undefined) => void
|
||||||
selectRate: (rate: SelectedRate) => void
|
selectRate: (rate: SelectedRate) => void
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import type { z } from "zod"
|
import type { z } from "zod"
|
||||||
|
|
||||||
import type { hotelSchema } from "@/server/routers/hotels/output"
|
import type { hotelSchema } from "@/server/routers/hotels/output"
|
||||||
import type {
|
|
||||||
extraPageSchema,
|
|
||||||
facilitySchema,
|
|
||||||
transformAdditionalData,
|
|
||||||
} from "@/server/routers/hotels/schemas/additionalData"
|
|
||||||
import type { citySchema } from "@/server/routers/hotels/schemas/city"
|
import type { citySchema } from "@/server/routers/hotels/schemas/city"
|
||||||
import type { attributesSchema } from "@/server/routers/hotels/schemas/hotel"
|
import type { attributesSchema } from "@/server/routers/hotels/schemas/hotel"
|
||||||
import type { addressSchema } from "@/server/routers/hotels/schemas/hotel/address"
|
import type { addressSchema } from "@/server/routers/hotels/schemas/hotel/address"
|
||||||
import type { hotelContentSchema } from "@/server/routers/hotels/schemas/hotel/content"
|
import type { hotelContentSchema } from "@/server/routers/hotels/schemas/hotel/content"
|
||||||
import type { detailedFacilitiesSchema } from "@/server/routers/hotels/schemas/hotel/detailedFacility"
|
import type {
|
||||||
|
detailedFacilitiesSchema,
|
||||||
|
detailedFacilitySchema,
|
||||||
|
} from "@/server/routers/hotels/schemas/hotel/detailedFacility"
|
||||||
import type { checkinSchema } from "@/server/routers/hotels/schemas/hotel/facts"
|
import type { checkinSchema } from "@/server/routers/hotels/schemas/hotel/facts"
|
||||||
import type { healthFacilitySchema } from "@/server/routers/hotels/schemas/hotel/healthFacilities"
|
import type { healthFacilitySchema } from "@/server/routers/hotels/schemas/hotel/healthFacilities"
|
||||||
|
import type {
|
||||||
|
extraPageSchema,
|
||||||
|
transformAdditionalData,
|
||||||
|
} from "@/server/routers/hotels/schemas/hotel/include/additionalData"
|
||||||
|
import type { facilitySchema } from "@/server/routers/hotels/schemas/hotel/include/additionalData/facility"
|
||||||
import type { nearbyHotelsSchema } from "@/server/routers/hotels/schemas/hotel/include/nearbyHotels"
|
import type { nearbyHotelsSchema } from "@/server/routers/hotels/schemas/hotel/include/nearbyHotels"
|
||||||
import type {
|
import type {
|
||||||
openingHoursDetailsSchema,
|
openingHoursDetailsSchema,
|
||||||
@@ -35,6 +38,7 @@ export type City = Pick<CitySchema, "id" | "type"> & CitySchema["attributes"]
|
|||||||
export type FacilityData = z.output<typeof facilitySchema>
|
export type FacilityData = z.output<typeof facilitySchema>
|
||||||
export type Facility = FacilityData & { id: string }
|
export type Facility = FacilityData & { id: string }
|
||||||
export type ApiImage = z.output<typeof imageSchema>
|
export type ApiImage = z.output<typeof imageSchema>
|
||||||
|
export type DetailedFacility = z.output<typeof detailedFacilitySchema>
|
||||||
export type HealthFacility = z.output<typeof healthFacilitySchema>
|
export type HealthFacility = z.output<typeof healthFacilitySchema>
|
||||||
export type HealthFacilities = HealthFacility[]
|
export type HealthFacilities = HealthFacility[]
|
||||||
export type Hotel = z.output<typeof attributesSchema>
|
export type Hotel = z.output<typeof attributesSchema>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import type {
|
|||||||
} from "@/types/trpc/routers/hotel/roomAvailability"
|
} from "@/types/trpc/routers/hotel/roomAvailability"
|
||||||
|
|
||||||
interface Actions {
|
interface Actions {
|
||||||
|
closeSection: (idx: number) => () => void
|
||||||
modifyRate: (idx: number) => () => void
|
modifyRate: (idx: number) => () => void
|
||||||
selectFilter: (idx: number) => (code: RoomPackageCodeEnum | undefined) => void
|
selectFilter: (idx: number) => (code: RoomPackageCodeEnum | undefined) => void
|
||||||
selectRate: (idx: number) => (rate: SelectedRate) => void
|
selectRate: (idx: number) => (rate: SelectedRate) => void
|
||||||
|
|||||||
17
utils/zod/arrayValidator.ts
Normal file
17
utils/zod/arrayValidator.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { nullableStringValidator } from "./stringValidator"
|
||||||
|
|
||||||
|
import type { ZodObject, ZodRawShape } from "zod"
|
||||||
|
|
||||||
|
export function nullableArrayObjectValidator<T extends ZodRawShape>(
|
||||||
|
schema: ZodObject<T>
|
||||||
|
) {
|
||||||
|
return schema
|
||||||
|
.array()
|
||||||
|
.nullish()
|
||||||
|
.transform((arr) => (arr ? arr.filter(Boolean) : []))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nullableArrayStringValidator = nullableStringValidator
|
||||||
|
.array()
|
||||||
|
.nullish()
|
||||||
|
.transform((arr) => (arr ? arr.filter(Boolean) : []))
|
||||||
@@ -5,6 +5,12 @@ export const nullableStringValidator = z
|
|||||||
.nullish()
|
.nullish()
|
||||||
.transform((str) => (str ? str : ""))
|
.transform((str) => (str ? str : ""))
|
||||||
|
|
||||||
|
export const nullableStringEmailValidator = z
|
||||||
|
.string()
|
||||||
|
.email()
|
||||||
|
.nullish()
|
||||||
|
.transform((str) => (str ? str : ""))
|
||||||
|
|
||||||
export const nullableStringUrlValidator = z
|
export const nullableStringUrlValidator = z
|
||||||
.string()
|
.string()
|
||||||
.url()
|
.url()
|
||||||
|
|||||||
Reference in New Issue
Block a user