fix(users): показывать причину отказа при удалении подписки

Дополнение к PR #542 при мерже.

Обработчик глушил ошибку целиком и показывал общее «Ошибка». Между тем сервер
отвечает ровно теми словами, которые админу и нужны: открытый временный доступ
(409, «сначала заверши или восстанови grace»), активная платная без force (409),
подписки нет (404). Первый случай автор сам описал в PR как штатный сценарий —
и именно он выглядел как сломанная кнопка. Прокинул getApiErrorMessage, он для
этого в репозитории и заведён и уже используется в восьми других местах.

Логику force не трогал: она совпадает с сервером. Сериализуемое is_active
собирается в _build_subscription_info как `status == active AND end_date > now`,
то есть в точности серверное свойство Subscription.is_active, по которому и
стоит защита. Разъехаться значения могут только в безопасную сторону: подписка
успела истечь между загрузкой страницы и нажатием — тогда защиты нет и на
сервере.
This commit is contained in:
Fringg
2026-08-06 02:25:02 +03:00
parent fa6b57b6ed
commit 415a76f7cf

View File

@@ -28,6 +28,7 @@ import { ActivityTab } from '../components/admin/userDetail/ActivityTab';
import { TicketsTab } from '../components/admin/userDetail/TicketsTab'; import { TicketsTab } from '../components/admin/userDetail/TicketsTab';
import { InfoTab } from '../components/admin/userDetail/InfoTab'; import { InfoTab } from '../components/admin/userDetail/InfoTab';
import { SubscriptionTab } from '../components/admin/userDetail/SubscriptionTab'; import { SubscriptionTab } from '../components/admin/userDetail/SubscriptionTab';
import { getApiErrorMessage } from '../utils/api-error';
import { toNumber } from '../utils/inputHelpers'; import { toNumber } from '../utils/inputHelpers';
import { usePermissionStore } from '../store/permissions'; import { usePermissionStore } from '../store/permissions';
@@ -673,8 +674,12 @@ export default function AdminUserDetail() {
notify.success(t('admin.users.detail.subscription.deleted'), t('common.success')); notify.success(t('admin.users.detail.subscription.deleted'), t('common.success'));
setSubscriptionDetailView(false); setSubscriptionDetailView(false);
await loadUser(); await loadUser();
} catch { } catch (err) {
notify.error(t('admin.users.userActions.error'), t('common.error')); // Отказы тут осмысленные и действенные: открытый временный доступ
// (409, «сначала заверши или восстанови grace»), активная платная без
// force (409), подписки нет (404). Общее «Ошибка» оставило бы админа
// гадать, почему кнопка не сработала, — показываем текст сервера.
notify.error(getApiErrorMessage(err, t('admin.users.userActions.error')), t('common.error'));
} finally { } finally {
setActionLoading(false); setActionLoading(false);
} }