From 332849393c00fa4f7e143af2b293a2775c0a4e8e Mon Sep 17 00:00:00 2001 From: Fringg Date: Fri, 24 Jul 2026 06:45:00 +0300 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=D1=81=D1=82=D0=B0=D1=82=D1=83?= =?UTF-8?q?=D1=81=20=D0=B8=20=D0=BE=D1=82=D0=BC=D0=B5=D0=BD=D0=B0=20=D0=A1?= =?UTF-8?q?=D0=91=D0=9F-=D0=B0=D0=B2=D1=82=D0=BE=D0=BF=D1=80=D0=BE=D0=B4?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BA=D0=B5=20=D1=8E=D0=B7=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/userDetail/SubscriptionTab.tsx | 45 +++++++++++++++++++ src/pages/AdminUserDetail.tsx | 17 ++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/components/admin/userDetail/SubscriptionTab.tsx b/src/components/admin/userDetail/SubscriptionTab.tsx index d633dc8..a8b8d8b 100644 --- a/src/components/admin/userDetail/SubscriptionTab.tsx +++ b/src/components/admin/userDetail/SubscriptionTab.tsx @@ -130,6 +130,7 @@ export interface SubscriptionTabProps { onAddTraffic: (gb: number) => Promise; onRemoveTraffic: (purchaseId: number) => Promise; onResetDevices: () => Promise; + onCancelSbpRecurring: () => Promise; onDeleteDevice: (hwid: string) => Promise; onRenameDevice: (hwid: string) => Promise; onLoadDevices: () => Promise; @@ -193,6 +194,7 @@ export function SubscriptionTab(props: SubscriptionTabProps) { onAddTraffic, onRemoveTraffic, onResetDevices, + onCancelSbpRecurring, onDeleteDevice, onRenameDevice, onLoadDevices, @@ -226,6 +228,14 @@ export function SubscriptionTab(props: SubscriptionTabProps) { {sub.tariff_name || `#${sub.id}`} + {sub.sbp_recurring_status && ( + + SBP + + )} @@ -380,6 +390,41 @@ export function SubscriptionTab(props: SubscriptionTabProps) { + {/* SBP (Platega) recurring auto-payment — status comes from the admin + detail response; cancel is idempotent on the backend. */} + {selectedSub.sbp_recurring_status && ( +
+
+
+
+ {t('admin.users.detail.subscription.sbpTitle')} +
+
+ {t( + `admin.users.detail.subscription.sbpStatus_${selectedSub.sbp_recurring_status}`, + selectedSub.sbp_recurring_status, + )} +
+
+ {hasPermission('users:subscription') && ( + + )} +
+
+ )} + {/* Traffic Packages */} {selectedSub.traffic_purchases && selectedSub.traffic_purchases.length > 0 && (
diff --git a/src/pages/AdminUserDetail.tsx b/src/pages/AdminUserDetail.tsx index ba63740..db7abb9 100644 --- a/src/pages/AdminUserDetail.tsx +++ b/src/pages/AdminUserDetail.tsx @@ -648,6 +648,20 @@ export default function AdminUserDetail() { } }; + const handleCancelSbpRecurring = async () => { + if (!userId || !selectedSub?.sbp_recurring_id) return; + setActionLoading(true); + try { + await adminUsersApi.cancelSbpRecurring(userId, selectedSub.id); + notify.success(t('admin.users.detail.subscription.sbpCancelled'), t('common.success')); + await loadUser(); + } catch { + notify.error(t('admin.users.userActions.error'), t('common.error')); + } finally { + setActionLoading(false); + } + }; + const handleDisableUser = async () => { if (!userId) return; setActionLoading(true); @@ -713,7 +727,7 @@ export default function AdminUserDetail() { const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; + return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`; }; const copyToClipboard = async (text: string) => { @@ -859,6 +873,7 @@ export default function AdminUserDetail() {