create a comprehensive admin dashbioard with permalink /admin/ and creat

This commit is contained in:
Leon Serfaty G
2025-07-17 10:40:28 +00:00
parent d6e45f10e4
commit a1f3e2b3f5
5 changed files with 246 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
import {NextResponse} from 'next/server';
import type {NextRequest} from 'next/server';
export function middleware(request: NextRequest) {
const session = request.cookies.get('session');
// Redirect to login if trying to access /admin without a session
if (request.nextUrl.pathname.startsWith('/admin') && !session) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Redirect to admin if trying to access /login with a session
if (request.nextUrl.pathname === '/login' && session) {
return NextResponse.redirect(new URL('/admin', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/admin/:path*', '/login'],
};
+64
View File
@@ -0,0 +1,64 @@
import * as React from 'react';
import {
Sidebar,
SidebarContent,
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarInset,
SidebarTrigger,
SidebarFooter,
} from '@/components/ui/sidebar';
import { LayoutDashboard, LogOut } from 'lucide-react';
import { signOut } from '@/lib/auth';
import { Button } from '@/components/ui/button';
export default function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<div className="flex items-center gap-2">
<div className="bg-primary size-8 rounded-lg" />
<span className="text-lg font-semibold">Admin</span>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton href="/admin" isActive>
<LayoutDashboard />
Dashboard
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarContent>
<SidebarFooter>
<form action={signOut}>
<Button variant="ghost" className="w-full justify-start">
<LogOut className="mr-2" />
Sign Out
</Button>
</form>
</SidebarFooter>
</Sidebar>
<SidebarInset>
<header className="flex h-12 items-center justify-between border-b bg-background p-2 px-4">
<SidebarTrigger />
<h1 className="text-lg font-semibold">Dashboard</h1>
</header>
<main className="flex-1 p-4">{children}</main>
</SidebarInset>
</SidebarProvider>
);
}
+50
View File
@@ -0,0 +1,50 @@
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { DollarSign, Users, Activity, Clock } from "lucide-react";
export default function AdminDashboard() {
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Revenue</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">$45,231.89</div>
<p className="text-xs text-muted-foreground">+20.1% from last month</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">New Users</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+2350</div>
<p className="text-xs text-muted-foreground">+180.1% from last month</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Projects</CardTitle>
<Activity className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+573</div>
<p className="text-xs text-muted-foreground">+20 since last hour</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Average Estimate</CardTitle>
<Clock className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">128 hours</div>
<p className="text-xs text-muted-foreground">Based on recent activity</p>
</CardContent>
</Card>
</div>
);
}
+54
View File
@@ -0,0 +1,54 @@
import { LogIn } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { signIn, getSession } from '@/lib/auth';
import { redirect } from 'next/navigation';
export default async function LoginPage() {
const session = await getSession();
if (session) {
redirect('/admin');
}
return (
<div className="flex min-h-screen items-center justify-center bg-background">
<Card className="w-full max-w-sm">
<CardHeader className="text-center">
<CardTitle className="text-2xl">Admin Login</CardTitle>
<CardDescription>Enter your credentials to access the dashboard.</CardDescription>
</CardHeader>
<CardContent>
<form action={signIn} className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
name="email"
placeholder="admin@example.com"
required
defaultValue="admin@example.com"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
name="password"
required
defaultValue="password"
/>
</div>
<Button type="submit" className="w-full">
<LogIn className="mr-2 h-4 w-4" /> Sign in
</Button>
</form>
</CardContent>
</Card>
</div>
);
}
+55
View File
@@ -0,0 +1,55 @@
'use server';
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';
const FAKE_USER = {
email: 'admin@example.com',
password: 'password',
name: 'Admin User',
};
export async function signIn(formData: FormData) {
const email = formData.get('email');
const password = formData.get('password');
if (email === FAKE_USER.email && password === FAKE_USER.password) {
const sessionData = {
isLoggedIn: true,
email: FAKE_USER.email,
name: FAKE_USER.name,
};
cookies().set('session', JSON.stringify(sessionData), {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // One week
path: '/',
});
redirect('/admin');
}
// In a real app, you'd handle the error case, e.g., redirect to login with an error message.
// For simplicity, we'll just redirect back.
redirect('/login');
}
export async function signOut() {
cookies().delete('session');
redirect('/login');
}
export async function getSession() {
const sessionCookie = cookies().get('session');
if (!sessionCookie) {
return null;
}
try {
return JSON.parse(sessionCookie.value);
} catch (error) {
console.error('Failed to parse session cookie:', error);
return null;
}
}