167 lines
6.9 KiB
TypeScript
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>
|
|
)
|
|
}
|