2026-09-19 21:50:06 +03:00
|
|
|
|
import { request } from './client';
|
|
|
|
|
|
import type { GuildSummary, User, UserSummary } from './types';
|
|
|
|
|
|
|
|
|
|
|
|
export interface UpdateProfileInput {
|
|
|
|
|
|
display_name?: string;
|
|
|
|
|
|
bio?: string;
|
|
|
|
|
|
status?: string;
|
|
|
|
|
|
custom_status?: string;
|
|
|
|
|
|
custom_status_emoji?: string;
|
|
|
|
|
|
locale?: string;
|
2026-09-20 01:32:11 +03:00
|
|
|
|
/** Часовой пояс в формате IANA (например `Europe/Moscow`). */
|
|
|
|
|
|
timezone?: string;
|
2026-09-19 21:50:06 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface OnboardingInput {
|
|
|
|
|
|
display_name?: string;
|
|
|
|
|
|
bio?: string;
|
|
|
|
|
|
locale?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const currentUserQueryKey = ['users', '@me'] as const;
|
|
|
|
|
|
export const myGuildsQueryKey = ['users', '@me', 'guilds'] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export function userQueryKey(userId: string) {
|
|
|
|
|
|
return ['users', userId] as const;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function fetchCurrentUser(signal?: AbortSignal): Promise<User> {
|
|
|
|
|
|
const payload = await request<{ user: User }>(
|
|
|
|
|
|
'/users/@me',
|
|
|
|
|
|
signal === undefined ? {} : { signal },
|
|
|
|
|
|
);
|
|
|
|
|
|
return payload.user;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function updateProfile(input: UpdateProfileInput): Promise<User> {
|
|
|
|
|
|
const payload = await request<{ user: User }>('/users/@me', { method: 'PATCH', body: input });
|
|
|
|
|
|
return payload.user;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function completeOnboarding(input: OnboardingInput): Promise<User> {
|
|
|
|
|
|
const payload = await request<{ user: User }>('/users/@me/onboarding/complete', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
body: input,
|
|
|
|
|
|
});
|
|
|
|
|
|
return payload.user;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-20 01:32:11 +03:00
|
|
|
|
/** Максимальный размер аватара: сервер отвечает `file.too_large` выше лимита. */
|
|
|
|
|
|
export const AVATAR_MAX_BYTES = 8 * 1024 * 1024;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Загрузка аватара: `POST /users/@me/avatar` multipart с полем `file`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Сервер может ответить как полным профилем, так и только идентификатором
|
|
|
|
|
|
* файла, поэтому возвращаем профиль, если он есть, иначе `null` — вызывающий
|
|
|
|
|
|
* код в этом случае перечитывает `GET /users/@me`.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function uploadAvatar(file: File): Promise<User | null> {
|
|
|
|
|
|
const form = new FormData();
|
|
|
|
|
|
form.append('file', file);
|
|
|
|
|
|
const payload = await request<{ user?: User }>('/users/@me/avatar', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
form,
|
|
|
|
|
|
});
|
|
|
|
|
|
return payload.user ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Удаление аватара: `DELETE /users/@me/avatar`. */
|
|
|
|
|
|
export async function deleteAvatar(): Promise<User | null> {
|
|
|
|
|
|
const payload = await request<{ user?: User }>('/users/@me/avatar', { method: 'DELETE' });
|
|
|
|
|
|
return payload.user ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-19 21:50:06 +03:00
|
|
|
|
export async function fetchMyGuilds(signal?: AbortSignal): Promise<GuildSummary[]> {
|
|
|
|
|
|
const payload = await request<{ guilds: GuildSummary[] }>(
|
|
|
|
|
|
'/users/@me/guilds',
|
|
|
|
|
|
signal === undefined ? {} : { signal },
|
|
|
|
|
|
);
|
|
|
|
|
|
return payload.guilds;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function fetchUser(userId: string, signal?: AbortSignal): Promise<UserSummary> {
|
|
|
|
|
|
const payload = await request<{ user: UserSummary }>(
|
|
|
|
|
|
`/users/${encodeURIComponent(userId)}`,
|
|
|
|
|
|
signal === undefined ? {} : { signal },
|
|
|
|
|
|
);
|
|
|
|
|
|
return payload.user;
|
|
|
|
|
|
}
|