feat(SW-341): add hotels listing sidepeeks
This commit is contained in:
@@ -48,12 +48,6 @@
|
|||||||
gap: var(--Spacing-x-half);
|
gap: var(--Spacing-x-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
|
||||||
display: flex;
|
|
||||||
padding: var(--Spacing-x2) var(--Spacing-x0);
|
|
||||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
.prices {
|
.prices {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -115,7 +109,7 @@
|
|||||||
padding-bottom: var(--Spacing-x2);
|
padding-bottom: var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.detailsButton {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
|
import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data"
|
||||||
import {
|
import { PriceTagIcon, ScandicLogoIcon } from "@/components/Icons"
|
||||||
ChevronRightIcon,
|
|
||||||
PriceTagIcon,
|
|
||||||
ScandicLogoIcon,
|
|
||||||
} from "@/components/Icons"
|
|
||||||
import TripAdvisorIcon from "@/components/Icons/TripAdvisor"
|
import TripAdvisorIcon from "@/components/Icons/TripAdvisor"
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
@@ -14,13 +8,16 @@ import Link from "@/components/TempDesignSystem/Link"
|
|||||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import ReadMore from "../ReadMore"
|
||||||
|
|
||||||
import styles from "./hotelCard.module.css"
|
import styles from "./hotelCard.module.css"
|
||||||
|
|
||||||
import { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps"
|
import { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps"
|
||||||
|
|
||||||
export default function HotelCard({ hotel }: HotelCardProps) {
|
export default async function HotelCard({ hotel }: HotelCardProps) {
|
||||||
const intl = useIntl()
|
const intl = await getIntl()
|
||||||
|
|
||||||
const { hotelData } = hotel
|
const { hotelData } = hotel
|
||||||
const { price } = hotel
|
const { price } = hotel
|
||||||
@@ -51,7 +48,7 @@ export default function HotelCard({ hotel }: HotelCardProps) {
|
|||||||
<Title as="h4" textTransform="capitalize">
|
<Title as="h4" textTransform="capitalize">
|
||||||
{hotelData.name}
|
{hotelData.name}
|
||||||
</Title>
|
</Title>
|
||||||
<Footnote color="textMediumContrast" className={styles.adress}>
|
<Footnote color="textMediumContrast">
|
||||||
{`${hotelData.address.streetAddress}, ${hotelData.address.city}`}
|
{`${hotelData.address.streetAddress}, ${hotelData.address.city}`}
|
||||||
</Footnote>
|
</Footnote>
|
||||||
<Footnote color="textMediumContrast">
|
<Footnote color="textMediumContrast">
|
||||||
@@ -70,10 +67,7 @@ export default function HotelCard({ hotel }: HotelCardProps) {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Link href="#" color="burgundy" className={styles.link}>
|
<ReadMore hotelId={hotelData.operaId} hotel={hotelData} />
|
||||||
{intl.formatMessage({ id: "See hotel details" })}
|
|
||||||
<ChevronRightIcon color="burgundy" />
|
|
||||||
</Link>
|
|
||||||
</section>
|
</section>
|
||||||
<section className={styles.prices}>
|
<section className={styles.prices}>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
|
||||||
import HotelCard from "../HotelCard"
|
import HotelCard from "../HotelCard"
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
.wrapper {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
font-family: var(--typography-Body-Regular-fontFamily);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address,
|
||||||
|
.contactInfo {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
grid-template-rows: subgrid;
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
grid-row: 1 / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contactInfo > li {
|
||||||
|
font-style: normal;
|
||||||
|
list-style-type: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soMeIcons {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecoLabel {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
column-gap: var(--Spacing-x-one-and-half);
|
||||||
|
grid-column: 2 / 3;
|
||||||
|
grid-row: 3 / 4;
|
||||||
|
font-size: var(--typography-Footnote-Regular-fontSize);
|
||||||
|
line-height: ();
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecoLabelText {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
85
components/HotelReservation/ReadMore/Contact/index.tsx
Normal file
85
components/HotelReservation/ReadMore/Contact/index.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import FacebookIcon from "@/components/Icons/Facebook"
|
||||||
|
import InstagramIcon from "@/components/Icons/Instagram"
|
||||||
|
import Image from "@/components/Image"
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
import styles from "./contact.module.css"
|
||||||
|
|
||||||
|
import { ContactProps } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||||
|
|
||||||
|
export default function Contact({ hotel }: ContactProps) {
|
||||||
|
const lang = useLang()
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.wrapper}>
|
||||||
|
<address className={styles.address}>
|
||||||
|
<ul className={styles.contactInfo}>
|
||||||
|
<li>
|
||||||
|
<span className={styles.heading}>
|
||||||
|
{intl.formatMessage({ id: "Address" })}
|
||||||
|
</span>
|
||||||
|
<span>{hotel.address.streetAddress}</span>
|
||||||
|
<span>{hotel.address.city}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={styles.heading}>
|
||||||
|
{intl.formatMessage({ id: "Driving directions" })}
|
||||||
|
</span>
|
||||||
|
<Link href="#">{intl.formatMessage({ id: "Google Maps" })}</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={styles.heading}>
|
||||||
|
{intl.formatMessage({ id: "Email" })}
|
||||||
|
</span>
|
||||||
|
<Link href={`mailto:${hotel.contactInformation.email}`}>
|
||||||
|
{hotel.contactInformation.email}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={styles.heading}>
|
||||||
|
{intl.formatMessage({ id: "Contact us" })}
|
||||||
|
</span>
|
||||||
|
<Link href={`tel:${hotel.contactInformation.phoneNumber}`}>
|
||||||
|
{hotel.contactInformation.phoneNumber}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={styles.heading}>
|
||||||
|
{intl.formatMessage({ id: "Follow us" })}
|
||||||
|
</span>
|
||||||
|
<div className={styles.soMeIcons}>
|
||||||
|
<Link href="#" target="_blank">
|
||||||
|
<InstagramIcon color="burgundy" />
|
||||||
|
</Link>
|
||||||
|
<Link href="#" target="_blank">
|
||||||
|
<FacebookIcon color="burgundy" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</address>
|
||||||
|
{hotel.hotelFacts.ecoLabels.nordicEcoLabel ? (
|
||||||
|
<div className={styles.ecoLabel}>
|
||||||
|
<Image
|
||||||
|
height={38}
|
||||||
|
width={38}
|
||||||
|
alt={intl.formatMessage({ id: "Nordic Swan Ecolabel" })}
|
||||||
|
src={`/_static/img/icons/swan-eco/swan_eco_dark_${lang}.png`}
|
||||||
|
/>
|
||||||
|
<div className={styles.ecoLabelText}>
|
||||||
|
<span>{intl.formatMessage({ id: "Nordic Swan Ecolabel" })}</span>
|
||||||
|
<span>
|
||||||
|
{hotel.hotelFacts.ecoLabels.svanenEcoLabelCertificateNumber}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
119
components/HotelReservation/ReadMore/index.tsx
Normal file
119
components/HotelReservation/ReadMore/index.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { ChevronRightIcon } from "@/components/Icons"
|
||||||
|
import Accordion from "@/components/TempDesignSystem/Accordion"
|
||||||
|
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
|
||||||
|
import Contact from "./Contact"
|
||||||
|
|
||||||
|
import styles from "./readMore.module.css"
|
||||||
|
|
||||||
|
import {
|
||||||
|
DetailedAmenity,
|
||||||
|
ParkingProps,
|
||||||
|
ReadMoreProps,
|
||||||
|
} from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||||
|
import { Hotel } from "@/types/hotel"
|
||||||
|
|
||||||
|
function getAmenitiesList(hotel: Hotel) {
|
||||||
|
const detailedAmenities: DetailedAmenity[] = Object.entries(
|
||||||
|
hotel.hotelFacts.hotelFacilityDetail
|
||||||
|
).map(([key, value]) => ({ name: key, ...value }))
|
||||||
|
|
||||||
|
// Remove Parking facilities since parking accordion is based on hotel.parking
|
||||||
|
const simpleAmenities = hotel.detailedFacilities.filter(
|
||||||
|
(facility) => !facility.name.startsWith("Parking")
|
||||||
|
)
|
||||||
|
return [...detailedAmenities, ...simpleAmenities]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReadMore({ hotel, hotelId }: ReadMoreProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const [sidePeekOpen, setSidePeekOpen] = useState(false)
|
||||||
|
|
||||||
|
const amenitiesList = getAmenitiesList(hotel)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onPress={() => {
|
||||||
|
setSidePeekOpen(true)
|
||||||
|
}}
|
||||||
|
intent={"text"}
|
||||||
|
color="burgundy"
|
||||||
|
className={styles.detailsButton}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "See hotel details" })}
|
||||||
|
<ChevronRightIcon color="burgundy" />
|
||||||
|
</Button>
|
||||||
|
<SidePeek
|
||||||
|
title={hotel.name}
|
||||||
|
isOpen={sidePeekOpen}
|
||||||
|
contentKey={`${hotelId}`}
|
||||||
|
handleClose={() => {
|
||||||
|
setSidePeekOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Subtitle>
|
||||||
|
{intl.formatMessage({ id: "Practical information" })}
|
||||||
|
</Subtitle>
|
||||||
|
<Contact hotel={hotel} />
|
||||||
|
<Accordion>
|
||||||
|
{/* parking */}
|
||||||
|
{hotel.parking.length ? (
|
||||||
|
<AccordionItem title={intl.formatMessage({ id: "Parking" })}>
|
||||||
|
{hotel.parking.map((p) => (
|
||||||
|
<Parking key={p.name} parking={p} />
|
||||||
|
))}
|
||||||
|
</AccordionItem>
|
||||||
|
) : null}
|
||||||
|
<AccordionItem title={intl.formatMessage({ id: "Accessibility" })}>
|
||||||
|
TODO: What content should be in the accessibility section?
|
||||||
|
</AccordionItem>
|
||||||
|
{amenitiesList.map((amenity) => {
|
||||||
|
return "description" in amenity ? (
|
||||||
|
<AccordionItem key={amenity.name} title={amenity.heading}>
|
||||||
|
{amenity.description}
|
||||||
|
</AccordionItem>
|
||||||
|
) : (
|
||||||
|
<div key={amenity.id} className={styles.amenity}>
|
||||||
|
{amenity.name}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Accordion>
|
||||||
|
{/* TODO: handle linking to Hotel Page */}
|
||||||
|
<Button theme={"base"}>To the hotel</Button>
|
||||||
|
</div>
|
||||||
|
</SidePeek>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Parking({ parking }: ParkingProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Body>{`${intl.formatMessage({ id: parking.type })} (${parking.name})`}</Body>
|
||||||
|
<ul className={styles.list}>
|
||||||
|
<li>
|
||||||
|
{`${intl.formatMessage({
|
||||||
|
id: "Number of charging points for electric cars",
|
||||||
|
})}: ${parking.numberOfChargingSpaces}`}
|
||||||
|
</li>
|
||||||
|
<li>{`${intl.formatMessage({ id: "Parking can be reserved in advance" })}: ${parking.canMakeReservation ? intl.formatMessage({ id: "Yes" }) : intl.formatMessage({ id: "No" })}`}</li>
|
||||||
|
<li>{`${intl.formatMessage({ id: "Number of parking spots" })}: ${parking.numberOfParkingSpots}`}</li>
|
||||||
|
<li>{`${intl.formatMessage({ id: "Distance to hotel" })}: ${parking.distanceToHotel}`}</li>
|
||||||
|
<li>{`${intl.formatMessage({ id: "Address" })}: ${parking.address}`}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
25
components/HotelReservation/ReadMore/readMore.module.css
Normal file
25
components/HotelReservation/ReadMore/readMore.module.css
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
.detailsButton {
|
||||||
|
align-self: start;
|
||||||
|
border-radius: 0;
|
||||||
|
height: auto;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.amenity {
|
||||||
|
font-family: var(--typography-Body-Regular-fontFamily);
|
||||||
|
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||||
|
/* padding set to align with AccordionItem which has a different composition */
|
||||||
|
padding: var(--Spacing-x2)
|
||||||
|
calc(var(--Spacing-x1) + var(--Spacing-x-one-and-half));
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
font-family: var(--typography-Body-Regular-fontFamily);
|
||||||
|
list-style: inside;
|
||||||
|
}
|
||||||
@@ -82,6 +82,11 @@ export default function Link({
|
|||||||
// track navigation nor start a router transition.
|
// track navigation nor start a router transition.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (href.startsWith("tel:") || href.startsWith("mailto:")) {
|
||||||
|
// If href contains tel or mailto protocols we don't want to
|
||||||
|
// track navigation nor start a router transition.
|
||||||
|
return
|
||||||
|
}
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
trackPageViewStart()
|
trackPageViewStart()
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useIsSSR } from "@react-aria/ssr"
|
import { useIsSSR } from "@react-aria/ssr"
|
||||||
import { useContext } from "react"
|
import { useContext, useState } from "react"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
@@ -29,6 +29,15 @@ function SidePeek({
|
|||||||
}: React.PropsWithChildren<SidePeekProps>) {
|
}: React.PropsWithChildren<SidePeekProps>) {
|
||||||
const isSSR = useIsSSR()
|
const isSSR = useIsSSR()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const [rootDiv, setRootDiv] = useState<HTMLDivElement | undefined>(undefined)
|
||||||
|
|
||||||
|
function setRef(node: HTMLDivElement | null) {
|
||||||
|
if (node) {
|
||||||
|
setRootDiv(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const context = useContext(SidePeekContext)
|
const context = useContext(SidePeekContext)
|
||||||
function onClose() {
|
function onClose() {
|
||||||
const closeHandler = handleClose || context?.handleClose
|
const closeHandler = handleClose || context?.handleClose
|
||||||
@@ -44,42 +53,45 @@ function SidePeek({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<DialogTrigger>
|
<div ref={setRef}>
|
||||||
<ModalOverlay
|
<DialogTrigger>
|
||||||
className={styles.overlay}
|
<ModalOverlay
|
||||||
isOpen={isOpen || contentKey === context?.activeSidePeek}
|
UNSTABLE_portalContainer={rootDiv}
|
||||||
onOpenChange={onClose}
|
className={styles.overlay}
|
||||||
isDismissable
|
isOpen={isOpen || contentKey === context?.activeSidePeek}
|
||||||
>
|
onOpenChange={onClose}
|
||||||
<Modal className={styles.modal}>
|
isDismissable
|
||||||
<Dialog className={styles.dialog}>
|
>
|
||||||
<aside className={styles.sidePeek}>
|
<Modal className={styles.modal}>
|
||||||
<header className={styles.header}>
|
<Dialog className={styles.dialog}>
|
||||||
{title ? (
|
<aside className={styles.sidePeek}>
|
||||||
<Title
|
<header className={styles.header}>
|
||||||
color="burgundy"
|
{title ? (
|
||||||
textTransform="uppercase"
|
<Title
|
||||||
level="h2"
|
color="burgundy"
|
||||||
as="h3"
|
textTransform="uppercase"
|
||||||
|
level="h2"
|
||||||
|
as="h3"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Title>
|
||||||
|
) : null}
|
||||||
|
<Button
|
||||||
|
aria-label={intl.formatMessage({ id: "Close" })}
|
||||||
|
className={styles.closeButton}
|
||||||
|
intent="text"
|
||||||
|
onPress={onClose}
|
||||||
>
|
>
|
||||||
{title}
|
<CloseIcon color="burgundy" height={32} width={32} />
|
||||||
</Title>
|
</Button>
|
||||||
) : null}
|
</header>
|
||||||
<Button
|
<div className={styles.sidePeekContent}>{children}</div>
|
||||||
aria-label={intl.formatMessage({ id: "Close" })}
|
</aside>
|
||||||
className={styles.closeButton}
|
</Dialog>
|
||||||
intent="text"
|
</Modal>
|
||||||
onPress={onClose}
|
</ModalOverlay>
|
||||||
>
|
</DialogTrigger>
|
||||||
<CloseIcon color="burgundy" height={32} width={32} />
|
</div>
|
||||||
</Button>
|
|
||||||
</header>
|
|
||||||
<div className={styles.sidePeekContent}>{children}</div>
|
|
||||||
</aside>
|
|
||||||
</Dialog>
|
|
||||||
</Modal>
|
|
||||||
</ModalOverlay>
|
|
||||||
</DialogTrigger>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
|
|
||||||
.sidePeekContent {
|
.sidePeekContent {
|
||||||
padding: var(--Spacing-x4);
|
padding: var(--Spacing-x4);
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
.modal {
|
.modal {
|
||||||
@@ -94,8 +95,4 @@
|
|||||||
.modal[data-exiting] {
|
.modal[data-exiting] {
|
||||||
animation: slide-in 250ms reverse;
|
animation: slide-in 250ms reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ const parkingPricingSchema = z.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const parkingSchema = z.object({
|
export const parkingSchema = z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
address: z.string().optional(),
|
address: z.string().optional(),
|
||||||
|
|||||||
@@ -1,4 +1,25 @@
|
|||||||
|
import { Hotel, ParkingData } from "@/types/hotel"
|
||||||
|
|
||||||
export enum AvailabilityEnum {
|
export enum AvailabilityEnum {
|
||||||
Available = "Available",
|
Available = "Available",
|
||||||
NotAvailable = "NotAvailable",
|
NotAvailable = "NotAvailable",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DetailedAmenity {
|
||||||
|
name: string
|
||||||
|
heading: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReadMoreProps {
|
||||||
|
hotelId: string
|
||||||
|
hotel: Hotel
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContactProps {
|
||||||
|
hotel: Hotel
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParkingProps {
|
||||||
|
parking: ParkingData
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
getHotelDataSchema,
|
getHotelDataSchema,
|
||||||
|
parkingSchema,
|
||||||
pointOfInterestSchema,
|
pointOfInterestSchema,
|
||||||
roomSchema,
|
roomSchema,
|
||||||
} from "@/server/routers/hotels/output"
|
} from "@/server/routers/hotels/output"
|
||||||
@@ -49,3 +50,5 @@ export enum PointOfInterestGroupEnum {
|
|||||||
PARKING = "Parking",
|
PARKING = "Parking",
|
||||||
SHOPPING_DINING = "Shopping & Dining",
|
SHOPPING_DINING = "Shopping & Dining",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ParkingData = z.infer<typeof parkingSchema>
|
||||||
|
|||||||
Reference in New Issue
Block a user