Files
web/server/routers/contentstack/reward/output.ts
Christian Andolf 633d259ce0 fix(LOY-39): refetch rewards when redeemed
update expiration date text

possible to redeem rewards with coupon code
2025-01-20 09:53:17 +01:00

244 lines
6.9 KiB
TypeScript

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
.date({ coerce: true })
.optional()
.transform((value) => {
if (value?.getFullYear() === 1) {
return null
}
return value
}),
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(),
operaRewardId: z.string().default(""),
})
export const validateApiRewardSchema = z
.object({
data: z.array(
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(),
operaRewardId: z.string().default(""),
}),
SurpriseReward,
])
),
})
.transform((data) => data.data)
enum TierKey {
tier1 = MembershipLevelEnum.L1,
tier2 = MembershipLevelEnum.L2,
tier3 = MembershipLevelEnum.L3,
tier4 = MembershipLevelEnum.L4,
tier5 = MembershipLevelEnum.L5,
tier6 = MembershipLevelEnum.L6,
tier7 = MembershipLevelEnum.L7,
}
type Key = keyof typeof TierKey
export const validateApiTierRewardsSchema = z.record(
z.nativeEnum(TierKey).transform((data) => {
return TierKey[data as unknown as Key]
}),
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(),
operaRewardId: z.string().default(""),
})
)
)
export const validateCmsRewardsSchema = z
.object({
data: z.object({
all_reward: z.object({
items: z.array(
z.object({
taxonomies: z.array(
z.object({
term_uid: z.string().optional().default(""),
})
),
label: z.string().optional(),
reward_id: z.string(),
grouped_label: z.string().optional(),
description: z.string().optional(),
grouped_description: z.string().optional(),
value: z.string().optional(),
})
),
}),
}),
})
.transform((data) => data.data.all_reward.items)
export const validateCmsRewardsWithRedeemSchema = z
.object({
data: z.object({
all_reward: z.object({
items: z.array(
z.object({
taxonomies: z.array(
z.object({
term_uid: z.string().optional().default(""),
})
),
label: z.string().optional(),
reward_id: z.string(),
grouped_label: z.string().optional(),
description: z.string().optional(),
redeem_description: z
.string()
.nullable()
.transform((val) => val || ""),
grouped_description: z.string().optional(),
value: z.string().optional(),
})
),
}),
}),
})
.transform((data) => data.data.all_reward.items)
export type ApiReward = z.output<typeof validateApiRewardSchema>[0]
export type SurpriseReward = z.output<typeof SurpriseReward>
export type CmsRewardsResponse = z.input<typeof validateCmsRewardsSchema>
export type CmsRewardsWithRedeemResponse = z.input<
typeof validateCmsRewardsWithRedeemSchema
>
export type CMSReward = z.output<typeof validateCmsRewardsSchema>[0]
export type CMSRewardWithRedeem = z.output<
typeof validateCmsRewardsWithRedeemSchema
>[0]
export type Reward = CMSReward & {
id: string | undefined
rewardType: string | undefined
redeemLocation: string | undefined
rewardTierLevel: string | undefined
operaRewardId: string
couponCode: string | undefined
}
export type RewardWithRedeem = CMSRewardWithRedeem & {
id: string | undefined
rewardType: string | undefined
redeemLocation: string | undefined
rewardTierLevel: string | undefined
operaRewardId: string
couponCode: string | undefined
}
// New endpoint related types and schemas.
const BenefitReward = z.object({
title: z.string().optional(),
id: z.string().optional(),
redeemLocation: z.string().optional(),
rewardId: z.string().optional(),
rewardType: z.string().optional(), // TODO: Should be "Tier" but can't because of backwards compatibility
rewardTierLevel: z.string().optional(),
status: z.string().optional(),
})
const CouponData = z.object({
couponCode: z.string().optional(),
unwrapped: z.boolean().default(false),
state: z.enum(["claimed", "redeemed", "viewed"]),
expiresAt: z.string().datetime({ offset: true }).optional(),
})
const CouponReward = z.object({
title: z.string().optional(),
id: z.string().optional(),
rewardId: z.string().optional(),
rewardType: z.enum(["Surprise", "Campaign", "Member-voucher"]),
redeemLocation: z.string().optional(),
operaRewardId: z.string().default(""),
status: z.string().optional(),
coupon: z.array(CouponData).optional(),
})
/**
* Schema for the new /profile/v1/Reward endpoint.
*
* TODO: Once we fully migrate to the new endpoint:
* 1. Remove the data transform and use the categorized structure directly.
* 2. Simplify surprise filtering in the query.
*/
export const validateCategorizedRewardsSchema = z
.object({
benefits: z.array(BenefitReward),
coupons: z.array(CouponReward),
})
.transform((data) => [
...data.benefits.map((benefit) => ({
...benefit,
type: "custom" as const, // Added for legacy compatibility.
})),
...data.coupons.map((coupon) => ({
...coupon,
type: "coupon" as const, // Added for legacy compatibility.
})),
])
export const validateApiAllTiersSchema = z.record(
z.nativeEnum(TierKey).transform((data) => {
return TierKey[data as unknown as Key]
}),
z.array(BenefitReward)
)
export type RedeemLocation = "Non-redeemable" | "On-site" | "Online"