streamline components

This commit is contained in:
2026-01-07 14:16:02 +01:00
parent 94d60527f4
commit 3b3971e653
16 changed files with 685 additions and 667 deletions

View File

@@ -4,7 +4,6 @@ import { useState } from 'react';
import Modal from '@/components/ui/Modal';
import Button from '@/components/ui/Button';
import type { FileProtestCommandDTO } from '@/lib/types/generated/FileProtestCommandDTO';
import type { ProtestIncidentDTO } from '@/lib/types/generated/ProtestIncidentDTO';
import { useFileProtest } from '@/hooks/race/useFileProtest';
import {
AlertTriangle,
@@ -16,11 +15,9 @@ import {
FileText,
CheckCircle2,
} from 'lucide-react';
type ProtestParticipant = {
id: string;
name: string;
};
import { useInject } from '@/lib/di/hooks/useInject';
import { PROTEST_SERVICE_TOKEN } from '@/lib/di/tokens';
import type { ProtestParticipant } from '@/lib/services/protests/ProtestService';
interface FileProtestModalProps {
isOpen: boolean;
@@ -41,6 +38,7 @@ export default function FileProtestModal({
}: FileProtestModalProps) {
const fileProtestMutation = useFileProtest();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const protestService = useInject(PROTEST_SERVICE_TOKEN);
// Form state
const [accusedDriverId, setAccusedDriverId] = useState<string>('');
@@ -53,51 +51,41 @@ export default function FileProtestModal({
const otherParticipants = participants.filter(p => p.id !== protestingDriverId);
const handleSubmit = async () => {
// Validation
if (!accusedDriverId) {
setErrorMessage('Please select the driver you are protesting against.');
return;
try {
// Use ProtestService for validation and command construction
const command = protestService.constructFileProtestCommand({
raceId,
leagueId,
protestingDriverId,
accusedDriverId,
lap,
timeInRace,
description,
comment,
proofVideoUrl,
});
setErrorMessage(null);
// Use existing hook for the actual API call
fileProtestMutation.mutate(command, {
onSuccess: () => {
// Reset form state on success
setAccusedDriverId('');
setLap('');
setTimeInRace('');
setDescription('');
setComment('');
setProofVideoUrl('');
},
onError: (error) => {
setErrorMessage(error.message || 'Failed to file protest');
},
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to validate protest input';
setErrorMessage(errorMessage);
}
if (!lap || parseInt(lap, 10) < 0) {
setErrorMessage('Please enter a valid lap number.');
return;
}
if (!description.trim()) {
setErrorMessage('Please describe what happened.');
return;
}
setErrorMessage(null);
const incident: ProtestIncidentDTO = {
lap: parseInt(lap, 10),
description: description.trim(),
...(timeInRace ? { timeInRace: parseInt(timeInRace, 10) } : {}),
};
const command = {
raceId,
protestingDriverId,
accusedDriverId,
incident,
...(comment.trim() ? { comment: comment.trim() } : {}),
...(proofVideoUrl.trim() ? { proofVideoUrl: proofVideoUrl.trim() } : {}),
} satisfies FileProtestCommandDTO;
fileProtestMutation.mutate(command, {
onSuccess: () => {
// Reset form state on success
setAccusedDriverId('');
setLap('');
setTimeInRace('');
setDescription('');
setComment('');
setProofVideoUrl('');
},
onError: (error) => {
setErrorMessage(error.message || 'Failed to file protest');
},
});
};
const handleClose = () => {
@@ -207,7 +195,7 @@ export default function FileProtestModal({
/>
</div>
</div>
{/* Incident Description */}
<div>
<label className="flex items-center gap-2 text-sm font-medium text-gray-300 mb-2">
@@ -223,7 +211,7 @@ export default function FileProtestModal({
className="w-full px-3 py-2.5 bg-deep-graphite border border-charcoal-outline rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-blue/50 focus:border-primary-blue disabled:opacity-50 resize-none"
/>
</div>
{/* Additional Comment */}
<div>
<label className="flex items-center gap-2 text-sm font-medium text-gray-300 mb-2">
@@ -239,7 +227,7 @@ export default function FileProtestModal({
className="w-full px-3 py-2.5 bg-deep-graphite border border-charcoal-outline rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-blue/50 focus:border-primary-blue disabled:opacity-50 resize-none"
/>
</div>
{/* Video Proof */}
<div>
<label className="flex items-center gap-2 text-sm font-medium text-gray-300 mb-2">
@@ -258,7 +246,7 @@ export default function FileProtestModal({
Providing video evidence significantly helps the stewards review your protest.
</p>
</div>
{/* Info Box */}
<div className="p-3 bg-iron-gray rounded-lg border border-charcoal-outline">
<p className="text-xs text-gray-400">
@@ -267,7 +255,7 @@ export default function FileProtestModal({
to grid penalties for future races, depending on the severity.
</p>
</div>
{/* Actions */}
<div className="flex gap-3 pt-2">
<Button
@@ -290,4 +278,4 @@ export default function FileProtestModal({
</div>
</Modal>
);
}
}