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:
2025-12-30 23:46:50 +01:00
parent 9b41571967
commit 551dbb7293
67 changed files with 8879 additions and 0 deletions

View 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">
&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>
);
}