feat(auth): add protected layout with role-based access control

This commit is contained in:
cfngc4594 2025-06-21 23:27:55 +08:00
parent 0695dd2f61
commit 19fac9b3d6
8 changed files with 54 additions and 58 deletions

View File

@ -1,5 +1,4 @@
import { notFound } from "next/navigation";
import { ProblemEditLayout } from "@/features/admin/ui/layouts/problem-edit-layout";
interface LayoutProps {
children: React.ReactNode;
@ -13,7 +12,7 @@ const Layout = async ({ children, params }: LayoutProps) => {
return notFound();
}
return <ProblemEditLayout>{children}</ProblemEditLayout>;
return <>{children}</>;
};
export default Layout;

View File

@ -8,6 +8,7 @@ import {
BarChart3,
Target,
Activity,
GraduationCapIcon,
} from "lucide-react";
import Link from "next/link";
import {
@ -197,21 +198,26 @@ export default async function DashboardPage() {
},
],
actions: [
{
label: "管理员管理",
href: "/dashboard/management",
icon: Target,
},
{
label: "用户管理",
href: "/dashboard/usermanagement/guest",
icon: Users,
},
{
label: "教师管理",
href: "/dashboard/usermanagement/teacher",
icon: GraduationCapIcon,
},
{
label: "题目管理",
href: "/dashboard/usermanagement/problem",
icon: BookOpen,
},
{
label: "管理员设置",
href: "/dashboard/management",
icon: Target,
},
],
};
case "TEACHER":
@ -240,7 +246,7 @@ export default async function DashboardPage() {
],
actions: [
{
label: "学生管理",
label: "用户管理",
href: "/dashboard/usermanagement/guest",
icon: Users,
},
@ -250,7 +256,7 @@ export default async function DashboardPage() {
icon: BookOpen,
},
{
label: "统计分析",
label: "完成情况",
href: "/dashboard/teacher/dashboard",
icon: BarChart3,
},
@ -281,12 +287,12 @@ export default async function DashboardPage() {
},
],
actions: [
{ label: "开始做题", href: "/problemset", icon: BookOpen },
{
label: "我的进度",
href: "/dashboard/student/dashboard",
icon: TrendingUp,
},
{ label: "开始做题", href: "/problemset", icon: BookOpen },
{ label: "个人设置", href: "/dashboard/management", icon: Target },
],
};
@ -361,7 +367,7 @@ export default async function DashboardPage() {
<CardDescription>访</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4 md:grid-cols-3">
<div className="grid gap-4 md:grid-cols-4">
{config.actions.map((action, index) => (
<Link key={index} href={action.href}>
<Button variant="outline" className="w-full justify-start">

View File

@ -0,0 +1,15 @@
import { ProtectedLayout } from "@/features/dashboard/layouts/protected-layout";
interface LayoutProps {
children: React.ReactNode;
}
const Layout = ({ children }: LayoutProps) => {
return (
<ProtectedLayout roles={["ADMIN", "TEACHER", "GUEST"]}>
{children}
</ProtectedLayout>
);
};
export default Layout;

View File

@ -33,8 +33,8 @@ const adminData = {
},
],
navSecondary: [
{ title: "帮助", url: "/", icon: LifeBuoy },
{ title: "反馈", url: siteConfig.url.repo.github, icon: Send },
{ title: "帮助", url: `${siteConfig.url.repo.github}/issues`, icon: LifeBuoy },
{ title: "反馈", url: `${siteConfig.url.repo.github}/pulls`, icon: Send },
],
};

View File

@ -16,24 +16,28 @@ import { NavMain } from "@/components/nav-main";
import { NavUser } from "@/components/nav-user";
import { NavProjects } from "@/components/nav-projects";
import { NavSecondary } from "@/components/nav-secondary";
import { Command, LifeBuoy, Send, SquareTerminal } from "lucide-react";
import { Command, LifeBuoy, Send, Shield } from "lucide-react";
const data = {
navMain: [
{
title: "页面",
url: "#",
icon: SquareTerminal,
title: "管理面板",
url: "/dashboard",
icon: Shield,
isActive: true,
items: [
{
title: "主页",
title: "我的进度",
url: "/dashboard/student/dashboard",
},
{
title: "目集",
title: "开始做题",
url: "/problemset",
},
{
title: "个人设置",
url: "/dashboard/management",
},
],
},
@ -71,12 +75,12 @@ const data = {
navSecondary: [
{
title: "帮助",
url: "/",
url: `${siteConfig.url.repo.github}/issues`,
icon: LifeBuoy,
},
{
title: "反馈",
url: siteConfig.url.repo.github,
url: `${siteConfig.url.repo.github}/pulls`,
icon: Send,
},
],

View File

@ -1,12 +1,6 @@
"use client";
import {
Command,
LifeBuoy,
PieChart,
Send,
SquareTerminal,
} from "lucide-react";
import { Command, LifeBuoy, Send, Shield } from "lucide-react";
import * as React from "react";
import {
Sidebar,
@ -26,9 +20,9 @@ import { NavSecondary } from "@/components/nav-secondary";
const data = {
navMain: [
{
title: "教师管理",
url: "#",
icon: SquareTerminal,
title: "管理面板",
url: "/dashboard",
icon: Shield,
isActive: true,
items: [
{
@ -36,47 +30,25 @@ const data = {
url: "/dashboard/usermanagement/guest",
},
{
title: "题管理",
title: "题管理",
url: "/dashboard/usermanagement/problem",
},
],
},
{
title: "统计分析",
url: "#",
icon: PieChart,
items: [
{
title: "完成情况",
url: "/dashboard/teacher/dashboard",
},
// {
// title: "错题统计",
// url: "/dashboard/teacher/dashboard",
// },
],
},
// {
// title: "设置",
// url: "#",
// icon: Settings2,
// items: [
// {
// title: "语言",
// url: "#",
// },
// ],
// },
],
navSecondary: [
{
title: "帮助",
url: "/",
url: `${siteConfig.url.repo.github}/issues`,
icon: LifeBuoy,
},
{
title: "反馈",
url: siteConfig.url.repo.github,
url: `${siteConfig.url.repo.github}/pulls`,
icon: Send,
},
],

View File

@ -25,7 +25,7 @@ export const ProtectedLayout = async ({
});
if (!user || !roles.includes(user.role)) {
redirect("unauthorized");
redirect("/unauthorized");
}
return <>{children}</>;

View File

@ -251,7 +251,7 @@ export function UserTable(props: UserTableProps) {
if (isProblem) {
// 如果是problem类型跳转到编辑路由使用displayId
const problem = item as Problem;
router.push(`/admin/problems/${problem.displayId}/edit`);
router.push(`/dashboard/admin/problems/${problem.id}/edit`);
} else {
// 如果是用户类型,打开编辑弹窗
setEditingUser(item);