diff --git a/backend/igny8_core/api/pagination.py b/backend/igny8_core/api/pagination.py index c471a27c..ead790dc 100644 --- a/backend/igny8_core/api/pagination.py +++ b/backend/igny8_core/api/pagination.py @@ -47,3 +47,19 @@ class CustomPageNumberPagination(PageNumberPagination): return Response(response_data) + +class LargeTablePagination(CustomPageNumberPagination): + """ + Pagination class for large reference tables (e.g., SeedKeywords). + + Default page size: 25 + Max page size: 500 + + Important: Server-side sorting/filtering is applied to ALL records first, + then only the requested page is returned. This ensures: + - Sorting by volume returns the 500 highest/lowest volume records globally + - Filters apply to all records, not just the visible page + - Pagination shows accurate total counts + """ + page_size = 25 + max_page_size = 500 diff --git a/backend/igny8_core/auth/views.py b/backend/igny8_core/auth/views.py index 4fb13e44..a46c2c64 100644 --- a/backend/igny8_core/auth/views.py +++ b/backend/igny8_core/auth/views.py @@ -15,7 +15,7 @@ from igny8_core.api.base import AccountModelViewSet from igny8_core.api.authentication import JWTAuthentication, CSRFExemptSessionAuthentication from igny8_core.api.response import success_response, error_response from igny8_core.api.throttles import DebugScopedRateThrottle -from igny8_core.api.pagination import CustomPageNumberPagination +from igny8_core.api.pagination import CustomPageNumberPagination, LargeTablePagination from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess from .models import User, Account, Plan, Subscription, Site, Sector, SiteUserAccess, Industry, IndustrySector, SeedKeyword from .serializers import ( @@ -827,11 +827,15 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet): """ ViewSet for SeedKeyword - Global reference data (read-only for non-admins). Unified API Standard v1.0 compliant + + Sorting and filtering is applied server-side to ALL records, then paginated. + This ensures operations like "sort by volume DESC" return the globally highest + volume keywords, not just the highest within the current page. """ queryset = SeedKeyword.objects.filter(is_active=True).select_related('industry', 'sector') serializer_class = SeedKeywordSerializer permission_classes = [permissions.AllowAny] # Read-only, allow any authenticated user - pagination_class = CustomPageNumberPagination + pagination_class = LargeTablePagination # Supports up to 500 records per page throttle_scope = 'auth' throttle_classes = [DebugScopedRateThrottle] @@ -855,12 +859,14 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet): ) def get_queryset(self): - """Filter by industry and sector if provided.""" + """Filter by industry, sector, and difficulty range if provided.""" queryset = super().get_queryset() industry_id = self.request.query_params.get('industry_id') industry_name = self.request.query_params.get('industry_name') sector_id = self.request.query_params.get('sector_id') sector_name = self.request.query_params.get('sector_name') + difficulty_min = self.request.query_params.get('difficulty_min') + difficulty_max = self.request.query_params.get('difficulty_max') if industry_id: queryset = queryset.filter(industry_id=industry_id) @@ -871,6 +877,18 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet): if sector_name: queryset = queryset.filter(sector__name__icontains=sector_name) + # Difficulty range filtering + if difficulty_min is not None: + try: + queryset = queryset.filter(difficulty__gte=int(difficulty_min)) + except (ValueError, TypeError): + pass + if difficulty_max is not None: + try: + queryset = queryset.filter(difficulty__lte=int(difficulty_max)) + except (ValueError, TypeError): + pass + return queryset @action(detail=False, methods=['get'], url_path='stats', url_name='stats') diff --git a/docs/plans/KEYWORDS-LIBRARY-REDESIGN-PLAN.md b/docs/plans/KEYWORDS-LIBRARY-REDESIGN-PLAN.md new file mode 100644 index 00000000..406cef3e --- /dev/null +++ b/docs/plans/KEYWORDS-LIBRARY-REDESIGN-PLAN.md @@ -0,0 +1,850 @@ +# Keywords Library Page Redesign Plan + +**Created:** January 18, 2026 +**Updated:** January 18, 2026 +**Status:** APPROVED - READY FOR IMPLEMENTATION +**Page:** `/setup/add-keywords` → `/keywords-library` +**Priority:** 🟡 HIGH - UX Improvement + +--- + +## Executive Summary + +Comprehensive redesign of the Keywords Library page to: +1. **Standardize naming** to "Keywords Library" across frontend, backend, URLs, menus, and admin +2. **Implement cascading filters** like Planner pages (Keywords, Clusters, Ideas) +3. **Remove sector selector** from AppHeader, use site-only selector +4. **Add Sector Metric Cards** with clickable filtering + 5-6 bulk add stat options +5. **Redesign Smart Suggestions** to appear after search/filter with breathing indicator +6. **Center filter bar** with sector filter added, matching Planner page styling +7. **Show table data by default** (remove "Browse" toggle) +8. **No backward compatibility** - single source of truth + +--- + +## Part 1: Terminology Standardization + +### Current State (Inconsistent) + +| Location | Current Term | Usage | +|----------|--------------|-------| +| Sidebar | "Keyword Library" | ❌ Should be "Keywords Library" | +| Page Title | "Keyword Library" | ❌ Should be "Keywords Library" | +| URL | `/setup/add-keywords` | ❌ Legacy | +| Backend Model | `SeedKeyword` | ✅ Keep internal | +| Backend URL | `/v1/auth/seed-keywords/` | ❌ Legacy | +| Backend Admin | "Industry Sector Keywords" | ❌ Should be "Keywords Library" | +| Frontend API | `fetchSeedKeywords()` | ❌ Legacy | +| High Opportunity | "Quick-Start Keywords" | ❌ Different term | +| Search Modal | Mixed terms | ❌ Inconsistent | +| Onboarding | "Add Keywords" | ❌ Legacy | +| Marketing Pages | "Seed Keywords", "High-Opportunity" | ❌ Mixed | + +### Target State (Unified) + +**Standardized Term:** "Keywords Library" (plural "Keywords") +**Internal/Code:** Keep `SeedKeyword` model name internally + +| Location | New Term | +|----------|----------| +| Sidebar | "Keywords Library" | +| Page Title | "Keywords Library" | +| URL | `/keywords-library` (**NO** redirect from legacy) | +| Backend URL | `/v1/keywords-library/` (**NO** legacy alias) | +| Backend Admin | "Keywords Library" | +| Frontend API | `fetchKeywordsLibrary()` (replace `fetchSeedKeywords`) | +| Smart Suggestions | "Smart Suggestions" | +| Search Modal | "Keywords Library" | +| Onboarding Step | "Browse Keywords Library" | + +### Files to Update + +``` +Frontend: +- src/layout/AppSidebar.tsx - Menu label → "Keywords Library" +- src/layout/AppHeader.tsx - Route patterns +- src/App.tsx - Route definition (NO redirect, direct change) +- src/components/common/SearchModal.tsx - Search references +- src/components/onboarding/OnboardingWizard.tsx - Step 4 name +- src/components/onboarding/steps/Step4AddKeywords.tsx - Rename to Step4KeywordsLibrary.tsx +- src/components/sites/SiteSetupChecklist.tsx - Link text +- src/services/api.ts - Rename function to fetchKeywordsLibrary() +- src/marketing/pages/Pricing.tsx - Feature descriptions +- src/marketing/pages/Solutions.tsx - Feature descriptions + +Backend: +- igny8_core/auth/urls.py - Change URL to /keywords-library/ +- igny8_core/admin/ - Change admin menu label to "Keywords Library" +- Keep model name as SeedKeyword (internal only) +``` + +--- + +## Part 2: Filter System Redesign + +### Current Filters (Keywords Library) + +``` +[Search] [Country â–ŧ] [Difficulty â–ŧ] [Status â–ŧ] +``` + +**Issues:** +1. ❌ No "Clear Filters" button near active filter indicator +2. ❌ No Volume filter (exists on Planner Keywords page) +3. ❌ No Sector filter in filter bar +4. ❌ Hardcoded country/difficulty options (not cascading) +5. ❌ Filters don't show only available options based on current data +6. ❌ Active/Clear button positioning doesn't match Planner pages + +### Target Filters (Match Planner Pages Pattern) + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ [🔍 Search] [Sector â–ŧ] [Volume â–ŧ] [Difficulty â–ŧ] [Country â–ŧ] [Status â–ŧ] │ +│ │ +│ Active: 2 filters [Clear] ← Same position as Planner pages │ +│ │ +│ â„šī¸ Search 54,180 keywords. Filter by volume or difficulty to find your │ +│ perfect keywords! │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +**Improvements:** +1. ✅ Active count + Clear button positioned **same as Planner pages** +2. ✅ Add Volume range filter (min/max popup like Planner pages) +3. ✅ Add Sector filter dropdown +4. ✅ Keep "Not Yet Added" status filter (in_workflow filter) +5. ✅ Implement cascading filters - options reflect available data +6. ✅ Center filter bar with explainer text below + +### Filter: Status Options + +| Value | Label | Description | +|-------|-------|-------------| +| `all` | All Keywords | Show all keywords | +| `available` | Not Yet Added | Keywords not in any workflow | +| `in_workflow` | In Workflow | Keywords already added to site | + +### Backend API Changes + +**New Endpoint:** `GET /v1/keywords-library/filter_options/` + +Returns available filter options based on current filters (cascading): + +```python +# Example Request +GET /v1/keywords-library/filter_options/?industry=5§or=12&difficulty_max=40 + +# Response +{ + "sectors": [ + {"value": 12, "label": "Technology", "count": 1250}, + {"value": 15, "label": "Mobile Apps", "count": 340}, + ... + ], + "countries": [ + {"value": "US", "label": "United States", "count": 1250}, + {"value": "GB", "label": "United Kingdom", "count": 340}, + ... + ], + "difficulties": [ + {"value": "1", "label": "Very Easy (1-20)", "count": 890}, + {"value": "2", "label": "Easy (21-40)", "count": 450}, + {"value": "3", "label": "Medium (41-60)", "count": 320}, + {"value": "4", "label": "Hard (61-80)", "count": 180}, + {"value": "5", "label": "Very Hard (81-100)", "count": 90}, + ], + "volume_stats": { + "min": 0, + "max": 421000, + "avg": 5420, + "p50": 1200, + "p90": 15000 + }, + "total_matching": 1590 +} +``` + +### Difficulty Mapping (Backend 1-100 → Frontend 1-5) + +| Frontend Level | Label | Backend Range | +|----------------|-------|---------------| +| 1 | Very Easy | 1-20 | +| 2 | Easy | 21-40 | +| 3 | Medium | 41-60 | +| 4 | Hard | 61-80 | +| 5 | Very Hard | 81-100 | + +**Low Difficulty = Level 1 and 2 (backend ≤ 40)** + +### Frontend Changes + +1. **Add `fetchKeywordsLibraryFilterOptions()` API function** +2. **Add Sector filter dropdown** (populated from site's sectors) +3. **Add Volume filter component** (reuse from Keywords.tsx) +4. **Position Active/Clear same as Planner pages** +5. **Load filter options dynamically** based on industry/sector selection +6. **Re-load options when any filter changes** (cascading) + +--- + +## Part 3: App Header Selector Change + +### Current Behavior + +- Keywords Library page shows `SiteAndSectorSelector` (Site + Sector dropdowns) +- This is redundant since page will have sector cards and sector filter + +### Target Behavior + +- Show `SingleSiteSelector` (Site only, like Automation pages) +- Remove from `SITE_AND_SECTOR_ROUTES` array +- Add to `SINGLE_SITE_ROUTES` array + +### Code Change (AppHeader.tsx) + +```diff +const SITE_AND_SECTOR_ROUTES = [ + '/planner', + '/writer', +- '/setup/add-keywords', +]; + +const SINGLE_SITE_ROUTES = [ + '/automation', + '/publisher', + '/account/content-settings', + '/sites', ++ '/keywords-library', +]; +``` + +--- + +## Part 4: Sector Metric Cards + +### Design + +Clickable sector cards that filter the table when clicked. Each card shows stats and has 5-6 bulk add options. + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Your Site Sectors [Add Sector]│ +│ │ +│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ đŸ’ģ Technology │ │ 📱 Mobile Apps │ │ 🌐 Web Dev │ │ +│ │ ─────────────────── │ │ ─────────────────── │ │ ─────────────────── │ │ +│ │ │ │ │ │ │ │ +│ │ Total: 1,245 │ │ Total: 892 │ │ Total: 456 │ │ +│ │ Available: 1,200 │ │ Available: 880 │ │ Available: 456 │ │ +│ │ In Workflow: 45 │ │ In Workflow: 12 │ │ In Workflow: 0 │ │ +│ │ │ │ │ │ │ │ +│ │ ─── Quick Add ───── │ │ ─── Quick Add ───── │ │ ─── Quick Add ───── │ │ +│ │ │ │ │ │ │ │ +│ │ 📈 High Volume │ │ 📈 High Volume │ │ 📈 High Volume │ │ +│ │ 125 kw (>8.5K) │ │ 67 kw (>6.2K) │ │ 50 kw (>3.4K) │ │ +│ │ [+50] [+100] │ │ [+50] [+67] │ │ [+50] │ │ +│ │ │ │ │ │ │ │ +│ │ đŸŽ¯ Low Difficulty │ │ đŸŽ¯ Low Difficulty │ │ đŸŽ¯ Low Difficulty │ │ +│ │ 340 kw (≤40) │ │ 212 kw (≤40) │ │ 89 kw (≤40) │ │ +│ │ [+50] [+100] │ │ [+50] [+100] │ │ [+50] [+89] │ │ +│ │ │ │ │ │ │ │ +│ │ đŸ”Ĩ High Vol + Easy │ │ đŸ”Ĩ High Vol + Easy │ │ đŸ”Ĩ High Vol + Easy │ │ +│ │ 45 kw │ │ 28 kw │ │ 12 kw │ │ +│ │ [+45] │ │ [+28] │ │ [+12] │ │ +│ │ │ │ │ │ │ │ +│ │ 💎 Premium (>50K) │ │ 💎 Premium (>50K) │ │ 💎 Premium (>50K) │ │ +│ │ 12 kw │ │ 8 kw │ │ 3 kw │ │ +│ │ [+12] │ │ [+8] │ │ [+3] │ │ +│ │ │ │ │ │ │ │ +│ │ 🌱 Long Tail │ │ 🌱 Long Tail │ │ 🌱 Long Tail │ │ +│ │ 890 kw (4+ words)│ │ 654 kw (4+ words)│ │ 312 kw (4+ words)│ │ +│ │ [+50] [+100] │ │ [+50] [+100] │ │ [+50] [+100] │ │ +│ │ │ │ │ │ │ │ +│ │ [Browse All →] │ │ [Browse All →] │ │ [Browse All →] │ │ +│ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │ +│ │ +│ 💡 Click any card to filter the table by that sector │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +### Click Behavior + +- **Click on sector card** → Filter table by that sector +- **Click on Quick Add button** → Add keywords matching that criteria + +### Stats Per Sector Card (6 Options) + +Based on actual keyword data analysis (volume ranges 50-281,000, difficulty 0-99). +**Dynamic fallback thresholds** ensure every sector shows useful data: + +| Stat | Criteria | Fallback Logic | Example | +|------|----------|----------------|---------| +| 📈 **High Volume** | Top 50/100 by volume | Dynamic threshold based on available data | "125 kw (>8.5K)" | +| đŸŽ¯ **Low Difficulty** | Difficulty ≤ 40 (levels 1-2) | Fixed threshold | "340 kw (≤40)" | +| đŸ”Ĩ **Best Opportunities** | High vol + Difficulty ≤ 40 | Combines above two criteria | "45 kw" | +| 💎 **Premium Traffic** | Volume > 50,000 | If 0 → try >25,000 → if 0 → try >10,000 | "12 kw (>50K)" | +| 🌱 **Long Tail** | Words â‰Ĩ 4 AND Volume > 1,000 | If 0 → try >500 → if 0 → try >200 | "890 kw (4+ words, >1K)" | +| ⚡ **Quick Wins** | Difficulty ≤ 20 AND Volume > 1,000 + Available | If 0 → try >500 → if 0 → try >200 | "67 kw" | + +### Dynamic Threshold Fallback Logic + +```python +# Premium Traffic - cascading volume thresholds +def get_premium_traffic(keywords): + for threshold in [50000, 25000, 10000]: + count = keywords.filter(volume__gt=threshold).count() + if count > 0: + return {"count": count, "threshold": threshold} + return {"count": 0, "threshold": 10000} + +# Long Tail - cascading volume with word count +def get_long_tail(keywords): + base = keywords.filter(word_count__gte=4) + for threshold in [1000, 500, 200]: + count = base.filter(volume__gt=threshold).count() + if count > 0: + return {"count": count, "min_volume": threshold} + return {"count": 0, "min_volume": 200} + +# Quick Wins - cascading volume with low difficulty + available +def get_quick_wins(keywords, attached_ids): + base = keywords.filter(difficulty__lte=20).exclude(id__in=attached_ids) + for threshold in [1000, 500, 200]: + count = base.filter(volume__gt=threshold).count() + if count > 0: + return {"count": count, "min_volume": threshold} + return {"count": 0, "min_volume": 200} +``` + +This ensures **every sector shows meaningful data** regardless of size, making the feature always useful and operational. + +### High Volume Dynamic Threshold + +Instead of fixed 10,000 threshold: +- Get top 50 keywords by volume for sector +- Show threshold as ">X" where X = lowest volume in top 50 +- If < 50 keywords available, use top 100 threshold +- Example: "125 kw (>8,540)" means 125 keywords with volume > 8,540 + +### Bulk Add Options + +Each stat shows add buttons based on availability: +- `[+50]` - Add top 50 +- `[+100]` - Add top 100 (if > 50 available) +- `[+200]` - Add top 200 (if > 100 available) +- `[+N]` - Add all N (if N ≤ 200) + +**Always show confirmation modal before adding.** + +### Backend API + +**Endpoint:** `GET /v1/keywords-library/sector_stats/` + +```python +# Request +GET /v1/keywords-library/sector_stats/?site_id=21 + +# Response +{ + "sectors": [ + { + "sector_id": 12, + "sector_name": "Technology", + "sector_slug": "technology", + "total_keywords": 1245, + "available": 1200, + "in_workflow": 45, + "stats": { + "high_volume": { + "count": 125, + "threshold": 8540, // Dynamic: lowest volume in top 50/100 + "total_volume": 1200000 + }, + "low_difficulty": { + "count": 340, + "threshold": 40, // Backend value (maps to frontend level 2) + "total_volume": 450000 + }, + "high_vol_easy": { + "count": 45, + "description": "High volume + Low difficulty" + }, + "premium": { + "count": 12, + "threshold": 50000, // Dynamic: 50K → 25K → 10K fallback + "used_fallback": false + }, + "long_tail": { + "count": 890, + "min_words": 4, + "min_volume": 1000, // Dynamic: 1K → 500 → 200 fallback + "used_fallback": false + }, + "quick_wins": { + "count": 67, + "difficulty_max": 20, // Level 1 only + "min_volume": 1000, // Dynamic: 1K → 500 → 200 fallback + "used_fallback": false + } + } + }, + // ... more sectors + ] +} +``` + +### Styling + +Use **subtle accents** matching IGNY8 design style: +- Light/dark mode compatible +- Muted colors from dashboard/billing/usage pages +- No bright/colorful backgrounds +- Subtle borders and shadows +- Consistent with existing card components + +--- + +## Part 5: Smart Suggestions Section + +### Concept + +Show **Smart Suggestions** dynamically based on current filters/search. Display a **breathing circle indicator** until user interacts with filters or search. + +### Initial State (Before Search/Filter) + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ 💡 Smart Suggestions │ +│ │ +│ ○ Ready-to-use keywords waiting for you! Search for a keyword or apply │ +│ any filter to see smart suggestions... │ +│ ↑ │ +│ Breathing/pulsing circle indicator (subtle animation) │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +### After Search/Filter Applied + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ 💡 Smart Suggestions for "seo tools" │ +│ │ +│ ┌────────────────────────────────────┐ ┌───────────────────────────────────┐ │ +│ │ 📈 High Volume Keywords │ │ đŸŽ¯ Easy Wins │ │ +│ │ │ │ │ │ +│ │ 89 keywords with >4,200 volume │ │ 156 keywords (difficulty ≤ 40) │ │ +│ │ Combined: 2.4M searches/month │ │ Combined: 890K searches/month │ │ +│ │ │ │ │ │ +│ │ Top 5: seo tools, seo audit, ... │ │ Top 5: local seo, seo tips, ... │ │ +│ │ │ │ │ │ +│ │ [+50] [+100] [+All 89] │ │ [+50] [+100] [+All 156] │ │ +│ └────────────────────────────────────┘ └───────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ ┌───────────────────────────────────┐ │ +│ │ đŸ”Ĩ Best Opportunities │ │ 🌱 Long Tail Keywords │ │ +│ │ │ │ │ │ +│ │ 45 kw (high volume + easy) │ │ 234 keywords (3+ words) │ │ +│ │ Combined: 1.2M searches/month │ │ Lower competition phrases │ │ +│ │ │ │ │ │ +│ │ [+45] │ │ [+50] [+100] [+200] │ │ +│ └────────────────────────────────────┘ └───────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────────────────────┐ │ +│ │ 🔍 All matching "seo tools" - 234 keywords │ │ +│ │ │ │ +│ │ [+50 by Volume] [+50 by Difficulty] [+100] [+200] [+All 234] │ │ +│ └──────────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +### Breathing Circle Indicator + +CSS animation for pulsing circle: +```css +.breathing-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--muted-foreground); + animation: breathe 2s ease-in-out infinite; +} + +@keyframes breathe { + 0%, 100% { opacity: 0.3; transform: scale(0.9); } + 50% { opacity: 0.8; transform: scale(1.1); } +} +``` + +### Suggestion Categories + +| Category | Criteria | Fallback Logic | +|----------|----------|----------------| +| 📈 High Volume | Top 50/100 by volume | Dynamic threshold | +| đŸŽ¯ Easy Wins | Difficulty ≤ 40 | Fixed threshold | +| đŸ”Ĩ Best Opportunities | High vol + Easy | Combines criteria | +| 💎 Premium | Volume > 50K | 50K → 25K → 10K fallback | +| 🌱 Long Tail | Words â‰Ĩ 4 + Vol > 1K | Vol: 1K → 500 → 200 fallback | +| ⚡ Quick Wins | Diff ≤ 20 + Vol > 1K + Available | Vol: 1K → 500 → 200 fallback | + +### Bulk Add Buttons + +| Button | Action | +|--------|--------| +| `[+50]` | Add top 50 sorted by relevant criteria | +| `[+100]` | Add top 100 | +| `[+200]` | Add top 200 | +| `[+All N]` | Add all N matching (shown when N ≤ 200) | +| `[+50 by Volume]` | Add top 50 sorted by volume DESC | +| `[+50 by Difficulty]` | Add top 50 sorted by difficulty ASC | + +**Always show confirmation modal before any bulk add.** + +### Styling + +Match IGNY8 dashboard/billing/usage styling: +- Subtle card backgrounds (muted) +- Light borders +- Dark mode compatible colors +- No bright/colorful accents +- Consistent typography + +--- + +## Part 6: Page Layout Redesign + +### Current Layout + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ [Header with Site+Sector selector] │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Quick-Start Keywords Section (always shown first) │ +│ │ +│ [Browse All Keywords] button │ +│ │ +│ Table (only shown after clicking Browse) │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +### New Layout + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ [Header with Site-only selector] │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────────┐│ +│ │ Sector Metric Cards (clickable - filters table by sector) ││ +│ │ - 5-6 bulk add stat options per card ││ +│ │ - [+50] [+100] [+200] buttons ││ +│ └─────────────────────────────────────────────────────────────────────────────┘│ +│ │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ [🔍 Search] [Sector â–ŧ] [Volume â–ŧ] [Difficulty â–ŧ] [Country â–ŧ] │ │ +│ │ │ │ +│ │ Active: 2 filters [Clear] │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ â„šī¸ Search 54,180 keywords. Filter by volume or difficulty │ +│ to find your perfect keywords! │ +│ │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────────────┐│ +│ │ 💡 Smart Suggestions ││ +│ │ ││ +│ │ ○ Search or filter keywords to see personalized suggestions... ││ +│ │ ↑ breathing indicator ││ +│ │ ││ +│ │ (After search/filter: shows suggestion cards with [+50] [+100] buttons) ││ +│ └─────────────────────────────────────────────────────────────────────────────┘│ +│ │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Keywords Table (always shown, data loaded by default) │ +│ - Shows first 500 records (with server-side sorting/filtering) │ +│ - Pagination shows total count │ +│ - Sorted by volume DESC by default │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +### Key Layout Changes + +1. **Site-only selector** in header (no sector dropdown) +2. **Sector cards at top** - clickable to filter table +3. **Centered filter bar** with sector filter + active/clear like Planner +4. **Smart Suggestions** - breathing indicator until search/filter +5. **Table always visible** - no "Browse" toggle +6. **Confirmation modal** for all bulk adds + +--- + +## Part 7: Implementation Tasks + +### Phase 1: Backend API Updates + +| Task | File | Priority | +|------|------|----------| +| Add `/filter_options/` endpoint | `auth/views.py` | High | +| Add `/sector_stats/` endpoint with 6 stat types | `auth/views.py` | High | +| Change URL to `/keywords-library/` (no alias) | `auth/urls.py` | High | +| Update admin menu to "Keywords Library" | `admin/` | High | +| Support volume_min/max filters | `auth/views.py` | High | +| Support sector filter | `auth/views.py` | High | +| Implement cascading filter logic | `auth/views.py` | High | +| Add bulk add endpoint with confirmation | `auth/views.py` | High | + +### Phase 2: Frontend Filter System + +| Task | File | Priority | +|------|------|----------| +| Rename `fetchSeedKeywords` → `fetchKeywordsLibrary` | `services/api.ts` | High | +| Add `fetchKeywordsLibraryFilterOptions()` | `services/api.ts` | High | +| Add `fetchKeywordsLibrarySectorStats()` | `services/api.ts` | High | +| Add Sector filter dropdown | `KeywordsLibrary.tsx` | High | +| Add Volume filter component | `KeywordsLibrary.tsx` | High | +| Position Active/Clear same as Planner | `KeywordsLibrary.tsx` | High | +| Implement cascading filter loading | `KeywordsLibrary.tsx` | High | +| Center filter bar + add explainer | `KeywordsLibrary.tsx` | Medium | + +### Phase 3: Selector & Layout Changes + +| Task | File | Priority | +|------|------|----------| +| Move route to `SINGLE_SITE_ROUTES` | `AppHeader.tsx` | High | +| Change route to `/keywords-library` | `App.tsx` | High | +| Remove "Browse" toggle, show table by default | `KeywordsLibrary.tsx` | High | +| Remove High Opportunity section | `KeywordsLibrary.tsx` | Medium | + +### Phase 4: Sector Metric Cards + +| Task | File | Priority | +|------|------|----------| +| Create SectorMetricCard component | `components/keywords-library/` | High | +| Implement 6 stat types per card | `SectorMetricCard.tsx` | High | +| Add click-to-filter behavior | `KeywordsLibrary.tsx` | High | +| Add [+50] [+100] [+200] buttons | `SectorMetricCard.tsx` | High | +| Add confirmation modal for bulk add | `KeywordsLibrary.tsx` | High | + +### Phase 5: Smart Suggestions + +| Task | File | Priority | +|------|------|----------| +| Create SmartSuggestions component | `components/keywords-library/` | High | +| Add breathing circle indicator | `SmartSuggestions.tsx` | High | +| Show suggestions after search/filter | `KeywordsLibrary.tsx` | High | +| Add bulk add buttons [+50] [+100] [+200] | `SmartSuggestions.tsx` | High | +| Implement 6 suggestion categories | `SmartSuggestions.tsx` | Medium | + +### Phase 6: Naming Standardization + +| Task | File | Priority | +|------|------|----------| +| Update sidebar label → "Keywords Library" | `AppSidebar.tsx` | High | +| Update SearchModal references | `SearchModal.tsx` | Medium | +| Rename Step4AddKeywords → Step4KeywordsLibrary | `onboarding/steps/` | Medium | +| Update marketing pages | `Pricing.tsx`, `Solutions.tsx` | Low | + +### Phase 7: Confirmation Modal + +| Task | File | Priority | +|------|------|----------| +| Create BulkAddConfirmation modal | `components/keywords-library/` | High | +| Show count and criteria being added | `BulkAddConfirmation.tsx` | High | +| Always require confirmation for bulk add | `KeywordsLibrary.tsx` | High | + +--- + +## Part 8: No Backward Compatibility + +### Single Source of Truth + +**No legacy URLs, no aliases, no fallbacks.** + +| Item | Action | +|------|--------| +| `/setup/add-keywords` URL | **REMOVE** - change to `/keywords-library` | +| `/v1/auth/seed-keywords/` API | **REMOVE** - change to `/v1/keywords-library/` | +| `fetchSeedKeywords()` function | **RENAME** to `fetchKeywordsLibrary()` | +| Backend admin "Industry Sector Keywords" | **RENAME** to "Keywords Library" | +| All references to old terms | **UPDATE** to new terms | + +### Route Change (App.tsx) + +```tsx +// BEFORE +} /> + +// AFTER (no redirect, direct change) +} /> +``` + +### API Change (auth/urls.py) + +```python +# BEFORE +path('seed-keywords/', SeedKeywordViewSet.as_view({'get': 'list'}), name='seed-keyword-list'), + +# AFTER (no alias) +urlpatterns = [ + path('keywords-library/', include([ + path('', SeedKeywordViewSet.as_view({'get': 'list'}), name='keywords-library-list'), + path('filter_options/', SeedKeywordViewSet.as_view({'get': 'filter_options'}), name='keywords-library-filter-options'), + path('sector_stats/', SeedKeywordViewSet.as_view({'get': 'sector_stats'}), name='keywords-library-sector-stats'), + path('bulk_add/', SeedKeywordViewSet.as_view({'post': 'bulk_add'}), name='keywords-library-bulk-add'), + ])), +] +``` + +### File Renames + +| From | To | +|------|-----| +| `IndustriesSectorsKeywords.tsx` | `KeywordsLibrary.tsx` | +| `Step4AddKeywords.tsx` | `Step4KeywordsLibrary.tsx` | + +--- + +## Part 9: Success Metrics + +| Metric | Current | Target | +|--------|---------|--------| +| Filter usage rate | Low (no volume/sector filter) | High (all filters used) | +| Keywords added per session | ~50 (quick-start only) | 100+ (smart bulk adds) | +| Bulk add usage | N/A | Track [+50], [+100], [+200] clicks | +| Sector card clicks | N/A | Track filtering by sector | +| Smart suggestions engagement | N/A | Track after search/filter | +| Time to first keyword add | Unknown | < 30 seconds | + +--- + +## Part 10: Design Decisions Summary + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| **URL** | `/keywords-library` | Clean, no legacy compat | +| **Naming** | "Keywords Library" (plural) | Consistent across all surfaces | +| **Backward Compat** | None | Single source of truth | +| **Sector cards click** | Filters table | Intuitive UX | +| **High Volume threshold** | Dynamic (top 50/100 lowest) | Adapts to actual data | +| **Low Difficulty** | ≤ 40 (levels 1-2 mapped) | "Easy" keywords | +| **Bulk add options** | 50, 100, 200, All | Flexible choices | +| **Confirmation modal** | Always | Prevent accidental adds | +| **Smart suggestions** | Breathing indicator until search | Encourages interaction | +| **Filter styling** | Match Planner pages | Consistency | + +--- + +## Files Summary + +### Files to Modify + +| File | Changes | +|------|---------| +| `frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx` | Major rewrite → rename to KeywordsLibrary.tsx | +| `frontend/src/services/api.ts` | Rename + add new API functions | +| `frontend/src/layout/AppHeader.tsx` | Move route category | +| `frontend/src/layout/AppSidebar.tsx` | Update label to "Keywords Library" | +| `frontend/src/App.tsx` | Change route (no redirect) | +| `frontend/src/components/common/SearchModal.tsx` | Update references | +| `backend/igny8_core/auth/views.py` | Add new endpoints, update existing | +| `backend/igny8_core/auth/urls.py` | Change URL (no alias) | +| `backend/igny8_core/admin/` | Update menu label | + +### Files to Create + +| File | Purpose | +|------|---------| +| `frontend/src/components/keywords-library/SectorMetricCard.tsx` | Sector stats card with 6 stat options | +| `frontend/src/components/keywords-library/SmartSuggestions.tsx` | Smart suggestions with breathing indicator | +| `frontend/src/components/keywords-library/BulkAddConfirmation.tsx` | Confirmation modal for bulk adds | + +### Files to Rename + +| From | To | +|------|-----| +| `IndustriesSectorsKeywords.tsx` | `KeywordsLibrary.tsx` | +| `Step4AddKeywords.tsx` | `Step4KeywordsLibrary.tsx` | + +--- + +## Approval Checklist + +- [x] Review by Product Owner - **APPROVED** +- [x] Design decisions finalized +- [x] Backend API design approved +- [x] Frontend component design approved +- [x] No backward compatibility confirmed +- [ ] Implementation started + +--- + +## Appendix A: Complete List of Files to Refactor + +### Frontend Source Files (Must Change) + +| File | Changes Required | +|------|------------------| +| `frontend/src/App.tsx` | Change route `/setup/add-keywords` → `/keywords-library`, remove `IndustriesSectorsKeywords` import | +| `frontend/src/layout/AppHeader.tsx` | Remove `/setup/add-keywords` from `SITE_AND_SECTOR_ROUTES`, add `/keywords-library` to `SINGLE_SITE_ROUTES` | +| `frontend/src/layout/AppSidebar.tsx` | Change label "Keyword Library" → "Keywords Library", path `/setup/add-keywords` → `/keywords-library` | +| `frontend/src/services/api.ts` | Rename `fetchSeedKeywords()` → `fetchKeywordsLibrary()`, rename `SeedKeyword` interface (user-facing), update API URL | +| `frontend/src/components/common/SearchModal.tsx` | Change all `/setup/add-keywords` → `/keywords-library`, "Keyword Library" → "Keywords Library" | +| `frontend/src/components/sites/SiteSetupChecklist.tsx` | Change href `/setup/add-keywords` → `/keywords-library` | +| `frontend/src/components/dashboard/QuickActionsWidget.tsx` | Change "Keyword Library" → "Keywords Library" | +| `frontend/src/components/onboarding/steps/Step4AddKeywords.tsx` | **RENAME FILE** → `Step4KeywordsLibrary.tsx`, update imports | +| `frontend/src/components/onboarding/WorkflowGuide.tsx` | Update comment reference to `IndustriesSectorsKeywords` | +| `frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx` | **RENAME FILE** → `KeywordsLibrary.tsx`, **MAJOR REWRITE** | +| `frontend/src/pages/Reference/SeedKeywords.tsx` | Update if needed for consistency | + +### Backend Files (Must Change) + +| File | Changes Required | +|------|------------------| +| `backend/igny8_core/auth/urls.py` | Change route `seed-keywords/` → `keywords-library/` | +| `backend/igny8_core/auth/views.py` | Keep `SeedKeywordViewSet` class name (internal), add new endpoints | +| `backend/igny8_core/auth/serializers.py` | Keep `SeedKeywordSerializer` name (internal) | +| `backend/igny8_core/auth/admin.py` | Update admin label to "Keywords Library" | +| `backend/igny8_core/settings.py` | Change sidebar menu "Seed Keywords" → "Keywords Library" | +| `backend/igny8_core/urls.py` | Update `seedkeyword_csv_template` and `seedkeyword_csv_import` URL paths | + +### Backend Files (Keep Internal - SeedKeyword Model) + +| File | Status | +|------|--------| +| `backend/igny8_core/auth/models.py` | **KEEP** `SeedKeyword` class name (internal) | +| `backend/igny8_core/business/planning/models.py` | **KEEP** `seed_keyword` field reference (internal) | +| `backend/scripts/import_seed_keywords_single.py` | **KEEP** internal script name | +| `backend/scripts/import_all_seed_keywords.py` | **KEEP** internal script name | + +### Documentation Files (Must Update) + +| File | Changes Required | +|------|------------------| +| `docs/INDEX.md` | Update route `/setup/add-keywords` → `/keywords-library` | +| `docs/20-API/ENDPOINTS.md` | Update endpoint `/seed-keywords/` → `/keywords-library/` | +| `docs/90-REFERENCE/SEED-KEYWORDS-IMPORT-GUIDE.md` | Update user-facing terminology, keep internal references | +| `docs/90-REFERENCE/MODELS.md` | Keep `SeedKeyword` model reference (internal) | +| `docs/plans/NAVIGATION_REFACOTR/NAVIGATION_FINAL_COMPLETION_SUMMARY.md` | Update "Keyword Library" → "Keywords Library" | +| `docs/plans/NAVIGATION_REFACOTR/NAVIGATION_REFACTOR_PLAN.md` | Update "Keyword Library" → "Keywords Library" | + +### Other Files (May Need Review) + +| File | Notes | +|------|-------| +| `CHANGELOG.md` | Historical references - may keep as-is | +| `.rules` | Keep `SeedKeyword` (internal model reference) | +| `frontend/dist/` | **REBUILD** after changes | +| `frontend/audit-results/` | Re-run audits after changes | + +--- + +**Status:** READY FOR IMPLEMENTATION + +**Next Steps:** Begin Phase 1 - Backend API Updates diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index bb6a5739..b87b51cb 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -2277,6 +2277,9 @@ export async function fetchSeedKeywords(filters?: { search?: string; page?: number; page_size?: number; + ordering?: string; + difficulty_min?: number; + difficulty_max?: number; }): Promise { const params = new URLSearchParams(); // Use industry_id and sector_id as per backend get_queryset, but also try industry/sector for filterset_fields @@ -2292,6 +2295,11 @@ export async function fetchSeedKeywords(filters?: { if (filters?.search) params.append('search', filters.search); if (filters?.page) params.append('page', filters.page.toString()); if (filters?.page_size) params.append('page_size', filters.page_size.toString()); + // Server-side sorting - critical for proper pagination + if (filters?.ordering) params.append('ordering', filters.ordering); + // Difficulty range filtering + if (filters?.difficulty_min !== undefined) params.append('difficulty_min', filters.difficulty_min.toString()); + if (filters?.difficulty_max !== undefined) params.append('difficulty_max', filters.difficulty_max.toString()); const queryString = params.toString(); return fetchAPI(`/v1/auth/seed-keywords/${queryString ? `?${queryString}` : ''}`);