import { Box, Button, Tab, Tabs, Typography, Menu, MenuItem, Divider, } from '@mui/material'; import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; import { useLocation, useNavigate } from 'react-router-dom'; import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded'; import { useEffect, useRef, useState, useCallback } from 'react'; import CustomTooltip from './Notifications/CustomTooltip'; import CoinsDisplay from './CoinsDisplay'; import { HeadAvatar } from './HeadAvatar'; import { fetchPlayer } from './../api'; import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; import PersonIcon from '@mui/icons-material/Person'; import SettingsIcon from '@mui/icons-material/Settings'; import { useTheme } from '@mui/material/styles'; import InventoryIcon from '@mui/icons-material/Inventory'; import { RiCoupon3Fill } from 'react-icons/ri'; import type { NotificationPosition } from '../components/Notifications/CustomNotification'; import { isNotificationsEnabled, getNotifPositionFromSettings, } from '../utils/notifications'; declare global { interface Window { electron: { ipcRenderer: { invoke(channel: string, ...args: unknown[]): Promise; on(channel: string, func: (...args: unknown[]) => void): void; removeAllListeners(channel: string): void; }; }; } } // Определяем пропсы interface TopBarProps { onRegister?: () => void; // Опционально, если нужен обработчик регистрации username?: string; } export default function TopBar({ onRegister, username }: TopBarProps) { // Получаем текущий путь const location = useLocation(); const isLoginPage = location.pathname === '/login'; const [isAuthed, setIsAuthed] = useState(false); const isLaunchPage = location.pathname.startsWith('/launch'); const isRegistrationPage = location.pathname === '/registration'; const navigate = useNavigate(); const tabsWrapperRef = useRef(null); const tabsRootRef = useRef(null); const theme = useTheme(); const updateGradientVars = useCallback(() => { const root = tabsRootRef.current; if (!root) return; const tabsRect = root.getBoundingClientRect(); const active = root.querySelector('.MuiTab-root.Mui-selected'); if (!active) return; const activeRect = active.getBoundingClientRect(); const x = activeRect.left - tabsRect.left; root.style.setProperty('--tabs-w', `${tabsRect.width}px`); root.style.setProperty('--active-x', `${x}px`); }, []); const [skinUrl, setSkinUrl] = useState(''); const [skinVersion, setSkinVersion] = useState(0); const [avatarAnchorEl, setAvatarAnchorEl] = useState( null, ); // ===== QUICK LAUNCH ===== \\ const [lastVersion, setLastVersion] = useState(null); useEffect(() => { try { const raw = localStorage.getItem('last_launched_version'); if (!raw) return; setLastVersion(JSON.parse(raw)); } catch { setLastVersion(null); } }, []); // ===== QUICK LAUNCH ===== \\ const path = location.pathname || ''; const isAuthPage = path.startsWith('/login') || path.startsWith('/registration'); const [notifOpen, setNotifOpen] = useState(false); const [notifMsg, setNotifMsg] = useState(''); const [notifSeverity, setNotifSeverity] = useState< 'success' | 'info' | 'warning' | 'error' >('info'); const [notifPos, setNotifPos] = useState({ vertical: 'bottom', horizontal: 'center', }); const showNotification = ( message: React.ReactNode, severity: 'success' | 'info' | 'warning' | 'error' = 'info', position: NotificationPosition = getNotifPositionFromSettings(), ) => { if (!isNotificationsEnabled()) return; setNotifMsg(message); setNotifSeverity(severity); setNotifPos(position); setNotifOpen(true); }; const TAB_ROUTES: Array<{ value: number; match: (p: string) => boolean; to: string; }> = [ { value: 0, match: (p) => p === '/news', to: '/news', }, { value: 1, match: (p) => p === '/', to: '/', }, { value: 2, match: (p) => p.startsWith('/shop'), to: '/shop', }, { value: 3, match: (p) => p.startsWith('/marketplace'), to: '/marketplace', }, { value: 4, match: (p) => p.startsWith('/voice'), to: '/voice', }, ]; const selectedTab = TAB_ROUTES.find((r) => r.match(location.pathname))?.value ?? false; useEffect(() => { updateGradientVars(); window.addEventListener('resize', updateGradientVars); return () => window.removeEventListener('resize', updateGradientVars); }, [updateGradientVars, selectedTab, location.pathname]); useEffect(() => { const saved = localStorage.getItem('launcher_config'); try { const cfg = saved ? JSON.parse(saved) : null; setIsAuthed(Boolean(cfg?.accessToken)); // или cfg?.uuid/username — как у тебя принято } catch { setIsAuthed(false); } }, [location.pathname]); // можно и без dependency, но так надёжнее при logout/login const avatarMenuOpen = Boolean(avatarAnchorEl); const handleAvatarClick = (event: React.MouseEvent) => { setAvatarAnchorEl(event.currentTarget); }; const handleAvatarMenuClose = () => { setAvatarAnchorEl(null); }; // useEffect(() => { // if (location.pathname === '/news') { // setValue(0); // setActivePage('news'); // } else if (location.pathname === '/') { // setValue(1); // setActivePage('versions'); // } else if (location.pathname.startsWith('/shop')) { // setValue(3); // setActivePage('shop'); // } else if (location.pathname.startsWith('/marketplace')) { // setValue(4); // setActivePage('marketplace'); // } else { // // любые страницы не из TopBar: /profile, /daily, /dailyquests, и т.д. // setValue(false); // setActivePage(''); // } // }, [location.pathname]); const handleLaunchPage = () => { navigate('/'); }; const handleTabsWheel = (event: React.WheelEvent) => { // чтобы страница не скроллилась вертикально event.preventDefault(); if (!tabsWrapperRef.current) return; // Находим внутренний скроллер MUI Tabs const scroller = tabsWrapperRef.current.querySelector( '.MuiTabs-scroller', ) as HTMLDivElement | null; if (!scroller) return; // Прокручиваем горизонтально, используя вертикальный скролл мыши scroller.scrollLeft += event.deltaY * 0.3; requestAnimationFrame(updateGradientVars); }; // const getPageTitle = () => { // if (isLoginPage) { // return 'Вход'; // } // if (isLaunchPage) { // return 'Запуск'; // } // if (isVersionsExplorerPage) { // if (activePage === 'versions') { // return 'Версии'; // } // if (activePage === 'profile') { // return 'Профиль'; // } // if (activePage === 'shop') { // return 'Магазин'; // } // if (activePage === 'marketplace') { // return 'Рынок'; // } // } // return 'Неизвестная страница'; // }; // Функция для получения количества монет const tabBaseSx = [{ fontSize: '0.7em' }, theme.launcher.topbar.tabBase]; const logout = () => { localStorage.removeItem('launcher_config'); localStorage.removeItem(`coins:${username}`); localStorage.removeItem('last_route'); navigate('/login'); window.electron.ipcRenderer.invoke('auth-changed', { isAuthed: false }); }; const loadSkin = useCallback(async () => { if (!isAuthed) { setSkinUrl(''); return; } const savedConfig = localStorage.getItem('launcher_config'); if (!savedConfig) return; let cfg: any = null; try { cfg = JSON.parse(savedConfig); } catch { return; } const uuid = cfg?.uuid; if (!uuid) return; try { const player = await fetchPlayer(uuid); setSkinUrl(player.skin_url || ''); } catch (e) { console.error('Не удалось получить скин:', e); setSkinUrl(''); } }, [isAuthed]); useEffect(() => { loadSkin(); }, [loadSkin, location.pathname]); useEffect(() => { const handler = () => { setSkinVersion((v) => v + 1); loadSkin(); }; window.addEventListener('skin-updated', handler as EventListener); return () => window.removeEventListener('skin-updated', handler as EventListener); }, [loadSkin]); useEffect(() => { const handler = () => { requestAnimationFrame(updateGradientVars); }; window.addEventListener('settings-updated', handler as EventListener); return () => window.removeEventListener('settings-updated', handler as EventListener); }, [updateGradientVars]); const handleQuickLaunch = async () => { const raw = localStorage.getItem('last_launched_version'); if (!raw) { showNotification('Вы не запускали ни одну из сборок!', 'warning'); return; } const ctx = JSON.parse(raw); const savedConfig = JSON.parse( localStorage.getItem('launcher_config') || '{}', ); if (!savedConfig.accessToken) { showNotification('Вы не авторизованы', 'error'); return; } await window.electron.ipcRenderer.invoke('launch-minecraft', { accessToken: savedConfig.accessToken, uuid: savedConfig.uuid, username: savedConfig.username, memory: ctx.memory, baseVersion: ctx.baseVersion, fabricVersion: ctx.fabricVersion, packName: ctx.packName, serverIp: ctx.serverIp, isVanillaVersion: ctx.isVanillaVersion, versionToLaunchOverride: ctx.versionToLaunchOverride, }); }; const getLastLaunchLabel = (v: any) => { if (!v) return ''; const title = v.isVanillaVersion ? `Minecraft ${v.versionId}` : `Сборка ${v.packName}`; const details = [ v.baseVersion ? `MC ${v.baseVersion}` : null, v.memory ? `${v.memory} MB RAM` : null, ] .filter(Boolean) .join(' · '); return ( {title} {details} ); }; return ( {/* Левая часть */} {(isLaunchPage || isRegistrationPage) && ( )} {isAuthed && !isLaunchPage && ( { const route = TAB_ROUTES.find((r) => r.value === newValue); if (route) navigate(route.to); }} aria-label="basic tabs example" variant="scrollable" scrollButtons={false} disableRipple={true} sx={{ ...theme.launcher.topbar.tabs, }} > )} {/* Центр */} {/* {getPageTitle()} */} {/* Правая часть со всеми кнопками */} {lastVersion && ( )} {!isLoginPage && !isRegistrationPage && username && ( )} {/* Кнопка регистрации, если на странице логина */} {!isLoginPage && !isRegistrationPage && username && ( navigate('/fakepaymentpage')} disableRefreshOnClick={true} // чтобы клик не дёргал fetchCoins /> )} {/* Кнопки управления окном */} {/* ===== 1 строка: аватар + ник + валюта ===== */} {username || 'Игрок'} { handleAvatarMenuClose(); navigate('/profile'); }} sx={[ { fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' }, theme.launcher.topbar.menuItem, ]} > Профиль { handleAvatarMenuClose(); navigate('/inventory'); }} sx={[ { fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' }, theme.launcher.topbar.menuItem, ]} > Инвентарь {/* ===== 2 строка: ежедневные задания ===== */} { handleAvatarMenuClose(); navigate('/dailyquests'); }} sx={[ { fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' }, theme.launcher.topbar.menuItem, ]} > Ежедневные задания {/* ===== 3 строка: ежедневная награда ===== */} { handleAvatarMenuClose(); navigate('/daily'); }} sx={[ { fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' }, theme.launcher.topbar.menuItem, ]} > Ежедневная награда { handleAvatarMenuClose(); navigate('/settings'); }} sx={[ { fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' }, theme.launcher.topbar.menuItem, ]} > Настройки { handleAvatarMenuClose(); navigate('/promocode'); }} sx={[ { fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' }, theme.launcher.topbar.menuItem, ]} > Промокоды {!isLoginPage && !isRegistrationPage && username && ( )} {/* ↓↓↓ дальше ты сам добавишь пункты ↓↓↓ */} ); }