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,6 +0,0 @@
export { useCurrentSession } from './useCurrentSession';
export { useLogin } from './useLogin';
export { useLogout } from './useLogout';
export { useSignup } from './useSignup';
export { useForgotPassword } from './useForgotPassword';
export { useResetPassword } from './useResetPassword';

View File

@@ -12,7 +12,15 @@ export function useCurrentSession(
const queryResult = useQuery({
queryKey: ['currentSession'],
queryFn: () => sessionService.getSession(),
queryFn: async () => {
const result = await sessionService.getSession();
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
const session = result.unwrap();
return session ? new SessionViewModel(session.user) : null;
},
initialData: options?.initialData,
...options,
});

View File

@@ -9,7 +9,13 @@ export function useLogout(
const authService = useInject(AUTH_SERVICE_TOKEN);
return useMutation<void, ApiError, void>({
mutationFn: () => authService.logout(),
mutationFn: async () => {
const result = await authService.logout();
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
},
...options,
});
}

View File

@@ -1,6 +0,0 @@
export { useCurrentDriver } from './useCurrentDriver';
export { useDriverLeaderboard } from '@/lib/hooks/useDriverLeaderboard';
export { useDriverProfile } from './useDriverProfile';
export { useUpdateDriverProfile } from './useUpdateDriverProfile';
export { useCreateDriver } from './useCreateDriver';
export { useFindDriverById } from './useFindDriverById';

View File

@@ -3,17 +3,22 @@ import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
type DriverData = any; // Replace with actual type
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
export function useCurrentDriver(
options?: Omit<UseQueryOptions<DriverData, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<DriverDTO | null, ApiError>, 'queryKey' | 'queryFn'>
) {
const driverService = useInject(DRIVER_SERVICE_TOKEN);
const queryResult = useQuery({
queryKey: ['currentDriver'],
queryFn: () => driverService.getCurrentDriver(),
queryFn: async () => {
const result = await driverService.getCurrentDriver();
if (result.isErr()) {
throw result.getError();
}
return result.unwrap();
},
...options,
});

View File

@@ -3,7 +3,7 @@ import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
import { DriverProfileViewModel } from '@/lib/view-models/DriverProfileViewModel';
import { DriverProfileViewModel, type DriverProfileViewModelData } from '@/lib/view-models/DriverProfileViewModel';
export function useDriverProfile(
driverId: string,
@@ -13,10 +13,17 @@ export function useDriverProfile(
const queryResult = useQuery({
queryKey: ['driverProfile', driverId],
queryFn: () => driverService.getDriverProfile(driverId),
queryFn: async () => {
const result = await driverService.getDriverProfile(driverId);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new globalThis.Date().toISOString() });
}
return new DriverProfileViewModel(result.unwrap() as unknown as DriverProfileViewModelData);
},
enabled: !!driverId,
...options,
});
return enhanceQueryResult(queryResult);
}
}

View File

@@ -13,10 +13,17 @@ export function useFindDriverById(
const queryResult = useQuery({
queryKey: ['driver', driverId],
queryFn: () => driverService.findById(driverId),
queryFn: async () => {
const result = await driverService.findById(driverId);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
enabled: !!driverId,
...options,
});
return enhanceQueryResult(queryResult);
}
}

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);
}
}

View File

@@ -1,7 +1,6 @@
'use client';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { OnboardingService } from '@/lib/services/onboarding/OnboardingService';
import { Result } from '@/lib/contracts/Result';
import { DomainError } from '@/lib/contracts/services/Service';
@@ -21,8 +20,7 @@ export function useGenerateAvatars(
options?: Omit<UseMutationOptions<Result<GenerateAvatarsResult, DomainError>, Error, GenerateAvatarsParams>, 'mutationFn'>
) {
return useMutation<Result<GenerateAvatarsResult, DomainError>, Error, GenerateAvatarsParams>({
mutationFn: async (params) => {
const service = new OnboardingService();
mutationFn: async (_params) => {
// This method doesn't exist in the service yet, but the hook is now created
// The service will need to implement this or we need to adjust the architecture
return Result.ok({ success: false, errorMessage: 'Not implemented' });

View File

@@ -10,7 +10,13 @@ export function useFileProtest(
const raceService = useInject(RACE_SERVICE_TOKEN);
return useMutation<void, ApiError, FileProtestCommandDTO>({
mutationFn: (command) => raceService.fileProtest(command),
mutationFn: async (command) => {
const result = await raceService.fileProtest(command);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
},
...options,
});
}
}

View File

@@ -15,7 +15,13 @@ export function useRegisterForRace(
const raceService = useInject(RACE_SERVICE_TOKEN);
return useMutation<void, ApiError, RegisterForRaceParams>({
mutationFn: (params) => raceService.registerForRace(params.raceId, params.leagueId, params.driverId),
mutationFn: async (params) => {
const result = await raceService.registerForRace(params.raceId, params.leagueId, params.driverId);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
},
...options,
});
}

View File

@@ -14,7 +14,13 @@ export function useWithdrawFromRace(
const raceService = useInject(RACE_SERVICE_TOKEN);
return useMutation<void, ApiError, WithdrawFromRaceParams>({
mutationFn: (params) => raceService.withdrawFromRace(params.raceId, params.driverId),
mutationFn: async (params) => {
const result = await raceService.withdrawFromRace(params.raceId, params.driverId);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
},
...options,
});
}

View File

@@ -1,5 +0,0 @@
export { useAvailableLeagues } from './useAvailableLeagues';
export { useSponsorDashboard } from './useSponsorDashboard';
export { useSponsorSponsorships } from './useSponsorSponsorships';
export { useSponsorBilling } from './useSponsorBilling';
export { useSponsorLeagueDetail } from './useSponsorLeagueDetail';

View File

@@ -11,7 +11,7 @@ export function useSponsorBilling(sponsorId: string) {
queryFn: async () => {
const result = await sponsorService.getBilling(sponsorId);
if (result.isErr()) {
throw result.getError();
throw new Error(result.getError().message);
}
return result.unwrap();
},
@@ -19,4 +19,4 @@ export function useSponsorBilling(sponsorId: string) {
});
return enhanceQueryResult(queryResult);
}
}

View File

@@ -1,11 +1,11 @@
import { useAuth } from '@/lib/auth/AuthContext';
import React from 'react';
import { useState, useEffect } from 'react';
export function useSponsorMode(): boolean {
const { session } = useAuth();
const [isSponsor, setIsSponsor] = React.useState(false);
const [isSponsor, setIsSponsor] = useState(false);
React.useEffect(() => {
useEffect(() => {
if (!session) {
setIsSponsor(false);
return;

View File

@@ -1,13 +0,0 @@
export { useAllTeams } from './useAllTeams';
export { useTeamDetails } from './useTeamDetails';
export { useTeamMembers } from './useTeamMembers';
export { useTeamJoinRequests } from './useTeamJoinRequests';
export { useCreateTeam } from './useCreateTeam';
export { useUpdateTeam } from './useUpdateTeam';
export { useTeamMembership } from './useTeamMembership';
export { useApproveJoinRequest } from './useApproveJoinRequest';
export { useRejectJoinRequest } from './useRejectJoinRequest';
export { useTeamStandings } from './useTeamStandings';
export { useJoinTeam } from './useJoinTeam';
export { useLeaveTeam } from './useLeaveTeam';
export { useTeamRoster } from './useTeamRoster';

View File

@@ -7,7 +7,13 @@ export function useApproveJoinRequest(options?: Omit<UseMutationOptions<void, Ap
const teamJoinService = useInject(TEAM_JOIN_SERVICE_TOKEN);
return useMutation<void, ApiError, void>({
mutationFn: () => teamJoinService.approveJoinRequest(),
mutationFn: async () => {
const result = await teamJoinService.approveJoinRequest();
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
},
...options,
});
}
}

View File

@@ -1,6 +1,4 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
interface JoinTeamParams {
@@ -10,8 +8,6 @@ interface JoinTeamParams {
}
export function useJoinTeam(options?: Omit<UseMutationOptions<void, ApiError, JoinTeamParams>, 'mutationFn'>) {
const teamService = useInject(TEAM_SERVICE_TOKEN);
return useMutation<void, ApiError, JoinTeamParams>({
mutationFn: async (params) => {
// Note: Team join functionality would need to be added to teamService

View File

@@ -1,6 +1,4 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/api/base/ApiError';
interface LeaveTeamParams {
@@ -9,8 +7,6 @@ interface LeaveTeamParams {
}
export function useLeaveTeam(options?: Omit<UseMutationOptions<void, ApiError, LeaveTeamParams>, 'mutationFn'>) {
const teamService = useInject(TEAM_SERVICE_TOKEN);
return useMutation<void, ApiError, LeaveTeamParams>({
mutationFn: async (params) => {
// Note: Leave team functionality would need to be added to teamService

View File

@@ -7,7 +7,13 @@ export function useRejectJoinRequest(options?: Omit<UseMutationOptions<void, Api
const teamJoinService = useInject(TEAM_JOIN_SERVICE_TOKEN);
return useMutation<void, ApiError, void>({
mutationFn: () => teamJoinService.rejectJoinRequest(),
mutationFn: async () => {
const result = await teamJoinService.rejectJoinRequest();
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
},
...options,
});
}
}

View File

@@ -2,15 +2,23 @@ import { useQuery } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_JOIN_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import { ApiError } from '@/lib/api/base/ApiError';
export function useTeamJoinRequests(teamId: string, currentUserId: string, isOwner: boolean) {
const teamJoinService = useInject(TEAM_JOIN_SERVICE_TOKEN);
const queryResult = useQuery({
queryKey: ['teamJoinRequests', teamId, currentUserId, isOwner],
queryFn: () => teamJoinService.getJoinRequests(teamId, currentUserId, isOwner),
queryFn: async () => {
const result = await teamJoinService.getJoinRequests(teamId, currentUserId, isOwner);
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
enabled: !!teamId && !!currentUserId,
});
return enhanceQueryResult(queryResult);
}
}

View File

@@ -2,7 +2,6 @@ import { useQuery } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO';
export function useTeamMembership(teamId: string, driverId: string) {
const teamService = useInject(TEAM_SERVICE_TOKEN);

View File

@@ -1,12 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { TEAM_SERVICE_TOKEN, DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
type TeamMemberRole = 'owner' | 'manager' | 'member';
interface TeamRosterMember {
driver: any;
driver: unknown;
role: TeamMemberRole;
joinedAt: string;
rating: number | null;
@@ -18,7 +18,6 @@ export function useTeamRoster(memberships: Array<{
role: string;
joinedAt: string;
}>) {
const teamService = useInject(TEAM_SERVICE_TOKEN);
const driverService = useInject(DRIVER_SERVICE_TOKEN);
const queryResult = useQuery<TeamRosterMember[]>({

View File

@@ -1,13 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
export function useTeamStandings(teamId: string, leagues: string[]) {
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
export function useTeamStandings(_teamId: string, leagues: string[]) {
const queryResult = useQuery({
queryKey: ['teamStandings', teamId, leagues],
queryKey: ['teamStandings', _teamId, leagues],
queryFn: async () => {
// For demo purposes, create mock standings
return leagues.map(leagueId => ({

View File

@@ -13,7 +13,14 @@ export function useCapability(
const queryResult = useQuery({
queryKey: ['policySnapshot', capabilityKey],
queryFn: () => policyService.getSnapshot(),
queryFn: async () => {
const result = await policyService.getSnapshot();
if (result.isErr()) {
const error = result.getError();
throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new Date().toISOString() });
}
return result.unwrap();
},
staleTime: 60_000,
gcTime: 5 * 60_000,
...options,
@@ -32,4 +39,4 @@ export function useCapability(
isCapabilityEnabled: capabilityState === 'enabled',
isCapabilityComingSoon: capabilityState === 'coming_soon',
};
}
}

View File

@@ -6,8 +6,7 @@
*/
import { useState, useCallback, useEffect, FormEvent, ChangeEvent, Dispatch, SetStateAction } from 'react';
import { parseApiError, formatValidationErrorsForForm, logErrorWithContext, createErrorContext } from '@/lib/utils/errorUtils';
import { ApiError } from '@/lib/api/base/ApiError';
import { parseApiError, formatValidationErrorsForForm, logErrorWithContext } from '@/lib/utils/errorUtils';
export interface FormField<T> {
value: T;
@@ -16,7 +15,7 @@ export interface FormField<T> {
validating: boolean;
}
export interface FormState<T extends Record<string, any>> {
export interface FormState<T extends Record<string, unknown>> {
fields: { [K in keyof T]: FormField<T[K]> };
isValid: boolean;
isSubmitting: boolean;
@@ -24,7 +23,7 @@ export interface FormState<T extends Record<string, any>> {
submitCount: number;
}
export interface FormOptions<T extends Record<string, any>> {
export interface FormOptions<T extends Record<string, unknown>> {
initialValues: T;
validate?: (values: T) => Record<string, string> | Promise<Record<string, string>>;
onSubmit: (values: T) => Promise<void>;
@@ -33,7 +32,7 @@ export interface FormOptions<T extends Record<string, any>> {
component?: string;
}
export interface UseEnhancedFormReturn<T extends Record<string, any>> {
export interface UseEnhancedFormReturn<T extends Record<string, unknown>> {
formState: FormState<T>;
setFormState: Dispatch<SetStateAction<FormState<T>>>;
handleChange: (e: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void;
@@ -49,7 +48,7 @@ export interface UseEnhancedFormReturn<T extends Record<string, any>> {
/**
* Enhanced form hook with comprehensive error handling
*/
export function useEnhancedForm<T extends Record<string, any>>(
export function useEnhancedForm<T extends Record<string, unknown>>(
options: FormOptions<T>
): UseEnhancedFormReturn<T> {
const [formState, setFormState] = useState<FormState<T>>(() => ({
@@ -68,6 +67,13 @@ export function useEnhancedForm<T extends Record<string, any>>(
submitCount: 0,
}));
const getValues = useCallback((): T => {
return Object.keys(formState.fields).reduce((acc, key) => ({
...acc,
[key]: formState.fields[key as keyof T].value,
}), {} as T);
}, [formState.fields]);
// Validate form on change
useEffect(() => {
if (options.validate && formState.submitCount > 0) {
@@ -91,18 +97,11 @@ export function useEnhancedForm<T extends Record<string, any>>(
};
validateAsync();
}
}, [formState.fields, formState.submitCount, options.validate]);
const getValues = useCallback((): T => {
return Object.keys(formState.fields).reduce((acc, key) => ({
...acc,
[key]: formState.fields[key as keyof T].value,
}), {} as T);
}, [formState.fields]);
}, [formState.fields, formState.submitCount, options.validate, getValues]);
const handleChange = useCallback((e: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
const { name, value, type } = e.target;
const checked = 'checked' in e.target ? e.target.checked : false;
const checked = 'checked' in e.target ? (e.target as HTMLInputElement).checked : false;
const fieldValue = type === 'checkbox' ? checked : value;
setFormState(prev => ({
@@ -267,7 +266,7 @@ export function useEnhancedForm<T extends Record<string, any>>(
}
} catch (validationError) {
logErrorWithContext(validationError, {
timestamp: new Date().toISOString(),
timestamp: new globalThis.Date().toISOString(),
component: options.component || 'useEnhancedForm',
action: 'validate',
formData: values,
@@ -298,7 +297,7 @@ export function useEnhancedForm<T extends Record<string, any>>(
// Log for developers
logErrorWithContext(error, {
timestamp: new Date().toISOString(),
timestamp: new globalThis.Date().toISOString(),
component: options.component || 'useEnhancedForm',
action: 'submit',
formData: values,

View File

@@ -9,9 +9,12 @@ export function useLeagueScoringPresets() {
const queryResult = useQuery({
queryKey: ['leagueScoringPresets'],
queryFn: async () => {
queryFn: async (): Promise<LeagueScoringPresetDTO[]> => {
const result = await leagueService.getScoringPresets();
return result as LeagueScoringPresetDTO[];
if (result.isErr()) {
throw new Error(result.getError().message);
}
return result.unwrap() as unknown as LeagueScoringPresetDTO[];
},
});

View File

@@ -77,7 +77,11 @@ export function useCreateLeagueWizard() {
// Use the league service to create the league
const result = await leagueService.createLeague(input);
return result;
if (result.isErr()) {
throw new Error(result.getError().message);
}
return result.unwrap();
},
});
}