From dde9ca190ac38bca25f0a59d3457dc3580f585d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20Landstr=C3=B6m?= Date: Sun, 8 Sep 2024 11:19:16 +0200 Subject: [PATCH] chore(SW-303): fetch activity card from CS --- .../HotelPage/Facilities/mockData.ts | 15 ------ .../ContentType/HotelPage/Facilities/utils.ts | 44 +++++++++++++++++ .../HotelPage/TabNavigation/index.tsx | 1 + components/ContentType/HotelPage/index.tsx | 4 ++ lib/graphql/Query/HotelPage.graphql | 28 +++++++++++ .../routers/contentstack/hotelPage/output.ts | 47 ++++++++++++++++++- server/routers/hotels/query.ts | 41 ++++++++++++++-- types/components/hotelPage/enums.ts | 3 ++ utils/cardTheme.ts | 6 ++- 9 files changed, 169 insertions(+), 20 deletions(-) create mode 100644 components/ContentType/HotelPage/Facilities/utils.ts create mode 100644 types/components/hotelPage/enums.ts diff --git a/components/ContentType/HotelPage/Facilities/mockData.ts b/components/ContentType/HotelPage/Facilities/mockData.ts index a54a39309..dfea0a74b 100644 --- a/components/ContentType/HotelPage/Facilities/mockData.ts +++ b/components/ContentType/HotelPage/Facilities/mockData.ts @@ -141,19 +141,4 @@ export const MOCK_FACILITIES: Facilities = [ columnSpan: "one", }, ], - [ - { - id: "activities", - theme: "primaryDark", - scriptedTopTitle: "Activities", - heading: "Upcoming activities at DownTown Camper", - bodyText: "Lorem ipsum dolor sit amet, consectetur adipiscing elit", - secondaryButton: { - href: `?s=${activities[lang]}`, - title: "Discover activities", - isExternal: false, - }, - columnSpan: "three", - }, - ], ] diff --git a/components/ContentType/HotelPage/Facilities/utils.ts b/components/ContentType/HotelPage/Facilities/utils.ts new file mode 100644 index 000000000..32d49ee4b --- /dev/null +++ b/components/ContentType/HotelPage/Facilities/utils.ts @@ -0,0 +1,44 @@ +import type { Facility } from "@/types/components/hotelPage/facilities" +import type { ImageVaultAsset } from "@/types/components/imageVault" + +type ActivityCard = { + background_image?: ImageVaultAsset + scripted_title?: string + heading: string + body_text: string + cta_text: string + contentPage: Array<{ href: string }> +} + +export function setActivityCard(activitiesCard: ActivityCard): Facility { + const hasImage = activitiesCard.background_image + return [ + { + id: "activities", + theme: hasImage ? "image" : "primaryDark", + scriptedTopTitle: activitiesCard.scripted_title, + heading: activitiesCard.heading, + bodyText: activitiesCard.body_text, + backgroundImage: hasImage ? activitiesCard.background_image : undefined, + primaryButton: hasImage + ? { + href: activitiesCard.contentPage[0].href, + title: activitiesCard.cta_text, + isExternal: false, + } + : undefined, + secondaryButton: hasImage + ? undefined + : { + href: activitiesCard.contentPage[0].href, + title: activitiesCard.cta_text, + isExternal: false, + }, + columnSpan: "three", + }, + ] +} + +export function getCardTheme() { + // TODO +} diff --git a/components/ContentType/HotelPage/TabNavigation/index.tsx b/components/ContentType/HotelPage/TabNavigation/index.tsx index 4a36a9324..2adf3dc0b 100644 --- a/components/ContentType/HotelPage/TabNavigation/index.tsx +++ b/components/ContentType/HotelPage/TabNavigation/index.tsx @@ -12,6 +12,7 @@ export default function TabNavigation() { const hash = useHash() const intl = useIntl() const hotelTabLinks: { href: HotelHashValues; text: string }[] = [ + // TODO these titles will need to reflect the facility card titles, which will vary between hotels { href: HotelHashValues.overview, text: "Overview" }, { href: HotelHashValues.rooms, text: "Rooms" }, { href: HotelHashValues.restaurant, text: "Restaurant & Bar" }, diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index 6b5abc9dd..cbd171f33 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -1,6 +1,7 @@ import { serverClient } from "@/lib/trpc/server" import { MOCK_FACILITIES } from "./Facilities/mockData" +import { setActivityCard } from "./Facilities/utils" import AmenitiesList from "./AmenitiesList" import Facilities from "./Facilities" import IntroSection from "./IntroSection" @@ -28,8 +29,11 @@ export default async function HotelPage() { hotelDetailedFacilities, hotelImages, roomCategories, + activitiesCard, } = hotelData + MOCK_FACILITIES.push(setActivityCard(activitiesCard)) + return (
diff --git a/lib/graphql/Query/HotelPage.graphql b/lib/graphql/Query/HotelPage.graphql index 84de71e64..39e4e230f 100644 --- a/lib/graphql/Query/HotelPage.graphql +++ b/lib/graphql/Query/HotelPage.graphql @@ -3,6 +3,34 @@ query GetHotelPage($locale: String!, $uid: String!) { hotel_page_id url title + content { + ... on HotelPageContentUpcomingActivitiesCard { + __typename + upcoming_activities_card { + background_image + cta_text + heading + body_text + open_in_new_tab + scripted_title + hotel_page_activities_content_pageConnection { + edges { + node { + ... on ContentPage { + url + web { + original_url + } + system { + locale + } + } + } + } + } + } + } + } } } diff --git a/server/routers/contentstack/hotelPage/output.ts b/server/routers/contentstack/hotelPage/output.ts index 3c002e50a..1002fc80c 100644 --- a/server/routers/contentstack/hotelPage/output.ts +++ b/server/routers/contentstack/hotelPage/output.ts @@ -1,6 +1,50 @@ import { z } from "zod" +import { HotelBlocksTypenameEnum } from "@/types/components/hotelPage/enums" + +export const activityCardSchema = z.object({ + background_image: z.any(), + cta_text: z.string(), + heading: z.string(), + open_in_new_tab: z.boolean(), + scripted_title: z.string().optional(), + body_text: z.string(), + hotel_page_activities_content_pageConnection: z.object({ + edges: z.array( + z.object({ + node: z.object({ + url: z.string(), + web: z.object({ + original_url: z.string().optional(), + }), + system: z.object({ + locale: z.string(), + }), + }), + }) + ), + }), +}) + export const validateHotelPageSchema = z.object({ + hotel_page: z.object({ + hotel_page_id: z.string(), + title: z.string(), + url: z.string(), + content: z + .array( + z.object({ + __typename: z.literal( + HotelBlocksTypenameEnum.HotelPageContentUpcomingActivitiesCard + ), + upcoming_activities_card: activityCardSchema.optional(), + }) + ) + .optional(), + }), +}) + +export const hotelPageSchema = z.object({ hotel_page: z.object({ hotel_page_id: z.string(), title: z.string(), @@ -12,5 +56,6 @@ export const validateHotelPageSchema = z.object({ export type HotelPageDataRaw = z.infer type HotelPageRaw = HotelPageDataRaw["hotel_page"] - export type HotelPage = HotelPageRaw + +export type ActivityCard = z.infer diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index 731b08306..50d4d0507 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -17,6 +17,9 @@ import { } from "@/server/trpc" import { toApiLang } from "@/server/utils" +import { makeImageVaultImage } from "@/utils/imageVault" +import { removeMultipleSlashes } from "@/utils/url" + import { HotelPageDataRaw, validateHotelPageSchema, @@ -37,6 +40,7 @@ import { import tempFilterData from "./tempFilterData.json" import tempRatesData from "./tempRatesData.json" +import { HotelBlocksTypenameEnum } from "@/types/components/hotelPage/enums" import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel" const meter = metrics.getMeter("trpc.hotels") @@ -52,7 +56,10 @@ const availabilityFailCounter = meter.createCounter( "trpc.hotel.availability-fail" ) -async function getHotelId(locale: string, uid: string | null | undefined) { +async function getContenstackData( + locale: string, + uid: string | null | undefined +) { const rawContentStackData = await request(GetHotelPage, { locale, uid, @@ -74,7 +81,7 @@ async function getHotelId(locale: string, uid: string | null | undefined) { return null } - return hotelPageData.data.hotel_page.hotel_page_id + return hotelPageData.data.hotel_page } export const hotelQueryRouter = router({ @@ -83,7 +90,8 @@ export const hotelQueryRouter = router({ .query(async ({ ctx, input }) => { const { lang, uid } = ctx const { include } = input - const hotelId = await getHotelId(lang, uid) + const contentstackData = await getContenstackData(lang, uid) + const hotelId = contentstackData?.hotel_page_id if (!hotelId) { throw notFound(`Hotel not found for uid: ${uid}`) @@ -203,6 +211,32 @@ export const hotelQueryRouter = router({ }) : [] + const activities = contentstackData?.content + ? contentstackData.content.map((block: any) => { + switch (block.__typename) { + case HotelBlocksTypenameEnum.HotelPageContentUpcomingActivitiesCard: + return { + ...block.upcoming_activities_card, + background_image: makeImageVaultImage( + block.upcoming_activities_card.background_image + ), + contentPage: + block.upcoming_activities_card?.hotel_page_activities_content_pageConnection?.edges.map( + ({ node: contentPage }: { node: any }) => { + return { + href: + contentPage.web?.original_url || + removeMultipleSlashes( + `/${contentPage.system.locale}/${contentPage.url}` + ), + } + } + ), + } + } + })[0] + : null + getHotelSuccessCounter.add(1, { hotelId, lang, include }) console.info( "api.hotels.hotel success", @@ -219,6 +253,7 @@ export const hotelQueryRouter = router({ hotelDetailedFacilities: hotelAttributes.detailedFacilities, hotelImages: images, roomCategories, + activitiesCard: activities, } }), availability: router({ diff --git a/types/components/hotelPage/enums.ts b/types/components/hotelPage/enums.ts new file mode 100644 index 000000000..d7e5f5bec --- /dev/null +++ b/types/components/hotelPage/enums.ts @@ -0,0 +1,3 @@ +export enum HotelBlocksTypenameEnum { + HotelPageContentUpcomingActivitiesCard = "HotelPageContentUpcomingActivitiesCard", +} diff --git a/utils/cardTheme.ts b/utils/cardTheme.ts index 39f42a4c1..6e8bfb700 100644 --- a/utils/cardTheme.ts +++ b/utils/cardTheme.ts @@ -38,7 +38,11 @@ export function getTheme(theme: CardProps["theme"]) { primaryLinkColor = "pale" secondaryLinkColor = "burgundy" break - case "primaryStrong" || "image": + case "primaryStrong": + buttonTheme = "primaryStrong" + primaryLinkColor = "red" + secondaryLinkColor = "white" + case "image": buttonTheme = "primaryStrong" primaryLinkColor = "red" secondaryLinkColor = "white"