diff --git a/miniapp/index.html b/miniapp/index.html index 8e1e0d17..7a7128b2 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -324,6 +324,45 @@ line-height: 1.4; } + .maintenance-state { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: 24px; + box-shadow: var(--shadow-lg); + margin-bottom: 24px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 12px; + } + + .maintenance-state .maintenance-icon { + width: 54px; + height: 54px; + font-size: 28px; + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(var(--primary-rgb), 0.1); + border-radius: 50%; + color: var(--primary); + box-shadow: var(--shadow-sm); + } + + .maintenance-state .maintenance-title { + font-size: 20px; + font-weight: 800; + color: var(--text-primary); + } + + .maintenance-state .maintenance-text { + font-size: 15px; + color: var(--text-secondary); + line-height: 1.7; + } + .spinner { width: 48px; height: 48px; @@ -4812,6 +4851,14 @@ + +
@@ -7547,6 +7594,21 @@ label.textContent = t(key); } + function resolveMaintenanceMessage() { + const resolvedMessage = (typeof maintenanceState.message === 'string' + ? maintenanceState.message.trim() + : '') + || t('maintenance.message'); + const translatedFallback = t('maintenance.message'); + const messageFallback = translatedFallback === 'maintenance.message' + ? 'The service is temporarily unavailable due to maintenance. Please try again later.' + : translatedFallback; + + return resolvedMessage === 'maintenance.message' + ? messageFallback + : resolvedMessage; + } + function renderMaintenanceBanner() { const banner = document.getElementById('maintenanceBanner'); const messageElement = document.getElementById('maintenanceMessage'); @@ -7560,26 +7622,50 @@ return; } - const resolvedMessage = (typeof maintenanceState.message === 'string' - ? maintenanceState.message.trim() - : '') - || t('maintenance.message'); - const translatedFallback = t('maintenance.message'); - const messageFallback = translatedFallback === 'maintenance.message' - ? 'The service is temporarily unavailable due to maintenance. Please try again later.' - : translatedFallback; - messageElement.textContent = resolvedMessage === 'maintenance.message' - ? messageFallback - : resolvedMessage; - + messageElement.textContent = resolveMaintenanceMessage(); banner.classList.remove('hidden'); } + function renderMaintenanceScreen() { + const screen = document.getElementById('maintenanceState'); + const messageElement = document.getElementById('maintenanceStateMessage'); + const banner = document.getElementById('maintenanceBanner'); + const loadingState = document.getElementById('loadingState'); + const errorState = document.getElementById('errorState'); + const mainContent = document.getElementById('mainContent'); + + if (!screen || !messageElement) { + return; + } + + if (!maintenanceState?.isActive) { + screen.classList.add('hidden'); + return; + } + + messageElement.textContent = resolveMaintenanceMessage(); + screen.classList.remove('hidden'); + + if (banner) { + banner.classList.add('hidden'); + } + if (loadingState) { + loadingState.classList.add('hidden'); + } + if (errorState) { + errorState.classList.add('hidden'); + } + if (mainContent) { + mainContent.classList.add('hidden'); + } + } + function applyMaintenanceStatus(status) { const isActive = Boolean(status?.isActive ?? status?.is_active); const message = typeof status?.message === 'string' ? status.message : null; maintenanceState = { isActive, message }; renderMaintenanceBanner(); + renderMaintenanceScreen(); } function refreshAfterLanguageChange() { @@ -8050,15 +8136,17 @@ async function checkMaintenance(initData) { if (!initData) { applyMaintenanceStatus({ isActive: false, message: null }); - return; + return false; } try { const status = await fetchMaintenanceStatus(initData); applyMaintenanceStatus(status); + return Boolean(status?.isActive ?? status?.is_active); } catch (error) { console.warn('Unable to load maintenance status:', error); applyMaintenanceStatus({ isActive: false, message: null }); + return false; } } @@ -8076,7 +8164,10 @@ await loadAppsConfig(); const initData = tg.initData || ''; - await checkMaintenance(initData); + const maintenanceActive = await checkMaintenance(initData); + if (maintenanceActive) { + return; + } await refreshSubscriptionData(); } catch (error) { console.error('Initialization error:', error); @@ -8086,12 +8177,13 @@ async function loadAppsConfig() { try { - const response = await fetch('/app-config.json', { cache: 'no-cache' }); - if (!response.ok) { - throw new Error('Failed to load app config'); + const fallbackPaths = getAppConfigCandidatePaths(); + const data = await fetchFirstAvailableAppConfig(fallbackPaths); + + if (!data) { + throw new Error('No available app config sources'); } - const data = await response.json(); appsConfig = sanitizeAppsConfig(data?.platforms || {}); ensurePlatformFilter(); @@ -8155,9 +8247,53 @@ } catch (error) { console.warn('Unable to load apps configuration:', error); appsConfig = {}; + ensurePlatformFilter(); } } + function getAppConfigCandidatePaths() { + const paths = []; + const currentPath = window.location?.pathname || '/'; + const basePath = currentPath.endsWith('/') + ? currentPath + : currentPath.replace(/[^/]*$/, ''); + + const normalizedBase = basePath || '/'; + const relativePath = `${normalizedBase}app-config.json`; + paths.push(relativePath); + + ['/miniapp/app-config.json', '/app-config.json'].forEach(path => { + if (!paths.includes(path)) { + paths.push(path); + } + }); + + return paths; + } + + async function fetchFirstAvailableAppConfig(paths) { + if (!Array.isArray(paths) || !paths.length) { + return null; + } + + for (const path of paths) { + try { + const response = await fetch(path, { cache: 'no-cache' }); + if (!response.ok) { + continue; + } + const data = await response.json(); + if (data && typeof data === 'object') { + return data; + } + } catch (error) { + console.warn('App config path failed:', path, error); + } + } + + return null; + } + function renderUserData() { if (!userData?.user) { return;