Split monolithic UserSettings into AccountPanel, VoicePanel, ConnectionsPanel, and InstancePanel tabs. Remove standalone InstanceSettings modal. Add reusable Toggle component fixing size deformation, color inconsistency (green→purple), and flex-shrink issues. Fix custom status clearing by always sending the field to the server. Wrap Log Out button in glass bubble. Add subtle card depth with borders.
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
import { create } from 'zustand';
|
|
import type { User, UserStatus } from '@backspace/shared';
|
|
import { api } from '../api/client';
|
|
import { useChatStore } from './chatStore';
|
|
import { useSpaceStore } from './spaceStore';
|
|
import { useSocialStore } from './socialStore';
|
|
import { useVoiceStore } from './voiceStore';
|
|
import { useInstanceStore } from './instanceStore';
|
|
|
|
interface AuthState {
|
|
token: string | null;
|
|
user: User | null;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
login: (username: string, password: string) => Promise<void>;
|
|
register: (username: string, password: string, displayName?: string) => Promise<void>;
|
|
logout: () => void;
|
|
loadUser: () => Promise<void>;
|
|
updateProfile: (data: { displayName?: string; avatar?: string; customStatus?: string; status?: UserStatus }) => Promise<void>;
|
|
setUser: (user: User) => void;
|
|
clearError: () => void;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set, get) => ({
|
|
token: localStorage.getItem('backspace_token'),
|
|
user: null,
|
|
isLoading: false,
|
|
error: null,
|
|
|
|
login: async (username: string, password: string) => {
|
|
set({ isLoading: true, error: null });
|
|
try {
|
|
const response = await api.auth.login({ username, password });
|
|
localStorage.setItem('backspace_token', response.token);
|
|
set({ token: response.token, user: response.user, isLoading: false });
|
|
// Auto-connect to remote instances (fire-and-forget)
|
|
useInstanceStore.getState().autoConnectAll().catch(() => {});
|
|
} catch (err) {
|
|
set({ isLoading: false, error: err instanceof Error ? err.message : 'Login failed' });
|
|
throw err;
|
|
}
|
|
},
|
|
|
|
register: async (username: string, password: string, displayName?: string) => {
|
|
set({ isLoading: true, error: null });
|
|
try {
|
|
const response = await api.auth.register({ username, password, displayName });
|
|
localStorage.setItem('backspace_token', response.token);
|
|
set({ token: response.token, user: response.user, isLoading: false });
|
|
} catch (err) {
|
|
set({ isLoading: false, error: err instanceof Error ? err.message : 'Registration failed' });
|
|
throw err;
|
|
}
|
|
},
|
|
|
|
logout: () => {
|
|
localStorage.removeItem('backspace_token');
|
|
// Clear all user-scoped state to prevent data leaking between sessions
|
|
useChatStore.getState().clearAllMessages();
|
|
useSpaceStore.getState().populateFromReady('', [], [], []);
|
|
useSocialStore.getState().reset();
|
|
useVoiceStore.getState().clearAllVoiceUsers();
|
|
useInstanceStore.getState().reset();
|
|
set({ token: null, user: null });
|
|
},
|
|
|
|
loadUser: async () => {
|
|
const token = get().token;
|
|
if (!token) return;
|
|
|
|
set({ isLoading: true });
|
|
try {
|
|
const user = await api.users.me();
|
|
set({ user, isLoading: false });
|
|
// Auto-connect to remote instances (fire-and-forget)
|
|
useInstanceStore.getState().autoConnectAll().catch(() => {});
|
|
} catch {
|
|
localStorage.removeItem('backspace_token');
|
|
set({ token: null, user: null, isLoading: false });
|
|
}
|
|
},
|
|
|
|
updateProfile: async (data) => {
|
|
try {
|
|
const user = await api.users.update(data);
|
|
set({ user });
|
|
} catch (err) {
|
|
set({ error: err instanceof Error ? err.message : 'Update failed' });
|
|
throw err;
|
|
}
|
|
},
|
|
|
|
setUser: (user: User) => set({ user }),
|
|
|
|
clearError: () => set({ error: null }),
|
|
}));
|