This guide provides detailed information for developers working on TaskFlow.
- Development Setup
- Architecture Overview
- Code Style
- Component Guidelines
- State Management
- Firebase Integration
- Common Patterns
- Troubleshooting
- 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)
# 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 devnpm 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- Lighter: Smaller bundle size
- Simpler: Less boilerplate code
- Flexible: No strict patterns required
- TypeScript: Excellent type inference
- Caching: Automatic background refetching
- Optimistic Updates: Better UX
- DevTools: Excellent debugging
- Loading States: Built-in management
- Utility-First: Rapid development
- Customizable: Easy theming
- Responsive: Mobile-first approach
- Performance: Purged in production
User Action
↓
Component Event Handler
↓
Custom Hook (useFirestore)
↓
React Query Mutation
↓
Firebase API
↓
Firestore Update
↓
Real-time Listener
↓
Zustand Store Update
↓
Component Re-render
// ✅ 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);// ✅ 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>
);
};// 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';-
Create component file
src/components/[category]/ComponentName.tsx
-
Component template
import { ComponentProps } from './types'; export const ComponentName = ({ prop }: ComponentProps) => { return ( <div className="..."> {/* Content */} </div> ); };
-
Export from index (if applicable)
// src/components/ui/index.ts export { Button } from './Button'; export { Input } from './Input';
ui/- Reusable UI primitives (Button, Input, Modal)layout/- Layout components (Navbar, Sidebar)board/- Board-specific componentsfeatures/- Feature-specific components
// ✅ Good - Explicit props
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: ReactNode;
onClick?: () => void;
}
// ❌ Bad - Too flexible
interface ButtonProps {
[key: string]: any;
}// 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),
}));Zustand (Client State)
- UI state (modals, sidebars)
- User preferences (theme)
- Current selections
React Query (Server State)
- API data (boards, cards)
- Cached responses
- Background refetching
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
// ✅ 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)
);useEffect(() => {
const unsubscribe = onSnapshot(
doc(db, 'boards', boardId),
(snapshot) => {
if (snapshot.exists()) {
setBoard(snapshot.data());
}
}
);
return () => unsubscribe();
}, [boardId]);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);
},
});const { data, isLoading, error } = useQuery({
queryKey: ['boards'],
queryFn: fetchBoards,
});
if (isLoading) return <Loading />;
if (error) return <Error message={error.message} />;
return <BoardList boards={data} />;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);
}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();
});
});// 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');
});Firebase "Missing or insufficient permissions"
# Check Firestore rules
# Ensure user is authenticated
# Verify user has access to the resourceTailwind styles not applying
# Restart dev server
npm run dev
# Clear cache
rm -rf node_modules/.viteType errors after updating dependencies
# Clear TypeScript cache
rm -rf node_modules/.cache
# Rebuild
npm run buildReact 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
}))
);If you're stuck:
- Check this guide
- Search existing issues
- Ask in Discord
- Create a new issue
Happy coding! 🚀