Pre luanch plan phase 1 complete

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-05 03:40:39 +00:00
parent 1f2e734ea2
commit e93ea77c2b
60 changed files with 492 additions and 5215 deletions

View File

@@ -3,11 +3,12 @@ import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import { Card } from '../../components/ui/card';
import { usePageLoading } from '../../context/PageLoadingContext';
export default function AccountSettings() {
const toast = useToast();
const [settings, setSettings] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const { startLoading, stopLoading } = usePageLoading();
useEffect(() => {
loadSettings();
@@ -15,34 +16,28 @@ export default function AccountSettings() {
const loadSettings = async () => {
try {
setLoading(true);
startLoading('Loading account settings...');
const response = await fetchAPI('/v1/system/settings/account/');
setSettings(response.results || []);
} catch (error: any) {
toast.error(`Failed to load account settings: ${error.message}`);
} finally {
setLoading(false);
stopLoading();
}
};
return (
<div className="p-6">
<>
<PageMeta title="Account Settings" />
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Account Settings</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">Manage your account preferences and profile</p>
</div>
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading...</div>
</div>
) : (
<Card className="p-6">
<p className="text-gray-600 dark:text-gray-400">Account settings management interface coming soon.</p>
</Card>
)}
</div>
<Card className="p-6">
<p className="text-gray-600 dark:text-gray-400">Account settings management interface coming soon.</p>
</Card>
</>
);
}

View File

@@ -66,18 +66,18 @@ const CreditsAndBilling: React.FC = () => {
if (loading) {
return (
<div className="p-6">
<>
<PageMeta title="Usage & Billing" description="View your usage and billing" />
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
<p className="mt-4 text-gray-600 dark:text-gray-400">Loading billing data...</p>
</div>
</div>
</>
);
}
return (
<div className="p-6">
<>
<PageMeta title="Usage & Billing" description="View your usage and billing" />
<div className="flex items-center justify-between mb-6">
@@ -243,7 +243,7 @@ const CreditsAndBilling: React.FC = () => {
</div>
</ComponentCard>
)}
</div>
</>
);
};

View File

@@ -1,69 +0,0 @@
import PageMeta from "../../components/common/PageMeta";
import ComponentCard from "../../components/common/ComponentCard";
import { Card } from "../../components/ui/card";
import { DownloadIcon, UploadIcon, DatabaseIcon, FileArchiveIcon, CheckCircleIcon } from '../../icons';
export default function ImportExport() {
return (
<>
<PageMeta title="Import/Export - IGNY8" description="Data management" />
<Card className="p-8">
<div className="text-center py-8 max-w-3xl mx-auto">
<div className="mb-6 flex justify-center">
<div className="p-4 bg-brand-100 dark:bg-brand-900/30 rounded-full">
<DatabaseIcon className="w-12 h-12 text-brand-600 dark:text-brand-400" />
</div>
</div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-3">
Coming Soon: Manage Your Data
</h1>
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8">
Import and Export Your Content - Backup your keywords, articles, and settings. Move your content to other platforms. Download everything safely.
</p>
<div className="bg-gradient-to-br from-brand-50 to-purple-50 dark:from-brand-900/20 dark:to-purple-900/20 rounded-lg p-6 border border-brand-200 dark:border-brand-800">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
What will be available:
</h2>
<div className="space-y-3 text-left">
<div className="flex items-start gap-3">
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
<div className="text-gray-700 dark:text-gray-300">
<strong>Export your keywords as a file</strong> (backup or share)
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
<div className="text-gray-700 dark:text-gray-300">
<strong>Export all your articles</strong> in different formats
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
<div className="text-gray-700 dark:text-gray-300">
<strong>Import keywords from other sources</strong>
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
<div className="text-gray-700 dark:text-gray-300">
<strong>Backup and restore</strong> your entire account
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
<div className="text-gray-700 dark:text-gray-300">
<strong>Download your settings</strong> and configurations
</div>
</div>
</div>
</div>
</div>
</Card>
</>
);
}

View File

@@ -1,14 +1,15 @@
import { useState, useEffect } from 'react';
import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { usePageLoading } from '../../context/PageLoadingContext';
import { fetchIndustries, Industry } from '../../services/api';
import { Card } from '../../components/ui/card';
import Badge from '../../components/ui/badge/Badge';
export default function Industries() {
const toast = useToast();
const { startLoading, stopLoading } = usePageLoading();
const [industries, setIndustries] = useState<Industry[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadIndustries();
@@ -16,49 +17,43 @@ export default function Industries() {
const loadIndustries = async () => {
try {
setLoading(true);
startLoading('Loading industries...');
const response = await fetchIndustries();
setIndustries(response.industries || []);
} catch (error: any) {
toast.error(`Failed to load industries: ${error.message}`);
} finally {
setLoading(false);
stopLoading();
}
};
return (
<div className="p-6">
<>
<PageMeta title="Industries" />
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Industries</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">Manage global industry templates (Admin Only)</p>
</div>
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading...</div>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{industries.map((industry) => (
<Card key={industry.id} className="p-6">
<div className="flex justify-between items-start mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{industry.name}</h3>
<Badge variant="light" color={industry.is_active ? 'success' : 'dark'}>
{industry.is_active ? 'Active' : 'Inactive'}
</Badge>
</div>
{industry.description && (
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">{industry.description}</p>
)}
<p className="text-sm text-gray-500 dark:text-gray-400">
Sectors: {industry.sectors_count || 0}
</p>
</Card>
))}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{industries.map((industry) => (
<Card key={industry.id} className="p-6">
<div className="flex justify-between items-start mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{industry.name}</h3>
<Badge variant="light" color={industry.is_active ? 'success' : 'dark'}>
{industry.is_active ? 'Active' : 'Inactive'}
</Badge>
</div>
{industry.description && (
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">{industry.description}</p>
)}
<p className="text-sm text-gray-500 dark:text-gray-400">
Sectors: {industry.sectors_count || 0}
</p>
</Card>
))}
</div>
</>
);
}

View File

@@ -4,6 +4,7 @@ import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import { PricingPlan } from '../../components/ui/pricing-table';
import PricingTable1 from '../../components/ui/pricing-table/pricing-table-1';
import { usePageLoading } from '../../context/PageLoadingContext';
interface Plan {
id: number;
@@ -110,8 +111,8 @@ const getPlanDescription = (plan: Plan): string => {
export default function Plans() {
const toast = useToast();
const { startLoading, stopLoading } = usePageLoading();
const [plans, setPlans] = useState<Plan[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadPlans();
@@ -119,7 +120,7 @@ export default function Plans() {
const loadPlans = async () => {
try {
setLoading(true);
startLoading('Loading plans...');
const response: PlanResponse = await fetchAPI('/v1/auth/plans/');
// Filter only active plans and sort by price
const activePlans = (response.results || [])
@@ -133,7 +134,7 @@ export default function Plans() {
} catch (error: any) {
toast.error(`Failed to load plans: ${error.message}`);
} finally {
setLoading(false);
stopLoading();
}
};
@@ -148,7 +149,7 @@ export default function Plans() {
);
return (
<div className="p-6">
<>
<PageMeta title="Plans" />
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Plans</h1>
@@ -157,11 +158,7 @@ export default function Plans() {
</p>
</div>
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading plans...</div>
</div>
) : pricingPlans.length === 0 ? (
{pricingPlans.length === 0 ? (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">No active plans available</div>
</div>
@@ -183,6 +180,6 @@ export default function Plans() {
</div>
</>
)}
</div>
</>
);
}

View File

@@ -4,6 +4,7 @@
*/
import React, { useState, useEffect } from 'react';
import PageMeta from '../../components/common/PageMeta';
import { usePageLoading } from '../../context/PageLoadingContext';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import Checkbox from '../../components/form/input/Checkbox';
@@ -16,7 +17,7 @@ import { fetchAPI } from '../../services/api';
export default function Publishing() {
const toast = useToast();
const [loading, setLoading] = useState(true);
const { startLoading, stopLoading } = usePageLoading();
const [saving, setSaving] = useState(false);
const [defaultDestinations, setDefaultDestinations] = useState<string[]>(['sites']);
const [autoPublishEnabled, setAutoPublishEnabled] = useState(false);
@@ -31,7 +32,7 @@ export default function Publishing() {
const loadSettings = async () => {
try {
setLoading(true);
startLoading('Loading publishing settings...');
// TODO: Load from backend API when endpoint is available
// For now, use defaults
setDefaultDestinations(['sites']);
@@ -43,7 +44,7 @@ export default function Publishing() {
} catch (error: any) {
toast.error(`Failed to load settings: ${error.message}`);
} finally {
setLoading(false);
stopLoading();
}
};
@@ -73,19 +74,8 @@ export default function Publishing() {
{ value: 'shopify', label: 'Publish to Shopify (your Shopify store)' },
];
if (loading) {
return (
<div className="p-6">
<PageMeta title="Publishing Settings" />
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading...</div>
</div>
</div>
);
}
return (
<div className="p-6">
<>
<PageMeta title="Publishing Settings - IGNY8" />
<div className="mb-6">
@@ -230,7 +220,7 @@ export default function Publishing() {
</Button>
</div>
</div>
</div>
</>
);
}

View File

@@ -1,325 +0,0 @@
import { useState, useEffect } from "react";
import PageMeta from "../../components/common/PageMeta";
import ComponentCard from "../../components/common/ComponentCard";
import { fetchAPI } from "../../services/api";
interface SystemStatus {
timestamp: string;
system: {
cpu: { usage_percent: number; cores: number; status: string };
memory: { total_gb: number; used_gb: number; available_gb: number; usage_percent: number; status: string };
disk: { total_gb: number; used_gb: number; free_gb: number; usage_percent: number; status: string };
};
database: {
connected: boolean;
version: string;
size: string;
active_connections: number;
status: string;
};
redis: {
connected: boolean;
status: string;
};
celery: {
workers: string[];
worker_count: number;
tasks: { active: number; scheduled: number; reserved: number };
status: string;
};
processes: {
by_stack: {
[key: string]: { count: number; cpu: number; memory_mb: number };
};
};
modules: {
planner: { keywords: number; clusters: number; content_ideas: number };
writer: { tasks: number; images: number };
};
}
const getStatusColor = (status: string) => {
switch (status) {
case 'healthy': return 'text-success-600 dark:text-success-400';
case 'warning': return 'text-warning-600 dark:text-warning-400';
case 'critical': return 'text-error-600 dark:text-error-400';
default: return 'text-gray-600 dark:text-gray-400';
}
};
const getStatusBadge = (status: string) => {
switch (status) {
case 'healthy': return 'bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-400';
case 'warning': return 'bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-400';
case 'critical': return 'bg-error-100 text-error-800 dark:bg-error-900/30 dark:text-error-400';
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400';
}
};
export default function Status() {
const [status, setStatus] = useState<SystemStatus | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchStatus = async () => {
try {
// fetchAPI extracts data from unified format {success: true, data: {...}}
// So response IS the data object
const response = await fetchAPI('/v1/system/status/');
setStatus(response);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchStatus();
const interval = setInterval(fetchStatus, 30000); // Refresh every 30 seconds
return () => clearInterval(interval);
}, []);
if (loading) {
return (
<>
<PageMeta title="System Status - IGNY8" description="System monitoring" />
<ComponentCard title="System Status" desc="Loading system information...">
<div className="text-center py-8">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 dark:border-white mx-auto"></div>
</div>
</ComponentCard>
</>
);
}
if (error || !status) {
return (
<>
<PageMeta title="System Status - IGNY8" description="System monitoring" />
<ComponentCard title="System Status" desc="Error loading system information">
<div className="text-center py-8 text-error-600 dark:text-error-400">
{error || 'Failed to load system status'}
</div>
</ComponentCard>
</>
);
}
return (
<>
<PageMeta title="System Status - IGNY8" description="System monitoring" />
<div className="space-y-6">
{/* System Resources */}
<ComponentCard title="System Resources" desc="CPU, Memory, and Disk Usage">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* CPU */}
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">CPU</span>
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.system?.cpu?.status || 'unknown')}`}>
{status.system?.cpu?.status || 'unknown'}
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-4">
<div
className={`h-4 rounded-full ${
(status.system?.cpu?.usage_percent || 0) < 80 ? 'bg-success-500' :
(status.system?.cpu?.usage_percent || 0) < 95 ? 'bg-warning-500' : 'bg-error-500'
}`}
style={{ width: `${status.system?.cpu?.usage_percent || 0}%` }}
></div>
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
{status.system?.cpu?.usage_percent?.toFixed(1)}% used ({status.system?.cpu?.cores} cores)
</div>
</div>
{/* Memory */}
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Memory</span>
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.system?.memory?.status || 'unknown')}`}>
{status.system?.memory?.status || 'unknown'}
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-4">
<div
className={`h-4 rounded-full ${
(status.system?.memory?.usage_percent || 0) < 80 ? 'bg-success-500' :
(status.system?.memory?.usage_percent || 0) < 95 ? 'bg-warning-500' : 'bg-error-500'
}`}
style={{ width: `${status.system?.memory?.usage_percent || 0}%` }}
></div>
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
{status.system?.memory?.used_gb?.toFixed(1)} GB / {status.system?.memory?.total_gb?.toFixed(1)} GB
</div>
</div>
{/* Disk */}
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Disk</span>
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.system?.disk?.status || 'unknown')}`}>
{status.system?.disk?.status || 'unknown'}
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-4">
<div
className={`h-4 rounded-full ${
(status.system?.disk?.usage_percent || 0) < 80 ? 'bg-success-500' :
(status.system?.disk?.usage_percent || 0) < 95 ? 'bg-warning-500' : 'bg-error-500'
}`}
style={{ width: `${status.system?.disk?.usage_percent || 0}%` }}
></div>
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
{status.system?.disk?.used_gb?.toFixed(1)} GB / {status.system?.disk?.total_gb?.toFixed(1)} GB
</div>
</div>
</div>
</ComponentCard>
{/* Services Status */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Database */}
<ComponentCard title="Database" desc="PostgreSQL Status">
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">Status</span>
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.database?.status || 'unknown')}`}>
{status.database?.connected ? 'Connected' : 'Disconnected'}
</span>
</div>
{status.database?.version && (
<div className="text-sm">
<span className="text-gray-600 dark:text-gray-400">Version:</span>
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.database.version.split(',')[0]}</span>
</div>
)}
{status.database?.size && (
<div className="text-sm">
<span className="text-gray-600 dark:text-gray-400">Size:</span>
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.database.size}</span>
</div>
)}
<div className="text-sm">
<span className="text-gray-600 dark:text-gray-400">Active Connections:</span>
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.database?.active_connections || 0}</span>
</div>
</div>
</ComponentCard>
{/* Redis */}
<ComponentCard title="Redis" desc="Cache & Message Broker">
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">Status</span>
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.redis?.status || 'unknown')}`}>
{status.redis?.connected ? 'Connected' : 'Disconnected'}
</span>
</div>
</div>
</ComponentCard>
{/* Celery */}
<ComponentCard title="Celery" desc="Task Queue Workers">
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">Workers</span>
<span className={`text-xs px-2 py-1 rounded ${getStatusBadge(status.celery?.status || 'unknown')}`}>
{status.celery?.worker_count || 0} active
</span>
</div>
<div className="text-sm">
<span className="text-gray-600 dark:text-gray-400">Active Tasks:</span>
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.celery?.tasks?.active || 0}</span>
</div>
<div className="text-sm">
<span className="text-gray-600 dark:text-gray-400">Scheduled:</span>
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.celery?.tasks?.scheduled || 0}</span>
</div>
<div className="text-sm">
<span className="text-gray-600 dark:text-gray-400">Reserved:</span>
<span className="ml-2 text-gray-800 dark:text-gray-200">{status.celery?.tasks?.reserved || 0}</span>
</div>
</div>
</ComponentCard>
</div>
{/* Process Monitoring by Stack */}
<ComponentCard title="Process Monitoring" desc="Resource usage by technology stack">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead>
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Stack</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Processes</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">CPU %</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Memory (MB)</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
{Object.entries(status.processes?.by_stack || {}).map(([stack, stats]) => (
<tr key={stack}>
<td className="px-4 py-3 text-sm font-medium text-gray-800 dark:text-gray-200 capitalize">{stack}</td>
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">{stats.count}</td>
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">{stats.cpu.toFixed(2)}%</td>
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">{stats.memory_mb.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
</ComponentCard>
{/* Module Statistics */}
<ComponentCard title="Module Statistics" desc="Data counts by module">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Planner Module */}
<div className="space-y-3">
<h4 className="text-sm font-semibold text-gray-800 dark:text-gray-200">Planner Module</h4>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Keywords:</span>
<span className="text-gray-800 dark:text-gray-200">{status.modules?.planner?.keywords?.toLocaleString() || 0}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Clusters:</span>
<span className="text-gray-800 dark:text-gray-200">{status.modules?.planner?.clusters?.toLocaleString() || 0}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Content Ideas:</span>
<span className="text-gray-800 dark:text-gray-200">{status.modules?.planner?.content_ideas?.toLocaleString() || 0}</span>
</div>
</div>
</div>
{/* Writer Module */}
<div className="space-y-3">
<h4 className="text-sm font-semibold text-gray-800 dark:text-gray-200">Writer Module</h4>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Tasks:</span>
<span className="text-gray-800 dark:text-gray-200">{status.modules?.writer?.tasks?.toLocaleString() || 0}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600 dark:text-gray-400">Images:</span>
<span className="text-gray-800 dark:text-gray-200">{status.modules?.writer?.images?.toLocaleString() || 0}</span>
</div>
</div>
</div>
</div>
</ComponentCard>
{/* Last Updated */}
<div className="text-center text-sm text-gray-500 dark:text-gray-400">
Last updated: {new Date(status.timestamp).toLocaleString()}
</div>
</div>
</>
);
}

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { usePageLoading } from '../../context/PageLoadingContext';
import { fetchAPI } from '../../services/api';
import { Card } from '../../components/ui/card';
import Badge from '../../components/ui/badge/Badge';
@@ -15,8 +16,8 @@ interface Subscription {
export default function Subscriptions() {
const toast = useToast();
const { startLoading, stopLoading } = usePageLoading();
const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadSubscriptions();
@@ -24,13 +25,13 @@ export default function Subscriptions() {
const loadSubscriptions = async () => {
try {
setLoading(true);
startLoading('Loading subscriptions...');
const response = await fetchAPI('/v1/auth/subscriptions/');
setSubscriptions(response.results || []);
} catch (error: any) {
toast.error(`Failed to load subscriptions: ${error.message}`);
} finally {
setLoading(false);
stopLoading();
}
};
@@ -48,52 +49,46 @@ export default function Subscriptions() {
};
return (
<div className="p-6">
<>
<PageMeta title="Subscriptions" />
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Subscriptions</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">Manage account subscriptions</p>
</div>
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading...</div>
</div>
) : (
<Card className="p-6">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700">
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Account</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Period Start</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Period End</th>
<Card className="p-6">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700">
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Account</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Period Start</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Period End</th>
</tr>
</thead>
<tbody>
{subscriptions.map((subscription) => (
<tr key={subscription.id} className="border-b border-gray-100 dark:border-gray-800">
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">{subscription.account_name}</td>
<td className="py-3 px-4">
<Badge variant="light" color={getStatusColor(subscription.status) as any}>
{subscription.status}
</Badge>
</td>
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
{new Date(subscription.current_period_start).toLocaleDateString()}
</td>
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
{new Date(subscription.current_period_end).toLocaleDateString()}
</td>
</tr>
</thead>
<tbody>
{subscriptions.map((subscription) => (
<tr key={subscription.id} className="border-b border-gray-100 dark:border-gray-800">
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">{subscription.account_name}</td>
<td className="py-3 px-4">
<Badge variant="light" color={getStatusColor(subscription.status) as any}>
{subscription.status}
</Badge>
</td>
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
{new Date(subscription.current_period_start).toLocaleDateString()}
</td>
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
{new Date(subscription.current_period_end).toLocaleDateString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
)}
</div>
))}
</tbody>
</table>
</div>
</Card>
</>
);
}

View File

@@ -3,11 +3,12 @@ import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import { Card } from '../../components/ui/card';
import { usePageLoading } from '../../context/PageLoadingContext';
export default function SystemSettings() {
const toast = useToast();
const [settings, setSettings] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const { startLoading, stopLoading } = usePageLoading();
useEffect(() => {
loadSettings();
@@ -15,34 +16,28 @@ export default function SystemSettings() {
const loadSettings = async () => {
try {
setLoading(true);
startLoading('Loading system settings...');
const response = await fetchAPI('/v1/system/settings/system/');
setSettings(response.results || []);
} catch (error: any) {
toast.error(`Failed to load system settings: ${error.message}`);
} finally {
setLoading(false);
stopLoading();
}
};
return (
<div className="p-6">
<>
<PageMeta title="System Settings" />
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">System Settings</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">Global platform-wide settings</p>
</div>
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading...</div>
</div>
) : (
<Card className="p-6">
<p className="text-gray-600 dark:text-gray-400">System settings management interface coming soon.</p>
</Card>
)}
</div>
<Card className="p-6">
<p className="text-gray-600 dark:text-gray-400">System settings management interface coming soon.</p>
</Card>
</>
);
}

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { usePageLoading } from '../../context/PageLoadingContext';
import { fetchAPI } from '../../services/api';
import { Card } from '../../components/ui/card';
import Badge from '../../components/ui/badge/Badge';
@@ -15,8 +16,8 @@ interface User {
export default function Users() {
const toast = useToast();
const { startLoading, stopLoading } = usePageLoading();
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadUsers();
@@ -24,61 +25,55 @@ export default function Users() {
const loadUsers = async () => {
try {
setLoading(true);
startLoading('Loading users...');
const response = await fetchAPI('/v1/auth/users/');
setUsers(response.results || []);
} catch (error: any) {
toast.error(`Failed to load users: ${error.message}`);
} finally {
setLoading(false);
stopLoading();
}
};
return (
<div className="p-6">
<>
<PageMeta title="Users" />
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Users</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">Manage account users and permissions</p>
</div>
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading...</div>
</div>
) : (
<Card className="p-6">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700">
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Email</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Username</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Role</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
<Card className="p-6">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700">
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Email</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Username</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Role</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id} className="border-b border-gray-100 dark:border-gray-800">
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">{user.email}</td>
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">{user.username}</td>
<td className="py-3 px-4">
<Badge variant="light" color="primary">{user.role}</Badge>
</td>
<td className="py-3 px-4">
<Badge variant="light" color={user.is_active ? 'success' : 'dark'}>
{user.is_active ? 'Active' : 'Inactive'}
</Badge>
</td>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id} className="border-b border-gray-100 dark:border-gray-800">
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">{user.email}</td>
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">{user.username}</td>
<td className="py-3 px-4">
<Badge variant="light" color="primary">{user.role}</Badge>
</td>
<td className="py-3 px-4">
<Badge variant="light" color={user.is_active ? 'success' : 'dark'}>
{user.is_active ? 'Active' : 'Inactive'}
</Badge>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
)}
</div>
))}
</tbody>
</table>
</div>
</Card>
</>
);
}