59 KiB
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:
- Publishing Progress Modals - Real-time feedback for single and bulk publishing operations
- Publishing Limits & Validation - Direct bulk publish limited to 5 items, unlimited scheduling
- Workflow Optimization - Remove publish from Review page, make it Approved-only
- Scheduling UI Enhancement - Add scheduling/rescheduling interfaces across multiple pages
- Site Settings Integration - Bulk scheduling uses default settings from Site Settings → Publishing tab
- 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:
PublisherServiceatbackend/igny8_core/business/publishing/services/publisher_service.py - API Endpoint:
POST /api/v1/publisher/publish - Credentials: Stored on
Sitemodel (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/failedscheduled_publish_at: ISO8601 datetimesite_status_updated_at: Timestamp of last status change
- API Endpoints:
POST /api/v1/writer/content/{id}/schedule/- Schedule for future publishingPOST /api/v1/writer/content/{id}/unschedule/- Cancel scheduled publishingPOST /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()→ callsPOST /v1/publisher/publish/ - Bulk publish:
handlePublishBulk()→ loops through items, publishes individually - Approve (changes status to 'approved')
- Single publish:
- 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'orstatus='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)
- Single publish:
- 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:
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
smoothProgressstate - Progress intervals cleared on completion/failure
- Backend provides:
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:
- Preparing content (0-25%): Validating content structure
- Uploading to site (25-50%): POST request to publishing platform API
- Processing response (50-75%): Handling platform API response
- Finalizing (75-100%): Updating content record
State Interface:
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_urlin 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:
{
"content_id": 123,
"destinations": ["wordpress"] // Can be: "wordpress", "shopify", "custom"
}
Response (success):
{
"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):
{
"success": false,
"error": "Publishing API error: Invalid credentials"
}
Since publishing is synchronous, the modal flow:
- Open modal immediately on "Publish" click
- Show "Preparing" stage with progress 0-25%
- Make API call (update to "Uploading" at 25%)
- On response:
- Success: Animate 25% → 100% with status updates
- Failure: Show error state
- 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:
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
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:
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)
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:
// 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:
// 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:
- Automation system (existing)
- Bulk scheduling from Approved page (new)
- Content Calendar default times (new)
Bulk Schedule Implementation
API Endpoint: POST /api/v1/writer/content/bulk_schedule/
Request:
{
"content_ids": [123, 124, 125, 126],
"use_site_defaults": true,
"site_id": 45
}
Response:
{
"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:
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:
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:
primaryAction={{
label: 'Approve',
icon: <CheckCircleIcon className="w-4 h-4" />,
onClick: () => handleBulkAction('bulk_approve', selectedIds),
variant: 'success',
}}
Updated Page Description:
// 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:
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:
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:
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:
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_atdate/time - API:
POST /v1/writer/content/{id}/reschedule/
Implementation:
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:
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):
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:
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
ScheduleContentModalwith existing date/time pre-filled - User can change date/time
- Calls reschedule API
Implementation (add to calendar item render):
{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'orsite_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:
- Approved Page: Filter by
site_status = 'failed' - Content Calendar: Separate "Failed" section
- 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:
{
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:
- Create
PublishingProgressModal.tsxcomponent (platform-agnostic) - Create
BulkPublishingModal.tsxcomponent (platform-agnostic) - Create
PublishLimitModal.tsxcomponent (new - limit validation) - Update
Approved.tsx:- Integrate single publish modal
- Integrate bulk publish modal (max 5 items)
- Add limit validation: show
PublishLimitModalif > 5 selected - Rename
handleBulkPublishWordPresstohandleBulkPublishToSite - Update action names:
publish_wordpress→publish_site - Display actual site name in UI
- Add "Schedule Selected" bulk action (no limit)
- Test with WordPress, Shopify, and custom sites
- Handle platform-specific error states and retry logic
- 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:
- Update
Review.tsx:- Remove
handlePublishSingle()function - Remove
handlePublishBulk()function - Remove publish actions from row actions
- Update primary action to "Approve"
- Remove
- Update
review.config.tsx:- Remove publish actions from rowActions array
- 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:
- Create
ScheduleContentModal.tsxcomponent - Create
BulkScheduleModal.tsxcomponent (manual date/time) - Create
BulkSchedulePreviewModal.tsxcomponent (site defaults preview) - Create backend endpoint:
POST /v1/writer/content/bulk_schedule/ - Create backend endpoint:
POST /v1/writer/content/bulk_schedule_preview/ - 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
- Test scheduling, rescheduling, unscheduling
- Test bulk scheduling with site defaults (10+ items)
- 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:
- Add failed content filter to Approved page
- Create failed items section in Content Calendar
- Create
ErrorDetailsModal.tsxfor viewing full errors - Add retry logic for failed items
- 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:
- Add "Edit Schedule" button to calendar items
- Add failed items section to calendar
- Improve calendar item tooltips
- 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:
- E2E testing of all workflows
- Test error scenarios
- Update user documentation
- Create user guide for new scheduling features
- 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=publishinghas 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:
- Publishing Workflow Overview
- Publishing from Approved Page
- Scheduling Content
- Managing Scheduled Content
- Handling Failed Publications
- Bulk Operations
- Troubleshooting
12.2 Developer Documentation
File: docs/30-FRONTEND/PUBLISHING-MODALS.md (new)
Sections:
- Modal Architecture
- Component API Reference
- State Management
- API Integration
- Progress Animation Logic
- Error Handling Patterns
- 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:
- ✅ Publishing Progress Modals - Real-time feedback for single and bulk operations
- ✅ Publishing Limits & Validation - Max 5 direct publish, unlimited scheduling
- ✅ Workflow Optimization - Clear separation of Review (approve only) and Approved (publish)
- ✅ Scheduling UI - Full scheduling/rescheduling capabilities across multiple pages
- ✅ Site Settings Integration - Bulk scheduling uses default configuration
- ✅ Failed Content Handling - Clear error display with retry/reschedule options
- ✅ Pattern Consistency - Follows existing modal patterns from ImageQueueModal
- ✅ User Experience - Intuitive, informative, and forgiving interface
- ✅ Platform Agnostic - Supports WordPress, Shopify, Custom sites with unified terminology
- ✅ 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
- Review this plan with stakeholders
- Prioritize phases based on business needs
- Begin Phase 1 implementation (Publishing Progress Modals)
- Conduct user testing after Phase 3
- Iterate based on feedback
Document Version: 1.0
Last Updated: January 2025
Status: Ready for Implementation