Merge branch 'feat/VersionsExplorer' of https://git.popa-popa.ru/DIKER/popa-launcher into feat/VersionsExplorer
This commit is contained in:
BIN
assets/icons/popa-popa.png
Normal file
BIN
assets/icons/popa-popa.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
18
assets/icons/popa-popa.svg
Normal file
18
assets/icons/popa-popa.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 80 KiB |
@ -1,11 +1,23 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="4" width="8" height="4" fill="#FF2D0F"/>
|
||||
<rect x="8" y="4" width="4" height="16" fill="#FF2D0F"/>
|
||||
<rect x="4" y="8" width="4" height="8" fill="#FF2D0F"/>
|
||||
<rect x="0" y="12" width="28" height="4" fill="#BD2211"/>
|
||||
<rect x="4" y="16" width="20" height="4" fill="#BD2211"/>
|
||||
<rect x="8" y="20" width="12" height="4" fill="#BD2211"/>
|
||||
<rect x="12" y="24" width="4" height="4" fill="#BD2211"/>
|
||||
<rect x="4" width="8" height="4.5" fill="#FF2D0F"/>
|
||||
<rect x="16" width="8" height="4.5" fill="#FF2D0F"/>
|
||||
<rect x="0" y="4" width="28" height="8" fill="#FF2D0F"/>
|
||||
<rect x="4" y="8" width="20" height="8" fill="#FF2D0F"/>
|
||||
<rect x="8" y="12" width="12" height="8" fill="#FF2D0F"/>
|
||||
<rect x="12" y="16" width="4" height="8" fill="#FF2D0F"/>
|
||||
<rect x="4" y="4" width="4" height="4" fill="#FFCAC8"/>
|
||||
<!-- <rect x="7" y="4" width="6" height="16" fill="#FF2D0F"/>
|
||||
<rect x="6" y="3" width="6" height="16" fill="#FF2D0F"/>
|
||||
<rect x="3" y="8" width="4" height="8" fill="#FF2D0F"/>
|
||||
<rect y="4" width="4" height="8" fill="#FF2D0F"/>
|
||||
<rect x="24" y="4" width="4" height="8" fill="#FF2D0F"/>
|
||||
<rect x="16" width="8" height="16" fill="#FF2D0F"/>
|
||||
<rect x="16" y="16" width="4" height="4" fill="#FF2D0F"/>
|
||||
<rect x="20" y="4" width="8" height="12" fill="#FF2D0F"/>
|
||||
<rect x="15" y="4" width="5" height="16" fill="#FF2D0F"/>
|
||||
<rect x="24" y="12" width="4" height="4" fill="#BD2211"/>
|
||||
<rect x="20" y="16" width="4" height="4" fill="#BD2211"/>
|
||||
<rect x="16" y="20" width="4" height="4" fill="#BD2211"/>
|
||||
@ -14,5 +26,5 @@
|
||||
<rect x="4" y="16" width="4" height="4" fill="#BD2211"/>
|
||||
<rect x="4" y="4" width="4" height="4" fill="#FFCAC8"/>
|
||||
<rect y="12" width="4" height="4" fill="#BD2211"/>
|
||||
<rect x="12" y="4" width="4" height="20" fill="#FF2D0F"/>
|
||||
<rect x="12" y="4" width="4" height="20" fill="#FF2D0F"/> -->
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 994 B After Width: | Height: | Size: 1.7 KiB |
19
package-lock.json
generated
19
package-lock.json
generated
@ -23,6 +23,7 @@
|
||||
"find-java-home": "^2.0.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"qr-code-styling": "^1.9.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.3.0",
|
||||
@ -18136,6 +18137,24 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qr-code-styling": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/qr-code-styling/-/qr-code-styling-1.9.2.tgz",
|
||||
"integrity": "sha512-RgJaZJ1/RrXJ6N0j7a+pdw3zMBmzZU4VN2dtAZf8ZggCfRB5stEQ3IoDNGaNhYY3nnZKYlYSLl5YkfWN5dPutg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"qrcode-generator": "^1.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode-generator": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz",
|
||||
"integrity": "sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
|
@ -117,6 +117,7 @@
|
||||
"find-java-home": "^2.0.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"qr-code-styling": "^1.9.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.3.0",
|
||||
|
@ -17,6 +17,7 @@ import { VersionsExplorer } from './pages/VersionsExplorer';
|
||||
import Profile from './pages/Profile';
|
||||
import Shop from './pages/Shop';
|
||||
import Marketplace from './pages/Marketplace';
|
||||
import { Registration } from './pages/Registration';
|
||||
|
||||
const AuthCheck = ({ children }: { children: ReactNode }) => {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
||||
@ -127,6 +128,7 @@ const App = () => {
|
||||
<Notifier />
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/registration" element={<Registration />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const API_BASE_URL = 'http://147.78.65.214:8000';
|
||||
export const API_BASE_URL = 'https://minecraft.api.popa-popa.ru';
|
||||
|
||||
export interface Player {
|
||||
uuid: string;
|
||||
@ -169,6 +169,66 @@ export interface OperationsResponse {
|
||||
operations: MarketplaceOperation[];
|
||||
}
|
||||
|
||||
export interface RegisterUserResponse {
|
||||
status: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export interface GenerateVerificationCodeResponse {
|
||||
status: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface VerificationStatusResponse {
|
||||
is_verified: boolean;
|
||||
}
|
||||
|
||||
export async function getVerificationStatus(
|
||||
username: string,
|
||||
): Promise<VerificationStatusResponse> {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/auth/verification_status/${username}`,
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Не удалось получить статус верификации');
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
export async function generateVerificationCode(
|
||||
username: string,
|
||||
): Promise<GenerateVerificationCodeResponse> {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/auth/generate_code?username=${username}`,
|
||||
{
|
||||
method: 'POST',
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Не удалось сгенерировать код верификации');
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
export async function registerUser(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<RegisterUserResponse> {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Не удалось зарегистрировать пользователя');
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
export async function getPlayerInventory(
|
||||
request_id: string,
|
||||
): Promise<PlayerInventory> {
|
||||
|
@ -107,6 +107,16 @@ export default function CapeCard({
|
||||
color={actionButton.color as 'primary' | 'success' | 'error'}
|
||||
onClick={() => onAction(capeId)}
|
||||
disabled={actionDisabled}
|
||||
sx={{
|
||||
borderRadius: '20px',
|
||||
p: '5px 25px',
|
||||
color: 'white',
|
||||
backgroundColor: 'rgb(0, 134, 0)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 134, 0, 0.5)',
|
||||
},
|
||||
fontFamily: 'Benzin-Bold',
|
||||
}}
|
||||
>
|
||||
{actionButton.text}
|
||||
</Button>
|
||||
|
@ -183,13 +183,13 @@ export default function PlayerInventory({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Box sx={{ mt: '1vw' }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '1vw',
|
||||
alignItems: 'center',
|
||||
mb: 2,
|
||||
mb: '2vw',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5" color="white">
|
||||
@ -199,6 +199,18 @@ export default function PlayerInventory({
|
||||
variant="outlined"
|
||||
onClick={fetchPlayerInventory}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
borderRadius: '20px',
|
||||
p: '10px 25px',
|
||||
color: 'white',
|
||||
borderColor: 'rgba(255, 77, 77, 1)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 77, 77, 1)',
|
||||
borderColor: 'rgba(255, 77, 77, 1)',
|
||||
},
|
||||
fontFamily: 'Benzin-Bold',
|
||||
fontSize: '1vw',
|
||||
}}
|
||||
>
|
||||
Обновить
|
||||
</Button>
|
||||
@ -235,28 +247,33 @@ export default function PlayerInventory({
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.2s',
|
||||
'&:hover': { transform: 'scale(1.03)' },
|
||||
borderRadius: '1vw',
|
||||
}}
|
||||
onClick={() => handleOpenSellDialog(item)}
|
||||
>
|
||||
<CardMedia
|
||||
component="img"
|
||||
sx={{
|
||||
height: 100,
|
||||
minWidth: '10vw',
|
||||
minHeight: '10vw',
|
||||
maxHeight: '10vw',
|
||||
objectFit: 'contain',
|
||||
bgcolor: 'rgba(0, 0, 0, 0.2)',
|
||||
p: 1,
|
||||
bgcolor: 'white',
|
||||
p: '1vw',
|
||||
imageRendering: 'pixelated',
|
||||
}}
|
||||
image={`/minecraft/${item.material.toLowerCase()}.png`}
|
||||
alt={item.material}
|
||||
/>
|
||||
<CardContent sx={{ p: 1 }}>
|
||||
<Typography variant="body2" color="white" noWrap>
|
||||
{getItemDisplayName(item.material)}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="white">
|
||||
x{item.amount}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: '1vw', justifyContent: 'space-between' }}>
|
||||
<Typography variant="body2" color="white" noWrap>
|
||||
{getItemDisplayName(item.material)}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="white">
|
||||
{item.amount > 1 ? `x${item.amount}` : ''}
|
||||
</Typography>
|
||||
</Box>
|
||||
{Object.keys(item.enchants || {}).length > 0 && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
|
@ -33,7 +33,9 @@ export default function SkinViewer({
|
||||
canvas: canvasRef.current,
|
||||
width,
|
||||
height,
|
||||
skin: skinUrl || undefined,
|
||||
skin:
|
||||
skinUrl ||
|
||||
'https://static.planetminecraft.com/files/resource_media/skin/original-steve-15053860.png',
|
||||
model: 'auto-detect',
|
||||
cape: capeUrl || undefined,
|
||||
});
|
||||
|
@ -30,9 +30,11 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
||||
const isLoginPage = location.pathname === '/login';
|
||||
const isLaunchPage = location.pathname.startsWith('/launch');
|
||||
const isVersionsExplorerPage = location.pathname.startsWith('/');
|
||||
const isRegistrationPage = location.pathname === '/registration';
|
||||
const navigate = useNavigate();
|
||||
const [coins, setCoins] = useState<number>(0);
|
||||
const [value, setValue] = useState(0);
|
||||
const [activePage, setActivePage] = useState('versions');
|
||||
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setValue(newValue);
|
||||
@ -59,7 +61,18 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
||||
return 'Запуск';
|
||||
}
|
||||
if (isVersionsExplorerPage) {
|
||||
return 'Версии';
|
||||
if (activePage === 'versions') {
|
||||
return 'Версии';
|
||||
}
|
||||
if (activePage === 'profile') {
|
||||
return 'Профиль';
|
||||
}
|
||||
if (activePage === 'shop') {
|
||||
return 'Магазин';
|
||||
}
|
||||
if (activePage === 'marketplace') {
|
||||
return 'Рынок';
|
||||
}
|
||||
}
|
||||
return 'Неизвестная страница';
|
||||
};
|
||||
@ -118,10 +131,9 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
||||
marginLeft: '1vw',
|
||||
}}
|
||||
>
|
||||
{isLaunchPage && (
|
||||
{(isLaunchPage || isRegistrationPage) && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => handleLaunchPage()}
|
||||
sx={{
|
||||
width: '3em',
|
||||
@ -136,43 +148,96 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
||||
<ArrowBackRoundedIcon />
|
||||
</Button>
|
||||
)}
|
||||
{!isLaunchPage && (
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
{!isLaunchPage && !isRegistrationPage && !isLoginPage && (
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: 1,
|
||||
borderColor: 'transparent',
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: 'rgba(255, 77, 77, 1)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
aria-label="basic tabs example"
|
||||
disableRipple={true}
|
||||
>
|
||||
<Tab
|
||||
label="Версии"
|
||||
disableRipple={true}
|
||||
onClick={() => {
|
||||
setActivePage('versions');
|
||||
}}
|
||||
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
|
||||
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
|
||||
label="Магазин"
|
||||
disableRipple={true}
|
||||
onClick={() => {
|
||||
setActivePage('shop');
|
||||
}}
|
||||
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
|
||||
label="Рынок"
|
||||
disableRipple={true}
|
||||
onClick={() => {
|
||||
setActivePage('marketplace');
|
||||
}}
|
||||
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',
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
@ -192,7 +257,10 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
||||
WebkitAppRegion: 'drag',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ color: 'white' }}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ color: 'white', fontFamily: 'Benzin-Bold' }}
|
||||
>
|
||||
{getPageTitle()}
|
||||
</Typography>
|
||||
</Box>
|
||||
@ -251,7 +319,7 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => onRegister && onRegister()}
|
||||
onClick={() => navigate('/registration')}
|
||||
sx={{
|
||||
width: '10em',
|
||||
height: '3em',
|
||||
|
@ -63,6 +63,19 @@ export default function Marketplace() {
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
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 () => {
|
||||
if (!username) return;
|
||||
@ -221,9 +234,18 @@ export default function Marketplace() {
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={checkPlayerStatus}
|
||||
sx={{ mt: 2 }}
|
||||
sx={{
|
||||
mt: '1%',
|
||||
borderRadius: '20px',
|
||||
p: '10px 25px',
|
||||
color: 'white',
|
||||
backgroundColor: 'rgb(255, 77, 77)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 77, 77, 0.5)',
|
||||
},
|
||||
fontFamily: 'Benzin-Bold',
|
||||
}}
|
||||
>
|
||||
Проверить снова
|
||||
</Button>
|
||||
@ -232,32 +254,81 @@ export default function Marketplace() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ padding: 3 }}>
|
||||
<Typography variant="h4" color="white" gutterBottom>
|
||||
Рынок сервера {playerServer?.name || ''}
|
||||
</Typography>
|
||||
<Box sx={{ padding: 3, width: '95%', height: '80%' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '1vw' }}>
|
||||
<Typography variant="h4" color="white" gutterBottom>
|
||||
Рынок сервера{' '}
|
||||
</Typography>
|
||||
<Typography
|
||||
style={{
|
||||
color: 'white',
|
||||
backgroundColor: 'rgba(255, 77, 77, 1)',
|
||||
padding: '0vw 2vw',
|
||||
borderRadius: '5vw',
|
||||
fontFamily: 'Benzin-Bold',
|
||||
textAlign: 'center',
|
||||
fontSize: '2vw',
|
||||
}}
|
||||
>
|
||||
{translateServer(playerServer || { name: '' })}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Вкладки */}
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'transparent' }}>
|
||||
<Tabs
|
||||
value={tabValue}
|
||||
onChange={handleTabChange}
|
||||
aria-label="marketplace tabs"
|
||||
disableRipple={true}
|
||||
sx={{
|
||||
'& .MuiTabs-indicator': {
|
||||
backgroundColor: 'rgba(255, 77, 77, 1)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tab label="Товары" />
|
||||
<Tab label="Мой инвентарь" />
|
||||
<Tab
|
||||
label="Товары"
|
||||
disableRipple={true}
|
||||
sx={{
|
||||
fontFamily: 'Benzin-Bold',
|
||||
color: 'white',
|
||||
'&.Mui-selected': {
|
||||
color: 'rgba(255, 77, 77, 1)',
|
||||
},
|
||||
'&:hover': {
|
||||
color: 'rgba(255, 77, 77, 1)',
|
||||
},
|
||||
transition: 'all 0.3s ease',
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Мой инвентарь"
|
||||
disableRipple={true}
|
||||
sx={{
|
||||
fontFamily: 'Benzin-Bold',
|
||||
color: 'white',
|
||||
'&.Mui-selected': {
|
||||
color: 'rgba(255, 77, 77, 1)',
|
||||
},
|
||||
'&:hover': {
|
||||
color: 'rgba(255, 77, 77, 1)',
|
||||
},
|
||||
transition: 'all 0.3s ease',
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{/* Содержимое вкладки "Товары" */}
|
||||
<TabPanel value={tabValue} index={0}>
|
||||
{marketLoading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', mt: '50vw' }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : !marketItems || marketItems.items.length === 0 ? (
|
||||
<Box sx={{ mt: 4, textAlign: 'center' }}>
|
||||
<Typography variant="h6" color="white">
|
||||
<Typography variant="h6" color="white" sx={{ mt: '10vw' }}>
|
||||
На данный момент на рынке нет предметов.
|
||||
</Typography>
|
||||
<Button
|
||||
@ -266,7 +337,18 @@ export default function Marketplace() {
|
||||
onClick={() =>
|
||||
playerServer && loadMarketItems(playerServer.ip, 1)
|
||||
}
|
||||
sx={{ mt: 2 }}
|
||||
sx={{
|
||||
mt: 2,
|
||||
borderRadius: '20px',
|
||||
p: '10px 25px',
|
||||
color: 'white',
|
||||
backgroundColor: 'rgb(255, 77, 77)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 77, 77, 0.5)',
|
||||
},
|
||||
fontFamily: 'Benzin-Bold',
|
||||
fontSize: '1vw',
|
||||
}}
|
||||
>
|
||||
Обновить
|
||||
</Button>
|
||||
@ -276,14 +358,21 @@ export default function Marketplace() {
|
||||
<Grid container spacing={2} sx={{ mt: 2 }}>
|
||||
{marketItems.items.map((item) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.id}>
|
||||
<Card sx={{ bgcolor: 'rgba(255, 255, 255, 0.05)' }}>
|
||||
<Card
|
||||
sx={{
|
||||
bgcolor: 'rgba(255, 255, 255, 0.05)',
|
||||
borderRadius: '1vw',
|
||||
}}
|
||||
>
|
||||
<CardMedia
|
||||
component="img"
|
||||
sx={{
|
||||
height: 140,
|
||||
minWidth: '10vw',
|
||||
minHeight: '10vw',
|
||||
maxHeight: '10vw',
|
||||
objectFit: 'contain',
|
||||
bgcolor: 'rgba(0, 0, 0, 0.2)',
|
||||
p: 1,
|
||||
bgcolor: 'white',
|
||||
p: '1vw',
|
||||
imageRendering: 'pixelated',
|
||||
}}
|
||||
image={`/minecraft/${item.material.toLowerCase()}.png`}
|
||||
@ -314,7 +403,18 @@ export default function Marketplace() {
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
sx={{ mt: 2 }}
|
||||
sx={{
|
||||
mt: '1vw',
|
||||
borderRadius: '20px',
|
||||
p: '0.3vw 0vw',
|
||||
color: 'white',
|
||||
backgroundColor: 'rgb(255, 77, 77)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 77, 77, 0.5)',
|
||||
},
|
||||
fontFamily: 'Benzin-Bold',
|
||||
fontSize: '1vw',
|
||||
}}
|
||||
onClick={() => handleBuyItem(item.id)}
|
||||
>
|
||||
Купить
|
||||
|
@ -167,7 +167,9 @@ export default function Profile() {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
gap: '100px',
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
@ -185,6 +187,20 @@ export default function Profile() {
|
||||
}}
|
||||
>
|
||||
{/* Используем переработанный компонент SkinViewer */}
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: 'Benzin-Bold',
|
||||
alignSelf: 'center',
|
||||
justifySelf: 'center',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
mb: '5vw',
|
||||
fontSize: '3vw',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
{username}
|
||||
</Typography>
|
||||
<SkinViewer
|
||||
width={300}
|
||||
height={400}
|
||||
@ -195,7 +211,13 @@ export default function Profile() {
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
@ -284,9 +306,17 @@ export default function Profile() {
|
||||
)}
|
||||
|
||||
<Button
|
||||
sx={{ color: 'white' }}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderRadius: '20px',
|
||||
p: '10px 25px',
|
||||
backgroundColor: 'rgb(0, 134, 0)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(0, 134, 0, 0.5)',
|
||||
},
|
||||
fontFamily: 'Benzin-Bold',
|
||||
}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
onClick={handleUploadSkin}
|
||||
disabled={uploadStatus === 'loading' || !skinFile}
|
||||
|
427
src/renderer/pages/Registration.tsx
Normal file
427
src/renderer/pages/Registration.tsx
Normal file
@ -0,0 +1,427 @@
|
||||
import Stepper from '@mui/material/Stepper';
|
||||
import Step from '@mui/material/Step';
|
||||
import StepLabel from '@mui/material/StepLabel';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
StepConnector,
|
||||
stepConnectorClasses,
|
||||
StepIconProps,
|
||||
styled,
|
||||
Typography,
|
||||
Box,
|
||||
TextField,
|
||||
Button,
|
||||
Snackbar,
|
||||
CircularProgress,
|
||||
} from '@mui/material';
|
||||
import LoginRoundedIcon from '@mui/icons-material/LoginRounded';
|
||||
import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded';
|
||||
import AssignmentIndRoundedIcon from '@mui/icons-material/AssignmentIndRounded';
|
||||
import {
|
||||
generateVerificationCode,
|
||||
registerUser,
|
||||
getVerificationStatus,
|
||||
} from '../api';
|
||||
import QRCodeStyling from 'qr-code-styling';
|
||||
import popalogo from '../../../assets/icons/popa-popa.svg';
|
||||
|
||||
const ColorlibConnector = styled(StepConnector)(({ theme }) => ({
|
||||
[`&.${stepConnectorClasses.alternativeLabel}`]: {
|
||||
top: 22,
|
||||
},
|
||||
[`&.${stepConnectorClasses.active}`]: {
|
||||
[`& .${stepConnectorClasses.line}`]: {
|
||||
backgroundImage:
|
||||
'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)',
|
||||
},
|
||||
},
|
||||
[`&.${stepConnectorClasses.completed}`]: {
|
||||
[`& .${stepConnectorClasses.line}`]: {
|
||||
backgroundImage:
|
||||
'linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)',
|
||||
},
|
||||
},
|
||||
[`& .${stepConnectorClasses.line}`]: {
|
||||
height: 3,
|
||||
border: 0,
|
||||
backgroundColor: '#eaeaf0',
|
||||
borderRadius: 1,
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: theme.palette.grey[800],
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
const ColorlibStepIconRoot = styled('div')<{
|
||||
ownerState: { completed?: boolean; active?: boolean };
|
||||
}>(({ theme }) => ({
|
||||
backgroundColor: '#ccc',
|
||||
zIndex: 1,
|
||||
color: '#fff',
|
||||
width: 50,
|
||||
height: 50,
|
||||
display: 'flex',
|
||||
borderRadius: '50%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: theme.palette.grey[700],
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: ({ ownerState }) => ownerState.active,
|
||||
style: {
|
||||
backgroundImage:
|
||||
'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
|
||||
boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: ({ ownerState }) => ownerState.completed,
|
||||
style: {
|
||||
backgroundImage:
|
||||
'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
function ColorlibStepIcon(props: StepIconProps) {
|
||||
const { active, completed, className } = props;
|
||||
|
||||
const icons: { [index: string]: React.ReactElement<unknown> } = {
|
||||
1: <AssignmentIndRoundedIcon />,
|
||||
2: <VerifiedRoundedIcon />,
|
||||
3: <LoginRoundedIcon />,
|
||||
};
|
||||
|
||||
return (
|
||||
<ColorlibStepIconRoot
|
||||
ownerState={{ completed, active }}
|
||||
className={className}
|
||||
>
|
||||
{icons[String(props.icon)]}
|
||||
</ColorlibStepIconRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const qrCode = new QRCodeStyling({
|
||||
width: 300,
|
||||
height: 300,
|
||||
image: popalogo,
|
||||
data: 'https://t.me/popa_popa_popa_bot?start=test',
|
||||
shape: 'square',
|
||||
margin: 10,
|
||||
dotsOptions: {
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgb(242,113,33)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgb(233,64,87)',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'extra-rounded',
|
||||
},
|
||||
imageOptions: {
|
||||
crossOrigin: 'anonymous',
|
||||
margin: 20,
|
||||
imageSize: 0.5,
|
||||
},
|
||||
backgroundOptions: {
|
||||
color: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
export const Registration = () => {
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [verificationCode, setVerificationCode] = useState<string | null>(null);
|
||||
const ref = useRef(null);
|
||||
const [url, setUrl] = useState('');
|
||||
const steps = ['Создание аккаунта', 'Верификация аккаунта в телеграмме'];
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
qrCode.append(ref.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
qrCode.update({
|
||||
data: url,
|
||||
});
|
||||
}, [url]);
|
||||
|
||||
const handleCreateAccount = async () => {
|
||||
const response = await registerUser(username, password);
|
||||
if (response.status === 'success') {
|
||||
setActiveStep(1);
|
||||
} else {
|
||||
setOpen(true);
|
||||
setMessage(response.status);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeStep === 1) {
|
||||
handleGenerateVerificationCode(username);
|
||||
setUrl(`https://t.me/popa_popa_popa_bot?start=${username}`);
|
||||
|
||||
while (ref.current.firstChild) {
|
||||
ref.current.removeChild(ref.current.firstChild);
|
||||
}
|
||||
|
||||
const newQrCode = new QRCodeStyling({
|
||||
width: 300,
|
||||
height: 300,
|
||||
image: popalogo,
|
||||
data: 'https://t.me/popa_popa_popa_bot?start=test',
|
||||
shape: 'square',
|
||||
margin: 10,
|
||||
dotsOptions: {
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgb(242,113,33)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgb(233,64,87)',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'extra-rounded',
|
||||
},
|
||||
imageOptions: {
|
||||
crossOrigin: 'anonymous',
|
||||
margin: 20,
|
||||
imageSize: 0.5,
|
||||
},
|
||||
backgroundOptions: {
|
||||
color: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
newQrCode.update({
|
||||
data: `https://t.me/popa_popa_popa_bot?start=${username}`,
|
||||
});
|
||||
|
||||
setUrl(`https://t.me/popa_popa_popa_bot?start=${username}`);
|
||||
|
||||
newQrCode.append(ref.current);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
handleVerifyCode();
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}
|
||||
}, [activeStep]);
|
||||
|
||||
const handleGenerateVerificationCode = async (username: string) => {
|
||||
console.log(username);
|
||||
const response = await generateVerificationCode(username);
|
||||
setVerificationCode(response.code);
|
||||
};
|
||||
|
||||
const handleVerifyCode = async () => {
|
||||
const response = await getVerificationStatus(username);
|
||||
if (response.is_verified) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenBot = () => {
|
||||
window.open(`https://t.me/popa_popa_popa_bot?start=${username}`, '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
alternativeLabel
|
||||
connector={<ColorlibConnector />}
|
||||
>
|
||||
{steps.map((label) => (
|
||||
<Step key={label}>
|
||||
<StepLabel
|
||||
sx={{
|
||||
'& .MuiStepLabel-label': {
|
||||
color: 'white',
|
||||
},
|
||||
'& .Mui-completed': {
|
||||
color: 'white !important',
|
||||
},
|
||||
'& .Mui-active': {
|
||||
backgroundImage:
|
||||
'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
StepIconComponent={ColorlibStepIcon}
|
||||
>
|
||||
{label}
|
||||
</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
{activeStep === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">Создание аккаунта</Typography>
|
||||
<Typography variant="body1">Введите ваш никнейм</Typography>
|
||||
<TextField
|
||||
required
|
||||
name="username"
|
||||
variant="outlined"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
sx={{
|
||||
width: '100%',
|
||||
// '& .MuiFormLabel-root': {
|
||||
// color: 'white',
|
||||
// },
|
||||
'& .MuiInputBase-input': {
|
||||
color: 'white',
|
||||
},
|
||||
'& .MuiInput-underline:after': {
|
||||
borderBottomColor: '#B2BAC2',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
borderColor: '#E0E3E7',
|
||||
color: 'white',
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
borderColor: '#B2BAC2',
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: '#6F7E8C',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography variant="body1">Введите ваш пароль</Typography>
|
||||
<TextField
|
||||
required
|
||||
name="password"
|
||||
type="password"
|
||||
variant="outlined"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
sx={{
|
||||
width: '100%',
|
||||
// '& .MuiFormLabel-root': {
|
||||
// color: 'white',
|
||||
// },
|
||||
'& .MuiInputBase-input': {
|
||||
color: 'white',
|
||||
},
|
||||
'& .MuiInput-underline:after': {
|
||||
borderBottomColor: '#B2BAC2',
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
borderColor: '#E0E3E7',
|
||||
color: 'white',
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
borderColor: '#B2BAC2',
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: '#6F7E8C',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ width: '100%', mt: 2 }}
|
||||
onClick={handleCreateAccount}
|
||||
>
|
||||
Создать
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
{activeStep === 1 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">Откройте бота в телеграмме</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ width: '100%', mt: 2 }}
|
||||
onClick={handleOpenBot}
|
||||
>
|
||||
Открыть бота
|
||||
</Button>
|
||||
<div
|
||||
ref={ref}
|
||||
style={{
|
||||
minHeight: 300,
|
||||
}}
|
||||
/>
|
||||
<Typography variant="body1">
|
||||
Введите код верификации в боте
|
||||
</Typography>
|
||||
{verificationCode ? (
|
||||
<>
|
||||
<Typography
|
||||
variant="h2"
|
||||
sx={{
|
||||
backgroundImage:
|
||||
'linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
}}
|
||||
>
|
||||
{verificationCode}
|
||||
</Typography>
|
||||
<Typography variant="body1">Ждем ответа от бота</Typography>
|
||||
<CircularProgress />
|
||||
</>
|
||||
) : (
|
||||
<CircularProgress />
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<Snackbar
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
message={message}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -83,9 +83,10 @@ export default function Shop() {
|
||||
gap: '2vw',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4">Shop</Typography>
|
||||
{loading ? (
|
||||
<Typography>Загрузка...</Typography>
|
||||
) : (
|
||||
@ -93,9 +94,11 @@ export default function Shop() {
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexWrap: 'wrap',
|
||||
alignContent: 'flex-start',
|
||||
width: '90%',
|
||||
height: '80%',
|
||||
gap: '2vw',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">Доступные плащи</Typography>
|
||||
|
Reference in New Issue
Block a user