From a07ca55a1e0be1cf0ed3899521bffe51e702cac9 Mon Sep 17 00:00:00 2001 From: g Date: Tue, 13 Jan 2026 05:49:14 +0000 Subject: [PATCH] Improve dashboard prefetching and analytics charts Removed dashboard prefetching from the login page to avoid unnecessary middleware redirects for unauthenticated users. Added delayed prefetching of dashboard routes after initial load for better navigation performance. Updated AdminAnalytics to use AreaChart instead of BarChart for daily metrics, improving visual clarity. Enhanced middleware to allow prefetch requests through without redirecting to login, supporting better caching and navigation. --- app/auth/login/page.tsx | 4 +- app/dashboard/dashboard-content-wrapper.tsx | 48 +++++--- components/admin/AdminAnalytics.tsx | 122 ++++++++++++++------ middleware.ts | 12 +- public/git-info.json | 4 +- 5 files changed, 132 insertions(+), 58 deletions(-) diff --git a/app/auth/login/page.tsx b/app/auth/login/page.tsx index e6a9ade..db2c68a 100644 --- a/app/auth/login/page.tsx +++ b/app/auth/login/page.tsx @@ -33,9 +33,7 @@ function LoginLoading() { export default function LoginPage() { const router = useRouter(); - useEffect(() => { - router.prefetch("/dashboard"); - }, [router]); + // Removed prefetch to avoid middleware redirects when unauthenticated return (
diff --git a/app/dashboard/dashboard-content-wrapper.tsx b/app/dashboard/dashboard-content-wrapper.tsx index 7726d3e..f1b4f47 100644 --- a/app/dashboard/dashboard-content-wrapper.tsx +++ b/app/dashboard/dashboard-content-wrapper.tsx @@ -6,6 +6,8 @@ import { Button } from "@/components/common/button"; import { AlertCircle, RefreshCw } from "lucide-react"; import { Skeleton } from "@/components/common/skeleton"; import { Card, CardContent, CardHeader } from "@/components/common/card"; +import { useRouter } from "next/navigation"; +import { sidebarConfig } from "@/config/sidebar"; // Error Boundary Component interface ErrorBoundaryState { @@ -91,14 +93,14 @@ class ErrorBoundary extends Component { } // Suspense wrapper with timeout -function SuspenseWithTimeout({ - children, - fallback, +function SuspenseWithTimeout({ + children, + fallback, timeout = 5000, - timeoutFallback -}: { - children: ReactNode; - fallback: ReactNode; + timeoutFallback +}: { + children: ReactNode; + fallback: ReactNode; timeout?: number; timeoutFallback?: ReactNode; }) { @@ -157,7 +159,7 @@ function DashboardContentSkeletonWithWarning() { // Import the skeleton from the page function DashboardContentSkeleton() { return ( -
{/* Subtle loading indicator */}
-
{[...Array(4)].map((_, i) => ( -
{[...Array(5)].map((_, i) => ( -
{ + // Prefetch main dashboard routes for snappier navigation + const prefetchRoutes = async () => { + // Small delay to prioritize initial page load + await new Promise(resolve => setTimeout(resolve, 2000)); + + sidebarConfig.forEach(section => { + section.items.forEach(item => { + if (item.href && item.href !== "/dashboard") { + router.prefetch(item.href); + } + }); + }); + }; + + prefetchRoutes(); + }, [router]); + return ( - } timeout={5000} timeoutFallback={} diff --git a/components/admin/AdminAnalytics.tsx b/components/admin/AdminAnalytics.tsx index e48ba7e..89647b5 100644 --- a/components/admin/AdminAnalytics.tsx +++ b/components/admin/AdminAnalytics.tsx @@ -42,6 +42,8 @@ import { LineChart, Line, ComposedChart, + AreaChart, + Area, } from "recharts"; import { formatGBP, formatNumber } from "@/lib/utils/format"; import { PieChart, Pie, Cell, Legend } from "recharts"; @@ -376,34 +378,38 @@ export default function AdminAnalytics() { data.count === undefined); return ( -
-

+

+

{data.formattedDate || label}

{isDualAxis ? ( -
-

- Orders: {data.orders} -

-

- Revenue:{" "} - {formatGBP(data.revenue)} -

+
+
+ Orders + {data.orders} +
+
+ Revenue + {formatGBP(data.revenue)} +
{data.avgOrderValue !== undefined && data.avgOrderValue > 0 && ( -

- Avg Order Value:{" "} - - {formatGBP(data.avgOrderValue)} - -

+
+ Avg Order Value + {formatGBP(data.avgOrderValue)} +
)}
) : ( -

- {isAmount - ? formatGBP(data.value || data.amount || 0) - : `${data.value || data.count || 0}`} -

+
+ + {isAmount ? "Revenue" : "Count"} + + + {isAmount + ? formatGBP(data.value || data.amount || 0) + : `${data.value || data.count || 0}`} + +
)}
); @@ -449,7 +455,7 @@ export default function AdminAnalytics() { }; // Calculate best month from daily data (for YTD, full year, or previous years) - const calculateBestMonth = () => { + const calculateBestMonth = (): { month: string; revenue: number; orders: number } | null => { // Show best month for YTD, full year view, or when viewing previous years const showBestMonth = dateRange === "ytd" || dateRange === "year" || !isViewingCurrentYear; @@ -734,16 +740,30 @@ export default function AdminAnalytics() { analyticsData.orders.dailyOrders.length > 0 ? (
- - - } cursor={{ fill: 'transparent' }} /> - + + + + + + + + } cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} /> +
) : ( @@ -788,16 +808,30 @@ export default function AdminAnalytics() { analyticsData.revenue.dailyRevenue.length > 0 ? (
- - - } cursor={{ fill: 'transparent' }} /> - + + + + + + + + } cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} /> +
) : ( @@ -844,16 +878,30 @@ export default function AdminAnalytics() { analyticsData.vendors.dailyGrowth.length > 0 ? (
- - - } cursor={{ fill: 'transparent' }} /> - + + + + + + + + } cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} /> +
) : ( diff --git a/middleware.ts b/middleware.ts index 77e3cff..b54dfa4 100644 --- a/middleware.ts +++ b/middleware.ts @@ -4,12 +4,19 @@ import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const { pathname } = request.nextUrl; - // Protect dashboard routes if (pathname.startsWith('/dashboard')) { const authToken = request.cookies.get('Authorization')?.value; + const isPrefetch = + request.headers.get('Next-Router-Prefetch') === '1' || + request.headers.get('purpose') === 'prefetch'; + if (!authToken) { - // Redirect to login if no token is found + if (isPrefetch) { + // Let prefetch requests through so caching works + return NextResponse.next(); + } + const loginUrl = new URL('/auth/login', request.url); loginUrl.searchParams.set('redirectUrl', pathname); return NextResponse.redirect(loginUrl); @@ -19,7 +26,6 @@ export function middleware(request: NextRequest) { return NextResponse.next(); } -// See "Matching Paths" below to learn more export const config = { matcher: ['/dashboard/:path*'], }; diff --git a/public/git-info.json b/public/git-info.json index c6ce719..408c62e 100644 --- a/public/git-info.json +++ b/public/git-info.json @@ -1,4 +1,4 @@ { - "commitHash": "a6e6cd0", - "buildTime": "2026-01-13T04:57:00.870Z" + "commitHash": "4c15f43", + "buildTime": "2026-01-13T05:27:43.269Z" } \ No newline at end of file