feat(frontend): add Dashboard page and components for #53

Implement the main dashboard homepage with:
- WelcomeHeader: Personalized greeting with user name
- DashboardQuickStats: Stats cards for projects, agents, issues, approvals
- RecentProjects: Dynamic grid showing 3-6 recent projects
- PendingApprovals: Action-required approvals section
- EmptyState: Onboarding experience for new users
- useDashboard hook: Mock data fetching with React Query

The dashboard serves as the authenticated homepage at /(authenticated)/
and provides quick access to all project management features.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 17:19:59 +01:00
parent 0ceee8545e
commit 6f5dd58b54
9 changed files with 960 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
/**
* Dashboard Component
*
* Main dashboard layout orchestrator.
* Combines all dashboard sub-components into a cohesive layout.
*
* Layout:
* +------------------------------------------+------------------+
* | Welcome Header | ACTIVITY |
* +------------------------------------------+ FEED |
* | Quick Stats (4 cards) | SIDEBAR |
* +------------------------------------------+ |
* | Recent Projects (3-6 cards) | |
* +------------------------------------------+ |
* | Pending Approvals (if any) | |
* +------------------------------------------+------------------+
*
* @see Issue #53
*/
'use client';
import { useCallback } from 'react';
import { toast } from 'sonner';
import { Card } from '@/components/ui/card';
import { ActivityFeed } from '@/components/activity/ActivityFeed';
import { WelcomeHeader } from './WelcomeHeader';
import { DashboardQuickStats } from './DashboardQuickStats';
import { RecentProjects } from './RecentProjects';
import { PendingApprovals } from './PendingApprovals';
import { EmptyState } from './EmptyState';
import { useDashboard, type PendingApproval } from '@/lib/api/hooks/useDashboard';
import { useAuth } from '@/lib/auth/AuthContext';
import { useProjectEvents } from '@/lib/hooks/useProjectEvents';
import { useProjectEventsFromStore } from '@/lib/stores/eventStore';
export interface DashboardProps {
/** Additional CSS classes */
className?: string;
}
export function Dashboard({ className }: DashboardProps) {
const { user } = useAuth();
const { data, isLoading, error } = useDashboard();
// Real-time events - using a generic project ID for dashboard-wide events
// In production, this would be a dedicated dashboard events endpoint
const { connectionState } = useProjectEvents('dashboard', {
autoConnect: true,
});
const events = useProjectEventsFromStore('dashboard');
// Get user's first name for empty state
const firstName = user?.first_name || user?.email?.split('@')[0] || 'there';
// Handle approval actions
const handleApprove = useCallback((approval: PendingApproval) => {
// TODO: Implement actual approval API call
toast.success(`Approved: ${approval.title}`, {
description: `${approval.projectName}`,
});
}, []);
const handleReject = useCallback((approval: PendingApproval) => {
// TODO: Implement actual rejection API call
toast.info(`Rejected: ${approval.title}`, {
description: `${approval.projectName}`,
});
}, []);
// Show error state
if (error) {
toast.error('Failed to load dashboard data', {
description: 'Please try refreshing the page',
});
}
// Check if user has no projects (empty state)
const hasNoProjects = !isLoading && (!data?.recentProjects || data.recentProjects.length === 0);
return (
<div className={className}>
<div className="container mx-auto px-4 py-6">
{/* Welcome Header - always shown */}
<WelcomeHeader className="mb-6" />
{hasNoProjects ? (
// Empty state for new users
<EmptyState userName={firstName} />
) : (
// Main dashboard layout
<div className="grid gap-6 lg:grid-cols-[1fr_350px]">
{/* Main Content */}
<div className="space-y-6">
{/* Quick Stats */}
<DashboardQuickStats stats={data?.stats} isLoading={isLoading} />
{/* Recent Projects */}
<RecentProjects projects={data?.recentProjects} isLoading={isLoading} />
{/* Pending Approvals */}
<PendingApprovals
approvals={data?.pendingApprovals}
isLoading={isLoading}
onApprove={handleApprove}
onReject={handleReject}
/>
</div>
{/* Activity Feed Sidebar */}
<div className="hidden lg:block">
<Card className="sticky top-4">
<ActivityFeed
events={events}
connectionState={connectionState}
isLoading={isLoading}
maxHeight={600}
showHeader
title="Recent Activity"
enableFiltering={false}
enableSearch={false}
compact
/>
</Card>
</div>
</div>
)}
</div>
</div>
);
}