# Website Flow Implementation - Task Breakdown

**Format**: Visual task structure for implementation  
**Status**: Ready to implement  

---

## 🎯 OVERVIEW: 5 Phases, 17 Major Tasks, ~17 Days

```
┌─────────────────────────────────────────────────────────────────────┐
│                    WEBSITE FLOW UNIFICATION                         │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Phase 1: Access Control (Middleware)                              │
│  ├─ WebsiteRequiredFilter (blocks access without website)          │
│  ├─ Update DashboardModule (add website check)                     │
│  ├─ Apply filter to routes                                         │
│  ├─ Write tests                                                    │
│  └─ Estimate: 3 days                                               │
│                                                                     │
│  Phase 2: Unified Onboarding                                       │
│  ├─ OnboardingController (view + logic)                            │
│  ├─ onboarding/index.php (form template)                           │
│  ├─ OnboardingApiController (API endpoint)                         │
│  ├─ Integration tests                                              │
│  └─ Estimate: 4 days                                               │
│                                                                     │
│  Phase 3: Consolidation (Cleanup)                                  │
│  ├─ Disable old entry points                                       │
│  ├─ Redirect to /onboard                                           │
│  ├─ Keep website management at /website                            │
│  ├─ E2E tests                                                      │
│  └─ Estimate: 2 days                                               │
│                                                                     │
│  Phase 4: Dashboard Completion                                     │
│  ├─ GET /api/v1/dashboard endpoint                                 │
│  ├─ KPI data (Google Search Console)                               │
│  ├─ Pipeline data (opportunities, approvals)                       │
│  ├─ Activity data (audit log)                                      │
│  ├─ dashboard/index.php rendering logic                            │
│  ├─ dashboard.js (fetch + hydrate)                                 │
│  ├─ Tests                                                          │
│  └─ Estimate: 5 days                                               │
│                                                                     │
│  Phase 5: Website Selector                                         │
│  ├─ Website dropdown component                                     │
│  ├─ localStorage persistence                                       │
│  ├─ Session management                                             │
│  ├─ Integration into layout/top                                    │
│  ├─ Tests                                                          │
│  └─ Estimate: 3 days                                               │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
```

---

## PHASE 1: Access Control (3 Days) ✓ FOUNDATION

### Task 1.1: Create WebsiteRequiredFilter (1 day)

**File**: `app/Filters/WebsiteRequiredFilter.php`

**Checklist**:
- [ ] Create filter class implementing FilterInterface
- [ ] Query: count websites for current user
- [ ] If count = 0, redirect to /onboard
- [ ] Otherwise, continue to next middleware
- [ ] Write unit test
- [ ] Handle logged-out users gracefully

**Code Pattern**:
```php
public function before(RequestInterface $request, $arguments = null) {
    $userId = session()->get('user_id');
    if (!$userId) return null; // auth filter handles
    
    $count = (new WebsiteModel())
        ->where('user_id', $userId)
        ->countAllResults();
    
    if ($count === 0) {
        return redirect()->to('/onboard');
    }
}
```

---

### Task 1.2: Update DashboardModule (1 day)

**File**: `app/Controllers/DashboardModule.php`

**Checklist**:
- [ ] Add $websiteCount property
- [ ] In initController(), query websites for current user
- [ ] If count = 0, redirect to /onboard
- [ ] Store count in $this->websiteCount for use in render()
- [ ] Add comments explaining the check
- [ ] Write unit test

**Modified Method**:
```php
public function initController(...) {
    parent::initController($request, $response, $logger);
    $this->user = (new UserModel())->find($this->userId);
    
    $this->websiteCount = (new WebsiteModel())
        ->where('user_id', $this->userId)
        ->countAllResults();
    
    if ($this->websiteCount === 0) {
        redirect('/onboard')->send();
        exit;
    }
}
```

---

### Task 1.3: Apply Filter to Routes (0.5 days)

**File**: `app/Config/Routes.php`

**Checklist**:
- [ ] Import WebsiteRequiredFilter in the filter array
- [ ] Apply filter to all dashboard routes EXCEPT /onboard
- [ ] Example: `['filter' => 'websiterequired|auth']`
- [ ] Test each route with/without websites
- [ ] Document which routes need the filter

**Routes Pattern**:
```php
// Routes WITHOUT filter (public access)
$routes->get('onboard', 'OnboardingController::index');

// Routes WITH filter (require ≥1 website)
$routes->get('dashboard', 'DashboardController::index', 
    ['filter' => 'websiterequired|auth']);
$routes->get('crawl', 'CrawlController::index',
    ['filter' => 'websiterequired|auth']);
// ... etc
```

---

### Task 1.4: Write & Run Tests (0.5 days)

**Checklist**:
- [ ] Unit test: WebsiteRequiredFilter with 0 websites
- [ ] Unit test: WebsiteRequiredFilter with 1+ websites
- [ ] Unit test: DashboardModule redirect on no websites
- [ ] Integration test: /dashboard redirects to /onboard if no websites
- [ ] Integration test: /dashboard allows access if websites exist
- [ ] Run full test suite

**Test Files**:
- `tests/unit/Filters/WebsiteRequiredFilterTest.php`
- `tests/integration/DashboardAccessTest.php`

---

## PHASE 2: Unified Onboarding (4 Days) ✓ NEW ENTRY POINT

### Task 2.1: Create OnboardingController (1 day)

**File**: `app/Controllers/OnboardingController.php`

**Checklist**:
- [ ] Extend DashboardModule (or create own base)
- [ ] Create index() method
- [ ] NO website requirement check (new users access this)
- [ ] Pass design data to view (page_title, page_kicker, extra_css/js)
- [ ] Write unit test

**Note**: This controller does NOT extend DashboardModule since new users won't have websites yet.

---

### Task 2.2: Create Onboarding View (1 day)

**File**: `app/Views/onboarding/index.php`

**Elements**:
- [ ] Header: "Add Your First Website"
- [ ] Subheader: "Get started with WebCrawlers"
- [ ] Domain input (protocol dropdown + domain field)
- [ ] Max pages slider (100-10,000, default 500)
- [ ] Verification method selector:
  - [ ] DNS TXT record (default)
  - [ ] HTML file upload
  - [ ] Google Search Console
- [ ] "Add Website" button
- [ ] Progress: "Step 1 of 3" (onboarding, verify, complete)

**Design**: Use `dash-*` component classes from existing design system

**References**:
- Design: `public/webcrawlers-dashboard-assets/dashboard.php`
- Components: `app/Views/dashboard/website.php` (reuse form elements)

---

### Task 2.3: Create Onboarding API (1 day)

**File**: `app/Controllers/Api/V1/OnboardingController.php`

**Endpoint**: `POST /api/v1/onboarding/complete-website`

**Input Validation**:
- [ ] domain (required, valid domain format)
- [ ] max_crawl_pages (required, 1-10000)
- [ ] protocol (https|http, default https)
- [ ] verification_method (dns_txt|html_file|search_console)
- [ ] sitemap_url (optional)

**Logic**:
- [ ] Check user ≥1 website limit NOT exceeded (use from WebsiteModel)
- [ ] Validate domain not already added by user
- [ ] Create website record via WebsiteModel::create()
- [ ] Return website data + next_url
- [ ] Handle errors with proper response codes

**Response** (success):
```json
{
  "success": true,
  "website": {
    "id": 123,
    "domain": "example.com",
    "status": "unverified"
  },
  "next_url": "/dashboard"
}
```

---

### Task 2.4: Update Routes (0.5 days)

**File**: `app/Config/Routes.php`

**Checklist**:
- [ ] Add GET /onboard → OnboardingController::index
- [ ] Add POST /api/v1/onboarding/complete-website → OnboardingApiController::complete
- [ ] No auth filter on /onboard (public)
- [ ] Auth/apiauth filter on API endpoint

---

### Task 2.5: Integration Tests (0.5 days)

**Checklist**:
- [ ] GET /onboard returns 200
- [ ] POST to complete-website with valid data → creates website
- [ ] POST with invalid domain → error 422
- [ ] POST with missing max_pages → error 422
- [ ] New user can complete onboarding flow end-to-end

---

## PHASE 3: Consolidation (2 Days) ⚠️ CLEANUP

### Task 3.1: Remove Dispersed Entry Points (0.5 days)

**Checklist**:
- [ ] Remove "Add Website" link from GET /settings/connections view
- [ ] Remove "Add Website" link from other dashboard pages
- [ ] Make GET /website "edit existing" only
- [ ] Add "+ Add Website" button on /website → /onboard

**Files to Update**:
- `app/Views/dashboard/settings-connections.php`
- `app/Views/dashboard/website.php`
- Any other view with website add link

---

### Task 3.2: Deprecate Old Endpoints (1 day)

**Checklist**:
- [ ] Decide: Remove POST /api/v1/websites or keep for mobile app?
- [ ] If keeping: Add deprecation header + documentation
- [ ] If removing: Create migration guide
- [ ] Redirect to /api/v1/onboarding/complete-website
- [ ] Document in API changelog

**Decision**:
- ⚠️ **Recommendation**: Keep POST /api/v1/websites for backward compatibility with mobile
- Move creation logic to shared service (both endpoints use it)
- POST /api/v1/websites redirects to /onboarding/complete-website in docs

---

### Task 3.3: E2E Flow Testing (0.5 days)

**Checklist**:
- [ ] New user login → redirects to /onboard ✓
- [ ] Add website → creates record ✓
- [ ] Redirects to /dashboard ✓
- [ ] /website shows list (only edit mode) ✓
- [ ] Can't access /crawl without website ✓
- [ ] After adding 2nd website, selector appears ✓

---

## PHASE 4: Dashboard Completion (5 Days) ✓ DATA & UI

### Task 4.1: Create Dashboard API Endpoint (1.5 days)

**File**: `app/Controllers/Api/V1/DashboardController.php`

**Endpoint**: `GET /api/v1/dashboard`

**Required Data**:

1. **KPIs** (from Google Search Console cache):
   ```json
   "kpis": {
     "organic_clicks": 48120,
     "organic_clicks_delta": 12.4,
     "organic_clicks_trend": [42, 55, 48, ...],  // 14 days
     "impressions": 1240000,
     "impressions_delta": 8.1,
     "impressions_trend": [40, 44, 50, ...],
     "conversions": 1043,
     "conversions_delta": 5.6,
     "attributed_revenue": 312400,
     "attributed_revenue_delta": 9.3
   }
   ```

2. **Pipeline Health** (from database):
   ```json
   "pipeline": {
     "open_opportunities": 37,
     "open_opportunities_highlight": "12 high impact",
     "pending_approvals": 6,
     "crawl_coverage_pct": 92,
     "connector_health_status": "3 of 4",
     "connector_health_details": ["GSC: active", "GA4: inactive", ...],
     "next_job": {
       "type": "full_crawl",
       "scheduled_time": "2026-08-18T23:00:00Z"
     }
   }
   ```

3. **Recent Activity** (from wc_audit_log or events):
   ```json
   "recent_activity": [
     {
       "type": "deployment",
       "title": "Deployment published",
       "description": "Emergency plumbing — Austin · v14",
       "tone": "success",
       "timestamp": "12m ago"
     },
     ...
   ]
   ```

**Checklist**:
- [ ] Create controller class extending BaseApiController
- [ ] Implement index() returning dashboard data
- [ ] Query GSC cache for KPIs (from SiteOverviewCacheModel)
- [ ] Query wc_opportunities for open_opportunities count
- [ ] Query wc_approvals for pending_approvals count
- [ ] Calculate crawl_coverage from wc_audits
- [ ] Get connector_health from wc_website_integrations
- [ ] Get next_job from wc_jobs table (if exists)
- [ ] Get recent_activity from audit log or new events table
- [ ] Add route: GET /api/v1/dashboard
- [ ] Add auth filter (apiauth)
- [ ] Error handling (return empty arrays on missing data)

---

### Task 4.2: Update Dashboard View (1.5 days)

**File**: `app/Views/dashboard/index.php`

**Current State**: Has skeleton with placeholders

**Changes**:
- [ ] Remove placeholder divs
- [ ] Add proper structure with data attributes (e.g., `data-kpi="organic_clicks"`)
- [ ] Create KPI stat components with spinner loading state
- [ ] Create pipeline health grid
- [ ] Create recent activity list
- [ ] Create trend chart container

**Markup Pattern**:
```php
<!-- KPI Tile -->
<div class="dash-stat" data-kpi="organic_clicks">
    <span class="dash-stat__label">Organic clicks</span>
    <span class="dash-stat__value" data-value="">—</span>
    <span class="dash-stat__delta" data-delta=""></span>
</div>

<!-- Activity List -->
<div id="recent-activity">
    <div class="dash-listrow" data-activity-item>
        <span class="dash-state" data-tone=""></span>
        <div class="dash-listrow__main">
            <div class="dash-listrow__title" data-title=""></div>
            <div class="dash-listrow__sub" data-description=""></div>
        </div>
        <span class="dash-table__mut" data-time=""></span>
    </div>
</div>
```

---

### Task 4.3: Create Dashboard JS (1.5 days)

**File**: `public/assets/js/dashboard.js`

**Functions**:

1. **boot()** - Initialize on page load
2. **loadDashboardData()** - Fetch from /api/v1/dashboard
3. **hydratKpis()** - Fill KPI tiles with values
4. **renderTrendChart()** - Render bars/line chart
5. **hydratePipeline()** - Fill pipeline health section
6. **hydrateActivity()** - Fill activity list

**Code Outline**:
```js
function boot() {
    if (typeof wcFetch !== 'function') {
        console.error('wcFetch not available');
        return;
    }
    loadDashboardData();
}

function loadDashboardData() {
    wcFetch('dashboard')
        .then(r => r.json())
        .then(data => {
            hydratKpis(data.kpis);
            renderTrendChart(data.kpis);
            hydratePipeline(data.pipeline);
            hydrateActivity(data.recent_activity);
        })
        .catch(err => console.error('Dashboard load failed', err));
}

function hydratKpis(kpis) {
    // For each KPI in kpis:
    //   - Find [data-kpi="key"]
    //   - Fill value, delta, trend
    //   - Remove loading spinner
}

function renderTrendChart(kpis) {
    // Create bar chart for clicks_trend + impressions_trend
    // Use D3 or simple div-based bars
    // 14 bars, height based on value
}

function hydratePipeline(pipeline) {
    // Fill each pipeline metric
    // Handle null/missing data
}

function hydrateActivity(activities) {
    // For each activity:
    //   - Create list row
    //   - Set tone badge
    //   - Format timestamp as "Xm ago"
}
```

---

### Task 4.4: Handle Missing Data (AC-03) (0.5 days)

**Requirement**: Show distinct "No data" instead of zero

**Checklist**:
- [ ] API returns `null` for unconnected data
- [ ] JS shows "—" or "Not connected" for nulls
- [ ] Not the same as 0 values
- [ ] Design note displayed: "Missing data shown distinctly from zero"

---

## PHASE 5: Website Selector (3 Days) ✓ NAVIGATION

### Task 5.1: Create Website Selector Component (1 day)

**File**: `app/Views/dashboard/components/website-selector.php`

**Markup**:
```php
<div class="dash-website-selector">
    <button class="dash-website-selector__trigger" id="wsBtn">
        <span class="dash-website-selector__name" id="wsName">
            Loading...
        </span>
        <svg>...</svg> <!-- Chevron down -->
    </button>
    <div class="dash-website-selector__menu d-none" id="wsMenu">
        <ul class="dash-website-selector__list" id="wsListl">
            <!-- Populated by JS -->
        </ul>
        <a href="<?= site_url('onboard') ?>" class="dash-website-selector__add">
            + Add Website
        </a>
    </div>
</div>
```

**Checklist**:
- [ ] Dropdown menu structure
- [ ] List of user's websites
- [ ] Radio/check active selection
- "+ Add Website" link → /onboard
- [ ] Click handler to close menu
- [ ] Keyboard navigation (arrow keys, enter)
- [ ] CSS styling (position, z-index, animations)

---

### Task 5.2: Integrate Website Selector into Layout (0.5 days)

**File**: `app/Views/dashboard/layout/top.php`

**Location**: Dashboard header/navbar, between logo and user menu

**Checklist**:
- [ ] Import website-selector component
- [ ] Render in correct position
- [ ] Pass user's websites to component (via JS fetch)
- [ ] Ensure z-index is correct (above other elements)

---

### Task 5.3: Create Website Selection JS (1 day)

**File**: `public/assets/js/website-selector.js`

**Functions**:

1. **boot()** - Initialize on page load
2. **loadWebsites()** - Fetch user's websites from /api/v1/websites
3. **renderWebsiteList()** - Populate dropdown menu
4. **selectWebsite()** - Handle website selection
5. **persistSelection()** - Save to localStorage & session
6. **restoreSelection()** - Load from localStorage on page load

**Behavior**:
```js
// On page load:
// 1. Check localStorage for last selected website
// 2. If exists & valid, use that
// 3. Otherwise, use primary website
// 4. Fetch current website data
// 5. Populate dropdown
// 6. Render in header

// On selection:
// 1. Update dropdown button
// 2. Save to localStorage
// 3. Make X-Website-Id API call for next action
// 4. Optionally reload dashboard
```

**Checklist**:
- [ ] GET /api/v1/websites → list all user websites
- [ ] Render list in dropdown menu
- [ ] Highlight current selection
- [ ] Click to select → update session
- [ ] Save to localStorage with key `wc_selected_website_id`
- [ ] On page load, restore from localStorage
- [ ] Add to all API calls: Header `X-Website-Id: {id}`
- [ ] Validate selection in middleware

---

### Task 5.4: Add Website ID Validation Middleware (0.5 days)

**File**: `app/Filters/WebsiteIdHeaderFilter.php`

**Checklist**:
- [ ] Read X-Website-Id header
- [ ] Validate format (numeric)
- [ ] Query: user owns this website
- [ ] If invalid, reject with 403
- [ ] If valid, continue

---

### Task 5.5: Tests (0.5 days)

**Checklist**:
- [ ] Website selector renders with user's websites
- [ ] Selection persists in localStorage
- [ ] Selection persists across page reloads
- [ ] X-Website-Id header sent on API calls
- [ ] Invalid website ID rejected
- [ ] Switching websites updates all data correctly

---

## 🧪 CROSS-PHASE TESTING

### Integration Tests

**Test Scenario 1: New User Onboarding**
```
1. User signs up → redirected to /onboard
2. Fills onboarding form (domain, max pages, verify)
3. Submits → website created
4. Redirected to /dashboard → dashboard loads
5. Website selector shows newly added site
✓ PASS
```

**Test Scenario 2: Multiple Websites**
```
1. User has 3 websites
2. Selector shows all 3
3. Clicks to switch → dashboard data updates
4. Website-specific data loads correctly
✓ PASS
```

**Test Scenario 3: Permissions**
```
1. User A tries to access Website B (owned by User C)
2. API call with X-Website-Id: B
3. Returns 403 Forbidden
✓ PASS
```

**Test Scenario 4: Dashboard Data**
```
1. User visits /dashboard
2. KPIs load within 500ms
3. Trend chart renders
4. Pipeline health populates
5. Recent activity shows events
✓ PASS
```

---

## 📊 PROGRESS TRACKING

Use this table to track implementation:

```
PHASE 1: Access Control
- [ ] 1.1 WebsiteRequiredFilter
- [ ] 1.2 DashboardModule update
- [ ] 1.3 Routes filter apply
- [ ] 1.4 Tests

PHASE 2: Onboarding
- [ ] 2.1 OnboardingController
- [ ] 2.2 Onboarding view
- [ ] 2.3 Onboarding API
- [ ] 2.4 Routes
- [ ] 2.5 Tests

PHASE 3: Consolidation
- [ ] 3.1 Remove dispersed entry points
- [ ] 3.2 Deprecate old endpoints
- [ ] 3.3 E2E tests

PHASE 4: Dashboard
- [ ] 4.1 API endpoint
- [ ] 4.2 View update
- [ ] 4.3 JS script
- [ ] 4.4 Missing data handling

PHASE 5: Website Selector
- [ ] 5.1 Component
- [ ] 5.2 Integration to layout
- [ ] 5.3 JS logic
- [ ] 5.4 Validation middleware
- [ ] 5.5 Tests
```

---

## ✅ SIGN-OFF CHECKLIST

Before deploying to production:

- [ ] All 17 tasks complete
- [ ] All unit tests passing
- [ ] All integration tests passing
- [ ] E2E test on staging passed
- [ ] Performance: Dashboard < 500ms load
- [ ] Security review passed
- [ ] Documentation updated
- [ ] Changelog entry added
- [ ] Support team notified
- [ ] Rollout plan reviewed

---

**Ready to begin implementation?** Start with Phase 1, Task 1.1 🚀
