import { Link, useLocation } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; import { useState, useEffect } from 'react'; import { initDataUser } from '@telegram-apps/sdk-react'; import { useAuthStore } from '@/store/auth'; import { useTheme } from '@/hooks/useTheme'; import { usePlatform } from '@/platform'; import { brandingApi, getCachedBranding, setCachedBranding, preloadLogo, isLogoPreloaded, } from '@/api/branding'; import { themeColorsApi } from '@/api/themeColors'; import { cn } from '@/lib/utils'; import LanguageSwitcher from '@/components/LanguageSwitcher'; import TicketNotificationBell from '@/components/TicketNotificationBell'; // Icons import { HomeIcon, SubscriptionIcon, WalletIcon, UsersIcon, ChatIcon, UserIcon, LogoutIcon, GamepadIcon, ClipboardIcon, InfoIcon, CogIcon, WheelIcon, MenuIcon, CloseIcon, SunIcon, MoonIcon, SearchIcon, } from './icons'; const FALLBACK_NAME = import.meta.env.VITE_APP_NAME || 'Cabinet'; const FALLBACK_LOGO = import.meta.env.VITE_APP_LOGO || 'V'; import type { TelegramPlatform } from '@/hooks/useTelegramSDK'; interface AppHeaderProps { mobileMenuOpen: boolean; setMobileMenuOpen: (open: boolean) => void; onCommandPaletteOpen: () => void; headerHeight: number; isFullscreen: boolean; safeAreaInset: { top: number; bottom: number; left: number; right: number }; contentSafeAreaInset: { top: number; bottom: number; left: number; right: number }; telegramPlatform?: TelegramPlatform; wheelEnabled?: boolean; referralEnabled?: boolean; hasContests?: boolean; hasPolls?: boolean; } export function AppHeader({ mobileMenuOpen, setMobileMenuOpen, onCommandPaletteOpen, headerHeight, isFullscreen, safeAreaInset, contentSafeAreaInset, telegramPlatform, wheelEnabled, referralEnabled, hasContests, hasPolls, }: AppHeaderProps) { const { t } = useTranslation(); const location = useLocation(); const { user, logout, isAdmin } = useAuthStore(); const { toggleTheme, isDark } = useTheme(); const { haptic, platform } = usePlatform(); const [userPhotoUrl, setUserPhotoUrl] = useState(null); const [logoLoaded, setLogoLoaded] = useState(() => isLogoPreloaded()); // Branding const { data: branding } = useQuery({ queryKey: ['branding'], queryFn: async () => { const data = await brandingApi.getBranding(); setCachedBranding(data); preloadLogo(data); return data; }, initialData: getCachedBranding() ?? undefined, staleTime: 60000, refetchOnWindowFocus: true, retry: 1, }); const appName = branding ? branding.name : FALLBACK_NAME; const logoLetter = branding?.logo_letter || FALLBACK_LOGO; const hasCustomLogo = branding?.has_custom_logo || false; const logoUrl = branding ? brandingApi.getLogoUrl(branding) : null; // Theme toggle visibility const { data: enabledThemes } = useQuery({ queryKey: ['enabled-themes'], queryFn: themeColorsApi.getEnabledThemes, staleTime: 1000 * 60 * 5, }); const canToggle = enabledThemes?.dark && enabledThemes?.light; // Get user photo from Telegram useEffect(() => { try { const user = initDataUser(); if (user?.photo_url) { setUserPhotoUrl(user.photo_url); } } catch { // Not in Telegram or init data not available } }, []); // Lock scroll when menu is open (works in iframe/Telegram Mini App) useEffect(() => { if (!mobileMenuOpen) return; const preventDefault = (e: TouchEvent) => { // Allow scrolling inside menu content const target = e.target as HTMLElement; if (target.closest('.mobile-menu-content')) return; e.preventDefault(); }; document.addEventListener('touchmove', preventDefault, { passive: false }); document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; return () => { document.removeEventListener('touchmove', preventDefault); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }; }, [mobileMenuOpen]); const isActive = (path: string) => { if (path === '/') return location.pathname === '/'; return location.pathname.startsWith(path); }; const isAdminActive = () => location.pathname.startsWith('/admin'); const navItems = [ { path: '/', label: t('nav.dashboard'), icon: HomeIcon }, { path: '/subscription', label: t('nav.subscription'), icon: SubscriptionIcon }, { path: '/balance', label: t('nav.balance'), icon: WalletIcon }, ...(referralEnabled ? [{ path: '/referral', label: t('nav.referral'), icon: UsersIcon }] : []), { path: '/support', label: t('nav.support'), icon: ChatIcon }, ...(hasContests ? [{ path: '/contests', label: t('nav.contests'), icon: GamepadIcon }] : []), ...(hasPolls ? [{ path: '/polls', label: t('nav.polls'), icon: ClipboardIcon }] : []), ...(wheelEnabled ? [{ path: '/wheel', label: t('nav.wheel'), icon: WheelIcon }] : []), { path: '/info', label: t('nav.info'), icon: InfoIcon }, ]; return ( <> {/* Header - only on mobile */}
mobileMenuOpen && setMobileMenuOpen(false)} >
{/* Logo */} setMobileMenuOpen(false)} className={cn('flex flex-shrink-0 items-center gap-2.5', !appName && 'mr-4')} >
{logoLetter} {hasCustomLogo && logoUrl && ( {appName setLogoLoaded(true)} /> )}
{appName && ( {appName} )} {/* Right side */}
{/* Command palette trigger (web only) */} {platform !== 'telegram' && ( )} {/* Theme toggle */} {canToggle && ( )}
setMobileMenuOpen(false)}>
setMobileMenuOpen(false)}>
{/* Mobile menu button */}
{/* Mobile menu overlay */} {mobileMenuOpen && (
{/* Backdrop */}
setMobileMenuOpen(false)} /> {/* Menu content */}
{/* User info */}
{userPhotoUrl ? ( Avatar { e.currentTarget.style.display = 'none'; e.currentTarget.nextElementSibling?.classList.remove('hidden'); }} /> ) : null}
{user?.first_name || user?.username}
@{user?.username || `ID: ${user?.telegram_id}`}
{/* Nav items */}
)} ); }