diff --git a/components/Blocks/DynamicContent/Rewards/Redeem/Flows/Tier.tsx b/components/Blocks/DynamicContent/Rewards/Redeem/Flows/Tier.tsx index edf0680b5..69831bd57 100644 --- a/components/Blocks/DynamicContent/Rewards/Redeem/Flows/Tier.tsx +++ b/components/Blocks/DynamicContent/Rewards/Redeem/Flows/Tier.tsx @@ -2,6 +2,7 @@ import { useIntl } from "react-intl" +import JsonToHtml from "@/components/JsonToHtml" import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" @@ -53,7 +54,12 @@ export default function Tier({ )} {redeemStep === "confirmation" && ( - {reward.redeem_description} + )} {redeemStep === "redeemed" && @@ -63,7 +69,10 @@ export default function Tier({ )} ) : ( - {reward.redeem_description} + )} diff --git a/components/JsonToHtml/renderOptions.tsx b/components/JsonToHtml/renderOptions.tsx index 4b9a8d308..315c37e4e 100644 --- a/components/JsonToHtml/renderOptions.tsx +++ b/components/JsonToHtml/renderOptions.tsx @@ -66,9 +66,11 @@ function extractPossibleAttributes(attrs: Attributes | undefined) { props.className = attrs["class-name"] } else if (attrs.classname) { props.className = attrs.classname - } else if (attrs?.style?.["text-align"]) { + } + + if (attrs.style?.["text-align"]) { props.style = { - textAlign: attrs?.style?.["text-align"], + textAlign: attrs.style["text-align"], } } diff --git a/lib/graphql/Query/RewardsWithRedeem.graphql b/lib/graphql/Query/RewardsWithRedeem.graphql index b6ad7e4ae..91ed59491 100644 --- a/lib/graphql/Query/RewardsWithRedeem.graphql +++ b/lib/graphql/Query/RewardsWithRedeem.graphql @@ -1,3 +1,25 @@ +#import "../Fragments/System.graphql" + +#import "../Fragments/PageLink/AccountPageLink.graphql" +#import "../Fragments/PageLink/CollectionPageLink.graphql" +#import "../Fragments/PageLink/ContentPageLink.graphql" +#import "../Fragments/PageLink/DestinationCityPageLink.graphql" +#import "../Fragments/PageLink/DestinationCountryPageLink.graphql" +#import "../Fragments/PageLink/DestinationOverviewPageLink.graphql" +#import "../Fragments/PageLink/HotelPageLink.graphql" +#import "../Fragments/PageLink/LoyaltyPageLink.graphql" +#import "../Fragments/PageLink/StartPageLink.graphql" + +#import "../Fragments/AccountPage/Ref.graphql" +#import "../Fragments/CollectionPage/Ref.graphql" +#import "../Fragments/ContentPage/Ref.graphql" +#import "../Fragments/DestinationCityPage/Ref.graphql" +#import "../Fragments/DestinationCountryPage/Ref.graphql" +#import "../Fragments/DestinationOverviewPage/Ref.graphql" +#import "../Fragments/HotelPage/Ref.graphql" +#import "../Fragments/LoyaltyPage/Ref.graphql" +#import "../Fragments/StartPage/Ref.graphql" + query GetRewards($locale: String!, $rewardIds: [String!]) { all_reward(locale: $locale, where: { reward_id_in: $rewardIds }) { items { @@ -7,10 +29,56 @@ query GetRewards($locale: String!, $rewardIds: [String!]) { label grouped_label description - redeem_description + redeem_description { + json + embedded_itemsConnection { + edges { + node { + __typename + ...AccountPageLink + ...CollectionPageLink + ...ContentPageLink + ...DestinationCityPageLink + ...DestinationCountryPageLink + ...DestinationOverviewPageLink + ...HotelPageLink + ...LoyaltyPageLink + ...StartPageLink + } + } + } + } grouped_description value reward_id } } } + +query GetRewardsRef($locale: String!, $rewardIds: [String!]) { + all_reward(locale: $locale, where: { reward_id_in: $rewardIds }) { + items { + redeem_description { + embedded_itemsConnection { + edges { + node { + __typename + ...AccountPageRef + ...CollectionPageRef + ...ContentPageRef + ...DestinationCityPageRef + ...DestinationCountryPageRef + ...DestinationOverviewPageRef + ...HotelPageRef + ...LoyaltyPageRef + ...StartPageRef + } + } + } + } + system { + ...System + } + } + } +} diff --git a/server/routers/contentstack/reward/output.ts b/server/routers/contentstack/reward/output.ts index 3152d5b5c..25e07a22e 100644 --- a/server/routers/contentstack/reward/output.ts +++ b/server/routers/contentstack/reward/output.ts @@ -2,6 +2,13 @@ import { z } from "zod" import { MembershipLevelEnum } from "@/constants/membershipLevels" +import { + linkRefsUnionSchema, + linkUnionSchema, + transformPageLink, +} from "../schemas/pageLinks" +import { systemSchema } from "../schemas/system" + const Coupon = z.object({ code: z.string().optional(), status: z.string().optional(), @@ -133,10 +140,22 @@ export const validateCmsRewardsWithRedeemSchema = z reward_id: z.string(), grouped_label: z.string().optional(), description: z.string().optional(), - redeem_description: z - .string() - .nullable() - .transform((val) => val || ""), + redeem_description: z.object({ + json: z.any(), // JSON + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: linkUnionSchema.transform((data) => { + const link = transformPageLink(data) + if (link) { + return link + } + return data + }), + }) + ), + }), + }), grouped_description: z.string().optional(), value: z.string().optional(), }) @@ -156,6 +175,30 @@ export type CmsRewardsWithRedeemResponse = z.input< typeof validateCmsRewardsWithRedeemSchema > +export const rewardWithRedeemRefsSchema = z.object({ + data: z.object({ + all_reward: z.object({ + items: z.array( + z.object({ + redeem_description: z.object({ + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: linkRefsUnionSchema, + }) + ), + }), + }), + system: systemSchema, + }) + ), + }), + }), +}) + +export interface GetRewardWithRedeemRefsSchema + extends z.input {} + export type CMSReward = z.output[0] export type CMSRewardWithRedeem = z.output< diff --git a/server/routers/contentstack/reward/utils.ts b/server/routers/contentstack/reward/utils.ts index e3309417a..63c850086 100644 --- a/server/routers/contentstack/reward/utils.ts +++ b/server/routers/contentstack/reward/utils.ts @@ -4,17 +4,22 @@ import { unstable_cache } from "next/cache" import { env } from "@/env/server" import * as api from "@/lib/api" import { GetRewards } from "@/lib/graphql/Query/Rewards.graphql" -import { GetRewards as GetRewardsWithReedem } from "@/lib/graphql/Query/RewardsWithRedeem.graphql" +import { + GetRewards as GetRewardsWithReedem, + GetRewardsRef as GetRewardsWithRedeemRef, +} from "@/lib/graphql/Query/RewardsWithRedeem.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" -import { generateLoyaltyConfigTag } from "@/utils/generateTag" +import { generateLoyaltyConfigTag, generateTag } from "@/utils/generateTag" import { type ApiReward, type CategorizedApiReward, type CmsRewardsResponse, type CmsRewardsWithRedeemResponse, + type GetRewardWithRedeemRefsSchema, + rewardWithRedeemRefsSchema, validateApiAllTiersSchema, validateApiTierRewardsSchema, validateCmsRewardsSchema, @@ -70,6 +75,16 @@ export const getRedeemSuccessCounter = meter.createCounter( "trpc.contentstack.reward.redeem-success" ) +export const getAllCMSRewardRefsCounter = meter.createCounter( + "trpc.contentstack.reward.all" +) +export const getAllCMSRewardRefsFailCounter = meter.createCounter( + "trpc.contentstack.reward.all-fail" +) +export const getAllCMSRewardRefsSuccessCounter = meter.createCounter( + "trpc.contentstack.reward.all-success" +) + const ONE_HOUR = 60 * 60 export function getUniqueRewardIds(rewardIds: string[]) { @@ -199,32 +214,100 @@ export const getCachedAllTierRewards = unstable_cache( { revalidate: ONE_HOUR } ) -export async function getCmsRewards(locale: Lang, rewardIds: string[]) { +export async function getCmsRewards(lang: Lang, rewardIds: string[]) { const tags = rewardIds.map((id) => - generateLoyaltyConfigTag(locale, "reward", id) + generateLoyaltyConfigTag(lang, "reward", id) ) - const cmsRewardsResponse = env.USE_NEW_REWARD_MODEL - ? await request( - GetRewardsWithReedem, - { - locale: locale, - rewardIds, + let cmsRewardsResponse + if (env.USE_NEW_REWARD_MODEL) { + getAllCMSRewardRefsCounter.add(1, { lang, rewardIds }) + console.info( + "contentstack.reward.refs start", + JSON.stringify({ + query: { lang, rewardIds }, + }) + ) + const refsResponse = await request( + GetRewardsWithRedeemRef, + { + locale: lang, + rewardIds, + }, + { + cache: "force-cache", + next: { + tags: rewardIds.map((rewardId) => generateTag(lang, rewardId)), }, - { next: { tags }, cache: "force-cache" } + } + ) + if (!refsResponse.data) { + const notFoundError = notFound(refsResponse) + getAllCMSRewardRefsFailCounter.add(1, { + lang, + rewardIds, + error_type: "not_found", + error: JSON.stringify({ code: notFoundError.code }), + }) + console.error( + "contentstack.reward.refs not found error", + JSON.stringify({ + query: { lang, rewardIds }, + error: { code: notFoundError.code }, + }) ) - : await request( - GetRewards, - { - locale: locale, - rewardIds, - }, - { next: { tags }, cache: "force-cache" } + throw notFoundError + } + + const validatedRefsData = rewardWithRedeemRefsSchema.safeParse(refsResponse) + + if (!validatedRefsData.success) { + getAllCMSRewardRefsFailCounter.add(1, { + lang, + rewardIds, + error_type: "validation_error", + error: JSON.stringify(validatedRefsData.error), + }) + console.error( + "contentstack.reward.refs validation error", + JSON.stringify({ + query: { lang, rewardIds }, + error: validatedRefsData.error, + }) ) + return null + } + + getAllCMSRewardRefsSuccessCounter.add(1, { lang, rewardIds }) + console.info( + "contentstack.startPage.refs success", + JSON.stringify({ + query: { lang, rewardIds }, + }) + ) + + cmsRewardsResponse = await request( + GetRewardsWithReedem, + { + locale: lang, + rewardIds, + }, + { next: { tags }, cache: "force-cache" } + ) + } else { + cmsRewardsResponse = await request( + GetRewards, + { + locale: lang, + rewardIds, + }, + { next: { tags }, cache: "force-cache" } + ) + } if (!cmsRewardsResponse.data) { getAllRewardFailCounter.add(1, { - lang: locale, + lang, error_type: "validation_error", error: JSON.stringify(cmsRewardsResponse.data), }) @@ -233,7 +316,7 @@ export async function getCmsRewards(locale: Lang, rewardIds: string[]) { "contentstack.rewards not found error", JSON.stringify({ query: { - locale, + locale: lang, rewardIds, }, error: { code: notFoundError.code }, @@ -248,7 +331,7 @@ export async function getCmsRewards(locale: Lang, rewardIds: string[]) { if (!validatedCmsRewards.success) { getAllRewardFailCounter.add(1, { - locale, + locale: lang, rewardIds, error_type: "validation_error", error: JSON.stringify(validatedCmsRewards.error), @@ -257,7 +340,7 @@ export async function getCmsRewards(locale: Lang, rewardIds: string[]) { console.error( "contentstack.rewards validation error", JSON.stringify({ - query: { locale, rewardIds }, + query: { locale: lang, rewardIds }, error: validatedCmsRewards.error, }) )