Files
dumpsterChat/web/src/stores/auth.ts
T
hobokenchicken e69553af02 Profiles, Giphy, uploads, settings UI
Backend:
- Auth: split routes into RegisterPublicRoutes + RegisterProtectedRoutes
- Auth: PATCH /me for profile updates (display_name, bio, accent_color, status_text)
- Auth: Gravatar fallback for avatars on register
- DB: users table now has bio, accent_color, status_text columns
- giphy/client.go: Search() and GetTrending() against Giphy API
- upload/handlers.go: MinIO file upload + serve
- config: added GiphyConfig and MinIO config
- cmd/server: wired giphy (/gifs/search, /gifs/trending), upload (/upload), files (/files/*) routes

Frontend:
- auth store: updated User interface with new profile fields, added updateProfile()
- UserSettings.tsx: terminal-styled profile editor (display_name, bio, accent_color, status_text, avatar upload)
- GiphyPicker.tsx: terminal-styled GIF picker with search + trending
- ChatArea.tsx: integrated [GIF] button into message input
- App.tsx: imports UserSettings, added /settings route
2026-06-28 16:06:08 -04:00

120 lines
3.1 KiB
TypeScript

import { create } from 'zustand';
import { api } from '../lib/api.ts';
export type UserStatus = 'online' | 'idle' | 'dnd' | 'offline';
export interface User {
id: string;
username: string;
display_name: string;
email: string;
avatar: string;
bio: string;
accent_color: string;
status: UserStatus;
status_text: string;
created_at: string;
}
export interface UpdateProfilePayload {
display_name?: string;
bio?: string;
accent_color?: string;
status_text?: string;
}
interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
login: (email: string, password: string) => Promise<void>;
register: (email: string, username: string, password: string, displayName?: string) => Promise<void>;
logout: () => Promise<void>;
fetchMe: () => Promise<void>;
updateProfile: (data: UpdateProfilePayload) => Promise<void>;
clearError: () => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
login: async (email, password) => {
set({ isLoading: true, error: null });
try {
await api.post('/auth/login/password', { email, password });
const user = await api.get<User>('/auth/me');
set({ user, isAuthenticated: true, isLoading: false });
} catch (error) {
set({
isLoading: false,
error: error instanceof Error ? error.message : 'Login failed',
});
throw error;
}
},
register: async (email, username, password, displayName) => {
set({ isLoading: true, error: null });
try {
await api.post('/auth/register', { email, username, password, display_name: displayName || username });
const user = await api.get<User>('/auth/me');
set({ user, isAuthenticated: true, isLoading: false });
} catch (error) {
set({
isLoading: false,
error: error instanceof Error ? error.message : 'Registration failed',
});
throw error;
}
},
logout: async () => {
set({ isLoading: true, error: null });
try {
await api.post('/auth/logout');
set({ user: null, isAuthenticated: false, isLoading: false });
} catch (error) {
set({
isLoading: false,
error: error instanceof Error ? error.message : 'Logout failed',
});
throw error;
}
},
fetchMe: async () => {
set({ isLoading: true, error: null });
try {
const user = await api.get<User>('/auth/me');
set({ user, isAuthenticated: true, isLoading: false });
} catch (error) {
set({
user: null,
isAuthenticated: false,
isLoading: false,
error: error instanceof Error ? error.message : 'Failed to fetch user',
});
}
},
updateProfile: async (data) => {
set({ isLoading: true, error: null });
try {
const user = await api.patch<User>('/auth/me', data);
set({ user, isLoading: false });
} catch (error) {
set({
isLoading: false,
error: error instanceof Error ? error.message : 'Update failed',
});
throw error;
}
},
clearError: () => set({ error: null }),
}));