This commit is contained in:
alorig
2025-11-28 12:08:21 +05:00
parent 719e477a2f
commit 081f94ffdb
7 changed files with 1521 additions and 0 deletions

View File

@@ -0,0 +1,338 @@
import React, { useState, useEffect } from 'react';
import {
Button,
Alert,
Chip,
IconButton,
Tooltip,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
CircularProgress,
Box,
Typography
} from '@mui/material';
import {
Publish as PublishIcon,
Refresh as RefreshIcon,
CheckCircle as SuccessIcon,
Error as ErrorIcon,
Schedule as PendingIcon,
Sync as SyncingIcon
} from '@mui/icons-material';
import { api } from '../../services/api';
interface WordPressPublishProps {
contentId: string;
contentTitle: string;
currentStatus?: 'draft' | 'publishing' | 'published' | 'failed';
onStatusChange?: (status: string) => void;
size?: 'small' | 'medium' | 'large';
}
interface WordPressStatus {
wordpress_sync_status: 'pending' | 'syncing' | 'success' | 'failed';
wordpress_post_id?: number;
wordpress_post_url?: string;
wordpress_sync_attempts: number;
last_wordpress_sync?: string;
}
export const WordPressPublish: React.FC<WordPressPublishProps> = ({
contentId,
contentTitle,
currentStatus = 'draft',
onStatusChange,
size = 'medium'
}) => {
const [wpStatus, setWpStatus] = useState<WordPressStatus | null>(null);
const [loading, setLoading] = useState(false);
const [publishDialogOpen, setPublishDialogOpen] = useState(false);
const [error, setError] = useState<string | null>(null);
// Fetch current WordPress status
const fetchWordPressStatus = async () => {
try {
const response = await api.get(`/api/v1/content/${contentId}/wordpress-status/`);
if (response.data.success) {
setWpStatus(response.data.data);
}
} catch (error) {
console.error('Failed to fetch WordPress status:', error);
}
};
useEffect(() => {
fetchWordPressStatus();
}, [contentId]);
// Handle publish to WordPress
const handlePublishToWordPress = async (force: boolean = false) => {
setLoading(true);
setError(null);
try {
const response = await api.post(`/api/v1/content/${contentId}/publish-to-wordpress/`, {
force: force
});
if (response.data.success) {
setWpStatus(prev => prev ? { ...prev, wordpress_sync_status: 'pending' } : null);
onStatusChange?.('publishing');
// Poll for status updates
pollForStatusUpdate();
} else {
setError(response.data.message || 'Failed to publish to WordPress');
}
} catch (error: any) {
setError(error.response?.data?.message || 'Error publishing to WordPress');
} finally {
setLoading(false);
setPublishDialogOpen(false);
}
};
// Poll for status updates after publishing
const pollForStatusUpdate = () => {
const pollInterval = setInterval(async () => {
try {
const response = await api.get(`/api/v1/content/${contentId}/wordpress-status/`);
if (response.data.success) {
const status = response.data.data;
setWpStatus(status);
// Stop polling if sync is complete (success or failed)
if (status.wordpress_sync_status === 'success' || status.wordpress_sync_status === 'failed') {
clearInterval(pollInterval);
onStatusChange?.(status.wordpress_sync_status === 'success' ? 'published' : 'failed');
}
}
} catch (error) {
clearInterval(pollInterval);
}
}, 3000); // Poll every 3 seconds
// Stop polling after 2 minutes
setTimeout(() => {
clearInterval(pollInterval);
}, 120000);
};
// Handle retry
const handleRetry = async () => {
setLoading(true);
setError(null);
try {
const response = await api.post(`/api/v1/content/${contentId}/retry-wordpress-sync/`);
if (response.data.success) {
setWpStatus(prev => prev ? { ...prev, wordpress_sync_status: 'pending' } : null);
onStatusChange?.('publishing');
pollForStatusUpdate();
} else {
setError(response.data.message || 'Failed to retry WordPress sync');
}
} catch (error: any) {
setError(error.response?.data?.message || 'Error retrying WordPress sync');
} finally {
setLoading(false);
}
};
// Get status display info
const getStatusInfo = () => {
if (!wpStatus) {
return {
color: 'default' as const,
icon: <PublishIcon />,
label: 'Not Published',
action: 'publish'
};
}
switch (wpStatus.wordpress_sync_status) {
case 'pending':
return {
color: 'warning' as const,
icon: <PendingIcon />,
label: 'Queued',
action: 'wait'
};
case 'syncing':
return {
color: 'info' as const,
icon: <SyncingIcon className="animate-spin" />,
label: 'Publishing...',
action: 'wait'
};
case 'success':
return {
color: 'success' as const,
icon: <SuccessIcon />,
label: 'Published',
action: 'view'
};
case 'failed':
return {
color: 'error' as const,
icon: <ErrorIcon />,
label: 'Failed',
action: 'retry'
};
default:
return {
color: 'default' as const,
icon: <PublishIcon />,
label: 'Not Published',
action: 'publish'
};
}
};
const statusInfo = getStatusInfo();
const renderButton = () => {
if (size === 'small') {
return (
<Tooltip title={`WordPress: ${statusInfo.label}`}>
<IconButton
size="small"
onClick={() => {
if (statusInfo.action === 'publish') {
setPublishDialogOpen(true);
} else if (statusInfo.action === 'retry') {
handleRetry();
} else if (statusInfo.action === 'view' && wpStatus?.wordpress_post_url) {
window.open(wpStatus.wordpress_post_url, '_blank');
}
}}
disabled={loading || statusInfo.action === 'wait'}
color={statusInfo.color}
>
{loading ? <CircularProgress size={16} /> : statusInfo.icon}
</IconButton>
</Tooltip>
);
}
return (
<Button
variant={statusInfo.action === 'publish' ? 'contained' : 'outlined'}
color={statusInfo.color}
startIcon={loading ? <CircularProgress size={20} /> : statusInfo.icon}
onClick={() => {
if (statusInfo.action === 'publish') {
setPublishDialogOpen(true);
} else if (statusInfo.action === 'retry') {
handleRetry();
} else if (statusInfo.action === 'view' && wpStatus?.wordpress_post_url) {
window.open(wpStatus.wordpress_post_url, '_blank');
}
}}
disabled={loading || statusInfo.action === 'wait'}
size={size}
>
{statusInfo.action === 'publish' && 'Publish to WordPress'}
{statusInfo.action === 'retry' && 'Retry'}
{statusInfo.action === 'view' && 'View on WordPress'}
{statusInfo.action === 'wait' && statusInfo.label}
</Button>
);
};
const renderStatusChip = () => {
if (size === 'small') return null;
return (
<Chip
icon={statusInfo.icon}
label={statusInfo.label}
color={statusInfo.color}
size="small"
variant="outlined"
onClick={() => {
if (wpStatus?.wordpress_post_url) {
window.open(wpStatus.wordpress_post_url, '_blank');
}
}}
style={{
marginLeft: 8,
cursor: wpStatus?.wordpress_post_url ? 'pointer' : 'default'
}}
/>
);
};
return (
<Box display="flex" alignItems="center" gap={1}>
{renderButton()}
{renderStatusChip()}
{error && (
<Alert severity="error" sx={{ mt: 1 }}>
{error}
<IconButton
size="small"
onClick={() => setError(null)}
sx={{ ml: 1 }}
>
×
</IconButton>
</Alert>
)}
{/* Publish Confirmation Dialog */}
<Dialog
open={publishDialogOpen}
onClose={() => setPublishDialogOpen(false)}
maxWidth="sm"
fullWidth
>
<DialogTitle>Publish to WordPress</DialogTitle>
<DialogContent>
<Typography variant="body1" gutterBottom>
Are you sure you want to publish "<strong>{contentTitle}</strong>" to WordPress?
</Typography>
<Typography variant="body2" color="textSecondary" sx={{ mt: 2 }}>
This will create a new post on your connected WordPress site with all content,
images, categories, and SEO metadata.
</Typography>
{wpStatus?.wordpress_sync_status === 'success' && (
<Alert severity="info" sx={{ mt: 2 }}>
This content is already published to WordPress.
You can force republish to update the existing post.
</Alert>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => setPublishDialogOpen(false)}>
Cancel
</Button>
{wpStatus?.wordpress_sync_status === 'success' && (
<Button
onClick={() => handlePublishToWordPress(true)}
color="warning"
disabled={loading}
>
Force Republish
</Button>
)}
<Button
onClick={() => handlePublishToWordPress(false)}
color="primary"
variant="contained"
disabled={loading}
>
{loading ? 'Publishing...' : 'Publish'}
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default WordPressPublish;