diff --git a/bot/app/web/frontend/src/App.svelte b/bot/app/web/frontend/src/App.svelte index 1d38e27..eb6c111 100644 --- a/bot/app/web/frontend/src/App.svelte +++ b/bot/app/web/frontend/src/App.svelte @@ -1,5 +1,4 @@ @@ -1123,8 +799,8 @@ initialSection={adminSectionFromPath(window.location.pathname)} initialUserId={adminUserIdFromPath(window.location.pathname)} onSectionChange={handleAdminSectionChange} - onSettingsSaved={handleSettingsSaved} - onTariffsSaved={handleTariffsSaved} + onSettingsSaved={handleAdminPersistedSaved} + onTariffsSaved={handleAdminPersistedSaved} brandTitle={brandTitle} logoUrl={CFG.logoUrl} logoEmoji={brandEmoji} @@ -1313,7 +989,6 @@ {topupOptions} {trafficMode} {t} - {termUnitLabel} /> {/if} diff --git a/bot/app/web/frontend/src/lib/webapp/billingOptionCache.js b/bot/app/web/frontend/src/lib/webapp/billingOptionCache.js new file mode 100644 index 0000000..91b0f1d --- /dev/null +++ b/bot/app/web/frontend/src/lib/webapp/billingOptionCache.js @@ -0,0 +1,4 @@ +/** Drop cached topup / change-tariff option payloads so the next open refetches from /api. */ +export function invalidateWebappTariffOptionCaches(billingStore) { + billingStore.update((s) => ({ ...s, topupOptions: null, deviceTopupOptions: null, changeOptions: null })); +} diff --git a/bot/app/web/frontend/src/lib/webapp/devicesLabels.js b/bot/app/web/frontend/src/lib/webapp/devicesLabels.js new file mode 100644 index 0000000..ab8894e --- /dev/null +++ b/bot/app/web/frontend/src/lib/webapp/devicesLabels.js @@ -0,0 +1,24 @@ +/** + * Pure helpers for HWID / device limits UI (used by DevicesScreen). + * @param {Record} devicesData API payload from /api/devices + * @param {(key: string, vars?: Record, fallback?: string) => string} t i18n function + * @param {unknown} [maxDevicesOverride] optional max_devices override (defaults to devicesData.max_devices) + */ +export function devicesLimitLabel(devicesData, t, maxDevicesOverride) { + const value = maxDevicesOverride !== undefined ? maxDevicesOverride : devicesData?.max_devices; + const numeric = Number(value ?? 0); + if (!Number.isFinite(numeric) || numeric <= 0) return t("wa_devices_unlimited"); + return String(Math.trunc(numeric)); +} + +export function devicesCountLabel(devicesData, t) { + const current = Number(devicesData?.current_devices ?? devicesData?.devices?.length ?? 0); + return t("wa_devices_count", { current, max: devicesLimitLabel(devicesData, t) }); +} + +export function devicesPercent(devicesData) { + const current = Number(devicesData?.current_devices ?? devicesData?.devices?.length ?? 0); + const max = Number(devicesData?.max_devices || 0); + if (!max || max <= 0) return 100; + return Math.max(0, Math.min(100, Math.round((current / max) * 100))); +} diff --git a/bot/app/web/frontend/src/lib/webapp/stores/accountStore.js b/bot/app/web/frontend/src/lib/webapp/stores/accountStore.js index 81c9c62..55e8cb7 100644 --- a/bot/app/web/frontend/src/lib/webapp/stores/accountStore.js +++ b/bot/app/web/frontend/src/lib/webapp/stores/accountStore.js @@ -211,5 +211,6 @@ export function createAccountStore({ api, publicApi, setToken, loadData, t, show linkTelegramAccount, updateAccountLanguage, logout, + clearLinkEmailResendTimer: clearCooldownTimer, }; } diff --git a/bot/app/web/frontend/src/lib/webapp/stores/authStore.js b/bot/app/web/frontend/src/lib/webapp/stores/authStore.js index bbfd8c2..79fe5b7 100644 --- a/bot/app/web/frontend/src/lib/webapp/stores/authStore.js +++ b/bot/app/web/frontend/src/lib/webapp/stores/authStore.js @@ -269,6 +269,7 @@ export function createAuthStore({ verifyEmailCode, openTelegramLogin, clearCooldownTimer, - setAuthStatus + stopTelegramLoginWatchdog, + setAuthStatus, }; } diff --git a/bot/app/web/frontend/src/lib/webapp/stores/devicesStore.js b/bot/app/web/frontend/src/lib/webapp/stores/devicesStore.js index 2fa7641..852f69b 100644 --- a/bot/app/web/frontend/src/lib/webapp/stores/devicesStore.js +++ b/bot/app/web/frontend/src/lib/webapp/stores/devicesStore.js @@ -63,28 +63,6 @@ export function createDevicesStore({ api, t, showToast }) { } } - function devicesLimitLabel() { - const s = get(state); - const value = s.devicesData?.max_devices; - const numeric = Number(value ?? 0); - if (!Number.isFinite(numeric) || numeric <= 0) return t("wa_devices_unlimited"); - return String(Math.trunc(numeric)); - } - - function devicesCountLabel() { - const s = get(state); - const current = Number(s.devicesData?.current_devices ?? s.devicesData?.devices?.length ?? 0); - return t("wa_devices_count", { current, max: devicesLimitLabel() }); - } - - function devicesPercent() { - const s = get(state); - const current = Number(s.devicesData?.current_devices ?? s.devicesData?.devices?.length ?? 0); - const max = Number(s.devicesData?.max_devices || 0); - if (!max || max <= 0) return 100; - return Math.max(0, Math.min(100, Math.round((current / max) * 100))); - } - return { subscribe: state.subscribe, set: state.set, @@ -93,8 +71,5 @@ export function createDevicesStore({ api, t, showToast }) { openDeviceDisconnectDialog, closeDeviceDisconnectDialog, disconnectDevice, - devicesLimitLabel, - devicesCountLabel, - devicesPercent, }; } diff --git a/bot/app/web/frontend/src/lib/webapp/webappBoot.js b/bot/app/web/frontend/src/lib/webapp/webappBoot.js new file mode 100644 index 0000000..11f0c1d --- /dev/null +++ b/bot/app/web/frontend/src/lib/webapp/webappBoot.js @@ -0,0 +1,87 @@ +import { + readMagicLoginToken, + readTelegramAuthStatus, + readTelegramLoginWidgetAuthData, + clearAuthQuery, +} from "./authHelpers.js"; +import { TELEGRAM_SDK_BOOT_TIMEOUT_MS } from "./constants.js"; + +/** + * Initial auth / session bootstrap for the subscription webapp (non-preview). + * Keeps side effects in App (mode, tg, token) via injected callbacks. + */ +export async function runWebappBoot({ + MOCK, + setMode, + hasTelegramLaunchParams, + loadTelegramSdk, + prepareTelegramMiniApp, + loadData, + showLogin, + clearToken, + clearManualLogoutFlag, + isManuallyLoggedOut, + finalizeMagicLogin, + finalizeTelegramAuth, + setAuthStatus, + t, + getInitDataForBoot, + getToken, + getCsrfToken, +}) { + setMode("loading"); + if (hasTelegramLaunchParams()) await loadTelegramSdk(TELEGRAM_SDK_BOOT_TIMEOUT_MS); + prepareTelegramMiniApp(); + + if (MOCK) { + await loadData(); + return; + } + + const magicToken = readMagicLoginToken(); + if (magicToken && (await finalizeMagicLogin(magicToken))) return; + + const telegramAuthStatus = readTelegramAuthStatus(); + if (telegramAuthStatus === "success") { + clearManualLogoutFlag(); + clearAuthQuery(); + try { + await loadData(); + return; + } catch { + clearToken(); + } + } else if (telegramAuthStatus) { + clearAuthQuery(); + setAuthStatus( + telegramAuthStatus === "cancelled" ? t("wa_auth_telegram_cancelled") : t("wa_auth_telegram_not_confirmed"), + true, + ); + } + + if (isManuallyLoggedOut()) { + showLogin(); + return; + } + + const widgetAuthData = readTelegramLoginWidgetAuthData(); + if (widgetAuthData && (await finalizeTelegramAuth(widgetAuthData, "auth_data"))) return; + + const initData = getInitDataForBoot(); + if (initData) { + try { + if (await finalizeTelegramAuth(initData, "init_data")) return; + } catch {} + } + + if (getToken() || getCsrfToken()) { + try { + await loadData(); + return; + } catch { + clearToken(); + } + } + + showLogin(); +} diff --git a/bot/app/web/frontend/src/webapp/TariffDialogs.svelte b/bot/app/web/frontend/src/webapp/TariffDialogs.svelte index a1aa8d3..83910bf 100644 --- a/bot/app/web/frontend/src/webapp/TariffDialogs.svelte +++ b/bot/app/web/frontend/src/webapp/TariffDialogs.svelte @@ -140,7 +140,6 @@ export let t = (key) => key; - export let termUnitLabel = () => ""; {}; export let openDeviceTopupModal = () => {}; export let t = (key) => key; - function devicesLimitLabel(value = devicesData?.max_devices) { - const numeric = Number(value ?? 0); - if (!Number.isFinite(numeric) || numeric <= 0) return t("wa_devices_unlimited"); - return String(Math.trunc(numeric)); - } - - function devicesCountLabel() { - const current = Number(devicesData?.current_devices ?? devicesData?.devices?.length ?? 0); - return t("wa_devices_count", { current, max: devicesLimitLabel() }); - } - - function devicesPercent() { - const current = Number(devicesData?.current_devices ?? devicesData?.devices?.length ?? 0); - const max = Number(devicesData?.max_devices || 0); - if (!max || max <= 0) return 100; - return Math.max(0, Math.min(100, Math.round((current / max) * 100))); - } @@ -41,14 +25,14 @@ {t("wa_devices_title")} - {devicesCountLabel()} + {devicesCountLabel(devicesData, t)}
- +
{#if subscription?.active && subscription?.max_devices !== 0}