feat(SW-1012): Started implementing restaurant data

This commit is contained in:
Erik Tiekstra
2024-12-02 13:47:05 +01:00
committed by Fredrik Thorsson
parent 05006506f0
commit 63a77b215d
17 changed files with 329 additions and 13 deletions

View File

@@ -0,0 +1,40 @@
import Body from "@/components/TempDesignSystem/Text/Body"
import { RestaurantBarOpeningHoursProps } from "@/types/components/hotelPage/sidepeek/restaurantBar"
import { RestaurantOpeningHoursDay } from "@/types/hotel"
export default function OpeningHours({
openingHours,
alternateOpeningHours,
}: RestaurantBarOpeningHoursProps) {
const days: (keyof typeof openingHours)[] = [
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday",
]
return (
<div>
<Body textTransform="bold" asChild>
<h5>{openingHours.name}</h5>
</Body>
{days.map((day) => {
const today = openingHours[day] as RestaurantOpeningHoursDay
return today ? (
<Body color="uiTextHighContrast">
{day}:{" "}
{today.isClosed
? "Closed"
: today.alwaysOpen
? "Always open"
: `${today.openingTime}-${today.closingTime}`}
</Body>
) : null
})}
</div>
)
}

View File

@@ -1 +0,0 @@
export default function RestaurantSidepeek() {}

View File

@@ -0,0 +1,101 @@
import NextLink from "next/link"
import { OpenInNewSmallIcon } from "@/components/Icons"
import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import OpeningHours from "../OpeningHours"
import styles from "./restaurantBarItem.module.css"
import { RestaurantBarItemProps } from "@/types/components/hotelPage/sidepeek/restaurantBar"
export default function RestaurantBarItem({
restaurant,
}: RestaurantBarItemProps) {
const { name, openingDetails, menus } = restaurant
const { images, bookTableUrl } = restaurant.content
const visibleImages = restaurant.content.images.slice(0, 2)
const imageWidth = images.length === 2 ? 240 : 496
// TODO: Not defined where this should lead.
const ctaUrl = ""
console.log({ bookTableUrl: bookTableUrl })
return (
<div className={styles.restaurantBarItem}>
<div className={styles.stickyHeading}>
<Subtitle type="one" color="burgundy" asChild>
<h3>{name}</h3>
</Subtitle>
</div>
<div className={styles.imageWrapper}>
{visibleImages.map(({ metaData, imageSizes }) => (
<Image
key={imageSizes.tiny}
src={imageSizes.tiny}
alt={metaData.altText}
width={imageWidth}
height={240}
className={styles.image}
/>
))}
</div>
{openingDetails.length ? (
<div className={styles.content}>
<Subtitle type="two" asChild>
<h4>Opening Hours</h4>
</Subtitle>
{openingDetails.map((details) => (
<OpeningHours
key={details.openingHours.name}
openingHours={details.openingHours}
alternateOpeningHours={details.alternateOpeningHours}
/>
))}
</div>
) : null}
{menus.length ? (
<div className={styles.content}>
<Subtitle type="two" asChild>
<h4>Menus</h4>
</Subtitle>
<ul className={styles.menuList}>
{menus.map(({ name, url }) => (
<li key={name}>
<Link
href={url}
target="_blank"
textDecoration="underline"
variant="icon"
color="baseTextMediumContrast"
className={styles.menuLink}
>
{name}
<OpenInNewSmallIcon />
</Link>
</li>
))}
</ul>
</div>
) : null}
{bookTableUrl || ctaUrl ? (
<div className={styles.ctaWrapper}>
{bookTableUrl ? (
<Button fullWidth theme="base" intent="primary" asChild>
<a href={bookTableUrl} target="_blank">
Book a table online
</a>
</Button>
) : null}
{ctaUrl ? (
<Button fullWidth theme="base" intent="secondary" asChild>
<NextLink href={ctaUrl}>Discover {name}</NextLink>
</Button>
) : null}
</div>
) : null}
</div>
)
}

View File

@@ -0,0 +1,49 @@
.restaurantBarItem {
display: grid;
gap: var(--Spacing-x3);
}
.stickyHeading {
position: sticky;
top: 0;
}
/* Hack to make it look like the heading has a background which covers the top of the sidepeek */
.stickyHeading::before {
content: "";
position: absolute;
margin-top: calc(-1 * var(--Spacing-x4));
background-color: var(--Base-Background-Primary-Normal);
z-index: -1;
width: 100%;
top: 0;
bottom: -16px;
}
.imageWrapper {
display: flex;
align-items: center;
gap: var(--Spacing-x2);
}
.image {
border-radius: var(--Corner-radius-Medium);
overflow: hidden;
width: 100%;
object-fit: cover;
}
.content {
display: grid;
gap: var(--Spacing-x1);
}
.menuList {
list-style: none;
display: grid;
gap: var(--Spacing-x1);
}
.ctaWrapper {
display: grid;
gap: var(--Spacing-x2);
}

View File

@@ -4,6 +4,8 @@ import SidePeek from "@/components/TempDesignSystem/SidePeek"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import RestaurantBarItem from "./RestaurantBarItem"
import styles from "./restaurantBar.module.css"
import type { RestaurantBarSidePeekProps } from "@/types/components/hotelPage/sidepeek/restaurantBar"
@@ -12,7 +14,6 @@ export default async function RestaurantBarSidePeek({
restaurants,
}: RestaurantBarSidePeekProps) {
const lang = getLang()
const intl = await getIntl()
return (
@@ -22,8 +23,8 @@ export default async function RestaurantBarSidePeek({
>
<div className={styles.content}>
{restaurants.map((restaurant) => (
<div key={restaurant.id}>
<h3>{restaurant.name}</h3>
<div key={restaurant.id} className={styles.item}>
<RestaurantBarItem restaurant={restaurant} />
</div>
))}
</div>

View File

@@ -0,0 +1,9 @@
.content {
display: grid;
gap: var(--Spacing-x4);
}
.item:not(:last-child) {
border-bottom: 1px solid var(--Base-Border-Subtle);
padding-bottom: var(--Spacing-x4);
}

View File

@@ -0,0 +1,27 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function OpenInNewIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
d="M5.09998 20.775C4.58434 20.775 4.14293 20.5914 3.77575 20.2242C3.40857 19.857 3.22498 19.4156 3.22498 18.9V5.09998C3.22498 4.58434 3.40857 4.14293 3.77575 3.77575C4.14293 3.40857 4.58434 3.22498 5.09998 3.22498H10.9875C11.2458 3.22498 11.4666 3.31664 11.65 3.49998C11.8333 3.68331 11.925 3.90414 11.925 4.16248C11.925 4.42081 11.8333 4.64164 11.65 4.82498C11.4666 5.00831 11.2458 5.09998 10.9875 5.09998H5.09998V18.9H18.9V13.0125C18.9 12.7541 18.9916 12.5333 19.175 12.35C19.3583 12.1666 19.5791 12.075 19.8375 12.075C20.0958 12.075 20.3166 12.1666 20.5 12.35C20.6833 12.5333 20.775 12.7541 20.775 13.0125V18.9C20.775 19.4156 20.5914 19.857 20.2242 20.2242C19.857 20.5914 19.4156 20.775 18.9 20.775H5.09998ZM18.9 6.41248L10.3875 14.925C10.2125 15.1 9.99789 15.1875 9.74373 15.1875C9.48956 15.1875 9.27081 15.0958 9.08748 14.9125C8.90414 14.7291 8.81248 14.5104 8.81248 14.2562C8.81248 14.0021 8.90311 13.7843 9.08438 13.6031L17.5875 5.09998H14.7375C14.4791 5.09998 14.2583 5.00831 14.075 4.82498C13.8916 4.64164 13.8 4.42081 13.8 4.16248C13.8 3.90414 13.8916 3.68331 14.075 3.49998C14.2583 3.31664 14.4791 3.22498 14.7375 3.22498H20.775V9.26248C20.775 9.52081 20.6833 9.74164 20.5 9.92498C20.3166 10.1083 20.0958 10.2 19.8375 10.2C19.5791 10.2 19.3583 10.1083 19.175 9.92498C18.9916 9.74164 18.9 9.52081 18.9 9.26248V6.41248Z"
fill="#26201E"
/>
</svg>
)
}

View File

@@ -0,0 +1,27 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function OpenInNewSmallIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
{...props}
>
<path
d="M4.58333 16.8125C4.19949 16.8125 3.87088 16.6758 3.59752 16.4025C3.32417 16.1291 3.1875 15.8005 3.1875 15.4167V4.58333C3.1875 4.19947 3.32417 3.87087 3.59752 3.59752C3.87088 3.32417 4.19949 3.1875 4.58333 3.1875H9.9375V4.58333H4.58333V15.4167H15.4167V10.0625H16.8125V15.4167C16.8125 15.8005 16.6758 16.1291 16.4025 16.4025C16.1291 16.6758 15.8005 16.8125 15.4167 16.8125H4.58333ZM8.09375 12.8958L7.10417 11.9062L14.4271 4.58333H11.8333V3.1875H16.8125V8.16667H15.4167V5.57292L8.09375 12.8958Z"
fill="#26201E"
/>
</svg>
)
}

View File

@@ -79,6 +79,8 @@ import {
NatureIcon,
NightlifeIcon,
NoSmokingIcon,
OpenInNewIcon,
OpenInNewSmallIcon,
OutdoorFurnitureIcon,
ParkingIcon,
People2Icon,
@@ -280,6 +282,10 @@ export function getIconByIconName(
return NightlifeIcon
case IconName.NoSmoking:
return NoSmokingIcon
case IconName.OpenInNew:
return OpenInNewIcon
case IconName.OpenInNewSmall:
return OpenInNewSmallIcon
case IconName.OutdoorFurniture:
return OutdoorFurnitureIcon
case IconName.Parking:

View File

@@ -127,6 +127,8 @@ export { default as NatureIcon } from "./Nature"
export { default as NightlifeIcon } from "./Nightlife"
export { default as NoBreakfastIcon } from "./NoBreakfast"
export { default as NoSmokingIcon } from "./NoSmoking"
export { default as OpenInNewIcon } from "./OpenInNew"
export { default as OpenInNewSmallIcon } from "./OpenInNewSmall"
export { default as OutdoorFurnitureIcon } from "./OutdoorFurniture"
export { default as ParkingIcon } from "./Parking"
export { default as People2Icon } from "./People2"

View File

@@ -174,12 +174,14 @@
}
.peach80 *,
.peach80 * {
.baseTextMediumContrast * {
fill: var(--Base-Text-Medium-contrast);
}
.peach80:hover *,
.peach80:active * {
.peach80:active *,
.baseTextMediumContrast:hover *,
.baseTextMediumContrast:active * {
fill: var(--Base-Text-High-contrast);
}

View File

@@ -7,14 +7,14 @@ const restaurantPriceSchema = z.object({
currency: z.string(),
amount: z.number(),
})
const restaurantDaySchema = z.object({
export const restaurantDaySchema = z.object({
sortOrder: z.number(),
alwaysOpen: z.boolean(),
isClosed: z.boolean(),
openingTime: z.string(),
closingTime: z.string(),
})
const restaurantOpeningHoursSchema = z.object({
export const restaurantOpeningHoursSchema = z.object({
isActive: z.boolean(),
name: z.string().optional(),
openingTime: z.string().optional(),
@@ -52,7 +52,7 @@ export const restaurantSchema = z
url: z.string(),
})
)
.optional(),
.default([]),
openingDetails: z.array(restaurantOpeningDetailSchema),
content: z.object({
images: z.array(imageSchema),

View File

@@ -1,5 +1,14 @@
import type { RestaurantData } from "@/types/hotel"
import type { RestaurantData, RestaurantOpeningHours } from "@/types/hotel"
export interface RestaurantBarSidePeekProps {
restaurants: RestaurantData[]
}
export interface RestaurantBarItemProps {
restaurant: RestaurantData
}
export interface RestaurantBarOpeningHoursProps {
openingHours: RestaurantOpeningHours
alternateOpeningHours?: RestaurantOpeningHours
}

View File

@@ -0,0 +1,11 @@
import { RouterOutput } from "@/lib/trpc/client"
export interface RoomProps {
booking: RouterOutput["booking"]["confirmation"]["booking"]
img: NonNullable<
RouterOutput["booking"]["confirmation"]["hotel"]["included"]
>["rooms"][number]["images"][number]
roomName: NonNullable<
RouterOutput["booking"]["confirmation"]["hotel"]["included"]
>["rooms"][number]["name"]
}

View File

@@ -87,6 +87,8 @@ export enum IconName {
Nature = "Nature",
Nightlife = "Nightlife",
NoSmoking = "NoSmoking",
OpenInNew = "OpenInNew",
OpenInNewSmall = "OpenInNewSmall",
OutdoorFurniture = "OutdoorFurniture",
Parking = "Parking",
People2 = "People2",

View File

@@ -8,10 +8,14 @@ import {
pointOfInterestSchema,
} from "@/server/routers/hotels/output"
import { imageSchema } from "@/server/routers/hotels/schemas/image"
import { restaurantSchema } from "@/server/routers/hotels/schemas/restaurants"
import {
restaurantDaySchema,
restaurantOpeningHoursSchema,
restaurantSchema,
} from "@/server/routers/hotels/schemas/restaurants"
import { roomSchema } from "@/server/routers/hotels/schemas/room"
export type HotelData = z.infer<typeof getHotelDataSchema>
export type HotelData = z.output<typeof getHotelDataSchema>
export type Hotel = HotelData["data"]["attributes"]
export type HotelAddress = HotelData["data"]["attributes"]["address"]
@@ -24,7 +28,11 @@ export type HotelTripAdvisor =
| undefined
export type RoomData = z.infer<typeof roomSchema>
export type RestaurantData = z.infer<typeof restaurantSchema>
export type RestaurantData = z.output<typeof restaurantSchema>
export type RestaurantOpeningHours = z.output<
typeof restaurantOpeningHoursSchema
>
export type RestaurantOpeningHoursDay = z.output<typeof restaurantDaySchema>
export type GalleryImage = z.infer<typeof imageSchema>
export type CheckInData = z.infer<typeof checkinSchema>

View File

@@ -0,0 +1,23 @@
import type { RouterOutput } from "@/lib/trpc/client"
export function getBookedHotelRoom(
hotel: RouterOutput["booking"]["confirmation"]["hotel"],
roomTypeCode: string
) {
const room = hotel.included?.rooms?.find((include) => {
return include.roomTypes.find((roomType) => roomType.code === roomTypeCode)
})
if (!room) {
return null
}
const bedType = room.roomTypes.find(
(roomType) => roomType.code === roomTypeCode
)
if (!bedType) {
return null
}
return {
...room,
bedType,
}
}