diff --git a/biome.json b/biome.json index c96170e..87b3337 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.7/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -44,6 +44,11 @@ } } }, + "css": { + "parser": { + "tailwindDirectives": true + } + }, "files": { "includes": [ "client/**/*", diff --git a/client/package.json b/client/package.json index 1fdc78f..ff61367 100644 --- a/client/package.json +++ b/client/package.json @@ -32,16 +32,19 @@ "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", - "@semoss/sdk": "1.0.0-beta.31", + "@semoss/sdk": "1.0.0-beta.32", "@tailwindcss/postcss": "^4.1.17", + "@tailwindcss/vite": "^4.1.17", "autoprefixer": "^10.4.22", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.553.0", + "next-themes": "^0.4.6", "postcss": "^8.5.6", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.9.6", + "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.17", "tw-animate-css": "^1.4.0" @@ -52,6 +55,6 @@ "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^5.1.1", "typescript": "^5.9.3", - "vite": "^7.2.4" + "vite": "^7.2.6" } } diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 1d4505c..4416840 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -87,11 +87,14 @@ importers: specifier: ^1.2.8 version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@semoss/sdk': - specifier: 1.0.0-beta.31 - version: 1.0.0-beta.31(react@18.3.1) + specifier: 1.0.0-beta.32 + version: 1.0.0-beta.32(react@18.3.1) '@tailwindcss/postcss': specifier: ^4.1.17 version: 4.1.17 + '@tailwindcss/vite': + specifier: ^4.1.17 + version: 4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)) autoprefixer: specifier: ^10.4.22 version: 10.4.22(postcss@8.5.6) @@ -104,6 +107,9 @@ importers: lucide-react: specifier: ^0.553.0 version: 0.553.0(react@18.3.1) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) postcss: specifier: ^8.5.6 version: 8.5.6 @@ -116,6 +122,9 @@ importers: react-router-dom: specifier: ^7.9.6 version: 7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -137,13 +146,13 @@ importers: version: 18.3.7(@types/react@18.3.27) '@vitejs/plugin-react': specifier: ^5.1.1 - version: 5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)) + version: 5.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)) typescript: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) + specifier: ^7.2.6 + version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) packages: @@ -1180,8 +1189,8 @@ packages: cpu: [x64] os: [win32] - '@semoss/sdk@1.0.0-beta.31': - resolution: {integrity: sha512-JIo4TvJvYhkw1aa3DsQ1rEC4zJXF9Qp7ImcdyrRTZpTXC1mxzFB0tqUC4hCGSwrbXN79q20Qr+nxo2fyO7H2Rg==} + '@semoss/sdk@1.0.0-beta.32': + resolution: {integrity: sha512-645fPMDQZxwbmzwXHlJi/JhxHT/upMRQHpHd9wrm3cfgdfjvG5MDDdPGGUlhk5xFPKANUsjnTx8WNQv9QzqvVQ==} peerDependencies: react: 18.3.1 peerDependenciesMeta: @@ -1276,6 +1285,11 @@ packages: '@tailwindcss/postcss@4.1.17': resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} + '@tailwindcss/vite@4.1.17': + resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1327,8 +1341,8 @@ packages: peerDependencies: postcss: ^8.1.0 - baseline-browser-mapping@2.8.31: - resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} + baseline-browser-mapping@2.8.32: + resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} hasBin: true browserslist@4.28.0: @@ -1355,8 +1369,8 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} csstype@3.2.3: @@ -1378,8 +1392,8 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - electron-to-chromium@1.5.260: - resolution: {integrity: sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==} + electron-to-chromium@1.5.263: + resolution: {integrity: sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==} enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} @@ -1532,6 +1546,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -1572,8 +1592,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -1628,6 +1648,12 @@ packages: set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1703,8 +1729,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2141,7 +2167,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) optionalDependencies: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) @@ -2251,7 +2277,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) optionalDependencies: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) @@ -2314,7 +2340,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) optionalDependencies: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) @@ -2461,7 +2487,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.3.27)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) optionalDependencies: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) @@ -2725,7 +2751,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true - '@semoss/sdk@1.0.0-beta.31(react@18.3.1)': + '@semoss/sdk@1.0.0-beta.32(react@18.3.1)': optionalDependencies: react: 18.3.1 @@ -2798,6 +2824,13 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.17 + '@tailwindcss/vite@4.1.17(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0))': + dependencies: + '@tailwindcss/node': 4.1.17 + '@tailwindcss/oxide': 4.1.17 + tailwindcss: 4.1.17 + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 @@ -2836,7 +2869,7 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 - '@vitejs/plugin-react@5.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0))': + '@vitejs/plugin-react@5.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -2844,7 +2877,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0) transitivePeerDependencies: - supports-color @@ -2865,13 +2898,13 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - baseline-browser-mapping@2.8.31: {} + baseline-browser-mapping@2.8.32: {} browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.31 + baseline-browser-mapping: 2.8.32 caniuse-lite: 1.0.30001757 - electron-to-chromium: 1.5.260 + electron-to-chromium: 1.5.263 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) @@ -2891,7 +2924,7 @@ snapshots: convert-source-map@2.0.0: {} - cookie@1.0.2: {} + cookie@1.1.1: {} csstype@3.2.3: {} @@ -2903,7 +2936,7 @@ snapshots: detect-node-es@1.1.0: {} - electron-to-chromium@1.5.260: {} + electron-to-chromium@1.5.263: {} enhanced-resolve@5.18.3: dependencies: @@ -3033,6 +3066,11 @@ snapshots: nanoid@3.3.11: {} + next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + node-releases@2.0.27: {} normalize-range@0.1.2: {} @@ -3065,7 +3103,7 @@ snapshots: optionalDependencies: '@types/react': 18.3.27 - react-remove-scroll@2.7.1(@types/react@18.3.27)(react@18.3.1): + react-remove-scroll@2.7.2(@types/react@18.3.27)(react@18.3.1): dependencies: react: 18.3.1 react-remove-scroll-bar: 2.3.8(@types/react@18.3.27)(react@18.3.1) @@ -3084,7 +3122,7 @@ snapshots: react-router@7.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - cookie: 1.0.2 + cookie: 1.1.1 react: 18.3.1 set-cookie-parser: 2.7.2 optionalDependencies: @@ -3138,6 +3176,11 @@ snapshots: set-cookie-parser@2.7.2: {} + sonner@2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -3201,7 +3244,7 @@ snapshots: dependencies: react: 18.3.1 - vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0): + vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) diff --git a/client/src/App.tsx b/client/src/App.tsx index 8809b9f..b730c71 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,5 +1,6 @@ import { Env } from "@semoss/sdk"; import { InsightProvider } from "@semoss/sdk/react"; +import { Toaster } from "sonner"; import { AppContextProvider } from "./contexts"; import { Router } from "./pages"; @@ -26,6 +27,9 @@ export const App = () => { This component is custom to this project, and can be edited in Router.tsx */} + + {/* Toaster for displaying toast notifications */} + ); }; diff --git a/client/src/components/base/LoadingScreen.tsx b/client/src/components/base/LoadingScreen.tsx index 46b996a..aa7bbc1 100644 --- a/client/src/components/base/LoadingScreen.tsx +++ b/client/src/components/base/LoadingScreen.tsx @@ -1,12 +1,21 @@ import { Spinner } from "@/components/ui/spinner"; +interface LoadingScreenProps { + /** Whether to overlay the loading screen on top of existing content */ + overlay?: boolean; +} + /** * Returns a loading screen with a centered circular progress indicator * * @component */ -export const LoadingScreen = () => ( -
+export const LoadingScreen = ({ overlay = false }: LoadingScreenProps) => ( +
); diff --git a/client/src/components/base/MainNavigation.tsx b/client/src/components/base/MainNavigation.tsx index 6cacc8a..aa0e44d 100644 --- a/client/src/components/base/MainNavigation.tsx +++ b/client/src/components/base/MainNavigation.tsx @@ -1,5 +1,4 @@ import { useInsight } from "@semoss/sdk/react"; -import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { SemossBlueLogo } from "@/assets"; import { Button } from "@/components/ui/button"; @@ -25,11 +24,6 @@ export const MainNavigation = () => { const { isAuthorized } = useInsight(); // Read whether the user is authorized, so that buttons only work if they are const navigate = useNavigate(); - /** - * State - */ - const [userMenuOpen, setUserMenuOpen] = useState(false); - return (
@@ -77,12 +71,7 @@ export const MainNavigation = () => {
{/* If the user is logged in, allow them to see their info */} - {isAuthorized && ( - - )} + {isAuthorized && }
); diff --git a/client/src/components/base/MessageSnackbar.tsx b/client/src/components/base/MessageSnackbar.tsx deleted file mode 100644 index afffd87..0000000 --- a/client/src/components/base/MessageSnackbar.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { AlertCircle, AlertTriangle, CheckCircle, Info, X } from "lucide-react"; -import { useEffect } from "react"; -import { Alert } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { useAppContext } from "@/contexts"; - -export interface MessageSnackbarProps { - message: string; - severity: "success" | "error" | "info" | "warning"; - open: boolean; -} - -/** - * Renders a snackbar for displaying messages, typically used for error or success notifications - * - * @component - */ -export const MessageSnackbar = ({ - open, - severity, - message, -}: MessageSnackbarProps) => { - const { setMessageSnackbarProps } = useAppContext(); - - /** - * Functions - */ - const handleClose = () => { - setMessageSnackbarProps((prev) => ({ - ...prev, - open: false, - })); - }; - - // Auto close after 5 seconds - useEffect(() => { - if (open) { - const timer = setTimeout(() => { - setMessageSnackbarProps((prev) => ({ - ...prev, - open: false, - })); - }, 5000); - return () => clearTimeout(timer); - } - }, [open, setMessageSnackbarProps]); - - const getIcon = () => { - switch (severity) { - case "success": - return ; - case "error": - return ; - case "warning": - return ; - default: - return ; - } - }; - - const getVariant = () => { - switch (severity) { - case "error": - return "destructive" as const; - default: - return "default" as const; - } - }; - - const getColors = () => { - switch (severity) { - case "success": - return "border-green-500/50 text-green-600 bg-green-50 dark:border-green-500 [&>svg]:text-green-600"; - case "warning": - return "border-yellow-500/50 text-yellow-600 bg-yellow-50 dark:border-yellow-500 [&>svg]:text-yellow-600"; - case "info": - return "border-blue-500/50 text-blue-600 bg-blue-50 dark:border-blue-500 [&>svg]:text-blue-600"; - default: - return ""; - } - }; - - if (!open) return null; - - return ( -
- - {getIcon()} -
{message}
- -
-
- ); -}; diff --git a/client/src/components/base/UserProfileMenu.tsx b/client/src/components/base/UserProfileMenu.tsx index c21c5eb..6019857 100644 --- a/client/src/components/base/UserProfileMenu.tsx +++ b/client/src/components/base/UserProfileMenu.tsx @@ -9,42 +9,26 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useAppContext } from "@/contexts"; -import { useLoadingState } from "@/hooks"; - -export interface UserProfileMenuProps { - open: boolean; - onOpenChange?: (open: boolean) => void; -} /** * Renders a menu showing users their name and allowing them to log out * * @component */ -export const UserProfileMenu = ({ - open, - onOpenChange, -}: UserProfileMenuProps) => { +export const UserProfileMenu = () => { const { logout, userLoginName } = useAppContext(); - /** - * State - */ - const [isLogoutLoading, setIsLogoutLoading] = useLoadingState(); - /** * Functions */ const handleLogout = async () => { - const loadingKey = setIsLogoutLoading(true); const success = await logout(); if (success) localStorage.clear(); window.location.reload(); - setIsLogoutLoading(false, loadingKey); }; return ( - +