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 ( + + + + {({ close }) => { + return ( + <> +
+ {surprises.length > 1 && showSurprises && ( + + {intl.formatMessage( + { id: "{amount} out of {total}" }, + { + amount: selectedSurprise + 1, + total: surprises.length, + } + )} + + )} + +
+ {showSurprises ? ( + <> +
+ + + This is just some dummy text describing the gift and + should be replaced. + +
+ Valid through DD M YYYY + Member ID 000000 +
+
+
+ {surprises.length > 1 && ( + <> + + + )} + + ) : ( +
+ {surprises.length > 1 ? ( + + + {intl.formatMessage( + { + id: "You have # gifts waiting for you!", + }, + { + amount: surprises.length, + b: (str) => {str}, + } + )} +
+ {intl.formatMessage({ + id: "Hurry up and use them before they expire!", + })} + + + {intl.formatMessage({ + id: "You'll find all your gifts in 'My benefits'", + })} + +
+ ) : ( + + + {intl.formatMessage({ + id: "We have a special gift waiting for you!", + })} + + + {intl.formatMessage({ + id: "You'll find all your gifts in 'My benefits'", + })} + + + )} + + +
+ )} + + ) + }} +
+
+
+ ) +} + +function Surprise({ + title, + children, +}: { + title?: string + children?: React.ReactNode +}) { + return ( + <> + Gift + + {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 + }), })