final section 10 -- and lgoabl styles adn compoeents plan
This commit is contained in:
@@ -1,24 +1,20 @@
|
||||
/**
|
||||
* BulkWordPressPublish Component
|
||||
*
|
||||
* Handles bulk publishing multiple content items to connected WordPress sites.
|
||||
*
|
||||
* 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from '../ui/button/Button';
|
||||
import { Modal } from '../ui/modal';
|
||||
import Alert from '../ui/alert/Alert';
|
||||
import { Spinner } from '../ui/spinner/Spinner';
|
||||
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';
|
||||
CheckCircleIcon,
|
||||
ErrorIcon,
|
||||
PaperPlaneIcon,
|
||||
} from '../../icons';
|
||||
import { api } from '../../services/api';
|
||||
|
||||
interface BulkWordPressPublishProps {
|
||||
@@ -121,140 +117,140 @@ export const BulkWordPressPublish: React.FC<BulkWordPressPublishProps> = ({
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<PublishIcon />}
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
startIcon={<PaperPlaneIcon className="h-4 w-4" />}
|
||||
onClick={() => setOpen(true)}
|
||||
size="small"
|
||||
size="sm"
|
||||
>
|
||||
Publish Ready ({readyToPublish.length})
|
||||
</Button>
|
||||
|
||||
<Dialog
|
||||
open={open}
|
||||
<Modal
|
||||
isOpen={open}
|
||||
onClose={handleClose}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
disableEscapeKeyDown={publishing}
|
||||
className="max-w-2xl p-6"
|
||||
>
|
||||
<DialogTitle>
|
||||
Bulk Publish to Site
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Bulk Publish to Site
|
||||
</h2>
|
||||
|
||||
{!publishing && results.length === 0 && (
|
||||
<>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
<p className="text-gray-700 dark:text-gray-300">
|
||||
Ready to publish <strong>{readyToPublish.length}</strong> content items to your site:
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
<Alert severity="info" sx={{ mt: 2, mb: 2 }}>
|
||||
Only content with generated images and not yet published will be included.
|
||||
</Alert>
|
||||
<Alert
|
||||
variant="info"
|
||||
title="Info"
|
||||
message="Only content with generated images and not yet published will be included."
|
||||
/>
|
||||
|
||||
<Box sx={{ maxHeight: 300, overflow: 'auto', border: 1, borderColor: 'divider', borderRadius: 1 }}>
|
||||
<List dense>
|
||||
{readyToPublish.map((item, index) => (
|
||||
<div key={item.id}>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={item.title}
|
||||
secondary={`ID: ${item.id}`}
|
||||
/>
|
||||
</ListItem>
|
||||
{index < readyToPublish.length - 1 && <Divider />}
|
||||
<div className="max-h-72 overflow-auto rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
{readyToPublish.map((item, index) => (
|
||||
<div key={item.id}>
|
||||
<div className="px-4 py-3">
|
||||
<p className="font-medium text-gray-900 dark:text-white">{item.title}</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">ID: {item.id}</p>
|
||||
</div>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
{index < readyToPublish.length - 1 && (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{publishing && (
|
||||
<Box display="flex" alignItems="center" gap={2} py={4}>
|
||||
<CircularProgress />
|
||||
<Typography>
|
||||
<div className="flex items-center gap-3 py-8">
|
||||
<Spinner size="md" />
|
||||
<p className="text-gray-700 dark:text-gray-300">
|
||||
Publishing {readyToPublish.length} items to WordPress...
|
||||
</Typography>
|
||||
</Box>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!publishing && results.length > 0 && (
|
||||
<>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<div className="space-y-2">
|
||||
{successCount > 0 && (
|
||||
<Alert severity="success" sx={{ mb: 1 }}>
|
||||
✓ Successfully published {successCount} items
|
||||
</Alert>
|
||||
<Alert
|
||||
variant="success"
|
||||
title="Published"
|
||||
message={`Successfully published ${successCount} items`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{failedCount > 0 && (
|
||||
<Alert severity="error">
|
||||
✗ Failed to publish {failedCount} items
|
||||
</Alert>
|
||||
<Alert
|
||||
variant="error"
|
||||
title="Failed"
|
||||
message={`Failed to publish ${failedCount} items`}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Results:
|
||||
</Typography>
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white">Results:</h3>
|
||||
|
||||
<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 || (result.status === 'success' ? 'Published successfully' : 'Publishing failed')}
|
||||
/>
|
||||
</Box>
|
||||
</ListItem>
|
||||
{index < results.length - 1 && <Divider />}
|
||||
<div className="max-h-96 overflow-auto rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
{results.map((result, index) => (
|
||||
<div key={result.id}>
|
||||
<div className="flex items-center gap-3 px-4 py-3">
|
||||
{result.status === 'success' ? (
|
||||
<CheckCircleIcon className="h-5 w-5 text-success-500" />
|
||||
) : (
|
||||
<ErrorIcon className="h-5 w-5 text-error-500" />
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-gray-900 dark:text-white">{result.title}</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{result.message || (result.status === 'success' ? 'Published successfully' : 'Publishing failed')}
|
||||
</p>
|
||||
</div>
|
||||
</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>
|
||||
{index < results.length - 1 && (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{publishing && (
|
||||
<Button disabled>
|
||||
Publishing...
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!publishing && results.length > 0 && (
|
||||
<Button onClick={handleClose} variant="contained">
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
{!publishing && results.length === 0 && (
|
||||
<>
|
||||
<Button variant="outline" tone="neutral" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
onClick={handleBulkPublish}
|
||||
startIcon={<PaperPlaneIcon className="h-4 w-4" />}
|
||||
>
|
||||
Publish All ({readyToPublish.length})
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{publishing && (
|
||||
<Button variant="outline" tone="neutral" disabled>
|
||||
Publishing...
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!publishing && results.length > 0 && (
|
||||
<Button variant="primary" tone="brand" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +1,21 @@
|
||||
import React, { useState } from 'react';
|
||||
/**
|
||||
* ContentActionsMenu Component
|
||||
*
|
||||
* Dropdown menu for content actions (edit, generate images, export, delete, publish).
|
||||
*
|
||||
* 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md
|
||||
*/
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Button } from '../ui/button/Button';
|
||||
import { Dropdown } from '../ui/dropdown/Dropdown';
|
||||
import {
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Divider
|
||||
} from '@mui/material';
|
||||
import {
|
||||
MoreVert as MoreVertIcon,
|
||||
Publish as PublishIcon,
|
||||
Edit as EditIcon,
|
||||
Image as ImageIcon,
|
||||
GetApp as ExportIcon,
|
||||
Delete as DeleteIcon
|
||||
} from '@mui/icons-material';
|
||||
MoreDotIcon,
|
||||
PaperPlaneIcon,
|
||||
PencilIcon,
|
||||
FileIcon,
|
||||
DownloadIcon,
|
||||
TrashBinIcon,
|
||||
} from '../../icons';
|
||||
import { WordPressPublish } from './WordPressPublish';
|
||||
|
||||
interface ContentActionsMenuProps {
|
||||
@@ -40,16 +41,12 @@ export const ContentActionsMenu: React.FC<ContentActionsMenuProps> = ({
|
||||
onDelete,
|
||||
onWordPressStatusChange
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [showWordPressDialog, setShowWordPressDialog] = useState(false);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handlePublishClick = () => {
|
||||
@@ -69,85 +66,85 @@ export const ContentActionsMenu: React.FC<ContentActionsMenuProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
<button
|
||||
ref={buttonRef}
|
||||
aria-label="more actions"
|
||||
id="content-actions-button"
|
||||
aria-controls={open ? 'content-actions-menu' : undefined}
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={handleClick}
|
||||
size="small"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="dropdown-toggle flex h-8 w-8 items-center justify-center rounded-lg text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<MoreDotIcon className="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<Menu
|
||||
id="content-actions-menu"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
onClose={handleClose}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'content-actions-button',
|
||||
}}
|
||||
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||
anchorRef={buttonRef as React.RefObject<HTMLElement>}
|
||||
placement="bottom-right"
|
||||
className="w-48"
|
||||
>
|
||||
{/* WordPress Publishing - Only show if images are ready */}
|
||||
{canPublishToWordPress && (
|
||||
<>
|
||||
<MenuItem onClick={handlePublishClick}>
|
||||
<ListItemIcon>
|
||||
<PublishIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Publish to Site</ListItemText>
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Edit Action */}
|
||||
{onEdit && (
|
||||
<MenuItem onClick={() => handleMenuAction(onEdit)}>
|
||||
<ListItemIcon>
|
||||
<EditIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Edit</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{/* Generate Image Action */}
|
||||
{onGenerateImage && (
|
||||
<MenuItem onClick={() => handleMenuAction(onGenerateImage)}>
|
||||
<ListItemIcon>
|
||||
<ImageIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Generate Image Prompts</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{/* Export Action */}
|
||||
{onExport && (
|
||||
<MenuItem onClick={() => handleMenuAction(onExport)}>
|
||||
<ListItemIcon>
|
||||
<ExportIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Export</ListItemText>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{/* Delete Action */}
|
||||
{onDelete && (
|
||||
<>
|
||||
<Divider />
|
||||
<MenuItem onClick={() => handleMenuAction(onDelete)} sx={{ color: 'error.main' }}>
|
||||
<ListItemIcon>
|
||||
<DeleteIcon fontSize="small" color="error" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Delete</ListItemText>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
<div className="py-1">
|
||||
{/* WordPress Publishing - Only show if images are ready */}
|
||||
{canPublishToWordPress && (
|
||||
<>
|
||||
<button
|
||||
onClick={handlePublishClick}
|
||||
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
<PaperPlaneIcon className="h-4 w-4" />
|
||||
<span>Publish to Site</span>
|
||||
</button>
|
||||
<div className="my-1 border-t border-gray-200 dark:border-gray-700" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Edit Action */}
|
||||
{onEdit && (
|
||||
<button
|
||||
onClick={() => handleMenuAction(onEdit)}
|
||||
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Generate Image Action */}
|
||||
{onGenerateImage && (
|
||||
<button
|
||||
onClick={() => handleMenuAction(onGenerateImage)}
|
||||
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
<FileIcon className="h-4 w-4" />
|
||||
<span>Generate Image Prompts</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Export Action */}
|
||||
{onExport && (
|
||||
<button
|
||||
onClick={() => handleMenuAction(onExport)}
|
||||
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
<span>Export</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Delete Action */}
|
||||
{onDelete && (
|
||||
<>
|
||||
<div className="my-1 border-t border-gray-200 dark:border-gray-700" />
|
||||
<button
|
||||
onClick={() => handleMenuAction(onDelete)}
|
||||
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-error-600 hover:bg-error-50 dark:text-error-400 dark:hover:bg-error-500/10"
|
||||
>
|
||||
<TrashBinIcon className="h-4 w-4" />
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
{/* WordPress Publish Dialog */}
|
||||
{showWordPressDialog && (
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
/**
|
||||
* WordPressPublish Component
|
||||
*
|
||||
* Handles publishing content to connected WordPress sites.
|
||||
*
|
||||
* 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md
|
||||
*/
|
||||
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 { Button } from '../ui/button/Button';
|
||||
import { Modal } from '../ui/modal';
|
||||
import Alert from '../ui/alert/Alert';
|
||||
import { Badge } from '../ui/badge/Badge';
|
||||
import { Tooltip } from '../ui/tooltip/Tooltip';
|
||||
import { Spinner } from '../ui/spinner/Spinner';
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ErrorIcon,
|
||||
TimeIcon,
|
||||
PaperPlaneIcon,
|
||||
} from '../../icons';
|
||||
import { api } from '../../services/api';
|
||||
|
||||
export interface WordPressPublishProps {
|
||||
@@ -149,8 +146,9 @@ export const WordPressPublish: React.FC<WordPressPublishProps> = ({
|
||||
const getStatusInfo = () => {
|
||||
if (!wpStatus) {
|
||||
return {
|
||||
color: 'default' as const,
|
||||
icon: <PublishIcon />,
|
||||
tone: 'neutral' as const,
|
||||
badgeTone: 'neutral' as const,
|
||||
icon: <PaperPlaneIcon className="h-4 w-4" />,
|
||||
label: 'Not Published',
|
||||
action: 'publish'
|
||||
};
|
||||
@@ -159,36 +157,41 @@ export const WordPressPublish: React.FC<WordPressPublishProps> = ({
|
||||
switch (wpStatus.wordpress_sync_status) {
|
||||
case 'pending':
|
||||
return {
|
||||
color: 'warning' as const,
|
||||
icon: <PendingIcon />,
|
||||
tone: 'warning' as const,
|
||||
badgeTone: 'warning' as const,
|
||||
icon: <TimeIcon className="h-4 w-4" />,
|
||||
label: 'Queued',
|
||||
action: 'wait'
|
||||
};
|
||||
case 'syncing':
|
||||
return {
|
||||
color: 'info' as const,
|
||||
icon: <SyncingIcon className="animate-spin" />,
|
||||
tone: 'brand' as const,
|
||||
badgeTone: 'info' as const,
|
||||
icon: <Spinner size="sm" />,
|
||||
label: 'Publishing...',
|
||||
action: 'wait'
|
||||
};
|
||||
case 'success':
|
||||
return {
|
||||
color: 'success' as const,
|
||||
icon: <SuccessIcon />,
|
||||
tone: 'success' as const,
|
||||
badgeTone: 'success' as const,
|
||||
icon: <CheckCircleIcon className="h-4 w-4" />,
|
||||
label: 'Published',
|
||||
action: 'view'
|
||||
};
|
||||
case 'failed':
|
||||
return {
|
||||
color: 'error' as const,
|
||||
icon: <ErrorIcon />,
|
||||
tone: 'danger' as const,
|
||||
badgeTone: 'danger' as const,
|
||||
icon: <ErrorIcon className="h-4 w-4" />,
|
||||
label: 'Failed',
|
||||
action: 'retry'
|
||||
};
|
||||
default:
|
||||
return {
|
||||
color: 'default' as const,
|
||||
icon: <PublishIcon />,
|
||||
tone: 'neutral' as const,
|
||||
badgeTone: 'neutral' as const,
|
||||
icon: <PaperPlaneIcon className="h-4 w-4" />,
|
||||
label: 'Not Published',
|
||||
action: 'publish'
|
||||
};
|
||||
@@ -202,181 +205,178 @@ export const WordPressPublish: React.FC<WordPressPublishProps> = ({
|
||||
|
||||
if (!shouldShowPublishButton) {
|
||||
return (
|
||||
<Tooltip title={`Images must be generated before publishing to site`}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Tooltip text="Images must be generated before publishing to site">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outlined"
|
||||
variant="outline"
|
||||
tone="neutral"
|
||||
disabled
|
||||
size={size}
|
||||
startIcon={<PendingIcon />}
|
||||
size={size === 'small' ? 'sm' : size === 'large' ? 'lg' : 'md'}
|
||||
startIcon={<TimeIcon className="h-4 w-4" />}
|
||||
>
|
||||
Awaiting Images
|
||||
</Button>
|
||||
{size !== 'small' && (
|
||||
<Chip
|
||||
icon={<PendingIcon />}
|
||||
label="Images Pending"
|
||||
color="warning"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
<Badge
|
||||
tone="warning"
|
||||
variant="outline"
|
||||
startIcon={<TimeIcon className="h-3 w-3" />}
|
||||
>
|
||||
Images Pending
|
||||
</Badge>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
const renderButton = () => {
|
||||
if (size === 'small') {
|
||||
return (
|
||||
<Tooltip title={`Site: ${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>
|
||||
);
|
||||
const handleButtonClick = () => {
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
const renderButton = () => {
|
||||
const buttonSize = size === 'small' ? 'sm' : size === 'large' ? 'lg' : 'md';
|
||||
|
||||
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 Site'}
|
||||
{statusInfo.action === 'retry' && 'Retry'}
|
||||
{statusInfo.action === 'view' && 'View on Site'}
|
||||
{statusInfo.action === 'wait' && statusInfo.label}
|
||||
</Button>
|
||||
<Tooltip text={`Site: ${statusInfo.label}`}>
|
||||
<Button
|
||||
size={buttonSize}
|
||||
variant={statusInfo.action === 'publish' ? 'primary' : 'outline'}
|
||||
tone={statusInfo.tone}
|
||||
onClick={handleButtonClick}
|
||||
disabled={loading || statusInfo.action === 'wait'}
|
||||
startIcon={loading ? <Spinner size="sm" /> : statusInfo.icon}
|
||||
>
|
||||
{statusInfo.action === 'publish' && 'Publish to Site'}
|
||||
{statusInfo.action === 'retry' && 'Retry'}
|
||||
{statusInfo.action === 'view' && 'View on Site'}
|
||||
{statusInfo.action === 'wait' && statusInfo.label}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const renderStatusChip = () => {
|
||||
const renderStatusBadge = () => {
|
||||
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'
|
||||
}}
|
||||
/>
|
||||
<Badge
|
||||
tone={statusInfo.badgeTone}
|
||||
variant="outline"
|
||||
startIcon={statusInfo.icon}
|
||||
className={wpStatus?.wordpress_post_url ? 'cursor-pointer' : ''}
|
||||
>
|
||||
<span
|
||||
onClick={() => {
|
||||
if (wpStatus?.wordpress_post_url) {
|
||||
window.open(wpStatus.wordpress_post_url, '_blank');
|
||||
}
|
||||
}}
|
||||
>
|
||||
{statusInfo.label}
|
||||
</span>
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<div className="flex items-center gap-2">
|
||||
{renderButton()}
|
||||
{renderStatusChip()}
|
||||
{renderStatusBadge()}
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mt: 1 }}>
|
||||
{error}
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setError(null)}
|
||||
sx={{ ml: 1 }}
|
||||
>
|
||||
×
|
||||
</IconButton>
|
||||
</Alert>
|
||||
<div className="mt-2">
|
||||
<Alert
|
||||
variant="error"
|
||||
title="Publishing Error"
|
||||
message={error}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Publish Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={publishDialogOpen}
|
||||
{/* Publish Confirmation Modal */}
|
||||
<Modal
|
||||
isOpen={publishDialogOpen}
|
||||
onClose={() => setPublishDialogOpen(false)}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
className="max-w-md p-6"
|
||||
>
|
||||
<DialogTitle>Publish to Site</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
Are you sure you want to publish "<strong>{contentTitle}</strong>" to your site?
|
||||
</Typography>
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Publish to Site
|
||||
</h2>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 2 }}>
|
||||
<p className="text-gray-700 dark:text-gray-300">
|
||||
Are you sure you want to publish "<strong>{contentTitle}</strong>" to your site?
|
||||
</p>
|
||||
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
This will create a new post on your connected site with all content,
|
||||
images, categories, and SEO metadata.
|
||||
</Typography>
|
||||
</p>
|
||||
|
||||
{imageGenerationStatus === 'complete' && (
|
||||
<Alert severity="success" sx={{ mt: 2 }}>
|
||||
✓ Images are generated and ready for publishing
|
||||
</Alert>
|
||||
<Alert
|
||||
variant="success"
|
||||
title="Ready to Publish"
|
||||
message="Images are generated and ready for publishing"
|
||||
/>
|
||||
)}
|
||||
|
||||
{imageGenerationStatus !== 'complete' && showOnlyIfImagesReady && (
|
||||
<Alert severity="warning" sx={{ mt: 2 }}>
|
||||
Images are still being generated. Please wait before publishing.
|
||||
</Alert>
|
||||
<Alert
|
||||
variant="warning"
|
||||
title="Images Not Ready"
|
||||
message="Images are still being generated. Please wait before publishing."
|
||||
/>
|
||||
)}
|
||||
|
||||
{wpStatus?.wordpress_sync_status === 'success' && (
|
||||
<Alert severity="info" sx={{ mt: 2 }}>
|
||||
This content is already published to your site.
|
||||
You can force republish to update the existing post.
|
||||
</Alert>
|
||||
<Alert
|
||||
variant="info"
|
||||
title="Already Published"
|
||||
message="This content is already published to your site. You can force republish to update the existing post."
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setPublishDialogOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
{wpStatus?.wordpress_sync_status === 'success' && (
|
||||
|
||||
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<Button
|
||||
onClick={() => handlePublishToWordPress(true)}
|
||||
color="warning"
|
||||
disabled={loading}
|
||||
variant="outline"
|
||||
tone="neutral"
|
||||
onClick={() => setPublishDialogOpen(false)}
|
||||
>
|
||||
Force Republish
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => handlePublishToWordPress(false)}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Publishing...' : 'Publish'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
|
||||
{wpStatus?.wordpress_sync_status === 'success' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
tone="warning"
|
||||
onClick={() => handlePublishToWordPress(true)}
|
||||
disabled={loading}
|
||||
>
|
||||
Force Republish
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
onClick={() => handlePublishToWordPress(false)}
|
||||
disabled={loading}
|
||||
startIcon={loading ? <Spinner size="sm" /> : <PaperPlaneIcon className="h-4 w-4" />}
|
||||
>
|
||||
{loading ? 'Publishing...' : 'Publish'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user