wip
This commit is contained in:
@@ -26,21 +26,11 @@ import {
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { getAuthService } from '@/lib/auth';
|
||||
import {
|
||||
getFeedRepository,
|
||||
getRaceRepository,
|
||||
getResultRepository,
|
||||
getDriverRepository,
|
||||
getLeagueRepository,
|
||||
getStandingRepository,
|
||||
getSocialRepository,
|
||||
getDriverStats,
|
||||
getImageService,
|
||||
getLeagueMembershipRepository,
|
||||
} from '@/lib/di-container';
|
||||
import type { FeedItem } from '@gridpilot/social/domain/entities/FeedItem';
|
||||
import type { Race } from '@gridpilot/racing/domain/entities/Race';
|
||||
import type { Driver } from '@gridpilot/racing/domain/entities/Driver';
|
||||
import { getGetDashboardOverviewUseCase } from '@/lib/di-container';
|
||||
import type {
|
||||
DashboardOverviewViewModel,
|
||||
DashboardFeedItemSummaryViewModel,
|
||||
} from '@gridpilot/racing/application/presenters/IDashboardOverviewPresenter';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -74,8 +64,9 @@ function timeUntil(date: Date): string {
|
||||
return `${diffMinutes}m`;
|
||||
}
|
||||
|
||||
function timeAgo(timestamp: Date): string {
|
||||
const diffMs = Date.now() - timestamp.getTime();
|
||||
function timeAgo(timestamp: Date | string): string {
|
||||
const time = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
|
||||
const diffMs = Date.now() - time.getTime();
|
||||
const diffMinutes = Math.floor(diffMs / 60000);
|
||||
if (diffMinutes < 1) return 'Just now';
|
||||
if (diffMinutes < 60) return `${diffMinutes}m ago`;
|
||||
@@ -100,73 +91,48 @@ export default async function DashboardPage() {
|
||||
redirect('/auth/iracing?returnTo=/dashboard');
|
||||
}
|
||||
|
||||
const feedRepository = getFeedRepository();
|
||||
const raceRepository = getRaceRepository();
|
||||
const resultRepository = getResultRepository();
|
||||
const driverRepository = getDriverRepository();
|
||||
const leagueRepository = getLeagueRepository();
|
||||
const standingRepository = getStandingRepository();
|
||||
const socialRepository = getSocialRepository();
|
||||
const leagueMembershipRepository = getLeagueMembershipRepository();
|
||||
const imageService = getImageService();
|
||||
|
||||
const currentDriverId = session.user.primaryDriverId ?? '';
|
||||
const currentDriver = await driverRepository.findById(currentDriverId);
|
||||
|
||||
const [feedItems, allRaces, allResults, allLeagues, friends] = await Promise.all([
|
||||
feedRepository.getFeedForDriver(currentDriverId),
|
||||
raceRepository.findAll(),
|
||||
resultRepository.findAll(),
|
||||
leagueRepository.findAll(),
|
||||
socialRepository.getFriends(currentDriverId),
|
||||
]);
|
||||
const useCase = getGetDashboardOverviewUseCase();
|
||||
await useCase.execute({ driverId: currentDriverId });
|
||||
const viewModel = useCase.presenter.getViewModel() as DashboardOverviewViewModel | null;
|
||||
|
||||
// Get driver's leagues by checking membership in each league
|
||||
const driverLeagues: typeof allLeagues = [];
|
||||
for (const league of allLeagues) {
|
||||
const membership = await leagueMembershipRepository.getMembership(league.id, currentDriverId);
|
||||
if (membership && membership.status === 'active') {
|
||||
driverLeagues.push(league);
|
||||
}
|
||||
if (!viewModel) {
|
||||
return null;
|
||||
}
|
||||
const driverLeagueIds = driverLeagues.map(l => l.id);
|
||||
|
||||
// Upcoming races (prioritize driver's leagues)
|
||||
const upcomingRaces = allRaces
|
||||
.filter((race) => race.status === 'scheduled')
|
||||
.sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
|
||||
|
||||
const myUpcomingRaces = upcomingRaces.filter(r => driverLeagueIds.includes(r.leagueId));
|
||||
const otherUpcomingRaces = upcomingRaces.filter(r => !driverLeagueIds.includes(r.leagueId));
|
||||
const nextRace = myUpcomingRaces[0] || otherUpcomingRaces[0];
|
||||
const {
|
||||
currentDriver,
|
||||
myUpcomingRaces,
|
||||
otherUpcomingRaces,
|
||||
nextRace: nextRaceSummary,
|
||||
recentResults,
|
||||
leagueStandingsSummaries,
|
||||
feedSummary,
|
||||
friends,
|
||||
upcomingRaces,
|
||||
activeLeaguesCount,
|
||||
} = viewModel;
|
||||
|
||||
// Recent results for driver
|
||||
const driverResults = allResults.filter(r => r.driverId === currentDriverId);
|
||||
const recentResults = driverResults.slice(0, 5);
|
||||
const nextRace =
|
||||
nextRaceSummary != null
|
||||
? {
|
||||
...nextRaceSummary,
|
||||
scheduledAt: new Date(nextRaceSummary.scheduledAt),
|
||||
}
|
||||
: null;
|
||||
|
||||
// Get stats
|
||||
const driverStats = getDriverStats(currentDriverId);
|
||||
const upcomingRacesForDisplay = upcomingRaces.map(race => ({
|
||||
...race,
|
||||
scheduledAt: new Date(race.scheduledAt),
|
||||
}));
|
||||
|
||||
// Get standings for driver's leagues
|
||||
const leagueStandings = await Promise.all(
|
||||
driverLeagues.slice(0, 3).map(async (league) => {
|
||||
const standings = await standingRepository.findByLeagueId(league.id);
|
||||
const driverStanding = standings.find(s => s.driverId === currentDriverId);
|
||||
return {
|
||||
league,
|
||||
position: driverStanding?.position ?? 0,
|
||||
points: driverStanding?.points ?? 0,
|
||||
totalDrivers: standings.length,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Calculate quick stats
|
||||
const totalRaces = driverStats?.totalRaces ?? 0;
|
||||
const wins = driverStats?.wins ?? 0;
|
||||
const podiums = driverStats?.podiums ?? 0;
|
||||
const rating = driverStats?.rating ?? 1500;
|
||||
const globalRank = driverStats?.overallRank ?? 0;
|
||||
const totalRaces = currentDriver?.totalRaces ?? 0;
|
||||
const wins = currentDriver?.wins ?? 0;
|
||||
const podiums = currentDriver?.podiums ?? 0;
|
||||
const rating = currentDriver?.rating ?? 1500;
|
||||
const globalRank = currentDriver?.globalRank ?? 0;
|
||||
const consistency = currentDriver?.consistency ?? 0;
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-deep-graphite">
|
||||
@@ -189,7 +155,7 @@ export default async function DashboardPage() {
|
||||
<div className="w-20 h-20 rounded-2xl bg-gradient-to-br from-primary-blue to-purple-600 p-0.5 shadow-xl shadow-primary-blue/20">
|
||||
<div className="w-full h-full rounded-xl overflow-hidden bg-iron-gray">
|
||||
<Image
|
||||
src={imageService.getDriverAvatar(currentDriverId)}
|
||||
src={currentDriver.avatarUrl}
|
||||
alt={currentDriver.name}
|
||||
width={80}
|
||||
height={80}
|
||||
@@ -267,7 +233,7 @@ export default async function DashboardPage() {
|
||||
<Target className="w-5 h-5 text-primary-blue" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-white">{driverStats?.consistency ?? 0}%</p>
|
||||
<p className="text-2xl font-bold text-white">{consistency}%</p>
|
||||
<p className="text-xs text-gray-500">Consistency</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,7 +244,7 @@ export default async function DashboardPage() {
|
||||
<Users className="w-5 h-5 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-white">{driverLeagues.length}</p>
|
||||
<p className="text-2xl font-bold text-white">{activeLeaguesCount}</p>
|
||||
<p className="text-xs text-gray-500">Active Leagues</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -302,7 +268,7 @@ export default async function DashboardPage() {
|
||||
<Play className="w-3.5 h-3.5 text-primary-blue" />
|
||||
<span className="text-xs font-semibold text-primary-blue uppercase tracking-wider">Next Race</span>
|
||||
</div>
|
||||
{myUpcomingRaces.includes(nextRace) && (
|
||||
{nextRace.isMyLeague && (
|
||||
<span className="px-2 py-0.5 rounded-full bg-performance-green/20 text-performance-green text-xs font-medium">
|
||||
Your League
|
||||
</span>
|
||||
@@ -350,7 +316,7 @@ export default async function DashboardPage() {
|
||||
)}
|
||||
|
||||
{/* League Standings Preview */}
|
||||
{leagueStandings.length > 0 && (
|
||||
{leagueStandingsSummaries.length > 0 && (
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
||||
@@ -362,10 +328,10 @@ export default async function DashboardPage() {
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{leagueStandings.map(({ league, position, points, totalDrivers }) => (
|
||||
{leagueStandingsSummaries.map(({ leagueId, leagueName, position, points, totalDrivers }) => (
|
||||
<Link
|
||||
key={league.id}
|
||||
href={`/leagues/${league.id}/standings`}
|
||||
key={leagueId}
|
||||
href={`/leagues/${leagueId}/standings`}
|
||||
className="flex items-center gap-4 p-4 rounded-xl bg-deep-graphite border border-charcoal-outline hover:border-primary-blue/30 transition-colors group"
|
||||
>
|
||||
<div className={`flex h-12 w-12 items-center justify-center rounded-xl font-bold text-xl ${
|
||||
@@ -378,7 +344,7 @@ export default async function DashboardPage() {
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white font-semibold truncate group-hover:text-primary-blue transition-colors">
|
||||
{league.name}
|
||||
{leagueName}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{points} points • {totalDrivers} drivers
|
||||
@@ -408,10 +374,10 @@ export default async function DashboardPage() {
|
||||
Recent Activity
|
||||
</h2>
|
||||
</div>
|
||||
{feedItems.length > 0 ? (
|
||||
{feedSummary.items.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{feedItems.slice(0, 5).map((item) => (
|
||||
<FeedItemRow key={item.id} item={item} imageService={imageService} />
|
||||
{feedSummary.items.slice(0, 5).map((item) => (
|
||||
<FeedItemRow key={item.id} item={item} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
@@ -437,10 +403,10 @@ export default async function DashboardPage() {
|
||||
View all
|
||||
</Link>
|
||||
</div>
|
||||
{upcomingRaces.length > 0 ? (
|
||||
{upcomingRacesForDisplay.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{upcomingRaces.slice(0, 5).map((race) => {
|
||||
const isMyRace = driverLeagueIds.includes(race.leagueId);
|
||||
{upcomingRacesForDisplay.slice(0, 5).map((race) => {
|
||||
const isMyRace = race.isMyLeague;
|
||||
return (
|
||||
<Link
|
||||
key={race.id}
|
||||
@@ -488,7 +454,7 @@ export default async function DashboardPage() {
|
||||
>
|
||||
<div className="w-9 h-9 rounded-full overflow-hidden bg-gradient-to-br from-primary-blue to-purple-600">
|
||||
<Image
|
||||
src={imageService.getDriverAvatar(friend.id)}
|
||||
src={friend.avatarUrl}
|
||||
alt={friend.name}
|
||||
width={36}
|
||||
height={36}
|
||||
@@ -530,7 +496,7 @@ export default async function DashboardPage() {
|
||||
}
|
||||
|
||||
// Feed Item Row Component
|
||||
function FeedItemRow({ item, imageService }: { item: FeedItem; imageService: any }) {
|
||||
function FeedItemRow({ item }: { item: DashboardFeedItemSummaryViewModel }) {
|
||||
const getActivityIcon = (type: string) => {
|
||||
if (type.includes('win')) return { icon: Trophy, color: 'text-yellow-400 bg-yellow-400/10' };
|
||||
if (type.includes('podium')) return { icon: Medal, color: 'text-warning-amber bg-warning-amber/10' };
|
||||
|
||||
Reference in New Issue
Block a user