Files
igny8/docs/plans/PUBLISHING-PROGRESS-AND-SCHEDULING-UX-PLAN.md
2026-01-16 13:28:24 +00:00

1596 lines
59 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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