Phase 1-3 Implemented - PUBLISHING-PROGRESS-AND-SCHEDULING-UX-PLAN
This commit is contained in:
@@ -0,0 +1,672 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user