Phase 1-3 Implemented - PUBLISHING-PROGRESS-AND-SCHEDULING-UX-PLAN

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-16 14:14:17 +00:00
parent e704ed8bcf
commit 1f0a31fe79
14 changed files with 1809 additions and 224 deletions

View File

@@ -0,0 +1,297 @@
# IGNY8 ↔ WordPress Integration Security Plan
**Date:** 2026-01-13
**Status:** Audit Complete - Plan Ready
---
## PART 1: ACTUAL ARCHITECTURE (VERIFIED)
### 1.1 Publishing Flow (WORKING - No Changes Needed)
```
Frontend (Review/Approved pages)
POST /v1/publisher/publish/
PublisherService._publish_to_destination()
├── Gets API key: content.site.wp_api_key (SINGLE SOURCE OF TRUTH)
├── Gets URL: content.site.domain or content.site.url
WordPressAdapter._publish_via_api_key()
POST {site_url}/wp-json/igny8/v1/publish
Headers: X-IGNY8-API-KEY: {api_key}
Plugin: check_permission() validates header against stored igny8_api_key
Plugin creates/updates WordPress post
```
**Key Finding:** Publishing does NOT use SiteIntegration model. It uses:
- `Site.wp_api_key` - for authentication
- `Site.domain` - for WordPress URL
### 1.2 Data Storage Locations
| Location | Field | Purpose | Used By |
|----------|-------|---------|---------|
| **Django Site model** | `wp_api_key` | API key (source of truth) | Publishing, test-connection |
| **Django Site model** | `domain` | WordPress URL | Publishing |
| **WordPress wp_options** | `igny8_api_key` | Stored copy of API key | Plugin auth check |
| **WordPress wp_options** | `igny8_site_id` | Site ID from key | Plugin display |
| **WordPress wp_options** | `igny8_integration_id` | Integration ID | Plugin (currently unused) |
| **WordPress wp_options** | `igny8_last_structure_sync` | Last sync timestamp | Connection status UI |
### 1.3 SiteIntegration Model Status
**Table exists:** `igny8_site_integrations`
**Records:** 0 (empty)
**Used by:**
- `publishing_scheduler.py` - scheduled publishing (requires record)
- `wordpress_publishing.py` - Celery task path (requires record)
- `sync_metadata_service.py` - metadata sync (requires record)
- `ContentViewSet.publish` action (requires record)
**NOT used by:**
- `PublisherService` (main UI publishing path) - uses Site directly
- `WordPressAdapter` - uses Site.wp_api_key directly
---
## PART 2: SECURITY ISSUES FOUND
### 2.1 Plugin Public Endpoints (SECURITY RISK)
| Endpoint | Current Permission | Risk | Data Exposed |
|----------|-------------------|------|--------------|
| `/igny8/v1/status` | `__return_true` (PUBLIC) | Medium | Plugin installed, has_api_key, plugin version |
| `/igny8/v1/site-metadata/` | `__return_true` (PUBLIC*) | Low | Post types, taxonomies (auth checked inside) |
*Note: `/site-metadata/` checks permission inside callback but still registers as public
### 2.2 Backend test-connection Flow
The `test_connection_collection` endpoint:
1. Uses `AllowAny` permission class
2. BUT validates authentication inside the handler
3. Calls public `/status` endpoint to check if plugin installed
4. Then calls authenticated `/verify-key` endpoint
**Issue:** Relies on public `/status` endpoint to detect plugin presence.
---
## PART 3: RECOMMENDED CHANGES
### 3.1 Plugin Changes (REQUIRED)
#### 3.1.1 Make `/status` Endpoint Authenticated
**File:** `plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-rest-api.php`
**Current (lines 83-87):**
```php
register_rest_route('igny8/v1', '/status', array(
'methods' => 'GET',
'callback' => array($this, 'get_status'),
'permission_callback' => '__return_true', // Public endpoint for health checks
));
```
**Change to:**
```php
register_rest_route('igny8/v1', '/status', array(
'methods' => 'GET',
'callback' => array($this, 'get_status'),
'permission_callback' => array($this, 'check_api_key_auth'),
));
```
#### 3.1.2 Add Lightweight Auth Check Method
Add new method to allow API key OR no key if not configured yet:
```php
/**
* Check API key authentication for status-type endpoints
* Returns true if: no API key stored yet, OR valid API key provided
*/
public function check_api_key_auth($request) {
$stored_api_key = function_exists('igny8_get_secure_option')
? igny8_get_secure_option('igny8_api_key')
: get_option('igny8_api_key');
// If no API key configured yet, allow access (plugin not connected)
if (empty($stored_api_key)) {
return true;
}
// If API key is configured, require valid key in header
$header_api_key = $request->get_header('x-igny8-api-key');
if ($header_api_key && hash_equals($stored_api_key, $header_api_key)) {
return true;
}
return new WP_Error('rest_forbidden', 'Invalid API key', array('status' => 401));
}
```
#### 3.1.3 Fix `/site-metadata/` Route Registration
**Current (lines 74-79):**
```php
register_rest_route('igny8/v1', '/site-metadata/', array(
'methods' => 'GET',
'callback' => array($this, 'get_site_metadata'),
'permission_callback' => '__return_true',
));
```
**Change to:**
```php
register_rest_route('igny8/v1', '/site-metadata/', array(
'methods' => 'GET',
'callback' => array($this, 'get_site_metadata'),
'permission_callback' => array($this, 'check_permission'),
));
```
Then remove the internal permission check from `get_site_metadata()` method.
### 3.2 Backend Changes (REQUIRED)
#### 3.2.1 Update test-connection to Not Rely on Public Endpoint
**File:** `backend/igny8_core/modules/integration/views.py`
**Current approach:**
1. Call `/status` (public) to check plugin installed
2. Call `/verify-key` (authenticated) to verify key
**New approach:**
1. Call `/verify-key` directly
2. If 200 → plugin installed AND key valid
3. If 404 → plugin not installed (route doesn't exist)
4. If 401/403 → plugin installed but key mismatch
**Change Check 2 section (approximately lines 218-237):**
```python
# Check 2 & 3 Combined: Plugin installed + API key verification
# Use /verify-key endpoint - if it succeeds, both are confirmed
try:
verify_response = http_requests.get(
f"{site_url.rstrip('/')}/wp-json/igny8/v1/verify-key",
headers={
'X-IGNY8-API-KEY': stored_api_key,
'Content-Type': 'application/json'
},
timeout=10
)
if verify_response.status_code == 200:
health_checks['plugin_installed'] = True
health_checks['plugin_has_api_key'] = True
health_checks['api_key_verified'] = True
elif verify_response.status_code == 404:
# Route not found = plugin not installed
issues.append("IGNY8 plugin not installed on WordPress site")
elif verify_response.status_code in [401, 403]:
# Auth failed = plugin installed but key doesn't match
health_checks['plugin_installed'] = True
issues.append("API key mismatch - copy the API key from IGNY8 to WordPress plugin settings")
else:
issues.append(f"Unexpected response from plugin: HTTP {verify_response.status_code}")
except http_requests.exceptions.ConnectionError:
issues.append("Cannot connect to WordPress site")
except Exception as e:
issues.append(f"Connection error: {str(e)}")
```
### 3.3 SiteIntegration Decision (OPTIONAL)
The SiteIntegration model is used by:
- Scheduled publishing task
- ContentViewSet.publish action
- Metadata sync service
**Options:**
**Option A: Remove SiteIntegration entirely**
- Modify scheduled publishing to use Site directly
- Modify sync service to use Site directly
- Remove model and migrations
- **Effort:** High, risk of breaking things
**Option B: Keep but don't require it**
- Current main publishing path works without it
- Scheduled publishing would need records created
- **Effort:** Low, minimal changes
**Recommendation:** Option B - Keep as-is. The main UI publishing flow works. If scheduled publishing is needed, run:
```bash
docker exec igny8_backend python manage.py sync_wordpress_api_keys
```
---
## PART 4: IMPLEMENTATION ORDER
### Phase 1: Plugin Security (30 min)
1. Add `check_api_key_auth()` method to plugin
2. Change `/status` endpoint to use `check_api_key_auth`
3. Change `/site-metadata/` to use `check_permission`
4. Remove internal permission check from `get_site_metadata()`
5. Build and deploy plugin
### Phase 2: Backend Update (15 min)
1. Update `test_connection_collection` to use only `/verify-key`
2. Remove `/status` endpoint call
3. Test connection flow
### Phase 3: Testing (15 min)
1. Test plugin uninstalled scenario (404 from /verify-key)
2. Test plugin installed, no key (401 from /verify-key)
3. Test plugin installed, wrong key (401 from /verify-key)
4. Test plugin installed, correct key (200 from /verify-key)
5. Test publishing still works
---
## PART 5: FILES TO MODIFY
### Plugin Files
| File | Changes |
|------|---------|
| `includes/class-igny8-rest-api.php` | Add `check_api_key_auth()`, update route permissions |
### Backend Files
| File | Changes |
|------|---------|
| `modules/integration/views.py` | Update `test_connection_collection` logic |
---
## PART 6: WHAT STAYS THE SAME
**Publishing flow** - No changes needed
**Site.wp_api_key** - Single source of truth
**Site.domain** - WordPress URL source
**WordPressAdapter** - Works correctly
**Plugin /publish endpoint** - Already authenticated
**Plugin /verify-key endpoint** - Already authenticated
**SiteIntegration model** - Keep for scheduled publishing (optional use)
---
## Document History
| Date | Author | Changes |
|------|--------|---------|
| 2026-01-13 | AI | Created after proper code audit |

View File

@@ -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

View File

@@ -0,0 +1,447 @@
# WordPress Integration Audit Report
## Overview
The WordPress plugin (IGNY8 WP Bridge v1.3.3) has been built with extensive features, many of which are either not fully implemented on the IGNY8 app side or are too complex for current needs.
---
## 1. CONTROLS PAGE (Plugin)
### What It Does:
The "Controls" page in the WordPress plugin lets users configure which content types and features should sync between WordPress and IGNY8.
### Current Components:
#### A) **Post Types to Sync**
- Shows: Posts, Pages, Products (if WooCommerce installed)
- **Purpose**: Determines which WordPress post types IGNY8 can publish to
- **What Actually Works**: Only "Posts" is fully functional. Pages and Products publishing is NOT implemented in IGNY8 app yet.
#### B) **Taxonomies to Sync**
- Shows: Categories, Tags, Product Categories, Product Tags, Product Shipping Classes, IGNY8 Sectors, IGNY8 Clusters, Brand, Brands, Expired
- **Purpose**: Determines which taxonomy terms sync between systems
- **What Actually Works**: Only Categories, Tags, IGNY8 Sectors, IGNY8 Clusters are used. The rest are either WooCommerce-specific (not supported) or custom taxonomies from the specific WordPress site.
#### C) **Control Mode** (Mirror vs Hybrid)
- **Mirror Mode**: IGNY8 is source of truth. WordPress reflects changes only. Content is read-only in WordPress.
- **Hybrid Mode**: Two-way sync. WordPress editors can edit content and changes sync back to IGNY8.
- **What Actually Works**: **NOTHING**. This is purely UI. The backend code doesn't check or enforce either mode. Content can always be edited in WordPress and it does NOT sync back to IGNY8. This is misleading UI.
#### D) **IGNY8 Modules**
- Shows: Sites (Data & Semantic Map), Planner (Keywords & Briefs), Writer (Tasks & Posts), Linker (Internal Links), Optimizer (Audits & Scores)
- **Purpose**: Allow users to disable specific IGNY8 features if they're not using them
- **What Actually Works**: **NOTHING**. This is purely UI. The plugin doesn't actually enable/disable any module functionality. These checkboxes have no effect on what gets synced or displayed.
#### E) **Default Post Status**
- Draft or Publish
- **Purpose**: When IGNY8 publishes content, should it be saved as Draft (for review) or Published immediately
- **What Actually Works**: **YES, this works**. The plugin checks this setting when receiving content from IGNY8.
#### F) **Sync WooCommerce Products**
- Checkbox (only shown if WooCommerce installed)
- **What Actually Works**: **NOT IMPLEMENTED** in IGNY8 app. WooCommerce product sync is planned but not built.
---
## 2. SYNC PAGE (Plugin)
### What It Does:
Shows sync status and history for different data types.
### Current Components:
#### A) **Sync Status Toggle**
- "Enable IGNY8 Sync" checkbox
- **Purpose**: Master switch to enable/disable all syncing
- **What Actually Works**: **YES, this works**. When disabled, the plugin rejects incoming content from IGNY8.
#### B) **Sync History**
- Shows: Site Data, Taxonomies, Keywords, Writers
- Shows last sync timestamp and status (Synced/Never)
- **What Actually Works**: **MISLEADING**.
- "Site Data" = metadata about WordPress site sent TO IGNY8 (one-way)
- "Taxonomies" = categories/tags metadata
- "Keywords" = NOT IMPLEMENTED (seed keywords feature)
- "Writers" = NOT IMPLEMENTED (author sync feature)
#### C) **Scheduled Syncs**
- Shows next scheduled site data sync
- **What Actually Works**: WordPress cron job that periodically sends site metadata to IGNY8. Works but rarely used.
---
## 3. DATA PAGE (Plugin)
### What It Does:
Shows internal link queue status.
### Current Components:
- Total Links, Pending, Processed counts
- Link Queue table showing posts, target URLs, anchors, status
### What Actually Works:
This is for the **LINKER** module - internal linking automation. When IGNY8 sends a post with internal link suggestions, they go into this queue and get processed.
**Current Status**: Linker module is partially implemented but not production-ready in IGNY8 app.
---
## 4. CONTENT TYPES PAGE (IGNY8 App - Site Settings)
### What It Does:
Shows WordPress site structure fetched from the plugin:
- Post Types (Pages, Posts, Products) with counts and "Enabled/Disabled" badges
- Taxonomies (Categories, Tags, etc.) with counts and "Enabled/Disabled" badges
- "Sync Structure" button to refresh data
### What Actually Works:
- **Sync Structure** = Calls WordPress plugin's `/igny8/v1/site-metadata/` endpoint to get counts
- **Enabled/Disabled** = Reflects what's checked in WordPress plugin's Controls page
- **Limit: 100** = Fetch limit per type (hardcoded, not configurable in app)
### Purpose:
This page is **informational only** - shows what's available on WordPress side. User cannot change anything here. They must change settings in WordPress plugin.
### Problem:
The "Enabled/Disabled" comes from WordPress but the IGNY8 app has NO way to enable/disable content types. It's display-only and confusing.
---
## 5. What Actually Gets Synced
### IGNY8 → WordPress (Working):
1. **Published Content**: When content is approved in IGNY8 and published, it gets sent to WordPress
2. **Categories**: IGNY8 categories sync as WordPress categories
3. **Tags**: IGNY8 tags sync as WordPress tags
4. **IGNY8 Sectors**: Custom taxonomy for content organization
5. **IGNY8 Clusters**: Custom taxonomy for content clustering
6. **Featured Images**: Images are uploaded to WordPress media library
### WordPress → IGNY8 (Working):
1. **Site Metadata**: Post counts, taxonomy counts (for display in app)
2. **Post Status Updates**: Webhooks notify IGNY8 when WordPress post status changes
### NOT Working / Not Implemented:
1. Pages publishing
2. Products publishing (WooCommerce)
3. Product Categories/Tags sync
4. Two-way content sync (Hybrid Mode)
5. Keywords sync
6. Writers sync
7. Module-based enable/disable
8. Internal linking automation (Linker)
9. Audits & Scores (Optimizer)
---
---
# IMPLEMENTATION PLAN
---
## Phase 1: WordPress Plugin Updates
### 1.1 Controls Page Redesign → Rename to "Settings"
#### New Structure:
**Section A: Post Types** (Top level toggles)
```
┌─────────────────────────────────────────────────────────────┐
│ POST TYPES │
├─────────────────────────────────────────────────────────────┤
│ ☑ Posts │
│ ☐ Pages [Coming Soon] │
│ ☐ Products (WooCommerce) [Coming Soon] │
│ ☐ {Other detected CPTs} [Coming Soon] │
└─────────────────────────────────────────────────────────────┘
```
- Auto-detect ALL registered post types in WordPress
- Show toggle for each post type
- "Coming Soon" badge on ALL post types except Posts
- Keep all toggles functional for now (testing) - will disable later
- Only show taxonomy cards below for ENABLED post types
**Section B: Post Type Cards** (Conditional - only shown if post type is enabled)
For each enabled post type, show a separate card with its taxonomies:
```
┌─────────────────────────────────────────────────────────────┐
│ 📝 POSTS │
├─────────────────────────────────────────────────────────────┤
│ Taxonomies: │
│ ☑ Categories │
│ ☑ Tags │
│ ☑ IGNY8 Sectors │
│ ☑ IGNY8 Clusters │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 📦 PRODUCTS [Coming Soon] │
├─────────────────────────────────────────────────────────────┤
│ Taxonomies: │
│ ☑ Product Categories │
│ ☑ Product Tags │
│ ☑ IGNY8 Sectors │
│ ☑ IGNY8 Clusters │
│ ☐ Product Shipping Classes │
│ ☐ Brands │
│ ☐ {Other product attributes/taxonomies} │
└─────────────────────────────────────────────────────────────┘
```
- Auto-detect ALL taxonomies registered for each post type
- For Products: auto-detect all WooCommerce attributes and taxonomies
- Default enabled: Categories, Tags, Product Categories, Product Tags, IGNY8 Sectors, IGNY8 Clusters
- Default disabled: All other detected taxonomies
- Remove "Expired" taxonomy (this was created by our plugin in error - delete it)
**Section C: Other Settings**
```
┌─────────────────────────────────────────────────────────────┐
│ DEFAULT SETTINGS │
├─────────────────────────────────────────────────────────────┤
│ Default Post Status: [Draft ▼] │
│ Enable IGNY8 Sync: ☑ ON │
└─────────────────────────────────────────────────────────────┘
```
### 1.2 Items to REMOVE from Plugin
1. **Control Mode** (Mirror/Hybrid) - doesn't do anything, misleading
2. **IGNY8 Modules checkboxes** - doesn't do anything, misleading
3. **Sync History for Keywords** - not implemented
4. **Sync History for Writers** - not implemented
5. **Scheduled Syncs section** - rarely used, confusing
6. **"Expired" taxonomy** - created in error by plugin, remove it
### 1.3 UI Terminology Changes
| Current | New |
|---------|-----|
| Controls | Settings |
| Sync | Connection |
| Sync History | Last Updated |
| Data | Link Queue (if Linker enabled) or hide |
### 1.4 Technical Implementation Notes
```php
// Auto-detect post types
$post_types = get_post_types(array('public' => true), 'objects');
// Auto-detect taxonomies for each post type
foreach ($post_types as $post_type) {
$taxonomies = get_object_taxonomies($post_type->name, 'objects');
}
// For WooCommerce Products, also get attributes
if (class_exists('WooCommerce')) {
$attributes = wc_get_attribute_taxonomies();
}
```
---
## Phase 2: IGNY8 App Updates
### 2.1 Site Content Page Updates (`/sites/{id}/content`)
#### A) Post Type Filters (Top Bar)
Add post type selector buttons above the content list:
- Only show if there are more than 1 post type enabled in WordPress plugin
- Show buttons like: `Posts` | `Products` | `Services` | etc.
- Default to "Posts"
- Filter content by selected post type
```
┌─────────────────────────────────────────────────────────────┐
│ Posts │ Products │ Services │
├─────────────────────────────────────────────────────────────┤
│ [Filters...] [Content Types] │
├─────────────────────────────────────────────────────────────┤
│ Content list... │
└─────────────────────────────────────────────────────────────┘
```
#### B) Content Types Button
Add "Content Types" button on the far right of the same row as post type filters:
- Opens a modal/drawer showing WordPress content structure
- This replaces the "Content Types" tab in Site Settings
### 2.2 New Content Structure Page (Full Page)
**Route:** `/sites/{id}/content/structure`
**Purpose:** Show site's content structure organized by Clusters - FULL PAGE, not modal
**Layout:**
```
┌─────────────────────────────────────────────────────────────┐
│ SITE > CONTENT > STRUCTURE │
├─────────────────────────────────────────────────────────────┤
│ Cluster: [Select Cluster ▼] │
├─────────────────────────────────────────────────────────────┤
│ (When cluster selected, show:) │
│ │
│ Keywords in this Cluster: │
│ ┌─────────────────────────────────────────────────────────┐
│ │ Keyword Name │ Content Count │
│ ├─────────────────────────────────────────────────────────┤
│ │ AI Content Writing │ 12 │
│ │ Blog Automation Tools │ 8 │
│ │ SEO Content Strategy │ 5 │
│ └─────────────────────────────────────────────────────────┘
│ │
│ Cluster Content: │
│ (REUSE existing component from /planner/clusters/{id}) │
│ - View Live link │
│ - View in App link │
│ - Edit link │
│ - Published/Pending/Draft counts │
│ - Content list with same styling │
└─────────────────────────────────────────────────────────────┘
```
**Key Features:**
- Full page with proper URL: `/sites/{id}/content/structure`
- Cluster selector dropdown at top
- Keywords list with content counts for selected cluster
- **REUSE** existing content list component from cluster detail page (`/planner/clusters/{id}`)
- Same functionality: View Live, View in App, Edit links
- Shows content stats and articles list
### 2.3 Remove from Site Settings Page
1. **Remove "Content Types" tab** from Site Settings
2. Only track content/terms that IGNY8 is publishing/using
### 2.4 Content & Taxonomy Tracking (KEEP & IMPROVE)
**IMPORTANT:** Do NOT break existing publishing and tracking systems.
**What We Track (KEEP):**
- Content published by IGNY8 against Clusters
- Content published by IGNY8 against Keywords
- IGNY8 Clusters taxonomy on WordPress (created/synced by plugin)
- IGNY8 Sectors taxonomy on WordPress (created/synced by plugin)
- Categories and Tags used by published content
**Improvements:**
- Validate IGNY8 Clusters taxonomy sync between app and WordPress
- Validate IGNY8 Sectors taxonomy sync between app and WordPress
- Ensure counts match on both local app and remote WordPress site
- Content tracking against clusters/keywords is critical for:
- Future optimization
- Content rewriting
- Content extension
- Performance analytics
### 2.5 Bulk Structure Syncing (SIMPLIFY, NOT REMOVE)
**KEEP:**
- Syncing of IGNY8 Clusters taxonomy
- Syncing of IGNY8 Sectors taxonomy
- Categories/Tags used by IGNY8 published content
- Content counts against clusters/keywords
**REMOVE:**
- Full WordPress site structure dump (all post types, all taxonomies)
- Counts of content not managed by IGNY8
- Periodic full re-sync of entire site structure
**Principle:** Only track what IGNY8 creates/publishes, not entire WordPress site.
---
## Phase 3: Summary of Changes
### WordPress Plugin Changes:
| Item | Action |
|------|--------|
| Controls page | Rename to "Settings", redesign |
| Post Types | Auto-detect all, show toggles, "Coming Soon" on all except Posts |
| Taxonomies | Group under post type cards, auto-detect all |
| Control Mode | REMOVE |
| IGNY8 Modules | REMOVE |
| Expired taxonomy | DELETE (was created in error) |
| Sync History (Keywords, Writers) | REMOVE |
| Scheduled Syncs | REMOVE |
| Data page | Keep for Linker queue only |
### IGNY8 App Changes:
| Item | Action |
|------|--------|
| `/sites/{id}/content` | Add post type filter buttons |
| Content Types button | Add to content page → links to `/sites/{id}/content/structure` |
| Content Structure page | New full page at `/sites/{id}/content/structure` |
| Site Settings > Content Types tab | REMOVE |
| Bulk structure syncing | SIMPLIFY - only track IGNY8-managed content |
| IGNY8 Clusters/Sectors sync | KEEP & VALIDATE - ensure app ↔ WordPress match |
| Content tracking (clusters/keywords) | KEEP - critical for optimization/rewriting |
### What Still Works (No Changes):
1. Publishing Posts to WordPress ✅
2. Categories sync ✅
3. Tags sync ✅
4. IGNY8 Sectors sync ✅
5. IGNY8 Clusters sync ✅
6. Featured images ✅
7. In-article images ✅
8. Post status webhooks ✅
9. Content tracking against Clusters ✅
10. Content tracking against Keywords ✅
---
## Implementation Checklist
### WordPress Plugin:
- [ ] Rename "Controls" to "Settings"
- [ ] Add post type auto-detection
- [ ] Add post type toggles with "Coming Soon" badges
- [ ] Create taxonomy cards for each enabled post type
- [ ] Auto-detect all taxonomies per post type
- [ ] Auto-detect WooCommerce attributes for Products
- [ ] Set default enabled taxonomies (Categories, Tags, Product Categories, Product Tags, Sectors, Clusters)
- [ ] Remove Control Mode section
- [ ] Remove IGNY8 Modules section
- [ ] Remove "Expired" taxonomy
- [ ] Remove Keywords/Writers from Sync History
- [ ] Remove Scheduled Syncs section
- [ ] Simplify Sync page → Connection page
### IGNY8 App:
- [ ] Add post type filter buttons to `/sites/{id}/content`
- [ ] Only show if >1 post type enabled
- [ ] Add "Structure" button (far right) → links to `/sites/{id}/content/structure`
- [ ] Create Content Structure page at `/sites/{id}/content/structure`
- [ ] Add cluster selector dropdown
- [ ] Show keywords with content counts
- [ ] REUSE content list component from `/planner/clusters/{id}`
- [ ] Remove Content Types tab from Site Settings
- [ ] Simplify bulk structure syncing (keep IGNY8 clusters/sectors only)
- [ ] Validate IGNY8 Clusters taxonomy sync (app ↔ WordPress)
- [ ] Validate IGNY8 Sectors taxonomy sync (app ↔ WordPress)
- [ ] Keep content tracking against clusters/keywords (DO NOT BREAK)
---
## Notes
- All post types except Posts show "Coming Soon" but remain configurable for testing
- Later: disable non-Posts post types after production testing
- Publishing flow MUST continue working throughout changes
- No changes to the actual publishing API endpoints
- **CRITICAL:** Content tracking against Clusters and Keywords must remain intact
- **CRITICAL:** IGNY8 Clusters and Sectors taxonomy sync must be validated and working
- Reuse existing components where possible (content list from cluster detail page)

View File

@@ -0,0 +1,215 @@
# WordPress Plugin & IGNY8 App Cleanup - Implementation Summary
**Date:** Implementation completed
**Related Plan:** [WP_PLUGIN_IGNY8_APP_CLEANUP.md](./WP_PLUGIN_IGNY8_APP_CLEANUP.md)
---
## Overview
This document summarizes the changes implemented to clean up and simplify the WordPress plugin (IGNY8 WP Bridge) and the IGNY8 app frontend based on the requirements discussed.
---
## WordPress Plugin Changes
### 1. Controls Page → Settings Page (RENAMED & REDESIGNED)
**File:** `plugins/wordpress/source/igny8-wp-bridge/admin/pages/settings.php`
**Changes:**
- Renamed "Controls" to "Settings" throughout the plugin
- Complete redesign with post type cards showing:
- Auto-detected post types using WordPress `get_post_types(['public' => true])`
- Each post type displayed in a card with its own taxonomies
- Enable/disable toggles for each post type
- **"Coming Soon" badges** on all post types except Posts
- Default settings section with Post Status and Sync Enable toggles
- Taxonomy cards shown only for enabled post types
**Key Features:**
- Auto-detects ALL public post types (Posts, Pages, Products, Custom Post Types)
- Shows custom taxonomies specific to each post type
- Products section auto-detects WooCommerce attributes/taxonomies
- Accordion/expandable cards for post type configuration
- Clean, scannable interface
### 2. Header Navigation Update
**File:** `plugins/wordpress/source/igny8-wp-bridge/admin/layout-header.php`
**Changes:**
- Changed "Controls" navigation link to "Settings"
- Updated icon to settings gear icon
### 3. Admin Class Updates
**File:** `plugins/wordpress/source/igny8-wp-bridge/admin/class-admin.php`
**Changes:**
- Changed menu slug from 'igny8-controls' to 'igny8-settings'
- Updated render_page() to load settings.php instead of controls.php
- Added new setting registration: `igny8_sync_enabled`
### 4. Dashboard Quick Links Update
**File:** `plugins/wordpress/source/igny8-wp-bridge/admin/pages/dashboard.php`
**Changes:**
- Updated "Controls" card to "Settings" with appropriate description and icon
### 5. Sync Page Simplified
**File:** `plugins/wordpress/source/igny8-wp-bridge/admin/pages/sync.php`
**Changes:**
- **Removed:** Keywords sync history section
- **Removed:** Writers sync history section
- **Removed:** Scheduled Syncs configuration section
- **Kept:** Connection status indicator
- **Kept:** Content stats (total synced count)
- **Kept:** Last updated timestamp
---
## IGNY8 App Frontend Changes
### 1. New Content Structure Page
**File:** `frontend/src/pages/Sites/ContentStructure.tsx`
**Route:** `/sites/:id/content/structure`
**Features:**
- **Full page** (not modal/drawer as originally planned)
- Cluster selector dropdown at top
- Keywords table showing:
- Keyword name
- Intent
- Volume
- Content count per keyword
- Content list reusing the ClusterDetail component style
- View/Edit buttons for each content item
- Back navigation to Content page
### 2. App Router Update
**File:** `frontend/src/App.tsx`
**Changes:**
- Added lazy import for `SiteContentStructure`
- Added route: `/sites/:id/content/structure`
### 3. Site Content Page Updates
**File:** `frontend/src/pages/Sites/Content.tsx`
**Changes:**
- Added `postTypeFilter` state for filtering by post type
- Added post type filter buttons (All, Posts, Pages, Products)
- Added "Structure" button linking to `/sites/{id}/content/structure`
- Filter buttons passed to API when fetching content
### 4. Site Settings - Content Types Tab Removed
**File:** `frontend/src/pages/Sites/Settings.tsx`
**Changes:**
- Removed 'content-types' from `TabType` union
- Removed Content Types tab button from navigation
- Removed Content Types tab content section
- Removed `loadContentTypes()` function and related state
- Added redirect: if tab is 'content-types', redirects to `/sites/{id}/content/structure`
---
## Files Created
| File | Purpose |
|------|---------|
| `plugins/wordpress/source/igny8-wp-bridge/admin/pages/settings.php` | New Settings page replacing Controls |
| `frontend/src/pages/Sites/ContentStructure.tsx` | New Content Structure full page |
---
## Files Modified
| File | Changes |
|------|---------|
| `plugins/wordpress/source/igny8-wp-bridge/admin/class-admin.php` | Menu slug change, page render update |
| `plugins/wordpress/source/igny8-wp-bridge/admin/layout-header.php` | Navigation link rename |
| `plugins/wordpress/source/igny8-wp-bridge/admin/pages/dashboard.php` | Quick links card rename |
| `plugins/wordpress/source/igny8-wp-bridge/admin/pages/sync.php` | Simplified (removed bulk sections) |
| `frontend/src/App.tsx` | Added content structure route |
| `frontend/src/pages/Sites/Content.tsx` | Added post type filters and Structure button |
| `frontend/src/pages/Sites/Settings.tsx` | Removed Content Types tab |
---
## What Remains Working (Unchanged)
As per requirements, the following features remain fully functional:
-**Single article publishing to WordPress** - Healthy and working
-**Article updates** - Still working
-**Taxonomies (categories/tags)** - Still applied during publish
-**Featured images** - Still uploaded and set
-**In-article images** - Still handled properly
-**Content tracking against clusters/keywords** - Intact
-**Cluster-based content view** - Now enhanced with new Content Structure page
---
## UI Indicator Summary
| Post Type | Status | UI Indicator |
|-----------|--------|--------------|
| Posts | Active | No badge (fully working) |
| Pages | Coming Soon | "Coming Soon" badge |
| Products | Coming Soon | "Coming Soon" badge |
| Custom Post Types | Coming Soon | "Coming Soon" badge |
---
## API Backend Requirements (Not Implemented)
The following API endpoints may need backend updates to fully support the new frontend features:
1. **Content Structure API** - Endpoint to fetch cluster-based content structure
- Clusters with their keywords
- Content counts per keyword
- Content list filterable by cluster/keyword
2. **Post Type Filter** - Backend support for `postTypeFilter` parameter in content listing API
---
## Testing Checklist
### WordPress Plugin
- [ ] Settings page loads correctly
- [ ] All public post types are detected
- [ ] Post type enable/disable toggles work
- [ ] Taxonomies display correctly per post type
- [ ] Coming Soon badges appear on non-Post types
- [ ] Sync page shows correct connection status
- [ ] Navigation links work correctly
### IGNY8 App
- [ ] Content Structure page loads at `/sites/{id}/content/structure`
- [ ] Cluster selector dropdown works
- [ ] Keywords table displays correctly
- [ ] Content list shows with View/Edit buttons
- [ ] Post type filter buttons work on Content page
- [ ] Structure button navigates correctly
- [ ] Site Settings no longer shows Content Types tab
- [ ] Publishing to WordPress still works as expected
---
## Notes
1. The "Expired" taxonomy mentioned in requirements should be removed from WordPress - this is a plugin configuration issue, not a code change
2. The Content Structure page reuses existing component styles for consistency
3. All TypeScript errors have been resolved
4. The implementation follows the existing design patterns and component library