From ea06ad1d8f7894f5460d150fa72d094617b9fbbe Mon Sep 17 00:00:00 2001 From: Fringg Date: Tue, 10 Mar 2026 01:27:36 +0300 Subject: [PATCH 1/2] fix: show fallback when tariff has no available periods for renewal When all periods are removed from a tariff, users saw an empty purchase form with no way to renew. Now shows a message explaining the situation and a button to choose a different tariff. --- src/locales/en.json | 3 +++ src/locales/fa.json | 3 +++ src/locales/ru.json | 3 +++ src/locales/zh.json | 3 +++ src/pages/SubscriptionPurchase.tsx | 27 +++++++++++++++++++++++++++ 5 files changed, 39 insertions(+) diff --git a/src/locales/en.json b/src/locales/en.json index 247732c..c3175bc 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -346,6 +346,9 @@ "daysBeforeExpiry_one": "{{count}} day before expiry", "daysBeforeExpiry_other": "{{count}} days before expiry", "selectPeriod": "Select Period", + "noPeriodsAvailable": "No available periods for renewal", + "noPeriodsAvailableHint": "The administrator has changed this plan's settings. Please choose a different plan to renew your subscription.", + "chooseDifferentTariff": "Choose a different plan", "selectTraffic": "Select Traffic", "selectServers": "Select Servers", "selectDevices": "Devices", diff --git a/src/locales/fa.json b/src/locales/fa.json index de94769..9cab3be 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -297,6 +297,9 @@ "daysBeforeExpiry": "روز قبل از انقضا", "daysBeforeExpiry_other": "{{count}} روز قبل از انقضا", "selectPeriod": "انتخاب دوره", + "noPeriodsAvailable": "دوره‌ای برای تمدید موجود نیست", + "noPeriodsAvailableHint": "مدیر تنظیمات این طرح را تغییر داده است. لطفاً طرح دیگری را برای تمدید اشتراک خود انتخاب کنید.", + "chooseDifferentTariff": "انتخاب طرح دیگر", "selectTraffic": "انتخاب ترافیک", "selectServers": "انتخاب سرورها", "selectDevices": "انتخاب دستگاه‌ها", diff --git a/src/locales/ru.json b/src/locales/ru.json index 213ad6c..88b897b 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -365,6 +365,9 @@ "daysBeforeExpiry_few": "{{count}} дня до окончания", "daysBeforeExpiry_many": "{{count}} дней до окончания", "selectPeriod": "Выберите период", + "noPeriodsAvailable": "Нет доступных периодов для продления", + "noPeriodsAvailableHint": "Администратор изменил настройки этого тарифа. Выберите другой тариф для продления подписки.", + "chooseDifferentTariff": "Выбрать другой тариф", "selectTraffic": "Выберите трафик", "selectServers": "Выберите серверы", "selectDevices": "Устройства", diff --git a/src/locales/zh.json b/src/locales/zh.json index c0b7307..d6b8206 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -297,6 +297,9 @@ "daysBeforeExpiry": "到期前天数", "daysBeforeExpiry_other": "到期前 {{count}} 天", "selectPeriod": "选择时长", + "noPeriodsAvailable": "没有可用的续订周期", + "noPeriodsAvailableHint": "管理员已更改此套餐的设置。请选择其他套餐续订您的订阅。", + "chooseDifferentTariff": "选择其他套餐", "selectTraffic": "选择流量", "selectServers": "选择服务器", "selectDevices": "选择设备", diff --git a/src/pages/SubscriptionPurchase.tsx b/src/pages/SubscriptionPurchase.tsx index 2bc0f50..84e84ba 100644 --- a/src/pages/SubscriptionPurchase.tsx +++ b/src/pages/SubscriptionPurchase.tsx @@ -1123,6 +1123,33 @@ export default function SubscriptionPurchase() { )} + {/* No periods available fallback */} + {selectedTariff.periods.length === 0 && + !useCustomDays && + !( + selectedTariff.custom_days_enabled && + (selectedTariff.price_per_day_kopeks ?? 0) > 0 + ) && ( +
+
+ {t('subscription.noPeriodsAvailable')} +
+
+ {t('subscription.noPeriodsAvailableHint')} +
+ +
+ )} + {/* Custom days option */} {selectedTariff.custom_days_enabled && (selectedTariff.price_per_day_kopeks ?? 0) > 0 && ( From 8629cfea18aab9daf818f1f6c8e250ede29054d4 Mon Sep 17 00:00:00 2001 From: Fringg Date: Tue, 10 Mar 2026 03:30:13 +0300 Subject: [PATCH 2/2] fix: daily tariff renewal uses purchaseTariff instead of renewSubscription - Expired daily tariffs now call purchaseTariff(tariff_id, 1) for 1-day purchase - Balance check uses daily_price_kopeks for daily tariffs instead of 100 kopeks minimum - Disabled daily tariffs still use togglePause for resume --- .../dashboard/SubscriptionCardExpired.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/dashboard/SubscriptionCardExpired.tsx b/src/components/dashboard/SubscriptionCardExpired.tsx index 5812312..ad76403 100644 --- a/src/components/dashboard/SubscriptionCardExpired.tsx +++ b/src/components/dashboard/SubscriptionCardExpired.tsx @@ -38,14 +38,13 @@ export default function SubscriptionCardExpired({ const formattedDate = new Date(subscription.end_date).toLocaleDateString(); - // Detect DISABLED daily subscription (suspended by system due to insufficient balance) - const isDisabledDaily = subscription.status === 'disabled' && subscription.is_daily; + // Detect daily subscription (disabled or expired) + const isDaily = subscription.is_daily; + const isDisabledDaily = subscription.status === 'disabled' && isDaily; - // For disabled daily subs, check if balance covers daily price + // For daily subs, check if balance covers daily price; otherwise 100 kopeks minimum const dailyPrice = subscription.daily_price_kopeks ?? 0; - const hasBalance = isDisabledDaily - ? balanceKopeks >= dailyPrice && dailyPrice > 0 - : balanceKopeks >= 100; + const hasBalance = isDaily ? balanceKopeks >= dailyPrice && dailyPrice > 0 : balanceKopeks >= 100; const handleQuickRenew = async () => { setIsRenewing(true); @@ -56,6 +55,9 @@ export default function SubscriptionCardExpired({ if (isDisabledDaily) { // Resume daily subscription via toggle pause endpoint await subscriptionApi.togglePause(); + } else if (isDaily && subscription.tariff_id) { + // Expired daily tariff — purchase for 1 day + await subscriptionApi.purchaseTariff(subscription.tariff_id, 1); } else { await subscriptionApi.renewSubscription(30); }