mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
feat: блокировка кнопки устройств при лимите + убраны дубли трафика
- Кнопка «Подключить устройство» заблокирована при достижении лимита - Haptic feedback при нажатии на заблокированную кнопку - Предупреждение «Отключите устройства для подключения новых» - Убран дублирующийся текст трафика под прогресс-баром (безлимит)
This commit is contained in:
@@ -10,6 +10,7 @@ import { useTrafficZone } from '../../hooks/useTrafficZone';
|
|||||||
import { formatTraffic } from '../../utils/formatTraffic';
|
import { formatTraffic } from '../../utils/formatTraffic';
|
||||||
import { getGlassColors } from '../../utils/glassTheme';
|
import { getGlassColors } from '../../utils/glassTheme';
|
||||||
import { HoverBorderGradient } from '../ui/hover-border-gradient';
|
import { HoverBorderGradient } from '../ui/hover-border-gradient';
|
||||||
|
import { useHaptic } from '../../platform';
|
||||||
import type { Subscription } from '../../types';
|
import type { Subscription } from '../../types';
|
||||||
|
|
||||||
interface SubscriptionCardActiveProps {
|
interface SubscriptionCardActiveProps {
|
||||||
@@ -58,6 +59,10 @@ export default function SubscriptionCardActive({
|
|||||||
const isUnlimited = trafficData?.is_unlimited ?? subscription.traffic_limit_gb === 0;
|
const isUnlimited = trafficData?.is_unlimited ?? subscription.traffic_limit_gb === 0;
|
||||||
const zone = useTrafficZone(usedPercent);
|
const zone = useTrafficZone(usedPercent);
|
||||||
const animatedPercent = useAnimatedNumber(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 formattedDate = new Date(subscription.end_date).toLocaleDateString();
|
||||||
const daysLeft = subscription.days_left;
|
const daysLeft = subscription.days_left;
|
||||||
@@ -203,8 +208,15 @@ export default function SubscriptionCardActive({
|
|||||||
<HoverBorderGradient
|
<HoverBorderGradient
|
||||||
as="button"
|
as="button"
|
||||||
accentColor={zone.mainHex}
|
accentColor={zone.mainHex}
|
||||||
onClick={() => navigate('/connection')}
|
disabled={isAtDeviceLimit}
|
||||||
className="mb-2.5 flex w-full items-center gap-3.5 rounded-[14px] p-3.5 text-left transition-shadow duration-300"
|
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"
|
data-onboarding="connect-devices"
|
||||||
style={{ fontFamily: 'inherit' }}
|
style={{ fontFamily: 'inherit' }}
|
||||||
>
|
>
|
||||||
@@ -243,6 +255,14 @@ export default function SubscriptionCardActive({
|
|||||||
max: subscription.device_limit,
|
max: subscription.device_limit,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{isAtDeviceLimit && (
|
||||||
|
<div
|
||||||
|
className="mt-1 text-[10px] font-medium"
|
||||||
|
style={{ color: 'rgb(var(--color-warning-400))' }}
|
||||||
|
>
|
||||||
|
{t('dashboard.deviceLimitReached')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Device indicator */}
|
{/* Device indicator */}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface TrafficProgressBarProps {
|
|||||||
const THRESHOLDS = [50, 75, 90];
|
const THRESHOLDS = [50, 75, 90];
|
||||||
|
|
||||||
export default function TrafficProgressBar({
|
export default function TrafficProgressBar({
|
||||||
usedGb,
|
usedGb: _usedGb,
|
||||||
limitGb,
|
limitGb,
|
||||||
percent,
|
percent,
|
||||||
isUnlimited,
|
isUnlimited,
|
||||||
@@ -83,29 +83,6 @@ export default function TrafficProgressBar({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Below bar: label + usage */}
|
|
||||||
{!compact && (
|
|
||||||
<div className="mt-2 flex items-center justify-between px-0.5">
|
|
||||||
<span
|
|
||||||
className="flex items-center gap-1.5 text-[11px] font-semibold"
|
|
||||||
style={{ color: zone.mainVar }}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="inline-block h-1.5 w-1.5 animate-unlimited-pulse rounded-full"
|
|
||||||
style={{
|
|
||||||
background: zone.mainVar,
|
|
||||||
boxShadow: `0 0 8px ${zone.mainVar}`,
|
|
||||||
}}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{t('dashboard.unlimitedTraffic')}
|
|
||||||
</span>
|
|
||||||
<span className="font-mono text-[11px] text-dark-50/30">
|
|
||||||
{t('dashboard.usedTraffic', { amount: formatTraffic(usedGb) })}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ interface HoverBorderGradientProps extends React.HTMLAttributes<HTMLElement> {
|
|||||||
accentColor?: string;
|
accentColor?: string;
|
||||||
/** Full rotation duration in seconds. Default: 3 */
|
/** Full rotation duration in seconds. Default: 3 */
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
/** Disables the element (applies to interactive elements like buttons) */
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -254,6 +254,7 @@
|
|||||||
"daysRemaining": "Days left",
|
"daysRemaining": "Days left",
|
||||||
"usageLast14Days": "Usage last 14 days",
|
"usageLast14Days": "Usage last 14 days",
|
||||||
"maxUsage": "max {{amount}}",
|
"maxUsage": "max {{amount}}",
|
||||||
|
"deviceLimitReached": "Disconnect devices to add new ones",
|
||||||
"expired": {
|
"expired": {
|
||||||
"title": "Subscription Expired",
|
"title": "Subscription Expired",
|
||||||
"trialTitle": "Trial Expired",
|
"trialTitle": "Trial Expired",
|
||||||
|
|||||||
@@ -242,6 +242,7 @@
|
|||||||
"devices": "دستگاهها",
|
"devices": "دستگاهها",
|
||||||
"expiredDate": "منقضی شده"
|
"expiredDate": "منقضی شده"
|
||||||
},
|
},
|
||||||
|
"deviceLimitReached": "برای افزودن دستگاه جدید، دستگاههای فعلی را قطع کنید",
|
||||||
"suspended": {
|
"suspended": {
|
||||||
"title": "اشتراک معلق شده",
|
"title": "اشتراک معلق شده",
|
||||||
"resume": "از سرگیری"
|
"resume": "از سرگیری"
|
||||||
|
|||||||
@@ -266,6 +266,7 @@
|
|||||||
"daysRemaining": "Дней осталось",
|
"daysRemaining": "Дней осталось",
|
||||||
"usageLast14Days": "Расход за 14 дней",
|
"usageLast14Days": "Расход за 14 дней",
|
||||||
"maxUsage": "макс {{amount}}",
|
"maxUsage": "макс {{amount}}",
|
||||||
|
"deviceLimitReached": "Отключите устройства для подключения новых",
|
||||||
"expired": {
|
"expired": {
|
||||||
"title": "Подписка истекла",
|
"title": "Подписка истекла",
|
||||||
"trialTitle": "Пробный период истёк",
|
"trialTitle": "Пробный период истёк",
|
||||||
|
|||||||
@@ -242,6 +242,7 @@
|
|||||||
"devices": "设备",
|
"devices": "设备",
|
||||||
"expiredDate": "过期时间"
|
"expiredDate": "过期时间"
|
||||||
},
|
},
|
||||||
|
"deviceLimitReached": "断开设备以添加新设备",
|
||||||
"suspended": {
|
"suspended": {
|
||||||
"title": "订阅已暂停",
|
"title": "订阅已暂停",
|
||||||
"resume": "恢复"
|
"resume": "恢复"
|
||||||
|
|||||||
@@ -35,6 +35,12 @@
|
|||||||
box-shadow: 0 0 20px var(--accent-glow, rgba(var(--color-accent-400), 0.25));
|
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 {
|
.light .hover-border-gradient {
|
||||||
--_bg: var(--border-inner-bg, rgb(var(--color-champagne-100)));
|
--_bg: var(--border-inner-bg, rgb(var(--color-champagne-100)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user