Merge pull request #1171 from Fr1ngg/revert-1170-2xx781-bedolaga/update-miniapp/index.html-logic

Revert "Polish mini app subscription state handling"
This commit is contained in:
Egor
2025-10-11 01:53:55 +03:00
committed by GitHub
3 changed files with 61 additions and 473 deletions

View File

@@ -2055,20 +2055,6 @@ async def _build_referral_info(
)
def _is_trial_available_for_user(user: User) -> bool:
if settings.TRIAL_DURATION_DAYS <= 0:
return False
if getattr(user, "has_had_paid_subscription", False):
return False
subscription = getattr(user, "subscription", None)
if subscription is not None:
return False
return True
@router.post("/subscription", response_model=MiniAppSubscriptionResponse)
async def get_subscription_details(
payload: MiniAppSubscriptionRequest,
@@ -2099,23 +2085,40 @@ async def get_subscription_details(
user = await get_user_by_telegram_id(db, telegram_id)
purchase_url = (settings.MINIAPP_PURCHASE_URL or "").strip()
if not user:
detail: Dict[str, Any] = {
"code": "user_not_found",
"message": "User not found. Please register in the bot to continue.",
"title": "Registration required",
}
if not user or not user.subscription:
detail: Union[str, Dict[str, str]] = "Subscription not found"
if purchase_url:
detail["purchase_url"] = purchase_url
detail = {
"message": "Subscription not found",
"purchase_url": purchase_url,
}
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=detail,
)
subscription = getattr(user, "subscription", None)
subscription = user.subscription
traffic_used = _format_gb(subscription.traffic_used_gb)
traffic_limit = subscription.traffic_limit_gb or 0
lifetime_used = _bytes_to_gb(getattr(user, "lifetime_used_traffic_bytes", 0))
status_actual = subscription.actual_status
links_payload = await _load_subscription_links(subscription)
subscription_url = links_payload.get("subscription_url") or subscription.subscription_url
subscription_crypto_link = (
links_payload.get("happ_crypto_link")
or subscription.subscription_crypto_link
)
happ_redirect_link = get_happ_cryptolink_redirect_link(subscription_crypto_link)
connected_squads: List[str] = list(subscription.connected_squads or [])
connected_servers = await _resolve_connected_servers(db, connected_squads)
devices_count, devices = await _load_devices_info(user)
links: List[str] = links_payload.get("links") or connected_squads
ss_conf_links: Dict[str, str] = links_payload.get("ss_conf_links") or {}
transactions_query = (
select(Transaction)
.where(Transaction.user_id == user.id)
@@ -2183,10 +2186,7 @@ async def get_subscription_details(
)
)
if subscription:
active_offer_contexts.extend(
await _find_active_test_access_offers(db, subscription)
)
active_offer_contexts.extend(await _find_active_test_access_offers(db, subscription))
promo_offers = await _build_promo_offer_models(
db,
@@ -2312,46 +2312,6 @@ async def get_subscription_details(
updated_at=getattr(service_rules, "updated_at", None),
)
links_payload: Dict[str, Any] = {}
connected_squads: List[str] = []
connected_servers: List[MiniAppConnectedServer] = []
links: List[str] = []
ss_conf_links: Dict[str, str] = {}
subscription_url: Optional[str] = None
subscription_crypto_link: Optional[str] = None
happ_redirect_link: Optional[str] = None
remnawave_short_uuid: Optional[str] = None
status_actual = "missing"
subscription_status_value = "none"
traffic_used_value = 0.0
traffic_limit_value = 0
device_limit_value: Optional[int] = settings.DEFAULT_DEVICE_LIMIT or None
autopay_enabled = False
if subscription:
traffic_used_value = _format_gb(subscription.traffic_used_gb)
traffic_limit_value = subscription.traffic_limit_gb or 0
status_actual = subscription.actual_status
subscription_status_value = subscription.status
links_payload = await _load_subscription_links(subscription)
subscription_url = (
links_payload.get("subscription_url") or subscription.subscription_url
)
subscription_crypto_link = (
links_payload.get("happ_crypto_link")
or subscription.subscription_crypto_link
)
happ_redirect_link = get_happ_cryptolink_redirect_link(subscription_crypto_link)
connected_squads = list(subscription.connected_squads or [])
connected_servers = await _resolve_connected_servers(db, connected_squads)
links = links_payload.get("links") or connected_squads
ss_conf_links = links_payload.get("ss_conf_links") or {}
remnawave_short_uuid = subscription.remnawave_short_uuid
device_limit_value = subscription.device_limit
autopay_enabled = bool(subscription.autopay_enabled)
devices_count, devices = await _load_devices_info(user)
response_user = MiniAppSubscriptionUser(
telegram_id=user.telegram_id,
username=user.username,
@@ -2367,15 +2327,15 @@ async def get_subscription_details(
),
language=user.language,
status=user.status,
subscription_status=subscription_status_value,
subscription_status=subscription.status,
subscription_actual_status=status_actual,
status_label=_status_label(status_actual),
expires_at=getattr(subscription, "end_date", None),
device_limit=device_limit_value,
traffic_used_gb=round(traffic_used_value, 2),
traffic_used_label=_format_gb_label(traffic_used_value),
traffic_limit_gb=traffic_limit_value,
traffic_limit_label=_format_limit_label(traffic_limit_value),
expires_at=subscription.end_date,
device_limit=subscription.device_limit,
traffic_used_gb=round(traffic_used, 2),
traffic_used_label=_format_gb_label(traffic_used),
traffic_limit_gb=traffic_limit,
traffic_limit_label=_format_limit_label(traffic_limit),
lifetime_used_traffic_gb=lifetime_used,
has_active_subscription=status_actual in {"active", "trial"},
promo_offer_discount_percent=active_discount_percent,
@@ -2385,14 +2345,9 @@ async def get_subscription_details(
referral_info = await _build_referral_info(db, user)
trial_available = _is_trial_available_for_user(user)
trial_duration_days = (
settings.TRIAL_DURATION_DAYS if settings.TRIAL_DURATION_DAYS > 0 else None
)
return MiniAppSubscriptionResponse(
subscription_id=getattr(subscription, "id", None),
remnawave_short_uuid=remnawave_short_uuid,
subscription_id=subscription.id,
remnawave_short_uuid=subscription.remnawave_short_uuid,
user=response_user,
subscription_url=subscription_url,
subscription_crypto_link=subscription_crypto_link,
@@ -2403,9 +2358,9 @@ async def get_subscription_details(
connected_servers=connected_servers,
connected_devices_count=devices_count,
connected_devices=devices,
happ=links_payload.get("happ") if subscription else None,
happ_link=links_payload.get("happ_link") if subscription else None,
happ_crypto_link=links_payload.get("happ_crypto_link") if subscription else None,
happ=links_payload.get("happ"),
happ_link=links_payload.get("happ_link"),
happ_crypto_link=links_payload.get("happ_crypto_link"),
happ_cryptolink_redirect_link=happ_redirect_link,
balance_kopeks=user.balance_kopeks,
balance_rubles=round(user.balance_rubles, 2),
@@ -2425,21 +2380,12 @@ async def get_subscription_details(
total_spent_kopeks=total_spent_kopeks,
total_spent_rubles=round(total_spent_kopeks / 100, 2),
total_spent_label=settings.format_price(total_spent_kopeks),
subscription_type=(
"trial"
if subscription and subscription.is_trial
else ("paid" if subscription else "none")
),
autopay_enabled=autopay_enabled,
subscription_type="trial" if subscription.is_trial else "paid",
autopay_enabled=bool(subscription.autopay_enabled),
branding=settings.get_miniapp_branding(),
faq=faq_payload,
legal_documents=legal_documents_payload,
referral=referral_info,
subscription_missing=subscription is None,
subscription_missing_reason="not_found" if subscription is None else None,
trial_available=trial_available,
trial_duration_days=trial_duration_days,
trial_status="available" if trial_available else "unavailable",
)

View File

@@ -383,7 +383,7 @@ class MiniAppPaymentStatusResponse(BaseModel):
class MiniAppSubscriptionResponse(BaseModel):
success: bool = True
subscription_id: Optional[int] = None
subscription_id: int
remnawave_short_uuid: Optional[str] = None
user: MiniAppSubscriptionUser
subscription_url: Optional[str] = None
@@ -415,11 +415,6 @@ class MiniAppSubscriptionResponse(BaseModel):
faq: Optional[MiniAppFaq] = None
legal_documents: Optional[MiniAppLegalDocuments] = None
referral: Optional[MiniAppReferralInfo] = None
subscription_missing: bool = False
subscription_missing_reason: Optional[str] = None
trial_available: bool = False
trial_duration_days: Optional[int] = None
trial_status: Optional[str] = None
class MiniAppSubscriptionServerOption(BaseModel):

View File

@@ -331,16 +331,6 @@
min-width: 220px;
}
.error.error-user-missing {
background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.14), rgba(var(--primary-rgb), 0.08));
box-shadow: var(--shadow-md);
border: 1px solid rgba(var(--primary-rgb), 0.25);
}
.error.error-user-missing .error-text {
color: var(--text-primary);
}
/* Cards */
.card {
background: var(--bg-secondary);
@@ -1791,21 +1781,6 @@
background: rgba(255, 255, 255, 0.2);
}
:root[data-theme="dark"] .subscription-missing-card {
background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.28), rgba(var(--primary-rgb), 0.12));
border-color: rgba(var(--primary-rgb), 0.5);
}
:root[data-theme="dark"] .subscription-missing-icon {
background: rgba(var(--primary-rgb), 0.3);
color: #bfdbfe;
}
:root[data-theme="dark"] .status-missing {
background: rgba(59, 130, 246, 0.18);
color: #bfdbfe;
}
:root[data-theme="dark"] .promo-offer-chip {
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.9);
@@ -1857,64 +1832,6 @@
min-width: 0;
}
.subscription-missing-card {
display: flex;
gap: 16px;
padding: 20px;
align-items: center;
background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.06), rgba(var(--primary-rgb), 0.12));
border: 2px dashed rgba(var(--primary-rgb), 0.35);
}
.subscription-missing-icon {
width: 56px;
height: 56px;
border-radius: 16px;
background: rgba(var(--primary-rgb), 0.15);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
flex-shrink: 0;
color: var(--primary);
}
.subscription-missing-content {
flex: 1;
min-width: 0;
}
.subscription-missing-title {
font-size: 18px;
font-weight: 700;
margin-bottom: 6px;
color: var(--text-primary);
}
.subscription-missing-description {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 12px;
}
.subscription-missing-hint {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 16px;
}
.subscription-missing-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.subscription-missing-actions .btn-primary,
.subscription-missing-actions .btn-secondary {
flex: 1;
min-width: 140px;
}
.user-name {
font-size: 20px;
font-weight: 700;
@@ -1965,11 +1882,6 @@
color: #41464b;
}
.status-missing {
background: linear-gradient(135deg, #e0e7ff, #eef2ff);
color: #1e3a8a;
}
/* Stats Grid */
.stats-grid {
display: grid;
@@ -4298,7 +4210,7 @@
<!-- Error State -->
<div id="errorState" class="error hidden">
<div class="error-icon" id="errorIcon">⚠️</div>
<div class="error-icon">⚠️</div>
<div class="error-title" id="errorTitle" data-i18n="error.default.title">Subscription Not Found</div>
<div class="error-text" id="errorText" data-i18n="error.default.message">Please contact support to activate your subscription</div>
<div class="error-actions">
@@ -4316,22 +4228,8 @@
<!-- Promo Offers -->
<div id="promoOffersContainer" class="promo-offers hidden"></div>
<!-- Subscription Missing -->
<div class="card subscription-missing-card hidden animate-in" id="subscriptionMissingCard">
<div class="subscription-missing-icon" aria-hidden="true">🛡️</div>
<div class="subscription-missing-content">
<div class="subscription-missing-title" id="subscriptionMissingTitle" data-i18n="subscription_missing.title">No active subscription</div>
<div class="subscription-missing-description" id="subscriptionMissingDescription" data-i18n="subscription_missing.description.default">Purchase a plan or activate a trial to continue.</div>
<div class="subscription-missing-hint" id="subscriptionMissingHint" data-i18n="subscription_missing.hint">Top up your balance after activation to stay connected.</div>
<div class="subscription-missing-actions">
<button class="btn btn-primary" type="button" id="subscriptionMissingPurchaseBtn" data-i18n="subscription_missing.action.buy">Buy subscription</button>
<button class="btn btn-secondary hidden" type="button" id="subscriptionMissingTrialBtn" data-i18n="subscription_missing.action.trial">Activate trial</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">
@@ -5074,8 +4972,6 @@
'app.loading': 'Loading your subscription...',
'error.default.title': 'Subscription Not Found',
'error.default.message': 'Please contact support to activate your subscription.',
'error.user_not_found.title': 'Register in the bot',
'error.user_not_found.message': 'Open the Telegram bot to register before using the mini app.',
'stats.days_left': 'Days left',
'stats.servers': 'Servers',
'stats.devices': 'Devices',
@@ -5135,7 +5031,6 @@
'topup.status.retry': 'Try again',
'topup.done': 'Done',
'button.buy_subscription': 'Buy Subscription',
'button.open_bot': 'Open Telegram bot',
'subscription_purchase.title': 'Purchase subscription',
'subscription_purchase.subtitle': 'Configure the plan before completing the purchase.',
'subscription_purchase.status.loading': 'Loading subscription options…',
@@ -5385,19 +5280,9 @@
'status.trial': 'Trial',
'status.expired': 'Expired',
'status.disabled': 'Disabled',
'status.missing': 'Inactive',
'status.unknown': 'Unknown',
'subscription.type.trial': 'Trial',
'subscription.type.paid': 'Paid',
'subscription.type.none': 'No subscription',
'subscription_missing.title': 'No active subscription',
'subscription_missing.description.default': 'You do not have an active subscription yet. Purchase a plan to get access.',
'subscription_missing.description.trial': 'Activate your free {days}-day trial or choose a plan to continue.',
'subscription_missing.description.trial_short': 'Activate your free trial or choose a plan to continue.',
'subscription_missing.description.no_trial': 'Your trial is no longer available. Purchase a plan to continue.',
'subscription_missing.hint': 'After activation you can top up your balance here to stay connected.',
'subscription_missing.action.buy': 'Buy subscription',
'subscription_missing.action.trial': 'Activate trial',
'autopay.enabled': 'Enabled',
'autopay.disabled': 'Disabled',
'platform.ios': 'iOS',
@@ -5442,8 +5327,6 @@
'app.loading': 'Загружаем вашу подписку...',
'error.default.title': 'Подписка не найдена',
'error.default.message': 'Свяжитесь с поддержкой, чтобы активировать подписку.',
'error.user_not_found.title': 'Зарегистрируйтесь в боте',
'error.user_not_found.message': 'Откройте телеграм-бота, чтобы зарегистрироваться перед использованием мини-приложения.',
'stats.days_left': 'Осталось дней',
'stats.servers': 'Серверы',
'stats.devices': 'Устройства',
@@ -5503,7 +5386,6 @@
'topup.status.retry': 'Повторить попытку',
'topup.done': 'Готово',
'button.buy_subscription': 'Купить подписку',
'button.open_bot': 'Открыть бота',
'subscription_purchase.title': 'Оформление подписки',
'subscription_purchase.subtitle': 'Настройте параметры перед покупкой.',
'subscription_purchase.status.loading': 'Загружаем доступные варианты…',
@@ -5753,19 +5635,9 @@
'status.trial': 'Пробная',
'status.expired': 'Истекла',
'status.disabled': 'Отключена',
'status.missing': 'Неактивна',
'status.unknown': 'Неизвестно',
'subscription.type.trial': 'Триал',
'subscription.type.paid': 'Платная',
'subscription.type.none': 'Нет подписки',
'subscription_missing.title': 'Нет активной подписки',
'subscription_missing.description.default': 'У вас ещё нет активной подписки. Оформите тариф, чтобы получить доступ.',
'subscription_missing.description.trial': 'Активируйте бесплатный триал на {days} дн. или выберите тариф, чтобы продолжить.',
'subscription_missing.description.trial_short': 'Активируйте бесплатный триал или выберите тариф, чтобы продолжить.',
'subscription_missing.description.no_trial': 'Пробный период недоступен. Оформите подписку, чтобы продолжить.',
'subscription_missing.hint': 'После активации вы сможете пополнить баланс здесь для бесперебойной работы.',
'subscription_missing.action.buy': 'Купить подписку',
'subscription_missing.action.trial': 'Активировать триал',
'autopay.enabled': 'Включен',
'autopay.disabled': 'Выключен',
'platform.ios': 'iOS',
@@ -6608,66 +6480,16 @@
if (!titleElement || !textElement) {
return;
}
const code = typeof currentErrorState?.code === 'string'
? currentErrorState.code.toLowerCase()
: null;
let title = currentErrorState?.title || null;
let message = currentErrorState?.message || null;
if (code) {
const titleKey = `error.${code}.title`;
const translatedTitle = t(titleKey);
if (translatedTitle && translatedTitle !== titleKey) {
title = translatedTitle;
}
const messageKey = `error.${code}.message`;
const translatedMessage = t(messageKey);
if (translatedMessage && translatedMessage !== messageKey) {
message = translatedMessage;
}
}
const defaultTitle = t('error.default.title');
if (!title) {
title = defaultTitle === 'error.default.title'
? (currentErrorState?.title || 'Subscription Not Found')
: defaultTitle;
}
const defaultMessage = t('error.default.message');
if (!message) {
message = defaultMessage === 'error.default.message'
? (currentErrorState?.message || 'Please contact support to activate your subscription.')
: defaultMessage;
}
const title = currentErrorState?.title || t('error.default.title');
const message = currentErrorState?.message || t('error.default.message');
titleElement.textContent = title;
textElement.textContent = message;
const errorStateElement = document.getElementById('errorState');
if (errorStateElement) {
errorStateElement.classList.toggle('error-user-missing', code === 'user_not_found');
}
const iconElement = document.getElementById('errorIcon');
if (iconElement) {
iconElement.textContent = code === 'user_not_found' ? '🤖' : '⚠️';
}
const purchaseButton = document.getElementById('purchaseBtn');
if (purchaseButton) {
const link = getEffectivePurchaseUrl();
purchaseButton.classList.toggle('hidden', !link);
purchaseButton.disabled = !link;
const buttonKey = code === 'user_not_found'
? 'button.open_bot'
: 'button.buy_subscription';
const label = t(buttonKey);
const fallback = code === 'user_not_found' ? 'Open bot' : 'Buy subscription';
purchaseButton.textContent = label === buttonKey ? fallback : label;
}
}
@@ -6801,7 +6623,6 @@
let title = response.status === 401 ? 'Authorization Error' : 'Subscription Not Found';
let purchaseUrl = null;
let code = null;
try {
const errorPayload = await response.json();
if (errorPayload?.detail) {
@@ -6811,12 +6632,6 @@
if (typeof errorPayload.detail.message === 'string') {
detail = errorPayload.detail.message;
}
if (typeof errorPayload.detail.title === 'string') {
title = errorPayload.detail.title;
}
if (typeof errorPayload.detail.code === 'string') {
code = errorPayload.detail.code;
}
purchaseUrl = errorPayload.detail.purchase_url
|| errorPayload.detail.purchaseUrl
|| purchaseUrl;
@@ -6829,10 +6644,6 @@
title = errorPayload.title;
}
if (!code && typeof errorPayload?.code === 'string') {
code = errorPayload.code;
}
purchaseUrl = purchaseUrl
|| errorPayload?.purchase_url
|| errorPayload?.purchaseUrl
@@ -6842,9 +6653,6 @@
}
const errorObject = createError(title, detail, response.status);
if (code) {
errorObject.code = code;
}
const normalizedPurchaseUrl = normalizeUrl(purchaseUrl);
if (normalizedPurchaseUrl) {
errorObject.purchaseUrl = normalizedPurchaseUrl;
@@ -6872,33 +6680,6 @@
subscriptionPurchaseUrl = normalizedPurchaseUrl;
userData.subscriptionPurchaseUrl = normalizedPurchaseUrl || null;
const subscriptionMissingValue = Boolean(
userData.subscription_missing ?? userData.subscriptionMissing
);
userData.subscription_missing = subscriptionMissingValue;
userData.subscriptionMissing = subscriptionMissingValue;
const trialAvailableValue = Boolean(
userData.trial_available ?? userData.trialAvailable
);
userData.trial_available = trialAvailableValue;
userData.trialAvailable = trialAvailableValue;
const trialDuration = coercePositiveInt(
userData.trial_duration_days ?? userData.trialDurationDays ?? null,
null,
);
userData.trial_duration_days = trialDuration;
userData.trialDurationDays = trialDuration;
const missingReason = (
userData.subscription_missing_reason
?? userData.subscriptionMissingReason
?? null
);
userData.subscription_missing_reason = missingReason;
userData.subscriptionMissingReason = missingReason;
if (userData.branding) {
applyBrandingOverrides(userData.branding);
}
@@ -7015,24 +6796,12 @@
|| `User ${user.telegram_id || ''}`.trim();
const avatarChar = (fallbackName.replace(/^@/, '')[0] || 'U').toUpperCase();
const subscriptionMissing = Boolean(
userData?.subscription_missing ?? userData?.subscriptionMissing
);
document.getElementById('userAvatar').textContent = avatarChar;
document.getElementById('userName').textContent = fallbackName;
const userCard = document.getElementById('userCard');
if (userCard) {
userCard.classList.toggle('hidden', subscriptionMissing);
}
const knownStatuses = ['active', 'trial', 'expired', 'disabled', 'missing'];
const knownStatuses = ['active', 'trial', 'expired', 'disabled'];
const statusValueRaw = (user.subscription_actual_status || user.subscription_status || 'active').toLowerCase();
let statusClass = knownStatuses.includes(statusValueRaw) ? statusValueRaw : 'unknown';
if (subscriptionMissing && statusClass !== 'missing') {
statusClass = 'missing';
}
const statusClass = knownStatuses.includes(statusValueRaw) ? statusValueRaw : 'unknown';
const statusBadge = document.getElementById('statusBadge');
const statusKey = `status.${statusClass}`;
const statusLabel = t(statusKey);
@@ -7110,7 +6879,6 @@
: autopayLabel;
}
renderSubscriptionMissingCard();
renderSubscriptionPurchaseCard();
renderSubscriptionRenewalCard();
renderSubscriptionSettingsCard();
@@ -7127,102 +6895,6 @@
updateActionButtons();
}
function renderSubscriptionMissingCard() {
const card = document.getElementById('subscriptionMissingCard');
if (!card) {
return;
}
const subscriptionMissing = Boolean(
userData?.subscription_missing ?? userData?.subscriptionMissing
);
card.classList.toggle('hidden', !subscriptionMissing);
if (!subscriptionMissing) {
return;
}
const titleElement = document.getElementById('subscriptionMissingTitle');
if (titleElement) {
const titleValue = t('subscription_missing.title');
titleElement.textContent = titleValue && titleValue !== 'subscription_missing.title'
? titleValue
: 'No active subscription';
}
const trialAvailable = Boolean(
userData?.trial_available ?? userData?.trialAvailable
);
const trialDuration = coercePositiveInt(
userData?.trial_duration_days ?? userData?.trialDurationDays ?? null,
null,
);
const missingReason = String(
userData?.subscription_missing_reason ?? userData?.subscriptionMissingReason ?? ''
).toLowerCase();
const descriptionElement = document.getElementById('subscriptionMissingDescription');
if (descriptionElement) {
let descriptionKey = 'subscription_missing.description.default';
if (trialAvailable) {
descriptionKey = trialDuration
? 'subscription_missing.description.trial'
: 'subscription_missing.description.trial_short';
} else if (missingReason === 'trial_expired') {
descriptionKey = 'subscription_missing.description.no_trial';
}
let descriptionValue = t(descriptionKey);
if (!descriptionValue || descriptionValue === descriptionKey) {
if (trialAvailable) {
descriptionValue = trialDuration
? `Activate your free ${trialDuration}-day trial or choose a plan to continue.`
: 'Activate your free trial or choose a plan to continue.';
} else if (missingReason === 'trial_expired') {
descriptionValue = 'Your trial is no longer available. Purchase a plan to continue.';
} else {
descriptionValue = 'You do not have an active subscription yet. Purchase a plan to get access.';
}
}
if (trialDuration && descriptionValue.includes('{days}')) {
descriptionValue = descriptionValue.replace('{days}', String(trialDuration));
}
descriptionElement.textContent = descriptionValue;
}
const hintElement = document.getElementById('subscriptionMissingHint');
if (hintElement) {
const hintValue = t('subscription_missing.hint');
hintElement.textContent = hintValue && hintValue !== 'subscription_missing.hint'
? hintValue
: 'After activation you can top up your balance here to stay connected.';
}
const purchaseButton = document.getElementById('subscriptionMissingPurchaseBtn');
if (purchaseButton) {
const label = t('subscription_missing.action.buy');
purchaseButton.textContent = label && label !== 'subscription_missing.action.buy'
? label
: 'Buy subscription';
const hasPurchaseFlow = shouldShowPurchaseConfigurator() || Boolean(getEffectivePurchaseUrl());
purchaseButton.classList.toggle('hidden', !hasPurchaseFlow);
purchaseButton.disabled = !hasPurchaseFlow;
}
const trialButton = document.getElementById('subscriptionMissingTrialBtn');
if (trialButton) {
const label = t('subscription_missing.action.trial');
trialButton.textContent = label && label !== 'subscription_missing.action.trial'
? label
: 'Activate trial';
trialButton.classList.toggle('hidden', !trialAvailable);
trialButton.disabled = !trialAvailable;
}
}
function resolvePromoOfferIcon(offer) {
if (offer?.icon && typeof offer.icon === 'string') {
return offer.icon;
@@ -15532,7 +15204,7 @@
async function copySubscriptionUrl(url) {
if (!url) return;
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(url);
@@ -15571,39 +15243,6 @@
}
}
function handlePurchaseAction(event) {
if (event && typeof event.preventDefault === 'function') {
event.preventDefault();
}
if (shouldShowPurchaseConfigurator()) {
openSubscriptionPurchaseModal();
return true;
}
const link = getEffectivePurchaseUrl();
if (link) {
openExternalLink(link, { openInMiniApp: true });
return true;
}
return false;
}
function handleTrialAction(event) {
if (event && typeof event.preventDefault === 'function') {
event.preventDefault();
}
const link = getEffectivePurchaseUrl();
if (link) {
openExternalLink(link, { openInMiniApp: true });
return true;
}
return handlePurchaseAction();
}
function updateActionButtons() {
const connectBtn = document.getElementById('connectBtn');
const copyBtn = document.getElementById('copyBtn');
@@ -15640,7 +15279,6 @@
currentErrorState = {
title: error?.title,
message: error?.message,
code: typeof error?.code === 'string' ? error.code : null,
purchaseUrl: normalizeUrl(error?.purchaseUrl) || null,
};
updateErrorTexts();
@@ -15767,9 +15405,18 @@
}
});
document.getElementById('purchaseBtn')?.addEventListener('click', handlePurchaseAction);
document.getElementById('subscriptionMissingPurchaseBtn')?.addEventListener('click', handlePurchaseAction);
document.getElementById('subscriptionMissingTrialBtn')?.addEventListener('click', handleTrialAction);
document.getElementById('purchaseBtn')?.addEventListener('click', event => {
if (shouldShowPurchaseConfigurator()) {
event.preventDefault();
openSubscriptionPurchaseModal();
return;
}
const link = getEffectivePurchaseUrl();
if (!link) {
return;
}
openExternalLink(link, { openInMiniApp: true });
});
initializePromoCodeForm();