From 54301510abf640183d07ab38b6691ee729649f1b Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Thu, 25 Jul 2024 18:04:06 +0200 Subject: [PATCH] feat: add confetti effect on create profile --- package-lock.json | 27 +++++ package.json | 1 + .../_hooks/useCreateProfile.tsx | 3 + src/app/(marketing)/create-profile/layout.tsx | 8 ++ src/v2/components/Confetti.tsx | 98 +++++++++++++++++++ 5 files changed, 137 insertions(+) create mode 100644 src/app/(marketing)/create-profile/layout.tsx create mode 100644 src/v2/components/Confetti.tsx diff --git a/package-lock.json b/package-lock.json index bcb5e80077..f242d5b22d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,6 +87,7 @@ "qrcode.react": "^3.1.0", "randombytes": "^2.1.0", "react": "^18.2.0", + "react-canvas-confetti": "^2.0.7", "react-device-detect": "^2.2.2", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", @@ -12687,6 +12688,11 @@ "@types/node": "*" } }, + "node_modules/@types/canvas-confetti": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.6.4.tgz", + "integrity": "sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==" + }, "node_modules/@types/color": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz", @@ -16944,6 +16950,15 @@ } ] }, + "node_modules/canvas-confetti": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.3.tgz", + "integrity": "sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -27808,6 +27823,18 @@ "react": ">=16.4.1" } }, + "node_modules/react-canvas-confetti": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/react-canvas-confetti/-/react-canvas-confetti-2.0.7.tgz", + "integrity": "sha512-DIj44O35TPAwJkUSIZqWdVsgAMHtVf8h7YNmnr3jF3bn5mG+d7Rh9gEcRmdJfYgRzh6K+MAGujwUoIqQyLnMJw==", + "dependencies": { + "@types/canvas-confetti": "^1.6.4", + "canvas-confetti": "^1.9.2" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-clientside-effect": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", diff --git a/package.json b/package.json index d60f4a5006..7317a323fb 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "qrcode.react": "^3.1.0", "randombytes": "^2.1.0", "react": "^18.2.0", + "react-canvas-confetti": "^2.0.7", "react-device-detect": "^2.2.2", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", diff --git a/src/app/(marketing)/create-profile/_hooks/useCreateProfile.tsx b/src/app/(marketing)/create-profile/_hooks/useCreateProfile.tsx index 49a7d5b576..17ee324aea 100644 --- a/src/app/(marketing)/create-profile/_hooks/useCreateProfile.tsx +++ b/src/app/(marketing)/create-profile/_hooks/useCreateProfile.tsx @@ -1,3 +1,4 @@ +import { useConfetti } from "@/components/Confetti" import { useToast } from "@/components/ui/hooks/useToast" import { SignedValidation, useSubmitWithSign } from "hooks/useSubmit" import { useRouter } from "next/navigation" @@ -8,6 +9,7 @@ import { profileSchema } from "../schemas" export const useCreateProfile = () => { const router = useRouter() const { toast } = useToast() + const { confetti } = useConfetti() const createProfile = async (signedValidation: SignedValidation) => fetcher(`/v2/profiles`, { @@ -21,6 +23,7 @@ export const useCreateProfile = () => { variant: "success", title: "Successfully created profile", }) + confetti.current?.() // router.push(`/profile/${response.username}`) }, onError: (response) => { diff --git a/src/app/(marketing)/create-profile/layout.tsx b/src/app/(marketing)/create-profile/layout.tsx new file mode 100644 index 0000000000..64efbb26a2 --- /dev/null +++ b/src/app/(marketing)/create-profile/layout.tsx @@ -0,0 +1,8 @@ +import { ConfettiProvider } from "@/components/Confetti" +import { PropsWithChildren } from "react" + +const Layout = ({ children }: PropsWithChildren) => { + return {children} +} + +export default Layout diff --git a/src/v2/components/Confetti.tsx b/src/v2/components/Confetti.tsx new file mode 100644 index 0000000000..9f62920306 --- /dev/null +++ b/src/v2/components/Confetti.tsx @@ -0,0 +1,98 @@ +"use client" + +import { + MutableRefObject, + PropsWithChildren, + createContext, + useContext, + useRef, +} from "react" +import ReactCanvasConfetti from "react-canvas-confetti/dist" +import { TCanvasConfettiInstance } from "react-canvas-confetti/dist/types" + +const doubleConfetti = (confetti: TCanvasConfettiInstance) => { + const count = 200 + const defaultsPerBarrage: confetti.Options[] = [ + { + origin: { x: -0.05 }, + angle: 60, + }, + { + origin: { x: 1.05 }, + angle: 120, + }, + ] as const + + const fire = (particleRatio: number, opts: confetti.Options) => { + confetti({ + ...opts, + particleCount: Math.floor(count * particleRatio), + }) + } + + for (const defaults of defaultsPerBarrage) { + fire(0.25, { + spread: 26, + startVelocity: 55, + ...defaults, + }) + fire(0.2, { + spread: 60, + ...defaults, + }) + fire(0.35, { + spread: 100, + decay: 0.91, + scalar: 0.8, + ...defaults, + }) + fire(0.1, { + spread: 120, + startVelocity: 25, + decay: 0.92, + scalar: 1.2, + ...defaults, + }) + fire(0.1, { + spread: 120, + startVelocity: 45, + ...defaults, + }) + } +} + +const ConfettiContext = createContext< + MutableRefObject +>({} as MutableRefObject) + +export const useConfetti = () => { + return { confetti: useContext(ConfettiContext) } +} + +export const ConfettiProvider = ({ children }: PropsWithChildren) => { + const confettiRef = useRef() + const onInitHandler = ({ confetti }: { confetti: TCanvasConfettiInstance }) => { + const confettiClosure = async (...args: Parameters) => { + if (args[0] === undefined) { + doubleConfetti(confetti) + return null + } + return confetti() + } + confettiClosure.reset = confetti.reset + confettiRef.current = confettiClosure + } + return ( + + {children} + + + ) +}