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
165 lines
4.3 KiB
TypeScript
165 lines
4.3 KiB
TypeScript
/**
|
|
* Custom hook for managing wizard state
|
|
*
|
|
* Handles step navigation logic including script mode shortcuts.
|
|
*/
|
|
|
|
import { useState, useCallback } from 'react';
|
|
|
|
import type { WizardState, WizardStep } from './types';
|
|
import { initialWizardState } from './types';
|
|
import { WIZARD_STEPS } from './constants';
|
|
|
|
interface UseWizardStateReturn {
|
|
state: WizardState;
|
|
updateState: (updates: Partial<WizardState>) => void;
|
|
resetState: () => void;
|
|
isScriptMode: boolean;
|
|
canProceed: boolean;
|
|
goNext: () => void;
|
|
goBack: () => void;
|
|
getProjectData: () => ProjectCreateData;
|
|
}
|
|
|
|
/**
|
|
* Data structure for project creation API call
|
|
*/
|
|
export interface ProjectCreateData {
|
|
name: string;
|
|
slug: string;
|
|
description: string | undefined;
|
|
autonomy_level: 'full_control' | 'milestone' | 'autonomous';
|
|
settings: {
|
|
complexity: string;
|
|
client_mode: string;
|
|
repo_url?: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate a URL-safe slug from a project name
|
|
*/
|
|
function generateSlug(name: string): string {
|
|
return name
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/[^\w\s-]/g, '') // Remove special characters
|
|
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
.replace(/-+/g, '-') // Replace multiple hyphens with single
|
|
.replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens
|
|
}
|
|
|
|
export function useWizardState(): UseWizardStateReturn {
|
|
const [state, setState] = useState<WizardState>(initialWizardState);
|
|
|
|
const isScriptMode = state.complexity === 'script';
|
|
|
|
const updateState = useCallback((updates: Partial<WizardState>) => {
|
|
setState((prev) => ({ ...prev, ...updates }));
|
|
}, []);
|
|
|
|
const resetState = useCallback(() => {
|
|
setState(initialWizardState);
|
|
}, []);
|
|
|
|
/**
|
|
* Check if user can proceed to next step
|
|
*/
|
|
const canProceed = (() => {
|
|
switch (state.step) {
|
|
case WIZARD_STEPS.BASIC_INFO:
|
|
return state.projectName.trim().length >= 3;
|
|
case WIZARD_STEPS.COMPLEXITY:
|
|
return state.complexity !== null;
|
|
case WIZARD_STEPS.CLIENT_MODE:
|
|
return isScriptMode || state.clientMode !== null;
|
|
case WIZARD_STEPS.AUTONOMY:
|
|
return isScriptMode || state.autonomyLevel !== null;
|
|
case WIZARD_STEPS.AGENT_CHAT:
|
|
return true; // Agent chat is preview only
|
|
case WIZARD_STEPS.REVIEW:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* Navigate to next step, handling script mode skip logic
|
|
*/
|
|
const goNext = useCallback(() => {
|
|
if (!canProceed) return;
|
|
|
|
setState((prev) => {
|
|
let nextStep = (prev.step + 1) as WizardStep;
|
|
const currentIsScriptMode = prev.complexity === 'script';
|
|
|
|
// For scripts, skip from step 2 directly to step 5 (agent chat)
|
|
if (currentIsScriptMode && prev.step === WIZARD_STEPS.COMPLEXITY) {
|
|
return {
|
|
...prev,
|
|
step: WIZARD_STEPS.AGENT_CHAT as WizardStep,
|
|
clientMode: 'auto',
|
|
autonomyLevel: 'autonomous',
|
|
};
|
|
}
|
|
|
|
// Don't go past review step
|
|
if (nextStep > WIZARD_STEPS.REVIEW) {
|
|
nextStep = WIZARD_STEPS.REVIEW as WizardStep;
|
|
}
|
|
|
|
return { ...prev, step: nextStep };
|
|
});
|
|
}, [canProceed]);
|
|
|
|
/**
|
|
* Navigate to previous step, handling script mode skip logic
|
|
*/
|
|
const goBack = useCallback(() => {
|
|
setState((prev) => {
|
|
if (prev.step <= 1) return prev;
|
|
|
|
let prevStep = (prev.step - 1) as WizardStep;
|
|
const currentIsScriptMode = prev.complexity === 'script';
|
|
|
|
// For scripts, go from step 5 back to step 2
|
|
if (currentIsScriptMode && prev.step === WIZARD_STEPS.AGENT_CHAT) {
|
|
prevStep = WIZARD_STEPS.COMPLEXITY as WizardStep;
|
|
}
|
|
|
|
return { ...prev, step: prevStep };
|
|
});
|
|
}, []);
|
|
|
|
/**
|
|
* Get data formatted for the project creation API
|
|
*/
|
|
const getProjectData = useCallback((): ProjectCreateData => {
|
|
const slug = generateSlug(state.projectName);
|
|
|
|
return {
|
|
name: state.projectName.trim(),
|
|
slug,
|
|
description: state.description.trim() || undefined,
|
|
autonomy_level: state.autonomyLevel || 'milestone',
|
|
settings: {
|
|
complexity: state.complexity || 'medium',
|
|
client_mode: state.clientMode || 'auto',
|
|
...(state.repoUrl && { repo_url: state.repoUrl }),
|
|
},
|
|
};
|
|
}, [state]);
|
|
|
|
return {
|
|
state,
|
|
updateState,
|
|
resetState,
|
|
isScriptMode,
|
|
canProceed,
|
|
goNext,
|
|
goBack,
|
|
getProjectData,
|
|
};
|
|
}
|