1596 lines
59 KiB
Markdown
1596 lines
59 KiB
Markdown
# Publishing Progress & Scheduling UX Enhancement Plan
|
||
|
||
## Document Overview
|
||
|
||
**Author**: System Analysis
|
||
**Date**: January 2025
|
||
**Status**: Planning Phase
|
||
**Target**: Publishing UX improvements with progress tracking and scheduling interfaces for multi-platform publishing (WordPress, Shopify, Custom Sites)
|
||
|
||
---
|
||
|
||
## 1. Executive Summary
|
||
|
||
This document provides a comprehensive implementation plan for enhancing the publishing workflow with:
|
||
|
||
1. **Publishing Progress Modals** - Real-time feedback for single and bulk publishing operations
|
||
2. **Publishing Limits & Validation** - Direct bulk publish limited to 5 items, unlimited scheduling
|
||
3. **Workflow Optimization** - Remove publish from Review page, make it Approved-only
|
||
4. **Scheduling UI Enhancement** - Add scheduling/rescheduling interfaces across multiple pages
|
||
5. **Site Settings Integration** - Bulk scheduling uses default settings from Site Settings → Publishing tab
|
||
6. **Failed Content Handling** - UI for rescheduling failed scheduled publications
|
||
|
||
### Key Design Principles
|
||
|
||
- **Pattern Consistency**: Follow existing modal patterns from ImageQueueModal and content generation
|
||
- **Progressive Disclosure**: Show appropriate details based on operation complexity
|
||
- **Real-time Feedback**: Live progress updates during publishing operations
|
||
- **Error Recovery**: Clear paths to retry/reschedule failed operations
|
||
|
||
---
|
||
|
||
## 2. Current System Analysis
|
||
|
||
### 2.1 Existing Publishing Architecture
|
||
|
||
**Backend System (v2.0)**:
|
||
- Service: `PublisherService` at `backend/igny8_core/business/publishing/services/publisher_service.py`
|
||
- API Endpoint: `POST /api/v1/publisher/publish`
|
||
- Credentials: Stored on `Site` model (`api_key`, `domain`, `platform_type`)
|
||
- Supported Platforms: WordPress, Shopify, Custom Sites
|
||
- Processing: Synchronous (not queued to Celery)
|
||
- Site Settings: Default publishing/scheduling configuration at `/sites/{site_id}/settings?tab=publishing`
|
||
|
||
**Scheduling System**:
|
||
- Celery Beat Task: `process_scheduled_publications` (runs every 5 minutes)
|
||
- Content States:
|
||
- `site_status`: `not_published` → `scheduled` → `publishing` → `published`/`failed`
|
||
- `scheduled_publish_at`: ISO8601 datetime
|
||
- `site_status_updated_at`: Timestamp of last status change
|
||
- API Endpoints:
|
||
- `POST /api/v1/writer/content/{id}/schedule/` - Schedule for future publishing
|
||
- `POST /api/v1/writer/content/{id}/unschedule/` - Cancel scheduled publishing
|
||
- `POST /api/v1/writer/content/{id}/reschedule/` - Reschedule from any status (NEW)
|
||
|
||
### 2.2 Current Publishing Workflows
|
||
|
||
**Review Page** (`frontend/src/pages/Writer/Review.tsx`):
|
||
- **Current Behavior**: Shows content with `status='review'`
|
||
- **Actions Available**:
|
||
- Single publish: `handlePublishSingle()` → calls `POST /v1/publisher/publish/`
|
||
- Bulk publish: `handlePublishBulk()` → loops through items, publishes individually
|
||
- Approve (changes status to 'approved')
|
||
- **Issue**: Publishing from Review bypasses approval workflow
|
||
- **No Feedback**: No progress modal, only toast notifications
|
||
- **Current Issue**: Uses "WordPress" terminology, limiting perceived platform support
|
||
|
||
**Approved Page** (`frontend/src/pages/Writer/Approved.tsx`):
|
||
- **Current Behavior**: Shows content with `status='approved'` or `status='published'`
|
||
- **Actions Available**:
|
||
- Single publish: `handleRowAction('publish_site')` → `POST /v1/publisher/publish/` (no limit)
|
||
- Bulk publish: `handleBulkPublishToSite()` → loops through items (max 5 items)
|
||
- Bulk schedule: Uses site default settings (no limit on items)
|
||
- **No Feedback**: No progress modal, only toast notifications
|
||
- **No Scheduling**: Cannot schedule for future publishing from UI
|
||
- **No Validation**: No limit enforcement for bulk operations
|
||
- **Current Issue**: Action names reference specific platforms instead of generic "site" terminology
|
||
|
||
**Content Calendar** (`frontend/src/pages/Publisher/ContentCalendar.tsx`):
|
||
- **Current Behavior**: Shows scheduled items with calendar view
|
||
- **Scheduling Method**: Drag-and-drop from approved sidebar to calendar dates
|
||
- **Schedule Time**: Defaults to 9 AM on dropped date
|
||
- **API Calls**:
|
||
- `scheduleContent(contentId, scheduledDate)` → `POST /v1/writer/content/{id}/schedule/`
|
||
- `unscheduleContent(contentId)` → `POST /v1/writer/content/{id}/unschedule/`
|
||
- **No Edit UI**: Cannot edit scheduled time without drag-drop
|
||
|
||
### 2.3 Existing Modal Patterns
|
||
|
||
**ImageQueueModal** (`frontend/src/components/common/ImageQueueModal.tsx`):
|
||
- **Purpose**: Shows AI image generation progress for multiple images
|
||
- **Key Features**:
|
||
- Queue display with individual progress bars
|
||
- Status per item: `pending` → `processing` → `completed`/`failed`
|
||
- Smooth progress animation (1% → 50% in 5s, 50% → 80% at 2%/200ms, 80% → 100% on completion)
|
||
- Real-time polling: `GET /v1/system/settings/task_progress/{taskId}/` every 1 second
|
||
- Visual feedback: Status icons, color-coded progress bars
|
||
- Thumbnail preview on completion
|
||
- Cannot close while processing
|
||
- **State Management**:
|
||
```typescript
|
||
interface ImageQueueItem {
|
||
imageId: number | null;
|
||
index: number;
|
||
label: string;
|
||
status: 'pending' | 'processing' | 'completed' | 'failed';
|
||
progress: number;
|
||
imageUrl: string | null;
|
||
error: string | null;
|
||
}
|
||
```
|
||
- **Progress Tracking**:
|
||
- Backend provides: `current_image`, `current_image_id`, `current_image_progress`, `results[]`
|
||
- Frontend manages smooth animation with `smoothProgress` state
|
||
- Progress intervals cleared on completion/failure
|
||
|
||
---
|
||
|
||
## 3. Publishing Progress Modal Implementation
|
||
|
||
### 3.1 Single Content Publishing Modal
|
||
|
||
**Component**: `PublishingProgressModal` (new)
|
||
**Location**: `frontend/src/components/common/PublishingProgressModal.tsx`
|
||
|
||
#### Design Specifications
|
||
|
||
**Modal Structure**:
|
||
```
|
||
┌────────────────────────────────────────────────────┐
|
||
│ 🚀 Publishing Content │
|
||
│ Publishing "Article Title Here" to [Site Name] │
|
||
├────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌────────────────────────────────────────────────┐ │
|
||
│ │ 📄 Preparing content... 25% │ │
|
||
│ │ ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
|
||
│ └────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ Status: Uploading to WordPress... │
|
||
│ │
|
||
├────────────────────────────────────────────────────┤
|
||
│ [Close button - only shown on completion/error] │
|
||
└────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Progress Stages**:
|
||
1. **Preparing content** (0-25%): Validating content structure
|
||
2. **Uploading to site** (25-50%): POST request to publishing platform API
|
||
3. **Processing response** (50-75%): Handling platform API response
|
||
4. **Finalizing** (75-100%): Updating content record
|
||
|
||
**State Interface**:
|
||
```typescript
|
||
interface PublishingProgressState {
|
||
contentId: number;
|
||
contentTitle: string;
|
||
destination: 'wordpress' | 'shopify' | 'custom' | string; // Platform type
|
||
siteName: string; // Actual site name for display
|
||
status: 'preparing' | 'uploading' | 'processing' | 'finalizing' | 'completed' | 'failed';
|
||
progress: number; // 0-100
|
||
statusMessage: string;
|
||
error: string | null;
|
||
externalUrl: string | null; // Published URL on success
|
||
externalId: string | null; // External platform post/page ID
|
||
}
|
||
```
|
||
|
||
**Progress Animation**:
|
||
- **Phase 1** (0-25%): 1% every 100ms for 2.5 seconds
|
||
- **Phase 2** (25-50%): During actual API call (may vary)
|
||
- **Phase 3** (50-75%): 2% every 80ms for 1 second
|
||
- **Phase 4** (75-100%): Fast animation on success (800ms)
|
||
|
||
**Error Handling**:
|
||
- Show error message in red alert box
|
||
- Display "Retry" and "Close" buttons
|
||
- Log full error for debugging
|
||
- Option to copy error details
|
||
|
||
**Success State**:
|
||
- Green checkmark icon
|
||
- "View on Site" button (opens `external_url` in new tab)
|
||
- Shows actual site name: "View on [Site Name]"
|
||
- "Close" button
|
||
- Auto-close after 3 seconds (optional)
|
||
|
||
#### API Integration
|
||
|
||
**Endpoint**: `POST /api/v1/publisher/publish/`
|
||
**Request**:
|
||
```json
|
||
{
|
||
"content_id": 123,
|
||
"destinations": ["wordpress"] // Can be: "wordpress", "shopify", "custom"
|
||
}
|
||
```
|
||
|
||
**Response** (success):
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"success": true,
|
||
"results": [
|
||
{
|
||
"destination": "wordpress", // or "shopify", "custom"
|
||
"success": true,
|
||
"external_id": "456",
|
||
"url": "https://site.com/article-title/",
|
||
"publishing_record_id": 789,
|
||
"platform_type": "wordpress"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**Response** (failure):
|
||
```json
|
||
{
|
||
"success": false,
|
||
"error": "Publishing API error: Invalid credentials"
|
||
}
|
||
```
|
||
|
||
**Since publishing is synchronous**, the modal flow:
|
||
1. Open modal immediately on "Publish" click
|
||
2. Show "Preparing" stage with progress 0-25%
|
||
3. Make API call (update to "Uploading" at 25%)
|
||
4. On response:
|
||
- Success: Animate 25% → 100% with status updates
|
||
- Failure: Show error state
|
||
5. Update parent component's content list
|
||
|
||
### 3.2 Bulk Publishing Modal
|
||
|
||
**Component**: `BulkPublishingModal` (new)
|
||
**Location**: `frontend/src/components/common/BulkPublishingModal.tsx`
|
||
|
||
#### Design Specifications
|
||
|
||
**Modal Structure** (similar to ImageQueueModal):
|
||
```
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ 🚀 Publishing Content │
|
||
│ Publishing 5 articles to [Site Name] │
|
||
├──────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌────────────────────────────────────────────────────┐ │
|
||
│ │ 1️⃣ First Article Title ✓ 100%│ │
|
||
│ │ █████████████████████████████████████████████████ │ │
|
||
│ │ Published: https://site.com/article-1/ │ │
|
||
│ └────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌────────────────────────────────────────────────────┐ │
|
||
│ │ 2️⃣ Second Article Title ⏳ 45% │ │
|
||
│ │ ████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
|
||
│ │ Status: Uploading to site... │ │
|
||
│ └────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌────────────────────────────────────────────────────┐ │
|
||
│ │ 3️⃣ Third Article Title ⏸️ 0% │ │
|
||
│ │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
|
||
│ │ Status: Pending │ │
|
||
│ └────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ [2 more items...] │
|
||
│ │
|
||
├──────────────────────────────────────────────────────────┤
|
||
│ 1 completed, 1 failed, 3 pending │
|
||
│ [Close - if done] │
|
||
└──────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Queue Item State**:
|
||
```typescript
|
||
interface PublishQueueItem {
|
||
contentId: number;
|
||
contentTitle: string;
|
||
index: number;
|
||
status: 'pending' | 'processing' | 'completed' | 'failed';
|
||
progress: number; // 0-100
|
||
statusMessage: string;
|
||
error: string | null;
|
||
externalUrl: string | null;
|
||
externalId: string | null;
|
||
}
|
||
```
|
||
|
||
**Processing Strategy**:
|
||
- **Sequential Processing**: Publish one item at a time (not parallel)
|
||
- **Reason**: Avoid overwhelming platform APIs, easier error tracking, respects rate limits
|
||
- **Progress Animation**: Same as single modal per item
|
||
- **Status Updates**: Real-time per item
|
||
- **Platform Aware**: Adapts to WordPress, Shopify, or custom platform requirements
|
||
|
||
**Visual Indicators**:
|
||
- ✓ (green) - Completed
|
||
- ⏳ (blue) - Processing
|
||
- ⏸️ (gray) - Pending
|
||
- ❌ (red) - Failed
|
||
|
||
**Error Recovery**:
|
||
- Failed items show "Retry" button
|
||
- Can retry individual failed items
|
||
- Can skip failed items and continue
|
||
- Summary at bottom: X completed, Y failed, Z pending
|
||
|
||
**Cannot Close Until Complete**:
|
||
- Close button disabled while `status='processing'` items exist
|
||
- User must wait for all items to complete/fail
|
||
- Similar to ImageQueueModal behavior
|
||
|
||
#### Implementation Flow
|
||
|
||
```typescript
|
||
const handleBulkPublish = async (contentIds: number[]) => {
|
||
// Initialize queue
|
||
const queue: PublishQueueItem[] = contentIds.map((id, index) => ({
|
||
contentId: id,
|
||
contentTitle: getContentTitle(id), // Lookup from parent
|
||
index: index + 1,
|
||
status: 'pending',
|
||
progress: 0,
|
||
statusMessage: 'Pending',
|
||
error: null,
|
||
externalUrl: null,
|
||
externalId: null,
|
||
}));
|
||
|
||
// Open modal with queue
|
||
setPublishQueue(queue);
|
||
setIsModalOpen(true);
|
||
|
||
// Process sequentially
|
||
for (let i = 0; i < queue.length; i++) {
|
||
// Update status to processing
|
||
updateQueueItem(i, { status: 'processing', progress: 0, statusMessage: 'Preparing...' });
|
||
|
||
try {
|
||
// Simulate progress animation
|
||
animateProgress(i, 0, 25, 2500); // 0-25% in 2.5s
|
||
|
||
// Call API (destination determined by site's platform_type)
|
||
const response = await fetchAPI('/v1/publisher/publish/', {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
content_id: queue[i].contentId,
|
||
destinations: [sitePlatformType] // 'wordpress', 'shopify', or 'custom'
|
||
})
|
||
});
|
||
|
||
// Handle response
|
||
if (response.success && response.data?.results?.[0]?.success) {
|
||
const result = response.data.results[0];
|
||
animateProgress(i, 25, 100, 1500); // 25-100% in 1.5s
|
||
updateQueueItem(i, {
|
||
status: 'completed',
|
||
progress: 100,
|
||
statusMessage: 'Published',
|
||
externalUrl: result.url,
|
||
externalId: result.external_id,
|
||
});
|
||
} else {
|
||
updateQueueItem(i, {
|
||
status: 'failed',
|
||
progress: 0,
|
||
statusMessage: 'Failed',
|
||
error: response.error || 'Unknown error',
|
||
});
|
||
}
|
||
} catch (error) {
|
||
updateQueueItem(i, {
|
||
status: 'failed',
|
||
progress: 0,
|
||
statusMessage: 'Failed',
|
||
error: error.message,
|
||
});
|
||
}
|
||
}
|
||
|
||
// All done - enable close button
|
||
setAllComplete(true);
|
||
};
|
||
```
|
||
|
||
### 3.3 Publishing Limits & Validation
|
||
|
||
#### Direct Publish Limit (5 Items Max)
|
||
|
||
**Rationale**:
|
||
- Direct publishing is synchronous and resource-intensive
|
||
- Limits server load and prevents API rate limiting
|
||
- Encourages use of scheduling for large batches
|
||
- Single-item publish (3-dot menu) has no limit (only 1 item)
|
||
|
||
**Implementation**:
|
||
|
||
**Validation in Approved Page**:
|
||
```typescript
|
||
const handleBulkPublishClick = () => {
|
||
const selectedCount = selectedIds.length;
|
||
|
||
// Validate: Max 5 items for direct bulk publish
|
||
if (selectedCount > 5) {
|
||
// Show limit exceeded modal
|
||
setShowPublishLimitModal(true);
|
||
return;
|
||
}
|
||
|
||
// Proceed with bulk publish
|
||
handleBulkPublishToSite(selectedIds);
|
||
};
|
||
```
|
||
|
||
**Limit Exceeded Modal**:
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ ⚠️ Publishing Limit Exceeded │
|
||
├────────────────────────────────────────────────────────────┤
|
||
│ You can publish only 5 content pages to site directly. │
|
||
│ │
|
||
│ You have selected {X} items. │
|
||
│ │
|
||
│ Options: │
|
||
│ • Deselect items to publish 5 or fewer │
|
||
│ • Use "Schedule Selected" to schedule all items │
|
||
│ │
|
||
│ ℹ️ Tip: Scheduling has no limit and uses your site's │
|
||
│ default publishing schedule. │
|
||
├────────────────────────────────────────────────────────────┤
|
||
│ [Schedule Selected] [Go Back] │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Modal Component**: `PublishLimitModal` (new)
|
||
```typescript
|
||
interface PublishLimitModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
selectedCount: number;
|
||
onScheduleInstead: () => void;
|
||
}
|
||
|
||
const PublishLimitModal = ({ isOpen, onClose, selectedCount, onScheduleInstead }) => {
|
||
return (
|
||
<Modal isOpen={isOpen} onClose={onClose}>
|
||
<div className="text-center">
|
||
<WarningIcon className="w-16 h-16 text-warning-500 mx-auto mb-4" />
|
||
<h3 className="text-lg font-semibold mb-2">Publishing Limit Exceeded</h3>
|
||
<p className="text-gray-600 mb-4">
|
||
You can publish only <strong>5 content pages</strong> to site directly.
|
||
</p>
|
||
<p className="text-gray-600 mb-4">
|
||
You have selected <strong>{selectedCount} items</strong>.
|
||
</p>
|
||
|
||
<div className="bg-blue-50 border-l-4 border-blue-500 p-4 mb-6 text-left">
|
||
<p className="font-semibold mb-2">Options:</p>
|
||
<ul className="list-disc list-inside space-y-1 text-sm">
|
||
<li>Deselect items to publish 5 or fewer</li>
|
||
<li>Use "Schedule Selected" to schedule all items</li>
|
||
</ul>
|
||
<p className="text-xs text-gray-600 mt-3">
|
||
💡 Tip: Scheduling has no limit and uses your site's default publishing schedule.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex gap-3 justify-center">
|
||
<Button variant="outline" onClick={onClose}>Go Back</Button>
|
||
<Button variant="primary" onClick={onScheduleInstead}>
|
||
Schedule Selected
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
);
|
||
};
|
||
```
|
||
|
||
**Button State Management**:
|
||
```typescript
|
||
// In Approved.tsx
|
||
const [showPublishLimitModal, setShowPublishLimitModal] = useState(false);
|
||
|
||
// Update primary action button
|
||
primaryAction={{
|
||
label: selectedIds.length > 5 ? 'Publish (Limit Exceeded)' : 'Publish to Site',
|
||
icon: <BoltIcon className="w-4 h-4" />,
|
||
onClick: handleBulkPublishClick,
|
||
variant: 'success',
|
||
disabled: selectedIds.length === 0,
|
||
tooltip: selectedIds.length > 5
|
||
? 'You can only publish 5 items at once. Use scheduling for more.'
|
||
: undefined
|
||
}}
|
||
```
|
||
|
||
#### Scheduling Has No Limit
|
||
|
||
**Bulk Schedule Action**:
|
||
- No limit on number of items
|
||
- Uses Site Settings default schedule
|
||
- Processes items through scheduling queue
|
||
- Better for large batches (10+ items)
|
||
|
||
**Visual Distinction**:
|
||
```typescript
|
||
// Bulk actions in Approved page
|
||
bulkActions: [
|
||
{
|
||
label: 'Publish Now (Max 5)',
|
||
value: 'bulk_publish_now',
|
||
icon: RocketLaunchIcon,
|
||
variant: 'success',
|
||
disabled: (selectedIds) => selectedIds.length > 5,
|
||
tooltip: (selectedIds) =>
|
||
selectedIds.length > 5
|
||
? 'Can only publish 5 items directly. Use scheduling for more.'
|
||
: 'Publish selected items immediately'
|
||
},
|
||
{
|
||
label: 'Schedule Selected',
|
||
value: 'bulk_schedule',
|
||
icon: CalendarIcon,
|
||
variant: 'primary',
|
||
tooltip: 'Schedule items using site default settings (no limit)'
|
||
}
|
||
]
|
||
```
|
||
|
||
### 3.4 Site Settings Integration for Bulk Scheduling
|
||
|
||
#### Default Publishing Schedule Configuration
|
||
|
||
**Location**: `/sites/{site_id}/settings?tab=publishing`
|
||
|
||
**Configuration Fields** (already exists in site settings):
|
||
- **Auto-publish Schedule**: Time of day to publish (e.g., 9:00 AM)
|
||
- **Publishing Frequency**: Daily, every X hours, specific days of week
|
||
- **Timezone**: Site's timezone for scheduling
|
||
- **Stagger Interval**: Minutes between each publish (e.g., 15 min intervals)
|
||
- **Max Daily Publishes**: Limit publications per day (optional)
|
||
|
||
**Used By**:
|
||
1. Automation system (existing)
|
||
2. Bulk scheduling from Approved page (new)
|
||
3. Content Calendar default times (new)
|
||
|
||
#### Bulk Schedule Implementation
|
||
|
||
**API Endpoint**: `POST /api/v1/writer/content/bulk_schedule/`
|
||
|
||
**Request**:
|
||
```json
|
||
{
|
||
"content_ids": [123, 124, 125, 126],
|
||
"use_site_defaults": true,
|
||
"site_id": 45
|
||
}
|
||
```
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"scheduled_count": 4,
|
||
"schedule_preview": [
|
||
{
|
||
"content_id": 123,
|
||
"scheduled_at": "2025-01-17T09:00:00Z",
|
||
"title": "First Article"
|
||
},
|
||
{
|
||
"content_id": 124,
|
||
"scheduled_at": "2025-01-17T09:15:00Z",
|
||
"title": "Second Article"
|
||
},
|
||
{
|
||
"content_id": 125,
|
||
"scheduled_at": "2025-01-17T09:30:00Z",
|
||
"title": "Third Article"
|
||
},
|
||
{
|
||
"content_id": 126,
|
||
"scheduled_at": "2025-01-17T09:45:00Z",
|
||
"title": "Fourth Article"
|
||
}
|
||
],
|
||
"site_settings": {
|
||
"base_time": "09:00 AM",
|
||
"stagger_interval": 15,
|
||
"timezone": "America/New_York"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Frontend Implementation**:
|
||
```typescript
|
||
const handleBulkScheduleWithDefaults = async (contentIds: number[]) => {
|
||
try {
|
||
// Show preview modal first
|
||
const preview = await fetchAPI('/v1/writer/content/bulk_schedule_preview/', {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
content_ids: contentIds,
|
||
site_id: activeSite.id
|
||
})
|
||
});
|
||
|
||
// Open confirmation modal with schedule preview
|
||
setBulkSchedulePreview(preview);
|
||
setShowBulkScheduleConfirmModal(true);
|
||
} catch (error) {
|
||
toast.error(`Failed to generate schedule preview: ${error.message}`);
|
||
}
|
||
};
|
||
|
||
const confirmBulkSchedule = async () => {
|
||
try {
|
||
const response = await fetchAPI('/v1/writer/content/bulk_schedule/', {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
content_ids: selectedIds.map(id => parseInt(id)),
|
||
use_site_defaults: true,
|
||
site_id: activeSite.id
|
||
})
|
||
});
|
||
|
||
toast.success(`Scheduled ${response.scheduled_count} items`);
|
||
loadContent(); // Refresh
|
||
setShowBulkScheduleConfirmModal(false);
|
||
} catch (error) {
|
||
toast.error(`Failed to schedule: ${error.message}`);
|
||
}
|
||
};
|
||
```
|
||
|
||
**Bulk Schedule Preview Modal**:
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 📅 Schedule 10 Articles │
|
||
├────────────────────────────────────────────────────────────┤
|
||
│ Using site default schedule: │
|
||
│ • Start time: 9:00 AM (America/New_York) │
|
||
│ • Stagger: 15 minutes between each │
|
||
│ • First publish: Tomorrow, Jan 17, 2025 at 9:00 AM │
|
||
│ • Last publish: Tomorrow, Jan 17, 2025 at 11:15 AM │
|
||
│ │
|
||
│ Schedule Preview: │
|
||
│ ┌────────────────────────────────────────────────────────┐ │
|
||
│ │ 1. First Article → Jan 17, 9:00 AM │ │
|
||
│ │ 2. Second Article → Jan 17, 9:15 AM │ │
|
||
│ │ 3. Third Article → Jan 17, 9:30 AM │ │
|
||
│ │ 4. Fourth Article → Jan 17, 9:45 AM │ │
|
||
│ │ 5. Fifth Article → Jan 17, 10:00 AM │ │
|
||
│ │ ... and 5 more │ │
|
||
│ └────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ℹ️ Modify defaults at Site Settings → Publishing tab │
|
||
│ │
|
||
├────────────────────────────────────────────────────────────┤
|
||
│ [Change Settings] [Cancel] [Confirm Schedule] │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Link to Site Settings**:
|
||
```typescript
|
||
const handleChangeSettings = () => {
|
||
// Open site settings in new tab
|
||
window.open(`/sites/${activeSite.id}/settings?tab=publishing`, '_blank');
|
||
// Keep modal open so user can return and confirm
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Workflow Changes: Remove Publish from Review Page
|
||
|
||
### 4.1 Current Problem
|
||
|
||
**Review Page** currently allows direct publishing, which:
|
||
- Bypasses the approval workflow
|
||
- Allows unapproved content to go live
|
||
- Creates confusion about content workflow states
|
||
- Inconsistent with expected review → approve → publish flow
|
||
- Uses platform-specific terminology ("Publish to WordPress") limiting perceived capabilities
|
||
|
||
### 4.2 Proposed Changes
|
||
|
||
**Review Page** (`frontend/src/pages/Writer/Review.tsx`):
|
||
|
||
**Remove**:
|
||
- ❌ `handlePublishSingle()` function
|
||
- ❌ `handlePublishBulk()` function
|
||
- ❌ "Publish to WordPress" action (replace with generic "Publish" in Approved page)
|
||
- ❌ "Publish to Site" bulk action button from Review page
|
||
|
||
**Keep**:
|
||
- ✅ `handleApproveSingle()` - Approve individual items
|
||
- ✅ `handleApproveBulk()` - Approve multiple items
|
||
- ✅ "Approve" action (changes status to 'approved')
|
||
- ✅ View, Edit, Delete actions
|
||
|
||
**New Primary Action**:
|
||
```typescript
|
||
primaryAction={{
|
||
label: 'Approve',
|
||
icon: <CheckCircleIcon className="w-4 h-4" />,
|
||
onClick: () => handleBulkAction('bulk_approve', selectedIds),
|
||
variant: 'success',
|
||
}}
|
||
```
|
||
|
||
**Updated Page Description**:
|
||
```typescript
|
||
// Old: "Shows content with status='review' ready for publishing"
|
||
// New: "Shows content with status='review' ready for approval"
|
||
```
|
||
|
||
**Impact**:
|
||
- Content in Review can only be approved
|
||
- To publish, content must first move to Approved page
|
||
- Clearer workflow: Draft → Review → Approved → Publish
|
||
- No breaking changes to backend
|
||
|
||
### 4.3 Updated Row Actions Config
|
||
|
||
**File**: `frontend/src/config/pages/review.config.tsx`
|
||
|
||
**Before**:
|
||
```typescript
|
||
rowActions: [
|
||
{ label: 'Approve', value: 'approve', icon: CheckCircleIcon },
|
||
{ label: 'Publish to WordPress', value: 'publish_wordpress', icon: RocketLaunchIcon }, // Platform-specific
|
||
{ label: 'View', value: 'view', icon: EyeIcon },
|
||
{ label: 'Edit', value: 'edit', icon: PencilIcon },
|
||
{ label: 'Delete', value: 'delete', icon: TrashBinIcon, danger: true },
|
||
]
|
||
```
|
||
|
||
**After**:
|
||
```typescript
|
||
rowActions: [
|
||
{ label: 'Approve', value: 'approve', icon: CheckCircleIcon },
|
||
{ label: 'View', value: 'view', icon: EyeIcon },
|
||
{ label: 'Edit', value: 'edit', icon: PencilIcon },
|
||
{ label: 'Delete', value: 'delete', icon: TrashBinIcon, danger: true },
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Scheduling UI Implementation
|
||
|
||
### 5.1 Schedule/Reschedule in Approved Page
|
||
|
||
**Component**: Add scheduling button to row actions and bulk actions
|
||
|
||
#### Row Action: Schedule Single Item
|
||
|
||
**Modal**: `ScheduleContentModal` (new)
|
||
**Location**: `frontend/src/components/common/ScheduleContentModal.tsx`
|
||
|
||
**Design**:
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ 📅 Schedule Content Publishing │
|
||
├────────────────────────────────────────┤
|
||
│ Content: "Article Title Here" │
|
||
│ │
|
||
│ Schedule Date: │
|
||
│ [Date Picker: MM/DD/YYYY ▼] │
|
||
│ │
|
||
│ Schedule Time: │
|
||
│ [Time Picker: HH:MM AM/PM ▼] │
|
||
│ │
|
||
│ Preview: January 15, 2025 at 9:00 AM │
|
||
│ │
|
||
├────────────────────────────────────────┤
|
||
│ [Cancel] [Schedule] │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
**Implementation**:
|
||
```typescript
|
||
interface ScheduleContentModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
content: Content;
|
||
onSchedule: (contentId: number, scheduledDate: string) => Promise<void>;
|
||
}
|
||
|
||
const ScheduleContentModal = ({ isOpen, onClose, content, onSchedule }) => {
|
||
const [selectedDate, setSelectedDate] = useState<Date>(getDefaultScheduleDate());
|
||
const [selectedTime, setSelectedTime] = useState<string>('09:00 AM');
|
||
|
||
const getDefaultScheduleDate = () => {
|
||
const tomorrow = new Date();
|
||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||
return tomorrow;
|
||
};
|
||
|
||
const handleSchedule = async () => {
|
||
// Combine date and time
|
||
const scheduledDateTime = combineDateAndTime(selectedDate, selectedTime);
|
||
|
||
try {
|
||
await onSchedule(content.id, scheduledDateTime.toISOString());
|
||
toast.success(`Scheduled for ${formatScheduledTime(scheduledDateTime)}`);
|
||
onClose();
|
||
} catch (error) {
|
||
toast.error(`Failed to schedule: ${error.message}`);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Modal isOpen={isOpen} onClose={onClose}>
|
||
{/* Date/Time pickers */}
|
||
<Button onClick={handleSchedule}>Schedule</Button>
|
||
</Modal>
|
||
);
|
||
};
|
||
```
|
||
|
||
**API Call**:
|
||
```typescript
|
||
const handleScheduleContent = async (contentId: number, scheduledDate: string) => {
|
||
const response = await fetchAPI(`/v1/writer/content/${contentId}/schedule/`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ scheduled_publish_at: scheduledDate }),
|
||
});
|
||
|
||
// Update local state
|
||
setContent(prevContent =>
|
||
prevContent.map(c => c.id === contentId ? {
|
||
...c,
|
||
site_status: 'scheduled',
|
||
scheduled_publish_at: scheduledDate,
|
||
} : c)
|
||
);
|
||
};
|
||
```
|
||
|
||
#### Row Action: Reschedule Item
|
||
|
||
**Trigger**: Show "Reschedule" action when:
|
||
- `site_status === 'scheduled'` (scheduled but not yet published)
|
||
- `site_status === 'failed'` (scheduled publishing failed)
|
||
|
||
**Modal**: Reuse `ScheduleContentModal` but:
|
||
- Title: "Reschedule Content Publishing"
|
||
- Pre-fill with existing `scheduled_publish_at` date/time
|
||
- API: `POST /v1/writer/content/{id}/reschedule/`
|
||
|
||
**Implementation**:
|
||
```typescript
|
||
const handleRescheduleContent = async (contentId: number, scheduledDate: string) => {
|
||
const response = await fetchAPI(`/v1/writer/content/${contentId}/reschedule/`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ scheduled_at: scheduledDate }),
|
||
});
|
||
|
||
// Update local state
|
||
setContent(prevContent =>
|
||
prevContent.map(c => c.id === contentId ? {
|
||
...c,
|
||
site_status: 'scheduled',
|
||
scheduled_publish_at: scheduledDate,
|
||
} : c)
|
||
);
|
||
|
||
toast.success('Rescheduled successfully');
|
||
};
|
||
```
|
||
|
||
#### Row Action: Unschedule Item
|
||
|
||
**Trigger**: Show when `site_status === 'scheduled'`
|
||
|
||
**Confirmation Modal**:
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ ⚠️ Unschedule Content? │
|
||
├────────────────────────────────────────┤
|
||
│ Are you sure you want to unschedule │
|
||
│ "Article Title Here"? │
|
||
│ │
|
||
│ Current schedule: Jan 15, 2025 9:00 AM │
|
||
│ │
|
||
├────────────────────────────────────────┤
|
||
│ [Cancel] [Unschedule] │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
**API Call**:
|
||
```typescript
|
||
const handleUnscheduleContent = async (contentId: number) => {
|
||
await fetchAPI(`/v1/writer/content/${contentId}/unschedule/`, {
|
||
method: 'POST',
|
||
});
|
||
|
||
setContent(prevContent =>
|
||
prevContent.map(c => c.id === contentId ? {
|
||
...c,
|
||
site_status: 'not_published',
|
||
scheduled_publish_at: null,
|
||
} : c)
|
||
);
|
||
|
||
toast.success('Unscheduled successfully');
|
||
};
|
||
```
|
||
|
||
#### Updated Row Actions for Approved Page
|
||
|
||
**File**: `frontend/src/config/pages/approved.config.tsx`
|
||
|
||
**Dynamic row actions based on content state** (platform-agnostic):
|
||
```typescript
|
||
const getRowActions = (content: Content, siteName: string) => {
|
||
const actions = [];
|
||
|
||
// Always available
|
||
actions.push({ label: 'View', value: 'view', icon: EyeIcon });
|
||
actions.push({ label: 'Edit', value: 'edit', icon: PencilIcon });
|
||
|
||
// Publishing actions based on site_status (platform-agnostic)
|
||
if (content.site_status === 'not_published') {
|
||
actions.push({ label: 'Publish Now', value: 'publish_site', icon: RocketLaunchIcon });
|
||
actions.push({ label: 'Schedule', value: 'schedule', icon: CalendarIcon });
|
||
} else if (content.site_status === 'scheduled') {
|
||
actions.push({ label: 'Reschedule', value: 'reschedule', icon: CalendarIcon });
|
||
actions.push({ label: 'Unschedule', value: 'unschedule', icon: XIcon });
|
||
actions.push({ label: 'Publish Now', value: 'publish_site', icon: RocketLaunchIcon });
|
||
} else if (content.site_status === 'failed') {
|
||
actions.push({ label: 'Publish Now', value: 'publish_site', icon: RocketLaunchIcon });
|
||
actions.push({ label: 'Reschedule', value: 'reschedule', icon: CalendarIcon });
|
||
} else if (content.site_status === 'published' && content.external_url) {
|
||
// Use actual site name for dynamic label
|
||
actions.push({
|
||
label: `View on ${siteName}`,
|
||
value: 'view_on_site',
|
||
icon: ExternalLinkIcon
|
||
});
|
||
}
|
||
|
||
actions.push({ label: 'Delete', value: 'delete', icon: TrashBinIcon, danger: true });
|
||
|
||
return actions;
|
||
};
|
||
```
|
||
|
||
#### Bulk Actions for Scheduling
|
||
|
||
**New bulk action**: "Schedule Selected"
|
||
|
||
**Modal**: `BulkScheduleModal` (new)
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ 📅 Schedule 5 Articles │
|
||
├────────────────────────────────────────┤
|
||
│ Schedule all selected articles for: │
|
||
│ │
|
||
│ Date: [MM/DD/YYYY ▼] │
|
||
│ Time: [HH:MM AM/PM ▼] │
|
||
│ │
|
||
│ Preview: January 15, 2025 at 9:00 AM │
|
||
│ │
|
||
│ ⚠️ All 5 articles will be scheduled │
|
||
│ for the same date and time. │
|
||
│ │
|
||
├────────────────────────────────────────┤
|
||
│ [Cancel] [Schedule All] │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
**Implementation**:
|
||
```typescript
|
||
const handleBulkSchedule = async (contentIds: number[], scheduledDate: string) => {
|
||
let successCount = 0;
|
||
let failedCount = 0;
|
||
|
||
for (const contentId of contentIds) {
|
||
try {
|
||
await fetchAPI(`/v1/writer/content/${contentId}/schedule/`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ scheduled_publish_at: scheduledDate }),
|
||
});
|
||
successCount++;
|
||
} catch (error) {
|
||
console.error(`Failed to schedule content ${contentId}:`, error);
|
||
failedCount++;
|
||
}
|
||
}
|
||
|
||
if (successCount > 0) {
|
||
toast.success(`Scheduled ${successCount} item(s)`);
|
||
}
|
||
if (failedCount > 0) {
|
||
toast.warning(`${failedCount} item(s) failed to schedule`);
|
||
}
|
||
|
||
loadContent(); // Refresh
|
||
};
|
||
```
|
||
|
||
### 5.2 Scheduling in Content Calendar Page
|
||
|
||
**Current State**: Scheduling via drag-and-drop works well
|
||
|
||
**Enhancement**: Add "Edit Schedule" button to scheduled items
|
||
|
||
**Design**:
|
||
- Each scheduled item in calendar has an "Edit" button
|
||
- Opens `ScheduleContentModal` with existing date/time pre-filled
|
||
- User can change date/time
|
||
- Calls reschedule API
|
||
|
||
**Implementation** (add to calendar item render):
|
||
```typescript
|
||
{item.site_status === 'scheduled' && (
|
||
<IconButton
|
||
icon={<PencilIcon className="w-4 h-4" />}
|
||
onClick={() => openRescheduleModal(item)}
|
||
title="Edit schedule"
|
||
size="sm"
|
||
/>
|
||
)}
|
||
```
|
||
|
||
**Failed Items Handling**:
|
||
- Show failed items in a separate section (not on calendar)
|
||
- Red error badge
|
||
- Two buttons: "Reschedule" and "Publish Now"
|
||
- Clicking "Reschedule" opens `ScheduleContentModal`
|
||
|
||
**Design**:
|
||
```
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ ❌ Failed Scheduled Publications (2) │
|
||
├──────────────────────────────────────────────────────────┤
|
||
│ ⚠️ Article Title 1 │
|
||
│ Site: My WordPress Blog │
|
||
│ Scheduled: Jan 13, 2025 9:00 AM │
|
||
│ Error: Publishing API error: Invalid credentials │
|
||
│ [Reschedule] [Publish Now] │
|
||
│ │
|
||
│ ⚠️ Article Title 2 │
|
||
│ Site: My Shopify Store │
|
||
│ Scheduled: Jan 13, 2025 10:00 AM │
|
||
│ Error: Network timeout │
|
||
│ [Reschedule] [Publish Now] │
|
||
└──────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 5.3 Scheduling in ContentView Template
|
||
|
||
**File**: `frontend/src/templates/ContentView.tsx` (if exists)
|
||
|
||
**Note**: File not found during analysis - may not exist or may be different path
|
||
|
||
**If this is the content detail view**:
|
||
- Add "Schedule" button in header actions (if `site_status === 'not_published'`)
|
||
- Add "Reschedule" button (if `site_status === 'scheduled'` or `site_status === 'failed'`)
|
||
- Add "Publish Now" button (if approved)
|
||
- Show current schedule prominently if scheduled
|
||
|
||
**Alternate**: If ContentView doesn't exist, check if content detail page is at:
|
||
- `frontend/src/pages/Writer/ContentDetail.tsx`
|
||
- Or individual content pages use a different template
|
||
|
||
---
|
||
|
||
## 6. Failed Content Handling
|
||
|
||
### 6.1 Identifying Failed Content
|
||
|
||
**Query**: Content where `site_status = 'failed'`
|
||
|
||
**Display Locations**:
|
||
1. **Approved Page**: Filter by `site_status = 'failed'`
|
||
2. **Content Calendar**: Separate "Failed" section
|
||
3. **Dashboard Widget**: "Failed Publications" count
|
||
|
||
### 6.2 Failed Item UI Treatment
|
||
|
||
**Visual Indicators**:
|
||
- ❌ Red error badge: "Failed"
|
||
- 🕐 Show original scheduled time
|
||
- 📄 Show error message (truncated)
|
||
- 🔄 Retry options: "Publish Now" or "Reschedule"
|
||
|
||
**Row Actions for Failed Items**:
|
||
```typescript
|
||
{
|
||
label: 'Publish Now',
|
||
value: 'publish_site',
|
||
icon: RocketLaunchIcon,
|
||
}
|
||
{
|
||
label: 'Reschedule',
|
||
value: 'reschedule',
|
||
icon: CalendarIcon,
|
||
}
|
||
{
|
||
label: 'View Error Details',
|
||
value: 'view_error',
|
||
icon: ErrorIcon,
|
||
}
|
||
```
|
||
|
||
**Error Details Modal**:
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ ❌ Publishing Error Details │
|
||
├────────────────────────────────────────────────────────────┤
|
||
│ Content: "Article Title Here" │
|
||
│ Site: My WordPress Blog (WordPress) │
|
||
│ Scheduled: January 13, 2025 at 9:00 AM │
|
||
│ Failed: January 13, 2025 at 9:05 AM │
|
||
│ │
|
||
│ Error Message: │
|
||
│ ┌────────────────────────────────────────────────────────┐ │
|
||
│ │ Publishing API error: Invalid credentials │ │
|
||
│ │ │ │
|
||
│ │ The publishing site returned a 403 Forbidden error. │ │
|
||
│ │ Please check the API key in Site Settings. │ │
|
||
│ └────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ Actions: │
|
||
│ [Fix Site Settings] [Publish Now] [Reschedule] [Close] │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 6.3 Retry Logic
|
||
|
||
**Publish Now** (from failed state):
|
||
- Opens `PublishingProgressModal`
|
||
- Same flow as normal publish
|
||
- On success: `site_status` → `published`, clear error
|
||
- On failure: Update error message, keep as `failed`
|
||
|
||
**Reschedule** (from failed state):
|
||
- Opens `ScheduleContentModal`
|
||
- Pre-fill with original scheduled time (or next day)
|
||
- On save: `site_status` → `scheduled`, clear error
|
||
- Celery task will retry at new scheduled time
|
||
|
||
---
|
||
|
||
## 7. Implementation Phases
|
||
|
||
### Phase 1: Publishing Progress Modals (Week 1-2)
|
||
|
||
**Tasks**:
|
||
1. Create `PublishingProgressModal.tsx` component (platform-agnostic)
|
||
2. Create `BulkPublishingModal.tsx` component (platform-agnostic)
|
||
3. Create `PublishLimitModal.tsx` component (new - limit validation)
|
||
4. Update `Approved.tsx`:
|
||
- Integrate single publish modal
|
||
- Integrate bulk publish modal (max 5 items)
|
||
- Add limit validation: show `PublishLimitModal` if > 5 selected
|
||
- Rename `handleBulkPublishWordPress` to `handleBulkPublishToSite`
|
||
- Update action names: `publish_wordpress` → `publish_site`
|
||
- Display actual site name in UI
|
||
- Add "Schedule Selected" bulk action (no limit)
|
||
5. Test with WordPress, Shopify, and custom sites
|
||
6. Handle platform-specific error states and retry logic
|
||
7. Test limit validation (6+ items selected)
|
||
|
||
**Deliverables**:
|
||
- ✅ Single publish with progress feedback (all platforms)
|
||
- ✅ Bulk publish with queue progress (max 5 items)
|
||
- ✅ Publishing limit validation modal
|
||
- ✅ Error handling with retry
|
||
- ✅ Success state with "View on [Site Name]" link
|
||
- ✅ Platform-agnostic terminology throughout
|
||
|
||
### Phase 2: Remove Publish from Review (Week 2)
|
||
|
||
**Tasks**:
|
||
1. Update `Review.tsx`:
|
||
- Remove `handlePublishSingle()` function
|
||
- Remove `handlePublishBulk()` function
|
||
- Remove publish actions from row actions
|
||
- Update primary action to "Approve"
|
||
2. Update `review.config.tsx`:
|
||
- Remove publish actions from rowActions array
|
||
3. Test workflow: Review → Approve → Approved → Publish
|
||
|
||
**Deliverables**:
|
||
- ✅ Review page only approves content
|
||
- ✅ No publishing from Review page
|
||
- ✅ Clearer workflow separation
|
||
|
||
### Phase 3: Scheduling UI in Approved Page (Week 3)
|
||
|
||
**Tasks**:
|
||
1. Create `ScheduleContentModal.tsx` component
|
||
2. Create `BulkScheduleModal.tsx` component (manual date/time)
|
||
3. Create `BulkSchedulePreviewModal.tsx` component (site defaults preview)
|
||
4. Create backend endpoint: `POST /v1/writer/content/bulk_schedule/`
|
||
5. Create backend endpoint: `POST /v1/writer/content/bulk_schedule_preview/`
|
||
6. Update `Approved.tsx`:
|
||
- Add "Schedule" row action
|
||
- Add "Reschedule" row action (conditional)
|
||
- Add "Unschedule" row action (conditional)
|
||
- Add "Schedule Selected" bulk action (uses site defaults)
|
||
- Dynamic row actions based on `site_status`
|
||
- Link to site settings from schedule preview modal
|
||
7. Test scheduling, rescheduling, unscheduling
|
||
8. Test bulk scheduling with site defaults (10+ items)
|
||
9. Verify site settings integration
|
||
|
||
**Deliverables**:
|
||
- ✅ Schedule content for future publishing
|
||
- ✅ Reschedule existing scheduled content
|
||
- ✅ Unschedule content
|
||
- ✅ Bulk scheduling with site default settings
|
||
- ✅ Schedule preview before confirmation
|
||
- ✅ Link to Site Settings → Publishing tab
|
||
- ✅ No limit on scheduled items
|
||
|
||
### Phase 4: Failed Content Handling (Week 3-4)
|
||
|
||
**Tasks**:
|
||
1. Add failed content filter to Approved page
|
||
2. Create failed items section in Content Calendar
|
||
3. Create `ErrorDetailsModal.tsx` for viewing full errors
|
||
4. Add retry logic for failed items
|
||
5. Update row actions for failed items
|
||
|
||
**Deliverables**:
|
||
- ✅ Visual indication of failed content
|
||
- ✅ Error details modal
|
||
- ✅ Retry/Reschedule options
|
||
- ✅ Clear error messages
|
||
|
||
### Phase 5: Content Calendar Enhancements (Week 4)
|
||
|
||
**Tasks**:
|
||
1. Add "Edit Schedule" button to calendar items
|
||
2. Add failed items section to calendar
|
||
3. Improve calendar item tooltips
|
||
4. Test drag-drop with new scheduling modals
|
||
|
||
**Deliverables**:
|
||
- ✅ Edit scheduled items from calendar
|
||
- ✅ Failed items displayed prominently
|
||
- ✅ Improved UX for scheduling
|
||
|
||
### Phase 6: Testing & Documentation (Week 5)
|
||
|
||
**Tasks**:
|
||
1. E2E testing of all workflows
|
||
2. Test error scenarios
|
||
3. Update user documentation
|
||
4. Create user guide for new scheduling features
|
||
5. Performance testing for bulk operations
|
||
|
||
**Deliverables**:
|
||
- ✅ All features tested and working
|
||
- ✅ Documentation updated
|
||
- ✅ User guide created
|
||
- ✅ Performance benchmarks
|
||
|
||
---
|
||
|
||
## 8. Technical Considerations
|
||
|
||
### 8.1 State Management
|
||
|
||
**Local State** (per page):
|
||
- Modal open/close states
|
||
- Queue items for bulk operations
|
||
- Selected date/time for scheduling
|
||
|
||
**Global State** (if needed):
|
||
- Active publishing operations (prevent duplicate publishes)
|
||
- Recent errors (for error dashboard)
|
||
|
||
**Recommendation**: Use local component state with React hooks. No need for Redux/Zustand unless state sharing becomes complex.
|
||
|
||
### 8.2 Performance
|
||
|
||
**Bulk Operations**:
|
||
- Sequential processing (not parallel) to avoid API rate limits
|
||
- Show progress per item
|
||
- Allow cancellation (future enhancement)
|
||
|
||
**Calendar View**:
|
||
- Lazy load calendar months
|
||
- Limit displayed items per date (show "X more..." if > 3)
|
||
- Virtual scrolling for large lists
|
||
|
||
**Polling** (if needed for async publishing):
|
||
- Currently publishing is synchronous, so no polling needed
|
||
- If made async in future, poll task status like ImageQueueModal does
|
||
|
||
### 8.3 Error Handling
|
||
|
||
**Network Errors**:
|
||
- Catch and display user-friendly messages
|
||
- Log full error to console for debugging
|
||
- Provide retry button
|
||
|
||
**Platform API Errors**:
|
||
- Parse platform-specific error messages (WordPress, Shopify, Custom API)
|
||
- Highlight common issues (invalid credentials, missing fields, rate limits)
|
||
- Link to Site Settings for credential fixes
|
||
- Show platform type in error messages for clarity
|
||
|
||
**Validation Errors**:
|
||
- Validate schedule date/time (must be in future)
|
||
- Validate content is approved before publishing
|
||
- Check site configuration exists (API key, domain)
|
||
- Verify platform-specific requirements are met
|
||
|
||
### 8.4 Accessibility
|
||
|
||
**Modals**:
|
||
- Keyboard navigation (Tab, Esc)
|
||
- ARIA labels for screen readers
|
||
- Focus trap when modal is open
|
||
- Announce status changes
|
||
|
||
**Progress Bars**:
|
||
- ARIA role="progressbar"
|
||
- aria-valuenow, aria-valuemin, aria-valuemax
|
||
- Announce percentage changes
|
||
|
||
**Color Indicators**:
|
||
- Don't rely only on color (use icons too)
|
||
- High contrast for dark mode
|
||
- Color-blind friendly palette
|
||
|
||
---
|
||
|
||
## 9. UI/UX Best Practices
|
||
|
||
### 9.1 Loading States
|
||
|
||
- Show skeleton loaders for content lists
|
||
- Disable buttons during API calls
|
||
- Show spinner in button: "Publishing..." (with spinner icon)
|
||
- Progress bars for multi-step operations
|
||
|
||
### 9.2 Success Feedback
|
||
|
||
- Green toast notification: "Published successfully"
|
||
- Green checkmark icon in modal
|
||
- Auto-refresh content list on success
|
||
- Optional: Confetti animation for first publish
|
||
|
||
### 9.3 Error Feedback
|
||
|
||
- Red toast notification: "Failed to publish: [error]"
|
||
- Red alert box in modal with full error
|
||
- "Copy error details" button for debugging
|
||
- "Contact support" link if needed
|
||
|
||
### 9.4 Empty States
|
||
|
||
- No scheduled content: Show calendar with "Drag approved content here"
|
||
- No failed content: "No failed publications" with green checkmark
|
||
- No approved content: "Approve content from Review page"
|
||
|
||
### 9.5 Tooltips and Help Text
|
||
|
||
- Calendar dates: "Drag content here to schedule"
|
||
- Schedule button: "Schedule for future publishing to site"
|
||
- Publish button: "Publish immediately to [Site Name]"
|
||
- Failed badge: "Publishing failed - click for details"
|
||
- Site name shown in all tooltips where relevant
|
||
|
||
---
|
||
|
||
## 10. Future Enhancements
|
||
|
||
### 10.1 Advanced Scheduling
|
||
|
||
- **Recurring schedules**: Publish every Monday at 9 AM
|
||
- **Bulk schedule spread**: Schedule 10 items over next 2 weeks (evenly distributed)
|
||
- **Optimal timing**: AI suggests best time to publish based on traffic data
|
||
- **Timezone support**: Schedule in site's timezone (not user's)
|
||
|
||
### 10.2 Publishing Queue Management
|
||
|
||
- **Pause/Resume queue**: Pause all scheduled publications
|
||
- **Reorder queue**: Drag to reorder in list view
|
||
- **Priority flag**: Mark content as high priority (publish first)
|
||
- **Batch limits**: Limit to X publications per day
|
||
|
||
### 10.3 Multi-Platform Publishing
|
||
|
||
- **Social media**: Publish to Facebook, Twitter, LinkedIn
|
||
- **Multiple sites**: Publish to multiple sites simultaneously (WordPress, Shopify, Custom)
|
||
- **Cross-post**: Publish same content to blog + Medium + Dev.to
|
||
- **Mixed platforms**: Schedule content to WordPress site and Shopify store at different times
|
||
|
||
### 10.4 Advanced Error Handling
|
||
|
||
- **Auto-retry**: Retry failed publications X times before marking as failed
|
||
- **Error patterns**: Detect common errors and suggest fixes
|
||
- **Health checks**: Pre-flight check before scheduling (test credentials)
|
||
|
||
### 10.5 Analytics Integration
|
||
|
||
- **Publishing stats**: Track success/failure rates
|
||
- **Performance metrics**: Average time to publish
|
||
- **Engagement tracking**: Track views/clicks on published content
|
||
- **Calendar heatmap**: Visual representation of publishing activity
|
||
|
||
---
|
||
|
||
## 11. Testing Checklist
|
||
|
||
### 11.1 Single Publishing
|
||
|
||
- [ ] Publish single approved content
|
||
- [ ] Progress modal shows correct stages
|
||
- [ ] Success state displays site URL (works for WordPress, Shopify, Custom)
|
||
- [ ] Modal shows actual site name, not platform type
|
||
- [ ] Error state displays error message
|
||
- [ ] Retry button works on failure
|
||
- [ ] Close button disabled during publish
|
||
- [ ] Content list refreshes on success
|
||
- [ ] Test with WordPress site
|
||
- [ ] Test with Shopify site
|
||
- [ ] Test with Custom site
|
||
|
||
### 11.2 Bulk Publishing
|
||
|
||
- [ ] Publish multiple approved content items (up to 5)
|
||
- [ ] Limit validation: 6+ items shows PublishLimitModal
|
||
- [ ] PublishLimitModal offers "Schedule Selected" option
|
||
- [ ] Single record publish (3-dot menu) has no limit
|
||
- [ ] Queue displays all items
|
||
- [ ] Sequential processing works
|
||
- [ ] Individual progress per item
|
||
- [ ] Mixed success/failure handling
|
||
- [ ] Retry individual failed items
|
||
- [ ] Close button enabled after completion
|
||
- [ ] Button tooltip shows limit info when > 5 selected
|
||
|
||
### 11.3 Scheduling
|
||
|
||
**Manual Scheduling:**
|
||
- [ ] Schedule single content (manual date/time)
|
||
- [ ] Schedule bulk content (manual date/time)
|
||
- [ ] Date/time picker validation
|
||
- [ ] Schedule in past shows error
|
||
- [ ] Scheduled items appear in calendar
|
||
- [ ] Drag-and-drop scheduling works
|
||
- [ ] Reschedule changes date/time
|
||
- [ ] Unschedule removes from calendar
|
||
|
||
**Bulk Scheduling with Site Defaults:**
|
||
- [ ] Select 10+ items for scheduling (no limit)
|
||
- [ ] "Schedule Selected" button opens preview modal
|
||
- [ ] Preview shows schedule with stagger intervals
|
||
- [ ] Preview displays site settings (time, stagger, timezone)
|
||
- [ ] "Change Settings" link opens site settings in new tab
|
||
- [ ] Confirm schedule applies to all selected items
|
||
- [ ] Items appear in calendar with scheduled times
|
||
- [ ] Site settings at `/sites/{id}/settings?tab=publishing` has scheduling config
|
||
- [ ] Automation uses same scheduling config
|
||
- [ ] Bulk scheduled items respect site's daily limits (if configured)
|
||
|
||
### 11.4 Failed Content
|
||
|
||
- [ ] Failed items display with error badge
|
||
- [ ] Error details modal shows full error
|
||
- [ ] Retry from failed state works
|
||
- [ ] Reschedule from failed state works
|
||
- [ ] Failed section in calendar shows items
|
||
- [ ] Filter failed items in Approved page
|
||
|
||
### 11.5 Review Page Changes
|
||
|
||
- [ ] Publish actions removed
|
||
- [ ] Approve action works
|
||
- [ ] Bulk approve works
|
||
- [ ] No direct publishing possible
|
||
- [ ] Approved content moves to Approved page
|
||
|
||
### 11.6 Edge Cases
|
||
|
||
- [ ] No site credentials: Error message
|
||
- [ ] Missing API key: Error message
|
||
- [ ] Unsupported platform type: Error message
|
||
- [ ] Network timeout: Error message
|
||
- [ ] Invalid content (missing required fields): Error
|
||
- [ ] Publishing already published content: Handled
|
||
- [ ] Scheduling already scheduled content: Reschedule
|
||
- [ ] Deleting scheduled content: Removes from calendar
|
||
- [ ] Site change: Refresh content
|
||
- [ ] Platform-specific validation (WordPress categories, Shopify collections, etc.)
|
||
|
||
**Publishing Limits:**
|
||
- [ ] Selecting exactly 5 items: Direct publish allowed
|
||
- [ ] Selecting 6 items: PublishLimitModal shown
|
||
- [ ] Selecting 10+ items: PublishLimitModal shown, suggests scheduling
|
||
- [ ] From limit modal: "Schedule Selected" opens bulk schedule preview
|
||
- [ ] From limit modal: "Go Back" closes modal, keeps selection
|
||
- [ ] Single publish (3-dot): Always allowed, no limit modal
|
||
- [ ] Bulk button disabled when no items selected
|
||
- [ ] Bulk button shows tooltip when > 5 items selected
|
||
|
||
**Bulk Scheduling:**
|
||
- [ ] No site scheduling config: Uses defaults (9 AM, 15 min stagger)
|
||
- [ ] Site has custom schedule: Preview reflects custom settings
|
||
- [ ] Scheduling 50+ items: No error, all scheduled
|
||
- [ ] Site settings link opens correct tab
|
||
- [ ] Return from site settings: Preview refreshes with new settings
|
||
|
||
---
|
||
|
||
## 12. Documentation Updates Required
|
||
|
||
### 12.1 User Documentation
|
||
|
||
**File**: `docs/40-WORKFLOWS/CONTENT-PUBLISHING.md` (new or update existing)
|
||
|
||
**Sections**:
|
||
1. Publishing Workflow Overview
|
||
2. Publishing from Approved Page
|
||
3. Scheduling Content
|
||
4. Managing Scheduled Content
|
||
5. Handling Failed Publications
|
||
6. Bulk Operations
|
||
7. Troubleshooting
|
||
|
||
### 12.2 Developer Documentation
|
||
|
||
**File**: `docs/30-FRONTEND/PUBLISHING-MODALS.md` (new)
|
||
|
||
**Sections**:
|
||
1. Modal Architecture
|
||
2. Component API Reference
|
||
3. State Management
|
||
4. API Integration
|
||
5. Progress Animation Logic
|
||
6. Error Handling Patterns
|
||
7. Testing Guidelines
|
||
|
||
### 12.3 API Documentation
|
||
|
||
**File**: `docs/20-API/PUBLISHER.md` (update existing)
|
||
|
||
**Add**:
|
||
- Schedule endpoint: `POST /v1/writer/content/{id}/schedule/`
|
||
- Reschedule endpoint: `POST /v1/writer/content/{id}/reschedule/`
|
||
- Unschedule endpoint: `POST /v1/writer/content/{id}/unschedule/`
|
||
- Examples with request/response
|
||
|
||
---
|
||
|
||
## 13. Conclusion
|
||
|
||
This comprehensive plan provides:
|
||
|
||
1. ✅ **Publishing Progress Modals** - Real-time feedback for single and bulk operations
|
||
2. ✅ **Publishing Limits & Validation** - Max 5 direct publish, unlimited scheduling
|
||
3. ✅ **Workflow Optimization** - Clear separation of Review (approve only) and Approved (publish)
|
||
4. ✅ **Scheduling UI** - Full scheduling/rescheduling capabilities across multiple pages
|
||
5. ✅ **Site Settings Integration** - Bulk scheduling uses default configuration
|
||
6. ✅ **Failed Content Handling** - Clear error display with retry/reschedule options
|
||
7. ✅ **Pattern Consistency** - Follows existing modal patterns from ImageQueueModal
|
||
8. ✅ **User Experience** - Intuitive, informative, and forgiving interface
|
||
9. ✅ **Platform Agnostic** - Supports WordPress, Shopify, Custom sites with unified terminology
|
||
10. ✅ **Scalable Architecture** - Easy to add new publishing platforms in the future
|
||
|
||
### Key Success Metrics
|
||
|
||
- **User Satisfaction**: Fewer support tickets about "where did my content go?"
|
||
- **Publishing Success Rate**: Increased from tracking and retry capabilities
|
||
- **Workflow Clarity**: Users understand: Review → Approve → Publish
|
||
- **Error Recovery**: Failed publications can be easily rescheduled
|
||
- **Time Savings**: Bulk operations with progress tracking reduce manual work
|
||
|
||
### Next Steps
|
||
|
||
1. Review this plan with stakeholders
|
||
2. Prioritize phases based on business needs
|
||
3. Begin Phase 1 implementation (Publishing Progress Modals)
|
||
4. Conduct user testing after Phase 3
|
||
5. Iterate based on feedback
|
||
|
||
---
|
||
|
||
**Document Version**: 1.0
|
||
**Last Updated**: January 2025
|
||
**Status**: Ready for Implementation
|