Compare commits

...

1 Commits

Author SHA1 Message Date
Jaayden Halko bb4d6a69c2 feat(site): use canvas-confetti for license success 2026-02-13 09:47:12 +00:00
3 changed files with 89 additions and 32 deletions
+2 -1
View File
@@ -74,6 +74,7 @@
"@xterm/xterm": "5.5.0",
"ansi-to-html": "0.7.2",
"axios": "1.13.2",
"canvas-confetti": "1.9.4",
"chroma-js": "2.6.0",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
@@ -94,7 +95,6 @@
"pretty-bytes": "6.1.1",
"react": "19.2.2",
"react-color": "2.19.3",
"react-confetti": "6.4.0",
"react-date-range": "1.4.0",
"react-dom": "19.2.2",
"react-markdown": "9.1.0",
@@ -135,6 +135,7 @@
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "14.3.1",
"@testing-library/user-event": "14.6.1",
"@types/canvas-confetti": "1.9.0",
"@types/chroma-js": "2.4.0",
"@types/color-convert": "2.0.4",
"@types/express": "4.17.17",
+16 -19
View File
@@ -136,6 +136,9 @@ importers:
axios:
specifier: 1.13.2
version: 1.13.2
canvas-confetti:
specifier: 1.9.4
version: 1.9.4
chroma-js:
specifier: 2.6.0
version: 2.6.0
@@ -196,9 +199,6 @@ importers:
react-color:
specifier: 2.19.3
version: 2.19.3(react@19.2.2)
react-confetti:
specifier: 6.4.0
version: 6.4.0(react@19.2.2)
react-date-range:
specifier: 1.4.0
version: 1.4.0(date-fns@2.30.0)(react@19.2.2)
@@ -314,6 +314,9 @@ importers:
'@testing-library/user-event':
specifier: 14.6.1
version: 14.6.1(@testing-library/dom@10.4.0)
'@types/canvas-confetti':
specifier: 1.9.0
version: 1.9.0
'@types/chroma-js':
specifier: 2.4.0
version: 2.4.0
@@ -2534,6 +2537,9 @@ packages:
'@types/body-parser@1.19.2':
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==, tarball: https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz}
'@types/canvas-confetti@1.9.0':
resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==, tarball: https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.9.0.tgz}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==, tarball: https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz}
@@ -3151,6 +3157,9 @@ packages:
caniuse-lite@1.0.30001757:
resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz}
canvas-confetti@1.9.4:
resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==, tarball: https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.4.tgz}
case-anything@2.1.13:
resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==, tarball: https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz}
engines: {node: '>=12.13'}
@@ -5331,12 +5340,6 @@ packages:
peerDependencies:
react: '*'
react-confetti@6.4.0:
resolution: {integrity: sha512-5MdGUcqxrTU26I2EU7ltkWPwxvucQTuqMm8dUz72z2YMqTD6s9vMcDUysk7n9jnC+lXuCPeJJ7Knf98VEYE9Rg==, tarball: https://registry.npmjs.org/react-confetti/-/react-confetti-6.4.0.tgz}
engines: {node: '>=16'}
peerDependencies:
react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0
react-date-range@1.4.0:
resolution: {integrity: sha512-+9t0HyClbCqw1IhYbpWecjsiaftCeRN5cdhsi9v06YdimwyMR2yYHWcgVn3URwtN/txhqKpEZB6UX1fHpvK76w==, tarball: https://registry.npmjs.org/react-date-range/-/react-date-range-1.4.0.tgz}
peerDependencies:
@@ -6022,9 +6025,6 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, tarball: https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz}
tween-functions@1.2.0:
resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==, tarball: https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz}
tweetnacl@0.14.5:
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==, tarball: https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz}
@@ -8565,6 +8565,8 @@ snapshots:
'@types/connect': 3.4.35
'@types/node': 20.19.25
'@types/canvas-confetti@1.9.0': {}
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
@@ -9246,6 +9248,8 @@ snapshots:
caniuse-lite@1.0.30001757: {}
canvas-confetti@1.9.4: {}
case-anything@2.1.13: {}
ccount@2.0.1: {}
@@ -11978,11 +11982,6 @@ snapshots:
reactcss: 1.2.3(react@19.2.2)
tinycolor2: 1.6.0
react-confetti@6.4.0(react@19.2.2):
dependencies:
react: 19.2.2
tween-functions: 1.2.0
react-date-range@1.4.0(date-fns@2.30.0)(react@19.2.2):
dependencies:
classnames: 2.3.2
@@ -12781,8 +12780,6 @@ snapshots:
tslib@2.8.1: {}
tween-functions@1.2.0: {}
tweetnacl@0.14.5: {}
type-check@0.4.0:
@@ -3,6 +3,7 @@ import MuiLink from "@mui/material/Link";
import Skeleton from "@mui/material/Skeleton";
import type { GetLicensesResponse } from "api/api";
import type { Feature, UserStatusChangeCount } from "api/typesGenerated";
import confetti from "canvas-confetti";
import { Button } from "components/Button/Button";
import {
SettingsHeader,
@@ -16,10 +17,8 @@ import {
TooltipContent,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { useWindowSize } from "hooks/useWindowSize";
import { PlusIcon, RotateCwIcon } from "lucide-react";
import type { FC } from "react";
import Confetti from "react-confetti";
import { type FC, useEffect } from "react";
import { Link } from "react-router";
import { AIGovernanceUsersConsumption } from "./AIGovernanceUsersConsumptionChart";
import { LicenseCard } from "./LicenseCard";
@@ -56,18 +55,78 @@ const LicensesSettingsPageView: FC<Props> = ({
aiGovernanceUserFeature,
}) => {
const theme = useTheme();
const { width, height } = useWindowSize();
useEffect(() => {
if (!showConfetti) {
return;
}
if (typeof window === "undefined") {
return;
}
if (
"matchMedia" in window &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches
) {
return;
}
const colors = [theme.palette.primary.main, theme.palette.secondary.main];
confetti({
particleCount: 150,
spread: 70,
origin: { y: 0.6 },
colors,
zIndex: 1000,
});
const durationMs = 2000;
const animationEnd = Date.now() + durationMs;
const defaults = {
startVelocity: 45,
spread: 60,
ticks: 80,
zIndex: 1000,
colors,
};
const randomInRange = (min: number, max: number) =>
Math.random() * (max - min) + min;
const interval = window.setInterval(() => {
const timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) {
window.clearInterval(interval);
return;
}
const particleCount = Math.ceil(30 * (timeLeft / durationMs));
confetti({
...defaults,
particleCount,
angle: 60,
origin: { x: randomInRange(0.1, 0.3), y: 0.85 },
});
confetti({
...defaults,
particleCount,
angle: 120,
origin: { x: randomInRange(0.7, 0.9), y: 0.85 },
});
}, 250);
return () => {
window.clearInterval(interval);
confetti.reset();
};
}, [showConfetti, theme.palette.primary.main, theme.palette.secondary.main]);
return (
<>
<Confetti
// For some reason this overflows the window and adds scrollbars if we don't subtract here.
width={width - 1}
height={height - 1}
numberOfPieces={showConfetti ? 200 : 0}
colors={[theme.palette.primary.main, theme.palette.secondary.main]}
/>
<Stack
alignItems="baseline"
direction="row"