fix(LOY-39): refetch rewards when redeemed

update expiration date text

possible to redeem rewards with coupon code
This commit is contained in:
Christian Andolf
2024-12-18 17:43:19 +01:00
parent c701c067b4
commit 633d259ce0
13 changed files with 181 additions and 117 deletions

View File

@@ -168,6 +168,7 @@ export type Reward = CMSReward & {
redeemLocation: string | undefined
rewardTierLevel: string | undefined
operaRewardId: string
couponCode: string | undefined
}
export type RewardWithRedeem = CMSRewardWithRedeem & {
@@ -176,6 +177,7 @@ export type RewardWithRedeem = CMSRewardWithRedeem & {
redeemLocation: string | undefined
rewardTierLevel: string | undefined
operaRewardId: string
couponCode: string | undefined
}
// New endpoint related types and schemas.

View File

@@ -1,5 +1,6 @@
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { notFound } from "@/server/errors/trpc"
import {
contentStackBaseWithProtectedProcedure,
@@ -8,6 +9,7 @@ import {
router,
} from "@/server/trpc"
import { langInput } from "../base/input"
import { getAllLoyaltyLevels, getLoyaltyLevel } from "../loyaltyLevel/query"
import {
rewardsAllInput,
@@ -160,118 +162,139 @@ export const rewardQueryRouter = router({
getByLevelRewardSuccessCounter.add(1)
return { level: loyaltyLevelsConfig, rewards: levelsWithRewards }
}),
current: contentStackBaseWithProtectedProcedure.query(async function ({
ctx,
}) {
getCurrentRewardCounter.add(1)
current: contentStackBaseWithProtectedProcedure
.input(langInput.optional()) // lang is required for client, but not for server
.query(async function ({ ctx }) {
getCurrentRewardCounter.add(1)
const isNewEndpoint = env.USE_NEW_REWARDS_ENDPOINT
const endpoint = isNewEndpoint
? api.endpoints.v1.Profile.Reward.reward
: api.endpoints.v1.Profile.reward
const isNewEndpoint = env.USE_NEW_REWARDS_ENDPOINT
const endpoint = isNewEndpoint
? api.endpoints.v1.Profile.Reward.reward
: api.endpoints.v1.Profile.reward
const apiResponse = await api.get(endpoint, {
cache: undefined, // override defaultOptions
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
},
next: { revalidate: ONE_HOUR },
})
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,
}),
const apiResponse = await api.get(endpoint, {
cache: undefined, // override defaultOptions
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
},
next: { revalidate: ONE_HOUR },
})
console.error(
"api.reward error ",
JSON.stringify({
error: {
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,
},
}),
})
)
return null
}
const data = await apiResponse.json()
const validatedApiRewards = isNewEndpoint
? validateCategorizedRewardsSchema.safeParse(data)
: 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.rewards 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
}
const wrappedSurprisesIds = validatedApiRewards.data
.filter(
(reward) =>
reward.type === "coupon" &&
reward.rewardType === "Surprise" &&
"coupon" in reward &&
reward.coupon?.some(({ unwrapped }) => !unwrapped)
)
.map(({ rewardId }) => rewardId)
const rewards = cmsRewards
.filter((cmsReward) => !wrappedSurprisesIds.includes(cmsReward.reward_id))
.map((cmsReward) => {
const apiReward = validatedApiRewards.data.find(
({ rewardId }) => rewardId === cmsReward.reward_id
console.error(
"api.reward error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
return {
...cmsReward,
id: apiReward?.id,
rewardType: apiReward?.rewardType,
redeemLocation: apiReward?.redeemLocation,
rewardTierLevel:
apiReward && "rewardTierLevel" in apiReward
? apiReward.rewardTierLevel
: undefined,
operaRewardId:
apiReward && "operaRewardId" in apiReward
? apiReward.operaRewardId
: "",
}
})
const data = await apiResponse.json()
getCurrentRewardSuccessCounter.add(1)
const validatedApiRewards = isNewEndpoint
? validateCategorizedRewardsSchema.safeParse(data)
: validateApiRewardSchema.safeParse(data)
return { rewards }
}),
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.rewards 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
}
const wrappedSurprisesIds = validatedApiRewards.data
.filter(
(reward) =>
reward.type === "coupon" &&
reward.rewardType === "Surprise" &&
"coupon" in reward &&
reward.coupon?.some(({ unwrapped }) => !unwrapped)
)
.map(({ rewardId }) => rewardId)
const rewards = cmsRewards
.filter(
(cmsReward) => !wrappedSurprisesIds.includes(cmsReward.reward_id)
)
.map((cmsReward) => {
const apiReward = validatedApiRewards.data.find(
({ rewardId }) => rewardId === cmsReward.reward_id
)
const redeemableCoupons =
(apiReward &&
"coupon" in apiReward &&
apiReward.coupon?.filter(
(coupon) => coupon.state !== "redeemed" && coupon.unwrapped
)) ||
[]
const firstRedeemableCouponToExpire = redeemableCoupons.reduce(
(earliest, coupon) => {
if (dt(coupon.expiresAt).isBefore(dt(earliest.expiresAt))) {
return coupon
}
return earliest
},
redeemableCoupons[0]
)?.couponCode
return {
...cmsReward,
id: apiReward?.id,
rewardType: apiReward?.rewardType,
redeemLocation: apiReward?.redeemLocation,
rewardTierLevel:
apiReward && "rewardTierLevel" in apiReward
? apiReward.rewardTierLevel
: undefined,
operaRewardId:
apiReward && "operaRewardId" in apiReward
? apiReward.operaRewardId
: "",
couponCode: firstRedeemableCouponToExpire,
}
})
getCurrentRewardSuccessCounter.add(1)
return { rewards }
}),
surprises: contentStackBaseWithProtectedProcedure.query(async ({ ctx }) => {
getCurrentRewardCounter.add(1)
@@ -380,7 +403,7 @@ export const rewardQueryRouter = router({
rewardType: surprise.rewardType,
rewardTierLevel: undefined,
redeemLocation: surprise.redeemLocation,
coupons: "coupon" in surprise ? surprise.coupon || [] : [],
coupon: "coupon" in surprise ? surprise.coupon || [] : [],
}
})
.flatMap((surprises) => (surprises ? [surprises] : []))