fix: preserve subscription context in navigation and cache keys

SubscriptionCardActive: passes ?sub= to /connection navigation.
Connection: QR state includes subscriptionId for back-navigation.
ConnectionQR: preserves subscription context on back button.
Dashboard + Subscription: localStorage traffic cache keyed per-subscription
to prevent cooldown bleed between different subscriptions.
This commit is contained in:
c0mrade
2026-03-25 11:47:31 +03:00
parent 4de47cfa94
commit fd01c0f393
5 changed files with 20 additions and 10 deletions

View File

@@ -214,7 +214,7 @@ export default function SubscriptionCardActive({
haptic.notification('error'); haptic.notification('error');
return; return;
} }
navigate('/connection'); navigate(`/connection?sub=${subscription.id}`);
}} }}
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' : ''}`} 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"

View File

@@ -43,9 +43,10 @@ export default function Connection() {
state: { state: {
url: appConfig?.subscriptionUrl, url: appConfig?.subscriptionUrl,
hideLink: appConfig?.hideLink ?? false, hideLink: appConfig?.hideLink ?? false,
subscriptionId: subId,
}, },
}); });
}, [navigate, appConfig?.subscriptionUrl, appConfig?.hideLink, isTelegramWebApp]); }, [navigate, appConfig?.subscriptionUrl, appConfig?.hideLink, isTelegramWebApp, subId]);
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {

View File

@@ -8,6 +8,7 @@ import { AdminBackButton } from '@/components/admin';
interface ConnectionQRState { interface ConnectionQRState {
url: string; url: string;
hideLink: boolean; hideLink: boolean;
subscriptionId?: number;
} }
function isValidState(state: unknown): state is ConnectionQRState { function isValidState(state: unknown): state is ConnectionQRState {
@@ -24,12 +25,14 @@ export default function ConnectionQR() {
const state = location.state as unknown; const state = location.state as unknown;
const validState = isValidState(state) ? state : null; const validState = isValidState(state) ? state : null;
const subId = validState?.subscriptionId;
const connectionPath = subId ? `/connection?sub=${subId}` : '/connection';
useEffect(() => { useEffect(() => {
if (!validState) { if (!validState) {
navigate('/connection', { replace: true }); navigate(connectionPath, { replace: true });
} }
}, [validState, navigate]); }, [validState, navigate, connectionPath]);
if (!validState) { if (!validState) {
return null; return null;
@@ -38,7 +41,7 @@ export default function ConnectionQR() {
return ( return (
<div className="animate-fade-in"> <div className="animate-fade-in">
<div className="mb-6 flex items-center gap-3"> <div className="mb-6 flex items-center gap-3">
<AdminBackButton to="/connection" replace /> <AdminBackButton to={connectionPath} replace />
<h1 className="text-2xl font-bold text-dark-100">{t('subscription.connection.qrTitle')}</h1> <h1 className="text-2xl font-bold text-dark-100">{t('subscription.connection.qrTitle')}</h1>
</div> </div>

View File

@@ -141,7 +141,10 @@ export default function Dashboard() {
traffic_used_percent: data.traffic_used_percent, traffic_used_percent: data.traffic_used_percent,
is_unlimited: data.is_unlimited, is_unlimited: data.is_unlimited,
}); });
localStorage.setItem('traffic_refresh_ts', Date.now().toString()); localStorage.setItem(
`traffic_refresh_ts_${subscription?.id ?? 'default'}`,
Date.now().toString(),
);
if (data.rate_limited && data.retry_after_seconds) { if (data.rate_limited && data.retry_after_seconds) {
setTrafficRefreshCooldown(data.retry_after_seconds); setTrafficRefreshCooldown(data.retry_after_seconds);
} else { } else {
@@ -176,7 +179,7 @@ export default function Dashboard() {
if (hasAutoRefreshed.current) return; if (hasAutoRefreshed.current) return;
hasAutoRefreshed.current = true; hasAutoRefreshed.current = true;
const lastRefresh = localStorage.getItem('traffic_refresh_ts'); const lastRefresh = localStorage.getItem(`traffic_refresh_ts_${subscription?.id ?? 'default'}`);
const now = Date.now(); const now = Date.now();
const cacheMs = API.TRAFFIC_CACHE_MS; const cacheMs = API.TRAFFIC_CACHE_MS;

View File

@@ -391,7 +391,10 @@ export default function Subscription() {
traffic_used_percent: data.traffic_used_percent, traffic_used_percent: data.traffic_used_percent,
is_unlimited: data.is_unlimited, is_unlimited: data.is_unlimited,
}); });
localStorage.setItem('traffic_refresh_ts', Date.now().toString()); localStorage.setItem(
`traffic_refresh_ts_${subscriptionId ?? 'default'}`,
Date.now().toString(),
);
if (data.rate_limited && data.retry_after_seconds) { if (data.rate_limited && data.retry_after_seconds) {
setTrafficRefreshCooldown(data.retry_after_seconds); setTrafficRefreshCooldown(data.retry_after_seconds);
} else { } else {
@@ -427,7 +430,7 @@ export default function Subscription() {
if (hasAutoRefreshed.current) return; if (hasAutoRefreshed.current) return;
hasAutoRefreshed.current = true; hasAutoRefreshed.current = true;
const lastRefresh = localStorage.getItem('traffic_refresh_ts'); const lastRefresh = localStorage.getItem(`traffic_refresh_ts_${subscriptionId ?? 'default'}`);
const now = Date.now(); const now = Date.now();
const cacheMs = 30 * 1000; const cacheMs = 30 * 1000;
@@ -441,7 +444,7 @@ export default function Subscription() {
} }
refreshTrafficMutation.mutate(); refreshTrafficMutation.mutate();
}, [subscription, refreshTrafficMutation]); }, [subscription, refreshTrafficMutation, subscriptionId]);
const copyUrl = () => { const copyUrl = () => {
if (subscription?.subscription_url) { if (subscription?.subscription_url) {