diff --git a/spectre-frontend/package.json b/spectre-frontend/package.json index 549c148..0f13df8 100644 --- a/spectre-frontend/package.json +++ b/spectre-frontend/package.json @@ -21,6 +21,7 @@ "framer-motion": "^12.23.9", "i18next": "^25.7.3", "idb": "^8.0.3", + "nprogress": "^0.2.0", "react": "~19.1.2", "react-dom": "~19.1.2", "react-hook-form": "^7.61.1", @@ -40,6 +41,7 @@ "@parcel/watcher": "^2.5.1", "@swc-contrib/plugin-graphql-codegen-client-preset": "^0.12.1", "@tailwindcss/vite": "^4.1.11", + "@types/nprogress": "^0.2.3", "@types/react": "~19.1.17", "@types/react-dom": "~19.1.11", "@vitejs/plugin-react-swc": "~4.2.0", diff --git a/spectre-frontend/pnpm-lock.yaml b/spectre-frontend/pnpm-lock.yaml index f3443d6..eddaaf5 100644 --- a/spectre-frontend/pnpm-lock.yaml +++ b/spectre-frontend/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: idb: specifier: ^8.0.3 version: 8.0.3 + nprogress: + specifier: ^0.2.0 + version: 0.2.0 react: specifier: ~19.1.2 version: 19.1.2 @@ -87,6 +90,9 @@ importers: '@tailwindcss/vite': specifier: ^4.1.11 version: 4.1.11(vite@7.2.7(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)(yaml@2.8.0)) + '@types/nprogress': + specifier: ^0.2.3 + version: 0.2.3 '@types/react': specifier: ~19.1.17 version: 19.1.17 @@ -2419,6 +2425,9 @@ packages: '@types/node@24.1.0': resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} + '@types/nprogress@0.2.3': + resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} + '@types/react-dom@19.1.11': resolution: {integrity: sha512-3BKc/yGdNTYQVVw4idqHtSOcFsgGuBbMveKCOgF8wQ5QtrYOc3jDIlzg3jef04zcXFIHLelyGlj0T+BJ8+KN+w==} peerDependencies: @@ -3629,6 +3638,9 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -7616,6 +7628,8 @@ snapshots: dependencies: undici-types: 7.8.0 + '@types/nprogress@0.2.3': {} + '@types/react-dom@19.1.11(@types/react@19.1.17)': dependencies: '@types/react': 19.1.17 @@ -8818,6 +8832,8 @@ snapshots: dependencies: path-key: 3.1.1 + nprogress@0.2.0: {} + nullthrows@1.1.1: {} object-assign@4.1.1: {} diff --git a/spectre-frontend/src/pages/layout.tsx b/spectre-frontend/src/pages/layout.tsx index f0f2d42..887a6a1 100644 --- a/spectre-frontend/src/pages/layout.tsx +++ b/spectre-frontend/src/pages/layout.tsx @@ -1,9 +1,17 @@ import { BreadcrumbItem, Breadcrumbs } from '@heroui/react' -import { Outlet, type UIMatch, useMatches, useNavigate } from 'react-router' -import React, { useCallback } from 'react' +import { + Outlet, + type UIMatch, + useMatches, + useNavigate, + useNavigation, +} from 'react-router' +import React, { useCallback, useEffect } from 'react' import type { RootState } from '@/store' import { useSelector } from 'react-redux' import Menu from '@/pages/Menu.tsx' +import nProgress from 'nprogress' +import 'nprogress/nprogress.css' type RouteHandle = { crumb: string @@ -11,6 +19,10 @@ type RouteHandle = { hideCrumb: boolean } +nProgress.configure({ + showSpinner: false, +}) + const NavBreadcrumbs: React.FC = () => { const matches = useMatches() as UIMatch[] const additionalCrumbs = useSelector( @@ -55,6 +67,15 @@ const NavBreadcrumbs: React.FC = () => { } const Layout: React.FC = () => { + const navigation = useNavigation() + + useEffect(() => { + if (navigation.state === 'loading') { + nProgress.start() + } else { + nProgress.done() + } + }, [navigation.state]) return (