fix(SW-696): split up surprises into more components for readability
add tsparticles for confetti
This commit is contained in:
217
components/MyPages/Surprises/Client.tsx
Normal file
217
components/MyPages/Surprises/Client.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
"use client"
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion"
|
||||
import { usePathname } from "next/navigation"
|
||||
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 Link from "@/components/TempDesignSystem/Link"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import confetti from "./confetti"
|
||||
import Header from "./Header"
|
||||
import Initial from "./Initial"
|
||||
import Navigation from "./Navigation"
|
||||
import Slide from "./Slide"
|
||||
|
||||
import styles from "./surprises.module.css"
|
||||
|
||||
import type { SurprisesProps } from "@/types/components/blocks/surprises"
|
||||
|
||||
const MotionModal = motion(Modal)
|
||||
|
||||
export default function SurprisesNotification({
|
||||
surprises,
|
||||
membershipNumber,
|
||||
}: SurprisesProps) {
|
||||
const lang = useLang()
|
||||
const intl = useIntl()
|
||||
const pathname = usePathname()
|
||||
const [open, setOpen] = useState(true)
|
||||
const [[selectedSurprise, direction], setSelectedSurprise] = useState([0, 0])
|
||||
const [showSurprises, setShowSurprises] = useState(false)
|
||||
const unwrap = trpc.contentstack.rewards.unwrap.useMutation({
|
||||
onSuccess: () => {
|
||||
if (pathname.indexOf(benefits[lang]) !== 0) {
|
||||
toast.success(
|
||||
<>
|
||||
{intl.formatMessage(
|
||||
{ id: "Gift(s) added to your benefits" },
|
||||
{ amount: surprises.length }
|
||||
)}
|
||||
<br />
|
||||
<Link href={benefits[lang]} variant="underscored" color="burgundy">
|
||||
{intl.formatMessage({ id: "Go to My Benefits" })}
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to unwrap surprise", error)
|
||||
},
|
||||
})
|
||||
|
||||
const totalSurprises = surprises.length
|
||||
if (!totalSurprises) {
|
||||
return null
|
||||
}
|
||||
|
||||
const surprise = surprises[selectedSurprise]
|
||||
|
||||
function showSurprise(newDirection: number) {
|
||||
setSelectedSurprise(([currentIndex]) => [
|
||||
currentIndex + newDirection,
|
||||
newDirection,
|
||||
])
|
||||
}
|
||||
|
||||
async function viewRewards() {
|
||||
const updates = surprises
|
||||
.map((surprise) => {
|
||||
const coupons = surprise.coupons
|
||||
?.map((coupon) => {
|
||||
if (coupon?.couponCode) {
|
||||
return {
|
||||
rewardId: surprise.id,
|
||||
couponCode: coupon.couponCode,
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter(
|
||||
(coupon): coupon is { rewardId: string; couponCode: string } =>
|
||||
!!coupon
|
||||
)
|
||||
|
||||
return coupons
|
||||
})
|
||||
.flat()
|
||||
.filter(
|
||||
(coupon): coupon is { rewardId: string; couponCode: string } => !!coupon
|
||||
)
|
||||
|
||||
unwrap.mutate(updates)
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalOverlay
|
||||
className={styles.overlay}
|
||||
isOpen={open}
|
||||
onOpenChange={setOpen}
|
||||
isKeyboardDismissDisabled
|
||||
>
|
||||
<canvas id="surprise-confetti" className={styles.confetti} />
|
||||
<AnimatePresence mode="wait">
|
||||
{open && (
|
||||
<MotionModal
|
||||
className={styles.modal}
|
||||
initial={{ y: 32, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: 32, opacity: 0 }}
|
||||
transition={{
|
||||
y: { duration: 0.4, ease: "easeInOut" },
|
||||
opacity: { duration: 0.4, ease: "easeInOut" },
|
||||
}}
|
||||
onAnimationComplete={confetti}
|
||||
>
|
||||
<Dialog aria-label="Surprises" className={styles.dialog}>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
onClose={() => {
|
||||
viewRewards()
|
||||
close()
|
||||
}}
|
||||
>
|
||||
{showSurprises && totalSurprises > 1 && (
|
||||
<Caption type="label" uppercase>
|
||||
{intl.formatMessage(
|
||||
{ id: "{amount} out of {total}" },
|
||||
{
|
||||
amount: selectedSurprise + 1,
|
||||
total: totalSurprises,
|
||||
}
|
||||
)}
|
||||
</Caption>
|
||||
)}
|
||||
</Header>
|
||||
|
||||
{showSurprises ? (
|
||||
<>
|
||||
<AnimatePresence
|
||||
mode="popLayout"
|
||||
initial={false}
|
||||
custom={direction}
|
||||
>
|
||||
<motion.div
|
||||
key={selectedSurprise}
|
||||
custom={direction}
|
||||
variants={variants}
|
||||
initial="enter"
|
||||
animate="center"
|
||||
exit="exit"
|
||||
transition={{
|
||||
x: { type: "ease", duration: 0.5 },
|
||||
opacity: { duration: 0.2 },
|
||||
}}
|
||||
layout
|
||||
>
|
||||
<Slide
|
||||
surprise={surprise}
|
||||
membershipNumber={membershipNumber}
|
||||
/>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
||||
{totalSurprises > 1 && (
|
||||
<Navigation
|
||||
selectedSurprise={selectedSurprise}
|
||||
totalSurprises={totalSurprises}
|
||||
showSurprise={showSurprise}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Initial
|
||||
totalSurprises={totalSurprises}
|
||||
onOpen={() => {
|
||||
setShowSurprises(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Dialog>
|
||||
</MotionModal>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</ModalOverlay>
|
||||
)
|
||||
}
|
||||
|
||||
const variants = {
|
||||
enter: (direction: number) => {
|
||||
return {
|
||||
x: direction > 0 ? 1000 : -1000,
|
||||
opacity: 0,
|
||||
}
|
||||
},
|
||||
center: {
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
},
|
||||
exit: (direction: number) => {
|
||||
return {
|
||||
x: direction < 0 ? 1000 : -1000,
|
||||
opacity: 0,
|
||||
}
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user