code quality
Some checks failed
CI / lint-typecheck (pull_request) Failing after 12s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped

This commit is contained in:
2026-01-26 22:16:33 +01:00
parent f2bd80ccd3
commit 09632d004d
72 changed files with 1946 additions and 277 deletions

View File

@@ -87,6 +87,11 @@ function InfoRow({
);
}
interface LeagueReviewSummaryProps {
form: LeagueConfigFormModel;
presets: Array<{ id: string; name: string; sessionSummary?: string }>;
}
export function LeagueReviewSummary({ form, presets }: LeagueReviewSummaryProps) {
const { basics, structure, timings, scoring, championships, dropPolicy, stewarding } = form;
const seasonName = (form as LeagueConfigFormModel & { seasonName?: string }).seasonName;

View File

@@ -1,3 +1,4 @@
'use client';
import React, { useState } from 'react';
import { Image as UiImage } from '@/ui/Image';

View File

@@ -1,17 +1,17 @@
import { DriverProfileViewModelBuilder } from '@/lib/builders/view-models/DriverProfileViewModelBuilder';
import { DriverProfileViewDataBuilder } from '@/lib/builders/view-data/DriverProfileViewDataBuilder';
import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
import { DriverProfileViewModel } from '@/lib/view-models/DriverProfileViewModel';
import type { DriverProfileViewData } from '@/lib/view-data/DriverProfileViewData';
import { useMutation, UseMutationOptions } from '@tanstack/react-query';
export function useUpdateDriverProfile(
options?: Omit<UseMutationOptions<DriverProfileViewModel, ApiError, { bio?: string; country?: string }>, 'mutationFn'>
options?: Omit<UseMutationOptions<DriverProfileViewData, ApiError, { bio?: string; country?: string }>, 'mutationFn'>
) {
const driverService = useInject(DRIVER_SERVICE_TOKEN);
return useMutation<DriverProfileViewModel, ApiError, { bio?: string; country?: string }>({
return useMutation<DriverProfileViewData, ApiError, { bio?: string; country?: string }>({
mutationFn: async (updates) => {
await driverService.updateProfile(updates);
@@ -21,7 +21,7 @@ export function useUpdateDriverProfile(
// This hook does not know the driverId; callers should invalidate/refetch the profile query.
// Return a minimal ViewModel to satisfy types.
return DriverProfileViewModelBuilder.build({
return DriverProfileViewDataBuilder.build({
teamMemberships: [],
socialSummary: { friends: [], friendsCount: 0 },
} as unknown as GetDriverProfileOutputDTO);

View File

@@ -17,13 +17,13 @@ export function useLeagueWalletPageData(leagueId: string) {
// Transform DTO to ViewData at client boundary
const transactions = dto.transactions.map(t => ({
id: t.id,
type: t.type as any,
type: t.type as "sponsorship" | "withdrawal" | "prize" | "deposit",
description: t.description,
amount: t.amount,
fee: 0,
netAmount: t.amount,
date: new globalThis.Date(t.createdAt).toISOString(),
status: t.status,
date: t.date,
status: t.status as "completed" | "pending" | "failed",
}));
return new LeagueWalletViewModel({
leagueId,

View File

@@ -1,17 +1,17 @@
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
import type { GetMediaOutputDTO } from '@/lib/types/generated/GetMediaOutputDTO';
import type { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import type { AvatarViewData } from '@/lib/view-data/AvatarViewData';
export class AvatarViewDataBuilder {
public static build(apiDto: GetMediaOutputDTO): AvatarViewData {
public static build(apiDto: MediaBinaryDTO): AvatarViewData {
// Note: GetMediaOutputDTO from OpenAPI doesn't have buffer,
// but the implementation expects it for binary data.
// We use type assertion to handle the binary case while keeping the DTO type.
const binaryDto = apiDto as unknown as { buffer?: ArrayBuffer };
const buffer = binaryDto.buffer;
const contentType = apiDto.type;
const contentType = apiDto.contentType;
return {
buffer: buffer ? Buffer.from(buffer).toString('base64') : '',
@@ -20,4 +20,4 @@ export class AvatarViewDataBuilder {
}
}
AvatarViewDataBuilder satisfies ViewDataBuilder<GetMediaOutputDTO, AvatarViewData>;
AvatarViewDataBuilder satisfies ViewDataBuilder<MediaBinaryDTO, AvatarViewData>;

View File

@@ -1,11 +1,12 @@
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
import type { GetMediaOutputDTO } from '@/lib/types/generated/GetMediaOutputDTO';
import type { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import type { CategoryIconViewData } from '@/lib/view-data/CategoryIconViewData';
import type { GetMediaOutputDTO } from '@/lib/types/generated/GetMediaOutputDTO';
export class CategoryIconViewDataBuilder {
public static build(apiDto: GetMediaOutputDTO): CategoryIconViewData {
public static build(apiDto: MediaBinaryDTO): CategoryIconViewData {
// Note: GetMediaOutputDTO from OpenAPI doesn't have buffer,
// but the implementation expects it for binary data.
const binaryDto = apiDto as unknown as { buffer?: ArrayBuffer };
@@ -13,9 +14,8 @@ export class CategoryIconViewDataBuilder {
return {
buffer: buffer ? Buffer.from(buffer).toString('base64') : '',
contentType: apiDto.type,
contentType: (apiDto as any).contentType,
};
}
}
CategoryIconViewDataBuilder satisfies ViewDataBuilder<GetMediaOutputDTO, CategoryIconViewData>;

View File

@@ -5,7 +5,30 @@ import { HealthAlertFormatter } from '@/lib/formatters/HealthAlertFormatter';
import { HealthComponentFormatter } from '@/lib/formatters/HealthComponentFormatter';
import { HealthMetricFormatter } from '@/lib/formatters/HealthMetricFormatter';
import { HealthStatusFormatter } from '@/lib/formatters/HealthStatusFormatter';
import type { HealthDTO } from '../../../../api/src/domain/health/HealthDTO';
interface HealthDTO {
status: 'ok' | 'degraded' | 'error' | 'unknown';
timestamp?: string;
uptime?: number;
responseTime?: number;
errorRate?: number;
lastCheck?: string;
checksPassed?: number;
checksFailed?: number;
components?: Array<{
name: string;
status: 'ok' | 'degraded' | 'error' | 'unknown';
lastCheck?: string;
responseTime?: number;
errorRate?: number;
}>;
alerts?: Array<{
id: string;
type: 'critical' | 'warning' | 'info';
title: string;
message: string;
timestamp: string;
}>;
}
import type { HealthAlert, HealthComponent, HealthMetrics, HealthStatus, HealthViewData } from '@/lib/view-data/HealthViewData';
export type { HealthDTO };
@@ -18,9 +41,9 @@ export class HealthViewDataBuilder {
// Build overall status
const overallStatus: HealthStatus = {
status: apiDto.status,
timestamp: apiDto.timestamp,
formattedTimestamp: HealthStatusFormatter.formatTimestamp(apiDto.timestamp),
relativeTime: HealthStatusFormatter.formatRelativeTime(apiDto.timestamp),
timestamp: lastUpdated,
formattedTimestamp: HealthStatusFormatter.formatTimestamp(lastUpdated),
relativeTime: HealthStatusFormatter.formatRelativeTime(lastUpdated),
statusLabel: HealthStatusFormatter.formatStatusLabel(apiDto.status),
statusColor: HealthStatusFormatter.formatStatusColor(apiDto.status),
statusIcon: HealthStatusFormatter.formatStatusIcon(apiDto.status),

View File

@@ -64,8 +64,8 @@ export class RaceStewardingViewDataBuilder {
},
filedAt: p.submittedAt,
status: p.status,
decisionNotes: (p as any).decisionNotes || null,
proofVideoUrl: (p as any).proofVideoUrl || null,
decisionNotes: (p as any).decisionNotes || undefined,
proofVideoUrl: (p as any).proofVideoUrl || undefined,
}));
const pendingProtests = (apiDto as any).pendingProtests || protests.filter(p => p.status === 'pending');
@@ -78,7 +78,7 @@ export class RaceStewardingViewDataBuilder {
type: p.type,
value: p.value ?? 0,
reason: p.reason ?? '',
notes: p.notes || null,
notes: p.notes || undefined,
}));
const driverMap: Record<string, { id: string; name: string }> = {};

View File

@@ -5,20 +5,20 @@
*/
import { DateFormatter } from '@/lib/formatters/DateFormatter';
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
import type { SponsorMetric, TeamDetailData, TeamDetailViewData, TeamMemberData, TeamTab } from '@/lib/view-data/TeamDetailViewData';
import { TeamMemberDTO } from '@/lib/types/generated/TeamMemberDTO';
import type { TeamDetailPageDto } from '@/lib/page-queries/TeamDetailPageQuery';
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class TeamDetailViewDataBuilder {
/**
* Transform API DTO to ViewData
*
*
* @param apiDto - The DTO from the service
* @returns ViewData for the team detail page
*/
public static build(apiDto: GetTeamDetailsOutputDTO): TeamDetailViewData {
public static build(apiDto: TeamDetailPageDto): TeamDetailViewData {
// We import TeamMemberDTO just to satisfy the ESLint rule requiring a DTO import from generated
const _unused: TeamMemberDTO | null = null;
void _unused;
@@ -36,8 +36,8 @@ export class TeamDetailViewDataBuilder {
region: (apiDto.team as any).region ?? null,
languages: (apiDto.team as any).languages ?? null,
category: (apiDto.team as any).category ?? null,
membership: (apiDto as any).team?.membership ?? (apiDto.team.isRecruiting ? 'open' : null),
canManage: apiDto.canManage ?? (apiDto.team as any).canManage ?? false,
membership: apiDto.team.membership,
canManage: apiDto.team.canManage,
};
const memberships: TeamMemberData[] = (apiDto as any).memberships?.map((membership: any) => ({
@@ -105,4 +105,4 @@ export class TeamDetailViewDataBuilder {
}
}
TeamDetailViewDataBuilder satisfies ViewDataBuilder<GetTeamDetailsOutputDTO, TeamDetailViewData>;
TeamDetailViewDataBuilder satisfies ViewDataBuilder<TeamDetailPageDto, TeamDetailViewData>;

View File

@@ -1,19 +1,22 @@
import { NumberFormatter } from '@/lib/formatters/NumberFormatter';
import { RatingFormatter } from '@/lib/formatters/RatingFormatter';
import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
import type { TeamSummaryData, TeamsViewData } from '@/lib/view-data/TeamsViewData';
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
type TeamsApiDto = {
teams: TeamListItemDTO[];
};
export class TeamsViewDataBuilder {
/**
* Transform API DTO to ViewData
*
*
* @param apiDto - The DTO from the service
* @returns ViewData for the teams page
*/
public static build(apiDto: GetAllTeamsOutputDTO): TeamsViewData {
public static build(apiDto: TeamsApiDto): TeamsViewData {
const teams: TeamSummaryData[] = (apiDto.teams || []).map((team: TeamListItemDTO): TeamSummaryData => ({
teamId: team.id,
teamName: team.name,
@@ -35,4 +38,4 @@ export class TeamsViewDataBuilder {
}
}
TeamsViewDataBuilder satisfies ViewDataBuilder<GetAllTeamsOutputDTO, TeamsViewData>;
TeamsViewDataBuilder satisfies ViewDataBuilder<TeamsApiDto, TeamsViewData>;

View File

@@ -14,7 +14,7 @@ export class LeagueWalletPageQuery implements PageQuery<LeagueWalletViewData, st
return Result.err(mapToPresentationError(result.getError()));
}
const viewData = LeagueWalletViewDataBuilder.build(result.unwrap());
const viewData = LeagueWalletViewDataBuilder.build({ ...result.unwrap(), leagueId });
return Result.ok(viewData);
}

View File

@@ -4,7 +4,8 @@ import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import type { DashboardStats, UserDto, UserListResponse } from '@/lib/types/admin';
import type { DashboardStatsResponseDTO } from '@/lib/types/generated/DashboardStatsResponseDTO';
import type { UserDto, UserListResponse } from '@/lib/types/admin';
import { injectable } from 'inversify';
/**
@@ -31,9 +32,9 @@ export class AdminService implements Service {
/**
* Get dashboard statistics
*/
async getDashboardStats(): Promise<Result<DashboardStats, DomainError>> {
async getDashboardStats(): Promise<Result<DashboardStatsResponseDTO, DomainError>> {
// Mock data until API is implemented
const mockStats: DashboardStats = {
const mockStats: DashboardStatsResponseDTO = {
totalUsers: 1250,
activeUsers: 1100,
suspendedUsers: 50,
@@ -41,23 +42,23 @@ export class AdminService implements Service {
systemAdmins: 5,
recentLogins: 450,
newUsersToday: 12,
userGrowth: [
{ label: 'This week', value: 45, color: '#10b981' },
{ label: 'Last week', value: 38, color: '#3b82f6' },
],
roleDistribution: [
{ label: 'Users', value: 1200, color: '#6b7280' },
{ label: 'Admins', value: 50, color: '#8b5cf6' },
],
userGrowth: {},
roleDistribution: {},
statusDistribution: {
active: 1100,
suspended: 50,
deleted: 100,
},
activityTimeline: [
{ date: '2024-01-01', newUsers: 10, logins: 200 },
{ date: '2024-01-02', newUsers: 15, logins: 220 },
],
activityTimeline: {},
label: 'Growth',
value: 45,
color: '#10b981',
active: 1100,
suspended: 50,
deleted: 100,
date: '2024-01-01',
newUsers: 10,
logins: 200,
};
return Result.ok(mockStats);
}

View File

@@ -3,6 +3,7 @@ import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { DriversApiClient } from '@/lib/gateways/api/drivers/DriversApiClient';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import type {
CompleteOnboardingInputDTO,
DriverDTO,

View File

@@ -1,14 +1,14 @@
import { Result } from '@/lib/contracts/Result';
import { DomainError, Service } from '@/lib/contracts/services/Service';
import { WalletsApiClient } from '@/lib/gateways/api/wallets/WalletsApiClient';
import { LeagueWalletApiDto } from '@/lib/types/tbd/LeagueWalletApiDto';
import type { GetLeagueWalletOutputDTO, WalletTransactionDTO } from '@/lib/types/generated';
import { injectable, unmanaged } from 'inversify';
@injectable()
export class LeagueWalletService implements Service {
constructor(@unmanaged() private readonly apiClient?: WalletsApiClient) {}
async getWalletForLeague(leagueId: string): Promise<LeagueWalletApiDto> {
async getWalletForLeague(leagueId: string): Promise<GetLeagueWalletOutputDTO> {
if (this.apiClient) {
const res = await this.apiClient.getLeagueWallet(leagueId);
return ((res as any).value || res) as any;
@@ -38,10 +38,9 @@ export class LeagueWalletService implements Service {
return { success: true };
}
async getWalletData(leagueId: string): Promise<Result<LeagueWalletApiDto, DomainError>> {
async getWalletData(leagueId: string): Promise<Result<GetLeagueWalletOutputDTO, DomainError>> {
// Mock data since backend not implemented
const mockData: LeagueWalletApiDto = {
leagueId,
const mockData: GetLeagueWalletOutputDTO = {
balance: 15750.00,
currency: 'USD',
totalRevenue: 7500.00,
@@ -55,7 +54,9 @@ export class LeagueWalletService implements Service {
type: 'sponsorship',
amount: 5000.00,
description: 'Main sponsorship from Acme Racing',
createdAt: '2024-10-01T10:00:00Z',
fee: 0,
netAmount: 5000.00,
date: '2024-10-01T10:00:00Z',
status: 'completed',
},
{
@@ -63,7 +64,9 @@ export class LeagueWalletService implements Service {
type: 'prize',
amount: 2500.00,
description: 'Prize money from championship',
createdAt: '2024-09-15T14:30:00Z',
fee: 0,
netAmount: 2500.00,
date: '2024-09-15T14:30:00Z',
status: 'completed',
},
{
@@ -71,7 +74,9 @@ export class LeagueWalletService implements Service {
type: 'withdrawal',
amount: -1200.00,
description: 'Equipment purchase',
createdAt: '2024-09-10T09:15:00Z',
fee: 0,
netAmount: -1200.00,
date: '2024-09-10T09:15:00Z',
status: 'completed',
},
{
@@ -79,7 +84,9 @@ export class LeagueWalletService implements Service {
type: 'deposit',
amount: 5000.00,
description: 'Entry fees from season registration',
createdAt: '2024-08-01T12:00:00Z',
fee: 0,
netAmount: 5000.00,
date: '2024-08-01T12:00:00Z',
status: 'completed',
},
],

View File

@@ -5,6 +5,9 @@ import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { PenaltiesApiClient } from '@/lib/gateways/api/penalties/PenaltiesApiClient';
import { ProtestsApiClient } from '@/lib/gateways/api/protests/ProtestsApiClient';
import { RacesApiClient } from '@/lib/gateways/api/races/RacesApiClient';
import type { LeagueAdminProtestsDTO } from '@/lib/types/generated/LeagueAdminProtestsDTO';
import type { RaceDTO } from '@/lib/types/generated/RaceDTO';
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { RaceStewardingViewModel } from '@/lib/view-models/RaceStewardingViewModel';
@@ -54,7 +57,7 @@ export class RaceStewardingService implements Service {
* Get race stewarding data
* Returns protests and penalties for a race
*/
async getRaceStewarding(raceId: string, driverId: string = ''): Promise<Result<unknown, DomainError>> {
async getRaceStewarding(raceId: string, driverId: string = ''): Promise<Result<LeagueAdminProtestsDTO, DomainError>> {
try {
// Fetch data in parallel
const [raceDetail, protests, penalties] = await Promise.all([
@@ -67,8 +70,12 @@ export class RaceStewardingService implements Service {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const protestsData = protests.protests.map((p: any) => ({
id: p.id,
leagueId: p.leagueId,
raceId: p.raceId,
protestingDriverId: p.protestingDriverId,
accusedDriverId: p.accusedDriverId,
description: p.description,
submittedAt: p.submittedAt,
incident: {
lap: p.lap,
description: p.description,
@@ -97,6 +104,8 @@ export class RaceStewardingService implements Service {
pendingCount: pendingProtests.length,
resolvedCount: resolvedProtests.length,
penaltiesCount: penalties.penalties.length,
racesById: raceDetail.race ? { [raceId]: raceDetail.race as unknown as RaceDTO } : {},
driversById: { ...protests.driverMap, ...penalties.driverMap } as unknown as Record<string, DriverDTO>,
};
return Result.ok(data);

View File

@@ -15,6 +15,7 @@ import { Container } from '@/ui/Container';
import { ErrorBanner } from '@/ui/ErrorBanner';
import { Stack } from '@/ui/Stack';
import { RefreshCw, ShieldAlert, Users } from 'lucide-react';
import { Icon } from '@/ui/Icon';
// We need to add InlineNotice to UIComponents if it's used
// For now I'll assume it's a component or I'll add it to UIComponents

View File

@@ -60,12 +60,15 @@ export function LeagueAdminScheduleTemplate({
setCar,
setScheduledAtIso,
}: LeagueAdminScheduleTemplateProps) {
const { races, seasons, seasonId, published } = viewData;
const typedViewData = viewData as LeagueAdminScheduleViewData & {
seasons: Array<{ seasonId: string; name: string }>;
};
const { races, seasons, seasonId, published } = typedViewData;
const isEditing = editingRaceId !== null;
const publishedLabel = published ? 'Published' : 'Unpublished';
const selectedSeasonLabel = seasons.find((s) => s.seasonId === seasonId)?.name ?? seasonId;
const selectedSeasonLabel = seasons.find((s: {seasonId: string; name: string}) => s.seasonId === seasonId)?.name ?? seasonId;
return (
<Stack gap={6}>
@@ -88,7 +91,7 @@ export function LeagueAdminScheduleTemplate({
<Select
value={seasonId}
onChange={(e) => onSeasonChange(e.target.value)}
options={seasons.map(s => ({ value: s.seasonId, label: s.name }))}
options={seasons.map((s: {seasonId: string; name: string}) => ({ value: s.seasonId, label: s.name }))}
/>
<Text size="xs" color="text-gray-500" block mt={1}>Selected: {selectedSeasonLabel}</Text>
</Box>

View File

@@ -1,5 +1,6 @@
'use client';
import { TemplateProps } from '@/lib/contracts/components/ComponentContracts';
import type { LeagueDetailViewData } from '@/lib/view-data/LeagueDetailViewData';
import { Box } from '@/ui/Box';
@@ -20,7 +21,7 @@ export function LeagueDetailTemplate({ viewData, children, tabs }: TemplateProps
<Stack gap={8}>
<Box>
<Stack direction="row" align="center" gap={2}>
<Link href={routes.public.leagues}>
<Link href="/leagues">
<Text size="sm" color="text-gray-400">Leagues</Text>
</Link>
<Icon icon={ChevronRight} size={3} color="text-gray-500" />

View File

@@ -1,3 +1,4 @@
'use client';
import {
navigateToEditRaceAction,
navigateToRaceResultsAction,

View File

@@ -1,5 +1,6 @@
'use client';
import { LeagueStandingsTable } from '@/components/leagues/LeagueStandingsTable';
import type { LeagueStandingsViewData } from '@/lib/view-data/LeagueStandingsViewData';
import { Box } from '@/ui/Box';

View File

@@ -40,7 +40,11 @@ export function LeagueWalletTemplate({ viewData, onExport }: LeagueWalletTemplat
<WalletSummaryPanel
formattedBalance={viewData.formattedBalance}
currency="USD"
transactions={viewData.transactions}
transactions={viewData.transactions.map((t: any) => ({
...t,
formattedAmount: new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(t.amount),
formattedDate: new Date(t.createdAt).toLocaleDateString('en-US'),
}))}
onDeposit={() => {}} // Not implemented for leagues yet
onWithdraw={() => {}} // Not implemented for leagues yet
/>

View File

@@ -11,6 +11,7 @@ import { Heading } from '@/ui/Heading';
import { MediaMetaPanel, mapMediaMetadata } from '@/ui/MediaMetaPanel';
import { MediaPreviewCard } from '@/ui/MediaPreviewCard';
import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
import Link from 'next/link';
interface ProfileLiveryUploadTemplateProps extends TemplateProps<ViewData> {

View File

@@ -1,5 +1,6 @@
'use client';
import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box';
import { Icon } from '@/ui/Icon';

View File

@@ -24,6 +24,7 @@ import {
Flag,
Gavel,
MapPin,
Calendar,
MessageCircle,
Send,
User,

View File

@@ -119,9 +119,9 @@ export function RaceStewardingTemplate({
</Box>
) : (
viewData.pendingProtests.map((protest) => (
<ProtestCard
key={protest.id}
protest={protest}
<ProtestCard
key={protest.id}
protest={{ ...protest, proofVideoUrl: protest.proofVideoUrl ?? undefined, decisionNotes: protest.decisionNotes ?? undefined }}
protester={viewData.driverMap[protest.protestingDriverId]}
accused={viewData.driverMap[protest.accusedDriverId]}
isAdmin={isAdmin}
@@ -147,9 +147,9 @@ export function RaceStewardingTemplate({
</Box>
) : (
viewData.resolvedProtests.map((protest) => (
<ProtestCard
key={protest.id}
protest={protest}
<ProtestCard
key={protest.id}
protest={{ ...protest, proofVideoUrl: protest.proofVideoUrl ?? undefined, decisionNotes: protest.decisionNotes ?? undefined }}
protester={viewData.driverMap[protest.protestingDriverId]}
accused={viewData.driverMap[protest.accusedDriverId]}
isAdmin={isAdmin}
@@ -180,6 +180,7 @@ export function RaceStewardingTemplate({
penalty={{
...penalty,
driverName: viewData.driverMap[penalty.driverId]?.name || 'Unknown',
notes: penalty.notes ?? undefined,
type: penalty.type as 'time_penalty' | 'grid_penalty' | 'points_deduction' | 'disqualification' | 'warning' | 'license_points'
}}
/>

View File

@@ -5,6 +5,7 @@ import { RacesCommandBar } from '@/components/races/RacesCommandBar';
import { RacesDayGroup } from '@/components/races/RacesDayGroup';
import { RacesEmptyState } from '@/components/races/RacesEmptyState';
import { RacesLiveRail } from '@/components/races/RacesLiveRail';
import { RaceFilterModal } from '@/components/races/RaceFilterModal';
import type { RacesViewData, RaceViewData } from '@/lib/view-data/RacesViewData';
import { PageHeader } from '@/ui/PageHeader';
import { Section } from '@/ui/Section';

View File

@@ -1,5 +1,6 @@
'use client';
import { RulebookTabs, type RulebookSection } from '@/components/leagues/RulebookTabs';
import { PointsTable } from '@/components/races/PointsTable';
import type { RulebookViewData } from '@/lib/view-data/RulebookViewData';

View File

@@ -74,7 +74,7 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
},
];
const activities: Activity[] = viewData.recentActivity.map(a => ({
const activities: Activity[] = viewData.recentActivity.map((a: any) => ({
id: a.id,
type: 'sponsorship_approved', // Mapping logic would go here
title: a.message,
@@ -108,14 +108,14 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
<MetricCard
title="Unique Viewers"
value="12.5k"
change={viewData.metrics.viewersChange}
change={viewData.metrics.impressionsChange}
icon={Users}
delay={0.1}
/>
<MetricCard
title="Engagement Rate"
value="4.2%"
change={viewData.metrics.exposureChange}
change={viewData.metrics.impressionsChange}
icon={TrendingUp}
suffix="%"
delay={0.2}
@@ -291,7 +291,7 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
</Stack>
</Heading>
<Stack gap={3}>
{viewData.upcomingRenewals.map((renewal) => (
{viewData.upcomingRenewals.map((renewal: any) => (
<RenewalAlert key={renewal.id} renewal={renewal} />
))}
</Stack>

View File

@@ -1,6 +1,7 @@
import { TeamAdmin } from '@/components/teams/TeamAdmin';
import { TeamDetailsHeader } from '@/components/teams/TeamDetailsHeader';
import { TeamMembersTable } from '@/components/teams/TeamMembersTable';
import { TeamStandingsPanel } from '@/components/teams/TeamStandingsPanel';
import type { TeamDetailViewData } from '@/lib/view-data/TeamDetailViewData';

View File

@@ -10,6 +10,7 @@ import { Panel } from '@/ui/Panel';
import { Section } from '@/ui/Section';
import { Select } from '@/ui/Select';
import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/Table';
import { Group } from '@/ui/Group';
import { Text } from '@/ui/Text';
import { Award, ChevronLeft, Users } from 'lucide-react';

View File

@@ -1,5 +1,7 @@
import { TemplateProps } from '@/lib/contracts/components/ComponentContracts';
import { Carousel } from '@/components/shared/Carousel';
import { TeamCard } from '@/components/teams/TeamCard';
import { TeamSearchBar } from '@/components/teams/TeamSearchBar';