UI: общие классы кнопок и бейджей вместо дублирующихся inline-стилей
- .btn-block в global.css заменяет 9 повторов style={{width:100%}};
- .badge-live — единый бейдж «идёт» (3 копии inline-стиля);
- незавершённые партии на главной используют стандартный .card,
как список партий (была единственная карточка на самодельных стилях);
- в списке партий добавлен бейдж 🎲 случайной фракции — как на странице
партии (раньше в списке он терялся);
- переключатель групп: кнопка «+» подписана «+ Создать группу»
(по образцу «+ Добавить игрока»), убран пустой span.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,7 +58,7 @@ export function CreateGroupForm({ onCreated }: { onCreated?: () => void }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{error && <p className="error-text">{error}</p>}
|
{error && <p className="error-text">{error}</p>}
|
||||||
<button className="btn btn-primary" style={{ width: "100%" }} onClick={submit} disabled={createGroup.isPending}>
|
<button className="btn btn-primary btn-block" onClick={submit} disabled={createGroup.isPending}>
|
||||||
Создать
|
Создать
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,17 +21,13 @@ export function GroupSwitcher() {
|
|||||||
className={"btn" + (active ? " btn-primary" : "")}
|
className={"btn" + (active ? " btn-primary" : "")}
|
||||||
onClick={() => !active && setActive.mutate(g.id)}
|
onClick={() => !active && setActive.mutate(g.id)}
|
||||||
>
|
>
|
||||||
{g.name} {active && "✓"} <span className="muted small"></span>
|
{g.name} {active && "✓"}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{/* Плашка создания новой группы — в одном ряду логики с выбором группы. */}
|
{/* Плашка создания новой группы — в одном ряду логики с выбором группы. */}
|
||||||
<button
|
<button className="btn" onClick={() => setCreating((v) => !v)}>
|
||||||
className="btn"
|
+ Создать группу
|
||||||
aria-label="Создать группу"
|
|
||||||
onClick={() => setCreating((v) => !v)}
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -18,23 +18,14 @@ export function InProgressMatches({ items }: { items: HomeInProgressMatch[] }) {
|
|||||||
// а интерактивные элементы внутри <button> — невалидная разметка.
|
// а интерактивные элементы внутри <button> — невалидная разметка.
|
||||||
<div
|
<div
|
||||||
key={m.id}
|
key={m.id}
|
||||||
className="row-between"
|
className="card row-between"
|
||||||
onClick={() => navigate(`/match/${m.id}`)}
|
onClick={() => navigate(`/match/${m.id}`)}
|
||||||
style={{
|
style={{ margin: 0, cursor: "pointer" }}
|
||||||
width: "100%",
|
|
||||||
textAlign: "left",
|
|
||||||
background: "var(--surface-2)",
|
|
||||||
border: "1px solid var(--border)",
|
|
||||||
borderRadius: "var(--radius-sm)",
|
|
||||||
padding: 12,
|
|
||||||
color: "var(--text)",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div style={{ minWidth: 0 }}>
|
<div style={{ minWidth: 0 }}>
|
||||||
<div className="row" style={{ gap: 8 }}>
|
<div className="row" style={{ gap: 8 }}>
|
||||||
<b>{m.group_name}</b>
|
<b>{m.group_name}</b>
|
||||||
<span className="badge" style={{ color: "var(--accent-2)" }}>идёт</span>
|
<span className="badge badge-live">идёт</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="muted small">
|
<div className="muted small">
|
||||||
{formatTime(m.started_at)} · {m.player_count}{" "}
|
{formatTime(m.started_at)} · {m.player_count}{" "}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function MatchListView({ items }: { items: MatchListItem[] }) {
|
|||||||
<div className="row-between">
|
<div className="row-between">
|
||||||
<span className="muted small">{formatDate(m.played_at)}</span>
|
<span className="muted small">{formatDate(m.played_at)}</span>
|
||||||
{inProgress ? (
|
{inProgress ? (
|
||||||
<span className="badge" style={{ color: "var(--accent-2)" }}>идёт</span>
|
<span className="badge badge-live">идёт</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="muted small">
|
<span className="muted small">
|
||||||
{formatDuration(m.duration_minutes)} · {winReasonLabel(m.win_reason)}
|
{formatDuration(m.duration_minutes)} · {winReasonLabel(m.win_reason)}
|
||||||
@@ -47,6 +47,7 @@ export function MatchListView({ items }: { items: MatchListItem[] }) {
|
|||||||
</span>
|
</span>
|
||||||
<span>{p.nickname}</span>
|
<span>{p.nickname}</span>
|
||||||
<span className="muted small">· {p.faction_name}</span>
|
<span className="muted small">· {p.faction_name}</span>
|
||||||
|
{p.was_random && <span className="badge">🎲</span>}
|
||||||
</PlayerLink>
|
</PlayerLink>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export function AccountPage() {
|
|||||||
|
|
||||||
{stats && <ProfileStatsCard stats={stats} />}
|
{stats && <ProfileStatsCard stats={stats} />}
|
||||||
|
|
||||||
<button className="btn btn-danger" style={{ width: "100%" }} onClick={doLogout}>
|
<button className="btn btn-danger btn-block" onClick={doLogout}>
|
||||||
Выйти
|
Выйти
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -236,8 +236,8 @@ export function CreateMatchPage() {
|
|||||||
{error && <p className="error-text">{error}</p>}
|
{error && <p className="error-text">{error}</p>}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary btn-block"
|
||||||
style={{ width: "100%", marginTop: 12 }}
|
style={{ marginTop: 12 }}
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
disabled={startMatch.isPending}
|
disabled={startMatch.isPending}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export function MatchDetailPage() {
|
|||||||
<div className="row-between">
|
<div className="row-between">
|
||||||
<h2 style={{ margin: 0 }}>{formatDate(match.played_at)}</h2>
|
<h2 style={{ margin: 0 }}>{formatDate(match.played_at)}</h2>
|
||||||
{inProgress ? (
|
{inProgress ? (
|
||||||
<span className="badge" style={{ color: "var(--accent-2)" }}>идёт</span>
|
<span className="badge badge-live">идёт</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="muted small">{formatDuration(match.duration_minutes)}</span>
|
<span className="muted small">{formatDuration(match.duration_minutes)}</span>
|
||||||
)}
|
)}
|
||||||
@@ -261,8 +261,7 @@ export function MatchDetailPage() {
|
|||||||
{error && <p className="error-text">{error}</p>}
|
{error && <p className="error-text">{error}</p>}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary btn-block"
|
||||||
style={{ width: "100%" }}
|
|
||||||
onClick={submitFinish}
|
onClick={submitFinish}
|
||||||
disabled={finish.isPending}
|
disabled={finish.isPending}
|
||||||
>
|
>
|
||||||
@@ -274,11 +273,7 @@ export function MatchDetailPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{canModify && (
|
{canModify && (
|
||||||
<button
|
<button className="btn btn-danger btn-block" onClick={() => setConfirmRemove(true)}>
|
||||||
className="btn btn-danger"
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
onClick={() => setConfirmRemove(true)}
|
|
||||||
>
|
|
||||||
{inProgress ? "Отменить партию" : "Удалить партию"}
|
{inProgress ? "Отменить партию" : "Удалить партию"}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ export function PublicProfilePage() {
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
{groups.length === 1 ? (
|
{groups.length === 1 ? (
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary btn-block"
|
||||||
style={{ width: "100%" }}
|
|
||||||
onClick={() => doInvite(groups[0].id)}
|
onClick={() => doInvite(groups[0].id)}
|
||||||
disabled={invite.isPending}
|
disabled={invite.isPending}
|
||||||
>
|
>
|
||||||
@@ -74,8 +73,7 @@ export function PublicProfilePage() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary btn-block"
|
||||||
style={{ width: "100%" }}
|
|
||||||
onClick={() => setPickOpen((v) => !v)}
|
onClick={() => setPickOpen((v) => !v)}
|
||||||
>
|
>
|
||||||
Добавить в группу
|
Добавить в группу
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function AdminLoginPage() {
|
|||||||
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
{error && <p className="error-text">{error}</p>}
|
{error && <p className="error-text">{error}</p>}
|
||||||
<button className="btn btn-primary" style={{ width: "100%" }} onClick={submit} disabled={login.isPending}>
|
<button className="btn btn-primary btn-block" onClick={submit} disabled={login.isPending}>
|
||||||
Войти
|
Войти
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ export function AdminMatchEdit({ matchId, onClose }: { matchId: number; onClose:
|
|||||||
|
|
||||||
{error && <p className="error-text">{error}</p>}
|
{error && <p className="error-text">{error}</p>}
|
||||||
|
|
||||||
<button className="btn btn-primary" style={{ width: "100%" }} onClick={save} disabled={update.isPending}>
|
<button className="btn btn-primary btn-block" onClick={save} disabled={update.isPending}>
|
||||||
Сохранить изменения
|
Сохранить изменения
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); }
|
|||||||
.btn-danger { background: transparent; color: var(--danger); border-color: var(--danger); }
|
.btn-danger { background: transparent; color: var(--danger); border-color: var(--danger); }
|
||||||
.btn-danger-solid { background: var(--danger); border-color: var(--danger); color: #fff; }
|
.btn-danger-solid { background: var(--danger); border-color: var(--danger); color: #fff; }
|
||||||
.btn[disabled] { opacity: 0.5; pointer-events: none; }
|
.btn[disabled] { opacity: 0.5; pointer-events: none; }
|
||||||
|
.btn-block { width: 100%; }
|
||||||
|
|
||||||
.row { display: flex; align-items: center; gap: 10px; }
|
.row { display: flex; align-items: center; gap: 10px; }
|
||||||
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||||
@@ -91,6 +92,8 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); }
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
/* Бейдж незавершённой партии («идёт») */
|
||||||
|
.badge-live { color: var(--accent-2); }
|
||||||
|
|
||||||
.error-text { color: var(--danger); font-size: 13px; }
|
.error-text { color: var(--danger); font-size: 13px; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user