From 821e991f51db6033d3e0f2befecf15c364d0e3e8 Mon Sep 17 00:00:00 2001 From: Fringg Date: Fri, 27 Feb 2026 00:53:50 +0300 Subject: [PATCH] fix: block wheel spin without active subscription - Add has_subscription to WheelConfig type - Disable spin button when user has no subscription - Add noSubscription guard in handleUnifiedSpin handler - Show warning banner directing user to activate subscription - Prevent banner stacking (noSubscription takes priority over daily limit) - Add noSubscription i18n keys for ru, en, zh, fa --- src/api/wheel.ts | 1 + src/locales/en.json | 3 ++- src/locales/fa.json | 3 ++- src/locales/ru.json | 3 ++- src/locales/zh.json | 3 ++- src/pages/Wheel.tsx | 43 +++++++++++++++++++++++++++++-------------- 6 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/api/wheel.ts b/src/api/wheel.ts index a507b77..1c36d94 100644 --- a/src/api/wheel.ts +++ b/src/api/wheel.ts @@ -26,6 +26,7 @@ export interface WheelConfig { can_pay_days: boolean; user_balance_kopeks: number; required_balance_kopeks: number; + has_subscription: boolean; } export interface SpinAvailability { diff --git a/src/locales/en.json b/src/locales/en.json index e91b0dd..15f991c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -858,7 +858,8 @@ "cannotSpin": "Cannot spin right now", "insufficientBalance": "Insufficient balance", "topUpRequired": "Top up your balance to pay for spin", - "starsNotAvailable": "Stars payment not available. Open the app via Telegram." + "starsNotAvailable": "Stars payment not available. Open the app via Telegram.", + "noSubscription": "An active subscription is required to spin the wheel. Activate your subscription first." }, "payWithStars": "Pay with Stars", "payWithDays": "Pay with days", diff --git a/src/locales/fa.json b/src/locales/fa.json index 838ad66..f09239c 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -694,7 +694,8 @@ "cannotSpin": "فعلاً امکان چرخش نیست", "insufficientBalance": "موجودی کافی نیست", "topUpRequired": "برای پرداخت شارژ کنید", - "starsNotAvailable": "پرداخت Stars در دسترس نیست. برنامه را از طریق تلگرام باز کنید." + "starsNotAvailable": "پرداخت Stars در دسترس نیست. برنامه را از طریق تلگرام باز کنید.", + "noSubscription": "برای چرخاندن گردونه نیاز به اشتراک فعال دارید. ابتدا اشتراک خود را فعال کنید." }, "payWithStars": "پرداخت {stars} Stars", "processingPayment": "در حال پردازش...", diff --git a/src/locales/ru.json b/src/locales/ru.json index 0326819..f4b01bb 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -887,7 +887,8 @@ "cannotSpin": "Сейчас нельзя крутить", "insufficientBalance": "Недостаточно средств на балансе", "topUpRequired": "Пополните баланс для оплаты спина", - "starsNotAvailable": "Оплата Stars недоступна. Откройте приложение через Telegram." + "starsNotAvailable": "Оплата Stars недоступна. Откройте приложение через Telegram.", + "noSubscription": "Для вращения колеса необходима активная подписка. Активируйте подписку в разделе «Подписка»." }, "payWithStars": "Оплатить Stars", "payWithDays": "Оплатить днями", diff --git a/src/locales/zh.json b/src/locales/zh.json index da947c9..5f6537f 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -698,7 +698,8 @@ "cannotSpin": "当前无法抽奖", "insufficientBalance": "余额不足", "topUpRequired": "请充值以支付抽奖费用", - "starsNotAvailable": "Stars支付不可用。请通过Telegram打开应用。" + "starsNotAvailable": "Stars支付不可用。请通过Telegram打开应用。", + "noSubscription": "需要有效订阅才能转动轮盘。请先激活订阅。" }, "payWithStars": "支付 {stars} Stars", "processingPayment": "处理中...", diff --git a/src/pages/Wheel.tsx b/src/pages/Wheel.tsx index 63b2dcc..9540793 100644 --- a/src/pages/Wheel.tsx +++ b/src/pages/Wheel.tsx @@ -403,6 +403,7 @@ export default function Wheel() { }; const handleUnifiedSpin = () => { + if (noSubscription) return; if (paymentType === 'telegram_stars') { if (!config?.spin_cost_stars_enabled || !config?.spin_cost_stars) { notify.warning(t('wheel.starsNotAvailable')); @@ -505,10 +506,12 @@ export default function Wheel() { // Stars via Telegram invoice don't require ruble balance, so only check daily limit const dailyLimitReached = config.daily_limit > 0 && config.user_spins_today >= config.daily_limit; + const noSubscription = !config.has_subscription; const spinDisabled = isSpinning || isPayingStars || dailyLimitReached || + noSubscription || (paymentType === 'telegram_stars' ? !starsEnabled : !config.can_spin); return ( @@ -614,22 +617,34 @@ export default function Wheel() { )} + {/* No subscription hint */} + {!isSpinning && noSubscription && ( +
+

{t('wheel.errors.noSubscription')}

+
+ )} {/* Cannot spin hint — only show for days payment (Stars via invoice always works) */} - {!isSpinning && paymentType !== 'telegram_stars' && !config.can_spin && ( -
-

- {config.can_spin_reason === 'daily_limit_reached' - ? t('wheel.errors.dailyLimitReached') - : t('wheel.errors.cannotSpin')} -

-
- )} + {!isSpinning && + !noSubscription && + paymentType !== 'telegram_stars' && + !config.can_spin && ( +
+

+ {config.can_spin_reason === 'daily_limit_reached' + ? t('wheel.errors.dailyLimitReached') + : t('wheel.errors.cannotSpin')} +

+
+ )} {/* Daily limit hint for Stars payment (not covered by can_spin check) */} - {!isSpinning && paymentType === 'telegram_stars' && dailyLimitReached && ( -
-

{t('wheel.errors.dailyLimitReached')}

-
- )} + {!isSpinning && + !noSubscription && + paymentType === 'telegram_stars' && + dailyLimitReached && ( +
+

{t('wheel.errors.dailyLimitReached')}

+
+ )} {/* Inline Result Card */} {spinResult && !isSpinning && (