website refactor
This commit is contained in:
@@ -2,24 +2,15 @@
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import LeaderboardsTemplate from '@/templates/LeaderboardsTemplate';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
import type { LeaderboardsViewData } from '@/lib/view-data/LeaderboardsViewData';
|
||||
|
||||
interface LeaderboardsPageData {
|
||||
drivers: DriverLeaderboardViewModel | null;
|
||||
teams: TeamSummaryViewModel[] | null;
|
||||
}
|
||||
|
||||
export function LeaderboardsPageWrapper({ data }: { data: LeaderboardsPageData | null }) {
|
||||
export function LeaderboardsPageWrapper({ data }: { data: LeaderboardsViewData | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
if (!data || (!data.drivers && !data.teams)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const drivers = data.drivers?.drivers || [];
|
||||
const teams = data.teams || [];
|
||||
|
||||
const handleDriverClick = (driverId: string) => {
|
||||
router.push(`/drivers/${driverId}`);
|
||||
};
|
||||
@@ -36,14 +27,34 @@ export function LeaderboardsPageWrapper({ data }: { data: LeaderboardsPageData |
|
||||
router.push('/teams/leaderboard');
|
||||
};
|
||||
|
||||
return (
|
||||
<LeaderboardsTemplate
|
||||
drivers={drivers}
|
||||
teams={teams}
|
||||
onDriverClick={handleDriverClick}
|
||||
onTeamClick={handleTeamClick}
|
||||
onNavigateToDrivers={handleNavigateToDrivers}
|
||||
onNavigateToTeams={handleNavigateToTeams}
|
||||
/>
|
||||
);
|
||||
// Transform ViewData to template props
|
||||
const templateData = {
|
||||
drivers: data.drivers.map(d => ({
|
||||
id: d.id,
|
||||
name: d.name,
|
||||
rating: d.rating,
|
||||
skillLevel: d.skillLevel,
|
||||
nationality: d.nationality,
|
||||
wins: d.wins,
|
||||
rank: d.rank,
|
||||
avatarUrl: d.avatarUrl,
|
||||
position: d.position,
|
||||
})),
|
||||
teams: data.teams.map(t => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
tag: t.tag,
|
||||
memberCount: t.memberCount,
|
||||
category: t.category,
|
||||
totalWins: t.totalWins,
|
||||
logoUrl: t.logoUrl,
|
||||
position: t.position,
|
||||
})),
|
||||
onDriverClick: handleDriverClick,
|
||||
onTeamClick: handleTeamClick,
|
||||
onNavigateToDrivers: handleNavigateToDrivers,
|
||||
onNavigateToTeams: handleNavigateToTeams,
|
||||
};
|
||||
|
||||
return <LeaderboardsTemplate {...templateData} />;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import DriverRankingsTemplate from '@/templates/DriverRankingsTemplate';
|
||||
import { useState } from 'react';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
|
||||
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
|
||||
type SortBy = 'rank' | 'rating' | 'wins' | 'podiums' | 'winRate';
|
||||
|
||||
export function DriverRankingsPageWrapper({ data }: { data: DriverLeaderboardViewModel | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
// Client-side state for filtering and sorting
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedSkill, setSelectedSkill] = useState<'all' | SkillLevel>('all');
|
||||
const [sortBy, setSortBy] = useState<SortBy>('rank');
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
||||
if (!data || !data.drivers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleDriverClick = (driverId: string) => {
|
||||
if (driverId.startsWith('demo-')) return;
|
||||
router.push(`/drivers/${driverId}`);
|
||||
};
|
||||
|
||||
const handleBackToLeaderboards = () => {
|
||||
router.push('/leaderboards');
|
||||
};
|
||||
|
||||
return (
|
||||
<DriverRankingsTemplate
|
||||
drivers={data.drivers}
|
||||
searchQuery={searchQuery}
|
||||
selectedSkill={selectedSkill}
|
||||
sortBy={sortBy}
|
||||
showFilters={showFilters}
|
||||
onSearchChange={setSearchQuery}
|
||||
onSkillChange={setSelectedSkill}
|
||||
onSortChange={setSortBy}
|
||||
onToggleFilters={() => setShowFilters(!showFilters)}
|
||||
onDriverClick={handleDriverClick}
|
||||
onBackToLeaderboards={handleBackToLeaderboards}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,50 +1,51 @@
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { DriverService } from '@/lib/services/drivers/DriverService';
|
||||
import { Users } from 'lucide-react';
|
||||
import { redirect } from 'next/navigation';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
import { DriverRankingsPageWrapper } from './DriverRankingsPageWrapper';
|
||||
import { DriverRankingsPageQuery } from '@/lib/page-queries/page-queries/DriverRankingsPageQuery';
|
||||
import { DriverRankingsViewDataBuilder } from '@/lib/builders/view-data/DriverRankingsViewDataBuilder';
|
||||
import { DriverRankingsTemplate } from '@/templates/DriverRankingsTemplate';
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PAGE COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
export default async function DriverLeaderboardPage() {
|
||||
// Fetch data using PageDataFetcher
|
||||
const driverData = await PageDataFetcher.fetch<DriverService, 'getDriverLeaderboard'>(
|
||||
DRIVER_SERVICE_TOKEN,
|
||||
'getDriverLeaderboard'
|
||||
);
|
||||
// Execute the page query
|
||||
const result = await DriverRankingsPageQuery.execute();
|
||||
|
||||
// Prepare data for template
|
||||
const data: DriverLeaderboardViewModel | null = driverData;
|
||||
// Handle different result statuses
|
||||
switch (result.status) {
|
||||
case 'notFound':
|
||||
redirect('/404');
|
||||
case 'redirect':
|
||||
redirect(result.to);
|
||||
case 'error':
|
||||
// For now, show empty state. In a real app, you'd pass error to client
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 py-12 text-center">
|
||||
<div className="text-red-400 mb-4">Error loading driver rankings</div>
|
||||
<p className="text-gray-400">Please try again later</p>
|
||||
</div>
|
||||
);
|
||||
case 'ok':
|
||||
const viewData = DriverRankingsViewDataBuilder.build(result.dto);
|
||||
const hasData = (viewData.drivers?.length ?? 0) > 0;
|
||||
|
||||
if (!hasData) {
|
||||
return (
|
||||
<DriverRankingsTemplate
|
||||
viewData={{
|
||||
drivers: [],
|
||||
podium: [],
|
||||
searchQuery: '',
|
||||
selectedSkill: 'all',
|
||||
sortBy: 'rank',
|
||||
showFilters: false,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const hasData = (driverData?.drivers?.length ?? 0) > 0;
|
||||
|
||||
// Handle loading state (should be fast since we're using async/await)
|
||||
const isLoading = false;
|
||||
const error = null;
|
||||
const retry = async () => {
|
||||
// In server components, we can't retry without a reload
|
||||
redirect('/leaderboards/drivers');
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper
|
||||
data={hasData ? data : null}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
retry={retry}
|
||||
Template={DriverRankingsPageWrapper}
|
||||
loading={{ variant: 'full-screen', message: 'Loading driver rankings...' }}
|
||||
errorConfig={{ variant: 'full-screen' }}
|
||||
empty={{
|
||||
icon: Users,
|
||||
title: 'No drivers found',
|
||||
description: 'There are no drivers in the system yet.',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<DriverRankingsTemplate viewData={viewData} />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,61 @@
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||
import { DRIVER_SERVICE_TOKEN, TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { DriverService } from '@/lib/services/drivers/DriverService';
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import { Trophy } from 'lucide-react';
|
||||
import { redirect } from 'next/navigation';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
import { LeaderboardsPageQuery } from '@/lib/page-queries/page-queries/LeaderboardsPageQuery';
|
||||
import { LeaderboardsViewDataBuilder } from '@/lib/builders/view-data/LeaderboardsViewDataBuilder';
|
||||
import { LeaderboardsPageWrapper } from './LeaderboardsPageWrapper';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
interface LeaderboardsPageData {
|
||||
drivers: DriverLeaderboardViewModel | null;
|
||||
teams: TeamSummaryViewModel[] | null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PAGE COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
export default async function LeaderboardsPage() {
|
||||
// Fetch data using PageDataFetcher with proper type annotations
|
||||
const [driverData, teamsData] = await Promise.all([
|
||||
PageDataFetcher.fetch<DriverService, 'getDriverLeaderboard'>(
|
||||
DRIVER_SERVICE_TOKEN,
|
||||
'getDriverLeaderboard'
|
||||
),
|
||||
PageDataFetcher.fetch<TeamService, 'getAllTeams'>(
|
||||
TEAM_SERVICE_TOKEN,
|
||||
'getAllTeams'
|
||||
),
|
||||
]);
|
||||
// Execute the page query
|
||||
const result = await LeaderboardsPageQuery.execute();
|
||||
|
||||
// Prepare data for template
|
||||
const data: LeaderboardsPageData = {
|
||||
drivers: driverData,
|
||||
teams: teamsData,
|
||||
};
|
||||
// Handle different result statuses
|
||||
switch (result.status) {
|
||||
case 'notFound':
|
||||
redirect('/404');
|
||||
case 'redirect':
|
||||
redirect(result.to);
|
||||
case 'error':
|
||||
// Show empty state with error
|
||||
return (
|
||||
<PageWrapper
|
||||
data={null}
|
||||
isLoading={false}
|
||||
error={new Error(result.errorId)}
|
||||
retry={async () => redirect('/leaderboards')}
|
||||
Template={LeaderboardsPageWrapper}
|
||||
loading={{ variant: 'full-screen', message: 'Loading leaderboards...' }}
|
||||
errorConfig={{ variant: 'full-screen' }}
|
||||
empty={{
|
||||
icon: Trophy,
|
||||
title: 'No leaderboard data',
|
||||
description: 'There is no leaderboard data available at the moment.',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 'ok':
|
||||
const viewData = LeaderboardsViewDataBuilder.build(result.dto.drivers, result.dto.teams);
|
||||
const hasData = (viewData.drivers?.length ?? 0) > 0 || (viewData.teams?.length ?? 0) > 0;
|
||||
|
||||
const hasData = (driverData?.drivers?.length ?? 0) > 0 || (teamsData?.length ?? 0) > 0;
|
||||
|
||||
// Handle loading state (should be fast since we're using async/await)
|
||||
const isLoading = false;
|
||||
const error = null;
|
||||
const retry = async () => {
|
||||
// In server components, we can't retry without a reload
|
||||
// This would typically trigger a page reload
|
||||
redirect('/leaderboards');
|
||||
};
|
||||
|
||||
return (
|
||||
<PageWrapper
|
||||
data={hasData ? data : null}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
retry={retry}
|
||||
Template={LeaderboardsPageWrapper}
|
||||
loading={{ variant: 'full-screen', message: 'Loading leaderboards...' }}
|
||||
errorConfig={{ variant: 'full-screen' }}
|
||||
empty={{
|
||||
icon: Trophy,
|
||||
title: 'No leaderboard data',
|
||||
description: 'There is no leaderboard data available at the moment.',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<PageWrapper
|
||||
data={hasData ? viewData : null}
|
||||
isLoading={false}
|
||||
error={null}
|
||||
retry={async () => redirect('/leaderboards')}
|
||||
Template={LeaderboardsPageWrapper}
|
||||
loading={{ variant: 'full-screen', message: 'Loading leaderboards...' }}
|
||||
errorConfig={{ variant: 'full-screen' }}
|
||||
empty={{
|
||||
icon: Trophy,
|
||||
title: 'No leaderboard data',
|
||||
description: 'There is no leaderboard data available at the moment.',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user