-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add responsive admin dashboard with login
- Add protected admin login page with password authentication - Add secure admin dashboard with responsive stats cards - Implement admin route protection in middleware - Add admin link to navbar - Fix Suspense boundary for useSearchParams
- Loading branch information
1 parent
ed58b4b
commit 05a451c
Showing
7 changed files
with
283 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
"use client"; | ||
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | ||
import { useState } from "react"; | ||
|
||
interface DashboardStats { | ||
totalUsers: number; | ||
emailsCollected: number; | ||
blogPosts: number; | ||
totalViews: number; | ||
} | ||
|
||
export default function AdminDashboard() { | ||
const [stats] = useState<DashboardStats>({ | ||
totalUsers: 1234, | ||
emailsCollected: 567, | ||
blogPosts: 15, | ||
totalViews: 45678, | ||
}); | ||
|
||
return ( | ||
<div className="p-8"> | ||
<h1 className="text-3xl font-bold mb-8">Portfolio Dashboard</h1> | ||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> | ||
<Card> | ||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> | ||
<CardTitle className="text-sm font-medium">Total Users</CardTitle> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
strokeWidth="2" | ||
className="h-4 w-4 text-muted-foreground" | ||
> | ||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /> | ||
<circle cx="9" cy="7" r="4" /> | ||
<path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" /> | ||
</svg> | ||
</CardHeader> | ||
<CardContent> | ||
<div className="text-2xl font-bold">{stats.totalUsers}</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">Emails Collected</CardTitle> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
strokeWidth="2" | ||
className="h-4 w-4 text-muted-foreground" | ||
> | ||
<path d="M22 17H2a3 3 0 0 0 3-3V9a7 7 0 0 1 14 0v5a3 3 0 0 0 3 3Z" /> | ||
<path d="M22 17v1a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-1" /> | ||
</svg> | ||
</CardHeader> | ||
<CardContent> | ||
<div className="text-2xl font-bold">{stats.emailsCollected}</div> | ||
<p className="text-xs text-muted-foreground"> | ||
+10.5% 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">Blog Posts</CardTitle> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
strokeWidth="2" | ||
className="h-4 w-4 text-muted-foreground" | ||
> | ||
<path d="M21 15V6" /> | ||
<path d="M18.5 18a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" /> | ||
<path d="M12 12H3" /> | ||
<path d="M16 6H3" /> | ||
<path d="M12 18H3" /> | ||
</svg> | ||
</CardHeader> | ||
<CardContent> | ||
<div className="text-2xl font-bold">{stats.blogPosts}</div> | ||
<p className="text-xs text-muted-foreground"> | ||
2 new posts this 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">Total Views</CardTitle> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
strokeWidth="2" | ||
className="h-4 w-4 text-muted-foreground" | ||
> | ||
<path d="M22 12h-4l-3 9L9 3l-3 9H2" /> | ||
</svg> | ||
</CardHeader> | ||
<CardContent> | ||
<div className="text-2xl font-bold">{stats.totalViews}</div> | ||
<p className="text-xs text-muted-foreground"> | ||
+35.2% from last month | ||
</p> | ||
</CardContent> | ||
</Card> | ||
</div> | ||
|
||
<Card className="mb-8"> | ||
<CardHeader> | ||
<CardTitle>Analytics Overview</CardTitle> | ||
<p className="text-sm text-muted-foreground"> | ||
Your portfolio performance over the last 6 months | ||
</p> | ||
</CardHeader> | ||
<CardContent> | ||
<div className="h-[300px] w-full"> | ||
{/* Add your chart component here */} | ||
<p className="text-muted-foreground text-center pt-20"> | ||
Chart visualization will be added here | ||
</p> | ||
</div> | ||
</CardContent> | ||
</Card> | ||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>Calendar</CardTitle> | ||
<p className="text-sm text-muted-foreground"> | ||
Keep track of important dates | ||
</p> | ||
</CardHeader> | ||
<CardContent> | ||
{/* Add calendar component here */} | ||
</CardContent> | ||
</Card> | ||
|
||
<Card> | ||
<CardHeader> | ||
<CardTitle>Todo List</CardTitle> | ||
<p className="text-sm text-muted-foreground"> | ||
Manage your tasks | ||
</p> | ||
</CardHeader> | ||
<CardContent> | ||
{/* Add todo list component here */} | ||
</CardContent> | ||
</Card> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
"use client"; | ||
|
||
import { useState } from "react"; | ||
import { useRouter } from "next/navigation"; | ||
import { Button } from "@/components/ui/button"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | ||
|
||
export default function AdminLogin() { | ||
const [password, setPassword] = useState(""); | ||
const [error, setError] = useState(""); | ||
const router = useRouter(); | ||
|
||
const handleSubmit = async (e: React.FormEvent) => { | ||
e.preventDefault(); | ||
|
||
try { | ||
const response = await fetch("/api/admin/login", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ password }), | ||
}); | ||
|
||
if (response.ok) { | ||
router.push("/admin/dashboard"); | ||
} else { | ||
setError("Invalid password"); | ||
} | ||
} catch { | ||
setError("An error occurred"); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="flex min-h-screen items-center justify-center bg-gray-100"> | ||
<Card className="w-[400px]"> | ||
<CardHeader> | ||
<CardTitle className="text-2xl font-bold">Admin Login</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
<form onSubmit={handleSubmit} className="space-y-4"> | ||
<div className="space-y-2"> | ||
<Input | ||
type="password" | ||
placeholder="Enter admin password" | ||
value={password} | ||
onChange={(e) => setPassword(e.target.value)} | ||
className="w-full" | ||
/> | ||
{error && <p className="text-sm text-red-500">{error}</p>} | ||
</div> | ||
<Button type="submit" className="w-full"> | ||
Login | ||
</Button> | ||
</form> | ||
</CardContent> | ||
</Card> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { NextResponse } from "next/server"; | ||
import { cookies } from "next/headers"; | ||
|
||
export async function POST(request: Request) { | ||
const body = await request.json(); | ||
const { password } = body; | ||
|
||
// Compare with environment variable | ||
if (password === process.env.ADMIN_PASSWORD) { | ||
// Set an HTTP-only cookie for authentication | ||
cookies().set("admin_token", "authenticated", { | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === "production", | ||
sameSite: "strict", | ||
maxAge: 60 * 60 * 24, // 24 hours | ||
}); | ||
|
||
return NextResponse.json({ success: true }); | ||
} | ||
|
||
return NextResponse.json( | ||
{ error: "Invalid password" }, | ||
{ status: 401 } | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use client'; | ||
|
||
import { Suspense } from 'react'; | ||
|
||
export function SuspenseWrapper({ children }: { children: React.ReactNode }) { | ||
return <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters