From 8636bd7659b6b18cc9e19bce5bf5c6637da21d72 Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 18 Mar 2026 04:21:34 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=BB=D0=B8=D0=BC=D0=B8=D1=82=D0=B5=20+?= =?UTF-8?q?=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD=D1=8B=20=D0=B4=D1=83=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=20=D1=82=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Кнопка «Подключить устройство» заблокирована при достижении лимита - Haptic feedback при нажатии на заблокированную кнопку - Предупреждение «Отключите устройства для подключения новых» - Убран дублирующийся текст трафика под прогресс-баром (безлимит) --- .../dashboard/SubscriptionCardActive.tsx | 24 ++++++++++++++++-- .../dashboard/TrafficProgressBar.tsx | 25 +------------------ src/components/ui/hover-border-gradient.tsx | 2 ++ src/locales/en.json | 1 + src/locales/fa.json | 1 + src/locales/ru.json | 1 + src/locales/zh.json | 1 + src/styles/globals.css | 6 +++++ 8 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/components/dashboard/SubscriptionCardActive.tsx b/src/components/dashboard/SubscriptionCardActive.tsx index 64b487e..a3e09ad 100644 --- a/src/components/dashboard/SubscriptionCardActive.tsx +++ b/src/components/dashboard/SubscriptionCardActive.tsx @@ -10,6 +10,7 @@ import { useTrafficZone } from '../../hooks/useTrafficZone'; import { formatTraffic } from '../../utils/formatTraffic'; import { getGlassColors } from '../../utils/glassTheme'; import { HoverBorderGradient } from '../ui/hover-border-gradient'; +import { useHaptic } from '../../platform'; import type { Subscription } from '../../types'; interface SubscriptionCardActiveProps { @@ -58,6 +59,10 @@ export default function SubscriptionCardActive({ const isUnlimited = trafficData?.is_unlimited ?? subscription.traffic_limit_gb === 0; const zone = useTrafficZone(usedPercent); const animatedPercent = useAnimatedNumber(usedPercent); + const haptic = useHaptic(); + + const isAtDeviceLimit = + subscription.device_limit > 0 && connectedDevices >= subscription.device_limit; const formattedDate = new Date(subscription.end_date).toLocaleDateString(); const daysLeft = subscription.days_left; @@ -203,8 +208,15 @@ export default function SubscriptionCardActive({ navigate('/connection')} - className="mb-2.5 flex w-full items-center gap-3.5 rounded-[14px] p-3.5 text-left transition-shadow duration-300" + disabled={isAtDeviceLimit} + onClick={() => { + if (isAtDeviceLimit) { + haptic.notification('error'); + return; + } + navigate('/connection'); + }} + className={`mb-2.5 flex w-full items-center gap-3.5 rounded-[14px] p-3.5 text-left transition-shadow duration-300${isAtDeviceLimit ? 'cursor-not-allowed opacity-50' : ''}`} data-onboarding="connect-devices" style={{ fontFamily: 'inherit' }} > @@ -243,6 +255,14 @@ export default function SubscriptionCardActive({ max: subscription.device_limit, })} + {isAtDeviceLimit && ( +
+ {t('dashboard.deviceLimitReached')} +
+ )} {/* Device indicator */} diff --git a/src/components/dashboard/TrafficProgressBar.tsx b/src/components/dashboard/TrafficProgressBar.tsx index 61c2919..a22f773 100644 --- a/src/components/dashboard/TrafficProgressBar.tsx +++ b/src/components/dashboard/TrafficProgressBar.tsx @@ -16,7 +16,7 @@ interface TrafficProgressBarProps { const THRESHOLDS = [50, 75, 90]; export default function TrafficProgressBar({ - usedGb, + usedGb: _usedGb, limitGb, percent, isUnlimited, @@ -83,29 +83,6 @@ export default function TrafficProgressBar({ aria-hidden="true" /> - - {/* Below bar: label + usage */} - {!compact && ( -
- - - - {t('dashboard.usedTraffic', { amount: formatTraffic(usedGb) })} - -
- )} ); } diff --git a/src/components/ui/hover-border-gradient.tsx b/src/components/ui/hover-border-gradient.tsx index a4302c6..97c8ad8 100644 --- a/src/components/ui/hover-border-gradient.tsx +++ b/src/components/ui/hover-border-gradient.tsx @@ -8,6 +8,8 @@ interface HoverBorderGradientProps extends React.HTMLAttributes { accentColor?: string; /** Full rotation duration in seconds. Default: 3 */ duration?: number; + /** Disables the element (applies to interactive elements like buttons) */ + disabled?: boolean; } /** diff --git a/src/locales/en.json b/src/locales/en.json index 3056aef..26ab68c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -254,6 +254,7 @@ "daysRemaining": "Days left", "usageLast14Days": "Usage last 14 days", "maxUsage": "max {{amount}}", + "deviceLimitReached": "Disconnect devices to add new ones", "expired": { "title": "Subscription Expired", "trialTitle": "Trial Expired", diff --git a/src/locales/fa.json b/src/locales/fa.json index 494059b..75464b1 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -242,6 +242,7 @@ "devices": "دستگاه‌ها", "expiredDate": "منقضی شده" }, + "deviceLimitReached": "برای افزودن دستگاه جدید، دستگاه‌های فعلی را قطع کنید", "suspended": { "title": "اشتراک معلق شده", "resume": "از سرگیری" diff --git a/src/locales/ru.json b/src/locales/ru.json index 1046358..2fd0b67 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -266,6 +266,7 @@ "daysRemaining": "Дней осталось", "usageLast14Days": "Расход за 14 дней", "maxUsage": "макс {{amount}}", + "deviceLimitReached": "Отключите устройства для подключения новых", "expired": { "title": "Подписка истекла", "trialTitle": "Пробный период истёк", diff --git a/src/locales/zh.json b/src/locales/zh.json index 5301b10..5c6f4e9 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -242,6 +242,7 @@ "devices": "设备", "expiredDate": "过期时间" }, + "deviceLimitReached": "断开设备以添加新设备", "suspended": { "title": "订阅已暂停", "resume": "恢复" diff --git a/src/styles/globals.css b/src/styles/globals.css index 3cc0f8c..9d48068 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -35,6 +35,12 @@ box-shadow: 0 0 20px var(--accent-glow, rgba(var(--color-accent-400), 0.25)); } +.hover-border-gradient:disabled, +.hover-border-gradient[disabled] { + box-shadow: none; + pointer-events: auto; +} + .light .hover-border-gradient { --_bg: var(--border-inner-bg, rgb(var(--color-champagne-100))); }