195 lines
4.9 KiB
TypeScript
195 lines
4.9 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;
|
|
banner_url?: string;
|
|
tagline?: string;
|
|
pronouns?: string;
|
|
social_links?: { platform: string; url: string }[];
|
|
}
|
|
|
|
export interface PublicProfile extends User {
|
|
badges: { id: string; name: string; icon: string; description?: string; server_id?: string }[];
|
|
}
|
|
|
|
export type BlockedUser = {
|
|
id: string;
|
|
username: string;
|
|
created_at: string;
|
|
};
|
|
|
|
export interface UpdateProfilePayload {
|
|
avatar_url?: string;
|
|
display_name?: string;
|
|
bio?: string;
|
|
accent_color?: string;
|
|
status_text?: string;
|
|
status?: UserStatus;
|
|
banner_url?: string;
|
|
tagline?: string;
|
|
pronouns?: string;
|
|
social_links?: { platform: string; url: string }[];
|
|
}
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
login: (identifier: 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>;
|
|
changePassword: (
|
|
currentPassword: string,
|
|
newPassword: string,
|
|
) => Promise<void>;
|
|
getPublicProfile: (userId: string) => Promise<PublicProfile>;
|
|
listBlocks: () => Promise<BlockedUser[]>;
|
|
blockUser: (userId: string) => Promise<void>;
|
|
unblockUser: (userId: string) => Promise<void>;
|
|
clearError: () => void;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set) => ({
|
|
user: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: null,
|
|
|
|
login: async (identifier, password) => {
|
|
set({ isLoading: true, error: null });
|
|
try {
|
|
await api.post("/auth/login/password", {
|
|
email: identifier,
|
|
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?t=${Date.now()}`);
|
|
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;
|
|
}
|
|
},
|
|
|
|
changePassword: async (currentPassword, newPassword) => {
|
|
set({ isLoading: true, error: null });
|
|
try {
|
|
await api.put("/auth/me/password", {
|
|
current_password: currentPassword,
|
|
new_password: newPassword,
|
|
});
|
|
set({ isLoading: false });
|
|
} catch (error) {
|
|
set({
|
|
isLoading: false,
|
|
error:
|
|
error instanceof Error
|
|
? error.message
|
|
: "Password change failed",
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
clearError: () => set({ error: null }),
|
|
|
|
getPublicProfile: async (userId) => api.get<PublicProfile>(`/users/${userId}/profile`),
|
|
|
|
listBlocks: async () => api.get<BlockedUser[]>('/users/me/blocks'),
|
|
|
|
blockUser: async (userId) => {
|
|
await api.post('/users/me/blocks', { user_id: userId });
|
|
},
|
|
|
|
unblockUser: async (userId) => {
|
|
await api.delete(`/users/me/blocks/${userId}`);
|
|
},
|
|
}));
|