This commit is contained in:
Joan
2025-11-27 16:27:01 +01:00
parent 33cc9586c2
commit 81f8912059
304 changed files with 56149 additions and 10122 deletions

View File

@@ -0,0 +1,265 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '../hooks/useAuth'
import './CharacterCreation.css'
function CharacterCreation() {
const { createCharacter } = useAuth()
const navigate = useNavigate()
const [name, setName] = useState('')
const [strength, setStrength] = useState(0)
const [agility, setAgility] = useState(0)
const [endurance, setEndurance] = useState(0)
const [intellect, setIntellect] = useState(0)
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const TOTAL_POINTS = 20
const usedPoints = strength + agility + endurance + intellect
const remainingPoints = TOTAL_POINTS - usedPoints
const calculateHP = (str: number) => 30 + (str * 2)
const calculateStamina = (end: number) => 20 + (end * 1)
const handleStatChange = (
stat: 'strength' | 'agility' | 'endurance' | 'intellect',
value: number
) => {
// Prevent negative values
if (value < 0) return
const currentTotal = strength + agility + endurance + intellect
const otherStats = currentTotal - (stat === 'strength' ? strength : stat === 'agility' ? agility : stat === 'endurance' ? endurance : intellect)
// Prevent exceeding total points
if (otherStats + value > TOTAL_POINTS) {
value = TOTAL_POINTS - otherStats
}
switch (stat) {
case 'strength':
setStrength(value)
break
case 'agility':
setAgility(value)
break
case 'endurance':
setEndurance(value)
break
case 'intellect':
setIntellect(value)
break
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
// Validation
if (name.length < 3 || name.length > 20) {
setError('Name must be between 3 and 20 characters')
return
}
if (usedPoints !== TOTAL_POINTS) {
setError(`You must allocate exactly ${TOTAL_POINTS} stat points (currently: ${usedPoints})`)
return
}
if (strength < 0 || agility < 0 || endurance < 0 || intellect < 0) {
setError('Stats cannot be negative')
return
}
setLoading(true)
try {
await createCharacter({
name,
strength,
agility,
endurance,
intellect,
})
navigate('/characters')
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to create character')
} finally {
setLoading(false)
}
}
const handleCancel = () => {
navigate('/characters')
}
return (
<div className="character-creation-container">
<div className="character-creation-card">
<h1>Create Your Character</h1>
<p className="subtitle">Choose your name and distribute your stat points</p>
<form onSubmit={handleSubmit}>
{/* Name Input */}
<div className="form-section">
<label htmlFor="name">Character Name</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter character name"
minLength={3}
maxLength={20}
required
disabled={loading}
/>
<p className="input-hint">3-20 characters, must be unique</p>
</div>
{/* Stat Allocation */}
<div className="form-section">
<h2>Stat Allocation</h2>
<div className="points-remaining">
<span className={remainingPoints === 0 ? 'points-complete' : remainingPoints < 0 ? 'points-over' : ''}>
Points Remaining: {remainingPoints} / {TOTAL_POINTS}
</span>
</div>
<div className="stats-grid">
<StatInput
label="Strength"
icon="💪"
value={strength}
onChange={(v) => handleStatChange('strength', v)}
description="Increases melee damage and carry capacity"
disabled={loading}
/>
<StatInput
label="Agility"
icon="⚡"
value={agility}
onChange={(v) => handleStatChange('agility', v)}
description="Improves dodge chance and critical hits"
disabled={loading}
/>
<StatInput
label="Endurance"
icon="🛡️"
value={endurance}
onChange={(v) => handleStatChange('endurance', v)}
description="Increases HP and stamina"
disabled={loading}
/>
<StatInput
label="Intellect"
icon="🧠"
value={intellect}
onChange={(v) => handleStatChange('intellect', v)}
description="Enhances crafting and resource gathering"
disabled={loading}
/>
</div>
</div>
{/* Character Preview */}
<div className="form-section character-preview">
<h2>Character Preview</h2>
<div className="preview-stats">
<div className="preview-stat">
<span className="preview-label">HP:</span>
<span className="preview-value">{calculateHP(strength)}</span>
</div>
<div className="preview-stat">
<span className="preview-label">Stamina:</span>
<span className="preview-value">{calculateStamina(endurance)}</span>
</div>
<div className="preview-stat">
<span className="preview-label">Level:</span>
<span className="preview-value">1</span>
</div>
</div>
</div>
{error && <div className="error">{error}</div>}
<div className="form-actions">
<button
type="button"
className="button-secondary"
onClick={handleCancel}
disabled={loading}
>
Cancel
</button>
<button
type="submit"
className="button-primary"
disabled={loading || remainingPoints !== 0}
>
{loading ? 'Creating...' : 'Create Character'}
</button>
</div>
</form>
</div>
</div>
)
}
function StatInput({
label,
icon,
value,
onChange,
description,
disabled,
}: {
label: string
icon: string
value: number
onChange: (value: number) => void
description: string
disabled: boolean
}) {
return (
<div className="stat-input">
<div className="stat-header">
<span className="stat-icon">{icon}</span>
<label>{label}</label>
</div>
<div className="stat-control">
<button
type="button"
className="stat-button"
onClick={() => onChange(value - 1)}
disabled={disabled || value <= 0}
>
-
</button>
<input
type="number"
value={value}
onChange={(e) => onChange(parseInt(e.target.value) || 0)}
min="0"
disabled={disabled}
/>
<button
type="button"
className="stat-button"
onClick={() => onChange(value + 1)}
disabled={disabled}
>
+
</button>
</div>
<p className="stat-description">{description}</p>
</div>
)
}
export default CharacterCreation