Skip to content

Latest commit

 

History

History
535 lines (414 loc) · 9.9 KB

File metadata and controls

535 lines (414 loc) · 9.9 KB

🛠️ Development Guide - TaskFlow

This guide provides detailed information for developers working on TaskFlow.


📋 Table of Contents

  1. Development Setup
  2. Architecture Overview
  3. Code Style
  4. Component Guidelines
  5. State Management
  6. Firebase Integration
  7. Common Patterns
  8. Troubleshooting

🚀 Development Setup

Required Tools

  • Node.js: v18 or higher
  • npm: v9 or higher
  • Git: Latest version
  • VSCode (recommended) with extensions:
    • ESLint
    • Prettier
    • Tailwind CSS IntelliSense
    • TypeScript Vue Plugin (Volar)

First Time Setup

# Clone repository
git clone https://github.com/yourusername/taskflow.git
cd taskflow

# Install dependencies
npm install

# Copy environment template
cp .env.example .env

# Start development server
npm run dev

Available Scripts

npm run dev          # Start dev server
npm run build        # Production build
npm run preview      # Preview production build
npm run lint         # Run ESLint
npm run type-check   # TypeScript type checking

🏗️ Architecture Overview

Technology Decisions

Why Zustand over Redux?

  • Lighter: Smaller bundle size
  • Simpler: Less boilerplate code
  • Flexible: No strict patterns required
  • TypeScript: Excellent type inference

Why React Query?

  • Caching: Automatic background refetching
  • Optimistic Updates: Better UX
  • DevTools: Excellent debugging
  • Loading States: Built-in management

Why Tailwind CSS?

  • Utility-First: Rapid development
  • Customizable: Easy theming
  • Responsive: Mobile-first approach
  • Performance: Purged in production

Data Flow

User Action
    ↓
Component Event Handler
    ↓
Custom Hook (useFirestore)
    ↓
React Query Mutation
    ↓
Firebase API
    ↓
Firestore Update
    ↓
Real-time Listener
    ↓
Zustand Store Update
    ↓
Component Re-render

🎨 Code Style

TypeScript Guidelines

// ✅ Good - Explicit types
interface UserProps {
  user: User;
  onUpdate: (user: User) => void;
}

// ❌ Bad - Implicit any
interface UserProps {
  user: any;
  onUpdate: (user) => void;
}

// ✅ Good - Use type inference when obvious
const [count, setCount] = useState(0);

// ❌ Bad - Unnecessary type annotation
const [count, setCount] = useState<number>(0);

Component Structure

// ✅ Good - Consistent structure
export const MyComponent = ({ prop1, prop2 }: Props) => {
  // 1. Hooks (state, context, custom hooks)
  const [state, setState] = useState();
  const customHook = useCustomHook();
  
  // 2. Event handlers
  const handleClick = () => {
    // ...
  };
  
  // 3. Effects
  useEffect(() => {
    // ...
  }, []);
  
  // 4. Render logic
  if (loading) return <Loading />;
  
  // 5. JSX
  return (
    <div>
      {/* Component content */}
    </div>
  );
};

Naming Conventions

// Components: PascalCase
export const BoardCard = () => {};

// Hooks: camelCase with 'use' prefix
export const useAuth = () => {};

// Utilities: camelCase
export const formatDate = () => {};

// Constants: UPPER_SNAKE_CASE
export const MAX_BOARD_TITLE_LENGTH = 100;

// Types/Interfaces: PascalCase
interface User {}
type CardStatus = 'todo' | 'doing' | 'done';

🧩 Component Guidelines

Creating New Components

  1. Create component file

    src/components/[category]/ComponentName.tsx
  2. Component template

    import { ComponentProps } from './types';
    
    export const ComponentName = ({ prop }: ComponentProps) => {
      return (
        <div className="...">
          {/* Content */}
        </div>
      );
    };
  3. Export from index (if applicable)

    // src/components/ui/index.ts
    export { Button } from './Button';
    export { Input } from './Input';

Component Categories

  • ui/ - Reusable UI primitives (Button, Input, Modal)
  • layout/ - Layout components (Navbar, Sidebar)
  • board/ - Board-specific components
  • features/ - Feature-specific components

Props Best Practices

// ✅ Good - Explicit props
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  children: ReactNode;
  onClick?: () => void;
}

// ❌ Bad - Too flexible
interface ButtonProps {
  [key: string]: any;
}

🗄️ State Management

Zustand Store Pattern

// Create a slice
interface BoardSlice {
  boards: Board[];
  setBoards: (boards: Board[]) => void;
  addBoard: (board: Board) => void;
}

const createBoardSlice = (set): BoardSlice => ({
  boards: [],
  setBoards: (boards) => set({ boards }),
  addBoard: (board) => set((state) => ({ 
    boards: [...state.boards, board] 
  })),
});

// Combine slices
export const useStore = create<AppState>((set) => ({
  ...createBoardSlice(set),
  ...createUserSlice(set),
}));

When to Use Zustand vs React Query

Zustand (Client State)

  • UI state (modals, sidebars)
  • User preferences (theme)
  • Current selections

React Query (Server State)

  • API data (boards, cards)
  • Cached responses
  • Background refetching

🔥 Firebase Integration

Firestore Data Model

boards/
  {boardId}/
    - id: string
    - title: string
    - ownerId: string
    - memberIds: string[]
    
lists/
  {listId}/
    - id: string
    - boardId: string
    - title: string
    - order: number
    
cards/
  {cardId}/
    - id: string
    - listId: string
    - boardId: string
    - title: string
    - order: number

Query Patterns

// ✅ Good - Compound queries
const q = query(
  collection(db, 'cards'),
  where('boardId', '==', boardId),
  where('status', '==', 'active'),
  orderBy('order', 'asc')
);

// ✅ Good - Limit results
const q = query(
  collection(db, 'boards'),
  orderBy('updatedAt', 'desc'),
  limit(20)
);

Real-time Listeners

useEffect(() => {
  const unsubscribe = onSnapshot(
    doc(db, 'boards', boardId),
    (snapshot) => {
      if (snapshot.exists()) {
        setBoard(snapshot.data());
      }
    }
  );
  
  return () => unsubscribe();
}, [boardId]);

🔄 Common Patterns

Optimistic Updates

const updateCard = useMutation({
  mutationFn: async (updates) => {
    // Update Firestore
    await updateDoc(doc(db, 'cards', id), updates);
  },
  onMutate: async (updates) => {
    // Cancel outgoing queries
    await queryClient.cancelQueries(['cards']);
    
    // Snapshot previous value
    const previous = queryClient.getQueryData(['cards']);
    
    // Optimistically update
    queryClient.setQueryData(['cards'], (old) => 
      old.map((card) => 
        card.id === id ? { ...card, ...updates } : card
      )
    );
    
    return { previous };
  },
  onError: (err, variables, context) => {
    // Rollback on error
    queryClient.setQueryData(['cards'], context.previous);
  },
});

Loading States

const { data, isLoading, error } = useQuery({
  queryKey: ['boards'],
  queryFn: fetchBoards,
});

if (isLoading) return <Loading />;
if (error) return <Error message={error.message} />;
return <BoardList boards={data} />;

Error Handling

try {
  await mutateAsync(data);
  toast.success('Success!');
} catch (error) {
  if (error instanceof FirebaseError) {
    toast.error(error.message);
  } else {
    toast.error('Something went wrong');
  }
  console.error(error);
}

🎯 Testing Strategy

Unit Tests

import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders children', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });
  
  it('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click</Button>);
    screen.getByText('Click').click();
    expect(handleClick).toHaveBeenCalled();
  });
});

Integration Tests

// Test complete user flows
it('creates a new board', async () => {
  render(<App />);
  
  // Navigate to dashboard
  await screen.findByText('Dashboard');
  
  // Click create button
  screen.getByText('Create Board').click();
  
  // Fill form
  const input = screen.getByPlaceholderText('Board title');
  userEvent.type(input, 'My Board');
  
  // Submit
  screen.getByText('Create').click();
  
  // Verify
  await screen.findByText('My Board');
});

🐛 Troubleshooting

Common Issues

Firebase "Missing or insufficient permissions"

# Check Firestore rules
# Ensure user is authenticated
# Verify user has access to the resource

Tailwind styles not applying

# Restart dev server
npm run dev

# Clear cache
rm -rf node_modules/.vite

Type errors after updating dependencies

# Clear TypeScript cache
rm -rf node_modules/.cache

# Rebuild
npm run build

Debug Tools

React Query DevTools

import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

<QueryClientProvider client={queryClient}>
  <App />
  <ReactQueryDevtools />
</QueryClientProvider>

Zustand DevTools

import { devtools } from 'zustand/middleware';

export const useStore = create(
  devtools((set) => ({
    // store
  }))
);

📚 Resources


🤝 Getting Help

If you're stuck:

  1. Check this guide
  2. Search existing issues
  3. Ask in Discord
  4. Create a new issue

Happy coding! 🚀