altarra/src/components/RackHeightsForm.tsx

167 lines
6.9 KiB
TypeScript

"use client"
import { useEffect, useRef, useState, lazy, Suspense } from 'react'
import dynamic from 'next/dynamic'
import FormField from '@/components/FormField'
import SubmitButton from '@/components/SubmitButton'
import StatusMessage from '@/components/StatusMessage'
import type { RackHeightsPayload } from '@/types/rackHeights'
import { required } from '@/utils/validation'
import { useLanguage } from '@/context/LanguageContext'
import { t } from '@/utils/translations'
const Checkmark = dynamic(() => import('react-checkmark').then(mod => ({ default: mod.Checkmark })), { ssr: false })
type Props = {
memberNumber: string
birthDate: string
onSuccess: () => void
}
export default function RackHeightsForm({ memberNumber, birthDate, onSuccess }: Props) {
const { language } = useLanguage()
const [squatRackHeight, setSquatRackHeight] = useState('')
const [squatRackInOut, setSquatRackInOut] = useState('')
const [benchRackHeight, setBenchRackHeight] = useState('')
const [benchRackSafety, setBenchRackSafety] = useState('')
const [benchRackFootBlocks, setBenchRackFootBlocks] = useState<'NONE' | '5cm' | '10cm' | '20cm' | '30cm'>('NONE')
const [loading, setLoading] = useState(false)
const [status, setStatus] = useState<{ type: 'idle' | 'success' | 'error'; msg: string | null }>({ type: 'idle', msg: null })
const [submitted, setSubmitted] = useState(false)
const firstFieldRef = useRef<HTMLInputElement | null>(null)
const errorMessages = {
validation_error: t('error_validation', language),
network_error: t('error_network', language),
api_error: t('error_api', language)
}
useEffect(() => {
firstFieldRef.current?.focus()
}, [])
async function onSubmit(e: React.FormEvent) {
e.preventDefault()
setStatus({ type: 'idle', msg: null })
if (!required(squatRackHeight) || !required(benchRackHeight) || !required(benchRackFootBlocks)) {
setStatus({ type: 'error', msg: errorMessages.validation_error })
return
}
setLoading(true)
try {
await new Promise(resolve => setTimeout(resolve, 1500))
const payload: RackHeightsPayload = {
memberNumber,
birthDate,
squatRackHeight,
squatRackInOut,
benchRackHeight,
benchRackSafety,
benchRackFootBlocks
}
const res = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
if (!res.ok) {
const data = await res.json().catch(() => ({}))
const msg = data?.message || errorMessages.api_error
setStatus({ type: 'error', msg })
return
}
setStatus({ type: 'success', msg: t('submission_success', language) })
setSubmitted(true)
} catch {
setStatus({ type: 'error', msg: errorMessages.network_error })
} finally {
setLoading(false)
}
}
function handleNewSubmission() {
setSubmitted(false)
setStatus({ type: 'idle', msg: null })
setSquatRackHeight('')
setSquatRackInOut('')
setBenchRackHeight('')
setBenchRackSafety('')
setBenchRackFootBlocks('NONE')
onSuccess()
}
return (
<div aria-live="polite">
{submitted ? (
<div className="space-y-4">
<div className="rounded-lg border-l-4 border-[#006600] bg-[#f0fdf4] px-6 py-6 shadow-sm">
<div className="mb-4 flex justify-center">
<Checkmark size="xxLarge" color="#006600" />
</div>
<h2 className="text-center text-xl font-bold text-[#004d00]">{t('heights_submitted_title', language)}</h2>
<p className="mt-3 text-center text-[#1f5e2e]">{t('heights_submitted_text', language)}</p>
</div>
<button
type="button"
onClick={handleNewSubmission}
className="inline-flex w-full items-center justify-center gap-2 rounded-lg bg-[#006600] px-5 py-3 font-semibold text-white shadow-sm hover:bg-[#004d00] transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#006600]"
>
{t('submit_another', language)}
</button>
</div>
) : (
<form onSubmit={onSubmit} aria-labelledby="alturas">
<h2 id="alturas" className="mb-4 text-lg font-semibold text-black">{t('step_2_heights', language)}</h2>
<div className="grid grid-cols-1 gap-3">
<div>
<label className="mb-2 block text-sm font-semibold text-gray-900">{t('squat_rack_height', language)} <span className="text-[#FF0000]" aria-hidden>*</span></label>
<input
ref={firstFieldRef}
type="number"
inputMode="numeric"
min={1}
max={20}
value={squatRackHeight}
onChange={(e) => setSquatRackHeight(e.target.value)}
className="w-full rounded-lg border-2 border-gray-300 px-4 py-3 text-base outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[#006600]/30"
required
/>
</div>
<div>
<label className="mb-2 block text-sm font-semibold text-gray-900">{t('bench_rack_height', language)} <span className="text-[#FF0000]" aria-hidden>*</span></label>
<input
type="number"
inputMode="numeric"
min={1}
max={20}
value={benchRackHeight}
onChange={(e) => setBenchRackHeight(e.target.value)}
className="w-full rounded-lg border-2 border-gray-300 px-4 py-3 text-base outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[#006600]/30"
required
/>
</div>
<div>
<label className="mb-2 block text-sm font-semibold text-gray-900">{t('bench_foot_blocks', language)} <span className="text-[#FF0000]" aria-hidden>*</span></label>
<select
value={benchRackFootBlocks}
onChange={(e) => setBenchRackFootBlocks(e.target.value as any)}
className="w-full rounded-lg border-2 border-gray-300 px-4 py-3 text-base outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[#006600]/30"
required
>
<option value="NONE">{t('foot_blocks_none', language)}</option>
<option value="5cm">5cm</option>
<option value="10cm">10cm</option>
<option value="20cm">20cm</option>
<option value="30cm">30cm</option>
</select>
</div>
</div>
<div className="mt-4">
<SubmitButton loading={loading}>{t('submit_heights', language)}</SubmitButton>
</div>
<StatusMessage status={status.type === 'success' ? 'success' : status.type === 'error' ? 'error' : 'info'} message={status.msg} />
</form>
)}
</div>
)
}