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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,6 @@ import {
TextField,
Button,
Snackbar,
CircularProgress,
} from '@mui/material';
import LoginRoundedIcon from '@mui/icons-material/LoginRounded';
import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded';
@ -25,6 +24,7 @@ import {
import QRCodeStyling from 'qr-code-styling';
import popalogo from '../../../assets/icons/popa-popa.svg';
import GradientTextField from '../components/GradientTextField';
import { FullScreenLoader } from '../components/FullScreenLoader';
const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
[`&.${stepConnectorClasses.alternativeLabel}`]: {
@ -34,7 +34,7 @@ const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
[`& .${stepConnectorClasses.line}`]: {
backgroundImage:
//'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}`]: {
@ -46,7 +46,8 @@ const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
[`& .${stepConnectorClasses.line}`]: {
height: 3,
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,
transition: 'background-image 1s ease, background-color 1s ease',
...theme.applyStyles('dark', {
@ -84,8 +85,7 @@ const ColorlibStepIconRoot = styled('div')<{
{
props: ({ ownerState }) => ownerState.completed,
style: {
backgroundImage:
'#adadad',
backgroundImage: '#adadad',
},
},
],
@ -311,7 +311,15 @@ export const Registration = () => {
))}
</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 && (
<Box
sx={{
@ -357,13 +365,13 @@ export const Registration = () => {
transition: 'transform 0.3s ease',
width: '60%',
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',
borderRadius: '2.5vw',
fontSize: '2vw',
'&:hover': {
transform: 'scale(1.1)',
},
}}
onClick={handleCreateAccount}
@ -389,13 +397,13 @@ export const Registration = () => {
transition: 'transform 0.3s ease',
width: '60%',
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',
borderRadius: '2.5vw',
fontSize: '2vw',
'&:hover': {
transform: 'scale(1.1)',
},
}}
onClick={handleOpenBot}
@ -425,10 +433,10 @@ export const Registration = () => {
{verificationCode}
</Typography>
<Typography variant="body1">Ждем ответа от бота</Typography>
<CircularProgress />
<FullScreenLoader fullScreen={false} />
</>
) : (
<CircularProgress />
<FullScreenLoader fullScreen={false} />
)}
</Box>
)}

View File

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

View File

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