diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 88a1e5b..91d9545 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -55,5 +55,6 @@ async function request(path: string, options?: RequestInit): Promise { export const api = { get: (path: string) => request(path), post: (path: string, body?: unknown) => request(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined }), + put: (path: string, body?: unknown) => request(path, { method: 'PUT', body: body ? JSON.stringify(body) : undefined }), del: (path: string) => request(path, { method: 'DELETE' }), }; diff --git a/frontend/src/api/endpoints.ts b/frontend/src/api/endpoints.ts index cccd885..b89017b 100644 --- a/frontend/src/api/endpoints.ts +++ b/frontend/src/api/endpoints.ts @@ -20,6 +20,7 @@ export const characterApi = { distributeStats: (stats: Record) => api.post('/characters/stats', stats), rest: () => api.post<{ hpBefore: number; hpAfter: number; hpMax: number; healed: number }>('/characters/rest'), + setTitle: (title: string | null) => api.put('/profile/title', { title }), }; // Combat diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 1eab148..39b5dfc 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { characterApi, itemApi } from '../api/endpoints'; +import { api } from '../api/client'; import { Bar } from '../components/Bar'; import { Zap, Heart, Star, Coins, Sword, Shield, BedDouble } from 'lucide-react'; @@ -134,6 +135,58 @@ function StatDistributor({ char }: { char: any }) { ); } +function TitleSelector({ char }: { char: any }) { + const qc = useQueryClient(); + const { data: achievements } = useQuery({ + queryKey: ['achievements'], + queryFn: () => api.get('/achievements/me'), + }); + + const titleMut = useMutation({ + mutationFn: (title: string | null) => characterApi.setTitle(title), + onSuccess: () => qc.invalidateQueries({ queryKey: ['character'] }), + }); + + // Collect unlocked titles from claimed achievements + const unlockedTitles: string[] = []; + if (achievements) { + for (const a of achievements) { + if (a.claimed && a.rewardTitle) { + unlockedTitles.push(a.rewardTitle); + } + } + } + + if (unlockedTitles.length === 0) return null; + + return ( +
+

🏅 Titre actif

+
+ + {unlockedTitles.map(t => ( + + ))} +
+
+ ); +} + function CombatStatsPanel({ char }: { char: any }) { const { data: inventory } = useQuery({ queryKey: ['inventory'], queryFn: itemApi.inventory }); @@ -204,6 +257,9 @@ export function DashboardPage() {

{char.name}

Niveau {char.level} + {char.activeTitle && ( + « {char.activeTitle} » + )}
@@ -301,6 +357,9 @@ export function DashboardPage() {
)} + {/* Titres */} + + {/* Équipement résumé */} diff --git a/frontend/src/pages/InventoryPage.tsx b/frontend/src/pages/InventoryPage.tsx index 2bb12e6..897c495 100644 --- a/frontend/src/pages/InventoryPage.tsx +++ b/frontend/src/pages/InventoryPage.tsx @@ -16,7 +16,10 @@ function ItemCard({ ci, onEquip, onUnequip, onSell, selling }: { const forgeBonusDEF = item.type === 'armor' ? ci.forgeLevel * 2 : 0; const totalATK = item.attackBonus + forgeBonusATK; const totalDEF = item.defenseBonus + forgeBonusDEF; - const sellPrice = Math.floor((item as any).buyPrice * 0.4) || 0; + const FORGE_COSTS: Record = { 1: 50, 2: 100, 3: 250, 4: 500, 5: 1000 }; + let forgeInvestment = 0; + for (let i = 1; i <= ci.forgeLevel; i++) forgeInvestment += FORGE_COSTS[i] ?? 0; + const sellPrice = Math.floor(((item as any).buyPrice || 0) * 0.4 + forgeInvestment * 0.5); const bonuses = [ totalATK > 0 && `+${totalATK} ATK${forgeBonusATK > 0 ? ` (${item.attackBonus}+${forgeBonusATK})` : ''}`, diff --git a/src/shop/shop.service.ts b/src/shop/shop.service.ts index 8a74c8c..0b040bc 100644 --- a/src/shop/shop.service.ts +++ b/src/shop/shop.service.ts @@ -153,7 +153,13 @@ export class ShopService { if (!charItem) throw new NotFoundException('Item non trouvé dans l\'inventaire'); if (charItem.equipped) throw new BadRequestException('Déséquipez l\'item avant de le vendre'); - const sellPrice = Math.floor(charItem.item.buyPrice * SELL_RATIO); + // Prix de vente = base + investissement forge (coûts cumulés * 50%) + const FORGE_GOLD_COST: Record = { 1: 50, 2: 100, 3: 250, 4: 500, 5: 1000 }; + let forgeInvestment = 0; + for (let i = 1; i <= charItem.forgeLevel; i++) { + forgeInvestment += FORGE_GOLD_COST[i] ?? 0; + } + const sellPrice = Math.floor(charItem.item.buyPrice * SELL_RATIO + forgeInvestment * 0.5); const char = await manager .getRepository(Character)