diff --git a/apps/website/app/auth/iracing/page.tsx b/apps/website/app/auth/iracing/page.tsx index bc366df5b..845077a37 100644 --- a/apps/website/app/auth/iracing/page.tsx +++ b/apps/website/app/auth/iracing/page.tsx @@ -1,29 +1,273 @@ -import Link from 'next/link'; +'use client'; -interface IracingAuthPageProps { - searchParams: Promise<{ - returnTo?: string; - }>; +import { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { useSearchParams } from 'next/navigation'; +import { motion, AnimatePresence, useReducedMotion } from 'framer-motion'; +import { + Gamepad2, + Flag, + ArrowRight, + Shield, + Link as LinkIcon, + User, + Trophy, + BarChart3, + CheckCircle2, + Loader2, +} from 'lucide-react'; + +import Card from '@/components/ui/Card'; +import Button from '@/components/ui/Button'; +import Heading from '@/components/ui/Heading'; + +interface ConnectionStep { + id: number; + icon: typeof Gamepad2; + title: string; + description: string; } -export default async function IracingAuthPage({ searchParams }: IracingAuthPageProps) { - const params = await searchParams; - const returnTo = params.returnTo ?? '/dashboard'; +const CONNECTION_STEPS: ConnectionStep[] = [ + { + id: 1, + icon: Gamepad2, + title: 'Connect iRacing', + description: 'Authorize GridPilot to access your profile', + }, + { + id: 2, + icon: User, + title: 'Import Profile', + description: 'We fetch your racing stats and history', + }, + { + id: 3, + icon: Trophy, + title: 'Sync Achievements', + description: 'Your licenses, iRating, and results', + }, + { + id: 4, + icon: BarChart3, + title: 'Ready to Race', + description: 'Access full GridPilot features', + }, +]; + +const BENEFITS = [ + 'Automatic profile creation with your iRacing data', + 'Real-time stats sync including iRating and Safety Rating', + 'Import your racing history and achievements', + 'No manual data entry required', + 'Verified driver identity in leagues', +]; + +export default function IracingAuthPage() { + const searchParams = useSearchParams(); + const returnTo = searchParams.get('returnTo') ?? '/dashboard'; const startUrl = `/auth/iracing/start?returnTo=${encodeURIComponent(returnTo)}`; + + const shouldReduceMotion = useReducedMotion(); + const [isMounted, setIsMounted] = useState(false); + const [activeStep, setActiveStep] = useState(0); + const [isHovering, setIsHovering] = useState(false); + + useEffect(() => { + setIsMounted(true); + }, []); + + useEffect(() => { + if (!isMounted || isHovering) return; + + const interval = setInterval(() => { + setActiveStep((prev) => (prev + 1) % CONNECTION_STEPS.length); + }, 2500); + + return () => clearInterval(interval); + }, [isMounted, isHovering]); return ( -
-
-

Authenticate with iRacing

-

- Connect a demo iRacing identity to explore the GridPilot dashboard with seeded data. +

+ {/* Background Pattern */} +
+
+
+
+ +
+ {/* Header */} +
+
+ + + + + + + + + +
+ Connect Your iRacing Account +

+ Link your iRacing profile for automatic stats sync and verified driver identity. +

+
+ + + {/* Background accent */} +
+
+ +
+ {/* Connection Flow Animation */} +
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + > +

Connection Flow

+ + {/* Steps */} +
+ {CONNECTION_STEPS.map((step, index) => { + const isActive = index === activeStep; + const isCompleted = index < activeStep; + const StepIcon = step.icon; + + return ( + setActiveStep(index)} + className="flex flex-col items-center text-center flex-1 cursor-pointer" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + + {isCompleted ? ( + + ) : ( + + )} + +

+ {step.title} +

+
+ ); + })} +
+ + {/* Active Step Description */} + + +

+ {CONNECTION_STEPS[activeStep].description} +

+
+
+
+ + {/* Benefits List */} +
+

What you'll get:

+
    + {BENEFITS.map((benefit, index) => ( + + + {benefit} + + ))} +
+
+ + {/* Connect Button */} + + + + + {/* Trust Indicators */} +
+
+
+ + Secure OAuth connection +
+
+ + Read-only access +
+
+
+ + {/* Alternative */} +

+ Don't have iRacing?{' '} + + Create account with email + +

+
+ + + {/* Footer */} +

+ GridPilot only requests read access to your iRacing profile. +
+ We never access your payment info or modify your account.

- - Start iRacing demo login -
); diff --git a/apps/website/app/auth/login/page.tsx b/apps/website/app/auth/login/page.tsx index cf94ebb02..7570f78b0 100644 --- a/apps/website/app/auth/login/page.tsx +++ b/apps/website/app/auth/login/page.tsx @@ -3,6 +3,7 @@ import { useState, FormEvent, type ChangeEvent } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; +import { motion, AnimatePresence } from 'framer-motion'; import { Mail, Lock, @@ -13,6 +14,11 @@ import { Flag, ArrowRight, Gamepad2, + Car, + Users, + Trophy, + Shield, + ChevronRight, } from 'lucide-react'; import Card from '@/components/ui/Card'; @@ -20,6 +26,7 @@ import Button from '@/components/ui/Button'; import Input from '@/components/ui/Input'; import Heading from '@/components/ui/Heading'; import { useAuth } from '@/lib/auth/AuthContext'; +import AuthWorkflowMockup from '@/components/auth/AuthWorkflowMockup'; interface FormErrors { email?: string; @@ -27,6 +34,27 @@ interface FormErrors { submit?: string; } +const USER_ROLES = [ + { + icon: Car, + title: 'Driver', + description: 'Race, track stats, join teams', + color: 'primary-blue', + }, + { + icon: Trophy, + title: 'League Admin', + description: 'Organize leagues and events', + color: 'performance-green', + }, + { + icon: Users, + title: 'Team Manager', + description: 'Manage team and drivers', + color: 'purple-400', + }, +]; + export default function LoginPage() { const router = useRouter(); const searchParams = useSearchParams(); @@ -97,8 +125,10 @@ export default function LoginPage() { const handleDemoLogin = async () => { setLoading(true); try { - // Redirect to iRacing auth start route - router.push(`/auth/iracing/start?returnTo=${encodeURIComponent(returnTo)}`); + // Demo: Set cookie to indicate driver mode (works without OAuth) + document.cookie = 'gridpilot_demo_mode=driver; path=/; max-age=86400'; + await new Promise(resolve => setTimeout(resolve, 500)); + router.push(returnTo); } catch (error) { setErrors({ submit: 'Demo login failed. Please try again.', @@ -108,7 +138,7 @@ export default function LoginPage() { }; return ( -
+
{/* Background Pattern */}
@@ -117,148 +147,238 @@ export default function LoginPage() { }} />
-
- {/* Logo/Header */} -
-
- + {/* Left Side - Info Panel (Hidden on mobile) */} +
+
+ {/* Logo */} +
+
+ +
+ GridPilot
- Welcome Back -

- Sign in to continue to GridPilot + + + Your Sim Racing Infrastructure + + +

+ Manage leagues, track performance, join teams, and compete with drivers worldwide. One account, multiple roles.

+ + {/* Role Cards */} +
+ {USER_ROLES.map((role, index) => ( + +
+ +
+
+

{role.title}

+

{role.description}

+
+
+ ))} +
+ + {/* Workflow Mockup */} + + + {/* Trust Indicators */} +
+
+ + Secure login +
+
+ + iRacing verified +
+
+
- - {/* Background accent */} -
- -
- {/* Email */} -
- -
- - ) => setFormData({ ...formData, email: e.target.value })} - error={!!errors.email} - errorMessage={errors.email} - placeholder="you@example.com" - disabled={loading} - className="pl-10" - autoComplete="email" - /> -
-
- - {/* Password */} -
-
- - - Forgot password? - -
-
- - ) => setFormData({ ...formData, password: e.target.value })} - error={!!errors.password} - errorMessage={errors.password} - placeholder="••••••••" - disabled={loading} - className="pl-10 pr-10" - autoComplete="current-password" - /> - -
-
- - {/* Error Message */} - {errors.submit && ( -
- -

{errors.submit}

-
- )} - - {/* Submit Button */} - -
- - {/* Divider */} -
-
-
-
-
- or continue with + {/* Right Side - Login Form */} +
+
+ {/* Mobile Logo/Header */} +
+
+
+ Welcome Back +

+ Sign in to continue to GridPilot +

- {/* Demo Login */} - + {/* Desktop Header */} +
+ Welcome Back +

+ Sign in to access your racing dashboard +

+
- {/* Sign Up Link */} -

- Don't have an account?{' '} - + {/* Background accent */} +

+ +
+ {/* Email */} +
+ +
+ + ) => setFormData({ ...formData, email: e.target.value })} + error={!!errors.email} + errorMessage={errors.email} + placeholder="you@example.com" + disabled={loading} + className="pl-10" + autoComplete="email" + /> +
+
+ + {/* Password */} +
+
+ + + Forgot password? + +
+
+ + ) => setFormData({ ...formData, password: e.target.value })} + error={!!errors.password} + errorMessage={errors.password} + placeholder="••••••••" + disabled={loading} + className="pl-10 pr-10" + autoComplete="current-password" + /> + +
+
+ + {/* Error Message */} + {errors.submit && ( + + +

{errors.submit}

+
+ )} + + {/* Submit Button */} + +
+ + {/* Divider */} +
+
+
+
+
+ or continue with +
+
+ + {/* Demo Login */} + - Create one - -

- + + Demo Login (iRacing) + +
- {/* Footer */} -

- By signing in, you agree to our{' '} - Terms of Service - {' '}and{' '} - Privacy Policy -

+ {/* Sign Up Link */} +

+ Don't have an account?{' '} + + Create one + +

+ + + {/* Footer */} +

+ By signing in, you agree to our{' '} + Terms of Service + {' '}and{' '} + Privacy Policy +

+ + {/* Mobile Role Info */} +
+

One account for all roles

+
+ {USER_ROLES.map((role) => ( +
+
+ +
+ {role.title} +
+ ))} +
+
+
); diff --git a/apps/website/app/auth/signup/page.tsx b/apps/website/app/auth/signup/page.tsx index ebccef008..a912d1883 100644 --- a/apps/website/app/auth/signup/page.tsx +++ b/apps/website/app/auth/signup/page.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, FormEvent, type ChangeEvent } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; +import { motion, AnimatePresence } from 'framer-motion'; import { Mail, Lock, @@ -16,6 +17,13 @@ import { X, Gamepad2, Loader2, + Car, + Users, + Trophy, + Shield, + ChevronRight, + ArrowRight, + Sparkles, } from 'lucide-react'; import Card from '@/components/ui/Card'; @@ -23,6 +31,7 @@ import Button from '@/components/ui/Button'; import Input from '@/components/ui/Input'; import Heading from '@/components/ui/Heading'; import { useAuth } from '@/lib/auth/AuthContext'; +import AuthWorkflowMockup from '@/components/auth/AuthWorkflowMockup'; interface FormErrors { displayName?: string; @@ -52,6 +61,36 @@ function checkPasswordStrength(password: string): PasswordStrength { return { score, label: 'Strong', color: 'bg-performance-green' }; } +const USER_ROLES = [ + { + icon: Car, + title: 'Driver', + description: 'Race, track stats, join teams', + color: 'primary-blue', + }, + { + icon: Trophy, + title: 'League Admin', + description: 'Organize leagues and events', + color: 'performance-green', + }, + { + icon: Users, + title: 'Team Manager', + description: 'Manage team and drivers', + color: 'purple-400', + }, +]; + +const FEATURES = [ + 'Track your racing statistics and progress', + 'Join or create competitive leagues', + 'Build or join racing teams', + 'Connect your iRacing account', + 'Compete in organized events', + 'Access detailed performance analytics', +]; + export default function SignupPage() { const router = useRouter(); const searchParams = useSearchParams(); @@ -169,8 +208,10 @@ export default function SignupPage() { const handleDemoLogin = async () => { setLoading(true); try { - // Redirect to iRacing auth start route - router.push(`/auth/iracing/start?returnTo=${encodeURIComponent(returnTo)}`); + // Demo: Set cookie to indicate driver mode (works without OAuth) + document.cookie = 'gridpilot_demo_mode=driver; path=/; max-age=86400'; + await new Promise(resolve => setTimeout(resolve, 500)); + router.push(returnTo === '/onboarding' ? '/dashboard' : returnTo); } catch { setErrors({ submit: 'Demo login failed. Please try again.', @@ -189,7 +230,7 @@ export default function SignupPage() { } return ( -
+
{/* Background Pattern */}
@@ -198,236 +239,350 @@ export default function SignupPage() { }} />
-
- {/* Logo/Header */} -
-
- + {/* Left Side - Info Panel (Hidden on mobile) */} +
+
+ {/* Logo */} +
+
+ +
+ GridPilot
- Join GridPilot -

- Create your account and start racing + + + Start Your Racing Journey + + +

+ Join thousands of sim racers. One account gives you access to all roles - race as a driver, organize leagues, or manage teams.

-
- - {/* Background accent */} -
- -
- {/* Display Name */} -
- -
- - ) => setFormData({ ...formData, displayName: e.target.value })} - error={!!errors.displayName} - errorMessage={errors.displayName} - placeholder="SpeedyRacer42" - disabled={loading} - className="pl-10" - autoComplete="username" - /> -
-

This is how other drivers will see you

-
- - {/* Email */} -
- -
- - ) => setFormData({ ...formData, email: e.target.value })} - error={!!errors.email} - errorMessage={errors.email} - placeholder="you@example.com" - disabled={loading} - className="pl-10" - autoComplete="email" - /> -
-
- - {/* Password */} -
- -
- - ) => setFormData({ ...formData, password: e.target.value })} - error={!!errors.password} - errorMessage={errors.password} - placeholder="••••••••" - disabled={loading} - className="pl-10 pr-10" - autoComplete="new-password" - /> - -
- - {/* Password Strength */} - {formData.password && ( -
-
-
-
-
- - {passwordStrength.label} - -
-
- {passwordRequirements.map((req, index) => ( -
- {req.met ? ( - - ) : ( - - )} - - {req.label} - -
- ))} -
+ {/* Role Cards */} +
+ {USER_ROLES.map((role, index) => ( + +
+
- )} -
- - {/* Confirm Password */} -
- -
- - ) => setFormData({ ...formData, confirmPassword: e.target.value })} - error={!!errors.confirmPassword} - errorMessage={errors.confirmPassword} - placeholder="••••••••" - disabled={loading} - className="pl-10 pr-10" - autoComplete="new-password" - /> - -
- {formData.confirmPassword && formData.password === formData.confirmPassword && ( -

- Passwords match -

- )} -
- - {/* Error Message */} - {errors.submit && ( -
- -

{errors.submit}

-
- )} - - {/* Submit Button */} - - - - {/* Divider */} -
-
-
-
-
- or continue with -
+
+

{role.title}

+

{role.description}

+
+ + ))}
- {/* Demo Login */} - + {/* Features List */} +
+
+ + What you'll get +
+
    + {FEATURES.map((feature, index) => ( + + + {feature} + + ))} +
+
- {/* Login Link */} -

- Already have an account?{' '} - +

+ + Secure signup +
+
+ + iRacing integration +
+
+
+
+ + {/* Right Side - Signup Form */} +
+
+ {/* Mobile Logo/Header */} +
+
+ +
+ Join GridPilot +

+ Create your account and start racing +

+
+ + {/* Desktop Header */} +
+ Create Account +

+ Get started with your free account +

+
+ + + {/* Background accent */} +
+ +
+ {/* Display Name */} +
+ +
+ + ) => setFormData({ ...formData, displayName: e.target.value })} + error={!!errors.displayName} + errorMessage={errors.displayName} + placeholder="SpeedyRacer42" + disabled={loading} + className="pl-10" + autoComplete="username" + /> +
+

This is how other drivers will see you

+
+ + {/* Email */} +
+ +
+ + ) => setFormData({ ...formData, email: e.target.value })} + error={!!errors.email} + errorMessage={errors.email} + placeholder="you@example.com" + disabled={loading} + className="pl-10" + autoComplete="email" + /> +
+
+ + {/* Password */} +
+ +
+ + ) => setFormData({ ...formData, password: e.target.value })} + error={!!errors.password} + errorMessage={errors.password} + placeholder="••••••••" + disabled={loading} + className="pl-10 pr-10" + autoComplete="new-password" + /> + +
+ + {/* Password Strength */} + {formData.password && ( +
+
+
+ +
+ + {passwordStrength.label} + +
+
+ {passwordRequirements.map((req, index) => ( +
+ {req.met ? ( + + ) : ( + + )} + + {req.label} + +
+ ))} +
+
+ )} +
+ + {/* Confirm Password */} +
+ +
+ + ) => setFormData({ ...formData, confirmPassword: e.target.value })} + error={!!errors.confirmPassword} + errorMessage={errors.confirmPassword} + placeholder="••••••••" + disabled={loading} + className="pl-10 pr-10" + autoComplete="new-password" + /> + +
+ {formData.confirmPassword && formData.password === formData.confirmPassword && ( +

+ Passwords match +

+ )} +
+ + {/* Error Message */} + + {errors.submit && ( + + +

{errors.submit}

+
+ )} +
+ + {/* Submit Button */} + +
+ + {/* Divider */} +
+
+
+
+
+ or sign up with +
+
+ + {/* iRacing Signup */} + - Sign in - -

- + + Demo Login (iRacing) + +
- {/* Footer */} -

- By creating an account, you agree to our{' '} - Terms of Service - {' '}and{' '} - Privacy Policy -

+ {/* Login Link */} +

+ Already have an account?{' '} + + Sign in + +

+ + + {/* Footer */} +

+ By creating an account, you agree to our{' '} + Terms of Service + {' '}and{' '} + Privacy Policy +

+ + {/* Mobile Role Info */} +
+

One account for all roles

+
+ {USER_ROLES.map((role) => ( +
+
+ +
+ {role.title} +
+ ))} +
+
+
); diff --git a/apps/website/app/sponsor/billing/page.tsx b/apps/website/app/sponsor/billing/page.tsx index 21884db33..7b0bf2d7f 100644 --- a/apps/website/app/sponsor/billing/page.tsx +++ b/apps/website/app/sponsor/billing/page.tsx @@ -1,40 +1,78 @@ 'use client'; import { useState } from 'react'; -import { useRouter } from 'next/navigation'; +import { motion, useReducedMotion } from 'framer-motion'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; -import { - CreditCard, - DollarSign, +import StatCard from '@/components/ui/StatCard'; +import SectionHeader from '@/components/ui/SectionHeader'; +import StatusBadge from '@/components/ui/StatusBadge'; +import InfoBanner from '@/components/ui/InfoBanner'; +import PageHeader from '@/components/ui/PageHeader'; +import { siteConfig } from '@/lib/siteConfig'; +import { + CreditCard, + DollarSign, Calendar, Download, Plus, Check, AlertTriangle, FileText, - ArrowRight + ArrowRight, + TrendingUp, + Receipt, + Building2, + Wallet, + Clock, + ChevronRight, + Info, + ExternalLink, + Percent } from 'lucide-react'; +// ============================================================================ +// Types +// ============================================================================ + interface PaymentMethod { id: string; - type: 'card' | 'bank'; + type: 'card' | 'bank' | 'sepa'; last4: string; brand?: string; isDefault: boolean; expiryMonth?: number; expiryYear?: number; + bankName?: string; } interface Invoice { id: string; + invoiceNumber: string; date: Date; + dueDate: Date; amount: number; - status: 'paid' | 'pending' | 'failed'; + vatAmount: number; + totalAmount: number; + status: 'paid' | 'pending' | 'overdue' | 'failed'; description: string; + sponsorshipType: 'league' | 'team' | 'driver' | 'race' | 'platform'; + pdfUrl: string; } -// Mock data +interface BillingStats { + totalSpent: number; + pendingAmount: number; + nextPaymentDate: Date; + nextPaymentAmount: number; + activeSponsorships: number; + averageMonthlySpend: number; +} + +// ============================================================================ +// Mock Data +// ============================================================================ + const MOCK_PAYMENT_METHODS: PaymentMethod[] = [ { id: 'pm-1', @@ -54,49 +92,142 @@ const MOCK_PAYMENT_METHODS: PaymentMethod[] = [ expiryMonth: 6, expiryYear: 2026, }, + { + id: 'pm-3', + type: 'sepa', + last4: '8901', + bankName: 'Deutsche Bank', + isDefault: false, + }, ]; const MOCK_INVOICES: Invoice[] = [ { id: 'inv-1', + invoiceNumber: 'GP-2025-001234', date: new Date('2025-11-01'), - amount: 1200, + dueDate: new Date('2025-11-15'), + amount: 1090.91, + vatAmount: 207.27, + totalAmount: 1298.18, status: 'paid', - description: 'GT3 Pro Championship - Main Sponsor (Q4 2025)', + description: 'GT3 Pro Championship - Primary Sponsor (Q4 2025)', + sponsorshipType: 'league', + pdfUrl: '#', }, { id: 'inv-2', + invoiceNumber: 'GP-2025-001235', date: new Date('2025-10-01'), - amount: 400, + dueDate: new Date('2025-10-15'), + amount: 363.64, + vatAmount: 69.09, + totalAmount: 432.73, status: 'paid', - description: 'Formula Sim Series - Secondary Sponsor (Q4 2025)', + description: 'Team Velocity - Gear Sponsor (Q4 2025)', + sponsorshipType: 'team', + pdfUrl: '#', }, { id: 'inv-3', + invoiceNumber: 'GP-2025-001236', date: new Date('2025-12-01'), - amount: 350, + dueDate: new Date('2025-12-15'), + amount: 318.18, + vatAmount: 60.45, + totalAmount: 378.63, status: 'pending', + description: 'Alex Thompson - Driver Sponsorship (Dec 2025)', + sponsorshipType: 'driver', + pdfUrl: '#', + }, + { + id: 'inv-4', + invoiceNumber: 'GP-2025-001237', + date: new Date('2025-11-15'), + dueDate: new Date('2025-11-29'), + amount: 454.55, + vatAmount: 86.36, + totalAmount: 540.91, + status: 'overdue', description: 'Touring Car Cup - Secondary Sponsor (Q1 2026)', + sponsorshipType: 'league', + pdfUrl: '#', }, ]; -function PaymentMethodCard({ method, onSetDefault }: { method: PaymentMethod; onSetDefault: () => void }) { +const MOCK_STATS: BillingStats = { + totalSpent: 12450, + pendingAmount: 919.54, + nextPaymentDate: new Date('2025-12-15'), + nextPaymentAmount: 378.63, + activeSponsorships: 6, + averageMonthlySpend: 2075, +}; + +// ============================================================================ +// Components +// ============================================================================ + +function PaymentMethodCard({ + method, + onSetDefault, + onRemove +}: { + method: PaymentMethod; + onSetDefault: () => void; + onRemove: () => void; +}) { + const shouldReduceMotion = useReducedMotion(); + + const getIcon = () => { + if (method.type === 'sepa') return Building2; + return CreditCard; + }; + + const Icon = getIcon(); + + const getLabel = () => { + if (method.type === 'sepa' && method.bankName) { + return `${method.bankName} •••• ${method.last4}`; + } + return `${method.brand} •••• ${method.last4}`; + }; + return ( -
+
-
-
- +
+
+
- {method.brand} •••• {method.last4} + {getLabel()} {method.isDefault && ( - Default + + Default + )}
{method.expiryMonth && method.expiryYear && ( - Expires {method.expiryMonth}/{method.expiryYear} + + Expires {String(method.expiryMonth).padStart(2, '0')}/{method.expiryYear} + + )} + {method.type === 'sepa' && ( + SEPA Direct Debit )}
@@ -106,58 +237,120 @@ function PaymentMethodCard({ method, onSetDefault }: { method: PaymentMethod; on Set Default )} -
-
+
); } -function InvoiceRow({ invoice }: { invoice: Invoice }) { +function InvoiceRow({ invoice, index }: { invoice: Invoice; index: number }) { + const shouldReduceMotion = useReducedMotion(); + const statusConfig = { - paid: { icon: Check, color: 'text-performance-green', bg: 'bg-performance-green/10' }, - pending: { icon: AlertTriangle, color: 'text-warning-amber', bg: 'bg-warning-amber/10' }, - failed: { icon: AlertTriangle, color: 'text-racing-red', bg: 'bg-racing-red/10' }, + paid: { + icon: Check, + label: 'Paid', + color: 'text-performance-green', + bg: 'bg-performance-green/10', + border: 'border-performance-green/30' + }, + pending: { + icon: Clock, + label: 'Pending', + color: 'text-warning-amber', + bg: 'bg-warning-amber/10', + border: 'border-warning-amber/30' + }, + overdue: { + icon: AlertTriangle, + label: 'Overdue', + color: 'text-racing-red', + bg: 'bg-racing-red/10', + border: 'border-racing-red/30' + }, + failed: { + icon: AlertTriangle, + label: 'Failed', + color: 'text-racing-red', + bg: 'bg-racing-red/10', + border: 'border-racing-red/30' + }, + }; + + const typeLabels = { + league: 'League', + team: 'Team', + driver: 'Driver', + race: 'Race', + platform: 'Platform', }; const status = statusConfig[invoice.status]; const StatusIcon = status.icon; return ( -
-
+ +
- +
-
-
{invoice.description}
-
- {invoice.date.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })} +
+
+ {invoice.description} + + {typeLabels[invoice.sponsorshipType]} + +
+
+ {invoice.invoiceNumber} + + + {invoice.date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} +
-
+ +
-
${invoice.amount.toLocaleString()}
-
- - {invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)} +
+ €{invoice.totalAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })} +
+
+ incl. €{invoice.vatAmount.toLocaleString('de-DE', { minimumFractionDigits: 2 })} VAT
-
-
+ ); } +// ============================================================================ +// Main Component +// ============================================================================ + export default function SponsorBillingPage() { - const router = useRouter(); + const shouldReduceMotion = useReducedMotion(); const [paymentMethods, setPaymentMethods] = useState(MOCK_PAYMENT_METHODS); + const [showAllInvoices, setShowAllInvoices] = useState(false); const handleSetDefault = (methodId: string) => { setPaymentMethods(methods => @@ -165,103 +358,229 @@ export default function SponsorBillingPage() { ); }; - const totalSpent = MOCK_INVOICES.filter(i => i.status === 'paid').reduce((sum, i) => sum + i.amount, 0); - const pendingAmount = MOCK_INVOICES.filter(i => i.status === 'pending').reduce((sum, i) => sum + i.amount, 0); + const handleRemoveMethod = (methodId: string) => { + if (confirm('Remove this payment method?')) { + setPaymentMethods(methods => methods.filter(m => m.id !== methodId)); + } + }; + + const displayedInvoices = showAllInvoices ? MOCK_INVOICES : MOCK_INVOICES.slice(0, 4); + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: shouldReduceMotion ? 0 : 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { opacity: 1, y: 0 }, + }; return ( -
+ {/* Header */} -
-

- - Billing & Payments -

-

Manage payment methods and view invoices

-
+ + + - {/* Summary Cards */} -
- -
- - Total Spent -
-
${totalSpent.toLocaleString()}
-
- -
- - Pending -
-
${pendingAmount.toLocaleString()}
-
- -
- - Next Payment -
-
Dec 15, 2025
-
-
+ {/* Stats Grid */} + + + i.status === 'pending' || i.status === 'overdue').length} invoices`} + color="text-warning-amber" + bgColor="bg-warning-amber/10" + /> + + + {/* Payment Methods */} - -
-

Payment Methods

- -
-
- {paymentMethods.map((method) => ( - handleSetDefault(method.id)} - /> - ))} -
-
+ + + + + Add Payment Method + + } + /> +
+ {paymentMethods.map((method) => ( + handleSetDefault(method.id)} + onRemove={() => handleRemoveMethod(method.id)} + /> + ))} +
+
+ +

We support Visa, Mastercard, American Express, and SEPA Direct Debit.

+

All payment information is securely processed and stored by our payment provider.

+
+
+
+
{/* Billing History */} - -
-

Billing History

- -
-
- {MOCK_INVOICES.map((invoice) => ( - - ))} -
-
- -
-
+ + + + + Export All + + } + /> +
+ {displayedInvoices.map((invoice, index) => ( + + ))} +
+ {MOCK_INVOICES.length > 4 && ( +
+ +
+ )} +
+
- {/* Platform Fee Notice */} -
-

Platform Fee

-

- A 10% platform fee is applied to all sponsorship payments. This fee helps maintain the platform, - provide analytics, and ensure quality sponsorship placements. -

-
+ {/* Platform Fee & VAT Information */} + + {/* Platform Fee */} + +
+

+
+ +
+ Platform Fee +

+
+
+
+ {siteConfig.fees.platformFeePercent}% +
+

+ {siteConfig.fees.description} +

+
+

• Applied to all sponsorship payments

+

• Covers platform maintenance and analytics

+

• Ensures quality sponsorship placements

+
+
+
- {/* Alpha Notice */} -
-

- Alpha Note: Payment processing is demonstration-only. - Real billing will be available when the payment system is fully implemented. -

-
-
+ {/* VAT Information */} + +
+

+
+ +
+ VAT Information +

+
+
+

+ {siteConfig.vat.notice} +

+
+
+ Standard VAT Rate + {siteConfig.vat.standardRate}% +
+
+ B2B Reverse Charge + Available +
+
+

+ Enter your VAT ID in Settings to enable reverse charge for B2B transactions. +

+
+
+ + + {/* Billing Support */} + + +
+
+
+ +
+
+

Need help with billing?

+

+ Contact our billing support for questions about invoices, payments, or refunds. +

+
+
+ +
+
+
+ ); } \ No newline at end of file diff --git a/apps/website/app/sponsor/campaigns/page.tsx b/apps/website/app/sponsor/campaigns/page.tsx index 976902dac..4fd5a1fc1 100644 --- a/apps/website/app/sponsor/campaigns/page.tsx +++ b/apps/website/app/sponsor/campaigns/page.tsx @@ -1,10 +1,13 @@ 'use client'; import { useState, useEffect } from 'react'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { motion, useReducedMotion, AnimatePresence } from 'framer-motion'; import Link from 'next/link'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; +import StatusBadge from '@/components/ui/StatusBadge'; +import InfoBanner from '@/components/ui/InfoBanner'; import { Megaphone, Trophy, @@ -16,320 +19,848 @@ import { ChevronRight, Check, Clock, - XCircle + XCircle, + Car, + Flag, + Search, + Filter, + TrendingUp, + BarChart3, + ArrowUpRight, + ArrowDownRight, + AlertCircle, + Send, + ThumbsUp, + ThumbsDown, + RefreshCw, + Handshake } from 'lucide-react'; +// ============================================================================ +// Types +// ============================================================================ + +type SponsorshipType = 'all' | 'leagues' | 'teams' | 'drivers' | 'races' | 'platform'; +type SponsorshipStatus = 'all' | 'active' | 'pending_approval' | 'approved' | 'rejected' | 'expired'; + interface Sponsorship { id: string; - leagueId: string; - leagueName: string; - tier: 'main' | 'secondary'; - status: 'active' | 'pending' | 'expired'; + type: SponsorshipType; + entityId: string; + entityName: string; + tier?: 'main' | 'secondary'; + status: 'active' | 'pending_approval' | 'approved' | 'rejected' | 'expired'; + applicationDate?: Date; + approvalDate?: Date; + rejectionReason?: string; startDate: Date; endDate: Date; price: number; impressions: number; - drivers: number; + impressionsChange?: number; + engagement?: number; + details?: string; + // For pending approvals + entityOwner?: string; + applicationMessage?: string; } -interface SponsorshipDetailApi { - id: string; - leagueId: string; - leagueName: string; - seasonId: string; - seasonName: string; - seasonStartDate?: string; - seasonEndDate?: string; - tier: 'main' | 'secondary'; - status: string; - pricing: { - amount: number; - currency: string; - }; - metrics: { - drivers: number; - races: number; - completedRaces: number; - impressions: number; - }; - createdAt: string; - activatedAt?: string; -} +// ============================================================================ +// Mock Data - Updated to show application workflow +// ============================================================================ -interface SponsorSponsorshipsResponse { - sponsorId: string; - sponsorName: string; - sponsorships: SponsorshipDetailApi[]; - summary: { - totalSponsorships: number; - activeSponsorships: number; - totalInvestment: number; - totalPlatformFees: number; - currency: string; - }; -} +const MOCK_SPONSORSHIPS: Sponsorship[] = [ + // Active sponsorships (approved and started) + { + id: 's1', + type: 'leagues', + entityId: 'l1', + entityName: 'GT3 Masters Championship', + tier: 'main', + status: 'active', + applicationDate: new Date('2025-09-15'), + approvalDate: new Date('2025-09-18'), + startDate: new Date('2025-10-01'), + endDate: new Date('2026-02-28'), + price: 1200, + impressions: 45200, + impressionsChange: 12.5, + engagement: 4.2, + details: '48 drivers • 12 races', + entityOwner: 'Championship Admin', + }, + { + id: 's2', + type: 'leagues', + entityId: 'l2', + entityName: 'Endurance Pro Series', + tier: 'secondary', + status: 'active', + applicationDate: new Date('2025-10-20'), + approvalDate: new Date('2025-10-25'), + startDate: new Date('2025-11-01'), + endDate: new Date('2026-03-31'), + price: 500, + impressions: 38400, + impressionsChange: 8.3, + engagement: 3.8, + details: '72 drivers • 6 races', + entityOwner: 'Endurance Racing LLC', + }, + { + id: 's3', + type: 'teams', + entityId: 't1', + entityName: 'Velocity Racing', + status: 'active', + applicationDate: new Date('2025-08-25'), + approvalDate: new Date('2025-08-26'), + startDate: new Date('2025-09-01'), + endDate: new Date('2026-08-31'), + price: 400, + impressions: 12300, + impressionsChange: 5.2, + engagement: 5.1, + details: '4 drivers • GT3 & LMP', + entityOwner: 'Team Principal', + }, + // Pending approval (waiting for entity owner) + { + id: 's9', + type: 'leagues', + entityId: 'l3', + entityName: 'Formula Sim Series', + tier: 'main', + status: 'pending_approval', + applicationDate: new Date('2025-12-14'), + startDate: new Date('2026-01-01'), + endDate: new Date('2026-06-30'), + price: 1500, + impressions: 0, + details: '36 drivers • F3 class', + entityOwner: 'Formula Sim Organization', + applicationMessage: 'We would love to be your main sponsor for the upcoming season.', + }, + { + id: 's10', + type: 'teams', + entityId: 't3', + entityName: 'Phoenix Racing Team', + status: 'pending_approval', + applicationDate: new Date('2025-12-12'), + startDate: new Date('2026-01-01'), + endDate: new Date('2026-12-31'), + price: 600, + impressions: 0, + details: '5 drivers • Multi-class', + entityOwner: 'Phoenix Team Manager', + applicationMessage: 'Interested in sponsoring your team for the full 2026 season.', + }, + { + id: 's11', + type: 'drivers', + entityId: 'd3', + entityName: 'James Rodriguez', + status: 'pending_approval', + applicationDate: new Date('2025-12-10'), + startDate: new Date('2026-01-01'), + endDate: new Date('2026-12-31'), + price: 250, + impressions: 0, + details: 'Rising rookie • GT3 Masters', + entityOwner: 'James Rodriguez', + applicationMessage: 'Would like to support your racing career.', + }, + // Recently approved (not yet started) + { + id: 's12', + type: 'races', + entityId: 'r1', + entityName: 'Spa 24 Hours', + status: 'approved', + applicationDate: new Date('2025-12-01'), + approvalDate: new Date('2025-12-05'), + startDate: new Date('2025-12-20'), + endDate: new Date('2025-12-21'), + price: 300, + impressions: 0, + details: 'Endurance Pro Series • Dec 20-21', + entityOwner: 'Race Director', + }, + { + id: 's13', + type: 'drivers', + entityId: 'd4', + entityName: 'Emma Wilson', + status: 'approved', + applicationDate: new Date('2025-12-08'), + approvalDate: new Date('2025-12-10'), + startDate: new Date('2026-01-01'), + endDate: new Date('2026-12-31'), + price: 180, + impressions: 0, + details: 'Touring Car specialist', + entityOwner: 'Emma Wilson', + }, + // Rejected applications + { + id: 's14', + type: 'leagues', + entityId: 'l4', + entityName: 'Elite GT Championship', + tier: 'main', + status: 'rejected', + applicationDate: new Date('2025-11-20'), + startDate: new Date('2026-01-01'), + endDate: new Date('2026-06-30'), + price: 2000, + impressions: 0, + details: '24 drivers • Invite-only', + entityOwner: 'Elite Racing Committee', + rejectionReason: 'Main sponsor position already filled for the upcoming season.', + }, + { + id: 's15', + type: 'teams', + entityId: 't4', + entityName: 'Apex Motorsport', + status: 'rejected', + applicationDate: new Date('2025-11-15'), + startDate: new Date('2026-01-01'), + endDate: new Date('2026-12-31'), + price: 450, + impressions: 0, + details: '3 drivers • LMP2', + entityOwner: 'Apex Team Owner', + rejectionReason: 'Already have exclusive sponsor agreement in this category.', + }, + // Existing active ones + { + id: 's4', + type: 'teams', + entityId: 't2', + entityName: 'Storm Motorsport', + status: 'active', + applicationDate: new Date('2025-10-01'), + approvalDate: new Date('2025-10-05'), + startDate: new Date('2025-10-15'), + endDate: new Date('2026-10-14'), + price: 350, + impressions: 8900, + impressionsChange: -2.1, + engagement: 4.5, + details: '3 drivers • Formula', + entityOwner: 'Storm Racing LLC', + }, + { + id: 's5', + type: 'drivers', + entityId: 'd1', + entityName: 'Max Velocity', + status: 'active', + applicationDate: new Date('2025-10-20'), + approvalDate: new Date('2025-10-21'), + startDate: new Date('2025-11-01'), + endDate: new Date('2026-10-31'), + price: 200, + impressions: 8200, + impressionsChange: 15.8, + engagement: 6.2, + details: 'Velocity Racing • P1 in GT3 Masters', + entityOwner: 'Max Velocity', + }, + { + id: 's6', + type: 'drivers', + entityId: 'd2', + entityName: 'Sarah Storm', + status: 'active', + applicationDate: new Date('2025-09-25'), + approvalDate: new Date('2025-09-26'), + startDate: new Date('2025-10-01'), + endDate: new Date('2026-09-30'), + price: 150, + impressions: 6100, + impressionsChange: 22.4, + engagement: 5.8, + details: 'Storm Motorsport • Rising star', + entityOwner: 'Sarah Storm', + }, + { + id: 's8', + type: 'platform', + entityId: 'p1', + entityName: 'Homepage Banner', + status: 'active', + applicationDate: new Date('2025-11-25'), + approvalDate: new Date('2025-11-25'), + startDate: new Date('2025-12-01'), + endDate: new Date('2025-12-31'), + price: 500, + impressions: 52000, + impressionsChange: 3.4, + engagement: 2.1, + details: 'Header position • All pages', + entityOwner: 'GridPilot', + }, +]; -function mapSponsorshipStatus(status: string): 'active' | 'pending' | 'expired' { - switch (status) { - case 'active': - return 'active'; - case 'pending': - return 'pending'; - default: - return 'expired'; - } -} +// ============================================================================ +// Configuration +// ============================================================================ -function mapApiToSponsorships(response: SponsorSponsorshipsResponse): Sponsorship[] { - return response.sponsorships.map((s) => { - const start = s.seasonStartDate ? new Date(s.seasonStartDate) : new Date(s.createdAt); - const end = s.seasonEndDate ? new Date(s.seasonEndDate) : start; +const TYPE_CONFIG = { + leagues: { icon: Trophy, color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', label: 'League' }, + teams: { icon: Users, color: 'text-purple-400', bgColor: 'bg-purple-400/10', label: 'Team' }, + drivers: { icon: Car, color: 'text-performance-green', bgColor: 'bg-performance-green/10', label: 'Driver' }, + races: { icon: Flag, color: 'text-warning-amber', bgColor: 'bg-warning-amber/10', label: 'Race' }, + platform: { icon: Megaphone, color: 'text-racing-red', bgColor: 'bg-racing-red/10', label: 'Platform' }, + all: { icon: BarChart3, color: 'text-gray-400', bgColor: 'bg-gray-400/10', label: 'All' }, +}; - return { - id: s.id, - leagueId: s.leagueId, - leagueName: s.leagueName, - tier: s.tier, - status: mapSponsorshipStatus(s.status), - startDate: start, - endDate: end, - price: s.pricing.amount, - impressions: s.metrics.impressions, - drivers: s.metrics.drivers, - }; - }); -} +const STATUS_CONFIG = { + active: { + icon: Check, + color: 'text-performance-green', + bgColor: 'bg-performance-green/10', + borderColor: 'border-performance-green/30', + label: 'Active' + }, + pending_approval: { + icon: Clock, + color: 'text-warning-amber', + bgColor: 'bg-warning-amber/10', + borderColor: 'border-warning-amber/30', + label: 'Awaiting Approval' + }, + approved: { + icon: ThumbsUp, + color: 'text-primary-blue', + bgColor: 'bg-primary-blue/10', + borderColor: 'border-primary-blue/30', + label: 'Approved' + }, + rejected: { + icon: ThumbsDown, + color: 'text-racing-red', + bgColor: 'bg-racing-red/10', + borderColor: 'border-racing-red/30', + label: 'Declined' + }, + expired: { + icon: XCircle, + color: 'text-gray-400', + bgColor: 'bg-gray-400/10', + borderColor: 'border-gray-400/30', + label: 'Expired' + }, +}; + +// ============================================================================ +// Components +// ============================================================================ function SponsorshipCard({ sponsorship }: { sponsorship: Sponsorship }) { const router = useRouter(); + const shouldReduceMotion = useReducedMotion(); - const statusConfig = { - active: { icon: Check, color: 'text-performance-green', bg: 'bg-performance-green/10', label: 'Active' }, - pending: { icon: Clock, color: 'text-warning-amber', bg: 'bg-warning-amber/10', label: 'Pending' }, - expired: { icon: XCircle, color: 'text-gray-400', bg: 'bg-gray-400/10', label: 'Expired' }, - }; + const typeConfig = TYPE_CONFIG[sponsorship.type as keyof typeof TYPE_CONFIG]; + const statusConfig = STATUS_CONFIG[sponsorship.status]; + const TypeIcon = typeConfig.icon; + const StatusIcon = statusConfig.icon; - const tierConfig = { - main: { color: 'text-primary-blue', bg: 'bg-primary-blue/10', border: 'border-primary-blue/30', label: 'Main Sponsor' }, - secondary: { color: 'text-purple-400', bg: 'bg-purple-400/10', border: 'border-purple-400/30', label: 'Secondary' }, - }; + const daysRemaining = Math.ceil((sponsorship.endDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)); + const isExpiringSoon = daysRemaining > 0 && daysRemaining <= 30; + const isPending = sponsorship.status === 'pending_approval'; + const isRejected = sponsorship.status === 'rejected'; + const isApproved = sponsorship.status === 'approved'; - const status = statusConfig[sponsorship.status]; - const tier = tierConfig[sponsorship.tier]; - const StatusIcon = status.icon; + const getEntityLink = () => { + switch (sponsorship.type) { + case 'leagues': return `/leagues/${sponsorship.entityId}`; + case 'teams': return `/teams/${sponsorship.entityId}`; + case 'drivers': return `/drivers/${sponsorship.entityId}`; + case 'races': return `/races/${sponsorship.entityId}`; + default: return '#'; + } + }; return ( - -
-
-
- {tier.label} + + + {/* Header */} +
+
+
+ +
+
+
+ + {typeConfig.label} + + {sponsorship.tier && ( + + {sponsorship.tier === 'main' ? 'Main Sponsor' : 'Secondary'} + + )} +
+
-
+
- {status.label} + {statusConfig.label}
- -
-

{sponsorship.leagueName}

+ {/* Entity Name */} +

{sponsorship.entityName}

+ {sponsorship.details && ( +

{sponsorship.details}

+ )} -
-
-
- - Impressions + {/* Application/Approval Info for non-active states */} + {isPending && ( +
+
+ + Application Pending +
+

+ Sent to {sponsorship.entityOwner} on{' '} + {sponsorship.applicationDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} +

+ {sponsorship.applicationMessage && ( +

"{sponsorship.applicationMessage}"

+ )}
-
{sponsorship.impressions.toLocaleString()}
-
-
-
- - Drivers -
-
{sponsorship.drivers}
-
-
-
- - Period -
-
- {sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {sponsorship.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} -
-
-
-
- - Investment -
-
${sponsorship.price}
-
-
+ )} -
- - {Math.ceil((sponsorship.endDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24))} days remaining - - -
- + {isApproved && ( +
+
+ + Approved! +
+

+ Approved by {sponsorship.entityOwner} on{' '} + {sponsorship.approvalDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} +

+

+ Starts {sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} +

+
+ )} + + {isRejected && ( +
+
+ + Application Declined +
+ {sponsorship.rejectionReason && ( +

+ Reason: {sponsorship.rejectionReason} +

+ )} + +
+ )} + + {/* Metrics Grid - Only show for active sponsorships */} + {sponsorship.status === 'active' && ( +
+
+
+ + Impressions +
+
+ {sponsorship.impressions.toLocaleString()} + {sponsorship.impressionsChange !== undefined && sponsorship.impressionsChange !== 0 && ( + 0 ? 'text-performance-green' : 'text-racing-red' + }`}> + {sponsorship.impressionsChange > 0 ? : } + {Math.abs(sponsorship.impressionsChange)}% + + )} +
+
+ + {sponsorship.engagement && ( +
+
+ + Engagement +
+
{sponsorship.engagement}%
+
+ )} + +
+
+ + Period +
+
+ {sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {sponsorship.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} +
+
+ +
+
+ + Investment +
+
${sponsorship.price}
+
+
+ )} + + {/* Basic info for non-active */} + {sponsorship.status !== 'active' && ( +
+
+ + {sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {sponsorship.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} +
+
+ + ${sponsorship.price} +
+
+ )} + + {/* Footer */} +
+
+ {sponsorship.status === 'active' && ( + + {daysRemaining > 0 ? `${daysRemaining} days remaining` : 'Ended'} + + )} + {isPending && ( + + Waiting for response... + + )} +
+
+ {sponsorship.type !== 'platform' && ( + + + + )} + {isPending && ( + + )} + {sponsorship.status === 'active' && ( + + )} +
+
+ + ); } +// ============================================================================ +// Main Component +// ============================================================================ + export default function SponsorCampaignsPage() { const router = useRouter(); - const [filter, setFilter] = useState<'all' | 'active' | 'pending' | 'expired'>('all'); - const [sponsorships, setSponsorships] = useState([]); + const searchParams = useSearchParams(); + const shouldReduceMotion = useReducedMotion(); + + const initialType = (searchParams.get('type') as SponsorshipType) || 'all'; + const [typeFilter, setTypeFilter] = useState(initialType); + const [statusFilter, setStatusFilter] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); const [loading, setLoading] = useState(true); useEffect(() => { - let isMounted = true; - - async function fetchSponsorships() { - try { - const response = await fetch('/api/sponsors/sponsorships'); - if (!response.ok) { - if (!isMounted) return; - setSponsorships([]); - return; - } - const json: SponsorSponsorshipsResponse = await response.json(); - if (!isMounted) return; - setSponsorships(mapApiToSponsorships(json)); - } catch { - if (!isMounted) return; - setSponsorships([]); - } finally { - if (isMounted) { - setLoading(false); - } - } - } - - fetchSponsorships(); - - return () => { - isMounted = false; - }; + const timer = setTimeout(() => setLoading(false), 300); + return () => clearTimeout(timer); }, []); - const filteredSponsorships = filter === 'all' - ? sponsorships - : sponsorships.filter(s => s.status === filter); + // Filter sponsorships + const filteredSponsorships = MOCK_SPONSORSHIPS.filter(s => { + if (typeFilter !== 'all' && s.type !== typeFilter) return false; + if (statusFilter !== 'all' && s.status !== statusFilter) return false; + if (searchQuery && !s.entityName.toLowerCase().includes(searchQuery.toLowerCase())) return false; + return true; + }); + // Calculate stats const stats = { - total: sponsorships.length, - active: sponsorships.filter(s => s.status === 'active').length, - pending: sponsorships.filter(s => s.status === 'pending').length, - totalInvestment: sponsorships.reduce((sum, s) => sum + s.price, 0), + total: MOCK_SPONSORSHIPS.length, + active: MOCK_SPONSORSHIPS.filter(s => s.status === 'active').length, + pending: MOCK_SPONSORSHIPS.filter(s => s.status === 'pending_approval').length, + approved: MOCK_SPONSORSHIPS.filter(s => s.status === 'approved').length, + rejected: MOCK_SPONSORSHIPS.filter(s => s.status === 'rejected').length, + totalInvestment: MOCK_SPONSORSHIPS.filter(s => s.status === 'active').reduce((sum, s) => sum + s.price, 0), + totalImpressions: MOCK_SPONSORSHIPS.reduce((sum, s) => sum + s.impressions, 0), + }; + + // Stats by type + const statsByType = { + leagues: MOCK_SPONSORSHIPS.filter(s => s.type === 'leagues').length, + teams: MOCK_SPONSORSHIPS.filter(s => s.type === 'teams').length, + drivers: MOCK_SPONSORSHIPS.filter(s => s.type === 'drivers').length, + races: MOCK_SPONSORSHIPS.filter(s => s.type === 'races').length, + platform: MOCK_SPONSORSHIPS.filter(s => s.type === 'platform').length, }; if (loading) { return ( -
-

Loading sponsorships…

+
+
+
+

Loading sponsorships...

+
); } return ( -
+
{/* Header */} -
+

My Sponsorships

-

Manage your league sponsorships

+

Manage applications and active sponsorship campaigns

+
+
+ + +
-
- {/* Stats */} -
- -
{stats.total}
-
Total Sponsorships
-
- -
{stats.active}
-
Active
-
- -
{stats.pending}
-
Pending
-
- -
${stats.totalInvestment.toLocaleString()}
-
Total Investment
-
+ {/* Info Banner about how sponsorships work */} + {stats.pending > 0 && ( + + +

+ You have {stats.pending} pending application{stats.pending !== 1 ? 's' : ''} waiting for approval. + League admins, team owners, and drivers review applications before accepting sponsorships. +

+
+
+ )} + + {/* Quick Stats */} +
+ + +
{stats.total}
+
Total
+
+
+ + +
{stats.active}
+
Active
+
+
+ + 0 ? 'border-warning-amber/30' : ''}`}> +
{stats.pending}
+
Pending
+
+
+ + +
{stats.approved}
+
Approved
+
+
+ + +
${stats.totalInvestment.toLocaleString()}
+
Active Investment
+
+
+ + +
{(stats.totalImpressions / 1000).toFixed(0)}k
+
Impressions
+
+
{/* Filters */} -
- {(['all', 'active', 'pending', 'expired'] as const).map((f) => ( - - ))} +
+ {/* Search */} +
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-charcoal-outline bg-iron-gray text-white placeholder-gray-500 focus:border-primary-blue focus:outline-none" + /> +
+ + {/* Type Filter */} +
+ {(['all', 'leagues', 'teams', 'drivers', 'races', 'platform'] as const).map((type) => { + const config = TYPE_CONFIG[type]; + const Icon = config.icon; + const count = type === 'all' ? stats.total : statsByType[type]; + return ( + + ); + })} +
+ + {/* Status Filter */} +
+ {(['all', 'active', 'pending_approval', 'approved', 'rejected'] as const).map((status) => { + const config = status === 'all' + ? { label: 'All', color: 'text-gray-400' } + : STATUS_CONFIG[status]; + const count = status === 'all' + ? stats.total + : MOCK_SPONSORSHIPS.filter(s => s.status === status).length; + return ( + + ); + })} +
{/* Sponsorship List */} {filteredSponsorships.length === 0 ? ( - +

No sponsorships found

-

Start sponsoring leagues to grow your brand visibility

- +

+ {searchQuery || typeFilter !== 'all' || statusFilter !== 'all' + ? 'Try adjusting your filters to see more results.' + : 'Start sponsoring leagues, teams, or drivers to grow your brand visibility.'} +

+
+ + + + + + + + + +
) : ( -
+
{filteredSponsorships.map((sponsorship) => ( ))}
)} - - {/* Alpha Notice */} -
-

- Alpha Note: Sponsorship data shown here is demonstration-only. - Real sponsorship management will be available when the system is fully implemented. -

-
); } \ No newline at end of file diff --git a/apps/website/app/sponsor/dashboard/page.tsx b/apps/website/app/sponsor/dashboard/page.tsx index a786ef671..f47d59d65 100644 --- a/apps/website/app/sponsor/dashboard/page.tsx +++ b/apps/website/app/sponsor/dashboard/page.tsx @@ -1,355 +1,652 @@ 'use client'; import { useState, useEffect } from 'react'; +import { motion, useReducedMotion, AnimatePresence } from 'framer-motion'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; -import { - BarChart3, - Eye, - Users, - Trophy, - TrendingUp, +import StatusBadge from '@/components/ui/StatusBadge'; +import InfoBanner from '@/components/ui/InfoBanner'; +import { + BarChart3, + Eye, + Users, + Trophy, + TrendingUp, Calendar, DollarSign, Target, ArrowUpRight, ArrowDownRight, ExternalLink, - Loader2 + Loader2, + Car, + Flag, + Megaphone, + ChevronRight, + Plus, + Bell, + Settings, + CreditCard, + FileText, + RefreshCw } from 'lucide-react'; import Link from 'next/link'; -interface SponsorshipMetrics { - impressions: number; - impressionsChange: number; - uniqueViewers: number; - viewersChange: number; - races: number; - drivers: number; - exposure: number; - exposureChange: number; -} - -interface SponsoredLeague { - id: string; - name: string; - tier: 'main' | 'secondary'; - drivers: number; - races: number; - impressions: number; - status: 'active' | 'upcoming' | 'completed'; -} - -interface SponsorDashboardData { - sponsorId: string; - sponsorName: string; - metrics: SponsorshipMetrics; - sponsoredLeagues: SponsoredLeague[]; - investment: { - activeSponsorships: number; - totalInvestment: number; - costPerThousandViews: number; - }; -} - +// Static mock data for prototype +const MOCK_SPONSOR_DATA = { + sponsorId: 'demo-sponsor-1', + sponsorName: 'Acme Racing Co.', + metrics: { + totalImpressions: 127450, + impressionsChange: 12.5, + uniqueViewers: 34200, + viewersChange: 8.3, + activeSponsors: 7, + totalInvestment: 4850, + avgEngagement: 4.2, + engagementChange: 0.8, + }, + sponsorships: { + leagues: [ + { id: 'l1', name: 'GT3 Masters Championship', tier: 'main', drivers: 48, impressions: 45200, status: 'active' }, + { id: 'l2', name: 'Endurance Pro Series', tier: 'secondary', drivers: 72, impressions: 38400, status: 'active' }, + ], + teams: [ + { id: 't1', name: 'Velocity Racing', drivers: 4, impressions: 12300, status: 'active' }, + { id: 't2', name: 'Storm Motorsport', drivers: 3, impressions: 8900, status: 'active' }, + ], + drivers: [ + { id: 'd1', name: 'Max Velocity', team: 'Velocity Racing', impressions: 8200, status: 'active' }, + { id: 'd2', name: 'Sarah Storm', team: 'Storm Motorsport', impressions: 6100, status: 'active' }, + ], + races: [ + { id: 'r1', name: 'Spa 24 Hours', league: 'Endurance Pro', impressions: 15600, date: '2025-12-20', status: 'upcoming' }, + ], + platform: [ + { id: 'p1', name: 'Homepage Banner', placement: 'Header', impressions: 52000, status: 'active' }, + ], + }, + recentActivity: [ + { id: 'a1', type: 'race', message: 'GT3 Masters Championship race completed', time: '2 hours ago', impressions: 1240 }, + { id: 'a2', type: 'driver', message: 'Max Velocity finished P1 at Monza', time: '5 hours ago', impressions: 890 }, + { id: 'a3', type: 'league', message: 'New driver joined Endurance Pro Series', time: '1 day ago', impressions: null }, + { id: 'a4', type: 'team', message: 'Velocity Racing won team championship', time: '2 days ago', impressions: 2100 }, + ], + upcomingRenewals: [ + { id: 'ren1', name: 'GT3 Masters Championship', type: 'league', renewDate: '2026-01-15', price: 1200 }, + { id: 'ren2', name: 'Homepage Banner', type: 'platform', renewDate: '2025-12-31', price: 500 }, + ], +}; +// Metric Card Component function MetricCard({ title, value, change, icon: Icon, suffix = '', + prefix = '', + delay = 0, }: { title: string; value: number | string; change?: number; icon: typeof Eye; suffix?: string; + prefix?: string; + delay?: number; }) { + const shouldReduceMotion = useReducedMotion(); const isPositive = change && change > 0; const isNegative = change && change < 0; return ( - -
-
- -
- {change !== undefined && ( -
- {isPositive ? : isNegative ? : null} - {Math.abs(change)}% + + +
+
+
- )} -
-
- {typeof value === 'number' ? value.toLocaleString() : value}{suffix} -
-
{title}
-
+ {change !== undefined && ( +
+ {isPositive ? : isNegative ? : null} + {Math.abs(change)}% +
+ )} +
+
+ {prefix}{typeof value === 'number' ? value.toLocaleString() : value}{suffix} +
+
{title}
+ + ); } -function LeagueRow({ league }: { league: SponsoredLeague }) { - const statusColors = { - active: 'bg-performance-green/20 text-performance-green', - upcoming: 'bg-warning-amber/20 text-warning-amber', - completed: 'bg-gray-500/20 text-gray-400', - }; +// Sponsorship Category Card +function SponsorshipCategoryCard({ + icon: Icon, + title, + count, + impressions, + color, + href +}: { + icon: typeof Trophy; + title: string; + count: number; + impressions: number; + color: string; + href: string; +}) { + return ( + + +
+
+
+ +
+
+

{title}

+

{count} active

+
+
+
+

{impressions.toLocaleString()}

+

impressions

+
+
+
+ + ); +} - const tierColors = { - main: 'bg-primary-blue/20 text-primary-blue border-primary-blue/30', - secondary: 'bg-purple-500/20 text-purple-400 border-purple-500/30', +// Activity Item +function ActivityItem({ activity }: { activity: typeof MOCK_SPONSOR_DATA.recentActivity[0] }) { + const typeColors = { + race: 'bg-warning-amber', + league: 'bg-primary-blue', + team: 'bg-purple-400', + driver: 'bg-performance-green', + platform: 'bg-racing-red', }; return ( -
-
-
- {league.tier === 'main' ? 'Main Sponsor' : 'Secondary'} -
-
-
{league.name}
-
- {league.drivers} drivers • {league.races} races -
+
+
+
+

{activity.message}

+
+ {activity.time} + {activity.impressions && ( + <> + + {activity.impressions.toLocaleString()} views + + )}
-
-
-
{league.impressions.toLocaleString()}
-
impressions
+
+ ); +} + +// Renewal Alert +function RenewalAlert({ renewal }: { renewal: typeof MOCK_SPONSOR_DATA.upcomingRenewals[0] }) { + const typeIcons = { + league: Trophy, + team: Users, + driver: Car, + race: Flag, + platform: Megaphone, + }; + const Icon = typeIcons[renewal.type as keyof typeof typeIcons] || Trophy; + + return ( +
+
+ +
+

{renewal.name}

+

Renews {renewal.renewDate}

-
- {league.status} -
- - - +
+
+

${renewal.price}

+
); } export default function SponsorDashboardPage() { + const shouldReduceMotion = useReducedMotion(); const [timeRange, setTimeRange] = useState<'7d' | '30d' | '90d' | 'all'>('30d'); - const [data, setData] = useState(null); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + // Simulate loading useEffect(() => { - async function fetchDashboard() { - try { - const response = await fetch('/api/sponsors/dashboard'); - if (response.ok) { - const dashboardData: SponsorDashboardData = await response.json(); - setData(dashboardData); - } else { - setError('Failed to load sponsor dashboard'); - } - } catch { - setError('Failed to load sponsor dashboard'); - } finally { - setLoading(false); - } - } - - fetchDashboard(); + const timer = setTimeout(() => setLoading(false), 500); + return () => clearTimeout(timer); }, []); + const data = MOCK_SPONSOR_DATA; + + // Calculate category totals + const categoryData = { + leagues: { + count: data.sponsorships.leagues.length, + impressions: data.sponsorships.leagues.reduce((sum, l) => sum + l.impressions, 0), + }, + teams: { + count: data.sponsorships.teams.length, + impressions: data.sponsorships.teams.reduce((sum, t) => sum + t.impressions, 0), + }, + drivers: { + count: data.sponsorships.drivers.length, + impressions: data.sponsorships.drivers.reduce((sum, d) => sum + d.impressions, 0), + }, + races: { + count: data.sponsorships.races.length, + impressions: data.sponsorships.races.reduce((sum, r) => sum + r.impressions, 0), + }, + platform: { + count: data.sponsorships.platform.length, + impressions: data.sponsorships.platform.reduce((sum, p) => sum + p.impressions, 0), + }, + }; + if (loading) { return ( -
- -
- ); - } - - if (!data) { - return ( -
-
-

- {error ?? 'No sponsor dashboard data available yet.'} -

+
+
+ +

Loading dashboard...

); } - const dashboardData = data; - return (
{/* Header */} -
+

Sponsor Dashboard

-

Track your sponsorship performance and exposure

+

Welcome back, {data.sponsorName}

-
- {(['7d', '30d', '90d', 'all'] as const).map((range) => ( - - ))} +
+ {/* Time Range Selector */} +
+ {(['7d', '30d', '90d', 'all'] as const).map((range) => ( + + ))} +
+ + {/* Quick Actions */} + + + +
- {/* Metrics Grid */} -
+ {/* Key Metrics */} +
+ {/* Sponsorship Categories */} +
+
+

Your Sponsorships

+ + + +
+ +
+ + + + + +
+
+ + {/* Main Content Grid */}
- {/* Sponsored Leagues */} -
+ {/* Left Column - Sponsored Entities */} +
+ {/* Top Performing Sponsorships */}
-

Sponsored Leagues

+

Top Performing

-
- {dashboardData.sponsoredLeagues.length > 0 ? ( - dashboardData.sponsoredLeagues.map((league) => ( - - )) +
+ {/* Leagues */} + {data.sponsorships.leagues.map((league) => ( +
+
+
+ {league.tier === 'main' ? 'Main' : 'Secondary'} +
+
+
+ + {league.name} +
+
{league.drivers} drivers
+
+
+
+
+
{league.impressions.toLocaleString()}
+
impressions
+
+ + + +
+
+ ))} + + {/* Teams */} + {data.sponsorships.teams.map((team) => ( +
+
+
+ Team +
+
+
+ + {team.name} +
+
{team.drivers} drivers
+
+
+
+
+
{team.impressions.toLocaleString()}
+
impressions
+
+ +
+
+ ))} + + {/* Drivers */} + {data.sponsorships.drivers.slice(0, 2).map((driver) => ( +
+
+
+ Driver +
+
+
+ + {driver.name} +
+
{driver.team}
+
+
+
+
+
{driver.impressions.toLocaleString()}
+
impressions
+
+ +
+
+ ))} +
+ + + {/* Upcoming Events */} + +
+

+ + Upcoming Sponsored Events +

+
+
+ {data.sponsorships.races.length > 0 ? ( +
+ {data.sponsorships.races.map((race) => ( +
+
+
+ +
+
+

{race.name}

+

{race.league} • {race.date}

+
+
+
+ + {race.status} + +
+
+ ))} +
) : ( -
-

No active sponsorships yet.

- - Browse leagues to sponsor - +
+ +

No upcoming sponsored events

)}
- {/* Quick Stats & Actions */} + {/* Right Column - Activity & Quick Actions */}
- {/* Investment Summary */} - -

Investment Summary

-
-
- Active Sponsorships - {dashboardData.investment.activeSponsorships} -
-
- Total Investment - ${dashboardData.investment.totalInvestment.toLocaleString()} -
-
- Cost per 1K Views - ${dashboardData.investment.costPerThousandViews.toFixed(2)} -
-
- Next Payment - Dec 15, 2025 -
-
-
- - {/* Recent Activity */} - -

Recent Activity

-
-
-
-
-

GT3 Pro Championship race completed

-

2 hours ago • 1,240 views

-
-
-
-
-
-

New driver joined Endurance Masters

-

5 hours ago

-
-
-
-
-
-

Touring Car Cup season starting soon

-

1 day ago

-
-
-
- - {/* Quick Actions */}

Quick Actions

- - - - + + + + + + + + + + + +
-
-
- {/* Alpha Notice */} -
-

- Alpha Note: Sponsor analytics data shown here is demonstration-only. - Real analytics will be available when the sponsorship system is fully implemented. -

+ {/* Renewal Alerts */} + {data.upcomingRenewals.length > 0 && ( + +

+ + Upcoming Renewals +

+
+ {data.upcomingRenewals.map((renewal) => ( + + ))} +
+
+ )} + + {/* Recent Activity */} + +

Recent Activity

+
+ {data.recentActivity.map((activity) => ( + + ))} +
+
+ + {/* Investment Summary */} + +

+ + Investment Summary +

+
+
+ Active Sponsorships + {data.metrics.activeSponsors} +
+
+ Total Investment + ${data.metrics.totalInvestment.toLocaleString()} +
+
+ Cost per 1K Views + + ${(data.metrics.totalInvestment / data.metrics.totalImpressions * 1000).toFixed(2)} + +
+
+ Next Invoice + Jan 1, 2026 +
+
+ + + +
+
+
+
); diff --git a/apps/website/app/sponsor/leagues/[id]/page.tsx b/apps/website/app/sponsor/leagues/[id]/page.tsx index 30ca8f6af..fe303d5bf 100644 --- a/apps/website/app/sponsor/leagues/[id]/page.tsx +++ b/apps/website/app/sponsor/leagues/[id]/page.tsx @@ -1,10 +1,12 @@ 'use client'; import { useState } from 'react'; -import { useParams } from 'next/navigation'; +import { useParams, useSearchParams } from 'next/navigation'; +import { motion, useReducedMotion } from 'framer-motion'; import Link from 'next/link'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; +import { siteConfig } from '@/lib/siteConfig'; import { Trophy, Users, @@ -14,147 +16,246 @@ import { Download, Image as ImageIcon, ExternalLink, - ChevronRight + ChevronRight, + Star, + Clock, + CheckCircle2, + Flag, + Car, + BarChart3, + ArrowUpRight, + Megaphone, + CreditCard, + FileText } from 'lucide-react'; -interface LeagueDriver { - id: string; - name: string; - country: string; - position: number; - races: number; - impressions: number; -} - -// Mock data +// Mock data for league detail const MOCK_LEAGUE = { id: 'league-1', - name: 'GT3 Pro Championship', - tier: 'main' as const, + name: 'GT3 Masters Championship', + game: 'iRacing', + tier: 'premium' as const, season: 'Season 3', - drivers: 32, + description: 'Premier GT3 racing with top-tier drivers competing across the world\'s most iconic circuits. Weekly broadcasts and an active community make this league a premium sponsorship opportunity.', + drivers: 48, races: 12, completedRaces: 8, - impressions: 45200, + totalImpressions: 45200, avgViewsPerRace: 5650, - logoPlacement: 'Primary hood placement + League page banner', - status: 'active' as const, + engagement: 4.2, + rating: 4.8, + seasonStatus: 'active' as const, + seasonDates: { start: '2025-10-01', end: '2026-02-28' }, + nextRace: { name: 'Spa-Francorchamps', date: '2025-12-20' }, + sponsorSlots: { + main: { + available: true, + price: 1200, + benefits: [ + 'Primary logo placement on all liveries', + 'League page header banner', + 'Race results page branding', + 'Social media feature posts', + 'Newsletter sponsor spot', + ] + }, + secondary: { + available: 1, + total: 2, + price: 400, + benefits: [ + 'Secondary logo on liveries', + 'League page sidebar placement', + 'Race results mention', + 'Social media mentions', + ] + }, + }, }; -const MOCK_DRIVERS: LeagueDriver[] = [ - { id: 'd1', name: 'Max Verstappen', country: 'NL', position: 1, races: 8, impressions: 4200 }, - { id: 'd2', name: 'Lewis Hamilton', country: 'GB', position: 2, races: 8, impressions: 3980 }, - { id: 'd3', name: 'Charles Leclerc', country: 'MC', position: 3, races: 8, impressions: 3750 }, - { id: 'd4', name: 'Lando Norris', country: 'GB', position: 4, races: 7, impressions: 3420 }, - { id: 'd5', name: 'Carlos Sainz', country: 'ES', position: 5, races: 8, impressions: 3100 }, +const MOCK_DRIVERS = [ + { id: 'd1', name: 'Max Verstappen', country: 'NL', position: 1, races: 8, impressions: 4200, team: 'Red Bull Racing' }, + { id: 'd2', name: 'Lewis Hamilton', country: 'GB', position: 2, races: 8, impressions: 3980, team: 'Mercedes AMG' }, + { id: 'd3', name: 'Charles Leclerc', country: 'MC', position: 3, races: 8, impressions: 3750, team: 'Ferrari' }, + { id: 'd4', name: 'Lando Norris', country: 'GB', position: 4, races: 7, impressions: 3420, team: 'McLaren' }, + { id: 'd5', name: 'Carlos Sainz', country: 'ES', position: 5, races: 8, impressions: 3100, team: 'Ferrari' }, ]; const MOCK_RACES = [ - { id: 'r1', name: 'Spa-Francorchamps', date: '2025-12-01', views: 6200, status: 'completed' }, + { id: 'r1', name: 'Spa-Francorchamps', date: '2025-12-20', views: 0, status: 'upcoming' }, { id: 'r2', name: 'Monza', date: '2025-12-08', views: 5800, status: 'completed' }, - { id: 'r3', name: 'Nürburgring', date: '2025-12-15', views: 0, status: 'upcoming' }, - { id: 'r4', name: 'Suzuka', date: '2025-12-22', views: 0, status: 'upcoming' }, + { id: 'r3', name: 'Silverstone', date: '2025-11-24', views: 6200, status: 'completed' }, + { id: 'r4', name: 'Nürburgring', date: '2025-11-10', views: 5400, status: 'completed' }, ]; +type TabType = 'overview' | 'drivers' | 'races' | 'sponsor'; + export default function SponsorLeagueDetailPage() { const params = useParams(); - const [activeTab, setActiveTab] = useState<'overview' | 'drivers' | 'races' | 'assets'>('overview'); + const searchParams = useSearchParams(); + const shouldReduceMotion = useReducedMotion(); + + const showSponsorAction = searchParams.get('action') === 'sponsor'; + const [activeTab, setActiveTab] = useState(showSponsorAction ? 'sponsor' : 'overview'); + const [selectedTier, setSelectedTier] = useState<'main' | 'secondary'>('main'); + + const league = MOCK_LEAGUE; + const tierConfig = { + premium: { color: 'text-yellow-400', bgColor: 'bg-yellow-500/10', border: 'border-yellow-500/30' }, + standard: { color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', border: 'border-primary-blue/30' }, + starter: { color: 'text-gray-400', bgColor: 'bg-gray-500/10', border: 'border-gray-500/30' }, + }; + + const config = tierConfig[league.tier]; return (
{/* Breadcrumb */}
- Dashboard + Dashboard - Leagues + Leagues - {MOCK_LEAGUE.name} + {league.name}
{/* Header */} -
-
+
+
-

{MOCK_LEAGUE.name}

- - Main Sponsor + + ⭐ {league.tier} + + Active Season + +
+ + {league.rating} +
-

{MOCK_LEAGUE.season} • {MOCK_LEAGUE.completedRaces}/{MOCK_LEAGUE.races} races completed

+

{league.name}

+

{league.game} • {league.season} • {league.completedRaces}/{league.races} races completed

+

{league.description}

-
- - + +
+ + + + {(league.sponsorSlots.main.available || league.sponsorSlots.secondary.available > 0) && ( + + )}
{/* Quick Stats */} -
- -
-
- +
+ + +
+
+ +
+
+
{league.totalImpressions.toLocaleString()}
+
Total Views
+
-
-
{MOCK_LEAGUE.impressions.toLocaleString()}
-
Total Impressions
+ + + + +
+
+ +
+
+
{league.avgViewsPerRace.toLocaleString()}
+
Avg/Race
+
-
-
- -
-
- + + + + +
+
+ +
+
+
{league.drivers}
+
Drivers
+
-
-
{MOCK_LEAGUE.avgViewsPerRace.toLocaleString()}
-
Avg Views/Race
+ + + + +
+
+ +
+
+
{league.engagement}%
+
Engagement
+
-
-
- -
-
- + + + + +
+
+ +
+
+
{league.races - league.completedRaces}
+
Races Left
+
-
-
{MOCK_LEAGUE.drivers}
-
Active Drivers
-
-
- - -
-
- -
-
-
{MOCK_LEAGUE.races - MOCK_LEAGUE.completedRaces}
-
Races Remaining
-
-
-
+ +
{/* Tabs */} -
- {(['overview', 'drivers', 'races', 'assets'] as const).map((tab) => ( +
+ {(['overview', 'drivers', 'races', 'sponsor'] as const).map((tab) => ( ))}
@@ -162,73 +263,122 @@ export default function SponsorLeagueDetailPage() { {/* Tab Content */} {activeTab === 'overview' && (
- -

Sponsorship Details

+ +

+ + League Information +

-
- Tier - Main Sponsor +
+ Platform + {league.game}
-
- Logo Placement - {MOCK_LEAGUE.logoPlacement} +
+ Season + {league.season}
-
- Season Duration - Oct 2025 - Feb 2026 +
+ Duration + Oct 2025 - Feb 2026
-
- Investment - $800/season +
+ Drivers + {league.drivers} +
+
+ Races + {league.races}
- -

Performance Metrics

+ +

+ + Sponsorship Value +

-
- Cost per 1K Impressions - $17.70 +
+ Total Season Views + {league.totalImpressions.toLocaleString()}
-
+
+ Projected Total + {Math.round(league.avgViewsPerRace * league.races).toLocaleString()} +
+
+ Main Sponsor CPM + + ${((league.sponsorSlots.main.price / (league.avgViewsPerRace * league.races)) * 1000).toFixed(2)} + +
+
Engagement Rate - 4.2% + {league.engagement}%
-
- Brand Recall Score - 78/100 -
-
- ROI Estimate - +24% +
+ League Rating +
+ + {league.rating}/5.0 +
+ + {/* Next Race */} + {league.nextRace && ( + +

+ + Next Race +

+
+
+
+ +
+
+

{league.nextRace.name}

+

{league.nextRace.date}

+
+
+ +
+
+ )}
)} {activeTab === 'drivers' && (
-

Drivers Carrying Your Brand

-

Top performing drivers with your sponsorship

+

Championship Standings

+

Top drivers carrying sponsor branding

-
+
{MOCK_DRIVERS.map((driver) => (
-
+
{driver.position}
{driver.name}
-
{driver.country} • {driver.races} races
+
{driver.team} • {driver.country}
-
-
{driver.impressions.toLocaleString()}
-
impressions
+
+
+
{driver.races}
+
races
+
+
+
{driver.impressions.toLocaleString()}
+
views
+
))} @@ -239,9 +389,10 @@ export default function SponsorLeagueDetailPage() { {activeTab === 'races' && (
-

Race Schedule & Performance

+

Race Calendar

+

Season schedule with view statistics

-
+
{MOCK_RACES.map((race) => (
@@ -250,17 +401,19 @@ export default function SponsorLeagueDetailPage() { }`} />
{race.name}
-
{race.date}
+
{race.date}
{race.status === 'completed' ? (
-
{race.views.toLocaleString()}
+
{race.views.toLocaleString()}
views
) : ( - Upcoming + + Upcoming + )}
@@ -269,38 +422,153 @@ export default function SponsorLeagueDetailPage() { )} - {activeTab === 'assets' && ( -
- -

Your Logo Assets

-
-
- + {activeTab === 'sponsor' && ( +
+ {/* Tier Selection */} +
+ {/* Main Sponsor */} + league.sponsorSlots.main.available && setSelectedTier('main')} + > +
+
+
+ +

Main Sponsor

+
+

Primary branding position

+
+ {league.sponsorSlots.main.available ? ( + + Available + + ) : ( + + Filled + + )}
-
- - + +
+ ${league.sponsorSlots.main.price} + /season +
+ +
    + {league.sponsorSlots.main.benefits.map((benefit, i) => ( +
  • + + {benefit} +
  • + ))} +
+ + {selectedTier === 'main' && league.sponsorSlots.main.available && ( +
+ +
+ )} + + + {/* Secondary Sponsor */} + league.sponsorSlots.secondary.available > 0 && setSelectedTier('secondary')} + > +
+
+
+ +

Secondary Sponsor

+
+

Supporting branding position

+
+ {league.sponsorSlots.secondary.available > 0 ? ( + + {league.sponsorSlots.secondary.available}/{league.sponsorSlots.secondary.total} Available + + ) : ( + + Full + + )} +
+ +
+ ${league.sponsorSlots.secondary.price} + /season +
+ +
    + {league.sponsorSlots.secondary.benefits.map((benefit, i) => ( +
  • + + {benefit} +
  • + ))} +
+ + {selectedTier === 'secondary' && league.sponsorSlots.secondary.available > 0 && ( +
+ +
+ )} +
+
+ + {/* Checkout Summary */} + +

+ + Sponsorship Summary +

+ +
+
+ Selected Tier + {selectedTier} Sponsor +
+
+ Season Price + + ${selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price} + +
+
+ Platform Fee ({siteConfig.fees.platformFeePercent}%) + + ${((selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price) * siteConfig.fees.platformFeePercent / 100).toFixed(2)} + +
+
+ Total (excl. VAT) + + ${((selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price) * (1 + siteConfig.fees.platformFeePercent / 100)).toFixed(2)} +
-
- -

Livery Preview

-
-
- -
-

- Your logo appears on the primary hood position for all 32 drivers in this league. -

- +
diff --git a/apps/website/app/sponsor/leagues/page.tsx b/apps/website/app/sponsor/leagues/page.tsx index bedfa63f4..082575233 100644 --- a/apps/website/app/sponsor/leagues/page.tsx +++ b/apps/website/app/sponsor/leagues/page.tsx @@ -1,16 +1,26 @@ 'use client'; import { useState } from 'react'; +import { motion, useReducedMotion } from 'framer-motion'; +import Link from 'next/link'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; +import { siteConfig } from '@/lib/siteConfig'; import { Trophy, Users, Eye, Search, - Filter, Star, - ChevronRight + ChevronRight, + Filter, + Car, + Flag, + TrendingUp, + CheckCircle2, + Clock, + Megaphone, + ArrowUpDown } from 'lucide-react'; interface AvailableLeague { @@ -23,6 +33,9 @@ interface AvailableLeague { secondarySlots: { available: number; total: number; price: number }; rating: number; tier: 'premium' | 'standard' | 'starter'; + nextRace?: string; + seasonStatus: 'active' | 'upcoming' | 'completed'; + description: string; } const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [ @@ -36,6 +49,9 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [ secondarySlots: { available: 1, total: 2, price: 400 }, rating: 4.8, tier: 'premium', + nextRace: 'Dec 20 - Spa', + seasonStatus: 'active', + description: 'Premier GT3 racing with top-tier drivers. Weekly broadcasts and active community.', }, { id: 'league-2', @@ -47,6 +63,9 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [ secondarySlots: { available: 2, total: 2, price: 500 }, rating: 4.9, tier: 'premium', + nextRace: 'Jan 5 - Nürburgring 24h', + seasonStatus: 'active', + description: 'Multi-class endurance racing. High engagement from dedicated endurance fans.', }, { id: 'league-3', @@ -58,6 +77,9 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [ secondarySlots: { available: 2, total: 2, price: 300 }, rating: 4.5, tier: 'standard', + nextRace: 'Dec 22 - Monza', + seasonStatus: 'active', + description: 'Open-wheel racing excellence. Competitive field with consistent racing.', }, { id: 'league-4', @@ -69,6 +91,9 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [ secondarySlots: { available: 2, total: 2, price: 200 }, rating: 4.2, tier: 'starter', + nextRace: 'Jan 10 - Brands Hatch', + seasonStatus: 'upcoming', + description: 'Touring car action with close racing. Great for building brand awareness.', }, { id: 'league-5', @@ -80,138 +105,299 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [ secondarySlots: { available: 1, total: 2, price: 350 }, rating: 4.6, tier: 'standard', + nextRace: 'Dec 28 - Sebring', + seasonStatus: 'active', + description: 'Prototype racing at its finest. Growing community with passionate fans.', + }, + { + id: 'league-6', + name: 'Rally Championship', + game: 'EA WRC', + drivers: 28, + avgViewsPerRace: 4500, + mainSponsorSlot: { available: true, price: 650 }, + secondarySlots: { available: 2, total: 2, price: 250 }, + rating: 4.4, + tier: 'standard', + nextRace: 'Jan 15 - Monte Carlo', + seasonStatus: 'upcoming', + description: 'Thrilling rally stages. Unique sponsorship exposure in rallying content.', }, ]; -function LeagueCard({ league }: { league: AvailableLeague }) { - const tierColors = { - premium: 'bg-gradient-to-r from-yellow-500/20 to-amber-500/20 border-yellow-500/30', - standard: 'bg-gradient-to-r from-blue-500/20 to-cyan-500/20 border-blue-500/30', - starter: 'bg-gradient-to-r from-gray-500/20 to-slate-500/20 border-gray-500/30', +type SortOption = 'rating' | 'drivers' | 'price' | 'views'; +type TierFilter = 'all' | 'premium' | 'standard' | 'starter'; +type AvailabilityFilter = 'all' | 'main' | 'secondary'; + +function LeagueCard({ league, index }: { league: AvailableLeague; index: number }) { + const shouldReduceMotion = useReducedMotion(); + + const tierConfig = { + premium: { + bg: 'bg-gradient-to-br from-yellow-500/10 to-amber-500/5', + border: 'border-yellow-500/30', + badge: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30', + icon: '⭐' + }, + standard: { + bg: 'bg-gradient-to-br from-primary-blue/10 to-cyan-500/5', + border: 'border-primary-blue/30', + badge: 'bg-primary-blue/20 text-primary-blue border-primary-blue/30', + icon: '🏆' + }, + starter: { + bg: 'bg-gradient-to-br from-gray-500/10 to-slate-500/5', + border: 'border-gray-500/30', + badge: 'bg-gray-500/20 text-gray-400 border-gray-500/30', + icon: '🚀' + }, }; - const tierBadgeColors = { - premium: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30', - standard: 'bg-blue-500/20 text-blue-400 border-blue-500/30', - starter: 'bg-gray-500/20 text-gray-400 border-gray-500/30', + const statusConfig = { + active: { color: 'text-performance-green', bg: 'bg-performance-green/10', label: 'Active Season' }, + upcoming: { color: 'text-warning-amber', bg: 'bg-warning-amber/10', label: 'Starting Soon' }, + completed: { color: 'text-gray-400', bg: 'bg-gray-400/10', label: 'Season Ended' }, }; + const config = tierConfig[league.tier]; + const status = statusConfig[league.seasonStatus]; + const cpm = (league.mainSponsorSlot.price / league.avgViewsPerRace * 1000).toFixed(0); + return ( - -
-
-
-
-

{league.name}

- - {league.tier} - + + +
+ {/* Header */} +
+
+
+ + {config.icon} {league.tier} + + + {status.label} + +
+

{league.name}

+

{league.game}

+
+
+ + {league.rating}
-

{league.game}

-
- - {league.rating} -
-
-
-
-
{league.drivers}
-
Drivers
-
-
-
{(league.avgViewsPerRace / 1000).toFixed(1)}k
-
Avg Views
-
-
-
${(league.mainSponsorSlot.price / league.avgViewsPerRace * 1000).toFixed(0)}
-
CPM
-
-
+ {/* Description */} +

{league.description}

-
-
-
-
- Main Sponsor + {/* Stats Grid */} +
+
+
{league.drivers}
+
Drivers
-
- {league.mainSponsorSlot.available ? ( - ${league.mainSponsorSlot.price}/season - ) : ( - Taken - )} +
+
{(league.avgViewsPerRace / 1000).toFixed(1)}k
+
Avg Views
+
+
+
${cpm}
+
CPM
-
-
-
0 ? 'bg-performance-green' : 'bg-racing-red'}`} /> - Secondary Slots -
-
- {league.secondarySlots.available > 0 ? ( - {league.secondarySlots.available}/{league.secondarySlots.total} @ ${league.secondarySlots.price} - ) : ( - Full - )} -
-
-
-
- - {(league.mainSponsorSlot.available || league.secondarySlots.available > 0) && ( - + {/* Next Race */} + {league.nextRace && ( +
+ + Next: + {league.nextRace} +
)} + + {/* Sponsorship Slots */} +
+
+
+
+ Main Sponsor +
+
+ {league.mainSponsorSlot.available ? ( + ${league.mainSponsorSlot.price}/season + ) : ( + + Filled + + )} +
+
+
+
+
0 ? 'bg-performance-green' : 'bg-racing-red'}`} /> + Secondary Slots +
+
+ {league.secondarySlots.available > 0 ? ( + + {league.secondarySlots.available}/{league.secondarySlots.total} @ ${league.secondarySlots.price} + + ) : ( + + Full + + )} +
+
+
+ + {/* Actions */} +
+ + + + {(league.mainSponsorSlot.available || league.secondarySlots.available > 0) && ( + + + + )} +
-
- + + ); } export default function SponsorLeaguesPage() { + const shouldReduceMotion = useReducedMotion(); const [searchQuery, setSearchQuery] = useState(''); - const [tierFilter, setTierFilter] = useState<'all' | 'premium' | 'standard' | 'starter'>('all'); - const [availabilityFilter, setAvailabilityFilter] = useState<'all' | 'main' | 'secondary'>('all'); + const [tierFilter, setTierFilter] = useState('all'); + const [availabilityFilter, setAvailabilityFilter] = useState('all'); + const [sortBy, setSortBy] = useState('rating'); - const filteredLeagues = MOCK_AVAILABLE_LEAGUES.filter(league => { - if (searchQuery && !league.name.toLowerCase().includes(searchQuery.toLowerCase())) { - return false; - } - if (tierFilter !== 'all' && league.tier !== tierFilter) { - return false; - } - if (availabilityFilter === 'main' && !league.mainSponsorSlot.available) { - return false; - } - if (availabilityFilter === 'secondary' && league.secondarySlots.available === 0) { - return false; - } - return true; - }); + // Filter and sort leagues + const filteredLeagues = MOCK_AVAILABLE_LEAGUES + .filter(league => { + if (searchQuery && !league.name.toLowerCase().includes(searchQuery.toLowerCase())) { + return false; + } + if (tierFilter !== 'all' && league.tier !== tierFilter) { + return false; + } + if (availabilityFilter === 'main' && !league.mainSponsorSlot.available) { + return false; + } + if (availabilityFilter === 'secondary' && league.secondarySlots.available === 0) { + return false; + } + return true; + }) + .sort((a, b) => { + switch (sortBy) { + case 'rating': return b.rating - a.rating; + case 'drivers': return b.drivers - a.drivers; + case 'price': return a.mainSponsorSlot.price - b.mainSponsorSlot.price; + case 'views': return b.avgViewsPerRace - a.avgViewsPerRace; + default: return 0; + } + }); + + // Calculate summary stats + const stats = { + total: MOCK_AVAILABLE_LEAGUES.length, + mainAvailable: MOCK_AVAILABLE_LEAGUES.filter(l => l.mainSponsorSlot.available).length, + secondaryAvailable: MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + l.secondarySlots.available, 0), + totalDrivers: MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + l.drivers, 0), + avgCpm: Math.round( + MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + (l.mainSponsorSlot.price / l.avgViewsPerRace * 1000), 0) / + MOCK_AVAILABLE_LEAGUES.length + ), + }; return (
{/* Breadcrumb */}
- Dashboard + Dashboard Browse Leagues
{/* Header */}
-

Find Leagues to Sponsor

-

Discover racing leagues looking for sponsors and grow your brand

+

+ + League Sponsorship Marketplace +

+

+ Discover racing leagues looking for sponsors. All prices shown exclude VAT. +

+
+ + {/* Stats Overview */} +
+ + +
{stats.total}
+
Leagues
+
+
+ + +
{stats.mainAvailable}
+
Main Slots
+
+
+ + +
{stats.secondaryAvailable}
+
Secondary Slots
+
+
+ + +
{stats.totalDrivers}
+
Total Drivers
+
+
+ + +
${stats.avgCpm}
+
Avg CPM
+
+
{/* Filters */} -
+
+ {/* Search */}
setSearchQuery(e.target.value)} - className="w-full pl-10 pr-4 py-2 rounded-lg border border-charcoal-outline bg-iron-gray text-white placeholder-gray-500 focus:border-primary-blue focus:outline-none" + className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-charcoal-outline bg-iron-gray text-white placeholder-gray-500 focus:border-primary-blue focus:outline-none" />
-
- - -
+ + {/* Tier Filter */} + + + {/* Availability Filter */} + + + {/* Sort */} +
- {/* Stats Banner */} -
- -
{MOCK_AVAILABLE_LEAGUES.length}
-
Available Leagues
-
- -
- {MOCK_AVAILABLE_LEAGUES.filter(l => l.mainSponsorSlot.available).length} -
-
Main Slots Open
-
- -
- {MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + l.secondarySlots.available, 0)} -
-
Secondary Slots Open
-
- -
- {MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + l.drivers, 0)} -
-
Total Drivers
-
+ {/* Results Count */} +
+

+ Showing {filteredLeagues.length} of {MOCK_AVAILABLE_LEAGUES.length} leagues +

+
+ + + + + + +
{/* League Grid */} -
- {filteredLeagues.map((league) => ( - - ))} -
- - {filteredLeagues.length === 0 && ( -
+ {filteredLeagues.length > 0 ? ( +
+ {filteredLeagues.map((league, index) => ( + + ))} +
+ ) : ( +

No leagues found

-

Try adjusting your filters to see more results

-
+

Try adjusting your filters to see more results

+ + )} - {/* Alpha Notice */} -
-

- Alpha Note: League sponsorship marketplace is demonstration-only. - Actual sponsorship purchases will be available when the payment system is fully implemented. -

+ {/* Platform Fee Notice */} +
+
+ +
+

Platform Fee

+

+ A {siteConfig.fees.platformFeePercent}% platform fee applies to all sponsorship payments. {siteConfig.fees.description} +

+
+
); diff --git a/apps/website/app/sponsor/settings/page.tsx b/apps/website/app/sponsor/settings/page.tsx index 7012f2ce7..7991dd1cb 100644 --- a/apps/website/app/sponsor/settings/page.tsx +++ b/apps/website/app/sponsor/settings/page.tsx @@ -2,28 +2,63 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { motion, useReducedMotion } from 'framer-motion'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import Input from '@/components/ui/Input'; -import { - Settings, - Building2, - Mail, +import Toggle from '@/components/ui/Toggle'; +import SectionHeader from '@/components/ui/SectionHeader'; +import FormField from '@/components/ui/FormField'; +import PageHeader from '@/components/ui/PageHeader'; +import { + Settings, + Building2, + Mail, Globe, Upload, Save, Bell, Shield, Eye, - Trash2 + Trash2, + CheckCircle, + User, + Phone, + MapPin, + FileText, + Link as LinkIcon, + Image as ImageIcon, + Lock, + Key, + Smartphone, + AlertCircle } from 'lucide-react'; +// ============================================================================ +// Types +// ============================================================================ + interface SponsorProfile { - name: string; - email: string; + companyName: string; + contactName: string; + contactEmail: string; + contactPhone: string; website: string; description: string; logoUrl: string | null; + industry: string; + address: { + street: string; + city: string; + country: string; + postalCode: string; + }; + taxId: string; + socialLinks: { + twitter: string; + linkedin: string; + instagram: string; + }; } interface NotificationSettings { @@ -31,15 +66,42 @@ interface NotificationSettings { emailWeeklyReport: boolean; emailRaceAlerts: boolean; emailPaymentAlerts: boolean; + emailNewOpportunities: boolean; + emailContractExpiry: boolean; } -// Mock data +interface PrivacySettings { + publicProfile: boolean; + showStats: boolean; + showActiveSponsorships: boolean; + allowDirectContact: boolean; +} + +// ============================================================================ +// Mock Data +// ============================================================================ + const MOCK_PROFILE: SponsorProfile = { - name: 'Acme Racing Co.', - email: 'sponsor@acme-racing.com', + companyName: 'Acme Racing Co.', + contactName: 'John Smith', + contactEmail: 'sponsor@acme-racing.com', + contactPhone: '+1 (555) 123-4567', website: 'https://acme-racing.com', - description: 'Premium sim racing equipment and accessories for competitive drivers.', + description: 'Premium sim racing equipment and accessories for competitive drivers. We specialize in high-performance steering wheels, pedals, and cockpit systems used by professionals worldwide.', logoUrl: null, + industry: 'Racing Equipment', + address: { + street: '123 Racing Boulevard', + city: 'Indianapolis', + country: 'United States', + postalCode: '46222', + }, + taxId: 'US12-3456789', + socialLinks: { + twitter: '@acmeracing', + linkedin: 'acme-racing-co', + instagram: '@acmeracing', + }, }; const MOCK_NOTIFICATIONS: NotificationSettings = { @@ -47,35 +109,63 @@ const MOCK_NOTIFICATIONS: NotificationSettings = { emailWeeklyReport: true, emailRaceAlerts: false, emailPaymentAlerts: true, + emailNewOpportunities: true, + emailContractExpiry: true, }; -function Toggle({ checked, onChange, label }: { checked: boolean; onChange: (checked: boolean) => void; label: string }) { +const MOCK_PRIVACY: PrivacySettings = { + publicProfile: true, + showStats: false, + showActiveSponsorships: true, + allowDirectContact: true, +}; + +const INDUSTRY_OPTIONS = [ + 'Racing Equipment', + 'Automotive', + 'Technology', + 'Gaming & Esports', + 'Energy Drinks', + 'Apparel', + 'Financial Services', + 'Other', +]; + +// ============================================================================ +// Components +// ============================================================================ + +function SavedIndicator({ visible }: { visible: boolean }) { + const shouldReduceMotion = useReducedMotion(); + return ( - + + + Changes saved + ); } +// ============================================================================ +// Main Component +// ============================================================================ + export default function SponsorSettingsPage() { const router = useRouter(); + const shouldReduceMotion = useReducedMotion(); const [profile, setProfile] = useState(MOCK_PROFILE); const [notifications, setNotifications] = useState(MOCK_NOTIFICATIONS); + const [privacy, setPrivacy] = useState(MOCK_PRIVACY); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const handleSaveProfile = async () => { setSaving(true); - // Simulate API call await new Promise(resolve => setTimeout(resolve, 800)); setSaving(false); setSaved(true); @@ -83,251 +173,498 @@ export default function SponsorSettingsPage() { }; const handleDeleteAccount = () => { - if (confirm('Are you sure you want to delete your sponsor account? This action cannot be undone.')) { - // Clear demo cookies and redirect + if (confirm('Are you sure you want to delete your sponsor account? This action cannot be undone. All sponsorship data will be permanently removed.')) { document.cookie = 'gridpilot_demo_mode=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; document.cookie = 'gridpilot_sponsor_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; router.push('/'); } }; + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: shouldReduceMotion ? 0 : 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { opacity: 1, y: 0 }, + }; + return ( -
+ {/* Header */} -
-

- - Sponsor Settings -

-

Manage your sponsor profile and preferences

-
+ + } + /> + {/* Company Profile */} - -
-

- - Company Profile -

-
-
-
- - setProfile({ ...profile, name: e.target.value })} - placeholder="Your company name" - /> -
- -
- - setProfile({ ...profile, email: e.target.value })} - placeholder="sponsor@company.com" - /> -
- -
- - setProfile({ ...profile, website: e.target.value })} - placeholder="https://company.com" - /> -
- -
- -