From 68f40a144e0e637e6de3fd593254b43eeefdcfc7 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 4 Jul 2024 10:35:23 +0200 Subject: [PATCH 01/12] feat: added RoomCard and CardContainer component to HotelPage --- .../CardContainer/cardContainer.module.css | 15 ++++ .../HotelPage/CardContainer/cardContainer.ts | 7 ++ .../HotelPage/CardContainer/index.tsx | 18 ++++ .../HotelPage/CardContainer/variants.ts | 16 ++++ .../ContentType/HotelPage/RoomCard/index.tsx | 52 +++++++++++ .../HotelPage/RoomCard/roomCard.module.css | 90 +++++++++++++++++++ .../HotelPage/RoomCard/roomCard.ts | 16 ++++ components/Icons/Camera.tsx | 23 +++++ components/Icons/Image.tsx | 36 ++++++++ components/Icons/get-icon-by-icon-name.ts | 6 ++ components/Icons/icon.module.css | 5 ++ components/Icons/index.tsx | 2 + components/Icons/variants.ts | 1 + .../Text/Body/body.module.css | 4 + .../TempDesignSystem/Text/Body/variants.ts | 1 + types/components/icon.ts | 2 + 16 files changed, 294 insertions(+) create mode 100644 components/ContentType/HotelPage/CardContainer/cardContainer.module.css create mode 100644 components/ContentType/HotelPage/CardContainer/cardContainer.ts create mode 100644 components/ContentType/HotelPage/CardContainer/index.tsx create mode 100644 components/ContentType/HotelPage/CardContainer/variants.ts create mode 100644 components/ContentType/HotelPage/RoomCard/index.tsx create mode 100644 components/ContentType/HotelPage/RoomCard/roomCard.module.css create mode 100644 components/ContentType/HotelPage/RoomCard/roomCard.ts create mode 100644 components/Icons/Camera.tsx create mode 100644 components/Icons/Image.tsx diff --git a/components/ContentType/HotelPage/CardContainer/cardContainer.module.css b/components/ContentType/HotelPage/CardContainer/cardContainer.module.css new file mode 100644 index 000000000..9636d90d5 --- /dev/null +++ b/components/ContentType/HotelPage/CardContainer/cardContainer.module.css @@ -0,0 +1,15 @@ +.cardContainer { + display: grid; + gap: var(--Spacing-x3); + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); +} + +.cardContainer.twoColumns { + grid-template-columns: repeat(2, 1fr); +} +.cardContainer.threeColumns { + grid-template-columns: repeat(3, 1fr); +} +.cardContainer.fourColumns { + grid-template-columns: repeat(4, 1fr); +} diff --git a/components/ContentType/HotelPage/CardContainer/cardContainer.ts b/components/ContentType/HotelPage/CardContainer/cardContainer.ts new file mode 100644 index 000000000..51f0c7775 --- /dev/null +++ b/components/ContentType/HotelPage/CardContainer/cardContainer.ts @@ -0,0 +1,7 @@ +import { VariantProps } from "class-variance-authority" + +import { cardContainerVariants } from "./variants" + +export interface CardContainerProps + extends React.PropsWithChildren>, + VariantProps {} diff --git a/components/ContentType/HotelPage/CardContainer/index.tsx b/components/ContentType/HotelPage/CardContainer/index.tsx new file mode 100644 index 000000000..f65e5981d --- /dev/null +++ b/components/ContentType/HotelPage/CardContainer/index.tsx @@ -0,0 +1,18 @@ +import { CardContainerProps } from "./cardContainer" +import { cardContainerVariants } from "./variants" + +export function CardContainer({ + children, + className, + columns, + ...props +}: CardContainerProps) { + return ( +
+ {children} +
+ ) +} diff --git a/components/ContentType/HotelPage/CardContainer/variants.ts b/components/ContentType/HotelPage/CardContainer/variants.ts new file mode 100644 index 000000000..a7599adb0 --- /dev/null +++ b/components/ContentType/HotelPage/CardContainer/variants.ts @@ -0,0 +1,16 @@ +import { cva } from "class-variance-authority" + +import styles from "./cardContainer.module.css" + +export const cardContainerVariants = cva(styles.cardContainer, { + variants: { + columns: { + 2: styles.twoColumns, + 3: styles.threeColumns, + 4: styles.fourColumns, + }, + }, + defaultVariants: { + columns: 3, + }, +}) diff --git a/components/ContentType/HotelPage/RoomCard/index.tsx b/components/ContentType/HotelPage/RoomCard/index.tsx new file mode 100644 index 000000000..4e4bd5c86 --- /dev/null +++ b/components/ContentType/HotelPage/RoomCard/index.tsx @@ -0,0 +1,52 @@ +"use client" + +import { ImageIcon } from "@/components/Icons" +import Image from "@/components/Image" +import Body from "@/components/TempDesignSystem/Text/Body" +import Title from "@/components/TempDesignSystem/Text/Title" + +import { RoomCardProps } from "./roomCard" + +import styles from "./roomCard.module.css" + +export function RoomCard({ + badgeText, + title, + subtitle, + cta, + image, + imageCount, + imageClick, +}: RoomCardProps) { + return ( +
+ +
+
+ + {title} + + {subtitle} +
+ +
+
+ ) +} diff --git a/components/ContentType/HotelPage/RoomCard/roomCard.module.css b/components/ContentType/HotelPage/RoomCard/roomCard.module.css new file mode 100644 index 000000000..f616a6767 --- /dev/null +++ b/components/ContentType/HotelPage/RoomCard/roomCard.module.css @@ -0,0 +1,90 @@ +.roomCard { + border-radius: var(--Corner-radius-Medium); + background-color: var(--Base-Surface-Primary-Normal); + border: 1px solid var(--Base-Border-Subtle); + display: grid; +} + +.badge { + position: absolute; + top: var(--Spacing-x1); + left: var(--Spacing-x1); + background-color: var(--Scandic-Blue-100); + padding: var(--Spacing-x-half) var(--Spacing-x1); + border-radius: var(--Corner-radius-Medium); + color: var(--Tertiary-Dark-On-Surface-Text, #fff0c2); + text-transform: uppercase; + font-family: var(--typography-Title-5-fontFamily); + font-size: var(--typography-Footnote-Regular-fontSize); + font-weight: var(--typography-Footnote-Regular-fontWeight); +} + +.imageCount { + position: absolute; + right: var(--Spacing-x1); + bottom: var(--Spacing-x1); + display: flex; + gap: var(--Spacing-x-half); + align-items: center; + background-color: color-mix( + in srgb, + var(--Scandic-Beige-80) 80%, + transparent + ); + color: white; + padding: var(--Spacing-x-half) var(--Spacing-x1); + border-radius: var(--Corner-radius-Medium); +} + +.content { + display: grid; + justify-items: center; + gap: var(--Spacing-x-one-and-half); + padding: var(--Spacing-x2); +} + +.innerContent { + display: grid; + justify-items: center; + gap: var(--Spacing-x1); +} + +.imageWrapper { + position: relative; + background-color: transparent; + border-width: 0; + cursor: pointer; + margin: 0; + padding: 0; + display: flex; +} + +.image { + width: 100%; + object-fit: cover; + border-top-left-radius: var(--Corner-radius-Medium); + border-top-right-radius: var(--Corner-radius-Medium); +} + +.subtitle { + color: var(--UI-Text-Placeholder, #787472); +} + +.cta { + background-color: transparent; + border-width: 0; + cursor: pointer; + margin: 0; + padding: 0; + display: flex; + align-items: center; + gap: var(--Spacing-x-half); + color: var(--Base-Text-Medium-contrast, #8f4350); + font-family: var(--typography-Body-Bold-fontFamily); + font-size: var(--typography-Body-Bold-fontSize); + font-weight: 600; + text-decoration: underline; +} +.cta:hover { + color: var(--Base-Text-High-contrast, #4d001b); +} diff --git a/components/ContentType/HotelPage/RoomCard/roomCard.ts b/components/ContentType/HotelPage/RoomCard/roomCard.ts new file mode 100644 index 000000000..0d174f46f --- /dev/null +++ b/components/ContentType/HotelPage/RoomCard/roomCard.ts @@ -0,0 +1,16 @@ +import { ImageProps } from "next/image" + +interface RoomCardCtaProps { + text: string + callback: () => void +} + +export interface RoomCardProps { + image: ImageProps + imageCount: number + imageClick: () => void + title: string + subtitle: string + cta: RoomCardCtaProps + badgeText?: string +} diff --git a/components/Icons/Camera.tsx b/components/Icons/Camera.tsx new file mode 100644 index 000000000..bed8a79e6 --- /dev/null +++ b/components/Icons/Camera.tsx @@ -0,0 +1,23 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function CameraIcon({ className, color, ...props }: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + ) +} diff --git a/components/Icons/Image.tsx b/components/Icons/Image.tsx new file mode 100644 index 000000000..9fcfe4a71 --- /dev/null +++ b/components/Icons/Image.tsx @@ -0,0 +1,36 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function ImageIcon({ 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 ae2534a5d..897ca8b28 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -7,6 +7,7 @@ import { BarIcon, BikingIcon, CalendarIcon, + CameraIcon, CellphoneIcon, CheckCircleIcon, CheckIcon, @@ -21,6 +22,7 @@ import { FitnessIcon, GlobeIcon, HouseIcon, + ImageIcon, InfoCircleIcon, LocationIcon, LockIcon, @@ -51,6 +53,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return BikingIcon case IconName.Calendar: return CalendarIcon + case IconName.Camera: + return CameraIcon case IconName.Cellphone: return CellphoneIcon case IconName.Check: @@ -79,6 +83,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return GlobeIcon case IconName.House: return HouseIcon + case IconName.Image: + return ImageIcon case IconName.InfoCircle: return InfoCircleIcon case IconName.Location: diff --git a/components/Icons/icon.module.css b/components/Icons/icon.module.css index 73b8f38a3..bc8715040 100644 --- a/components/Icons/icon.module.css +++ b/components/Icons/icon.module.css @@ -41,3 +41,8 @@ .red * { fill: var(--Scandic-Brand-Scandic-Red); } + +.white, +.white * { + fill: var(--Scandic-Opacity-White-100); +} diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index a69a2a77e..07c9b7016 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -4,6 +4,7 @@ export { default as ArrowRightIcon } from "./ArrowRight" export { default as BarIcon } from "./Bar" export { default as BikingIcon } from "./Biking" export { default as CalendarIcon } from "./Calendar" +export { default as CameraIcon } from "./Camera" export { default as CellphoneIcon } from "./Cellphone" export { default as CheckIcon } from "./Check" export { default as CheckCircleIcon } from "./CheckCircle" @@ -18,6 +19,7 @@ export { default as EmailIcon } from "./Email" export { default as FitnessIcon } from "./Fitness" export { default as GlobeIcon } from "./Globe" export { default as HouseIcon } from "./House" +export { default as ImageIcon } from "./Image" export { default as InfoCircleIcon } from "./InfoCircle" export { default as LocationIcon } from "./Location" export { default as LockIcon } from "./Lock" diff --git a/components/Icons/variants.ts b/components/Icons/variants.ts index e39422ece..39e4ff51e 100644 --- a/components/Icons/variants.ts +++ b/components/Icons/variants.ts @@ -13,6 +13,7 @@ const config = { primaryLightOnSurfaceAccent: styles.plosa, red: styles.red, green: styles.green, + white: styles.white, }, }, defaultVariants: { diff --git a/components/TempDesignSystem/Text/Body/body.module.css b/components/TempDesignSystem/Text/Body/body.module.css index 2176cf473..6a547fd9c 100644 --- a/components/TempDesignSystem/Text/Body/body.module.css +++ b/components/TempDesignSystem/Text/Body/body.module.css @@ -50,6 +50,10 @@ color: var(--Scandic-Brand-Burgundy); } +.grey { + color: var(--UI-Text-Placeholder, #787472); +} + .pale { color: var(--Scandic-Brand-Pale-Peach); } diff --git a/components/TempDesignSystem/Text/Body/variants.ts b/components/TempDesignSystem/Text/Body/variants.ts index 96d36937e..d044b4286 100644 --- a/components/TempDesignSystem/Text/Body/variants.ts +++ b/components/TempDesignSystem/Text/Body/variants.ts @@ -7,6 +7,7 @@ const config = { color: { black: styles.black, burgundy: styles.burgundy, + grey: styles.grey, pale: styles.pale, red: styles.red, textMediumContrast: styles.textMediumContrast, diff --git a/types/components/icon.ts b/types/components/icon.ts index e340e9aff..ff76c28eb 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -13,6 +13,7 @@ export enum IconName { Bar = "Bar", Biking = "Biking", Calendar = "Calendar", + Camera = "Camera", Cellphone = "Cellphone", Check = "Check", CheckCircle = "CheckCircle", @@ -27,6 +28,7 @@ export enum IconName { Fitness = "Fitness", Globe = "Globe", House = "House", + Image = "Image", InfoCircle = "InfoCircle", Location = "Location", Lock = "Lock", From 7eea7aa400bc85c50d77b135f3e0f009bb0a6ca1 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 5 Jul 2024 07:47:39 +0200 Subject: [PATCH 02/12] feat: added Rooms component to Hotelpage with some dummy rooms typing --- .../CardContainer/cardContainer.module.css | 15 ---- .../HotelPage/CardContainer/cardContainer.ts | 7 -- .../HotelPage/CardContainer/index.tsx | 18 ---- .../HotelPage/CardContainer/variants.ts | 16 ---- .../ContentType/HotelPage/HotelPage.tsx | 5 ++ .../HotelPage/{ => Rooms}/RoomCard/index.tsx | 0 .../{ => Rooms}/RoomCard/roomCard.module.css | 0 .../{ => Rooms}/RoomCard/roomCard.ts | 2 +- .../ContentType/HotelPage/Rooms/index.tsx | 56 +++++++++++++ .../HotelPage/Rooms/rooms.module.css | 10 +++ .../ContentType/HotelPage/Rooms/rooms.ts | 12 +++ .../HotelPage/tempHotelPageData.ts | 82 +++++++++++++++++++ 12 files changed, 166 insertions(+), 57 deletions(-) delete mode 100644 components/ContentType/HotelPage/CardContainer/cardContainer.module.css delete mode 100644 components/ContentType/HotelPage/CardContainer/cardContainer.ts delete mode 100644 components/ContentType/HotelPage/CardContainer/index.tsx delete mode 100644 components/ContentType/HotelPage/CardContainer/variants.ts rename components/ContentType/HotelPage/{ => Rooms}/RoomCard/index.tsx (100%) rename components/ContentType/HotelPage/{ => Rooms}/RoomCard/roomCard.module.css (100%) rename components/ContentType/HotelPage/{ => Rooms}/RoomCard/roomCard.ts (90%) create mode 100644 components/ContentType/HotelPage/Rooms/index.tsx create mode 100644 components/ContentType/HotelPage/Rooms/rooms.module.css create mode 100644 components/ContentType/HotelPage/Rooms/rooms.ts create mode 100644 components/ContentType/HotelPage/tempHotelPageData.ts diff --git a/components/ContentType/HotelPage/CardContainer/cardContainer.module.css b/components/ContentType/HotelPage/CardContainer/cardContainer.module.css deleted file mode 100644 index 9636d90d5..000000000 --- a/components/ContentType/HotelPage/CardContainer/cardContainer.module.css +++ /dev/null @@ -1,15 +0,0 @@ -.cardContainer { - display: grid; - gap: var(--Spacing-x3); - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); -} - -.cardContainer.twoColumns { - grid-template-columns: repeat(2, 1fr); -} -.cardContainer.threeColumns { - grid-template-columns: repeat(3, 1fr); -} -.cardContainer.fourColumns { - grid-template-columns: repeat(4, 1fr); -} diff --git a/components/ContentType/HotelPage/CardContainer/cardContainer.ts b/components/ContentType/HotelPage/CardContainer/cardContainer.ts deleted file mode 100644 index 51f0c7775..000000000 --- a/components/ContentType/HotelPage/CardContainer/cardContainer.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { VariantProps } from "class-variance-authority" - -import { cardContainerVariants } from "./variants" - -export interface CardContainerProps - extends React.PropsWithChildren>, - VariantProps {} diff --git a/components/ContentType/HotelPage/CardContainer/index.tsx b/components/ContentType/HotelPage/CardContainer/index.tsx deleted file mode 100644 index f65e5981d..000000000 --- a/components/ContentType/HotelPage/CardContainer/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { CardContainerProps } from "./cardContainer" -import { cardContainerVariants } from "./variants" - -export function CardContainer({ - children, - className, - columns, - ...props -}: CardContainerProps) { - return ( -
- {children} -
- ) -} diff --git a/components/ContentType/HotelPage/CardContainer/variants.ts b/components/ContentType/HotelPage/CardContainer/variants.ts deleted file mode 100644 index a7599adb0..000000000 --- a/components/ContentType/HotelPage/CardContainer/variants.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { cva } from "class-variance-authority" - -import styles from "./cardContainer.module.css" - -export const cardContainerVariants = cva(styles.cardContainer, { - variants: { - columns: { - 2: styles.twoColumns, - 3: styles.threeColumns, - 4: styles.fourColumns, - }, - }, - defaultVariants: { - columns: 3, - }, -}) diff --git a/components/ContentType/HotelPage/HotelPage.tsx b/components/ContentType/HotelPage/HotelPage.tsx index 3621be0c3..6268318a6 100644 --- a/components/ContentType/HotelPage/HotelPage.tsx +++ b/components/ContentType/HotelPage/HotelPage.tsx @@ -3,6 +3,9 @@ import { serverClient } from "@/lib/trpc/server" import AmenitiesList from "./AmenitiesList" import IntroSection from "./IntroSection" +import { Rooms } from "./Rooms" +import { MOCK_ROOMS } from "./tempHotelPageData" + import styles from "./hotelPage.module.css" import type { LangParams } from "@/types/params" @@ -19,6 +22,7 @@ export default async function HotelPage({ lang }: LangParams) { hotelId: hotelPageIdentifierData.hotel_page_id, language: lang, }) + const rooms = MOCK_ROOMS return (
@@ -32,6 +36,7 @@ export default async function HotelPage({ lang }: LangParams) { /> +
) } diff --git a/components/ContentType/HotelPage/RoomCard/index.tsx b/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx similarity index 100% rename from components/ContentType/HotelPage/RoomCard/index.tsx rename to components/ContentType/HotelPage/Rooms/RoomCard/index.tsx diff --git a/components/ContentType/HotelPage/RoomCard/roomCard.module.css b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css similarity index 100% rename from components/ContentType/HotelPage/RoomCard/roomCard.module.css rename to components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css diff --git a/components/ContentType/HotelPage/RoomCard/roomCard.ts b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.ts similarity index 90% rename from components/ContentType/HotelPage/RoomCard/roomCard.ts rename to components/ContentType/HotelPage/Rooms/RoomCard/roomCard.ts index 0d174f46f..06226e7c8 100644 --- a/components/ContentType/HotelPage/RoomCard/roomCard.ts +++ b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.ts @@ -12,5 +12,5 @@ export interface RoomCardProps { title: string subtitle: string cta: RoomCardCtaProps - badgeText?: string + badgeText?: string | null } diff --git a/components/ContentType/HotelPage/Rooms/index.tsx b/components/ContentType/HotelPage/Rooms/index.tsx new file mode 100644 index 000000000..d98c514d7 --- /dev/null +++ b/components/ContentType/HotelPage/Rooms/index.tsx @@ -0,0 +1,56 @@ +"use client" + +import { useIntl } from "react-intl" + +import { RoomCard } from "./RoomCard" +import { RoomsProps } from "./rooms" + +import styles from "./rooms.module.css" + +export function Rooms({ rooms }: RoomsProps) { + const { formatMessage } = useIntl() + + // TODO: Typings should be adjusted to match the actual data structure + const mappedRooms = rooms.map((room) => ({ + id: room.id, + image: room.images[0], + imageCount: room.images.length, + title: room.title, + subtitle: room.subtitle, + popularChoice: room.popularChoice, + })) + + function handleImageClick(id: string) { + // TODO: Implement opening of a model with carousel + console.log("Image clicked: ", id) + } + + function handleRoomCtaClick(id: string) { + // TODO: Implement opening side-peek component with room details + console.log("Room CTA clicked: ", id) + } + + return ( +
+ {mappedRooms.map( + ({ id, image, imageCount, title, subtitle, popularChoice }) => ( + handleImageClick(id)} + cta={{ + text: formatMessage({ id: "See room details" }), + callback: () => handleRoomCtaClick(id), + }} + /> + ) + )} +
+ ) +} diff --git a/components/ContentType/HotelPage/Rooms/rooms.module.css b/components/ContentType/HotelPage/Rooms/rooms.module.css new file mode 100644 index 000000000..7a74c6009 --- /dev/null +++ b/components/ContentType/HotelPage/Rooms/rooms.module.css @@ -0,0 +1,10 @@ +.cardContainer { + display: grid; + gap: var(--Spacing-x3); +} + +@media screen and (min-width: 768px) { + .cardContainer { + grid-template-columns: repeat(3, 1fr); + } +} diff --git a/components/ContentType/HotelPage/Rooms/rooms.ts b/components/ContentType/HotelPage/Rooms/rooms.ts new file mode 100644 index 000000000..d7ccb5859 --- /dev/null +++ b/components/ContentType/HotelPage/Rooms/rooms.ts @@ -0,0 +1,12 @@ +import { ImageProps } from "next/image" + +// TODO: Typings should be adjusted to match the actual data structure +export interface RoomsProps { + rooms: { + id: string + title: string + subtitle: string + popularChoice: boolean + images: ImageProps[] + }[] +} diff --git a/components/ContentType/HotelPage/tempHotelPageData.ts b/components/ContentType/HotelPage/tempHotelPageData.ts new file mode 100644 index 000000000..783565208 --- /dev/null +++ b/components/ContentType/HotelPage/tempHotelPageData.ts @@ -0,0 +1,82 @@ +import { RoomsProps } from "./Rooms/rooms" + +export const MOCK_ROOMS: RoomsProps["rooms"] = [ + { + id: "1", + title: "Cabin", + subtitle: "15 - 20 m² (2 personer)", + images: [ + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + ], + popularChoice: false, + }, + { + id: "2", + title: "Standard", + subtitle: "15 - 20 m² (2 personer)", + images: [ + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + ], + popularChoice: true, + }, + { + id: "3", + title: "Superior", + subtitle: "15 m² (2 personer)", + images: [ + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + { + src: "https://placehold.co/300x200", + alt: "Placeholder image", + width: 300, + height: 200, + }, + ], + popularChoice: false, + }, +] From ea8165bec9e68d5ebc778e691a7ce61ed74509f4 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 5 Jul 2024 12:49:52 +0200 Subject: [PATCH 03/12] fix: moved typings to typings folder and removed fallback variable values. Also fixed so only RoomCard is client component --- .../HotelPage/Rooms/RoomCard/index.tsx | 55 ++++++++++++------- .../Rooms/RoomCard/roomCard.module.css | 16 ++---- .../HotelPage/Rooms/RoomCard/roomCard.ts | 16 ------ .../ContentType/HotelPage/Rooms/index.tsx | 51 +++++------------ .../HotelPage/tempHotelPageData.ts | 2 +- types/components/hotelPage/roomCard.ts | 9 +++ .../components/hotelPage}/rooms.ts | 0 7 files changed, 64 insertions(+), 85 deletions(-) delete mode 100644 components/ContentType/HotelPage/Rooms/RoomCard/roomCard.ts create mode 100644 types/components/hotelPage/roomCard.ts rename {components/ContentType/HotelPage/Rooms => types/components/hotelPage}/rooms.ts (100%) diff --git a/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx b/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx index 4e4bd5c86..257d73cbf 100644 --- a/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx +++ b/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx @@ -1,39 +1,54 @@ "use client" +import { useIntl } from "react-intl" + import { ImageIcon } from "@/components/Icons" import Image from "@/components/Image" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" -import { RoomCardProps } from "./roomCard" - import styles from "./roomCard.module.css" +import { RoomCardProps } from "@/types/components/hotelPage/roomCard" + export function RoomCard({ - badgeText, - title, + badgeTextTransKey, + id, + images, subtitle, - cta, - image, - imageCount, - imageClick, + title, }: RoomCardProps) { + const { formatMessage } = useIntl() + const mainImage = images[0] + + function handleImageClick() { + // TODO: Implement opening of a model with carousel + console.log("Image clicked: ", id) + } + + function handleRoomCtaClick() { + // TODO: Implement opening side-peek component with room details + console.log("Room CTA clicked: ", id) + } + return (
-
@@ -43,8 +58,8 @@ export function RoomCard({ {subtitle}
-
diff --git a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css index f616a6767..3678cdff2 100644 --- a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css +++ b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css @@ -12,7 +12,7 @@ background-color: var(--Scandic-Blue-100); padding: var(--Spacing-x-half) var(--Spacing-x1); border-radius: var(--Corner-radius-Medium); - color: var(--Tertiary-Dark-On-Surface-Text, #fff0c2); + color: var(--Tertiary-Dark-On-Surface-Text); text-transform: uppercase; font-family: var(--typography-Title-5-fontFamily); font-size: var(--typography-Footnote-Regular-fontSize); @@ -26,12 +26,8 @@ display: flex; gap: var(--Spacing-x-half); align-items: center; - background-color: color-mix( - in srgb, - var(--Scandic-Beige-80) 80%, - transparent - ); - color: white; + background-color: var(--Scandic-Opacity-Almost-Black-60); + color: var(--Scandic-Opacity-White-100); padding: var(--Spacing-x-half) var(--Spacing-x1); border-radius: var(--Corner-radius-Medium); } @@ -67,7 +63,7 @@ } .subtitle { - color: var(--UI-Text-Placeholder, #787472); + color: var(--UI-Text-Placeholder); } .cta { @@ -79,12 +75,12 @@ display: flex; align-items: center; gap: var(--Spacing-x-half); - color: var(--Base-Text-Medium-contrast, #8f4350); + color: var(--Base-Text-Medium-contrast); font-family: var(--typography-Body-Bold-fontFamily); font-size: var(--typography-Body-Bold-fontSize); font-weight: 600; text-decoration: underline; } .cta:hover { - color: var(--Base-Text-High-contrast, #4d001b); + color: var(--Base-Text-High-contrast); } diff --git a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.ts b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.ts deleted file mode 100644 index 06226e7c8..000000000 --- a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ImageProps } from "next/image" - -interface RoomCardCtaProps { - text: string - callback: () => void -} - -export interface RoomCardProps { - image: ImageProps - imageCount: number - imageClick: () => void - title: string - subtitle: string - cta: RoomCardCtaProps - badgeText?: string | null -} diff --git a/components/ContentType/HotelPage/Rooms/index.tsx b/components/ContentType/HotelPage/Rooms/index.tsx index d98c514d7..7dd0f2c88 100644 --- a/components/ContentType/HotelPage/Rooms/index.tsx +++ b/components/ContentType/HotelPage/Rooms/index.tsx @@ -1,56 +1,31 @@ -"use client" - -import { useIntl } from "react-intl" - import { RoomCard } from "./RoomCard" -import { RoomsProps } from "./rooms" import styles from "./rooms.module.css" -export function Rooms({ rooms }: RoomsProps) { - const { formatMessage } = useIntl() +import { RoomsProps } from "@/types/components/hotelPage/rooms" +export function Rooms({ rooms }: RoomsProps) { // TODO: Typings should be adjusted to match the actual data structure const mappedRooms = rooms.map((room) => ({ id: room.id, - image: room.images[0], - imageCount: room.images.length, + images: room.images, title: room.title, subtitle: room.subtitle, popularChoice: room.popularChoice, })) - function handleImageClick(id: string) { - // TODO: Implement opening of a model with carousel - console.log("Image clicked: ", id) - } - - function handleRoomCtaClick(id: string) { - // TODO: Implement opening side-peek component with room details - console.log("Room CTA clicked: ", id) - } - return (
- {mappedRooms.map( - ({ id, image, imageCount, title, subtitle, popularChoice }) => ( - handleImageClick(id)} - cta={{ - text: formatMessage({ id: "See room details" }), - callback: () => handleRoomCtaClick(id), - }} - /> - ) - )} + {mappedRooms.map(({ id, images, title, subtitle, popularChoice }) => ( + + ))}
) } diff --git a/components/ContentType/HotelPage/tempHotelPageData.ts b/components/ContentType/HotelPage/tempHotelPageData.ts index 783565208..30b4921b6 100644 --- a/components/ContentType/HotelPage/tempHotelPageData.ts +++ b/components/ContentType/HotelPage/tempHotelPageData.ts @@ -1,4 +1,4 @@ -import { RoomsProps } from "./Rooms/rooms" +import { RoomsProps } from "../../../types/components/hotelPage/rooms" export const MOCK_ROOMS: RoomsProps["rooms"] = [ { diff --git a/types/components/hotelPage/roomCard.ts b/types/components/hotelPage/roomCard.ts new file mode 100644 index 000000000..bfdec37bb --- /dev/null +++ b/types/components/hotelPage/roomCard.ts @@ -0,0 +1,9 @@ +import { ImageProps } from "next/image" + +export interface RoomCardProps { + id: string + images: ImageProps[] + title: string + subtitle: string + badgeTextTransKey?: string | null +} diff --git a/components/ContentType/HotelPage/Rooms/rooms.ts b/types/components/hotelPage/rooms.ts similarity index 100% rename from components/ContentType/HotelPage/Rooms/rooms.ts rename to types/components/hotelPage/rooms.ts From f71d0a07d55920bb9f1adaf79153ae83f6f59e71 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Wed, 10 Jul 2024 13:39:00 +0200 Subject: [PATCH 04/12] feat: add textTransform prop to section headers --- components/Section/Header/index.tsx | 2 ++ types/components/myPages/header.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/components/Section/Header/index.tsx b/components/Section/Header/index.tsx index 228d3b6ac..e005a8c07 100644 --- a/components/Section/Header/index.tsx +++ b/components/Section/Header/index.tsx @@ -12,6 +12,7 @@ export default function SectionHeader({ subtitle, title, topTitle = false, + textTransform, }: HeaderProps) { return (
@@ -19,6 +20,7 @@ export default function SectionHeader({ as={topTitle ? "h3" : "h4"} className={styles.title} level={topTitle ? "h1" : "h2"} + textTransform={textTransform} > {title} diff --git a/types/components/myPages/header.ts b/types/components/myPages/header.ts index 5eaafa0b4..af1ed6a7e 100644 --- a/types/components/myPages/header.ts +++ b/types/components/myPages/header.ts @@ -1,9 +1,12 @@ +import { HeadingProps } from "@/components/TempDesignSystem/Text/Title/title" + export type HeaderProps = { link?: { href: string text: string } subtitle: string | null + textTransform?: HeadingProps["textTransform"] title: string | null topTitle?: boolean } From 0697c8d9efc8d0b0907bf2bafae835c547b58a38 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Wed, 10 Jul 2024 13:41:39 +0200 Subject: [PATCH 05/12] feat: add included param to hotel call, fetch room data, setup schema, use in hotelpage --- .../ContentType/HotelPage/HotelPage.tsx | 7 +- .../HotelPage/Rooms/RoomCard/index.tsx | 22 +++-- .../Rooms/RoomCard/roomCard.module.css | 19 ---- .../ContentType/HotelPage/Rooms/index.tsx | 67 ++++++++++----- .../ContentType/HotelPage/Rooms/types.ts | 5 ++ .../HotelPage/hotelPage.module.css | 1 - .../HotelPage/tempHotelPageData.ts | 82 ------------------ i18n/dictionaries/en.json | 4 + i18n/dictionaries/sv.json | 4 + server/routers/hotels/input.ts | 3 + server/routers/hotels/output.ts | 86 +++++++++++++++++-- server/routers/hotels/query.ts | 33 +++++-- types/components/hotelPage/roomCard.ts | 4 +- types/components/hotelPage/rooms.ts | 12 --- types/hotel.ts | 4 +- 15 files changed, 191 insertions(+), 162 deletions(-) create mode 100644 components/ContentType/HotelPage/Rooms/types.ts delete mode 100644 components/ContentType/HotelPage/tempHotelPageData.ts delete mode 100644 types/components/hotelPage/rooms.ts diff --git a/components/ContentType/HotelPage/HotelPage.tsx b/components/ContentType/HotelPage/HotelPage.tsx index 6268318a6..5a69bf9cf 100644 --- a/components/ContentType/HotelPage/HotelPage.tsx +++ b/components/ContentType/HotelPage/HotelPage.tsx @@ -4,7 +4,6 @@ import AmenitiesList from "./AmenitiesList" import IntroSection from "./IntroSection" import { Rooms } from "./Rooms" -import { MOCK_ROOMS } from "./tempHotelPageData" import styles from "./hotelPage.module.css" @@ -18,11 +17,11 @@ export default async function HotelPage({ lang }: LangParams) { return null } - const attributes = await serverClient().hotel.getHotel({ + const { attributes, roomCategories } = await serverClient().hotel.getHotel({ hotelId: hotelPageIdentifierData.hotel_page_id, language: lang, + include: ["RoomCategories"], }) - const rooms = MOCK_ROOMS return (
@@ -36,7 +35,7 @@ export default async function HotelPage({ lang }: LangParams) { /> - +
) } diff --git a/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx b/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx index 257d73cbf..a42124498 100644 --- a/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx +++ b/components/ContentType/HotelPage/Rooms/RoomCard/index.tsx @@ -4,6 +4,7 @@ import { useIntl } from "react-intl" import { ImageIcon } from "@/components/Icons" import Image from "@/components/Image" +import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" @@ -43,12 +44,14 @@ export function RoomCard({ {images.length} + {/*NOTE: images from the test API are hosted on test3.scandichotels.com, + which can't be accessed unless on Scandic's Wifi or using Citrix. */} {mainImage.alt}
@@ -58,9 +61,14 @@ export function RoomCard({ {subtitle}
- + + {formatMessage({ id: "hotelPages.rooms.roomCard.seeRoomDetails" })} + ) diff --git a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css index 3678cdff2..88446a44a 100644 --- a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css +++ b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css @@ -65,22 +65,3 @@ .subtitle { color: var(--UI-Text-Placeholder); } - -.cta { - background-color: transparent; - border-width: 0; - cursor: pointer; - margin: 0; - padding: 0; - display: flex; - align-items: center; - gap: var(--Spacing-x-half); - color: var(--Base-Text-Medium-contrast); - font-family: var(--typography-Body-Bold-fontFamily); - font-size: var(--typography-Body-Bold-fontSize); - font-weight: 600; - text-decoration: underline; -} -.cta:hover { - color: var(--Base-Text-High-contrast); -} diff --git a/components/ContentType/HotelPage/Rooms/index.tsx b/components/ContentType/HotelPage/Rooms/index.tsx index 7dd0f2c88..f7cdde172 100644 --- a/components/ContentType/HotelPage/Rooms/index.tsx +++ b/components/ContentType/HotelPage/Rooms/index.tsx @@ -1,31 +1,54 @@ +import SectionContainer from "@/components/Section/Container" +import SectionHeader from "@/components/Section/Header" +import { getIntl } from "@/i18n" + import { RoomCard } from "./RoomCard" +import { RoomsProps } from "./types" import styles from "./rooms.module.css" -import { RoomsProps } from "@/types/components/hotelPage/rooms" +export async function Rooms({ rooms }: RoomsProps) { + const { formatMessage } = await getIntl() + const mappedRooms = rooms + .map((room) => { + const size = `${room.attributes.roomSize.min} - ${room.attributes.roomSize.max} m²` + const personLabel = + room.attributes.occupancy.total === 1 + ? formatMessage({ id: "hotelPages.rooms.roomCard.person" }) + : formatMessage({ id: "hotelPages.rooms.roomCard.persons" }) -export function Rooms({ rooms }: RoomsProps) { - // TODO: Typings should be adjusted to match the actual data structure - const mappedRooms = rooms.map((room) => ({ - id: room.id, - images: room.images, - title: room.title, - subtitle: room.subtitle, - popularChoice: room.popularChoice, - })) + const subtitle = `${size} (${room.attributes.occupancy.total} ${personLabel})` + return { + id: room.id, + images: room.attributes.content.images, + title: room.attributes.name, + subtitle: subtitle, + sortOrder: room.attributes.sortOrder, + popularChoice: null, + } + }) + .sort((a, b) => a.sortOrder - b.sortOrder) + .slice(0, 3) //TODO: Remove this and render all rooms once we've implemented "show more" logic in SW-203. return ( -
- {mappedRooms.map(({ id, images, title, subtitle, popularChoice }) => ( - - ))} -
+ + +
+ {mappedRooms.map(({ id, images, title, subtitle, popularChoice }) => ( + + ))} +
+
) } diff --git a/components/ContentType/HotelPage/Rooms/types.ts b/components/ContentType/HotelPage/Rooms/types.ts new file mode 100644 index 000000000..b4c320af2 --- /dev/null +++ b/components/ContentType/HotelPage/Rooms/types.ts @@ -0,0 +1,5 @@ +import { RoomData } from "@/types/hotel" + +export type RoomsProps = { + rooms: RoomData[] +} diff --git a/components/ContentType/HotelPage/hotelPage.module.css b/components/ContentType/HotelPage/hotelPage.module.css index 666cd92db..d168ea390 100644 --- a/components/ContentType/HotelPage/hotelPage.module.css +++ b/components/ContentType/HotelPage/hotelPage.module.css @@ -12,7 +12,6 @@ @media screen and (min-width: 1367px) { .pageContainer { - gap: var(--Spacing-x3); padding: var(--Spacing-x9) var(--Spacing-x5); } .introContainer { diff --git a/components/ContentType/HotelPage/tempHotelPageData.ts b/components/ContentType/HotelPage/tempHotelPageData.ts deleted file mode 100644 index 30b4921b6..000000000 --- a/components/ContentType/HotelPage/tempHotelPageData.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { RoomsProps } from "../../../types/components/hotelPage/rooms" - -export const MOCK_ROOMS: RoomsProps["rooms"] = [ - { - id: "1", - title: "Cabin", - subtitle: "15 - 20 m² (2 personer)", - images: [ - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - ], - popularChoice: false, - }, - { - id: "2", - title: "Standard", - subtitle: "15 - 20 m² (2 personer)", - images: [ - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - ], - popularChoice: true, - }, - { - id: "3", - title: "Superior", - subtitle: "15 m² (2 personer)", - images: [ - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - { - src: "https://placehold.co/300x200", - alt: "Placeholder image", - width: 300, - height: 200, - }, - ], - popularChoice: false, - }, -] diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index b59ff7859..b6608a825 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -41,6 +41,10 @@ "From": "From", "Get inspired": "Get inspired", "Go back to overview": "Go back to overview", + "hotelPages.rooms.title": "Rooms", + "hotelPages.rooms.roomCard.person": "person", + "hotelPages.rooms.roomCard.persons": "persons", + "hotelPages.rooms.roomCard.seeRoomDetails": "See room details", "How it works": "How it works", "Join Scandic Friends": "Join Scandic Friends", "Language": "Language", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 6f0e99cde..0a8cabaa0 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -41,6 +41,10 @@ "Get inspired": "Bli inspirerad", "Go back to overview": "Gå tillbaka till översikten", "How it works": "Hur det fungerar", + "hotelPages.rooms.title": "Rum", + "hotelPages.rooms.roomCard.person": "person", + "hotelPages.rooms.roomCard.persons": "personer", + "hotelPages.rooms.roomCard.seeRoomDetails": "Se rumsdetaljer", "Join Scandic Friends": "Gå med i Scandic Friends", "Language": "Språk", "Level": "Nivå", diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts index bb8a1ebdd..03ea9e11d 100644 --- a/server/routers/hotels/input.ts +++ b/server/routers/hotels/input.ts @@ -5,6 +5,9 @@ import { Lang } from "@/constants/languages" export const getHotelInputSchema = z.object({ hotelId: z.string(), language: z.nativeEnum(Lang), + include: z + .array(z.enum(["RoomCategories", "NearbyHotels", "Restaurants", "City"])) + .optional(), }) export const getRatesInputSchema = z.object({ diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index d62c25f5f..044280d6e 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -335,6 +335,83 @@ const RelationshipsSchema = z.object({ }), }) +const RoomContentSchema = z.object({ + images: z.array( + z.object({ + metaData: ImageMetaDataSchema, + imageSizes: ImageSizesSchema, + }) + ), + texts: z.object({ + descriptions: z.object({ + short: z.string(), + medium: z.string(), + }), + }), +}) + +const RoomTypesSchema = z.object({ + name: z.string(), + description: z.string(), + code: z.string(), + roomCount: z.number(), + mainBed: z.object({ + type: z.string(), + description: z.string(), + widthRange: z.object({ + min: z.number(), + max: z.number(), + }), + }), + fixedExtraBed: z.object({ + type: z.string(), + description: z.string().optional(), + widthRange: z.object({ + min: z.number(), + max: z.number(), + }), + }), + roomSize: z.object({ + min: z.number(), + max: z.number(), + }), + occupancy: z.object({ + total: z.number(), + adults: z.number(), + children: z.number(), + }), + isLackingCribs: z.boolean(), + isLackingExtraBeds: z.boolean(), +}) + +const RoomFacilitiesSchema = z.object({ + availableInAllRooms: z.boolean(), + name: z.string(), + isUniqueSellingPoint: z.boolean(), + sortOrder: z.number(), +}) + +export const RoomSchema = z.object({ + attributes: z.object({ + name: z.string(), + sortOrder: z.number(), + content: RoomContentSchema, + roomTypes: z.array(RoomTypesSchema), + roomFacilities: z.array(RoomFacilitiesSchema), + occupancy: z.object({ + total: z.number(), + adults: z.number(), + children: z.number(), + }), + roomSize: z.object({ + min: z.number(), + max: z.number(), + }), + }), + id: z.string(), + type: z.enum(["roomcategories"]), +}) + // NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html export const getHotelDataSchema = z.object({ data: z.object({ @@ -385,11 +462,10 @@ export const getHotelDataSchema = z.object({ }), relationships: RelationshipsSchema, }), - //TODO: We can pass an "included" param to the hotel API to retrieve additional data for an individual hotel. - // - This is out of scope for current work (and I'm unsure if we need it for hotel pages specifically), - // - but if/when we do we can extend this schema to add necessary requirements. - // - Example "included" data available in our tempHotelData file. - // included: z.any(), + // NOTE: We can pass an "include" param to the hotel API to retrieve + // additional data for an individual hotel. + // Example "included" data can be found in our tempHotelData file. + included: z.array(RoomSchema).optional(), }) const Rate = z.object({ diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index fb4ca8da3..edd888519 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -3,7 +3,7 @@ import { badRequestError } from "@/server/errors/trpc" import { publicProcedure, router } from "@/server/trpc" import { getHotelInputSchema, getRatesInputSchema } from "./input" -import { getHotelDataSchema, getRatesSchema } from "./output" +import { getHotelDataSchema, getRatesSchema, RoomSchema } from "./output" import tempHotelData from "./tempHotelData.json" import tempRatesData from "./tempRatesData.json" import { toApiLang } from "./utils" @@ -12,12 +12,15 @@ export const hotelQueryRouter = router({ getHotel: publicProcedure .input(getHotelInputSchema) .query(async ({ input, ctx }) => { - const { hotelId, language } = input + const { hotelId, language, include } = input const params = new URLSearchParams() const apiLang = toApiLang(language) params.set("hotelId", hotelId.toString()) params.set("language", apiLang) + if (include) { + params.set("include", include.join(",")) + } // TODO: Enable once we have authorized API access. // const apiResponse = await api.get( @@ -33,10 +36,9 @@ export const hotelQueryRouter = router({ // } // const apiJson = await apiResponse.json() - //TODO: We can pass an "included" param to the hotel API to retrieve additional data for an individual hotel. - // - This is out of scope for current work (and I'm unsure if we need it for hotel pages specifically), - // - but if/when we do we can extend the endpoint (and schema) to add necessary requirements. - // - Example "included" data available in our tempHotelData file. + // NOTE: We can pass an "include" param to the hotel API to retrieve + // additional data for an individual hotel. + // Example "included" data can be found in our tempHotelData file. const { included, ...apiJsonWithoutIncluded } = tempHotelData const validatedHotelData = getHotelDataSchema.safeParse( apiJsonWithoutIncluded @@ -48,7 +50,24 @@ export const hotelQueryRouter = router({ throw badRequestError() } - return validatedHotelData.data.data.attributes + const roomCategories = included + ? included + .filter((item) => item.type === "roomcategories") + .map((roomCategory) => { + const validatedRoom = RoomSchema.safeParse(roomCategory) + if (!validatedRoom.success) { + console.info(`Get Room Category Data - Verified Data Error`) + console.error(validatedRoom.error) + throw badRequestError() + } + return validatedRoom.data + }) + : [] + + return { + attributes: validatedHotelData.data.data.attributes, + roomCategories: roomCategories, + } }), getRates: publicProcedure .input(getRatesInputSchema) diff --git a/types/components/hotelPage/roomCard.ts b/types/components/hotelPage/roomCard.ts index bfdec37bb..00238fb9a 100644 --- a/types/components/hotelPage/roomCard.ts +++ b/types/components/hotelPage/roomCard.ts @@ -1,8 +1,8 @@ -import { ImageProps } from "next/image" +import { RoomData } from "@/types/hotel" export interface RoomCardProps { id: string - images: ImageProps[] + images: RoomData["attributes"]["content"]["images"] title: string subtitle: string badgeTextTransKey?: string | null diff --git a/types/components/hotelPage/rooms.ts b/types/components/hotelPage/rooms.ts deleted file mode 100644 index d7ccb5859..000000000 --- a/types/components/hotelPage/rooms.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ImageProps } from "next/image" - -// TODO: Typings should be adjusted to match the actual data structure -export interface RoomsProps { - rooms: { - id: string - title: string - subtitle: string - popularChoice: boolean - images: ImageProps[] - }[] -} diff --git a/types/hotel.ts b/types/hotel.ts index a8a715c3d..9d277d145 100644 --- a/types/hotel.ts +++ b/types/hotel.ts @@ -1,6 +1,6 @@ import { z } from "zod" -import { getHotelDataSchema } from "@/server/routers/hotels/output" +import { getHotelDataSchema,RoomSchema } from "@/server/routers/hotels/output" export type HotelData = z.infer @@ -9,3 +9,5 @@ export type HotelAddress = HotelData["data"]["attributes"]["address"] export type HotelLocation = HotelData["data"]["attributes"]["location"] export type HotelTripAdvisor = HotelData["data"]["attributes"]["ratings"]["tripAdvisor"] + +export type RoomData = z.infer From 1573722843d811b9328318fafcf0337b576e258f Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Wed, 10 Jul 2024 13:43:46 +0200 Subject: [PATCH 06/12] fix: remove placeholder and use gray custom property --- components/TempDesignSystem/Text/Body/body.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/TempDesignSystem/Text/Body/body.module.css b/components/TempDesignSystem/Text/Body/body.module.css index 6a547fd9c..1bda01dd8 100644 --- a/components/TempDesignSystem/Text/Body/body.module.css +++ b/components/TempDesignSystem/Text/Body/body.module.css @@ -51,7 +51,7 @@ } .grey { - color: var(--UI-Text-Placeholder, #787472); + color: var(--UI-Grey-60); } .pale { From 48b7c79953c2b3a78137656c8d9861aee4adf33c Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Wed, 10 Jul 2024 19:14:51 +0200 Subject: [PATCH 07/12] fix: accessing hotel in hotel reservation --- .../(live)/(public)/hotelreservation/select-hotel/page.tsx | 4 ++-- components/ContentType/HotelPage/IntroSection/index.tsx | 4 ++-- .../ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx index 9e1fc07d3..33df35812 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx @@ -9,12 +9,12 @@ import { LangParams, PageArgs } from "@/types/params" export default async function SelectHotelPage({ params, }: PageArgs) { - const hotel = await serverClient().hotel.getHotel({ + const { attributes } = await serverClient().hotel.getHotel({ hotelId: "d98c7ab1-ebaa-4102-b351-758daf1ddf55", language: params.lang, }) - const hotels = [hotel] + const hotels = [attributes] return (
diff --git a/components/ContentType/HotelPage/IntroSection/index.tsx b/components/ContentType/HotelPage/IntroSection/index.tsx index 007f2c0ac..5efb41292 100644 --- a/components/ContentType/HotelPage/IntroSection/index.tsx +++ b/components/ContentType/HotelPage/IntroSection/index.tsx @@ -3,7 +3,7 @@ import TripAdvisorIcon from "@/components/Icons/TripAdvisor" import Link from "@/components/TempDesignSystem/Link" import BiroScript from "@/components/TempDesignSystem/Text/BiroScript" import Body from "@/components/TempDesignSystem/Text/Body" -import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Title from "@/components/TempDesignSystem/Text/Title" import { getIntl } from "@/i18n" @@ -55,7 +55,7 @@ export default async function IntroSection({
- {hotelDescription} + {hotelDescription} Date: Wed, 10 Jul 2024 19:46:45 +0200 Subject: [PATCH 08/12] fix: eslint autofix --- components/ContentType/HotelPage/HotelPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/ContentType/HotelPage/HotelPage.tsx b/components/ContentType/HotelPage/HotelPage.tsx index 5a69bf9cf..d57b3c36a 100644 --- a/components/ContentType/HotelPage/HotelPage.tsx +++ b/components/ContentType/HotelPage/HotelPage.tsx @@ -2,7 +2,6 @@ import { serverClient } from "@/lib/trpc/server" import AmenitiesList from "./AmenitiesList" import IntroSection from "./IntroSection" - import { Rooms } from "./Rooms" import styles from "./hotelPage.module.css" From 56eeaeb28ed95f3971bc7a214cec6db8661a36c6 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Thu, 11 Jul 2024 14:26:40 +0200 Subject: [PATCH 09/12] fix: Replace custom container w. grids component for rooms --- components/ContentType/HotelPage/Rooms/index.tsx | 7 +++---- .../ContentType/HotelPage/Rooms/rooms.module.css | 10 ---------- 2 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 components/ContentType/HotelPage/Rooms/rooms.module.css diff --git a/components/ContentType/HotelPage/Rooms/index.tsx b/components/ContentType/HotelPage/Rooms/index.tsx index f7cdde172..c865e23e7 100644 --- a/components/ContentType/HotelPage/Rooms/index.tsx +++ b/components/ContentType/HotelPage/Rooms/index.tsx @@ -1,12 +1,11 @@ import SectionContainer from "@/components/Section/Container" import SectionHeader from "@/components/Section/Header" +import Grids from "@/components/TempDesignSystem/Grids" import { getIntl } from "@/i18n" import { RoomCard } from "./RoomCard" import { RoomsProps } from "./types" -import styles from "./rooms.module.css" - export async function Rooms({ rooms }: RoomsProps) { const { formatMessage } = await getIntl() const mappedRooms = rooms @@ -37,7 +36,7 @@ export async function Rooms({ rooms }: RoomsProps) { title={formatMessage({ id: "hotelPages.rooms.title" })} subtitle={null} /> -
+ {mappedRooms.map(({ id, images, title, subtitle, popularChoice }) => ( ))} -
+ ) } diff --git a/components/ContentType/HotelPage/Rooms/rooms.module.css b/components/ContentType/HotelPage/Rooms/rooms.module.css deleted file mode 100644 index 7a74c6009..000000000 --- a/components/ContentType/HotelPage/Rooms/rooms.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.cardContainer { - display: grid; - gap: var(--Spacing-x3); -} - -@media screen and (min-width: 768px) { - .cardContainer { - grid-template-columns: repeat(3, 1fr); - } -} From 96b2be6ea1f5b2d8ba09f3af00c36e6de3334092 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Thu, 11 Jul 2024 14:39:01 +0200 Subject: [PATCH 10/12] fix: remove undefined type from badgeTextTransKey prop --- types/components/hotelPage/roomCard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/components/hotelPage/roomCard.ts b/types/components/hotelPage/roomCard.ts index 00238fb9a..4a555c621 100644 --- a/types/components/hotelPage/roomCard.ts +++ b/types/components/hotelPage/roomCard.ts @@ -5,5 +5,5 @@ export interface RoomCardProps { images: RoomData["attributes"]["content"]["images"] title: string subtitle: string - badgeTextTransKey?: string | null + badgeTextTransKey: string | null } From 5b91e94733c82faf567ed930b34e5eaa602f5105 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Thu, 11 Jul 2024 14:45:41 +0200 Subject: [PATCH 11/12] fix: use correct custom properties --- .../HotelPage/Rooms/RoomCard/roomCard.module.css | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css index dab806d0d..9850bb30d 100644 --- a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css +++ b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css @@ -9,14 +9,13 @@ position: absolute; top: var(--Spacing-x1); left: var(--Spacing-x1); - background-color: var(--Scandic-Blue-100); + background-color: var(--Tertiary-Dark-Surface-Hover); padding: var(--Spacing-x-half) var(--Spacing-x1); border-radius: var(--Corner-radius-Medium); color: var(--Tertiary-Dark-On-Surface-Text); text-transform: uppercase; - font-family: var(--typography-Title-5-fontFamily); - font-size: var(--typography-Footnote-Regular-fontSize); - font-weight: var(--typography-Footnote-Regular-fontWeight); + font-size: var(--typography-Chip-fontSize-Placeholder); + font-weight: 400; } .imageCount { From 7e92bf3f9bb78b3aa65f2078238f23fd0d16f7cd Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Thu, 11 Jul 2024 14:48:15 +0200 Subject: [PATCH 12/12] Add todo comment for badge component --- .../ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css index 9850bb30d..5134103e0 100644 --- a/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css +++ b/components/ContentType/HotelPage/Rooms/RoomCard/roomCard.module.css @@ -5,6 +5,7 @@ display: grid; } +/*TODO: Build Chip/Badge component. */ .badge { position: absolute; top: var(--Spacing-x1);