e69553af02
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
120 lines
3.1 KiB
TypeScript
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 }),
|
|
}));
|