di usage in website
This commit is contained in:
@@ -4,8 +4,10 @@ import { useState, FormEvent } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Input from '../ui/Input';
|
||||
import Button from '../ui/Button';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { useCreateLeague } from '@/hooks/league/useCreateLeague';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
interface FormErrors {
|
||||
name?: string;
|
||||
@@ -17,7 +19,6 @@ interface FormErrors {
|
||||
|
||||
export default function CreateLeagueForm() {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
@@ -51,12 +52,13 @@ export default function CreateLeagueForm() {
|
||||
};
|
||||
|
||||
const { session } = useAuth();
|
||||
const { driverService, leagueService } = useServices();
|
||||
const driverService = useInject(DRIVER_SERVICE_TOKEN);
|
||||
const createLeagueMutation = useCreateLeague();
|
||||
|
||||
const handleSubmit = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (loading) return;
|
||||
if (createLeagueMutation.isPending) return;
|
||||
|
||||
if (!validateForm()) return;
|
||||
|
||||
@@ -65,15 +67,12 @@ export default function CreateLeagueForm() {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// Get current driver
|
||||
const currentDriver = await driverService.getDriverProfile(session.user.userId);
|
||||
|
||||
if (!currentDriver) {
|
||||
setErrors({ submit: 'No driver profile found. Please create a profile first.' });
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -85,14 +84,13 @@ export default function CreateLeagueForm() {
|
||||
ownerId: session.user.userId,
|
||||
};
|
||||
|
||||
const result = await leagueService.createLeague(input);
|
||||
const result = await createLeagueMutation.mutateAsync(input);
|
||||
router.push(`/leagues/${result.leagueId}`);
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
setErrors({
|
||||
submit: error instanceof Error ? error.message : 'Failed to create league'
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -112,7 +110,7 @@ export default function CreateLeagueForm() {
|
||||
errorMessage={errors.name}
|
||||
placeholder="European GT Championship"
|
||||
maxLength={100}
|
||||
disabled={loading}
|
||||
disabled={createLeagueMutation.isPending}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500 text-right">
|
||||
{formData.name.length}/100
|
||||
@@ -130,7 +128,7 @@ export default function CreateLeagueForm() {
|
||||
placeholder="Weekly GT3 racing with professional drivers"
|
||||
maxLength={500}
|
||||
rows={4}
|
||||
disabled={loading}
|
||||
disabled={createLeagueMutation.isPending}
|
||||
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">
|
||||
@@ -149,7 +147,7 @@ export default function CreateLeagueForm() {
|
||||
id="pointsSystem"
|
||||
value={formData.pointsSystem}
|
||||
onChange={(e) => setFormData({ ...formData, pointsSystem: e.target.value as 'f1-2024' | 'indycar' })}
|
||||
disabled={loading}
|
||||
disabled={createLeagueMutation.isPending}
|
||||
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 focus:ring-2 focus:ring-inset focus:ring-primary-blue transition-all duration-150 sm:text-sm sm:leading-6"
|
||||
>
|
||||
<option value="f1-2024">F1 2024</option>
|
||||
@@ -170,7 +168,7 @@ export default function CreateLeagueForm() {
|
||||
errorMessage={errors.sessionDuration}
|
||||
min={1}
|
||||
max={240}
|
||||
disabled={loading}
|
||||
disabled={createLeagueMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -183,10 +181,10 @@ export default function CreateLeagueForm() {
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={loading}
|
||||
disabled={createLeagueMutation.isPending}
|
||||
className="w-full"
|
||||
>
|
||||
{loading ? 'Creating League...' : 'Create League'}
|
||||
{createLeagueMutation.isPending ? 'Creating League...' : 'Create League'}
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { getMembership } from '@/lib/leagueMembership';
|
||||
import { useState } from 'react';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { useLeagueMembershipMutation } from '@/hooks/league/useLeagueMembershipMutation';
|
||||
import Button from '../ui/Button';
|
||||
|
||||
interface JoinLeagueButtonProps {
|
||||
@@ -18,16 +18,16 @@ export default function JoinLeagueButton({
|
||||
onMembershipChange,
|
||||
}: JoinLeagueButtonProps) {
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
const membership = getMembership(leagueId, currentDriverId);
|
||||
const { leagueMembershipService } = useServices();
|
||||
const membership = currentDriverId ? getMembership(leagueId, currentDriverId) : null;
|
||||
const { joinLeague, leaveLeague } = useLeagueMembershipMutation();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||
const [dialogAction, setDialogAction] = useState<'join' | 'leave' | 'request'>('join');
|
||||
|
||||
const handleJoin = async () => {
|
||||
setLoading(true);
|
||||
if (!currentDriverId) return;
|
||||
|
||||
setError(null);
|
||||
try {
|
||||
if (isInviteOnly) {
|
||||
@@ -36,33 +36,30 @@ export default function JoinLeagueButton({
|
||||
);
|
||||
}
|
||||
|
||||
await leagueMembershipService.joinLeague(leagueId, currentDriverId);
|
||||
await joinLeague.mutateAsync({ leagueId, driverId: currentDriverId });
|
||||
|
||||
onMembershipChange?.();
|
||||
setShowConfirmDialog(false);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to join league');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLeave = async () => {
|
||||
setLoading(true);
|
||||
if (!currentDriverId) return;
|
||||
|
||||
setError(null);
|
||||
try {
|
||||
if (membership?.role === 'owner') {
|
||||
throw new Error('League owner cannot leave the league');
|
||||
}
|
||||
|
||||
await leagueMembershipService.leaveLeague(leagueId, currentDriverId);
|
||||
await leaveLeague.mutateAsync({ leagueId, driverId: currentDriverId });
|
||||
|
||||
onMembershipChange?.();
|
||||
setShowConfirmDialog(false);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to leave league');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,7 +90,7 @@ export default function JoinLeagueButton({
|
||||
return 'danger';
|
||||
};
|
||||
|
||||
const isDisabled = membership?.role === 'owner' || loading;
|
||||
const isDisabled = membership?.role === 'owner' || joinLeague.isPending || leaveLeague.isPending;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -109,7 +106,7 @@ export default function JoinLeagueButton({
|
||||
disabled={isDisabled}
|
||||
className="w-full"
|
||||
>
|
||||
{loading ? 'Processing...' : getButtonText()}
|
||||
{(joinLeague.isPending || leaveLeague.isPending) ? 'Processing...' : getButtonText()}
|
||||
</Button>
|
||||
|
||||
{error && (
|
||||
@@ -142,15 +139,15 @@ export default function JoinLeagueButton({
|
||||
<Button
|
||||
variant={dialogAction === 'leave' ? 'danger' : 'primary'}
|
||||
onClick={dialogAction === 'leave' ? handleLeave : handleJoin}
|
||||
disabled={loading}
|
||||
disabled={joinLeague.isPending || leaveLeague.isPending}
|
||||
className="flex-1"
|
||||
>
|
||||
{loading ? 'Processing...' : 'Confirm'}
|
||||
{(joinLeague.isPending || leaveLeague.isPending) ? 'Processing...' : 'Confirm'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={closeDialog}
|
||||
disabled={loading}
|
||||
disabled={joinLeague.isPending || leaveLeague.isPending}
|
||||
className="flex-1"
|
||||
>
|
||||
Cancel
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { Calendar, Award, UserPlus, UserMinus, Shield, Flag, AlertTriangle } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import type { RaceListItemViewModel } from '@/lib/view-models/RaceListItemViewModel';
|
||||
import { useLeagueRaces } from '@/hooks/league/useLeagueRaces';
|
||||
|
||||
export type LeagueActivity =
|
||||
export type LeagueActivity =
|
||||
| { type: 'race_completed'; raceId: string; raceName: string; timestamp: Date }
|
||||
| { type: 'race_scheduled'; raceId: string; raceName: string; timestamp: Date }
|
||||
| { type: 'penalty_applied'; penaltyId: string; driverName: string; reason: string; points: number; timestamp: Date }
|
||||
@@ -32,60 +30,45 @@ function timeAgo(timestamp: Date): string {
|
||||
}
|
||||
|
||||
export default function LeagueActivityFeed({ leagueId, limit = 10 }: LeagueActivityFeedProps) {
|
||||
const { raceService, driverService } = useServices();
|
||||
const [activities, setActivities] = useState<LeagueActivity[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { data: raceList = [], isLoading } = useLeagueRaces(leagueId);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadActivities() {
|
||||
try {
|
||||
const raceList = await raceService.findByLeagueId(leagueId);
|
||||
const activities: LeagueActivity[] = [];
|
||||
|
||||
if (!isLoading && raceList.length > 0) {
|
||||
const completedRaces = raceList
|
||||
.filter((r) => r.status === 'completed')
|
||||
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
|
||||
.slice(0, 5);
|
||||
|
||||
const completedRaces = raceList
|
||||
.filter((r) => r.status === 'completed')
|
||||
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
|
||||
.slice(0, 5);
|
||||
const upcomingRaces = raceList
|
||||
.filter((r) => r.status === 'scheduled')
|
||||
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
|
||||
.slice(0, 3);
|
||||
|
||||
const upcomingRaces = raceList
|
||||
.filter((r) => r.status === 'scheduled')
|
||||
.sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())
|
||||
.slice(0, 3);
|
||||
|
||||
const activityList: LeagueActivity[] = [];
|
||||
|
||||
for (const race of completedRaces) {
|
||||
activityList.push({
|
||||
type: 'race_completed',
|
||||
raceId: race.id,
|
||||
raceName: `${race.track} - ${race.car}`,
|
||||
timestamp: new Date(race.scheduledAt),
|
||||
});
|
||||
}
|
||||
|
||||
for (const race of upcomingRaces) {
|
||||
activityList.push({
|
||||
type: 'race_scheduled',
|
||||
raceId: race.id,
|
||||
raceName: `${race.track} - ${race.car}`,
|
||||
timestamp: new Date(new Date(race.scheduledAt).getTime() - 7 * 24 * 60 * 60 * 1000), // Simulate schedule announcement
|
||||
});
|
||||
}
|
||||
|
||||
// Sort all activities by timestamp
|
||||
activityList.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
||||
|
||||
setActivities(activityList.slice(0, limit));
|
||||
} catch (err) {
|
||||
console.error('Failed to load activities:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
for (const race of completedRaces) {
|
||||
activities.push({
|
||||
type: 'race_completed',
|
||||
raceId: race.id,
|
||||
raceName: `${race.track} - ${race.car}`,
|
||||
timestamp: new Date(race.scheduledAt),
|
||||
});
|
||||
}
|
||||
|
||||
loadActivities();
|
||||
}, [leagueId, limit, raceService, driverService]);
|
||||
for (const race of upcomingRaces) {
|
||||
activities.push({
|
||||
type: 'race_scheduled',
|
||||
raceId: race.id,
|
||||
raceName: `${race.track} - ${race.car}`,
|
||||
timestamp: new Date(new Date(race.scheduledAt).getTime() - 7 * 24 * 60 * 60 * 1000), // Simulate schedule announcement
|
||||
});
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
// Sort all activities by timestamp
|
||||
activities.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
||||
activities.splice(limit); // Limit results
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="text-center text-gray-400 py-8">
|
||||
Loading activities...
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import DriverIdentity from '../drivers/DriverIdentity';
|
||||
import { useEffectiveDriverId } from '../../hooks/useEffectiveDriverId';
|
||||
import { useServices } from '../../lib/services/ServiceProvider';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_MEMBERSHIP_SERVICE_TOKEN, DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import type { LeagueMembership } from '@/lib/types/LeagueMembership';
|
||||
import type { MembershipRole } from '@/lib/types/MembershipRole';
|
||||
import { DriverViewModel } from '@/lib/view-models/DriverViewModel';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
// Migrated to useServices-based website services; legacy EntityMapper removed.
|
||||
// Migrated to useInject-based DI; legacy EntityMapper removed.
|
||||
|
||||
interface LeagueMembersProps {
|
||||
leagueId: string;
|
||||
@@ -28,7 +29,8 @@ export default function LeagueMembers({
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [sortBy, setSortBy] = useState<'role' | 'name' | 'date' | 'rating' | 'points' | 'wins'>('rating');
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
const { leagueMembershipService, driverService } = useServices();
|
||||
const leagueMembershipService = useInject(LEAGUE_MEMBERSHIP_SERVICE_TOKEN);
|
||||
const driverService = useInject(DRIVER_SERVICE_TOKEN);
|
||||
|
||||
const loadMembers = useCallback(async () => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { useRegisterForRace, useWithdrawFromRace } from '@/hooks/useRaceService';
|
||||
import { useRegisterForRace } from '@/hooks/race/useRegisterForRace';
|
||||
import { useWithdrawFromRace } from '@/hooks/race/useWithdrawFromRace';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { LeagueScheduleRaceViewModel } from '@/lib/view-models/LeagueScheduleViewModel';
|
||||
|
||||
// Shared state components
|
||||
import { useDataFetching } from '@/components/shared/hooks/useDataFetching';
|
||||
import { StateContainer } from '@/components/shared/state/StateContainer';
|
||||
import { EmptyState } from '@/components/shared/state/EmptyState';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { useLeagueSchedule } from '@/hooks/league/useLeagueSchedule';
|
||||
import { Calendar } from 'lucide-react';
|
||||
|
||||
interface LeagueScheduleProps {
|
||||
@@ -22,12 +22,8 @@ export default function LeagueSchedule({ leagueId }: LeagueScheduleProps) {
|
||||
const [filter, setFilter] = useState<'all' | 'upcoming' | 'past'>('upcoming');
|
||||
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
const { leagueService } = useServices();
|
||||
|
||||
const { data: schedule, isLoading, error, retry } = useDataFetching({
|
||||
queryKey: ['leagueSchedule', leagueId],
|
||||
queryFn: () => leagueService.getLeagueSchedule(leagueId),
|
||||
});
|
||||
const { data: schedule, isLoading, error, retry } = useLeagueSchedule(leagueId);
|
||||
|
||||
const registerMutation = useRegisterForRace();
|
||||
const withdrawMutation = useWithdrawFromRace();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { Award, DollarSign, Star, X } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import PendingSponsorshipRequests, { type PendingRequestDTO } from '../sponsors/PendingSponsorshipRequests';
|
||||
import Button from '../ui/Button';
|
||||
import Input from '../ui/Input';
|
||||
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { useLeagueSeasons } from '@/hooks/league/useLeagueSeasons';
|
||||
import { useSponsorshipRequests } from '@/hooks/league/useSponsorshipRequests';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { SPONSORSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
interface SponsorshipSlot {
|
||||
tier: 'main' | 'secondary';
|
||||
@@ -29,7 +32,8 @@ export function LeagueSponsorshipsSection({
|
||||
readOnly = false
|
||||
}: LeagueSponsorshipsSectionProps) {
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
const { sponsorshipService, leagueService } = useServices();
|
||||
const sponsorshipService = useInject(SPONSORSHIP_SERVICE_TOKEN);
|
||||
|
||||
const [slots, setSlots] = useState<SponsorshipSlot[]>([
|
||||
{ tier: 'main', price: 500, isOccupied: false },
|
||||
{ tier: 'secondary', price: 200, isOccupied: false },
|
||||
@@ -37,73 +41,21 @@ export function LeagueSponsorshipsSection({
|
||||
]);
|
||||
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
||||
const [tempPrice, setTempPrice] = useState<string>('');
|
||||
const [pendingRequests, setPendingRequests] = useState<PendingRequestDTO[]>([]);
|
||||
const [requestsLoading, setRequestsLoading] = useState(false);
|
||||
const [seasonId, setSeasonId] = useState<string | undefined>(propSeasonId);
|
||||
|
||||
// Load season ID if not provided
|
||||
useEffect(() => {
|
||||
async function loadSeasonId() {
|
||||
if (propSeasonId) {
|
||||
setSeasonId(propSeasonId);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const seasons = await leagueService.getLeagueSeasons(leagueId);
|
||||
const activeSeason = seasons.find((s) => s.status === 'active') ?? seasons[0];
|
||||
if (activeSeason) setSeasonId(activeSeason.seasonId);
|
||||
} catch (err) {
|
||||
console.error('Failed to load season:', err);
|
||||
}
|
||||
}
|
||||
loadSeasonId();
|
||||
}, [leagueId, propSeasonId, leagueService]);
|
||||
const { data: seasons = [], isLoading: seasonsLoading } = useLeagueSeasons(leagueId);
|
||||
const activeSeason = seasons.find((s) => s.status === 'active') ?? seasons[0];
|
||||
const seasonId = propSeasonId || activeSeason?.seasonId;
|
||||
|
||||
// Load pending sponsorship requests
|
||||
const loadPendingRequests = useCallback(async () => {
|
||||
if (!seasonId) return;
|
||||
|
||||
setRequestsLoading(true);
|
||||
try {
|
||||
const requests = await sponsorshipService.getPendingSponsorshipRequests({
|
||||
entityType: 'season',
|
||||
entityId: seasonId,
|
||||
});
|
||||
|
||||
// Convert service view-models to component DTO type (UI-only)
|
||||
setPendingRequests(
|
||||
requests.map(
|
||||
(r): PendingRequestDTO => ({
|
||||
id: r.id,
|
||||
sponsorId: r.sponsorId,
|
||||
sponsorName: r.sponsorName,
|
||||
sponsorLogo: r.sponsorLogo,
|
||||
tier: r.tier,
|
||||
offeredAmount: r.offeredAmount,
|
||||
currency: r.currency,
|
||||
formattedAmount: r.formattedAmount,
|
||||
message: r.message,
|
||||
createdAt: r.createdAt,
|
||||
platformFee: r.platformFee,
|
||||
netAmount: r.netAmount,
|
||||
}),
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Failed to load pending requests:', err);
|
||||
} finally {
|
||||
setRequestsLoading(false);
|
||||
}
|
||||
}, [seasonId, sponsorshipService]);
|
||||
|
||||
useEffect(() => {
|
||||
loadPendingRequests();
|
||||
}, [loadPendingRequests]);
|
||||
const { data: pendingRequests = [], isLoading: requestsLoading, refetch: refetchRequests } = useSponsorshipRequests('season', seasonId || '');
|
||||
|
||||
const handleAcceptRequest = async (requestId: string) => {
|
||||
if (!currentDriverId) return;
|
||||
|
||||
try {
|
||||
await sponsorshipService.acceptSponsorshipRequest(requestId, currentDriverId);
|
||||
await loadPendingRequests();
|
||||
await refetchRequests();
|
||||
} catch (err) {
|
||||
console.error('Failed to accept request:', err);
|
||||
alert(err instanceof Error ? err.message : 'Failed to accept request');
|
||||
@@ -111,9 +63,11 @@ export function LeagueSponsorshipsSection({
|
||||
};
|
||||
|
||||
const handleRejectRequest = async (requestId: string, reason?: string) => {
|
||||
if (!currentDriverId) return;
|
||||
|
||||
try {
|
||||
await sponsorshipService.rejectSponsorshipRequest(requestId, currentDriverId, reason);
|
||||
await loadPendingRequests();
|
||||
await refetchRequests();
|
||||
} catch (err) {
|
||||
console.error('Failed to reject request:', err);
|
||||
alert(err instanceof Error ? err.message : 'Failed to reject request');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { usePenaltyMutation } from '@/hooks/league/usePenaltyMutation';
|
||||
import { AlertTriangle, Clock, Flag, Zap } from 'lucide-react';
|
||||
|
||||
interface DriverOption {
|
||||
@@ -41,16 +41,14 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelecte
|
||||
const [infractionType, setInfractionType] = useState<string>('');
|
||||
const [severity, setSeverity] = useState<string>('');
|
||||
const [notes, setNotes] = useState<string>('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const { penaltyService } = useServices();
|
||||
const penaltyMutation = usePenaltyMutation();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!selectedRaceId || !selectedDriver || !infractionType || !severity) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
@@ -64,15 +62,14 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelecte
|
||||
if (notes.trim()) {
|
||||
command.notes = notes.trim();
|
||||
}
|
||||
await penaltyService.applyPenalty(command);
|
||||
|
||||
await penaltyMutation.mutateAsync(command);
|
||||
|
||||
// Refresh the page to show updated results
|
||||
router.refresh();
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to apply penalty');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -206,7 +203,7 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelecte
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
className="flex-1"
|
||||
disabled={loading}
|
||||
disabled={penaltyMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -214,9 +211,9 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelecte
|
||||
type="submit"
|
||||
variant="primary"
|
||||
className="flex-1"
|
||||
disabled={loading || !selectedRaceId || !selectedDriver || !infractionType || !severity}
|
||||
disabled={penaltyMutation.isPending || !selectedRaceId || !selectedDriver || !infractionType || !severity}
|
||||
>
|
||||
{loading ? 'Applying...' : 'Apply Penalty'}
|
||||
{penaltyMutation.isPending ? 'Applying...' : 'Apply Penalty'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Button from '../ui/Button';
|
||||
import Input from '../ui/Input';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { useAllLeagues } from '@/hooks/league/useAllLeagues';
|
||||
import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
|
||||
|
||||
interface ScheduleRaceFormData {
|
||||
@@ -35,10 +35,7 @@ export default function ScheduleRaceForm({
|
||||
onCancel
|
||||
}: ScheduleRaceFormProps) {
|
||||
const router = useRouter();
|
||||
const { leagueService, raceService } = useServices();
|
||||
const [leagues, setLeagues] = useState<LeagueSummaryViewModel[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { data: leagues = [], isLoading, error } = useAllLeagues();
|
||||
|
||||
const [formData, setFormData] = useState<ScheduleRaceFormData>({
|
||||
leagueId: preSelectedLeagueId || '',
|
||||
@@ -51,18 +48,6 @@ export default function ScheduleRaceForm({
|
||||
|
||||
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const loadLeagues = async () => {
|
||||
try {
|
||||
const allLeagues = await leagueService.getAllLeagues();
|
||||
setLeagues(allLeagues);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load leagues');
|
||||
}
|
||||
};
|
||||
void loadLeagues();
|
||||
}, [leagueService]);
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
@@ -107,9 +92,6 @@ export default function ScheduleRaceForm({
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Create race using the race service
|
||||
// Note: This assumes the race service has a create method
|
||||
@@ -137,9 +119,8 @@ export default function ScheduleRaceForm({
|
||||
router.push(`/races/${createdRace.id}`);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to create race');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// Error handling is now done through the component state
|
||||
console.error('Failed to create race:', err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -160,7 +141,7 @@ export default function ScheduleRaceForm({
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{error && (
|
||||
<div className="p-4 rounded-lg bg-red-500/10 border border-red-500/30 text-red-400">
|
||||
{error}
|
||||
{error.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -310,10 +291,10 @@ export default function ScheduleRaceForm({
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={loading}
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
>
|
||||
{loading ? 'Creating...' : 'Schedule Race'}
|
||||
{isLoading ? 'Creating...' : 'Schedule Race'}
|
||||
</Button>
|
||||
|
||||
{onCancel && (
|
||||
@@ -321,7 +302,7 @@ export default function ScheduleRaceForm({
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
disabled={loading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user