Files
Linus Flood 5fc93472f4 Merged in feat/rework-contentstack (pull request #3493)
Feat(SW-3708): refactor contentstack fetching (removing all refs) and cache invalidation

* Remove all REFS

* Revalidate correct language

* PR fixes

* PR fixes

* Throw when errors from contentstack api


Approved-by: Joakim Jäderberg
2026-01-27 12:38:36 +00:00

145 lines
3.8 KiB
TypeScript

import { z } from "zod"
import { MembershipLevelEnum } from "@scandic-hotels/common/constants/membershipLevels"
import { linkUnionSchema, transformPageLink } from "../schemas/pageLinks"
export {
BenefitReward,
CouponData,
CouponReward,
REDEEM_LOCATIONS,
REWARD_TYPES,
validateApiAllTiersSchema,
validateCategorizedRewardsSchema,
validateCmsRewardsSchema,
}
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(),
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
}),
})
),
}),
})
// This is primarily added in order to handle a transition
// switching from string to RTE
.nullable(),
grouped_description: z.string().optional(),
value: z.string().optional(),
})
),
}),
}),
})
.transform((data) => data.data.all_reward.items)
const REDEEM_LOCATIONS = ["Non-redeemable", "On-site", "Online"] as const
const REWARD_CATEGORIES = [
"Restaurants",
"Bar",
"Voucher",
"Services and rooms",
"Spa and gym",
] as const
const BaseReward = z.object({
title: z.string().optional(),
id: z.string(),
categories: z
.array(z.enum(REWARD_CATEGORIES).or(z.literal("")))
.optional()
// we sometimes receive empty categories, this filters them out
.transform((categories = []) =>
categories.filter(
(c): c is (typeof REWARD_CATEGORIES)[number] => c !== ""
)
),
rewardId: z.string(),
redeemLocation: z.enum(REDEEM_LOCATIONS),
status: z.enum(["active", "expired"]),
})
const REWARD_TYPES = {
Surprise: "Surprise",
Campaign: "Campaign",
MemberVoucher: "Member-voucher",
Tier: "Tier",
} as const
const BenefitReward = BaseReward.merge(
z.object({
rewardType: z.enum([REWARD_TYPES.Tier]),
rewardTierLevel: z.string().optional(),
})
)
const CouponData = z.object({
couponCode: z.string(),
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([
REWARD_TYPES.Surprise,
REWARD_TYPES.Campaign,
REWARD_TYPES.MemberVoucher,
]),
operaRewardId: z.string().default(""),
coupon: z
.array(CouponData)
.optional()
.transform((val) => val || []),
})
)
const validateCategorizedRewardsSchema = z.object({
benefits: z.array(BenefitReward),
coupons: z.array(CouponReward),
})
const TierKeyMapping = {
tier1: MembershipLevelEnum.L1,
tier2: MembershipLevelEnum.L2,
tier3: MembershipLevelEnum.L3,
tier4: MembershipLevelEnum.L4,
tier5: MembershipLevelEnum.L5,
tier6: MembershipLevelEnum.L6,
tier7: MembershipLevelEnum.L7,
} as const
const TierKeys = Object.keys(TierKeyMapping) as [keyof typeof TierKeyMapping]
const validateApiAllTiersSchema = z.record(
z.enum(TierKeys).transform((data) => TierKeyMapping[data]),
z.array(BenefitReward)
)