π Module 10 Capstone Project: Full-Stack SaaS Platform
Build a production-ready Software-as-a-Service platform that showcases everything you've learned throughout this course. This comprehensive project integrates TypeScript, React, state management, routing, forms, testing, and deploymentβculminating in a portfolio-worthy application!
π― Project Overview
You'll build TaskFlow Proβa collaborative project management SaaS platform with team workspaces, real-time updates, advanced analytics, and subscription tiers. This project demonstrates your ability to architect and deploy a complete production application.
Estimated Time: 30-40 hours over 2-3 weeks
Difficulty: Advanced βββββ
π Skills You'll Demonstrate
This capstone project showcases mastery of:
- TypeScript advanced patterns and type safety across a large codebase
- Complex component architecture with reusable patterns
- Global state management with Redux Toolkit or Zustand
- Authentication and authorization flows
- Real-time data synchronization (WebSocket or polling)
- Advanced routing with protected routes and role-based access
- Complex form handling with multi-step wizards and validation
- Data visualization with charts and analytics dashboards
- Performance optimization for production
- Comprehensive testing strategy
- Accessibility (WCAG 2.1 AA compliance)
- Production deployment with CI/CD
π Project Guide
π Project Requirements
TaskFlow Pro is a modern project management platform designed to help teams organize work, track progress, and collaborate effectively. Think of it as a blend of Trello, Asana, and Linear with your own unique touches.
π― What You're Building
π‘ Project Description
TaskFlow Pro is a collaborative workspace where teams can:
- Create and manage projects with customizable workflows
- Organize tasks with boards, lists, and cards (Kanban-style)
- Assign tasks to team members with due dates and priorities
- Track project progress with real-time analytics and charts
- Collaborate with comments, mentions, and notifications
- Manage team workspaces with role-based permissions
- Access the platform from any device (fully responsive)
β Minimum Viable Product (MVP) Requirements
β οΈ Must-Have Features
Your application MUST include these core features:
- Authentication System
- User registration with email verification
- Login/logout functionality
- Password reset flow
- Protected routes and authentication guards
- Workspace Management
- Create and manage workspaces
- Invite team members via email
- Role-based access control (Owner, Admin, Member, Viewer)
- Project & Task Management
- Create projects within workspaces
- Kanban board with drag-and-drop task cards
- Task creation with title, description, assignee, due date, priority
- Task status management (To Do, In Progress, Done, etc.)
- Task filtering and search functionality
- Real-Time Updates
- Live task updates when team members make changes
- Real-time notifications for mentions and assignments
- Optimistic UI updates for smooth user experience
- Analytics Dashboard
- Project completion rate visualization
- Team productivity charts
- Task distribution by status
- Time tracking and burndown charts
- Responsive Design
- Mobile-first responsive layout
- Touch-optimized interactions for mobile devices
- Adaptive navigation for different screen sizes
π Bonus Features (Choose 2-3)
β Nice-to-Have Enhancements
Implement at least 2-3 of these advanced features to showcase your skills:
- File Attachments: Upload and attach files to tasks (images, documents, etc.)
- Activity Feed: Timeline showing all workspace activity and changes
- Custom Labels/Tags: Color-coded labels for task categorization
- Time Tracking: Track time spent on tasks with start/stop timers
- Recurring Tasks: Automatically create tasks on a schedule
- Dark Mode: Complete dark theme implementation with persistence
- Keyboard Shortcuts: Power-user keyboard navigation (like Linear)
- Calendar View: Alternative view showing tasks in calendar format
- Email Integration: Create tasks via email, send digests
- Export Features: Export projects to CSV/PDF for reporting
- Advanced Search: Full-text search with filters and autocomplete
- Webhooks: Integrate with external services (Slack, Discord, etc.)
π« What NOT to Build (Keep It Focused)
β Out of Scope
To keep the project manageable, do NOT include:
- Payment processing or subscription billing (you can mock this)
- Video/audio chat features
- Advanced AI or machine learning features
- Mobile native apps (web-only is fine)
- Backend infrastructure (use Firebase, Supabase, or mock API)
Focus on frontend excellence! Use existing backend services rather than building your own API.
π¨ Core Features Overview
Let's break down each major feature area and what it should include.
π Authentication & User Management
// Types for authentication
interface User {
id: string;
email: string;
name: string;
avatar?: string;
createdAt: Date;
emailVerified: boolean;
}
interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
}
// Features to implement:
// - Registration form with validation
// - Email verification flow
// - Login with remember me option
// - Password reset via email
// - Profile management page
// - Avatar upload
// - Account settings
π₯ Workspace & Team Management
interface Workspace {
id: string;
name: string;
description?: string;
ownerId: string;
members: WorkspaceMember[];
createdAt: Date;
updatedAt: Date;
}
interface WorkspaceMember {
userId: string;
role: 'owner' | 'admin' | 'member' | 'viewer';
joinedAt: Date;
}
// Features to implement:
// - Create workspace wizard
// - Workspace settings page
// - Invite members via email
// - Member management table
// - Role-based permissions system
// - Leave/delete workspace functionality
π Project & Board Management
interface Project {
id: string;
workspaceId: string;
name: string;
description?: string;
color?: string;
status: 'active' | 'archived' | 'completed';
lists: List[];
createdAt: Date;
updatedAt: Date;
}
interface List {
id: string;
projectId: string;
title: string;
position: number;
tasks: Task[];
}
interface Task {
id: string;
listId: string;
title: string;
description?: string;
assigneeId?: string;
dueDate?: Date;
priority: 'low' | 'medium' | 'high' | 'urgent';
status: 'todo' | 'in-progress' | 'review' | 'done';
labels?: string[];
position: number;
createdAt: Date;
updatedAt: Date;
}
// Features to implement:
// - Kanban board with drag-and-drop
// - Quick task creation
// - Task detail modal/sidebar
// - Task assignment dropdown
// - Due date picker with calendar
// - Priority indicators
// - Progress tracking
π¬ Comments & Activity
interface Comment {
id: string;
taskId: string;
authorId: string;
content: string;
mentions?: string[]; // User IDs mentioned
createdAt: Date;
updatedAt: Date;
}
interface Activity {
id: string;
workspaceId: string;
userId: string;
type: 'task_created' | 'task_updated' | 'comment_added' | 'member_added';
entityId: string;
metadata: Record;
createdAt: Date;
}
// Features to implement:
// - Comment thread on tasks
// - @mention functionality
// - Activity feed timeline
// - Notification system
// - Real-time updates
π Analytics & Reporting
interface Analytics {
projectId: string;
completionRate: number;
totalTasks: number;
completedTasks: number;
overdueTasks: number;
tasksByStatus: Record;
tasksByPriority: Record;
memberProductivity: {
userId: string;
tasksCompleted: number;
tasksInProgress: number;
}[];
}
// Features to implement:
// - Dashboard with key metrics cards
// - Completion rate chart (line/area)
// - Task distribution pie/donut chart
// - Team productivity bar chart
// - Filter by date range
// - Export to CSV
ποΈ Technical Architecture
Let's design the technical foundation for your application.
π¦ Tech Stack Requirements
| Category | Required | Alternatives |
|---|---|---|
| Core Framework | React 18+ with TypeScript | - |
| Build Tool | Vite | Create React App, Next.js |
| Routing | React Router v6 | TanStack Router |
| State Management | Redux Toolkit or Zustand | Context API + useReducer |
| Data Fetching | TanStack Query (React Query) | SWR, custom hooks |
| Forms | React Hook Form + Zod | Formik + Yup |
| UI Components | Tailwind CSS + Headless UI | Material-UI, Chakra UI, shadcn/ui |
| Drag & Drop | dnd kit or react-beautiful-dnd | - |
| Charts | Recharts or Chart.js | Victory, Nivo |
| Testing | Vitest + React Testing Library | Jest + RTL |
| Backend/Database | Firebase or Supabase | Mock API with MSW |
| Deployment | Vercel or Netlify | GitHub Pages, Railway |
π‘ Backend Strategy
You have three options for handling backend functionality:
- Firebase/Supabase (Recommended): Use a BaaS platform for auth, database, and real-time features
- Mock API: Use MSW (Mock Service Worker) to simulate backend responses
- Build Your Own: Create a simple Express/Nest.js API (only if you have time)
Recommendation: Use Firebase or Supabase to focus on frontend skills!
ποΈ Folder Structure
taskflow-pro/
βββ src/
β βββ assets/ # Images, icons, fonts
β βββ components/
β β βββ auth/ # Login, Register, ResetPassword
β β βββ board/ # Kanban board components
β β βββ common/ # Button, Modal, Input, etc.
β β βββ dashboard/ # Dashboard widgets
β β βββ layout/ # Header, Sidebar, Footer
β β βββ tasks/ # TaskCard, TaskDetail, TaskForm
β β βββ workspace/ # Workspace components
β βββ features/ # Feature-based organization
β β βββ auth/
β β β βββ authSlice.ts
β β β βββ authAPI.ts
β β β βββ authHooks.ts
β β βββ projects/
β β βββ tasks/
β β βββ workspaces/
β βββ hooks/ # Custom React hooks
β β βββ useAuth.ts
β β βββ useDebounce.ts
β β βββ useLocalStorage.ts
β β βββ useRealtime.ts
β βββ lib/ # Third-party configs
β β βββ firebase.ts
β β βββ queryClient.ts
β β βββ store.ts
β βββ pages/ # Page components
β β βββ Dashboard.tsx
β β βββ Login.tsx
β β βββ Project.tsx
β β βββ Workspace.tsx
β βββ services/ # API services
β β βββ api.ts
β β βββ auth.service.ts
β β βββ project.service.ts
β β βββ task.service.ts
β βββ types/ # TypeScript types
β β βββ auth.types.ts
β β βββ project.types.ts
β β βββ task.types.ts
β βββ utils/ # Utility functions
β β βββ date.ts
β β βββ validation.ts
β β βββ formatters.ts
β βββ App.tsx
β βββ main.tsx
β βββ router.tsx
βββ public/
βββ tests/
β βββ components/
β βββ integration/
β βββ utils/
βββ .env.example
βββ package.json
βββ tsconfig.json
βββ vite.config.ts
βββ README.md
π Data Flow Architecture
π Authentication Flow
ποΈ Phase 1: Foundation & Setup (4-6 hours)
Start by setting up your development environment and creating the basic project structure.
β Phase 1 Checklist
Tasks to Complete
- Initialize project with Vite + React + TypeScript
- Set up Tailwind CSS and configure custom theme
- Install and configure all required dependencies
- Create folder structure following the architecture
- Set up React Router with route configuration
- Configure Redux Toolkit or Zustand for state management
- Set up TanStack Query (React Query)
- Create environment variables setup
- Initialize Git repository with .gitignore
- Set up ESLint and Prettier for code quality
- Create basic layout components (Header, Sidebar, Footer)
- Implement theme provider (light/dark mode foundation)
π Step-by-Step Setup
Step 1: Create Project
# Create Vite project with React + TypeScript
npm create vite@latest taskflow-pro -- --template react-ts
cd taskflow-pro
# Install dependencies
npm install
# Install additional packages
npm install react-router-dom @tanstack/react-query
npm install @reduxjs/toolkit react-redux # OR zustand
npm install react-hook-form zod @hookform/resolvers
npm install @dnd-kit/core @dnd-kit/sortable
npm install recharts date-fns
npm install firebase # OR @supabase/supabase-js
# Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
# Install dev dependencies
npm install -D @types/node
npm install -D vitest @testing-library/react @testing-library/jest-dom
npm install -D msw # for mocking APIs
Step 2: Configure Tailwind CSS
// tailwind.config.js
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
primary: {
50: '#f0f4ff',
100: '#e0e9ff',
200: '#c7d7fe',
300: '#a5b8fc',
400: '#8192f8',
500: '#667eea', // Main brand color
600: '#5b6cd6',
700: '#4f58b3',
800: '#434990',
900: '#3a3d76',
},
// Add more custom colors for your app
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
plugins: [],
darkMode: 'class', // Enable dark mode
};
Step 3: Set Up Router
// src/router.tsx
import { createBrowserRouter, Navigate } from 'react-router-dom';
import { RootLayout } from './components/layout/RootLayout';
import { AuthLayout } from './components/layout/AuthLayout';
import { ProtectedRoute } from './components/auth/ProtectedRoute';
// Page imports
import { LandingPage } from './pages/Landing';
import { LoginPage } from './pages/Login';
import { RegisterPage } from './pages/Register';
import { DashboardPage } from './pages/Dashboard';
import { WorkspacePage } from './pages/Workspace';
import { ProjectPage } from './pages/Project';
import { AnalyticsPage } from './pages/Analytics';
import { SettingsPage } from './pages/Settings';
import { NotFoundPage } from './pages/NotFound';
export const router = createBrowserRouter([
{
path: '/',
element: ,
children: [
{
index: true,
element: ,
},
{
path: 'auth',
element: ,
children: [
{ path: 'login', element: },
{ path: 'register', element: },
{ path: 'reset-password', element: },
],
},
{
path: 'app',
element: ,
children: [
{ path: 'dashboard', element: },
{ path: 'workspace/:workspaceId', element: },
{ path: 'project/:projectId', element: },
{ path: 'analytics', element: },
{ path: 'settings', element: },
],
},
{
path: '*',
element: ,
},
],
},
]);
Step 4: Create TypeScript Types
// src/types/index.ts
// User types
export interface User {
id: string;
email: string;
name: string;
avatar?: string;
createdAt: Date;
emailVerified: boolean;
}
// Workspace types
export interface Workspace {
id: string;
name: string;
description?: string;
ownerId: string;
members: WorkspaceMember[];
createdAt: Date;
updatedAt: Date;
}
export interface WorkspaceMember {
userId: string;
role: WorkspaceRole;
joinedAt: Date;
}
export type WorkspaceRole = 'owner' | 'admin' | 'member' | 'viewer';
// Project types
export interface Project {
id: string;
workspaceId: string;
name: string;
description?: string;
color?: string;
status: ProjectStatus;
lists: List[];
createdAt: Date;
updatedAt: Date;
}
export type ProjectStatus = 'active' | 'archived' | 'completed';
// Task types
export interface Task {
id: string;
listId: string;
title: string;
description?: string;
assigneeId?: string;
dueDate?: Date;
priority: TaskPriority;
status: TaskStatus;
labels?: string[];
position: number;
createdAt: Date;
updatedAt: Date;
}
export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
export type TaskStatus = 'todo' | 'in-progress' | 'review' | 'done';
// List types
export interface List {
id: string;
projectId: string;
title: string;
position: number;
tasks: Task[];
}
// Comment types
export interface Comment {
id: string;
taskId: string;
authorId: string;
content: string;
mentions?: string[];
createdAt: Date;
updatedAt: Date;
}
// Activity types
export interface Activity {
id: string;
workspaceId: string;
userId: string;
type: ActivityType;
entityId: string;
metadata: Record;
createdAt: Date;
}
export type ActivityType =
| 'task_created'
| 'task_updated'
| 'task_completed'
| 'task_deleted'
| 'comment_added'
| 'member_added'
| 'member_removed'
| 'project_created';
π‘ Pro Tip: Environment Variables
Create a .env.example file with all required environment variables:
VITE_FIREBASE_API_KEY=your_api_key_here
VITE_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your_project_id
VITE_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=123456789
VITE_FIREBASE_APP_ID=1:123456789:web:abc123
Create a real .env file with your actual credentials and add it to .gitignore!
β Phase 1 Completion Criteria
β οΈ Before Moving to Phase 2
Ensure you have:
- β Project running locally without errors
- β Routing working (can navigate between pages)
- β Tailwind CSS styles applying correctly
- β TypeScript configured with no errors
- β All major dependencies installed
- β Basic layout components created (Header, Footer)
- β Environment variables configured
- β Git repository initialized with first commit
π Phase 2: Authentication & User Management (6-8 hours)
Build a complete authentication system with registration, login, password reset, and protected routes.
β Phase 2 Checklist
Tasks to Complete
- Set up Firebase Authentication or Supabase Auth
- Create registration form with validation
- Create login form with validation
- Implement email verification flow
- Create password reset flow
- Build protected route component
- Implement auth state management
- Create user profile page
- Add avatar upload functionality
- Build account settings page
- Add logout functionality
- Write tests for auth flows
π Implementation Guide
Authentication Service
// src/services/auth.service.ts
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
sendEmailVerification,
sendPasswordResetEmail,
updateProfile,
User as FirebaseUser
} from 'firebase/auth';
import { auth } from '../lib/firebase';
import type { User } from '../types';
class AuthService {
async register(email: string, password: string, name: string): Promise {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
await updateProfile(userCredential.user, { displayName: name });
await sendEmailVerification(userCredential.user);
return this.mapFirebaseUser(userCredential.user);
}
async login(email: string, password: string): Promise {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
return this.mapFirebaseUser(userCredential.user);
}
async logout(): Promise {
await signOut(auth);
}
async resetPassword(email: string): Promise {
await sendPasswordResetEmail(auth, email);
}
async verifyEmail(): Promise {
const user = auth.currentUser;
if (user) {
await sendEmailVerification(user);
}
}
getCurrentUser(): User | null {
const firebaseUser = auth.currentUser;
return firebaseUser ? this.mapFirebaseUser(firebaseUser) : null;
}
private mapFirebaseUser(firebaseUser: FirebaseUser): User {
return {
id: firebaseUser.uid,
email: firebaseUser.email!,
name: firebaseUser.displayName || '',
avatar: firebaseUser.photoURL || undefined,
createdAt: new Date(firebaseUser.metadata.creationTime!),
emailVerified: firebaseUser.emailVerified,
};
}
}
export const authService = new AuthService();
Registration Form Component
// src/components/auth/RegisterForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useNavigate } from 'react-router-dom';
import { authService } from '../../services/auth.service';
const registerSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain an uppercase letter')
.regex(/[0-9]/, 'Password must contain a number'),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
type RegisterFormData = z.infer;
export function RegisterForm() {
const navigate = useNavigate();
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
setError,
} = useForm({
resolver: zodResolver(registerSchema),
});
const onSubmit = async (data: RegisterFormData) => {
try {
await authService.register(data.email, data.password, data.name);
// Show success message about email verification
navigate('/auth/verify-email');
} catch (error: any) {
setError('root', {
message: error.message || 'Failed to create account',
});
}
};
return (
);
}
Protected Route Component
// src/components/auth/ProtectedRoute.tsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../../hooks/useAuth';
import { LoadingSpinner } from '../common/LoadingSpinner';
export function ProtectedRoute() {
const { user, isLoading } = useAuth();
if (isLoading) {
return (
);
}
if (!user) {
return ;
}
if (!user.emailVerified) {
return ;
}
return ;
}
β οΈ Security Best Practices
- Never store passwords in plain text: Use Firebase/Supabase which handles this
- Implement rate limiting: Prevent brute force attacks on login
- Require email verification: Confirm users own the email address
- Use HTTPS only: Ensure deployment uses SSL/TLS
- Store tokens securely: Use httpOnly cookies when possible
- Implement logout everywhere: Allow users to sign out of all devices
β Phase 2 Completion Criteria
Before Moving to Phase 3
- β Users can register new accounts
- β Email verification is sent and checked
- β Users can log in with email/password
- β Password reset flow works end-to-end
- β Protected routes redirect unauthenticated users
- β User profile displays current user info
- β Users can upload/change avatar
- β Logout works and clears session
- β Form validation provides clear error messages
- β Auth state persists across page refreshes
π Phase 3: Core Features (10-12 hours)
Build the heart of your application: workspaces, projects, and the Kanban board with drag-and-drop functionality.
β Phase 3 Checklist
Tasks to Complete
- Create workspace creation wizard/form
- Build workspace list and dashboard
- Implement workspace member invitation system
- Create role-based permission system
- Build project creation form
- Create project list view with cards
- Implement Kanban board layout
- Add drag-and-drop for tasks and lists
- Create task creation form (quick add + detailed)
- Build task detail modal/sidebar
- Implement task editing and deletion
- Add task assignment functionality
- Create due date picker component
- Implement priority indicators
- Add task filtering and search
π’ Workspace Management Implementation
Workspace Creation Form
// src/components/workspace/CreateWorkspaceForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { workspaceService } from '../../services/workspace.service';
const workspaceSchema = z.object({
name: z.string()
.min(3, 'Name must be at least 3 characters')
.max(50, 'Name must be less than 50 characters'),
description: z.string()
.max(500, 'Description must be less than 500 characters')
.optional(),
});
type WorkspaceFormData = z.infer;
interface CreateWorkspaceFormProps {
onSuccess?: (workspaceId: string) => void;
onCancel?: () => void;
}
export function CreateWorkspaceForm({ onSuccess, onCancel }: CreateWorkspaceFormProps) {
const queryClient = useQueryClient();
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(workspaceSchema),
});
const createMutation = useMutation({
mutationFn: workspaceService.create,
onSuccess: (workspace) => {
queryClient.invalidateQueries({ queryKey: ['workspaces'] });
onSuccess?.(workspace.id);
},
});
const onSubmit = (data: WorkspaceFormData) => {
createMutation.mutate(data);
};
return (
);
}
Member Invitation System
// src/components/workspace/InviteMemberForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { workspaceService } from '../../services/workspace.service';
import type { WorkspaceRole } from '../../types';
const inviteSchema = z.object({
email: z.string().email('Invalid email address'),
role: z.enum(['admin', 'member', 'viewer']),
});
type InviteFormData = z.infer;
interface InviteMemberFormProps {
workspaceId: string;
onSuccess?: () => void;
}
export function InviteMemberForm({ workspaceId, onSuccess }: InviteMemberFormProps) {
const queryClient = useQueryClient();
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm({
resolver: zodResolver(inviteSchema),
defaultValues: { role: 'member' },
});
const inviteMutation = useMutation({
mutationFn: (data: InviteFormData) =>
workspaceService.inviteMember(workspaceId, data.email, data.role),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['workspace', workspaceId] });
reset();
onSuccess?.();
},
});
const onSubmit = (data: InviteFormData) => {
inviteMutation.mutate(data);
};
return (
);
}
π Kanban Board Implementation
Board Layout Component
// src/components/board/KanbanBoard.tsx
import { useState } from 'react';
import {
DndContext,
DragOverlay,
closestCorners,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragStartEvent,
DragOverEvent,
DragEndEvent,
} from '@dnd-kit/core';
import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
import { BoardList } from './BoardList';
import { TaskCard } from './TaskCard';
import type { Project, List, Task } from '../../types';
interface KanbanBoardProps {
project: Project;
onUpdateProject: (project: Project) => void;
}
export function KanbanBoard({ project, onUpdateProject }: KanbanBoardProps) {
const [activeTask, setActiveTask] = useState(null);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
const handleDragStart = (event: DragStartEvent) => {
const { active } = event;
const task = findTask(active.id as string);
setActiveTask(task);
};
const handleDragOver = (event: DragOverEvent) => {
const { active, over } = event;
if (!over) return;
const activeId = active.id as string;
const overId = over.id as string;
if (activeId === overId) return;
// Find which lists contain the tasks
const activeList = findListByTaskId(activeId);
const overList = findListByTaskId(overId) || findListById(overId);
if (!activeList || !overList) return;
// Handle moving task between lists
if (activeList.id !== overList.id) {
moveTaskBetweenLists(activeId, activeList.id, overList.id);
}
};
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
setActiveTask(null);
if (!over) return;
const activeId = active.id as string;
const overId = over.id as string;
if (activeId === overId) return;
const activeList = findListByTaskId(activeId);
if (!activeList) return;
// Reorder tasks within the same list
const oldIndex = activeList.tasks.findIndex((task) => task.id === activeId);
const newIndex = activeList.tasks.findIndex((task) => task.id === overId);
if (oldIndex !== -1 && newIndex !== -1) {
const newTasks = arrayMove(activeList.tasks, oldIndex, newIndex);
updateListTasks(activeList.id, newTasks);
}
};
const findTask = (taskId: string): Task | null => {
for (const list of project.lists) {
const task = list.tasks.find((t) => t.id === taskId);
if (task) return task;
}
return null;
};
const findListByTaskId = (taskId: string): List | null => {
return project.lists.find((list) =>
list.tasks.some((task) => task.id === taskId)
) || null;
};
const findListById = (listId: string): List | null => {
return project.lists.find((list) => list.id === listId) || null;
};
const moveTaskBetweenLists = (
taskId: string,
fromListId: string,
toListId: string
) => {
const updatedLists = project.lists.map((list) => {
if (list.id === fromListId) {
return {
...list,
tasks: list.tasks.filter((task) => task.id !== taskId),
};
}
if (list.id === toListId) {
const task = findTask(taskId);
return task
? { ...list, tasks: [...list.tasks, task] }
: list;
}
return list;
});
onUpdateProject({ ...project, lists: updatedLists });
};
const updateListTasks = (listId: string, newTasks: Task[]) => {
const updatedLists = project.lists.map((list) =>
list.id === listId ? { ...list, tasks: newTasks } : list
);
onUpdateProject({ ...project, lists: updatedLists });
};
return (
{project.lists.map((list) => (
))}
{activeTask ? : null}
);
}
Task Card Component
// src/components/board/TaskCard.tsx
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { format } from 'date-fns';
import type { Task } from '../../types';
interface TaskCardProps {
task: Task;
onClick?: () => void;
}
export function TaskCard({ task, onClick }: TaskCardProps) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: task.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
};
const priorityColors = {
low: 'bg-gray-100 text-gray-800',
medium: 'bg-blue-100 text-blue-800',
high: 'bg-orange-100 text-orange-800',
urgent: 'bg-red-100 text-red-800',
};
const isOverdue = task.dueDate && new Date(task.dueDate) < new Date();
return (
{task.title}
{task.priority}
{task.description && (
{task.description}
)}
{task.dueDate && (
{format(new Date(task.dueDate), 'MMM d')}
)}
{task.assigneeId && (
{/* Replace with actual avatar */}
JD
)}
{task.labels && task.labels.length > 0 && (
{task.labels.map((label) => (
{label}
))}
)}
);
}
π‘ Drag & Drop Best Practices
- Keyboard Accessibility: Ensure keyboard users can reorder tasks
- Visual Feedback: Show clear drop zones and drag previews
- Touch Support: Test on mobile devices for touch gestures
- Optimistic Updates: Update UI immediately, sync with backend after
- Error Handling: Revert changes if backend sync fails
- Performance: Virtualize long lists to maintain smooth dragging
β Phase 3 Completion Criteria
Before Moving to Phase 4
- β Users can create and manage workspaces
- β Workspace members can be invited via email
- β Role-based permissions work correctly
- β Projects can be created within workspaces
- β Kanban board displays with multiple lists
- β Tasks can be dragged between lists
- β Tasks can be reordered within lists
- β Quick task creation works
- β Task detail view shows all information
- β Tasks can be assigned to members
- β Due dates can be set and displayed
- β Priority indicators are visible
- β Task filtering and search work
- β All CRUD operations persist to backend
β‘ Phase 4: Advanced Features (8-10 hours)
Add real-time updates, comments, notifications, and analytics to create a truly collaborative experience.
β Phase 4 Checklist
Tasks to Complete
- Implement real-time task updates (WebSocket or polling)
- Create notification system for mentions and assignments
- Build comment thread component
- Add @mention functionality in comments
- Create activity feed showing workspace changes
- Build analytics dashboard with charts
- Implement completion rate visualization
- Create team productivity metrics
- Add task distribution charts (by status, priority)
- Implement file upload for task attachments
- Create custom labels/tags system
- Add time tracking features (optional)
π Real-Time Updates Implementation
Real-Time Hook with Firebase
// src/hooks/useRealtime.ts
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { onSnapshot, doc, collection } from 'firebase/firestore';
import { db } from '../lib/firebase';
import type { Task, Project } from '../types';
export function useRealtimeProject(projectId: string) {
const queryClient = useQueryClient();
useEffect(() => {
// Subscribe to project document changes
const unsubscribe = onSnapshot(
doc(db, 'projects', projectId),
(snapshot) => {
if (snapshot.exists()) {
const project = {
id: snapshot.id,
...snapshot.data(),
} as Project;
// Update React Query cache
queryClient.setQueryData(['project', projectId], project);
}
},
(error) => {
console.error('Real-time subscription error:', error);
}
);
// Cleanup subscription on unmount
return () => unsubscribe();
}, [projectId, queryClient]);
}
export function useRealtimeTasks(projectId: string) {
const queryClient = useQueryClient();
useEffect(() => {
// Subscribe to tasks collection changes
const unsubscribe = onSnapshot(
collection(db, 'projects', projectId, 'tasks'),
(snapshot) => {
const tasks: Task[] = [];
snapshot.forEach((doc) => {
tasks.push({ id: doc.id, ...doc.data() } as Task);
});
// Update cache with new tasks
queryClient.setQueryData(['tasks', projectId], tasks);
// Show toast notification for changes made by others
snapshot.docChanges().forEach((change) => {
if (change.type === 'added') {
// Show "New task added" notification
} else if (change.type === 'modified') {
// Show "Task updated" notification
}
});
}
);
return () => unsubscribe();
}, [projectId, queryClient]);
}
Notification System
// src/components/notifications/NotificationCenter.tsx
import { useQuery } from '@tanstack/react-query';
import { notificationService } from '../../services/notification.service';
import { formatDistanceToNow } from 'date-fns';
import type { Notification } from '../../types';
export function NotificationCenter() {
const { data: notifications, isLoading } = useQuery({
queryKey: ['notifications'],
queryFn: notificationService.getAll,
});
const unreadCount = notifications?.filter((n) => !n.read).length || 0;
const getNotificationIcon = (type: Notification['type']) => {
switch (type) {
case 'task_assigned':
return 'π';
case 'mention':
return '@';
case 'task_completed':
return 'β
';
case 'comment':
return 'π¬';
default:
return 'π';
}
};
return (
{/* Notification dropdown */}
Notifications
{isLoading ? (
Loading...
) : notifications?.length === 0 ? (
No notifications
) : (
notifications?.map((notification) => (
{getNotificationIcon(notification.type)}
{notification.message}
{formatDistanceToNow(new Date(notification.createdAt), {
addSuffix: true,
})}
))
)}
);
}
π¬ Comments System
// src/components/task/CommentThread.tsx
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { commentService } from '../../services/comment.service';
import { formatDistanceToNow } from 'date-fns';
import type { Comment } from '../../types';
interface CommentThreadProps {
taskId: string;
}
export function CommentThread({ taskId }: CommentThreadProps) {
const queryClient = useQueryClient();
const [showInput, setShowInput] = useState(false);
const { data: comments, isLoading } = useQuery({
queryKey: ['comments', taskId],
queryFn: () => commentService.getByTaskId(taskId),
});
const { register, handleSubmit, reset } = useForm<{ content: string }>();
const addCommentMutation = useMutation({
mutationFn: (content: string) =>
commentService.create(taskId, content),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['comments', taskId] });
reset();
setShowInput(false);
},
});
const onSubmit = (data: { content: string }) => {
addCommentMutation.mutate(data.content);
};
// Parse @mentions in comment content
const renderCommentContent = (content: string) => {
const mentionRegex = /@(\w+)/g;
const parts = content.split(mentionRegex);
return parts.map((part, index) => {
if (index % 2 === 1) {
// This is a mention
return (
@{part}
);
}
return part;
});
};
return (
Comments ({comments?.length || 0})
{!showInput && (
)}
{showInput && (
)}
{isLoading ? (
Loading comments...
) : comments?.length === 0 ? (
No comments yet
) : (
comments?.map((comment) => (
{/* Avatar or initials */}
JD
John Doe
{formatDistanceToNow(new Date(comment.createdAt), {
addSuffix: true,
})}
{renderCommentContent(comment.content)}
))
)}
);
}
π Analytics Dashboard
// src/components/analytics/AnalyticsDashboard.tsx
import { useQuery } from '@tanstack/react-query';
import {
LineChart,
Line,
PieChart,
Pie,
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
Cell,
} from 'recharts';
import { analyticsService } from '../../services/analytics.service';
interface AnalyticsDashboardProps {
workspaceId: string;
projectId?: string;
}
export function AnalyticsDashboard({ workspaceId, projectId }: AnalyticsDashboardProps) {
const { data: analytics, isLoading } = useQuery({
queryKey: ['analytics', workspaceId, projectId],
queryFn: () => analyticsService.getAnalytics(workspaceId, projectId),
});
if (isLoading) {
return Loading analytics...;
}
const COLORS = ['#667eea', '#48bb78', '#ed8936', '#f56565'];
// Prepare data for charts
const completionData = analytics?.completionHistory || [];
const statusData = Object.entries(analytics?.tasksByStatus || {}).map(
([status, count]) => ({
name: status,
value: count,
})
);
const priorityData = Object.entries(analytics?.tasksByPriority || {}).map(
([priority, count]) => ({
name: priority,
count,
})
);
return (
{/* Key Metrics */}
Total Tasks
{analytics?.totalTasks || 0}
Completed
{analytics?.completedTasks || 0}
In Progress
{analytics?.inProgressTasks || 0}
Completion Rate
{analytics?.completionRate || 0}%
{/* Completion Trend */}
Completion Trend
{/* Task Status Distribution */}
Tasks by Status
`${name}: ${(percent * 100).toFixed(0)}%`
}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{statusData.map((entry, index) => (
|
))}
{/* Task Priority Distribution */}
Tasks by Priority
{/* Team Productivity */}
Team Productivity
{analytics?.memberProductivity?.map((member) => (
{member.name?.slice(0, 2).toUpperCase()}
{member.name}
β
{member.tasksCompleted} completed
π {member.tasksInProgress} in progress
))}
);
}
π‘ Real-Time Best Practices
- Optimistic Updates: Update UI immediately, sync with server after
- Conflict Resolution: Handle cases where multiple users edit simultaneously
- Connection Status: Show indicator when connection is lost
- Debouncing: Don't send updates on every keystroke
- Presence Indicators: Show who's currently viewing/editing
- Graceful Degradation: App should work offline with limited functionality
β Phase 4 Completion Criteria
Before Moving to Phase 5
- β Real-time updates work across multiple browser tabs
- β Notifications appear for assignments and mentions
- β Comment system allows threaded discussions
- β @mentions work and notify users
- β Activity feed shows recent workspace changes
- β Analytics dashboard displays key metrics
- β Charts visualize task distribution and trends
- β Team productivity metrics are accurate
- β All bonus features (if implemented) are functional
β¨ Phase 5: Polish & Optimization (6-8 hours)
Refine your application with performance optimizations, accessibility improvements, and professional polish.
β Phase 5 Checklist
Tasks to Complete
- Implement loading states and skeleton screens
- Add error boundaries for graceful error handling
- Optimize images and assets
- Implement code splitting and lazy loading
- Add performance monitoring
- Improve accessibility (ARIA labels, keyboard navigation)
- Add tooltips and help text throughout
- Implement dark mode (if not done earlier)
- Add empty states for lists and boards
- Create onboarding flow for new users
- Add keyboard shortcuts for power users
- Polish animations and transitions
- Optimize bundle size
- Add meta tags for SEO
β‘ Performance Optimization
Code Splitting and Lazy Loading
// src/router.tsx - Updated with lazy loading
import { lazy, Suspense } from 'react';
import { createBrowserRouter } from 'react-router-dom';
import { LoadingSpinner } from './components/common/LoadingSpinner';
// Lazy load heavy components
const DashboardPage = lazy(() => import('./pages/Dashboard'));
const ProjectPage = lazy(() => import('./pages/Project'));
const AnalyticsPage = lazy(() => import('./pages/Analytics'));
const SettingsPage = lazy(() => import('./pages/Settings'));
// Wrapper component for suspense
function LazyPage({ Component }: { Component: React.LazyExoticComponent }) {
return (
React Query Optimization
// src/lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false,
retry: 1,
// Prefetch adjacent data
onSuccess: (data) => {
// Prefetch related queries
},
},
mutations: {
// Optimistic updates
onMutate: async (variables) => {
// Cancel outgoing refetches
// Snapshot previous value
// Optimistically update cache
},
onError: (err, variables, context) => {
// Rollback on error
},
},
},
});
Memoization for Performance
// src/components/board/KanbanBoard.tsx - With memoization
import { useMemo, memo } from 'react';
interface KanbanBoardProps {
project: Project;
onUpdateProject: (project: Project) => void;
}
export const KanbanBoard = memo(function KanbanBoard({
project,
onUpdateProject
}: KanbanBoardProps) {
// Memoize expensive computations
const sortedLists = useMemo(() => {
return [...project.lists].sort((a, b) => a.position - b.position);
}, [project.lists]);
const taskCount = useMemo(() => {
return project.lists.reduce((sum, list) => sum + list.tasks.length, 0);
}, [project.lists]);
// Memoize callbacks to prevent child re-renders
const handleTaskUpdate = useCallback((taskId: string, updates: Partial) => {
// Update logic
}, [project]);
return (
// ... board JSX
);
});
βΏ Accessibility Improvements
// src/components/common/Button.tsx - Accessible button component
interface ButtonProps extends React.ButtonHTMLAttributes {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
}
export function Button({
children,
variant = 'primary',
size = 'md',
isLoading = false,
leftIcon,
rightIcon,
disabled,
...props
}: ButtonProps) {
return (
);
}
Keyboard Shortcuts
// src/hooks/useKeyboardShortcuts.ts
import { useEffect } from 'react';
interface Shortcut {
key: string;
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
action: () => void;
description: string;
}
export function useKeyboardShortcuts(shortcuts: Shortcut[]) {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
for (const shortcut of shortcuts) {
const ctrlMatch = shortcut.ctrl ? event.ctrlKey || event.metaKey : !event.ctrlKey && !event.metaKey;
const shiftMatch = shortcut.shift ? event.shiftKey : !event.shiftKey;
const altMatch = shortcut.alt ? event.altKey : !event.altKey;
const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase();
if (ctrlMatch && shiftMatch && altMatch && keyMatch) {
event.preventDefault();
shortcut.action();
break;
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [shortcuts]);
}
// Usage in component
export function ProjectBoard() {
const navigate = useNavigate();
useKeyboardShortcuts([
{
key: 'n',
action: () => openNewTaskModal(),
description: 'Create new task',
},
{
key: 'k',
ctrl: true,
action: () => openCommandPalette(),
description: 'Open command palette',
},
{
key: '/',
action: () => focusSearch(),
description: 'Focus search',
},
]);
// ... component logic
}
Error Boundaries
// src/components/common/ErrorBoundary.tsx
import { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
// Log to error tracking service (Sentry, LogRocket, etc.)
}
render() {
if (this.state.hasError) {
return (
this.props.fallback || (
Something went wrong
We're sorry for the inconvenience. Please try refreshing the page.
)
);
}
return this.props.children;
}
}
π¨ Empty States and Loading States
// src/components/common/EmptyState.tsx
interface EmptyStateProps {
icon?: React.ReactNode;
title: string;
description: string;
action?: {
label: string;
onClick: () => void;
};
}
export function EmptyState({ icon, title, description, action }: EmptyStateProps) {
return (
{icon && (
)}
{title}
{description}
{action && (
)}
);
}
// Usage
openTaskModal(),
}}
/>
β οΈ Performance Checklist
- β Lighthouse score > 90 in all categories
- β First Contentful Paint < 1.8s
- β Time to Interactive < 3.8s
- β Largest Contentful Paint < 2.5s
- β Cumulative Layout Shift < 0.1
- β Bundle size optimized (lazy loading, tree shaking)
- β Images optimized (WebP, responsive images)
- β No unnecessary re-renders (use React DevTools)
β Phase 5 Completion Criteria
Before Moving to Phase 6
- β Loading states display during data fetching
- β Error boundaries catch and handle errors gracefully
- β Images are optimized and lazy loaded
- β Routes are code-split and lazy loaded
- β Lighthouse performance score > 90
- β WCAG 2.1 AA accessibility compliance
- β Keyboard navigation works throughout app
- β Screen readers can navigate effectively
- β Dark mode implemented (optional)
- β Empty states provide clear guidance
- β Keyboard shortcuts work for common actions
- β Animations are smooth and purposeful
- β Bundle size is optimized
- β App feels polished and professional
π§ͺ Phase 6: Testing & Deployment (6-8 hours)
Ensure your application is production-ready with comprehensive testing, CI/CD setup, and professional deployment.
β Phase 6 Checklist
Tasks to Complete
- Write unit tests for utility functions
- Write component tests for UI components
- Test user interactions and workflows
- Test async operations and data fetching
- Write integration tests for key features
- Set up GitHub Actions for CI/CD
- Configure environment variables for production
- Deploy to Vercel or Netlify
- Set up error tracking (Sentry)
- Configure analytics (Google Analytics, Plausible)
- Set up monitoring and alerts
- Create comprehensive README documentation
π§ͺ Testing Strategy
Unit Tests for Utilities
// src/utils/__tests__/date.test.ts
import { describe, it, expect } from 'vitest';
import { formatDate, isOverdue, getDaysUntil } from '../date';
describe('date utilities', () => {
it('formats dates correctly', () => {
const date = new Date('2024-01-15');
expect(formatDate(date)).toBe('Jan 15, 2024');
});
it('detects overdue dates', () => {
const pastDate = new Date('2020-01-01');
const futureDate = new Date('2030-01-01');
expect(isOverdue(pastDate)).toBe(true);
expect(isOverdue(futureDate)).toBe(false);
});
it('calculates days until date', () => {
const date = new Date();
date.setDate(date.getDate() + 5);
expect(getDaysUntil(date)).toBe(5);
});
});
Component Tests
// src/components/board/__tests__/TaskCard.test.tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { TaskCard } from '../TaskCard';
import type { Task } from '../../../types';
const mockTask: Task = {
id: '1',
listId: 'list-1',
title: 'Test Task',
description: 'Test description',
priority: 'high',
status: 'todo',
position: 0,
createdAt: new Date(),
updatedAt: new Date(),
};
describe('TaskCard', () => {
it('renders task information', () => {
render( );
expect(screen.getByText('Test Task')).toBeInTheDocument();
expect(screen.getByText('Test description')).toBeInTheDocument();
expect(screen.getByText('high')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render( );
fireEvent.click(screen.getByText('Test Task'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('displays overdue indicator for past due dates', () => {
const overdueTask = {
...mockTask,
dueDate: new Date('2020-01-01'),
};
render( );
const dateElement = screen.getByText(/Jan 1/);
expect(dateElement).toHaveClass('text-red-600');
});
});
Integration Tests
// src/__tests__/integration/task-workflow.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ProjectBoard } from '../../pages/ProjectBoard';
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return ({ children }: { children: React.ReactNode }) => (
{children}
);
};
describe('Task Workflow', () => {
it('creates, edits, and completes a task', async () => {
const user = userEvent.setup();
render( , { wrapper: createWrapper() });
// Create task
const createButton = screen.getByRole('button', { name: /create task/i });
await user.click(createButton);
const titleInput = screen.getByLabelText(/title/i);
await user.type(titleInput, 'New Test Task');
const submitButton = screen.getByRole('button', { name: /create/i });
await user.click(submitButton);
// Verify task appears
await waitFor(() => {
expect(screen.getByText('New Test Task')).toBeInTheDocument();
});
// Edit task
const taskCard = screen.getByText('New Test Task');
await user.click(taskCard);
const editButton = screen.getByRole('button', { name: /edit/i });
await user.click(editButton);
const descriptionInput = screen.getByLabelText(/description/i);
await user.type(descriptionInput, 'Updated description');
const saveButton = screen.getByRole('button', { name: /save/i });
await user.click(saveButton);
// Complete task
const completeButton = screen.getByRole('button', { name: /complete/i });
await user.click(completeButton);
await waitFor(() => {
expect(screen.getByText(/completed/i)).toBeInTheDocument();
});
});
});
π CI/CD Setup
GitHub Actions Workflow
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run type check
run: npm run type-check
- name: Run tests
run: npm run test:ci
env:
CI: true
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
env:
VITE_FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.FIREBASE_AUTH_DOMAIN }}
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Environment Configuration
# .env.production
VITE_APP_ENV=production
VITE_API_URL=https://api.taskflow.com
VITE_FIREBASE_API_KEY=your_production_key
VITE_FIREBASE_AUTH_DOMAIN=taskflow-prod.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=taskflow-prod
VITE_SENTRY_DSN=your_sentry_dsn
VITE_GA_TRACKING_ID=your_ga_id
π Monitoring & Analytics
Error Tracking with Sentry
// src/lib/sentry.ts
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
export function initSentry() {
if (import.meta.env.PROD) {
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [
new BrowserTracing(),
new Sentry.Replay(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
environment: import.meta.env.VITE_APP_ENV,
});
}
}
// Usage in main.tsx
import { initSentry } from './lib/sentry';
initSentry();
π‘ Deployment Best Practices
- Environment Variables: Never commit secrets to Git
- Preview Deployments: Test changes before production
- Automated Testing: Run tests on every commit
- Error Tracking: Monitor production errors in real-time
- Performance Monitoring: Track Core Web Vitals
- Rollback Plan: Be able to revert deployments quickly
β Phase 6 Completion Criteria
Production Ready Checklist
- β Unit test coverage > 70%
- β All critical user flows tested
- β CI/CD pipeline configured and running
- β Application deployed to production
- β Custom domain configured (optional)
- β SSL/HTTPS enabled
- β Error tracking active
- β Analytics configured
- β Performance monitoring in place
- β README documentation complete
- β All environment variables secured
- β Backup/disaster recovery plan
π‘ Implementation Guide
Here are additional tips and patterns to help you build your capstone project effectively.
ποΈ State Management Strategy
π‘ When to Use Which State Management
- useState: Component-local UI state (modals, form inputs)
- useReducer: Complex component state with multiple actions
- Context: Theme, user preferences, auth state
- Zustand/Redux: Truly global state (workspaces, projects)
- React Query: All server state (API data, cache)
π― Feature Implementation Order
β‘ Performance Tips
Optimize Component Re-renders
- Use React.memo() for expensive components
- Memoize callbacks with useCallback
- Memoize computed values with useMemo
- Split large components into smaller ones
- Use React DevTools Profiler to find bottlenecks
Optimize Data Fetching
- Implement pagination for large lists
- Use infinite scroll with React Query
- Prefetch data for likely next actions
- Cache aggressively, invalidate precisely
- Implement optimistic updates for better UX
Optimize Bundle Size
- Use dynamic imports for heavy libraries
- Tree-shake unused code
- Analyze bundle with webpack-bundle-analyzer
- Use lightweight alternatives (date-fns vs moment)
- Remove unused dependencies
π οΈ Debugging Tips
β οΈ Common Issues and Solutions
- Infinite re-renders: Check useEffect dependencies
- Stale closures: Update dependency arrays properly
- Type errors: Ensure proper TypeScript types everywhere
- State not updating: Don't mutate state directly
- Tests failing: Mock external dependencies properly
- Memory leaks: Clean up subscriptions in useEffect
π Recommended Resources
| Resource | Description | When to Use |
|---|---|---|
| React Documentation | Official React docs | Understanding core concepts |
| TanStack Query Docs | Data fetching patterns | Implementing server state |
| dnd kit Documentation | Drag and drop guides | Building Kanban board |
| Firebase Docs | Backend integration | Auth and real-time features |
| Tailwind CSS Docs | Utility-first styling | Styling components |
π Evaluation Criteria
Your capstone project will be evaluated based on the following criteria. Use this as a checklist to ensure you've met all requirements.
π― Grading Rubric
| Category | Weight | Criteria |
|---|---|---|
| Functionality | 30% |
- All MVP features implemented - Features work as expected - No critical bugs - Edge cases handled |
| Code Quality | 25% |
- Clean, readable code - Proper TypeScript usage - Consistent code style - Follows React best practices |
| Architecture | 20% |
- Well-organized folder structure - Proper separation of concerns - Reusable components - Efficient state management |
| UI/UX | 15% |
- Professional appearance - Responsive design - Intuitive user experience - Accessibility compliance |
| Testing | 10% |
- Adequate test coverage - Tests for key features - Tests actually pass - Good test practices |
π Excellence Indicators
π What Makes a Portfolio-Worthy Project
- Professional Polish: Looks and feels like a real product
- Attention to Detail: Loading states, error handling, empty states
- Performance: Fast, responsive, optimized
- Accessibility: Works for everyone, keyboard navigation
- Documentation: Comprehensive README, code comments
- Real-World Features: Goes beyond basic CRUD
- Production Ready: Deployed, monitored, maintained
π Submission Requirements
What to Submit
- Live Demo URL: Deployed application on Vercel/Netlify
- GitHub Repository: Complete source code with README
- Demo Video: 3-5 minute walkthrough of key features (optional)
- Project Report: Brief document covering:
- Technical decisions and trade-offs
- Challenges faced and solutions
- Features implemented and future plans
- What you learned from the project
π README Template
# TaskFlow Pro
A collaborative project management platform built with React, TypeScript, and Firebase.
## π Features
- **Workspace Management:** Create and manage team workspaces
- **Kanban Boards:** Drag-and-drop task organization
- **Real-Time Collaboration:** Live updates across team members
- **Analytics Dashboard:** Track project progress and team productivity
- **Comments & Mentions:** Collaborate directly on tasks
- **Role-Based Access:** Owner, Admin, Member, and Viewer roles
## π οΈ Tech Stack
- **Frontend:** React 18, TypeScript, Vite
- **Styling:** Tailwind CSS
- **State Management:** Zustand + TanStack Query
- **Routing:** React Router v6
- **Forms:** React Hook Form + Zod
- **Drag & Drop:** dnd kit
- **Charts:** Recharts
- **Backend:** Firebase (Auth, Firestore, Storage)
- **Testing:** Vitest, React Testing Library
- **Deployment:** Vercel
## π Getting Started
### Prerequisites
- Node.js 18+
- npm or pnpm
### Installation
```bash
git clone https://github.com/yourusername/taskflow-pro.git
cd taskflow-pro
npm install
```
### Environment Setup
Create a `.env` file:
```
VITE_FIREBASE_API_KEY=your_api_key
VITE_FIREBASE_AUTH_DOMAIN=your_domain
VITE_FIREBASE_PROJECT_ID=your_project_id
```
### Run Development Server
```bash
npm run dev
```
Visit http://localhost:5173
## π§ͺ Testing
```bash
npm run test # Run tests
npm run test:ui # Open Vitest UI
npm run coverage # Generate coverage report
```
## π¦ Building
```bash
npm run build # Create production build
npm run preview # Preview production build
```
## π― Key Features Implemented
### Phase 1: Foundation
- [x] Project setup with Vite + TypeScript
- [x] Tailwind CSS configuration
- [x] React Router setup
### Phase 2: Authentication
- [x] User registration and login
- [x] Email verification
- [x] Password reset flow
- [x] Protected routes
### Phase 3: Core Features
- [x] Workspace CRUD operations
- [x] Project management
- [x] Kanban board with drag & drop
- [x] Task management
### Phase 4: Advanced Features
- [x] Real-time updates
- [x] Comment system with @mentions
- [x] Notifications
- [x] Analytics dashboard
### Phase 5: Polish
- [x] Performance optimization
- [x] Accessibility (WCAG 2.1 AA)
- [x] Error boundaries
- [x] Loading states
### Phase 6: Production
- [x] Comprehensive testing
- [x] CI/CD pipeline
- [x] Production deployment
- [x] Error tracking
## π‘ Challenges & Solutions
**Challenge:** Implementing real-time updates across multiple users
**Solution:** Used Firebase onSnapshot with React Query cache invalidation
**Challenge:** Complex drag-and-drop state management
**Solution:** Leveraged dnd kit with optimistic updates
## π What I Learned
- Advanced TypeScript patterns and generics
- Real-time data synchronization strategies
- Production deployment and monitoring
- Writing maintainable, testable React code
## π§ Future Enhancements
- [ ] Mobile app with React Native
- [ ] Offline support with service workers
- [ ] Advanced filtering and search
- [ ] Calendar view for tasks
- [ ] Email integrations
## π License
MIT License
## π€ Author
Your Name - [GitHub](https://github.com/yourusername)
π Final Thoughts & Next Steps
π Congratulations!
You've reached the capstone projectβthe culmination of everything you've learned in this course. This project is your opportunity to showcase your skills, creativity, and problem-solving abilities. Take your time, build something you're proud of, and remember: this is what goes in your portfolio!
πͺ Making the Most of Your Capstone
π‘ Success Strategies
- Start Early: Give yourself 2-3 weeks to build properly
- Build Daily: Consistent progress beats cramming
- Test Continuously: Don't wait until the end to test
- Git Often: Commit frequently with clear messages
- Document as You Go: Update README with each feature
- Deploy Early: Set up deployment in Phase 1, not Phase 6
- Ask for Feedback: Share your progress with peers
- Refactor Boldly: Don't be afraid to improve code
π― Time Management
| Week | Focus | Deliverables |
|---|---|---|
| Week 1 | Phases 1-2 | Setup complete, auth working, first deployment |
| Week 2 | Phases 3-4 | Core features working, real-time updates live |
| Week 3 | Phases 5-6 | Polished, tested, documented, production-ready |
π Going Above and Beyond
Optional: Add Advanced Features
If you finish early and want to challenge yourself further:
- Webhooks: Integrate with Slack or Discord
- AI Features: Auto-categorize tasks, smart suggestions
- Advanced Analytics: Burndown charts, velocity tracking
- Mobile App: Build React Native version
- Email Integration: Create tasks via email
- API Documentation: If you built custom backend
πΌ Using This in Job Applications
β How to Present Your Capstone
- Portfolio: Feature it prominently on your portfolio site
- Resume: List it under "Projects" with 2-3 bullet points
- LinkedIn: Share it as a project with screenshots
- GitHub: Pin it to your profile with excellent README
- Interviews: Use it to demonstrate technical decisions
- Case Study: Write a blog post about building it
π Continue Learning
After completing your capstone, consider exploring:
- Next.js: Server-side rendering and modern React patterns
- React Native: Build mobile apps with your React skills
- Advanced TypeScript: Type gymnastics and advanced patterns
- System Design: Architecture for large-scale applications
- DevOps: CI/CD, Docker, Kubernetes
- Open Source: Contribute to React ecosystem projects
π¬ Get Help & Support
β οΈ When You Get Stuck
Remember these debugging strategies:
- Read the Error: Error messages usually tell you what's wrong
- Console.log Strategically: Trace data flow through your app
- React DevTools: Inspect component state and props
- Check Dependencies: Ensure useEffect dependencies are correct
- Isolate the Problem: Create a minimal reproduction
- Google Effectively: Include error message and technologies
- Ask for Help: StackOverflow, Discord, Reddit with code examples
ποΈ Badge of Completion
π
React TypeScript Capstone
By completing this capstone project, you demonstrate
mastery of production-level React development with TypeScript.
You are now ready for professional React developer positions!
β¨ Final Words
This capstone is more than just a projectβit's proof of your journey from beginner to skilled developer. You've learned TypeScript, mastered React, built complex state management, implemented real-time features, and deployed to production. That's incredible!
Take pride in what you build. Make it uniquely yours. Add features that excite you. Polish it until it shines. This is the project that will open doors for you.
Remember: the best developers aren't those who know everythingβthey're those who can figure anything out. You've proven you can do that. Now go build something amazing!
Good luck with your capstone! π
You've got this! πͺ