website refactor
This commit is contained in:
15
apps/website/hooks/league/useAllLeagues.ts
Normal file
15
apps/website/hooks/league/useAllLeagues.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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 useAllLeagues() {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['allLeagues'],
|
||||
queryFn: () => leagueService.getAllLeagues(),
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
9
apps/website/hooks/league/useCreateLeague.ts
Normal file
9
apps/website/hooks/league/useCreateLeague.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useCreateLeagueWithBlockers } from './useCreateLeagueWithBlockers';
|
||||
|
||||
/**
|
||||
* @deprecated Use useCreateLeagueWithBlockers instead
|
||||
* This wrapper maintains backward compatibility while using the new blocker-aware hook
|
||||
*/
|
||||
export function useCreateLeague() {
|
||||
return useCreateLeagueWithBlockers();
|
||||
}
|
||||
50
apps/website/hooks/league/useCreateLeagueWithBlockers.ts
Normal file
50
apps/website/hooks/league/useCreateLeagueWithBlockers.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import { CreateLeagueInputDTO } from '@/lib/types/generated/CreateLeagueInputDTO';
|
||||
import { CreateLeagueOutputDTO } from '@/lib/types/generated/CreateLeagueOutputDTO';
|
||||
|
||||
interface CreateLeagueInput {
|
||||
name: string;
|
||||
description: string;
|
||||
maxDrivers: number;
|
||||
scoringPresetId: string;
|
||||
}
|
||||
|
||||
interface CreateLeagueResult {
|
||||
success: boolean;
|
||||
leagueId: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export function useCreateLeagueWithBlockers(
|
||||
options?: Omit<UseMutationOptions<CreateLeagueResult, ApiError, CreateLeagueInput>, 'mutationFn'>
|
||||
) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useMutation<CreateLeagueResult, ApiError, CreateLeagueInput>({
|
||||
mutationFn: async (input) => {
|
||||
try {
|
||||
// Transform input to DTO - note: maxDrivers and scoringPresetId are not in the DTO
|
||||
// This hook may need to be updated based on actual API requirements
|
||||
const inputDto: CreateLeagueInputDTO = {
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
visibility: 'public', // Default value
|
||||
ownerId: '', // Will be set by the service
|
||||
};
|
||||
|
||||
const result: CreateLeagueOutputDTO = await leagueService.createLeague(inputDto);
|
||||
return { success: result.success, leagueId: result.leagueId };
|
||||
} catch (error) {
|
||||
// Check if it's a rate limit error
|
||||
if (error instanceof ApiError && error.type === 'RATE_LIMIT_ERROR') {
|
||||
return { success: false, leagueId: '', error: 'Rate limited' };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
21
apps/website/hooks/league/useLeagueAdminStatus.ts
Normal file
21
apps/website/hooks/league/useLeagueAdminStatus.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_MEMBERSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility';
|
||||
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
|
||||
|
||||
export function useLeagueAdminStatus(leagueId: string, currentDriverId: string) {
|
||||
const leagueMembershipService = useInject(LEAGUE_MEMBERSHIP_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['leagueMembership', leagueId, currentDriverId],
|
||||
queryFn: async () => {
|
||||
await leagueMembershipService.fetchLeagueMemberships(leagueId);
|
||||
const membership = leagueMembershipService.getMembership(leagueId, currentDriverId);
|
||||
return membership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(membership.role) : false;
|
||||
},
|
||||
enabled: !!leagueId && !!currentDriverId,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
54
apps/website/hooks/league/useLeagueDetail.ts
Normal file
54
apps/website/hooks/league/useLeagueDetail.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO';
|
||||
import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO';
|
||||
import type { AllLeaguesWithCapacityAndScoringDTO } from '@/lib/types/AllLeaguesWithCapacityAndScoringDTO';
|
||||
|
||||
interface UseLeagueDetailOptions {
|
||||
leagueId: string;
|
||||
queryOptions?: UseQueryOptions<LeagueWithCapacityAndScoringDTO, ApiError>;
|
||||
}
|
||||
|
||||
interface UseLeagueMembershipsOptions {
|
||||
leagueId: string;
|
||||
queryOptions?: UseQueryOptions<LeagueMembershipsDTO, ApiError>;
|
||||
}
|
||||
|
||||
export function useLeagueDetail({ leagueId, queryOptions }: UseLeagueDetailOptions) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useQuery<LeagueWithCapacityAndScoringDTO, ApiError>({
|
||||
queryKey: ['league-detail', leagueId],
|
||||
queryFn: async () => {
|
||||
const result = await leagueService.getAllLeagues() as AllLeaguesWithCapacityAndScoringDTO;
|
||||
// Filter for the specific league
|
||||
const leagues = Array.isArray(result?.leagues) ? result.leagues : [];
|
||||
const league = leagues.find(l => l.id === leagueId);
|
||||
if (!league) {
|
||||
throw new ApiError('League not found', 'NOT_FOUND', {
|
||||
endpoint: 'getAllLeagues',
|
||||
statusCode: 404,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
return league;
|
||||
},
|
||||
...queryOptions,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLeagueMemberships({ leagueId, queryOptions }: UseLeagueMembershipsOptions) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useQuery<LeagueMembershipsDTO, ApiError>({
|
||||
queryKey: ['league-memberships', leagueId],
|
||||
queryFn: async () => {
|
||||
const result = await leagueService.getLeagueMemberships(leagueId);
|
||||
// The DTO already has the correct structure with members property
|
||||
return result;
|
||||
},
|
||||
...queryOptions,
|
||||
});
|
||||
}
|
||||
31
apps/website/hooks/league/useLeagueMembershipMutation.ts
Normal file
31
apps/website/hooks/league/useLeagueMembershipMutation.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_MEMBERSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
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),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['leagueMemberships'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['allLeagues'] });
|
||||
},
|
||||
});
|
||||
|
||||
const leaveLeagueMutation = useMutation({
|
||||
mutationFn: ({ leagueId, driverId }: { leagueId: string; driverId: string }) =>
|
||||
leagueMembershipService.leaveLeague(leagueId, driverId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['leagueMemberships'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['allLeagues'] });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
joinLeague: joinLeagueMutation,
|
||||
leaveLeague: leaveLeagueMutation,
|
||||
};
|
||||
}
|
||||
16
apps/website/hooks/league/useLeagueMemberships.ts
Normal file
16
apps/website/hooks/league/useLeagueMemberships.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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 useLeagueMemberships(leagueId: string, currentUserId: string) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['leagueMemberships', leagueId, currentUserId],
|
||||
queryFn: () => leagueService.getLeagueMemberships(leagueId),
|
||||
enabled: !!leagueId && !!currentUserId,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
16
apps/website/hooks/league/useLeagueRaces.ts
Normal file
16
apps/website/hooks/league/useLeagueRaces.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { RACE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
|
||||
|
||||
export function useLeagueRaces(leagueId: string) {
|
||||
const raceService = useInject(RACE_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['leagueRaces', leagueId],
|
||||
queryFn: () => raceService.findByLeagueId(leagueId),
|
||||
enabled: !!leagueId,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
94
apps/website/hooks/league/useLeagueRosterAdmin.ts
Normal file
94
apps/website/hooks/league/useLeagueRosterAdmin.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useMutation, useQuery, UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
|
||||
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
|
||||
|
||||
interface UpdateMemberRoleInput {
|
||||
leagueId: string;
|
||||
driverId: string;
|
||||
newRole: 'owner' | 'admin' | 'steward' | 'member';
|
||||
}
|
||||
|
||||
interface RemoveMemberInput {
|
||||
leagueId: string;
|
||||
driverId: string;
|
||||
}
|
||||
|
||||
interface JoinRequestActionInput {
|
||||
leagueId: string;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export function useLeagueRosterAdmin(leagueId: string, options?: UseQueryOptions<LeagueRosterMemberDTO[], ApiError>) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useQuery<LeagueRosterMemberDTO[], ApiError>({
|
||||
queryKey: ['league-roster-admin', leagueId],
|
||||
queryFn: () => leagueService.getAdminRosterMembers(leagueId),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLeagueJoinRequests(leagueId: string, options?: UseQueryOptions<LeagueRosterJoinRequestDTO[], ApiError>) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useQuery<LeagueRosterJoinRequestDTO[], ApiError>({
|
||||
queryKey: ['league-join-requests', leagueId],
|
||||
queryFn: () => leagueService.getAdminRosterJoinRequests(leagueId),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateMemberRole(
|
||||
options?: Omit<UseMutationOptions<{ success: boolean }, ApiError, UpdateMemberRoleInput>, 'mutationFn'>
|
||||
) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useMutation<{ success: boolean }, ApiError, UpdateMemberRoleInput>({
|
||||
mutationFn: async (input) => {
|
||||
return leagueService.updateMemberRole(input.leagueId, input.driverId, input.newRole);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemoveMember(
|
||||
options?: Omit<UseMutationOptions<{ success: boolean }, ApiError, RemoveMemberInput>, 'mutationFn'>
|
||||
) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useMutation<{ success: boolean }, ApiError, RemoveMemberInput>({
|
||||
mutationFn: async (input) => {
|
||||
return leagueService.removeMember(input.leagueId, input.driverId);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function useApproveJoinRequest(
|
||||
options?: Omit<UseMutationOptions<{ success: boolean }, ApiError, JoinRequestActionInput>, 'mutationFn'>
|
||||
) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useMutation<{ success: boolean }, ApiError, JoinRequestActionInput>({
|
||||
mutationFn: async (input) => {
|
||||
return leagueService.approveJoinRequest(input.leagueId, input.requestId);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRejectJoinRequest(
|
||||
options?: Omit<UseMutationOptions<{ success: boolean }, ApiError, JoinRequestActionInput>, 'mutationFn'>
|
||||
) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return useMutation<{ success: boolean }, ApiError, JoinRequestActionInput>({
|
||||
mutationFn: async (input) => {
|
||||
return leagueService.rejectJoinRequest(input.leagueId, input.requestId);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
43
apps/website/hooks/league/useLeagueSchedule.ts
Normal file
43
apps/website/hooks/league/useLeagueSchedule.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
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';
|
||||
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,
|
||||
status: isPast ? 'completed' : 'scheduled',
|
||||
track: undefined,
|
||||
car: undefined,
|
||||
sessionType: undefined,
|
||||
isRegistered: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function useLeagueSchedule(leagueId: string) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['leagueSchedule', leagueId],
|
||||
queryFn: async (): Promise<LeagueScheduleViewModel> => {
|
||||
const dto = await leagueService.getLeagueSchedule(leagueId);
|
||||
const races = dto.races.map(mapRaceDtoToViewModel);
|
||||
return new LeagueScheduleViewModel(races);
|
||||
},
|
||||
enabled: !!leagueId,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
75
apps/website/hooks/league/useLeagueScheduleAdminPageData.ts
Normal file
75
apps/website/hooks/league/useLeagueScheduleAdminPageData.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { usePageData } from '@/lib/page/usePageData';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN, LEAGUE_MEMBERSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
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';
|
||||
|
||||
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,
|
||||
status: isPast ? 'completed' : 'scheduled',
|
||||
track: undefined,
|
||||
car: undefined,
|
||||
sessionType: undefined,
|
||||
isRegistered: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function useLeagueAdminStatus(leagueId: string, currentDriverId: string) {
|
||||
const leagueMembershipService = useInject(LEAGUE_MEMBERSHIP_SERVICE_TOKEN);
|
||||
|
||||
return usePageData({
|
||||
queryKey: ['admin-check', leagueId, currentDriverId],
|
||||
queryFn: async () => {
|
||||
await leagueMembershipService.fetchLeagueMemberships(leagueId);
|
||||
const membership = leagueMembershipService.getMembership(leagueId, currentDriverId);
|
||||
return membership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(membership.role) : false;
|
||||
},
|
||||
enabled: !!leagueId && !!currentDriverId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLeagueSeasons(leagueId: string, isAdmin: boolean) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return usePageData({
|
||||
queryKey: ['leagueSeasons', leagueId],
|
||||
queryFn: async (): Promise<LeagueSeasonSummaryViewModel[]> => {
|
||||
const dtos = await leagueService.getLeagueSeasonSummaries(leagueId);
|
||||
return dtos.map((dto: LeagueSeasonSummaryDTO) => new LeagueSeasonSummaryViewModel(dto));
|
||||
},
|
||||
enabled: !!leagueId && !!isAdmin,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLeagueAdminSchedule(leagueId: string, selectedSeasonId: string, isAdmin: boolean) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
return usePageData({
|
||||
queryKey: ['adminSchedule', leagueId, selectedSeasonId],
|
||||
queryFn: async (): Promise<LeagueAdminScheduleViewModel> => {
|
||||
const dto = await leagueService.getAdminSchedule(leagueId, selectedSeasonId);
|
||||
const races = dto.races.map(mapRaceDtoToViewModel);
|
||||
return new LeagueAdminScheduleViewModel({
|
||||
seasonId: dto.seasonId,
|
||||
published: dto.published,
|
||||
races,
|
||||
});
|
||||
},
|
||||
enabled: !!leagueId && !!selectedSeasonId && !!isAdmin,
|
||||
});
|
||||
}
|
||||
16
apps/website/hooks/league/useLeagueSeasons.ts
Normal file
16
apps/website/hooks/league/useLeagueSeasons.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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 useLeagueSeasons(leagueId: string) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['leagueSeasons', leagueId],
|
||||
queryFn: () => leagueService.getLeagueSeasons(leagueId),
|
||||
enabled: !!leagueId,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
22
apps/website/hooks/league/useLeagueSettings.ts
Normal file
22
apps/website/hooks/league/useLeagueSettings.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SETTINGS_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
|
||||
import { ApiError } from '@/lib/api/base/ApiError';
|
||||
import type { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel';
|
||||
|
||||
export function useLeagueSettings(
|
||||
leagueId: string,
|
||||
options?: Omit<UseQueryOptions<LeagueSettingsViewModel | null, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) {
|
||||
const leagueSettingsService = useInject(LEAGUE_SETTINGS_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['leagueSettings', leagueId],
|
||||
queryFn: () => leagueSettingsService.getLeagueSettings(leagueId),
|
||||
enabled: !!leagueId,
|
||||
...options,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
21
apps/website/hooks/league/useLeagueSponsorshipsPageData.ts
Normal file
21
apps/website/hooks/league/useLeagueSponsorshipsPageData.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { usePageDataMultiple } from '@/lib/page/usePageData';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_SERVICE_TOKEN, LEAGUE_MEMBERSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
export function useLeagueSponsorshipsPageData(leagueId: string, currentDriverId: string) {
|
||||
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
||||
const leagueMembershipService = useInject(LEAGUE_MEMBERSHIP_SERVICE_TOKEN);
|
||||
|
||||
return usePageDataMultiple({
|
||||
league: {
|
||||
queryKey: ['leagueDetail', leagueId, currentDriverId],
|
||||
queryFn: () => leagueService.getLeagueDetail(leagueId),
|
||||
},
|
||||
membership: {
|
||||
queryKey: ['leagueMembership', leagueId, currentDriverId],
|
||||
queryFn: () => leagueMembershipService.fetchLeagueMemberships(leagueId).then(() => {
|
||||
return leagueMembershipService.getMembership(leagueId, currentDriverId);
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
16
apps/website/hooks/league/useLeagueStewardingData.ts
Normal file
16
apps/website/hooks/league/useLeagueStewardingData.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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';
|
||||
|
||||
export function useLeagueStewardingData(leagueId: string) {
|
||||
const leagueStewardingService = useInject(LEAGUE_STEWARDING_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['leagueStewardingData', leagueId],
|
||||
queryFn: () => leagueStewardingService.getLeagueStewardingData(leagueId),
|
||||
enabled: !!leagueId,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
46
apps/website/hooks/league/useLeagueStewardingMutations.ts
Normal file
46
apps/website/hooks/league/useLeagueStewardingMutations.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
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 }) => {
|
||||
// TODO: Implement protest review and penalty application
|
||||
// await leagueStewardingService.reviewProtest({
|
||||
// protestId: variables.protestId,
|
||||
// stewardId: currentDriverId,
|
||||
// decision: 'uphold',
|
||||
// decisionNotes: variables.stewardNotes,
|
||||
// });
|
||||
|
||||
// await leagueStewardingService.applyPenalty({
|
||||
// raceId: variables.raceId,
|
||||
// driverId: variables.accusedDriverId,
|
||||
// stewardId: currentDriverId,
|
||||
// type: variables.penaltyType,
|
||||
// value: variables.penaltyValue,
|
||||
// reason: variables.reason,
|
||||
// protestId: variables.protestId,
|
||||
// notes: variables.stewardNotes,
|
||||
// });
|
||||
},
|
||||
{
|
||||
onSuccess: () => onRefetch(),
|
||||
}
|
||||
);
|
||||
|
||||
const rejectProtestMutation = usePageMutation(
|
||||
async (variables: { protestId: string; stewardNotes: string }) => {
|
||||
// TODO: Implement protest rejection
|
||||
// await leagueStewardingService.reviewProtest({
|
||||
// protestId: variables.protestId,
|
||||
// stewardId: currentDriverId,
|
||||
// decision: 'dismiss',
|
||||
// decisionNotes: variables.stewardNotes,
|
||||
// });
|
||||
},
|
||||
{
|
||||
onSuccess: () => onRefetch(),
|
||||
}
|
||||
);
|
||||
|
||||
return { acceptProtestMutation, rejectProtestMutation };
|
||||
}
|
||||
53
apps/website/hooks/league/useLeagueWalletPageData.ts
Normal file
53
apps/website/hooks/league/useLeagueWalletPageData.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
'use client';
|
||||
|
||||
import { usePageData } from '@/lib/page/usePageData';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { LEAGUE_WALLET_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { LeagueWalletViewModel } from '@/lib/view-models/LeagueWalletViewModel';
|
||||
import { WalletTransactionViewModel } from '@/lib/view-models/WalletTransactionViewModel';
|
||||
import { useLeagueWalletWithdrawalWithBlockers } from './useLeagueWalletWithdrawalWithBlockers';
|
||||
|
||||
export function useLeagueWalletPageData(leagueId: string) {
|
||||
const leagueWalletService = useInject(LEAGUE_WALLET_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = usePageData({
|
||||
queryKey: ['leagueWallet', leagueId],
|
||||
queryFn: async () => {
|
||||
const dto = await leagueWalletService.getWalletForLeague(leagueId);
|
||||
// Transform DTO to ViewModel at client boundary
|
||||
const transactions = dto.transactions.map(t => new WalletTransactionViewModel({
|
||||
id: t.id,
|
||||
type: t.type,
|
||||
description: t.description,
|
||||
amount: t.amount,
|
||||
fee: t.fee,
|
||||
netAmount: t.netAmount,
|
||||
date: new Date(t.date),
|
||||
status: t.status,
|
||||
reference: t.reference,
|
||||
}));
|
||||
return new LeagueWalletViewModel({
|
||||
balance: dto.balance,
|
||||
currency: dto.currency,
|
||||
totalRevenue: dto.totalRevenue,
|
||||
totalFees: dto.totalFees,
|
||||
totalWithdrawals: dto.totalWithdrawals,
|
||||
pendingPayouts: dto.pendingPayouts,
|
||||
transactions,
|
||||
canWithdraw: dto.canWithdraw,
|
||||
withdrawalBlockReason: dto.withdrawalBlockReason,
|
||||
});
|
||||
},
|
||||
enabled: !!leagueId,
|
||||
});
|
||||
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
return useLeagueWalletWithdrawalWithBlockers(leagueId, data, refetch);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
'use client';
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const leagueWalletService = useInject(LEAGUE_WALLET_SERVICE_TOKEN);
|
||||
|
||||
// Client-side blockers for UX improvement
|
||||
const submitBlocker = new SubmitBlocker();
|
||||
const throttle = new ThrottleBlocker(500);
|
||||
|
||||
const withdrawMutation = usePageMutation(
|
||||
async ({ amount }: { amount: number }) => {
|
||||
if (!data) throw new 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');
|
||||
}
|
||||
|
||||
submitBlocker.block();
|
||||
throttle.block();
|
||||
|
||||
try {
|
||||
const result = await leagueWalletService.withdraw(
|
||||
leagueId,
|
||||
amount,
|
||||
data.currency,
|
||||
'season-2', // Current active season
|
||||
'bank-account-***1234'
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || 'Withdrawal failed');
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
submitBlocker.release();
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
// Refetch wallet data after successful withdrawal
|
||||
refetch();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return withdrawMutation;
|
||||
}
|
||||
19
apps/website/hooks/league/usePenaltyMutation.ts
Normal file
19
apps/website/hooks/league/usePenaltyMutation.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { PENALTY_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
|
||||
export function usePenaltyMutation() {
|
||||
const penaltyService = useInject(PENALTY_SERVICE_TOKEN);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const applyPenaltyMutation = useMutation({
|
||||
mutationFn: (command: any) => penaltyService.applyPenalty(command),
|
||||
onSuccess: () => {
|
||||
// Invalidate relevant queries to refresh data
|
||||
queryClient.invalidateQueries({ queryKey: ['leagueStewardingData'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['penalties'] });
|
||||
},
|
||||
});
|
||||
|
||||
return applyPenaltyMutation;
|
||||
}
|
||||
16
apps/website/hooks/league/useProtestDetail.ts
Normal file
16
apps/website/hooks/league/useProtestDetail.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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';
|
||||
|
||||
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),
|
||||
enabled: enabled && !!leagueId && !!protestId,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
25
apps/website/hooks/league/useSponsorshipRequests.ts
Normal file
25
apps/website/hooks/league/useSponsorshipRequests.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { SPONSOR_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
|
||||
|
||||
export function useSponsorshipRequests(entityType: string, entityId: string) {
|
||||
const sponsorshipService = useInject(SPONSOR_SERVICE_TOKEN);
|
||||
|
||||
const queryResult = useQuery({
|
||||
queryKey: ['sponsorshipRequests', entityType, entityId],
|
||||
queryFn: async () => {
|
||||
const result = await sponsorshipService.getPendingSponsorshipRequests({
|
||||
entityType,
|
||||
entityId,
|
||||
});
|
||||
if (result.isErr()) {
|
||||
throw result.getError();
|
||||
}
|
||||
return result.unwrap();
|
||||
},
|
||||
enabled: !!entityType && !!entityId,
|
||||
});
|
||||
|
||||
return enhanceQueryResult(queryResult);
|
||||
}
|
||||
Reference in New Issue
Block a user