UI: приглашение игрока перенесено из настроек в «Список игроков»

Карточка «Статистика игроков» на странице группы переименована в
«Список игроков», под списком — поле приглашения по нику (с проверкой
максимума участников и отправкой по Enter). В настройках группы остался
только состав с удалением участников.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 17:17:47 +03:00
co-authored by Claude Fable 5
parent 49864db87e
commit 378094c366
2 changed files with 52 additions and 33 deletions
+49 -2
View File
@@ -1,14 +1,25 @@
import { UserPlus } from "lucide-react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { ApiError } from "../api/client";
import { CreateGroupForm } from "../components/CreateGroupForm";
import { GroupInvitations } from "../components/GroupInvitations";
import { GroupSwitcher } from "../components/GroupSwitcher";
import { Leaderboard } from "../components/Leaderboard";
import { MatchListView } from "../components/MatchList";
import { Spinner } from "../components/Spinner";
import { MAX_GROUP_SIZE } from "../domain/constants";
import { formatDate } from "../domain/format";
import { useToast } from "../context/ToastContext";
import { useMe } from "../hooks/auth";
import { useGroup, useGroupMatches, useGroupStats } from "../hooks/groups";
import {
useGroup,
useGroupMatches,
useGroupMembers,
useGroupStats,
useInviteMember,
} from "../hooks/groups";
export function GroupPage() {
const { data: me, isLoading } = useMe();
@@ -18,9 +29,25 @@ export function GroupPage() {
const { data: group } = useGroup(groupId);
const { data: stats } = useGroupStats(groupId);
const { data: matches } = useGroupMatches(groupId);
const { data: members } = useGroupMembers(groupId);
const inviteMember = useInviteMember();
const toast = useToast();
const [newNick, setNewNick] = useState("");
if (isLoading) return <Spinner />;
const invite = async () => {
const nick = newNick.trim();
if (!nick || !groupId) return;
try {
await inviteMember.mutateAsync({ groupId, nickname: nick });
setNewNick("");
toast.show("Приглашение отправлено");
} catch (e) {
toast.show(e instanceof ApiError ? e.message : "Ошибка");
}
};
if (!groupId) {
return (
<div>
@@ -57,7 +84,7 @@ export function GroupPage() {
<GroupInvitations />
<div className="card">
<h3>Статистика игроков</h3>
<h3>Список игроков</h3>
{stats && (
<Leaderboard
entries={stats.leaderboard}
@@ -65,6 +92,26 @@ export function GroupPage() {
inactive={stats.inactive}
/>
)}
{/* Приглашение нового игрока — здесь, рядом со списком (не в настройках). */}
{(members ?? []).length >= MAX_GROUP_SIZE ? (
<p className="muted small" style={{ margin: "10px 0 0" }}>
Достигнут максимум участников ({MAX_GROUP_SIZE}).
</p>
) : (
<div className="row" style={{ marginTop: 10 }}>
<input
placeholder="Ник игрока — отправить приглашение"
value={newNick}
onChange={(e) => setNewNick(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") invite();
}}
/>
<button className="btn btn-primary" onClick={invite} disabled={inviteMember.isPending}>
<UserPlus size={18} />
</button>
</div>
)}
</div>
<GroupSwitcher />