import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import DOMPurify from 'dompurify'; import { infoApi, FaqPage } from '../api/info'; import { promoApi, LoyaltyTierInfo } from '../api/promo'; const InfoIcon = () => ( ); const QuestionIcon = () => ( ); const DocumentIcon = () => ( ); const ShieldIcon = () => ( ); const StarIcon = () => ( ); const ChevronIcon = ({ expanded }: { expanded: boolean }) => ( ); type TabType = 'faq' | 'rules' | 'privacy' | 'offer' | 'loyalty'; // Sanitize HTML content to prevent XSS const sanitizeHtml = (html: string): string => { return DOMPurify.sanitize(html, { ALLOWED_TAGS: [ 'p', 'br', 'b', 'i', 'u', 'strong', 'em', 'a', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre', 's', 'del', 'ins', 'span', 'div', 'tg-spoiler', ], ALLOWED_ATTR: ['href', 'target', 'rel', 'class', 'start'], ALLOW_DATA_ATTR: false, }); }; // Convert content to formatted HTML (handles Telegram HTML + plain text) const formatContent = (content: string): string => { if (!content) return ''; // Check if content has block-level HTML (full HTML document) const hasBlockHtml = /<(p|div|h[1-6]|ul|ol|blockquote)\b/i.test(content); if (hasBlockHtml) { return sanitizeHtml(content); } // Content may have inline Telegram HTML (, , , , ) but uses // newlines for structure. Convert newlines to paragraphs while preserving inline tags. const result = content .split(/\n\n+/) .map((paragraph) => { const trimmed = paragraph.trim(); if (!trimmed) return ''; // Check if it's a markdown header if (/^#{1,4}\s/.test(trimmed)) { const level = trimmed.match(/^(#{1,4})/)?.[1].length || 1; const text = trimmed.replace(/^#{1,4}\s*/, ''); return `${text}`; } // Check for list items if (/^[-•]\s/.test(trimmed) || /^\d+[.)]\s/.test(trimmed)) { const lines = trimmed.split('\n'); const isOrdered = /^\d+[.)]\s/.test(lines[0]); const startNum = isOrdered ? parseInt(lines[0].match(/^(\d+)/)?.[1] || '1', 10) : 1; const listItems = lines .map((line) => line.replace(/^[-•]\s*/, '').replace(/^\d+[.)]\s*/, '')) .filter((line) => line.trim()) .map((line) => `
  • ${line}
  • `) .join(''); return isOrdered ? `
      ${listItems}
    ` : `
      ${listItems}
    `; } // Regular paragraph — single newlines become
    const formatted = trimmed.split('\n').join('
    '); return `

    ${formatted}

    `; }) .filter(Boolean) .join(''); return sanitizeHtml(result); }; export default function Info() { const { t } = useTranslation(); const [activeTab, setActiveTab] = useState('faq'); const [expandedFaq, setExpandedFaq] = useState(null); const { data: faqPages, isLoading: faqLoading } = useQuery({ queryKey: ['faq-pages'], queryFn: infoApi.getFaqPages, enabled: activeTab === 'faq', staleTime: 0, refetchOnMount: 'always', }); const { data: rules, isLoading: rulesLoading } = useQuery({ queryKey: ['rules'], queryFn: infoApi.getRules, enabled: activeTab === 'rules', staleTime: 0, refetchOnMount: 'always', }); const { data: privacy, isLoading: privacyLoading } = useQuery({ queryKey: ['privacy-policy'], queryFn: infoApi.getPrivacyPolicy, enabled: activeTab === 'privacy', staleTime: 0, refetchOnMount: 'always', }); const { data: offer, isLoading: offerLoading } = useQuery({ queryKey: ['public-offer'], queryFn: infoApi.getPublicOffer, enabled: activeTab === 'offer', staleTime: 0, refetchOnMount: 'always', }); const { data: loyaltyData, isLoading: loyaltyLoading } = useQuery({ queryKey: ['loyalty-tiers'], queryFn: promoApi.getLoyaltyTiers, enabled: activeTab === 'loyalty', staleTime: 0, refetchOnMount: 'always', }); const tabs = [ { id: 'faq' as TabType, label: t('info.faq'), icon: QuestionIcon }, { id: 'rules' as TabType, label: t('info.rules'), icon: DocumentIcon }, { id: 'privacy' as TabType, label: t('info.privacy'), icon: ShieldIcon }, { id: 'offer' as TabType, label: t('info.offer'), icon: DocumentIcon }, { id: 'loyalty' as TabType, label: t('info.loyalty'), icon: StarIcon }, ]; const toggleFaq = (id: number) => { setExpandedFaq(expandedFaq === id ? null : id); }; const renderContent = () => { if (activeTab === 'faq') { if (faqLoading) { return (
    ); } if (!faqPages || faqPages.length === 0) { return
    {t('info.noFaq')}
    ; } return (
    {faqPages.map((faq: FaqPage) => (
    {expandedFaq === faq.id && (
    )}
    ))}
    ); } if (activeTab === 'rules') { if (rulesLoading) { return (
    ); } if (!rules?.content) { return
    {t('info.noContent')}
    ; } return (
    {rules.updated_at && (

    {t('info.updatedAt')}: {new Date(rules.updated_at).toLocaleDateString()}

    )}
    ); } if (activeTab === 'privacy') { if (privacyLoading) { return (
    ); } if (!privacy?.content) { return
    {t('info.noContent')}
    ; } return (
    {privacy.updated_at && (

    {t('info.updatedAt')}: {new Date(privacy.updated_at).toLocaleDateString()}

    )}
    ); } if (activeTab === 'offer') { if (offerLoading) { return (
    ); } if (!offer?.content) { return
    {t('info.noContent')}
    ; } return (
    {offer.updated_at && (

    {t('info.updatedAt')}: {new Date(offer.updated_at).toLocaleDateString()}

    )}
    ); } if (activeTab === 'loyalty') { if (loyaltyLoading) { return (
    ); } if (!loyaltyData || loyaltyData.tiers.length === 0) { return
    {t('info.noLoyaltyTiers')}
    ; } const formatCurrency = (amount: number) => { return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(amount); }; const getStatusBadge = (tier: LoyaltyTierInfo) => { if (tier.is_current) { return ( {t('info.statusCurrent')} ); } if (tier.is_achieved) { return ( {t('info.statusAchieved')} ); } return ( {t('info.statusLocked')} ); }; const hasAnyDiscount = (tier: LoyaltyTierInfo) => { return ( tier.server_discount_percent > 0 || tier.traffic_discount_percent > 0 || tier.device_discount_percent > 0 || Object.keys(tier.period_discounts).length > 0 ); }; return (
    {/* Progress Card */}

    {t('info.yourProgress')}

    {t('info.totalSpent')}
    {formatCurrency(loyaltyData.current_spent_rubles)}
    {t('info.currentStatus')}
    {loyaltyData.current_tier_name || '-'}
    {/* Progress bar to next tier */} {loyaltyData.next_tier_name && loyaltyData.next_tier_threshold_rubles ? (
    {t('info.nextStatus')}: {loyaltyData.next_tier_name} {t('info.toNextStatus')}:{' '} {formatCurrency( loyaltyData.next_tier_threshold_rubles - loyaltyData.current_spent_rubles, )}
    {loyaltyData.progress_percent.toFixed(1)}%
    ) : (
    {t('info.allStatusesAchieved')}
    )}
    {/* Tiers List */}
    {loyaltyData.tiers.map((tier) => (

    {tier.name}

    {t('info.threshold')}: {formatCurrency(tier.threshold_rubles)}

    {getStatusBadge(tier)}
    {/* Discounts */} {hasAnyDiscount(tier) ? (
    {t('info.discounts')}:
    {tier.server_discount_percent > 0 && ( {t('info.serverDiscount')}: -{tier.server_discount_percent}% )} {tier.traffic_discount_percent > 0 && ( {t('info.trafficDiscount')}: -{tier.traffic_discount_percent}% )} {tier.device_discount_percent > 0 && ( {t('info.deviceDiscount')}: -{tier.device_discount_percent}% )} {Object.entries(tier.period_discounts).map(([days, percent]) => ( {t('info.periodDiscount', { days })}: -{percent}% ))}
    ) : (
    {t('info.noDiscounts')}
    )}
    ))}
    ); } return null; }; return (

    {t('info.title')}

    {/* Tabs */}
    {tabs.map((tab) => ( ))}
    {/* Content */} {renderContent()}
    ); }