Files
web/apps/scandic-web/components/Blocks/DynamicContent/Rewards/Redeem/index.tsx
Anton Gunnarsson 002d093af4 Merged in feat/sw-2863-move-contentstack-router-to-trpc-package (pull request #2389)
feat(SW-2863): Move contentstack router to trpc package

* Add exports to packages and lint rule to prevent relative imports

* Add env to trpc package

* Add eslint to trpc package

* Apply lint rules

* Use direct imports from trpc package

* Add lint-staged config to trpc

* Move lang enum to common

* Restructure trpc package folder structure

* WIP first step

* update internal imports in trpc

* Fix most errors in scandic-web

Just 100 left...

* Move Props type out of trpc

* Fix CategorizedFilters types

* Move more schemas in hotel router

* Fix deps

* fix getNonContentstackUrls

* Fix import error

* Fix entry error handling

* Fix generateMetadata metrics

* Fix alertType enum

* Fix duplicated types

* lint:fix

* Merge branch 'master' into feat/sw-2863-move-contentstack-router-to-trpc-package

* Fix broken imports

* Merge branch 'master' into feat/sw-2863-move-contentstack-router-to-trpc-package


Approved-by: Linus Flood
2025-06-26 07:53:01 +00:00

185 lines
5.2 KiB
TypeScript

"use client"
import { motion } from "motion/react"
import { useState } from "react"
import {
Dialog,
DialogTrigger,
Modal,
ModalOverlay,
} from "react-aria-components"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { trpc } from "@/lib/trpc/client"
import Button from "@/components/TempDesignSystem/Button"
import useLang from "@/hooks/useLang"
import { isRestaurantOnSiteTierReward } from "@/utils/rewards"
import RedeemCampaign from "./Flows/Campaign"
import RedeemTier from "./Flows/Tier"
import { ConfirmClose } from "./ConfirmClose"
import { RedeemContext } from "./useRedeemFlow"
import styles from "./redeem.module.css"
import type { Reward } from "@scandic-hotels/trpc/types/rewards"
import type {
RedeemModalState,
RedeemProps,
RedeemStep,
} from "@/types/components/myPages/myPage/accountPage"
const MotionOverlay = motion.create(ModalOverlay)
const MotionModal = motion.create(Modal)
const thirtyMinutesInMs = 1000 * 60 * 30
export default function Redeem({ reward, membershipNumber }: RedeemProps) {
const [animation, setAnimation] = useState<RedeemModalState>("unmounted")
const intl = useIntl()
const lang = useLang()
const utils = trpc.useUtils()
const [redeemStep, setRedeemStep] = useState<RedeemStep>("initial")
const [timeRemaining, setTimeRemaining] = useState(thirtyMinutesInMs)
function modalStateHandler(newAnimationState: RedeemModalState) {
setAnimation((currentAnimationState) =>
newAnimationState === "hidden" && currentAnimationState === "hidden"
? "unmounted"
: currentAnimationState
)
if (newAnimationState === "unmounted") {
setRedeemStep("initial")
}
}
return (
<RedeemContext.Provider
value={{
redeemStep,
setRedeemStep,
defaultTimeRemaining: thirtyMinutesInMs,
timeRemaining,
setTimeRemaining,
}}
>
<DialogTrigger
onOpenChange={(isOpen) => setAnimation(isOpen ? "visible" : "hidden")}
>
<Button intent="primary" fullWidth>
{reward.redeemLocation === "Non-redeemable"
? intl.formatMessage({
defaultMessage: "How to use",
})
: intl.formatMessage({
defaultMessage: "Open",
})}
</Button>
<MotionOverlay
className={styles.overlay}
isExiting={animation === "hidden"}
onAnimationComplete={modalStateHandler}
variants={variants.fade}
initial="hidden"
animate={animation}
>
<MotionModal
className={styles.modal}
variants={variants.slideInOut}
initial="hidden"
animate={animation}
>
<Dialog className={styles.dialog} aria-label={reward.label}>
{({ close }) => {
function closeModal() {
if (
redeemStep === "redeemed" ||
redeemStep === "confirm-close"
) {
utils.contentstack.rewards.current.invalidate({
lang,
})
}
close()
}
return (
<>
<header className={styles.modalHeader}>
<button
onClick={() => {
if (
redeemStep === "redeemed" &&
!isRestaurantOnSiteTierReward(reward)
) {
setRedeemStep("confirm-close")
} else {
closeModal()
}
}}
type="button"
className={styles.modalClose}
>
<MaterialIcon icon="close" />
</button>
</header>
{redeemStep === "confirm-close" ? (
<ConfirmClose close={closeModal} />
) : (
getRedeemFlow(reward, membershipNumber || "")
)}
</>
)
}}
</Dialog>
</MotionModal>
</MotionOverlay>
</DialogTrigger>
</RedeemContext.Provider>
)
}
const variants = {
fade: {
hidden: {
opacity: 0,
transition: { duration: 0.4, ease: "easeInOut" },
},
visible: {
opacity: 1,
transition: { duration: 0.4, ease: "easeInOut" },
},
},
slideInOut: {
hidden: {
opacity: 0,
y: 32,
transition: { duration: 0.4, ease: "easeInOut" },
},
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: "easeInOut" },
},
},
}
function getRedeemFlow(reward: Reward, membershipNumber: string) {
const { rewardType } = reward
switch (rewardType) {
case "Campaign":
return <RedeemCampaign reward={reward} />
case "Surprise":
case "Tier":
return <RedeemTier reward={reward} membershipNumber={membershipNumber} />
default:
console.warn("Unsupported reward type for redeem:", rewardType)
return null
}
}