website refactor
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { ErrorPageContainer } from '@/ui/ErrorPageContainer';
|
||||
import { ErrorActionButtons } from '@/ui/ErrorActionButtons';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { ErrorPageContainer } from '@/ui/ErrorPageContainer';
|
||||
import { ErrorActionButtons } from '@/ui/ErrorActionButtons';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
import { LoginFlowController, LoginState } from '@/lib/auth/LoginFlowController';
|
||||
import { LoginViewData } from '@/lib/builders/view-data/types/LoginViewData';
|
||||
import { LoginTemplate } from '@/templates/auth/LoginTemplate';
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
import { SignupViewData } from '@/lib/builders/view-data/types/SignupViewData';
|
||||
import { SignupTemplate } from '@/templates/auth/SignupTemplate';
|
||||
import { SignupMutation } from '@/lib/mutations/auth/SignupMutation';
|
||||
|
||||
@@ -34,7 +34,6 @@ export function LeagueAdminSchedulePageClient() {
|
||||
const [seasonId, setSeasonId] = useState<string>('');
|
||||
const [form, setForm] = useState(() => new RaceScheduleCommandModel());
|
||||
const [editingRaceId, setEditingRaceId] = useState<string | null>(null);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
// Action state
|
||||
const [isPublishing, setIsPublishing] = useState(false);
|
||||
@@ -60,7 +59,6 @@ export function LeagueAdminSchedulePageClient() {
|
||||
setSeasonId(newSeasonId);
|
||||
setEditingRaceId(null);
|
||||
setForm(new RaceScheduleCommandModel());
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
const handlePublishToggle = async () => {
|
||||
@@ -87,7 +85,6 @@ export function LeagueAdminSchedulePageClient() {
|
||||
|
||||
const validationErrors = form.validate();
|
||||
if (Object.keys(validationErrors).length > 0) {
|
||||
setErrors(validationErrors as Record<string, string>);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,7 +97,6 @@ export function LeagueAdminSchedulePageClient() {
|
||||
if (result.isOk()) {
|
||||
// Reset form
|
||||
setForm(new RaceScheduleCommandModel());
|
||||
setErrors({});
|
||||
setEditingRaceId(null);
|
||||
router.refresh();
|
||||
} else {
|
||||
@@ -122,7 +118,6 @@ export function LeagueAdminSchedulePageClient() {
|
||||
car: race.car || '',
|
||||
scheduledAtIso: race.scheduledAt.toISOString(),
|
||||
}));
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
const handleDelete = async (raceId: string) => {
|
||||
@@ -146,7 +141,6 @@ export function LeagueAdminSchedulePageClient() {
|
||||
const handleCancelEdit = () => {
|
||||
setEditingRaceId(null);
|
||||
setForm(new RaceScheduleCommandModel());
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
// Derived states
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
import {
|
||||
AlertCircle,
|
||||
Award,
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function CreateLeaguePage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const wizardParams = SearchParamParser.parseWizard(searchParams as any).unwrap();
|
||||
const wizardParams = SearchParamParser.parseWizard(searchParams as unknown as URLSearchParams).unwrap();
|
||||
const rawStep = wizardParams.step;
|
||||
|
||||
let currentStepName: StepName = 'basics';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { OnboardingWizard } from '@/components/onboarding/OnboardingWizard';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { completeOnboardingAction } from '@/app/onboarding/completeOnboardingAction';
|
||||
import { generateAvatarsAction } from '@/app/onboarding/generateAvatarsAction';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
|
||||
export function OnboardingWizardClient() {
|
||||
const { session } = useAuth();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { StatefulPageWrapper } from '@/components/shared/state/StatefulPageWrapper';
|
||||
import { RacesAllTemplate } from '@/templates/RacesAllTemplate';
|
||||
import { RacesAllPageQuery } from '@/lib/page-queries/races/RacesAllPageQuery';
|
||||
import { useAllRacesPageData } from '@/hooks/race/useAllRacesPageData';
|
||||
import { type RacesViewData, type RaceViewData } from '@/lib/view-data/RacesViewData';
|
||||
import { Flag } from 'lucide-react';
|
||||
|
||||
@@ -12,7 +12,7 @@ import { routes } from '@/lib/routing/RouteConfig';
|
||||
|
||||
const ITEMS_PER_PAGE = 10;
|
||||
|
||||
export function RacesAllPageClient({ initialViewData }: { initialViewData: unknown }) {
|
||||
export function RacesAllPageClient({ initialViewData }: { initialViewData: RacesViewData | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
// Client-side state for filters and pagination
|
||||
@@ -23,38 +23,8 @@ export function RacesAllPageClient({ initialViewData }: { initialViewData: unkno
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||
|
||||
// Data state
|
||||
const [pageData, setPageData] = useState<RacesViewData | null>(initialViewData as RacesViewData);
|
||||
const [isLoading, setIsLoading] = useState(!initialViewData);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
// Fetch data
|
||||
const fetchData = useCallback(async () => {
|
||||
if (pageData && !isLoading) return; // Already have data from server
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const result = await RacesAllPageQuery.execute();
|
||||
|
||||
if (result.isErr()) {
|
||||
throw new globalThis.Error('Failed to fetch races');
|
||||
}
|
||||
|
||||
setPageData(result.unwrap() as unknown as RacesViewData);
|
||||
} catch (err) {
|
||||
setError(err instanceof globalThis.Error ? err : new globalThis.Error('Unknown error'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [pageData, isLoading]);
|
||||
|
||||
// Fetch on mount if no initial data
|
||||
useEffect(() => {
|
||||
if (!initialViewData) {
|
||||
fetchData();
|
||||
}
|
||||
}, [initialViewData, fetchData]);
|
||||
// Use React Query hook
|
||||
const { data: pageData, isLoading, error, refetch } = useAllRacesPageData(initialViewData);
|
||||
|
||||
// Transform data
|
||||
const races: RaceViewData[] = pageData?.races ?? [];
|
||||
@@ -102,8 +72,8 @@ export function RacesAllPageClient({ initialViewData }: { initialViewData: unkno
|
||||
<StatefulPageWrapper
|
||||
data={pageData}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
retry={fetchData}
|
||||
error={error as Error | null}
|
||||
retry={refetch}
|
||||
Template={() => pageData ? (
|
||||
<RacesAllTemplate
|
||||
viewData={pageData}
|
||||
|
||||
@@ -28,17 +28,22 @@ export function TeamLeaderboardPageWrapper({ data }: { data: TeamSummaryViewMode
|
||||
router.push('/teams');
|
||||
};
|
||||
|
||||
const viewData = {
|
||||
teams: data,
|
||||
searchQuery,
|
||||
filterLevel,
|
||||
sortBy,
|
||||
filteredAndSortedTeams: data,
|
||||
};
|
||||
|
||||
return (
|
||||
<TeamLeaderboardTemplate
|
||||
teams={data}
|
||||
searchQuery={searchQuery}
|
||||
filterLevel={filterLevel}
|
||||
sortBy={sortBy}
|
||||
viewData={viewData}
|
||||
onSearchChange={setSearchQuery}
|
||||
onFilterLevelChange={setFilterLevel}
|
||||
filterLevelChange={setFilterLevel}
|
||||
onSortChange={setSortBy}
|
||||
onTeamClick={handleTeamClick}
|
||||
onBackToTeams={handleBackToTeams}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ContainerProvider } from '@/lib/di/providers/ContainerProvider';
|
||||
import { QueryClientProvider } from '@/lib/providers/QueryClientProvider';
|
||||
import { AuthProvider } from '@/lib/auth/AuthContext';
|
||||
import { AuthProvider } from '@/components/auth/AuthContext';
|
||||
import { FeatureFlagProvider } from '@/lib/feature/FeatureFlagProvider';
|
||||
import { NotificationProvider } from '@/components/notifications/NotificationProvider';
|
||||
import { NotificationIntegration } from '@/components/errors/NotificationIntegration';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { DriverViewModel } from "@/lib/view-models/DriverViewModel";
|
||||
import { ProtestViewModel } from "@/lib/view-models/ProtestViewModel";
|
||||
import { RaceViewModel } from "@/lib/view-models/RaceViewModel";
|
||||
import { Box } from "@/ui/Box";
|
||||
import { Card } from "@/ui/Card";
|
||||
import { ProtestListItem } from "./ProtestListItem";
|
||||
@@ -12,7 +13,10 @@ import { Flag } from "lucide-react";
|
||||
interface PendingProtestsListProps {
|
||||
protests: ProtestViewModel[];
|
||||
drivers: Record<string, DriverViewModel>;
|
||||
races: Record<string, RaceViewModel>;
|
||||
leagueId: string;
|
||||
onReviewProtest: (protest: ProtestViewModel) => void;
|
||||
onProtestReviewed?: () => void;
|
||||
}
|
||||
|
||||
export function PendingProtestsList({
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Settings,
|
||||
Shield
|
||||
} from 'lucide-react';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { DriverViewModel as DriverViewModelClass } from '@/lib/view-models/DriverViewModel';
|
||||
import { useFindDriverById } from '@/hooks/driver/useFindDriverById';
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId";
|
||||
import { useTeamMembership, useJoinTeam, useLeaveTeam } from "@/hooks/team";
|
||||
import { useTeamMembership } from "@/hooks/team/useTeamMembership";
|
||||
import { useJoinTeam } from "@/hooks/team/useJoinTeam";
|
||||
import { useLeaveTeam } from "@/hooks/team/useLeaveTeam";
|
||||
|
||||
interface JoinTeamButtonProps {
|
||||
teamId: string;
|
||||
|
||||
@@ -13,7 +13,10 @@ import { JoinRequestList } from '@/ui/JoinRequestList';
|
||||
import { JoinRequestItem } from '@/ui/JoinRequestItem';
|
||||
import { DangerZone } from '@/ui/DangerZone';
|
||||
import { MinimalEmptyState } from '@/components/shared/state/EmptyState';
|
||||
import { useTeamJoinRequests, useUpdateTeam, useApproveJoinRequest, useRejectJoinRequest } from "@/hooks/team";
|
||||
import { useTeamJoinRequests } from "@/hooks/team/useTeamJoinRequests";
|
||||
import { useUpdateTeam } from "@/hooks/team/useUpdateTeam";
|
||||
import { useApproveJoinRequest } from "@/hooks/team/useApproveJoinRequest";
|
||||
import { useRejectJoinRequest } from "@/hooks/team/useRejectJoinRequest";
|
||||
import type { TeamJoinRequestViewModel } from '@/lib/view-models/TeamJoinRequestViewModel';
|
||||
|
||||
interface TeamAdminProps {
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import { usePageData } from '@/lib/page/usePageData';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { RACE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { RacesViewDataBuilder } from '@/lib/builders/view-data/RacesViewDataBuilder';
|
||||
import { RacesViewData } from '@/lib/view-data/RacesViewData';
|
||||
|
||||
export function useAllRacesPageData() {
|
||||
export function useAllRacesPageData(initialData?: RacesViewData | null) {
|
||||
const raceService = useInject(RACE_SERVICE_TOKEN);
|
||||
|
||||
return usePageData({
|
||||
return useQuery({
|
||||
queryKey: ['races', 'all'],
|
||||
queryFn: () => raceService.getAllRacesPageData(),
|
||||
queryFn: async () => {
|
||||
const result = await raceService.getAllRacesPageData();
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.getError().message);
|
||||
}
|
||||
return RacesViewDataBuilder.build(result.unwrap());
|
||||
},
|
||||
initialData: initialData ?? undefined,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useSponsorMode(): boolean {
|
||||
|
||||
60
apps/website/lib/api/ApiClient.ts
Normal file
60
apps/website/lib/api/ApiClient.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { AdminApiClient } from './admin/AdminApiClient';
|
||||
import { AnalyticsApiClient } from './analytics/AnalyticsApiClient';
|
||||
import { AuthApiClient } from './auth/AuthApiClient';
|
||||
import { DashboardApiClient } from './dashboard/DashboardApiClient';
|
||||
import { DriversApiClient } from './drivers/DriversApiClient';
|
||||
import { LeaguesApiClient } from './leagues/LeaguesApiClient';
|
||||
import { MediaApiClient } from './media/MediaApiClient';
|
||||
import { PaymentsApiClient } from './payments/PaymentsApiClient';
|
||||
import { PenaltiesApiClient } from './penalties/PenaltiesApiClient';
|
||||
import { PolicyApiClient } from './policy/PolicyApiClient';
|
||||
import { ProtestsApiClient } from './protests/ProtestsApiClient';
|
||||
import { RacesApiClient } from './races/RacesApiClient';
|
||||
import { SponsorsApiClient } from './sponsors/SponsorsApiClient';
|
||||
import { TeamsApiClient } from './teams/TeamsApiClient';
|
||||
import { WalletsApiClient } from './wallets/WalletsApiClient';
|
||||
import { ErrorReporter } from '../interfaces/ErrorReporter';
|
||||
import { Logger } from '../interfaces/Logger';
|
||||
|
||||
export class ApiClient {
|
||||
public readonly admin: AdminApiClient;
|
||||
public readonly analytics: AnalyticsApiClient;
|
||||
public readonly auth: AuthApiClient;
|
||||
public readonly dashboard: DashboardApiClient;
|
||||
public readonly drivers: DriversApiClient;
|
||||
public readonly leagues: LeaguesApiClient;
|
||||
public readonly media: MediaApiClient;
|
||||
public readonly payments: PaymentsApiClient;
|
||||
public readonly penalties: PenaltiesApiClient;
|
||||
public readonly policy: PolicyApiClient;
|
||||
public readonly protests: ProtestsApiClient;
|
||||
public readonly races: RacesApiClient;
|
||||
public readonly sponsors: SponsorsApiClient;
|
||||
public readonly teams: TeamsApiClient;
|
||||
public readonly wallets: WalletsApiClient;
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
// Default implementations for logger and error reporter if needed
|
||||
const logger: Logger = console;
|
||||
const errorReporter: ErrorReporter = { report: (error) => console.error(error) };
|
||||
|
||||
this.admin = new AdminApiClient(baseUrl, errorReporter, logger);
|
||||
this.analytics = new AnalyticsApiClient(baseUrl, errorReporter, logger);
|
||||
this.auth = new AuthApiClient(baseUrl, errorReporter, logger);
|
||||
this.dashboard = new DashboardApiClient(baseUrl, errorReporter, logger);
|
||||
this.drivers = new DriversApiClient(baseUrl, errorReporter, logger);
|
||||
this.leagues = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
this.media = new MediaApiClient(baseUrl, errorReporter, logger);
|
||||
this.payments = new PaymentsApiClient(baseUrl, errorReporter, logger);
|
||||
this.penalties = new PenaltiesApiClient(baseUrl, errorReporter, logger);
|
||||
this.policy = new PolicyApiClient(baseUrl, errorReporter, logger);
|
||||
this.protests = new ProtestsApiClient(baseUrl, errorReporter, logger);
|
||||
this.races = new RacesApiClient(baseUrl, errorReporter, logger);
|
||||
this.sponsors = new SponsorsApiClient(baseUrl, errorReporter, logger);
|
||||
this.teams = new TeamsApiClient(baseUrl, errorReporter, logger);
|
||||
this.wallets = new WalletsApiClient(baseUrl, errorReporter, logger);
|
||||
}
|
||||
}
|
||||
|
||||
// Export a default instance if needed, but apiClient.ts seems to handle it
|
||||
export const api = new ApiClient(process.env.NEXT_PUBLIC_API_URL || '');
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ApiClient, api } from './index';
|
||||
import { ApiClient, api } from './ApiClient';
|
||||
|
||||
describe('ApiClient', () => {
|
||||
it('should be defined', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiClient } from './api/index';
|
||||
import { ApiClient } from './api/ApiClient';
|
||||
import { getWebsiteApiBaseUrl } from './config/apiBaseUrl';
|
||||
|
||||
export const apiClient = new ApiClient(getWebsiteApiBaseUrl());
|
||||
export const apiClient = new ApiClient(getWebsiteApiBaseUrl());
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { RacesAllViewData } from '@/lib/view-data/races/RacesAllViewData';
|
||||
|
||||
/**
|
||||
* Races All View Data Builder
|
||||
*
|
||||
* Transforms API DTO into ViewData for the all races template.
|
||||
* Deterministic, side-effect free.
|
||||
*/
|
||||
export class RacesAllViewDataBuilder {
|
||||
static build(apiDto: any): RacesAllViewData {
|
||||
const races = apiDto.races.map((race: any) => ({
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
scheduledAt: race.scheduledAt,
|
||||
status: race.status as 'scheduled' | 'running' | 'completed' | 'cancelled',
|
||||
sessionType: 'race',
|
||||
leagueId: race.leagueId,
|
||||
leagueName: race.leagueName,
|
||||
strengthOfField: race.strengthOfField ?? undefined,
|
||||
}));
|
||||
|
||||
return {
|
||||
races,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ export class TeamDetailPageQuery implements PageQuery<TeamDetailViewData, string
|
||||
const teamData = teamResult.unwrap();
|
||||
|
||||
// Fetch team members
|
||||
const membersResult = await service.getTeamMembers(teamId, currentDriverId, teamData.ownerId);
|
||||
const membersResult = await service.getTeamMembers(teamId, currentDriverId, teamData.team.ownerId);
|
||||
|
||||
if (membersResult.isErr()) {
|
||||
return Result.err(mapToPresentationError(membersResult.getError()));
|
||||
@@ -79,17 +79,17 @@ export class TeamDetailPageQuery implements PageQuery<TeamDetailViewData, string
|
||||
// Transform to raw serializable DTO
|
||||
const dto: TeamDetailPageDto = {
|
||||
team: {
|
||||
id: teamData.id,
|
||||
name: teamData.name,
|
||||
tag: teamData.tag,
|
||||
description: teamData.description,
|
||||
ownerId: teamData.ownerId,
|
||||
leagues: teamData.leagues,
|
||||
createdAt: teamData.createdAt,
|
||||
specialization: teamData.specialization,
|
||||
region: teamData.region,
|
||||
languages: teamData.languages,
|
||||
category: teamData.category,
|
||||
id: teamData.team.id,
|
||||
name: teamData.team.name,
|
||||
tag: teamData.team.tag,
|
||||
description: teamData.team.description,
|
||||
ownerId: teamData.team.ownerId,
|
||||
leagues: teamData.team.leagues,
|
||||
createdAt: teamData.team.createdAt,
|
||||
specialization: (teamData.team as any).specialization,
|
||||
region: (teamData.team as any).region,
|
||||
languages: (teamData.team as any).languages,
|
||||
category: teamData.team.category,
|
||||
membership: teamData.membership ? {
|
||||
role: teamData.membership.role,
|
||||
joinedAt: teamData.membership.joinedAt,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
|
||||
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
|
||||
import { RacesAllViewData } from '@/lib/view-data/races/RacesAllViewData';
|
||||
import { RacesViewData } from '@/lib/view-data/RacesViewData';
|
||||
import { RacesService } from '@/lib/services/races/RacesService';
|
||||
import { RacesAllViewDataBuilder } from '@/lib/builders/view-data/RacesAllViewDataBuilder';
|
||||
import { RacesViewDataBuilder } from '@/lib/builders/view-data/RacesViewDataBuilder';
|
||||
|
||||
/**
|
||||
* Races All Page Query
|
||||
*
|
||||
* Fetches all races data for the all races page.
|
||||
* Returns Result<RacesAllViewData, PresentationError>
|
||||
* Returns Result<RacesViewData, PresentationError>
|
||||
*/
|
||||
export class RacesAllPageQuery implements PageQuery<RacesAllViewData, void> {
|
||||
async execute(): Promise<Result<RacesAllViewData, PresentationError>> {
|
||||
export class RacesAllPageQuery implements PageQuery<RacesViewData, void> {
|
||||
async execute(): Promise<Result<RacesViewData, PresentationError>> {
|
||||
// Manual wiring: Service creates its own dependencies
|
||||
const service = new RacesService();
|
||||
|
||||
@@ -24,12 +24,12 @@ export class RacesAllPageQuery implements PageQuery<RacesAllViewData, void> {
|
||||
}
|
||||
|
||||
// Transform to ViewData using builder
|
||||
const viewData = RacesAllViewDataBuilder.build(result.unwrap());
|
||||
const viewData = RacesViewDataBuilder.build(result.unwrap());
|
||||
return Result.ok(viewData);
|
||||
}
|
||||
|
||||
// Static method to avoid object construction in server code
|
||||
static async execute(): Promise<Result<RacesAllViewData, PresentationError>> {
|
||||
static async execute(): Promise<Result<RacesViewData, PresentationError>> {
|
||||
const query = new RacesAllPageQuery();
|
||||
return await query.execute();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ export interface ErrorStats {
|
||||
};
|
||||
}
|
||||
|
||||
export function getErrorAnalyticsStats(): ErrorStats {
|
||||
return ErrorAnalyticsService.getErrorAnalyticsStats();
|
||||
}
|
||||
|
||||
export class ErrorAnalyticsService implements Service {
|
||||
static getErrorAnalyticsStats(): ErrorStats {
|
||||
const globalHandler = getGlobalErrorHandler();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { ApiClient } from '@/lib/api';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { Service, type DomainError } from '@/lib/contracts/services/Service';
|
||||
@@ -28,9 +30,11 @@ export class ProfileLeaguesService implements Service {
|
||||
async getProfileLeagues(driverId: string): Promise<Result<ProfileLeaguesPageDto, DomainError>> {
|
||||
try {
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const apiClient = new ApiClient(baseUrl);
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new ConsoleErrorReporter();
|
||||
const leaguesApiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
const leaguesDto = await apiClient.leagues.getAllWithCapacity();
|
||||
const leaguesDto = await leaguesApiClient.getAllWithCapacity();
|
||||
|
||||
if (!leaguesDto?.leagues) {
|
||||
return Result.err({ type: 'notFound', message: 'Leagues not found' });
|
||||
@@ -40,7 +44,7 @@ export class ProfileLeaguesService implements Service {
|
||||
const leagueMemberships = await Promise.all(
|
||||
leaguesDto.leagues.map(async (league) => {
|
||||
try {
|
||||
const membershipsDto = await apiClient.leagues.getMemberships(league.id);
|
||||
const membershipsDto = await leaguesApiClient.getMemberships(league.id);
|
||||
|
||||
let memberships: MembershipDTO[] = [];
|
||||
if (membershipsDto && typeof membershipsDto === 'object') {
|
||||
|
||||
@@ -41,6 +41,15 @@ export class RaceService implements Service {
|
||||
}
|
||||
}
|
||||
|
||||
async getAllRacesPageData(): Promise<Result<any, DomainError>> {
|
||||
try {
|
||||
const data = await this.apiClient.getPageData();
|
||||
return Result.ok(data);
|
||||
} catch (error: unknown) {
|
||||
return Result.err(this.mapError(error, 'Failed to fetch all races page data'));
|
||||
}
|
||||
}
|
||||
|
||||
async findByLeagueId(leagueId: string): Promise<Result<unknown[], DomainError>> {
|
||||
try {
|
||||
const result = await this.apiClient.getPageData(leagueId);
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
|
||||
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
|
||||
import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO';
|
||||
import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO';
|
||||
import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO';
|
||||
import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO';
|
||||
import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO';
|
||||
import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO';
|
||||
import type { GetTeamJoinRequestsOutputDTO } from '@/lib/types/generated/GetTeamJoinRequestsOutputDTO';
|
||||
import { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
|
||||
import { Result } from '@/lib/contracts/Result';
|
||||
import { DomainError, Service } from '@/lib/contracts/services/Service';
|
||||
@@ -37,7 +44,7 @@ export class TeamService implements Service {
|
||||
}
|
||||
}
|
||||
|
||||
async getTeamDetails(teamId: string, _: string): Promise<Result<unknown, DomainError>> {
|
||||
async getTeamDetails(teamId: string, _: string): Promise<Result<any, DomainError>> {
|
||||
try {
|
||||
const result = await this.apiClient.getDetails(teamId);
|
||||
if (!result) {
|
||||
@@ -58,23 +65,48 @@ export class TeamService implements Service {
|
||||
}
|
||||
}
|
||||
|
||||
async getTeamJoinRequests(_: string): Promise<Result<unknown, DomainError>> {
|
||||
return Result.err({ type: 'notImplemented', message: 'getTeamJoinRequests' });
|
||||
async getTeamJoinRequests(teamId: string): Promise<Result<GetTeamJoinRequestsOutputDTO, DomainError>> {
|
||||
try {
|
||||
const result = await this.apiClient.getJoinRequests(teamId);
|
||||
return Result.ok(result);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch team join requests' });
|
||||
}
|
||||
}
|
||||
|
||||
async createTeam(__: unknown): Promise<Result<unknown, DomainError>> {
|
||||
return Result.err({ type: 'notImplemented', message: 'createTeam' });
|
||||
async createTeam(input: CreateTeamInputDTO): Promise<Result<CreateTeamOutputDTO, DomainError>> {
|
||||
try {
|
||||
const result = await this.apiClient.create(input);
|
||||
return Result.ok(result);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to create team' });
|
||||
}
|
||||
}
|
||||
|
||||
async updateTeam(_: string, __: unknown): Promise<Result<unknown, DomainError>> {
|
||||
return Result.err({ type: 'notImplemented', message: 'updateTeam' });
|
||||
async updateTeam(teamId: string, input: UpdateTeamInputDTO): Promise<Result<UpdateTeamOutputDTO, DomainError>> {
|
||||
try {
|
||||
const result = await this.apiClient.update(teamId, input);
|
||||
return Result.ok(result);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to update team' });
|
||||
}
|
||||
}
|
||||
|
||||
async getDriverTeam(_: string): Promise<Result<unknown, DomainError>> {
|
||||
return Result.err({ type: 'notImplemented', message: 'getDriverTeam' });
|
||||
async getDriverTeam(driverId: string): Promise<Result<GetDriverTeamOutputDTO | null, DomainError>> {
|
||||
try {
|
||||
const result = await this.apiClient.getDriverTeam(driverId);
|
||||
return Result.ok(result);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch driver team' });
|
||||
}
|
||||
}
|
||||
|
||||
async getMembership(_: string, __: string): Promise<Result<unknown, DomainError>> {
|
||||
return Result.err({ type: 'notImplemented', message: 'getMembership' });
|
||||
async getMembership(teamId: string, driverId: string): Promise<Result<GetTeamMembershipOutputDTO | null, DomainError>> {
|
||||
try {
|
||||
const result = await this.apiClient.getMembership(teamId, driverId);
|
||||
return Result.ok(result);
|
||||
} catch (error: unknown) {
|
||||
return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch team membership' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,18 @@ export interface SponsorDashboardViewData {
|
||||
activeSponsorships: number;
|
||||
formattedTotalInvestment: string;
|
||||
costPerThousandViews: string;
|
||||
upcomingRenewals: unknown[];
|
||||
recentActivity: unknown[];
|
||||
}
|
||||
upcomingRenewals: Array<{
|
||||
id: string;
|
||||
type: 'league' | 'team' | 'driver' | 'race' | 'platform';
|
||||
name: string;
|
||||
formattedRenewDate: string;
|
||||
formattedPrice: string;
|
||||
}>;
|
||||
recentActivity: Array<{
|
||||
id: string;
|
||||
message: string;
|
||||
time: string;
|
||||
typeColor: string;
|
||||
formattedImpressions?: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Races All View Data
|
||||
*
|
||||
* ViewData for the all races page template.
|
||||
* JSON-serializable, template-ready data structure.
|
||||
*/
|
||||
|
||||
export interface RacesAllRace {
|
||||
id: string;
|
||||
track: string;
|
||||
car: string;
|
||||
scheduledAt: string;
|
||||
status: 'scheduled' | 'running' | 'completed' | 'cancelled';
|
||||
sessionType: string;
|
||||
leagueId?: string;
|
||||
leagueName?: string;
|
||||
strengthOfField?: number;
|
||||
}
|
||||
|
||||
export interface RacesAllViewData {
|
||||
races: RacesAllRace[];
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Races View Data
|
||||
*
|
||||
* ViewData for the main races page template.
|
||||
* JSON-serializable, template-ready data structure.
|
||||
*/
|
||||
|
||||
export interface RacesRace {
|
||||
id: string;
|
||||
track: string;
|
||||
car: string;
|
||||
scheduledAt: string;
|
||||
status: 'scheduled' | 'running' | 'completed' | 'cancelled';
|
||||
sessionType: string;
|
||||
leagueId?: string;
|
||||
leagueName?: string;
|
||||
strengthOfField?: number;
|
||||
isUpcoming: boolean;
|
||||
isLive: boolean;
|
||||
isPast: boolean;
|
||||
}
|
||||
|
||||
export interface RacesViewData {
|
||||
races: RacesRace[];
|
||||
totalCount: number;
|
||||
scheduledRaces: RacesRace[];
|
||||
runningRaces: RacesRace[];
|
||||
completedRaces: RacesRace[];
|
||||
}
|
||||
Reference in New Issue
Block a user