import { useState, useMemo, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useQuery } from '@tanstack/react-query' import { subscriptionApi } from '../api/subscription' import type { AppInfo, AppConfig, LocalizedText } from '../types' interface ConnectionModalProps { onClose: () => void } // Platform SVG Icons const IosIcon = () => ( ) const AndroidIcon = () => ( ) const WindowsIcon = () => ( ) const MacosIcon = () => ( ) const LinuxIcon = () => ( ) const TvIcon = () => ( ) // Platform icon components map const platformIconComponents: Record = { ios: IosIcon, android: AndroidIcon, macos: MacosIcon, windows: WindowsIcon, linux: LinuxIcon, androidTV: TvIcon, appleTV: TvIcon, } // Platform order for display const platformOrder = ['ios', 'android', 'windows', 'macos', 'linux', 'androidTV', 'appleTV'] // Detect user's platform from user agent function detectPlatform(): string | null { if (typeof window === 'undefined' || !navigator?.userAgent) { return null } const ua = navigator.userAgent.toLowerCase() // Check for mobile devices first if (/iphone|ipad|ipod/.test(ua)) { return 'ios' } if (/android/.test(ua)) { // Check if it's Android TV if (/tv|television|smart-tv|smarttv/.test(ua)) { return 'androidTV' } return 'android' } // Desktop platforms if (/macintosh|mac os x/.test(ua)) { return 'macos' } if (/windows/.test(ua)) { return 'windows' } if (/linux/.test(ua)) { return 'linux' } return null } export default function ConnectionModal({ onClose }: ConnectionModalProps) { const { t, i18n } = useTranslation() const [selectedPlatform, setSelectedPlatform] = useState(null) const [selectedApp, setSelectedApp] = useState(null) const [copied, setCopied] = useState(false) const [detectedPlatform, setDetectedPlatform] = useState(null) const { data: appConfig, isLoading, error } = useQuery({ queryKey: ['appConfig'], queryFn: () => subscriptionApi.getAppConfig(), }) // Auto-detect platform on mount useEffect(() => { const detected = detectPlatform() setDetectedPlatform(detected) }, []) // Helper to get localized text const getLocalizedText = (text: LocalizedText | undefined): string => { if (!text) return '' const lang = i18n.language || 'en' return text[lang] || text['en'] || text['ru'] || Object.values(text)[0] || '' } // Get platform name const getPlatformName = (platformKey: string): string => { if (!appConfig?.platformNames?.[platformKey]) { return platformKey } return getLocalizedText(appConfig.platformNames[platformKey]) } // Get available platforms sorted (detected platform first) const availablePlatforms = useMemo(() => { if (!appConfig?.platforms) return [] const available = platformOrder.filter( (key) => appConfig.platforms[key] && appConfig.platforms[key].length > 0 ) // Move detected platform to the front if (detectedPlatform && available.includes(detectedPlatform)) { const filtered = available.filter(p => p !== detectedPlatform) return [detectedPlatform, ...filtered] } return available }, [appConfig, detectedPlatform]) // Get apps for selected platform const platformApps = useMemo(() => { if (!selectedPlatform || !appConfig?.platforms?.[selectedPlatform]) return [] return appConfig.platforms[selectedPlatform] }, [selectedPlatform, appConfig]) // Copy subscription link const copySubscriptionLink = async () => { if (!appConfig?.subscriptionUrl) return try { await navigator.clipboard.writeText(appConfig.subscriptionUrl) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch { // Fallback for older browsers const textarea = document.createElement('textarea') textarea.value = appConfig.subscriptionUrl document.body.appendChild(textarea) textarea.select() document.execCommand('copy') document.body.removeChild(textarea) setCopied(true) setTimeout(() => setCopied(false), 2000) } } // Handle deep link click - use miniapp redirect page like in miniapp index.html const handleConnect = (app: AppInfo) => { if (!app.deepLink) return const lang = i18n.language?.startsWith('ru') ? 'ru' : 'en' const redirectUrl = `${window.location.origin}/miniapp/redirect.html?url=${encodeURIComponent(app.deepLink)}&lang=${lang}` // Check if it's a custom URL scheme (not http/https) const isCustomScheme = !/^https?:\/\//i.test(app.deepLink) const tg = (window as any).Telegram?.WebApp if (isCustomScheme && tg?.openLink) { // For custom URL schemes - open redirect page in external browser via Telegram try { tg.openLink(redirectUrl, { try_instant_view: false }) return } catch (e) { console.warn('tg.openLink failed:', e) } } // Fallback - direct navigation window.location.href = redirectUrl } // Modal wrapper classes - bottom sheet on mobile, centered on desktop const modalOverlayClass = "fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-end sm:items-center sm:justify-center" const modalCardClass = "w-full sm:max-w-lg sm:mx-4 bg-dark-850 sm:rounded-2xl rounded-t-2xl rounded-b-none border-t border-x sm:border border-dark-700/50 shadow-2xl overflow-hidden" const modalContentClass = "p-4 sm:p-6 pb-[calc(1rem+env(safe-area-inset-bottom,0px))] sm:pb-6" // Allow the sheet to almost fill the viewport height on phones const modalScrollableClass = "h-[calc(100vh-1rem)] sm:h-auto sm:max-h-[85vh]" if (isLoading) { return (
e.stopPropagation()}>
) } if (error || !appConfig) { return (
e.stopPropagation()}>

{t('common.error')}

) } if (!appConfig.hasSubscription) { return (
e.stopPropagation()}>

{t('subscription.connection.title')}

{t('subscription.connection.noSubscription')}

) } // Step 1: Select platform if (!selectedPlatform) { return (
e.stopPropagation()}> {/* Header - fixed */}

{t('subscription.connection.title')}

{/* Scrollable content */}

{t('subscription.connection.selectDevice')}

{availablePlatforms.map((platform) => { const IconComponent = platformIconComponents[platform] return ( ) })}
{/* Footer - fixed with safe area */}
) } // Step 2: Select app (if not selected yet) if (!selectedApp) { return (
e.stopPropagation()}> {/* Header - fixed */}

{getPlatformName(selectedPlatform)}

{/* Scrollable content */}

{t('subscription.connection.selectApp')}

{platformApps.length === 0 ? (

{t('subscription.connection.noApps')}

) : (
{platformApps.map((app) => ( ))}
)}
) } // Step 3: Show app instructions and connect button return (
e.stopPropagation()}> {/* Header - fixed */}

{selectedApp.name}

{/* Scrollable content */}
{/* Step 1: Install */} {selectedApp.installationStep && (

{t('subscription.connection.installApp')}

{getLocalizedText(selectedApp.installationStep.description)}

{selectedApp.installationStep.buttons && selectedApp.installationStep.buttons.length > 0 && (
{selectedApp.installationStep.buttons.map((btn, idx) => ( {getLocalizedText(btn.buttonText)} ))}
)}
)} {/* Additional before add subscription */} {selectedApp.additionalBeforeAddSubscriptionStep && (
{selectedApp.additionalBeforeAddSubscriptionStep.title && (

{getLocalizedText(selectedApp.additionalBeforeAddSubscriptionStep.title)}

)}

{getLocalizedText(selectedApp.additionalBeforeAddSubscriptionStep.description)}

)} {/* Step 2: Add subscription */} {selectedApp.addSubscriptionStep && (

{t('subscription.connection.addSubscription')}

{getLocalizedText(selectedApp.addSubscriptionStep.description)}

{/* Connect button */} {selectedApp.deepLink && ( )} {/* Copy link fallback */}
)} {/* Additional after add subscription */} {selectedApp.additionalAfterAddSubscriptionStep && (
{selectedApp.additionalAfterAddSubscriptionStep.title && (

{getLocalizedText(selectedApp.additionalAfterAddSubscriptionStep.title)}

)}

{getLocalizedText(selectedApp.additionalAfterAddSubscriptionStep.description)}

)} {/* Step 3: Connect */} {selectedApp.connectAndUseStep && (

{t('subscription.connection.connectVpn')}

{getLocalizedText(selectedApp.connectAndUseStep.description)}

)}
{/* Footer - fixed with safe area */}
) }