Merged in fix/LOY-137-fetch-surprises-client (pull request #1363)
fix(LOY-137): now fetches surprises client side in intervals Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
@@ -24,10 +24,13 @@ import Slide from "./Slide"
|
|||||||
import styles from "./surprises.module.css"
|
import styles from "./surprises.module.css"
|
||||||
|
|
||||||
import type { SurprisesProps } from "@/types/components/blocks/surprises"
|
import type { SurprisesProps } from "@/types/components/blocks/surprises"
|
||||||
|
import type { Surprise } from "@/server/routers/contentstack/reward/output"
|
||||||
|
|
||||||
const MotionModal = motion(Modal)
|
const MotionModal = motion(Modal)
|
||||||
|
|
||||||
export default function SurprisesNotification({ surprises }: SurprisesProps) {
|
export default function SurprisesNotification({
|
||||||
|
surprises: initialData,
|
||||||
|
}: SurprisesProps) {
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
@@ -35,6 +38,20 @@ export default function SurprisesNotification({ surprises }: SurprisesProps) {
|
|||||||
const [[selectedSurprise, direction], setSelectedSurprise] = useState([0, 0])
|
const [[selectedSurprise, direction], setSelectedSurprise] = useState([0, 0])
|
||||||
const [showSurprises, setShowSurprises] = useState(false)
|
const [showSurprises, setShowSurprises] = useState(false)
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
|
|
||||||
|
const { data: surprises } = trpc.contentstack.rewards.surprises.useQuery<
|
||||||
|
Surprise[]
|
||||||
|
>(
|
||||||
|
{
|
||||||
|
lang,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initialData,
|
||||||
|
refetchInterval: 1000 * 60 * 5, // every 5 minutes
|
||||||
|
refetchIntervalInBackground: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const unwrap = trpc.contentstack.rewards.unwrap.useMutation({
|
const unwrap = trpc.contentstack.rewards.unwrap.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.contentstack.rewards.current.invalidate({ lang })
|
utils.contentstack.rewards.current.invalidate({ lang })
|
||||||
|
|||||||
@@ -223,6 +223,10 @@ export type RewardWithRedeem = CMSRewardWithRedeem & {
|
|||||||
couponCode: string | undefined
|
couponCode: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Surprise extends Omit<Reward, "operaRewardId" | "couponCode"> {
|
||||||
|
coupons: { couponCode?: string | undefined; expiresAt?: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
// New endpoint related types and schemas.
|
// New endpoint related types and schemas.
|
||||||
const BaseReward = z.object({
|
const BaseReward = z.object({
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
} from "./input"
|
} from "./input"
|
||||||
import {
|
import {
|
||||||
type Reward,
|
type Reward,
|
||||||
|
type Surprise,
|
||||||
validateApiRewardSchema,
|
validateApiRewardSchema,
|
||||||
validateCategorizedRewardsSchema,
|
validateCategorizedRewardsSchema,
|
||||||
} from "./output"
|
} from "./output"
|
||||||
@@ -293,121 +294,123 @@ export const rewardQueryRouter = router({
|
|||||||
|
|
||||||
return { rewards }
|
return { rewards }
|
||||||
}),
|
}),
|
||||||
surprises: contentStackBaseWithProtectedProcedure.query(async ({ ctx }) => {
|
surprises: contentStackBaseWithProtectedProcedure
|
||||||
getCurrentRewardCounter.add(1)
|
.input(langInput.optional()) // lang is required for client, but not for server
|
||||||
|
.query(async ({ ctx }) => {
|
||||||
|
getCurrentRewardCounter.add(1)
|
||||||
|
|
||||||
const isNewEndpoint = env.USE_NEW_REWARDS_ENDPOINT
|
const isNewEndpoint = env.USE_NEW_REWARDS_ENDPOINT
|
||||||
const endpoint = isNewEndpoint
|
const endpoint = isNewEndpoint
|
||||||
? api.endpoints.v1.Profile.Reward.reward
|
? api.endpoints.v1.Profile.Reward.reward
|
||||||
: api.endpoints.v1.Profile.reward
|
: api.endpoints.v1.Profile.reward
|
||||||
|
|
||||||
const apiResponse = await api.get(endpoint, {
|
const apiResponse = await api.get(endpoint, {
|
||||||
cache: undefined,
|
cache: undefined,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||||
},
|
},
|
||||||
next: { revalidate: ONE_HOUR },
|
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,
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
console.error(
|
|
||||||
"api.reward error ",
|
if (!apiResponse.ok) {
|
||||||
JSON.stringify({
|
const text = await apiResponse.text()
|
||||||
error: {
|
getCurrentRewardFailCounter.add(1, {
|
||||||
|
error_type: "http_error",
|
||||||
|
error: JSON.stringify({
|
||||||
status: apiResponse.status,
|
status: apiResponse.status,
|
||||||
statusText: apiResponse.statusText,
|
statusText: apiResponse.statusText,
|
||||||
text,
|
text,
|
||||||
},
|
}),
|
||||||
})
|
})
|
||||||
)
|
console.error(
|
||||||
return null
|
"api.reward error ",
|
||||||
}
|
JSON.stringify({
|
||||||
|
error: {
|
||||||
const data = await apiResponse.json()
|
status: apiResponse.status,
|
||||||
const validatedApiRewards = isNewEndpoint
|
statusText: apiResponse.statusText,
|
||||||
? validateCategorizedRewardsSchema.safeParse(data)
|
text,
|
||||||
: 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.surprises 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
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentRewardSuccessCounter.add(1)
|
|
||||||
|
|
||||||
const surprises = validatedApiRewards.data
|
|
||||||
// TODO: Add predicates once legacy endpoints are removed
|
|
||||||
.filter((reward) => {
|
|
||||||
if (reward?.rewardType !== "Surprise") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!("coupon" in reward)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const unwrappedCoupons =
|
|
||||||
reward.coupon.filter((coupon) => !coupon.unwrapped) || []
|
|
||||||
if (unwrappedCoupons.length === 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.map((surprise) => {
|
|
||||||
const reward = cmsRewards.find(
|
|
||||||
({ reward_id }) => surprise.rewardId === reward_id
|
|
||||||
)
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
if (!reward) {
|
const data = await apiResponse.json()
|
||||||
return null
|
const validatedApiRewards = isNewEndpoint
|
||||||
}
|
? validateCategorizedRewardsSchema.safeParse(data)
|
||||||
|
: validateApiRewardSchema.safeParse(data)
|
||||||
|
|
||||||
return {
|
if (!validatedApiRewards.success) {
|
||||||
...reward,
|
getCurrentRewardFailCounter.add(1, {
|
||||||
id: surprise.id,
|
locale: ctx.lang,
|
||||||
rewardType: surprise.rewardType,
|
error_type: "validation_error",
|
||||||
rewardTierLevel: undefined,
|
error: JSON.stringify(validatedApiRewards.error),
|
||||||
redeemLocation: surprise.redeemLocation,
|
})
|
||||||
coupons: "coupon" in surprise ? surprise.coupon || [] : [],
|
console.error(validatedApiRewards.error)
|
||||||
}
|
console.error(
|
||||||
})
|
"contentstack.surprises validation error",
|
||||||
.flatMap((surprises) => (surprises ? [surprises] : []))
|
JSON.stringify({
|
||||||
|
query: { locale: ctx.lang },
|
||||||
|
error: validatedApiRewards.error,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return surprises
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentRewardSuccessCounter.add(1)
|
||||||
|
|
||||||
|
const surprises: Surprise[] = validatedApiRewards.data
|
||||||
|
// TODO: Add predicates once legacy endpoints are removed
|
||||||
|
.filter((reward) => {
|
||||||
|
if (reward?.rewardType !== "Surprise") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!("coupon" in reward)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const unwrappedCoupons =
|
||||||
|
reward.coupon.filter((coupon) => !coupon.unwrapped) || []
|
||||||
|
if (unwrappedCoupons.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.map((surprise) => {
|
||||||
|
const reward = cmsRewards.find(
|
||||||
|
({ reward_id }) => surprise.rewardId === reward_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!reward) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...reward,
|
||||||
|
id: surprise.id,
|
||||||
|
rewardType: surprise.rewardType,
|
||||||
|
rewardTierLevel: undefined,
|
||||||
|
redeemLocation: surprise.redeemLocation,
|
||||||
|
coupons: "coupon" in surprise ? surprise.coupon || [] : [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatMap((surprises) => (surprises ? [surprises] : []))
|
||||||
|
|
||||||
|
return surprises
|
||||||
|
}),
|
||||||
unwrap: protectedProcedure
|
unwrap: protectedProcedure
|
||||||
.input(rewardsUpdateInput)
|
.input(rewardsUpdateInput)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import type { Reward } from "@/server/routers/contentstack/reward/output"
|
import type { Surprise } from "@/server/routers/contentstack/reward/output"
|
||||||
|
|
||||||
export interface Surprise extends Omit<Reward, "operaRewardId" | "couponCode"> {
|
|
||||||
coupons: { couponCode?: string | undefined; expiresAt?: string }[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SurprisesProps {
|
export interface SurprisesProps {
|
||||||
surprises: Surprise[]
|
surprises: Surprise[]
|
||||||
|
|||||||
Reference in New Issue
Block a user