# High Opportunity Keywords for Add Keywords Page

**Plan Date:** January 14, 2026  
**Target Page:** `/setup/add-keywords` ([IndustriesSectorsKeywords.tsx](../../frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx))  
**Reference Implementation:** Step 4 of Wizard ([Step4AddKeywords.tsx](../../frontend/src/components/onboarding/steps/Step4AddKeywords.tsx))

## Overview

Add a "High Opportunity Keywords" section to the top of the `/setup/add-keywords` page, similar to the wizard's Step 4 interface. This will allow users to quickly add curated keyword sets (Top 50 High Volume & Top 50 Low Difficulty) for each of their site's sectors.

## Current State

### Current Page Structure
- **Page:** `IndustriesSectorsKeywords.tsx` (854 lines)
- **Route:** `/setup/add-keywords`
- **Primary Function:** Browse and search global seed keywords with filters, sorting, and pagination
- **Current Features:**
  - Search by keyword text
  - Filter by country, difficulty, status (not added only)
  - Sort by keyword, volume, difficulty, country
  - Bulk selection and "Add Selected to Workflow"
  - Individual "Add to Workflow" buttons
  - Shows stats: X added / Y available
  - Admin CSV import functionality
  - Table-based display with pagination
  - Requires active sector selection to add keywords

### Wizard Step 4 Implementation
- **Component:** `Step4AddKeywords.tsx` (738 lines)
- **Features to Replicate:**
  - Groups keywords by sector (e.g., "Physiotherapy & Rehabilitation", "Relaxation Devices", "Massage & Therapy")
  - Two options per sector:
    - **Top 50 High Volume** - Keywords sorted by highest volume
    - **Top 50 Low Difficulty** - Keywords sorted by lowest difficulty (KD)
  - Shows keyword count and sample keywords (first 3 with "+X more" badge)
  - "Add All" button for each option
  - Visual feedback when keywords are added (success badge, green styling)
  - Loads seed keywords from API filtered by sector

## Proposed Implementation

### UI Design & Layout

```
┌─────────────────────────────────────────────────────────────────┐
│ Page Header: "Find Keywords"                                     │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Back to options (link)                                           │
│                                                                   │
│ High Opportunity Keywords (h2)                                   │
│ Add top keywords for each of your sectors. Keywords will be      │
│ added to your planner workflow.                                  │
│                                                                   │
│ ┌─────────────┬─────────────┬─────────────┐                    │
│ │ Sector 1    │ Sector 2    │ Sector 3    │ (3 columns)        │
│ ├─────────────┼─────────────┼─────────────┤                    │
│ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │                    │
│ │ │Top 50 HV│ │ │Top 50 HV│ │ │Top 50 HV│ │                    │
│ │ │X keywords│ │ │X keywords│ │ │X keywords│ │                    │
│ │ │[kw] [kw]│ │ │[kw] [kw]│ │ │[kw] [kw]│ │                    │
│ │ │+X more  │ │ │+X more  │ │ │+X more  │ │                    │
│ │ │[Add All]│ │ │[Add All]│ │ │[Add All]│ │                    │
│ │ └─────────┘ │ └─────────┘ │ └─────────┘ │                    │
│ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │                    │
│ │ │Top 50 LD│ │ │Top 50 LD│ │ │Top 50 LD│ │                    │
│ │ │X keywords│ │ │X keywords│ │ │X keywords│ │                    │
│ │ │[kw] [kw]│ │ │[kw] [kw]│ │ │[kw] [kw]│ │                    │
│ │ │+X more  │ │ │+X more  │ │ │+X more  │ │                    │
│ │ │[Add All]│ │ │[Add All]│ │ │[Add All]│ │                    │
│ │ └─────────┘ │ └─────────┘ │ └─────────┘ │                    │
│ └─────────────┴─────────────┴─────────────┘                    │
│                                                                   │
│ ✓ X keywords added to your workflow                              │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Browse All Keywords (existing table interface)                   │
│ [Search] [Filters] [Table] [Pagination]                         │
└─────────────────────────────────────────────────────────────────┘
```

### Component Architecture

#### 1. New State Management
Add to `IndustriesSectorsKeywords.tsx`:

```typescript
// High Opportunity Keywords state
const [showHighOpportunity, setShowHighOpportunity] = useState(true);
const [loadingOpportunityKeywords, setLoadingOpportunityKeywords] = useState(false);
const [sectorKeywordData, setSectorKeywordData] = useState<SectorKeywordData[]>([]);
const [addingOption, setAddingOption] = useState<string | null>(null);

interface SectorKeywordOption {
  type: 'high-volume' | 'low-difficulty';
  label: string;
  keywords: SeedKeyword[];
  added: boolean;
  keywordCount: number;
}

interface SectorKeywordData {
  sectorSlug: string;
  sectorName: string;
  sectorId: number;
  options: SectorKeywordOption[];
}
```

#### 2. Data Loading Function
Create `loadHighOpportunityKeywords()`:

```typescript
const loadHighOpportunityKeywords = async () => {
  if (!activeSite || !activeSite.industry) {
    setSectorKeywordData([]);
    return;
  }

  setLoadingOpportunityKeywords(true);

  try {
    // 1. Get site sectors
    const siteSectors = await fetchSiteSectors(activeSite.id);
    
    // 2. Get industry data
    const industriesResponse = await fetchIndustries();
    const industry = industriesResponse.industries?.find(
      i => i.id === activeSite.industry || i.slug === activeSite.industry_slug
    );

    if (!industry?.id) {
      console.warn('Could not find industry information');
      return;
    }

    // 3. Get already-attached keywords to mark as added
    const attachedSeedKeywordIds = new Set<number>();
    for (const sector of siteSectors) {
      try {
        const keywordsData = await fetchKeywords({
          site_id: activeSite.id,
          sector_id: sector.id,
          page_size: 1000,
        });
        (keywordsData.results || []).forEach((k: any) => {
          const seedKeywordId = k.seed_keyword_id || (k.seed_keyword && k.seed_keyword.id);
          if (seedKeywordId) {
            attachedSeedKeywordIds.add(Number(seedKeywordId));
          }
        });
      } catch (err) {
        console.warn(`Could not fetch attached keywords for sector ${sector.id}:`, err);
      }
    }

    // 4. Build sector keyword data
    const sectorData: SectorKeywordData[] = [];
    
    for (const siteSector of siteSectors) {
      if (!siteSector.is_active) continue;

      // Fetch all keywords for this sector
      const response = await fetchSeedKeywords({
        industry: industry.id,
        sector: siteSector.industry_sector,
        page_size: 500,
      });

      const sectorKeywords = response.results;

      // Top 50 by highest volume
      const highVolumeKeywords = [...sectorKeywords]
        .sort((a, b) => (b.volume || 0) - (a.volume || 0))
        .slice(0, 50);

      // Top 50 by lowest difficulty
      const lowDifficultyKeywords = [...sectorKeywords]
        .sort((a, b) => (a.difficulty || 100) - (b.difficulty || 100))
        .slice(0, 50);

      // Check if all keywords in each option are already added
      const hvAdded = highVolumeKeywords.every(kw => 
        attachedSeedKeywordIds.has(Number(kw.id))
      );
      const ldAdded = lowDifficultyKeywords.every(kw => 
        attachedSeedKeywordIds.has(Number(kw.id))
      );

      sectorData.push({
        sectorSlug: siteSector.slug,
        sectorName: siteSector.name,
        sectorId: siteSector.id,
        options: [
          {
            type: 'high-volume',
            label: 'Top 50 High Volume',
            keywords: highVolumeKeywords,
            added: hvAdded && highVolumeKeywords.length > 0,
            keywordCount: highVolumeKeywords.length,
          },
          {
            type: 'low-difficulty',
            label: 'Top 50 Low Difficulty',
            keywords: lowDifficultyKeywords,
            added: ldAdded && lowDifficultyKeywords.length > 0,
            keywordCount: lowDifficultyKeywords.length,
          },
        ],
      });
    }

    setSectorKeywordData(sectorData);
  } catch (error: any) {
    console.error('Failed to load high opportunity keywords:', error);
    toast.error(`Failed to load high opportunity keywords: ${error.message}`);
  } finally {
    setLoadingOpportunityKeywords(false);
  }
};
```

#### 3. Add Keywords Function
Create `handleAddSectorKeywords()`:

```typescript
const handleAddSectorKeywords = async (
  sectorSlug: string,
  optionType: 'high-volume' | 'low-difficulty'
) => {
  const sector = sectorKeywordData.find(s => s.sectorSlug === sectorSlug);
  if (!sector || !activeSite) return;

  const option = sector.options.find(o => o.type === optionType);
  if (!option || option.added || option.keywords.length === 0) return;

  const addingKey = `${sectorSlug}-${optionType}`;
  setAddingOption(addingKey);

  try {
    const seedKeywordIds = option.keywords.map(kw => kw.id);

    const result = await addSeedKeywordsToWorkflow(
      seedKeywordIds,
      activeSite.id,
      sector.sectorId
    );

    if (result.success && result.created > 0) {
      // Mark option as added
      setSectorKeywordData(prev =>
        prev.map(s =>
          s.sectorSlug === sectorSlug
            ? {
                ...s,
                options: s.options.map(o =>
                  o.type === optionType ? { ...o, added: true } : o
                ),
              }
            : s
        )
      );

      let message = `Added ${result.created} keywords to ${sector.sectorName}`;
      if (result.skipped && result.skipped > 0) {
        message += ` (${result.skipped} already exist)`;
      }
      toast.success(message);

      // Reload the main table to reflect changes
      loadSeedKeywords();
    } else if (result.errors && result.errors.length > 0) {
      toast.error(result.errors[0]);
    } else {
      toast.warning('No keywords were added. They may already exist in your workflow.');
    }
  } catch (err: any) {
    toast.error(err.message || 'Failed to add keywords to workflow');
  } finally {
    setAddingOption(null);
  }
};
```

#### 4. UI Component
Create `HighOpportunityKeywordsSection` component within the file:

```typescript
const HighOpportunityKeywordsSection = () => {
  if (!showHighOpportunity) return null;
  if (!activeSite || sectorKeywordData.length === 0) return null;

  const addedCount = sectorKeywordData.reduce(
    (acc, s) =>
      acc +
      s.options
        .filter(o => o.added)
        .reduce((sum, o) => sum + o.keywordCount, 0),
    0
  );

  return (
    <div className="mx-6 mt-6 mb-6 p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
      {/* Header */}
      <div className="mb-6">
        <div className="flex items-center justify-between mb-2">
          <h2 className="text-xl font-bold text-gray-900 dark:text-white">
            High Opportunity Keywords
          </h2>
          <button
            onClick={() => setShowHighOpportunity(false)}
            className="text-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
          >
            Hide
          </button>
        </div>
        <p className="text-sm text-gray-600 dark:text-gray-400">
          Add top keywords for each of your sectors. Keywords will be added to your planner workflow.
        </p>
      </div>

      {/* Loading State */}
      {loadingOpportunityKeywords ? (
        <div className="flex items-center justify-center py-12">
          <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-brand-500" />
        </div>
      ) : (
        <>
          {/* Sector Grid */}
          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6 items-start">
            {sectorKeywordData.map((sector) => (
              <div key={sector.sectorSlug} className="flex flex-col gap-3">
                {/* Sector Name */}
                <h4 className="text-base font-semibold text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">
                  {sector.sectorName}
                </h4>

                {/* Options Cards */}
                {sector.options.map((option) => {
                  const addingKey = `${sector.sectorSlug}-${option.type}`;
                  const isAdding = addingOption === addingKey;

                  return (
                    <Card
                      key={option.type}
                      className={`p-4 transition-all flex flex-col ${
                        option.added
                          ? 'border-success-300 dark:border-success-700 bg-success-50 dark:bg-success-900/20'
                          : 'hover:border-brand-300 dark:hover:border-brand-700'
                      }`}
                    >
                      {/* Option Header */}
                      <div className="flex items-center justify-between mb-3">
                        <div>
                          <h5 className="text-sm font-medium text-gray-900 dark:text-white">
                            {option.label}
                          </h5>
                          <p className="text-xs text-gray-500 dark:text-gray-400">
                            {option.keywordCount} keywords
                          </p>
                        </div>
                        {option.added ? (
                          <Badge tone="success" variant="soft" size="sm">
                            <CheckCircleIcon className="w-3 h-3 mr-1" />
                            Added
                          </Badge>
                        ) : (
                          <Button
                            variant="primary"
                            size="xs"
                            onClick={() =>
                              handleAddSectorKeywords(sector.sectorSlug, option.type)
                            }
                            disabled={isAdding || !activeSector}
                            title={
                              !activeSector
                                ? 'Please select a sector from the sidebar first'
                                : ''
                            }
                          >
                            {isAdding ? 'Adding...' : 'Add All'}
                          </Button>
                        )}
                      </div>

                      {/* Sample Keywords */}
                      <div className="flex flex-wrap gap-1.5 flex-1">
                        {option.keywords.slice(0, 3).map((kw) => (
                          <Badge
                            key={kw.id}
                            tone={option.added ? 'success' : 'neutral'}
                            variant="soft"
                            size="xs"
                            className="text-xs"
                          >
                            {kw.keyword}
                          </Badge>
                        ))}
                        {option.keywordCount > 3 && (
                          <Badge
                            tone="neutral"
                            variant="outline"
                            size="xs"
                            className="text-xs"
                          >
                            +{option.keywordCount - 3} more
                          </Badge>
                        )}
                      </div>
                    </Card>
                  );
                })}
              </div>
            ))}
          </div>

          {/* Success Summary */}
          {addedCount > 0 && (
            <Card className="p-4 bg-success-50 dark:bg-success-900/20 border-success-200 dark:border-success-800">
              <div className="flex items-center gap-3">
                <CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400" />
                <span className="text-sm text-success-700 dark:text-success-300">
                  {addedCount} keywords added to your workflow
                </span>
              </div>
            </Card>
          )}
        </>
      )}
    </div>
  );
};
```

#### 5. Integration Points

**In `IndustriesSectorsKeywords.tsx`:**

1. **Add imports:**
   ```typescript
   import { CheckCircleIcon } from '../../icons';
   import { fetchIndustries, fetchKeywords } from '../../services/api';
   ```

2. **Add state variables** (see #1 above)

3. **Add useEffect to load opportunity keywords:**
   ```typescript
   useEffect(() => {
     if (activeSite && activeSite.id && showHighOpportunity) {
       loadHighOpportunityKeywords();
     }
   }, [activeSite?.id, showHighOpportunity]);
   ```

4. **Insert component before TablePageTemplate:**
   ```tsx
   return (
     <>
       <PageMeta ... />
       <PageHeader ... />
       
       {/* High Opportunity Keywords Section */}
       <HighOpportunityKeywordsSection />
       
       {/* Existing sector banner */}
       {!activeSector && activeSite && ( ... )}
       
       <TablePageTemplate ... />
       
       {/* Import Modal */}
       <Modal ... />
     </>
   );
   ```

### Visual Design Details

#### Colors & Styling
- **Card default:** White bg, gray-200 border, hover: brand-300 border
- **Card added:** success-50 bg, success-300 border, success badge
- **Button:** Primary variant, size xs for cards
- **Badges:** xs size for keywords, soft variant
- **Grid:** 1 column on mobile, 2 on md, 3 on lg

#### Responsive Behavior
- **Mobile (< 768px):** Single column, sectors stack vertically
- **Tablet (768px - 1024px):** 2 columns
- **Desktop (> 1024px):** 3 columns

#### User Interactions
1. **"Add All" button:**
   - Disabled during adding (shows "Adding...")
   - Disabled if no sector selected (with tooltip)
   - Disappears after successful add (replaced with "Added" badge)

2. **Success feedback:**
   - Toast notification with count
   - Green success card appears at bottom when any keywords added
   - Card styling changes to success state
   - Badge changes to success badge with checkmark

3. **Hide/Show:**
   - "Hide" button in header to collapse section
   - Could add "Show High Opportunity Keywords" link when hidden

### Error Handling

1. **No active site:**
   - Don't render the section
   - Show existing WorkflowGuide

2. **No sectors:**
   - Don't render the section
   - Show existing sector selection banner

3. **API failures:**
   - Show toast error
   - Log to console
   - Don't crash the page

4. **No keywords found:**
   - Show empty state or hide section
   - Log warning to console

### Performance Considerations

1. **Lazy loading:**
   - Only load opportunity keywords when section is visible
   - Use `showHighOpportunity` state flag

2. **Caching:**
   - Store loaded keyword data in state
   - Only reload on site/sector change

3. **Batch API calls:**
   - Load all sector keywords in parallel if possible
   - Use Promise.all() for concurrent requests

4. **Pagination:**
   - Fetch 500 keywords max per sector
   - This should cover top 50 for both options with margin

### Testing Scenarios

1. **Happy path:**
   - User has active site with 3 sectors
   - Each sector has 50+ keywords
   - Click "Add All" on each option
   - Verify keywords added to workflow
   - Verify success feedback shown

2. **Edge cases:**
   - No active site: Section hidden
   - No sectors: Section hidden
   - No keywords for sector: Empty cards
   - Already added keywords: Show "Added" badge
   - API failure: Show error toast

3. **Interaction:**
   - Add from high opportunity section
   - Verify table below refreshes
   - Verify count updates in header
   - Verify keywords marked as "Added" in table

4. **Responsive:**
   - Test on mobile, tablet, desktop
   - Verify grid layout adapts
   - Verify cards are readable

## Implementation Steps

### Phase 1: Core Functionality (Day 1)
1. ✅ Create plan document (this file)
2. Add state management and types
3. Implement `loadHighOpportunityKeywords()` function
4. Implement `handleAddSectorKeywords()` function
5. Test data loading and API integration

### Phase 2: UI Components (Day 1-2)
6. Create `HighOpportunityKeywordsSection` component
7. Build sector cards with options
8. Add loading states
9. Add success states and feedback
10. Style according to design system

### Phase 3: Integration (Day 2)
11. Integrate with existing page
12. Connect to existing state (activeSite, activeSector)
13. Ensure table refreshes after keywords added
14. Test hide/show functionality

### Phase 4: Polish & Testing (Day 2-3)
15. Add responsive styles
16. Test on different screen sizes
17. Handle edge cases and errors
18. Add tooltips and help text
19. Performance optimization
20. Code review and cleanup

## Dependencies

- ✅ Existing API functions in `services/api.ts`
- ✅ `fetchSeedKeywords()` - Fetch seed keywords
- ✅ `fetchSiteSectors()` - Get sectors for site
- ✅ `fetchIndustries()` - Get industry data
- ✅ `fetchKeywords()` - Check already-attached keywords
- ✅ `addSeedKeywordsToWorkflow()` - Bulk add keywords

- ✅ Existing components
- ✅ `Card` component
- ✅ `Badge` component  
- ✅ `Button` component
- ✅ `CheckCircleIcon` icon

## Success Criteria

1. **Functional:**
   - [ ] High opportunity keywords section displays at top of page
   - [ ] Shows sectors with 2 options each (High Volume, Low Difficulty)
   - [ ] "Add All" button adds keywords to workflow successfully
   - [ ] Success feedback shown after adding
   - [ ] Main table refreshes to show added keywords
   - [ ] Hide/show functionality works

2. **Visual:**
   - [ ] Matches wizard step 4 design
   - [ ] Responsive on all screen sizes
   - [ ] Success states clearly visible
   - [ ] Loading states smooth

3. **UX:**
   - [ ] Fast loading (< 2 seconds)
   - [ ] Clear feedback on actions
   - [ ] Tooltips help users understand
   - [ ] Graceful error handling
   - [ ] No blocking errors

## Future Enhancements

1. **Expand/collapse sectors:** Allow users to collapse individual sectors
2. **Keyword preview modal:** Click on "+X more" to see full list
3. **Custom keyword counts:** Let users choose how many keywords to add (25, 50, 100)
4. **Filter by metrics:** Show only keywords above certain volume/below certain difficulty
5. **Save preferences:** Remember hidden/shown state
6. **Analytics:** Track which options users add most frequently
7. **Smart recommendations:** Suggest sectors based on site content
8. **Batch operations:** "Add all high volume" button to add from all sectors at once

## Notes

- This feature improves onboarding by bringing wizard functionality into the main app
- Users can quickly populate their workflow without searching/filtering
- Reduces friction for new users who don't know which keywords to target
- Leverages existing curated seed keyword database
- No new API endpoints required - uses existing bulk add functionality

## Related Files

**Frontend:**
- `/frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx` - Target file for implementation
- `/frontend/src/components/onboarding/steps/Step4AddKeywords.tsx` - Reference implementation
- `/frontend/src/services/api.ts` - API functions

**Backend:**
- `/backend/igny8_core/api/views/keywords.py` - Keywords API
- `/backend/igny8_core/api/views/seed_keywords.py` - Seed keywords API
- `/backend/igny8_core/business/seed_keywords.py` - Bulk add logic

**Docs:**
- `/docs/40-WORKFLOWS/ADD_KEYWORDS.md` - Add keywords workflow documentation
- `/docs/10-MODULES/PLANNER.md` - Planner module documentation
