This commit is contained in:
2025-12-05 12:47:20 +01:00
parent 5a9cd28d5b
commit b6c2b4a422
7 changed files with 1093 additions and 713 deletions

View File

@@ -2,10 +2,20 @@
import { useEffect, useState, FormEvent } from 'react';
import { useRouter } from 'next/navigation';
import {
FileText,
Users,
Calendar,
Trophy,
Award,
CheckCircle2,
ChevronLeft,
ChevronRight
} from 'lucide-react';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import Heading from '@/components/ui/Heading';
import LeagueReviewSummary from './LeagueReviewSummary';
import LeagueReviewSummary from '@/components/leagues/LeagueReviewSummary';
import {
getDriverRepository,
getListLeagueScoringPresetsQuery,
@@ -365,12 +375,12 @@ export default function CreateLeagueWizard() {
};
const steps = [
{ id: 1 as Step, label: 'Basics' },
{ id: 2 as Step, label: 'Structure' },
{ id: 3 as Step, label: 'Schedule & timings' },
{ id: 4 as Step, label: 'Scoring pattern' },
{ id: 5 as Step, label: 'Championships & drops' },
{ id: 6 as Step, label: 'Review & confirm' },
{ id: 1 as Step, label: 'Basics', icon: FileText },
{ id: 2 as Step, label: 'Structure', icon: Users },
{ id: 3 as Step, label: 'Schedule', icon: Calendar },
{ id: 4 as Step, label: 'Scoring', icon: Trophy },
{ id: 5 as Step, label: 'Championships', icon: Award },
{ id: 6 as Step, label: 'Review', icon: CheckCircle2 },
];
const getStepTitle = (currentStep: Step): string => {
@@ -412,35 +422,56 @@ export default function CreateLeagueWizard() {
};
return (
<form onSubmit={handleSubmit} className="space-y-6">
<Heading level={1} className="mb-2">
Create a new league
</Heading>
<p className="text-sm text-gray-400 mb-4">
Configure basics, structure, schedule, scoring, and drop rules in a few
simple steps.
</p>
<form onSubmit={handleSubmit} className="space-y-6 max-w-5xl mx-auto">
{/* Header */}
<div className="text-center space-y-3">
<Heading level={1} className="mb-2">
Create a new league
</Heading>
<p className="text-sm text-gray-400">
Configure your league in {steps.length} simple steps
</p>
</div>
<div className="mb-4 flex flex-col gap-2">
<div className="flex flex-wrap gap-3">
{/* Progress indicators */}
<div className="relative">
<div className="flex items-center justify-between mb-8">
{steps.map((wizardStep, index) => {
const isCompleted = wizardStep.id < step;
const isCurrent = wizardStep.id === step;
const baseCircleClasses =
'flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold';
const circleClasses = isCurrent
? 'bg-primary-blue text-white'
: isCompleted
? 'bg-primary-blue/20 border border-primary-blue text-primary-blue'
: 'bg-iron-gray border border-charcoal-outline text-gray-400';
const StepIcon = wizardStep.icon;
return (
<div key={wizardStep.id} className="flex items-center gap-2">
<div className={baseCircleClasses + ' ' + circleClasses}>
{isCompleted ? '✓' : wizardStep.id}
<div key={wizardStep.id} className="flex flex-col items-center gap-2 flex-1">
<div className="relative flex items-center justify-center">
{index > 0 && (
<div
className={`absolute right-1/2 top-1/2 -translate-y-1/2 h-0.5 transition-all duration-300 ${
isCompleted || isCurrent
? 'bg-primary-blue'
: 'bg-charcoal-outline'
}`}
style={{ width: 'calc(100vw / 6 - 48px)', maxWidth: '120px' }}
/>
)}
<div
className={`relative z-10 flex h-12 w-12 items-center justify-center rounded-full transition-all duration-200 ${
isCurrent
? 'bg-primary-blue text-white shadow-[0_0_20px_rgba(25,140,255,0.5)] scale-110'
: isCompleted
? 'bg-primary-blue/20 border-2 border-primary-blue text-primary-blue'
: 'bg-iron-gray border-2 border-charcoal-outline text-gray-500'
}`}
>
{isCompleted ? (
<CheckCircle2 className="w-5 h-5" />
) : (
<StepIcon className="w-5 h-5" />
)}
</div>
</div>
<span
className={`text-xs ${
className={`text-xs font-medium transition-colors duration-200 ${
isCurrent
? 'text-white'
: isCompleted
@@ -450,134 +481,180 @@ export default function CreateLeagueWizard() {
>
{wizardStep.label}
</span>
{index < steps.length - 1 && (
<span className="mx-1 h-px w-6 bg-charcoal-outline/70" />
)}
</div>
);
})}
</div>
</div>
<Card>
<div>
<Heading level={2} className="text-2xl text-white">
{getStepTitle(step)}
</Heading>
<p className="mt-1 text-sm text-gray-400">
{getStepSubtitle(step)}
</p>
<hr className="my-4 border-charcoal-outline/40" />
{/* Main content card */}
<Card className="relative overflow-hidden">
{/* Decorative gradient */}
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-primary-blue/50 via-primary-blue to-primary-blue/50" />
<div className="space-y-6">
{/* Step header */}
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary-blue/10">
{(() => {
const currentStepData = steps.find((s) => s.id === step);
if (!currentStepData) return null;
const Icon = currentStepData.icon;
return <Icon className="w-5 h-5 text-primary-blue" />;
})()}
</div>
<div className="flex-1">
<Heading level={2} className="text-2xl text-white">
{getStepTitle(step)}
</Heading>
<p className="text-sm text-gray-400">
{getStepSubtitle(step)}
</p>
</div>
<span className="text-xs font-medium text-gray-500">
Step {step} of {steps.length}
</span>
</div>
</div>
<hr className="border-charcoal-outline/40" />
{/* Step content */}
<div className="min-h-[400px]">
{step === 1 && (
<LeagueBasicsSection
form={form}
onChange={setForm}
errors={errors.basics}
/>
)}
{step === 2 && (
<LeagueStructureSection
form={form}
onChange={setForm}
readOnly={false}
/>
)}
{step === 3 && (
<LeagueTimingsSection
form={form}
onChange={setForm}
errors={errors.timings}
weekendTemplate={weekendTemplate}
onWeekendTemplateChange={handleWeekendTemplateChange}
/>
)}
{step === 4 && (
<div className="space-y-4">
<ScoringPatternSection
scoring={form.scoring}
presets={presets}
readOnly={presetsLoading}
patternError={errors.scoring?.patternId}
onChangePatternId={(patternId) =>
setForm((prev) => ({
...prev,
scoring: {
...prev.scoring,
patternId,
customScoringEnabled: false,
},
}))
}
onToggleCustomScoring={() =>
setForm((prev) => ({
...prev,
scoring: {
...prev.scoring,
customScoringEnabled: !prev.scoring.customScoringEnabled,
},
}))
}
/>
</div>
)}
{step === 5 && (
<div className="space-y-6">
<div className="space-y-6">
<ChampionshipsSection form={form} onChange={setForm} readOnly={presetsLoading} />
</div>
<div className="space-y-3">
<LeagueDropSection form={form} onChange={setForm} readOnly={false} />
</div>
{errors.submit && (
<div className="rounded-lg bg-warning-amber/10 p-4 border border-warning-amber/20 text-sm text-warning-amber flex items-start gap-3">
<div className="shrink-0 mt-0.5"></div>
<div>{errors.submit}</div>
</div>
)}
</div>
)}
{step === 6 && (
<div className="space-y-6">
<LeagueReviewSummary form={form} presets={presets} />
{errors.submit && (
<div className="rounded-lg bg-warning-amber/10 p-4 border border-warning-amber/20 text-sm text-warning-amber flex items-start gap-3">
<div className="shrink-0 mt-0.5"></div>
<div>{errors.submit}</div>
</div>
)}
</div>
)}
</div>
</div>
{step === 1 && (
<LeagueBasicsSection
form={form}
onChange={setForm}
errors={errors.basics}
/>
)}
{step === 2 && (
<LeagueStructureSection
form={form}
onChange={setForm}
readOnly={false}
/>
)}
{step === 3 && (
<LeagueTimingsSection
form={form}
onChange={setForm}
errors={errors.timings}
weekendTemplate={weekendTemplate}
onWeekendTemplateChange={handleWeekendTemplateChange}
/>
)}
{step === 4 && (
<div className="space-y-4">
<ScoringPatternSection
scoring={form.scoring}
presets={presets}
readOnly={presetsLoading}
patternError={errors.scoring?.patternId}
onChangePatternId={(patternId) =>
setForm((prev) => ({
...prev,
scoring: {
...prev.scoring,
patternId,
customScoringEnabled: false,
},
}))
}
onToggleCustomScoring={() =>
setForm((prev) => ({
...prev,
scoring: {
...prev.scoring,
customScoringEnabled: !prev.scoring.customScoringEnabled,
},
}))
}
/>
</div>
)}
{step === 5 && (
<div className="space-y-6">
<div className="space-y-6">
<ChampionshipsSection form={form} onChange={setForm} readOnly={presetsLoading} />
</div>
<div className="space-y-3">
<LeagueDropSection form={form} onChange={setForm} readOnly={false} />
</div>
{errors.submit && (
<div className="rounded-md bg-warning-amber/10 p-3 border border-warning-amber/20 text-xs text-warning-amber">
{errors.submit}
</div>
)}
</div>
)}
{step === 6 && (
<div className="space-y-6">
<LeagueReviewSummary form={form} presets={presets} />
{errors.submit && (
<div className="rounded-md bg-warning-amber/10 p-3 border border-warning-amber/20 text-xs text-warning-amber">
{errors.submit}
</div>
)}
</div>
)}
</Card>
<div className="flex justify-between items-center">
{/* Navigation buttons */}
<div className="flex justify-between items-center pt-2">
<Button
type="button"
variant="secondary"
disabled={step === 1 || loading}
onClick={goToPreviousStep}
className="flex items-center gap-2"
>
<ChevronLeft className="w-4 h-4" />
Back
</Button>
<div className="flex gap-2">
<div className="flex gap-3">
{step < 6 && (
<Button
type="button"
variant="primary"
disabled={loading}
onClick={goToNextStep}
className="flex items-center gap-2"
>
Next
Continue
<ChevronRight className="w-4 h-4" />
</Button>
)}
{step === 6 && (
<Button type="submit" variant="primary" disabled={loading}>
{loading ? 'Creating…' : 'Create league'}
<Button
type="submit"
variant="primary"
disabled={loading}
className="flex items-center gap-2 min-w-[160px] justify-center"
>
{loading ? (
<>
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
Creating
</>
) : (
<>
<CheckCircle2 className="w-4 h-4" />
Create league
</>
)}
</Button>
)}
</div>