website refactor

This commit is contained in:
2026-01-12 01:01:49 +01:00
parent 5ca6023a5a
commit fefd8d1cd6
294 changed files with 4628 additions and 4991 deletions

View File

@@ -3,7 +3,7 @@
import { useState, useEffect } from 'react';
import { apiClient } from '@/lib/apiClient';
import Card from '@/components/ui/Card';
import { AdminViewModelService } from '@/lib/services/AdminViewModelService';
import { AdminViewModelPresenter } from '@/lib/view-models/AdminViewModelPresenter';
import { DashboardStatsViewModel } from '@/lib/view-models/AdminUserViewModel';
import {
Users,
@@ -31,7 +31,7 @@ export function AdminDashboardPage() {
const response = await apiClient.admin.getDashboardStats();
// Map DTO to View Model
const viewModel = AdminViewModelService.mapDashboardStats(response);
const viewModel = AdminViewModelPresenter.mapDashboardStats(response);
setStats(viewModel);
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to load stats';
@@ -222,4 +222,4 @@ export function AdminDashboardPage() {
</Card>
</div>
);
}
}

View File

@@ -10,6 +10,7 @@ import {
Activity
} from 'lucide-react';
import { useRouter, usePathname } from 'next/navigation';
import { logoutAction } from '@/app/actions/logoutAction';
interface AdminLayoutProps {
children: ReactNode;
@@ -62,15 +63,6 @@ export function AdminLayout({ children }: AdminLayoutProps) {
}
};
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', { method: 'POST' });
router.push('/');
} catch (error) {
console.error('Logout failed:', error);
}
};
return (
<div className="flex h-screen bg-deep-graphite overflow-hidden">
{/* Sidebar */}
@@ -132,13 +124,16 @@ export function AdminLayout({ children }: AdminLayoutProps) {
{isSidebarOpen && <span className="text-sm">Toggle Sidebar</span>}
</button>
<button
onClick={handleLogout}
className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-racing-red hover:bg-racing-red/10 transition-colors"
>
<LogOut className="w-5 h-5" />
{isSidebarOpen && <span className="text-sm">Logout</span>}
</button>
{/* Use form with server action for logout */}
<form action={logoutAction}>
<button
type="submit"
className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-racing-red hover:bg-racing-red/10 transition-colors"
>
<LogOut className="w-5 h-5" />
{isSidebarOpen && <span className="text-sm">Logout</span>}
</button>
</form>
</div>
</aside>

View File

@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
import { apiClient } from '@/lib/apiClient';
import Card from '@/components/ui/Card';
import StatusBadge from '@/components/ui/StatusBadge';
import { AdminViewModelService } from '@/lib/services/AdminViewModelService';
import { AdminViewModelPresenter } from '@/lib/view-models/AdminViewModelPresenter';
import { AdminUserViewModel, UserListViewModel } from '@/lib/view-models/AdminUserViewModel';
import {
Search,
@@ -47,7 +47,7 @@ export function AdminUsersPage() {
});
// Map DTO to View Model
const viewModel = AdminViewModelService.mapUserList(response);
const viewModel = AdminViewModelPresenter.mapUserList(response);
setUserList(viewModel);
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to load users';
@@ -356,4 +356,4 @@ export function AdminUsersPage() {
)}
</div>
);
}
}

View File

@@ -4,6 +4,18 @@ import { useState, useEffect } from 'react';
import { Bug, X, Settings, Shield, Activity } from 'lucide-react';
import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
import { getGlobalApiLogger } from '@/lib/infrastructure/ApiRequestLogger';
import type { GlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler';
import type { ApiRequestLogger } from '@/lib/infrastructure/ApiRequestLogger';
// Extend Window interface for debug globals
declare global {
interface Window {
__GRIDPILOT_FETCH_LOGGED__?: boolean;
__GRIDPILOT_GLOBAL_HANDLER__?: GlobalErrorHandler;
__GRIDPILOT_API_LOGGER__?: ApiRequestLogger;
__GRIDPILOT_REACT_ERRORS__?: Array<{ error: unknown; componentStack?: string }>;
}
}
interface DebugModeToggleProps {
/**
@@ -74,21 +86,21 @@ export function DebugModeToggle({ show }: DebugModeToggleProps) {
globalHandler.initialize();
// Override fetch with logging
if (!(window as any).__GRIDPILOT_FETCH_LOGGED__) {
if (!window.__GRIDPILOT_FETCH_LOGGED__) {
const loggedFetch = apiLogger.createLoggedFetch();
window.fetch = loggedFetch as any;
(window as any).__GRIDPILOT_FETCH_LOGGED__ = true;
window.fetch = loggedFetch as typeof fetch;
window.__GRIDPILOT_FETCH_LOGGED__ = true;
}
// Expose to window for easy access
(window as any).__GRIDPILOT_GLOBAL_HANDLER__ = globalHandler;
(window as any).__GRIDPILOT_API_LOGGER__ = apiLogger;
window.__GRIDPILOT_GLOBAL_HANDLER__ = globalHandler;
window.__GRIDPILOT_API_LOGGER__ = apiLogger;
console.log('%c[DEBUG MODE] Enabled', 'color: #00ff88; font-weight: bold; font-size: 14px;');
console.log('Available globals:', {
__GRIDPILOT_GLOBAL_HANDLER__: globalHandler,
__GRIDPILOT_API_LOGGER__: apiLogger,
__GRIDPILOT_REACT_ERRORS__: (window as any).__GRIDPILOT_REACT_ERRORS__ || [],
__GRIDPILOT_REACT_ERRORS__: window.__GRIDPILOT_REACT_ERRORS__ || [],
});
};

View File

@@ -1,6 +1,6 @@
'use client';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { useNotifications } from '@/components/notifications/NotificationProvider';
import type { NotificationVariant } from '@/components/notifications/notificationTypes';
import { Wrench, ChevronDown, ChevronUp, X, MessageSquare, Activity, AlertTriangle } from 'lucide-react';

View File

@@ -1,7 +1,7 @@
'use client';
import { Bell } from 'lucide-react';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { useNotifications } from '@/components/notifications/NotificationProvider';
import type { NotificationVariant } from '@/components/notifications/notificationTypes';
import type { DemoNotificationType, DemoUrgency } from '../types';

View File

@@ -4,7 +4,7 @@ import { useState, FormEvent } from 'react';
import { useRouter } from 'next/navigation';
import Input from '../ui/Input';
import Button from '../ui/Button';
import { useCreateDriver } from '@/hooks/driver/useCreateDriver';
import { useCreateDriver } from "@/lib/hooks/driver/useCreateDriver";
interface FormErrors {
name?: string;

View File

@@ -8,7 +8,7 @@ import ProfileStats from './ProfileStats';
import CareerHighlights from './CareerHighlights';
import DriverRankings from './DriverRankings';
import PerformanceMetrics from './PerformanceMetrics';
import { useDriverProfile } from '@/hooks/driver/useDriverProfile';
import { useDriverProfile } from "@/lib/hooks/driver/useDriverProfile";
interface DriverProfileProps {
driver: DriverViewModel;

View File

@@ -1,6 +1,6 @@
'use client';
import { useDriverProfile } from '@/hooks/driver';
import { useDriverProfile } from "@/lib/hooks/driver";
import { useMemo } from 'react';
import Card from '../ui/Card';
import RankBadge from './RankBadge';

View File

@@ -2,7 +2,7 @@
import Container from '@/components/ui/Container';
import Heading from '@/components/ui/Heading';
import { useParallax } from '@/hooks/useScrollProgress';
import { useParallax } from "@/lib/hooks/useScrollProgress";
import { useRef } from 'react';
interface AlternatingSectionProps {

View File

@@ -4,7 +4,7 @@ import { useRef } from 'react';
import Button from '@/components/ui/Button';
import Container from '@/components/ui/Container';
import Heading from '@/components/ui/Heading';
import { useParallax } from '../../hooks/useScrollProgress';
import { useParallax } from '@/lib/hooks/useScrollProgress';
const discordUrl = process.env.NEXT_PUBLIC_DISCORD_URL || '#';

View File

@@ -4,7 +4,7 @@ import { useState, FormEvent } from 'react';
import { useRouter } from 'next/navigation';
import Input from '../ui/Input';
import Button from '../ui/Button';
import { useCreateLeague } from '@/hooks/league/useCreateLeague';
import { useCreateLeague } from "@/lib/hooks/league/useCreateLeague";
import { useAuth } from '@/lib/auth/AuthContext';
import { useInject } from '@/lib/di/hooks/useInject';
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';

View File

@@ -26,8 +26,8 @@ import { FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { LeagueWizardCommandModel } from '@/lib/command-models/leagues/LeagueWizardCommandModel';
import { useCreateLeagueWizard } from '@/hooks/useLeagueWizardService';
import { useLeagueScoringPresets } from '@/hooks/useLeagueScoringPresets';
import { useCreateLeagueWizard } from "@/lib/hooks/useLeagueWizardService";
import { useLeagueScoringPresets } from "@/lib/hooks/useLeagueScoringPresets";
import { LeagueBasicsSection } from './LeagueBasicsSection';
import { LeagueDropSection } from './LeagueDropSection';
import {
@@ -316,7 +316,7 @@ export default function CreateLeagueWizard({ stepName, onStepChange }: CreateLea
const validateStep = (currentStep: Step): boolean => {
// Convert form to LeagueWizardFormData for validation
const formData: any = {
const formData: LeagueWizardCommandModel.LeagueWizardFormData = {
leagueId: form.leagueId || '',
basics: {
name: form.basics?.name || '',
@@ -409,7 +409,7 @@ export default function CreateLeagueWizard({ stepName, onStepChange }: CreateLea
}
// Convert form to LeagueWizardFormData for validation
const formData: any = {
const formData: LeagueWizardCommandModel.LeagueWizardFormData = {
leagueId: form.leagueId || '',
basics: {
name: form.basics?.name || '',
@@ -520,7 +520,7 @@ export default function CreateLeagueWizard({ stepName, onStepChange }: CreateLea
};
});
};
const steps = [
{ id: 1 as Step, label: 'Basics', icon: FileText, shortLabel: 'Name' },
{ id: 2 as Step, label: 'Visibility', icon: Award, shortLabel: 'Type' },
@@ -870,8 +870,8 @@ export default function CreateLeagueWizard({ stepName, onStepChange }: CreateLea
{/* Championships & Drop Rules side by side on larger screens */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<ChampionshipsSection form={form} onChange={setForm as any} readOnly={presetsLoading} />
<LeagueDropSection form={form} onChange={setForm as any} readOnly={false} />
<ChampionshipsSection form={form} onChange={setForm} readOnly={presetsLoading} />
<LeagueDropSection form={form} onChange={setForm} readOnly={false} />
</div>
{errors.submit && (

View File

@@ -1,9 +1,9 @@
'use client';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { getMembership } from '@/lib/leagueMembership';
import { useState } from 'react';
import { useLeagueMembershipMutation } from '@/hooks/league/useLeagueMembershipMutation';
import { useLeagueMembershipMutation } from "@/lib/hooks/league/useLeagueMembershipMutation";
import Button from '../ui/Button';
interface JoinLeagueButtonProps {

View File

@@ -1,7 +1,7 @@
'use client';
import { Calendar, Award, UserPlus, UserMinus, Shield, Flag, AlertTriangle } from 'lucide-react';
import { useLeagueRaces } from '@/hooks/league/useLeagueRaces';
import { useLeagueRaces } from "@/lib/hooks/league/useLeagueRaces";
export type LeagueActivity =
| { type: 'race_completed'; raceId: string; raceName: string; timestamp: Date }

View File

@@ -1,7 +1,7 @@
'use client';
import DriverIdentity from '../drivers/DriverIdentity';
import { useEffectiveDriverId } from '../../hooks/useEffectiveDriverId';
import { useEffectiveDriverId } from '@/lib/hooks/useEffectiveDriverId';
import { useInject } from '@/lib/di/hooks/useInject';
import { LEAGUE_MEMBERSHIP_SERVICE_TOKEN, DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
import type { LeagueMembership } from '@/lib/types/LeagueMembership';
@@ -45,7 +45,7 @@ export default function LeagueMembers({
const byId: Record<string, DriverViewModel> = {};
for (const dto of driverDtos) {
byId[dto.id] = new DriverViewModel({ ...dto, avatarUrl: (dto as any).avatarUrl ?? null });
byId[dto.id] = new DriverViewModel({ ...dto, avatarUrl: dto.avatarUrl ?? null });
}
setDriversById(byId);
} else {

View File

@@ -48,7 +48,7 @@ export default function LeagueOwnershipTransfer({
driver={new DriverViewModel({
id: ownerSummary.driver.id,
name: ownerSummary.driver.name,
avatarUrl: (ownerSummary.driver as any).avatarUrl ?? null,
avatarUrl: ownerSummary.driver.avatarUrl ?? null,
iracingId: ownerSummary.driver.iracingId,
country: ownerSummary.driver.country,
bio: ownerSummary.driver.bio,

View File

@@ -1,8 +1,8 @@
'use client';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useRegisterForRace } from '@/hooks/race/useRegisterForRace';
import { useWithdrawFromRace } from '@/hooks/race/useWithdrawFromRace';
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { useRegisterForRace } from "@/lib/hooks/race/useRegisterForRace";
import { useWithdrawFromRace } from "@/lib/hooks/race/useWithdrawFromRace";
import { useRouter } from 'next/navigation';
import { useMemo, useState } from 'react';
import type { LeagueScheduleRaceViewModel } from '@/lib/view-models/LeagueScheduleViewModel';
@@ -10,7 +10,7 @@ import type { LeagueScheduleRaceViewModel } from '@/lib/view-models/LeagueSchedu
// Shared state components
import { StateContainer } from '@/components/shared/state/StateContainer';
import { EmptyState } from '@/components/shared/state/EmptyState';
import { useLeagueSchedule } from '@/hooks/league/useLeagueSchedule';
import { useLeagueSchedule } from "@/lib/hooks/league/useLeagueSchedule";
import { Calendar } from 'lucide-react';
interface LeagueScheduleProps {

View File

@@ -6,9 +6,9 @@ import PendingSponsorshipRequests, { type PendingRequestDTO } from '../sponsors/
import Button from '../ui/Button';
import Input from '../ui/Input';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useLeagueSeasons } from '@/hooks/league/useLeagueSeasons';
import { useSponsorshipRequests } from '@/hooks/league/useSponsorshipRequests';
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { useLeagueSeasons } from "@/lib/hooks/league/useLeagueSeasons";
import { useSponsorshipRequests } from "@/lib/hooks/league/useSponsorshipRequests";
import { useInject } from '@/lib/di/hooks/useInject';
import { SPONSORSHIP_SERVICE_TOKEN } from '@/lib/di/tokens';

View File

@@ -1,6 +1,6 @@
'use client';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { getMembership } from '@/lib/leagueMembership';
import type { MembershipRole } from '@/lib/types/MembershipRole';

View File

@@ -3,7 +3,7 @@
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import Button from '@/components/ui/Button';
import { usePenaltyMutation } from '@/hooks/league/usePenaltyMutation';
import { usePenaltyMutation } from "@/lib/hooks/league/usePenaltyMutation";
import { AlertTriangle, Clock, Flag, Zap } from 'lucide-react';
interface DriverOption {
@@ -52,16 +52,14 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelecte
setError(null);
try {
const command: any = {
const command = {
raceId: selectedRaceId,
driverId: selectedDriver,
adminId,
infractionType: infractionType as any,
severity: severity as any,
stewardId: adminId,
type: infractionType,
reason: severity,
notes: notes.trim() || undefined,
};
if (notes.trim()) {
command.notes = notes.trim();
}
await penaltyMutation.mutateAsync(command);

View File

@@ -1,7 +1,7 @@
"use client";
import { useMemo, useState } from "react";
import { usePenaltyTypesReference } from "@/hooks/usePenaltyTypesReference";
import { usePenaltyTypesReference } from "@/lib/hooks/usePenaltyTypesReference";
import type { PenaltyValueKindDTO } from "@/lib/types/PenaltyTypesReferenceDTO";
import { ProtestViewModel } from "../../lib/view-models/ProtestViewModel";
import Modal from "../ui/Modal";

View File

@@ -4,7 +4,7 @@ import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Button from '../ui/Button';
import Input from '../ui/Input';
import { useAllLeagues } from '@/hooks/league/useAllLeagues';
import { useAllLeagues } from "@/lib/hooks/league/useAllLeagues";
import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
interface ScheduleRaceFormData {

View File

@@ -23,9 +23,9 @@ import Input from '@/components/ui/Input';
import Heading from '@/components/ui/Heading';
import CountrySelect from '@/components/ui/CountrySelect';
import { useAuth } from '@/lib/auth/AuthContext';
import { useCompleteOnboarding } from '@/hooks/onboarding/useCompleteOnboarding';
import { useGenerateAvatars } from '@/hooks/onboarding/useGenerateAvatars';
import { useValidateFacePhoto } from '@/hooks/onboarding/useValidateFacePhoto';
import { useCompleteOnboarding } from "@/lib/hooks/onboarding/useCompleteOnboarding";
import { useGenerateAvatars } from "@/lib/hooks/onboarding/useGenerateAvatars";
import { useValidateFacePhoto } from "@/lib/hooks/onboarding/useValidateFacePhoto";
// ============================================================================
// TYPES

View File

@@ -6,12 +6,11 @@ import { BarChart3, Building2, ChevronDown, CreditCard, Handshake, LogOut, Megap
import Link from 'next/link';
import React, { useEffect, useMemo, useState } from 'react';
import DriverSummaryPill from '@/components/profile/DriverSummaryPill';
import { CapabilityGate } from '@/components/shared/CapabilityGate';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { DriverViewModel as DriverViewModelClass } from '@/lib/view-models/DriverViewModel';
import { useFindDriverById } from '@/hooks/driver/useFindDriverById';
import { useEffectiveDriverId } from '@/lib/hooks/useEffectiveDriverId';
import type { DriverViewModel } from '@/lib/view-models/view-models/DriverViewModel';
import { DriverViewModel as DriverViewModelClass } from '@/lib/view-models/view-models/DriverViewModel';
import { useFindDriverById } from '@/lib/hooks/driver/useFindDriverById';
// Hook to detect demo user mode based on session
function useDemoUserMode(): { isDemo: boolean; demoRole: string | null } {
@@ -27,8 +26,8 @@ function useDemoUserMode(): { isDemo: boolean; demoRole: string | null } {
const email = session.user.email?.toLowerCase() || '';
const displayName = session.user.displayName?.toLowerCase() || '';
const primaryDriverId = (session.user as any).primaryDriverId || '';
const role = (session.user as any).role;
const primaryDriverId = session.user.primaryDriverId || '';
const role = 'role' in session.user ? (session.user as { role?: string }).role : undefined;
// Check if this is a demo user
if (email.includes('demo') ||
@@ -151,7 +150,7 @@ export default function UserPill() {
// Transform DTO to ViewModel
const driver = useMemo(() => {
if (!driverDto) return null;
return new DriverViewModelClass({ ...driverDto, avatarUrl: (driverDto as any).avatarUrl ?? null });
return new DriverViewModelClass({ ...driverDto, avatarUrl: driverDto.avatarUrl ?? null });
}, [driverDto]);
const data = useMemo(() => {

View File

@@ -4,7 +4,7 @@ import { useState } from 'react';
import Modal from '@/components/ui/Modal';
import Button from '@/components/ui/Button';
import type { FileProtestCommandDTO } from '@/lib/types/generated/FileProtestCommandDTO';
import { useFileProtest } from '@/hooks/race/useFileProtest';
import { useFileProtest } from "@/lib/hooks/race/useFileProtest";
import {
AlertTriangle,
Video,

View File

@@ -1,7 +1,7 @@
'use client';
import { ReactNode } from 'react';
import { useCapability } from '@/hooks/useCapability';
import { useCapability } from "@/lib/hooks/useCapability";
import { useInject } from '@/lib/di/hooks/useInject';
import { POLICY_SERVICE_TOKEN } from '@/lib/di/tokens';

View File

@@ -456,7 +456,7 @@ export function useSponsorMode(): boolean {
}
// Check session.user.role for sponsor
const role = (session.user as any).role;
const role = session.user?.role;
if (role === 'sponsor') {
setIsSponsor(true);
return;

View File

@@ -2,8 +2,8 @@
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useCreateTeam } from '@/hooks/team';
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { useCreateTeam } from "@/lib/hooks/team";
import { useRouter } from 'next/navigation';
import { useState } from 'react';

View File

@@ -1,6 +1,5 @@
import Image from 'next/image';
import { UserPlus, Users, Trophy } from 'lucide-react';
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
import { getMediaUrl } from '@/lib/utilities/media';
const SKILL_LEVELS: {
@@ -14,7 +13,7 @@ const SKILL_LEVELS: {
{
id: 'pro',
label: 'Pro',
icon: () => null, // We'll import Crown if needed
icon: () => null,
color: 'text-yellow-400',
bgColor: 'bg-yellow-400/10',
borderColor: 'border-yellow-400/30',
@@ -46,7 +45,17 @@ const SKILL_LEVELS: {
];
interface FeaturedRecruitingProps {
teams: TeamSummaryViewModel[];
teams: Array<{
id: string;
name: string;
description?: string;
logoUrl?: string;
category?: string;
memberCount: number;
totalWins: number;
performanceLevel: string;
isRecruiting: boolean;
}>;
onTeamClick: (id: string) => void;
}

View File

@@ -1,8 +1,8 @@
'use client';
import Button from '@/components/ui/Button';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useTeamMembership, useJoinTeam, useLeaveTeam } from '@/hooks/team';
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { useTeamMembership, useJoinTeam, useLeaveTeam } from "@/lib/hooks/team";
import { useState } from 'react';
interface JoinTeamButtonProps {

View File

@@ -1,6 +1,5 @@
import { useState } from 'react';
import { ChevronRight, Users, Trophy, UserPlus } from 'lucide-react';
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
import TeamCard from './TeamCard';
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
@@ -18,7 +17,22 @@ interface SkillLevelConfig {
interface SkillLevelSectionProps {
level: SkillLevelConfig;
teams: TeamSummaryViewModel[];
teams: Array<{
id: string;
name: string;
description?: string;
logoUrl?: string;
memberCount: number;
rating?: number;
totalWins: number;
totalRaces: number;
performanceLevel: string;
isRecruiting: boolean;
specialization?: string;
region?: string;
languages: string[];
category?: string;
}>;
onTeamClick: (id: string) => void;
defaultExpanded?: boolean;
}

View File

@@ -4,12 +4,17 @@ import { useState } from 'react';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import { useTeamJoinRequests, useUpdateTeam, useApproveJoinRequest, useRejectJoinRequest } from '@/hooks/team';
import { useTeamJoinRequests, useUpdateTeam, useApproveJoinRequest, useRejectJoinRequest } from "@/lib/hooks/team";
import type { TeamJoinRequestViewModel } from '@/lib/view-models/TeamJoinRequestViewModel';
import type { TeamDetailsViewModel } from '@/lib/view-models/TeamDetailsViewModel';
interface TeamAdminProps {
team: Pick<TeamDetailsViewModel, 'id' | 'name' | 'tag' | 'description' | 'ownerId'>;
team: {
id: string;
name: string;
tag: string;
description?: string;
ownerId: string;
};
onUpdate: () => void;
}
@@ -18,7 +23,7 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
const [editedTeam, setEditedTeam] = useState({
name: team.name,
tag: team.tag,
description: team.description,
description: team.description || '',
});
// Use hooks for data fetching
@@ -141,7 +146,7 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
setEditedTeam({
name: team.name,
tag: team.tag,
description: team.description,
description: team.description || '',
});
}}
>

View File

@@ -2,7 +2,6 @@ import { useRouter } from 'next/navigation';
import Image from 'next/image';
import { Award, ChevronRight, Crown, Trophy, Users } from 'lucide-react';
import Button from '@/components/ui/Button';
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
import { getMediaUrl } from '@/lib/utilities/media';
const SKILL_LEVELS: {
@@ -48,7 +47,17 @@ const SKILL_LEVELS: {
];
interface TeamLeaderboardPreviewProps {
topTeams: TeamSummaryViewModel[];
topTeams: Array<{
id: string;
name: string;
logoUrl?: string;
category?: string;
memberCount: number;
totalWins: number;
isRecruiting: boolean;
rating?: number;
performanceLevel: string;
}>;
onTeamClick: (id: string) => void;
}

View File

@@ -2,7 +2,7 @@
import Card from '@/components/ui/Card';
import DriverIdentity from '@/components/drivers/DriverIdentity';
import { useTeamRoster } from '@/hooks/team';
import { useTeamRoster } from "@/lib/hooks/team";
import { useState } from 'react';
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
@@ -11,7 +11,14 @@ type TeamMemberRole = 'owner' | 'manager' | 'member';
interface TeamRosterProps {
teamId: string;
memberships: any[];
memberships: Array<{
driverId: string;
driverName: string;
role: 'owner' | 'manager' | 'member';
joinedAt: string;
isActive: boolean;
avatarUrl: string;
}>;
isAdmin: boolean;
onRemoveMember?: (driverId: string) => void;
onChangeRole?: (driverId: string, newRole: TeamRole) => void;

View File

@@ -1,7 +1,7 @@
'use client';
import Card from '@/components/ui/Card';
import { useTeamStandings } from '@/hooks/team';
import { useTeamStandings } from "@/lib/hooks/team";
interface TeamStandingsProps {
teamId: string;