mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-23 21:01:17 +00:00
Revert "Add promo code activation UI to mini app"
This commit is contained in:
@@ -39,7 +39,6 @@ from app.services.remnawave_service import (
|
||||
RemnaWaveService,
|
||||
)
|
||||
from app.services.promo_offer_service import promo_offer_service
|
||||
from app.services.promocode_service import PromoCodeService
|
||||
from app.services.subscription_service import SubscriptionService
|
||||
from app.utils.subscription_utils import get_happ_cryptolink_redirect_link
|
||||
from app.utils.telegram_webapp import (
|
||||
@@ -59,8 +58,6 @@ from ..schemas.miniapp import (
|
||||
MiniAppPromoOffer,
|
||||
MiniAppPromoOfferClaimRequest,
|
||||
MiniAppPromoOfferClaimResponse,
|
||||
MiniAppPromoCodeActivationRequest,
|
||||
MiniAppPromoCodeActivationResponse,
|
||||
MiniAppRichTextDocument,
|
||||
MiniAppSubscriptionRequest,
|
||||
MiniAppSubscriptionResponse,
|
||||
@@ -72,7 +69,6 @@ from ..schemas.miniapp import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
promo_code_service = PromoCodeService()
|
||||
|
||||
|
||||
def _format_gb(value: Optional[float]) -> float:
|
||||
@@ -1051,83 +1047,6 @@ async def get_subscription_details(
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/promo-codes/activate",
|
||||
response_model=MiniAppPromoCodeActivationResponse,
|
||||
)
|
||||
async def activate_promo_code(
|
||||
payload: MiniAppPromoCodeActivationRequest,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
) -> MiniAppPromoCodeActivationResponse:
|
||||
try:
|
||||
webapp_data = parse_webapp_init_data(payload.init_data, settings.BOT_TOKEN)
|
||||
except TelegramWebAppAuthError as error:
|
||||
raise HTTPException(
|
||||
status.HTTP_401_UNAUTHORIZED,
|
||||
detail={"code": "unauthorized", "message": str(error)},
|
||||
) from error
|
||||
|
||||
telegram_user = webapp_data.get("user")
|
||||
if not isinstance(telegram_user, dict) or "id" not in telegram_user:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
detail={"code": "invalid_user", "message": "Invalid Telegram user payload"},
|
||||
)
|
||||
|
||||
try:
|
||||
telegram_id = int(telegram_user["id"])
|
||||
except (TypeError, ValueError):
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
detail={"code": "invalid_user", "message": "Invalid Telegram user identifier"},
|
||||
) from None
|
||||
|
||||
user = await get_user_by_telegram_id(db, telegram_id)
|
||||
if not user:
|
||||
return MiniAppPromoCodeActivationResponse(
|
||||
success=False,
|
||||
error_code="user_not_found",
|
||||
message="User not found",
|
||||
)
|
||||
|
||||
normalized_code = (payload.code or "").strip().upper()
|
||||
if not normalized_code:
|
||||
return MiniAppPromoCodeActivationResponse(
|
||||
success=False,
|
||||
error_code="invalid_code",
|
||||
message="Promo code is required",
|
||||
)
|
||||
|
||||
result = await promo_code_service.activate_promocode(db, user.id, normalized_code)
|
||||
|
||||
if result.get("success"):
|
||||
description = (result.get("description") or "").strip()
|
||||
promocode_data = result.get("promocode") or {}
|
||||
return MiniAppPromoCodeActivationResponse(
|
||||
success=True,
|
||||
description=description or None,
|
||||
code=promocode_data.get("code") or normalized_code,
|
||||
)
|
||||
|
||||
error_code = result.get("error") or "generic"
|
||||
message_map = {
|
||||
"not_found": "Promo code not found",
|
||||
"expired": "Promo code expired",
|
||||
"used": "Promo code already used",
|
||||
"already_used_by_user": "Promo code already used by this user",
|
||||
"server_error": "Failed to activate promo code",
|
||||
"user_not_found": "User not found",
|
||||
"invalid_code": "Promo code is required",
|
||||
}
|
||||
message = message_map.get(error_code, "Failed to activate promo code")
|
||||
|
||||
return MiniAppPromoCodeActivationResponse(
|
||||
success=False,
|
||||
error_code=error_code,
|
||||
message=message,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/promo-offers/{offer_id}/claim",
|
||||
response_model=MiniAppPromoOfferClaimResponse,
|
||||
|
||||
@@ -123,19 +123,6 @@ class MiniAppPromoOfferClaimResponse(BaseModel):
|
||||
code: Optional[str] = None
|
||||
|
||||
|
||||
class MiniAppPromoCodeActivationRequest(BaseModel):
|
||||
init_data: str = Field(..., alias="initData")
|
||||
code: str
|
||||
|
||||
|
||||
class MiniAppPromoCodeActivationResponse(BaseModel):
|
||||
success: bool
|
||||
description: Optional[str] = None
|
||||
code: Optional[str] = None
|
||||
error_code: Optional[str] = Field(default=None, alias="errorCode")
|
||||
message: Optional[str] = None
|
||||
|
||||
|
||||
class MiniAppFaqItem(BaseModel):
|
||||
id: int
|
||||
title: Optional[str] = None
|
||||
|
||||
@@ -1079,139 +1079,6 @@
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Promo Code */
|
||||
.promo-code-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.promo-code-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.promo-code-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.promo-code-subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.promo-code-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.promo-code-input-wrapper {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
padding: 6px;
|
||||
border-radius: var(--radius-lg);
|
||||
background: rgba(var(--primary-rgb), 0.04);
|
||||
border: 1px solid rgba(var(--primary-rgb), 0.18);
|
||||
}
|
||||
|
||||
.promo-code-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 10px 12px;
|
||||
outline: none;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.promo-code-input::placeholder {
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.7;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.promo-code-button {
|
||||
border: none;
|
||||
border-radius: calc(var(--radius-lg) - 4px);
|
||||
background: linear-gradient(135deg, var(--primary), rgba(var(--primary-rgb), 0.8));
|
||||
color: var(--tg-theme-button-text-color);
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
padding: 0 18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.promo-code-button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.promo-code-button:not(:disabled):hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.promo-code-status {
|
||||
border-radius: var(--radius);
|
||||
padding: 12px 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.promo-code-status::before {
|
||||
content: '\2714';
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.promo-code-status-info {
|
||||
background: rgba(var(--primary-rgb), 0.08);
|
||||
color: var(--primary);
|
||||
border: 1px solid rgba(var(--primary-rgb), 0.25);
|
||||
}
|
||||
|
||||
.promo-code-status-info::before {
|
||||
content: '\2139';
|
||||
}
|
||||
|
||||
.promo-code-status-success {
|
||||
background: rgba(16, 185, 129, 0.12);
|
||||
color: var(--success);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.promo-code-status-success::before {
|
||||
content: '\2728';
|
||||
}
|
||||
|
||||
.promo-code-status-error {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
color: var(--danger);
|
||||
border: 1px solid rgba(239, 68, 68, 0.28);
|
||||
}
|
||||
|
||||
.promo-code-status-error::before {
|
||||
content: '\26A0';
|
||||
}
|
||||
|
||||
/* Transaction History */
|
||||
.history-list {
|
||||
list-style: none;
|
||||
@@ -2134,7 +2001,6 @@
|
||||
:root[data-theme="dark"] .card,
|
||||
:root[data-theme="dark"] .user-card,
|
||||
:root[data-theme="dark"] .balance-card,
|
||||
:root[data-theme="dark"] .promo-code-card,
|
||||
:root[data-theme="dark"] .card.expandable,
|
||||
:root[data-theme="dark"] .language-select,
|
||||
:root[data-theme="dark"] .theme-toggle {
|
||||
@@ -2146,30 +2012,10 @@
|
||||
:root[data-theme="dark"] .stat-item,
|
||||
:root[data-theme="dark"] .server-item,
|
||||
:root[data-theme="dark"] .device-item,
|
||||
:root[data-theme="dark"] .app-card,
|
||||
:root[data-theme="dark"] .promo-code-input-wrapper {
|
||||
:root[data-theme="dark"] .app-card {
|
||||
border-color: rgba(148, 163, 184, 0.25);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .promo-code-input-wrapper {
|
||||
background: rgba(148, 163, 184, 0.12);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .promo-code-status-info {
|
||||
background: rgba(var(--primary-rgb), 0.18);
|
||||
border-color: rgba(var(--primary-rgb), 0.35);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .promo-code-status-success {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
border-color: rgba(16, 185, 129, 0.45);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .promo-code-status-error {
|
||||
background: rgba(239, 68, 68, 0.24);
|
||||
border-color: rgba(239, 68, 68, 0.45);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .btn-primary {
|
||||
box-shadow: 0 10px 30px rgba(37, 99, 235, 0.45);
|
||||
}
|
||||
@@ -2372,33 +2218,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Promo Code Card -->
|
||||
<div class="card promo-code-card" id="promoCodeCard">
|
||||
<div class="promo-code-header">
|
||||
<div class="promo-code-title" data-i18n="promo_code.title">Have a promo code?</div>
|
||||
<div class="promo-code-subtitle" data-i18n="promo_code.subtitle">Enter it to receive your reward.</div>
|
||||
</div>
|
||||
<form class="promo-code-form" id="promoCodeForm" novalidate>
|
||||
<div class="promo-code-input-wrapper">
|
||||
<input
|
||||
type="text"
|
||||
id="promoCodeInput"
|
||||
class="promo-code-input"
|
||||
inputmode="text"
|
||||
autocomplete="off"
|
||||
autocapitalize="characters"
|
||||
spellcheck="false"
|
||||
data-i18n-placeholder="promo_code.placeholder"
|
||||
placeholder="Enter promo code"
|
||||
>
|
||||
<button type="submit" class="promo-code-button" id="promoCodeSubmit">
|
||||
<span class="promo-code-button-label" data-i18n="promo_code.button">Activate</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="promo-code-status hidden" id="promoCodeStatus" role="status" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
<!-- History Card (Expandable) -->
|
||||
<div class="card expandable" id="historyCard">
|
||||
<div class="card-header">
|
||||
@@ -2523,7 +2342,6 @@
|
||||
let hasAnimatedCards = false;
|
||||
let promoOfferTimers = [];
|
||||
let promoOfferTimerHandle = null;
|
||||
let promoCodeStatusState = null;
|
||||
|
||||
if (typeof tg.expand === 'function') {
|
||||
tg.expand();
|
||||
@@ -2676,22 +2494,6 @@
|
||||
'button.copy': 'Copy subscription link',
|
||||
'button.buy_subscription': 'Buy Subscription',
|
||||
'card.balance.title': 'Balance',
|
||||
'promo_code.title': 'Have a promo code?',
|
||||
'promo_code.subtitle': 'Enter it to receive your reward.',
|
||||
'promo_code.placeholder': 'Enter promo code',
|
||||
'promo_code.button': 'Activate',
|
||||
'promo_code.status.activating': 'Activating promo code…',
|
||||
'promo_code.success': 'Promo code activated! {description}',
|
||||
'promo_code.error.empty': 'Please enter a promo code.',
|
||||
'promo_code.error.invalid_code': 'Please enter a promo code.',
|
||||
'promo_code.error.not_found': 'Promo code not found.',
|
||||
'promo_code.error.expired': 'Promo code has expired.',
|
||||
'promo_code.error.used': 'Promo code has already been used.',
|
||||
'promo_code.error.already_used_by_user': 'You have already activated this promo code.',
|
||||
'promo_code.error.server_error': 'Something went wrong. Please try again later.',
|
||||
'promo_code.error.generic': 'Failed to activate the promo code. Please try again later.',
|
||||
'promo_code.error.user_not_found': 'Unable to find your account. Please relaunch the mini app.',
|
||||
'promo_code.error.auth': 'Authorization error. Please open the mini app from Telegram.',
|
||||
'card.history.title': 'Transaction History',
|
||||
'card.servers.title': 'Connected Servers',
|
||||
'card.devices.title': 'Connected Devices',
|
||||
@@ -2811,22 +2613,6 @@
|
||||
'button.copy': 'Скопировать ссылку подписки',
|
||||
'button.buy_subscription': 'Купить подписку',
|
||||
'card.balance.title': 'Баланс',
|
||||
'promo_code.title': 'Есть промокод?',
|
||||
'promo_code.subtitle': 'Введите его и получите бонус.',
|
||||
'promo_code.placeholder': 'Введите промокод',
|
||||
'promo_code.button': 'Активировать',
|
||||
'promo_code.status.activating': 'Активируем промокод…',
|
||||
'promo_code.success': 'Промокод активирован! {description}',
|
||||
'promo_code.error.empty': 'Введите промокод.',
|
||||
'promo_code.error.invalid_code': 'Введите промокод.',
|
||||
'promo_code.error.not_found': 'Промокод не найден.',
|
||||
'promo_code.error.expired': 'Срок действия промокода истёк.',
|
||||
'promo_code.error.used': 'Промокод уже использован.',
|
||||
'promo_code.error.already_used_by_user': 'Вы уже активировали этот промокод.',
|
||||
'promo_code.error.server_error': 'Не удалось активировать промокод. Попробуйте позже.',
|
||||
'promo_code.error.generic': 'Не удалось активировать промокод. Попробуйте позже.',
|
||||
'promo_code.error.user_not_found': 'Не удалось найти ваш профиль. Откройте мини-приложение снова из Telegram.',
|
||||
'promo_code.error.auth': 'Ошибка авторизации. Откройте мини-приложение из Telegram.',
|
||||
'card.history.title': 'История операций',
|
||||
'card.servers.title': 'Подключённые серверы',
|
||||
'card.devices.title': 'Подключенные устройства',
|
||||
@@ -3091,65 +2877,6 @@
|
||||
return key;
|
||||
}
|
||||
|
||||
function translateWithFallback(key, replacements = {}, fallback = '') {
|
||||
if (!key) {
|
||||
return fallback || '';
|
||||
}
|
||||
const template = t(key);
|
||||
const base = template && template !== key ? template : fallback || '';
|
||||
if (!base) {
|
||||
return base;
|
||||
}
|
||||
let result = base;
|
||||
if (replacements && typeof replacements === 'object') {
|
||||
Object.entries(replacements).forEach(([token, value]) => {
|
||||
const pattern = new RegExp(`\\{${token}\\}`, 'g');
|
||||
result = result.replace(pattern, value ?? '');
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function renderPromoCodeStatus() {
|
||||
const statusElement = document.getElementById('promoCodeStatus');
|
||||
if (!statusElement) {
|
||||
return;
|
||||
}
|
||||
if (!promoCodeStatusState || (!promoCodeStatusState.key && !promoCodeStatusState.fallback)) {
|
||||
statusElement.textContent = '';
|
||||
statusElement.className = 'promo-code-status';
|
||||
statusElement.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
const { key, fallback, replacements, variant } = promoCodeStatusState;
|
||||
const message = key
|
||||
? translateWithFallback(key, replacements, fallback)
|
||||
: (fallback || '');
|
||||
if (!message) {
|
||||
statusElement.textContent = '';
|
||||
statusElement.className = 'promo-code-status';
|
||||
statusElement.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
statusElement.textContent = message.trim();
|
||||
statusElement.className = `promo-code-status promo-code-status-${variant || 'info'}`;
|
||||
statusElement.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function setPromoCodeStatus(state) {
|
||||
if (!state) {
|
||||
promoCodeStatusState = null;
|
||||
} else {
|
||||
promoCodeStatusState = {
|
||||
key: state.key || null,
|
||||
fallback: state.fallback || '',
|
||||
replacements: state.replacements || {},
|
||||
variant: state.variant || 'info',
|
||||
};
|
||||
}
|
||||
renderPromoCodeStatus();
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = value ?? '';
|
||||
@@ -3210,23 +2937,12 @@
|
||||
}
|
||||
element.textContent = t(key);
|
||||
});
|
||||
document.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
|
||||
const key = element.getAttribute('data-i18n-placeholder');
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
const value = t(key);
|
||||
if (value && value !== key) {
|
||||
element.setAttribute('placeholder', value);
|
||||
}
|
||||
});
|
||||
const languageSelect = document.getElementById('languageSelect');
|
||||
if (languageSelect) {
|
||||
languageSelect.value = preferredLanguage;
|
||||
languageSelect.setAttribute('aria-label', t('language.ariaLabel'));
|
||||
}
|
||||
updateErrorTexts();
|
||||
renderPromoCodeStatus();
|
||||
}
|
||||
|
||||
function updateConnectButtonLabel() {
|
||||
@@ -4262,122 +3978,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePromoCode(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
return value.trim().toUpperCase().replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
async function handlePromoCodeSubmit(event) {
|
||||
event?.preventDefault?.();
|
||||
|
||||
const form = event?.currentTarget || document.getElementById('promoCodeForm');
|
||||
const input = document.getElementById('promoCodeInput');
|
||||
const submitButton = form?.querySelector('button[type="submit"]')
|
||||
|| document.getElementById('promoCodeSubmit');
|
||||
|
||||
const rawCode = input?.value ?? '';
|
||||
const code = normalizePromoCode(rawCode);
|
||||
|
||||
if (!code) {
|
||||
setPromoCodeStatus({
|
||||
key: 'promo_code.error.empty',
|
||||
fallback: 'Please enter a promo code.',
|
||||
variant: 'error',
|
||||
});
|
||||
input?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.value = code;
|
||||
}
|
||||
|
||||
const initData = tg.initData || '';
|
||||
if (!initData) {
|
||||
setPromoCodeStatus({
|
||||
key: 'promo_code.error.auth',
|
||||
fallback: 'Authorization error. Please open the mini app from Telegram.',
|
||||
variant: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (submitButton) {
|
||||
submitButton.disabled = true;
|
||||
}
|
||||
|
||||
setPromoCodeStatus({
|
||||
key: 'promo_code.status.activating',
|
||||
fallback: 'Activating promo code…',
|
||||
variant: 'info',
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch('/miniapp/promo-codes/activate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ initData, code }),
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
|
||||
if (!response.ok) {
|
||||
const detailCode = payload?.detail?.code || payload?.error_code || payload?.errorCode;
|
||||
const detailMessage = payload?.detail?.message || payload?.message || '';
|
||||
const messageKey = detailCode ? `promo_code.error.${detailCode}` : 'promo_code.error.generic';
|
||||
setPromoCodeStatus({
|
||||
key: messageKey,
|
||||
fallback: detailMessage || 'Failed to activate the promo code.',
|
||||
variant: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload?.success) {
|
||||
const description = (payload.description || '').trim();
|
||||
const replacements = { description };
|
||||
const fallback = description
|
||||
? `Promo code activated! ${description}`
|
||||
: 'Promo code activated!';
|
||||
const successMessage = translateWithFallback('promo_code.success', replacements, fallback);
|
||||
setPromoCodeStatus({
|
||||
key: 'promo_code.success',
|
||||
replacements,
|
||||
fallback,
|
||||
variant: 'success',
|
||||
});
|
||||
if (input) {
|
||||
input.value = '';
|
||||
}
|
||||
showPopup(successMessage.trim());
|
||||
await refreshSubscriptionData({ silent: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const errorCode = payload?.error_code || payload?.errorCode || payload?.error;
|
||||
const fallbackMessage = payload?.message || '';
|
||||
const messageKey = errorCode ? `promo_code.error.${errorCode}` : 'promo_code.error.generic';
|
||||
setPromoCodeStatus({
|
||||
key: messageKey,
|
||||
fallback: fallbackMessage || 'Failed to activate the promo code.',
|
||||
variant: 'error',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to activate promo code:', error);
|
||||
setPromoCodeStatus({
|
||||
key: 'promo_code.error.generic',
|
||||
fallback: 'Failed to activate the promo code. Please try again later.',
|
||||
variant: 'error',
|
||||
});
|
||||
} finally {
|
||||
if (submitButton) {
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function detectPlatform() {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.includes('iphone') || userAgent.includes('ipad')) {
|
||||
@@ -5635,8 +5235,6 @@
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('promoCodeForm')?.addEventListener('submit', handlePromoCodeSubmit);
|
||||
|
||||
document.getElementById('connectBtn')?.addEventListener('click', () => {
|
||||
const link = getConnectLink();
|
||||
openExternalLink(link);
|
||||
|
||||
Reference in New Issue
Block a user