Removed redundant client-side redirect logic from DashboardContent since server-side middleware now handles route protection (Phase 1 complete). Changes: - Removed redirect to login page from useEffect (now handled by middleware) - Renamed checkAuth to fetchUser (more accurate purpose) - Removed locale and router from useEffect dependencies (no longer needed) - Kept loading state and user fetching for display purposes - Component now trusts middleware protection and focuses on data display The component still: - Fetches user data for display (email, etc.) - Shows loading state during fetch - Handles logout functionality - Maintains existing UI/UX Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
191 lines
6.4 KiB
TypeScript
191 lines
6.4 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Loader2, BarChart2, Users, Eye, Clock, TrendingUp, TrendingDown, LogOut } from 'lucide-react';
|
|
import { useTranslations, useLocale } from 'next-intl';
|
|
import { createClient } from '@/lib/supabase/client';
|
|
import type { User } from '@supabase/supabase-js';
|
|
|
|
interface MetricCard {
|
|
title: string;
|
|
value: string;
|
|
subtitle: string;
|
|
trend?: 'up' | 'down';
|
|
trendValue?: string;
|
|
icon: React.ReactNode;
|
|
}
|
|
|
|
export default function DashboardContent() {
|
|
const t = useTranslations('dashboard');
|
|
const locale = useLocale();
|
|
const router = useRouter();
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const fetchUser = async () => {
|
|
const supabase = createClient();
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
|
|
setUser(user);
|
|
setLoading(false);
|
|
};
|
|
|
|
fetchUser();
|
|
}, []);
|
|
|
|
const handleLogout = async () => {
|
|
const supabase = createClient();
|
|
await supabase.auth.signOut();
|
|
router.push(`/${locale}`);
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="flex flex-col items-center space-y-4">
|
|
<Loader2 className="h-8 w-8 text-zinc-400 animate-spin" />
|
|
<p className="text-zinc-400 text-sm">{t('status.loading')}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!user) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<p className="text-zinc-400">{t('status.notAuthenticated')}</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const metrics: MetricCard[] = [
|
|
{
|
|
title: t('metrics.pageViews.title'),
|
|
value: '12,543',
|
|
subtitle: '45 ' + t('metrics.pageViews.perMinute'),
|
|
trend: 'up',
|
|
trendValue: '+12.5%',
|
|
icon: <Eye className="h-5 w-5" />,
|
|
},
|
|
{
|
|
title: t('metrics.uniqueVisitors.title'),
|
|
value: '3,721',
|
|
subtitle: '127 ' + t('metrics.uniqueVisitors.currentlyActive'),
|
|
trend: 'up',
|
|
trendValue: '+8.2%',
|
|
icon: <Users className="h-5 w-5" />,
|
|
},
|
|
{
|
|
title: t('metrics.conversions.title'),
|
|
value: '156',
|
|
subtitle: '4.2% ' + t('metrics.conversions.rate'),
|
|
trend: 'down',
|
|
trendValue: '-2.1%',
|
|
icon: <BarChart2 className="h-5 w-5" />,
|
|
},
|
|
{
|
|
title: t('metrics.timeOnSite.title'),
|
|
value: '4:32',
|
|
subtitle: '32% ' + t('metrics.timeOnSite.bounceRate'),
|
|
trend: 'up',
|
|
trendValue: '+15.3%',
|
|
icon: <Clock className="h-5 w-5" />,
|
|
},
|
|
];
|
|
|
|
const topPages = [
|
|
{ path: '/', views: 4521, change: '+12%' },
|
|
{ path: '/portfolio', views: 2341, change: '+8%' },
|
|
{ path: '/about', views: 1823, change: '+5%' },
|
|
{ path: '/blog', views: 1456, change: '-2%' },
|
|
{ path: '/contact', views: 892, change: '+3%' },
|
|
];
|
|
|
|
return (
|
|
<main className="min-h-screen py-16 sm:py-20 lg:py-24">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-12">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-white">{t('header.title')}</h1>
|
|
<p className="text-zinc-400 mt-2">
|
|
127 {t('header.activeUsers')}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleLogout}
|
|
className="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 rounded-lg text-zinc-300 transition-colors"
|
|
>
|
|
<LogOut className="h-4 w-4" />
|
|
Logout
|
|
</button>
|
|
</div>
|
|
|
|
{/* Metrics Grid */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
|
{metrics.map((metric, index) => (
|
|
<div
|
|
key={index}
|
|
className="bg-zinc-900/50 backdrop-blur-sm rounded-xl p-6 ring-1 ring-zinc-800"
|
|
>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="p-2 bg-zinc-800 rounded-lg text-zinc-400">
|
|
{metric.icon}
|
|
</div>
|
|
{metric.trend && (
|
|
<div className={`flex items-center gap-1 text-sm ${
|
|
metric.trend === 'up' ? 'text-green-500' : 'text-red-500'
|
|
}`}>
|
|
{metric.trend === 'up' ? (
|
|
<TrendingUp className="h-4 w-4" />
|
|
) : (
|
|
<TrendingDown className="h-4 w-4" />
|
|
)}
|
|
{metric.trendValue}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<h3 className="text-sm text-zinc-400 mb-1">{metric.title}</h3>
|
|
<p className="text-2xl font-bold text-white">{metric.value}</p>
|
|
<p className="text-sm text-zinc-500 mt-1">{metric.subtitle}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Top Pages */}
|
|
<div className="bg-zinc-900/50 backdrop-blur-sm rounded-xl ring-1 ring-zinc-800">
|
|
<div className="p-6 border-b border-zinc-800">
|
|
<h2 className="text-xl font-semibold text-white">{t('topPages.title')}</h2>
|
|
</div>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="text-left text-sm text-zinc-500 border-b border-zinc-800">
|
|
<th className="px-6 py-4 font-medium">{t('topPages.columns.path')}</th>
|
|
<th className="px-6 py-4 font-medium">{t('topPages.columns.views')}</th>
|
|
<th className="px-6 py-4 font-medium">{t('topPages.columns.change')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{topPages.map((page, index) => (
|
|
<tr key={index} className="border-b border-zinc-800/50 last:border-0">
|
|
<td className="px-6 py-4 text-white font-mono text-sm">{page.path}</td>
|
|
<td className="px-6 py-4 text-zinc-300">{page.views.toLocaleString()}</td>
|
|
<td className={`px-6 py-4 ${
|
|
page.change.startsWith('+') ? 'text-green-500' : 'text-red-500'
|
|
}`}>
|
|
{page.change}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|