-
- 🛡️
- No active subscription
- Purchase a plan or activate the trial in the bot to start using VPN.
- Your balance and other sections remain available below.
-
-
-
- A trial is available — activate it in the bot to try the service for free.
-
+
U
@@ -5050,14 +4972,6 @@
'app.loading': 'Loading your subscription...',
'error.default.title': 'Subscription Not Found',
'error.default.message': 'Please contact support to activate your subscription.',
- 'empty.no_subscription.title': 'No active subscription',
- 'empty.no_subscription.subtitle': 'Purchase a plan or activate the trial in the bot to start using VPN.',
- 'empty.no_subscription.info': 'Your balance and other sections remain available below.',
- 'empty.no_subscription.action.open_bot': 'Open bot',
- 'empty.no_subscription.trial_hint': 'A trial is available — activate it in the bot to try the service for free.',
- 'registration.title': 'Start in Telegram',
- 'registration.subtitle': 'Open the bot and complete registration to access the mini app.',
- 'registration.action': 'Open bot',
'stats.days_left': 'Days left',
'stats.servers': 'Servers',
'stats.devices': 'Devices',
@@ -5367,10 +5281,8 @@
'status.expired': 'Expired',
'status.disabled': 'Disabled',
'status.unknown': 'Unknown',
- 'status.none': 'No subscription',
'subscription.type.trial': 'Trial',
'subscription.type.paid': 'Paid',
- 'subscription.type.none': 'No subscription',
'autopay.enabled': 'Enabled',
'autopay.disabled': 'Disabled',
'platform.ios': 'iOS',
@@ -5415,14 +5327,6 @@
'app.loading': 'Загружаем вашу подписку...',
'error.default.title': 'Подписка не найдена',
'error.default.message': 'Свяжитесь с поддержкой, чтобы активировать подписку.',
- 'empty.no_subscription.title': 'Подписка не активна',
- 'empty.no_subscription.subtitle': 'Оформите подписку или активируйте пробный период в боте, чтобы пользоваться VPN.',
- 'empty.no_subscription.info': 'Ваш баланс и другие разделы доступны ниже.',
- 'empty.no_subscription.action.open_bot': 'Открыть бота',
- 'empty.no_subscription.trial_hint': 'Доступен пробный период — активируйте его в боте, чтобы протестировать сервис бесплатно.',
- 'registration.title': 'Начните в Telegram',
- 'registration.subtitle': 'Откройте бота и зарегистрируйтесь, чтобы пользоваться мини-приложением.',
- 'registration.action': 'Открыть бота',
'stats.days_left': 'Осталось дней',
'stats.servers': 'Серверы',
'stats.devices': 'Устройства',
@@ -5732,10 +5636,8 @@
'status.expired': 'Истекла',
'status.disabled': 'Отключена',
'status.unknown': 'Неизвестно',
- 'status.none': 'Нет подписки',
'subscription.type.trial': 'Триал',
'subscription.type.paid': 'Платная',
- 'subscription.type.none': 'Нет подписки',
'autopay.enabled': 'Включен',
'autopay.disabled': 'Выключен',
'platform.ios': 'iOS',
@@ -5886,7 +5788,6 @@
let preferredLanguage = 'en';
let languageLockedByUser = false;
let currentErrorState = null;
- let currentRegistrationState = null;
let paymentMethodsCache = null;
let paymentMethodsPromise = null;
let activePaymentMethod = null;
@@ -6559,8 +6460,6 @@
function getEffectivePurchaseUrl() {
const candidates = [
currentErrorState?.purchaseUrl,
- currentRegistrationState?.purchaseUrl,
- userData?.empty_state?.purchase_url,
subscriptionPurchaseUrl,
configPurchaseUrl,
];
@@ -6617,8 +6516,6 @@
languageSelect.setAttribute('aria-label', t('language.ariaLabel'));
}
updateErrorTexts();
- renderRegistrationState();
- renderNoSubscriptionState();
}
function updateConnectButtonLabel() {
@@ -6674,7 +6571,7 @@
setLanguage(event.target.value, { persist: true });
});
- function createError(title, message, status, code) {
+ function createError(title, message, status) {
const error = new Error(message || title);
if (title) {
error.title = title;
@@ -6682,9 +6579,6 @@
if (status) {
error.status = status;
}
- if (code) {
- error.code = code;
- }
return error;
}
@@ -6728,7 +6622,6 @@
: 'Subscription not found';
let title = response.status === 401 ? 'Authorization Error' : 'Subscription Not Found';
let purchaseUrl = null;
- let code = null;
try {
const errorPayload = await response.json();
@@ -6739,9 +6632,6 @@
if (typeof errorPayload.detail.message === 'string') {
detail = errorPayload.detail.message;
}
- if (typeof errorPayload.detail.code === 'string') {
- code = errorPayload.detail.code;
- }
purchaseUrl = errorPayload.detail.purchase_url
|| errorPayload.detail.purchaseUrl
|| purchaseUrl;
@@ -6754,10 +6644,6 @@
title = errorPayload.title;
}
- if (typeof errorPayload?.code === 'string' && !code) {
- code = errorPayload.code;
- }
-
purchaseUrl = purchaseUrl
|| errorPayload?.purchase_url
|| errorPayload?.purchaseUrl
@@ -6766,7 +6652,7 @@
// ignore JSON parsing errors
}
- const errorObject = createError(title, detail, response.status, code);
+ const errorObject = createError(title, detail, response.status);
const normalizedPurchaseUrl = normalizeUrl(purchaseUrl);
if (normalizedPurchaseUrl) {
errorObject.purchaseUrl = normalizedPurchaseUrl;
@@ -6784,29 +6670,6 @@
userData.subscriptionCryptoLink = userData.subscription_crypto_link || null;
userData.referral = userData.referral || null;
- const registrationStateElement = document.getElementById('registrationState');
- currentRegistrationState = null;
- registrationStateElement?.classList.add('hidden');
- renderRegistrationState();
-
- const normalizedState = typeof userData.state === 'string'
- ? userData.state.toLowerCase()
- : 'subscription';
- userData.state = normalizedState;
-
- const rawEmptyState = userData.empty_state || userData.emptyState;
- if (rawEmptyState && typeof rawEmptyState === 'object') {
- const emptyState = { ...rawEmptyState };
- const purchaseUrl = emptyState.purchase_url || emptyState.purchaseUrl;
- emptyState.purchase_url = normalizeUrl(purchaseUrl);
- emptyState.trial_available = Boolean(
- emptyState.trial_available ?? emptyState.trialAvailable
- );
- userData.empty_state = emptyState;
- } else {
- userData.empty_state = null;
- }
-
resetSubscriptionRenewalState(null);
prepareSubscriptionRenewalFromUserData();
@@ -6921,237 +6784,100 @@
}
}
- function renderNoSubscriptionState() {
- const block = document.getElementById('noSubscriptionState');
- if (!block) {
- return;
- }
-
- const stateValue = String(userData?.state || '').toLowerCase();
- const isNoSubscription = stateValue === 'no_subscription';
- block.classList.toggle('hidden', !isNoSubscription);
- if (!isNoSubscription) {
- const actionBtn = document.getElementById('noSubscriptionActionBtn');
- if (actionBtn) {
- actionBtn.dataset.link = '';
- }
- return;
- }
-
- const emptyState = userData?.empty_state || {};
-
- const titleElement = document.getElementById('noSubscriptionTitle');
- if (titleElement) {
- const key = 'empty.no_subscription.title';
- const translation = t(key);
- titleElement.textContent = translation === key
- ? (emptyState.title || 'No active subscription')
- : translation;
- }
-
- const textElement = document.getElementById('noSubscriptionText');
- if (textElement) {
- const key = 'empty.no_subscription.subtitle';
- const translation = t(key);
- textElement.textContent = translation === key
- ? (emptyState.message || 'Purchase a plan or activate the trial in the bot to start using VPN.')
- : translation;
- }
-
- const noteElement = document.getElementById('noSubscriptionNote');
- if (noteElement) {
- const key = 'empty.no_subscription.info';
- const translation = t(key);
- if (translation && translation !== key) {
- noteElement.textContent = translation;
- }
- }
-
- const purchaseCandidates = [
- emptyState.purchase_url,
- subscriptionPurchaseUrl,
- configPurchaseUrl,
- ];
- const purchaseLink = purchaseCandidates
- .map(candidate => normalizeUrl(candidate))
- .find(Boolean) || null;
-
- const actionButton = document.getElementById('noSubscriptionActionBtn');
- if (actionButton) {
- const key = 'empty.no_subscription.action.open_bot';
- const translation = t(key);
- actionButton.textContent = translation === key ? 'Open bot' : translation;
- actionButton.dataset.link = purchaseLink || '';
- actionButton.disabled = !purchaseLink;
- actionButton.classList.toggle('hidden', !purchaseLink);
- }
-
- const trialHint = document.getElementById('noSubscriptionTrialHint');
- if (trialHint) {
- const trialAvailable = Boolean(emptyState.trial_available);
- trialHint.classList.toggle('hidden', !trialAvailable);
- if (trialAvailable) {
- const key = 'empty.no_subscription.trial_hint';
- const translation = t(key);
- trialHint.textContent = translation === key
- ? 'A trial is available — activate it in the bot to try the service for free.'
- : translation;
- }
- }
- }
-
- function renderRegistrationState() {
- const state = document.getElementById('registrationState');
- if (!state) {
- return;
- }
-
- const isActive = Boolean(currentRegistrationState);
- state.classList.toggle('hidden', !isActive);
- if (!isActive) {
- const button = document.getElementById('registrationActionBtn');
- if (button) {
- button.dataset.link = '';
- }
- return;
- }
-
- const titleKey = 'registration.title';
- const subtitleKey = 'registration.subtitle';
- const actionKey = 'registration.action';
-
- const titleElement = state.querySelector('[data-i18n="registration.title"]');
- if (titleElement) {
- const translation = t(titleKey);
- titleElement.textContent = translation === titleKey
- ? 'Start in Telegram'
- : translation;
- }
-
- const textElement = state.querySelector('[data-i18n="registration.subtitle"]');
- if (textElement) {
- const translation = t(subtitleKey);
- textElement.textContent = translation === subtitleKey
- ? 'Open the bot and complete registration to access the mini app.'
- : translation;
- }
-
- const button = document.getElementById('registrationActionBtn');
- if (button) {
- const translation = t(actionKey);
- button.textContent = translation === actionKey ? 'Open bot' : translation;
- const link = normalizeUrl(currentRegistrationState?.purchaseUrl)
- || getEffectivePurchaseUrl();
- button.dataset.link = link || '';
- button.disabled = !link;
- button.classList.toggle('hidden', !link);
- }
- }
-
function renderUserData() {
if (!userData?.user) {
return;
}
- const isNoSubscription = String(userData?.state || '').toLowerCase() === 'no_subscription';
- const userCard = document.getElementById('userCard');
- if (userCard) {
- userCard.classList.toggle('hidden', isNoSubscription);
+ const user = userData.user;
+ const rawName = user.display_name || user.username || '';
+ const fallbackName = rawName
+ || [user.first_name, user.last_name].filter(Boolean).join(' ')
+ || `User ${user.telegram_id || ''}`.trim();
+ const avatarChar = (fallbackName.replace(/^@/, '')[0] || 'U').toUpperCase();
+
+ document.getElementById('userAvatar').textContent = avatarChar;
+ document.getElementById('userName').textContent = fallbackName;
+
+ const knownStatuses = ['active', 'trial', 'expired', 'disabled'];
+ const statusValueRaw = (user.subscription_actual_status || user.subscription_status || 'active').toLowerCase();
+ const statusClass = knownStatuses.includes(statusValueRaw) ? statusValueRaw : 'unknown';
+ const statusBadge = document.getElementById('statusBadge');
+ const statusKey = `status.${statusClass}`;
+ const statusLabel = t(statusKey);
+ statusBadge.textContent = statusLabel === statusKey
+ ? (user.status_label || statusClass.charAt(0).toUpperCase() + statusClass.slice(1))
+ : statusLabel;
+ statusBadge.className = `status-badge status-${statusClass}`;
+
+ const expiresAt = user.expires_at ? new Date(user.expires_at) : null;
+ let daysLeft = '—';
+ if (expiresAt && !Number.isNaN(expiresAt.getTime())) {
+ const diffDays = Math.ceil((expiresAt - new Date()) / (1000 * 60 * 60 * 24));
+ daysLeft = diffDays > 0 ? diffDays : '0';
}
+ document.getElementById('daysLeft').textContent = daysLeft;
+ document.getElementById('expiresAt').textContent = formatDate(user.expires_at);
- if (!isNoSubscription) {
- const user = userData.user;
- const rawName = user.display_name || user.username || '';
- const fallbackName = rawName
- || [user.first_name, user.last_name].filter(Boolean).join(' ')
- || `User ${user.telegram_id || ''}`.trim();
- const avatarChar = (fallbackName.replace(/^@/, '')[0] || 'U').toUpperCase();
-
- document.getElementById('userAvatar').textContent = avatarChar;
- document.getElementById('userName').textContent = fallbackName;
-
- const knownStatuses = ['active', 'trial', 'expired', 'disabled'];
- const statusValueRaw = (user.subscription_actual_status || user.subscription_status || 'active').toLowerCase();
- const statusClass = knownStatuses.includes(statusValueRaw) ? statusValueRaw : 'unknown';
- const statusBadge = document.getElementById('statusBadge');
- const statusKey = `status.${statusClass}`;
- const statusLabel = t(statusKey);
- statusBadge.textContent = statusLabel === statusKey
- ? (user.status_label || statusClass.charAt(0).toUpperCase() + statusClass.slice(1))
- : statusLabel;
- statusBadge.className = `status-badge status-${statusClass}`;
-
- const expiresAt = user.expires_at ? new Date(user.expires_at) : null;
- let daysLeft = '—';
- if (expiresAt && !Number.isNaN(expiresAt.getTime())) {
- const diffDays = Math.ceil((expiresAt - new Date()) / (1000 * 60 * 60 * 24));
- daysLeft = diffDays > 0 ? diffDays : '0';
- }
- document.getElementById('daysLeft').textContent = daysLeft;
- document.getElementById('expiresAt').textContent = formatDate(user.expires_at);
-
- const serversCount = Array.isArray(userData.connected_squads)
- ? userData.connected_squads.length
- : Array.isArray(userData.connected_servers)
- ? userData.connected_servers.length
- : Array.isArray(userData.links)
- ? userData.links.length
- : 0;
- document.getElementById('serversCount').textContent = serversCount;
-
- const devicesCountRaw = Number(userData?.connected_devices_count);
- const devicesCount = Number.isFinite(devicesCountRaw)
- ? devicesCountRaw
- : Array.isArray(userData?.connected_devices)
- ? userData.connected_devices.length
+ const serversCount = Array.isArray(userData.connected_squads)
+ ? userData.connected_squads.length
+ : Array.isArray(userData.connected_servers)
+ ? userData.connected_servers.length
+ : Array.isArray(userData.links)
+ ? userData.links.length
: 0;
- const devicesCountElement = document.getElementById('devicesCount');
- if (devicesCountElement) {
- devicesCountElement.textContent = devicesCount;
- }
+ document.getElementById('serversCount').textContent = serversCount;
- document.getElementById('trafficUsed').textContent =
- user.traffic_used_label || formatTraffic(user.traffic_used_gb);
- document.getElementById('trafficLimit').textContent =
- user.traffic_limit_label || formatTrafficLimit(user.traffic_limit_gb);
-
- const deviceLimitElement = document.getElementById('deviceLimit');
- if (deviceLimitElement) {
- const limitValue = typeof user.device_limit === 'number'
- ? user.device_limit
- : Number.parseInt(user.device_limit ?? '', 10);
- deviceLimitElement.textContent = Number.isFinite(limitValue)
- ? String(limitValue)
- : t('values.not_available');
- }
-
- const subscriptionTypeElement = document.getElementById('subscriptionType');
- if (subscriptionTypeElement) {
- const fallbackSubscriptionType = (user?.subscription_status || '').toLowerCase() === 'trial'
- ? 'trial'
- : 'paid';
- const subscriptionTypeRaw = String(
- userData?.subscription_type
- || fallbackSubscriptionType
- ).toLowerCase();
- const subscriptionTypeKey = `subscription.type.${subscriptionTypeRaw}`;
- const subscriptionTypeLabel = t(subscriptionTypeKey);
- subscriptionTypeElement.textContent = subscriptionTypeLabel === subscriptionTypeKey
- ? subscriptionTypeRaw
- : subscriptionTypeLabel;
- }
-
- const autopayElement = document.getElementById('autopayStatus');
- if (autopayElement) {
- const autopayKey = userData?.autopay_enabled ? 'autopay.enabled' : 'autopay.disabled';
- const autopayLabel = t(autopayKey);
- autopayElement.textContent = autopayLabel === autopayKey
- ? (userData?.autopay_enabled ? 'On' : 'Off')
- : autopayLabel;
- }
+ const devicesCountRaw = Number(userData?.connected_devices_count);
+ const devicesCount = Number.isFinite(devicesCountRaw)
+ ? devicesCountRaw
+ : Array.isArray(userData?.connected_devices)
+ ? userData.connected_devices.length
+ : 0;
+ const devicesCountElement = document.getElementById('devicesCount');
+ if (devicesCountElement) {
+ devicesCountElement.textContent = devicesCount;
}
- renderNoSubscriptionState();
+ document.getElementById('trafficUsed').textContent =
+ user.traffic_used_label || formatTraffic(user.traffic_used_gb);
+ document.getElementById('trafficLimit').textContent =
+ user.traffic_limit_label || formatTrafficLimit(user.traffic_limit_gb);
+
+ const deviceLimitElement = document.getElementById('deviceLimit');
+ if (deviceLimitElement) {
+ const limitValue = typeof user.device_limit === 'number'
+ ? user.device_limit
+ : Number.parseInt(user.device_limit ?? '', 10);
+ deviceLimitElement.textContent = Number.isFinite(limitValue)
+ ? String(limitValue)
+ : t('values.not_available');
+ }
+
+ const subscriptionTypeElement = document.getElementById('subscriptionType');
+ if (subscriptionTypeElement) {
+ const fallbackSubscriptionType = (user?.subscription_status || '').toLowerCase() === 'trial'
+ ? 'trial'
+ : 'paid';
+ const subscriptionTypeRaw = String(
+ userData?.subscription_type
+ || fallbackSubscriptionType
+ ).toLowerCase();
+ const subscriptionTypeKey = `subscription.type.${subscriptionTypeRaw}`;
+ const subscriptionTypeLabel = t(subscriptionTypeKey);
+ subscriptionTypeElement.textContent = subscriptionTypeLabel === subscriptionTypeKey
+ ? subscriptionTypeRaw
+ : subscriptionTypeLabel;
+ }
+
+ const autopayElement = document.getElementById('autopayStatus');
+ if (autopayElement) {
+ const autopayKey = userData?.autopay_enabled ? 'autopay.enabled' : 'autopay.disabled';
+ const autopayLabel = t(autopayKey);
+ autopayElement.textContent = autopayLabel === autopayKey
+ ? (userData?.autopay_enabled ? 'On' : 'Off')
+ : autopayLabel;
+ }
renderSubscriptionPurchaseCard();
renderSubscriptionRenewalCard();
@@ -15550,30 +15276,13 @@
function showError(error) {
document.getElementById('loadingState').classList.add('hidden');
document.getElementById('mainContent').classList.add('hidden');
- const errorState = document.getElementById('errorState');
- errorState?.classList.add('hidden');
-
- currentErrorState = null;
- currentRegistrationState = null;
- renderRegistrationState();
- renderNoSubscriptionState();
-
- if (error?.code === 'user_not_found') {
- currentRegistrationState = {
- purchaseUrl: normalizeUrl(error?.purchaseUrl) || null,
- };
- renderRegistrationState();
- updateActionButtons();
- return;
- }
-
currentErrorState = {
title: error?.title,
message: error?.message,
purchaseUrl: normalizeUrl(error?.purchaseUrl) || null,
};
updateErrorTexts();
- errorState?.classList.remove('hidden');
+ document.getElementById('errorState').classList.remove('hidden');
updateActionButtons();
}
@@ -15635,26 +15344,6 @@
}
});
- const registrationActionBtn = document.getElementById('registrationActionBtn');
- if (registrationActionBtn) {
- registrationActionBtn.addEventListener('click', () => {
- const link = registrationActionBtn.dataset.link;
- if (link) {
- openExternalLink(link);
- }
- });
- }
-
- const noSubscriptionActionBtn = document.getElementById('noSubscriptionActionBtn');
- if (noSubscriptionActionBtn) {
- noSubscriptionActionBtn.addEventListener('click', () => {
- const link = noSubscriptionActionBtn.dataset.link;
- if (link) {
- openExternalLink(link);
- }
- });
- }
-
document.getElementById('referralToggleBtn')?.addEventListener('click', () => {
referralListExpanded = !referralListExpanded;
updateReferralToggleState();