diff --git a/package-lock.json b/package-lock.json index f03f5a87..d098fa01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,10 +72,12 @@ "next": "15.5.6", "next-intl": "^4.3.12", "next-themes": "0.4.6", + "nextjs-toploader": "^3.9.17", "node-cache": "5.1.2", "node-fetch": "3.3.2", "nodemailer": "7.0.9", "npm": "^11.6.2", + "nprogress": "^0.2.0", "oslo": "1.2.1", "pg": "^8.16.2", "posthog-node": "^5.9.5", @@ -118,6 +120,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/node": "24.8.1", "@types/nodemailer": "7.0.2", + "@types/nprogress": "^0.2.3", "@types/pg": "8.15.5", "@types/react": "19.2.2", "@types/react-dom": "19.2.2", @@ -8691,6 +8694,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/nprogress": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.2.3.tgz", + "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/pg": { "version": "8.15.5", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz", @@ -15197,6 +15207,24 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/nextjs-toploader": { + "version": "3.9.17", + "resolved": "https://registry.npmjs.org/nextjs-toploader/-/nextjs-toploader-3.9.17.tgz", + "integrity": "sha512-9OF0KSSLtoSAuNg2LZ3aTl4hR9mBDj5L9s9DZiFCbMlXehyICGjkIz5dVGzuATU2bheJZoBdFgq9w07AKSuQQw==", + "license": "MIT", + "dependencies": { + "nprogress": "^0.2.0", + "prop-types": "^15.8.1" + }, + "funding": { + "url": "https://buymeacoffee.com/thesgj" + }, + "peerDependencies": { + "next": ">= 6.0.0", + "react": ">= 16.0.0", + "react-dom": ">= 16.0.0" + } + }, "node_modules/node-abi": { "version": "3.78.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", @@ -17727,6 +17755,12 @@ "inBundle": true, "license": "ISC" }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", diff --git a/package.json b/package.json index 44985a2f..029f5840 100644 --- a/package.json +++ b/package.json @@ -95,10 +95,12 @@ "next": "15.5.6", "next-intl": "^4.3.12", "next-themes": "0.4.6", + "nextjs-toploader": "^3.9.17", "node-cache": "5.1.2", "node-fetch": "3.3.2", "nodemailer": "7.0.9", "npm": "^11.6.2", + "nprogress": "^0.2.0", "oslo": "1.2.1", "pg": "^8.16.2", "posthog-node": "^5.9.5", @@ -141,6 +143,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/node": "24.8.1", "@types/nodemailer": "7.0.2", + "@types/nprogress": "^0.2.3", "@types/pg": "8.15.5", "@types/react": "19.2.2", "@types/react-dom": "19.2.2", diff --git a/src/app/globals.css b/src/app/globals.css index e643cfb6..1147e37e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -40,7 +40,7 @@ } .dark { - --background: oklch(0.20 0.006 285.885); + --background: oklch(0.2 0.006 285.885); --foreground: oklch(0.985 0 0); --card: oklch(0.21 0.006 285.885); --card-foreground: oklch(0.985 0 0); @@ -140,3 +140,7 @@ p { word-break: keep-all; white-space: normal; } + +#nprogress .bar { + background: var(--color-primary) !important; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5cd083b8..e498d7da 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -18,6 +18,7 @@ import { NextIntlClientProvider } from "next-intl"; import { getLocale } from "next-intl/server"; import { Toaster } from "@app/components/ui/toaster"; import { build } from "@server/build"; +import { TopLoader } from "@app/components/Toploader"; export const metadata: Metadata = { title: `Dashboard - ${process.env.BRANDING_APP_NAME || "Pangolin"}`, @@ -62,9 +63,9 @@ export default async function RootLayout({ if (build === "enterprise") { const licenseStatusRes = await cache( async () => - await priv.get>( - "/license/status" - ) + await priv.get>( + "/license/status" + ) )(); licenseStatus = licenseStatusRes.data.data; } else if (build === "saas") { @@ -84,6 +85,7 @@ export default async function RootLayout({ return ( + + + + + ); +} + +function FinishingLoader() { + const pathname = usePathname(); + const router = useRouter(); + const searchParams = useSearchParams(); + React.useEffect(() => { + NProgress.done(); + }, [pathname, router, searchParams]); + React.useEffect(() => { + const linkClickListener = (ev: MouseEvent) => { + const element = ev.target as HTMLElement; + const closestlink = element.closest("a"); + const isOpenToNewTabClick = + ev.ctrlKey || + ev.shiftKey || + ev.metaKey || // apple + (ev.button && ev.button == 1); // middle click, >IE9 + everyone else + + if (closestlink && isOpenToNewTabClick) { + NProgress.done(); + } + }; + window.addEventListener("click", linkClickListener); + return () => window.removeEventListener("click", linkClickListener); + }, []); + return null; +}