diff --git a/components/Blocks/DynamicContent/Rewards/CurrentLevel/Client.tsx b/components/Blocks/DynamicContent/Rewards/CurrentLevel/Client.tsx
index a57e5e160..bad1f6694 100644
--- a/components/Blocks/DynamicContent/Rewards/CurrentLevel/Client.tsx
+++ b/components/Blocks/DynamicContent/Rewards/CurrentLevel/Client.tsx
@@ -1,7 +1,7 @@
"use client"
import { trpc } from "@/lib/trpc/client"
-import { Reward } from "@/server/routers/contentstack/reward/output"
+import { ApiReward, Reward } from "@/server/routers/contentstack/reward/output"
import LoadingSpinner from "@/components/LoadingSpinner"
import Grids from "@/components/TempDesignSystem/Grids"
@@ -9,10 +9,16 @@ import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton"
import Title from "@/components/TempDesignSystem/Text/Title"
import useLang from "@/hooks/useLang"
+import Surprises from "../Surprises"
+
import styles from "./current.module.css"
type CurrentRewardsClientProps = {
- initialCurrentRewards: { rewards: Reward[]; nextCursor: number | undefined }
+ initialCurrentRewards: {
+ rewards: Reward[]
+ apiRewards: ApiReward[]
+ nextCursor: number | undefined
+ }
}
export default function ClientCurrentRewards({
initialCurrentRewards,
@@ -32,25 +38,34 @@ export default function ClientCurrentRewards({
},
}
)
- function loadMoreData() {
- if (hasNextPage) {
- fetchNextPage()
- }
- }
- const filteredRewards =
- data?.pages.filter((page) => page && page.rewards) ?? []
- const rewards = filteredRewards.flatMap((page) => page?.rewards) as Reward[]
if (isLoading) {
return
}
- if (!rewards.length) {
+ const rewards =
+ data?.pages
+ .flatMap((page) => page?.rewards)
+ .filter((reward): reward is Reward => !!reward) ?? []
+
+ const surprises =
+ data?.pages
+ .flatMap((page) => page?.apiRewards)
+ .filter((reward): reward is ApiReward => reward?.type === "surprise") ??
+ []
+
+ if (!rewards.length && !surprises.length) {
return null
}
+ function loadMoreData() {
+ if (hasNextPage) {
+ fetchNextPage()
+ }
+ }
+
return (
-
+ <>
{rewards.map((reward, idx) => (
@@ -71,6 +86,7 @@ export default function ClientCurrentRewards({
) : (
))}
-
+
+ >
)
}
diff --git a/components/Blocks/DynamicContent/Rewards/Surprises/index.tsx b/components/Blocks/DynamicContent/Rewards/Surprises/index.tsx
new file mode 100644
index 000000000..486b0423b
--- /dev/null
+++ b/components/Blocks/DynamicContent/Rewards/Surprises/index.tsx
@@ -0,0 +1,231 @@
+"use client"
+
+import React, { useState } from "react"
+import { Dialog, Modal, ModalOverlay } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import { benefits } from "@/constants/routes/myPages"
+import { trpc } from "@/lib/trpc/client"
+import { ApiReward } from "@/server/routers/contentstack/reward/output"
+
+import { ChevronRightSmallIcon, CloseLargeIcon } from "@/components/Icons"
+import Image from "@/components/Image"
+import Button from "@/components/TempDesignSystem/Button"
+import Body from "@/components/TempDesignSystem/Text/Body"
+import Caption from "@/components/TempDesignSystem/Text/Caption"
+import CaptionLabel from "@/components/TempDesignSystem/Text/CaptionLabel"
+import Title from "@/components/TempDesignSystem/Text/Title"
+import { toast } from "@/components/TempDesignSystem/Toasts"
+import useLang from "@/hooks/useLang"
+
+import styles from "./surprises.module.css"
+
+interface SurprisesProps {
+ surprises: ApiReward[]
+}
+
+export default function Surprises({ surprises }: SurprisesProps) {
+ const lang = useLang()
+ const [open, setOpen] = useState(true)
+ const [selectedSurprise, setSelectedSurprise] = useState(0)
+ const [showSurprises, setShowSurprises] = useState(false)
+ const update = trpc.contentstack.rewards.update.useMutation()
+ const intl = useIntl()
+
+ if (!surprises.length) return null
+
+ function showSurprise(n: number) {
+ setSelectedSurprise((surprise) => surprise + n)
+ }
+
+ function viewRewards(id?: string) {
+ if (!id) return
+ update.mutate({ id })
+ }
+
+ const surprise = surprises[selectedSurprise]
+
+ return (
+
+
+
+
+
+ )
+}
+
+function Surprise({
+ title,
+ children,
+}: {
+ title?: string
+ children?: React.ReactNode
+}) {
+ return (
+ <>
+
+
+ {title}
+
+
+ {children}
+ >
+ )
+}
diff --git a/components/Blocks/DynamicContent/Rewards/Surprises/surprises.module.css b/components/Blocks/DynamicContent/Rewards/Surprises/surprises.module.css
new file mode 100644
index 000000000..5932145c8
--- /dev/null
+++ b/components/Blocks/DynamicContent/Rewards/Surprises/surprises.module.css
@@ -0,0 +1,140 @@
+.icon {
+ align-self: center;
+}
+
+@keyframes modal-fade {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes slide-up {
+ from {
+ transform: translateY(100%);
+ }
+
+ to {
+ transform: translateY(0);
+ }
+}
+
+.overlay {
+ background: rgba(0, 0, 0, 0.5);
+ height: var(--visual-viewport-height);
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ z-index: 100;
+
+ &[data-entering] {
+ animation: modal-fade 200ms;
+ }
+ &[data-exiting] {
+ animation: modal-fade 200ms reverse ease-in;
+ }
+}
+
+@media screen and (min-width: 768px) {
+ .overlay {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+}
+
+.modal {
+ background-color: var(--Base-Surface-Primary-light-Normal);
+ border-radius: var(--Corner-radius-Medium);
+ box-shadow: 0px 4px 24px 0px rgba(38, 32, 30, 0.08);
+ width: 100%;
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ transition: height 200ms ease-in-out;
+
+ &[data-entering] {
+ animation: slide-up 200ms;
+ }
+ &[data-exiting] {
+ animation: slide-up 200ms reverse ease-in-out;
+ }
+}
+
+.dialog {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Spacing-x2);
+ padding-bottom: var(--Spacing-x2);
+}
+
+@media screen and (min-width: 768px) {
+ .modal {
+ left: auto;
+ bottom: auto;
+ width: 400px;
+ }
+}
+
+.top {
+ --button-height: 32px;
+ box-sizing: content-box;
+ display: flex;
+ align-items: center;
+ height: var(--button-height);
+ position: relative;
+ justify-content: center;
+ padding: var(--Spacing-x2) var(--Spacing-x2) 0;
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 0 var(--Spacing-x3);
+ gap: var(--Spacing-x2);
+}
+
+.nav {
+ border-top: 1px solid var(--Base-Border-Subtle);
+ display: flex;
+ justify-content: space-between;
+ padding: 0 var(--Spacing-x2);
+ width: 100%;
+}
+
+.nav button {
+ &[disabled] {
+ visibility: hidden;
+ }
+}
+
+.chevron {
+ transform: rotate(180deg);
+}
+
+.badge {
+ padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: var(--Spacing-x-half);
+ background-color: var(--Base-Surface-Secondary-light-Normal);
+ border-radius: var(--Corner-radius-Small);
+}
+
+.close {
+ background: none;
+ border: none;
+ cursor: pointer;
+ position: absolute;
+ right: var(--Spacing-x2);
+ width: 32px;
+ height: var(--button-height);
+ display: flex;
+ align-items: center;
+}
diff --git a/components/Icons/ChevronRightSmall.tsx b/components/Icons/ChevronRightSmall.tsx
index 25fb29002..1b69fd25b 100644
--- a/components/Icons/ChevronRightSmall.tsx
+++ b/components/Icons/ChevronRightSmall.tsx
@@ -18,23 +18,10 @@ export default function ChevronRightSmallIcon({
fill="none"
{...props}
>
-
-
-
-
-
-
+
)
}
diff --git a/components/TempDesignSystem/Text/CaptionLabel/captionLabel.module.css b/components/TempDesignSystem/Text/CaptionLabel/captionLabel.module.css
new file mode 100644
index 000000000..987f31d71
--- /dev/null
+++ b/components/TempDesignSystem/Text/CaptionLabel/captionLabel.module.css
@@ -0,0 +1,77 @@
+p.caption {
+ margin: 0;
+ padding: 0;
+}
+
+.captionFontOnly {
+ font-style: normal;
+}
+
+.uppercase {
+ font-family: var(--typography-Caption-Labels-fontFamily);
+ font-size: var(--typography-Caption-Labels-fontSize);
+ font-weight: var(--typography-Caption-Labels-fontWeight);
+ letter-spacing: var(--typography-Caption-Labels-letterSpacing);
+ line-height: var(--typography-Caption-Labels-lineHeight);
+ text-transform: uppercase;
+}
+
+.regular {
+ font-family: var(--typography-Caption-Labels-fontFamily);
+ font-size: var(--typography-Caption-Labels-fontSize);
+ font-weight: var(--typography-Caption-Labels-fontWeight);
+ letter-spacing: var(--typography-Caption-Labels-letterSpacing);
+ line-height: var(--typography-Caption-Labels-lineHeight);
+}
+
+.baseTextAccent {
+ color: var(--Base-Text-Accent);
+}
+
+.black {
+ color: var(--Main-Grey-100);
+}
+
+.burgundy {
+ color: var(--Scandic-Brand-Burgundy);
+}
+
+.pale {
+ color: var(--Scandic-Brand-Pale-Peach);
+}
+
+.textMediumContrast {
+ color: var(--Base-Text-Medium-contrast);
+}
+
+.red {
+ color: var(--Scandic-Brand-Scandic-Red);
+}
+
+.white {
+ color: var(--UI-Opacity-White-100);
+}
+
+.uiTextActive {
+ color: var(--UI-Text-Active);
+}
+
+.uiTextMediumContrast {
+ color: var(--UI-Text-Medium-contrast);
+}
+
+.uiTextHighContrast {
+ color: var(--UI-Text-High-contrast);
+}
+
+.disabled {
+ color: var(--Base-Text-Disabled);
+}
+
+.center {
+ text-align: center;
+}
+
+.left {
+ text-align: left;
+}
diff --git a/components/TempDesignSystem/Text/CaptionLabel/captionLabel.ts b/components/TempDesignSystem/Text/CaptionLabel/captionLabel.ts
new file mode 100644
index 000000000..89bb313f0
--- /dev/null
+++ b/components/TempDesignSystem/Text/CaptionLabel/captionLabel.ts
@@ -0,0 +1,10 @@
+import { captionVariants } from "./variants"
+
+import type { VariantProps } from "class-variance-authority"
+
+export interface CaptionLabelProps
+ extends Omit, "color">,
+ VariantProps {
+ asChild?: boolean
+ fontOnly?: boolean
+}
diff --git a/components/TempDesignSystem/Text/CaptionLabel/index.tsx b/components/TempDesignSystem/Text/CaptionLabel/index.tsx
new file mode 100644
index 000000000..21c696eea
--- /dev/null
+++ b/components/TempDesignSystem/Text/CaptionLabel/index.tsx
@@ -0,0 +1,32 @@
+import { Slot } from "@radix-ui/react-slot"
+
+import { captionVariants, fontOnlycaptionVariants } from "./variants"
+
+import type { CaptionLabelProps } from "./captionLabel"
+
+export default function CaptionLabel({
+ asChild = false,
+ className = "",
+ color,
+ fontOnly = false,
+ textAlign,
+ textTransform,
+ uppercase,
+ ...props
+}: CaptionLabelProps) {
+ const Comp = asChild ? Slot : "span"
+ const classNames = fontOnly
+ ? fontOnlycaptionVariants({
+ className,
+ textTransform,
+ uppercase,
+ })
+ : captionVariants({
+ className,
+ color,
+ textTransform,
+ textAlign,
+ uppercase,
+ })
+ return
+}
diff --git a/components/TempDesignSystem/Text/CaptionLabel/variants.ts b/components/TempDesignSystem/Text/CaptionLabel/variants.ts
new file mode 100644
index 000000000..ab2911a25
--- /dev/null
+++ b/components/TempDesignSystem/Text/CaptionLabel/variants.ts
@@ -0,0 +1,58 @@
+import { cva } from "class-variance-authority"
+
+import styles from "./captionLabel.module.css"
+
+const config = {
+ variants: {
+ color: {
+ baseTextAccent: styles.baseTextAccent,
+ black: styles.black,
+ burgundy: styles.burgundy,
+ pale: styles.pale,
+ textMediumContrast: styles.textMediumContrast,
+ red: styles.red,
+ white: styles.white,
+ uiTextHighContrast: styles.uiTextHighContrast,
+ uiTextActive: styles.uiTextActive,
+ uiTextMediumContrast: styles.uiTextMediumContrast,
+ disabled: styles.disabled,
+ },
+ textTransform: {
+ regular: styles.regular,
+ uppercase: styles.uppercase,
+ },
+ textAlign: {
+ center: styles.center,
+ left: styles.left,
+ },
+ uppercase: {
+ true: styles.uppercase,
+ },
+ },
+ defaultVariants: {
+ color: "black",
+ textTransform: "regular",
+ },
+} as const
+
+export const captionVariants = cva(styles.caption, config)
+
+const fontOnlyConfig = {
+ variants: {
+ textTransform: {
+ regular: styles.regular,
+ uppercase: styles.uppercase,
+ },
+ uppercase: {
+ true: styles.uppercase,
+ },
+ },
+ defaultVariants: {
+ textTransform: "regular",
+ },
+} as const
+
+export const fontOnlycaptionVariants = cva(
+ styles.captionFontOnly,
+ fontOnlyConfig
+)
diff --git a/components/TempDesignSystem/Toasts/index.tsx b/components/TempDesignSystem/Toasts/index.tsx
index df08c6803..f78360a0b 100644
--- a/components/TempDesignSystem/Toasts/index.tsx
+++ b/components/TempDesignSystem/Toasts/index.tsx
@@ -49,7 +49,7 @@ export function Toast({ message, onClose, variant }: ToastsProps) {
}
export const toast = {
- success: (message: string, options?: ExternalToast) =>
+ success: (message: React.ReactNode, options?: ExternalToast) =>
sonnerToast.custom(
(t) => (
+ info: (message: React.ReactNode, options?: ExternalToast) =>
sonnerToast.custom(
(t) => (
+ error: (message: React.ReactNode, options?: ExternalToast) =>
sonnerToast.custom(
(t) => (
+ warning: (message: React.ReactNode, options?: ExternalToast) =>
sonnerToast.custom(
(t) => (
, "color">,
VariantProps {
- message: string
+ message: React.ReactNode
onClose: () => void
}
diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json
index 88ac5e6b3..3644b55ae 100644
--- a/i18n/dictionaries/da.json
+++ b/i18n/dictionaries/da.json
@@ -364,5 +364,15 @@
"Zoom out": "Zoom ud",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
- "{width} cm × {length} cm": "{width} cm × {length} cm"
+ "{width} cm × {length} cm": "{width} cm × {length} cm",
+ "Surprise!": "Overraskelse!",
+ "You have # gifts waiting for you!": "Du har {amount} gaver, der venter på dig!",
+ "Hurry up and use them before they expire!": "Skynd dig og brug dem, før de udløber!",
+ "We have a special gift waiting for you!": "Vi har en speciel gave, der venter på dig!",
+ "You'll find all your gifts in 'My benefits'": "Du finder alle dine gaver i ‘Mine fordele’",
+ "Open gift(s)": "Åbne {amount, plural, one {gave} other {gaver}}",
+ "{amount} out of {total}": "{amount} ud af {total}",
+ "Gift(s) added to your benefits": "{amount, plural, one {Gave} other {Gaver}} tilføjet til dine fordele",
+ "Go to My Benefits": "Gå til ‘Mine fordele’",
+ "Previous": "Forudgående"
}
diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json
index 8c3c9e4ce..8d513f1e8 100644
--- a/i18n/dictionaries/de.json
+++ b/i18n/dictionaries/de.json
@@ -364,5 +364,15 @@
"Zoom out": "Verkleinern",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
- "{width} cm × {length} cm": "{width} cm × {length} cm"
+ "{width} cm × {length} cm": "{width} cm × {length} cm",
+ "Surprise!": "Überraschung!",
+ "You have # gifts waiting for you!": "Es warten {amount} Geschenke auf Sie!",
+ "Hurry up and use them before they expire!": "Beeilen Sie sich und nutzen Sie sie, bevor sie ablaufen!",
+ "We have a special gift waiting for you!": "Wir haben ein besonderes Geschenk für Sie!",
+ "You'll find all your gifts in 'My benefits'": "Alle Ihre Geschenke finden Sie unter „Meine Vorteile“",
+ "Open gift(s)": "{amount, plural, one {Geschenk} other {Geschenke}} öffnen",
+ "{amount} out of {total}": "{amount} von {total}",
+ "Gift(s) added to your benefits": "{amount, plural, one {Geschenk zu Ihren Vorteilen hinzugefügt} other {Geschenke, die zu Ihren Vorteilen hinzugefügt werden}}",
+ "Go to My Benefits": "Gehen Sie zu „Meine Vorteile“",
+ "Previous": "Früher"
}
diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json
index 3500a8d01..aad71941d 100644
--- a/i18n/dictionaries/en.json
+++ b/i18n/dictionaries/en.json
@@ -387,5 +387,15 @@
"{amount} {currency}": "{amount} {currency}",
"{card} ending with {cardno}": "{card} ending with {cardno}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
- "{width} cm × {length} cm": "{width} cm × {length} cm"
-}
\ No newline at end of file
+ "{width} cm × {length} cm": "{width} cm × {length} cm",
+ "Surprise!": "Surprise!",
+ "You have # gifts waiting for you!": "You have {amount} gifts waiting for you!",
+ "Hurry up and use them before they expire!": "Hurry up and use them before they expire!",
+ "We have a special gift waiting for you!": "We have a special gift waiting for you!",
+ "You'll find all your gifts in 'My benefits'": "You’ll find all your gifts in ‘My benefits’",
+ "Open gift(s)": "Open {amount, plural, one {gift} other {gifts}}",
+ "{amount} out of {total}": "{amount} out of {total}",
+ "Gift(s) added to your benefits": "{amount, plural, one {Gift} other {Gifts}} added to your benefits",
+ "Go to My Benefits": "Go to My Benefits",
+ "Previous": "Previous"
+}
diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json
index ddb3b80fa..c18b9d7c3 100644
--- a/i18n/dictionaries/fi.json
+++ b/i18n/dictionaries/fi.json
@@ -365,5 +365,15 @@
"Zoom out": "Loitonna",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
- "{width} cm × {length} cm": "{width} cm × {length} cm"
+ "{width} cm × {length} cm": "{width} cm × {length} cm",
+ "Surprise!": "Yllätys!",
+ "You have # gifts waiting for you!": "Sinulla on {amount} lahjaa odottamassa sinua!",
+ "Hurry up and use them before they expire!": "Ole nopea ja käytä ne ennen kuin ne vanhenevat!",
+ "We have a special gift waiting for you!": "Meillä on erityinen lahja odottamassa sinua!",
+ "You'll find all your gifts in 'My benefits'": "Löydät kaikki lahjasi kohdasta ‘Omat edut’",
+ "Open gift(s)": "{amount, plural, one {Avoin lahja} other {Avoimet lahjat}}",
+ "{amount} out of {total}": "{amount}/{total}",
+ "Gift(s) added to your benefits": "{amount, plural, one {Lahja} other {Lahjat}} lisätty etuusi",
+ "Go to My Benefits": "Siirry kohtaan ‘Omat edut’",
+ "Previous": "Aikaisempi"
}
diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json
index 56fcc5dc8..86e9a7809 100644
--- a/i18n/dictionaries/no.json
+++ b/i18n/dictionaries/no.json
@@ -361,5 +361,15 @@
"Zoom out": "Zoom ut",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
- "{width} cm × {length} cm": "{width} cm × {length} cm"
+ "{width} cm × {length} cm": "{width} cm × {length} cm",
+ "Surprise!": "Overraskelse!",
+ "You have # gifts waiting for you!": "Du har {amount} gaver som venter på deg!",
+ "Hurry up and use them before they expire!": "Skynd deg og bruk dem før de utløper!",
+ "We have a special gift waiting for you!": "Vi har en spesiell gave som venter på deg!",
+ "You'll find all your gifts in 'My benefits'": "Du finner alle gavene dine i ‘Mine fordeler’",
+ "Open gift(s)": "{amount, plural, one {Åpen gave} other {Åpnen gaver}}",
+ "{amount} out of {total}": "{amount} av {total}",
+ "Gift(s) added to your benefits": "{amount, plural, one {Gave} other {Gaver}} lagt til fordelene dine",
+ "Go to My Benefits": "Gå til ‘Mine fordeler’",
+ "Previous": "Tidligere"
}
diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json
index b22278223..c88b0f392 100644
--- a/i18n/dictionaries/sv.json
+++ b/i18n/dictionaries/sv.json
@@ -362,5 +362,15 @@
"Zoom out": "Zooma ut",
"{amount} {currency}": "{amount} {currency}",
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
- "{width} cm × {length} cm": "{width} cm × {length} cm"
+ "{width} cm × {length} cm": "{width} cm × {length} cm",
+ "Surprise!": "Överraskning!",
+ "You have # gifts waiting for you!": "Du har {amount} presenter som väntar på dig!",
+ "Hurry up and use them before they expire!": "Skynda dig och använd dem innan de går ut!",
+ "We have a special gift waiting for you!": "Vi har en speciell present som väntar på dig!",
+ "You'll find all your gifts in 'My benefits'": "Du hittar alla dina gåvor i ‘Mina förmåner’",
+ "Open gift(s)": "Öppna {amount, plural, one {gåva} other {gåvor}}",
+ "{amount} out of {total}": "{amount} av {total}",
+ "Gift(s) added to your benefits": "{amount, plural, one {Gåva} other {Gåvor}} läggs till dina förmåner",
+ "Go to My Benefits": "Gå till ‘Mina förmåner’",
+ "Previous": "Föregående"
}
diff --git a/public/_static/img/loyalty-award.png b/public/_static/img/loyalty-award.png
new file mode 100644
index 000000000..b8e7054ca
Binary files /dev/null and b/public/_static/img/loyalty-award.png differ
diff --git a/server/routers/contentstack/reward/input.ts b/server/routers/contentstack/reward/input.ts
index bf02e9255..acf4a67f8 100644
--- a/server/routers/contentstack/reward/input.ts
+++ b/server/routers/contentstack/reward/input.ts
@@ -17,3 +17,7 @@ export const rewardsCurrentInput = z.object({
cursor: z.number().optional().default(0),
lang: z.nativeEnum(Lang).optional(),
})
+
+export const rewardsUpdateInput = z.object({
+ id: z.string(),
+})
diff --git a/server/routers/contentstack/reward/output.ts b/server/routers/contentstack/reward/output.ts
index 302a6b4a7..8fe45c5c9 100644
--- a/server/routers/contentstack/reward/output.ts
+++ b/server/routers/contentstack/reward/output.ts
@@ -2,10 +2,10 @@ import { z } from "zod"
import { MembershipLevelEnum } from "@/constants/membershipLevels"
-export const validateApiRewardSchema = z.object({
- data: z.array(
- z
- .object({
+export const validateApiRewardSchema = z
+ .object({
+ data: z.array(
+ z.object({
title: z.string().optional(),
id: z.string().optional(),
type: z.string().optional(),
@@ -16,9 +16,9 @@ export const validateApiRewardSchema = z.object({
rewardType: z.string().optional(),
rewardTierLevel: z.string().optional(),
})
- .optional()
- ),
-})
+ ),
+ })
+ .transform((data) => data.data)
enum TierKey {
tier1 = MembershipLevelEnum.L1,
@@ -37,19 +37,17 @@ export const validateApiTierRewardsSchema = z.record(
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(),
- })
- .optional()
+ 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(),
+ })
)
)
@@ -77,6 +75,8 @@ export const validateCmsRewardsSchema = z
})
.transform((data) => data.data.all_reward.items)
+export type ApiReward = z.output[0]
+
export type CmsRewardsResponse = z.input
export type Reward = z.output[0]
diff --git a/server/routers/contentstack/reward/query.ts b/server/routers/contentstack/reward/query.ts
index 85470553a..8f5ff7667 100644
--- a/server/routers/contentstack/reward/query.ts
+++ b/server/routers/contentstack/reward/query.ts
@@ -19,6 +19,7 @@ import {
rewardsAllInput,
rewardsByLevelInput,
rewardsCurrentInput,
+ rewardsUpdateInput,
} from "./input"
import {
CmsRewardsResponse,
@@ -242,10 +243,10 @@ export const rewardQueryRouter = router({
return null
}
- const rewardIds = validatedApiRewards.data.data
+ const rewardIds = validatedApiRewards.data
.map((reward) => reward?.rewardId)
- .filter(Boolean)
- .sort() as string[]
+ .filter((rewardId): rewardId is string => !!rewardId)
+ .sort()
const slicedData = rewardIds.slice(cursor, limit + cursor)
@@ -259,8 +260,35 @@ export const rewardQueryRouter = router({
limit + cursor < rewardIds.length ? limit + cursor : undefined
getCurrentRewardSuccessCounter.add(1)
+
return {
rewards: cmsRewards,
+ apiRewards: validatedApiRewards.data
+ // FIXME: Remove these mocks before merging
+ .concat([
+ {
+ autoApplyReward: false,
+ title: "Free kids drink when staying",
+ id: "fake-id",
+ type: "surprise",
+ status: "active",
+ rewardId: "tier_free_kids_drink",
+ redeemLocation: "On-site",
+ rewardType: "Tier",
+ rewardTierLevel: "L1",
+ },
+ {
+ autoApplyReward: false,
+ title: "Free kanelbulle",
+ id: "fake-id-2",
+ type: "surprise",
+ status: "active",
+ rewardId: "tier_free_kanelbulle",
+ redeemLocation: "On-site",
+ rewardType: "Tier",
+ rewardTierLevel: "L1",
+ },
+ ]),
nextCursor,
}
}),
@@ -374,4 +402,19 @@ export const rewardQueryRouter = router({
getAllRewardSuccessCounter.add(1)
return levelsWithRewards
}),
+ update: contentStackBaseWithProtectedProcedure
+ .input(rewardsUpdateInput)
+ .mutation(async ({ input, ctx }) => {
+ const response = await Promise.resolve({ ok: true })
+ // const response = await api.post(api.endpoints.v1.rewards, {
+ // body: {
+ // ids: [input.id],
+ // },
+ // })
+ if (!response.ok) {
+ return false
+ }
+
+ return true
+ }),
})