diff --git a/src/components/layout/AppShell/AppShell.tsx b/src/components/layout/AppShell/AppShell.tsx index 76bae6f..4eef427 100644 --- a/src/components/layout/AppShell/AppShell.tsx +++ b/src/components/layout/AppShell/AppShell.tsx @@ -2,6 +2,7 @@ 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'; @@ -109,11 +110,13 @@ export function AppShell({ children }: AppShellProps) { }; }, []); - // Desktop navigation items - const desktopNavItems = [ + // 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 }, @@ -128,6 +131,48 @@ export function AppShell({ children }: AppShellProps) { 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 && ( + + )} + + {label} + + ); + }; + // headerHeight comes from useHeaderHeight() — accounts for TG safe area in fullscreen return ( @@ -170,81 +215,12 @@ export function AppShell({ children }: AppShellProps) { {/* Center Navigation */} - - {desktopNavItems.map((item) => ( - - - - {item.label} - - - ))} - {referralEnabled && ( - - - - {t('nav.referral')} - - - )} - {giftEnabled && ( - - - - {t('nav.gift')} - - - )} + + {desktopNav.map((item) => renderNavLink(item.path, item.label, item.icon))} {isAdmin && ( <> - - - - - {t('admin.nav.title')} - - + + {renderNavLink('/admin', t('admin.nav.title'), ShieldIcon, true)} > )} diff --git a/src/components/layout/AppShell/DesktopSidebar.tsx b/src/components/layout/AppShell/DesktopSidebar.tsx deleted file mode 100644 index 6f44363..0000000 --- a/src/components/layout/AppShell/DesktopSidebar.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import { Link, useLocation } from 'react-router'; -import { useTranslation } from 'react-i18next'; -import { motion } from 'framer-motion'; -import { useQuery } from '@tanstack/react-query'; - -import { useAuthStore } from '@/store/auth'; -import { displayName } from '@/utils/displayName'; -import { - brandingApi, - getCachedBranding, - setCachedBranding, - preloadLogo, - isLogoPreloaded, -} from '@/api/branding'; -import { cn } from '@/lib/utils'; -import { usePlatform } from '@/platform'; -// Icons -import { - HomeIcon, - SubscriptionIcon, - WalletIcon, - UsersIcon, - ChatIcon, - UserIcon, - LogoutIcon, - GamepadIcon, - ClipboardIcon, - InfoIcon, - CogIcon, - WheelIcon, -} from './icons'; - -const FALLBACK_NAME = import.meta.env.VITE_APP_NAME || 'Cabinet'; -const FALLBACK_LOGO = import.meta.env.VITE_APP_LOGO || 'V'; - -interface DesktopSidebarProps { - isAdmin?: boolean; - wheelEnabled?: boolean; - referralEnabled?: boolean; - hasContests?: boolean; - hasPolls?: boolean; -} - -export function DesktopSidebar({ - isAdmin, - wheelEnabled, - referralEnabled, - hasContests, - hasPolls, -}: DesktopSidebarProps) { - const { t } = useTranslation(); - const location = useLocation(); - const user = useAuthStore((state) => state.user); - const logout = useAuthStore((state) => state.logout); - const { haptic } = usePlatform(); - - // Branding - const { data: branding } = useQuery({ - queryKey: ['branding'], - queryFn: async () => { - const data = await brandingApi.getBranding(); - setCachedBranding(data); - await preloadLogo(data); - return data; - }, - initialData: getCachedBranding() ?? undefined, - initialDataUpdatedAt: 0, - 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; - - const isActive = (path: string) => - path === '/' ? location.pathname === '/' : location.pathname.startsWith(path); - const isAdminActive = () => location.pathname.startsWith('/admin'); - - const navItems = [ - { path: '/', label: t('nav.dashboard'), icon: HomeIcon }, - { path: '/subscriptions', 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 }, - ]; - - const handleNavClick = () => { - haptic.impact('light'); - }; - - return ( - - ); -} diff --git a/src/components/layout/AppShell/index.ts b/src/components/layout/AppShell/index.ts index 233b39d..37de912 100644 --- a/src/components/layout/AppShell/index.ts +++ b/src/components/layout/AppShell/index.ts @@ -1,4 +1,3 @@ export { AppShell } from './AppShell'; -export { DesktopSidebar } from './DesktopSidebar'; export { MobileBottomNav } from './MobileBottomNav'; export { AppHeader } from './AppHeader';