refactor(LOY-175): rewrite reward types according to new api endpoints

This commit is contained in:
Christian Andolf
2025-03-11 16:09:15 +01:00
parent 0ae4c5db17
commit b86347b4f4
15 changed files with 196 additions and 246 deletions

View File

@@ -10,7 +10,24 @@ import {
} from "../schemas/pageLinks"
import { systemSchema } from "../schemas/system"
import type { RewardCategory } from "@/types/components/myPages/rewards"
export {
type ApiReward,
type CMSReward,
type CMSRewardsResponse,
type CMSRewardsWithRedeemResponse,
type CMSRewardWithRedeem,
type Coupon,
type GetRewardWithRedeemRefsSchema,
type RedeemableCoupon,
type RedeemLocation,
rewardWithRedeemRefsSchema,
type Surprise,
type SurpriseReward,
validateApiAllTiersSchema,
validateCategorizedRewardsSchema,
validateCmsRewardsSchema,
validateCmsRewardsWithRedeemSchema,
}
enum TierKey {
tier1 = MembershipLevelEnum.L1,
@@ -24,7 +41,10 @@ enum TierKey {
type Key = keyof typeof TierKey
export const validateCmsRewardsSchema = z
/*
* TODO: Remove this once we start using the new CMS model with redeem entirely
*/
const validateCmsRewardsSchema = z
.object({
data: z.object({
all_reward: z.object({
@@ -48,7 +68,10 @@ export const validateCmsRewardsSchema = z
})
.transform((data) => data.data.all_reward.items)
export const validateCmsRewardsWithRedeemSchema = z
type CMSRewardsResponse = z.input<typeof validateCmsRewardsSchema>
type CMSReward = z.output<typeof validateCmsRewardsSchema>[number]
const validateCmsRewardsWithRedeemSchema = z
.object({
data: z.object({
all_reward: z.object({
@@ -88,13 +111,14 @@ export const validateCmsRewardsWithRedeemSchema = z
})
.transform((data) => data.data.all_reward.items)
export type CmsRewardsResponse = z.input<typeof validateCmsRewardsSchema>
export type CmsRewardsWithRedeemResponse = z.input<
type CMSRewardsWithRedeemResponse = z.input<
typeof validateCmsRewardsWithRedeemSchema
>
type CMSRewardWithRedeem = z.output<
typeof validateCmsRewardsWithRedeemSchema
>[number]
export const rewardWithRedeemRefsSchema = z.object({
const rewardWithRedeemRefsSchema = z.object({
data: z.object({
all_reward: z.object({
items: z.array(
@@ -115,70 +139,36 @@ export const rewardWithRedeemRefsSchema = z.object({
}),
})
export interface GetRewardWithRedeemRefsSchema
extends z.input<typeof rewardWithRedeemRefsSchema> {}
type GetRewardWithRedeemRefsSchema = z.input<typeof rewardWithRedeemRefsSchema>
export type CMSReward = z.output<typeof validateCmsRewardsSchema>[0]
const REDEEM_LOCATIONS = ["Non-redeemable", "On-site", "Online"] as const
type RedeemLocation = (typeof REDEEM_LOCATIONS)[number]
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
categories: RewardCategory[]
couponCode: string | undefined
coupons: Coupon[]
}
export type RewardWithRedeem = CMSRewardWithRedeem & {
id: string | undefined
rewardType: string | undefined
redeemLocation: string | undefined
rewardTierLevel: string | undefined
operaRewardId: string
categories: RewardCategory[]
couponCode: string | undefined
coupons: Coupon[]
}
export interface Coupon {
couponCode?: string
expiresAt?: string
unwrapped: boolean
state: "claimed" | "redeemed" | "viewed"
}
export interface Surprise extends Omit<Reward, "operaRewardId" | "couponCode"> {
coupons: Coupon[]
}
// New endpoint related types and schemas.
const BaseReward = z.object({
title: z.string().optional(),
id: z.string().optional(),
rewardId: z.string().optional(),
redeemLocation: z.string().optional(),
status: z.string().optional(),
id: z.string(),
rewardId: z.string(),
redeemLocation: z.enum(REDEEM_LOCATIONS),
status: z.enum(["active", "expired"]),
})
const BenefitReward = BaseReward.merge(
z.object({
rewardType: z.string().optional(), // TODO: Should be "Tier" but can't because of backwards compatibility
rewardType: z.enum(["Tier"]),
rewardTierLevel: z.string().optional(),
})
)
const CouponData = z.object({
couponCode: z.string().optional(),
couponCode: z.string(),
unwrapped: z.boolean().default(false),
state: z.enum(["claimed", "redeemed", "viewed"]),
expiresAt: z.string().datetime({ offset: true }).optional(),
})
type Coupon = z.output<typeof CouponData>
type RedeemableCoupon = Coupon & {
state: Exclude<Coupon["state"], "redeemed">
}
const CouponReward = BaseReward.merge(
z.object({
@@ -192,38 +182,26 @@ const CouponReward = BaseReward.merge(
})
)
/**
* 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
type SurpriseReward = z.output<typeof CouponReward> & {
rewardType: "Surprise"
}
interface Surprise extends CMSReward {
data: SurpriseReward
}
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.
})),
])
.transform((data) => [...data.benefits, ...data.coupons])
export type CategorizedApiReward = z.output<
typeof validateCategorizedRewardsSchema
>[number]
type ApiReward = z.output<typeof validateCategorizedRewardsSchema>[number]
export const validateApiAllTiersSchema = z.record(
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"