import { useEffect, useState } from 'react';
import { useLocation, Link } from 'react-router';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { motion } from 'framer-motion';
import { useAuthStore } from '@/store/auth';
import { useHaptic } from '@/platform';
import { useTelegramSDK } from '@/hooks/useTelegramSDK';
import { useHeaderHeight } from '@/hooks/useHeaderHeight';
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 WebSocketNotifications from '@/components/WebSocketNotifications';
import CampaignBonusNotifier from '@/components/CampaignBonusNotifier';
import SuccessNotificationModal from '@/components/SuccessNotificationModal';
import { PromptDialogHost } from '@/components/PromptDialogHost';
import LanguageSwitcher from '@/components/LanguageSwitcher';
import TicketNotificationBell from '@/components/TicketNotificationBell';
import {
SubscriptionIcon,
GiftIcon,
HomeIcon,
CreditCardIcon,
ChatIcon,
UserIcon,
UsersIcon,
ShieldIcon,
InfoIcon,
LogoutIcon,
SunIcon,
MoonIcon,
} from '@/components/icons';
import { MobileBottomNav } from './MobileBottomNav';
import { AppHeader } from './AppHeader';
import { BackgroundRenderer } from '@/components/backgrounds/BackgroundRenderer';
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 { mobile: headerHeight } = useHeaderHeight();
const haptic = useHaptic();
const { toggleTheme, isDark } = useTheme();
// Extracted hooks
const { appName, logoLetter, hasCustomLogo, logoUrl } = useBranding();
const { referralEnabled, wheelEnabled, hasContests, hasPolls, giftEnabled } = 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);
// Reset keyboard state on route change — prevents bottom nav staying hidden after navigation
useEffect(() => {
setIsKeyboardOpen(false);
}, [location.pathname]);
// 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 — labels always visible (no hover-reveal gimmick)
const desktopNav = [
{ path: '/', label: t('nav.dashboard'), icon: HomeIcon },
{ path: '/subscriptions', label: t('nav.subscription'), icon: SubscriptionIcon },
{ path: '/balance', label: t('nav.balance'), icon: CreditCardIcon },
...(referralEnabled ? [{ path: '/referral', label: t('nav.referral'), icon: UsersIcon }] : []),
...(giftEnabled ? [{ path: '/gift', label: t('nav.gift'), icon: GiftIcon }] : []),
{ 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');
};
// A single elegant nav link: icon + label always visible, with a shared
// framer-motion pill that slides to the active item on navigation.
const renderNavLink = (
path: string,
label: string,
Icon: React.ComponentType<{ className?: string }>,
admin = false,
) => {
const active = admin ? location.pathname.startsWith('/admin') : isActive(path);
return (
{active && (