clean routes

This commit is contained in:
2026-01-03 02:42:47 +01:00
parent 07985fb8f1
commit 2f21dc4595
107 changed files with 7596 additions and 3401 deletions

View File

@@ -1,50 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
export default function AlphaBanner() {
const [isDismissed, setIsDismissed] = useState(false);
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
const dismissed = sessionStorage.getItem('alpha-banner-dismissed');
if (dismissed === 'true') {
setIsDismissed(true);
}
}, []);
const handleDismiss = () => {
sessionStorage.setItem('alpha-banner-dismissed', 'true');
setIsDismissed(true);
};
if (!isMounted) return null;
if (isDismissed) return null;
return (
<div className="sticky top-0 z-50 bg-warning-amber/10 border-b border-warning-amber/20 backdrop-blur-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<svg className="w-5 h-5 text-warning-amber flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<p className="text-sm text-white">
Alpha Version Data resets on page reload. No persistent storage.
</p>
</div>
<button
onClick={handleDismiss}
className="text-gray-400 hover:text-white transition-colors p-1"
aria-label="Dismiss banner"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
);
}

View File

@@ -1,36 +0,0 @@
'use client';
import Link from 'next/link';
export default function AlphaFooter() {
return (
<footer className="mt-auto border-t border-charcoal-outline bg-deep-graphite">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-2 text-sm text-gray-400">
<span className="px-2 py-1 bg-warning-amber/10 text-warning-amber rounded border border-warning-amber/20 font-medium">
Alpha
</span>
</div>
<div className="flex items-center gap-6 text-sm">
<a
href="https://discord.gg/gridpilot"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-primary-blue transition-colors"
>
Give Feedback
</a>
<a
href="/docs/roadmap"
className="text-gray-400 hover:text-primary-blue transition-colors"
>
Roadmap
</a>
</div>
</div>
</div>
</footer>
);
}

View File

@@ -1,79 +0,0 @@
'use client';
import React from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import UserPill from '@/components/profile/UserPill';
import NotificationCenter from '@/components/notifications/NotificationCenter';
import { useAuth } from '@/lib/auth/AuthContext';
type AlphaNavProps = Record<string, never>;
const nonHomeLinks = [
{ href: '/leagues', label: 'Leagues' },
{ href: '/races', label: 'Races' },
{ href: '/teams', label: 'Teams' },
{ href: '/drivers', label: 'Drivers' },
{ href: '/leaderboards', label: 'Leaderboards' },
] as const;
export function AlphaNav({}: AlphaNavProps) {
const pathname = usePathname();
const { session } = useAuth();
const isAuthenticated = !!session;
const navLinks = isAuthenticated
? ([{ href: '/dashboard', label: 'Dashboard' } as const, ...nonHomeLinks] as const)
: ([{ href: '/', label: 'Home' } as const, ...nonHomeLinks] as const);
const loginHref = '/auth/iracing/start?returnTo=/dashboard';
return (
<nav className="sticky top-0 z-40 bg-deep-graphite/95 backdrop-blur-md border-b border-white/5">
<div className="max-w-7xl mx-auto px-6">
<div className="flex items-center justify-between h-14">
<div className="flex items-baseline space-x-3">
<Link
href="/"
className="text-xl font-semibold text-white hover:text-primary-blue transition-colors"
>
GridPilot
</Link>
<span className="text-xs text-gray-500 font-light">ALPHA</span>
</div>
<div className="hidden md:flex items-center space-x-1">
{navLinks.map((link) => {
const isActive = pathname === link.href;
return (
<Link
key={link.href}
href={link.href}
className={`
relative px-4 py-2 text-sm font-medium transition-all duration-200
${
isActive
? 'text-primary-blue'
: 'text-gray-400 hover:text-white'
}
`}
>
{link.label}
{isActive && (
<span className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary-blue rounded-full" />
)}
</Link>
);
})}
</div>
<div className="hidden md:flex items-center space-x-3">
<NotificationCenter />
<UserPill />
</div>
<div className="md:hidden w-8" />
</div>
</div>
</nav>
);
}

View File

@@ -1,136 +0,0 @@
'use client';
import { useState } from 'react';
import Button from '../ui/Button';
import Card from '../ui/Card';
type CompanionRace = {
track: string;
car: string;
scheduledAt: string | Date;
sessionType: string;
};
interface CompanionInstructionsProps {
race: CompanionRace;
leagueName?: string;
}
export default function CompanionInstructions({ race, leagueName }: CompanionInstructionsProps) {
const [copied, setCopied] = useState(false);
const formatDateTime = (date: Date) => {
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short',
});
};
const scheduledAt = typeof race.scheduledAt === 'string' ? new Date(race.scheduledAt) : race.scheduledAt;
const raceDetails = `GridPilot Race: ${leagueName || 'League'}
Track: ${race.track}
Car: ${race.car}
Date/Time: ${formatDateTime(scheduledAt)}
Session Type: ${race.sessionType.charAt(0).toUpperCase() + race.sessionType.slice(1)}`;
const handleCopyDetails = async () => {
try {
await navigator.clipboard.writeText(raceDetails);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};
return (
<Card className="border border-primary-blue/20 bg-iron-gray">
<div className="flex items-start gap-3 mb-4">
<div className="w-10 h-10 rounded-lg bg-primary-blue/10 flex items-center justify-center flex-shrink-0">
<svg className="w-5 h-5 text-primary-blue" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-1">Alpha Manual Workflow</h3>
<p className="text-sm text-gray-400">
Companion automation coming in production. For alpha, races are created manually.
</p>
</div>
</div>
<div className="space-y-3 mb-4">
<div className="flex items-start gap-3">
<span className="flex items-center justify-center w-6 h-6 rounded-full bg-primary-blue/20 text-primary-blue text-xs font-semibold flex-shrink-0">
1
</span>
<p className="text-sm text-gray-300 pt-0.5">
Schedule race in GridPilot (completed)
</p>
</div>
<div className="flex items-start gap-3">
<span className="flex items-center justify-center w-6 h-6 rounded-full bg-charcoal-outline text-gray-400 text-xs font-semibold flex-shrink-0">
2
</span>
<p className="text-sm text-gray-300 pt-0.5">
Copy race details using button below
</p>
</div>
<div className="flex items-start gap-3">
<span className="flex items-center justify-center w-6 h-6 rounded-full bg-charcoal-outline text-gray-400 text-xs font-semibold flex-shrink-0">
3
</span>
<p className="text-sm text-gray-300 pt-0.5">
Create hosted session manually in iRacing website
</p>
</div>
<div className="flex items-start gap-3">
<span className="flex items-center justify-center w-6 h-6 rounded-full bg-charcoal-outline text-gray-400 text-xs font-semibold flex-shrink-0">
4
</span>
<p className="text-sm text-gray-300 pt-0.5">
Return to GridPilot after race completes
</p>
</div>
<div className="flex items-start gap-3">
<span className="flex items-center justify-center w-6 h-6 rounded-full bg-charcoal-outline text-gray-400 text-xs font-semibold flex-shrink-0">
5
</span>
<p className="text-sm text-gray-300 pt-0.5">
Import results via CSV upload
</p>
</div>
</div>
<div className="pt-4 border-t border-charcoal-outline">
<div className="bg-deep-graphite rounded-lg p-3 mb-3">
<pre className="text-xs text-gray-300 whitespace-pre-wrap font-mono">
{raceDetails}
</pre>
</div>
<Button
variant="primary"
onClick={handleCopyDetails}
className="w-full"
>
<div className="flex items-center justify-center gap-2">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
{copied ? 'Copied!' : 'Copy Race Details'}
</div>
</Button>
</div>
</Card>
);
}

View File

@@ -1,27 +0,0 @@
'use client';
interface CompanionStatusProps {
className?: string;
}
export default function CompanionStatus({ className = '' }: CompanionStatusProps) {
// Alpha: always disconnected
const isConnected = false;
const statusMessage = "Companion app available in production";
return (
<div className={`flex items-center gap-3 ${className}`}>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${isConnected ? 'bg-performance-green' : 'bg-gray-500'}`} />
<span className="text-sm text-gray-400">
Companion App: <span className={isConnected ? 'text-performance-green' : 'text-gray-400'}>
{isConnected ? 'Connected' : 'Disconnected'}
</span>
</span>
</div>
<span className="text-xs text-gray-500">
{statusMessage}
</span>
</div>
);
}

View File

@@ -1,23 +0,0 @@
'use client';
interface FeatureLimitationTooltipProps {
message: string;
children: React.ReactNode;
}
export default function FeatureLimitationTooltip({ message, children }: FeatureLimitationTooltipProps) {
return (
<div className="group relative inline-block">
{children}
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 bg-iron-gray border border-charcoal-outline rounded-lg text-sm text-gray-300 whitespace-nowrap opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 pointer-events-none z-50">
<div className="flex items-center gap-2">
<svg className="w-4 h-4 text-primary-blue flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{message}</span>
</div>
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-iron-gray" />
</div>
</div>
);
}

View File

@@ -1 +0,0 @@
export { default } from '../leagues/ScheduleRaceForm';

View File

@@ -71,9 +71,22 @@ export default function DevToolbar() {
// Determine login mode based on user email patterns
const email = session.user.email?.toLowerCase() || '';
const displayName = session.user.displayName?.toLowerCase() || '';
const role = (session.user as any).role;
let mode: LoginMode = 'none';
if (email.includes('sponsor') || displayName.includes('sponsor')) {
// First check session.role if available
if (role) {
if (role === 'sponsor') mode = 'sponsor';
else if (role === 'league-owner') mode = 'league-owner';
else if (role === 'league-steward') mode = 'league-steward';
else if (role === 'league-admin') mode = 'league-admin';
else if (role === 'system-owner') mode = 'system-owner';
else if (role === 'super-admin') mode = 'super-admin';
else if (role === 'driver') mode = 'driver';
}
// Fallback to email patterns
else if (email.includes('sponsor') || displayName.includes('sponsor')) {
mode = 'sponsor';
} else if (email.includes('league-owner') || displayName.includes('owner')) {
mode = 'league-owner';

View File

@@ -13,22 +13,7 @@ import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { DriverViewModel as DriverViewModelClass } from '@/lib/view-models/DriverViewModel';
import { useServices } from '@/lib/services/ServiceProvider';
// Hook to detect sponsor mode
function useSponsorMode(): boolean {
const [isSponsor, setIsSponsor] = useState(false);
useEffect(() => {
const cookie = document.cookie
.split('; ')
.find(row => row.startsWith('gridpilot_demo_mode='));
if (cookie) {
const value = cookie.split('=')[1];
setIsSponsor(value === 'sponsor');
}
}, []);
return isSponsor;
}
// Hook to detect demo user mode
// Hook to detect demo user mode based on session
function useDemoUserMode(): { isDemo: boolean; demoRole: string | null } {
const { session } = useAuth();
const [demoMode, setDemoMode] = useState({ isDemo: false, demoRole: null as string | null });
@@ -42,21 +27,26 @@ 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;
// Check if this is a demo user
if (email.includes('demo') ||
displayName.includes('demo') ||
primaryDriverId.startsWith('demo-')) {
let role = 'driver';
if (email.includes('sponsor')) role = 'sponsor';
else if (email.includes('league-owner') || displayName.includes('owner')) role = 'league-owner';
else if (email.includes('league-steward') || displayName.includes('steward')) role = 'league-steward';
else if (email.includes('league-admin') || displayName.includes('admin')) role = 'league-admin';
else if (email.includes('system-owner') || displayName.includes('system owner')) role = 'system-owner';
else if (email.includes('super-admin') || displayName.includes('super admin')) role = 'super-admin';
// Use role from session if available, otherwise derive from email
let roleToUse = role;
if (!roleToUse) {
if (email.includes('sponsor')) roleToUse = 'sponsor';
else if (email.includes('league-owner') || displayName.includes('owner')) roleToUse = 'league-owner';
else if (email.includes('league-steward') || displayName.includes('steward')) roleToUse = 'league-steward';
else if (email.includes('league-admin') || displayName.includes('admin')) roleToUse = 'league-admin';
else if (email.includes('system-owner') || displayName.includes('system owner')) roleToUse = 'system-owner';
else if (email.includes('super-admin') || displayName.includes('super admin')) roleToUse = 'super-admin';
else roleToUse = 'driver';
}
setDemoMode({ isDemo: true, demoRole: role });
setDemoMode({ isDemo: true, demoRole: roleToUse });
} else {
setDemoMode({ isDemo: false, demoRole: null });
}
@@ -149,7 +139,6 @@ export default function UserPill() {
const { driverService, mediaService } = useServices();
const [driver, setDriver] = useState<DriverViewModel | null>(null);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const isSponsorMode = useSponsorMode();
const { isDemo, demoRole } = useDemoUserMode();
const shouldReduceMotion = useReducedMotion();
@@ -236,8 +225,6 @@ export default function UserPill() {
try {
// Call the logout API
await fetch('/api/auth/logout', { method: 'POST' });
// Clear any demo mode cookies
document.cookie = 'gridpilot_demo_mode=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
// Redirect to home
window.location.href = '/';
} catch (error) {
@@ -509,4 +496,4 @@ export default function UserPill() {
</AnimatePresence>
</div>
);
}
}

View File

@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { useServices } from '@/lib/services/ServiceProvider';
import { useAuth } from '@/lib/auth/AuthContext';
import {
Eye,
TrendingUp,
@@ -445,18 +446,28 @@ export default function SponsorInsightsCard({
// ============================================================================
export function useSponsorMode(): boolean {
const { session } = useAuth();
const [isSponsor, setIsSponsor] = React.useState(false);
React.useEffect(() => {
if (typeof document !== 'undefined') {
const cookies = document.cookie.split(';');
const demoModeCookie = cookies.find(c => c.trim().startsWith('gridpilot_demo_mode='));
if (demoModeCookie) {
const value = demoModeCookie.split('=')[1]?.trim();
setIsSponsor(value === 'sponsor');
}
if (!session?.user) {
setIsSponsor(false);
return;
}
}, []);
// Check session.user.role for sponsor
const role = (session.user as any).role;
if (role === 'sponsor') {
setIsSponsor(true);
return;
}
// Fallback: check email patterns
const email = session.user.email?.toLowerCase() || '';
const displayName = session.user.displayName?.toLowerCase() || '';
setIsSponsor(email.includes('sponsor') || displayName.includes('sponsor'));
}, [session]);
return isSponsor;
}