Files
ForbiddenStarsApp/frontend/src/components/CreateGroupForm.tsx
T
NotBigGhostandClaude Opus 5 3587fc8af2 Тосты: убрать лишние, остальные показывать как уведомления сверху
Многие тосты повторяли то, что пользователь и так видит сразу после действия:
новую фракцию или аватар в профиле, переход на страницу партии, исчезнувшую
карточку приглашения, закрывшийся редактор. Такой шум приучает не читать
всплывашки, в том числе ошибки. Убраны 19 таких тостов в профиле, группах,
партиях и админке.

Остались ошибки, предупреждения (лимит фото, повторы фракций, конфликт правок,
«вы не в группе») и сообщения о действиях без видимого следа: приглашение
отправлено, дополнения сохранены, вступление в группу, пароль задан. Сохранение
текстового поля, где заметна только погасшая кнопка, тоже осталось.

По просьбе владельца оставшиеся тосты показываются не нижней «таблеткой», а
всплывашкой сверху, как входящие уведомления. ToastProvider держит одну стопку
на всё приложение: show — обычное сообщение, error — ошибка с красной полосой
(держится 5 с), notify — уведомление с переходом по клику. Одинаковое сообщение
не копится, а заменяет прежнее; клик по карточке закрывает её.
NotificationToaster больше не рисует свою стопку, а отдаёт новые уведомления в
общую, поэтому они не наезжают на сообщения. Все вызовы с ApiError и отказы
(лимит фото, партия изменилась на другом устройстве) переведены на toast.error.
Нижний класс .toast удалён.

#41

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfTsytzT6TojfmprRDKiV6
2026-09-13 15:45:34 +03:00

64 lines
2.2 KiB
TypeScript

import { useState } from "react";
import { ApiError } from "../api/client";
import { useCreateGroup } from "../hooks/groups";
import { useExpansions } from "../hooks/reference";
import { Switch } from "./Switch";
export function CreateGroupForm({ onCreated }: { onCreated?: () => void }) {
const { data: expansions } = useExpansions();
const createGroup = useCreateGroup();
const [name, setName] = useState("");
const [selected, setSelected] = useState<Set<number>>(new Set());
const [error, setError] = useState<string | null>(null);
const toggle = (id: number) =>
setSelected((s) => {
const next = new Set(s);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
const submit = async () => {
setError(null);
try {
await createGroup.mutateAsync({ name: name.trim(), expansion_ids: [...selected] });
onCreated?.();
} catch (e) {
setError(e instanceof ApiError ? e.message : "Не удалось создать группу");
}
};
return (
<div className="card">
<h3>Новая группа</h3>
<div className="field">
<label className="label">Название</label>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Например: Вечер 40k" />
</div>
<div className="field">
<label className="label">Дополнения группы</label>
<div className="stack">
{(expansions ?? []).map((e) => (
<label key={e.id} className="row" style={{ justifyContent: "space-between" }}>
<span>
{e.name_ru}
{e.is_base && <span className="muted small"> (всегда включено)</span>}
</span>
<Switch
disabled={e.is_base}
checked={e.is_base || selected.has(e.id)}
onChange={() => toggle(e.id)}
/>
</label>
))}
</div>
</div>
{error && <p className="error-text">{error}</p>}
<button className="btn btn-primary btn-block" onClick={submit} disabled={createGroup.isPending}>
Создать
</button>
</div>
);
}