mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-22 12:21:26 +00:00
Merge pull request #1165 from Fr1ngg/revert-1164-revert-1153-revert-1152-7gq87l-bedolaga/update-miniapp/index.html-logic
Revert "Revert "Revert "Improve miniapp onboarding states for unregistered users"""
This commit is contained in:
@@ -320,90 +320,6 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Empty States */
|
||||
.empty-state {
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.empty-state-card {
|
||||
padding: 28px 24px;
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: var(--shadow-sm);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.empty-state-title {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.empty-state-text {
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.empty-state-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-state-button {
|
||||
width: 100%;
|
||||
padding: 14px 18px;
|
||||
border: none;
|
||||
border-radius: var(--radius-lg);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.empty-state-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.empty-state-button.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.empty-state-chip {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(var(--primary-rgb), 0.12);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .empty-state-card {
|
||||
background: rgba(15, 23, 42, 0.75);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .empty-state-button.secondary {
|
||||
border-color: rgba(148, 163, 184, 0.4);
|
||||
}
|
||||
|
||||
.error-actions {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
@@ -4307,48 +4223,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Registration Prompt -->
|
||||
<div id="registrationState" class="empty-state hidden">
|
||||
<div class="empty-state-card">
|
||||
<div class="empty-state-icon">🤖</div>
|
||||
<div class="empty-state-title" data-i18n="state.registration.title">Register in the bot</div>
|
||||
<div class="empty-state-text" data-i18n="state.registration.text">
|
||||
Start the bot in Telegram to create an account and get access to the mini app features.
|
||||
</div>
|
||||
<div class="empty-state-actions">
|
||||
<button class="empty-state-button" id="openBotBtn" type="button" data-i18n="state.registration.button">
|
||||
Open bot
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="mainContent" class="hidden">
|
||||
<!-- Promo Offers -->
|
||||
<div id="promoOffersContainer" class="promo-offers hidden"></div>
|
||||
|
||||
<div id="noSubscriptionState" class="empty-state hidden">
|
||||
<div class="empty-state-card">
|
||||
<div class="empty-state-chip" data-i18n="state.no_subscription.badge">No active plan</div>
|
||||
<div class="empty-state-icon">🛒</div>
|
||||
<div class="empty-state-title" data-i18n="state.no_subscription.title">No subscription yet</div>
|
||||
<div class="empty-state-text" data-i18n="state.no_subscription.text">
|
||||
Choose a plan and activate your subscription to unlock all features.
|
||||
</div>
|
||||
<div class="empty-state-actions">
|
||||
<button class="empty-state-button" id="noSubscriptionPurchaseBtn" type="button" data-i18n="state.no_subscription.action.purchase">
|
||||
Choose a plan
|
||||
</button>
|
||||
<button class="empty-state-button secondary" id="noSubscriptionTopupBtn" type="button" data-i18n="state.no_subscription.action.topup">
|
||||
Top up balance
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Card -->
|
||||
<div class="card user-card animate-in" id="userCard">
|
||||
<div class="card user-card animate-in">
|
||||
<div class="user-header">
|
||||
<div class="user-avatar" id="userAvatar">U</div>
|
||||
<div class="user-info">
|
||||
@@ -5091,14 +4972,6 @@
|
||||
'app.loading': 'Loading your subscription...',
|
||||
'error.default.title': 'Subscription Not Found',
|
||||
'error.default.message': 'Please contact support to activate your subscription.',
|
||||
'state.registration.title': 'Register in the bot',
|
||||
'state.registration.text': 'Start the bot in Telegram to create an account and access the mini app.',
|
||||
'state.registration.button': 'Open bot',
|
||||
'state.no_subscription.badge': 'No active plan',
|
||||
'state.no_subscription.title': 'No subscription yet',
|
||||
'state.no_subscription.text': 'Choose a plan and activate your subscription to unlock all features.',
|
||||
'state.no_subscription.action.purchase': 'Choose a plan',
|
||||
'state.no_subscription.action.topup': 'Top up balance',
|
||||
'stats.days_left': 'Days left',
|
||||
'stats.servers': 'Servers',
|
||||
'stats.devices': 'Devices',
|
||||
@@ -5454,14 +5327,6 @@
|
||||
'app.loading': 'Загружаем вашу подписку...',
|
||||
'error.default.title': 'Подписка не найдена',
|
||||
'error.default.message': 'Свяжитесь с поддержкой, чтобы активировать подписку.',
|
||||
'state.registration.title': 'Зарегистрируйтесь в боте',
|
||||
'state.registration.text': 'Запустите бота в Telegram, чтобы создать аккаунт и получить доступ к мини-приложению.',
|
||||
'state.registration.button': 'Открыть бота',
|
||||
'state.no_subscription.badge': 'Нет подписки',
|
||||
'state.no_subscription.title': 'Подписка не активна',
|
||||
'state.no_subscription.text': 'Выберите подходящий тариф и подключите подписку, чтобы пользоваться сервисом.',
|
||||
'state.no_subscription.action.purchase': 'Выбрать тариф',
|
||||
'state.no_subscription.action.topup': 'Пополнить баланс',
|
||||
'stats.days_left': 'Осталось дней',
|
||||
'stats.servers': 'Серверы',
|
||||
'stats.devices': 'Устройства',
|
||||
@@ -5915,58 +5780,6 @@
|
||||
applyKey('app.subtitle', rawServiceDescription);
|
||||
}
|
||||
|
||||
function buildFallbackUserData(purchaseData) {
|
||||
const currency = (purchaseData?.currency || 'RUB').toString().toUpperCase();
|
||||
const balanceKopeks = coercePositiveInt(
|
||||
purchaseData?.balanceKopeks
|
||||
?? purchaseData?.balance_kopeks
|
||||
?? null,
|
||||
0
|
||||
) || 0;
|
||||
|
||||
const telegramUser = tg?.initDataUnsafe?.user || {};
|
||||
const username = telegramUser.username ? `@${telegramUser.username}` : null;
|
||||
const nameCandidates = [
|
||||
[telegramUser.first_name, telegramUser.last_name].filter(Boolean).join(' ').trim() || null,
|
||||
telegramUser.first_name || null,
|
||||
telegramUser.last_name || null,
|
||||
username,
|
||||
].filter(Boolean);
|
||||
const fallbackName = nameCandidates[0] || 'User';
|
||||
|
||||
return {
|
||||
user: {
|
||||
display_name: fallbackName,
|
||||
username,
|
||||
first_name: telegramUser.first_name || null,
|
||||
last_name: telegramUser.last_name || null,
|
||||
telegram_id: telegramUser.id || null,
|
||||
subscription_status: 'disabled',
|
||||
subscription_actual_status: 'disabled',
|
||||
has_active_subscription: false,
|
||||
expires_at: null,
|
||||
traffic_used_label: t('values.not_available'),
|
||||
traffic_limit_label: t('values.not_available'),
|
||||
device_limit: null,
|
||||
},
|
||||
balance_kopeks: balanceKopeks,
|
||||
balance_currency: currency,
|
||||
balance_rubles: balanceKopeks / 100,
|
||||
subscription_type: 'trial',
|
||||
autopay_enabled: false,
|
||||
connected_servers: [],
|
||||
connected_devices: [],
|
||||
connected_devices_count: 0,
|
||||
transactions: [],
|
||||
promo_offers: [],
|
||||
referral: null,
|
||||
faq: null,
|
||||
legal_documents: null,
|
||||
subscription_url: null,
|
||||
subscriptionPurchaseUrl: null,
|
||||
};
|
||||
}
|
||||
|
||||
let userData = null;
|
||||
let appsConfig = {};
|
||||
let currentPlatform = 'android';
|
||||
@@ -5975,7 +5788,6 @@
|
||||
let preferredLanguage = 'en';
|
||||
let languageLockedByUser = false;
|
||||
let currentErrorState = null;
|
||||
let subscriptionViewState = 'loading';
|
||||
let paymentMethodsCache = null;
|
||||
let paymentMethodsPromise = null;
|
||||
let activePaymentMethod = null;
|
||||
@@ -6022,10 +5834,6 @@
|
||||
periodId: null,
|
||||
};
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search || '');
|
||||
const botUsernameRaw = urlParams.get('tgWebAppBotName') || urlParams.get('bot') || '';
|
||||
const botUsername = botUsernameRaw ? botUsernameRaw.trim().replace(/^@/, '') : null;
|
||||
|
||||
const PAYMENT_STATUS_INITIAL_DELAY_MS = 2000;
|
||||
const PAYMENT_STATUS_POLL_INTERVAL_MS = 5000;
|
||||
const PAYMENT_STATUS_TIMEOUT_MS = 180000;
|
||||
@@ -6685,25 +6493,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setSubscriptionViewState(state) {
|
||||
subscriptionViewState = state;
|
||||
|
||||
const registrationBlock = document.getElementById('registrationState');
|
||||
if (registrationBlock) {
|
||||
registrationBlock.classList.toggle('hidden', state !== 'unregistered');
|
||||
}
|
||||
|
||||
const noSubscriptionBlock = document.getElementById('noSubscriptionState');
|
||||
if (noSubscriptionBlock) {
|
||||
noSubscriptionBlock.classList.toggle('hidden', state !== 'no-subscription');
|
||||
}
|
||||
|
||||
const userCard = document.getElementById('userCard');
|
||||
if (userCard) {
|
||||
userCard.classList.toggle('hidden', state !== 'active');
|
||||
}
|
||||
}
|
||||
|
||||
function applyTranslations() {
|
||||
document.title = t('app.title');
|
||||
document.documentElement.setAttribute('lang', preferredLanguage);
|
||||
@@ -6902,7 +6691,6 @@
|
||||
|
||||
currentErrorState = null;
|
||||
updateErrorTexts();
|
||||
setSubscriptionViewState('active');
|
||||
|
||||
const errorState = document.getElementById('errorState');
|
||||
if (errorState) {
|
||||
@@ -6928,83 +6716,6 @@
|
||||
return userData;
|
||||
}
|
||||
|
||||
async function handleMissingSubscription(error) {
|
||||
if (!error || error.status !== 404) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const initData = tg.initData || '';
|
||||
if (!initData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/miniapp/subscription/purchase/options', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ initData }),
|
||||
});
|
||||
|
||||
const body = await parseJsonSafe(response);
|
||||
if (!response.ok || (body && body.success === false)) {
|
||||
const message = extractPurchaseErrorMessage(body, response.status);
|
||||
const fallbackError = createError('Subscription purchase error', message, response.status);
|
||||
const code = extractErrorCode(body);
|
||||
if (code) {
|
||||
fallbackError.code = code;
|
||||
}
|
||||
fallbackError.payload = body;
|
||||
throw fallbackError;
|
||||
}
|
||||
|
||||
const normalized = normalizeSubscriptionPurchasePayload(body);
|
||||
if (!normalized) {
|
||||
throw createError('Subscription purchase error', t('subscription_purchase.error.default'));
|
||||
}
|
||||
subscriptionPurchaseData = normalized;
|
||||
subscriptionPurchaseError = null;
|
||||
subscriptionPurchaseLoading = false;
|
||||
subscriptionPurchasePromise = null;
|
||||
subscriptionPurchaseFeatureEnabled = true;
|
||||
subscriptionPurchasePreview = null;
|
||||
subscriptionPurchasePreviewError = null;
|
||||
subscriptionPurchasePreviewLoading = false;
|
||||
subscriptionPurchasePreviewPromise = null;
|
||||
resetSubscriptionPurchaseSelections(normalized);
|
||||
renderSubscriptionPurchaseCard();
|
||||
requestSubscriptionPurchasePreviewUpdate({ immediate: true });
|
||||
|
||||
userData = buildFallbackUserData(normalized);
|
||||
currentErrorState = null;
|
||||
updateErrorTexts();
|
||||
|
||||
setSubscriptionViewState('no-subscription');
|
||||
document.getElementById('errorState')?.classList.add('hidden');
|
||||
document.getElementById('loadingState')?.classList.add('hidden');
|
||||
document.getElementById('mainContent')?.classList.remove('hidden');
|
||||
|
||||
renderUserData();
|
||||
updateActionButtons();
|
||||
|
||||
return true;
|
||||
} catch (fallbackError) {
|
||||
const code = fallbackError?.code || extractErrorCode(fallbackError?.payload);
|
||||
if (fallbackError?.status === 404 || code === 'user_not_found') {
|
||||
currentErrorState = null;
|
||||
updateErrorTexts();
|
||||
setSubscriptionViewState('unregistered');
|
||||
document.getElementById('loadingState')?.classList.add('hidden');
|
||||
document.getElementById('errorState')?.classList.add('hidden');
|
||||
document.getElementById('mainContent')?.classList.add('hidden');
|
||||
updateActionButtons();
|
||||
return true;
|
||||
}
|
||||
|
||||
console.warn('Failed to prepare fallback subscription state:', fallbackError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshSubscriptionData(options = {}) {
|
||||
const { silent = false } = options;
|
||||
const initData = tg.initData || '';
|
||||
@@ -7019,16 +6730,8 @@
|
||||
document.getElementById('loadingState')?.classList.remove('hidden');
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = await fetchSubscriptionPayload(initData);
|
||||
return applySubscriptionData(payload);
|
||||
} catch (error) {
|
||||
const handled = await handleMissingSubscription(error);
|
||||
if (handled) {
|
||||
return userData;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const payload = await fetchSubscriptionPayload(initData);
|
||||
return applySubscriptionData(payload);
|
||||
}
|
||||
|
||||
async function init() {
|
||||
@@ -7047,10 +6750,7 @@
|
||||
await refreshSubscriptionData();
|
||||
} catch (error) {
|
||||
console.error('Initialization error:', error);
|
||||
const handled = await handleMissingSubscription(error);
|
||||
if (!handled) {
|
||||
showError(error);
|
||||
}
|
||||
showError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12134,11 +11834,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscriptionViewState !== 'active') {
|
||||
card.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldShow = hasPaidSubscription();
|
||||
card.classList.toggle('hidden', !shouldShow);
|
||||
if (!shouldShow) {
|
||||
@@ -12565,11 +12260,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscriptionViewState !== 'active') {
|
||||
card.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldShow = hasPaidSubscription();
|
||||
card.classList.toggle('hidden', !shouldShow);
|
||||
if (!shouldShow) {
|
||||
@@ -13747,23 +13437,6 @@
|
||||
return t('subscription_purchase.error.default');
|
||||
}
|
||||
|
||||
function extractErrorCode(payload) {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof payload.code === 'string') {
|
||||
return payload.code;
|
||||
}
|
||||
|
||||
const detail = payload.detail;
|
||||
if (detail && typeof detail === 'object' && typeof detail.code === 'string') {
|
||||
return detail.code;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function ensureSubscriptionPurchaseData(options = {}) {
|
||||
const { force = false } = options;
|
||||
|
||||
@@ -15585,7 +15258,6 @@
|
||||
if (copyBtn) {
|
||||
const hasUrl = Boolean(subscriptionUrl);
|
||||
copyBtn.disabled = !hasUrl || !navigator.clipboard;
|
||||
copyBtn.classList.toggle('hidden', subscriptionViewState !== 'active');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15602,7 +15274,6 @@
|
||||
}
|
||||
|
||||
function showError(error) {
|
||||
setSubscriptionViewState('loading');
|
||||
document.getElementById('loadingState').classList.add('hidden');
|
||||
document.getElementById('mainContent').classList.add('hidden');
|
||||
currentErrorState = {
|
||||
@@ -15673,44 +15344,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
const openBotBtn = document.getElementById('openBotBtn');
|
||||
if (openBotBtn) {
|
||||
if (!botUsername) {
|
||||
openBotBtn.disabled = true;
|
||||
openBotBtn.classList.add('secondary');
|
||||
} else {
|
||||
openBotBtn.addEventListener('click', () => {
|
||||
const link = `https://t.me/${botUsername}`;
|
||||
if (typeof tg.openTelegramLink === 'function') {
|
||||
try {
|
||||
tg.openTelegramLink(link);
|
||||
return;
|
||||
} catch (openError) {
|
||||
console.warn('tg.openTelegramLink failed:', openError);
|
||||
}
|
||||
}
|
||||
openExternalLink(link, { openInMiniApp: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('noSubscriptionPurchaseBtn')?.addEventListener('click', event => {
|
||||
if (shouldShowPurchaseConfigurator()) {
|
||||
event.preventDefault();
|
||||
openSubscriptionPurchaseModal();
|
||||
return;
|
||||
}
|
||||
const link = getEffectivePurchaseUrl() || configPurchaseUrl;
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
openExternalLink(link, { openInMiniApp: true });
|
||||
});
|
||||
|
||||
document.getElementById('noSubscriptionTopupBtn')?.addEventListener('click', () => {
|
||||
openTopupModal();
|
||||
});
|
||||
|
||||
document.getElementById('referralToggleBtn')?.addEventListener('click', () => {
|
||||
referralListExpanded = !referralListExpanded;
|
||||
updateReferralToggleState();
|
||||
|
||||
Reference in New Issue
Block a user