Files
syndarix/frontend/src/components/projects/wizard/ProjectWizard.tsx
Felipe Cardoso 924fbbda5d fix(frontend): remove locale-dependent routing and migrate to centralized locale-aware router
- Replaced `next/navigation` with `@/lib/i18n/routing` across components, pages, and tests.
- Removed redundant `locale` props from `ProjectWizard` and related pages.
- Updated navigation to exclude explicit `locale` in paths.
- Refactored tests to use mocks from `next-intl/navigation`.
2026-01-03 01:34:53 +01:00

224 lines
7.1 KiB
TypeScript

'use client';
/**
* Project Creation Wizard
*
* Multi-step wizard for creating new Syndarix projects.
* Adapts based on project complexity - scripts use a simplified 4-step flow.
*/
import { useState } from 'react';
import { useRouter } from '@/lib/i18n/routing';
import { ArrowLeft, ArrowRight, Check, CheckCircle2, Loader2 } from 'lucide-react';
import { useMutation } from '@tanstack/react-query';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { apiClient } from '@/lib/api/client';
import { StepIndicator } from './StepIndicator';
import { useWizardState, type ProjectCreateData } from './useWizardState';
import { WIZARD_STEPS } from './constants';
import {
BasicInfoStep,
ComplexityStep,
ClientModeStep,
AutonomyStep,
AgentChatStep,
ReviewStep,
} from './steps';
/**
* Project response from API
*/
interface ProjectResponse {
id: string;
name: string;
slug: string;
description: string | null;
autonomy_level: string;
status: string;
settings: Record<string, unknown>;
owner_id: string | null;
created_at: string;
updated_at: string;
agent_count: number;
issue_count: number;
active_sprint_name: string | null;
}
interface ProjectWizardProps {
className?: string;
}
export function ProjectWizard({ className }: ProjectWizardProps) {
const router = useRouter();
const [isCreated, setIsCreated] = useState(false);
const {
state,
updateState,
resetState,
isScriptMode,
canProceed,
goNext,
goBack,
getProjectData,
} = useWizardState();
// Project creation mutation using the configured API client
const createProjectMutation = useMutation({
mutationFn: async (projectData: ProjectCreateData): Promise<ProjectResponse> => {
// Call the projects API endpoint
// Note: The API client already handles authentication via interceptors
const response = await apiClient.instance.post<ProjectResponse>('/api/v1/projects', {
name: projectData.name,
slug: projectData.slug,
description: projectData.description,
autonomy_level: projectData.autonomy_level,
settings: projectData.settings,
});
return response.data;
},
onSuccess: () => {
setIsCreated(true);
},
onError: (error) => {
// Error handling - in production, show toast notification
console.error('Failed to create project:', error);
},
});
const handleCreate = () => {
const projectData = getProjectData();
createProjectMutation.mutate(projectData);
};
const handleReset = () => {
resetState();
setIsCreated(false);
createProjectMutation.reset();
};
const handleGoToProject = () => {
// Navigate to project dashboard - using slug from successful creation
if (createProjectMutation.data) {
router.push(`/projects/${createProjectMutation.data.slug}`);
} else {
router.push(`/projects`);
}
};
// Success screen
if (isCreated && createProjectMutation.data) {
return (
<div className={className}>
<div className="mx-auto max-w-2xl">
<Card className="text-center">
<CardContent className="space-y-6 p-8">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-green-100 dark:bg-green-900">
<CheckCircle2
className="h-8 w-8 text-green-600 dark:text-green-400"
aria-hidden="true"
/>
</div>
<div>
<h2 className="text-2xl font-bold">Project Created Successfully!</h2>
<p className="mt-2 text-muted-foreground">
&quot;{createProjectMutation.data.name}&quot; has been created. The Product Owner
agent will begin the requirements discovery process shortly.
</p>
</div>
<div className="flex flex-col justify-center gap-4 sm:flex-row">
<Button onClick={handleGoToProject}>Go to Project Dashboard</Button>
<Button variant="outline" onClick={handleReset}>
Create Another Project
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
);
}
return (
<div className={className}>
<div className="mx-auto max-w-3xl">
{/* Step Indicator */}
<div className="mb-8">
<StepIndicator currentStep={state.step} isScriptMode={isScriptMode} />
</div>
{/* Step Content */}
<Card>
<CardContent className="p-6 md:p-8">
{state.step === WIZARD_STEPS.BASIC_INFO && (
<BasicInfoStep state={state} updateState={updateState} />
)}
{state.step === WIZARD_STEPS.COMPLEXITY && (
<ComplexityStep state={state} updateState={updateState} />
)}
{state.step === WIZARD_STEPS.CLIENT_MODE && !isScriptMode && (
<ClientModeStep state={state} updateState={updateState} />
)}
{state.step === WIZARD_STEPS.AUTONOMY && !isScriptMode && (
<AutonomyStep state={state} updateState={updateState} />
)}
{state.step === WIZARD_STEPS.AGENT_CHAT && <AgentChatStep />}
{state.step === WIZARD_STEPS.REVIEW && <ReviewStep state={state} />}
</CardContent>
{/* Navigation Footer */}
<Separator />
<div className="flex items-center justify-between p-6">
<Button
variant="ghost"
onClick={goBack}
disabled={state.step === WIZARD_STEPS.BASIC_INFO}
className={state.step === WIZARD_STEPS.BASIC_INFO ? 'invisible' : ''}
>
<ArrowLeft className="mr-2 h-4 w-4" aria-hidden="true" />
Back
</Button>
<div className="flex gap-2">
{state.step < WIZARD_STEPS.REVIEW ? (
<Button onClick={goNext} disabled={!canProceed}>
Next
<ArrowRight className="ml-2 h-4 w-4" aria-hidden="true" />
</Button>
) : (
<Button onClick={handleCreate} disabled={createProjectMutation.isPending}>
{createProjectMutation.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
Creating...
</>
) : (
<>
<Check className="mr-2 h-4 w-4" aria-hidden="true" />
Create Project
</>
)}
</Button>
)}
</div>
</div>
{/* Error display */}
{createProjectMutation.isError && (
<div className="border-t bg-destructive/10 p-4">
<p className="text-sm text-destructive">
Failed to create project. Please try again.
</p>
</div>
)}
</Card>
</div>
</div>
);
}