refacto: découpage composants — 5 extractions
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 34s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 34s
- MonsterCard, CombatViews (Log+Multi+History), CreateCharacter - RarityBadge + RarityDot partagés (Guide, Drawer, pages) - CombatPage 341→215 lignes (−37%) - DashboardPage 368→307 lignes (−17%) - 9 composants dans components/
This commit is contained in:
70
frontend/src/components/CreateCharacter.tsx
Normal file
70
frontend/src/components/CreateCharacter.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useState } from 'react';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { characterApi } from '../api/endpoints';
|
||||
import { STAT_LABELS } from '../constants';
|
||||
|
||||
const STATS = ['force', 'agilite', 'intelligence', 'chance', 'vitalite'] as const;
|
||||
|
||||
export function CreateCharacter() {
|
||||
const qc = useQueryClient();
|
||||
const [name, setName] = useState('');
|
||||
const [pts, setPts] = useState<Record<string, number>>({ force:1, agilite:1, intelligence:1, chance:1, vitalite:1 });
|
||||
const used = Object.values(pts).reduce((a, b) => a + b, 0) - 5;
|
||||
const remaining = 5 - used;
|
||||
|
||||
const mut = useMutation({
|
||||
mutationFn: () => characterApi.create(name, pts),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['character'] }),
|
||||
});
|
||||
|
||||
const adjust = (stat: string, delta: number) => {
|
||||
const next = (pts[stat] ?? 1) + delta;
|
||||
if (next < 1 || next > 10) return;
|
||||
if (delta > 0 && remaining <= 0) return;
|
||||
setPts(p => ({ ...p, [stat]: next }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 420, margin: '4rem auto' }}>
|
||||
<div className="card card-gold" style={{ padding: '1.5rem' }}>
|
||||
<h2 style={{ margin: '0 0 4px', color: '#f4c94e', fontSize: 20 }}>Créer ton personnage</h2>
|
||||
<p style={{ margin: '0 0 1.25rem', color: '#6b7a99', fontSize: 13 }}>
|
||||
{remaining > 0 ? `${remaining} point${remaining > 1 ? 's' : ''} à répartir` : 'Tous les points répartis'}
|
||||
</p>
|
||||
|
||||
<input
|
||||
className="input-rpg"
|
||||
placeholder="Nom du personnage"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
style={{ marginBottom: '1rem' }}
|
||||
maxLength={30}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: '1.25rem' }}>
|
||||
{STATS.map(s => (
|
||||
<div key={s} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<span style={{ fontSize: 13, width: 110, color: '#dce4f0' }}>{STAT_LABELS[s]}</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<button className="btn btn-ghost" style={{ padding: '0.15rem 0.5rem', fontSize: 14 }} onClick={() => adjust(s, -1)}>−</button>
|
||||
<span style={{ width: 20, textAlign: 'center', fontWeight: 700, color: '#f4c94e' }}>{pts[s]}</span>
|
||||
<button className="btn btn-ghost" style={{ padding: '0.15rem 0.5rem', fontSize: 14 }} onClick={() => adjust(s, +1)}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="btn btn-gold"
|
||||
style={{ width: '100%' }}
|
||||
disabled={!name.trim() || remaining !== 0 || mut.isPending}
|
||||
onClick={() => mut.mutate()}
|
||||
>
|
||||
{mut.isPending ? 'Création…' : 'Commencer l\'aventure ⚔️'}
|
||||
</button>
|
||||
|
||||
{mut.isError && <p style={{ color: '#e84040', fontSize: 12, marginTop: 8 }}>{(mut.error as Error).message}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user