wip
This commit is contained in:
@@ -10,7 +10,11 @@ import {
|
||||
Award,
|
||||
CheckCircle2,
|
||||
ChevronLeft,
|
||||
ChevronRight
|
||||
ChevronRight,
|
||||
Loader2,
|
||||
AlertCircle,
|
||||
Sparkles,
|
||||
Check,
|
||||
} from 'lucide-react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
@@ -33,7 +37,7 @@ import {
|
||||
import { LeagueDropSection } from './LeagueDropSection';
|
||||
import { LeagueTimingsSection } from './LeagueTimingsSection';
|
||||
|
||||
type Step = 1 | 2 | 3 | 4 | 5 | 6;
|
||||
type Step = 1 | 2 | 3 | 4 | 5;
|
||||
|
||||
interface WizardErrors {
|
||||
basics?: {
|
||||
@@ -109,11 +113,6 @@ export default function CreateLeagueWizard() {
|
||||
const [form, setForm] = useState<LeagueConfigFormModel>(() =>
|
||||
createDefaultForm(),
|
||||
);
|
||||
/**
|
||||
* Local-only weekend template selection for Step 3.
|
||||
* This does not touch domain models; it only seeds timing defaults.
|
||||
*/
|
||||
const [weekendTemplate, setWeekendTemplate] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
async function loadPresets() {
|
||||
@@ -229,7 +228,7 @@ export default function CreateLeagueWizard() {
|
||||
if (!validateStep(step)) {
|
||||
return;
|
||||
}
|
||||
setStep((prev) => (prev < 6 ? ((prev + 1) as Step) : prev));
|
||||
setStep((prev) => (prev < 5 ? ((prev + 1) as Step) : prev));
|
||||
};
|
||||
|
||||
const goToPreviousStep = () => {
|
||||
@@ -244,8 +243,7 @@ export default function CreateLeagueWizard() {
|
||||
!validateStep(1) ||
|
||||
!validateStep(2) ||
|
||||
!validateStep(3) ||
|
||||
!validateStep(4) ||
|
||||
!validateStep(5)
|
||||
!validateStep(4)
|
||||
) {
|
||||
setStep(1);
|
||||
return;
|
||||
@@ -326,77 +324,77 @@ export default function CreateLeagueWizard() {
|
||||
const currentPreset =
|
||||
presets.find((p) => p.id === form.scoring.patternId) ?? null;
|
||||
|
||||
const handleWeekendTemplateChange = (template: string) => {
|
||||
setWeekendTemplate(template);
|
||||
|
||||
// Handler for scoring preset selection - updates timing defaults based on preset
|
||||
const handleScoringPresetChange = (patternId: string) => {
|
||||
const lowerPresetId = patternId.toLowerCase();
|
||||
|
||||
setForm((prev) => {
|
||||
const timings = prev.timings ?? {};
|
||||
if (template === 'feature') {
|
||||
return {
|
||||
...prev,
|
||||
timings: {
|
||||
...timings,
|
||||
practiceMinutes: 20,
|
||||
qualifyingMinutes: 30,
|
||||
sprintRaceMinutes: undefined,
|
||||
mainRaceMinutes: 40,
|
||||
sessionCount: 1,
|
||||
},
|
||||
let updatedTimings = { ...timings };
|
||||
|
||||
// Auto-configure session durations based on preset type
|
||||
if (lowerPresetId.includes('sprint') || lowerPresetId.includes('double')) {
|
||||
updatedTimings = {
|
||||
...updatedTimings,
|
||||
practiceMinutes: 15,
|
||||
qualifyingMinutes: 20,
|
||||
sprintRaceMinutes: 20,
|
||||
mainRaceMinutes: 35,
|
||||
sessionCount: 2,
|
||||
};
|
||||
} else if (lowerPresetId.includes('endurance') || lowerPresetId.includes('long')) {
|
||||
updatedTimings = {
|
||||
...updatedTimings,
|
||||
practiceMinutes: 30,
|
||||
qualifyingMinutes: 30,
|
||||
sprintRaceMinutes: undefined,
|
||||
mainRaceMinutes: 90,
|
||||
sessionCount: 1,
|
||||
};
|
||||
} else {
|
||||
// Standard/feature format
|
||||
updatedTimings = {
|
||||
...updatedTimings,
|
||||
practiceMinutes: 20,
|
||||
qualifyingMinutes: 30,
|
||||
sprintRaceMinutes: undefined,
|
||||
mainRaceMinutes: 40,
|
||||
sessionCount: 1,
|
||||
};
|
||||
}
|
||||
if (template === 'sprintFeature') {
|
||||
return {
|
||||
...prev,
|
||||
timings: {
|
||||
...timings,
|
||||
practiceMinutes: 15,
|
||||
qualifyingMinutes: 20,
|
||||
sprintRaceMinutes: 20,
|
||||
mainRaceMinutes: 35,
|
||||
sessionCount: 2,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (template === 'endurance') {
|
||||
return {
|
||||
...prev,
|
||||
timings: {
|
||||
...timings,
|
||||
practiceMinutes: 30,
|
||||
qualifyingMinutes: 30,
|
||||
sprintRaceMinutes: undefined,
|
||||
mainRaceMinutes: 90,
|
||||
sessionCount: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
return prev;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
scoring: {
|
||||
...prev.scoring,
|
||||
patternId,
|
||||
customScoringEnabled: false,
|
||||
},
|
||||
timings: updatedTimings,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const steps = [
|
||||
{ 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 },
|
||||
{ id: 1 as Step, label: 'Basics', icon: FileText, shortLabel: 'Name' },
|
||||
{ id: 2 as Step, label: 'Structure', icon: Users, shortLabel: 'Mode' },
|
||||
{ id: 3 as Step, label: 'Schedule', icon: Calendar, shortLabel: 'Time' },
|
||||
{ id: 4 as Step, label: 'Scoring', icon: Trophy, shortLabel: 'Points' },
|
||||
{ id: 5 as Step, label: 'Review', icon: CheckCircle2, shortLabel: 'Done' },
|
||||
];
|
||||
|
||||
const getStepTitle = (currentStep: Step): string => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return 'Step 1 — Basics';
|
||||
return 'Name your league';
|
||||
case 2:
|
||||
return 'Step 2 — Structure';
|
||||
return 'Choose the structure';
|
||||
case 3:
|
||||
return 'Step 3 — Schedule & timings';
|
||||
return 'Set the schedule';
|
||||
case 4:
|
||||
return 'Step 4 — Scoring pattern';
|
||||
return 'Scoring & championships';
|
||||
case 5:
|
||||
return 'Step 5 — Championships & drops';
|
||||
case 6:
|
||||
return 'Step 6 — Review & confirm';
|
||||
return 'Review & create';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -405,215 +403,249 @@ export default function CreateLeagueWizard() {
|
||||
const getStepSubtitle = (currentStep: Step): string => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return 'Give your league a clear name, description, and visibility.';
|
||||
return 'Give your league a memorable name and choose who can join.';
|
||||
case 2:
|
||||
return 'Choose whether this is a solo or team-based championship.';
|
||||
return 'Will drivers compete individually or as part of teams?';
|
||||
case 3:
|
||||
return 'Roughly outline how long your weekends and season should run.';
|
||||
return 'Configure session durations and plan your season calendar.';
|
||||
case 4:
|
||||
return 'Pick a scoring pattern that matches your weekends.';
|
||||
return 'Select a scoring preset, enable championships, and set drop rules.';
|
||||
case 5:
|
||||
return 'Decide which championships to track and how drops behave.';
|
||||
case 6:
|
||||
return 'Double-check the summary before creating your new league.';
|
||||
return 'Everything looks good? Launch your new league!';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const currentStepData = steps.find((s) => s.id === step);
|
||||
const CurrentStepIcon = currentStepData?.icon ?? FileText;
|
||||
|
||||
return (
|
||||
<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>
|
||||
|
||||
{/* 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 StepIcon = wizardStep.icon;
|
||||
|
||||
return (
|
||||
<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 font-medium transition-colors duration-200 ${
|
||||
isCurrent
|
||||
? 'text-white'
|
||||
: isCompleted
|
||||
? 'text-gray-300'
|
||||
: 'text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{wizardStep.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<form onSubmit={handleSubmit} className="max-w-4xl mx-auto pb-8">
|
||||
{/* Header with icon */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-gradient-to-br from-primary-blue/20 to-primary-blue/5 border border-primary-blue/20">
|
||||
<Sparkles className="w-5 h-5 text-primary-blue" />
|
||||
</div>
|
||||
<div>
|
||||
<Heading level={1} className="text-2xl sm:text-3xl">
|
||||
Create a new league
|
||||
</Heading>
|
||||
<p className="text-sm text-gray-500">
|
||||
Set up your racing series in {steps.length} easy steps
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
{/* Desktop Progress Bar */}
|
||||
<div className="hidden md:block mb-8">
|
||||
<div className="relative">
|
||||
{/* Background track */}
|
||||
<div className="absolute top-5 left-6 right-6 h-0.5 bg-charcoal-outline rounded-full" />
|
||||
{/* Progress fill */}
|
||||
<div
|
||||
className="absolute top-5 left-6 h-0.5 bg-gradient-to-r from-primary-blue to-neon-aqua rounded-full transition-all duration-500 ease-out"
|
||||
style={{ width: `calc(${((step - 1) / (steps.length - 1)) * 100}% - 48px)` }}
|
||||
/>
|
||||
|
||||
<div className="relative flex justify-between">
|
||||
{steps.map((wizardStep) => {
|
||||
const isCompleted = wizardStep.id < step;
|
||||
const isCurrent = wizardStep.id === step;
|
||||
const StepIcon = wizardStep.icon;
|
||||
|
||||
return (
|
||||
<div key={wizardStep.id} className="flex flex-col items-center">
|
||||
<div
|
||||
className={`
|
||||
relative z-10 flex h-10 w-10 items-center justify-center rounded-full
|
||||
transition-all duration-300 ease-out
|
||||
${isCurrent
|
||||
? 'bg-primary-blue text-white shadow-[0_0_24px_rgba(25,140,255,0.5)] scale-110'
|
||||
: isCompleted
|
||||
? 'bg-primary-blue text-white'
|
||||
: 'bg-iron-gray text-gray-500 border-2 border-charcoal-outline'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{isCompleted ? (
|
||||
<Check className="w-4 h-4" strokeWidth={3} />
|
||||
) : (
|
||||
<StepIcon className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-2 text-center">
|
||||
<p
|
||||
className={`text-xs font-medium transition-colors duration-200 ${
|
||||
isCurrent
|
||||
? 'text-white'
|
||||
: isCompleted
|
||||
? 'text-primary-blue'
|
||||
: 'text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{wizardStep.label}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="border-charcoal-outline/40" />
|
||||
{/* Mobile Progress */}
|
||||
<div className="md:hidden mb-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<CurrentStepIcon className="w-4 h-4 text-primary-blue" />
|
||||
<span className="text-sm font-medium text-white">{currentStepData?.label}</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">
|
||||
{step}/{steps.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-1.5 bg-charcoal-outline rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-primary-blue to-neon-aqua rounded-full transition-all duration-500 ease-out"
|
||||
style={{ width: `${(step / steps.length) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
{/* Step dots */}
|
||||
<div className="flex justify-between mt-2 px-0.5">
|
||||
{steps.map((s) => (
|
||||
<div
|
||||
key={s.id}
|
||||
className={`
|
||||
h-1.5 rounded-full transition-all duration-300
|
||||
${s.id === step
|
||||
? 'w-4 bg-primary-blue'
|
||||
: s.id < step
|
||||
? 'w-1.5 bg-primary-blue/60'
|
||||
: 'w-1.5 bg-charcoal-outline'
|
||||
}
|
||||
`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step content */}
|
||||
<div className="min-h-[400px]">
|
||||
{/* Main Card */}
|
||||
<Card className="relative overflow-hidden">
|
||||
{/* Top gradient accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-transparent via-primary-blue to-transparent" />
|
||||
|
||||
{step === 1 && (
|
||||
{/* Step header */}
|
||||
<div className="flex items-start gap-4 mb-6">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-blue/10 shrink-0 transition-transform duration-300">
|
||||
<CurrentStepIcon className="w-6 h-6 text-primary-blue" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<Heading level={2} className="text-xl sm:text-2xl text-white leading-tight">
|
||||
{getStepTitle(step)}
|
||||
</Heading>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
{getStepSubtitle(step)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden sm:flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-deep-graphite border border-charcoal-outline">
|
||||
<span className="text-xs text-gray-500">Step</span>
|
||||
<span className="text-sm font-semibold text-white">{step}</span>
|
||||
<span className="text-xs text-gray-500">/ {steps.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="h-px bg-gradient-to-r from-transparent via-charcoal-outline to-transparent mb-6" />
|
||||
|
||||
{/* Step content with min-height for consistency */}
|
||||
<div className="min-h-[320px]">
|
||||
{step === 1 && (
|
||||
<div className="animate-fade-in">
|
||||
<LeagueBasicsSection
|
||||
form={form}
|
||||
onChange={setForm}
|
||||
errors={errors.basics}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
{step === 2 && (
|
||||
<div className="animate-fade-in">
|
||||
<LeagueStructureSection
|
||||
form={form}
|
||||
onChange={setForm}
|
||||
readOnly={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 3 && (
|
||||
{step === 3 && (
|
||||
<div className="animate-fade-in">
|
||||
<LeagueTimingsSection
|
||||
form={form}
|
||||
onChange={setForm}
|
||||
errors={errors.timings}
|
||||
weekendTemplate={weekendTemplate}
|
||||
onWeekendTemplateChange={handleWeekendTemplateChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{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,
|
||||
},
|
||||
}))
|
||||
}
|
||||
/>
|
||||
{step === 4 && (
|
||||
<div className="animate-fade-in space-y-8">
|
||||
{/* Scoring Pattern Selection */}
|
||||
<ScoringPatternSection
|
||||
scoring={form.scoring}
|
||||
presets={presets}
|
||||
readOnly={presetsLoading}
|
||||
patternError={errors.scoring?.patternId}
|
||||
onChangePatternId={handleScoringPresetChange}
|
||||
onToggleCustomScoring={() =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
scoring: {
|
||||
...prev.scoring,
|
||||
customScoringEnabled: !prev.scoring.customScoringEnabled,
|
||||
},
|
||||
}))
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="h-px bg-gradient-to-r from-transparent via-charcoal-outline to-transparent" />
|
||||
|
||||
{/* Championships & Drop Rules side by side on larger screens */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<ChampionshipsSection form={form} onChange={setForm} readOnly={presetsLoading} />
|
||||
<LeagueDropSection form={form} onChange={setForm} readOnly={false} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 5 && (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-6">
|
||||
<ChampionshipsSection form={form} onChange={setForm} readOnly={presetsLoading} />
|
||||
|
||||
{errors.submit && (
|
||||
<div className="flex items-start gap-3 rounded-lg bg-warning-amber/10 p-4 border border-warning-amber/20">
|
||||
<AlertCircle className="w-5 h-5 text-warning-amber shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-warning-amber">{errors.submit}</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<LeagueDropSection form={form} onChange={setForm} readOnly={false} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 5 && (
|
||||
<div className="animate-fade-in space-y-6">
|
||||
<LeagueReviewSummary form={form} presets={presets} />
|
||||
{errors.submit && (
|
||||
<div className="flex items-start gap-3 rounded-lg bg-warning-amber/10 p-4 border border-warning-amber/20">
|
||||
<AlertCircle className="w-5 h-5 text-warning-amber shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-warning-amber">{errors.submit}</p>
|
||||
</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>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Navigation buttons */}
|
||||
<div className="flex justify-between items-center pt-2">
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between items-center mt-6">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
@@ -622,10 +654,24 @@ export default function CreateLeagueWizard() {
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
Back
|
||||
<span className="hidden sm:inline">Back</span>
|
||||
</Button>
|
||||
<div className="flex gap-3">
|
||||
{step < 6 && (
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Mobile step dots */}
|
||||
<div className="flex sm:hidden items-center gap-1">
|
||||
{steps.map((s) => (
|
||||
<div
|
||||
key={s.id}
|
||||
className={`
|
||||
h-1.5 rounded-full transition-all duration-300
|
||||
${s.id === step ? 'w-3 bg-primary-blue' : s.id < step ? 'w-1.5 bg-primary-blue/50' : 'w-1.5 bg-charcoal-outline'}
|
||||
`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{step < 5 ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
@@ -633,32 +679,36 @@ export default function CreateLeagueWizard() {
|
||||
onClick={goToNextStep}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
Continue
|
||||
<span>Continue</span>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
{step === 6 && (
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
disabled={loading}
|
||||
className="flex items-center gap-2 min-w-[160px] justify-center"
|
||||
className="flex items-center gap-2 min-w-[150px] justify-center"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||
Creating…
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
<span>Creating…</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
Create league
|
||||
<Sparkles className="w-4 h-4" />
|
||||
<span>Create League</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Helper text */}
|
||||
<p className="text-center text-xs text-gray-500 mt-4">
|
||||
You can edit all settings after creating your league
|
||||
</p>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user