import { Box, Typography, Button, Grid, Snackbar, Alert } from '@mui/material'; import { Cape, fetchCapes, fetchCapesStore, purchaseCape, StoreCape, Case, CaseItem, fetchCases, fetchCase, 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 BonusShopItem from '../components/BonusShopItem'; import ShopItem from '../components/ShopItem'; function getRarityByWeight( weight?: number, ): 'common' | 'rare' | 'epic' | 'legendary' { if (weight === undefined || weight === null) return 'common'; if (weight <= 5) return 'legendary'; if (weight <= 20) return 'epic'; if (weight <= 50) return 'rare'; return 'common'; } function getRarityColor(weight?: number): string { const rarity = getRarityByWeight(weight); switch (rarity) { case 'legendary': return 'rgba(255, 215, 0, 1)'; // золотой case 'epic': return 'rgba(186, 85, 211, 1)'; // фиолетовый case 'rare': return 'rgba(65, 105, 225, 1)'; // синий case 'common': default: return 'rgba(255, 255, 255, 0.25)'; // сероватый } } export default function Shop() { const [storeCapes, setStoreCapes] = useState([]); const [userCapes, setUserCapes] = useState([]); const [username, setUsername] = useState(''); const [uuid, setUuid] = useState(''); const [loading, setLoading] = useState(false); const [playerSkinUrl, setPlayerSkinUrl] = useState(''); // Прокачка const [bonusTypes, setBonusTypes] = useState([]); const [userBonuses, setUserBonuses] = useState([]); const [bonusesLoading, setBonusesLoading] = useState(false); const [processingBonusIds, setProcessingBonusIds] = useState([]); // Кейсы const [cases, setCases] = useState([]); const [casesLoading, setCasesLoading] = useState(false); // Онлайн/сервер (по аналогии с Marketplace) const [isOnline, setIsOnline] = useState(false); const [playerServer, setPlayerServer] = useState(null); const [onlineCheckLoading, setOnlineCheckLoading] = useState(true); // Рулетка const [isOpening, setIsOpening] = useState(false); const [selectedCase, setSelectedCase] = useState(null); const [rouletteOpen, setRouletteOpen] = useState(false); const [rouletteCaseItems, setRouletteCaseItems] = useState([]); const [rouletteReward, setRouletteReward] = useState(null); // Уведомления const [notification, setNotification] = useState<{ open: boolean; message: string; type: 'success' | 'error'; }>({ open: false, message: '', 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); setPlayerSkinUrl(player.skin_url); } catch (error) { console.error('Ошибка при получении скина игрока:', error); setPlayerSkinUrl(''); } }; // Функция для загрузки плащей из магазина const loadStoreCapes = async () => { try { const capes = await fetchCapesStore(); setStoreCapes(capes); } catch (error) { console.error('Ошибка при получении плащей магазина:', error); setStoreCapes([]); } }; // Функция для загрузки плащей пользователя const loadUserCapes = async (username: string) => { try { const userCapes = await fetchCapes(username); setUserCapes(userCapes); } catch (error) { console.error('Ошибка при получении плащей пользователя:', error); setUserCapes([]); } }; const handlePurchaseCape = async (cape_id: string) => { try { await purchaseCape(username, cape_id); await loadUserCapes(username); setNotification({ open: true, message: 'Плащ успешно куплен!', type: 'success', }); } catch (error) { console.error('Ошибка при покупке плаща:', error); setNotification({ open: true, message: error instanceof Error ? error.message : 'Ошибка при покупке плаща', type: 'error', }); } }; // Загрузка кейсов const loadCases = async () => { try { setCasesLoading(true); const casesData = await fetchCases(); setCases(casesData); } catch (error) { console.error('Ошибка при получении кейсов:', error); setCases([]); } finally { setCasesLoading(false); } }; // Проверка онлайна игрока (по аналогии с Marketplace.tsx) const checkPlayerStatus = async () => { if (!username) return; try { setOnlineCheckLoading(true); const { online, server } = await getPlayerServer(username); setIsOnline(online); setPlayerServer(server || null); } catch (error) { console.error('Ошибка при проверке онлайн-статуса:', error); setIsOnline(false); setPlayerServer(null); } finally { setOnlineCheckLoading(false); } }; // Загружаем базовые данные при монтировании useEffect(() => { const savedConfig = localStorage.getItem('launcher_config'); if (savedConfig) { const config = JSON.parse(savedConfig); if (config.uuid && config.username) { setUsername(config.username); setUuid(config.uuid); setLoading(true); Promise.all([ loadStoreCapes(), loadUserCapes(config.username), loadCases(), loadPlayerSkin(config.uuid), loadBonuses(config.username), ]) .catch((err) => console.error(err)) .finally(() => { setLoading(false); }); } } }, []); // Проверяем онлайн после того, как знаем username useEffect(() => { if (username) { checkPlayerStatus(); } }, [username]); const withProcessing = async (id: string, fn: () => Promise) => { 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) => !userCapes.some((userCape) => userCape.cape_id === storeCape.id), ); const handleOpenCase = async (caseData: Case) => { if (!username) { setNotification({ open: true, message: 'Не найдено имя игрока. Авторизуйтесь в лаунчере.', type: 'error', }); return; } if (!isOnline || !playerServer) { setNotification({ open: true, message: 'Для открытия кейсов необходимо находиться на сервере в игре.', type: 'error', }); return; } if (isOpening) return; try { setIsOpening(true); // 1. получаем полный кейс const fullCase = await fetchCase(caseData.id); const caseItems: CaseItem[] = fullCase.items || []; setSelectedCase(fullCase); // 2. открываем кейс на бэке const result = await openCase(fullCase.id, username, playerServer.id); // 3. сохраняем данные для рулетки setRouletteCaseItems(caseItems); setRouletteReward(result.reward); setRouletteOpen(true); // 4. уведомление setNotification({ open: true, message: result.message || 'Кейс открыт!', type: 'success', }); setIsOpening(false); } catch (error) { console.error('Ошибка при открытии кейса:', error); setNotification({ open: true, message: error instanceof Error ? error.message : 'Ошибка при открытии кейса', type: 'error', }); setIsOpening(false); } }; const handleCloseNotification = () => { setNotification((prev) => ({ ...prev, open: false })); }; const handleCloseRoulette = () => { setRouletteOpen(false); }; return ( {(loading || onlineCheckLoading) && ( )} {!loading && !onlineCheckLoading && ( {/* Блок прокачки */} Прокачка {bonusesLoading ? ( ) : bonusTypes.length > 0 ? ( {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 ( handlePurchaseBonus(bt.id) : undefined } onUpgrade={ owned ? () => handleUpgradeBonus(userBonus!.id) : undefined } onToggleActive={ owned ? () => handleToggleBonusActivation(userBonus!.id) : undefined } /> ); })} ) : ( Прокачка временно недоступна. )} {/* Блок кейсов */} Кейсы {!isOnline && ( )} {!isOnline ? ( Для открытия кейсов вам необходимо находиться на одном из серверов игры. Зайдите в игру и нажмите кнопку «Обновить». ) : casesLoading ? ( ) : cases.length > 0 ? ( {cases.map((c) => ( handleOpenCase(c)} /> ))} ) : ( Кейсы временно недоступны. )} {/* Блок плащей (как был) */} {/* Блок плащей */} Плащи {availableCapes.length > 0 ? ( {availableCapes.map((cape) => ( handlePurchaseCape(cape.id)} /> ))} ) : ( У вас уже есть все доступные плащи! )} )} {/* Компонент с анимацией рулетки */} {/* Уведомления */} {notification.message} ); }