rework circulars loaders

This commit is contained in:
2025-12-05 01:29:06 +05:00
parent fc5e65f189
commit 215e3d6d39
11 changed files with 130 additions and 93 deletions

View File

@ -18,6 +18,7 @@ import Profile from './pages/Profile';
import Shop from './pages/Shop'; import Shop from './pages/Shop';
import Marketplace from './pages/Marketplace'; import Marketplace from './pages/Marketplace';
import { Registration } from './pages/Registration'; import { Registration } from './pages/Registration';
import { FullScreenLoader } from './components/FullScreenLoader';
const AuthCheck = ({ children }: { children: ReactNode }) => { const AuthCheck = ({ children }: { children: ReactNode }) => {
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null); const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
@ -86,7 +87,7 @@ const AuthCheck = ({ children }: { children: ReactNode }) => {
}; };
if (isAuthenticated === null) { if (isAuthenticated === null) {
return <div>Loading...</div>; return <FullScreenLoader message="Загрузка..." />;
} }
return isAuthenticated ? children : <Navigate to="/login" replace />; return isAuthenticated ? children : <Navigate to="/login" replace />;
@ -127,7 +128,10 @@ const App = () => {
<TopBar onRegister={handleRegister} username={username || ''} /> <TopBar onRegister={handleRegister} username={username || ''} />
<Notifier /> <Notifier />
<Routes> <Routes>
<Route path="/login" element={<Login />} /> <Route
path="/login"
element={<Login onLoginSuccess={setUsername} />}
/>
<Route path="/registration" element={<Registration />} /> <Route path="/registration" element={<Registration />} />
<Route <Route
path="/" path="/"

View File

@ -8,12 +8,12 @@ import {
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
Collapse, Collapse,
CircularProgress,
} from '@mui/material'; } from '@mui/material';
import FolderIcon from '@mui/icons-material/Folder'; import FolderIcon from '@mui/icons-material/Folder';
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { FullScreenLoader } from '../components/FullScreenLoader';
interface FileNode { interface FileNode {
name: string; name: string;
@ -190,7 +190,7 @@ export default function FilesSelector({
}; };
if (loading) { if (loading) {
return <CircularProgress />; return <FullScreenLoader fullScreen={false} />;
} }
if (error) { if (error) {

View File

@ -1,21 +1,39 @@
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
export const FullScreenLoader = ({ message }: { message?: string }) => ( interface FullScreenLoaderProps {
<Box message?: string;
sx={{ fullScreen?: boolean; // <-- новый проп
position: 'fixed', }
top: '50%',
left: '50%', export const FullScreenLoader = ({
transform: 'translate(-50%, -50%)', message,
fullScreen = true,
}: FullScreenLoaderProps) => {
const containerSx = fullScreen
? {
position: 'fixed' as const,
inset: 0,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column' as const,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center',
gap: 3, gap: 3,
zIndex: 9999, zIndex: 9999,
pointerEvents: 'none', // чтобы не блокировал клики, если нужно pointerEvents: 'none' as const,
}} }
> : {
display: 'flex',
flexDirection: 'column' as const,
alignItems: 'center',
justifyContent: 'center',
gap: 3,
width: '100%',
height: '100%',
};
return (
<Box sx={containerSx}>
{/* Градиентное вращающееся кольцо */} {/* Градиентное вращающееся кольцо */}
<Box <Box
sx={{ sx={{
@ -24,16 +42,10 @@ export const FullScreenLoader = ({ message }: { message?: string }) => (
borderRadius: '50%', borderRadius: '50%',
position: 'relative', position: 'relative',
overflow: 'hidden', overflow: 'hidden',
// сам градиент, который будет крутиться
background: 'conic-gradient(#F27121, #E940CD, #8A2387, #F27121)', background: 'conic-gradient(#F27121, #E940CD, #8A2387, #F27121)',
animation: 'spin 1s linear infinite', animation: 'spin 1s linear infinite',
// создаём отверстие внутри маской
WebkitMask: 'radial-gradient(circle, transparent 55%, black 56%)', WebkitMask: 'radial-gradient(circle, transparent 55%, black 56%)',
mask: 'radial-gradient(circle, transparent 55%, black 56%)', mask: 'radial-gradient(circle, transparent 55%, black 56%)',
'@keyframes spin': { '@keyframes spin': {
'0%': { transform: 'rotate(0deg)' }, '0%': { transform: 'rotate(0deg)' },
'100%': { transform: 'rotate(360deg)' }, '100%': { transform: 'rotate(360deg)' },
@ -57,3 +69,4 @@ export const FullScreenLoader = ({ message }: { message?: string }) => (
)} )}
</Box> </Box>
); );
};

View File

@ -8,7 +8,6 @@ import {
CardMedia, CardMedia,
CardContent, CardContent,
Button, Button,
CircularProgress,
Dialog, Dialog,
DialogTitle, DialogTitle,
DialogContent, DialogContent,
@ -22,6 +21,7 @@ import {
sellItem, sellItem,
PlayerInventoryItem, PlayerInventoryItem,
} from '../api'; } from '../api';
import { FullScreenLoader } from './FullScreenLoader';
interface PlayerInventoryProps { interface PlayerInventoryProps {
username: string; username: string;
@ -223,9 +223,7 @@ export default function PlayerInventory({
)} )}
{loading ? ( {loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', my: 4 }}> <FullScreenLoader fullScreen={false} />
<CircularProgress />
</Box>
) : ( ) : (
<> <>
{inventoryItems.length === 0 ? ( {inventoryItems.length === 0 ? (
@ -266,7 +264,13 @@ export default function PlayerInventory({
alt={item.material} alt={item.material}
/> />
<CardContent sx={{ p: 1 }}> <CardContent sx={{ p: 1 }}>
<Box sx={{ display: 'flex', gap: '1vw', justifyContent: 'space-between' }}> <Box
sx={{
display: 'flex',
gap: '1vw',
justifyContent: 'space-between',
}}
>
<Typography variant="body2" color="white" noWrap> <Typography variant="body2" color="white" noWrap>
{getItemDisplayName(item.material)} {getItemDisplayName(item.material)}
</Typography> </Typography>
@ -363,7 +367,7 @@ export default function PlayerInventory({
color="primary" color="primary"
disabled={sellLoading} disabled={sellLoading}
> >
{sellLoading ? <CircularProgress size={24} /> : 'Продать'} {sellLoading ? <FullScreenLoader fullScreen={false} /> : 'Продать'}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -1,4 +1,4 @@
import { Box, Typography, CircularProgress, Avatar } from '@mui/material'; import { Box, Typography, Avatar } from '@mui/material';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
interface ServerStatusProps { interface ServerStatusProps {

View File

@ -8,7 +8,11 @@ import useConfig from '../hooks/useConfig';
import { useState } from 'react'; import { useState } from 'react';
import { FullScreenLoader } from '../components/FullScreenLoader'; import { FullScreenLoader } from '../components/FullScreenLoader';
const Login = () => { interface LoginProps {
onLoginSuccess?: (username: string) => void;
}
const Login = ({ onLoginSuccess }: LoginProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { config, setConfig, saveConfig, handleInputChange } = useConfig(); const { config, setConfig, saveConfig, handleInputChange } = useConfig();
const { status, validateSession, refreshSession, authenticateWithElyBy } = const { status, validateSession, refreshSession, authenticateWithElyBy } =
@ -88,6 +92,11 @@ const Login = () => {
} }
console.log('Авторизация успешно завершена'); console.log('Авторизация успешно завершена');
if (onLoginSuccess) {
onLoginSuccess(config.username);
}
navigate('/'); navigate('/');
} catch (error: any) { } catch (error: any) {
console.log(`ОШИБКА при авторизации: ${error.message}`); console.log(`ОШИБКА при авторизации: ${error.message}`);

View File

@ -3,7 +3,6 @@ import { useEffect, useState } from 'react';
import { import {
Box, Box,
Typography, Typography,
CircularProgress,
Button, Button,
Grid, Grid,
Card, Card,
@ -18,6 +17,7 @@ import {
import { isPlayerOnline, getPlayerServer } from '../utils/playerOnlineCheck'; import { isPlayerOnline, getPlayerServer } from '../utils/playerOnlineCheck';
import { buyItem, fetchMarketplace, MarketplaceResponse, Server } from '../api'; import { buyItem, fetchMarketplace, MarketplaceResponse, Server } from '../api';
import PlayerInventory from '../components/PlayerInventory'; import PlayerInventory from '../components/PlayerInventory';
import { FullScreenLoader } from '../components/FullScreenLoader';
interface TabPanelProps { interface TabPanelProps {
children?: React.ReactNode; children?: React.ReactNode;
@ -195,14 +195,14 @@ export default function Marketplace() {
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
height: '100%', height: '20vh',
gap: 2, gap: 2,
}} }}
> >
<CircularProgress size={60} /> <FullScreenLoader
<Typography variant="h6" color="white"> fullScreen={true}
Проверяем, находитесь ли вы на сервере... message="Проверяем, находитесь ли вы на сервере..."
</Typography> />
</Box> </Box>
); );
} }
@ -339,7 +339,7 @@ export default function Marketplace() {
<TabPanel value={tabValue} index={0}> <TabPanel value={tabValue} index={0}>
{marketLoading ? ( {marketLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: '50vw' }}> <Box sx={{ display: 'flex', justifyContent: 'center', mt: '50vw' }}>
<CircularProgress /> <FullScreenLoader fullScreen={false} />
</Box> </Box>
) : !marketItems || marketItems.items.length === 0 ? ( ) : !marketItems || marketItems.items.length === 0 ? (
<Box sx={{ mt: 4, textAlign: 'center' }}> <Box sx={{ mt: 4, textAlign: 'center' }}>

View File

@ -19,10 +19,10 @@ import {
Select, Select,
MenuItem, MenuItem,
Alert, Alert,
CircularProgress,
} from '@mui/material'; } from '@mui/material';
import CapeCard from '../components/CapeCard'; import CapeCard from '../components/CapeCard';
import { FullScreenLoader } from '../components/FullScreenLoader';
export default function Profile() { export default function Profile() {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
@ -195,7 +195,7 @@ export default function Profile() {
}} }}
> >
{loading ? ( {loading ? (
<CircularProgress /> <FullScreenLoader message="Загрузка вашего профиля" />
) : ( ) : (
<> <>
<Paper <Paper
@ -406,12 +406,12 @@ export default function Profile() {
disabled={uploadStatus === 'loading' || !skinFile} disabled={uploadStatus === 'loading' || !skinFile}
startIcon={ startIcon={
uploadStatus === 'loading' ? ( uploadStatus === 'loading' ? (
<CircularProgress size={20} color="inherit" /> <FullScreenLoader fullScreen={false} />
) : null ) : null
} }
> >
{uploadStatus === 'loading' ? ( {uploadStatus === 'loading' ? (
<Typography sx={{ color: 'white' }}>Загрузка...</Typography> <FullScreenLoader message="Загрузка..." />
) : ( ) : (
<Typography sx={{ color: 'white' }}> <Typography sx={{ color: 'white' }}>
Установить скин Установить скин

View File

@ -12,7 +12,6 @@ import {
TextField, TextField,
Button, Button,
Snackbar, Snackbar,
CircularProgress,
} from '@mui/material'; } from '@mui/material';
import LoginRoundedIcon from '@mui/icons-material/LoginRounded'; import LoginRoundedIcon from '@mui/icons-material/LoginRounded';
import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded'; import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded';
@ -25,6 +24,7 @@ import {
import QRCodeStyling from 'qr-code-styling'; import QRCodeStyling from 'qr-code-styling';
import popalogo from '../../../assets/icons/popa-popa.svg'; import popalogo from '../../../assets/icons/popa-popa.svg';
import GradientTextField from '../components/GradientTextField'; import GradientTextField from '../components/GradientTextField';
import { FullScreenLoader } from '../components/FullScreenLoader';
const ColorlibConnector = styled(StepConnector)(({ theme }) => ({ const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
[`&.${stepConnectorClasses.alternativeLabel}`]: { [`&.${stepConnectorClasses.alternativeLabel}`]: {
@ -34,7 +34,7 @@ const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
[`& .${stepConnectorClasses.line}`]: { [`& .${stepConnectorClasses.line}`]: {
backgroundImage: backgroundImage:
//'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)', //'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)',
'linear-gradient( 95deg,rgb(150,150,150) 0%, rgb(242,113,33) 80%,rgb(233,64,87) 110%,rgb(138,35,135) 150%)' 'linear-gradient( 95deg,rgb(150,150,150) 0%, rgb(242,113,33) 80%,rgb(233,64,87) 110%,rgb(138,35,135) 150%)',
}, },
}, },
[`&.${stepConnectorClasses.completed}`]: { [`&.${stepConnectorClasses.completed}`]: {
@ -46,7 +46,8 @@ const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
[`& .${stepConnectorClasses.line}`]: { [`& .${stepConnectorClasses.line}`]: {
height: 3, height: 3,
border: 0, border: 0,
backgroundImage: 'linear-gradient( 275deg,rgb(150,150,150) 0%, rgb(242,113,33) 80%,rgb(233,64,87) 110%,rgb(138,35,135) 150%)', backgroundImage:
'linear-gradient( 275deg,rgb(150,150,150) 0%, rgb(242,113,33) 80%,rgb(233,64,87) 110%,rgb(138,35,135) 150%)',
borderRadius: 1, borderRadius: 1,
transition: 'background-image 1s ease, background-color 1s ease', transition: 'background-image 1s ease, background-color 1s ease',
...theme.applyStyles('dark', { ...theme.applyStyles('dark', {
@ -84,8 +85,7 @@ const ColorlibStepIconRoot = styled('div')<{
{ {
props: ({ ownerState }) => ownerState.completed, props: ({ ownerState }) => ownerState.completed,
style: { style: {
backgroundImage: backgroundImage: '#adadad',
'#adadad',
}, },
}, },
], ],
@ -311,7 +311,15 @@ export const Registration = () => {
))} ))}
</Stepper> </Stepper>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: '50vw', mt: activeStep === 1 ? '20%' : '0%' }}> <Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: '1vh',
width: '50vw',
mt: activeStep === 1 ? '20%' : '0%',
}}
>
{activeStep === 0 && ( {activeStep === 0 && (
<Box <Box
sx={{ sx={{
@ -357,13 +365,13 @@ export const Registration = () => {
transition: 'transform 0.3s ease', transition: 'transform 0.3s ease',
width: '60%', width: '60%',
mt: 2, mt: 2,
background: 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)', background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
fontFamily: 'Benzin-Bold', fontFamily: 'Benzin-Bold',
borderRadius: '2.5vw', borderRadius: '2.5vw',
fontSize: '2vw', fontSize: '2vw',
'&:hover': { '&:hover': {
transform: 'scale(1.1)', transform: 'scale(1.1)',
}, },
}} }}
onClick={handleCreateAccount} onClick={handleCreateAccount}
@ -389,13 +397,13 @@ export const Registration = () => {
transition: 'transform 0.3s ease', transition: 'transform 0.3s ease',
width: '60%', width: '60%',
mt: 2, mt: 2,
background: 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)', background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
fontFamily: 'Benzin-Bold', fontFamily: 'Benzin-Bold',
borderRadius: '2.5vw', borderRadius: '2.5vw',
fontSize: '2vw', fontSize: '2vw',
'&:hover': { '&:hover': {
transform: 'scale(1.1)', transform: 'scale(1.1)',
}, },
}} }}
onClick={handleOpenBot} onClick={handleOpenBot}
@ -425,10 +433,10 @@ export const Registration = () => {
{verificationCode} {verificationCode}
</Typography> </Typography>
<Typography variant="body1">Ждем ответа от бота</Typography> <Typography variant="body1">Ждем ответа от бота</Typography>
<CircularProgress /> <FullScreenLoader fullScreen={false} />
</> </>
) : ( ) : (
<CircularProgress /> <FullScreenLoader fullScreen={false} />
)} )}
</Box> </Box>
)} )}

View File

@ -9,6 +9,7 @@ import {
StoreCape, StoreCape,
} from '../api'; } from '../api';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FullScreenLoader } from '../components/FullScreenLoader';
export default function Shop() { export default function Shop() {
const [storeCapes, setStoreCapes] = useState<StoreCape[]>([]); const [storeCapes, setStoreCapes] = useState<StoreCape[]>([]);
@ -88,7 +89,7 @@ export default function Shop() {
}} }}
> >
{loading ? ( {loading ? (
<Typography>Загрузка...</Typography> <FullScreenLoader message="Загрузка..." />
) : ( ) : (
<Box <Box
sx={{ sx={{

View File

@ -8,7 +8,6 @@ import {
CardContent, CardContent,
CardActions, CardActions,
Button, Button,
CircularProgress,
Modal, Modal,
List, List,
ListItem, ListItem,
@ -18,6 +17,7 @@ import {
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import DownloadIcon from '@mui/icons-material/Download'; import DownloadIcon from '@mui/icons-material/Download';
import { FullScreenLoader } from '../components/FullScreenLoader';
interface VersionCardProps { interface VersionCardProps {
id: string; id: string;
@ -308,9 +308,7 @@ export const VersionsExplorer = () => {
/> />
{loading ? ( {loading ? (
<Box display="flex" justifyContent="center" my={5}> <FullScreenLoader message="Загрузка ваших версий..." />
<CircularProgress />
</Box>
) : ( ) : (
<Grid <Grid
container container