150 lines
4.8 KiB
TypeScript
150 lines
4.8 KiB
TypeScript
import { useState } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { useAuth } from '../hooks/useAuth'
|
|
import { GameButton } from './common/GameButton'
|
|
import './CharacterCreation.css'
|
|
|
|
function CharacterCreation() {
|
|
const { createCharacter } = useAuth()
|
|
const [name, setName] = useState('')
|
|
const [stats, setStats] = useState({
|
|
strength: 10,
|
|
agility: 10,
|
|
endurance: 10,
|
|
intellect: 10
|
|
})
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState('')
|
|
const navigate = useNavigate()
|
|
|
|
const totalPoints = 40
|
|
const pointsUsed = stats.strength + stats.agility + stats.endurance + stats.intellect
|
|
const pointsRemaining = totalPoints - pointsUsed
|
|
|
|
const handleStatChange = (stat: keyof typeof stats, delta: number) => {
|
|
const newValue = stats[stat] + delta
|
|
if (newValue < 5 || newValue > 20) return
|
|
if (delta > 0 && pointsRemaining <= 0) return
|
|
|
|
setStats({
|
|
...stats,
|
|
[stat]: newValue
|
|
})
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (pointsRemaining !== 0) {
|
|
setError('You must use all attribute points')
|
|
return
|
|
}
|
|
|
|
setLoading(true)
|
|
setError('')
|
|
|
|
try {
|
|
await createCharacter({ ...stats, name })
|
|
navigate('/game')
|
|
} catch (err: any) {
|
|
setError(err.response?.data?.detail || 'Failed to create character')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="char-creation-page">
|
|
<div className="char-creation-container">
|
|
<div className="char-creation-card game-panel">
|
|
<h1 className="creation-title">Character Creation</h1>
|
|
<p className="creation-subtitle">Forge your survivor for the wasteland</p>
|
|
|
|
<form onSubmit={handleSubmit} className="creation-form">
|
|
<div className="form-group-creation">
|
|
<label htmlFor="name">Survivor Name</label>
|
|
<input
|
|
type="text"
|
|
id="name"
|
|
className="game-input"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="Enter survivor name..."
|
|
required
|
|
disabled={loading}
|
|
maxLength={20}
|
|
/>
|
|
</div>
|
|
|
|
<div className="attributes-section">
|
|
<div className="attributes-header">
|
|
<h3>Attributes</h3>
|
|
<div className={`points-remaining ${pointsRemaining === 0 ? 'zero' : ''}`}>
|
|
Points Remaining: {pointsRemaining}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="attributes-grid-creation">
|
|
{(Object.keys(stats) as Array<keyof typeof stats>).map((stat) => (
|
|
<div key={stat} className="attribute-control game-panel">
|
|
<div className="attr-info">
|
|
<span className="attr-name">{stat}</span>
|
|
<span className="attr-value">{stats[stat]}</span>
|
|
</div>
|
|
<div className="attr-buttons">
|
|
<GameButton
|
|
variant="secondary"
|
|
size="sm"
|
|
onClick={(e) => {
|
|
e.preventDefault()
|
|
handleStatChange(stat, -1)
|
|
}}
|
|
disabled={loading || stats[stat] <= 5}
|
|
>
|
|
-
|
|
</GameButton>
|
|
<GameButton
|
|
variant="secondary"
|
|
size="sm"
|
|
onClick={(e) => {
|
|
e.preventDefault()
|
|
handleStatChange(stat, 1)
|
|
}}
|
|
disabled={loading || stats[stat] >= 20 || pointsRemaining <= 0}
|
|
>
|
|
+
|
|
</GameButton>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{error && <div className="error-message-creation">{error}</div>}
|
|
|
|
<div className="creation-actions">
|
|
<GameButton
|
|
variant="secondary"
|
|
onClick={() => navigate('/characters')}
|
|
disabled={loading}
|
|
>
|
|
Cancel
|
|
</GameButton>
|
|
<GameButton
|
|
variant="primary"
|
|
size="lg"
|
|
className="create-submit-btn"
|
|
disabled={loading || pointsRemaining !== 0 || !name.trim()}
|
|
onClick={() => { }} // Handled by form submit
|
|
>
|
|
{loading ? 'Forging...' : 'Create Survivor'}
|
|
</GameButton>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default CharacterCreation
|