add commands tab in shop
This commit is contained in:
59
src/renderer/api/commands.ts
Normal file
59
src/renderer/api/commands.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { API_BASE_URL } from '../api';
|
||||
|
||||
export interface PrankCommand {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
price: number;
|
||||
command_template: string;
|
||||
server_ids: string[]; // ["*"] или конкретные id
|
||||
targetDescription: string;
|
||||
globalDescription: string;
|
||||
}
|
||||
|
||||
export interface PrankServer {
|
||||
id: string;
|
||||
name: string;
|
||||
ip: string;
|
||||
online_players: number;
|
||||
max_players: number;
|
||||
}
|
||||
|
||||
export const fetchPrankCommands = async (): Promise<PrankCommand[]> => {
|
||||
const res = await fetch(`${API_BASE_URL}/api/pranks/commands`);
|
||||
if (!res.ok) throw new Error('Failed to load prank commands');
|
||||
return res.json();
|
||||
};
|
||||
|
||||
export const fetchPrankServers = async (): Promise<PrankServer[]> => {
|
||||
const res = await fetch(`${API_BASE_URL}/api/pranks/servers`);
|
||||
if (!res.ok) throw new Error('Failed to load prank servers');
|
||||
return res.json();
|
||||
};
|
||||
|
||||
export const executePrank = async (
|
||||
username: string,
|
||||
commandId: string,
|
||||
targetPlayer: string,
|
||||
serverId: string,
|
||||
) => {
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/pranks/execute?username=${username}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
command_id: commandId,
|
||||
target_player: targetPlayer,
|
||||
server_id: serverId,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
};
|
||||
@ -1,6 +1,19 @@
|
||||
import {
|
||||
Box, Typography, Button, Grid,
|
||||
FormControl, Select, MenuItem, InputLabel
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
Grid,
|
||||
FormControl,
|
||||
Select,
|
||||
MenuItem,
|
||||
InputLabel,
|
||||
Tabs,
|
||||
Tab,
|
||||
TextField,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Cape,
|
||||
@ -32,9 +45,36 @@ import ShopItem from '../components/ShopItem';
|
||||
import { playBuySound, primeSounds } from '../utils/sounds';
|
||||
import CustomNotification from '../components/Notifications/CustomNotification';
|
||||
import type { NotificationPosition } from '../components/Notifications/CustomNotification';
|
||||
import { isNotificationsEnabled, getNotifPositionFromSettings } from '../utils/notifications';
|
||||
import {
|
||||
executePrank,
|
||||
fetchPrankCommands,
|
||||
fetchPrankServers,
|
||||
PrankCommand,
|
||||
PrankServer,
|
||||
} from '../api/commands';
|
||||
|
||||
import {
|
||||
isNotificationsEnabled,
|
||||
getNotifPositionFromSettings,
|
||||
} from '../utils/notifications';
|
||||
import { translateServer } from '../utils/serverTranslator';
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
function TabPanel(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div role="tabpanel" hidden={value !== index} {...other}>
|
||||
{value === index && <Box sx={{ pt: '1.4vw' }}>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getRarityByWeight(
|
||||
weight?: number,
|
||||
): 'common' | 'rare' | 'epic' | 'legendary' {
|
||||
@ -64,6 +104,15 @@ function getRarityColor(weight?: number): string {
|
||||
const GRADIENT =
|
||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)';
|
||||
|
||||
const GLASS_DIALOG_SX = {
|
||||
borderRadius: '1.4vw',
|
||||
background:
|
||||
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.16), transparent 55%), rgba(10,10,20,0.92)',
|
||||
border: '1px solid rgba(255,255,255,0.10)',
|
||||
boxShadow: '0 1.6vw 4vw rgba(0,0,0,0.6)',
|
||||
backdropFilter: 'blur(16px)',
|
||||
};
|
||||
|
||||
export default function Shop() {
|
||||
const [storeCapes, setStoreCapes] = useState<StoreCape[]>([]);
|
||||
const [userCapes, setUserCapes] = useState<Cape[]>([]);
|
||||
@ -112,6 +161,33 @@ export default function Shop() {
|
||||
const [rouletteCaseItems, setRouletteCaseItems] = useState<CaseItem[]>([]);
|
||||
const [rouletteReward, setRouletteReward] = useState<CaseItem | null>(null);
|
||||
|
||||
// TABS
|
||||
const [tabValue, setTabValue] = useState<number>(0);
|
||||
|
||||
const handleTabChange = (_: React.SyntheticEvent, newValue: number) => {
|
||||
setTabValue(newValue);
|
||||
};
|
||||
|
||||
//команды
|
||||
|
||||
const [prankCommands, setPrankCommands] = useState<PrankCommand[]>([]);
|
||||
const [prankServers, setPrankServers] = useState<PrankServer[]>([]);
|
||||
|
||||
const [pranksLoading, setPranksLoading] = useState(false);
|
||||
|
||||
const [selectedPrankServer, setSelectedPrankServer] = useState<string>('');
|
||||
const [targetPlayer, setTargetPlayer] = useState<string>('');
|
||||
const [processingCommandId, setProcessingCommandId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const [prankDialogOpen, setPrankDialogOpen] = useState(false);
|
||||
const [selectedPrank, setSelectedPrank] = useState<PrankCommand | null>(null);
|
||||
|
||||
const [prankTarget, setPrankTarget] = useState('');
|
||||
const [prankServerId, setPrankServerId] = useState<string>('');
|
||||
const [prankProcessing, setPrankProcessing] = useState(false);
|
||||
|
||||
// Уведомления
|
||||
const [notification, setNotification] = useState<{
|
||||
open: boolean;
|
||||
@ -148,7 +224,7 @@ export default function Shop() {
|
||||
console.error('Ошибка при получении прокачек:', error);
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Ошибка при загрузке прокачки!', 'error')
|
||||
showNotification('Ошибка при загрузке прокачки!', 'error');
|
||||
} finally {
|
||||
setBonusesLoading(false);
|
||||
}
|
||||
@ -175,6 +251,31 @@ export default function Shop() {
|
||||
}
|
||||
};
|
||||
|
||||
// Загрузка команд
|
||||
useEffect(() => {
|
||||
if (tabValue !== 3) return;
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
setPranksLoading(true);
|
||||
const [commands, servers] = await Promise.all([
|
||||
fetchPrankCommands(),
|
||||
fetchPrankServers(),
|
||||
]);
|
||||
setPrankCommands(commands);
|
||||
setPrankServers(servers);
|
||||
if (servers.length) setSelectedPrankServer(servers[0].id);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showNotification('Ошибка загрузки пакостей', 'error');
|
||||
} finally {
|
||||
setPranksLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
load();
|
||||
}, [tabValue]);
|
||||
|
||||
// Функция для загрузки плащей пользователя
|
||||
const loadUserCapes = async (username: string) => {
|
||||
try {
|
||||
@ -194,12 +295,12 @@ export default function Shop() {
|
||||
playBuySound();
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Плащ успешно куплен!', 'success')
|
||||
showNotification('Плащ успешно куплен!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при покупке плаща:', error);
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Ошибка при покупке плаща!', 'error')
|
||||
showNotification('Ошибка при покупке плаща!', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@ -279,9 +380,11 @@ export default function Shop() {
|
||||
|
||||
const handlePurchaseBonus = async (bonusTypeId: string) => {
|
||||
if (!username) {
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Не найдено имя игрока. Авторизируйтесь в лаунчере!', 'error')
|
||||
showNotification(
|
||||
'Не найдено имя игрока. Авторизируйтесь в лаунчере!',
|
||||
'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -294,12 +397,12 @@ export default function Shop() {
|
||||
await loadBonuses(username);
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Прокачка успешно куплена!', 'success')
|
||||
showNotification('Прокачка успешно куплена!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при покупке прокачки:', error);
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Ошибка при прокачке!', 'error')
|
||||
showNotification('Ошибка при прокачке!', 'error');
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -314,12 +417,12 @@ export default function Shop() {
|
||||
await loadBonuses(username);
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Бонус улучшен!', 'success')
|
||||
showNotification('Бонус улучшен!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при улучшении бонуса:', error);
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Ошибка при улучшении бонуса!', 'error')
|
||||
showNotification('Ошибка при улучшении бонуса!', 'error');
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -334,17 +437,13 @@ export default function Shop() {
|
||||
} catch (error) {
|
||||
console.error('Ошибка при переключении бонуса:', error);
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Ошибка при переключении бонуса!', 'error')
|
||||
showNotification('Ошибка при переключении бонуса!', 'error');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const caseServers = Array.from(
|
||||
new Set(
|
||||
(cases || [])
|
||||
.flatMap((c) => c.server_ips || [])
|
||||
.filter(Boolean),
|
||||
),
|
||||
new Set((cases || []).flatMap((c) => c.server_ips || []).filter(Boolean)),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -377,22 +476,28 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
const handleOpenCase = async (caseData: Case) => {
|
||||
if (!username) {
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Не найдено имя игрока. Авторизуйтесь в лаунчере!', 'error')
|
||||
showNotification(
|
||||
'Не найдено имя игрока. Авторизуйтесь в лаунчере!',
|
||||
'error',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedCaseServerIp) {
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Выберите сервер для открытия кейса!', 'warning')
|
||||
showNotification('Выберите сервер для открытия кейса!', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const allowedIps = caseData.server_ips || [];
|
||||
if (allowedIps.length > 0 && !allowedIps.includes(selectedCaseServerIp)) {
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification(`Этот кейс доступен на: ${allowedIps
|
||||
showNotification(
|
||||
`Этот кейс доступен на: ${allowedIps
|
||||
.map((ip) => translateServer(`Server ${ip}`))
|
||||
.join(', ')}`, 'warning')
|
||||
.join(', ')}`,
|
||||
'warning',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -406,7 +511,11 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
setSelectedCase(fullCase);
|
||||
|
||||
// ✅ открываем на выбранном сервере (даже если игрок не на сервере)
|
||||
const result = await openCase(fullCase.id, username, selectedCaseServerIp);
|
||||
const result = await openCase(
|
||||
fullCase.id,
|
||||
username,
|
||||
selectedCaseServerIp,
|
||||
);
|
||||
|
||||
setRouletteCaseItems(caseItems);
|
||||
setRouletteReward(result.reward);
|
||||
@ -414,12 +523,17 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
playBuySound();
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification('Кейс открыт!', 'success')
|
||||
showNotification('Кейс открыт!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при открытии кейса:', error);
|
||||
|
||||
if (!isNotificationsEnabled()) return;
|
||||
showNotification((String(error instanceof Error ? error.message : 'Ошибка при открытии кейса!')), 'error')
|
||||
showNotification(
|
||||
String(
|
||||
error instanceof Error ? error.message : 'Ошибка при открытии кейса!',
|
||||
),
|
||||
'error',
|
||||
);
|
||||
} finally {
|
||||
setIsOpening(false);
|
||||
}
|
||||
@ -470,13 +584,53 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
paddingRight: '1.5vw',
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={tabValue}
|
||||
onChange={handleTabChange}
|
||||
disableRipple
|
||||
sx={{
|
||||
minHeight: '3vw',
|
||||
mb: '1.2vw',
|
||||
'& .MuiTabs-indicator': {
|
||||
height: '0.35vw',
|
||||
borderRadius: '999px',
|
||||
background: GRADIENT,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{['Прокачка', 'Кейсы', 'Плащи', 'Предметы/пакости'].map((label) => (
|
||||
<Tab
|
||||
key={label}
|
||||
label={label}
|
||||
disableRipple
|
||||
sx={{
|
||||
minHeight: '3vw',
|
||||
fontFamily: 'Benzin-Bold',
|
||||
color: 'rgba(255,255,255,0.85)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.4,
|
||||
borderRadius: '999px',
|
||||
mx: '0.2vw',
|
||||
'&.Mui-selected': {
|
||||
color: '#fff',
|
||||
},
|
||||
'&:hover': {
|
||||
color: '#fff',
|
||||
background: 'rgba(255,255,255,0.06)',
|
||||
},
|
||||
transition: 'all 0.18s ease',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
<TabPanel value={tabValue} index={0}>
|
||||
{/* Блок прокачки */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
mt: '2vh'
|
||||
mt: '2vh',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -541,7 +695,9 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
isPermanent={isPermanent}
|
||||
disabled={processing}
|
||||
onBuy={
|
||||
!owned ? () => handlePurchaseBonus(bt.id) : undefined
|
||||
!owned
|
||||
? () => handlePurchaseBonus(bt.id)
|
||||
: undefined
|
||||
}
|
||||
onUpgrade={
|
||||
owned
|
||||
@ -562,7 +718,9 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
<Typography>Прокачка временно недоступна.</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tabValue} index={1}>
|
||||
{/* Блок кейсов */}
|
||||
<Box
|
||||
sx={{
|
||||
@ -664,7 +822,9 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
labelId="cases-server-label"
|
||||
label="Сервер"
|
||||
value={selectedCaseServerIp}
|
||||
onChange={(e) => setSelectedCaseServerIp(String(e.target.value))}
|
||||
onChange={(e) =>
|
||||
setSelectedCaseServerIp(String(e.target.value))
|
||||
}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
@ -761,7 +921,10 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
</Box>
|
||||
|
||||
{casesLoading ? (
|
||||
<FullScreenLoader fullScreen={false} message="Загрузка кейсов..." />
|
||||
<FullScreenLoader
|
||||
fullScreen={false}
|
||||
message="Загрузка кейсов..."
|
||||
/>
|
||||
) : cases.length > 0 ? (
|
||||
<Grid container spacing={2} sx={{ mb: 4 }}>
|
||||
{filteredCases.map((c) => (
|
||||
@ -785,9 +948,9 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
<Typography>Кейсы временно недоступны.</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</TabPanel>
|
||||
|
||||
{/* Блок плащей (как был) */}
|
||||
|
||||
<TabPanel value={tabValue} index={2}>
|
||||
{/* Блок плащей */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Typography
|
||||
@ -825,9 +988,171 @@ const filteredCases = (cases || []).filter((c) => {
|
||||
<Typography>У вас уже есть все доступные плащи!</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tabValue} index={3}>
|
||||
<Grid container spacing={2}>
|
||||
{prankCommands.map((cmd) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={cmd.id}>
|
||||
<Box
|
||||
sx={{
|
||||
p: '1.4vw',
|
||||
borderRadius: '1.4vw',
|
||||
background:
|
||||
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.14), transparent 55%), rgba(10,10,20,0.88)',
|
||||
border: '1px solid rgba(255,255,255,0.10)',
|
||||
boxShadow: '0 1.2vw 3.2vw rgba(0,0,0,0.55)',
|
||||
backdropFilter: 'blur(14px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.8vw',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: 'Benzin-Bold',
|
||||
fontSize: '1.1vw',
|
||||
background: GRADIENT,
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
}}
|
||||
>
|
||||
{cmd.name}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{ color: 'rgba(255,255,255,0.75)', fontWeight: 700 }}
|
||||
>
|
||||
{cmd.description}
|
||||
</Typography>
|
||||
|
||||
<Typography sx={{ fontWeight: 900 }}>
|
||||
Цена: {cmd.price} монет
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
disableRipple
|
||||
onClick={() => {
|
||||
setSelectedPrank(cmd);
|
||||
setPrankTarget('');
|
||||
setPrankDialogOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
mt: '0.6vw',
|
||||
borderRadius: '999px',
|
||||
fontFamily: 'Benzin-Bold',
|
||||
color: '#fff',
|
||||
background: GRADIENT,
|
||||
py: '0.6vw',
|
||||
'&:hover': { filter: 'brightness(1.05)' },
|
||||
}}
|
||||
>
|
||||
Выполнить
|
||||
</Button>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
open={prankDialogOpen}
|
||||
onClose={() => setPrankDialogOpen(false)}
|
||||
fullWidth
|
||||
maxWidth="xs"
|
||||
PaperProps={{ sx: GLASS_DIALOG_SX }}
|
||||
>
|
||||
<DialogTitle sx={{ fontFamily: 'Benzin-Bold' }}>
|
||||
{selectedPrank?.name}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent dividers sx={{ borderColor: 'rgba(255,255,255,0.10)' }}>
|
||||
<Typography sx={{ opacity: 0.75, mb: 2 }}>
|
||||
{selectedPrank?.description}
|
||||
</Typography>
|
||||
|
||||
{/* сервер */}
|
||||
<FormControl fullWidth sx={{ mb: 2 }}>
|
||||
<Select
|
||||
value={prankServerId}
|
||||
onChange={(e) => setPrankServerId(String(e.target.value))}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem disabled value="">
|
||||
Выберите сервер
|
||||
</MenuItem>
|
||||
{prankServers.map((s) => (
|
||||
<MenuItem key={s.id} value={s.id}>
|
||||
{translateServer(s.name)} ({s.online_players}/{s.max_players})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* цель */}
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Ник игрока"
|
||||
value={prankTarget}
|
||||
onChange={(e) => setPrankTarget(e.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ p: '1.2vw' }}>
|
||||
<Button
|
||||
onClick={() => setPrankDialogOpen(false)}
|
||||
sx={{ color: 'rgba(255,255,255,0.75)', fontFamily: 'Benzin-Bold' }}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabled={!prankTarget || !prankServerId || prankProcessing}
|
||||
onClick={async () => {
|
||||
if (!selectedPrank) return;
|
||||
|
||||
try {
|
||||
setPrankProcessing(true);
|
||||
await executePrank(
|
||||
username,
|
||||
selectedPrank.id,
|
||||
prankTarget,
|
||||
prankServerId,
|
||||
);
|
||||
|
||||
playBuySound();
|
||||
showNotification(
|
||||
selectedPrank.globalDescription
|
||||
.replace('{username}', username)
|
||||
.replace('{targetPlayer}', prankTarget),
|
||||
'success',
|
||||
);
|
||||
|
||||
setPrankDialogOpen(false);
|
||||
} catch (e) {
|
||||
showNotification(
|
||||
e instanceof Error ? e.message : 'Ошибка выполнения',
|
||||
'error',
|
||||
);
|
||||
} finally {
|
||||
setPrankProcessing(false);
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
fontFamily: 'Benzin-Bold',
|
||||
color: '#fff',
|
||||
background: GRADIENT,
|
||||
borderRadius: '999px',
|
||||
px: '1.6vw',
|
||||
}}
|
||||
>
|
||||
Подтвердить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Компонент с анимацией рулетки */}
|
||||
<CaseRoulette
|
||||
open={rouletteOpen}
|
||||
|
||||
Reference in New Issue
Block a user