fix(SW-556): now fetching surprises separately in component.
also showing surprises on any account page
This commit is contained in:
@@ -2,20 +2,52 @@ import { z } from "zod"
|
||||
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
|
||||
const Coupon = z.object({
|
||||
code: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
createdAt: z.string().datetime({ offset: true }).optional(),
|
||||
customer: z.object({
|
||||
id: z.string().optional(),
|
||||
}),
|
||||
name: z.string().optional(),
|
||||
claimedAt: z.string().datetime({ offset: true }).optional(),
|
||||
// redeemedAt: z.string().datetime({ offset: true }).optional(),
|
||||
type: z.string().optional(),
|
||||
value: z.number().optional(),
|
||||
pool: z.string().optional(),
|
||||
cfUnwrapped: z.boolean().default(false),
|
||||
})
|
||||
|
||||
const SurpriseReward = z.object({
|
||||
title: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
type: z.literal("coupon"),
|
||||
status: z.string().optional(),
|
||||
rewardId: z.string().optional(),
|
||||
redeemLocation: z.string().optional(),
|
||||
autoApplyReward: z.boolean().default(false),
|
||||
rewardType: z.string().optional(),
|
||||
endsAt: z.string().datetime({ offset: true }).optional(),
|
||||
coupons: z.array(Coupon).optional(),
|
||||
})
|
||||
|
||||
export const validateApiRewardSchema = z
|
||||
.object({
|
||||
data: z.array(
|
||||
z.object({
|
||||
title: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
rewardId: z.string().optional(),
|
||||
redeemLocation: z.string().optional(),
|
||||
autoApplyReward: z.boolean().default(false),
|
||||
rewardType: z.string().optional(),
|
||||
rewardTierLevel: z.string().optional(),
|
||||
})
|
||||
z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
title: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
type: z.literal("custom"),
|
||||
status: z.string().optional(),
|
||||
rewardId: z.string().optional(),
|
||||
redeemLocation: z.string().optional(),
|
||||
autoApplyReward: z.boolean().default(false),
|
||||
rewardType: z.string().optional(),
|
||||
rewardTierLevel: z.string().optional(),
|
||||
}),
|
||||
SurpriseReward,
|
||||
])
|
||||
),
|
||||
})
|
||||
.transform((data) => data.data)
|
||||
@@ -77,6 +109,8 @@ export const validateCmsRewardsSchema = z
|
||||
|
||||
export type ApiReward = z.output<typeof validateApiRewardSchema>[0]
|
||||
|
||||
export type SurpriseReward = z.output<typeof SurpriseReward>
|
||||
|
||||
export type CmsRewardsResponse = z.input<typeof validateCmsRewardsSchema>
|
||||
|
||||
export type Reward = z.output<typeof validateCmsRewardsSchema>[0]
|
||||
|
||||
@@ -24,11 +24,14 @@ import {
|
||||
import {
|
||||
CmsRewardsResponse,
|
||||
Reward,
|
||||
SurpriseReward,
|
||||
validateApiRewardSchema,
|
||||
validateApiTierRewardsSchema,
|
||||
validateCmsRewardsSchema,
|
||||
} from "./output"
|
||||
|
||||
import { Surprise } from "@/types/components/blocks/surprises"
|
||||
|
||||
const meter = metrics.getMeter("trpc.reward")
|
||||
// OpenTelemetry metrics: Reward
|
||||
|
||||
@@ -259,36 +262,21 @@ export const rewardQueryRouter = router({
|
||||
const nextCursor =
|
||||
limit + cursor < rewardIds.length ? limit + cursor : undefined
|
||||
|
||||
const surprisesIds = validatedApiRewards.data
|
||||
.filter(
|
||||
({ type, rewardType }) =>
|
||||
type === "coupon" && rewardType === "Surprise"
|
||||
)
|
||||
.map(({ rewardId }) => rewardId)
|
||||
|
||||
const rewards = cmsRewards.filter(
|
||||
(reward) => !surprisesIds.includes(reward.reward_id)
|
||||
)
|
||||
|
||||
getCurrentRewardSuccessCounter.add(1)
|
||||
|
||||
return {
|
||||
rewards: cmsRewards,
|
||||
apiRewards: validatedApiRewards.data
|
||||
// FIXME: Remove these mocks before merging
|
||||
.concat([
|
||||
{
|
||||
autoApplyReward: false,
|
||||
title: "Free kids drink when staying",
|
||||
id: "fake-id",
|
||||
type: "surprise",
|
||||
status: "active",
|
||||
rewardId: "tier_free_kids_drink",
|
||||
redeemLocation: "On-site",
|
||||
rewardType: "Tier",
|
||||
rewardTierLevel: "L1",
|
||||
},
|
||||
{
|
||||
autoApplyReward: false,
|
||||
title: "Free kanelbulle",
|
||||
id: "fake-id-2",
|
||||
type: "surprise",
|
||||
status: "active",
|
||||
rewardId: "tier_free_kanelbulle",
|
||||
redeemLocation: "On-site",
|
||||
rewardType: "Tier",
|
||||
rewardTierLevel: "L1",
|
||||
},
|
||||
]),
|
||||
rewards,
|
||||
nextCursor,
|
||||
}
|
||||
}),
|
||||
@@ -402,6 +390,99 @@ export const rewardQueryRouter = router({
|
||||
getAllRewardSuccessCounter.add(1)
|
||||
return levelsWithRewards
|
||||
}),
|
||||
surprises: contentStackBaseWithProtectedProcedure.query(async ({ ctx }) => {
|
||||
getCurrentRewardCounter.add(1)
|
||||
|
||||
const apiResponse = await api.get(api.endpoints.v1.rewards, {
|
||||
cache: undefined, // override defaultOptions
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
next: { revalidate: 60 * 60 },
|
||||
})
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.reward error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const data = await apiResponse.json()
|
||||
|
||||
const validatedApiRewards = validateApiRewardSchema.safeParse(data)
|
||||
|
||||
if (!validatedApiRewards.success) {
|
||||
getCurrentRewardFailCounter.add(1, {
|
||||
locale: ctx.lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedApiRewards.error),
|
||||
})
|
||||
console.error(validatedApiRewards.error)
|
||||
console.error(
|
||||
"contentstack.surprises validation error",
|
||||
JSON.stringify({
|
||||
query: { locale: ctx.lang },
|
||||
error: validatedApiRewards.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const rewardIds = validatedApiRewards.data
|
||||
.map((reward) => reward?.rewardId)
|
||||
.filter((rewardId): rewardId is string => !!rewardId)
|
||||
.sort()
|
||||
|
||||
const cmsRewards = await getCmsRewards(ctx.lang, rewardIds)
|
||||
|
||||
if (!cmsRewards) {
|
||||
return null
|
||||
}
|
||||
|
||||
getCurrentRewardSuccessCounter.add(1)
|
||||
|
||||
const surprises =
|
||||
validatedApiRewards.data
|
||||
.filter(
|
||||
(reward): reward is SurpriseReward =>
|
||||
reward?.type === "coupon" && reward?.rewardType === "Surprise"
|
||||
)
|
||||
.map((surprise) => {
|
||||
const reward = cmsRewards.find(
|
||||
({ reward_id }) => surprise.rewardId === reward_id
|
||||
)
|
||||
|
||||
if (!reward) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...reward,
|
||||
id: surprise.id,
|
||||
endsAt: surprise.endsAt,
|
||||
}
|
||||
})
|
||||
.filter((surprise): surprise is Surprise => !!surprise) ?? []
|
||||
|
||||
return surprises
|
||||
}),
|
||||
update: contentStackBaseWithProtectedProcedure
|
||||
.input(rewardsUpdateInput)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
Reference in New Issue
Block a user