From 3aedc4ff25839d6ac67626dc67dae9a126cb55e9 Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Thu, 14 Nov 2024 17:08:55 +0100 Subject: [PATCH] fix(SW-696): split up surprises into more components for readability add tsparticles for confetti --- components/MyPages/Surprises/Card.tsx | 26 ++ components/MyPages/Surprises/Client.tsx | 217 ++++++++++++ components/MyPages/Surprises/Header.tsx | 16 + components/MyPages/Surprises/Initial.tsx | 62 ++++ components/MyPages/Surprises/Navigation.tsx | 45 +++ components/MyPages/Surprises/Slide.tsx | 49 +++ .../Surprises/SurprisesNotification.tsx | 320 ------------------ components/MyPages/Surprises/confetti.ts | 13 + components/MyPages/Surprises/index.tsx | 4 +- .../MyPages/Surprises/surprises.module.css | 67 ++-- package-lock.json | 274 +++++++++++++++ package.json | 1 + types/components/blocks/surprises.ts | 24 ++ 13 files changed, 760 insertions(+), 358 deletions(-) create mode 100644 components/MyPages/Surprises/Card.tsx create mode 100644 components/MyPages/Surprises/Client.tsx create mode 100644 components/MyPages/Surprises/Header.tsx create mode 100644 components/MyPages/Surprises/Initial.tsx create mode 100644 components/MyPages/Surprises/Navigation.tsx create mode 100644 components/MyPages/Surprises/Slide.tsx delete mode 100644 components/MyPages/Surprises/SurprisesNotification.tsx create mode 100644 components/MyPages/Surprises/confetti.ts diff --git a/components/MyPages/Surprises/Card.tsx b/components/MyPages/Surprises/Card.tsx new file mode 100644 index 000000000..932f0ecc9 --- /dev/null +++ b/components/MyPages/Surprises/Card.tsx @@ -0,0 +1,26 @@ +import Image from "@/components/Image" +import Title from "@/components/TempDesignSystem/Text/Title" + +import styles from "./surprises.module.css" + +import type { CardProps } from "@/types/components/blocks/surprises" + +export default function Card({ title, children }: CardProps) { + return ( +
+ Gift +
+ + {title} + +
+ + {children} +
+ ) +} diff --git a/components/MyPages/Surprises/Client.tsx b/components/MyPages/Surprises/Client.tsx new file mode 100644 index 000000000..2e4202103 --- /dev/null +++ b/components/MyPages/Surprises/Client.tsx @@ -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 } + )} +
+ + {intl.formatMessage({ id: "Go to My Benefits" })} + + + ) + } + }, + 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 ( + + + + {open && ( + + + {({ close }) => { + return ( + <> +
{ + viewRewards() + close() + }} + > + {showSurprises && totalSurprises > 1 && ( + + {intl.formatMessage( + { id: "{amount} out of {total}" }, + { + amount: selectedSurprise + 1, + total: totalSurprises, + } + )} + + )} +
+ + {showSurprises ? ( + <> + + + + + + + {totalSurprises > 1 && ( + + )} + + ) : ( + { + setShowSurprises(true) + }} + /> + )} + + ) + }} +
+
+ )} +
+
+ ) +} + +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, + } + }, +} diff --git a/components/MyPages/Surprises/Header.tsx b/components/MyPages/Surprises/Header.tsx new file mode 100644 index 000000000..3a25400a9 --- /dev/null +++ b/components/MyPages/Surprises/Header.tsx @@ -0,0 +1,16 @@ +import { CloseLargeIcon } from "@/components/Icons" + +import styles from "./surprises.module.css" + +import type { HeaderProps } from "@/types/components/blocks/surprises" + +export default function Header({ onClose, children }: HeaderProps) { + return ( +
+ {children} + +
+ ) +} diff --git a/components/MyPages/Surprises/Initial.tsx b/components/MyPages/Surprises/Initial.tsx new file mode 100644 index 000000000..8e3a60b9c --- /dev/null +++ b/components/MyPages/Surprises/Initial.tsx @@ -0,0 +1,62 @@ +import { useIntl } from "react-intl" + +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" + +import Card from "./Card" + +import type { InitialProps } from "@/types/components/blocks/surprises" + +export default function Initial({ totalSurprises, onOpen }: InitialProps) { + const intl = useIntl() + + return ( + + + {totalSurprises > 1 ? ( + <> + {intl.formatMessage( + { + id: "You have # gifts waiting for you!", + }, + { + amount: totalSurprises, + b: (str) => {str}, + } + )} +
+ {intl.formatMessage({ + id: "Hurry up and use them before they expire!", + })} + + ) : ( + intl.formatMessage({ + id: "We have a special gift waiting for you!", + }) + )} + + + {intl.formatMessage({ + id: "You'll find all your gifts in 'My benefits'", + })} + + + +
+ ) +} diff --git a/components/MyPages/Surprises/Navigation.tsx b/components/MyPages/Surprises/Navigation.tsx new file mode 100644 index 000000000..2a1296f01 --- /dev/null +++ b/components/MyPages/Surprises/Navigation.tsx @@ -0,0 +1,45 @@ +import { useIntl } from "react-intl" + +import { ChevronRightSmallIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" + +import styles from "./surprises.module.css" + +import type { NavigationProps } from "@/types/components/blocks/surprises" + +export default function Navigation({ + selectedSurprise, + totalSurprises, + showSurprise, +}: NavigationProps) { + const intl = useIntl() + + return ( + + ) +} diff --git a/components/MyPages/Surprises/Slide.tsx b/components/MyPages/Surprises/Slide.tsx new file mode 100644 index 000000000..2a4173fca --- /dev/null +++ b/components/MyPages/Surprises/Slide.tsx @@ -0,0 +1,49 @@ +import { useIntl } from "react-intl" + +import { dt } from "@/lib/dt" + +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import useLang from "@/hooks/useLang" + +import Card from "./Card" + +import styles from "./surprises.module.css" + +import type { SlideProps } from "@/types/components/blocks/surprises" + +export default function Slide({ surprise, membershipNumber }: SlideProps) { + const lang = useLang() + const intl = useIntl() + + const earliestExpirationDate = surprise.coupons?.reduce( + (earliestDate, coupon) => { + const expiresAt = dt(coupon.expiresAt) + return earliestDate.isBefore(expiresAt) ? earliestDate : expiresAt + }, + dt() + ) + return ( + + {surprise.description} +
+ + {intl.formatMessage( + { id: "Expires at the earliest" }, + { + date: dt(earliestExpirationDate) + .locale(lang) + .format("D MMM YYYY"), + } + )} + + + {intl.formatMessage({ + id: "Membership ID", + })}{" "} + {membershipNumber} + +
+
+ ) +} diff --git a/components/MyPages/Surprises/SurprisesNotification.tsx b/components/MyPages/Surprises/SurprisesNotification.tsx deleted file mode 100644 index f69269c8c..000000000 --- a/components/MyPages/Surprises/SurprisesNotification.tsx +++ /dev/null @@ -1,320 +0,0 @@ -"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 { dt } from "@/lib/dt" -import { trpc } from "@/lib/trpc/client" - -import { ChevronRightSmallIcon, CloseLargeIcon } from "@/components/Icons" -import Image from "@/components/Image" -import Button from "@/components/TempDesignSystem/Button" -import Link from "@/components/TempDesignSystem/Link" -import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" -import Title from "@/components/TempDesignSystem/Text/Title" -import { toast } from "@/components/TempDesignSystem/Toasts" -import useLang from "@/hooks/useLang" - -import styles from "./surprises.module.css" - -import type { - Surprise, - SurprisesProps, -} from "@/types/components/blocks/surprises" - -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, - } - }, -} - -export default function SurprisesNotification({ - surprises, - membershipNumber, -}: SurprisesProps) { - const lang = useLang() - 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 } - )} -
- - {intl.formatMessage({ id: "Go to My Benefits" })} - - - ) - } - }, - onError: (error) => { - console.error("Error", error) - }, - }) - const intl = useIntl() - - if (!surprises.length) { - 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) - } - - const earliestExpirationDate = surprise.coupons?.reduce( - (earliestDate, coupon) => { - const expiresAt = dt(coupon.expiresAt) - return earliestDate.isBefore(expiresAt) ? earliestDate : expiresAt - }, - dt() - ) - - return ( - - - - {({ close }) => { - return ( - <> -
- {surprises.length > 1 && showSurprises && ( - - {intl.formatMessage( - { id: "{amount} out of {total}" }, - { - amount: selectedSurprise + 1, - total: surprises.length, - } - )} - - )} - -
- {showSurprises ? ( - <> - - - - {surprise.description} -
- - {intl.formatMessage( - { id: "Expires at the earliest" }, - { - date: dt(earliestExpirationDate) - .locale(lang) - .format("D MMM YYYY"), - } - )} - - - {intl.formatMessage({ - id: "Membership ID", - })}{" "} - {membershipNumber} - -
-
-
-
- {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: "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/MyPages/Surprises/confetti.ts b/components/MyPages/Surprises/confetti.ts new file mode 100644 index 000000000..2990fade4 --- /dev/null +++ b/components/MyPages/Surprises/confetti.ts @@ -0,0 +1,13 @@ +import { confetti as particlesConfetti } from "@tsparticles/confetti" + +export default function confetti() { + particlesConfetti("surprise-confetti", { + count: 300, + spread: 150, + position: { + y: 60, + }, + colors: ["#cd0921", "#4d001b", "#fff"], + shapes: ["star", "square", "circle", "polygon"], + }) +} diff --git a/components/MyPages/Surprises/index.tsx b/components/MyPages/Surprises/index.tsx index 4a0eae273..2ae414a3a 100644 --- a/components/MyPages/Surprises/index.tsx +++ b/components/MyPages/Surprises/index.tsx @@ -2,7 +2,7 @@ import { env } from "@/env/server" import { getProfile } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" -import SurprisesNotification from "./SurprisesNotification" +import SurprisesClient from "./Client" export default async function Surprises() { if (env.HIDE_FOR_NEXT_RELEASE) { @@ -22,7 +22,7 @@ export default async function Surprises() { } return ( - diff --git a/components/MyPages/Surprises/surprises.module.css b/components/MyPages/Surprises/surprises.module.css index c752fece3..9a80761f4 100644 --- a/components/MyPages/Surprises/surprises.module.css +++ b/components/MyPages/Surprises/surprises.module.css @@ -1,4 +1,4 @@ -@keyframes modal-fade { +@keyframes fade { from { opacity: 0; } @@ -8,16 +8,6 @@ } } -@keyframes slide-up { - from { - transform: translateY(100%); - } - - to { - transform: translateY(0); - } -} - .overlay { background: rgba(0, 0, 0, 0.5); height: var(--visual-viewport-height); @@ -28,10 +18,10 @@ z-index: 100; &[data-entering] { - animation: modal-fade 200ms ease-in; + animation: fade 400ms ease-in; } &[data-exiting] { - animation: modal-fade 200ms reverse ease-in; + animation: fade 400ms reverse ease-in; } } @@ -40,17 +30,19 @@ display: flex; justify-content: center; align-items: center; + } +} - &:before { - background-image: url("/_static/img/confetti.svg"); - background-repeat: no-repeat; - background-position: center 40%; - content: ""; - width: 100%; - height: 100%; - animation: modal-fade 200ms ease-in; - display: block; - } +@media screen and (min-width: 768px) and (prefers-reduced-motion) { + .overlay:before { + background-image: url("/_static/img/confetti.svg"); + background-repeat: no-repeat; + background-position: center 40%; + content: ""; + width: 100%; + height: 100%; + animation: fade 400ms ease-in; + display: block; } } @@ -62,12 +54,14 @@ position: absolute; left: 0; bottom: 0; + z-index: 102; +} - &[data-entering] { - animation: slide-up 200ms; - } - &[data-exiting] { - animation: slide-up 200ms reverse ease-in-out; +@media screen and (min-width: 768px) { + .modal { + left: auto; + bottom: auto; + width: 400px; } } @@ -82,14 +76,6 @@ overflow: hidden; } -@media screen and (min-width: 768px) { - .modal { - left: auto; - bottom: auto; - width: 400px; - } -} - .top { --button-height: 32px; box-sizing: content-box; @@ -160,3 +146,12 @@ display: flex; align-items: center; } + +/* + * temporary fix until next version of tsparticles is released + * https://github.com/tsparticles/tsparticles/issues/5375 + */ +.confetti { + position: relative; + z-index: 101; +} diff --git a/package-lock.json b/package-lock.json index e611379b4..d1a2b5f1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@trpc/client": "^11.0.0-rc.467", "@trpc/react-query": "^11.0.0-rc.467", "@trpc/server": "^11.0.0-rc.467", + "@tsparticles/confetti": "^3.5.0", "@vercel/otel": "^1.9.1", "@vis.gl/react-google-maps": "^1.2.0", "class-variance-authority": "^0.7.0", @@ -6402,6 +6403,279 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@tsparticles/basic": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/basic/-/basic-3.5.0.tgz", + "integrity": "sha512-oty33TxM2aHWrzcwWRic1bQ04KBCdpnvzv8JXEkx5Uyp70vgVegUbtKmwGki3shqKZIt3v2qE4I8NsK6onhLrA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "dependencies": { + "@tsparticles/engine": "^3.5.0", + "@tsparticles/move-base": "^3.5.0", + "@tsparticles/shape-circle": "^3.5.0", + "@tsparticles/updater-color": "^3.5.0", + "@tsparticles/updater-opacity": "^3.5.0", + "@tsparticles/updater-out-modes": "^3.5.0", + "@tsparticles/updater-size": "^3.5.0" + } + }, + "node_modules/@tsparticles/confetti": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/confetti/-/confetti-3.5.0.tgz", + "integrity": "sha512-wS3nqtanbCvAbNlyAffKJq6lgIPzHFljEOO3JSCDgRD6rG5X/jvidhw2vR3kLrjBTV40c+Xv6MpJgSgTRWkogg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "dependencies": { + "@tsparticles/basic": "^3.5.0", + "@tsparticles/engine": "^3.5.0", + "@tsparticles/plugin-emitters": "^3.5.0", + "@tsparticles/plugin-motion": "^3.5.0", + "@tsparticles/shape-cards": "^3.5.0", + "@tsparticles/shape-emoji": "^3.5.0", + "@tsparticles/shape-heart": "^3.5.0", + "@tsparticles/shape-image": "^3.5.0", + "@tsparticles/shape-polygon": "^3.5.0", + "@tsparticles/shape-square": "^3.5.0", + "@tsparticles/shape-star": "^3.5.0", + "@tsparticles/updater-life": "^3.5.0", + "@tsparticles/updater-roll": "^3.5.0", + "@tsparticles/updater-rotate": "^3.5.0", + "@tsparticles/updater-tilt": "^3.5.0", + "@tsparticles/updater-wobble": "^3.5.0" + } + }, + "node_modules/@tsparticles/engine": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/engine/-/engine-3.5.0.tgz", + "integrity": "sha512-RCwrJ2SvSYdhXJ24oUCjSUKEZQ9lXwObOWMvfMC9vS6/bk+Qo0N7Xx8AfumqzP/LebB1YJdlCvuoJMauAon0Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true + }, + "node_modules/@tsparticles/move-base": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/move-base/-/move-base-3.5.0.tgz", + "integrity": "sha512-9oDk7zTxyhUCstj3lHTNTiWAgqIBzWa2o1tVQFK63Qwq+/WxzJCSwZOocC9PAHGM1IP6nA4zYJSfjbMBTrUocA==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/plugin-emitters": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-emitters/-/plugin-emitters-3.5.0.tgz", + "integrity": "sha512-8Vg6wAPS75ibkukqtTM7yoC+8NnfXBl8xVUUbTaoeQCE0WDWwztboMf5L4pUgWe9WA52ZgFkWtT/mFH5wk5T9g==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/plugin-motion": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-motion/-/plugin-motion-3.5.0.tgz", + "integrity": "sha512-juP8f9ABjlhQmg4SO+tTofLYJwvwLPfKWJYvG8c6HU2rlJxJ/6eeWe9kDpv/T8nun3kXYHtrLhcJAmvWg/b5qA==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-cards": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-cards/-/shape-cards-3.5.0.tgz", + "integrity": "sha512-rU7rp1Yn1leHpCNA/7vrfY6tcLjvrG6A6sOT11dSanIj2J8zgLNXnbVtRJPtU13x+masft9Ta1tpw3dFRdtHcA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-circle": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-circle/-/shape-circle-3.5.0.tgz", + "integrity": "sha512-59TmXkeeI6Jzv5vt/D3TkclglabaoEXQi2kbDjSCBK68SXRHzlQu29mSAL41Y5S0Ft5ZJKkAQHX1IqEnm8Hyjg==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-emoji": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-emoji/-/shape-emoji-3.5.0.tgz", + "integrity": "sha512-cxWHxQxnG5vLDltkoxdo7KS87uKPwQgf4SDWy/WCxW4Psm1TEeeSGYMJPVed+wWPspOKmLb7u8OaEexgE2pHHQ==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-heart": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-heart/-/shape-heart-3.5.0.tgz", + "integrity": "sha512-MvOxW6X7w1jHH+KRJShvHMDhRZ+bpei2mAqQOFR5HY+2D6KFzaDVgtfGFwoiaX8Pm6oP6OQssQ3QnDtrywLRFw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-image": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-image/-/shape-image-3.5.0.tgz", + "integrity": "sha512-lWYg7DTv74dSOnXy+4dr7t1/OSuUmxDpIo12Lbxgx/QBN7A5I/HoqbKcs13TSA0RS1hcuMgtti07BcDTEYW3Dw==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-polygon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-polygon/-/shape-polygon-3.5.0.tgz", + "integrity": "sha512-sqYL+YXpnq3nSWcOEGZaJ4Z7Cb7x8M0iORSLpPdNEIvwDKdPczYyQM95D8ep19Pv1CV5L0uRthV36wg7UpnJ9Q==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-square": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-square/-/shape-square-3.5.0.tgz", + "integrity": "sha512-rPHpA4Pzm1W5DIIow+lQS+VS7D2thSBQQbV9eHxb933Wh0/QC3me3w4vovuq7hdtVANhsUVO04n44Gc/2TgHkw==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-star": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-star/-/shape-star-3.5.0.tgz", + "integrity": "sha512-EDEJc4MYv3UbOeA3wrZjuJVtZ08PdCzzBij3T/7Tp3HUCf/p9XnfHBd/CPR5Mo6X0xpGfrein8UQN9CjGLHwUA==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-color": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-color/-/updater-color-3.5.0.tgz", + "integrity": "sha512-TGGgiLixIg37sst2Fj9IV4XbdMwkT6PYanM7qEqyfmv4hJ/RHMQlCznEe6b7OhChQVBg5ov5EMl/BT4/fIWEYw==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-life": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-life/-/updater-life-3.5.0.tgz", + "integrity": "sha512-jlMEq16dwN+rZmW/UmLdqaCe4W0NFrVdmXkZV8QWYgu06a+Ucslz337nHYaP89/9rZWpNua/uq1JDjDzaVD5Jg==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-opacity": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-opacity/-/updater-opacity-3.5.0.tgz", + "integrity": "sha512-T2YfqdIFV/f5VOg1JQsXu6/owdi9g9K2wrJlBfgteo+IboVp6Lcuo4PGAfilWVkWrTdp1Nz4mz39NrLHfOce2g==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-out-modes": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-out-modes/-/updater-out-modes-3.5.0.tgz", + "integrity": "sha512-y6NZe2OSk5SrYdaLwUIQnHICsNEDIdPPJHQ2nAWSvAuPJphlSKjUknc7OaGiFwle6l0OkhWoZZe1rV1ktbw/lA==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-roll": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-roll/-/updater-roll-3.5.0.tgz", + "integrity": "sha512-K3NfBGqVIu2zyJv72oNPlYLMDQKmUXTaCvnxUjzBEJJCYRdx7KhZPQVjAsfVYLHd7m7D7/+wKlkXmdYYAd67bg==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-rotate": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-rotate/-/updater-rotate-3.5.0.tgz", + "integrity": "sha512-j4qPHQd1eUmDoGnIJOsVswHLqtTof1je+b2GTOLB3WIoKmlyUpzQYjVc7PNfLMuCEUubwpZCfcd/vC80VZeWkg==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-size": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-size/-/updater-size-3.5.0.tgz", + "integrity": "sha512-TnWlOChBsVZffT2uO0S4ALGSzxT6UAMIVlhGMGFgSeIlktKMqM+dxDGAPrYa1n8IS2dkVGisiXzsV0Ss6Ceu1A==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-tilt": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-tilt/-/updater-tilt-3.5.0.tgz", + "integrity": "sha512-ovK6jH4fAmTav1kCC5Z1FW/pPjKxtK+X+w9BZJEddpS5cyBEdWD4FgvNgLnmZYpK0xad/nb+xxqeDkpSu/O51Q==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-wobble": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-wobble/-/updater-wobble-3.5.0.tgz", + "integrity": "sha512-fpN0XPvAf3dJ5UU++C+ETVDLurpnkzje02w865Ar4ubPBgGpMhowr6AbtFUe37Zl8rFUTYntBOSEoxqNYJAUgQ==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", diff --git a/package.json b/package.json index 5871b48b4..7baa98fe1 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@trpc/client": "^11.0.0-rc.467", "@trpc/react-query": "^11.0.0-rc.467", "@trpc/server": "^11.0.0-rc.467", + "@tsparticles/confetti": "^3.5.0", "@vercel/otel": "^1.9.1", "@vis.gl/react-google-maps": "^1.2.0", "class-variance-authority": "^0.7.0", diff --git a/types/components/blocks/surprises.ts b/types/components/blocks/surprises.ts index da4a8730c..674c52d39 100644 --- a/types/components/blocks/surprises.ts +++ b/types/components/blocks/surprises.ts @@ -9,3 +9,27 @@ export interface SurprisesProps { surprises: Surprise[] membershipNumber?: string } + +export interface NavigationProps { + selectedSurprise: number + totalSurprises: number + showSurprise: (direction: number) => void +} + +export interface CardProps extends React.PropsWithChildren { + title?: string +} + +export interface InitialProps { + totalSurprises: number + onOpen: VoidFunction +} + +export interface SlideProps { + surprise: Surprise + membershipNumber?: string +} + +export interface HeaderProps extends React.PropsWithChildren { + onClose: VoidFunction +}