Initial commit

This commit is contained in:
wangxuming
2026-07-12 15:53:24 +08:00
commit 68d61700d5
252 changed files with 23291 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
import { ApiError, api } from "../api";
import type { AuthPayload, ProjectDto, UserDto } from "../types";
interface AuthContextValue {
user: UserDto | null;
projects: ProjectDto[];
loading: boolean;
error: ApiError | null;
login: (username: string, password: string) => Promise<AuthPayload>;
logout: () => Promise<void>;
refresh: () => Promise<void>;
}
export type AuthPortal = "staff" | "admin";
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children, portal }: { children: ReactNode; portal: AuthPortal }) {
const [user, setUser] = useState<UserDto | null>(null);
const [projects, setProjects] = useState<ProjectDto[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<ApiError | null>(null);
const refresh = async () => {
setLoading(true);
try {
const payload = await api.me(portal);
setUser(payload.user);
setProjects(payload.projects ?? []);
setError(null);
} catch (caught) {
const apiError = caught instanceof ApiError ? caught : new ApiError("无法确认登录状态。", 0, caught);
setUser(null);
setProjects([]);
if (apiError.status !== 401) setError(apiError);
} finally {
setLoading(false);
}
};
useEffect(() => {
void refresh();
}, [portal]);
const login = async (username: string, password: string) => {
const payload = await api.login(portal, username, password);
setUser(payload.user);
setProjects(payload.projects ?? []);
setError(null);
return payload;
};
const logout = async () => {
try {
await api.logout(portal);
} finally {
setUser(null);
setProjects([]);
}
};
const value = useMemo<AuthContextValue>(
() => ({ user, projects, loading, error, login, logout, refresh }),
[user, projects, loading, error],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextValue {
const context = useContext(AuthContext);
if (!context) throw new Error("useAuth 必须在 AuthProvider 内使用");
return context;
}