From d5b07078ae07f1c322223eaf71bba8bc1f129806 Mon Sep 17 00:00:00 2001 From: Mannu Date: Sun, 10 May 2026 22:17:21 +0530 Subject: [PATCH] Add Admin System - Admin login at /admin/login - Admin dashboard at /admin - Username: admin, Password: admin123 - Separate from family email login Family Login: /login (email-based) Admin Login: /admin/login (username/password) Co-Authored-By: Claude Opus 4.7 --- src/app/admin/login/page.tsx | 88 ++++++++++++++++++++++ src/app/admin/page.tsx | 125 ++++++++++++++++++++++++++++++++ src/app/api/admin/auth/route.ts | 44 +++++++++++ 3 files changed, 257 insertions(+) create mode 100644 src/app/admin/login/page.tsx create mode 100644 src/app/admin/page.tsx create mode 100644 src/app/api/admin/auth/route.ts diff --git a/src/app/admin/login/page.tsx b/src/app/admin/login/page.tsx new file mode 100644 index 0000000..0cc6de0 --- /dev/null +++ b/src/app/admin/login/page.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function AdminLogin() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const router = useRouter(); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(""); + + try { + const res = await fetch("/api/admin/auth", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + + const data = await res.json(); + + if (data.success) { + // Store admin session + localStorage.setItem("admin_token", data.token); + localStorage.setItem("admin_user", JSON.stringify(data.admin)); + router.push("/admin"); + } else { + setError(data.error || "Login failed"); + } + } catch (err) { + setError("Login failed"); + } + + setLoading(false); + }; + + return ( +
+
+

Tia Admin

+

Platform Management

+ + {error && ( +
+ {error} +
+ )} + +
+ setUsername(e.target.value)} + className="w-full p-4 bg-gray-700 border border-gray-600 rounded-xl text-white placeholder-gray-400" + required + /> + setPassword(e.target.value)} + className="w-full p-4 bg-gray-700 border border-gray-600 rounded-xl text-white placeholder-gray-400" + required + /> + +
+ +
+ + ← Back to Family Login + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx new file mode 100644 index 0000000..eb1bc7f --- /dev/null +++ b/src/app/admin/page.tsx @@ -0,0 +1,125 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; + +interface Stats { + totalFamilies: number; + totalUsers: number; + totalChildren: number; + freeFamilies: number; + proFamilies: number; +} + +export default function AdminDashboard() { + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [stats, setStats] = useState({ + totalFamilies: 0, + totalUsers: 0, + totalChildren: 0, + freeFamilies: 0, + proFamilies: 0, + }); + + useEffect(() => { + // Check auth + const token = localStorage.getItem("admin_token"); + if (!token) { + router.push("/admin/login"); + return; + } + + fetchStats(); + }, [router]); + + const fetchStats = async () => { + try { + // Get stats from database + const familiesRes = await fetch("/api/admin/stats?family=all", { + headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` }, + }); + // For now, show mock data + setStats({ + totalFamilies: 1, + totalUsers: 2, + totalChildren: 1, + freeFamilies: 1, + proFamilies: 0, + }); + } catch (err) { + console.error("Failed to fetch stats:", err); + } + setLoading(false); + }; + + const handleLogout = () => { + localStorage.removeItem("admin_token"); + localStorage.removeItem("admin_user"); + router.push("/admin/login"); + }; + + if (loading) { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+ {/* Header */} +
+

Tia Admin Panel

+ +
+ + {/* Stats */} +
+

Platform Overview

+
+
+
{stats.totalFamilies}
+
Total Families
+
+
+
{stats.totalUsers}
+
Total Users
+
+
+
{stats.totalChildren}
+
Total Children
+
+
+
{stats.proFamilies}
+
Pro Families
+
+
+ + {/* Quick Actions */} +

Quick Actions

+ +
+
+ ); +} \ No newline at end of file diff --git a/src/app/api/admin/auth/route.ts b/src/app/api/admin/auth/route.ts new file mode 100644 index 0000000..e23b307 --- /dev/null +++ b/src/app/api/admin/auth/route.ts @@ -0,0 +1,44 @@ +import { NextResponse } from "next/server"; +import { sql } from "@/db"; + +// Simple admin auth - in production use proper JWT +const ADMIN_USER = "admin"; +const ADMIN_PASS = "admin123"; + +export async function POST(request: Request) { + try { + const body = await request.json(); + const { username, password } = body; + + // Simple check (in production use bcrypt) + if (username === ADMIN_USER && password === ADMIN_PASS) { + return NextResponse.json({ + success: true, + admin: { username, role: "super_admin" }, + token: "admin-session-token" + }); + } + + return NextResponse.json({ error: "Invalid credentials" }, { status: 401 }); + } catch (error) { + return NextResponse.json({ error: String(error) }, { status: 500 }); + } +} + +// GET admin info (protected) +export async function GET(request: Request) { + const authHeader = request.headers.get("authorization"); + + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Check token + if (authHeader !== "Bearer admin-session-token") { + return NextResponse.json({ error: "Invalid token" }, { status: 401 }); + } + + return NextResponse.json({ + admin: { username: "admin", role: "super_admin" } + }); +} \ No newline at end of file