299 lines
8.2 KiB
TypeScript
299 lines
8.2 KiB
TypeScript
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(),
|
|
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.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(),
|
|
})
|
|
),
|
|
}),
|
|
}),
|
|
})
|
|
.transform((data) => data.data.all_reward.items)
|
|
|
|
export type ApiReward = z.output<typeof validateApiRewardSchema>[number]
|
|
|
|
export type SurpriseReward = z.output<typeof SurpriseReward>
|
|
|
|
export type CmsRewardsResponse = z.input<typeof validateCmsRewardsSchema>
|
|
|
|
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<
|
|
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
|
|
}
|
|
|
|
export interface Surprise extends Omit<Reward, "operaRewardId" | "couponCode"> {
|
|
coupons: { couponCode?: string | undefined; expiresAt?: string }[]
|
|
}
|
|
|
|
// 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(),
|
|
})
|
|
|
|
const BenefitReward = BaseReward.merge(
|
|
z.object({
|
|
rewardType: z.string().optional(), // TODO: Should be "Tier" but can't because of backwards compatibility
|
|
rewardTierLevel: 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 = BaseReward.merge(
|
|
z.object({
|
|
rewardType: z.enum(["Surprise", "Campaign", "Member-voucher"]),
|
|
operaRewardId: z.string().default(""),
|
|
coupon: z
|
|
.array(CouponData)
|
|
.optional()
|
|
.transform((val) => val || []),
|
|
})
|
|
)
|
|
|
|
/**
|
|
* 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 type CategorizedApiReward = z.output<
|
|
typeof validateCategorizedRewardsSchema
|
|
>[number]
|
|
|
|
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"
|