forked from cardosofelipe/pragma-stack
- 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`.
224 lines
7.1 KiB
TypeScript
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">
|
|
"{createProjectMutation.data.name}" 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>
|
|
);
|
|
}
|