create a comprehensive admin dashbioard with permalink /admin/ and creat
This commit is contained in:
@@ -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'],
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user