UI: единый PlayerLink — ники игроков кликабельны во всех местах просмотра

Новый компонент PlayerLink (ссылка на /u/:id, кликабельно всё поле строки,
stopPropagation для вложенности в кликабельные карточки). Применён:
- Leaderboard и общая статистика (рефакторинг ручных Link);
- страница партии: результаты и состав;
- настройки группы: список участников;
- список партий и незавершённые на главной (карточки button -> div,
  т.к. ссылки внутри button — невалидная разметка).
«пригласил: X» в приглашениях остаётся текстом — в API нет id пригласившего.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 17:37:19 +03:00
co-authored by Claude Fable 5
parent d81f5d8964
commit cbf3cfc6e6
7 changed files with 68 additions and 28 deletions
+8 -5
View File
@@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
import { formatDuration } from "../domain/format";
import { winReasonLabel } from "../domain/winReasons";
import type { MatchListItem } from "../domain/types";
import { PlayerLink } from "./PlayerLink";
export function MatchListView({ items }: { items: MatchListItem[] }) {
const navigate = useNavigate();
@@ -14,10 +15,12 @@ export function MatchListView({ items }: { items: MatchListItem[] }) {
const inProgress = m.status === "in_progress";
const sorted = [...m.participants].sort((a, b) => (a.place ?? 99) - (b.place ?? 99));
return (
<button
// div, а не button: внутри карточки лежат ссылки на профили игроков,
// а интерактивные элементы внутри <button> — невалидная разметка.
<div
key={m.id}
className="card"
style={{ textAlign: "left", width: "100%", margin: 0 }}
style={{ textAlign: "left", width: "100%", margin: 0, cursor: "pointer" }}
onClick={() => navigate(`/match/${m.id}`)}
>
<div className="row-between">
@@ -32,7 +35,7 @@ export function MatchListView({ items }: { items: MatchListItem[] }) {
</div>
<div className="stack" style={{ marginTop: 6, gap: 4 }}>
{sorted.map((p) => (
<div key={p.user_id} className="row" style={{ gap: 8 }}>
<PlayerLink key={p.user_id} userId={p.user_id} className="row" style={{ gap: 8 }}>
<span
className={
"place-badge" +
@@ -44,10 +47,10 @@ export function MatchListView({ items }: { items: MatchListItem[] }) {
</span>
<span>{p.nickname}</span>
<span className="muted small">· {p.faction_name}</span>
</div>
</PlayerLink>
))}
</div>
</button>
</div>
);
})}
</div>