870 lines
26 KiB
TypeScript
870 lines
26 KiB
TypeScript
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<any>;
|
||
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<HTMLDivElement | null>(null);
|
||
const tabsRootRef = useRef<HTMLDivElement | null>(null);
|
||
const theme = useTheme();
|
||
|
||
const updateGradientVars = useCallback(() => {
|
||
const root = tabsRootRef.current;
|
||
if (!root) return;
|
||
|
||
const tabsRect = root.getBoundingClientRect();
|
||
const active = root.querySelector<HTMLElement>('.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<string>('');
|
||
const [skinVersion, setSkinVersion] = useState(0);
|
||
const [avatarAnchorEl, setAvatarAnchorEl] = useState<null | HTMLElement>(
|
||
null,
|
||
);
|
||
|
||
// ===== QUICK LAUNCH ===== \\
|
||
const [lastVersion, setLastVersion] = useState<null | any>(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<React.ReactNode>('');
|
||
const [notifSeverity, setNotifSeverity] = useState<
|
||
'success' | 'info' | 'warning' | 'error'
|
||
>('info');
|
||
|
||
const [notifPos, setNotifPos] = useState<NotificationPosition>({
|
||
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<HTMLElement>) => {
|
||
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<HTMLDivElement>) => {
|
||
// чтобы страница не скроллилась вертикально
|
||
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 (
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '0.2vw' }}>
|
||
<Typography sx={{ fontSize: '0.9vw', fontWeight: 600 }}>
|
||
{title}
|
||
</Typography>
|
||
<Typography sx={{ fontSize: '0.75vw', opacity: 0.7 }}>
|
||
{details}
|
||
</Typography>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<Box
|
||
className={isAuthPage ? undefined : 'glass-ui'}
|
||
sx={[
|
||
{
|
||
display: 'flex',
|
||
position: 'fixed',
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
height: '8vh',
|
||
zIndex: 1000,
|
||
width: '100%',
|
||
WebkitAppRegion: 'drag',
|
||
overflow: 'hidden',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
},
|
||
theme.launcher.topbar.firstBox,
|
||
]}
|
||
>
|
||
{/* Левая часть */}
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
WebkitAppRegion: 'no-drag',
|
||
gap: '2vw',
|
||
alignItems: 'center',
|
||
marginLeft: '1vw',
|
||
}}
|
||
>
|
||
{(isLaunchPage || isRegistrationPage) && (
|
||
<Button
|
||
variant="outlined"
|
||
onClick={() => handleLaunchPage()}
|
||
sx={[
|
||
{
|
||
width: '3em',
|
||
height: '3em',
|
||
borderRadius: '50%',
|
||
minWidth: 'unset',
|
||
minHeight: 'unset',
|
||
},
|
||
theme.launcher.topbar.backButton,
|
||
]}
|
||
>
|
||
<ArrowBackRoundedIcon />
|
||
</Button>
|
||
)}
|
||
{isAuthed && !isLaunchPage && (
|
||
<Box
|
||
ref={tabsWrapperRef}
|
||
onWheel={handleTabsWheel}
|
||
// старый вариант
|
||
sx={{
|
||
borderBottom: 1,
|
||
...theme.launcher.topbar.tabsBox,
|
||
// '& .MuiTabs-indicator': {
|
||
// backgroundColor: 'rgba(255, 77, 77, 1)',
|
||
// },
|
||
}}
|
||
// sx={{
|
||
// borderBottom: 'none',
|
||
// borderRadius: '2vw',
|
||
// px: '0.6vw',
|
||
// py: '0.4vw',
|
||
// background: 'rgba(0,0,0,0.35)',
|
||
// border: '1px solid rgba(255,255,255,0.08)',
|
||
// boxShadow: '0 8px 20px rgba(0,0,0,0.25)',
|
||
// '& .MuiTabs-indicator': {
|
||
// height: '100%',
|
||
// borderRadius: '1.6vw',
|
||
// background:
|
||
// 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
||
// opacity: 0.18,
|
||
// },
|
||
// }}
|
||
>
|
||
<CustomTooltip
|
||
title={
|
||
'Покрути колесиком мыши чтобы увидеть остальные элементы меню'
|
||
}
|
||
arrow
|
||
placement="bottom"
|
||
TransitionProps={{ timeout: 100 }}
|
||
>
|
||
<Tabs
|
||
ref={tabsRootRef}
|
||
value={selectedTab}
|
||
onChange={(_, newValue) => {
|
||
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,
|
||
}}
|
||
>
|
||
<Tab
|
||
label="Новости"
|
||
disableRipple={true}
|
||
sx={[
|
||
...tabBaseSx,
|
||
selectedTab === 0 ? theme.launcher.topbar.tabActive : null,
|
||
]}
|
||
/>
|
||
<Tab
|
||
label="Версии"
|
||
disableRipple={true}
|
||
sx={[
|
||
...tabBaseSx,
|
||
selectedTab === 1 ? theme.launcher.topbar.tabActive : null,
|
||
]}
|
||
/>
|
||
<Tab
|
||
label="Магазин"
|
||
disableRipple={true}
|
||
sx={[
|
||
...tabBaseSx,
|
||
selectedTab === 2 ? theme.launcher.topbar.tabActive : null,
|
||
]}
|
||
/>
|
||
<Tab
|
||
label="Рынок"
|
||
disableRipple={true}
|
||
sx={[
|
||
...tabBaseSx,
|
||
selectedTab === 3 ? theme.launcher.topbar.tabActive : null,
|
||
]}
|
||
/>
|
||
<Tab
|
||
label="Голосовой чат"
|
||
disableRipple={true}
|
||
sx={[
|
||
...tabBaseSx,
|
||
selectedTab === 4 ? theme.launcher.topbar.tabActive : null,
|
||
]}
|
||
/>
|
||
</Tabs>
|
||
</CustomTooltip>
|
||
</Box>
|
||
)}
|
||
</Box>
|
||
{/* Центр */}
|
||
<Box
|
||
sx={{
|
||
position: 'absolute',
|
||
left: '50%',
|
||
transform: 'translateX(-50%)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
flexGrow: 1,
|
||
WebkitAppRegion: 'drag',
|
||
}}
|
||
>
|
||
{/* <Typography
|
||
variant="h6"
|
||
sx={{ color: 'white', fontFamily: 'Benzin-Bold' }}
|
||
>
|
||
{getPageTitle()}
|
||
</Typography> */}
|
||
</Box>
|
||
{/* Правая часть со всеми кнопками */}
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
WebkitAppRegion: 'no-drag',
|
||
gap: '1vw',
|
||
alignItems: 'center',
|
||
marginRight: '1vw',
|
||
}}
|
||
>
|
||
{lastVersion && (
|
||
<CustomTooltip
|
||
title={getLastLaunchLabel(lastVersion)}
|
||
arrow
|
||
placement="bottom"
|
||
essential
|
||
TransitionProps={{ timeout: 120 }}
|
||
>
|
||
<Button
|
||
onClick={handleQuickLaunch}
|
||
disableRipple
|
||
disableFocusRipple
|
||
sx={{
|
||
minWidth: 'unset',
|
||
width: '3vw',
|
||
height: '3vw',
|
||
borderRadius: '3vw',
|
||
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'flex-end',
|
||
overflow: 'hidden',
|
||
|
||
px: '0.8vw',
|
||
|
||
background:
|
||
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.20), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.16), transparent 55%), rgba(10,10,20,0.92)',
|
||
border: '1px solid rgba(255,255,255,0.10)',
|
||
boxShadow: '0 1.4vw 3.8vw rgba(0,0,0,0.55)',
|
||
color: 'white',
|
||
backdropFilter: 'blur(14px)',
|
||
|
||
transition: 'all 0.3s ease',
|
||
|
||
'& .quick-text': {
|
||
opacity: 0,
|
||
whiteSpace: 'nowrap',
|
||
marginRight: '0.6vw',
|
||
fontSize: '0.9vw',
|
||
fontFamily: 'Benzin-Bold',
|
||
transform: 'translateX(10px)',
|
||
transition: 'all 0.25s ease',
|
||
},
|
||
|
||
'&:hover': {
|
||
width: '16.5vw',
|
||
transform: 'scale(1.05)',
|
||
|
||
'& .quick-text': {
|
||
opacity: 1,
|
||
transform: 'translateX(0)',
|
||
},
|
||
},
|
||
|
||
'&:after': {
|
||
content: '""',
|
||
position: 'absolute',
|
||
left: '0%',
|
||
right: '0%',
|
||
bottom: 0,
|
||
height: '0.15vw',
|
||
borderRadius: '999px',
|
||
background:
|
||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
||
opacity: 0.9,
|
||
},
|
||
}}
|
||
>
|
||
<span className="quick-text">Быстрый запуск</span>
|
||
<span style={{ fontSize: '1vw' }}>⚡</span>
|
||
</Button>
|
||
</CustomTooltip>
|
||
)}
|
||
{!isLoginPage && !isRegistrationPage && username && (
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '1vw' }}>
|
||
<HeadAvatar
|
||
skinUrl={skinUrl}
|
||
size={44}
|
||
version={skinVersion}
|
||
style={{
|
||
borderRadius: '3vw',
|
||
cursor: 'pointer',
|
||
}}
|
||
onClick={handleAvatarClick}
|
||
/>
|
||
</Box>
|
||
)}
|
||
{/* Кнопка регистрации, если на странице логина */}
|
||
{!isLoginPage && !isRegistrationPage && username && (
|
||
<CoinsDisplay
|
||
username={username}
|
||
size="medium"
|
||
autoUpdate={true}
|
||
showTooltip={true}
|
||
onClick={() => navigate('/fakepaymentpage')}
|
||
disableRefreshOnClick={true} // чтобы клик не дёргал fetchCoins
|
||
/>
|
||
)}
|
||
|
||
{/* Кнопки управления окном */}
|
||
<Button
|
||
onClick={() => {
|
||
window.electron.ipcRenderer.invoke('minimize-app');
|
||
}}
|
||
sx={{
|
||
minWidth: 'unset',
|
||
minHeight: 'unset',
|
||
width: '3em',
|
||
height: '3em',
|
||
borderRadius: '50%',
|
||
...theme.launcher.topbar.windowControlButton,
|
||
}}
|
||
>
|
||
<svg
|
||
className="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium"
|
||
focusable="false"
|
||
aria-hidden="true"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path
|
||
d="M 7 19 h 10 c 0.55 0 1 0.45 1 1 s -0.45 1 -1 1 H 7 c -0.55 0 -1 -0.45 -1 -1 s 0.45 -1 1 -1"
|
||
fill={theme.launcher.topbar.windowControlIcon.color}
|
||
></path>
|
||
</svg>
|
||
</Button>
|
||
<Button
|
||
onClick={() => {
|
||
window.electron.ipcRenderer.invoke('close-app');
|
||
}}
|
||
sx={{
|
||
minWidth: 'unset',
|
||
minHeight: 'unset',
|
||
width: '3em',
|
||
height: '3em',
|
||
borderRadius: '50%',
|
||
...theme.launcher.topbar.windowControlButton,
|
||
}}
|
||
>
|
||
<CloseRoundedIcon
|
||
sx={{ color: theme.launcher.topbar.windowControlIcon.color }}
|
||
/>
|
||
</Button>
|
||
</Box>
|
||
<Menu
|
||
anchorEl={avatarAnchorEl}
|
||
open={avatarMenuOpen}
|
||
onClose={handleAvatarMenuClose}
|
||
anchorOrigin={{
|
||
vertical: 'bottom',
|
||
horizontal: 'right',
|
||
}}
|
||
transformOrigin={{
|
||
vertical: 'top',
|
||
horizontal: 'right',
|
||
}}
|
||
PaperProps={{
|
||
sx: {
|
||
mt: '0.5vw',
|
||
borderRadius: '1vw',
|
||
minWidth: '16vw',
|
||
...theme.launcher.topbar.menuPaper,
|
||
},
|
||
}}
|
||
>
|
||
{/* ===== 1 строка: аватар + ник + валюта ===== */}
|
||
<Box
|
||
sx={{
|
||
display: 'grid',
|
||
gridTemplateColumns: 'auto 1fr',
|
||
gap: '1.5vw',
|
||
alignItems: 'center',
|
||
px: '2vw',
|
||
py: '0.8vw',
|
||
}}
|
||
>
|
||
<HeadAvatar
|
||
skinUrl={skinUrl}
|
||
size={40}
|
||
version={skinVersion}
|
||
style={{ borderRadius: '3vw' }}
|
||
/>
|
||
|
||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||
<Typography
|
||
sx={[{ fontSize: '2vw' }, theme.launcher.topbar.menuUsername]}
|
||
>
|
||
{username || 'Игрок'}
|
||
</Typography>
|
||
|
||
<CoinsDisplay
|
||
username={username}
|
||
size="medium"
|
||
autoUpdate={true}
|
||
showTooltip={false}
|
||
sx={{
|
||
border: 'none',
|
||
padding: '0vw',
|
||
}}
|
||
backgroundColor={'rgba(0, 0, 0, 0)'}
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Divider sx={{ my: '0.4vw', ...theme.launcher.topbar.menuDivider }} />
|
||
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleAvatarMenuClose();
|
||
navigate('/profile');
|
||
}}
|
||
sx={[
|
||
{ fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' },
|
||
theme.launcher.topbar.menuItem,
|
||
]}
|
||
>
|
||
<PersonIcon sx={{ fontSize: '2vw' }} /> Профиль
|
||
</MenuItem>
|
||
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleAvatarMenuClose();
|
||
navigate('/inventory');
|
||
}}
|
||
sx={[
|
||
{ fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' },
|
||
theme.launcher.topbar.menuItem,
|
||
]}
|
||
>
|
||
<InventoryIcon sx={{ fontSize: '2vw' }} /> Инвентарь
|
||
</MenuItem>
|
||
|
||
{/* ===== 2 строка: ежедневные задания ===== */}
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleAvatarMenuClose();
|
||
navigate('/dailyquests');
|
||
}}
|
||
sx={[
|
||
{ fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' },
|
||
theme.launcher.topbar.menuItem,
|
||
]}
|
||
>
|
||
<CalendarMonthIcon sx={{ fontSize: '2vw' }} /> Ежедневные задания
|
||
</MenuItem>
|
||
|
||
{/* ===== 3 строка: ежедневная награда ===== */}
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleAvatarMenuClose();
|
||
navigate('/daily');
|
||
}}
|
||
sx={[
|
||
{ fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' },
|
||
theme.launcher.topbar.menuItem,
|
||
]}
|
||
>
|
||
<EmojiEventsIcon sx={{ fontSize: '2vw' }} /> Ежедневная награда
|
||
</MenuItem>
|
||
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleAvatarMenuClose();
|
||
navigate('/settings');
|
||
}}
|
||
sx={[
|
||
{ fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' },
|
||
theme.launcher.topbar.menuItem,
|
||
]}
|
||
>
|
||
<SettingsIcon sx={{ fontSize: '2vw' }} /> Настройки
|
||
</MenuItem>
|
||
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleAvatarMenuClose();
|
||
navigate('/promocode');
|
||
}}
|
||
sx={[
|
||
{ fontSize: '1.5vw', gap: '0.5vw', py: '0.7vw' },
|
||
theme.launcher.topbar.menuItem,
|
||
]}
|
||
>
|
||
<RiCoupon3Fill style={{ fontSize: '2vw' }} /> Промокоды
|
||
</MenuItem>
|
||
|
||
<Divider sx={{ my: '0.4vw', ...theme.launcher.topbar.menuDivider }} />
|
||
{!isLoginPage && !isRegistrationPage && username && (
|
||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||
<Button
|
||
variant="outlined"
|
||
color="primary"
|
||
onClick={() => {
|
||
handleAvatarMenuClose();
|
||
logout();
|
||
}}
|
||
sx={[
|
||
{
|
||
width: '90%',
|
||
height: '3vw',
|
||
fontSize: '1.2vw',
|
||
mx: '1vw',
|
||
},
|
||
theme.launcher.topbar.logoutButton,
|
||
]}
|
||
>
|
||
Выйти
|
||
</Button>
|
||
</Box>
|
||
)}
|
||
|
||
{/* ↓↓↓ дальше ты сам добавишь пункты ↓↓↓ */}
|
||
</Menu>
|
||
</Box>
|
||
);
|
||
}
|