website refactor

This commit is contained in:
2026-01-16 01:00:03 +01:00
parent ce7be39155
commit a98e3e3166
286 changed files with 5522 additions and 5261 deletions

View File

@@ -1,14 +1,20 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_MEMBERSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
export function useLeagueMembershipMutation() {
const leagueMembershipService = useInject(LEAGUE_MEMBERSHIP_SERVICE_TOKEN);
const queryClient = useQueryClient();
const joinLeagueMutation = useMutation({
mutationFn: ({ leagueId, driverId }: { leagueId: string; driverId: string }) =>
leagueMembershipService.joinLeague(leagueId, driverId),
mutationFn: async ({ leagueId, driverId }: { leagueId: string; driverId: string }) => {
const result = await leagueMembershipService.joinLeague(leagueId, driverId);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['leagueMemberships'] });
queryClient.invalidateQueries({ queryKey: ['allLeagues'] });
@@ -16,8 +22,13 @@ export function useLeagueMembershipMutation() {
});
const leaveLeagueMutation = useMutation({
mutationFn: ({ leagueId, driverId }: { leagueId: string; driverId: string }) =>
leagueMembershipService.leaveLeague(leagueId, driverId),
mutationFn: async ({ leagueId, driverId }: { leagueId: string; driverId: string }) => {
const result = await leagueMembershipService.leaveLeague(leagueId, driverId);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['leagueMemberships'] });
queryClient.invalidateQueries({ queryKey: ['allLeagues'] });
@@ -28,4 +39,4 @@ export function useLeagueMembershipMutation() {
joinLeague: joinLeagueMutation,
leaveLeague: leaveLeagueMutation,
};
}
}

View File

@@ -26,7 +26,13 @@ export function useLeagueRosterAdmin(leagueId: string, options?: UseQueryOptions
return useQuery<LeagueRosterMemberDTO[], ApiError>({
queryKey: ['league-roster-admin', leagueId],
queryFn: () => leagueService.getAdminRosterMembers(leagueId),
queryFn: async () => {
const result = await leagueService.getAdminRosterMembers(leagueId);
if (result.isErr()) {
throw new ApiError(result.getError().message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
...options,
});
}
@@ -36,7 +42,13 @@ export function useLeagueJoinRequests(leagueId: string, options?: UseQueryOption
return useQuery<LeagueRosterJoinRequestDTO[], ApiError>({
queryKey: ['league-join-requests', leagueId],
queryFn: () => leagueService.getAdminRosterJoinRequests(leagueId),
queryFn: async () => {
const result = await leagueService.getAdminRosterJoinRequests(leagueId);
if (result.isErr()) {
throw new ApiError(result.getError().message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
...options,
});
}
@@ -48,7 +60,11 @@ export function useUpdateMemberRole(
return useMutation<{ success: boolean }, ApiError, UpdateMemberRoleInput>({
mutationFn: async (input) => {
return leagueService.updateMemberRole(input.leagueId, input.driverId, input.newRole);
const result = await leagueService.updateMemberRole(input.leagueId, input.driverId, input.newRole);
if (result.isErr()) {
throw new ApiError(result.getError().message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
...options,
});
@@ -61,7 +77,11 @@ export function useRemoveMember(
return useMutation<{ success: boolean }, ApiError, RemoveMemberInput>({
mutationFn: async (input) => {
return leagueService.removeMember(input.leagueId, input.driverId);
const result = await leagueService.removeMember(input.leagueId, input.driverId);
if (result.isErr()) {
throw new ApiError(result.getError().message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
...options,
});
@@ -74,7 +94,11 @@ export function useApproveJoinRequest(
return useMutation<{ success: boolean }, ApiError, JoinRequestActionInput>({
mutationFn: async (input) => {
return leagueService.approveJoinRequest(input.leagueId, input.requestId);
const result = await leagueService.approveJoinRequest(input.leagueId, input.requestId);
if (result.isErr()) {
throw new ApiError(result.getError().message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
...options,
});
@@ -87,8 +111,12 @@ export function useRejectJoinRequest(
return useMutation<{ success: boolean }, ApiError, JoinRequestActionInput>({
mutationFn: async (input) => {
return leagueService.rejectJoinRequest(input.leagueId, input.requestId);
const result = await leagueService.rejectJoinRequest(input.leagueId, input.requestId);
if (result.isErr()) {
throw new ApiError(result.getError().message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
...options,
});
}
}

View File

@@ -3,21 +3,19 @@ import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { LeagueScheduleViewModel, LeagueScheduleRaceViewModel } from '@/lib/view-models/LeagueScheduleViewModel';
import type { LeagueScheduleDTO } from '@/lib/types/generated/LeagueScheduleDTO';
import type { RaceDTO } from '@/lib/types/generated/RaceDTO';
function mapRaceDtoToViewModel(race: RaceDTO): LeagueScheduleRaceViewModel {
const scheduledAt = race.date ? new Date(race.date) : new Date(0);
const now = new Date();
const isPast = scheduledAt.getTime() < now.getTime();
const isUpcoming = !isPast;
return {
id: race.id,
name: race.name,
scheduledAt,
isPast,
isUpcoming,
isUpcoming: !isPast,
status: isPast ? 'completed' : 'scheduled',
track: undefined,
car: undefined,
@@ -32,7 +30,11 @@ export function useLeagueSchedule(leagueId: string) {
const queryResult = useQuery({
queryKey: ['leagueSchedule', leagueId],
queryFn: async (): Promise<LeagueScheduleViewModel> => {
const dto = await leagueService.getLeagueSchedule(leagueId);
const result = await leagueService.getLeagueSchedule(leagueId);
if (result.isErr()) {
throw new Error(result.getError().message);
}
const dto = result.unwrap();
const races = dto.races.map(mapRaceDtoToViewModel);
return new LeagueScheduleViewModel(races);
},
@@ -40,4 +42,4 @@ export function useLeagueSchedule(leagueId: string) {
});
return enhanceQueryResult(queryResult);
}
}

View File

@@ -5,7 +5,6 @@ import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility';
import { LeagueAdminScheduleViewModel } from '@/lib/view-models/LeagueAdminScheduleViewModel';
import { LeagueScheduleRaceViewModel } from '@/lib/view-models/LeagueScheduleViewModel';
import { LeagueSeasonSummaryViewModel } from '@/lib/view-models/LeagueSeasonSummaryViewModel';
import type { LeagueScheduleDTO } from '@/lib/types/generated/LeagueScheduleDTO';
import type { RaceDTO } from '@/lib/types/generated/RaceDTO';
import type { LeagueSeasonSummaryDTO } from '@/lib/types/generated/LeagueSeasonSummaryDTO';
@@ -13,14 +12,13 @@ function mapRaceDtoToViewModel(race: RaceDTO): LeagueScheduleRaceViewModel {
const scheduledAt = race.date ? new Date(race.date) : new Date(0);
const now = new Date();
const isPast = scheduledAt.getTime() < now.getTime();
const isUpcoming = !isPast;
return {
id: race.id,
name: race.name,
scheduledAt,
isPast,
isUpcoming,
isUpcoming: !isPast,
status: isPast ? 'completed' : 'scheduled',
track: undefined,
car: undefined,
@@ -49,7 +47,11 @@ export function useLeagueSeasons(leagueId: string, isAdmin: boolean) {
return usePageData({
queryKey: ['leagueSeasons', leagueId],
queryFn: async (): Promise<LeagueSeasonSummaryViewModel[]> => {
const dtos = await leagueService.getLeagueSeasonSummaries(leagueId);
const result = await leagueService.getLeagueSeasonSummaries(leagueId);
if (result.isErr()) {
throw new Error(result.getError().message);
}
const dtos = result.unwrap();
return dtos.map((dto: LeagueSeasonSummaryDTO) => new LeagueSeasonSummaryViewModel(dto));
},
enabled: !!leagueId && !!isAdmin,
@@ -62,7 +64,11 @@ export function useLeagueAdminSchedule(leagueId: string, selectedSeasonId: strin
return usePageData({
queryKey: ['adminSchedule', leagueId, selectedSeasonId],
queryFn: async (): Promise<LeagueAdminScheduleViewModel> => {
const dto = await leagueService.getAdminSchedule(leagueId, selectedSeasonId);
const result = await leagueService.getAdminSchedule(leagueId, selectedSeasonId);
if (result.isErr()) {
throw new Error(result.getError().message);
}
const dto = result.unwrap();
const races = dto.races.map(mapRaceDtoToViewModel);
return new LeagueAdminScheduleViewModel({
seasonId: dto.seasonId,
@@ -72,4 +78,4 @@ export function useLeagueAdminSchedule(leagueId: string, selectedSeasonId: strin
},
enabled: !!leagueId && !!selectedSeasonId && !!isAdmin,
});
}
}

View File

@@ -2,7 +2,7 @@ import { usePageMutation } from '@/lib/page/usePageData';
export function useLeagueStewardingMutations(onRefetch: () => void) {
const acceptProtestMutation = usePageMutation(
async (variables: { protestId: string; penaltyType: string; penaltyValue: number; stewardNotes: string; raceId: string; accusedDriverId: string; reason: string }) => {
async (_variables: { protestId: string; penaltyType: string; penaltyValue: number; stewardNotes: string; raceId: string; accusedDriverId: string; reason: string }) => {
// TODO: Implement protest review and penalty application
// await leagueStewardingService.reviewProtest({
// protestId: variables.protestId,
@@ -28,7 +28,7 @@ export function useLeagueStewardingMutations(onRefetch: () => void) {
);
const rejectProtestMutation = usePageMutation(
async (variables: { protestId: string; stewardNotes: string }) => {
async (_variables: { protestId: string; stewardNotes: string }) => {
// TODO: Implement protest rejection
// await leagueStewardingService.reviewProtest({
// protestId: variables.protestId,

View File

@@ -22,7 +22,7 @@ export function useLeagueWalletPageData(leagueId: string) {
amount: t.amount,
fee: t.fee,
netAmount: t.netAmount,
date: new Date(t.date),
date: new globalThis.Date(t.date),
status: t.status,
reference: t.reference,
}));
@@ -48,6 +48,6 @@ export function useLeagueWalletPageData(leagueId: string) {
* @deprecated Use useLeagueWalletWithdrawalWithBlockers instead
* This wrapper maintains backward compatibility while using the new blocker-aware hook
*/
export function useLeagueWalletWithdrawal(leagueId: string, data: any, refetch: () => void) {
export function useLeagueWalletWithdrawal(leagueId: string, data: LeagueWalletViewModel | null, refetch: () => void) {
return useLeagueWalletWithdrawalWithBlockers(leagueId, data, refetch);
}

View File

@@ -3,13 +3,15 @@
import { usePageMutation } from '@/lib/page/usePageData';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_WALLET_SERVICE_TOKEN } from '@/lib/di/tokens';
import { SubmitBlocker, ThrottleBlocker } from '@/lib/blockers';
import { SubmitBlocker } from '@/lib/blockers/SubmitBlocker';
import { ThrottleBlocker } from '@/lib/blockers/ThrottleBlocker';
import type { LeagueWalletViewModel } from '@/lib/view-models/LeagueWalletViewModel';
/**
* Hook for wallet withdrawals with client-side blockers
* Handles UX prevention mechanisms (rate limiting, duplicate submission prevention)
*/
export function useLeagueWalletWithdrawalWithBlockers(leagueId: string, data: any, refetch: () => void) {
export function useLeagueWalletWithdrawalWithBlockers(leagueId: string, data: LeagueWalletViewModel | null, refetch: () => void) {
const leagueWalletService = useInject(LEAGUE_WALLET_SERVICE_TOKEN);
// Client-side blockers for UX improvement
@@ -18,11 +20,11 @@ export function useLeagueWalletWithdrawalWithBlockers(leagueId: string, data: an
const withdrawMutation = usePageMutation(
async ({ amount }: { amount: number }) => {
if (!data) throw new Error('Wallet data not available');
if (!data) throw new globalThis.Error('Wallet data not available');
// Client-side blockers (UX only, not security)
if (!submitBlocker.canExecute() || !throttle.canExecute()) {
throw new Error('Request blocked due to rate limiting');
throw new globalThis.Error('Request blocked due to rate limiting');
}
submitBlocker.block();
@@ -38,7 +40,7 @@ export function useLeagueWalletWithdrawalWithBlockers(leagueId: string, data: an
);
if (!result.success) {
throw new Error(result.message || 'Withdrawal failed');
throw new globalThis.Error(result.message || 'Withdrawal failed');
}
return result;

View File

@@ -7,7 +7,7 @@ export function usePenaltyMutation() {
const queryClient = useQueryClient();
const applyPenaltyMutation = useMutation({
mutationFn: (command: any) => penaltyService.applyPenalty(command),
mutationFn: (command: unknown) => penaltyService.applyPenalty(command),
onSuccess: () => {
// Invalidate relevant queries to refresh data
queryClient.invalidateQueries({ queryKey: ['leagueStewardingData'] });

View File

@@ -2,15 +2,23 @@ import { useQuery } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_STEWARDING_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
export function useProtestDetail(leagueId: string, protestId: string, enabled: boolean = true) {
const leagueStewardingService = useInject(LEAGUE_STEWARDING_SERVICE_TOKEN);
const queryResult = useQuery({
queryKey: ['protestDetail', leagueId, protestId],
queryFn: () => leagueStewardingService.getProtestDetailViewModel(leagueId, protestId),
queryFn: async () => {
const result = await leagueStewardingService.getProtestDetailViewModel(leagueId, protestId);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
enabled: enabled && !!leagueId && !!protestId,
});
return enhanceQueryResult(queryResult);
}
}