diff --git a/src/components/ConnectionModal.tsx b/src/components/ConnectionModal.tsx index c9826da..a7a2687 100644 --- a/src/components/ConnectionModal.tsx +++ b/src/components/ConnectionModal.tsx @@ -2,6 +2,7 @@ import { useState, useMemo, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useQuery } from '@tanstack/react-query' import { subscriptionApi } from '../api/subscription' +import { useTelegramWebApp } from '../hooks/useTelegramWebApp' import type { AppInfo, AppConfig, LocalizedText } from '../types' interface ConnectionModalProps { @@ -207,6 +208,17 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { const [showAppSelector, setShowAppSelector] = useState(false) const modalContentRef = useRef(null) + // Telegram Mini App support + const { isTelegramWebApp, safeAreaInset, contentSafeAreaInset } = useTelegramWebApp() + + // Calculate safe area - prefer Telegram values, fallback to CSS env + const safeTop = isTelegramWebApp + ? Math.max(safeAreaInset.top, contentSafeAreaInset.top) + : 0 + const safeBottom = isTelegramWebApp + ? Math.max(safeAreaInset.bottom, contentSafeAreaInset.bottom) + : 0 + const { data: appConfig, isLoading, error } = useQuery({ queryKey: ['appConfig'], queryFn: () => subscriptionApi.getAppConfig(), @@ -342,37 +354,61 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { window.location.href = redirectUrl } - // Modal wrapper - top aligned with auto-focus - const ModalWrapper = ({ children }: { children: React.ReactNode }) => ( -
+ // Modal wrapper - fullscreen on mobile, centered on desktop + const ModalWrapper = ({ children }: { children: React.ReactNode }) => { + // For Telegram, use JS values; for browser, use CSS env() + const closeButtonTop = isTelegramWebApp + ? `${12 + safeTop}px` + : `calc(0.75rem + env(safe-area-inset-top, 0px))` + + const safeTopStyle = isTelegramWebApp + ? `${safeTop}px` + : 'env(safe-area-inset-top, 0px)' + + const safeBottomStyle = isTelegramWebApp + ? `${safeBottom}px` + : 'env(safe-area-inset-bottom, 0px)' + + return (
e.stopPropagation()} + className="fixed inset-0 bg-black/70 z-[60] flex items-start justify-center md:p-4 md:pt-8 overflow-hidden" + onClick={onClose} > - {children} + {/* Mobile: fullscreen */} +
e.stopPropagation()} + > + {/* Mobile close button - fixed top right */} + + + {/* Mobile safe area spacer - top */} +
+ + {children} + + {/* Mobile safe area spacer - bottom */} +
+
-
- ) + ) + } // Loading state if (isLoading) { return ( -
+
@@ -383,12 +419,12 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { if (error || !appConfig) { return ( -
-
- 😕 +
+
+ 😕
-

{t('common.error')}

-
@@ -400,13 +436,13 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { if (!appConfig.hasSubscription) { return ( -
-
- 📱 +
+
+ 📱
-

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

-

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

-
@@ -419,7 +455,7 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { return ( {/* Header */} -
+
{selectedApp && ( )}
-

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

-

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

+

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

+

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

-
{/* Apps by platform */} -
+
{allAppsForSelector.map(({ platform, apps }) => { const IconComponent = platformIconComponents[platform] const isCurrentPlatform = platform === detectedPlatform @@ -487,10 +523,10 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) {
{/* Copy link */} -
+
-

{selectedApp.name}

-

{t('subscription.connection.changeApp') || 'Сменить'} →

+

{selectedApp.name}

+

{t('subscription.connection.changeApp') || 'Сменить'} →

-
{/* Instructions */} -
+
{/* Step 1: Install */} {selectedApp.installationStep && (
@@ -614,8 +650,8 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) { )}
- {/* Footer */} -
+ {/* Footer - hidden on mobile since we have close button on top */} +