This guide is designed for someone new to React who wants to understand how this web application works, where to find things, and how to make changes safely.
- What This App Does
- The Big Picture: How Everything Connects
- React Basics in This App
- Finding Your Way Around
- How Pages Connect
- Where to Change UI Elements
- How Data Flows
- How to Add New Features
- Quick Reference: Common Tasks
This is a Tableau Cloud Migration Checklist web application with two types of users:
- InterWorks Consultants (admins): Create migration projects, manage questions, oversee all clients
- Client Users (guests): Answer questions for their assigned migration project
Think of it like a smart survey system where consultants create custom checklists for clients to fill out during their Tableau migration process.
┌─────────────────────────────────────────────────────────────┐
│ USER VISITS SITE │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────┐
│ Login Page │ (frontend/src/pages/Login.jsx)
└──────┬──────┘
│ Credentials sent to backend
▼
┌──────────────────────┐
│ Backend API │ (backend/src/routes/auth.js)
│ Verifies & Returns │
│ JWT Token │
└──────┬───────────────┘
│ Token stored in browser
▼
┌────────────────────────────┐
│ Dashboard Page │ (frontend/src/pages/Dashboard.jsx)
│ │
│ Shows all migrations as │
│ cards with progress bars │
└────────┬──────────┬────────┘
│ │
┌──────────┘ └──────────┐
│ Click "View" │ Click "New Migration"
▼ ▼
┌────────────────────┐ ┌─────────────────┐
│ Migration Checklist│ │ Create Dialog │
│ Page │ │ (Pick a client) │
│ │ └─────────────────┘
│ - Client Info │
│ - 54 Questions │
│ - Progress Bar │
│ - Save Button │
└────────────────────┘
For InterWorks users only:
├─ User Management Page (create/delete users)
└─ Client Management Page (manage client records)
If you're new to React, here are the key concepts used in this codebase:
Think of components like LEGO blocks. A button is a component, a form is a component made of smaller components.
// A simple component
function Button({ text, onClick }) {
return <button onClick={onClick}>{text}</button>;
}
// Using it
<Button text="Save" onClick={handleSave} />Props are like function arguments. You pass data into a component to customize it.
<MigrationCard
clientName="Acme Corp"
progress={75}
/>State is data that can change over time (user input, loaded data, etc.). When state changes, React re-renders the component.
const [email, setEmail] = useState(''); // email starts as empty string
setEmail('user@example.com'); // changes email, triggers re-renderHooks let you use React features. They start with use:
useState- Store changing datauseEffect- Run code when component loads or data changesuseAuth- Custom hook for login/logout (specific to this app)useMigration- Custom hook for loading/saving migrations (specific to this app)
The frontend (React) talks to the backend (Node.js) through HTTP requests:
// Get data from backend
const response = await api.get('/migrations');
const migrations = response.data;
// Send data to backend
await api.post('/migrations', { clientId: '123' });TCMApp/
├── frontend/ # React app (what users see)
│ └── src/
│ ├── pages/ # Full pages (5 files)
│ ├── components/ # Reusable UI pieces (organized by size)
│ ├── hooks/ # Shared logic (auth, data loading)
│ └── lib/ # Utilities (API calls)
│
└── backend/ # Node.js server (handles data)
└── src/
├── routes/ # API endpoints (URLs the frontend calls)
├── models/ # Database structure (User, Migration)
└── middleware/ # Security (checks if user is logged in)
Components are organized by size/complexity:
ui/ (smallest)
↓ Basic building blocks: Button, Input, Checkbox
atoms/
↓ Simple components: InfoTooltip, ProgressBar
molecules/
↓ Combinations: QuestionCheckbox (checkbox + label + timestamp)
organisms/
↓ Complex features: QuestionSection (multiple questions in a collapsible section)
templates/
↓ Page layouts: DashboardLayout (header + content area)
pages/ (largest)
↓ Complete pages: Dashboard, Login, MigrationChecklist
Why this matters: If you want to change how a checkbox looks, you go to molecules/QuestionCheckbox.jsx. If you want to change the entire dashboard layout, you go to pages/Dashboard.jsx.
START → Login Page
│
│ (after login, role check)
▼
Dashboard ─────────────┐
│ │
│ │ (InterWorks only)
│ ▼
│ User Management
│ Client Management
│
│ (click View on a migration card)
▼
Migration Checklist Page
│
│ - View/Edit Client Info
│ - Answer Questions
│ - Click Save
│ - Export PDF (coming soon)
│
│ (click back/logo)
▼
Dashboard
Found in: frontend/src/App.jsx
// Public routes (no login required)
/login → Login Page
// Protected routes (must be logged in)
/ → Dashboard
/migration/:id → MigrationChecklist (shows specific migration)
/users → UserManagement (InterWorks only)
/clients → ClientManagement (InterWorks only)How it works:
ProtectedRoutecomponent wraps protected pages- Before rendering, it checks: "Is the user logged in?"
- If NO → Redirect to
/login - If YES → Show the page
- Extra check: If page requires InterWorks role and user is a guest → Redirect to dashboard
Login.jsx (frontend/src/pages/Login.jsx)
└── Uses: Input, Button, Card from ui/
To change:
- Form layout: Lines 75-125 in
Login.jsx - Logo/title: Lines 80-85 in
Login.jsx
Dashboard.jsx (frontend/src/pages/Dashboard.jsx)
├── Header (organism)
│ ├── Logo
│ ├── Navigation menu
│ └── User info + Logout
├── SearchFilter (molecule)
│ ├── Search input
│ └── Status dropdown
└── Migration Cards Grid
└── MigrationCard (organism) × many
├── Client name
├── Progress bar
└── View/Delete buttons
To change:
- Header appearance:
components/organisms/Header.jsx(lines 15-89) - Card layout:
components/organisms/MigrationCard.jsx(lines 25-78) - Grid columns:
pages/Dashboard.jsxline 225 (currently 3 columns on desktop) - Search bar:
components/molecules/SearchFilter.jsx
MigrationChecklist.jsx (frontend/src/pages/MigrationChecklist.jsx)
├── MigrationLayout (template)
│ ├── Header (organism)
│ ├── ProgressSection (organism) - sticky
│ │ └── Shows "X/Y questions completed"
│ └── Page Header - sticky
│ ├── SearchFilter
│ └── Save Button
├── ClientInfoSection (organism)
│ └── ClientInfoField (molecule) × 8 fields
│ ├── Client Name
│ ├── Region
│ ├── Server URL
│ └── Dates, contacts, etc.
└── QuestionSection (organism) × 7 sections
└── QuestionItem (organism) × many
└── Renders appropriate type:
├── QuestionCheckbox
├── QuestionTextInput
├── QuestionDateInput
├── QuestionDropdown
└── QuestionYesNo
To change:
- Client info fields:
components/organisms/ClientInfoSection.jsx(lines 13-22) - Question section appearance:
components/organisms/QuestionSection.jsx - Checkbox styling:
components/molecules/QuestionCheckbox.jsx - Add new question type: Create in
molecules/, add case inQuestionItem.jsx(lines 32-93) - Page layout:
components/templates/MigrationLayout.jsx
UserManagement.jsx (frontend/src/pages/UserManagement.jsx)
├── Header (organism)
├── "New User" button
├── User Table
│ └── Shows email, name, role, client
└── Create User Dialog (modal)
├── Email field
├── Name field
├── Password field
├── Role selector (Guest/InterWorks)
└── Client dropdown (for Guest users)
To change:
- Form fields: Lines 240-327 in
UserManagement.jsx - Table layout: Lines 171-221 in
UserManagement.jsx - Client selector:
components/molecules/ClientSelector.jsx
This app uses Tailwind CSS, which means styling is done with class names instead of separate CSS files.
Common patterns you'll see:
// Spacing
<div className="p-4"> // padding: 1rem (16px)
<div className="mt-6"> // margin-top: 1.5rem (24px)
<div className="space-y-4"> // gap between children
// Layout
<div className="flex items-center justify-between"> // flexbox
<div className="grid grid-cols-2 gap-4"> // 2-column grid
// Colors (purple theme)
<div className="bg-primary"> // purple background
<div className="text-primary"> // purple text
<div className="border-destructive"> // red border (errors)
// Responsive (mobile-first)
<div className="grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
// 1 column on mobile, 2 on tablet, 3 on desktopTo change the theme colors: Edit frontend/src/index.css (lines with CSS variables)
This section explains how data moves through the app from user action to database and back.
┌─────────────────────────────────────────────────────────────┐
│ 1. USER ENTERS EMAIL + PASSWORD │
│ Location: Login.jsx │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. FRONTEND SENDS TO BACKEND │
│ Code: api.post('/auth/login', { email, password }) │
│ File: hooks/useAuth.jsx (login function) │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. BACKEND CHECKS DATABASE │
│ File: backend/src/routes/auth.js │
│ - Finds user by email │
│ - Compares password (encrypted) │
│ - Creates JWT token (expires in 7 days) │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. FRONTEND STORES TOKEN │
│ - Saves to browser localStorage │
│ - Token automatically added to all future requests │
│ - User object saved (email, role, name) │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. USER REDIRECTED TO DASHBOARD │
│ Now authenticated for all protected pages │
└─────────────────────────────────────────────────────────────┘
Key files:
- Frontend:
frontend/src/hooks/useAuth.jsx(manages login state) - Backend:
backend/src/routes/auth.js(verifies credentials) - Token storage: Browser localStorage (survives page refresh)
┌─────────────────────────────────────────────────────────────┐
│ USER OPENS MIGRATION CHECKLIST │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. COMPONENT LOADS │
│ File: pages/MigrationChecklist.jsx │
│ Hook: useMigration(id) is called │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. FETCH DATA FROM BACKEND │
│ Code: api.get(`/migrations/${id}`) │
│ File: hooks/useMigration.js (fetchMigration function) │
│ - Token automatically attached to request │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. BACKEND GETS FROM DATABASE │
│ File: backend/src/routes/migrations.js │
│ - Verifies user is allowed to see this migration │
│ - Loads migration with all 54 questions │
│ - Calculates progress percentage │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. FRONTEND DISPLAYS DATA │
│ - Questions rendered in sections │
│ - Client info shown in fields │
│ - Progress bar updated │
└─────────────────────────────────────────────────────────────┘
│
│ USER ANSWERS A QUESTION
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. OPTIMISTIC UPDATE (IMMEDIATE) │
│ File: hooks/useMigration.js (updateQuestion function) │
│ - Updates React state immediately (UI responds fast) │
│ - Marks as "unsaved changes" │
│ - Does NOT send to backend yet │
└─────────────────────────────────────────────────────────────┘
│
│ USER CLICKS "SAVE" BUTTON
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. SAVE TO BACKEND │
│ Code: api.put(`/migrations/${id}`, migration) │
│ File: hooks/useMigration.js (saveMigration function) │
│ - Sends entire migration object │
│ - Shows "Saving..." status │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 7. BACKEND SAVES TO DATABASE │
│ File: backend/src/routes/migrations.js (PUT route) │
│ - Validates data │
│ - Updates MongoDB document │
│ - Returns success │
└────────┬────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 8. FRONTEND CONFIRMS SAVE │
│ - Shows "Saved at 2:45 PM" │
│ - Clears "unsaved changes" flag │
└─────────────────────────────────────────────────────────────┘
Important concept - "Optimistic Updates": When you answer a question, the UI updates immediately (you see the checkbox checked right away). But the data isn't sent to the backend until you click Save. This makes the app feel fast while still giving you control over when data is saved.
Every time the frontend needs data from the backend, it uses this pattern:
Frontend (React):
import api from '@/lib/api';
// GET request (fetch data)
const response = await api.get('/migrations');
const migrations = response.data;
// POST request (create data)
await api.post('/migrations', { clientId: '123' });
// PUT request (update data)
await api.put('/migrations/456', { questions: [...] });
// DELETE request (remove data)
await api.delete('/migrations/789');What happens behind the scenes:
-
Request interceptor (in
lib/api.js) automatically adds the JWT token:Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... -
Backend middleware (in
backend/src/middleware/auth.js) verifies the token:- Checks token is valid and not expired
- Loads user from database
- Attaches user info to the request
- If invalid: Returns 401 error → Frontend logs user out
-
Route handler processes the request:
- Checks user has permission (role-based)
- Queries/updates MongoDB
- Returns response
┌────────────────────────────────────────────────────────┐
│ BROWSER (Frontend) │
├────────────────────────────────────────────────────────┤
│ localStorage: │
│ - authToken (JWT, survives page refresh) │
│ - user (email, role, name) │
│ │
│ React State (lost on page refresh): │
│ - Current migration data │
│ - User input before saving │
│ - Loading/error states │
└────────────────────────────────────────────────────────┘
│
│ API calls
▼
┌────────────────────────────────────────────────────────┐
│ BACKEND (Node.js/Express) │
├────────────────────────────────────────────────────────┤
│ Memory (temporary): │
│ - Request/response objects │
│ - User session during request │
└────────────────────────────────────────────────────────┘
│
│ Database queries
▼
┌────────────────────────────────────────────────────────┐
│ DATABASE (MongoDB) │
├────────────────────────────────────────────────────────┤
│ Permanent storage: │
│ - Users (email, password, role) │
│ - Migrations (client info, questions, answers) │
│ - Clients (company information) │
└────────────────────────────────────────────────────────┘
1. Plan the change:
- Where will users see it? (Dashboard cards? Checklist page?)
- Who can edit it? (InterWorks only? Or clients too?)
- What type of data? (Date picker)
2. Update the database model:
File: backend/src/models/Migration.js
const MigrationSchema = new mongoose.Schema({
// ... existing fields ...
dueDate: {
type: Date,
required: false, // Optional field
},
});3. Update the API (if needed):
File: backend/src/routes/migrations.js
The PUT route already saves all migration fields, so no change needed! But if you want validation:
// In the PUT /migrations/:id route
if (req.body.dueDate && !isValidDate(req.body.dueDate)) {
return res.status(400).json({ error: 'Invalid due date' });
}4. Add UI component:
File: frontend/src/components/molecules/DueDateField.jsx (new file)
import { Label } from '@/components/ui/Label';
import { Input } from '@/components/ui/Input';
export function DueDateField({ value, onChange, readOnly }) {
return (
<div className="space-y-2">
<Label>Due Date</Label>
<Input
type="date"
value={value || ''}
onChange={(e) => onChange(e.target.value)}
disabled={readOnly}
/>
</div>
);
}5. Add to the page:
File: frontend/src/components/organisms/ClientInfoSection.jsx
import { DueDateField } from '@/components/molecules/DueDateField';
// Inside the render:
<DueDateField
value={clientInfo.dueDate}
onChange={(value) => onChange('dueDate', value)}
readOnly={readOnly}
/>6. Test:
- Can InterWorks users set a due date?
- Does it save to the database?
- Does it show up after page refresh?
- Can client users see it (but not edit it)?
Frontend changes:
-
UI-only changes (styling, layout):
- Modify component JSX and Tailwind classes
- No backend changes needed
-
New form fields:
- Add to appropriate molecule component
- Update state management (useState or hook)
- Wire up onChange handlers
-
New pages:
- Create in
pages/folder - Add route in
App.jsx - Wrap with
ProtectedRouteif authentication required
- Create in
Backend changes:
-
New data fields:
- Update model in
backend/src/models/ - Schema automatically handles saving
- Update model in
-
New API endpoints:
- Add route in
backend/src/routes/ - Use
requireAuthmiddleware for protected routes - Use
requireInterWorksfor admin-only routes
- Add route in
-
New permissions:
- Check
req.user.rolein route handlers - Return 403 if not authorized
- Check
1. Component Creation:
// Always start with imports
import { useState } from 'react';
import { Button } from '@/components/ui/Button';
// Export named component
export function MyComponent({ prop1, prop2 }) {
// State at the top
const [data, setData] = useState(null);
// Event handlers
const handleClick = () => {
// ...
};
// Render
return (
<div>
{/* JSX here */}
</div>
);
}2. API Call Pattern:
import api from '@/lib/api';
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await api.get('/endpoint');
setData(response.data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};3. Backend Route Pattern:
const requireAuth = require('../middleware/auth');
router.post('/endpoint', requireAuth, async (req, res) => {
try {
// 1. Validate input
if (!req.body.requiredField) {
return res.status(400).json({ error: 'Missing field' });
}
// 2. Check permissions
if (req.user.role !== 'interworks') {
return res.status(403).json({ error: 'Not authorized' });
}
// 3. Do the work
const result = await Model.create(req.body);
// 4. Return success
res.status(201).json({ success: true, data: result });
} catch (error) {
res.status(500).json({ error: error.message });
}
});| What | Where to Look | Line Numbers |
|---|---|---|
| Login page styling | frontend/src/pages/Login.jsx |
75-125 |
| Dashboard card appearance | frontend/src/components/organisms/MigrationCard.jsx |
25-78 |
| Header (logo, menu) | frontend/src/components/organisms/Header.jsx |
15-89 |
| Button styles | frontend/src/components/ui/Button.jsx |
Entire file |
| Colors (theme) | frontend/src/index.css |
CSS variables |
| Question checkbox | frontend/src/components/molecules/QuestionCheckbox.jsx |
15-50 |
| What | Where to Look |
|---|---|
| How login works | frontend/src/hooks/useAuth.jsx + backend/src/routes/auth.js |
| How migrations save | frontend/src/hooks/useMigration.js + backend/src/routes/migrations.js |
| Database structure | backend/src/models/ (User.js, Migration.js) |
| API token handling | frontend/src/lib/api.js + backend/src/middleware/auth.js |
| Default questions | backend/src/seeds/questionTemplate.js |
| Task | Steps |
|---|---|
| New form field | 1. Add to database model (backend/src/models/) 2. Create molecule component (frontend/src/components/molecules/) 3. Add to organism/page 4. Wire up onChange handler |
| New question type | 1. Create molecule in molecules/Question*.jsx2. Add case in organisms/QuestionItem.jsx3. Update backend validation if needed |
| New page | 1. Create in pages/ folder2. Add route in App.jsx3. Add to navigation in Header.jsx4. Create API endpoint in backend/src/routes/ |
| New permission level | 1. Add role check in backend/src/middleware/auth.js2. Update ProtectedRoute if needed3. Add conditional rendering in components |
| Problem | Check These Files |
|---|---|
| Can't log in | useAuth.jsx, backend/routes/auth.js, check browser console |
| Data not saving | useMigration.js (saveToBackend function), backend routes |
| Page not loading | App.jsx (routes), ProtectedRoute.jsx (auth check) |
| API error | Browser Network tab, backend console, middleware/auth.js |
| Styling broken | Check Tailwind classes, index.css for custom CSS |
🎨 STYLING
frontend/src/index.css # Theme colors, global CSS
frontend/src/components/ui/ # Base component styles
🔐 AUTHENTICATION
frontend/src/hooks/useAuth.jsx # Login/logout logic
backend/src/routes/auth.js # Login API
backend/src/middleware/auth.js # Token verification
📄 PAGES
frontend/src/pages/Login.jsx # Login form
frontend/src/pages/Dashboard.jsx # Migration list
frontend/src/pages/MigrationChecklist.jsx # Main checklist
frontend/src/pages/UserManagement.jsx # Create/manage users
🧩 KEY COMPONENTS
frontend/src/components/organisms/
├── Header.jsx # Top navigation
├── MigrationCard.jsx # Dashboard cards
├── QuestionSection.jsx # Question grouping
└── QuestionItem.jsx # Question type router
frontend/src/components/molecules/
├── Question*.jsx # All question types
├── ClientSelector.jsx # Client dropdown
└── SearchFilter.jsx # Search/filter UI
🔌 DATA/API
frontend/src/hooks/useMigration.js # Load/save migrations
frontend/src/lib/api.js # HTTP client (Axios)
backend/src/routes/migrations.js # Migration CRUD API
backend/src/routes/users.js # User management API
💾 DATABASE
backend/src/models/User.js # User schema
backend/src/models/Migration.js # Migration schema
backend/src/seeds/questionTemplate.js # Default 54 questions
-
Always read a file before editing it
- Use the Read tool to see what's there
- Understand the current structure before making changes
-
Start small
- Test one change at a time
- Make sure it works before adding more
-
Follow existing patterns
- Look at how similar features are built
- Copy the structure and adapt it
-
Use the browser developer tools
- Console tab: See JavaScript errors
- Network tab: See API calls and responses
- Elements tab: Inspect HTML and see which component rendered what
-
Git is your friend
- Commit working code before trying something new
- You can always go back if something breaks
-
When stuck, trace the data flow
- User action → Component → Hook → API → Backend → Database
- Find where in that chain things go wrong
Now that you understand the structure:
- Explore a feature you want to change - Follow the file references to see how it works
- Try making a small change - Maybe change a button label or add a form field
- Read the CLAUDE.md files - There are detailed docs in
frontend/CLAUDE.mdandbackend/CLAUDE.md - Ask questions! - If you're unsure about something, ask before making changes
You've got this! The codebase is well-organized, and now you have a map to navigate it.