final polish phase 1

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-27 21:27:37 +00:00
parent 627938aa95
commit 5f9a4b8dca
25 changed files with 3286 additions and 1397 deletions

View File

@@ -19,6 +19,7 @@ import ConfigModal from '../../components/Automation/ConfigModal';
import RunHistory from '../../components/Automation/RunHistory';
import CurrentProcessingCard from '../../components/Automation/CurrentProcessingCard';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import ComponentCard from '../../components/common/ComponentCard';
import EnhancedMetricCard from '../../components/dashboard/EnhancedMetricCard';
import DebugSiteSelector from '../../components/common/DebugSiteSelector';
@@ -379,49 +380,34 @@ const AutomationPage: React.FC = () => {
return (
<>
<PageMeta title="Content Automation | IGNY8" description="Automatically create and publish content on your schedule" />
<PageHeader
title="Automation"
description="Automatically create and publish content on your schedule"
badge={{ icon: <BoltIcon />, color: 'teal' }}
parent="Automation"
/>
<div className="space-y-6">
{/* Header */}
<div className="relative flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center size-10 rounded-xl bg-gradient-to-br from-teal-500 to-teal-600">
<BoltIcon className="text-white size-5" />
{/* Compact Ready-to-Run card (header) - absolutely centered in header */}
<div className="flex justify-center">
<div className={`w-full max-w-sm rounded-lg border-2 p-2 transition-all flex items-center gap-3 shadow-sm
${currentRun?.status === 'running' ? 'border-blue-500 bg-blue-50' : currentRun?.status === 'paused' ? 'border-amber-500 bg-amber-50' : totalPending > 0 ? 'border-success-500 bg-success-50' : 'border-slate-300 bg-slate-50'}`}>
<div className={`size-9 rounded-lg flex items-center justify-center flex-shrink-0
${currentRun?.status === 'running' ? 'bg-gradient-to-br from-blue-500 to-blue-600' : currentRun?.status === 'paused' ? 'bg-gradient-to-br from-amber-500 to-amber-600' : totalPending > 0 ? 'bg-gradient-to-br from-success-500 to-success-600' : 'bg-gradient-to-br from-slate-400 to-slate-500'}`}>
{!currentRun && totalPending > 0 ? <CheckCircleIcon className="size-4 text-white" /> : currentRun?.status === 'running' ? <BoltIcon className="size-4 text-white" /> : currentRun?.status === 'paused' ? <ClockIcon className="size-4 text-white" /> : <BoltIcon className="size-4 text-white" />}
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-semibold text-slate-900 dark:text-white truncate">
{currentRun?.status === 'running' && `Running - Stage ${currentRun.current_stage}/7`}
{currentRun?.status === 'paused' && 'Paused'}
{!currentRun && totalPending > 0 && 'Ready to Run'}
{!currentRun && totalPending === 0 && 'No Items Pending'}
</div>
<div>
<h2 className="text-2xl font-bold text-gray-800 dark:text-white/90">Automation</h2>
{activeSite && (
<p className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
Site: <span className="font-medium text-brand-600 dark:text-brand-400">{activeSite.name}</span>
</p>
)}
<div className="text-xs text-slate-600 dark:text-gray-400 truncate">
{currentRun ? `Started: ${new Date(currentRun.started_at).toLocaleTimeString()}` : (totalPending > 0 ? `${totalPending} items in pipeline` : 'All stages clear')}
</div>
</div>
</div>
{/* Compact Ready-to-Run card (header) - absolutely centered in header */}
<div className="hidden sm:flex absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10">
<div className={`w-full max-w-sm rounded-lg border-2 p-2 transition-all flex items-center gap-3 shadow-sm
${currentRun?.status === 'running' ? 'border-blue-500 bg-blue-50' : currentRun?.status === 'paused' ? 'border-amber-500 bg-amber-50' : totalPending > 0 ? 'border-success-500 bg-success-50' : 'border-slate-300 bg-slate-50'}`}>
<div className={`size-9 rounded-lg flex items-center justify-center flex-shrink-0
${currentRun?.status === 'running' ? 'bg-gradient-to-br from-blue-500 to-blue-600' : currentRun?.status === 'paused' ? 'bg-gradient-to-br from-amber-500 to-amber-600' : totalPending > 0 ? 'bg-gradient-to-br from-success-500 to-success-600' : 'bg-gradient-to-br from-slate-400 to-slate-500'}`}>
{!currentRun && totalPending > 0 ? <CheckCircleIcon className="size-4 text-white" /> : currentRun?.status === 'running' ? <BoltIcon className="size-4 text-white" /> : currentRun?.status === 'paused' ? <ClockIcon className="size-4 text-white" /> : <BoltIcon className="size-4 text-white" />}
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-semibold text-slate-900 dark:text-white truncate">
{currentRun?.status === 'running' && `Running - Stage ${currentRun.current_stage}/7`}
{currentRun?.status === 'paused' && 'Paused'}
{!currentRun && totalPending > 0 && 'Ready to Run'}
{!currentRun && totalPending === 0 && 'No Items Pending'}
</div>
<div className="text-xs text-slate-600 dark:text-gray-400 truncate">
{currentRun ? `Started: ${new Date(currentRun.started_at).toLocaleTimeString()}` : (totalPending > 0 ? `${totalPending} items in pipeline` : 'All stages clear')}
</div>
</div>
</div>
</div>
<DebugSiteSelector />
</div>
{/* Compact Schedule & Controls Panel */}

File diff suppressed because it is too large Load Diff

View File

@@ -7,13 +7,15 @@ import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import PageMeta from '../../components/common/PageMeta';
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 { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI, fetchSiteSectors } from '../../services/api';
import SiteSetupChecklist from '../../components/sites/SiteSetupChecklist';
import { integrationApi } from '../../services/integration.api';
import SiteConfigWidget from '../../components/dashboard/SiteConfigWidget';
import OperationsCostsWidget from '../../components/dashboard/OperationsCostsWidget';
import CreditAvailabilityWidget from '../../components/dashboard/CreditAvailabilityWidget';
import { useBillingStore } from '../../store/billingStore';
import {
FileIcon,
PlugInIcon,
@@ -21,7 +23,6 @@ import {
BoltIcon,
PageIcon,
ArrowRightIcon,
ArrowUpIcon
} from '../../icons';
interface Site {
@@ -42,28 +43,46 @@ interface Site {
interface SiteSetupState {
hasIndustry: boolean;
hasSectors: boolean;
sectorsCount: number;
hasWordPressIntegration: boolean;
hasKeywords: boolean;
keywordsCount: number;
hasAuthorProfiles: boolean;
authorProfilesCount: number;
}
interface OperationStat {
type: 'clustering' | 'ideas' | 'content' | 'images';
count: number;
creditsUsed: number;
avgCreditsPerOp: number;
}
export default function SiteDashboard() {
const { id: siteId } = useParams<{ id: string }>();
const navigate = useNavigate();
const toast = useToast();
const { balance, loadBalance } = useBillingStore();
const [site, setSite] = useState<Site | null>(null);
const [setupState, setSetupState] = useState<SiteSetupState>({
hasIndustry: false,
hasSectors: false,
sectorsCount: 0,
hasWordPressIntegration: false,
hasKeywords: false,
keywordsCount: 0,
hasAuthorProfiles: false,
authorProfilesCount: 0,
});
const [operations, setOperations] = useState<OperationStat[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (siteId) {
loadSiteData();
loadBalance();
}
}, [siteId]);
}, [siteId, loadBalance]);
const loadSiteData = async () => {
try {
@@ -79,9 +98,11 @@ export default function SiteDashboard() {
// Load sectors
let hasSectors = false;
let sectorsCount = 0;
try {
const sectors = await fetchSiteSectors(Number(siteId));
hasSectors = sectors && sectors.length > 0;
sectorsCount = sectors?.length || 0;
} catch (err) {
console.log('Could not load sectors');
}
@@ -97,20 +118,47 @@ export default function SiteDashboard() {
// Check keywords - try to load keywords for this site
let hasKeywords = false;
let keywordsCount = 0;
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;
keywordsCount = keywordsData?.count || 0;
} catch (err) {
// No keywords is fine
}
// Check author profiles
let hasAuthorProfiles = false;
let authorProfilesCount = 0;
try {
const authorsData = await fetchAPI(`/v1/thinker/author-profiles/?site_id=${siteId}&page_size=1`);
hasAuthorProfiles = authorsData?.count > 0;
authorProfilesCount = authorsData?.count || 0;
} catch (err) {
// No profiles is fine
}
setSetupState({
hasIndustry,
hasSectors,
sectorsCount,
hasWordPressIntegration,
hasKeywords,
keywordsCount,
hasAuthorProfiles,
authorProfilesCount,
});
// Load operation stats (mock data for now - would come from backend)
// In real implementation, fetch from /api/v1/dashboard/site/{siteId}/operations/
const mockOperations: OperationStat[] = [
{ type: 'clustering', count: 8, creditsUsed: 80, avgCreditsPerOp: 10 },
{ type: 'ideas', count: 12, creditsUsed: 24, avgCreditsPerOp: 2 },
{ type: 'content', count: 28, creditsUsed: 1400, avgCreditsPerOp: 50 },
{ type: 'images', count: 45, creditsUsed: 225, avgCreditsPerOp: 5 },
];
setOperations(mockOperations);
}
} catch (error: any) {
toast.error(`Failed to load site data: ${error.message}`);
@@ -185,6 +233,28 @@ export default function SiteDashboard() {
/>
</div>
{/* Site Insights - 3 Column Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<SiteConfigWidget
setupState={{
hasIndustry: setupState.hasIndustry,
sectorsCount: setupState.sectorsCount,
hasWordPressIntegration: setupState.hasWordPressIntegration,
keywordsCount: setupState.keywordsCount,
authorProfilesCount: setupState.authorProfilesCount
}}
siteId={Number(siteId)}
/>
<OperationsCostsWidget operations={operations} siteId={Number(siteId)} />
<CreditAvailabilityWidget
availableCredits={balance?.credits_remaining ?? 0}
totalCredits={balance?.plan_credits_per_month ?? 0}
loading={loading}
/>
</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">

View File

@@ -17,6 +17,8 @@ import SelectDropdown from '../../components/form/SelectDropdown';
import Label from '../../components/form/Label';
import Checkbox from '../../components/form/input/Checkbox';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import { BoxCubeIcon } from '../../icons';
type TabType = 'content' | 'publishing' | 'images';
@@ -325,19 +327,16 @@ export default function ContentSettingsPage() {
return (
<div className="p-6">
<PageMeta title="Content Settings" description="Configure your content generation settings" />
{/* Page Header */}
<div className="mb-6">
<div className="text-sm text-gray-500 dark:text-gray-400 mb-1">
Content Settings / {tabTitles[activeTab]}
</div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">{tabTitles[activeTab]}</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
{activeTab === 'content' && 'Customize how your articles are written'}
{activeTab === 'publishing' && 'Configure automatic publishing settings'}
{activeTab === 'images' && 'Set up AI image generation preferences'}
</p>
</div>
<PageHeader
title={tabTitles[activeTab]}
description={
activeTab === 'content' ? 'Customize how your articles are written' :
activeTab === 'publishing' ? 'Configure automatic publishing settings' :
'Set up AI image generation preferences'
}
badge={{ icon: <BoxCubeIcon />, color: 'blue' }}
parent="Content Settings"
/>
{/* Tab Content */}
<div className="mt-6">