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

59 KiB
Raw Blame History

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_publishedscheduledpublishingpublished/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: pendingprocessingcompleted/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 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:

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:

{
  "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:

  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:

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:

  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:

{
  "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_at date/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 ScheduleContentModal with 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' 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:

{
  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_statuspublished, 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_statusscheduled, 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_wordpresspublish_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