mnoga che sdelal
This commit is contained in:
@ -134,7 +134,7 @@ const AppLayout = () => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: location.pathname === '/profile' || location.pathname.startsWith('/launch')
|
justifyContent: location.pathname === '/profile' || location.pathname.startsWith('/launch') || location.pathname === '/login' || '/registration'
|
||||||
? 'center'
|
? 'center'
|
||||||
: 'flex-start',
|
: 'flex-start',
|
||||||
overflowX: 'hidden',
|
overflowX: 'hidden',
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Box, Typography } from '@mui/material';
|
|||||||
import CustomTooltip from './Notifications/CustomTooltip';
|
import CustomTooltip from './Notifications/CustomTooltip';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { fetchCoins } from '../api';
|
import { fetchCoins } from '../api';
|
||||||
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
|
|
||||||
interface CoinsDisplayProps {
|
interface CoinsDisplayProps {
|
||||||
// Основные пропсы
|
// Основные пропсы
|
||||||
@ -23,6 +24,8 @@ interface CoinsDisplayProps {
|
|||||||
// Стилизация
|
// Стилизация
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
textColor?: string;
|
textColor?: string;
|
||||||
|
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CoinsDisplay({
|
export default function CoinsDisplay({
|
||||||
@ -44,6 +47,8 @@ export default function CoinsDisplay({
|
|||||||
// Стилизация
|
// Стилизация
|
||||||
backgroundColor = 'rgba(0, 0, 0, 0.2)',
|
backgroundColor = 'rgba(0, 0, 0, 0.2)',
|
||||||
textColor = 'white',
|
textColor = 'white',
|
||||||
|
|
||||||
|
sx,
|
||||||
}: CoinsDisplayProps) {
|
}: CoinsDisplayProps) {
|
||||||
const [coins, setCoins] = useState<number>(externalValue || 0);
|
const [coins, setCoins] = useState<number>(externalValue || 0);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
@ -142,6 +147,8 @@ export default function CoinsDisplay({
|
|||||||
cursor: showTooltip ? 'help' : 'default',
|
cursor: showTooltip ? 'help' : 'default',
|
||||||
opacity: isLoading ? 0.7 : 1,
|
opacity: isLoading ? 0.7 : 1,
|
||||||
transition: 'opacity 0.2s ease',
|
transition: 'opacity 0.2s ease',
|
||||||
|
|
||||||
|
...sx,
|
||||||
}}
|
}}
|
||||||
onClick={username ? handleRefresh : undefined}
|
onClick={username ? handleRefresh : undefined}
|
||||||
title={username ? 'Нажмите для обновления' : undefined}
|
title={username ? 'Нажмите для обновления' : undefined}
|
||||||
|
|||||||
@ -1,28 +1,33 @@
|
|||||||
// src/renderer/components/HeadAvatar.tsx
|
// src/renderer/components/HeadAvatar.tsx
|
||||||
import { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
interface HeadAvatarProps {
|
interface HeadAvatarProps {
|
||||||
skinUrl?: string;
|
skinUrl?: string;
|
||||||
size?: number; // финальный размер головы, px
|
size?: number;
|
||||||
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SKIN =
|
||||||
|
'https://static.planetminecraft.com/files/resource_media/skin/original-steve-15053860.png';
|
||||||
|
|
||||||
export const HeadAvatar: React.FC<HeadAvatarProps> = ({
|
export const HeadAvatar: React.FC<HeadAvatarProps> = ({
|
||||||
skinUrl,
|
skinUrl,
|
||||||
size = 24,
|
size = 24,
|
||||||
|
style,
|
||||||
|
...canvasProps
|
||||||
}) => {
|
}) => {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!skinUrl || !canvasRef.current) return;
|
const finalSkinUrl = skinUrl?.trim() ? skinUrl : DEFAULT_SKIN;
|
||||||
|
|
||||||
const img = new Image();
|
|
||||||
img.crossOrigin = 'anonymous'; // на всякий случай, если CDN
|
|
||||||
img.src = skinUrl;
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'anonymous';
|
||||||
|
img.src = finalSkinUrl;
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
@ -30,26 +35,12 @@ export const HeadAvatar: React.FC<HeadAvatarProps> = ({
|
|||||||
canvas.height = size;
|
canvas.height = size;
|
||||||
|
|
||||||
ctx.clearRect(0, 0, size, size);
|
ctx.clearRect(0, 0, size, size);
|
||||||
|
|
||||||
// Координаты головы в стандартном скине 64x64:
|
|
||||||
// База головы: (8, 8, 8, 8)
|
|
||||||
// Слой шляпы/маски: (40, 8, 8, 8)
|
|
||||||
|
|
||||||
// Рисуем основную голову
|
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
ctx.drawImage(
|
|
||||||
img,
|
|
||||||
8, // sx
|
|
||||||
8, // sy
|
|
||||||
8, // sWidth
|
|
||||||
8, // sHeight
|
|
||||||
0, // dx
|
|
||||||
0, // dy
|
|
||||||
size, // dWidth
|
|
||||||
size, // dHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
// Рисуем слой шляпы поверх (если есть)
|
// База головы: (8, 8, 8, 8)
|
||||||
|
ctx.drawImage(img, 8, 8, 8, 8, 0, 0, size, size);
|
||||||
|
|
||||||
|
// Слой шляпы: (40, 8, 8, 8)
|
||||||
ctx.drawImage(img, 40, 8, 8, 8, 0, 0, size, size);
|
ctx.drawImage(img, 40, 8, 8, 8, 0, 0, size, size);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -61,11 +52,13 @@ export const HeadAvatar: React.FC<HeadAvatarProps> = ({
|
|||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
|
{...canvasProps}
|
||||||
style={{
|
style={{
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
imageRendering: 'pixelated',
|
imageRendering: 'pixelated',
|
||||||
|
...style, // 👈 даём переопределять снаружи
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
|||||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
import GradientTextField from '../GradientTextField';
|
import GradientTextField from '../GradientTextField';
|
||||||
import GradientVisibilityToggleIcon from '../../assets/Icons/GradientVisibilityToggleIcon'
|
import GradientVisibilityToggleIcon from '../../assets/Icons/GradientVisibilityToggleIcon'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
interface AuthFormProps {
|
interface AuthFormProps {
|
||||||
config: {
|
config: {
|
||||||
@ -16,6 +17,7 @@ interface AuthFormProps {
|
|||||||
|
|
||||||
const AuthForm = ({ config, handleInputChange, onLogin }: AuthFormProps) => {
|
const AuthForm = ({ config, handleInputChange, onLogin }: AuthFormProps) => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -92,6 +94,33 @@ const AuthForm = ({ config, handleInputChange, onLogin }: AuthFormProps) => {
|
|||||||
}}>
|
}}>
|
||||||
Войти
|
Войти
|
||||||
</Button>
|
</Button>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
onClick={() => navigate('/registration')}
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
fontSize: '1vw',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.08em',
|
||||||
|
cursor: 'pointer',
|
||||||
|
backgroundImage:
|
||||||
|
'linear-gradient(71deg, #F27121 0%, #E940CD 60%, #8A2387 100%)',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
textShadow: '0 0 15px rgba(0,0,0,0.9)',
|
||||||
|
'&:hover': {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Зарегистрироваться
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Box, Typography } from '@mui/material';
|
import { Box, Typography } from '@mui/material';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useMemo } from 'react';
|
import { useMemo, useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface HeaderConfig {
|
interface HeaderConfig {
|
||||||
title: string;
|
title: string;
|
||||||
@ -10,6 +10,19 @@ interface HeaderConfig {
|
|||||||
|
|
||||||
export default function PageHeader() {
|
export default function PageHeader() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const [isAuthed, setIsAuthed] = useState(false);
|
||||||
|
|
||||||
|
const isLaunchPage = location.pathname.startsWith('/launch');
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
const headerConfig: HeaderConfig | null = useMemo(() => {
|
const headerConfig: HeaderConfig | null = useMemo(() => {
|
||||||
const path = location.pathname;
|
const path = location.pathname;
|
||||||
@ -34,7 +47,7 @@ export default function PageHeader() {
|
|||||||
|
|
||||||
if (path === '/') {
|
if (path === '/') {
|
||||||
return {
|
return {
|
||||||
title: 'Выбор версию клиента',
|
title: 'Выбор версии клиента',
|
||||||
subtitle: 'Выберите установленную версию или добавьте новую сборку',
|
subtitle: 'Выберите установленную версию или добавьте новую сборку',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -49,39 +62,31 @@ export default function PageHeader() {
|
|||||||
if (path.startsWith('/dailyquests')) {
|
if (path.startsWith('/dailyquests')) {
|
||||||
return {
|
return {
|
||||||
title: 'Ежедневные задания',
|
title: 'Ежедневные задания',
|
||||||
subtitle: 'Выполняйте ежедневные задания разной сложности и получайте награды!',
|
subtitle:
|
||||||
};
|
'Выполняйте ежедневные задания разной сложности и получайте награды!',
|
||||||
}
|
|
||||||
|
|
||||||
if (path.startsWith('/profile')) {
|
|
||||||
return {
|
|
||||||
title: 'Профиль пользователя',
|
|
||||||
subtitle: 'Профиль пользователя для личных настроек',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.startsWith('/shop')) {
|
if (path.startsWith('/shop')) {
|
||||||
return {
|
return {
|
||||||
title: 'Внутриигровой магазин',
|
title: 'Внутриигровой магазин',
|
||||||
subtitle: 'Тратьте свою уникалькую, виртуальную валюту - Попы!',
|
subtitle: 'Тратьте свою уникальную виртуальную валюту — Попы!',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.startsWith('/marketplace')) {
|
if (path.startsWith('/marketplace')) {
|
||||||
return {
|
return {
|
||||||
title: 'Маркетплейс',
|
title: 'Маркетплейс',
|
||||||
subtitle: 'Покупайте или продавайте - торговая площадка между игроками',
|
subtitle: 'Покупайте или продавайте — торговая площадка между игроками',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Дефолт на всякий случай
|
// Дефолт
|
||||||
return {
|
return { title: 'test', subtitle: 'test' };
|
||||||
title: 'test',
|
|
||||||
subtitle: 'test',
|
|
||||||
};
|
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
if (!headerConfig || headerConfig.hidden) {
|
// ✅ один общий guard — тут и “hidden”, и “не авторизован”, и launch
|
||||||
|
if (!headerConfig || headerConfig.hidden || !isAuthed || isLaunchPage) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Box, Button, Tab, Tabs, Typography } from '@mui/material';
|
import { Box, Button, Tab, Tabs, Typography, Menu, MenuItem, Divider } from '@mui/material';
|
||||||
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
|
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
|
||||||
@ -7,6 +7,11 @@ import { Tooltip } from '@mui/material';
|
|||||||
import { fetchCoins } from '../api';
|
import { fetchCoins } from '../api';
|
||||||
import CustomTooltip from './Notifications/CustomTooltip';
|
import CustomTooltip from './Notifications/CustomTooltip';
|
||||||
import CoinsDisplay from './CoinsDisplay';
|
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';
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: {
|
electron: {
|
||||||
@ -29,14 +34,38 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
// Получаем текущий путь
|
// Получаем текущий путь
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isLoginPage = location.pathname === '/login';
|
const isLoginPage = location.pathname === '/login';
|
||||||
|
const [isAuthed, setIsAuthed] = useState(false);
|
||||||
const isLaunchPage = location.pathname.startsWith('/launch');
|
const isLaunchPage = location.pathname.startsWith('/launch');
|
||||||
const isVersionsExplorerPage = location.pathname.startsWith('/');
|
const isVersionsExplorerPage = location.pathname.startsWith('/');
|
||||||
const isRegistrationPage = location.pathname === '/registration';
|
const isRegistrationPage = location.pathname === '/registration';
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [coins, setCoins] = useState<number>(0);
|
|
||||||
const [value, setValue] = useState(1);
|
const [value, setValue] = useState(1);
|
||||||
const [activePage, setActivePage] = useState('versions');
|
const [activePage, setActivePage] = useState('versions');
|
||||||
const tabsWrapperRef = useRef<HTMLDivElement | null>(null);
|
const tabsWrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [skinUrl, setSkinUrl] = useState<string>('');
|
||||||
|
const [avatarAnchorEl, setAvatarAnchorEl] =
|
||||||
|
useState<null | HTMLElement>(null);
|
||||||
|
const [menuOpen, setMenuOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
@ -147,6 +176,19 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
navigate('/login');
|
navigate('/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const savedConfig = localStorage.getItem('launcher_config');
|
||||||
|
if (!savedConfig) return;
|
||||||
|
|
||||||
|
const config = JSON.parse(savedConfig);
|
||||||
|
const uuid = config.uuid;
|
||||||
|
if (!uuid) return;
|
||||||
|
|
||||||
|
fetchPlayer(uuid)
|
||||||
|
.then((player) => setSkinUrl(player.skin_url))
|
||||||
|
.catch((e) => console.error('Не удалось получить скин:', e));
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -197,7 +239,7 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
<ArrowBackRoundedIcon />
|
<ArrowBackRoundedIcon />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isLaunchPage && !isRegistrationPage && !isLoginPage && (
|
{isAuthed && !isLaunchPage && (
|
||||||
<Box
|
<Box
|
||||||
ref={tabsWrapperRef}
|
ref={tabsWrapperRef}
|
||||||
onWheel={handleTabsWheel}
|
onWheel={handleTabsWheel}
|
||||||
@ -264,25 +306,6 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tab
|
|
||||||
label="Профиль"
|
|
||||||
disableRipple={true}
|
|
||||||
onClick={() => {
|
|
||||||
setActivePage('profile');
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
color: 'white',
|
|
||||||
fontFamily: 'Benzin-Bold',
|
|
||||||
fontSize: '0.7em',
|
|
||||||
'&.Mui-selected': {
|
|
||||||
color: 'rgba(255, 77, 77, 1)',
|
|
||||||
},
|
|
||||||
'&:hover': {
|
|
||||||
color: 'rgb(177, 52, 52)',
|
|
||||||
},
|
|
||||||
transition: 'all 0.3s ease',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tab
|
<Tab
|
||||||
label="Магазин"
|
label="Магазин"
|
||||||
disableRipple={true}
|
disableRipple={true}
|
||||||
@ -357,32 +380,17 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isLoginPage && !isRegistrationPage && username && (
|
{!isLoginPage && !isRegistrationPage && username && (
|
||||||
<Button
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: '1vw' }}>
|
||||||
variant="outlined"
|
<HeadAvatar
|
||||||
color="primary"
|
skinUrl={skinUrl}
|
||||||
onClick={() => logout()}
|
size={44}
|
||||||
sx={{
|
style={{
|
||||||
width: '8em',
|
borderRadius: '3vw',
|
||||||
height: '3em',
|
cursor: 'pointer',
|
||||||
borderRadius: '2.5vw',
|
|
||||||
fontFamily: 'Benzin-Bold',
|
|
||||||
fontSize: '0.9em',
|
|
||||||
background:
|
|
||||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
transition: 'transform 0.3s ease',
|
|
||||||
'&:hover': {
|
|
||||||
background:
|
|
||||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
|
||||||
transform: 'scale(1.01)',
|
|
||||||
boxShadow: '0 4px 15px rgba(242, 113, 33, 0.4)',
|
|
||||||
},
|
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
|
||||||
}}
|
}}
|
||||||
>
|
onClick={handleAvatarClick}
|
||||||
Выйти
|
/>
|
||||||
</Button>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{/* Кнопка регистрации, если на странице логина */}
|
{/* Кнопка регистрации, если на странице логина */}
|
||||||
{!isLoginPage && !isRegistrationPage && username && (
|
{!isLoginPage && !isRegistrationPage && username && (
|
||||||
@ -393,37 +401,6 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
showTooltip={true}
|
showTooltip={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isLoginPage && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={() => navigate('/registration')}
|
|
||||||
sx={{
|
|
||||||
width: '13em',
|
|
||||||
height: '3em',
|
|
||||||
borderRadius: '2.5vw',
|
|
||||||
fontFamily: 'Benzin-Bold',
|
|
||||||
fontSize: '0.9em',
|
|
||||||
background:
|
|
||||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
transition: 'all 0.3s ease',
|
|
||||||
'&:hover': {
|
|
||||||
background:
|
|
||||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
|
||||||
transform: 'scale(1.01)',
|
|
||||||
boxShadow: '0 4px 15px rgba(242, 113, 33, 0.4)',
|
|
||||||
},
|
|
||||||
'&:active': {
|
|
||||||
color: 'transparent',
|
|
||||||
},
|
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
|
||||||
userSelect: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography sx={{ fontSize: '1em', color: 'white' }}>Регистрация</Typography>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Кнопки управления окном */}
|
{/* Кнопки управления окном */}
|
||||||
<Button
|
<Button
|
||||||
@ -465,6 +442,165 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
<CloseRoundedIcon sx={{ color: 'white' }} />
|
<CloseRoundedIcon sx={{ color: 'white' }} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</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',
|
||||||
|
bgcolor: 'rgba(0,0,0,0.92)',
|
||||||
|
color: 'white',
|
||||||
|
boxShadow: '0 0 20px rgba(0,0,0,0.6)',
|
||||||
|
border: '1px solid rgba(255,77,77,0.35)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* ===== 1 строка: аватар + ник + валюта ===== */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'auto 1fr',
|
||||||
|
gap: '1.5vw',
|
||||||
|
alignItems: 'center',
|
||||||
|
px: '2vw',
|
||||||
|
py: '0.8vw',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeadAvatar
|
||||||
|
skinUrl={skinUrl}
|
||||||
|
size={40}
|
||||||
|
style={{ borderRadius: '3vw' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
fontSize: '2vw',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{username || 'Игрок'}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<CoinsDisplay
|
||||||
|
username={username}
|
||||||
|
size="medium"
|
||||||
|
autoUpdate={true}
|
||||||
|
showTooltip={true}
|
||||||
|
sx={{
|
||||||
|
border: 'none',
|
||||||
|
padding: '0vw',
|
||||||
|
}}
|
||||||
|
backgroundColor={'rgba(0, 0, 0, 0)'}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider sx={{ my: '0.4vw', borderColor: 'rgba(255,255,255,0.08)' }} />
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleAvatarMenuClose();
|
||||||
|
navigate('/profile');
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
fontSize: '1.5vw',
|
||||||
|
gap: '0.5vw',
|
||||||
|
py: '0.7vw',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'rgba(255,77,77,0.15)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PersonIcon sx={{ fontSize: '2vw' }}/> Профиль
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{/* ===== 2 строка: ежедневные задания ===== */}
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleAvatarMenuClose();
|
||||||
|
navigate('/dailyquests');
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
fontSize: '1.5vw',
|
||||||
|
gap: '0.5vw',
|
||||||
|
py: '0.7vw',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'rgba(255,77,77,0.15)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CalendarMonthIcon sx={{ fontSize: '2vw' }} /> Ежедневные задания
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{/* ===== 3 строка: ежедневная награда ===== */}
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleAvatarMenuClose();
|
||||||
|
navigate('/daily');
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
fontSize: '1.5vw',
|
||||||
|
gap: '0.5vw',
|
||||||
|
py: '0.7vw',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'rgba(255,77,77,0.15)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EmojiEventsIcon sx={{ fontSize: '2vw' }} /> Ежедневная награда
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<Divider sx={{ my: '0.4vw', borderColor: 'rgba(255,255,255,0.08)' }} />
|
||||||
|
{!isLoginPage && !isRegistrationPage && username && (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
logout();
|
||||||
|
setMenuOpen(false);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
width: '8vw',
|
||||||
|
height: '3vw',
|
||||||
|
borderRadius: '2.5vw',
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
fontSize: '1.2vw',
|
||||||
|
background:
|
||||||
|
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
transition: 'transform 0.3s ease',
|
||||||
|
'&:hover': {
|
||||||
|
background:
|
||||||
|
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
||||||
|
transform: 'scale(1.01)',
|
||||||
|
boxShadow: '0 4px 15px rgba(242, 113, 33, 0.4)',
|
||||||
|
},
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
||||||
|
m: '0 0 0 18vw'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Выйти
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ↓↓↓ дальше ты сам добавишь пункты ↓↓↓ */}
|
||||||
|
</Menu>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import CustomNotification from '../components/Notifications/CustomNotification';
|
|||||||
import type { NotificationPosition } from '../components/Notifications/CustomNotification';
|
import type { NotificationPosition } from '../components/Notifications/CustomNotification';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { playBuySound } from '../utils/sounds';
|
import { playBuySound } from '../utils/sounds';
|
||||||
|
import { translateServer } from '../utils/serverTranslator'
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -66,19 +67,6 @@ export default function Marketplace() {
|
|||||||
horizontal: 'center',
|
horizontal: 'center',
|
||||||
});
|
});
|
||||||
|
|
||||||
const translateServer = (server: Server) => {
|
|
||||||
switch (server.name) {
|
|
||||||
case 'Server minecraft.hub.popa-popa.ru':
|
|
||||||
return 'Хаб';
|
|
||||||
case 'Server survival.hub.popa-popa.ru':
|
|
||||||
return 'Выживание';
|
|
||||||
case 'Server minecraft.minigames.popa-popa.ru':
|
|
||||||
return 'Миниигры';
|
|
||||||
default:
|
|
||||||
return server.name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Функция для проверки онлайн-статуса игрока и определения сервера
|
// Функция для проверки онлайн-статуса игрока и определения сервера
|
||||||
const checkPlayerStatus = async () => {
|
const checkPlayerStatus = async () => {
|
||||||
if (!username) return;
|
if (!username) return;
|
||||||
|
|||||||
@ -62,14 +62,6 @@ export default function Profile() {
|
|||||||
horizontal: 'right',
|
horizontal: 'right',
|
||||||
});
|
});
|
||||||
|
|
||||||
const navigateDaily = () => {
|
|
||||||
navigate('/daily');
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateDailyQuests = () => {
|
|
||||||
navigate('/dailyquests');
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedConfig = localStorage.getItem('launcher_config');
|
const savedConfig = localStorage.getItem('launcher_config');
|
||||||
if (savedConfig) {
|
if (savedConfig) {
|
||||||
@ -501,38 +493,6 @@ export default function Profile() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<OnlinePlayersPanel currentUsername={username} />
|
<OnlinePlayersPanel currentUsername={username} />
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={navigateDaily}
|
|
||||||
sx={{
|
|
||||||
mt: 1,
|
|
||||||
transition: 'transform 0.3s ease',
|
|
||||||
background:
|
|
||||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
|
||||||
fontFamily: 'Benzin-Bold',
|
|
||||||
borderRadius: '2.5vw',
|
|
||||||
'&:hover': { transform: 'scale(1.03)' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Ежедневные награды
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
onClick={navigateDailyQuests}
|
|
||||||
sx={{
|
|
||||||
mt: 1,
|
|
||||||
transition: 'transform 0.3s ease',
|
|
||||||
background:
|
|
||||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
|
||||||
fontFamily: 'Benzin-Bold',
|
|
||||||
borderRadius: '2.5vw',
|
|
||||||
'&:hover': { transform: 'scale(1.03)' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Ежедневные квесты
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,21 +1,15 @@
|
|||||||
// src/renderer/utils/serverTranslator.ts
|
// src/renderer/utils/serverTranslator.ts
|
||||||
import { Server } from '../api';
|
import { Server } from '../api';
|
||||||
|
|
||||||
type ServerLike = Pick<Server, 'name'> | { name: string };
|
export function translateServer(server: Server): string {
|
||||||
|
|
||||||
export const translateServer = (
|
|
||||||
server: ServerLike | null | undefined,
|
|
||||||
): string => {
|
|
||||||
if (!server?.name) return '';
|
|
||||||
|
|
||||||
switch (server.name) {
|
switch (server.name) {
|
||||||
case 'Server minecraft.hub.popa-popa.ru':
|
case 'Server minecraft.hub.popa-popa.ru':
|
||||||
return 'Хаб';
|
return 'Хаб';
|
||||||
case 'Server survival.hub.popa-popa.ru':
|
case 'Server minecraft.survival.popa-popa.ru':
|
||||||
return 'Выживание';
|
return 'Выживание';
|
||||||
case 'Server minecraft.minigames.popa-popa.ru':
|
case 'Server minecraft.minigames.popa-popa.ru':
|
||||||
return 'Миниигры';
|
return 'Миниигры';
|
||||||
default:
|
default:
|
||||||
return server.name;
|
return server.name;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user