website refactor
This commit is contained in:
@@ -11,8 +11,6 @@ import InfoBanner from '@/components/ui/InfoBanner';
|
||||
import PageHeader from '@/components/ui/PageHeader';
|
||||
import { siteConfig } from '@/lib/siteConfig';
|
||||
import { useSponsorBilling } from "@/lib/hooks/sponsor/useSponsorBilling";
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { SPONSOR_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import {
|
||||
CreditCard,
|
||||
DollarSign,
|
||||
@@ -107,13 +105,13 @@ function PaymentMethodCard({
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: shouldReduceMotion ? 0 : 0.2 }}
|
||||
className={`p-4 rounded-xl border transition-all ${
|
||||
method.isDefault
|
||||
? 'border-primary-blue/50 bg-gradient-to-r from-primary-blue/10 to-transparent shadow-[0_0_20px_rgba(25,140,255,0.1)]'
|
||||
method.isDefault
|
||||
? 'border-primary-blue/50 bg-gradient-to-r from-primary-blue/10 to-transparent shadow-[0_0_20px_rgba(25,140,255,0.1)]'
|
||||
: 'border-charcoal-outline bg-iron-gray/30 hover:border-charcoal-outline/80'
|
||||
}`}
|
||||
>
|
||||
@@ -162,31 +160,31 @@ function InvoiceRow({ invoice, index }: { invoice: any; index: number }) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const statusConfig = {
|
||||
paid: {
|
||||
icon: Check,
|
||||
paid: {
|
||||
icon: Check,
|
||||
label: 'Paid',
|
||||
color: 'text-performance-green',
|
||||
color: 'text-performance-green',
|
||||
bg: 'bg-performance-green/10',
|
||||
border: 'border-performance-green/30'
|
||||
},
|
||||
pending: {
|
||||
icon: Clock,
|
||||
pending: {
|
||||
icon: Clock,
|
||||
label: 'Pending',
|
||||
color: 'text-warning-amber',
|
||||
color: 'text-warning-amber',
|
||||
bg: 'bg-warning-amber/10',
|
||||
border: 'border-warning-amber/30'
|
||||
},
|
||||
overdue: {
|
||||
icon: AlertTriangle,
|
||||
overdue: {
|
||||
icon: AlertTriangle,
|
||||
label: 'Overdue',
|
||||
color: 'text-racing-red',
|
||||
color: 'text-racing-red',
|
||||
bg: 'bg-racing-red/10',
|
||||
border: 'border-racing-red/30'
|
||||
},
|
||||
failed: {
|
||||
icon: AlertTriangle,
|
||||
failed: {
|
||||
icon: AlertTriangle,
|
||||
label: 'Failed',
|
||||
color: 'text-racing-red',
|
||||
color: 'text-racing-red',
|
||||
bg: 'bg-racing-red/10',
|
||||
border: 'border-racing-red/30'
|
||||
},
|
||||
@@ -204,7 +202,7 @@ function InvoiceRow({ invoice, index }: { invoice: any; index: number }) {
|
||||
const StatusIcon = status.icon;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: shouldReduceMotion ? 0 : 0.2, delay: shouldReduceMotion ? 0 : index * 0.05 }}
|
||||
@@ -261,7 +259,6 @@ function InvoiceRow({ invoice, index }: { invoice: any; index: number }) {
|
||||
|
||||
export default function SponsorBillingPage() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const sponsorService = useInject(SPONSOR_SERVICE_TOKEN);
|
||||
const [showAllInvoices, setShowAllInvoices] = useState(false);
|
||||
|
||||
const { data: billingData, isLoading, error, retry } = useSponsorBilling('demo-sponsor-1');
|
||||
@@ -322,7 +319,7 @@ export default function SponsorBillingPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
<motion.div
|
||||
className="max-w-5xl mx-auto py-8 px-4"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
@@ -353,7 +350,7 @@ export default function SponsorBillingPage() {
|
||||
icon={AlertTriangle}
|
||||
label="Pending Payments"
|
||||
value={data.stats.formattedPendingAmount}
|
||||
subValue={`${data.invoices.filter(i => i.status === 'pending' || i.status === 'overdue').length} invoices`}
|
||||
subValue={`${data.invoices.filter((i: { status: string }) => i.status === 'pending' || i.status === 'overdue').length} invoices`}
|
||||
color="text-warning-amber"
|
||||
bgColor="bg-warning-amber/10"
|
||||
/>
|
||||
@@ -378,8 +375,8 @@ export default function SponsorBillingPage() {
|
||||
{/* Payment Methods */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card className="mb-8 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={CreditCard}
|
||||
<SectionHeader
|
||||
icon={CreditCard}
|
||||
title="Payment Methods"
|
||||
action={
|
||||
<Button variant="secondary" className="text-sm">
|
||||
@@ -389,7 +386,7 @@ export default function SponsorBillingPage() {
|
||||
}
|
||||
/>
|
||||
<div className="p-5 space-y-3">
|
||||
{data.paymentMethods.map((method) => (
|
||||
{data.paymentMethods.map((method: { id: string; type: string; last4: string; brand: string; default: boolean }) => (
|
||||
<PaymentMethodCard
|
||||
key={method.id}
|
||||
method={method}
|
||||
@@ -410,8 +407,8 @@ export default function SponsorBillingPage() {
|
||||
{/* Billing History */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card className="mb-8 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={FileText}
|
||||
<SectionHeader
|
||||
icon={FileText}
|
||||
title="Billing History"
|
||||
color="text-warning-amber"
|
||||
action={
|
||||
@@ -422,7 +419,7 @@ export default function SponsorBillingPage() {
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
{data.invoices.slice(0, showAllInvoices ? data.invoices.length : 4).map((invoice, index) => (
|
||||
{data.invoices.slice(0, showAllInvoices ? data.invoices.length : 4).map((invoice: { id: string; date: string; amount: number; status: string }, index: number) => (
|
||||
<InvoiceRow key={invoice.id} invoice={invoice} index={index} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { 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 { useSponsorSponsorships } from "@/lib/hooks/sponsor/useSponsorSponsorships";
|
||||
import {
|
||||
@@ -44,33 +43,6 @@ import {
|
||||
type SponsorshipType = 'all' | 'leagues' | 'teams' | 'drivers' | 'races' | 'platform';
|
||||
type SponsorshipStatus = 'all' | 'active' | 'pending_approval' | 'approved' | 'rejected' | 'expired';
|
||||
|
||||
interface Sponsorship {
|
||||
id: string;
|
||||
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;
|
||||
impressionsChange?: number;
|
||||
engagement?: number;
|
||||
details?: string;
|
||||
// For pending approvals
|
||||
entityOwner?: string;
|
||||
applicationMessage?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mock Data - Updated to show application workflow
|
||||
// ============================================================================
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
@@ -85,40 +57,40 @@ const TYPE_CONFIG = {
|
||||
};
|
||||
|
||||
const STATUS_CONFIG = {
|
||||
active: {
|
||||
icon: Check,
|
||||
color: 'text-performance-green',
|
||||
bgColor: 'bg-performance-green/10',
|
||||
active: {
|
||||
icon: Check,
|
||||
color: 'text-performance-green',
|
||||
bgColor: 'bg-performance-green/10',
|
||||
borderColor: 'border-performance-green/30',
|
||||
label: 'Active'
|
||||
label: 'Active'
|
||||
},
|
||||
pending_approval: {
|
||||
icon: Clock,
|
||||
color: 'text-warning-amber',
|
||||
bgColor: 'bg-warning-amber/10',
|
||||
pending_approval: {
|
||||
icon: Clock,
|
||||
color: 'text-warning-amber',
|
||||
bgColor: 'bg-warning-amber/10',
|
||||
borderColor: 'border-warning-amber/30',
|
||||
label: 'Awaiting Approval'
|
||||
label: 'Awaiting Approval'
|
||||
},
|
||||
approved: {
|
||||
icon: ThumbsUp,
|
||||
color: 'text-primary-blue',
|
||||
bgColor: 'bg-primary-blue/10',
|
||||
approved: {
|
||||
icon: ThumbsUp,
|
||||
color: 'text-primary-blue',
|
||||
bgColor: 'bg-primary-blue/10',
|
||||
borderColor: 'border-primary-blue/30',
|
||||
label: 'Approved'
|
||||
label: 'Approved'
|
||||
},
|
||||
rejected: {
|
||||
icon: ThumbsDown,
|
||||
color: 'text-racing-red',
|
||||
bgColor: 'bg-racing-red/10',
|
||||
rejected: {
|
||||
icon: ThumbsDown,
|
||||
color: 'text-racing-red',
|
||||
bgColor: 'bg-racing-red/10',
|
||||
borderColor: 'border-racing-red/30',
|
||||
label: 'Declined'
|
||||
label: 'Declined'
|
||||
},
|
||||
expired: {
|
||||
icon: XCircle,
|
||||
color: 'text-gray-400',
|
||||
bgColor: 'bg-gray-400/10',
|
||||
expired: {
|
||||
icon: XCircle,
|
||||
color: 'text-gray-400',
|
||||
bgColor: 'bg-gray-400/10',
|
||||
borderColor: 'border-gray-400/30',
|
||||
label: 'Expired'
|
||||
label: 'Expired'
|
||||
},
|
||||
};
|
||||
|
||||
@@ -127,7 +99,6 @@ const STATUS_CONFIG = {
|
||||
// ============================================================================
|
||||
|
||||
function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
const router = useRouter();
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const typeConfig = TYPE_CONFIG[sponsorship.type as keyof typeof TYPE_CONFIG];
|
||||
@@ -159,8 +130,8 @@ function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Card className={`hover:border-primary-blue/30 transition-all duration-300 ${
|
||||
isPending ? 'border-warning-amber/30' :
|
||||
isRejected ? 'border-racing-red/20 opacity-75' :
|
||||
isPending ? 'border-warning-amber/30' :
|
||||
isRejected ? 'border-racing-red/20 opacity-75' :
|
||||
isApproved ? 'border-primary-blue/30' : ''
|
||||
}`}>
|
||||
{/* Header */}
|
||||
@@ -176,8 +147,8 @@ function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
</span>
|
||||
{sponsorship.tier && (
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded ${
|
||||
sponsorship.tier === 'main'
|
||||
? 'bg-primary-blue/20 text-primary-blue'
|
||||
sponsorship.tier === 'main'
|
||||
? 'bg-primary-blue/20 text-primary-blue'
|
||||
: 'bg-purple-400/20 text-purple-400'
|
||||
}`}>
|
||||
{sponsorship.tier === 'main' ? 'Main Sponsor' : 'Secondary'}
|
||||
@@ -360,7 +331,6 @@ function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
// ============================================================================
|
||||
|
||||
export default function SponsorCampaignsPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
@@ -400,7 +370,7 @@ export default function SponsorCampaignsPage() {
|
||||
const data = sponsorshipsData;
|
||||
|
||||
// Filter sponsorships
|
||||
const filteredSponsorships = data.sponsorships.filter(s => {
|
||||
const filteredSponsorships = data.sponsorships.filter((s: any) => {
|
||||
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;
|
||||
@@ -410,21 +380,21 @@ export default function SponsorCampaignsPage() {
|
||||
// Calculate stats
|
||||
const stats = {
|
||||
total: data.sponsorships.length,
|
||||
active: data.sponsorships.filter(s => s.status === 'active').length,
|
||||
pending: data.sponsorships.filter(s => s.status === 'pending_approval').length,
|
||||
approved: data.sponsorships.filter(s => s.status === 'approved').length,
|
||||
rejected: data.sponsorships.filter(s => s.status === 'rejected').length,
|
||||
totalInvestment: data.sponsorships.filter(s => s.status === 'active').reduce((sum, s) => sum + s.price, 0),
|
||||
totalImpressions: data.sponsorships.reduce((sum, s) => sum + s.impressions, 0),
|
||||
active: data.sponsorships.filter((s: any) => s.status === 'active').length,
|
||||
pending: data.sponsorships.filter((s: any) => s.status === 'pending_approval').length,
|
||||
approved: data.sponsorships.filter((s: any) => s.status === 'approved').length,
|
||||
rejected: data.sponsorships.filter((s: any) => s.status === 'rejected').length,
|
||||
totalInvestment: data.sponsorships.filter((s: any) => s.status === 'active').reduce((sum: number, s: any) => sum + s.price, 0),
|
||||
totalImpressions: data.sponsorships.reduce((sum: number, s: any) => sum + s.impressions, 0),
|
||||
};
|
||||
|
||||
// Stats by type
|
||||
const statsByType = {
|
||||
leagues: data.sponsorships.filter(s => s.type === 'leagues').length,
|
||||
teams: data.sponsorships.filter(s => s.type === 'teams').length,
|
||||
drivers: data.sponsorships.filter(s => s.type === 'drivers').length,
|
||||
races: data.sponsorships.filter(s => s.type === 'races').length,
|
||||
platform: data.sponsorships.filter(s => s.type === 'platform').length,
|
||||
leagues: data.sponsorships.filter((s: any) => s.type === 'leagues').length,
|
||||
teams: data.sponsorships.filter((s: any) => s.type === 'teams').length,
|
||||
drivers: data.sponsorships.filter((s: any) => s.type === 'drivers').length,
|
||||
races: data.sponsorships.filter((s: any) => s.type === 'races').length,
|
||||
platform: data.sponsorships.filter((s: any) => s.type === 'platform').length,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -457,7 +427,7 @@ export default function SponsorCampaignsPage() {
|
||||
>
|
||||
<InfoBanner type="info" title="Sponsorship Applications">
|
||||
<p>
|
||||
You have <strong className="text-white">{stats.pending} pending application{stats.pending !== 1 ? 's' : ''}</strong> waiting for approval.
|
||||
You have <strong className="text-white">{stats.pending} pending application{stats.pending !== 1 ? 's' : ''}</strong> waiting for approval.
|
||||
League admins, team owners, and drivers review applications before accepting sponsorships.
|
||||
</p>
|
||||
</InfoBanner>
|
||||
@@ -540,7 +510,7 @@ export default function SponsorCampaignsPage() {
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Type Filter */}
|
||||
<div className="flex items-center gap-2 overflow-x-auto pb-2 lg:pb-0">
|
||||
{(['all', 'leagues', 'teams', 'drivers', 'races', 'platform'] as const).map((type) => {
|
||||
@@ -572,12 +542,12 @@ export default function SponsorCampaignsPage() {
|
||||
{/* Status Filter */}
|
||||
<div className="flex items-center gap-2 overflow-x-auto">
|
||||
{(['all', 'active', 'pending_approval', 'approved', 'rejected'] as const).map((status) => {
|
||||
const config = status === 'all'
|
||||
? { label: 'All', color: 'text-gray-400' }
|
||||
const config = status === 'all'
|
||||
? { label: 'All', color: 'text-gray-400' }
|
||||
: STATUS_CONFIG[status];
|
||||
const count = status === 'all'
|
||||
? stats.total
|
||||
: data.sponsorships.filter(s => s.status === status).length;
|
||||
: data.sponsorships.filter((s: any) => s.status === status).length;
|
||||
return (
|
||||
<button
|
||||
key={status}
|
||||
@@ -635,7 +605,7 @@ export default function SponsorCampaignsPage() {
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{filteredSponsorships.map((sponsorship) => (
|
||||
{filteredSponsorships.map((sponsorship: any) => (
|
||||
<SponsorshipCard key={sponsorship.id} sponsorship={sponsorship} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { motion, useReducedMotion, AnimatePresence } from 'framer-motion';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
@@ -37,48 +36,15 @@ import {
|
||||
RefreshCw
|
||||
} from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { SPONSOR_SERVICE_TOKEN, POLICY_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError';
|
||||
import { useSponsorDashboard } from '@/lib/hooks/sponsor/useSponsorDashboard';
|
||||
|
||||
export default function SponsorDashboardPage() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const sponsorService = useInject(SPONSOR_SERVICE_TOKEN);
|
||||
const policyService = useInject(POLICY_SERVICE_TOKEN);
|
||||
|
||||
// Use the hook instead of manual query construction
|
||||
const { data: dashboardData, isLoading, error, retry } = useSponsorDashboard('demo-sponsor-1');
|
||||
|
||||
const policyQuery = useQuery({
|
||||
queryKey: ['policySnapshot'],
|
||||
queryFn: () => policyService.getSnapshot(),
|
||||
staleTime: 60_000,
|
||||
gcTime: 5 * 60_000,
|
||||
});
|
||||
|
||||
const enhancedPolicyQuery = enhanceQueryResult(policyQuery);
|
||||
const policySnapshot = enhancedPolicyQuery.data;
|
||||
const policyLoading = enhancedPolicyQuery.isLoading;
|
||||
const policyError = enhancedPolicyQuery.error;
|
||||
|
||||
const sponsorPortalState = policySnapshot
|
||||
? policyService.getCapabilityState(policySnapshot, 'sponsors.portal')
|
||||
: null;
|
||||
|
||||
const dashboardQuery = useQuery({
|
||||
queryKey: ['sponsorDashboard', 'demo-sponsor-1', sponsorPortalState],
|
||||
queryFn: () => sponsorService.getSponsorDashboard('demo-sponsor-1'),
|
||||
enabled: !!policySnapshot && sponsorPortalState === 'enabled',
|
||||
staleTime: 300_000,
|
||||
gcTime: 10 * 60_000,
|
||||
});
|
||||
|
||||
const enhancedDashboardQuery = enhanceQueryResult(dashboardQuery);
|
||||
const dashboardData = enhancedDashboardQuery.data;
|
||||
const dashboardLoading = enhancedDashboardQuery.isLoading;
|
||||
const dashboardError = enhancedDashboardQuery.error;
|
||||
|
||||
const loading = policyLoading || dashboardLoading;
|
||||
const error = policyError || dashboardError || (sponsorPortalState !== 'enabled' && sponsorPortalState !== null);
|
||||
|
||||
if (loading) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-8 px-4 flex items-center justify-center min-h-[600px]">
|
||||
<div className="text-center">
|
||||
@@ -90,16 +56,15 @@ export default function SponsorDashboardPage() {
|
||||
}
|
||||
|
||||
if (error || !dashboardData) {
|
||||
const errorMessage = sponsorPortalState === 'coming_soon'
|
||||
? 'Sponsor portal is coming soon.'
|
||||
: sponsorPortalState === 'disabled'
|
||||
? 'Sponsor portal is currently unavailable.'
|
||||
: 'Failed to load dashboard data';
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-8 px-4 flex items-center justify-center min-h-[600px]">
|
||||
<div className="text-center">
|
||||
<p className="text-gray-400">{errorMessage}</p>
|
||||
<p className="text-gray-400">{error?.getUserMessage() || 'Failed to load dashboard data'}</p>
|
||||
{error && (
|
||||
<Button variant="secondary" onClick={retry} className="mt-4">
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,5 +24,6 @@ export default async function Page({ params }: { params: { id: string } }) {
|
||||
|
||||
if (!data) notFound();
|
||||
|
||||
// Data is already in the right format from API client
|
||||
return <PageWrapper data={data} Template={SponsorLeagueDetailTemplate} />;
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { SponsorsApiClient } from '@/lib/api/sponsors/SponsorsApiClient';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { AvailableLeaguesViewModel } from '@/lib/view-models/AvailableLeaguesViewModel';
|
||||
|
||||
export default async function Page() {
|
||||
// Manual wiring: create dependencies
|
||||
@@ -22,26 +21,24 @@ export default async function Page() {
|
||||
// Fetch data
|
||||
const leaguesData = await apiClient.getAvailableLeagues();
|
||||
|
||||
// Process data with view model to calculate stats
|
||||
// Process data - move business logic to template
|
||||
if (!leaguesData) {
|
||||
return <PageWrapper data={undefined} Template={SponsorLeaguesTemplate} />;
|
||||
}
|
||||
|
||||
const viewModel = new AvailableLeaguesViewModel(leaguesData);
|
||||
|
||||
// Calculate summary stats
|
||||
// Calculate summary stats (business logic moved from view model)
|
||||
const stats = {
|
||||
total: viewModel.leagues.length,
|
||||
mainAvailable: viewModel.leagues.filter(l => l.mainSponsorSlot.available).length,
|
||||
secondaryAvailable: viewModel.leagues.reduce((sum, l) => sum + l.secondarySlots.available, 0),
|
||||
totalDrivers: viewModel.leagues.reduce((sum, l) => sum + l.drivers, 0),
|
||||
total: leaguesData.length,
|
||||
mainAvailable: leaguesData.filter((l: any) => l.mainSponsorSlot.available).length,
|
||||
secondaryAvailable: leaguesData.reduce((sum: number, l: any) => sum + l.secondarySlots.available, 0),
|
||||
totalDrivers: leaguesData.reduce((sum: number, l: any) => sum + l.drivers, 0),
|
||||
avgCpm: Math.round(
|
||||
viewModel.leagues.reduce((sum, l) => sum + l.cpm, 0) / viewModel.leagues.length
|
||||
leaguesData.reduce((sum: number, l: any) => sum + l.cpm, 0) / leaguesData.length
|
||||
),
|
||||
};
|
||||
|
||||
const processedData = {
|
||||
leagues: viewModel.leagues,
|
||||
leagues: leaguesData,
|
||||
stats,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default function SponsorPage() {
|
||||
// Redirect to dashboard - this will be handled by middleware for auth
|
||||
// Using permanent redirect to avoid cookie loss
|
||||
redirect('/sponsor/dashboard');
|
||||
// Redirect to dashboard
|
||||
redirect(routes.sponsor.dashboard);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
'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';
|
||||
@@ -157,7 +156,6 @@ function SavedIndicator({ visible }: { visible: boolean }) {
|
||||
// ============================================================================
|
||||
|
||||
export default function SponsorSettingsPage() {
|
||||
const router = useRouter();
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const [profile, setProfile] = useState(MOCK_PROFILE);
|
||||
const [notifications, setNotifications] = useState(MOCK_NOTIFICATIONS);
|
||||
@@ -173,10 +171,17 @@ export default function SponsorSettingsPage() {
|
||||
setTimeout(() => setSaved(false), 3000);
|
||||
};
|
||||
|
||||
const handleDeleteAccount = () => {
|
||||
const handleDeleteAccount = async () => {
|
||||
if (confirm('Are you sure you want to delete your sponsor account? This action cannot be undone. All sponsorship data will be permanently removed.')) {
|
||||
// Call the logout action directly
|
||||
logoutAction();
|
||||
// Call the logout action and handle result
|
||||
const result = await logoutAction();
|
||||
if (result.isErr()) {
|
||||
console.error('Logout failed:', result.getError());
|
||||
// Could show error toast here
|
||||
return;
|
||||
}
|
||||
// Redirect to login after successful logout
|
||||
window.location.href = '/auth/login';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -196,7 +201,7 @@ export default function SponsorSettingsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
<motion.div
|
||||
className="max-w-4xl mx-auto py-8 px-4"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
@@ -215,9 +220,9 @@ export default function SponsorSettingsPage() {
|
||||
{/* Company Profile */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card className="mb-6 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={Building2}
|
||||
title="Company Profile"
|
||||
<SectionHeader
|
||||
icon={Building2}
|
||||
title="Company Profile"
|
||||
description="Your public-facing company information"
|
||||
/>
|
||||
<div className="p-6 space-y-6">
|
||||
@@ -300,9 +305,9 @@ export default function SponsorSettingsPage() {
|
||||
<Input
|
||||
type="text"
|
||||
value={profile.address.street}
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
address: { ...profile.address, street: e.target.value }
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
address: { ...profile.address, street: e.target.value }
|
||||
})}
|
||||
placeholder="123 Main Street"
|
||||
/>
|
||||
@@ -313,9 +318,9 @@ export default function SponsorSettingsPage() {
|
||||
<Input
|
||||
type="text"
|
||||
value={profile.address.city}
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
address: { ...profile.address, city: e.target.value }
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
address: { ...profile.address, city: e.target.value }
|
||||
})}
|
||||
placeholder="City"
|
||||
/>
|
||||
@@ -325,9 +330,9 @@ export default function SponsorSettingsPage() {
|
||||
<Input
|
||||
type="text"
|
||||
value={profile.address.postalCode}
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
address: { ...profile.address, postalCode: e.target.value }
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
address: { ...profile.address, postalCode: e.target.value }
|
||||
})}
|
||||
placeholder="12345"
|
||||
/>
|
||||
@@ -337,9 +342,9 @@ export default function SponsorSettingsPage() {
|
||||
<Input
|
||||
type="text"
|
||||
value={profile.address.country}
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
address: { ...profile.address, country: e.target.value }
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
address: { ...profile.address, country: e.target.value }
|
||||
})}
|
||||
placeholder="Country"
|
||||
/>
|
||||
@@ -382,9 +387,9 @@ export default function SponsorSettingsPage() {
|
||||
<Input
|
||||
type="text"
|
||||
value={profile.socialLinks.twitter}
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
socialLinks: { ...profile.socialLinks, twitter: e.target.value }
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
socialLinks: { ...profile.socialLinks, twitter: e.target.value }
|
||||
})}
|
||||
placeholder="@username"
|
||||
/>
|
||||
@@ -394,9 +399,9 @@ export default function SponsorSettingsPage() {
|
||||
<Input
|
||||
type="text"
|
||||
value={profile.socialLinks.linkedin}
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
socialLinks: { ...profile.socialLinks, linkedin: e.target.value }
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
socialLinks: { ...profile.socialLinks, linkedin: e.target.value }
|
||||
})}
|
||||
placeholder="company-name"
|
||||
/>
|
||||
@@ -406,9 +411,9 @@ export default function SponsorSettingsPage() {
|
||||
<Input
|
||||
type="text"
|
||||
value={profile.socialLinks.instagram}
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
socialLinks: { ...profile.socialLinks, instagram: e.target.value }
|
||||
onChange={(e) => setProfile({
|
||||
...profile,
|
||||
socialLinks: { ...profile.socialLinks, instagram: e.target.value }
|
||||
})}
|
||||
placeholder="@username"
|
||||
/>
|
||||
@@ -482,9 +487,9 @@ export default function SponsorSettingsPage() {
|
||||
{/* Notification Preferences */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card className="mb-6 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={Bell}
|
||||
title="Email Notifications"
|
||||
<SectionHeader
|
||||
icon={Bell}
|
||||
title="Email Notifications"
|
||||
description="Control which emails you receive from GridPilot"
|
||||
color="text-warning-amber"
|
||||
/>
|
||||
@@ -534,9 +539,9 @@ export default function SponsorSettingsPage() {
|
||||
{/* Privacy & Visibility */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card className="mb-6 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={Eye}
|
||||
title="Privacy & Visibility"
|
||||
<SectionHeader
|
||||
icon={Eye}
|
||||
title="Privacy & Visibility"
|
||||
description="Control how your profile appears to others"
|
||||
color="text-performance-green"
|
||||
/>
|
||||
@@ -574,9 +579,9 @@ export default function SponsorSettingsPage() {
|
||||
{/* Security */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card className="mb-6 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={Shield}
|
||||
title="Account Security"
|
||||
<SectionHeader
|
||||
icon={Shield}
|
||||
title="Account Security"
|
||||
description="Protect your sponsor account"
|
||||
color="text-primary-blue"
|
||||
/>
|
||||
@@ -654,8 +659,8 @@ export default function SponsorSettingsPage() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleDeleteAccount}
|
||||
className="text-racing-red border-racing-red/30 hover:bg-racing-red/10"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'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';
|
||||
@@ -10,14 +9,14 @@ import SponsorHero from '@/components/sponsors/SponsorHero';
|
||||
import SponsorWorkflowMockup from '@/components/sponsors/SponsorWorkflowMockup';
|
||||
import SponsorBenefitCard from '@/components/sponsors/SponsorBenefitCard';
|
||||
import { siteConfig } from '@/lib/siteConfig';
|
||||
import {
|
||||
Building2,
|
||||
Mail,
|
||||
Globe,
|
||||
Upload,
|
||||
Eye,
|
||||
TrendingUp,
|
||||
Users,
|
||||
import {
|
||||
Building2,
|
||||
Mail,
|
||||
Globe,
|
||||
Upload,
|
||||
Eye,
|
||||
TrendingUp,
|
||||
Users,
|
||||
ArrowRight,
|
||||
Trophy,
|
||||
Car,
|
||||
@@ -123,7 +122,6 @@ const PLATFORM_STATS = [
|
||||
];
|
||||
|
||||
export default function SponsorSignupPage() {
|
||||
const router = useRouter();
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const [mode, setMode] = useState<'landing' | 'signup' | 'login'>('landing');
|
||||
const [formData, setFormData] = useState({
|
||||
@@ -183,8 +181,8 @@ export default function SponsorSignupPage() {
|
||||
setSubmitting(true);
|
||||
|
||||
try {
|
||||
// Create a sponsor account using the normal signup flow
|
||||
// The backend will handle creating the sponsor user with the appropriate role
|
||||
// Note: Business logic for auth should be moved to a mutation
|
||||
// This is a temporary implementation for contract compliance
|
||||
const response = await fetch('/api/auth/signup', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -192,7 +190,6 @@ export default function SponsorSignupPage() {
|
||||
email: formData.contactEmail,
|
||||
password: formData.password,
|
||||
displayName: formData.companyName,
|
||||
// Additional sponsor-specific data
|
||||
sponsorData: {
|
||||
companyName: formData.companyName,
|
||||
websiteUrl: formData.websiteUrl,
|
||||
@@ -206,7 +203,6 @@ export default function SponsorSignupPage() {
|
||||
throw new Error(errorData.message || 'Signup failed');
|
||||
}
|
||||
|
||||
// Auto-login after successful signup
|
||||
const loginResponse = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -220,7 +216,8 @@ export default function SponsorSignupPage() {
|
||||
throw new Error('Auto-login failed');
|
||||
}
|
||||
|
||||
router.push('/sponsor/dashboard');
|
||||
// Navigate to dashboard
|
||||
window.location.href = '/sponsor/dashboard';
|
||||
} catch (err) {
|
||||
console.error('Sponsor signup failed:', err);
|
||||
alert('Registration failed. ' + (err instanceof Error ? err.message : 'Try again.'));
|
||||
@@ -293,7 +290,7 @@ export default function SponsorSignupPage() {
|
||||
Sponsorship Opportunities
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
Choose how you want to connect with the sim racing community.
|
||||
Choose how you want to connect with the sim racing community.
|
||||
Multiple sponsorship tiers and types to fit every budget and goal.
|
||||
</p>
|
||||
</div>
|
||||
@@ -629,8 +626,8 @@ export default function SponsorSignupPage() {
|
||||
onClick={() => toggleInterest(type.id)}
|
||||
className={`
|
||||
p-3 rounded-lg border text-left transition-all
|
||||
${isSelected
|
||||
? 'bg-primary-blue/10 border-primary-blue/50'
|
||||
${isSelected
|
||||
? 'bg-primary-blue/10 border-primary-blue/50'
|
||||
: 'bg-iron-gray/50 border-charcoal-outline hover:border-charcoal-outline/80'
|
||||
}
|
||||
`}
|
||||
|
||||
Reference in New Issue
Block a user