diff --git a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/OpeningHours/index.tsx b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/OpeningHours/index.tsx new file mode 100644 index 000000000..6d31febdc --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/OpeningHours/index.tsx @@ -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 ( +
+ +
{openingHours.name}
+ + {days.map((day) => { + const today = openingHours[day] as RestaurantOpeningHoursDay + return today ? ( + + {day}:{" "} + {today.isClosed + ? "Closed" + : today.alwaysOpen + ? "Always open" + : `${today.openingTime}-${today.closingTime}`} + + ) : null + })} +
+ ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/Restaurant/index.tsx b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/Restaurant/index.tsx deleted file mode 100644 index b7be3faa4..000000000 --- a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/Restaurant/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export default function RestaurantSidepeek() {} diff --git a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/RestaurantBarItem/index.tsx b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/RestaurantBarItem/index.tsx new file mode 100644 index 000000000..585ef67a9 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/RestaurantBarItem/index.tsx @@ -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 ( +
+
+ +

{name}

+
+
+
+ {visibleImages.map(({ metaData, imageSizes }) => ( + {metaData.altText} + ))} +
+ {openingDetails.length ? ( +
+ +

Opening Hours

+
+ {openingDetails.map((details) => ( + + ))} +
+ ) : null} + {menus.length ? ( +
+ +

Menus

+
+ +
+ ) : null} + {bookTableUrl || ctaUrl ? ( +
+ {bookTableUrl ? ( + + ) : null} + {ctaUrl ? ( + + ) : null} +
+ ) : null} +
+ ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/RestaurantBarItem/restaurantBarItem.module.css b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/RestaurantBarItem/restaurantBarItem.module.css new file mode 100644 index 000000000..49f893fef --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/RestaurantBarItem/restaurantBarItem.module.css @@ -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); +} diff --git a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/index.tsx b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/index.tsx index 9b1e2501a..44ec4e7eb 100644 --- a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/index.tsx @@ -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({ >
{restaurants.map((restaurant) => ( -
-

{restaurant.name}

+
+
))}
diff --git a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/restaurantBar.module.css b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/restaurantBar.module.css index e69de29bb..3da2da7d8 100644 --- a/components/ContentType/HotelPage/SidePeeks/RestaurantBar/restaurantBar.module.css +++ b/components/ContentType/HotelPage/SidePeeks/RestaurantBar/restaurantBar.module.css @@ -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); +} diff --git a/components/Icons/OpenInNew.tsx b/components/Icons/OpenInNew.tsx new file mode 100644 index 000000000..fa42ddb24 --- /dev/null +++ b/components/Icons/OpenInNew.tsx @@ -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 ( + + + + ) +} diff --git a/components/Icons/OpenInNewSmall.tsx b/components/Icons/OpenInNewSmall.tsx new file mode 100644 index 000000000..a7ff6547f --- /dev/null +++ b/components/Icons/OpenInNewSmall.tsx @@ -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 ( + + + + ) +} diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index bbd41da37..172a16e20 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -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: diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index a72e03543..d1c61fc22 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -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" diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css index da8dc802a..8ff6a9932 100644 --- a/components/TempDesignSystem/Link/link.module.css +++ b/components/TempDesignSystem/Link/link.module.css @@ -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); } diff --git a/server/routers/hotels/schemas/restaurants.ts b/server/routers/hotels/schemas/restaurants.ts index 0a65a60e3..549c10f81 100644 --- a/server/routers/hotels/schemas/restaurants.ts +++ b/server/routers/hotels/schemas/restaurants.ts @@ -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), diff --git a/types/components/hotelPage/sidepeek/restaurantBar.ts b/types/components/hotelPage/sidepeek/restaurantBar.ts index fe0f3d0bd..2cd1e4c74 100644 --- a/types/components/hotelPage/sidepeek/restaurantBar.ts +++ b/types/components/hotelPage/sidepeek/restaurantBar.ts @@ -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 +} diff --git a/types/components/hotelReservation/bookingConfirmation/room.ts b/types/components/hotelReservation/bookingConfirmation/room.ts new file mode 100644 index 000000000..509ac5c64 --- /dev/null +++ b/types/components/hotelReservation/bookingConfirmation/room.ts @@ -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"] +} diff --git a/types/components/icon.ts b/types/components/icon.ts index edf01e8c8..dc0975fc7 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -87,6 +87,8 @@ export enum IconName { Nature = "Nature", Nightlife = "Nightlife", NoSmoking = "NoSmoking", + OpenInNew = "OpenInNew", + OpenInNewSmall = "OpenInNewSmall", OutdoorFurniture = "OutdoorFurniture", Parking = "Parking", People2 = "People2", diff --git a/types/hotel.ts b/types/hotel.ts index c42f15423..78b79bea7 100644 --- a/types/hotel.ts +++ b/types/hotel.ts @@ -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 +export type HotelData = z.output 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 -export type RestaurantData = z.infer +export type RestaurantData = z.output +export type RestaurantOpeningHours = z.output< + typeof restaurantOpeningHoursSchema +> +export type RestaurantOpeningHoursDay = z.output export type GalleryImage = z.infer export type CheckInData = z.infer diff --git a/utils/getBookedHotelRoom.ts b/utils/getBookedHotelRoom.ts new file mode 100644 index 000000000..271bf33b3 --- /dev/null +++ b/utils/getBookedHotelRoom.ts @@ -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, + } +}