add bonuses in shop

This commit is contained in:
2025-12-07 20:11:41 +05:00
parent 3e03c1024d
commit 833444df2e
3 changed files with 705 additions and 15 deletions

View File

@ -1,17 +1,4 @@
import {
Box,
Typography,
Button,
Grid,
Card,
CardMedia,
CardContent,
Snackbar,
Alert,
Dialog,
DialogContent,
} from '@mui/material';
import CapeCard from '../components/CapeCard';
import { Box, Typography, Button, Grid, Snackbar, Alert } from '@mui/material';
import {
Cape,
fetchCapes,
@ -25,12 +12,19 @@ import {
openCase,
Server,
fetchPlayer,
BonusType,
UserBonus,
fetchBonusTypes,
fetchUserBonuses,
purchaseBonus,
upgradeBonus,
toggleBonusActivation,
} from '../api';
import { useEffect, useState } from 'react';
import { FullScreenLoader } from '../components/FullScreenLoader';
import { getPlayerServer } from '../utils/playerOnlineCheck';
import CaseRoulette from '../components/CaseRoulette';
import CoinsDisplay from '../components/CoinsDisplay';
import BonusShopItem from '../components/BonusShopItem';
import ShopItem from '../components/ShopItem';
function getRarityByWeight(
@ -68,6 +62,13 @@ export default function Shop() {
const [playerSkinUrl, setPlayerSkinUrl] = useState<string>('');
// Прокачка
const [bonusTypes, setBonusTypes] = useState<BonusType[]>([]);
const [userBonuses, setUserBonuses] = useState<UserBonus[]>([]);
const [bonusesLoading, setBonusesLoading] = useState<boolean>(false);
const [processingBonusIds, setProcessingBonusIds] = useState<string[]>([]);
// Кейсы
const [cases, setCases] = useState<Case[]>([]);
const [casesLoading, setCasesLoading] = useState<boolean>(false);
@ -96,6 +97,30 @@ export default function Shop() {
type: 'success',
});
const loadBonuses = async (username: string) => {
try {
setBonusesLoading(true);
const [types, user] = await Promise.all([
fetchBonusTypes(),
fetchUserBonuses(username),
]);
setBonusTypes(types);
setUserBonuses(user);
} catch (error) {
console.error('Ошибка при получении прокачек:', error);
setNotification({
open: true,
message:
error instanceof Error
? error.message
: 'Ошибка при загрузке прокачки',
type: 'error',
});
} finally {
setBonusesLoading(false);
}
};
const loadPlayerSkin = async (uuid: string) => {
try {
const player = await fetchPlayer(uuid);
@ -196,6 +221,7 @@ export default function Shop() {
loadUserCapes(config.username),
loadCases(),
loadPlayerSkin(config.uuid),
loadBonuses(config.username),
])
.catch((err) => console.error(err))
.finally(() => {
@ -212,6 +238,95 @@ export default function Shop() {
}
}, [username]);
const withProcessing = async (id: string, fn: () => Promise<void>) => {
setProcessingBonusIds((prev) => [...prev, id]);
try {
await fn();
} finally {
setProcessingBonusIds((prev) => prev.filter((x) => x !== id));
}
};
const handlePurchaseBonus = async (bonusTypeId: string) => {
if (!username) {
setNotification({
open: true,
message: 'Не найдено имя игрока. Авторизуйтесь в лаунчере.',
type: 'error',
});
return;
}
await withProcessing(bonusTypeId, async () => {
try {
const res = await purchaseBonus(username, bonusTypeId);
setNotification({
open: true,
message: res.message || 'Прокачка успешно куплена!',
type: 'success',
});
await loadBonuses(username);
} catch (error) {
console.error('Ошибка при покупке прокачки:', error);
setNotification({
open: true,
message:
error instanceof Error
? error.message
: 'Ошибка при покупке прокачки',
type: 'error',
});
}
});
};
const handleUpgradeBonus = async (bonusId: string) => {
if (!username) return;
await withProcessing(bonusId, async () => {
try {
await upgradeBonus(username, bonusId);
setNotification({
open: true,
message: 'Бонус улучшен!',
type: 'success',
});
await loadBonuses(username);
} catch (error) {
console.error('Ошибка при улучшении бонуса:', error);
setNotification({
open: true,
message:
error instanceof Error
? error.message
: 'Ошибка при улучшении бонуса',
type: 'error',
});
}
});
};
const handleToggleBonusActivation = async (bonusId: string) => {
if (!username) return;
await withProcessing(bonusId, async () => {
try {
await toggleBonusActivation(username, bonusId);
await loadBonuses(username);
} catch (error) {
console.error('Ошибка при переключении бонуса:', error);
setNotification({
open: true,
message:
error instanceof Error
? error.message
: 'Ошибка при переключении бонуса',
type: 'error',
});
}
});
};
// Фильтруем плащи, которые уже куплены пользователем
const availableCapes = storeCapes.filter(
(storeCape) =>
@ -312,6 +427,87 @@ export default function Shop() {
paddingRight: '5vw',
}}
>
{/* Блок прокачки */}
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Typography variant="h6">Прокачка</Typography>
{bonusesLoading ? (
<FullScreenLoader
fullScreen={false}
message="Загрузка прокачки..."
/>
) : bonusTypes.length > 0 ? (
<Grid container spacing={2} sx={{ mb: 4 }}>
{bonusTypes.map((bt) => {
const userBonus = userBonuses.find(
(ub) => ub.bonus_type_id === bt.id,
);
const owned = !!userBonus;
const level = owned ? userBonus!.level : 0;
const effectValue = owned
? userBonus!.effect_value
: bt.base_effect_value;
const nextEffectValue =
owned && userBonus!.can_upgrade
? bt.base_effect_value +
userBonus!.level * bt.effect_increment
: undefined;
const isActive = owned ? userBonus!.is_active : false;
const isPermanent = owned
? userBonus!.is_permanent
: bt.duration === 0;
const cardId = owned ? userBonus!.id : bt.id;
const processing = processingBonusIds.includes(cardId);
return (
<Grid item xs={12} sm={6} md={4} lg={3} key={bt.id}>
<BonusShopItem
id={cardId}
name={bt.name}
description={bt.description}
imageUrl={bt.image_url}
level={level}
effectValue={effectValue}
nextEffectValue={nextEffectValue}
price={bt.price}
upgradePrice={bt.upgrade_price}
canUpgrade={userBonus?.can_upgrade ?? false}
mode={owned ? 'upgrade' : 'buy'}
isActive={isActive}
isPermanent={isPermanent}
disabled={processing}
onBuy={
!owned ? () => handlePurchaseBonus(bt.id) : undefined
}
onUpgrade={
owned
? () => handleUpgradeBonus(userBonus!.id)
: undefined
}
onToggleActive={
owned
? () => handleToggleBonusActivation(userBonus!.id)
: undefined
}
/>
</Grid>
);
})}
</Grid>
) : (
<Typography>Прокачка временно недоступна.</Typography>
)}
</Box>
{/* Блок кейсов */}
<Box
sx={{