diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index fbca146a..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,9 +47,6 @@ from ..schemas.miniapp import ( MiniAppAutoPromoGroupLevel, MiniAppConnectedServer, MiniAppDevice, - MiniAppFaqPage, - MiniAppFaqRequest, - MiniAppFaqResponse, MiniAppPromoGroup, MiniAppPromoOffer, MiniAppPromoOfferClaimRequest, @@ -712,101 +708,6 @@ async def _load_subscription_links( return payload -@router.post("/faq", response_model=MiniAppFaqResponse) -async def get_faq_pages( - payload: MiniAppFaqRequest, - db: AsyncSession = Depends(get_db_session), -) -> MiniAppFaqResponse: - try: - webapp_data = parse_webapp_init_data(payload.init_data, settings.BOT_TOKEN) - except TelegramWebAppAuthError as error: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=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_code=status.HTTP_400_BAD_REQUEST, - detail="Invalid Telegram user payload", - ) - - try: - telegram_id = int(telegram_user["id"]) - except (TypeError, ValueError): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Invalid Telegram user identifier", - ) from None - - user = await get_user_by_telegram_id(db, telegram_id) - user_language = getattr(user, "language", None) - telegram_language = telegram_user.get("language_code") if isinstance(telegram_user.get("language_code"), str) else None - - language_candidates = [ - payload.language, - user_language, - telegram_language, - settings.DEFAULT_LANGUAGE, - ] - - requested_language: Optional[str] = None - for candidate in language_candidates: - if not candidate: - continue - try: - normalized = FaqService.normalize_language(str(candidate)) - except Exception: # pragma: no cover - defensive - continue - if normalized: - requested_language = normalized - break - - if not requested_language: - requested_language = FaqService.normalize_language(settings.DEFAULT_LANGUAGE or "ru") - - fallback = True if payload.fallback is None else bool(payload.fallback) - - pages = await FaqService.get_pages( - db, - requested_language, - include_inactive=False, - fallback=fallback, - ) - - setting = await FaqService.get_setting(db, requested_language, fallback=fallback) - resolved_language = requested_language - if pages: - resolved_language = pages[0].language - if setting and setting.language: - resolved_language = setting.language - - serialized_pages: List[MiniAppFaqPage] = [] - for index, page in enumerate(pages): - content = page.content or "" - serialized_pages.append( - MiniAppFaqPage( - id=page.id, - language=page.language, - title=page.title, - content=content, - content_pages=FaqService.split_content_into_pages(content), - display_order=page.display_order or (index + 1), - ) - ) - - is_enabled = bool(setting.is_enabled) if setting else bool(serialized_pages) - - return MiniAppFaqResponse( - requested_language=requested_language, - language=resolved_language, - is_enabled=is_enabled and bool(serialized_pages), - total=len(serialized_pages), - items=serialized_pages, - ) - - @router.post("/subscription", response_model=MiniAppSubscriptionResponse) async def get_subscription_details( payload: MiniAppSubscriptionRequest, diff --git a/app/webapi/schemas/miniapp.py b/app/webapi/schemas/miniapp.py index 5a51e355..e2c2a348 100644 --- a/app/webapi/schemas/miniapp.py +++ b/app/webapi/schemas/miniapp.py @@ -15,30 +15,6 @@ class MiniAppSubscriptionRequest(BaseModel): init_data: str = Field(..., alias="initData") -class MiniAppFaqRequest(BaseModel): - init_data: str = Field(..., alias="initData") - language: Optional[str] = None - fallback: Optional[bool] = None - - -class MiniAppFaqPage(BaseModel): - id: int - language: str - title: str - content: str - content_pages: List[str] = Field(default_factory=list) - display_order: int = 0 - - -class MiniAppFaqResponse(BaseModel): - success: bool = True - requested_language: str - language: str - is_enabled: bool = False - total: int = 0 - items: List[MiniAppFaqPage] = Field(default_factory=list) - - class MiniAppSubscriptionUser(BaseModel): telegram_id: int username: Optional[str] = None diff --git a/miniapp/index.html b/miniapp/index.html index 1851ebd4..de98a1e7 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -1599,121 +1599,6 @@ box-shadow: var(--shadow-sm); } - /* FAQ */ - .faq-card .card-content { - padding: 0 16px 16px; - } - - .faq-list { - display: flex; - flex-direction: column; - gap: 12px; - } - - .faq-item { - border: 1px solid var(--border-color); - border-radius: var(--radius); - background: var(--bg-primary); - box-shadow: var(--shadow-sm); - overflow: hidden; - transition: border-color 0.3s ease, box-shadow 0.3s ease; - } - - .faq-item:hover { - border-color: rgba(var(--primary-rgb), 0.3); - box-shadow: var(--shadow-md); - } - - :root[data-theme="dark"] .faq-item { - background: var(--bg-secondary); - } - - .faq-question { - width: 100%; - padding: 16px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - background: transparent; - border: none; - color: inherit; - font-size: 15px; - font-weight: 600; - text-align: left; - cursor: pointer; - } - - .faq-question:focus { - outline: none; - box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.12); - } - - .faq-question-title { - flex: 1; - } - - .faq-question-icon { - width: 20px; - height: 20px; - color: var(--text-secondary); - transition: transform 0.3s ease; - flex-shrink: 0; - } - - .faq-item.expanded .faq-question-icon { - transform: rotate(180deg); - } - - .faq-answer { - max-height: 0; - overflow: hidden; - opacity: 0; - padding: 0 16px; - transition: all 0.3s ease; - } - - .faq-item.expanded .faq-answer { - max-height: 2000px; - opacity: 1; - padding: 0 16px 16px; - } - - .faq-answer-page { - font-size: 14px; - color: var(--text-secondary); - line-height: 1.6; - } - - .faq-answer-page + .faq-answer-page { - margin-top: 12px; - } - - .faq-answer-divider { - height: 1px; - background: var(--border-color); - margin: 12px 0; - } - - .faq-answer-page ul, - .faq-answer-page ol { - margin: 8px 0 8px 20px; - padding: 0; - } - - .faq-answer-page li { - margin-bottom: 4px; - } - - .faq-answer-page a { - color: var(--tg-theme-link-color, var(--primary)); - text-decoration: none; - } - - .faq-answer-page a:hover { - text-decoration: underline; - } - /* Hidden */ .hidden { display: none !important; @@ -2176,21 +2061,6 @@ - - -
@@ -2208,7 +2078,6 @@ let hasAnimatedCards = false; let promoOfferTimers = []; let promoOfferTimerHandle = null; - let faqData = null; if (typeof tg.expand === 'function') { tg.expand(); @@ -2381,8 +2250,6 @@ 'apps.step.download': 'Download & install', 'apps.step.add': 'Add subscription', 'apps.step.connect': 'Connect & use', - 'faq.title': 'FAQ', - 'faq.default_question': 'Question {index}', 'history.empty': 'No transactions yet', 'history.status.completed': 'Completed', 'history.status.pending': 'Processing', @@ -2494,8 +2361,6 @@ 'apps.step.download': 'Скачать и установить', 'apps.step.add': 'Добавить подписку', 'apps.step.connect': 'Подключиться и пользоваться', - 'faq.title': 'Вопросы и ответы', - 'faq.default_question': 'Вопрос {index}', 'history.empty': 'Операции ещё не проводились', 'history.status.completed': 'Выполнено', 'history.status.pending': 'Обрабатывается', @@ -2815,8 +2680,6 @@ safeSetStoredLanguage(preferredLanguage); } refreshAfterLanguageChange(); - refreshFaqData({ language: preferredLanguage }) - .catch(error => console.warn('Unable to refresh FAQ after language change:', error)); } const storedLanguage = resolveLanguage(safeGetStoredLanguage()); @@ -2996,53 +2859,6 @@ return applySubscriptionData(payload); } - async function refreshFaqData(options = {}) { - const { language = preferredLanguage, fallback = true } = options; - - if (!tg.initData) { - faqData = null; - renderFaq(); - return null; - } - - const body = { - initData: tg.initData, - }; - - if (language) { - body.language = language; - } - - if (fallback === false) { - body.fallback = false; - } - - const previousData = faqData; - - try { - const response = await fetch('/miniapp/faq', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - throw new Error(`Request failed with status ${response.status}`); - } - - const data = await response.json(); - faqData = data; - } catch (error) { - console.warn('Unable to load FAQ:', error); - faqData = previousData ?? null; - } - - renderFaq(); - return faqData; - } - async function init() { try { const telegramUser = tg.initDataUnsafe?.user; @@ -3057,7 +2873,6 @@ await loadAppsConfig(); await refreshSubscriptionData(); - await refreshFaqData({ language: preferredLanguage }); } catch (error) { console.error('Initialization error:', error); showError(error); @@ -3353,46 +3168,6 @@ return doc.body.innerHTML.replace(/\n/g, '