Initial commit
This commit is contained in:
75
web/src/auth/AuthContext.tsx
Normal file
75
web/src/auth/AuthContext.tsx
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user