fix(SW-696): split up surprises into more components for readability
add tsparticles for confetti
This commit is contained in:
26
components/MyPages/Surprises/Card.tsx
Normal file
26
components/MyPages/Surprises/Card.tsx
Normal file
@@ -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 (
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Image
|
||||||
|
src="/_static/img/loyalty-award.png"
|
||||||
|
width={113}
|
||||||
|
height={125}
|
||||||
|
alt="Gift"
|
||||||
|
/>
|
||||||
|
<header>
|
||||||
|
<Title textAlign="center" level="h4">
|
||||||
|
{title}
|
||||||
|
</Title>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
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,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
16
components/MyPages/Surprises/Header.tsx
Normal file
16
components/MyPages/Surprises/Header.tsx
Normal file
@@ -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 (
|
||||||
|
<div className={styles.top}>
|
||||||
|
{children}
|
||||||
|
<button onClick={onClose} type="button" className={styles.close}>
|
||||||
|
<CloseLargeIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
62
components/MyPages/Surprises/Initial.tsx
Normal file
62
components/MyPages/Surprises/Initial.tsx
Normal file
@@ -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 (
|
||||||
|
<Card title={intl.formatMessage({ id: "Surprise!" })}>
|
||||||
|
<Body textAlign="center">
|
||||||
|
{totalSurprises > 1 ? (
|
||||||
|
<>
|
||||||
|
{intl.formatMessage<React.ReactNode>(
|
||||||
|
{
|
||||||
|
id: "You have <b>#</b> gifts waiting for you!",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
amount: totalSurprises,
|
||||||
|
b: (str) => <b>{str}</b>,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "Hurry up and use them before they expire!",
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
intl.formatMessage({
|
||||||
|
id: "We have a special gift waiting for you!",
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
|
<Caption>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "You'll find all your gifts in 'My benefits'",
|
||||||
|
})}
|
||||||
|
</Caption>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
intent="primary"
|
||||||
|
onPress={onOpen}
|
||||||
|
size="medium"
|
||||||
|
theme="base"
|
||||||
|
fullWidth
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: "Open gift(s)",
|
||||||
|
},
|
||||||
|
{ amount: totalSurprises }
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
45
components/MyPages/Surprises/Navigation.tsx
Normal file
45
components/MyPages/Surprises/Navigation.tsx
Normal file
@@ -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 (
|
||||||
|
<nav className={styles.nav}>
|
||||||
|
<Button
|
||||||
|
variant="icon"
|
||||||
|
intent="tertiary"
|
||||||
|
disabled={selectedSurprise === 0}
|
||||||
|
onPress={() => showSurprise(-1)}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ChevronRightSmallIcon
|
||||||
|
className={styles.chevron}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
{intl.formatMessage({ id: "Previous" })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="icon"
|
||||||
|
intent="tertiary"
|
||||||
|
disabled={selectedSurprise === totalSurprises - 1}
|
||||||
|
onPress={() => showSurprise(1)}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "Next" })}
|
||||||
|
<ChevronRightSmallIcon width={20} height={20} />
|
||||||
|
</Button>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
49
components/MyPages/Surprises/Slide.tsx
Normal file
49
components/MyPages/Surprises/Slide.tsx
Normal file
@@ -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 (
|
||||||
|
<Card title={surprise.label}>
|
||||||
|
<Body textAlign="center">{surprise.description}</Body>
|
||||||
|
<div className={styles.badge}>
|
||||||
|
<Caption>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "Expires at the earliest" },
|
||||||
|
{
|
||||||
|
date: dt(earliestExpirationDate)
|
||||||
|
.locale(lang)
|
||||||
|
.format("D MMM YYYY"),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Caption>
|
||||||
|
<Caption>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "Membership ID",
|
||||||
|
})}{" "}
|
||||||
|
{membershipNumber}
|
||||||
|
</Caption>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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 }
|
|
||||||
)}
|
|
||||||
<br />
|
|
||||||
<Link href={benefits[lang]} variant="underscored" color="burgundy">
|
|
||||||
{intl.formatMessage({ id: "Go to My Benefits" })}
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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 (
|
|
||||||
<ModalOverlay
|
|
||||||
className={styles.overlay}
|
|
||||||
isOpen={open}
|
|
||||||
onOpenChange={setOpen}
|
|
||||||
isKeyboardDismissDisabled
|
|
||||||
>
|
|
||||||
<Modal className={styles.modal}>
|
|
||||||
<Dialog aria-label="Surprises" className={styles.dialog}>
|
|
||||||
{({ close }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={styles.top}>
|
|
||||||
{surprises.length > 1 && showSurprises && (
|
|
||||||
<Caption type="label" uppercase>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "{amount} out of {total}" },
|
|
||||||
{
|
|
||||||
amount: selectedSurprise + 1,
|
|
||||||
total: surprises.length,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</Caption>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
viewRewards()
|
|
||||||
close()
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
className={styles.close}
|
|
||||||
>
|
|
||||||
<CloseLargeIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{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 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Surprise title={surprise.label}>
|
|
||||||
<Body textAlign="center">{surprise.description}</Body>
|
|
||||||
<div className={styles.badge}>
|
|
||||||
<Caption>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: "Expires at the earliest" },
|
|
||||||
{
|
|
||||||
date: dt(earliestExpirationDate)
|
|
||||||
.locale(lang)
|
|
||||||
.format("D MMM YYYY"),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</Caption>
|
|
||||||
<Caption>
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: "Membership ID",
|
|
||||||
})}{" "}
|
|
||||||
{membershipNumber}
|
|
||||||
</Caption>
|
|
||||||
</div>
|
|
||||||
</Surprise>
|
|
||||||
</motion.div>
|
|
||||||
</AnimatePresence>
|
|
||||||
{surprises.length > 1 && (
|
|
||||||
<>
|
|
||||||
<nav className={styles.nav}>
|
|
||||||
<Button
|
|
||||||
variant="icon"
|
|
||||||
intent="tertiary"
|
|
||||||
disabled={selectedSurprise === 0}
|
|
||||||
onPress={() => showSurprise(-1)}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<ChevronRightSmallIcon
|
|
||||||
className={styles.chevron}
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
{intl.formatMessage({ id: "Previous" })}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="icon"
|
|
||||||
intent="tertiary"
|
|
||||||
disabled={selectedSurprise === surprises.length - 1}
|
|
||||||
onPress={() => showSurprise(1)}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: "Next" })}
|
|
||||||
<ChevronRightSmallIcon width={20} height={20} />
|
|
||||||
</Button>
|
|
||||||
</nav>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Surprise title={intl.formatMessage({ id: "Surprise!" })}>
|
|
||||||
<Body textAlign="center">
|
|
||||||
{surprises.length > 1 ? (
|
|
||||||
<>
|
|
||||||
{intl.formatMessage<React.ReactNode>(
|
|
||||||
{
|
|
||||||
id: "You have <b>#</b> gifts waiting for you!",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: surprises.length,
|
|
||||||
b: (str) => <b>{str}</b>,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
<br />
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: "Hurry up and use them before they expire!",
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
intl.formatMessage({
|
|
||||||
id: "We have a special gift waiting for you!",
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</Body>
|
|
||||||
<Caption>
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: "You'll find all your gifts in 'My benefits'",
|
|
||||||
})}
|
|
||||||
</Caption>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
intent="primary"
|
|
||||||
onPress={() => {
|
|
||||||
setShowSurprises(true)
|
|
||||||
}}
|
|
||||||
size="medium"
|
|
||||||
theme="base"
|
|
||||||
fullWidth
|
|
||||||
autoFocus
|
|
||||||
>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{
|
|
||||||
id: "Open gift(s)",
|
|
||||||
},
|
|
||||||
{ amount: surprises.length }
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Surprise>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</Dialog>
|
|
||||||
</Modal>
|
|
||||||
</ModalOverlay>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Surprise({
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
title?: string
|
|
||||||
children?: React.ReactNode
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className={styles.content}>
|
|
||||||
<Image
|
|
||||||
src="/_static/img/loyalty-award.png"
|
|
||||||
width={113}
|
|
||||||
height={125}
|
|
||||||
alt="Gift"
|
|
||||||
/>
|
|
||||||
<header>
|
|
||||||
<Title textAlign="center" level="h4">
|
|
||||||
{title}
|
|
||||||
</Title>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
13
components/MyPages/Surprises/confetti.ts
Normal file
13
components/MyPages/Surprises/confetti.ts
Normal file
@@ -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"],
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { env } from "@/env/server"
|
|||||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import SurprisesNotification from "./SurprisesNotification"
|
import SurprisesClient from "./Client"
|
||||||
|
|
||||||
export default async function Surprises() {
|
export default async function Surprises() {
|
||||||
if (env.HIDE_FOR_NEXT_RELEASE) {
|
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||||
@@ -22,7 +22,7 @@ export default async function Surprises() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SurprisesNotification
|
<SurprisesClient
|
||||||
surprises={surprises}
|
surprises={surprises}
|
||||||
membershipNumber={user.membership?.membershipNumber}
|
membershipNumber={user.membership?.membershipNumber}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@keyframes modal-fade {
|
@keyframes fade {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@@ -8,16 +8,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slide-up {
|
|
||||||
from {
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
height: var(--visual-viewport-height);
|
height: var(--visual-viewport-height);
|
||||||
@@ -28,10 +18,10 @@
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
&[data-entering] {
|
&[data-entering] {
|
||||||
animation: modal-fade 200ms ease-in;
|
animation: fade 400ms ease-in;
|
||||||
}
|
}
|
||||||
&[data-exiting] {
|
&[data-exiting] {
|
||||||
animation: modal-fade 200ms reverse ease-in;
|
animation: fade 400ms reverse ease-in;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,17 +30,19 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:before {
|
@media screen and (min-width: 768px) and (prefers-reduced-motion) {
|
||||||
background-image: url("/_static/img/confetti.svg");
|
.overlay:before {
|
||||||
background-repeat: no-repeat;
|
background-image: url("/_static/img/confetti.svg");
|
||||||
background-position: center 40%;
|
background-repeat: no-repeat;
|
||||||
content: "";
|
background-position: center 40%;
|
||||||
width: 100%;
|
content: "";
|
||||||
height: 100%;
|
width: 100%;
|
||||||
animation: modal-fade 200ms ease-in;
|
height: 100%;
|
||||||
display: block;
|
animation: fade 400ms ease-in;
|
||||||
}
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +54,14 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
z-index: 102;
|
||||||
|
}
|
||||||
|
|
||||||
&[data-entering] {
|
@media screen and (min-width: 768px) {
|
||||||
animation: slide-up 200ms;
|
.modal {
|
||||||
}
|
left: auto;
|
||||||
&[data-exiting] {
|
bottom: auto;
|
||||||
animation: slide-up 200ms reverse ease-in-out;
|
width: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +76,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.modal {
|
|
||||||
left: auto;
|
|
||||||
bottom: auto;
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
--button-height: 32px;
|
--button-height: 32px;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
@@ -160,3 +146,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
274
package-lock.json
generated
274
package-lock.json
generated
@@ -27,6 +27,7 @@
|
|||||||
"@trpc/client": "^11.0.0-rc.467",
|
"@trpc/client": "^11.0.0-rc.467",
|
||||||
"@trpc/react-query": "^11.0.0-rc.467",
|
"@trpc/react-query": "^11.0.0-rc.467",
|
||||||
"@trpc/server": "^11.0.0-rc.467",
|
"@trpc/server": "^11.0.0-rc.467",
|
||||||
|
"@tsparticles/confetti": "^3.5.0",
|
||||||
"@vercel/otel": "^1.9.1",
|
"@vercel/otel": "^1.9.1",
|
||||||
"@vis.gl/react-google-maps": "^1.2.0",
|
"@vis.gl/react-google-maps": "^1.2.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
@@ -6402,6 +6403,279 @@
|
|||||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/aria-query": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"@trpc/client": "^11.0.0-rc.467",
|
"@trpc/client": "^11.0.0-rc.467",
|
||||||
"@trpc/react-query": "^11.0.0-rc.467",
|
"@trpc/react-query": "^11.0.0-rc.467",
|
||||||
"@trpc/server": "^11.0.0-rc.467",
|
"@trpc/server": "^11.0.0-rc.467",
|
||||||
|
"@tsparticles/confetti": "^3.5.0",
|
||||||
"@vercel/otel": "^1.9.1",
|
"@vercel/otel": "^1.9.1",
|
||||||
"@vis.gl/react-google-maps": "^1.2.0",
|
"@vis.gl/react-google-maps": "^1.2.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
|||||||
@@ -9,3 +9,27 @@ export interface SurprisesProps {
|
|||||||
surprises: Surprise[]
|
surprises: Surprise[]
|
||||||
membershipNumber?: string
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user