Многие тосты повторяли то, что пользователь и так видит сразу после действия: новую фракцию или аватар в профиле, переход на страницу партии, исчезнувшую карточку приглашения, закрывшийся редактор. Такой шум приучает не читать всплывашки, в том числе ошибки. Убраны 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
68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { useState } from "react";
|
|
|
|
import { ApiError, api, unwrap } from "../../api/client";
|
|
import { qk } from "../../api/queryKeys";
|
|
import { ConfirmDialog } from "../../components/ConfirmDialog";
|
|
import { useToast } from "../../context/ToastContext";
|
|
|
|
/**
|
|
* DEV-ТОЛЬКО кнопка жёсткого удаления аккаунта.
|
|
*
|
|
* Эндпоинт `DELETE /api/admin/dev/users/{id}` существует только в dev-сборке бэкенда
|
|
* (backend/app/routers/dev_admin.py, исключён из прод/тест-образа). Этот модуль
|
|
* рендерится лишь под `import.meta.env.DEV` в AdminAccountsPage, поэтому в прод-сборке
|
|
* он не используется и вырезается тришейкингом — в прод/тест удаление недоступно.
|
|
*/
|
|
export function DevDeleteAccountButton({
|
|
userId,
|
|
nickname,
|
|
}: {
|
|
userId: number;
|
|
nickname: string;
|
|
}) {
|
|
const qc = useQueryClient();
|
|
const toast = useToast();
|
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
const del = useMutation({
|
|
mutationFn: async () =>
|
|
unwrap(
|
|
await api.DELETE("/api/admin/dev/users/{user_id}", {
|
|
params: { path: { user_id: userId } },
|
|
}),
|
|
),
|
|
onSuccess: () => qc.invalidateQueries({ queryKey: qk.adminUsers }),
|
|
});
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
className="btn small"
|
|
// Яркая (сплошная) красная кнопка. Стиль инлайном, а не классом в общем CSS,
|
|
// чтобы в прод/тест-стили не попало ничего, связанного с удалением.
|
|
style={{ background: "var(--danger)", borderColor: "var(--danger)", color: "#fff" }}
|
|
disabled={del.isPending}
|
|
onClick={() => setConfirmOpen(true)}
|
|
>
|
|
Удалить
|
|
</button>
|
|
<ConfirmDialog
|
|
open={confirmOpen}
|
|
danger
|
|
title={`Удалить аккаунт «${nickname}»?`}
|
|
message="Партии сохранятся, но игрок будет вычеркнут из них. Действие необратимо."
|
|
confirmLabel="Удалить"
|
|
onCancel={() => setConfirmOpen(false)}
|
|
onConfirm={async () => {
|
|
setConfirmOpen(false);
|
|
try {
|
|
await del.mutateAsync();
|
|
} catch (e) {
|
|
toast.error(e instanceof ApiError ? e.message : "Не удалось удалить");
|
|
}
|
|
}}
|
|
/>
|
|
</>
|
|
);
|
|
}
|