add inventory and change cases

This commit is contained in:
aurinex
2025-12-16 15:30:40 +05:00
parent 6db213d602
commit c15c36891e
10 changed files with 640 additions and 96 deletions

View File

@ -1,4 +1,7 @@
import { Box, Typography, Button, Grid, Snackbar, Alert } from '@mui/material';
import {
Box, Typography, Button, Grid,
FormControl, Select, MenuItem, InputLabel
} from '@mui/material';
import {
Cape,
fetchCapes,
@ -30,6 +33,7 @@ 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 { translateServer } from '../utils/serverTranslator';
function getRarityByWeight(
weight?: number,
@ -66,6 +70,8 @@ export default function Shop() {
const [playerSkinUrl, setPlayerSkinUrl] = useState<string>('');
const [selectedCaseServerIp, setSelectedCaseServerIp] = useState<string>('');
// Уведомления
const [notifOpen, setNotifOpen] = useState(false);
@ -345,6 +351,28 @@ export default function Shop() {
});
};
const caseServers = Array.from(
new Set(
(cases || [])
.flatMap((c) => c.server_ips || [])
.filter(Boolean),
),
);
useEffect(() => {
if (caseServers.length > 0) {
// если игрок онлайн — по умолчанию его сервер, если он есть в кейсах
const preferred =
playerServer?.ip && caseServers.includes(playerServer.ip)
? playerServer.ip
: caseServers[0];
setSelectedCaseServerIp(preferred);
}
}, [caseServers.length, playerServer?.ip]);
const filteredCases = cases;
// Фильтруем плащи, которые уже куплены пользователем
const availableCapes = storeCapes.filter(
(storeCape) =>
@ -352,62 +380,72 @@ export default function Shop() {
);
const handleOpenCase = async (caseData: Case) => {
if (!username) {
if (!isNotificationsEnabled()) return;
setNotifMsg('Не найдено имя игрока. Авторизуйтесь в лаунчере!');
setNotifSeverity('error');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
}
if (!username) {
if (!isNotificationsEnabled()) return;
setNotifMsg('Не найдено имя игрока. Авторизуйтесь в лаунчере!');
setNotifSeverity('error');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
return;
}
if (!isOnline || !playerServer) {
if (!isNotificationsEnabled()) return;
setNotifMsg('Для открытия кейсов необходимо находиться на сервере в игре!');
setNotifSeverity('error');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
return;
}
if (!selectedCaseServerIp) {
if (!isNotificationsEnabled()) return;
setNotifMsg('Выберите сервер для открытия кейса!');
setNotifSeverity('warning');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
return;
}
if (isOpening) return;
const allowedIps = caseData.server_ips || [];
if (allowedIps.length > 0 && !allowedIps.includes(selectedCaseServerIp)) {
if (!isNotificationsEnabled()) return;
setNotifMsg(
`Этот кейс доступен на: ${allowedIps
.map((ip) => translateServer(`Server ${ip}`))
.join(', ')}`,
);
setNotifSeverity('warning');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
return;
}
try {
setIsOpening(true);
if (isOpening) return;
// 1. получаем полный кейс
const fullCase = await fetchCase(caseData.id);
const caseItems: CaseItem[] = fullCase.items || [];
setSelectedCase(fullCase);
try {
setIsOpening(true);
// 2. открываем кейс на бэке
const result = await openCase(fullCase.id, username, playerServer.id);
const fullCase = await fetchCase(caseData.id);
const caseItems: CaseItem[] = fullCase.items || [];
setSelectedCase(fullCase);
// 3. сохраняем данные для рулетки
setRouletteCaseItems(caseItems);
setRouletteReward(result.reward);
setRouletteOpen(true);
playBuySound();
// ✅ открываем на выбранном сервере (даже если игрок не на сервере)
const result = await openCase(fullCase.id, username, selectedCaseServerIp);
setIsOpening(false);
setRouletteCaseItems(caseItems);
setRouletteReward(result.reward);
setRouletteOpen(true);
playBuySound();
// 4. уведомление
if (!isNotificationsEnabled()) return;
setNotifMsg('Кейс открыт!');
setNotifSeverity('success');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
} catch (error) {
console.error('Ошибка при открытии кейса:', error);
if (!isNotificationsEnabled()) return;
setNotifMsg('Кейс открыт!');
setNotifSeverity('success');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
} catch (error) {
console.error('Ошибка при открытии кейса:', error);
setIsOpening(false);
if (!isNotificationsEnabled()) return;
setNotifMsg('Ошибка при открытии кейса!');
setNotifSeverity('error');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
}
};
if (!isNotificationsEnabled()) return;
setNotifMsg(String(error instanceof Error ? error.message : 'Ошибка при открытии кейса!'));
setNotifSeverity('error');
setNotifPos({ vertical: 'top', horizontal: 'center' });
setNotifOpen(true);
} finally {
setIsOpening(false);
}
};
const handleCloseNotification = () => {
setNotification((prev) => ({ ...prev, open: false }));
@ -570,8 +608,6 @@ export default function Shop() {
>
Кейсы
</Typography>
{!isOnline && (
<Button
disableRipple
disableFocusRipple
@ -599,22 +635,76 @@ export default function Shop() {
>
Обновить
</Button>
)}
{caseServers.length > 0 && (
<FormControl size="small" sx={{ minWidth: 220 }}>
<InputLabel id="cases-server-label" sx={{ fontFamily: 'Benzin-Bold', color: 'rgba(255,255,255,0.75)' }}>
Сервер
</InputLabel>
<Select
labelId="cases-server-label"
label="Сервер"
value={selectedCaseServerIp}
onChange={(e) => setSelectedCaseServerIp(String(e.target.value))}
MenuProps={{
PaperProps: {
sx: {
bgcolor: 'rgba(10,10,20,0.96)',
border: '1px solid rgba(255,255,255,0.10)',
borderRadius: '1vw',
backdropFilter: 'blur(14px)',
'& .MuiMenuItem-root': {
color: 'rgba(255,255,255,0.9)',
fontFamily: 'Benzin-Bold',
},
'& .MuiMenuItem-root.Mui-selected': {
backgroundColor: 'rgba(242,113,33,0.16)',
},
'& .MuiMenuItem-root:hover': {
backgroundColor: 'rgba(233,64,205,0.14)',
},
},
},
}}
sx={{
borderRadius: '999px',
bgcolor: 'rgba(255,255,255,0.04)',
color: 'rgba(255,255,255,0.92)',
fontFamily: 'Benzin-Bold',
'& .MuiSelect-select': {
py: '0.9vw',
px: '1.2vw',
},
'& .MuiOutlinedInput-notchedOutline': {
borderColor: 'rgba(255,255,255,0.14)',
},
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: 'rgba(242,113,33,0.55)',
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: 'rgba(233,64,205,0.65)',
borderWidth: '2px',
},
'& .MuiSelect-icon': {
color: 'rgba(255,255,255,0.75)',
},
}}
>
{caseServers.map((ip) => (
<MenuItem key={ip} value={ip}>
{translateServer(`Server ${ip}`)}
</MenuItem>
))}
</Select>
</FormControl>
)}
</Box>
{!isOnline ? (
<Typography variant="body1" color="error" sx={{ mb: 2 }}>
Для открытия кейсов вам необходимо находиться на одном из
серверов игры. Зайдите в игру и нажмите кнопку «Обновить».
</Typography>
) : casesLoading ? (
<FullScreenLoader
fullScreen={false}
message="Загрузка кейсов..."
/>
{casesLoading ? (
<FullScreenLoader fullScreen={false} message="Загрузка кейсов..." />
) : cases.length > 0 ? (
<Grid container spacing={2} sx={{ mb: 4 }}>
{cases.map((c) => (
{filteredCases.map((c) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={c.id}>
<ShopItem
type="case"
@ -625,7 +715,7 @@ export default function Shop() {
price={c.price}
itemsCount={c.items_count}
isOpening={isOpening && selectedCase?.id === c.id}
disabled={!isOnline || isOpening}
disabled={isOpening || !selectedCaseServerIp}
onClick={() => handleOpenCase(c)}
/>
</Grid>