forked from cardosofelipe/pragma-stack
feat(frontend): implement main dashboard page (#48)
Implement the main dashboard / projects list page for Syndarix as the landing page after login. The implementation includes: Dashboard Components: - QuickStats: Overview cards showing active projects, agents, issues, approvals - ProjectsSection: Grid/list view with filtering and sorting controls - ProjectCardGrid: Rich project cards for grid view - ProjectRowList: Compact rows for list view - ActivityFeed: Real-time activity sidebar with connection status - PerformanceCard: Performance metrics display - EmptyState: Call-to-action for new users - ProjectStatusBadge: Status indicator with icons - ComplexityIndicator: Visual complexity dots - ProgressBar: Accessible progress bar component Features: - Projects grid/list view with view mode toggle - Filter by status (all, active, paused, completed, archived) - Sort by recent, name, progress, or issues - Quick stats overview with counts - Real-time activity feed sidebar with live/reconnecting status - Performance metrics card - Create project button linking to wizard - Responsive layout for mobile/desktop - Loading skeleton states - Empty state for new users API Integration: - useProjects hook for fetching projects (mock data until backend ready) - useDashboardStats hook for statistics - TanStack Query for caching and data fetching Testing: - 37 unit tests covering all dashboard components - E2E test suite for dashboard functionality - Accessibility tests (keyboard nav, aria attributes, heading hierarchy) Technical: - TypeScript strict mode compliance - ESLint passing - WCAG AA accessibility compliance - Mobile-first responsive design - Dark mode support via semantic tokens - Follows design system guidelines
This commit is contained in:
227
frontend/src/components/projects/wizard/ProjectWizard.tsx
Normal file
227
frontend/src/components/projects/wizard/ProjectWizard.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
'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 'next/navigation';
|
||||
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 {
|
||||
locale: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ProjectWizard({ locale, 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(`/${locale}/projects/${createProjectMutation.data.slug}`);
|
||||
} else {
|
||||
router.push(`/${locale}/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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user