add bonuses in shop
This commit is contained in:
@ -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={{
|
||||
|
||||
Reference in New Issue
Block a user