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 ea688679c..e594d757c 100644
--- a/components/ContentType/HotelPage/TabNavigation/index.tsx
+++ b/components/ContentType/HotelPage/TabNavigation/index.tsx
@@ -13,6 +13,7 @@ export default function TabNavigation() {
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 d866f1691..abc3b9713 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"