From de7c911a7e065453e1ab5f9e8f157927b33d4a48 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 1 Feb 2026 17:19:59 +0300 Subject: [PATCH 1/3] Update Subscription.tsx --- src/pages/Subscription.tsx | 201 +++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx index a470666..0fab533 100644 --- a/src/pages/Subscription.tsx +++ b/src/pages/Subscription.tsx @@ -131,6 +131,8 @@ export default function Subscription() { // Device/traffic topup state const [showDeviceTopup, setShowDeviceTopup] = useState(false); const [devicesToAdd, setDevicesToAdd] = useState(1); + const [showDeviceReduction, setShowDeviceReduction] = useState(false); + const [targetDeviceLimit, setTargetDeviceLimit] = useState(1); const [showTrafficTopup, setShowTrafficTopup] = useState(false); const [selectedTrafficPackage, setSelectedTrafficPackage] = useState(null); const [showServerManagement, setShowServerManagement] = useState(false); @@ -287,6 +289,7 @@ export default function Subscription() { setShowPurchaseForm(false); setShowTariffPurchase(false); setShowDeviceTopup(false); + setShowDeviceReduction(false); setShowTrafficTopup(false); setShowServerManagement(false); setSwitchTariffId(null); @@ -359,6 +362,36 @@ export default function Subscription() { }, }); + // Device reduction info query + const { data: deviceReductionInfo } = useQuery({ + queryKey: ['device-reduction-info'], + queryFn: subscriptionApi.getDeviceReductionInfo, + enabled: showDeviceReduction && !!subscription, + }); + + // Initialize target device limit when reduction info loads + useEffect(() => { + if (deviceReductionInfo && showDeviceReduction) { + setTargetDeviceLimit( + Math.max( + deviceReductionInfo.min_device_limit, + deviceReductionInfo.current_device_limit - 1, + ), + ); + } + }, [deviceReductionInfo, showDeviceReduction]); + + // Device reduction mutation + const deviceReductionMutation = useMutation({ + mutationFn: () => subscriptionApi.reduceDevices(targetDeviceLimit), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['subscription'] }); + queryClient.invalidateQueries({ queryKey: ['devices'] }); + queryClient.invalidateQueries({ queryKey: ['device-reduction-info'] }); + setShowDeviceReduction(false); + }, + }); + // Traffic packages query const { data: trafficPackages } = useQuery({ queryKey: ['traffic-packages'], @@ -1155,6 +1188,174 @@ export default function Subscription() { )} + {/* Reduce Devices */} +
+ {!showDeviceReduction ? ( + + ) : ( +
+
+

+ {t('subscription.additionalOptions.reduceDevicesTitle')} +

+ +
+ + {deviceReductionInfo?.available === false ? ( +
+ {deviceReductionInfo.reason || + t('subscription.additionalOptions.reduceUnavailable')} +
+ ) : deviceReductionInfo ? ( +
+ {/* Device limit selector */} +
+ +
+
{targetDeviceLimit}
+
+ {t('subscription.additionalOptions.devicesUnit')} +
+
+ +
+ + {/* Info */} +
+
+ {t('subscription.additionalOptions.currentDeviceLimit', { + count: deviceReductionInfo.current_device_limit, + })} +
+
+ {t('subscription.additionalOptions.minDeviceLimit', { + count: deviceReductionInfo.min_device_limit, + })} +
+
+ {t('subscription.additionalOptions.connectedDevices', { + count: deviceReductionInfo.connected_devices_count, + })} +
+
+ + {/* Warning if connected devices block reduction */} + {deviceReductionInfo.connected_devices_count > + deviceReductionInfo.min_device_limit && ( +
+ {t('subscription.additionalOptions.disconnectDevicesFirst', { + count: deviceReductionInfo.connected_devices_count, + })} +
+ )} + + {/* New limit preview */} +
+
+ {t('subscription.additionalOptions.newDeviceLimit', { + count: targetDeviceLimit, + })} +
+
+ + + + {deviceReductionMutation.isError && ( +
+ {getErrorMessage(deviceReductionMutation.error)} +
+ )} +
+ ) : ( +
+ +
+ )} +
+ )} +
+ {/* Buy Traffic */} {subscription.traffic_limit_gb > 0 && (
From 8a0ff740e1afbb3bb8301dc5d0efc1c37e4380a5 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 1 Feb 2026 17:20:27 +0300 Subject: [PATCH 2/3] Add files via upload --- src/locales/en.json | 10 ++++++++++ src/locales/ru.json | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/locales/en.json b/src/locales/en.json index 2ec1b8c..95ce627 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -397,6 +397,16 @@ "devicesUnavailable": "Device purchase is not available", "devicesUnit": "devices", "buy": "Buy", + "reduceDevices": "Reduce devices", + "reduceDevicesTitle": "Reduce device limit", + "reduceDevicesDescription": "You can reduce the number of devices to the tariff minimum", + "minDeviceLimit": "Tariff minimum: {{count}} devices", + "connectedDevices": "Connected devices: {{count}}", + "disconnectDevicesFirst": "Disconnect devices first to reduce the limit below {{count}}", + "reduceUnavailable": "Device reduction is not available", + "reduce": "Reduce", + "reducing": "Reducing...", + "newDeviceLimit": "New limit: {{count}} devices", "buyTraffic": "Buy more traffic", "currentTrafficLimit": "Current limit: {{limit}} GB (used {{used}} GB)", "buyTrafficTitle": "Buy more traffic", diff --git a/src/locales/ru.json b/src/locales/ru.json index 881019a..7f2f016 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -419,6 +419,16 @@ "devicesUnavailable": "Докупка устройств недоступна", "devicesUnit": "устройств", "buy": "Купить", + "reduceDevices": "Уменьшить устройства", + "reduceDevicesTitle": "Уменьшить лимит устройств", + "reduceDevicesDescription": "Вы можете уменьшить количество устройств до минимального на тарифе", + "minDeviceLimit": "Минимум на тарифе: {{count}} устройств", + "connectedDevices": "Подключено устройств: {{count}}", + "disconnectDevicesFirst": "Сначала отключите устройства, чтобы уменьшить лимит ниже {{count}}", + "reduceUnavailable": "Уменьшение устройств недоступно", + "reduce": "Уменьшить", + "reducing": "Уменьшение...", + "newDeviceLimit": "Новый лимит: {{count}} устройств", "buyTraffic": "Докупить трафик", "currentTrafficLimit": "Текущий лимит: {{limit}} ГБ (использовано {{used}} ГБ)", "buyTrafficTitle": "Докупить трафик", From 92905770e42de754098005d2ecfe6388e451b265 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 1 Feb 2026 17:21:03 +0300 Subject: [PATCH 3/3] Update subscription.ts --- src/api/subscription.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/api/subscription.ts b/src/api/subscription.ts index a6d9911..868d517 100644 --- a/src/api/subscription.ts +++ b/src/api/subscription.ts @@ -103,6 +103,34 @@ export const subscriptionApi = { await apiClient.post('/cabinet/subscription/devices/save-cart', { devices }); }, + // Get device reduction info (for reducing device limit) + getDeviceReductionInfo: async (): Promise<{ + available: boolean; + reason?: string; + current_device_limit: number; + min_device_limit: number; + can_reduce: number; + connected_devices_count: number; + }> => { + const response = await apiClient.get('/cabinet/subscription/devices/reduction-info'); + return response.data; + }, + + // Reduce device limit + reduceDevices: async ( + newDeviceLimit: number, + ): Promise<{ + success: boolean; + message: string; + old_device_limit: number; + new_device_limit: number; + }> => { + const response = await apiClient.post('/cabinet/subscription/devices/reduce', { + new_device_limit: newDeviceLimit, + }); + return response.data; + }, + // Save traffic cart for later purchase after top-up saveTrafficCart: async (trafficGb: number): Promise => { await apiClient.post('/cabinet/subscription/traffic/save-cart', { gb: trafficGb });