Merged in feat/LOY-135-change-redeem-desc-to-rte (pull request #1350)

feat(LOY-135): change redeem description to be RTE

Approved-by: Erik Tiekstra
This commit is contained in:
Christian Andolf
2025-02-17 14:12:16 +00:00
5 changed files with 236 additions and 31 deletions

View File

@@ -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" && (
<Body textAlign="center">{reward.redeem_description}</Body>
<JsonToHtml
embeds={
reward.redeem_description.embedded_itemsConnection.edges
}
nodes={reward.redeem_description.json.children}
/>
)}
{redeemStep === "redeemed" &&
@@ -63,7 +69,10 @@ export default function Tier({
)}
</>
) : (
<Body textAlign="center">{reward.redeem_description}</Body>
<JsonToHtml
embeds={reward.redeem_description.embedded_itemsConnection.edges}
nodes={reward.redeem_description.json.children}
/>
)}
</div>

View File

@@ -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"],
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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<typeof rewardWithRedeemRefsSchema> {}
export type CMSReward = z.output<typeof validateCmsRewardsSchema>[0]
export type CMSRewardWithRedeem = z.output<

View File

@@ -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<CmsRewardsWithRedeemResponse>(
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<GetRewardWithRedeemRefsSchema>(
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<CmsRewardsResponse>(
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<CmsRewardsWithRedeemResponse>(
GetRewardsWithReedem,
{
locale: lang,
rewardIds,
},
{ next: { tags }, cache: "force-cache" }
)
} else {
cmsRewardsResponse = await request<CmsRewardsResponse>(
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,
})
)