From 56da7c75438a2fa9da210da7202380f99d6da0c8 Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Sat, 19 Jul 2025 01:36:33 +0500 Subject: [PATCH] add: shop capes and refactor cape card --- src/renderer/api.ts | 54 ++++++++++++ src/renderer/components/CapeCard.tsx | 117 +++++++++++++++++++++++++ src/renderer/pages/Profile.tsx | 80 ++++------------- src/renderer/pages/Shop.tsx | 126 ++++++++++++++++++++++++++- 4 files changed, 315 insertions(+), 62 deletions(-) create mode 100644 src/renderer/components/CapeCard.tsx diff --git a/src/renderer/api.ts b/src/renderer/api.ts index dd698bd..52438a2 100644 --- a/src/renderer/api.ts +++ b/src/renderer/api.ts @@ -30,6 +30,16 @@ export interface Cape { export type CapesResponse = Cape[]; +export interface StoreCape { + id: string; + name: string; + description: string; + price: number; + image_url: string; +} + +export type StoreCapesResponse = StoreCape[]; + export interface ApiError { message: string; details?: string; @@ -81,6 +91,50 @@ export async function fetchCapes(username: string): Promise { } } +export async function purchaseCape( + username: string, + cape_id: string, +): Promise { + // Создаем URL с query-параметрами + const url = new URL(`${API_BASE_URL}/store/purchase/cape`); + url.searchParams.append('username', username); + url.searchParams.append('cape_id', cape_id); + + const response = await fetch(url.toString(), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + // Не нужно отправлять тело запроса + // body: JSON.stringify({ + // username: username, + // cape_id: cape_id, + // }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + errorData.message || + errorData.detail?.toString() || + 'Не удалось купить плащ', + ); + } +} + +export async function fetchCapesStore(): Promise { + try { + const response = await fetch(`${API_BASE_URL}/store/capes`); + if (!response.ok) { + return []; + } + return await response.json(); + } catch (error) { + console.error('API ошибка:', error); + return []; + } +} + export async function activateCape( username: string, cape_id: string, diff --git a/src/renderer/components/CapeCard.tsx b/src/renderer/components/CapeCard.tsx new file mode 100644 index 0000000..3b24527 --- /dev/null +++ b/src/renderer/components/CapeCard.tsx @@ -0,0 +1,117 @@ +// src/renderer/components/CapeCard.tsx +import React from 'react'; +import { + Card, + CardMedia, + CardContent, + Typography, + CardActions, + Button, + Tooltip, + Box, + Chip, +} from '@mui/material'; + +// Тип для плаща с необязательными полями для обоих вариантов использования +export interface CapeCardProps { + cape: { + cape_id?: string; + id?: string; + cape_name?: string; + name?: string; + cape_description?: string; + description?: string; + image_url: string; + is_active?: boolean; + price?: number; + purchased_at?: string; + }; + mode: 'profile' | 'shop'; + onAction: (capeId: string) => void; + actionDisabled?: boolean; +} + +export default function CapeCard({ + cape, + mode, + onAction, + actionDisabled = false, +}: CapeCardProps) { + // Определяем текст и цвет кнопки в зависимости от режима + const getActionButton = () => { + if (mode === 'shop') { + return { + text: 'Купить', + color: 'primary', + }; + } else { + // Профиль + return cape.is_active + ? { text: 'Снять', color: 'error' } + : { text: 'Надеть', color: 'success' }; + } + }; + + const actionButton = getActionButton(); + + // В функции компонента добавьте нормализацию данных + const capeId = cape.cape_id || cape.id || ''; + const capeName = cape.cape_name || cape.name || ''; + const capeDescription = cape.cape_description || cape.description || ''; + + return ( + + + {/* Ценник для магазина */} + {mode === 'shop' && cape.price !== undefined && ( + + )} + + + + + {capeName} + + + + + + + + ); +} diff --git a/src/renderer/pages/Profile.tsx b/src/renderer/pages/Profile.tsx index 37ceb1d..c176ed2 100644 --- a/src/renderer/pages/Profile.tsx +++ b/src/renderer/pages/Profile.tsx @@ -20,13 +20,10 @@ import { MenuItem, Alert, CircularProgress, - Card, - CardContent, - CardMedia, - Tooltip, - CardActions, } from '@mui/material'; +import CapeCard from '../components/CapeCard'; + export default function Profile() { const fileInputRef = useRef(null); const [walkingSpeed, setWalkingSpeed] = useState(0.5); @@ -317,63 +314,24 @@ export default function Profile() { }} > Ваши плащи - + {capes.map((cape) => ( - - - - - - {cape.cape_name} - - - {cape.is_active ? ( - - - - ) : ( - - - - )} - - + ))} diff --git a/src/renderer/pages/Shop.tsx b/src/renderer/pages/Shop.tsx index 8bd2459..0e2e50b 100644 --- a/src/renderer/pages/Shop.tsx +++ b/src/renderer/pages/Shop.tsx @@ -1,3 +1,127 @@ +import { Box } from '@mui/material'; +import { Typography } from '@mui/material'; +import CapeCard from '../components/CapeCard'; +import { + Cape, + fetchCapes, + fetchCapesStore, + purchaseCape, + StoreCape, +} from '../api'; +import { useEffect, useState } from 'react'; + export default function Shop() { - return
Shop
; + const [storeCapes, setStoreCapes] = useState([]); + const [userCapes, setUserCapes] = useState([]); + const [username, setUsername] = useState(''); + const [uuid, setUuid] = useState(''); + const [loading, setLoading] = useState(false); + + // Функция для загрузки плащей из магазина + 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); + } catch (error) { + console.error('Ошибка при покупке плаща:', error); + } + }; + + // Загружаем данные при монтировании + 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)]).finally( + () => { + setLoading(false); + }, + ); + } + } + }, []); + + // Фильтруем плащи, которые уже куплены пользователем + const availableCapes = storeCapes.filter( + (storeCape) => + !userCapes.some((userCape) => userCape.cape_id === storeCape.id), + ); + + return ( + + Shop + {loading ? ( + Загрузка... + ) : ( + + Доступные плащи + {availableCapes.length > 0 ? ( + + {availableCapes.map((cape) => ( + + ))} + + ) : ( + У вас уже есть все доступные плащи! + )} + + )} + + ); }