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

@@ -2,27 +2,52 @@
import { useRef, useState } from "react" import { useRef, useState } from "react"
import { trpc } from "@/lib/trpc/client"
import { RewardIcon } from "@/components/Blocks/DynamicContent/Rewards/RewardIcon" import { RewardIcon } from "@/components/Blocks/DynamicContent/Rewards/RewardIcon"
import ScriptedRewardText from "@/components/Blocks/DynamicContent/Rewards/ScriptedRewardText" import ScriptedRewardText from "@/components/Blocks/DynamicContent/Rewards/ScriptedRewardText"
import Pagination from "@/components/MyPages/Pagination" import Pagination from "@/components/MyPages/Pagination"
import Grids from "@/components/TempDesignSystem/Grids" import Grids from "@/components/TempDesignSystem/Grids"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import useLang from "@/hooks/useLang"
import Redeem from "../Redeem" import Redeem from "../Redeem"
import styles from "./current.module.css" import styles from "./current.module.css"
import type { CurrentRewardsClientProps } from "@/types/components/myPages/myPage/accountPage" import type { CurrentRewardsClientProps } from "@/types/components/myPages/myPage/accountPage"
import type {
Reward,
RewardWithRedeem,
} from "@/server/routers/contentstack/reward/output"
export default function ClientCurrentRewards({ export default function ClientCurrentRewards({
rewards, rewards: initialData,
pageSize, pageSize,
showRedeem, showRedeem,
membershipNumber, membershipNumber,
}: CurrentRewardsClientProps) { }: CurrentRewardsClientProps) {
const lang = useLang()
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
const [currentPage, setCurrentPage] = useState(1) const [currentPage, setCurrentPage] = useState(1)
const { data } = trpc.contentstack.rewards.current.useQuery<{
rewards: (Reward | RewardWithRedeem)[]
}>(
{
lang,
},
{
initialData: { rewards: initialData },
}
)
if (!data) {
return null
}
const rewards = data.rewards
const totalPages = Math.ceil(rewards.length / pageSize) const totalPages = Math.ceil(rewards.length / pageSize)
const startIndex = (currentPage - 1) * pageSize const startIndex = (currentPage - 1) * pageSize
const endIndex = startIndex + pageSize const endIndex = startIndex + pageSize

View File

@@ -1,9 +1,11 @@
"use client" "use client"
import { createContext, useCallback, useContext, useState } from "react" import { createContext, useCallback, useContext } from "react"
import { trpc } from "@/lib/trpc/client" import { trpc } from "@/lib/trpc/client"
import useLang from "@/hooks/useLang"
import type { RedeemFlowContext } from "@/types/components/myPages/myPage/accountPage" import type { RedeemFlowContext } from "@/types/components/myPages/myPage/accountPage"
import type { RewardWithRedeem } from "@/server/routers/contentstack/reward/output" import type { RewardWithRedeem } from "@/server/routers/contentstack/reward/output"
@@ -14,18 +16,22 @@ export const RedeemContext = createContext<RedeemFlowContext>({
export default function useRedeemFlow(reward: RewardWithRedeem) { export default function useRedeemFlow(reward: RewardWithRedeem) {
const { redeemStep, setRedeemStep } = useContext(RedeemContext) const { redeemStep, setRedeemStep } = useContext(RedeemContext)
const lang = useLang()
const update = trpc.contentstack.rewards.redeem.useMutation<{ const update = trpc.contentstack.rewards.redeem.useMutation<{
rewards: RewardWithRedeem[] rewards: RewardWithRedeem[]
}>() }>()
const utils = trpc.useUtils()
const onRedeem = useCallback(() => { const onRedeem = useCallback(() => {
if (reward?.id) { if (reward?.id) {
update.mutate( update.mutate(
{ rewardId: reward.id }, { rewardId: reward.id, couponCode: reward.couponCode },
{ {
onSuccess() { onSuccess() {
setRedeemStep("redeemed") setRedeemStep("redeemed")
utils.contentstack.rewards.current.invalidate({ lang })
}, },
onError(error) { onError(error) {
console.error("Failed to redeem", error) console.error("Failed to redeem", error)
@@ -33,7 +39,7 @@ export default function useRedeemFlow(reward: RewardWithRedeem) {
} }
) )
} }
}, [reward, update, setRedeemStep]) }, [reward, update, setRedeemStep, utils.contentstack.rewards, lang])
return { return {
onRedeem, onRedeem,

View File

@@ -36,8 +36,11 @@ export default function SurprisesNotification({
const [open, setOpen] = useState(true) const [open, setOpen] = useState(true)
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 unwrap = trpc.contentstack.rewards.unwrap.useMutation({ const unwrap = trpc.contentstack.rewards.unwrap.useMutation({
onSuccess: () => { onSuccess: () => {
utils.contentstack.rewards.current.invalidate({ lang })
if (pathname.indexOf(benefits[lang]) !== 0) { if (pathname.indexOf(benefits[lang]) !== 0) {
toast.success( toast.success(
<> <>
@@ -57,6 +60,11 @@ export default function SurprisesNotification({
}, },
onError: (error) => { onError: (error) => {
console.error("Failed to unwrap surprise", error) console.error("Failed to unwrap surprise", error)
toast.error(
<>
{intl.formatMessage({ id: "An error occurred. Please try again." })}
</>
)
}, },
}) })
@@ -77,7 +85,7 @@ export default function SurprisesNotification({
async function viewRewards() { async function viewRewards() {
const updates = surprises const updates = surprises
.map((surprise) => { .map((surprise) => {
const coupons = surprise.coupons const coupons = surprise.coupon
?.map((coupon) => { ?.map((coupon) => {
if (coupon?.couponCode) { if (coupon?.couponCode) {
return { return {

View File

@@ -16,7 +16,7 @@ export default function Slide({ surprise, membershipNumber }: SlideProps) {
const lang = useLang() const lang = useLang()
const intl = useIntl() const intl = useIntl()
const earliestExpirationDate = surprise.coupons?.reduce( const earliestExpirationDate = surprise.coupon?.reduce(
(earliestDate, coupon) => { (earliestDate, coupon) => {
const expiresAt = dt(coupon.expiresAt) const expiresAt = dt(coupon.expiresAt)
return earliestDate.isBefore(expiresAt) ? earliestDate : expiresAt return earliestDate.isBefore(expiresAt) ? earliestDate : expiresAt
@@ -30,7 +30,7 @@ export default function Slide({ surprise, membershipNumber }: SlideProps) {
<div className={styles.badge}> <div className={styles.badge}>
<Caption> <Caption>
{intl.formatMessage( {intl.formatMessage(
{ id: "Expires at the earliest {expirationDate}" }, { id: "Valid through {expirationDate}" },
{ {
expirationDate: dt(earliestExpirationDate) expirationDate: dt(earliestExpirationDate)
.locale(lang) .locale(lang)

View File

@@ -150,7 +150,6 @@
"Enter your details": "Indtast dine oplysninger", "Enter your details": "Indtast dine oplysninger",
"Events that make an impression": "Events that make an impression", "Events that make an impression": "Events that make an impression",
"Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}", "Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}",
"Expires at the earliest {expirationDate}": "Udløber tidligst {expirationDate}",
"Explore all levels and benefits": "Udforsk alle niveauer og fordele", "Explore all levels and benefits": "Udforsk alle niveauer og fordele",
"Explore nearby": "Udforsk i nærheden", "Explore nearby": "Udforsk i nærheden",
"Extra bed (child) × {count}": "Ekstra seng (barn) × {count}", "Extra bed (child) × {count}": "Ekstra seng (barn) × {count}",
@@ -467,6 +466,7 @@
"Use code/voucher": "Brug kode/voucher", "Use code/voucher": "Brug kode/voucher",
"User information": "Brugeroplysninger", "User information": "Brugeroplysninger",
"VAT {vat}%": "Moms {vat}%", "VAT {vat}%": "Moms {vat}%",
"Valid through {expirationDate}": "Gyldig til og med {expirationDate}",
"View as list": "Vis som liste", "View as list": "Vis som liste",
"View as map": "Vis som kort", "View as map": "Vis som kort",
"View your booking": "Se din booking", "View your booking": "Se din booking",

View File

@@ -149,7 +149,6 @@
"Enter your details": "Geben Sie Ihre Daten ein", "Enter your details": "Geben Sie Ihre Daten ein",
"Events that make an impression": "Events that make an impression", "Events that make an impression": "Events that make an impression",
"Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}", "Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}",
"Expires at the earliest {expirationDate}": "Läuft frühestens am {expirationDate} ab",
"Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile", "Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile",
"Explore nearby": "Erkunden Sie die Umgebung", "Explore nearby": "Erkunden Sie die Umgebung",
"Extra bed (child) × {count}": "Ekstra seng (Kind) × {count}", "Extra bed (child) × {count}": "Ekstra seng (Kind) × {count}",
@@ -465,6 +464,7 @@
"Use code/voucher": "Code/Gutschein nutzen", "Use code/voucher": "Code/Gutschein nutzen",
"User information": "Nutzerinformation", "User information": "Nutzerinformation",
"VAT {vat}%": "MwSt. {vat}%", "VAT {vat}%": "MwSt. {vat}%",
"Valid through {expirationDate}": "Gültig bis {expirationDate}",
"View as list": "Als Liste anzeigen", "View as list": "Als Liste anzeigen",
"View as map": "Als Karte anzeigen", "View as map": "Als Karte anzeigen",
"View your booking": "Ihre Buchung ansehen", "View your booking": "Ihre Buchung ansehen",

View File

@@ -161,7 +161,6 @@
"Enter your details": "Enter your details", "Enter your details": "Enter your details",
"Events that make an impression": "Events that make an impression", "Events that make an impression": "Events that make an impression",
"Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}", "Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}",
"Expires at the earliest {expirationDate}": "Expires at the earliest {expirationDate}",
"Explore all levels and benefits": "Explore all levels and benefits", "Explore all levels and benefits": "Explore all levels and benefits",
"Explore nearby": "Explore nearby", "Explore nearby": "Explore nearby",
"Extra bed (child) × {count}": "Extra bed (child) × {count}", "Extra bed (child) × {count}": "Extra bed (child) × {count}",
@@ -508,6 +507,7 @@
"VAT": "VAT", "VAT": "VAT",
"VAT amount": "VAT amount", "VAT amount": "VAT amount",
"VAT {vat}%": "VAT {vat}%", "VAT {vat}%": "VAT {vat}%",
"Valid through {expirationDate}": "Valid through {expirationDate}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View as list": "View as list", "View as list": "View as list",
"View as map": "View as map", "View as map": "View as map",

View File

@@ -150,7 +150,6 @@
"Enter your details": "Anna tietosi", "Enter your details": "Anna tietosi",
"Events that make an impression": "Events that make an impression", "Events that make an impression": "Events that make an impression",
"Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}", "Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}",
"Expires at the earliest {expirationDate}": "Päättyy aikaisintaan {expirationDate}",
"Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin", "Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin",
"Explore nearby": "Tutustu lähialueeseen", "Explore nearby": "Tutustu lähialueeseen",
"Extra bed (child) × {count}": "Lisävuode (lasta) × {count}", "Extra bed (child) × {count}": "Lisävuode (lasta) × {count}",
@@ -466,6 +465,7 @@
"Use code/voucher": "Käytä koodia/voucheria", "Use code/voucher": "Käytä koodia/voucheria",
"User information": "Käyttäjän tiedot", "User information": "Käyttäjän tiedot",
"VAT {vat}%": "ALV {vat}%", "VAT {vat}%": "ALV {vat}%",
"Valid through {expirationDate}": "Voimassa {expirationDate} asti",
"View as list": "Näytä listana", "View as list": "Näytä listana",
"View as map": "Näytä kartalla", "View as map": "Näytä kartalla",
"View your booking": "Näytä varauksesi", "View your booking": "Näytä varauksesi",

View File

@@ -149,7 +149,6 @@
"Enter your details": "Skriv inn detaljene dine", "Enter your details": "Skriv inn detaljene dine",
"Events that make an impression": "Events that make an impression", "Events that make an impression": "Events that make an impression",
"Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}", "Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}",
"Expires at the earliest {expirationDate}": "Utløper tidligst {expirationDate}",
"Explore all levels and benefits": "Utforsk alle nivåer og fordeler", "Explore all levels and benefits": "Utforsk alle nivåer og fordeler",
"Explore nearby": "Utforsk i nærheten", "Explore nearby": "Utforsk i nærheten",
"Extra bed (child) × {count}": "Ekstra seng (barn) × {count}", "Extra bed (child) × {count}": "Ekstra seng (barn) × {count}",
@@ -465,6 +464,7 @@
"Use code/voucher": "Bruk kode/voucher", "Use code/voucher": "Bruk kode/voucher",
"User information": "Brukerinformasjon", "User information": "Brukerinformasjon",
"VAT {vat}%": "mva {vat}%", "VAT {vat}%": "mva {vat}%",
"Valid through {expirationDate}": "Gyldig til og med {expirationDate}",
"View as list": "Vis som liste", "View as list": "Vis som liste",
"View as map": "Vis som kart", "View as map": "Vis som kart",
"View your booking": "Se din bestilling", "View your booking": "Se din bestilling",

View File

@@ -149,7 +149,6 @@
"Enter your details": "Ange dina uppgifter", "Enter your details": "Ange dina uppgifter",
"Events that make an impression": "Events that make an impression", "Events that make an impression": "Events that make an impression",
"Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}", "Expiration Date: {expirationDate}": "Expiration Date: {expirationDate}",
"Expires at the earliest {expirationDate}": "Löper ut tidigast {expirationDate}",
"Explore all levels and benefits": "Utforska alla nivåer och fördelar", "Explore all levels and benefits": "Utforska alla nivåer och fördelar",
"Explore nearby": "Utforska i närheten", "Explore nearby": "Utforska i närheten",
"Extra bed (child) × {count}": "Extra säng (barn) × {count}", "Extra bed (child) × {count}": "Extra säng (barn) × {count}",
@@ -465,6 +464,7 @@
"Use code/voucher": "Använd kod/voucher", "Use code/voucher": "Använd kod/voucher",
"User information": "Användarinformation", "User information": "Användarinformation",
"VAT {vat}%": "Moms {vat}%", "VAT {vat}%": "Moms {vat}%",
"Valid through {expirationDate}": "Gäller till och med {expirationDate}",
"View as list": "Visa som lista", "View as list": "Visa som lista",
"View as map": "Visa som karta", "View as map": "Visa som karta",
"View your booking": "Visa din bokning", "View your booking": "Visa din bokning",

View File

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

View File

@@ -1,5 +1,6 @@
import { env } from "@/env/server" import { env } from "@/env/server"
import * as api from "@/lib/api" import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { notFound } from "@/server/errors/trpc" import { notFound } from "@/server/errors/trpc"
import { import {
contentStackBaseWithProtectedProcedure, contentStackBaseWithProtectedProcedure,
@@ -8,6 +9,7 @@ import {
router, router,
} from "@/server/trpc" } from "@/server/trpc"
import { langInput } from "../base/input"
import { getAllLoyaltyLevels, getLoyaltyLevel } from "../loyaltyLevel/query" import { getAllLoyaltyLevels, getLoyaltyLevel } from "../loyaltyLevel/query"
import { import {
rewardsAllInput, rewardsAllInput,
@@ -160,118 +162,139 @@ export const rewardQueryRouter = router({
getByLevelRewardSuccessCounter.add(1) getByLevelRewardSuccessCounter.add(1)
return { level: loyaltyLevelsConfig, rewards: levelsWithRewards } return { level: loyaltyLevelsConfig, rewards: levelsWithRewards }
}), }),
current: contentStackBaseWithProtectedProcedure.query(async function ({ current: contentStackBaseWithProtectedProcedure
ctx, .input(langInput.optional()) // lang is required for client, but not for server
}) { .query(async function ({ ctx }) {
getCurrentRewardCounter.add(1) 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, // override defaultOptions cache: undefined, // override defaultOptions
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,
statusText: apiResponse.statusText,
const validatedApiRewards = isNewEndpoint text,
? 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
) )
return null
}
return { const data = await apiResponse.json()
...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
: "",
}
})
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 }) => { surprises: contentStackBaseWithProtectedProcedure.query(async ({ ctx }) => {
getCurrentRewardCounter.add(1) getCurrentRewardCounter.add(1)
@@ -380,7 +403,7 @@ export const rewardQueryRouter = router({
rewardType: surprise.rewardType, rewardType: surprise.rewardType,
rewardTierLevel: undefined, rewardTierLevel: undefined,
redeemLocation: surprise.redeemLocation, redeemLocation: surprise.redeemLocation,
coupons: "coupon" in surprise ? surprise.coupon || [] : [], coupon: "coupon" in surprise ? surprise.coupon || [] : [],
} }
}) })
.flatMap((surprises) => (surprises ? [surprises] : [])) .flatMap((surprises) => (surprises ? [surprises] : []))

View File

@@ -1,7 +1,7 @@
import type { Reward } from "@/server/routers/contentstack/reward/output" import type { Reward } from "@/server/routers/contentstack/reward/output"
export interface Surprise extends Omit<Reward, "operaRewardId"> { export interface Surprise extends Omit<Reward, "operaRewardId" | "couponCode"> {
coupons: { couponCode?: string; expiresAt?: string }[] coupon: { couponCode?: string | undefined; expiresAt?: string }[]
} }
export interface SurprisesProps { export interface SurprisesProps {