website refactor
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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__ || [],
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 || '#';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 || '',
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user