feat: add auth guards to protected routes and extend requireAuth coverage
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { AccountSidebar } from "@/components/account-sidebar"
|
import { AccountSidebar } from "@/components/account-sidebar"
|
||||||
|
import { AuthGuard } from "@/components/auth-guard"
|
||||||
import { Header } from "@/components/header"
|
import { Header } from "@/components/header"
|
||||||
|
|
||||||
export default function AccountLayout({ children }: { children: React.ReactNode }) {
|
export default function AccountLayout({ children }: { children: React.ReactNode }) {
|
||||||
@@ -9,7 +10,9 @@ export default function AccountLayout({ children }: { children: React.ReactNode
|
|||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<AccountSidebar />
|
<AccountSidebar />
|
||||||
</div>
|
</div>
|
||||||
<main className="flex-1 p-6">{children}</main>
|
<main className="flex-1 p-6">
|
||||||
|
<AuthGuard>{children}</AuthGuard>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AuthGuard } from "@/components/auth-guard"
|
||||||
import { DashboardSidebar } from "@/components/dashboard-sidebar"
|
import { DashboardSidebar } from "@/components/dashboard-sidebar"
|
||||||
import { Header } from "@/components/header"
|
import { Header } from "@/components/header"
|
||||||
|
|
||||||
@@ -9,7 +10,9 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
|||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<DashboardSidebar />
|
<DashboardSidebar />
|
||||||
</div>
|
</div>
|
||||||
<main className="flex-1 p-6">{children}</main>
|
<main className="flex-1 p-6">
|
||||||
|
<AuthGuard>{children}</AuthGuard>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
|
import { useRequireAuth } from "@/lib/use-require-auth"
|
||||||
|
|
||||||
const postSchema = z.object({
|
const postSchema = z.object({
|
||||||
title: z.string().min(2, "标题至少2个字符").max(50, "标题最多50个字符"),
|
title: z.string().min(2, "标题至少2个字符").max(50, "标题最多50个字符"),
|
||||||
@@ -30,6 +31,7 @@ const tagOptions = ["英雄联盟", "王者荣耀", "CS2", "原神", "上分", "
|
|||||||
|
|
||||||
export default function NewPostPage() {
|
export default function NewPostPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { isAuthenticated, requireAuth } = useRequireAuth()
|
||||||
const [postType, setPostType] = useState("normal")
|
const [postType, setPostType] = useState("normal")
|
||||||
const [selectedTags, setSelectedTags] = useState<string[]>([])
|
const [selectedTags, setSelectedTags] = useState<string[]>([])
|
||||||
const [imageCount, setImageCount] = useState(0)
|
const [imageCount, setImageCount] = useState(0)
|
||||||
@@ -49,6 +51,11 @@ export default function NewPostPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
requireAuth(() => undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 500))
|
await new Promise((r) => setTimeout(r, 500))
|
||||||
router.push("/community")
|
router.push("/community")
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -1,10 +1,22 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { usePathname } from "next/navigation"
|
||||||
|
import { AuthGuard } from "@/components/auth-guard"
|
||||||
import { Header } from "@/components/header"
|
import { Header } from "@/components/header"
|
||||||
|
|
||||||
export default function OrderLayout({ children }: { children: React.ReactNode }) {
|
export default function OrderLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const pathname = usePathname()
|
||||||
|
const protectedPathPrefixes = ["/orders", "/order", "/chat", "/dispute", "/review"]
|
||||||
|
const shouldGuard = protectedPathPrefixes.some(
|
||||||
|
(prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`),
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col">
|
<div className="flex min-h-screen flex-col">
|
||||||
<Header />
|
<Header />
|
||||||
<main className="flex-1 bg-muted/30">{children}</main>
|
<main className="flex-1 bg-muted/30">
|
||||||
|
{shouldGuard ? <AuthGuard>{children}</AuthGuard> : children}
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import { Label } from "@/components/ui/label"
|
|||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { mockPlayers, mockServices, walletBalance } from "@/lib/mock-data"
|
import { mockPlayers, mockServices, walletBalance } from "@/lib/mock-data"
|
||||||
|
import { useRequireAuth } from "@/lib/use-require-auth"
|
||||||
|
|
||||||
export default function NewOrderPage() {
|
export default function NewOrderPage() {
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
const { requireAuth } = useRequireAuth()
|
||||||
const serviceId = searchParams.get("serviceId")
|
const serviceId = searchParams.get("serviceId")
|
||||||
|
|
||||||
const service = mockServices.find((s) => s.id === serviceId)
|
const service = mockServices.find((s) => s.id === serviceId)
|
||||||
@@ -168,7 +170,7 @@ export default function NewOrderPage() {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
size="lg"
|
size="lg"
|
||||||
disabled={walletBalance < totalPrice}
|
disabled={walletBalance < totalPrice}
|
||||||
onClick={() => setSubmitted(true)}
|
onClick={() => requireAuth(() => setSubmitted(true))}
|
||||||
>
|
>
|
||||||
确认支付 ¥{totalPrice}
|
确认支付 ¥{totalPrice}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { useAuthStore } from "@/store/auth"
|
||||||
|
import { useLoginDialogStore } from "@/store/login-dialog"
|
||||||
|
|
||||||
|
interface AuthGuardProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AuthGuard({ children }: AuthGuardProps) {
|
||||||
|
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
||||||
|
const openLoginDialog = useLoginDialogStore((s) => s.openLoginDialog)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
openLoginDialog()
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, openLoginDialog])
|
||||||
|
|
||||||
|
if (isAuthenticated) {
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4 text-center">
|
||||||
|
<p className="text-sm text-muted-foreground">请先登录后继续访问此页面</p>
|
||||||
|
<Button onClick={openLoginDialog}>登录</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user