Section 2 COmpleted

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-27 02:20:55 +00:00
parent 890e138829
commit add04e2ad5
9 changed files with 527 additions and 574 deletions

View File

@@ -93,7 +93,6 @@ const Sites = lazy(() => import("./pages/Settings/Sites"));
// Sites - Lazy loaded
const SiteList = lazy(() => import("./pages/Sites/List"));
const SiteManage = lazy(() => import("./pages/Sites/Manage"));
const SiteDashboard = lazy(() => import("./pages/Sites/Dashboard"));
const SiteContent = lazy(() => import("./pages/Sites/Content"));
const PageManager = lazy(() => import("./pages/Sites/PageManager"));
@@ -232,7 +231,6 @@ export default function App() {
{/* Sites Management */}
<Route path="/sites" element={<SiteList />} />
<Route path="/sites/manage" element={<SiteManage />} />
<Route path="/sites/:id" element={<SiteDashboard />} />
<Route path="/sites/:id/pages" element={<PageManager />} />
<Route path="/sites/:id/pages/new" element={<PageManager />} />

View File

@@ -0,0 +1,191 @@
/**
* Site Setup Checklist Component
* Displays setup progress for a site to guide users through configuration
*/
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Card } from '../ui/card';
import Button from '../ui/button/Button';
import { CheckLineIcon } from '../../icons';
interface SetupItem {
id: string;
label: string;
completed: boolean;
href: string;
}
interface SiteSetupChecklistProps {
siteId: number;
siteName: string;
hasIndustry: boolean;
hasSectors: boolean;
hasWordPressIntegration: boolean;
hasKeywords: boolean;
compact?: boolean;
}
export default function SiteSetupChecklist({
siteId,
siteName,
hasIndustry,
hasSectors,
hasWordPressIntegration,
hasKeywords,
compact = false,
}: SiteSetupChecklistProps) {
const navigate = useNavigate();
const setupItems: SetupItem[] = [
{
id: 'created',
label: 'Site created',
completed: true, // Always true if this component is rendered
href: `/sites/${siteId}/settings`,
},
{
id: 'industry',
label: 'Industry/Sectors selected',
completed: hasIndustry && hasSectors,
href: `/sites/${siteId}/settings`,
},
{
id: 'wordpress',
label: 'WordPress integration configured',
completed: hasWordPressIntegration,
href: `/sites/${siteId}/settings?tab=integrations`,
},
{
id: 'keywords',
label: 'Keywords added',
completed: hasKeywords,
href: '/setup/add-keywords',
},
];
const completedCount = setupItems.filter((item) => item.completed).length;
const totalCount = setupItems.length;
const isComplete = completedCount === totalCount;
const progressPercent = Math.round((completedCount / totalCount) * 100);
// Find first incomplete item for "Complete Setup" button
const firstIncomplete = setupItems.find((item) => !item.completed);
if (compact) {
// Compact version for list view
return (
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
{setupItems.map((item) => (
<div
key={item.id}
className={`w-2 h-2 rounded-full ${
item.completed
? 'bg-green-500'
: 'bg-gray-300 dark:bg-gray-600'
}`}
title={item.label}
/>
))}
</div>
<span className="text-xs text-gray-500 dark:text-gray-400">
{completedCount}/{totalCount}
</span>
{isComplete && (
<span className="text-xs text-green-600 dark:text-green-400 font-medium">
Ready
</span>
)}
</div>
);
}
// Full version for dashboard
return (
<Card className="p-5">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Site Setup Progress
</h3>
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
{progressPercent}% complete
</span>
</div>
{/* Progress bar */}
<div className="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full mb-4">
<div
className={`h-full rounded-full transition-all duration-500 ${
isComplete ? 'bg-green-500' : 'bg-blue-500'
}`}
style={{ width: `${progressPercent}%` }}
/>
</div>
{/* Checklist items */}
<div className="space-y-2 mb-4">
{setupItems.map((item) => (
<button
key={item.id}
onClick={() => navigate(item.href)}
className="w-full flex items-center gap-3 p-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors text-left"
>
<div
className={`flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center ${
item.completed
? 'bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400'
: 'bg-gray-100 dark:bg-gray-800 text-gray-400 dark:text-gray-500'
}`}
>
{item.completed ? (
<CheckLineIcon className="w-3 h-3" />
) : (
<span className="w-2 h-2 rounded-full bg-current" />
)}
</div>
<span
className={`text-sm ${
item.completed
? 'text-gray-600 dark:text-gray-400'
: 'text-gray-900 dark:text-white font-medium'
}`}
>
{item.label}
</span>
</button>
))}
</div>
{/* Action button */}
{isComplete ? (
<div className="flex items-center gap-2 p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
<CheckLineIcon className="w-5 h-5 text-green-600 dark:text-green-400" />
<span className="text-sm font-medium text-green-700 dark:text-green-300">
Ready to create content!
</span>
<Button
size="sm"
variant="primary"
className="ml-auto"
onClick={() => navigate('/planner/keywords')}
>
Start Planning
</Button>
</div>
) : firstIncomplete ? (
<Button
variant="primary"
className="w-full"
onClick={() => navigate(firstIncomplete.href)}
endIcon={
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
}
>
Complete Setup
</Button>
) : null}
</Card>
);
}

View File

@@ -6,6 +6,7 @@
*/
import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import { useToast } from '../../components/ui/toast/ToastContainer';
@@ -60,6 +61,14 @@ export default function IndustriesSectorsKeywords() {
const [searchTerm, setSearchTerm] = useState('');
const [countryFilter, setCountryFilter] = useState('');
const [difficultyFilter, setDifficultyFilter] = useState('');
const [showNotAddedOnly, setShowNotAddedOnly] = useState(false);
// Keyword count tracking
const [addedCount, setAddedCount] = useState(0);
const [availableCount, setAvailableCount] = useState(0);
// Navigation
const navigate = useNavigate();
// Import modal state
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
@@ -183,6 +192,17 @@ export default function IndustriesSectorsKeywords() {
};
});
// Calculate counts before applying filters
const totalAdded = filteredResults.filter(sk => sk.isAdded).length;
const totalAvailable = filteredResults.filter(sk => !sk.isAdded).length;
setAddedCount(totalAdded);
setAvailableCount(totalAvailable);
// Apply "not yet added" filter
if (showNotAddedOnly) {
filteredResults = filteredResults.filter(sk => !sk.isAdded);
}
// Apply difficulty filter
if (difficultyFilter) {
const difficultyNum = parseInt(difficultyFilter);
@@ -245,8 +265,10 @@ export default function IndustriesSectorsKeywords() {
setSeedKeywords([]);
setTotalCount(0);
setTotalPages(1);
setAddedCount(0);
setAvailableCount(0);
}
}, [activeSite, activeSector, currentPage, pageSize, searchTerm, countryFilter, difficultyFilter, sortBy, sortDirection, toast]);
}, [activeSite, activeSector, currentPage, pageSize, searchTerm, countryFilter, difficultyFilter, showNotAddedOnly, sortBy, sortDirection, toast]);
// Load data on mount and when filters change
useEffect(() => {
@@ -598,6 +620,15 @@ export default function IndustriesSectorsKeywords() {
{ value: '5', label: '5 - Very Hard' },
],
},
{
key: 'showNotAddedOnly',
label: 'Status',
type: 'select' as const,
options: [
{ value: '', label: 'All Keywords' },
{ value: 'true', label: 'Not Yet Added Only' },
],
},
],
bulkActions: !activeSector ? [] : [
{
@@ -673,6 +704,45 @@ export default function IndustriesSectorsKeywords() {
</div>
)}
{/* Keyword Count Summary & Next Step CTA */}
{activeSite && activeSector && (
<div className="mx-6 mt-6 mb-4">
<div className="flex flex-wrap items-center justify-between gap-4 p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
<span className="text-green-600 dark:text-green-400 font-bold">{addedCount}</span> keywords in your workflow
</span>
</div>
<div className="w-px h-5 bg-gray-300 dark:bg-gray-600 hidden sm:block" />
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
<span className="text-blue-600 dark:text-blue-400 font-bold">{availableCount}</span> available to add
</span>
</div>
</div>
{addedCount > 0 && (
<Button
variant="success"
size="sm"
onClick={() => navigate('/planner/keywords')}
endIcon={
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
}
>
Next: Plan Your Content
</Button>
)}
</div>
{/* Coming Soon Teaser */}
<p className="text-xs text-gray-500 dark:text-gray-400 mt-2 text-center">
Looking for more keywords? Keyword Research coming soon!
</p>
</div>
)}
<TablePageTemplate
columns={pageConfig.columns}
data={seedKeywords}
@@ -683,6 +753,7 @@ export default function IndustriesSectorsKeywords() {
search: searchTerm,
country: countryFilter,
difficulty: difficultyFilter,
showNotAddedOnly: showNotAddedOnly ? 'true' : '',
}}
onFilterChange={(key, value) => {
const stringValue = value === null || value === undefined ? '' : String(value);
@@ -695,6 +766,9 @@ export default function IndustriesSectorsKeywords() {
} else if (key === 'difficulty') {
setDifficultyFilter(stringValue);
setCurrentPage(1);
} else if (key === 'showNotAddedOnly') {
setShowNotAddedOnly(stringValue === 'true');
setCurrentPage(1);
}
}}
onBulkAction={async (actionKey: string, ids: string[]) => {

View File

@@ -10,21 +10,18 @@ import PageHeader from '../../components/common/PageHeader';
import ComponentCard from '../../components/common/ComponentCard';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import EnhancedMetricCard from '../../components/dashboard/EnhancedMetricCard';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
// import { fetchSiteBlueprints } from '../../services/api';
import SiteProgressWidget from '../../components/sites/SiteProgressWidget';
import { fetchAPI, fetchSiteSectors } from '../../services/api';
import SiteSetupChecklist from '../../components/sites/SiteSetupChecklist';
import { integrationApi } from '../../services/integration.api';
import {
EyeIcon,
FileIcon,
PlugInIcon,
ArrowUpIcon,
CalendarIcon,
GridIcon,
BoltIcon,
PageIcon,
ArrowRightIcon
ArrowRightIcon,
ArrowUpIcon
} from '../../icons';
interface Site {
@@ -38,19 +35,15 @@ interface Site {
created_at: string;
updated_at: string;
domain?: string;
industry?: string;
industry_name?: string;
}
interface SiteStats {
total_pages: number;
published_pages: number;
draft_pages: number;
total_content: number;
published_content: number;
integrations_count: number;
deployments_count: number;
last_deployment?: string;
views_count?: number;
visitors_count?: number;
interface SiteSetupState {
hasIndustry: boolean;
hasSectors: boolean;
hasWordPressIntegration: boolean;
hasKeywords: boolean;
}
export default function SiteDashboard() {
@@ -58,8 +51,12 @@ export default function SiteDashboard() {
const navigate = useNavigate();
const toast = useToast();
const [site, setSite] = useState<Site | null>(null);
const [stats, setStats] = useState<SiteStats | null>(null);
const [blueprints, setBlueprints] = useState<any[]>([]);
const [setupState, setSetupState] = useState<SiteSetupState>({
hasIndustry: false,
hasSectors: false,
hasWordPressIntegration: false,
hasKeywords: false,
});
const [loading, setLoading] = useState(true);
useEffect(() => {
@@ -71,22 +68,49 @@ export default function SiteDashboard() {
const loadSiteData = async () => {
try {
setLoading(true);
const [siteData, statsData] = await Promise.all([
fetchAPI(`/v1/auth/sites/${siteId}/`),
fetchSiteStats(),
// fetchSiteBlueprints({ site_id: Number(siteId) }),
]);
// Load site data
const siteData = await fetchAPI(`/v1/auth/sites/${siteId}/`);
if (siteData) {
setSite(siteData);
}
if (statsData) {
setStats(statsData);
}
if (blueprintsData && blueprintsData.results) {
setBlueprints(blueprintsData.results);
// Check setup state
const hasIndustry = !!siteData.industry || !!siteData.industry_name;
// Load sectors
let hasSectors = false;
try {
const sectors = await fetchSiteSectors(Number(siteId));
hasSectors = sectors && sectors.length > 0;
} catch (err) {
console.log('Could not load sectors');
}
// Check WordPress integration
let hasWordPressIntegration = false;
try {
const wpIntegration = await integrationApi.getWordPressIntegration(Number(siteId));
hasWordPressIntegration = !!wpIntegration;
} catch (err) {
// No integration is fine
}
// Check keywords - try to load keywords for this site
let hasKeywords = false;
try {
const { fetchKeywords } = await import('../../services/api');
const keywordsData = await fetchKeywords({ site_id: Number(siteId), page_size: 1 });
hasKeywords = keywordsData?.results?.length > 0 || keywordsData?.count > 0;
} catch (err) {
// No keywords is fine
}
setSetupState({
hasIndustry,
hasSectors,
hasWordPressIntegration,
hasKeywords,
});
}
} catch (error: any) {
toast.error(`Failed to load site data: ${error.message}`);
@@ -95,25 +119,6 @@ export default function SiteDashboard() {
}
};
const fetchSiteStats = async (): Promise<SiteStats | null> => {
try {
// TODO: Create backend endpoint for site stats
// For now, return mock data structure
return {
total_pages: 0,
published_pages: 0,
draft_pages: 0,
total_content: 0,
published_content: 0,
integrations_count: 0,
deployments_count: 0,
};
} catch (error) {
console.error('Error fetching site stats:', error);
return null;
}
};
if (loading) {
return (
<div className="p-6">
@@ -139,51 +144,6 @@ export default function SiteDashboard() {
);
}
const statCards = [
{
title: 'Total Pages',
value: stats?.total_pages || 0,
icon: <FileIcon />,
accentColor: 'blue' as const,
href: `/sites/${siteId}/pages`,
},
{
title: 'Published Pages',
value: stats?.published_pages || 0,
icon: <GridIcon />,
accentColor: 'success' as const,
href: `/sites/${siteId}/pages?status=published`,
},
{
title: 'Draft Pages',
value: stats?.draft_pages || 0,
icon: <FileIcon />,
accentColor: 'orange' as const,
href: `/sites/${siteId}/pages?status=draft`,
},
{
title: 'Integrations',
value: stats?.integrations_count || 0,
icon: <PlugInIcon />,
accentColor: 'purple' as const,
href: `/sites/${siteId}/settings?tab=integrations`,
},
{
title: 'Deployments',
value: stats?.deployments_count || 0,
icon: <ArrowUpIcon />,
accentColor: 'blue' as const,
href: `/sites/${siteId}/preview`,
},
{
title: 'Total Content',
value: stats?.total_content || 0,
icon: <FileIcon />,
accentColor: 'purple' as const,
href: `/sites/${siteId}/content`,
},
];
return (
<div className="p-6">
<PageMeta title={`${site.name} - Dashboard`} />
@@ -212,34 +172,18 @@ export default function SiteDashboard() {
</div>
</div>
{/* Stage 3: Site Progress Widget */}
{blueprints.length > 0 && (
<div className="mb-6">
{blueprints.map((blueprint) => (
<SiteProgressWidget
key={blueprint.id}
blueprintId={blueprint.id}
siteId={Number(siteId)}
/>
))}
</div>
)}
{/* Stats Grid - Using EnhancedMetricCard */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
{statCards.map((stat, index) => (
<EnhancedMetricCard
key={index}
title={stat.title}
value={stat.value}
icon={stat.icon}
accentColor={stat.accentColor}
href={stat.href}
/>
))}
{/* Site Setup Checklist */}
<div className="mb-6">
<SiteSetupChecklist
siteId={Number(siteId)}
siteName={site.name}
hasIndustry={setupState.hasIndustry}
hasSectors={setupState.hasSectors}
hasWordPressIntegration={setupState.hasWordPressIntegration}
hasKeywords={setupState.hasKeywords}
/>
</div>
{/* Quick Actions */}
<ComponentCard title="Quick Actions" desc="Common site management tasks">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@@ -315,34 +259,14 @@ export default function SiteDashboard() {
</div>
</ComponentCard>
{/* Recent Activity */}
<Card className="p-6">
{/* Recent Activity - Placeholder */}
<Card className="p-6 mt-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Recent Activity
</h2>
<div className="space-y-3">
{stats?.last_deployment ? (
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex-shrink-0 size-10 rounded-lg bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] flex items-center justify-center text-white shadow-md">
<CalendarIcon className="h-5 w-5" />
</div>
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">
Last Deployment
</p>
<p className="text-xs text-gray-600 dark:text-gray-400">
{new Date(stats.last_deployment).toLocaleString()}
</p>
</div>
</div>
) : (
<p className="text-sm text-gray-600 dark:text-gray-400">
No recent activity
</p>
)}
</div>
</Card>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400">
No recent activity
</p>
);
}

View File

@@ -1,238 +0,0 @@
/**
* Site Management Dashboard
* Phase 6: Site Integration & Multi-Destination Publishing
*/
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { PlusIcon, EditIcon, SettingsIcon, EyeIcon, TrashIcon, PlugIcon } from 'lucide-react';
import PageMeta from '../../components/common/PageMeta';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import SiteTypeBadge from '../../components/sites/SiteTypeBadge';
import Badge from '../../components/ui/badge/Badge';
interface Site {
id: number;
name: string;
slug: string;
site_type: string;
hosting_type: string;
status: string;
is_active: boolean;
created_at: string;
updated_at: string;
page_count?: number;
integration_count?: number;
has_wordpress_integration?: boolean;
}
export default function SiteManagement() {
const navigate = useNavigate();
const toast = useToast();
const [sites, setSites] = useState<Site[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadSites();
}, []);
const loadSites = async () => {
try {
setLoading(true);
const data = await fetchAPI('/v1/auth/sites/');
if (data && Array.isArray(data)) {
// Check for WordPress integrations
const sitesWithIntegrations = await Promise.all(
data.map(async (site: Site) => {
if (site.hosting_type === 'wordpress') {
try {
const integrations = await fetchAPI(`/v1/integration/integrations/?site=${site.id}&platform=wordpress`);
return {
...site,
has_wordpress_integration: integrations?.results?.length > 0 || integrations?.length > 0,
};
} catch {
return { ...site, has_wordpress_integration: false };
}
}
return site;
})
);
setSites(sitesWithIntegrations);
}
} catch (error: any) {
toast.error(`Failed to load sites: ${error.message}`);
} finally {
setLoading(false);
}
};
const handleIntegration = (siteId: number) => {
navigate(`/sites/${siteId}/settings?tab=integrations`);
};
const handleCreateSite = () => {
navigate('/sites/builder');
};
const handleEdit = (siteId: number) => {
navigate(`/sites/${siteId}/edit`);
};
const handleSettings = (siteId: number) => {
navigate(`/sites/${siteId}/settings`);
};
const handleView = (siteId: number) => {
navigate(`/sites/${siteId}`);
};
const handleDelete = async (siteId: number) => {
if (!confirm('Are you sure you want to delete this site?')) return;
try {
await fetchAPI(`/v1/auth/sites/${siteId}/`, {
method: 'DELETE',
});
toast.success('Site deleted successfully');
loadSites();
} catch (error: any) {
toast.error(`Failed to delete site: ${error.message}`);
}
};
if (loading) {
return (
<div className="p-6">
<PageMeta title="Site Management" />
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading sites...</div>
</div>
</div>
);
}
return (
<div className="p-6">
<PageMeta title="Site Management - IGNY8" />
<div className="mb-6 flex justify-between items-center">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Site Management
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Manage your sites, pages, and content
</p>
</div>
<Button onClick={handleCreateSite} variant="primary" startIcon={<PlusIcon className="w-4 h-4" />}>
Create New Site
</Button>
</div>
{sites.length === 0 ? (
<Card className="p-12 text-center">
<p className="text-gray-600 dark:text-gray-400 mb-4">
No sites created yet
</p>
<Button onClick={handleCreateSite} variant="primary">
Create Your First Site
</Button>
</Card>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{sites.map((site) => (
<Card key={site.id} className="p-4 hover:shadow-lg transition-shadow">
<div className="space-y-3">
<div className="flex justify-between items-start">
<div className="flex-1">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{site.name}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{site.slug}
</p>
</div>
<span
className={`px-2 py-1 text-xs rounded ${
site.is_active
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
: 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200'
}`}
>
{site.is_active ? 'Active' : 'Inactive'}
</span>
</div>
<div className="flex flex-wrap gap-2 items-center">
<SiteTypeBadge hostingType={site.hosting_type} />
<Badge variant="soft" color="neutral" size="xs">
{site.site_type}
</Badge>
</div>
{/* WordPress Integration Button */}
{site.hosting_type === 'wordpress' && (
<div className="pt-2">
<Button
variant={site.has_wordpress_integration ? "outline" : "primary"}
size="sm"
onClick={() => handleIntegration(site.id)}
className="w-full"
startIcon={<PlugIcon className="w-4 h-4" />}
>
{site.has_wordpress_integration ? 'Manage Integration' : 'Connect WordPress'}
</Button>
</div>
)}
<div className="flex items-center justify-between pt-3 border-t border-gray-200 dark:border-gray-700">
<div className="text-xs text-gray-600 dark:text-gray-400">
{site.page_count || 0} pages
</div>
<div className="flex gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => handleView(site.id)}
title="View"
>
<EyeIcon className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleEdit(site.id)}
title="Edit"
>
<EditIcon className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleSettings(site.id)}
title="Settings"
>
<SettingsIcon className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDelete(site.id)}
title="Delete"
>
<TrashIcon className="w-4 h-4" />
</Button>
</div>
</div>
</div>
</Card>
))}
</div>
)}
</div>
);
}

View File

@@ -194,8 +194,34 @@ export default function ContentSettingsPage() {
});
}
// TODO: Load content generation settings when API is available
// TODO: Load publishing settings when API is available
// Load content generation settings
try {
const contentData = await fetchAPI('/v1/system/settings/content/content_generation/');
if (contentData?.config) {
setContentSettings({
appendToPrompt: contentData.config.append_to_prompt || '',
defaultTone: contentData.config.default_tone || 'professional',
defaultLength: contentData.config.default_length || 'medium',
});
}
} catch (err) {
// Settings may not exist yet, use defaults
console.log('Content generation settings not found, using defaults');
}
// Load publishing settings
try {
const publishData = await fetchAPI('/v1/system/settings/content/publishing/');
if (publishData?.config) {
setPublishingSettings({
autoPublishEnabled: publishData.config.auto_publish_enabled || false,
autoSyncEnabled: publishData.config.auto_sync_enabled || false,
});
}
} catch (err) {
// Settings may not exist yet, use defaults
console.log('Publishing settings not found, using defaults');
}
} catch (error: any) {
console.error('Error loading content settings:', error);
@@ -231,8 +257,32 @@ export default function ContentSettingsPage() {
});
}
// TODO: Save content generation settings when API is available
// TODO: Save publishing settings when API is available
// Save content generation settings
if (activeTab === 'content') {
await fetchAPI('/v1/system/settings/content/content_generation/save/', {
method: 'POST',
body: JSON.stringify({
config: {
append_to_prompt: contentSettings.appendToPrompt,
default_tone: contentSettings.defaultTone,
default_length: contentSettings.defaultLength,
}
}),
});
}
// Save publishing settings
if (activeTab === 'publishing') {
await fetchAPI('/v1/system/settings/content/publishing/save/', {
method: 'POST',
body: JSON.stringify({
config: {
auto_publish_enabled: publishingSettings.autoPublishEnabled,
auto_sync_enabled: publishingSettings.autoSyncEnabled,
}
}),
});
}
toast.success('Settings saved successfully');
} catch (error: any) {