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