- 新增 POST /auth/set-password 端点(仅限无密码用户) - /auth/me 返回 hasPassword 字段 - SettingsDialog:无密码用户显示"设置密码"表单(无需当前密码) - API Key reveal/copy:无密码时引导用户先设置密码 - 中英双语 i18n 支持 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
75 lines
2.4 KiB
TypeScript
75 lines
2.4 KiB
TypeScript
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
|
|
import { getAccessToken, clearTokens, setTokens, apiFetch } from './api';
|
|
|
|
type User = { id: string; email: string; name: string; hasPassword?: boolean };
|
|
|
|
type AuthContextType = {
|
|
user: User | null;
|
|
loading: boolean;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
register: (email: string, password: string, name: string) => Promise<void>;
|
|
logout: () => void;
|
|
updateUser: (updates: Partial<User>) => void;
|
|
loginWithTokens: (accessToken: string, refreshToken: string) => Promise<void>;
|
|
};
|
|
|
|
const AuthContext = createContext<AuthContextType | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (getAccessToken()) {
|
|
apiFetch<User>('/auth/me')
|
|
.then(setUser)
|
|
.catch(() => clearTokens())
|
|
.finally(() => setLoading(false));
|
|
} else {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const login = async (email: string, password: string) => {
|
|
const data = await apiFetch<{ user: User; accessToken: string; refreshToken: string }>(
|
|
'/auth/login',
|
|
{ method: 'POST', body: JSON.stringify({ email, password }) },
|
|
);
|
|
setTokens(data.accessToken, data.refreshToken);
|
|
setUser(data.user);
|
|
};
|
|
|
|
const register = async (email: string, password: string, name: string) => {
|
|
const data = await apiFetch<{ user: User; accessToken: string; refreshToken: string }>(
|
|
'/auth/register',
|
|
{ method: 'POST', body: JSON.stringify({ email, password, name }) },
|
|
);
|
|
setTokens(data.accessToken, data.refreshToken);
|
|
setUser(data.user);
|
|
};
|
|
|
|
const logout = () => { clearTokens(); setUser(null); };
|
|
|
|
const updateUser = (updates: Partial<User>) => {
|
|
setUser(prev => prev ? { ...prev, ...updates } : null);
|
|
};
|
|
|
|
const loginWithTokens = async (access: string, refresh: string) => {
|
|
setTokens(access, refresh);
|
|
const user = await apiFetch<User>('/auth/me');
|
|
setUser(user);
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, loading, login, register, logout, updateUser, loginWithTokens }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
|
return ctx;
|
|
}
|