feat: header reflects auth state (account/logout); fix admin api requests losing Content-Type (user delete 422)
This commit is contained in:
parent
a06b5f44c4
commit
d296b7aa72
@ -1,14 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CloseIcon, MenuIcon } from "@/components/icons";
|
||||
import { navigation } from "@/lib/content";
|
||||
import { getMe, logout } from "@/lib/admin";
|
||||
import { BrandMark } from "@/components/brand-mark";
|
||||
import { BrandIcon } from "@/components/brand-icon";
|
||||
|
||||
export function SiteHeader() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [authed, setAuthed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getMe()
|
||||
.then(() => setAuthed(true))
|
||||
.catch(() => setAuthed(false));
|
||||
}, []);
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await logout();
|
||||
setAuthed(false);
|
||||
setOpen(false);
|
||||
} catch {
|
||||
window.location.href = "/login";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 border-b border-white/5 bg-ink/90 backdrop-blur-xl">
|
||||
@ -24,9 +42,20 @@ export function SiteHeader() {
|
||||
))}
|
||||
</nav>
|
||||
<div className="desktop-nav items-center gap-6">
|
||||
{authed ? (
|
||||
<>
|
||||
<Link className="button-ghost min-h-10 px-4 py-2 text-sm" href="/account">
|
||||
<BrandIcon />我的页面
|
||||
</Link>
|
||||
<button className="button-ghost min-h-10 px-4 py-2 text-sm" onClick={() => void handleLogout()} type="button">
|
||||
<BrandIcon />退出登录
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<Link className="button-ghost min-h-10 px-4 py-2 text-sm" href="/login">
|
||||
<BrandIcon />登录
|
||||
</Link>
|
||||
)}
|
||||
<Link className="button-primary min-h-10 px-4 py-2 text-sm" href="/contact">
|
||||
<BrandIcon />联系我们
|
||||
</Link>
|
||||
@ -43,9 +72,24 @@ export function SiteHeader() {
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
{authed ? (
|
||||
<>
|
||||
<Link className="mt-2 rounded-lg border-t border-line px-3 py-3 text-sm text-copy" href="/account" onClick={() => setOpen(false)}>
|
||||
我的页面
|
||||
</Link>
|
||||
<button
|
||||
className="mt-1 rounded-lg border-t border-line px-3 py-4 text-left text-sm text-copy hover:bg-panel-raised"
|
||||
onClick={() => void handleLogout()}
|
||||
type="button"
|
||||
>
|
||||
退出登录
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<Link className="mt-2 rounded-lg border-t border-line px-3 py-4 text-sm text-copy" href="/login" onClick={() => setOpen(false)}>
|
||||
登录 / 账户入口
|
||||
</Link>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
13
lib/admin.ts
13
lib/admin.ts
@ -28,11 +28,8 @@ export type AdminUserDetail = {
|
||||
export type Page<T> = { items: T[]; total: number; page: number; limit: number; pages: number };
|
||||
|
||||
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const response = await fetch(`${apiBase}${path}`, {
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json", ...(options.headers ?? {}) },
|
||||
...options,
|
||||
});
|
||||
const headers = { "Content-Type": "application/json", ...(options.headers as Record<string, string> | undefined) };
|
||||
const response = await fetch(`${apiBase}${path}`, { credentials: "include", ...options, headers });
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
if (response.status === 403) throw new Error("无权执行此操作");
|
||||
throw new Error("请登录");
|
||||
@ -43,6 +40,7 @@ async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const msg = Array.isArray(message) ? (message as Array<{ msg?: string }>).map((m) => m.msg ?? "").join("; ") : (message as string);
|
||||
throw new Error(msg || "请求失败");
|
||||
}
|
||||
if (response.status === 204) return undefined as T;
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
@ -52,6 +50,11 @@ export async function fetchCsrf(): Promise<string> {
|
||||
return body.csrf_token ?? "";
|
||||
}
|
||||
|
||||
export async function logout(): Promise<void> {
|
||||
const csrf = await fetchCsrf();
|
||||
await request<void>("/auth/logout", { method: "POST", headers: { "X-CSRF-Token": csrf } });
|
||||
}
|
||||
|
||||
export async function getMe(): Promise<{ email: string; role: string; plan: string; status: string }> {
|
||||
return request("/auth/me");
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user