test if works or revert

This commit is contained in:
alorig
2025-11-29 11:23:42 +05:00
parent 0b3830c891
commit e9e0de40d0
10 changed files with 422 additions and 85 deletions

View File

@@ -210,8 +210,8 @@ export default function App() {
</Suspense>
} />
{/* Writer Module - Redirect dashboard to content */}
<Route path="/writer" element={<Navigate to="/writer/content" replace />} />
{/* Writer Module - Redirect dashboard to tasks */}
<Route path="/writer" element={<Navigate to="/writer/tasks" replace />} />
<Route path="/writer/tasks" element={
<Suspense fallback={null}>
<ModuleGuard module="writer">

View File

@@ -59,29 +59,8 @@ export default function ContentImageCell({ image, maxPromptLength = 100 }: Conte
);
}
const prompt = image.prompt || '';
const shouldTruncate = prompt.length > maxPromptLength;
const displayPrompt = showFullPrompt || !shouldTruncate ? prompt : `${prompt.substring(0, maxPromptLength)}...`;
return (
<div className="space-y-2">
{/* Prompt Text */}
{prompt && (
<div className="text-sm">
<p className="text-gray-700 dark:text-gray-300">
{displayPrompt}
{shouldTruncate && (
<button
onClick={() => setShowFullPrompt(!showFullPrompt)}
className="ml-1 text-brand-500 hover:text-brand-600 text-xs"
>
{showFullPrompt ? 'Show less' : 'Show more'}
</button>
)}
</p>
</div>
)}
{/* Image Display */}
<div className="relative">
{image.status === 'pending' && (

View File

@@ -83,8 +83,10 @@ export const createContentPageConfig = (
setStatusFilter: (value: string) => void;
setCurrentPage: (page: number) => void;
onViewContent?: (row: Content) => void;
enableToggleContent?: boolean; // If false, do not add toggleable content column behavior
}
): ContentPageConfig => {
const enableToggle = handlers.enableToggleContent !== false;
const showSectorColumn = !handlers.activeSector;
const statusColors: Record<string, 'warning' | 'info' | 'success' | 'primary'> = {
@@ -99,9 +101,9 @@ export const createContentPageConfig = (
...titleColumn,
sortable: true,
sortField: 'title',
toggleable: true,
toggleContentKey: 'content_html',
toggleContentLabel: 'Generated Content',
toggleable: enableToggle,
toggleContentKey: enableToggle ? 'content_html' : undefined,
toggleContentLabel: enableToggle ? 'Generated Content' : undefined,
render: (value: string, row: Content) => (
<div>
{handlers.onViewContent ? (

View File

@@ -4,6 +4,7 @@
*/
import { Content } from '../../services/api';
import { Link } from 'react-router-dom';
import Badge from '../../components/ui/badge/Badge';
import { formatRelativeDate } from '../../utils/date';
import { CheckCircleIcon } from '../../icons';
@@ -56,44 +57,61 @@ export function createReviewPageConfig(params: {
const showSectorColumn = !params.activeSector;
const columns: ColumnConfig[] = [
{
key: 'categories',
label: 'Categories',
sortable: false,
width: '180px',
render: (_value: any, row: Content) => {
const categories = row.taxonomy_terms_data?.filter((t: any) => t.taxonomy_type === 'category') || [];
if (!categories.length) return <span className="text-gray-400 dark:text-gray-500">-</span>;
return (
<div className="flex flex-wrap gap-1">
{categories.map((cat: any) => (
<span key={cat.id} className="px-2 py-0.5 bg-purple-50 dark:bg-purple-900/20 text-purple-700 dark:text-purple-300 rounded-full text-xs font-medium">{cat.name}</span>
))}
</div>
);
},
toggleable: true,
defaultVisible: false,
},
{
key: 'tags',
label: 'Tags',
sortable: false,
width: '180px',
render: (_value: any, row: Content) => {
const tags = row.taxonomy_terms_data?.filter((t: any) => t.taxonomy_type === 'tag') || [];
if (!tags.length) return <span className="text-gray-400 dark:text-gray-500">-</span>;
return (
<div className="flex flex-wrap gap-1">
{tags.map((tag: any) => (
<span key={tag.id} className="px-2 py-0.5 bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300 rounded-full text-xs font-medium">{tag.name}</span>
))}
</div>
);
},
toggleable: true,
defaultVisible: false,
},
// Title first, then categories and tags (moved after title per change request)
{
key: 'title',
label: 'Title',
sortable: true,
sortField: 'title',
toggleable: true,
toggleContentKey: 'content_html',
toggleContentLabel: 'Generated Content',
render: (value: string, row: Content) => (
<div className="flex items-center gap-2">
<Link to={`/writer/content/${row.id}`} className="font-medium text-gray-900 dark:text-white hover:underline">
{value || `Content #${row.id}`}
</Link>
</div>
),
},
{
key: 'categories',
label: 'Categories',
sortable: false,
width: '180px',
render: (_value: any, row: Content) => {
const categories = row.taxonomy_terms_data?.filter((t: any) => t.taxonomy_type === 'category') || [];
if (!categories.length) return <span className="text-gray-400 dark:text-gray-500">-</span>;
return (
<div className="flex flex-wrap gap-1">
{categories.map((cat: any) => (
<span key={cat.id} className="px-2 py-0.5 bg-purple-50 dark:bg-purple-900/20 text-purple-700 dark:text-purple-300 rounded-full text-xs font-medium">{cat.name}</span>
))}
</div>
);
},
toggleable: false,
defaultVisible: true,
},
{
key: 'tags',
label: 'Tags',
sortable: false,
width: '180px',
render: (_value: any, row: Content) => {
const tags = row.taxonomy_terms_data?.filter((t: any) => t.taxonomy_type === 'tag') || [];
if (!tags.length) return <span className="text-gray-400 dark:text-gray-500">-</span>;
return (
<div className="flex flex-wrap gap-1">
{tags.map((tag: any) => (
<span key={tag.id} className="px-2 py-0.5 bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300 rounded-full text-xs font-medium">{tag.name}</span>
))}
</div>
);
},
toggleable: false,
defaultVisible: true,
},
{
key: 'title',
label: 'Title',

View File

@@ -0,0 +1,24 @@
import { useMemo } from 'react';
import { useSettingsStore } from '../store/settingsStore';
// Simple client-side feature flag hook backed by accountSettings
export function useFeatureFlag(key: string): boolean {
const accountSettings = useSettingsStore((s) => s.accountSettings);
const setting = accountSettings?.[key];
const enabled = useMemo(() => {
if (!setting || !setting.config) return false;
try {
// Expect config to be boolean or { enabled: boolean }
if (typeof setting.config === 'boolean') return Boolean(setting.config);
if (typeof setting.config === 'object' && setting.config !== null && 'enabled' in setting.config) {
return Boolean((setting.config as any).enabled);
}
return Boolean(setting.config);
} catch {
return false;
}
}, [setting]);
return enabled;
}

View File

@@ -130,7 +130,7 @@ const AppSidebar: React.FC = () => {
workflowItems.push({
icon: <TaskIcon />,
name: "Writer",
path: "/writer/content", // Default to content, submenus shown as in-page navigation
path: "/writer/tasks", // Default to tasks (changed from content)
});
}

View File

@@ -20,7 +20,6 @@ import { useSectorStore } from '../../store/sectorStore';
import { usePageSizeStore } from '../../store/pageSizeStore';
import ProgressModal from '../../components/common/ProgressModal';
import { useProgressModal } from '../../hooks/useProgressModal';
import ContentViewerModal from '../../components/common/ContentViewerModal';
import PageHeader from '../../components/common/PageHeader';
import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs';
import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter';
@@ -50,9 +49,6 @@ export default function Content() {
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [showContent, setShowContent] = useState(false);
// Content viewer modal state
const [isViewerModalOpen, setIsViewerModalOpen] = useState(false);
const [viewerContent, setViewerContent] = useState<ContentType | null>(null);
// Progress modal for AI functions
const progressModal = useProgressModal();
@@ -138,11 +134,10 @@ export default function Content() {
setCurrentPage(1);
};
// Handle view content
// Handle view content - navigate to content view instead of opening modal
const handleViewContent = useCallback((row: ContentType) => {
setViewerContent(row);
setIsViewerModalOpen(true);
}, []);
navigate(`/writer/content/${row.id}`);
}, [navigate]);
// Create page config
const pageConfig = useMemo(() => {
@@ -154,6 +149,7 @@ export default function Content() {
setStatusFilter,
setCurrentPage,
onViewContent: handleViewContent,
enableToggleContent: false, // Disable dropdown toggle on this page; open full view instead
});
}, [
activeSector,
@@ -300,16 +296,7 @@ export default function Content() {
}}
/>
{/* Content Viewer Modal */}
<ContentViewerModal
isOpen={isViewerModalOpen}
onClose={() => {
setIsViewerModalOpen(false);
setViewerContent(null);
}}
title={viewerContent?.title || 'Content'}
contentHtml={viewerContent?.content_html || ''}
/>
{/* Content view opens in its own route; modal removed */}
{/* Progress Modal for AI Functions */}
<ProgressModal

View File

@@ -16,7 +16,10 @@
*/
import React, { useEffect, useMemo, useState } from 'react';
import { Content, fetchImages, ImageRecord } from '../services/api';
import { useNavigate } from 'react-router-dom';
import { Content, fetchImages, ImageRecord, publishContent, generateImagePrompts } from '../services/api';
import { useToast } from '../components/ui/toast/ToastContainer';
import { useFeatureFlag } from '../hooks/useFeatureFlag';
import { ArrowLeftIcon, CalendarIcon, TagIcon, FileTextIcon, CheckCircleIcon, XCircleIcon, ClockIcon } from '../icons';
interface ContentViewTemplateProps {
@@ -591,6 +594,59 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
const shouldShowFeaturedBlock = imagesLoading || Boolean(resolvedFeaturedImage);
const navigate = useNavigate();
const toast = useToast();
const [publishing, setPublishing] = useState(false);
const [generatingImages, setGeneratingImages] = useState(false);
const featureEnabled = useFeatureFlag('feature.content_manager_refactor');
const handleEditContent = () => {
const siteId = content?.site ?? content?.site_id ?? null;
if (!siteId || !content?.id) {
toast.error('Site or content id missing');
return;
}
navigate(`/sites/${siteId}/posts/${content.id}/edit`);
};
const handleGenerateImages = async () => {
if (!content?.id) return;
try {
setGeneratingImages(true);
const result = await generateImagePrompts([content.id]);
if (result && result.success) {
toast.success('Image generation started');
// If async task_id returned, open progress modal elsewhere; refresh images after short delay
setTimeout(() => window.location.reload(), 1500);
} else {
toast.error(result?.error || 'Failed to start image generation');
}
} catch (e: any) {
toast.error(`Failed to generate images: ${e?.message || e}`);
} finally {
setGeneratingImages(false);
}
};
const handlePublish = async () => {
if (!content?.id) return;
try {
setPublishing(true);
const result = await publishContent(content.id);
if (result && (result.external_url || result.external_id)) {
toast.success('Content published successfully');
// Reload to show updated external_id/status
setTimeout(() => window.location.reload(), 800);
} else {
toast.error('Failed to publish content');
}
} catch (e: any) {
toast.error(`Publish failed: ${e?.message || e}`);
} finally {
setPublishing(false);
}
};
if (loading) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
@@ -677,7 +733,7 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
{/* Header Section */}
<div className="bg-gradient-to-r from-brand-500 to-brand-600 px-8 py-6 text-white">
<div className="flex items-start justify-between gap-4">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-3 mb-3">
<FileTextIcon className="w-6 h-6" />
@@ -694,6 +750,34 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
</p>
)}
</div>
<div className="flex-shrink-0 flex items-center gap-2">
{featureEnabled && (content.status === 'draft' || content.status === 'review') && (
<button
onClick={handleEditContent}
className="inline-flex items-center gap-2 px-3 py-2 bg-white text-gray-700 rounded-lg border border-gray-200 hover:bg-gray-50 transition"
>
Edit content
</button>
)}
{featureEnabled && content.status === 'draft' && (
<button
onClick={handleGenerateImages}
disabled={generatingImages}
className="inline-flex items-center gap-2 px-3 py-2 bg-white text-gray-700 rounded-lg border border-gray-200 hover:bg-gray-50 transition"
>
{generatingImages ? 'Generating...' : 'Generate images'}
</button>
)}
{featureEnabled && content.status === 'review' && (
<button
onClick={handlePublish}
disabled={publishing}
className="inline-flex items-center gap-2 px-3 py-2 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition"
>
{publishing ? 'Publishing...' : 'Publish'}
</button>
)}
</div>
</div>
</div>