From 960063fdca186e89837fb006d83f1eb8dbc16b47 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 11 Oct 2025 04:44:04 +0300 Subject: [PATCH] Revert "Add autopay toggle controls to miniapp UI" --- miniapp/index.html | 791 +-------------------------------------------- 1 file changed, 3 insertions(+), 788 deletions(-) diff --git a/miniapp/index.html b/miniapp/index.html index 6e53995b..1023062b 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -2041,174 +2041,6 @@ border-radius: var(--radius-sm); } - .info-item--autopay { - flex-direction: column; - align-items: stretch; - gap: 12px; - } - - .info-item--autopay:hover { - padding-left: 0; - padding-right: 0; - margin: 0; - background: none; - } - - .info-item--autopay .info-item-header { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - } - - .autopay-controls { - display: flex; - flex-direction: column; - gap: 12px; - width: 100%; - } - - .autopay-controls.hidden { - display: none; - } - - .autopay-toggle { - display: flex; - gap: 8px; - } - - .autopay-toggle-btn { - flex: 1; - padding: 10px 12px; - border-radius: var(--radius); - border: 1px solid var(--border-color); - background: var(--bg-secondary); - color: var(--text-primary); - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: all 0.25s ease; - } - - .autopay-toggle-btn:hover:not(:disabled) { - border-color: var(--primary); - color: var(--primary); - box-shadow: 0 2px 6px rgba(var(--primary-rgb), 0.1); - } - - .autopay-toggle-btn:disabled { - opacity: 0.6; - cursor: not-allowed; - } - - .autopay-toggle-btn.active { - background: rgba(var(--primary-rgb), 0.12); - border-color: rgba(var(--primary-rgb), 0.6); - color: var(--primary); - } - - .autopay-slider { - display: flex; - flex-direction: column; - gap: 8px; - padding: 0 4px; - } - - .autopay-slider.hidden { - display: none; - } - - .autopay-slider-caption { - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - } - - .autopay-slider-input { - width: 100%; - appearance: none; - height: 6px; - border-radius: 999px; - background: var(--border-color); - outline: none; - transition: background 0.3s ease; - } - - .autopay-slider-input::-webkit-slider-thumb { - appearance: none; - width: 18px; - height: 18px; - border-radius: 50%; - background: var(--primary); - border: 2px solid var(--bg-primary); - cursor: pointer; - box-shadow: 0 2px 6px rgba(var(--primary-rgb), 0.3); - transition: transform 0.2s ease, background 0.3s ease; - } - - .autopay-slider-input::-moz-range-thumb { - width: 18px; - height: 18px; - border-radius: 50%; - background: var(--primary); - border: 2px solid var(--bg-primary); - cursor: pointer; - box-shadow: 0 2px 6px rgba(var(--primary-rgb), 0.3); - transition: transform 0.2s ease, background 0.3s ease; - } - - .autopay-slider-input:disabled::-webkit-slider-thumb, - .autopay-slider-input:disabled::-moz-range-thumb { - background: var(--border-color); - box-shadow: none; - cursor: not-allowed; - } - - .autopay-slider-input::-webkit-slider-thumb:hover, - .autopay-slider-input::-moz-range-thumb:hover { - transform: scale(1.05); - } - - .autopay-slider-marks { - display: flex; - justify-content: space-between; - align-items: flex-start; - font-size: 11px; - font-weight: 600; - color: var(--text-secondary); - } - - .autopay-slider-mark { - flex: 1; - text-align: center; - position: relative; - padding-top: 6px; - user-select: none; - } - - .autopay-slider-mark::before { - content: ''; - position: absolute; - top: 0; - left: 50%; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--border-color); - transform: translateX(-50%); - transition: background 0.3s ease, transform 0.3s ease; - } - - .autopay-slider-mark.active { - color: var(--text-primary); - } - - .autopay-slider-mark.active::before { - background: var(--primary); - transform: translateX(-50%) scale(1.2); - } - .info-label { font-size: 14px; color: var(--text-secondary); @@ -4712,42 +4544,9 @@ Device Limit - -
-
- Auto-Pay - - -
- +
+ Auto-Pay + -
@@ -5335,7 +5134,6 @@ setupSubscriptionSettingsEvents(); setupSubscriptionPurchaseEvents(); setupSubscriptionRenewalEvents(); - setupAutopayControlsEvents(); const themeToggle = document.getElementById('themeToggle'); const THEME_STORAGE_KEY = 'remnawave-miniapp-theme'; @@ -5787,18 +5585,6 @@ 'trial.activation.error.already_active': 'You already have an active subscription.', 'autopay.enabled': 'Enabled', 'autopay.disabled': 'Disabled', - 'autopay.toggle.enable': 'Enable auto-pay', - 'autopay.toggle.disable': 'Disable auto-pay', - 'autopay.update_in_progress': 'Updating…', - 'autopay.slider.caption': 'Charge {count} days before renewal', - 'autopay.slider.caption.one': 'Charge {count} day before renewal', - 'autopay.slider.hint': 'Choose when to charge automatically.', - 'autopay.days.label': '{count} days', - 'autopay.days.label.one': '{count} day', - 'autopay.error.generic': 'Failed to update auto-pay settings. Please try again later.', - 'autopay.success.enabled': 'Auto-pay enabled.', - 'autopay.success.disabled': 'Auto-pay disabled.', - 'autopay.success.updated': 'Auto-pay schedule updated.', 'platform.ios': 'iOS', 'platform.android': 'Android', 'platform.pc': 'PC', @@ -6176,18 +5962,6 @@ 'trial.activation.error.already_active': 'У вас уже есть активная подписка.', 'autopay.enabled': 'Включен', 'autopay.disabled': 'Выключен', - 'autopay.toggle.enable': 'Включить автоплатеж', - 'autopay.toggle.disable': 'Отключить автоплатеж', - 'autopay.update_in_progress': 'Обновляем…', - 'autopay.slider.caption': 'Списывать за {count} дн. до окончания', - 'autopay.slider.caption.one': 'Списывать за {count} день до окончания', - 'autopay.slider.hint': 'Выберите, за сколько дней до окончания списывать оплату автоматически.', - 'autopay.days.label': '{count} дн.', - 'autopay.days.label.one': '{count} день', - 'autopay.error.generic': 'Не удалось обновить автоплатеж. Попробуйте позже.', - 'autopay.success.enabled': 'Автоплатеж включен.', - 'autopay.success.disabled': 'Автоплатеж отключен.', - 'autopay.success.updated': 'Настройки автоплатежа обновлены.', 'platform.ios': 'iOS', 'platform.android': 'Android', 'platform.pc': 'ПК', @@ -6343,15 +6117,6 @@ const activePaymentMonitors = new Map(); let paymentStatusPollTimer = null; let isPaymentStatusPolling = false; - const autopayState = { - enabled: false, - daysBefore: null, - options: [], - previewDays: null, - updating: false, - pendingAction: null, - available: false, - }; let subscriptionSettingsData = null; let subscriptionSettingsPromise = null; let subscriptionSettingsError = null; @@ -7489,7 +7254,6 @@ detectPlatform(); setActivePlatformButton(); - syncAutopayStateFromUserData(userData); refreshAfterLanguageChange(); animateCardsOnce(); @@ -7682,8 +7446,6 @@ : autopayLabel; } - renderAutopayControls(); - renderSubscriptionMissingCard(); renderSubscriptionPurchaseCard(); renderSubscriptionRenewalCard(); @@ -7701,553 +7463,6 @@ updateActionButtons(); } - function resolveAutopayString(key, fallback) { - const value = t(key); - if (value && value !== key) { - return value; - } - return fallback; - } - - function normalizeAutopaySourceValues(source) { - if (Array.isArray(source)) { - return source; - } - if (source === undefined || source === null) { - return []; - } - if (typeof source === 'number') { - return [source]; - } - if (typeof source === 'string') { - return source - .split(/[,;\s]+/) - .map(entry => entry.trim()) - .filter(Boolean); - } - if (typeof source === 'object') { - const collected = []; - if (Array.isArray(source.options)) { - collected.push(...source.options); - } - if (Array.isArray(source.values)) { - collected.push(...source.values); - } - if (Array.isArray(source.days)) { - collected.push(...source.days); - } - if (Array.isArray(source.list)) { - collected.push(...source.list); - } - ['min', 'max', 'default', 'value', 'days_before', 'daysBefore'].forEach(key => { - if (source[key] !== undefined && source[key] !== null) { - collected.push(source[key]); - } - }); - return collected; - } - return []; - } - - function resolveAutopayOptions(data = userData) { - const sources = [ - data?.autopay_days_options, - data?.autopayDaysOptions, - data?.autopay_warning_days, - data?.autopayWarningDays, - data?.autopay?.days_before_options, - data?.autopay?.daysBeforeOptions, - data?.autopay?.options, - data?.autopay?.values, - appsConfig?.autopay_days_options, - appsConfig?.autopayDaysOptions, - appsConfig?.autopay_warning_days, - appsConfig?.autopayWarningDays, - ]; - const values = new Set(); - sources.forEach(source => { - normalizeAutopaySourceValues(source).forEach(entry => { - const number = coercePositiveInt(entry, null); - if (number !== null && number > 0) { - values.add(number); - } - }); - }); - if (values.size === 0) { - return [1, 3]; - } - return Array.from(values).sort((a, b) => a - b); - } - - function extractAutopayDaysValue(data = userData) { - const candidates = [ - data?.autopay_days_before, - data?.autopayDaysBefore, - data?.autopay?.days_before, - data?.autopay?.daysBefore, - data?.user?.autopay_days_before, - data?.user?.autopayDaysBefore, - ]; - for (const candidate of candidates) { - const value = coercePositiveInt(candidate, null); - if (value !== null) { - return value; - } - } - return null; - } - - function syncAutopayStateFromUserData(data = userData) { - if (!data) { - autopayState.available = false; - autopayState.enabled = false; - autopayState.options = []; - autopayState.daysBefore = null; - autopayState.previewDays = null; - return; - } - - const subscriptionMissing = Boolean(data.subscription_missing ?? data.subscriptionMissing); - const hasSubscription = Boolean(data.subscription_id ?? data.subscriptionId); - const available = hasSubscription && !subscriptionMissing && hasPaidSubscription(); - autopayState.available = available; - - const enabled = Boolean(data.autopay_enabled ?? data.autopayEnabled); - autopayState.enabled = enabled; - - const options = resolveAutopayOptions(data); - const daysValue = extractAutopayDaysValue(data); - - const normalizedOptions = Array.isArray(options) ? options.slice() : []; - if (daysValue !== null && !normalizedOptions.includes(daysValue)) { - normalizedOptions.push(daysValue); - } - normalizedOptions.sort((a, b) => a - b); - autopayState.options = normalizedOptions; - - if (daysValue !== null) { - autopayState.daysBefore = daysValue; - } else if (!autopayState.daysBefore || !normalizedOptions.includes(autopayState.daysBefore)) { - autopayState.daysBefore = normalizedOptions.length - ? normalizedOptions[normalizedOptions.length - 1] - : null; - } - - if (!available) { - autopayState.previewDays = null; - } - } - - function getAutopayActiveDays() { - if (autopayState.previewDays !== null && autopayState.previewDays !== undefined) { - return autopayState.previewDays; - } - if (autopayState.daysBefore !== null && autopayState.daysBefore !== undefined) { - return autopayState.daysBefore; - } - if (autopayState.options.length) { - return autopayState.options[autopayState.options.length - 1]; - } - return null; - } - - function formatAutopayDaysLabel(days) { - const value = coercePositiveInt(days, null); - if (value === null) { - return ''; - } - const key = value === 1 ? 'autopay.days.label.one' : 'autopay.days.label'; - const template = t(key); - if (template && template !== key) { - return template.replace('{count}', String(value)); - } - return value === 1 ? `${value} day` : `${value} days`; - } - - function formatAutopayCaption(days) { - const value = coercePositiveInt(days, null); - if (value === null) { - return resolveAutopayString('autopay.slider.hint', 'Choose when to charge automatically.'); - } - const key = value === 1 ? 'autopay.slider.caption.one' : 'autopay.slider.caption'; - const template = t(key); - if (template && template !== key) { - return template.replace('{count}', String(value)); - } - return value === 1 - ? `Charge ${value} day before renewal` - : `Charge ${value} days before renewal`; - } - - function updateAutopaySliderTrack(slider, activeIndex) { - if (!slider) { - return; - } - const hasMultiple = autopayState.options.length > 1; - if (!autopayState.enabled || !hasMultiple) { - slider.style.background = `linear-gradient(90deg, var(--border-color) 0%, var(--border-color) 100%)`; - return; - } - const maxIndex = autopayState.options.length - 1; - const safeIndex = Math.max(0, Math.min(maxIndex, activeIndex || 0)); - const percentage = (safeIndex / maxIndex) * 100; - slider.style.background = `linear-gradient(90deg, rgba(var(--primary-rgb), 0.7) 0%, rgba(var(--primary-rgb), 0.7) ${percentage}%, var(--border-color) ${percentage}%, var(--border-color) 100%)`; - } - - function renderAutopayControls() { - const controls = document.getElementById('autopayControls'); - if (!controls) { - return; - } - - const shouldShowControls = autopayState.available; - controls.classList.toggle('hidden', !shouldShowControls); - if (!shouldShowControls) { - return; - } - - const enableBtn = document.getElementById('autopayEnableBtn'); - const disableBtn = document.getElementById('autopayDisableBtn'); - const sliderWrapper = document.getElementById('autopaySliderWrapper'); - const slider = document.getElementById('autopaySlider'); - const marksContainer = document.getElementById('autopaySliderMarks'); - const caption = document.getElementById('autopaySliderLabel'); - - const activeDays = getAutopayActiveDays(); - const options = autopayState.options; - const hasSlider = autopayState.enabled && options.length > 0; - - if (enableBtn) { - const key = autopayState.updating && autopayState.pendingAction === 'enable' - ? 'autopay.update_in_progress' - : 'autopay.toggle.enable'; - const fallback = autopayState.updating && autopayState.pendingAction === 'enable' - ? 'Updating…' - : 'Enable auto-pay'; - enableBtn.textContent = resolveAutopayString(key, fallback); - enableBtn.disabled = autopayState.updating || autopayState.enabled; - enableBtn.classList.toggle('active', autopayState.enabled); - enableBtn.setAttribute('aria-pressed', autopayState.enabled ? 'true' : 'false'); - } - - if (disableBtn) { - const key = autopayState.updating && autopayState.pendingAction === 'disable' - ? 'autopay.update_in_progress' - : 'autopay.toggle.disable'; - const fallback = autopayState.updating && autopayState.pendingAction === 'disable' - ? 'Updating…' - : 'Disable auto-pay'; - disableBtn.textContent = resolveAutopayString(key, fallback); - disableBtn.disabled = autopayState.updating || !autopayState.enabled; - disableBtn.classList.toggle('active', !autopayState.enabled); - disableBtn.setAttribute('aria-pressed', autopayState.enabled ? 'false' : 'true'); - } - - if (sliderWrapper) { - sliderWrapper.classList.toggle('hidden', !hasSlider); - } - - if (caption) { - caption.textContent = hasSlider && activeDays !== null - ? formatAutopayCaption(activeDays) - : resolveAutopayString('autopay.slider.hint', 'Choose when to charge automatically.'); - } - - if (slider) { - const maxIndex = Math.max(options.length - 1, 0); - slider.setAttribute('aria-valuemin', '0'); - slider.setAttribute('aria-valuemax', String(maxIndex)); - slider.disabled = autopayState.updating || !autopayState.enabled || options.length <= 1; - - if (options.length > 0) { - let activeIndex = options.findIndex(value => value === activeDays); - if (activeIndex < 0) { - activeIndex = options.length - 1; - } - slider.max = String(Math.max(options.length - 1, 0)); - slider.value = String(Math.max(activeIndex, 0)); - slider.setAttribute('aria-valuenow', activeDays !== null ? String(activeDays) : '0'); - slider.setAttribute('aria-label', formatAutopayCaption(activeDays)); - updateAutopaySliderTrack(slider, activeIndex); - } else { - slider.max = '0'; - slider.value = '0'; - slider.setAttribute('aria-valuenow', '0'); - slider.setAttribute('aria-label', resolveAutopayString('autopay.slider.hint', 'Choose when to charge automatically.')); - slider.style.background = `linear-gradient(90deg, var(--border-color) 0%, var(--border-color) 100%)`; - } - } - - if (marksContainer) { - marksContainer.innerHTML = ''; - if (hasSlider && options.length) { - const activeValue = activeDays; - options.forEach((value, index) => { - const mark = document.createElement('div'); - mark.className = 'autopay-slider-mark'; - if (value === activeValue) { - mark.classList.add('active'); - } - mark.textContent = formatAutopayDaysLabel(value); - if (!slider?.disabled) { - mark.style.cursor = 'pointer'; - mark.addEventListener('click', () => { - if (autopayState.updating || !autopayState.enabled) { - return; - } - const sliderElement = document.getElementById('autopaySlider'); - if (!sliderElement) { - return; - } - sliderElement.value = String(index); - sliderElement.dispatchEvent(new Event('input')); - sliderElement.dispatchEvent(new Event('change')); - }); - } - marksContainer.appendChild(mark); - }); - } - } - } - - function handleAutopayToggle(enabled) { - if (autopayState.updating || !autopayState.available) { - return; - } - if (autopayState.enabled === enabled) { - return; - } - - const previousEnabled = autopayState.enabled; - const previousDays = autopayState.daysBefore; - const targetDays = autopayState.daysBefore - ?? (autopayState.options.length ? autopayState.options[autopayState.options.length - 1] : null); - - autopayState.enabled = enabled; - autopayState.previewDays = null; - renderAutopayControls(); - - submitAutopayUpdate({ - enabled, - daysBefore: targetDays, - action: enabled ? 'enable' : 'disable', - }).catch(error => { - console.warn('Failed to toggle autopay:', error); - autopayState.enabled = previousEnabled; - autopayState.daysBefore = previousDays; - renderAutopayControls(); - }); - } - - function handleAutopaySliderInput(event) { - if (!autopayState.options.length) { - return; - } - const index = Math.round(Number(event.target.value) || 0); - const safeIndex = Math.max(0, Math.min(autopayState.options.length - 1, index)); - autopayState.previewDays = autopayState.options[safeIndex]; - renderAutopayControls(); - } - - function handleAutopaySliderChange(event) { - if (!autopayState.options.length) { - return; - } - const index = Math.round(Number(event.target.value) || 0); - const safeIndex = Math.max(0, Math.min(autopayState.options.length - 1, index)); - const selected = autopayState.options[safeIndex]; - autopayState.previewDays = null; - - if (selected === autopayState.daysBefore) { - renderAutopayControls(); - return; - } - - const previousDays = autopayState.daysBefore; - autopayState.daysBefore = selected; - renderAutopayControls(); - - if (!autopayState.enabled) { - return; - } - - submitAutopayUpdate({ - enabled: true, - daysBefore: selected, - action: 'days', - }).catch(error => { - console.warn('Failed to update autopay days:', error); - autopayState.daysBefore = previousDays; - renderAutopayControls(); - }); - } - - async function submitAutopayUpdate(options) { - const { enabled, daysBefore, action } = options || {}; - const initData = tg.initData || ''; - if (!initData) { - const error = createError('Authorization Error', t('subscription_settings.error.unauthorized')); - showPopup(error.message, resolveAutopayString('info.autopay', 'Auto-pay')); - throw error; - } - - const subscriptionId = userData?.subscription_id ?? userData?.subscriptionId ?? null; - if (!subscriptionId) { - const fallback = resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.'); - const error = createError('Autopay Error', fallback); - showPopup(error.message, resolveAutopayString('info.autopay', 'Auto-pay')); - throw error; - } - - const payload = { - initData, - subscription_id: subscriptionId, - subscriptionId, - enabled, - autopay_enabled: enabled, - autopayEnabled: enabled, - }; - - const daysValue = coercePositiveInt(daysBefore, null); - if (daysValue !== null) { - payload.days_before = daysValue; - payload.daysBefore = daysValue; - payload.autopay_days_before = daysValue; - payload.autopayDaysBefore = daysValue; - } - - autopayState.updating = true; - autopayState.pendingAction = action || (enabled ? 'enable' : 'disable'); - renderAutopayControls(); - - try { - const response = await fetch('/miniapp/subscription/autopay', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - const body = await parseJsonSafe(response); - if (!response.ok || (body && body.success === false)) { - const message = extractAutopayError(body, response.status); - throw createError('Autopay update error', message, response.status); - } - - if (!userData) { - userData = {}; - } - userData.autopay_enabled = enabled; - userData.autopayEnabled = enabled; - if (daysValue !== null) { - userData.autopay_days_before = daysValue; - userData.autopayDaysBefore = daysValue; - } - - autopayState.enabled = enabled; - if (daysValue !== null) { - autopayState.daysBefore = daysValue; - } - autopayState.previewDays = null; - - const successKey = action === 'days' - ? 'autopay.success.updated' - : enabled - ? 'autopay.success.enabled' - : 'autopay.success.disabled'; - const successFallback = action === 'days' - ? 'Auto-pay schedule updated.' - : enabled - ? 'Auto-pay enabled.' - : 'Auto-pay disabled.'; - showPopup(resolveAutopayString(successKey, successFallback), resolveAutopayString('info.autopay', 'Auto-pay')); - - renderUserData(); - - try { - await refreshSubscriptionData({ silent: true }); - } catch (refreshError) { - console.warn('Failed to refresh subscription after autopay update:', refreshError); - } - - return true; - } catch (error) { - const message = resolveAutopayErrorMessage(error); - showPopup(message, resolveAutopayString('info.autopay', 'Auto-pay')); - throw error; - } finally { - autopayState.updating = false; - autopayState.pendingAction = null; - renderAutopayControls(); - } - } - - function extractAutopayError(payload, status) { - if (status === 401) { - return t('subscription_settings.error.unauthorized'); - } - if (!payload || typeof payload !== 'object') { - return resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.'); - } - if (typeof payload.detail === 'string') { - return payload.detail; - } - if (payload.detail && typeof payload.detail === 'object') { - if (typeof payload.detail.message === 'string') { - return payload.detail.message; - } - if (typeof payload.detail.error === 'string') { - return payload.detail.error; - } - } - if (typeof payload.message === 'string') { - return payload.message; - } - if (typeof payload.error === 'string') { - return payload.error; - } - return resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.'); - } - - function resolveAutopayErrorMessage(error) { - if (!error) { - return resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.'); - } - if (typeof error === 'string') { - return error; - } - if (typeof error.message === 'string' && error.message.trim()) { - return error.message; - } - if (error.detail) { - if (typeof error.detail === 'string' && error.detail.trim()) { - return error.detail; - } - if (typeof error.detail.message === 'string' && error.detail.message.trim()) { - return error.detail.message; - } - } - if (error.status === 401) { - const unauthorized = t('subscription_settings.error.unauthorized'); - if (unauthorized && unauthorized !== 'subscription_settings.error.unauthorized') { - return unauthorized; - } - } - return resolveAutopayString('autopay.error.generic', 'Failed to update auto-pay settings. Please try again later.'); - } - - function setupAutopayControlsEvents() { - document.getElementById('autopayEnableBtn')?.addEventListener('click', () => { - handleAutopayToggle(true); - }); - document.getElementById('autopayDisableBtn')?.addEventListener('click', () => { - handleAutopayToggle(false); - }); - const slider = document.getElementById('autopaySlider'); - if (slider) { - slider.addEventListener('input', handleAutopaySliderInput); - slider.addEventListener('change', handleAutopaySliderChange); - } - } - function renderSubscriptionMissingCard() { const card = document.getElementById('subscriptionMissingCard'); if (!card) {