All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 32s
- RarityBadge, RarityDot → Tailwind classes - MonsterCard → flex/text-rpg-* classes - CreateCharacter → full Tailwind (max-w, grid, gap) - Onboarding → Tailwind + responsive grid-cols-1 mobile - CombatViews (Log+Multi+History) → Tailwind - NotFoundPage → full Tailwind - Pattern posé : couleurs dynamiques en style, layout en classes
69 lines
2.6 KiB
TypeScript
69 lines
2.6 KiB
TypeScript
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 className="max-w-md mx-auto mt-16">
|
||
<div className="card card-gold p-6">
|
||
<h2 className="text-rpg-gold text-xl font-bold mb-1">Créer ton personnage</h2>
|
||
<p className="text-rpg-muted text-sm mb-5">
|
||
{remaining > 0 ? `${remaining} point${remaining > 1 ? 's' : ''} à répartir` : 'Tous les points répartis'}
|
||
</p>
|
||
|
||
<input
|
||
className="input-rpg mb-4"
|
||
placeholder="Nom du personnage"
|
||
value={name}
|
||
onChange={e => setName(e.target.value)}
|
||
maxLength={30}
|
||
/>
|
||
|
||
<div className="flex flex-col gap-2 mb-5">
|
||
{STATS.map(s => (
|
||
<div key={s} className="flex items-center justify-between">
|
||
<span className="text-sm text-rpg-text w-28">{STAT_LABELS[s]}</span>
|
||
<div className="flex items-center gap-2">
|
||
<button className="btn btn-ghost px-2 py-0.5 text-sm" onClick={() => adjust(s, -1)}>−</button>
|
||
<span className="w-5 text-center font-bold text-rpg-gold">{pts[s]}</span>
|
||
<button className="btn btn-ghost px-2 py-0.5 text-sm" onClick={() => adjust(s, +1)}>+</button>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<button
|
||
className="btn btn-gold w-full"
|
||
disabled={!name.trim() || remaining !== 0 || mut.isPending}
|
||
onClick={() => mut.mutate()}
|
||
>
|
||
{mut.isPending ? 'Création…' : 'Commencer l\'aventure ⚔️'}
|
||
</button>
|
||
|
||
{mut.isError && <p className="text-rpg-red text-xs mt-2">{(mut.error as Error).message}</p>}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|