diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index 17819e41..f083256a 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -30,7 +30,6 @@ from app.database.models import ( Transaction, User, ) -from app.services.faq_service import FaqService from app.services.remnawave_service import ( RemnaWaveConfigurationError, RemnaWaveService, @@ -48,7 +47,6 @@ from ..schemas.miniapp import ( MiniAppAutoPromoGroupLevel, MiniAppConnectedServer, MiniAppDevice, - MiniAppFaqPage, MiniAppPromoGroup, MiniAppPromoOffer, MiniAppPromoOfferClaimRequest, @@ -881,31 +879,6 @@ async def get_subscription_details( promo_offer_discount_source=promo_offer_source, ) - faq_pages: List[MiniAppFaqPage] = [] - faq_language: Optional[str] = None - try: - preferred_faq_language = user.language or settings.DEFAULT_LANGUAGE or "ru" - pages = await FaqService.get_pages( - db, - preferred_faq_language, - include_inactive=False, - fallback=True, - ) - if pages: - faq_language = pages[0].language - faq_pages = [ - MiniAppFaqPage( - id=page.id, - language=page.language, - title=page.title, - content=page.content, - display_order=page.display_order, - ) - for page in pages - ] - except Exception as error: # pragma: no cover - defensive logging - logger.warning("Failed to load FAQ pages for miniapp: %s", error) - return MiniAppSubscriptionResponse( subscription_id=subscription.id, remnawave_short_uuid=subscription.remnawave_short_uuid, @@ -944,9 +917,6 @@ async def get_subscription_details( subscription_type="trial" if subscription.is_trial else "paid", autopay_enabled=bool(subscription.autopay_enabled), branding=settings.get_miniapp_branding(), - faq_enabled=bool(faq_pages), - faq_language=faq_language, - faq_pages=faq_pages, ) diff --git a/app/webapi/schemas/miniapp.py b/app/webapi/schemas/miniapp.py index b7fc1c1f..e2c2a348 100644 --- a/app/webapi/schemas/miniapp.py +++ b/app/webapi/schemas/miniapp.py @@ -123,14 +123,6 @@ class MiniAppPromoOfferClaimResponse(BaseModel): code: Optional[str] = None -class MiniAppFaqPage(BaseModel): - id: int - language: str - title: str - content: str - display_order: int = 0 - - class MiniAppSubscriptionResponse(BaseModel): success: bool = True subscription_id: int @@ -162,7 +154,4 @@ class MiniAppSubscriptionResponse(BaseModel): subscription_type: str autopay_enabled: bool = False branding: Optional[MiniAppBranding] = None - faq_enabled: bool = False - faq_language: Optional[str] = None - faq_pages: List[MiniAppFaqPage] = Field(default_factory=list) diff --git a/miniapp/index.html b/miniapp/index.html index a45d2ab8..de98a1e7 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -1599,73 +1599,6 @@ box-shadow: var(--shadow-sm); } - /* FAQ */ - .faq-entry { - padding: 16px 0; - border-bottom: 1px solid var(--border-color); - } - - .faq-entry:first-child { - padding-top: 0; - } - - .faq-entry:last-child { - border-bottom: none; - padding-bottom: 0; - } - - .faq-title { - font-size: 16px; - font-weight: 700; - color: var(--text-primary); - margin-bottom: 8px; - } - - .faq-content { - font-size: 14px; - color: var(--text-secondary); - line-height: 1.7; - display: grid; - gap: 8px; - } - - .faq-content p { - margin: 0; - } - - .faq-content ul, - .faq-content ol { - margin: 0; - padding-left: 20px; - } - - .faq-content li { - margin-bottom: 6px; - } - - .faq-content code { - font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; - background: rgba(var(--primary-rgb), 0.08); - padding: 2px 4px; - border-radius: 4px; - color: var(--text-primary); - } - - .faq-content pre { - font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; - background: var(--bg-primary); - border-radius: var(--radius-sm); - padding: 12px; - overflow-x: auto; - color: var(--text-primary); - margin: 0; - } - - .faq-content a { - color: var(--primary); - text-decoration: underline; - } - /* Hidden */ .hidden { display: none !important; @@ -1749,14 +1682,6 @@ background: var(--bg-secondary); } - .faq-content code { - background: rgba(37, 99, 235, 0.2); - } - - .faq-content pre { - background: rgba(15, 23, 42, 0.6); - } - .theme-toggle .icon-sun { display: none; } @@ -2136,16 +2061,6 @@ - - - @@ -2335,7 +2250,6 @@ 'apps.step.download': 'Download & install', 'apps.step.add': 'Add subscription', 'apps.step.connect': 'Connect & use', - 'faq.title': 'FAQ', 'history.empty': 'No transactions yet', 'history.status.completed': 'Completed', 'history.status.pending': 'Processing', @@ -2447,7 +2361,6 @@ 'apps.step.download': 'Скачать и установить', 'apps.step.add': 'Добавить подписку', 'apps.step.connect': 'Подключиться и пользоваться', - 'faq.title': 'Частые вопросы', 'history.empty': 'Операции ещё не проводились', 'history.status.completed': 'Выполнено', 'history.status.pending': 'Обрабатывается', @@ -2752,7 +2665,6 @@ updateConnectButtonLabel(); } renderApps(); - renderFaq(); updateActionButtons(); } @@ -3098,7 +3010,6 @@ renderTransactionHistory(); renderServersList(); renderDevicesList(); - renderFaq(); updateConnectButtonLabel(); updateActionButtons(); } @@ -3257,91 +3168,6 @@ return doc.body.innerHTML.replace(/\n/g, '
'); } - function sanitizeFaqHtml(input) { - if (!input || typeof input !== 'string') { - return ''; - } - - const parser = new DOMParser(); - const doc = parser.parseFromString(`
${input}
`, 'text/html'); - const allowedTags = new Set([ - 'P', - 'BR', - 'STRONG', - 'B', - 'EM', - 'I', - 'U', - 'A', - 'UL', - 'OL', - 'LI', - 'SPAN', - 'CODE', - 'PRE', - 'BLOCKQUOTE', - 'H1', - 'H2', - 'H3', - 'H4', - 'H5', - 'H6' - ]); - - const nodes = []; - const walker = document.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, null); - while (walker.nextNode()) { - nodes.push(walker.currentNode); - } - - nodes.forEach(node => { - const tagName = node.tagName; - - if (!allowedTags.has(tagName)) { - if (tagName === 'DIV') { - const fragment = document.createDocumentFragment(); - while (node.firstChild) { - fragment.appendChild(node.firstChild); - } - node.replaceWith(fragment); - return; - } - - node.replaceWith(document.createTextNode(node.textContent || '')); - return; - } - - if (tagName === 'A') { - const href = node.getAttribute('href') || ''; - const normalizedHref = href.trim(); - const isAllowedLink = /^https?:\/\//i.test(normalizedHref) - || normalizedHref.toLowerCase().startsWith('mailto:') - || normalizedHref.toLowerCase().startsWith('tg://') - || normalizedHref.toLowerCase().startsWith('ton://') - || normalizedHref.toLowerCase().startsWith('tel:'); - - if (!isAllowedLink) { - node.replaceWith(document.createTextNode(node.textContent || '')); - return; - } - - node.setAttribute('href', normalizedHref); - node.setAttribute('target', '_blank'); - node.setAttribute('rel', 'noopener noreferrer'); - Array.from(node.attributes).forEach(attr => { - if (!['href', 'target', 'rel'].includes(attr.name.toLowerCase())) { - node.removeAttribute(attr.name); - } - }); - return; - } - - Array.from(node.attributes).forEach(attr => node.removeAttribute(attr.name)); - }); - - return doc.body.innerHTML; - } - function formatShortDuration(seconds) { if (!Number.isFinite(seconds) || seconds <= 0) { return t('time.less_than_minute'); @@ -3807,67 +3633,6 @@ return html; } - function renderFaq() { - const faqCard = document.getElementById('faqCard'); - const container = document.getElementById('faqContainer'); - if (!faqCard || !container) { - return; - } - - const enabled = Boolean(userData?.faq_enabled); - const pages = Array.isArray(userData?.faq_pages) - ? [...userData.faq_pages].sort((a, b) => { - const orderAValue = Number(a?.display_order); - const orderBValue = Number(b?.display_order); - const orderA = Number.isFinite(orderAValue) ? orderAValue : 0; - const orderB = Number.isFinite(orderBValue) ? orderBValue : 0; - if (orderA === orderB) { - return (a?.id || 0) - (b?.id || 0); - } - return orderA - orderB; - }) - : []; - - if (!enabled || !pages.length) { - container.innerHTML = ''; - faqCard.classList.add('hidden'); - return; - } - - const entriesHtml = pages - .map(page => { - if (!page) { - return ''; - } - - const title = page.title ? `
${escapeHtml(page.title)}
` : ''; - const contentHtml = page.content ? sanitizeFaqHtml(page.content) : ''; - const trimmedContent = contentHtml.trim(); - - if (!title && !trimmedContent) { - return ''; - } - - const contentBlock = trimmedContent - ? `
${trimmedContent}
` - : ''; - - return `
${title}${contentBlock}
`; - }) - .filter(Boolean) - .join('') - .trim(); - - if (!entriesHtml) { - container.innerHTML = ''; - faqCard.classList.add('hidden'); - return; - } - - container.innerHTML = entriesHtml; - faqCard.classList.remove('hidden'); - } - function getLocalizedText(textObj) { if (!textObj) { return ''; @@ -4785,7 +4550,6 @@ } function showError(error) { - userData = null; document.getElementById('loadingState').classList.add('hidden'); document.getElementById('mainContent').classList.add('hidden'); currentErrorState = { @@ -4795,7 +4559,6 @@ }; updateErrorTexts(); document.getElementById('errorState').classList.remove('hidden'); - renderFaq(); updateActionButtons(); }