Профиль: блок истории игр с переключателями режима и подробности

В «Аккаунте» появился блок «История игр» с двумя тумблерами — «Только
лучшая партия» и «Подробные карточки»; выбор уходит в профиль, поэтому
переживает перезаход и другое устройство.

Новый компонент MatchHistory: подробный режим переиспользует список партий
группы (MatchListView со всеми участниками), компактный рисует строку с
результатом самого игрока — место, фракция, дата и длительность.

Публичный профиль показывает ту же историю в режиме владельца и без
контролов: страница намеренно read-only.

schema.d.ts пересобран с живого бэкенда (npm run gen:api).

#1

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 19:56:15 +03:00
co-authored by Claude Opus 5
parent be80891449
commit 9dda7de067
7 changed files with 250 additions and 0 deletions
+17
View File
@@ -137,6 +137,23 @@ export function useUpdateFavoriteFaction() {
});
}
// Витрина истории партий: режим (all/best) и подробность (compact/full).
// Шлём только изменившееся поле — роутер обновляет лишь пришедшие.
export function useUpdateHistoryPrefs() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (prefs: {
history_mode?: string;
history_detail?: string;
}): Promise<Me> => unwrap(await api.PATCH("/api/users/me/profile", { body: prefs })),
onSuccess: (me) => {
qc.setQueryData(qk.me, me);
// Список истории приходит вместе с режимом — перезапрашиваем его целиком.
qc.invalidateQueries({ queryKey: qk.userMatches(me.id) });
},
});
}
// Аватар. Картинку уменьшаем на клиенте (≤512px) и грузим multipart'ом отдельным
// fetch (openapi-fetch неудобен для файлов); CSRF-токен из cookie ставим вручную.
function readCsrfToken(): string | null {
+15
View File
@@ -15,6 +15,21 @@ function useDebounced<T>(value: T, ms = 100): T {
return debounced;
}
/** История партий игрока. Режим и подробность приходят вместе со списком:
* это витрина ВЛАДЕЛЬЦА профиля, её видят и гости. */
export function useUserMatches(userId: number | null) {
return useQuery({
queryKey: userId ? qk.userMatches(userId) : ["userMatches", "none"],
enabled: userId != null,
queryFn: async () =>
unwrap(
await api.GET("/api/users/{user_id}/matches", {
params: { path: { user_id: userId as number } },
}),
),
});
}
/** Подсказки по нику среди всех активных игроков (GET /api/users/search). */
export function useUserSearch(q: string, limit = 3) {
const dq = useDebounced(q.trim());