import { useEffect, useState } from 'react'; import { useLocation, Link } from 'react-router'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '@/store/auth'; import { useHaptic } from '@/platform'; import { useTelegramSDK } from '@/hooks/useTelegramSDK'; import { useTheme } from '@/hooks/useTheme'; import { useBranding } from '@/hooks/useBranding'; import { useFeatureFlags } from '@/hooks/useFeatureFlags'; import { useScrollRestoration } from '@/hooks/useScrollRestoration'; import { themeColorsApi } from '@/api/themeColors'; import { isLogoPreloaded } from '@/api/branding'; import { cn } from '@/lib/utils'; import { UI } from '@/config/constants'; import WebSocketNotifications from '@/components/WebSocketNotifications'; import CampaignBonusNotifier from '@/components/CampaignBonusNotifier'; import SuccessNotificationModal from '@/components/SuccessNotificationModal'; import LanguageSwitcher from '@/components/LanguageSwitcher'; import TicketNotificationBell from '@/components/TicketNotificationBell'; import { MobileBottomNav } from './MobileBottomNav'; import { AppHeader } from './AppHeader'; import { Aurora } from './Aurora'; // Desktop nav icons const HomeIcon = ({ className }: { className?: string }) => ( ); const CreditCardIcon = ({ className }: { className?: string }) => ( ); const ChatIcon = ({ className }: { className?: string }) => ( ); const UserIcon = ({ className }: { className?: string }) => ( ); const UsersIcon = ({ className }: { className?: string }) => ( ); const ShieldIcon = ({ className }: { className?: string }) => ( ); const InfoIcon = ({ className }: { className?: string }) => ( ); const LogoutIcon = ({ className }: { className?: string }) => ( ); const SunIcon = ({ className }: { className?: string }) => ( ); const MoonIcon = ({ className }: { className?: string }) => ( ); interface AppShellProps { children: React.ReactNode; } export function AppShell({ children }: AppShellProps) { const { t } = useTranslation(); const location = useLocation(); const isAdmin = useAuthStore((state) => state.isAdmin); const logout = useAuthStore((state) => state.logout); const { isFullscreen, safeAreaInset, contentSafeAreaInset, platform, isMobile } = useTelegramSDK(); const haptic = useHaptic(); const { toggleTheme, isDark } = useTheme(); // Extracted hooks const { appName, logoLetter, hasCustomLogo, logoUrl } = useBranding(); const { referralEnabled, wheelEnabled, hasContests, hasPolls } = useFeatureFlags(); useScrollRestoration(); // Theme toggle visibility const { data: enabledThemes } = useQuery({ queryKey: ['enabled-themes'], queryFn: themeColorsApi.getEnabledThemes, staleTime: 1000 * 60 * 5, }); const canToggleTheme = enabledThemes?.dark && enabledThemes?.light; // Only apply fullscreen UI adjustments on mobile Telegram (iOS/Android) const isMobileFullscreen = isFullscreen && isMobile; const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); // Keyboard detection for hiding bottom nav useEffect(() => { const handleFocusIn = (e: FocusEvent) => { const target = e.target as HTMLElement; if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { setIsKeyboardOpen(true); } }; const handleFocusOut = (e: FocusEvent) => { const relatedTarget = e.relatedTarget as HTMLElement | null; if ( !relatedTarget || (relatedTarget.tagName !== 'INPUT' && relatedTarget.tagName !== 'TEXTAREA' && !relatedTarget.isContentEditable) ) { setIsKeyboardOpen(false); } }; document.addEventListener('focusin', handleFocusIn); document.addEventListener('focusout', handleFocusOut); return () => { document.removeEventListener('focusin', handleFocusIn); document.removeEventListener('focusout', handleFocusOut); }; }, []); // Desktop navigation items const desktopNavItems = [ { path: '/', label: t('nav.dashboard'), icon: HomeIcon }, { path: '/balance', label: t('nav.balance'), icon: CreditCardIcon }, { path: '/support', label: t('nav.support'), icon: ChatIcon }, { path: '/info', label: t('nav.info'), icon: InfoIcon }, { path: '/profile', label: t('nav.profile'), icon: UserIcon }, ]; const isActive = (path: string) => { if (path === '/') return location.pathname === '/'; return location.pathname.startsWith(path); }; const handleNavClick = () => { haptic.impact('light'); }; // Calculate header height based on fullscreen mode (only on mobile Telegram) // On iOS: contentSafeAreaInset.top includes status bar + dynamic island + Telegram header // On Android: safeAreaInset.top only includes status bar, need to add Telegram header height (~48px) const telegramHeaderHeight = platform === 'android' ? UI.TELEGRAM_HEADER_ANDROID_PX : UI.TELEGRAM_HEADER_IOS_PX; const headerHeight = isMobileFullscreen ? 64 + Math.max(safeAreaInset.top, contentSafeAreaInset.top) + telegramHeaderHeight : 64; return (