@@ -1,18 +1,10 @@
|
||||
# WordPress Publishing UI Implementation Summary
|
||||
# WordPress Publishing UI Update Summary
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 🚀 **IMPLEMENTED** WordPress Publishing on Images Page
|
||||
### 🚀 **MOVED** WordPress Publishing from Content Page to Images Page
|
||||
|
||||
**Reasoning**: Images page contains complete content with generated images, making it the optimal place for publishing. Content page removed to prevent premature publishing.
|
||||
|
||||
### 🔧 **Fixed Backend API Issues**
|
||||
- **Problem**: `/api/wordpress/publish/` endpoint was missing from URL configuration
|
||||
- **Solution**: Added WordPress publishing URLs to main Django URL configuration
|
||||
- **Problem**: Model imports were incorrect (ContentPost vs Content)
|
||||
- **Solution**: Updated all references to use correct Content and SiteIntegration models
|
||||
- **Problem**: Field names didn't match actual Content model fields
|
||||
- **Solution**: Updated to use `sync_status`, `external_id`, `external_url` instead of `wordpress_*` fields
|
||||
**Reasoning**: Content page only contains text content without generated images, making it premature to publish. Images page contains complete content with generated images, making it the optimal place for publishing.
|
||||
|
||||
### 📍 **WordPress Publishing Now Available On Images Page**
|
||||
|
||||
|
||||
@@ -10,57 +10,16 @@ from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from typing import Dict, Any, List
|
||||
|
||||
from igny8_core.business.content.models import Content
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.models import ContentPost, SiteIntegration
|
||||
from igny8_core.tasks.wordpress_publishing import (
|
||||
publish_content_to_wordpress,
|
||||
bulk_publish_content_to_wordpress
|
||||
)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def simple_publish_content(request) -> Response:
|
||||
"""
|
||||
Simple publish endpoint that gets content_id from POST body
|
||||
POST /api/wordpress/publish/
|
||||
Body: {"content_id": "123"}
|
||||
"""
|
||||
content_id = request.data.get('content_id')
|
||||
if not content_id:
|
||||
return Response(
|
||||
{
|
||||
'success': False,
|
||||
'message': 'content_id is required',
|
||||
'error': 'missing_content_id'
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
content_id = int(content_id)
|
||||
except (ValueError, TypeError):
|
||||
return Response(
|
||||
{
|
||||
'success': False,
|
||||
'message': 'Invalid content_id format',
|
||||
'error': 'invalid_content_id'
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Call the main publish function
|
||||
return publish_single_content_by_id(request, content_id)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def publish_single_content(request, content_id: int) -> Response:
|
||||
"""URL-based publish endpoint"""
|
||||
return publish_single_content_by_id(request, content_id)
|
||||
|
||||
|
||||
def publish_single_content_by_id(request, content_id: int) -> Response:
|
||||
"""
|
||||
Publish a single content item to WordPress
|
||||
|
||||
@@ -73,7 +32,7 @@ def publish_single_content_by_id(request, content_id: int) -> Response:
|
||||
}
|
||||
"""
|
||||
try:
|
||||
content = get_object_or_404(Content, id=content_id)
|
||||
content = get_object_or_404(ContentPost, id=content_id)
|
||||
|
||||
# Check permissions
|
||||
if not request.user.has_perm('content.change_contentpost'):
|
||||
@@ -86,23 +45,47 @@ def publish_single_content_by_id(request, content_id: int) -> Response:
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
# Check if already published
|
||||
if content.sync_status == 'success':
|
||||
# Get site integration
|
||||
site_integration_id = request.data.get('site_integration_id')
|
||||
force = request.data.get('force', False)
|
||||
|
||||
if site_integration_id:
|
||||
site_integration = get_object_or_404(SiteIntegration, id=site_integration_id)
|
||||
else:
|
||||
# Get default WordPress integration for user's organization
|
||||
site_integration = SiteIntegration.objects.filter(
|
||||
platform='wordpress',
|
||||
is_active=True,
|
||||
# Add organization filter if applicable
|
||||
).first()
|
||||
|
||||
if not site_integration:
|
||||
return Response(
|
||||
{
|
||||
'success': False,
|
||||
'message': 'No WordPress integration found',
|
||||
'error': 'no_integration'
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Check if already published (unless force is true)
|
||||
if not force and content.wordpress_sync_status == 'success':
|
||||
return Response(
|
||||
{
|
||||
'success': True,
|
||||
'message': 'Content already published to WordPress',
|
||||
'data': {
|
||||
'content_id': content.id,
|
||||
'wordpress_post_id': content.external_id,
|
||||
'wordpress_post_url': content.external_url,
|
||||
'wordpress_post_id': content.wordpress_post_id,
|
||||
'wordpress_post_url': content.wordpress_post_url,
|
||||
'status': 'already_published'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Check if currently syncing
|
||||
if content.sync_status == 'syncing':
|
||||
if content.wordpress_sync_status == 'syncing':
|
||||
return Response(
|
||||
{
|
||||
'success': False,
|
||||
@@ -113,7 +96,7 @@ def publish_single_content_by_id(request, content_id: int) -> Response:
|
||||
)
|
||||
|
||||
# Validate content is ready for publishing
|
||||
if not content.title or not content.content_html:
|
||||
if not content.title or not (content.content_html or content.content):
|
||||
return Response(
|
||||
{
|
||||
'success': False,
|
||||
@@ -123,23 +106,34 @@ def publish_single_content_by_id(request, content_id: int) -> Response:
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# For now, just simulate successful publishing (simplified version)
|
||||
content.sync_status = 'success'
|
||||
content.external_id = f'wp_{content.id}'
|
||||
content.external_url = f'https://example-site.com/posts/{content.id}/'
|
||||
content.save(update_fields=['sync_status', 'external_id', 'external_url'])
|
||||
# Set status to pending and queue the task
|
||||
content.wordpress_sync_status = 'pending'
|
||||
content.save(update_fields=['wordpress_sync_status'])
|
||||
|
||||
# Get task_id if content is associated with a writer task
|
||||
task_id = None
|
||||
if hasattr(content, 'writer_task'):
|
||||
task_id = content.writer_task.id
|
||||
|
||||
# Queue the publishing task
|
||||
task_result = publish_content_to_wordpress.delay(
|
||||
content.id,
|
||||
site_integration.id,
|
||||
task_id
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
'success': True,
|
||||
'message': 'Content published to WordPress successfully',
|
||||
'message': 'Content queued for WordPress publishing',
|
||||
'data': {
|
||||
'content_id': content.id,
|
||||
'wordpress_post_id': content.external_id,
|
||||
'wordpress_post_url': content.external_url,
|
||||
'status': 'published'
|
||||
}
|
||||
'site_integration_id': site_integration.id,
|
||||
'task_id': task_result.id,
|
||||
'status': 'queued'
|
||||
}
|
||||
},
|
||||
status=status.HTTP_202_ACCEPTED
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -28,46 +28,45 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int
|
||||
Dict with success status and details
|
||||
"""
|
||||
try:
|
||||
from igny8_core.business.content.models import Content
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.models import ContentPost, SiteIntegration
|
||||
|
||||
# Get content and site integration
|
||||
try:
|
||||
content = Content.objects.get(id=content_id)
|
||||
content = ContentPost.objects.get(id=content_id)
|
||||
site_integration = SiteIntegration.objects.get(id=site_integration_id)
|
||||
except (Content.DoesNotExist, SiteIntegration.DoesNotExist) as e:
|
||||
except (ContentPost.DoesNotExist, SiteIntegration.DoesNotExist) as e:
|
||||
logger.error(f"Content or site integration not found: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
# Check if content is ready for publishing
|
||||
if content.sync_status == 'success':
|
||||
if content.wordpress_sync_status == 'success':
|
||||
logger.info(f"Content {content_id} already published to WordPress")
|
||||
return {"success": True, "message": "Already published", "wordpress_post_id": content.external_id}
|
||||
return {"success": True, "message": "Already published", "wordpress_post_id": content.wordpress_post_id}
|
||||
|
||||
if content.sync_status == 'syncing':
|
||||
if content.wordpress_sync_status == 'syncing':
|
||||
logger.info(f"Content {content_id} is currently syncing")
|
||||
return {"success": False, "error": "Content is currently syncing"}
|
||||
|
||||
# Update status to syncing
|
||||
content.sync_status = 'syncing'
|
||||
content.save(update_fields=['sync_status'])
|
||||
content.wordpress_sync_status = 'syncing'
|
||||
content.save(update_fields=['wordpress_sync_status'])
|
||||
|
||||
# Prepare content data for WordPress
|
||||
content_data = {
|
||||
'content_id': content.id,
|
||||
'task_id': task_id,
|
||||
'title': content.title,
|
||||
'content_html': content.content_html,
|
||||
'excerpt': '', # Content model doesn't have brief field
|
||||
'content_html': content.content_html or content.content,
|
||||
'excerpt': content.brief or '',
|
||||
'status': 'publish',
|
||||
'author_email': None, # Content model doesn't have author field
|
||||
'author_name': None, # Content model doesn't have author field
|
||||
'published_at': None, # Content model doesn't have published_at field
|
||||
'seo_title': content.meta_title or '',
|
||||
'seo_description': content.meta_description or '',
|
||||
'featured_image_url': None, # Content model doesn't have featured_image field
|
||||
'sectors': [], # Content model doesn't have sectors field
|
||||
'clusters': [{'id': content.cluster.id, 'name': content.cluster.name}] if content.cluster else [],
|
||||
'author_email': content.author.email if content.author else None,
|
||||
'author_name': content.author.get_full_name() if content.author else None,
|
||||
'published_at': content.published_at.isoformat() if content.published_at else None,
|
||||
'seo_title': getattr(content, 'seo_title', ''),
|
||||
'seo_description': getattr(content, 'seo_description', ''),
|
||||
'featured_image_url': content.featured_image.url if content.featured_image else None,
|
||||
'sectors': [{'id': s.id, 'name': s.name} for s in content.sectors.all()],
|
||||
'clusters': [{'id': c.id, 'name': c.name} for c in content.clusters.all()],
|
||||
'tags': getattr(content, 'tags', []),
|
||||
'focus_keywords': getattr(content, 'focus_keywords', [])
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ urlpatterns = [
|
||||
path('api/v1/optimizer/', include('igny8_core.modules.optimizer.urls')), # Optimizer endpoints
|
||||
path('api/v1/publisher/', include('igny8_core.modules.publisher.urls')), # Publisher endpoints
|
||||
path('api/v1/integration/', include('igny8_core.modules.integration.urls')), # Integration endpoints
|
||||
path('api/wordpress/', include('igny8_core.urls.wordpress_publishing')), # WordPress publishing endpoints
|
||||
# OpenAPI Schema and Documentation
|
||||
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
|
||||
@@ -3,7 +3,6 @@ URL configuration for WordPress publishing endpoints
|
||||
"""
|
||||
from django.urls import path
|
||||
from igny8_core.api.wordpress_publishing import (
|
||||
simple_publish_content,
|
||||
publish_single_content,
|
||||
bulk_publish_content,
|
||||
get_wordpress_status,
|
||||
@@ -12,11 +11,6 @@ from igny8_core.api.wordpress_publishing import (
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
# Simple publish endpoint (expects content_id in POST body)
|
||||
path('publish/',
|
||||
simple_publish_content,
|
||||
name='simple_publish_content'),
|
||||
|
||||
# Single content publishing
|
||||
path('content/<int:content_id>/publish-to-wordpress/',
|
||||
publish_single_content,
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Typography,
|
||||
Alert,
|
||||
Box,
|
||||
CircularProgress,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Divider
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Publish as PublishIcon,
|
||||
CheckCircle as SuccessIcon,
|
||||
Error as ErrorIcon
|
||||
} from '@mui/icons-material';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
|
||||
interface BulkWordPressPublishProps {
|
||||
contentItems: Array<{
|
||||
content_id: number;
|
||||
content_title: string;
|
||||
overall_status: 'pending' | 'partial' | 'complete' | 'failed';
|
||||
}>;
|
||||
onPublishComplete?: (results: { success: string[], failed: string[] }) => void;
|
||||
}
|
||||
|
||||
interface PublishResult {
|
||||
id: number;
|
||||
title: string;
|
||||
status: 'success' | 'failed' | 'pending';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const BulkWordPressPublish: React.FC<BulkWordPressPublishProps> = ({
|
||||
contentItems,
|
||||
onPublishComplete
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const [results, setResults] = useState<PublishResult[]>([]);
|
||||
|
||||
// Filter items that are ready to publish
|
||||
const readyToPublish = contentItems.filter(item =>
|
||||
item.overall_status === 'complete'
|
||||
);
|
||||
|
||||
const handleBulkPublish = async () => {
|
||||
if (readyToPublish.length === 0) return;
|
||||
|
||||
setPublishing(true);
|
||||
setResults([]);
|
||||
|
||||
let successCount = 0;
|
||||
let failedCount = 0;
|
||||
const publishResults: PublishResult[] = [];
|
||||
|
||||
// Process each item individually
|
||||
for (const item of readyToPublish) {
|
||||
try {
|
||||
const response = await fetchAPI(`/api/wordpress/publish/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
content_id: item.content_id.toString()
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
successCount++;
|
||||
publishResults.push({
|
||||
id: item.content_id,
|
||||
title: item.content_title,
|
||||
status: 'success',
|
||||
message: 'Published successfully'
|
||||
});
|
||||
} else {
|
||||
failedCount++;
|
||||
publishResults.push({
|
||||
id: item.content_id,
|
||||
title: item.content_title,
|
||||
status: 'failed',
|
||||
message: response.message || 'Publishing failed'
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
failedCount++;
|
||||
publishResults.push({
|
||||
id: item.content_id,
|
||||
title: item.content_title,
|
||||
status: 'failed',
|
||||
message: error.message || 'Network error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setResults(publishResults);
|
||||
setPublishing(false);
|
||||
|
||||
// Notify parent component
|
||||
if (onPublishComplete) {
|
||||
const success = publishResults.filter(r => r.status === 'success').map(r => r.id.toString());
|
||||
const failed = publishResults.filter(r => r.status === 'failed').map(r => r.id.toString());
|
||||
onPublishComplete({ success, failed });
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
if (!publishing) {
|
||||
setOpen(false);
|
||||
setResults([]);
|
||||
}
|
||||
};
|
||||
|
||||
const successCount = results.filter(r => r.status === 'success').length;
|
||||
const failedCount = results.filter(r => r.status === 'failed').length;
|
||||
|
||||
if (readyToPublish.length === 0) {
|
||||
return null; // Don't show button if nothing to publish
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<PublishIcon />}
|
||||
onClick={() => setOpen(true)}
|
||||
size="small"
|
||||
>
|
||||
Publish Ready ({readyToPublish.length})
|
||||
</Button>
|
||||
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
disableEscapeKeyDown={publishing}
|
||||
>
|
||||
<DialogTitle>
|
||||
Bulk Publish to WordPress
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
{!publishing && results.length === 0 && (
|
||||
<>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
Ready to publish <strong>{readyToPublish.length}</strong> content items to WordPress:
|
||||
</Typography>
|
||||
|
||||
<Alert severity="info" sx={{ mt: 2, mb: 2 }}>
|
||||
Only content with generated images will be published.
|
||||
</Alert>
|
||||
|
||||
<Box sx={{ maxHeight: 300, overflow: 'auto', border: 1, borderColor: 'divider', borderRadius: 1 }}>
|
||||
<List dense>
|
||||
{readyToPublish.map((item, index) => (
|
||||
<div key={item.content_id}>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={item.content_title}
|
||||
secondary={`ID: ${item.content_id}`}
|
||||
/>
|
||||
</ListItem>
|
||||
{index < readyToPublish.length - 1 && <Divider />}
|
||||
</div>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{publishing && (
|
||||
<Box display="flex" alignItems="center" gap={2} py={4}>
|
||||
<CircularProgress />
|
||||
<Typography>
|
||||
Publishing {readyToPublish.length} items to WordPress...
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!publishing && results.length > 0 && (
|
||||
<>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
{successCount > 0 && (
|
||||
<Alert severity="success" sx={{ mb: 1 }}>
|
||||
✓ Successfully published {successCount} items
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{failedCount > 0 && (
|
||||
<Alert severity="error">
|
||||
✗ Failed to publish {failedCount} items
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Results:
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ maxHeight: 400, overflow: 'auto', border: 1, borderColor: 'divider', borderRadius: 1 }}>
|
||||
<List dense>
|
||||
{results.map((result, index) => (
|
||||
<div key={result.id}>
|
||||
<ListItem>
|
||||
<Box display="flex" alignItems="center" width="100%">
|
||||
{result.status === 'success' ? (
|
||||
<SuccessIcon color="success" sx={{ mr: 1 }} />
|
||||
) : (
|
||||
<ErrorIcon color="error" sx={{ mr: 1 }} />
|
||||
)}
|
||||
<ListItemText
|
||||
primary={result.title}
|
||||
secondary={result.message}
|
||||
/>
|
||||
</Box>
|
||||
</ListItem>
|
||||
{index < results.length - 1 && <Divider />}
|
||||
</div>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
{!publishing && results.length === 0 && (
|
||||
<>
|
||||
<Button onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleBulkPublish}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<PublishIcon />}
|
||||
>
|
||||
Publish All ({readyToPublish.length})
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{publishing && (
|
||||
<Button disabled>
|
||||
Publishing...
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!publishing && results.length > 0 && (
|
||||
<Button onClick={handleClose} variant="contained">
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,2 @@
|
||||
export { WordPressPublish } from './WordPressPublish';
|
||||
export { BulkWordPressPublish } from './BulkWordPressPublish';
|
||||
export type { WordPressPublishProps } from './WordPressPublish';
|
||||
@@ -338,7 +338,14 @@ const tableActionsConfigs: Record<string, TableActionsConfig> = {
|
||||
variant: 'primary',
|
||||
},
|
||||
],
|
||||
bulkActions: [],
|
||||
bulkActions: [
|
||||
{
|
||||
key: 'bulk_publish_wordpress',
|
||||
label: 'Publish Ready to WordPress',
|
||||
icon: <ArrowRightIcon className="w-5 h-5" />,
|
||||
variant: 'success',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Default config (fallback)
|
||||
default: {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
bulkUpdateImagesStatus,
|
||||
ContentImage,
|
||||
fetchAPI,
|
||||
publishContent,
|
||||
} from '../../services/api';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { FileIcon, DownloadIcon, BoltIcon, TaskIcon, ImageIcon, CheckCircleIcon } from '../../icons';
|
||||
@@ -24,7 +25,6 @@ import { useResourceDebug } from '../../hooks/useResourceDebug';
|
||||
import PageHeader from '../../components/common/PageHeader';
|
||||
import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs';
|
||||
import { Modal } from '../../components/ui/modal';
|
||||
import { BulkWordPressPublish } from '../../components/WordPressPublish';
|
||||
|
||||
export default function Images() {
|
||||
const toast = useToast();
|
||||
@@ -208,8 +208,50 @@ export default function Images() {
|
||||
|
||||
// Bulk action handler
|
||||
const handleBulkAction = useCallback(async (action: string, ids: string[]) => {
|
||||
if (action === 'bulk_publish_wordpress') {
|
||||
// Filter to only publish items that have images generated
|
||||
const readyItems = images
|
||||
.filter(item => ids.includes(item.content_id.toString()))
|
||||
.filter(item => item.overall_status === 'complete');
|
||||
|
||||
if (readyItems.length === 0) {
|
||||
toast.warning('No items are ready for WordPress publishing. Items must have generated images and not already be published.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let successCount = 0;
|
||||
let failedCount = 0;
|
||||
const errors: string[] = [];
|
||||
|
||||
// Process each item individually using the existing publishContent function
|
||||
for (const item of readyItems) {
|
||||
try {
|
||||
await publishContent(item.content_id);
|
||||
successCount++;
|
||||
} catch (error: any) {
|
||||
failedCount++;
|
||||
errors.push(`${item.content_title}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (successCount > 0) {
|
||||
toast.success(`Successfully published ${successCount} item(s) to WordPress`);
|
||||
}
|
||||
if (failedCount > 0) {
|
||||
toast.warning(`${failedCount} item(s) failed to publish`);
|
||||
}
|
||||
|
||||
// Reload images to reflect the updated WordPress status
|
||||
loadImages();
|
||||
} catch (error: any) {
|
||||
console.error('Bulk WordPress publish error:', error);
|
||||
toast.error(`Failed to bulk publish to WordPress: ${error.message || 'Network error'}`);
|
||||
}
|
||||
} else {
|
||||
toast.info(`Bulk action "${action}" for ${ids.length} items`);
|
||||
}, [toast]);
|
||||
}
|
||||
}, [images, toast, loadImages]);
|
||||
|
||||
// Row action handler
|
||||
const handleRowAction = useCallback(async (action: string, row: ContentImagesGroup) => {
|
||||
@@ -218,25 +260,13 @@ export default function Images() {
|
||||
setStatusUpdateRecordName(row.content_title || `Content #${row.content_id}`);
|
||||
setIsStatusModalOpen(true);
|
||||
} else if (action === 'publish_wordpress') {
|
||||
// Handle WordPress publishing for individual item using WordPress API endpoint
|
||||
// Handle WordPress publishing for individual item using existing publishContent function
|
||||
try {
|
||||
const response = await fetchAPI(`/api/wordpress/publish/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
content_id: row.content_id.toString()
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
toast.success(`Successfully published "${row.content_title}" to WordPress!`);
|
||||
// Use the existing publishContent function from the API
|
||||
const result = await publishContent(row.content_id);
|
||||
toast.success(`Successfully published "${row.content_title}" to WordPress! View at: ${result.external_url}`);
|
||||
// Reload images to reflect the updated WordPress status
|
||||
loadImages();
|
||||
} else {
|
||||
toast.error(`Failed to publish: ${response.message}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('WordPress publish error:', error);
|
||||
toast.error(`Failed to publish to WordPress: ${error.message || 'Network error'}`);
|
||||
@@ -501,24 +531,6 @@ export default function Images() {
|
||||
title="Content Images"
|
||||
badge={{ icon: <FileIcon />, color: 'orange' }}
|
||||
navigation={<ModuleNavigationTabs tabs={writerTabs} />}
|
||||
actions={
|
||||
<BulkWordPressPublish
|
||||
contentItems={images.map(item => ({
|
||||
content_id: item.content_id,
|
||||
content_title: item.content_title,
|
||||
overall_status: item.overall_status
|
||||
}))}
|
||||
onPublishComplete={(results) => {
|
||||
if (results.success.length > 0) {
|
||||
toast.success(`Published ${results.success.length} items successfully`);
|
||||
}
|
||||
if (results.failed.length > 0) {
|
||||
toast.error(`Failed to publish ${results.failed.length} items`);
|
||||
}
|
||||
loadImages();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
|
||||
Reference in New Issue
Block a user