# 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 ( Publishing Limit Exceeded You can publish only 5 content pages to site directly. You have selected {selectedCount} 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. Go Back Schedule Selected ); }; ``` **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: , 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: , 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; } const ScheduleContentModal = ({ isOpen, onClose, content, onSchedule }) => { const [selectedDate, setSelectedDate] = useState(getDefaultScheduleDate()); const [selectedTime, setSelectedTime] = useState('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 ( {/* Date/Time pickers */} Schedule ); }; ``` **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' && ( } 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
You can publish only 5 content pages to site directly.
You have selected {selectedCount} items.
Options:
💡 Tip: Scheduling has no limit and uses your site's default publishing schedule.