refactor(dashboard): extract RoleGuard and unify mobile nav with Button

This commit is contained in:
zetaloop
2026-02-25 15:18:20 +08:00
parent f8659b5ebc
commit 37d83d8805
5 changed files with 86 additions and 51 deletions
@@ -42,10 +42,6 @@ export default function ServicesPage() {
const scopedPlayerIdSet = new Set(scopedPlayerIds)
const scopedServices = services.filter((service) => scopedPlayerIdSet.has(service.playerId))
if (currentRole !== "player" && currentRole !== "owner") {
return <div className="text-sm text-muted-foreground"></div>
}
return (
<div className="container mx-auto max-w-6xl px-4 py-8 space-y-8">
<div className="flex items-center justify-between">
+2 -15
View File
@@ -1,16 +1,11 @@
"use client"
import Link from "next/link"
import { AuthGuard } from "@/components/auth-guard"
import { DashboardSidebar } from "@/components/dashboard-sidebar"
import { Header } from "@/components/header"
import { Button } from "@/components/ui/button"
import { useAuthStore } from "@/store/auth"
import { RoleGuard } from "@/components/role-guard"
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
const currentRole = useAuthStore((state) => state.currentRole)
return (
<div className="flex min-h-screen flex-col">
<Header />
@@ -20,15 +15,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
</div>
<main className="flex-1 p-6">
<AuthGuard>
{isAuthenticated && currentRole === "consumer" ? (
<div className="flex min-h-[50vh] items-center justify-center">
<Button asChild>
<Link href="/"></Link>
</Button>
</div>
) : (
children
)}
<RoleGuard>{children}</RoleGuard>
</AuthGuard>
</main>
</div>
+2 -2
View File
@@ -17,12 +17,12 @@ import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { useAuthStore } from "@/store/auth"
const playerLinks = [
export const playerLinks = [
{ href: "/dashboard", label: "概览", icon: LayoutDashboard },
{ href: "/dashboard/services", label: "服务管理", icon: ListOrdered },
]
const ownerLinks = [
export const ownerLinks = [
{ href: "/dashboard", label: "概览", icon: LayoutDashboard },
{ href: "/dashboard/services", label: "服务管理", icon: ListOrdered },
{ href: "/dashboard/shop", label: "店铺管理", icon: Store },
+46 -30
View File
@@ -33,6 +33,8 @@ import { useAuthStore } from "@/store/auth"
import { useNotificationStore } from "@/store/notifications"
import { useShopStore } from "@/store/shops"
import { canAccessDashboard } from "@/components/role-guard"
const roleLabels: Record<UserRole, string> = {
consumer: "客户",
player: "打手",
@@ -53,6 +55,7 @@ export function Header() {
? [
{ href: "/", label: "发现" },
{ href: "/community", label: "社区" },
{ href: "/orders", label: "订单" },
{ href: "/chat", label: "消息" },
]
: [
@@ -69,7 +72,9 @@ export function Header() {
const handleRoleSwitch = (role: UserRole) => {
switchRole(role)
router.push(role === "consumer" ? "/" : "/dashboard")
if (pathname.startsWith("/dashboard") && !canAccessDashboard(role, pathname)) {
router.push(role === "consumer" ? "/" : "/dashboard")
}
setMobileOpen(false)
}
@@ -247,7 +252,7 @@ export function Header() {
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-72">
<SheetContent side="right" className="w-72">
<SheetHeader>
<SheetTitle className="flex items-center gap-2">
<Gamepad2 className="h-5 w-5" />
@@ -269,38 +274,49 @@ export function Header() {
)}
<nav className="flex flex-col gap-1">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={() => setMobileOpen(false)}
className={cn(
"px-3 py-2 rounded-full text-sm font-medium transition-colors",
pathname === link.href ||
(link.href !== "/" && pathname.startsWith(link.href))
? "bg-primary/10 text-primary"
: "text-muted-foreground hover:text-foreground hover:bg-accent/50",
)}
>
{link.label}
</Link>
))}
{navLinks.map((link) => {
const isActive =
pathname === link.href ||
(link.href !== "/" && pathname.startsWith(link.href))
return (
<Button
key={link.href}
variant={isActive ? "secondary" : "ghost"}
className={cn("w-full justify-start", !isActive && "text-muted-foreground")}
asChild
>
<Link href={link.href} onClick={() => setMobileOpen(false)}>
{link.label}
</Link>
</Button>
)
})}
{isAuthenticated && (
<>
<Link
href="/wallet"
onClick={() => setMobileOpen(false)}
className="px-3 py-2 rounded-full text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-accent/50"
<Button
variant={pathname === "/wallet" ? "secondary" : "ghost"}
className={cn(
"w-full justify-start",
pathname !== "/wallet" && "text-muted-foreground",
)}
asChild
>
</Link>
<Link
href="/settings"
onClick={() => setMobileOpen(false)}
className="px-3 py-2 rounded-full text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-accent/50"
<Link href="/wallet" onClick={() => setMobileOpen(false)}>
</Link>
</Button>
<Button
variant={pathname === "/settings" ? "secondary" : "ghost"}
className={cn(
"w-full justify-start",
pathname !== "/settings" && "text-muted-foreground",
)}
asChild
>
</Link>
<Link href="/settings" onClick={() => setMobileOpen(false)}>
</Link>
</Button>
</>
)}
</nav>
+36
View File
@@ -0,0 +1,36 @@
"use client"
import { usePathname, useRouter } from "next/navigation"
import { useEffect } from "react"
import { ownerLinks, playerLinks } from "@/components/dashboard-sidebar"
import type { UserRole } from "@/lib/types"
import { useAuthStore } from "@/store/auth"
const dashboardRoutes: Record<string, readonly { href: string }[]> = {
player: playerLinks,
owner: ownerLinks,
}
export function canAccessDashboard(role: UserRole, pathname: string) {
const routes = dashboardRoutes[role]
if (!routes) return false
return routes.some((link) => pathname === link.href || pathname.startsWith(link.href + "/"))
}
export function RoleGuard({ children }: { children: React.ReactNode }) {
const currentRole = useAuthStore((state) => state.currentRole)
const pathname = usePathname()
const router = useRouter()
const allowed = canAccessDashboard(currentRole, pathname)
useEffect(() => {
if (!allowed) {
router.replace(currentRole === "consumer" ? "/" : "/dashboard")
}
}, [allowed, currentRole, pathname, router])
if (!allowed) return null
return <>{children}</>
}