add secret page
This commit is contained in:
BIN
assets/images/fake-payment.png
Normal file
BIN
assets/images/fake-payment.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@ -26,6 +26,7 @@ import DailyReward from './pages/DailyReward';
|
|||||||
import DailyQuests from './pages/DailyQuests';
|
import DailyQuests from './pages/DailyQuests';
|
||||||
import Settings from './pages/Settings';
|
import Settings from './pages/Settings';
|
||||||
import Inventory from './pages/Inventory';
|
import Inventory from './pages/Inventory';
|
||||||
|
import FakePaymentPage from './pages/FakePaymentPage';
|
||||||
import { TrayBridge } from './utils/TrayBridge';
|
import { TrayBridge } from './utils/TrayBridge';
|
||||||
import { API_BASE_URL } from './api';
|
import { API_BASE_URL } from './api';
|
||||||
|
|
||||||
@ -398,6 +399,14 @@ const AppLayout = () => {
|
|||||||
</AuthCheck>
|
</AuthCheck>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/fakepaymentpage"
|
||||||
|
element={
|
||||||
|
<AuthCheck>
|
||||||
|
<FakePaymentPage />
|
||||||
|
</AuthCheck>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -21,6 +21,9 @@ interface CoinsDisplayProps {
|
|||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
textColor?: string;
|
textColor?: string;
|
||||||
|
|
||||||
|
onClick?: () => void;
|
||||||
|
disableRefreshOnClick?: boolean;
|
||||||
|
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +43,9 @@ export default function CoinsDisplay({
|
|||||||
backgroundColor = 'rgba(0, 0, 0, 0.2)',
|
backgroundColor = 'rgba(0, 0, 0, 0.2)',
|
||||||
textColor = 'white',
|
textColor = 'white',
|
||||||
|
|
||||||
|
onClick,
|
||||||
|
disableRefreshOnClick = false,
|
||||||
|
|
||||||
sx,
|
sx,
|
||||||
}: CoinsDisplayProps) {
|
}: CoinsDisplayProps) {
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
@ -61,6 +67,14 @@ export default function CoinsDisplay({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
// 1) если передали внешний обработчик — выполняем его
|
||||||
|
if (onClick) onClick();
|
||||||
|
|
||||||
|
// 2) опционально оставляем обновление баланса по клику
|
||||||
|
if (!disableRefreshOnClick && username) fetchCoinsData();
|
||||||
|
};
|
||||||
|
|
||||||
const [coins, setCoins] = useState<number>(() => {
|
const [coins, setCoins] = useState<number>(() => {
|
||||||
// 1) если пришло значение извне — оно приоритетнее
|
// 1) если пришло значение извне — оно приоритетнее
|
||||||
if (externalValue !== undefined) return externalValue;
|
if (externalValue !== undefined) return externalValue;
|
||||||
@ -76,7 +90,8 @@ export default function CoinsDisplay({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = () => setSettingsVersion((v) => v + 1);
|
const handler = () => setSettingsVersion((v) => v + 1);
|
||||||
window.addEventListener('settings-updated', handler as EventListener);
|
window.addEventListener('settings-updated', handler as EventListener);
|
||||||
return () => window.removeEventListener('settings-updated', handler as EventListener);
|
return () =>
|
||||||
|
window.removeEventListener('settings-updated', handler as EventListener);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const isTooltipDisabledBySettings = useMemo(() => {
|
const isTooltipDisabledBySettings = useMemo(() => {
|
||||||
@ -191,7 +206,7 @@ export default function CoinsDisplay({
|
|||||||
borderRadius: sizes.borderRadius,
|
borderRadius: sizes.borderRadius,
|
||||||
padding: sizes.containerPadding,
|
padding: sizes.containerPadding,
|
||||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||||
cursor: tooltipEnabled ? 'help' : 'default',
|
cursor: onClick ? 'pointer' : tooltipEnabled ? 'help' : 'default',
|
||||||
|
|
||||||
// можно оставить лёгкий намёк на загрузку, но без "пульса" текста
|
// можно оставить лёгкий намёк на загрузку, но без "пульса" текста
|
||||||
opacity: isLoading ? 0.85 : 1,
|
opacity: isLoading ? 0.85 : 1,
|
||||||
@ -199,7 +214,7 @@ export default function CoinsDisplay({
|
|||||||
|
|
||||||
...sx,
|
...sx,
|
||||||
}}
|
}}
|
||||||
onClick={username ? handleRefresh : undefined}
|
onClick={handleClick}
|
||||||
title={username ? 'Нажмите для обновления' : undefined}
|
title={username ? 'Нажмите для обновления' : undefined}
|
||||||
>
|
>
|
||||||
{showIcon && (
|
{showIcon && (
|
||||||
|
|||||||
@ -34,6 +34,7 @@ export default function PageHeader() {
|
|||||||
path === '/marketplace' ||
|
path === '/marketplace' ||
|
||||||
path === '/profile' ||
|
path === '/profile' ||
|
||||||
path === '/inventory' ||
|
path === '/inventory' ||
|
||||||
|
path === '/fakepaymentpage' ||
|
||||||
path.startsWith('/launch')
|
path.startsWith('/launch')
|
||||||
) {
|
) {
|
||||||
return { title: '', subtitle: '', hidden: true };
|
return { title: '', subtitle: '', hidden: true };
|
||||||
@ -43,7 +44,7 @@ export default function PageHeader() {
|
|||||||
return {
|
return {
|
||||||
title: 'Настройки',
|
title: 'Настройки',
|
||||||
subtitle: 'Персонализация интерфейса и поведения лаунчера',
|
subtitle: 'Персонализация интерфейса и поведения лаунчера',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === '/news') {
|
if (path === '/news') {
|
||||||
@ -63,7 +64,8 @@ export default function PageHeader() {
|
|||||||
if (path.startsWith('/daily')) {
|
if (path.startsWith('/daily')) {
|
||||||
return {
|
return {
|
||||||
title: 'Ежедневные награды',
|
title: 'Ежедневные награды',
|
||||||
subtitle: 'Ежедневный вход на сервер приносит бонусы и полезные награды!',
|
subtitle:
|
||||||
|
'Ежедневный вход на сервер приносит бонусы и полезные награды!',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -262,12 +262,13 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('settings-updated', handler as EventListener);
|
window.addEventListener('settings-updated', handler as EventListener);
|
||||||
return () => window.removeEventListener('settings-updated', handler as EventListener);
|
return () =>
|
||||||
|
window.removeEventListener('settings-updated', handler as EventListener);
|
||||||
}, [updateGradientVars]);
|
}, [updateGradientVars]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
className={isAuthPage ? undefined : 'glass-ui'}
|
className={isAuthPage ? undefined : 'glass-ui'}
|
||||||
sx={[
|
sx={[
|
||||||
{
|
{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -454,6 +455,8 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
size="medium"
|
size="medium"
|
||||||
autoUpdate={true}
|
autoUpdate={true}
|
||||||
showTooltip={true}
|
showTooltip={true}
|
||||||
|
onClick={() => navigate('/fakepaymentpage')}
|
||||||
|
disableRefreshOnClick={true} // чтобы клик не дёргал fetchCoins
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
518
src/renderer/pages/FakePaymentPage.tsx
Normal file
518
src/renderer/pages/FakePaymentPage.tsx
Normal file
@ -0,0 +1,518 @@
|
|||||||
|
// pages/TopUpPage.tsx
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
ToggleButton,
|
||||||
|
ToggleButtonGroup,
|
||||||
|
CircularProgress,
|
||||||
|
LinearProgress,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import fakePaymentImg from '../../../assets/images/fake-payment.png';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
type PayMethod = 'sbp' | 'card' | 'crypto' | 'other';
|
||||||
|
type Stage = 'form' | 'processing' | 'done';
|
||||||
|
|
||||||
|
const STEPS: string[] = [
|
||||||
|
'Создаём счёт…',
|
||||||
|
'Проверяем данные…',
|
||||||
|
'Подключаем платёжный шлюз…',
|
||||||
|
'Ожидаем подтверждение…',
|
||||||
|
'Подписываем запрос…',
|
||||||
|
'Проверяем лимиты…',
|
||||||
|
'Синхронизируем баланс…',
|
||||||
|
'Завершаем операцию…',
|
||||||
|
'Почти готово…',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ===== Styles “как Registration” =====
|
||||||
|
const GRADIENT =
|
||||||
|
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)';
|
||||||
|
|
||||||
|
const GlassPaper = styled(Paper)(() => ({
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderRadius: 28,
|
||||||
|
background: 'rgba(0,0,0,0.35)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
backdropFilter: 'blur(14px)',
|
||||||
|
boxShadow: '0 20px 60px rgba(0,0,0,0.45)',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Glow = styled('div')(() => ({
|
||||||
|
position: 'absolute',
|
||||||
|
inset: -2,
|
||||||
|
background:
|
||||||
|
'radial-gradient(800px 300px at 20% 10%, rgba(242,113,33,0.22), transparent 60%),' +
|
||||||
|
'radial-gradient(800px 300px at 80% 0%, rgba(233,64,205,0.18), transparent 55%),' +
|
||||||
|
'radial-gradient(900px 420px at 50% 110%, rgba(138,35,135,0.20), transparent 60%)',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const GradientTitle = styled(Typography)(() => ({
|
||||||
|
fontWeight: 900,
|
||||||
|
backgroundImage:
|
||||||
|
'linear-gradient(136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
fontFamily: 'Benzin-Bold, sans-serif',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const GradientButton = styled(Button)(() => ({
|
||||||
|
background: GRADIENT,
|
||||||
|
fontFamily: 'Benzin-Bold, sans-serif',
|
||||||
|
borderRadius: 999,
|
||||||
|
textTransform: 'none',
|
||||||
|
transition: 'transform 0.25s ease, filter 0.25s ease, box-shadow 0.25s ease',
|
||||||
|
boxShadow: '0 12px 30px rgba(0,0,0,0.35)',
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'scale(1.04)',
|
||||||
|
filter: 'brightness(1.06)',
|
||||||
|
boxShadow: '0 16px 42px rgba(0,0,0,0.48)',
|
||||||
|
background: GRADIENT,
|
||||||
|
},
|
||||||
|
'&:disabled': {
|
||||||
|
background: 'rgba(255,255,255,0.08)',
|
||||||
|
color: 'rgba(255,255,255,0.35)',
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(() => ({
|
||||||
|
borderRadius: 999,
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
'& .MuiToggleButton-root': {
|
||||||
|
border: 'none',
|
||||||
|
color: 'rgba(255,255,255,0.75)',
|
||||||
|
fontFamily: 'Benzin-Bold, sans-serif',
|
||||||
|
letterSpacing: '0.02em',
|
||||||
|
paddingTop: 10,
|
||||||
|
paddingBottom: 10,
|
||||||
|
transition: 'transform 0.2s ease, background 0.2s ease, color 0.2s ease',
|
||||||
|
},
|
||||||
|
'& .MuiToggleButton-root:hover': {
|
||||||
|
background: 'rgba(255,255,255,0.08)',
|
||||||
|
transform: 'scale(1.02)',
|
||||||
|
},
|
||||||
|
'& .MuiToggleButton-root.Mui-selected': {
|
||||||
|
color: '#fff',
|
||||||
|
background: GRADIENT,
|
||||||
|
},
|
||||||
|
'& .MuiToggleButton-root.Mui-selected:hover': {
|
||||||
|
background: GRADIENT,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTextField = styled(TextField)(() => ({
|
||||||
|
'& .MuiInputLabel-root': {
|
||||||
|
color: 'rgba(255,255,255,0.65)',
|
||||||
|
},
|
||||||
|
'& .MuiInputLabel-root.Mui-focused': {
|
||||||
|
color: 'rgba(255,255,255,0.9)',
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
borderRadius: 20,
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
color: '#fff',
|
||||||
|
fontFamily: 'Benzin-Bold, sans-serif',
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: 'rgba(255,255,255,0.10)',
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: 'rgba(255,255,255,0.18)',
|
||||||
|
},
|
||||||
|
'& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: 'rgba(233,64,205,0.55)',
|
||||||
|
boxShadow: '0 0 0 6px rgba(233,64,205,0.12)',
|
||||||
|
},
|
||||||
|
'& input': {
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function TopUpPage() {
|
||||||
|
const [coins, setCoins] = useState<number>(100);
|
||||||
|
const [method, setMethod] = useState<PayMethod>('sbp');
|
||||||
|
const [stage, setStage] = useState<Stage>('form');
|
||||||
|
|
||||||
|
const [stepText, setStepText] = useState<string>('Обработка платежа…');
|
||||||
|
const [progress, setProgress] = useState<number>(0);
|
||||||
|
|
||||||
|
const doneTimerRef = useRef<number | null>(null);
|
||||||
|
const stepIntervalRef = useRef<number | null>(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const rubles = useMemo(() => {
|
||||||
|
const safe = Number.isFinite(coins) ? coins : 0;
|
||||||
|
return Math.max(0, Math.floor(safe));
|
||||||
|
}, [coins]);
|
||||||
|
|
||||||
|
const methodLabel = useMemo(() => {
|
||||||
|
switch (method) {
|
||||||
|
case 'sbp':
|
||||||
|
return 'СБП';
|
||||||
|
case 'card':
|
||||||
|
return 'Карта';
|
||||||
|
case 'crypto':
|
||||||
|
return 'Crypto';
|
||||||
|
default:
|
||||||
|
return 'Другое';
|
||||||
|
}
|
||||||
|
}, [method]);
|
||||||
|
|
||||||
|
const clearTimers = () => {
|
||||||
|
if (doneTimerRef.current !== null) {
|
||||||
|
window.clearTimeout(doneTimerRef.current);
|
||||||
|
doneTimerRef.current = null;
|
||||||
|
}
|
||||||
|
if (stepIntervalRef.current !== null) {
|
||||||
|
window.clearInterval(stepIntervalRef.current);
|
||||||
|
stepIntervalRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startProcessing = () => {
|
||||||
|
clearTimers();
|
||||||
|
setStage('processing');
|
||||||
|
setProgress(0);
|
||||||
|
|
||||||
|
const used = new Set<number>();
|
||||||
|
const pickStep = () => {
|
||||||
|
if (used.size >= STEPS.length) used.clear();
|
||||||
|
let idx = Math.floor(Math.random() * STEPS.length);
|
||||||
|
while (used.has(idx)) idx = Math.floor(Math.random() * STEPS.length);
|
||||||
|
used.add(idx);
|
||||||
|
return STEPS[idx];
|
||||||
|
};
|
||||||
|
|
||||||
|
setStepText(pickStep());
|
||||||
|
|
||||||
|
const totalMs = 1600 + Math.floor(Math.random() * 1600); // 1.6–3.2
|
||||||
|
const stepsCount = 3 + Math.floor(Math.random() * 4); // 3–6
|
||||||
|
|
||||||
|
let ticks = 0;
|
||||||
|
stepIntervalRef.current = window.setInterval(
|
||||||
|
() => {
|
||||||
|
ticks += 1;
|
||||||
|
|
||||||
|
setStepText(pickStep());
|
||||||
|
setProgress((p) => {
|
||||||
|
const bump = 8 + Math.floor(Math.random() * 18); // 8..25
|
||||||
|
return Math.min(95, p + bump);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ticks >= stepsCount && stepIntervalRef.current !== null) {
|
||||||
|
window.clearInterval(stepIntervalRef.current);
|
||||||
|
stepIntervalRef.current = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
400 + Math.floor(Math.random() * 500),
|
||||||
|
); // 400..900
|
||||||
|
|
||||||
|
doneTimerRef.current = window.setTimeout(() => {
|
||||||
|
setProgress(100);
|
||||||
|
setStepText('Готово!');
|
||||||
|
window.setTimeout(() => setStage('done'), 250);
|
||||||
|
doneTimerRef.current = null;
|
||||||
|
}, totalMs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePay = () => {
|
||||||
|
if (rubles <= 0) return;
|
||||||
|
startProcessing();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => clearTimers();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ===== Layout wrapper =====
|
||||||
|
const PageCenter = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: 'calc(100vh - 8vh)',
|
||||||
|
pt: '8vh',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
px: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
// ===== DONE =====
|
||||||
|
if (stage === 'done') {
|
||||||
|
return (
|
||||||
|
<PageCenter>
|
||||||
|
<GlassPaper sx={{ width: 'min(680px, 92vw)', p: 3 }}>
|
||||||
|
<Glow />
|
||||||
|
<Stack
|
||||||
|
spacing={2.2}
|
||||||
|
alignItems="center"
|
||||||
|
sx={{ position: 'relative' }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
src={fakePaymentImg}
|
||||||
|
alt="payment"
|
||||||
|
sx={{
|
||||||
|
width: 'min(440px, 82vw)',
|
||||||
|
height: 'auto',
|
||||||
|
borderRadius: '24px',
|
||||||
|
boxShadow: '0 20px 60px rgba(0,0,0,0.55)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GradientTitle variant="h5" sx={{ textAlign: 'center' }}>
|
||||||
|
Че реально думал донат добавили?
|
||||||
|
</GradientTitle>
|
||||||
|
|
||||||
|
<Typography sx={{ opacity: 0.8, textAlign: 'center' }}>
|
||||||
|
Хуй тебе а не донат
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/');
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
borderRadius: 999,
|
||||||
|
px: 3,
|
||||||
|
borderColor: 'rgba(255,255,255,0.18)',
|
||||||
|
color: 'rgba(255,255,255,0.85)',
|
||||||
|
textTransform: 'none',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'rgba(255,255,255,0.30)',
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Вернуться назад
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</GlassPaper>
|
||||||
|
</PageCenter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== PROCESSING =====
|
||||||
|
if (stage === 'processing') {
|
||||||
|
return (
|
||||||
|
<PageCenter>
|
||||||
|
<GlassPaper sx={{ width: 'min(680px, 92vw)', p: 3 }}>
|
||||||
|
<Glow />
|
||||||
|
<Stack
|
||||||
|
spacing={2.2}
|
||||||
|
alignItems="center"
|
||||||
|
sx={{ position: 'relative' }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 72,
|
||||||
|
height: 72,
|
||||||
|
borderRadius: '50%',
|
||||||
|
display: 'grid',
|
||||||
|
placeItems: 'center',
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<GradientTitle variant="h6" sx={{ textAlign: 'center' }}>
|
||||||
|
{stepText}
|
||||||
|
</GradientTitle>
|
||||||
|
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<LinearProgress
|
||||||
|
variant="determinate"
|
||||||
|
value={progress}
|
||||||
|
sx={{
|
||||||
|
height: 10,
|
||||||
|
borderRadius: 999,
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.08)',
|
||||||
|
'& .MuiLinearProgress-bar': {
|
||||||
|
borderRadius: 999,
|
||||||
|
backgroundImage: GRADIENT,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography sx={{ fontSize: 12, opacity: 0.75, mt: 1 }}>
|
||||||
|
{Math.round(progress)}%
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
gap: 1,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: 1.5,
|
||||||
|
py: 0.8,
|
||||||
|
borderRadius: 999,
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{ opacity: 0.85, fontSize: 13 }}>
|
||||||
|
{rubles.toLocaleString('ru-RU')} ₽
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: 1.5,
|
||||||
|
py: 0.8,
|
||||||
|
borderRadius: 999,
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{ opacity: 0.85, fontSize: 13 }}>
|
||||||
|
{rubles.toLocaleString('ru-RU')} монет
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: 1.5,
|
||||||
|
py: 0.8,
|
||||||
|
borderRadius: 999,
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{ opacity: 0.85, fontSize: 13 }}>
|
||||||
|
{methodLabel}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{ fontSize: 12, opacity: 0.55, textAlign: 'center' }}
|
||||||
|
>
|
||||||
|
Пожалуйста, не закрывайте окно
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</GlassPaper>
|
||||||
|
</PageCenter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== FORM =====
|
||||||
|
return (
|
||||||
|
<PageCenter>
|
||||||
|
<GlassPaper sx={{ width: 'min(680px, 92vw)', p: 3 }}>
|
||||||
|
<Glow />
|
||||||
|
<Stack spacing={2.2} sx={{ position: 'relative' }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||||
|
<GradientTitle variant="h5">Пополнение баланса</GradientTitle>
|
||||||
|
<Typography sx={{ opacity: 0.75 }}>1 ₽ = 1 монета</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: { xs: '1fr', md: '1.3fr 1fr' },
|
||||||
|
gap: 2,
|
||||||
|
alignItems: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledTextField
|
||||||
|
label="Сколько монет нужно?"
|
||||||
|
type="number"
|
||||||
|
value={coins}
|
||||||
|
onChange={(e) => setCoins(Number(e.target.value))}
|
||||||
|
inputProps={{ min: 0, step: 1 }}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
borderRadius: 20,
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
p: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{ opacity: 0.75, fontSize: 12 }}>
|
||||||
|
Итого к оплате
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
mt: 0.5,
|
||||||
|
fontSize: 26,
|
||||||
|
fontWeight: 900,
|
||||||
|
fontFamily: 'Benzin-Bold, sans-serif',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{rubles.toLocaleString('ru-RU')} ₽
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ opacity: 0.65, fontSize: 12, mt: 0.5 }}>
|
||||||
|
Начислим: {rubles.toLocaleString('ru-RU')} монет
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Typography sx={{ mb: 1, fontWeight: 700, opacity: 0.9 }}>
|
||||||
|
Способ оплаты
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<StyledToggleButtonGroup
|
||||||
|
value={method}
|
||||||
|
exclusive
|
||||||
|
onChange={(_, v) => v && setMethod(v)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<ToggleButton value="sbp">СБП</ToggleButton>
|
||||||
|
<ToggleButton value="card">Карта</ToggleButton>
|
||||||
|
<ToggleButton value="crypto">Crypto</ToggleButton>
|
||||||
|
<ToggleButton value="other">Другое</ToggleButton>
|
||||||
|
</StyledToggleButtonGroup>
|
||||||
|
|
||||||
|
<Typography sx={{ mt: 1, fontSize: 12, opacity: 0.55 }}>
|
||||||
|
Выбрано: {methodLabel}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<GradientButton
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
onClick={handlePay}
|
||||||
|
disabled={rubles <= 0}
|
||||||
|
sx={{
|
||||||
|
height: 52,
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Оплатить
|
||||||
|
</GradientButton>
|
||||||
|
</Stack>
|
||||||
|
</GlassPaper>
|
||||||
|
</PageCenter>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user