Merged in fix/move-sas-comparison-to-dynamic-content (pull request #1279)

Move SASTierComparison block to DynamicContent

Approved-by: Erik Tiekstra
This commit is contained in:
Anton Gunnarsson
2025-02-11 09:36:41 +00:00
parent 1d4993fb7f
commit 6eb01750e8
20 changed files with 211 additions and 147 deletions

View File

@@ -0,0 +1,25 @@
import { serverClient } from "@/lib/trpc/server"
import { SasTierComparison } from "@/components/SasTierComparison"
type SASTierComparisonBlockProps = {
title: string
preamble: string
}
export default async function SASTierComparisonBlock({
title,
preamble,
}: SASTierComparisonBlockProps) {
const tierComparison =
await serverClient().contentstack.partner.getSasTierComparison()
if (!tierComparison) return null
return (
<SasTierComparison
title={title}
preamble={preamble}
tierComparison={tierComparison}
/>
)
}

View File

@@ -11,6 +11,7 @@ import ExpiringPoints from "@/components/Blocks/DynamicContent/Points/ExpiringPo
import PointsOverview from "@/components/Blocks/DynamicContent/Points/Overview"
import CurrentRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/CurrentRewards"
import NextLevelRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/NextLevel"
import SASTierComparisonBlock from "@/components/Blocks/DynamicContent/SASTierComparison"
import SignupFormWrapper from "@/components/Blocks/DynamicContent/SignupFormWrapper"
import SignUpVerification from "@/components/Blocks/DynamicContent/SignUpVerification"
import PreviousStays from "@/components/Blocks/DynamicContent/Stays/Previous"
@@ -74,6 +75,13 @@ function DynamicContentBlocks(props: DynamicContentProps) {
return <SoonestStays {...dynamic_content} />
case DynamicContentEnum.Blocks.components.upcoming_stays:
return <UpcomingStays {...dynamic_content} />
case DynamicContentEnum.Blocks.components.sas_tier_comparison:
return (
<SASTierComparisonBlock
title={dynamic_content.title}
preamble={dynamic_content.subtitle}
/>
)
default:
return null
}

View File

@@ -7,7 +7,6 @@ import ShortcutsList from "@/components/Blocks/ShortcutsList"
import TextCols from "@/components/Blocks/TextCols"
import UspGrid from "@/components/Blocks/UspGrid"
import JsonToHtml from "@/components/JsonToHtml"
import { SasTierComparison } from "@/components/SasTierComparison"
import AccordionSection from "./Accordion"
import FullWidthCampaign from "./FullWidthCampaign"
@@ -103,8 +102,6 @@ export default function Blocks({ blocks }: BlocksProps) {
)
case BlocksEnums.block.UspGrid:
return <UspGrid usp_grid={block.usp_grid} />
case BlocksEnums.block.SasTierComparison:
return <SasTierComparison content={block.sas_tier_comparison} />
case BlocksEnums.block.FullWidthCampaign:
return <FullWidthCampaign content={block.full_width_campaign} />
case BlocksEnums.block.JoinScandicFriends:

View File

@@ -12,30 +12,28 @@ import Caption from "../TempDesignSystem/Text/Caption"
import Subtitle from "../TempDesignSystem/Text/Subtitle"
import Title from "../TempDesignSystem/Text/Title"
import styles from "./sas-tier-comparison.module.css"
import styles from "./sasTierComparison.module.css"
import type { ReactNode } from "react"
import type { SasTierComparison } from "@/types/trpc/routers/contentstack/blocks"
type SasTierComparisonContent = SasTierComparison["sas_tier_comparison"]
import type { SasTierComparison } from "@/types/trpc/routers/contentstack/partner"
type TierComparisonProps = {
content: SasTierComparisonContent
title?: string
preamble?: string
tierComparison: NonNullable<SasTierComparison>
}
export function SasTierComparison({ content }: TierComparisonProps) {
const comparisonContent = content.sasTierComparison
if (!comparisonContent) return null
export function SasTierComparison({
title,
preamble,
tierComparison,
}: TierComparisonProps) {
return (
<SectionContainer className={styles.comparisonSection}>
<div className={styles.header}>
<Title level="h2">{comparisonContent.title}</Title>
{comparisonContent.preamble && (
<p className={styles.preamble}>{comparisonContent.preamble}</p>
)}
<Title level="h2">{title}</Title>
{preamble && <p className={styles.preamble}>{preamble}</p>}
</div>
<div>
<div className={styles.columnHeaders}>
@@ -53,11 +51,11 @@ export function SasTierComparison({ content }: TierComparisonProps) {
priority
width={215}
/>
<ColumnTitle>{comparisonContent.scandic_column_title}</ColumnTitle>
<ColumnTitle>{comparisonContent.sas_column_title}</ColumnTitle>
<ColumnTitle>{tierComparison.scandic_column_title}</ColumnTitle>
<ColumnTitle>{tierComparison.sas_column_title}</ColumnTitle>
</div>
<div className={styles.tierMatchList}>
{comparisonContent.tier_matches.map((tierMatch, i) => (
{tierComparison.tier_matches.map((tierMatch, i) => (
<TierDetails key={i} tierMatch={tierMatch}>
<JsonToHtml
nodes={tierMatch.content.json?.children}
@@ -68,10 +66,10 @@ export function SasTierComparison({ content }: TierComparisonProps) {
))}
</div>
</div>
{comparisonContent.cta?.href && (
{tierComparison.cta?.href && (
<Button theme="primaryLight" asChild className={styles.ctaButton}>
<Link href={comparisonContent.cta.href} color="white">
{comparisonContent.cta.title}
<Link href={tierComparison.cta.href} color="white">
{tierComparison.cta.title}
</Link>
</Button>
)}
@@ -79,9 +77,7 @@ export function SasTierComparison({ content }: TierComparisonProps) {
)
}
type TierMatch = NonNullable<
SasTierComparisonContent["sasTierComparison"]
>["tier_matches"][number]
type TierMatch = NonNullable<SasTierComparison>["tier_matches"][number]
function TierDetails({
children,

View File

@@ -1,35 +0,0 @@
fragment SasTierComparison_ContentPage on ContentPageBlocksSasTierComparison {
__typename
sas_tier_comparison {
comparisonConnection {
totalCount
edges {
node {
__typename
... on SasTierComparison {
title
preamble
scandic_column_title
sas_column_title
tier_matches {
title
scandic_friends_tier_name
sas_eb_tier_name
content {
json
}
link {
href
title
}
}
cta {
title
href
}
}
}
}
}
}
}

View File

@@ -9,7 +9,6 @@
#import "../../Fragments/Blocks/Table.graphql"
#import "../../Fragments/Blocks/TextCols.graphql"
#import "../../Fragments/Blocks/UspGrid.graphql"
#import "../../Fragments/Blocks/SasTierComparison.graphql"
#import "../../Fragments/ContentPage/NavigationLinks.graphql"
#import "../../Fragments/Sidebar/Content.graphql"
@@ -76,15 +75,6 @@ query GetContentPageBlocksBatch2($locale: String!, $uid: String!) {
}
}
query GetContentPageBlocksBatch3($locale: String!, $uid: String!) {
content_page(uid: $uid, locale: $locale) {
blocks {
__typename
...SasTierComparison_ContentPage
}
}
}
query GetContentPageRefs($locale: String!, $uid: String!) {
content_page(locale: $locale, uid: $uid) {
header {

View File

@@ -0,0 +1,24 @@
query GetAllSasTierComparison($lang: String!) {
all_sas_tier_comparison(locale: $lang) {
items {
scandic_column_title
sas_column_title
tier_matches {
title
scandic_friends_tier_name
sas_eb_tier_name
content {
json
}
link {
href
title
}
}
cta {
title
href
}
}
}
}

View File

@@ -19,7 +19,6 @@ import {
dynamicContentSchema as blockDynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import { hotelListingSchema } from "../schemas/blocks/hotelListing"
import { sasTierComparisonSchema } from "../schemas/blocks/sasTierComparison"
import {
shortcutsRefsSchema,
shortcutsSchema,
@@ -106,14 +105,6 @@ export const contentPageAccordion = z
})
.merge(accordionSchema)
export const contentPageLoyaltyTierComparison = z
.object({
__typename: z.literal(
ContentPageEnum.ContentStack.blocks.SasTierComparison
),
})
.merge(sasTierComparisonSchema)
export const contentPageHotelListing = z
.object({
__typename: z.literal(ContentPageEnum.ContentStack.blocks.HotelListing),
@@ -129,7 +120,6 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
contentPageTable,
contentPageTextCols,
contentPageUspGrid,
contentPageLoyaltyTierComparison,
contentPageHotelListing,
])

View File

@@ -3,7 +3,6 @@ import {
GetContentPage,
GetContentPageBlocksBatch1,
GetContentPageBlocksBatch2,
GetContentPageBlocksBatch3,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
@@ -73,17 +72,6 @@ export const contentPageQueryRouter = router({
},
},
},
{
document: GetContentPageBlocksBatch3,
variables: { locale: lang, uid },
options: {
cache: "force-cache",
next: {
tags,
},
},
},
])
const contentPage = contentPageSchema.safeParse(contentPageRequest.data)

View File

@@ -15,6 +15,7 @@ import { loyaltyLevelRouter } from "./loyaltyLevel"
import { loyaltyPageRouter } from "./loyaltyPage"
import { metadataRouter } from "./metadata"
import { myPagesRouter } from "./myPages"
import { partnerRouter } from "./partner"
import { rewardRouter } from "./reward"
import { startPageRouter } from "./startPage"
@@ -36,4 +37,5 @@ export const contentstackRouter = router({
rewards: rewardRouter,
loyaltyLevels: loyaltyLevelRouter,
startPage: startPageRouter,
partner: partnerRouter,
})

View File

@@ -0,0 +1,5 @@
import { mergeRouters } from "@/server/trpc"
import { partnerQueryRouter } from "./query"
export const partnerRouter = mergeRouters(partnerQueryRouter)

View File

@@ -0,0 +1,31 @@
import { z } from "zod"
const link = z.object({
href: z.string(),
title: z.string(),
})
export const validateSasTierComparisonSchema = z
.object({
all_sas_tier_comparison: z.object({
items: z.array(
z.object({
scandic_column_title: z.string(),
sas_column_title: z.string(),
tier_matches: z.array(
z.object({
scandic_friends_tier_name: z.string(),
sas_eb_tier_name: z.string(),
title: z.string(),
content: z.object({
json: z.any(), // json
}),
link: link.optional(),
})
),
cta: link.optional(),
})
),
}),
})
.transform((data) => data.all_sas_tier_comparison.items.at(0))

View File

@@ -0,0 +1,86 @@
import { metrics } from "@opentelemetry/api"
import { cache } from "react"
import { GetAllSasTierComparison } from "@/lib/graphql/Query/SASTierComparison.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { validateSasTierComparisonSchema } from "./output"
import type { SasTierComparisonResponse } from "@/types/trpc/routers/contentstack/partner"
import type { Context } from "@/server/context"
const meter = metrics.getMeter("trpc.partner")
const getSasTierComparisonCounter = meter.createCounter(
"trpc.contentstack.partner.getSasTierComparison"
)
const getSasTierComparisonSuccessCounter = meter.createCounter(
"trpc.contentstack.partner.getSasTierComparison-success"
)
const getSasTierComparisonFailCounter = meter.createCounter(
"trpc.contentstack.partner.getSasTierComparison-fail"
)
export const getSasTierComparison = cache(async (ctx: Context) => {
getSasTierComparisonCounter.add(1)
const tag = `${ctx.lang}:sas_tier_comparison`
const sasTierComparisonConfigResponse =
await request<SasTierComparisonResponse>(
GetAllSasTierComparison,
{ lang: ctx.lang },
{
next: {
tags: [tag],
},
cache: "force-cache",
}
)
if (!sasTierComparisonConfigResponse.data) {
getSasTierComparisonFailCounter.add(1)
const notFoundError = notFound(sasTierComparisonConfigResponse)
console.error(
"contentstack.sas not found error",
JSON.stringify({
query: {
lang: ctx.lang,
},
error: { code: notFoundError.code },
})
)
throw notFoundError
}
const validatedSasTierComparison = validateSasTierComparisonSchema.safeParse(
sasTierComparisonConfigResponse.data
)
if (!validatedSasTierComparison.success) {
getSasTierComparisonFailCounter.add(1)
console.error(validatedSasTierComparison.error)
console.error(
"contentstack.sas validation error",
JSON.stringify({
query: {
lang: ctx.lang,
},
error: validatedSasTierComparison.error,
})
)
return null
}
getSasTierComparisonSuccessCounter.add(1)
return validatedSasTierComparison.data
})
export const partnerQueryRouter = router({
getSasTierComparison: contentstackBaseProcedure.query(async function ({
ctx,
}) {
return getSasTierComparison(ctx)
}),
})

View File

@@ -1,49 +0,0 @@
import { z } from "zod"
import { MembershipLevelEnum } from "@/constants/membershipLevels"
import { BlocksEnums } from "@/types/enums/blocks"
const link = z.object({
href: z.string(),
title: z.string(),
})
export const sasTierComparisonSchema = z.object({
typename: z
.literal(BlocksEnums.block.SasTierComparison)
.optional()
.default(BlocksEnums.block.SasTierComparison),
sas_tier_comparison: z
.object({
comparisonConnection: z.object({
edges: z.array(
z.object({
node: z.object({
title: z.string(),
preamble: z.string().optional(),
scandic_column_title: z.string(),
sas_column_title: z.string(),
tier_matches: z.array(
z.object({
scandic_friends_tier_name: z.string(),
sas_eb_tier_name: z.string(),
title: z.string(),
content: z.object({
json: z.any(), // json
}),
link: link.optional(),
})
),
cta: link.optional(),
}),
})
),
}),
})
.transform((data) => {
return {
sasTierComparison: data.comparisonConnection.edges.at(0)?.node ?? null,
}
}),
})

View File

@@ -9,7 +9,6 @@ export namespace BlocksEnums {
TextCols = "TextCols",
TextContent = "TextContent",
UspGrid = "UspGrid",
SasTierComparison = "SasTierComparison",
HotelListing = "HotelListing",
FullWidthCampaign = "FullWidthCampaign",
CarouselCards = "CarouselCards",

View File

@@ -9,7 +9,6 @@ export namespace ContentPageEnum {
TextCols = "ContentPageBlocksTextCols",
UspGrid = "ContentPageBlocksUspGrid",
Table = "ContentPageBlocksTable",
SasTierComparison = "ContentPageBlocksSasTierComparison",
HotelListing = "ContentPageBlocksHotelListing",
}

View File

@@ -16,6 +16,7 @@ export namespace DynamicContentEnum {
sign_up_verification = "sign_up_verification",
soonest_stays = "soonest_stays",
upcoming_stays = "upcoming_stays",
sas_tier_comparison = "sas_tier_comparison",
}
/** Type needed to satisfy zod enum type */
@@ -35,6 +36,7 @@ export namespace DynamicContentEnum {
components.sign_up_verification,
components.soonest_stays,
components.upcoming_stays,
components.sas_tier_comparison,
]
}

View File

@@ -6,7 +6,6 @@ import type { carouselCardsSchema } from "@/server/routers/contentstack/schemas/
import type { contentSchema } from "@/server/routers/contentstack/schemas/blocks/content"
import type { dynamicContentSchema } from "@/server/routers/contentstack/schemas/blocks/dynamicContent"
import type { hotelListingSchema } from "@/server/routers/contentstack/schemas/blocks/hotelListing"
import type { sasTierComparisonSchema } from "@/server/routers/contentstack/schemas/blocks/sasTierComparison"
import type { shortcutsSchema } from "@/server/routers/contentstack/schemas/blocks/shortcuts"
import type { tableSchema } from "@/server/routers/contentstack/schemas/blocks/table"
import type { textColsSchema } from "@/server/routers/contentstack/schemas/blocks/textCols"
@@ -24,6 +23,4 @@ export interface TextCols extends z.output<typeof textColsSchema> {}
export interface UspGrid extends z.output<typeof uspGridSchema> {}
interface GetHotelListing extends z.output<typeof hotelListingSchema> {}
export type HotelListing = GetHotelListing["hotel_listing"]
export interface SasTierComparison
extends z.output<typeof sasTierComparisonSchema> {}
export interface CarouselCards extends z.output<typeof carouselCardsSchema> {}

View File

@@ -0,0 +1,9 @@
import type { z } from "zod"
import type { validateSasTierComparisonSchema } from "@/server/routers/contentstack/partner/output"
export type SasTierComparisonResponse = z.input<
typeof validateSasTierComparisonSchema
>
export type SasTierComparison = z.output<typeof validateSasTierComparisonSchema>