import { useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { ApiError } from "../api/client"; import { Avatar } from "../components/Avatar"; import { MatchHistory } from "../components/MatchHistory"; import { PickerSelect } from "../components/PickerSelect"; import { ProfileStatsCard } from "../components/ProfileStatsCard"; import { Spinner } from "../components/Spinner"; import { Switch } from "../components/Switch"; import { useToast } from "../context/ToastContext"; import { useDeleteAvatar, useLogout, useMe, useUpdateBio, useUpdateFavoriteFaction, useUpdateHistoryPrefs, useUpdateNickname, useUploadAvatar, } from "../hooks/auth"; import { useExpansions, useFactions } from "../hooks/reference"; import { useMyStats } from "../hooks/stats"; import { useUserMatches } from "../hooks/users"; export function AccountPage() { const { data: me, isLoading } = useMe(); const { data: stats } = useMyStats(); const updateNick = useUpdateNickname(); const updateBio = useUpdateBio(); const updateFavorite = useUpdateFavoriteFaction(); const updateHistory = useUpdateHistoryPrefs(); const { data: factions } = useFactions(); const { data: expansions } = useExpansions(); const { data: history, fetchNextPage, hasNextPage, isFetchingNextPage, } = useUserMatches(me?.id ?? null); const uploadAvatar = useUploadAvatar(); const deleteAvatar = useDeleteAvatar(); const logout = useLogout(); const toast = useToast(); const navigate = useNavigate(); const fileRef = useRef(null); const [nickname, setNickname] = useState(""); const [bio, setBio] = useState(null); if (isLoading || !me) return ; // bio редактируется локально; до первого ввода берём значение из профиля. const bioValue = bio ?? me.bio ?? ""; // Любимая фракция выбирается из ПОЛНОГО справочника: это свойство игрока, а не // группы, и не должно зависеть от подключённых у неё дополнений. const favorite = factions?.find((f) => f.id === me.favorite_faction_id) ?? null; const expansionName = (id: number) => expansions?.find((e) => e.id === id)?.name_ru ?? ""; const saveFavorite = async (factionId: number | null) => { try { await updateFavorite.mutateAsync(factionId); toast.show(factionId == null ? "Любимая фракция убрана" : "Любимая фракция сохранена"); } catch (e) { toast.show(e instanceof ApiError ? e.message : "Ошибка"); } }; const saveHistory = async (prefs: { history_mode?: string; history_detail?: string }) => { try { await updateHistory.mutateAsync(prefs); } catch (e) { toast.show(e instanceof ApiError ? e.message : "Ошибка"); } }; const saveNick = async () => { const nick = nickname.trim(); if (!nick) return; try { await updateNick.mutateAsync(nick); setNickname(""); toast.show("Никнейм обновлён"); } catch (e) { toast.show(e instanceof ApiError ? e.message : "Ошибка"); } }; const saveBio = async () => { try { await updateBio.mutateAsync(bioValue.trim()); toast.show("Сохранено"); } catch (e) { toast.show(e instanceof ApiError ? e.message : "Ошибка"); } }; const onPickFile = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; e.target.value = ""; // позволяем выбрать тот же файл повторно if (!file) return; try { await uploadAvatar.mutateAsync(file); toast.show("Аватар обновлён"); } catch (err) { toast.show(err instanceof ApiError ? err.message : "Не удалось загрузить"); } }; const removeAvatar = async () => { try { await deleteAvatar.mutateAsync(); toast.show("Аватар удалён"); } catch (e) { toast.show(e instanceof ApiError ? e.message : "Ошибка"); } }; const doLogout = async () => { await logout.mutateAsync().catch(() => {}); navigate("/login", { replace: true }); }; return (

Профиль

{me.nickname} {stats &&
Партий: {stats.overall.games}
}
{me.avatar_url && ( )}