auth rework
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
@@ -25,12 +26,127 @@ import {
|
||||
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import type {
|
||||
DashboardOverviewViewModel,
|
||||
DashboardFeedItemSummaryViewModel,
|
||||
} from '@core/racing/application/presenters/IDashboardOverviewPresenter';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
// TODO: Re-enable API integration once backend is ready
|
||||
// import type {
|
||||
// DashboardOverviewViewModel,
|
||||
// DashboardFeedItemSummaryViewModel,
|
||||
// } from '@core/racing/application/presenters/IDashboardOverviewPresenter';
|
||||
|
||||
// Mock data for prototype
|
||||
const MOCK_CURRENT_DRIVER = {
|
||||
id: 'driver-1',
|
||||
name: 'Max Verstappen',
|
||||
avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=MaxV',
|
||||
country: 'NL',
|
||||
totalRaces: 142,
|
||||
wins: 28,
|
||||
podiums: 67,
|
||||
rating: 2847,
|
||||
globalRank: 15,
|
||||
consistency: 94,
|
||||
};
|
||||
|
||||
const MOCK_NEXT_RACE = {
|
||||
id: 'race-1',
|
||||
track: 'Spa-Francorchamps',
|
||||
car: 'Porsche 911 GT3 R',
|
||||
scheduledAt: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now
|
||||
isMyLeague: true,
|
||||
leagueName: 'GT3 Masters Series',
|
||||
};
|
||||
|
||||
const MOCK_UPCOMING_RACES = [
|
||||
{
|
||||
id: 'race-1',
|
||||
track: 'Spa-Francorchamps',
|
||||
car: 'Porsche 911 GT3 R',
|
||||
scheduledAt: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000),
|
||||
isMyLeague: true,
|
||||
},
|
||||
{
|
||||
id: 'race-2',
|
||||
track: 'Nürburgring GP',
|
||||
car: 'BMW M4 GT3',
|
||||
scheduledAt: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000),
|
||||
isMyLeague: true,
|
||||
},
|
||||
{
|
||||
id: 'race-3',
|
||||
track: 'Monza',
|
||||
car: 'Ferrari 296 GT3',
|
||||
scheduledAt: new Date(Date.now() + 8 * 24 * 60 * 60 * 1000),
|
||||
isMyLeague: false,
|
||||
},
|
||||
{
|
||||
id: 'race-4',
|
||||
track: 'Silverstone',
|
||||
car: 'Aston Martin Vantage GT3',
|
||||
scheduledAt: new Date(Date.now() + 12 * 24 * 60 * 60 * 1000),
|
||||
isMyLeague: true,
|
||||
},
|
||||
];
|
||||
|
||||
const MOCK_LEAGUE_STANDINGS = [
|
||||
{
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'GT3 Masters Series',
|
||||
position: 2,
|
||||
points: 186,
|
||||
totalDrivers: 24,
|
||||
},
|
||||
{
|
||||
leagueId: 'league-2',
|
||||
leagueName: 'Endurance Pro League',
|
||||
position: 5,
|
||||
points: 142,
|
||||
totalDrivers: 32,
|
||||
},
|
||||
{
|
||||
leagueId: 'league-3',
|
||||
leagueName: 'F1 Weekend Warriors',
|
||||
position: 1,
|
||||
points: 225,
|
||||
totalDrivers: 18,
|
||||
},
|
||||
];
|
||||
|
||||
const MOCK_FEED_ITEMS = [
|
||||
{
|
||||
id: 'feed-1',
|
||||
type: 'win',
|
||||
headline: 'You won the race at Spa-Francorchamps!',
|
||||
body: 'Great driving! You finished P1 with a 3.2s gap to second place.',
|
||||
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000),
|
||||
ctaHref: '/races/race-prev-1',
|
||||
ctaLabel: 'View Results',
|
||||
},
|
||||
{
|
||||
id: 'feed-2',
|
||||
type: 'friend_join',
|
||||
headline: 'Lewis Hamilton joined GT3 Masters Series',
|
||||
body: null,
|
||||
timestamp: new Date(Date.now() - 8 * 60 * 60 * 1000),
|
||||
ctaHref: '/leagues/league-1',
|
||||
ctaLabel: 'View League',
|
||||
},
|
||||
{
|
||||
id: 'feed-3',
|
||||
type: 'podium',
|
||||
headline: 'Charles Leclerc finished P2 at Monza',
|
||||
body: 'Your friend had a great race!',
|
||||
timestamp: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
||||
ctaHref: '/drivers/driver-2',
|
||||
ctaLabel: 'View Profile',
|
||||
},
|
||||
];
|
||||
|
||||
const MOCK_FRIENDS = [
|
||||
{ id: 'friend-1', name: 'Lewis Hamilton', avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Lewis', country: 'GB' },
|
||||
{ id: 'friend-2', name: 'Charles Leclerc', avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Charles', country: 'MC' },
|
||||
{ id: 'friend-3', name: 'Lando Norris', avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Lando', country: 'GB' },
|
||||
{ id: 'friend-4', name: 'Oscar Piastri', avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Oscar', country: 'AU' },
|
||||
];
|
||||
|
||||
// Helper functions
|
||||
function getCountryFlag(countryCode: string): string {
|
||||
@@ -81,58 +197,29 @@ function getGreeting(): string {
|
||||
return 'Good evening';
|
||||
}
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const authService = getAuthService();
|
||||
const session = await authService.getCurrentSession();
|
||||
interface FeedItem {
|
||||
id: string;
|
||||
type: string;
|
||||
headline: string;
|
||||
body: string | null;
|
||||
timestamp: Date;
|
||||
ctaHref?: string;
|
||||
ctaLabel?: string;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
redirect('/auth/iracing?returnTo=/dashboard');
|
||||
}
|
||||
export default function DashboardPage() {
|
||||
// TODO: Re-enable API integration once backend is ready
|
||||
// Currently using mock data for prototype
|
||||
|
||||
const currentDriver = MOCK_CURRENT_DRIVER;
|
||||
const nextRace = MOCK_NEXT_RACE;
|
||||
const upcomingRaces = MOCK_UPCOMING_RACES;
|
||||
const leagueStandingsSummaries = MOCK_LEAGUE_STANDINGS;
|
||||
const feedSummary = { items: MOCK_FEED_ITEMS };
|
||||
const friends = MOCK_FRIENDS;
|
||||
const activeLeaguesCount = 3;
|
||||
|
||||
const currentDriverId = session.user.primaryDriverId ?? '';
|
||||
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/races/dashboard/overview?driverId=${currentDriverId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch dashboard overview');
|
||||
}
|
||||
const viewModel: DashboardOverviewViewModel = await response.json();
|
||||
|
||||
if (!viewModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
currentDriver,
|
||||
myUpcomingRaces,
|
||||
otherUpcomingRaces,
|
||||
nextRace: nextRaceSummary,
|
||||
recentResults,
|
||||
leagueStandingsSummaries,
|
||||
feedSummary,
|
||||
friends,
|
||||
upcomingRaces,
|
||||
activeLeaguesCount,
|
||||
} = viewModel;
|
||||
|
||||
const nextRace =
|
||||
nextRaceSummary != null
|
||||
? {
|
||||
...nextRaceSummary,
|
||||
scheduledAt: new Date(nextRaceSummary.scheduledAt),
|
||||
}
|
||||
: null;
|
||||
|
||||
const upcomingRacesForDisplay = upcomingRaces.map(race => ({
|
||||
...race,
|
||||
scheduledAt: new Date(race.scheduledAt),
|
||||
}));
|
||||
|
||||
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;
|
||||
const { totalRaces, wins, podiums, rating, globalRank, consistency } = currentDriver;
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-deep-graphite">
|
||||
@@ -150,27 +237,25 @@ export default async function DashboardPage() {
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-8">
|
||||
{/* Welcome Message */}
|
||||
<div className="flex items-start gap-5">
|
||||
{currentDriver && (
|
||||
<div className="relative">
|
||||
<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={currentDriver.avatarUrl}
|
||||
alt={currentDriver.name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<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={currentDriver.avatarUrl}
|
||||
alt={currentDriver.name}
|
||||
width={80}
|
||||
height={80}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute -bottom-1 -right-1 w-5 h-5 rounded-full bg-performance-green border-3 border-deep-graphite" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute -bottom-1 -right-1 w-5 h-5 rounded-full bg-performance-green border-3 border-deep-graphite" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm mb-1">{getGreeting()},</p>
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-white mb-2">
|
||||
{currentDriver?.name ?? 'Racer'}
|
||||
<span className="ml-3 text-2xl">{currentDriver ? getCountryFlag(currentDriver.country) : '🏁'}</span>
|
||||
{currentDriver.name}
|
||||
<span className="ml-3 text-2xl">{getCountryFlag(currentDriver.country)}</span>
|
||||
</h1>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-primary-blue/10 border border-primary-blue/30">
|
||||
@@ -370,7 +455,7 @@ export default async function DashboardPage() {
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
||||
<Activity className="w-5 h-5 text-neon-aqua" />
|
||||
<Activity className="w-5 h-5 text-cyan-400" />
|
||||
Recent Activity
|
||||
</h2>
|
||||
</div>
|
||||
@@ -403,9 +488,9 @@ export default async function DashboardPage() {
|
||||
View all
|
||||
</Link>
|
||||
</div>
|
||||
{upcomingRacesForDisplay.length > 0 ? (
|
||||
{upcomingRaces.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{upcomingRacesForDisplay.slice(0, 5).map((race) => {
|
||||
{upcomingRaces.slice(0, 5).map((race) => {
|
||||
const isMyRace = race.isMyLeague;
|
||||
return (
|
||||
<Link
|
||||
@@ -496,7 +581,7 @@ export default async function DashboardPage() {
|
||||
}
|
||||
|
||||
// Feed Item Row Component
|
||||
function FeedItemRow({ item }: { item: DashboardFeedItemSummaryViewModel }) {
|
||||
function FeedItemRow({ item }: { item: FeedItem }) {
|
||||
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' };
|
||||
|
||||
@@ -1,20 +1,51 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import OnboardingWizard from '@/components/onboarding/OnboardingWizard';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
// TODO: Re-enable API integration once backend is ready
|
||||
// import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function OnboardingPage() {
|
||||
const authService = getAuthService();
|
||||
const session = await authService.getCurrentSession();
|
||||
export default function OnboardingPage() {
|
||||
const router = useRouter();
|
||||
const [checking, setChecking] = useState(true);
|
||||
|
||||
if (!session) {
|
||||
redirect('/auth/iracing?returnTo=/onboarding');
|
||||
}
|
||||
useEffect(() => {
|
||||
// TODO: Re-enable auth check once backend is ready
|
||||
// For now, just show onboarding after a brief check
|
||||
const checkDemoMode = () => {
|
||||
// Check if user has demo mode cookie
|
||||
const cookies = document.cookie.split(';');
|
||||
const demoModeCookie = cookies.find(c => c.trim().startsWith('gridpilot_demo_mode='));
|
||||
|
||||
if (!demoModeCookie) {
|
||||
// Not logged in, redirect to auth
|
||||
router.push('/auth/login?returnTo=/onboarding');
|
||||
return;
|
||||
}
|
||||
|
||||
// For demo, skip onboarding and go to dashboard
|
||||
// In production, this would check if onboarding is complete
|
||||
router.push('/dashboard');
|
||||
};
|
||||
|
||||
const primaryDriverId = session.user.primaryDriverId ?? '';
|
||||
// Brief delay to prevent flash
|
||||
const timer = setTimeout(() => {
|
||||
checkDemoMode();
|
||||
}, 500);
|
||||
|
||||
if (primaryDriverId) {
|
||||
redirect('/dashboard');
|
||||
return () => clearTimeout(timer);
|
||||
}, [router]);
|
||||
|
||||
// Show loading while checking
|
||||
if (checking) {
|
||||
return (
|
||||
<main className="min-h-screen bg-deep-graphite flex items-center justify-center">
|
||||
<Loader2 className="w-8 h-8 text-primary-blue animate-spin" />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user