c9fe2bd9fe
- Add servers list page and missing routes (servers, settings redirect, account security)
- Fix members page .map error (API returns { data } wrapper, not flat array)
- Fix auth store fetchUser expecting flat User but API returns { user } wrapper
- Add node token display dialog after creation
- Add allocation management UI to node detail page
- Add account security page with password change
- Add change-password API endpoint
- Add node servers and stats API endpoints
- Fix config save using PATCH instead of PUT, add api.put method
- Fix audit logs field name mismatch (userName vs username)
- Replace admin nodes page to avoid orgId dependency
- Remove duplicate sidebar nav items
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
1.8 KiB
TypeScript
70 lines
1.8 KiB
TypeScript
import { create } from 'zustand';
|
|
import { api, ApiError } from '@/lib/api';
|
|
|
|
interface User {
|
|
id: string;
|
|
email: string;
|
|
username: string;
|
|
isSuperAdmin: boolean;
|
|
avatarUrl: string | null;
|
|
}
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
isLoading: boolean;
|
|
isAuthenticated: boolean;
|
|
|
|
login: (email: string, password: string) => Promise<void>;
|
|
register: (email: string, username: string, password: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
fetchUser: () => Promise<void>;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set) => ({
|
|
user: null,
|
|
isLoading: true,
|
|
isAuthenticated: false,
|
|
|
|
login: async (email, password) => {
|
|
const data = await api.post<{ accessToken: string; user: User }>('/auth/login', {
|
|
email,
|
|
password,
|
|
});
|
|
localStorage.setItem('access_token', data.accessToken);
|
|
set({ user: data.user, isAuthenticated: true });
|
|
},
|
|
|
|
register: async (email, username, password) => {
|
|
const data = await api.post<{ accessToken: string; user: User }>('/auth/register', {
|
|
email,
|
|
username,
|
|
password,
|
|
});
|
|
localStorage.setItem('access_token', data.accessToken);
|
|
set({ user: data.user, isAuthenticated: true });
|
|
},
|
|
|
|
logout: async () => {
|
|
try {
|
|
await api.post('/auth/logout');
|
|
} catch {
|
|
// ignore
|
|
}
|
|
localStorage.removeItem('access_token');
|
|
set({ user: null, isAuthenticated: false });
|
|
},
|
|
|
|
fetchUser: async () => {
|
|
try {
|
|
const data = await api.get<{ user: User }>('/auth/me');
|
|
set({ user: data.user, isAuthenticated: true, isLoading: false });
|
|
} catch (err) {
|
|
if (err instanceof ApiError && err.status === 401) {
|
|
set({ user: null, isAuthenticated: false, isLoading: false });
|
|
} else {
|
|
set({ isLoading: false });
|
|
}
|
|
}
|
|
},
|
|
}));
|