Improve dashboard prefetching and analytics charts
All checks were successful
Build Frontend / build (push) Successful in 1m14s
All checks were successful
Build Frontend / build (push) Successful in 1m14s
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.
This commit is contained in:
@@ -33,9 +33,7 @@ function LoginLoading() {
|
|||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
// Removed prefetch to avoid middleware redirects when unauthenticated
|
||||||
router.prefetch("/dashboard");
|
|
||||||
}, [router]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center justify-center min-h-screen overflow-hidden">
|
<div className="relative flex items-center justify-center min-h-screen overflow-hidden">
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { Button } from "@/components/common/button";
|
|||||||
import { AlertCircle, RefreshCw } from "lucide-react";
|
import { AlertCircle, RefreshCw } from "lucide-react";
|
||||||
import { Skeleton } from "@/components/common/skeleton";
|
import { Skeleton } from "@/components/common/skeleton";
|
||||||
import { Card, CardContent, CardHeader } from "@/components/common/card";
|
import { Card, CardContent, CardHeader } from "@/components/common/card";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { sidebarConfig } from "@/config/sidebar";
|
||||||
|
|
||||||
// Error Boundary Component
|
// Error Boundary Component
|
||||||
interface ErrorBoundaryState {
|
interface ErrorBoundaryState {
|
||||||
@@ -247,6 +249,26 @@ function DashboardContentSkeleton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function DashboardContentWrapper({ children }: { children: ReactNode }) {
|
export default function DashboardContentWrapper({ children }: { children: ReactNode }) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 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 (
|
return (
|
||||||
<ErrorBoundary componentName="Dashboard Content">
|
<ErrorBoundary componentName="Dashboard Content">
|
||||||
<SuspenseWithTimeout
|
<SuspenseWithTimeout
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import {
|
|||||||
LineChart,
|
LineChart,
|
||||||
Line,
|
Line,
|
||||||
ComposedChart,
|
ComposedChart,
|
||||||
|
AreaChart,
|
||||||
|
Area,
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { formatGBP, formatNumber } from "@/lib/utils/format";
|
import { formatGBP, formatNumber } from "@/lib/utils/format";
|
||||||
import { PieChart, Pie, Cell, Legend } from "recharts";
|
import { PieChart, Pie, Cell, Legend } from "recharts";
|
||||||
@@ -376,34 +378,38 @@ export default function AdminAnalytics() {
|
|||||||
data.count === undefined);
|
data.count === undefined);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-background border border-border p-3 rounded-lg shadow-lg">
|
<div className="bg-[#050505]/90 p-4 rounded-xl shadow-xl border border-white/10 backdrop-blur-md ring-1 ring-white/5">
|
||||||
<p className="text-sm font-medium mb-2">
|
<p className="font-bold text-[10px] uppercase tracking-wide text-muted-foreground mb-3 border-b border-white/5 pb-2">
|
||||||
{data.formattedDate || label}
|
{data.formattedDate || label}
|
||||||
</p>
|
</p>
|
||||||
{isDualAxis ? (
|
{isDualAxis ? (
|
||||||
<div className="space-y-1">
|
<div className="space-y-2">
|
||||||
<p className="text-sm text-blue-600">
|
<div className="flex items-center justify-between gap-8">
|
||||||
Orders: <span className="font-semibold">{data.orders}</span>
|
<span className="text-[11px] font-semibold text-blue-400">Orders</span>
|
||||||
</p>
|
<span className="text-[11px] font-bold text-foreground tabular-nums">{data.orders}</span>
|
||||||
<p className="text-sm text-green-600">
|
</div>
|
||||||
Revenue:{" "}
|
<div className="flex items-center justify-between gap-8">
|
||||||
<span className="font-semibold">{formatGBP(data.revenue)}</span>
|
<span className="text-[11px] font-semibold text-emerald-400">Revenue</span>
|
||||||
</p>
|
<span className="text-[11px] font-bold text-foreground tabular-nums">{formatGBP(data.revenue)}</span>
|
||||||
|
</div>
|
||||||
{data.avgOrderValue !== undefined && data.avgOrderValue > 0 && (
|
{data.avgOrderValue !== undefined && data.avgOrderValue > 0 && (
|
||||||
<p className="text-sm text-purple-600">
|
<div className="flex items-center justify-between gap-8 pt-2 border-t border-white/5">
|
||||||
Avg Order Value:{" "}
|
<span className="text-[10px] font-medium text-purple-400">Avg Order Value</span>
|
||||||
<span className="font-semibold">
|
<span className="text-[10px] font-bold text-foreground tabular-nums">{formatGBP(data.avgOrderValue)}</span>
|
||||||
{formatGBP(data.avgOrderValue)}
|
</div>
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-primary">
|
<div className="flex items-center justify-between gap-8">
|
||||||
|
<span className={`text-[11px] font-semibold ${isAmount ? "text-emerald-400" : "text-blue-400"}`}>
|
||||||
|
{isAmount ? "Revenue" : "Count"}
|
||||||
|
</span>
|
||||||
|
<span className="text-[11px] font-bold text-foreground tabular-nums">
|
||||||
{isAmount
|
{isAmount
|
||||||
? formatGBP(data.value || data.amount || 0)
|
? formatGBP(data.value || data.amount || 0)
|
||||||
: `${data.value || data.count || 0}`}
|
: `${data.value || data.count || 0}`}
|
||||||
</p>
|
</span>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -449,7 +455,7 @@ export default function AdminAnalytics() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Calculate best month from daily data (for YTD, full year, or previous years)
|
// 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
|
// Show best month for YTD, full year view, or when viewing previous years
|
||||||
const showBestMonth =
|
const showBestMonth =
|
||||||
dateRange === "ytd" || dateRange === "year" || !isViewingCurrentYear;
|
dateRange === "ytd" || dateRange === "year" || !isViewingCurrentYear;
|
||||||
@@ -734,16 +740,30 @@ export default function AdminAnalytics() {
|
|||||||
analyticsData.orders.dailyOrders.length > 0 ? (
|
analyticsData.orders.dailyOrders.length > 0 ? (
|
||||||
<div className="mt-4 h-14 -mx-2">
|
<div className="mt-4 h-14 -mx-2">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<RechartsBarChart
|
<AreaChart
|
||||||
data={transformChartData(
|
data={transformChartData(
|
||||||
analyticsData.orders.dailyOrders,
|
analyticsData.orders.dailyOrders,
|
||||||
"count",
|
"count",
|
||||||
)}
|
)}
|
||||||
margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
|
margin={{ top: 5, right: 0, left: 0, bottom: 0 }}
|
||||||
>
|
>
|
||||||
<Bar dataKey="value" fill="#3b82f6" fillOpacity={0.8} radius={[2, 2, 0, 0]} />
|
<defs>
|
||||||
<Tooltip content={<CustomTooltip />} cursor={{ fill: 'transparent' }} />
|
<linearGradient id="colorOrdersAdmin" x1="0" y1="0" x2="0" y2="1">
|
||||||
</RechartsBarChart>
|
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
|
||||||
|
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="value"
|
||||||
|
stroke="#3b82f6"
|
||||||
|
fillOpacity={1}
|
||||||
|
fill="url(#colorOrdersAdmin)"
|
||||||
|
strokeWidth={2}
|
||||||
|
activeDot={{ r: 4, strokeWidth: 0, fill: "#3b82f6" }}
|
||||||
|
/>
|
||||||
|
<Tooltip content={<CustomTooltip />} cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} />
|
||||||
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -788,16 +808,30 @@ export default function AdminAnalytics() {
|
|||||||
analyticsData.revenue.dailyRevenue.length > 0 ? (
|
analyticsData.revenue.dailyRevenue.length > 0 ? (
|
||||||
<div className="mt-4 h-14 -mx-2">
|
<div className="mt-4 h-14 -mx-2">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<RechartsBarChart
|
<AreaChart
|
||||||
data={transformChartData(
|
data={transformChartData(
|
||||||
analyticsData.revenue.dailyRevenue,
|
analyticsData.revenue.dailyRevenue,
|
||||||
"amount",
|
"amount",
|
||||||
)}
|
)}
|
||||||
margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
|
margin={{ top: 5, right: 0, left: 0, bottom: 0 }}
|
||||||
>
|
>
|
||||||
<Bar dataKey="value" fill="#10b981" fillOpacity={0.8} radius={[2, 2, 0, 0]} />
|
<defs>
|
||||||
<Tooltip content={<CustomTooltip />} cursor={{ fill: 'transparent' }} />
|
<linearGradient id="colorRevenueAdmin" x1="0" y1="0" x2="0" y2="1">
|
||||||
</RechartsBarChart>
|
<stop offset="5%" stopColor="#10b981" stopOpacity={0.3} />
|
||||||
|
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="value"
|
||||||
|
stroke="#10b981"
|
||||||
|
fillOpacity={1}
|
||||||
|
fill="url(#colorRevenueAdmin)"
|
||||||
|
strokeWidth={2}
|
||||||
|
activeDot={{ r: 4, strokeWidth: 0, fill: "#10b981" }}
|
||||||
|
/>
|
||||||
|
<Tooltip content={<CustomTooltip />} cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} />
|
||||||
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -844,16 +878,30 @@ export default function AdminAnalytics() {
|
|||||||
analyticsData.vendors.dailyGrowth.length > 0 ? (
|
analyticsData.vendors.dailyGrowth.length > 0 ? (
|
||||||
<div className="mt-2 h-12 -mx-2">
|
<div className="mt-2 h-12 -mx-2">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<RechartsBarChart
|
<AreaChart
|
||||||
data={transformChartData(
|
data={transformChartData(
|
||||||
analyticsData.vendors.dailyGrowth,
|
analyticsData.vendors.dailyGrowth,
|
||||||
"count",
|
"count",
|
||||||
)}
|
)}
|
||||||
margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
|
margin={{ top: 5, right: 0, left: 0, bottom: 0 }}
|
||||||
>
|
>
|
||||||
<Bar dataKey="value" fill="#8b5cf6" fillOpacity={0.8} radius={[2, 2, 0, 0]} />
|
<defs>
|
||||||
<Tooltip content={<CustomTooltip />} cursor={{ fill: 'transparent' }} />
|
<linearGradient id="colorVendorsAdmin" x1="0" y1="0" x2="0" y2="1">
|
||||||
</RechartsBarChart>
|
<stop offset="5%" stopColor="#8b5cf6" stopOpacity={0.3} />
|
||||||
|
<stop offset="95%" stopColor="#8b5cf6" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="value"
|
||||||
|
stroke="#8b5cf6"
|
||||||
|
fillOpacity={1}
|
||||||
|
fill="url(#colorVendorsAdmin)"
|
||||||
|
strokeWidth={2}
|
||||||
|
activeDot={{ r: 4, strokeWidth: 0, fill: "#8b5cf6" }}
|
||||||
|
/>
|
||||||
|
<Tooltip content={<CustomTooltip />} cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} />
|
||||||
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -4,12 +4,19 @@ import type { NextRequest } from 'next/server';
|
|||||||
export function middleware(request: NextRequest) {
|
export function middleware(request: NextRequest) {
|
||||||
const { pathname } = request.nextUrl;
|
const { pathname } = request.nextUrl;
|
||||||
|
|
||||||
// Protect dashboard routes
|
|
||||||
if (pathname.startsWith('/dashboard')) {
|
if (pathname.startsWith('/dashboard')) {
|
||||||
const authToken = request.cookies.get('Authorization')?.value;
|
const authToken = request.cookies.get('Authorization')?.value;
|
||||||
|
|
||||||
|
const isPrefetch =
|
||||||
|
request.headers.get('Next-Router-Prefetch') === '1' ||
|
||||||
|
request.headers.get('purpose') === 'prefetch';
|
||||||
|
|
||||||
if (!authToken) {
|
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);
|
const loginUrl = new URL('/auth/login', request.url);
|
||||||
loginUrl.searchParams.set('redirectUrl', pathname);
|
loginUrl.searchParams.set('redirectUrl', pathname);
|
||||||
return NextResponse.redirect(loginUrl);
|
return NextResponse.redirect(loginUrl);
|
||||||
@@ -19,7 +26,6 @@ export function middleware(request: NextRequest) {
|
|||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// See "Matching Paths" below to learn more
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ['/dashboard/:path*'],
|
matcher: ['/dashboard/:path*'],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"commitHash": "a6e6cd0",
|
"commitHash": "4c15f43",
|
||||||
"buildTime": "2026-01-13T04:57:00.870Z"
|
"buildTime": "2026-01-13T05:27:43.269Z"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user