mirror of
https://github.com/woodchen-ink/Q58Connect.git
synced 2025-07-18 14:01:55 +08:00
feat: Add admin route and navigation for admin users
This commit is contained in:
parent
a9a5d2d59a
commit
5c2085d238
113
src/app/(admin)/admin/page.tsx
Normal file
113
src/app/(admin)/admin/page.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
async function getStats() {
|
||||
const [userCount, clientCount] = await Promise.all([
|
||||
prisma.user.count(),
|
||||
prisma.client.count(),
|
||||
]);
|
||||
|
||||
const clients = await prisma.client.findMany({
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
username: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: 10,
|
||||
});
|
||||
|
||||
return {
|
||||
userCount,
|
||||
clientCount,
|
||||
recentClients: clients,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function AdminPage() {
|
||||
const user = await getCurrentUser();
|
||||
if (!user || user.role !== "ADMIN") {
|
||||
redirect("/dashboard");
|
||||
}
|
||||
|
||||
const stats = await getStats();
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>用户统计</CardTitle>
|
||||
<CardDescription>系统中的总用户数</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-3xl font-bold">{stats.userCount}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>应用统计</CardTitle>
|
||||
<CardDescription>系统中的总应用数</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-3xl font-bold">{stats.clientCount}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="mt-8">
|
||||
<CardHeader>
|
||||
<CardTitle>最近创建的应用</CardTitle>
|
||||
<CardDescription>显示最近创建的 10 个应用</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>应用名称</TableHead>
|
||||
<TableHead>创建者</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>Client ID</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{stats.recentClients.map((client) => (
|
||||
<TableRow key={client.id}>
|
||||
<TableCell className="font-medium">{client.name}</TableCell>
|
||||
<TableCell>{client.user.username}</TableCell>
|
||||
<TableCell>
|
||||
{new Date(client.createdAt).toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono">{client.clientId}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
26
src/app/(admin)/layout.tsx
Normal file
26
src/app/(admin)/layout.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { DashboardHeader } from "@/components/layout/dashboard-header";
|
||||
|
||||
export default async function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const user = await getCurrentUser();
|
||||
if (!user) {
|
||||
redirect("/sign-in");
|
||||
}
|
||||
|
||||
if (user.role !== "ADMIN") {
|
||||
redirect("/dashboard");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<DashboardHeader />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -10,7 +10,6 @@ export default async function AuthLayout({
|
||||
const user = await getCurrentUser();
|
||||
|
||||
if (user) {
|
||||
if (user.role === "ADMIN") redirect("/admin");
|
||||
redirect("/dashboard");
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,9 @@ export default async function DashboardLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const user = await getCurrentUser();
|
||||
if (!user) redirect("/sign-in");
|
||||
if (!user) {
|
||||
redirect("/sign-in");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
|
@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { signIn } from "@/actions/user-authorize";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
@ -17,7 +16,6 @@ export function UserAuthorize({
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<Error | unknown>(null);
|
||||
const { update } = useSession();
|
||||
const router = useRouter();
|
||||
|
||||
const signInCallback = useCallback(async () => {
|
||||
if (isLoading) {
|
||||
@ -25,22 +23,18 @@ export function UserAuthorize({
|
||||
}
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await signIn({ ...data, redirectTo: "/dashboard" });
|
||||
const result = await signIn({ ...data, redirectTo: "/dashboard" });
|
||||
// 更新 session
|
||||
await update();
|
||||
router.push("/dashboard");
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [data, isLoading, update, router]);
|
||||
}, [data, isLoading, update]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(signInCallback, 5);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
signInCallback();
|
||||
}, [signInCallback]);
|
||||
|
||||
return (
|
||||
@ -48,7 +42,7 @@ export function UserAuthorize({
|
||||
{error ? (
|
||||
<p className="text-center">登录异常,授权失败!</p>
|
||||
) : (
|
||||
<p className="text-center">账号信息验证中,准备跳转中,请稍等...</p>
|
||||
<p className="text-center">账号信息验证中,请稍等...</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -11,6 +11,7 @@ export function DashboardHeader() {
|
||||
if (pathname === "/dashboard") return "控制台";
|
||||
if (pathname === "/dashboard/clients") return "应用管理";
|
||||
if (pathname.includes("/dashboard/clients/")) return "应用编辑";
|
||||
if (pathname === "/admin") return "管理后台";
|
||||
return "";
|
||||
};
|
||||
|
||||
|
@ -89,6 +89,14 @@ export function NavBar() {
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/dashboard/clients">应用管理</Link>
|
||||
</DropdownMenuItem>
|
||||
{user.role === "ADMIN" && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/admin">管理后台</Link>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="text-red-600 dark:text-red-400"
|
||||
|
Loading…
x
Reference in New Issue
Block a user