186 lines
5.4 KiB
TypeScript
186 lines
5.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState, FormEvent } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Input from '../ui/Input';
|
|
import Button from '../ui/Button';
|
|
import { Driver } from '@core/racing';
|
|
import { getDriverRepository } from '../../lib/di-container';
|
|
|
|
interface FormErrors {
|
|
name?: string;
|
|
iracingId?: string;
|
|
country?: string;
|
|
bio?: string;
|
|
submit?: string;
|
|
}
|
|
|
|
export default function CreateDriverForm() {
|
|
const router = useRouter();
|
|
const [loading, setLoading] = useState(false);
|
|
const [errors, setErrors] = useState<FormErrors>({});
|
|
|
|
const [formData, setFormData] = useState({
|
|
name: '',
|
|
iracingId: '',
|
|
country: '',
|
|
bio: ''
|
|
});
|
|
|
|
const validateForm = async (): Promise<boolean> => {
|
|
const newErrors: FormErrors = {};
|
|
|
|
if (!formData.name.trim()) {
|
|
newErrors.name = 'Name is required';
|
|
}
|
|
|
|
if (!formData.iracingId.trim()) {
|
|
newErrors.iracingId = 'iRacing ID is required';
|
|
} else {
|
|
const driverRepo = getDriverRepository();
|
|
const exists = await driverRepo.existsByIRacingId(formData.iracingId);
|
|
if (exists) {
|
|
newErrors.iracingId = 'This iRacing ID is already registered';
|
|
}
|
|
}
|
|
|
|
if (!formData.country.trim()) {
|
|
newErrors.country = 'Country is required';
|
|
} else if (!/^[A-Z]{2,3}$/i.test(formData.country)) {
|
|
newErrors.country = 'Invalid country code (use 2-3 letter ISO code)';
|
|
}
|
|
|
|
if (formData.bio && formData.bio.length > 500) {
|
|
newErrors.bio = 'Bio must be 500 characters or less';
|
|
}
|
|
|
|
setErrors(newErrors);
|
|
return Object.keys(newErrors).length === 0;
|
|
};
|
|
|
|
const handleSubmit = async (e: FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (loading) return;
|
|
|
|
const isValid = await validateForm();
|
|
if (!isValid) return;
|
|
|
|
setLoading(true);
|
|
|
|
try {
|
|
const driverRepo = getDriverRepository();
|
|
const bio = formData.bio.trim();
|
|
|
|
const driver = Driver.create({
|
|
id: crypto.randomUUID(),
|
|
iracingId: formData.iracingId.trim(),
|
|
name: formData.name.trim(),
|
|
country: formData.country.trim().toUpperCase(),
|
|
...(bio ? { bio } : {}),
|
|
});
|
|
|
|
await driverRepo.create(driver);
|
|
router.push('/profile');
|
|
router.refresh();
|
|
} catch (error) {
|
|
setErrors({
|
|
submit: error instanceof Error ? error.message : 'Failed to create profile'
|
|
});
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
<div>
|
|
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">
|
|
Driver Name *
|
|
</label>
|
|
<Input
|
|
id="name"
|
|
type="text"
|
|
value={formData.name}
|
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
error={!!errors.name}
|
|
errorMessage={errors.name}
|
|
placeholder="Alex Vermeer"
|
|
disabled={loading}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="iracingId" className="block text-sm font-medium text-gray-300 mb-2">
|
|
iRacing ID *
|
|
</label>
|
|
<Input
|
|
id="iracingId"
|
|
type="text"
|
|
value={formData.iracingId}
|
|
onChange={(e) => setFormData({ ...formData, iracingId: e.target.value })}
|
|
error={!!errors.iracingId}
|
|
errorMessage={errors.iracingId}
|
|
placeholder="123456"
|
|
disabled={loading}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="country" className="block text-sm font-medium text-gray-300 mb-2">
|
|
Country Code *
|
|
</label>
|
|
<Input
|
|
id="country"
|
|
type="text"
|
|
value={formData.country}
|
|
onChange={(e) => setFormData({ ...formData, country: e.target.value })}
|
|
error={!!errors.country}
|
|
errorMessage={errors.country}
|
|
placeholder="NL"
|
|
maxLength={3}
|
|
disabled={loading}
|
|
/>
|
|
<p className="mt-1 text-xs text-gray-500">Use ISO 3166-1 alpha-2 or alpha-3 code</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="bio" className="block text-sm font-medium text-gray-300 mb-2">
|
|
Bio (Optional)
|
|
</label>
|
|
<textarea
|
|
id="bio"
|
|
value={formData.bio}
|
|
onChange={(e) => setFormData({ ...formData, bio: e.target.value })}
|
|
placeholder="Tell us about yourself..."
|
|
maxLength={500}
|
|
rows={4}
|
|
disabled={loading}
|
|
className="block w-full rounded-md border-0 px-4 py-3 bg-iron-gray text-white shadow-sm ring-1 ring-inset ring-charcoal-outline placeholder:text-gray-500 focus:ring-2 focus:ring-inset focus:ring-primary-blue transition-all duration-150 sm:text-sm sm:leading-6 resize-none"
|
|
/>
|
|
<p className="mt-1 text-xs text-gray-500 text-right">
|
|
{formData.bio.length}/500
|
|
</p>
|
|
{errors.bio && (
|
|
<p className="mt-2 text-sm text-warning-amber">{errors.bio}</p>
|
|
)}
|
|
</div>
|
|
|
|
{errors.submit && (
|
|
<div className="rounded-md bg-warning-amber/10 p-4 border border-warning-amber/20">
|
|
<p className="text-sm text-warning-amber">{errors.submit}</p>
|
|
</div>
|
|
)}
|
|
|
|
<Button
|
|
type="submit"
|
|
variant="primary"
|
|
disabled={loading}
|
|
className="w-full"
|
|
>
|
|
{loading ? 'Creating Profile...' : 'Create Profile'}
|
|
</Button>
|
|
</form>
|
|
</>
|
|
);
|
|
} |