diff --git a/frontend/src/components/admin/SystemStatusBar.tsx b/frontend/src/components/admin/SystemStatusBar.tsx
index 8b67f63..65c6a2f 100644
--- a/frontend/src/components/admin/SystemStatusBar.tsx
+++ b/frontend/src/components/admin/SystemStatusBar.tsx
@@ -1,5 +1,4 @@
import { useState, useEffect } from 'react';
-import { getDirectusClient, readItems } from '@/lib/directus/client';
interface SystemStatus {
coreApi: 'online' | 'offline' | 'checking';
@@ -24,27 +23,38 @@ export default function SystemStatusBar() {
const [showLogs, setShowLogs] = useState(false);
useEffect(() => {
- checkSystemStatus();
- const interval = setInterval(checkSystemStatus, 30000); // Check every 30 seconds
+ checkStatus();
+ const interval = setInterval(checkStatus, 30000);
return () => clearInterval(interval);
}, []);
- const checkSystemStatus = async () => {
+ const addLog = (message: string, type: LogEntry['type']) => {
+ const newLog: LogEntry = {
+ time: new Date().toLocaleTimeString(),
+ message,
+ type
+ };
+ setLogs(prev => [newLog, ...prev].slice(0, 50));
+ };
+
+ const checkStatus = async () => {
try {
- const client = getDirectusClient();
+ const directusUrl = 'https://spark.jumpstartscaling.com';
- // Test database connection by fetching a single site
- const sites = await client.request(
- readItems('sites', { limit: 1 })
- );
-
- setStatus({
- coreApi: 'online',
- database: 'connected',
- wpConnection: 'ready'
+ const response = await fetch(`${directusUrl}/server/health`, {
+ method: 'GET',
});
- addLog('System check passed', 'success');
+ if (response.ok) {
+ setStatus({
+ coreApi: 'online',
+ database: 'connected',
+ wpConnection: 'ready'
+ });
+ addLog('System check passed', 'success');
+ } else {
+ throw new Error(`Health check failed: ${response.status}`);
+ }
} catch (error) {
console.error('Status check failed:', error);
setStatus({
@@ -56,15 +66,6 @@ export default function SystemStatusBar() {
}
};
- const addLog = (message: string, type: LogEntry['type']) => {
- const newLog: LogEntry = {
- time: new Date().toLocaleTimeString(),
- message,
- type
- };
- setLogs(prev => [newLog, ...prev].slice(0, 50)); // Keep last 50 logs
- };
-
const getStatusColor = (state: string) => {
switch (state) {
case 'online':
@@ -96,60 +97,53 @@ export default function SystemStatusBar() {
};
return (
-
- {/* Main Status Bar */}
+
- {/* Title */}
-
- API & Logistics
-
+
API & Logistics
- {/* Status Items */}
- Core API
+ Core API
{status.coreApi.charAt(0).toUpperCase() + status.coreApi.slice(1)}
- Database (Directus
+ Database (Directus)
{status.database.charAt(0).toUpperCase() + status.database.slice(1)}
- WP Connection
+ WP Connection
{status.wpConnection.charAt(0).toUpperCase() + status.wpConnection.slice(1)}
- {/* Toggle Logs Button */}
- {/* Processing Log Panel */}
{showLogs && (
-
+
{logs.length === 0 ? (
-
No recent activity
+
No recent activity
) : (
logs.map((log, index) => (
- [{log.time}]
+ [{log.time}]
{log.message}
))
diff --git a/frontend/src/components/collections/CollectionManager.tsx b/frontend/src/components/collections/CollectionManager.tsx
new file mode 100644
index 0000000..f4157d4
--- /dev/null
+++ b/frontend/src/components/collections/CollectionManager.tsx
@@ -0,0 +1,176 @@
+/**
+ * Universal Collection Manager
+ * Reusable component for all collection pages
+ */
+
+'use client';
+
+import { useState, useEffect } from 'react';
+
+interface CollectionManagerProps {
+ collection: string;
+ title: string;
+ description: string;
+ icon: string;
+ displayField: string;
+}
+
+export default function CollectionManager({
+ collection,
+ title,
+ description,
+ icon,
+ displayField,
+}: CollectionManagerProps) {
+ const [items, setItems] = useState
([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetchItems();
+ }, [collection]);
+
+ const fetchItems = async () => {
+ try {
+ setLoading(true);
+ const response = await fetch(
+ `https://spark.jumpstartscaling.com/items/${collection}?limit=100`,
+ {
+ headers: {
+ Authorization: `Bearer ${import.meta.env.PUBLIC_DIRECTUS_TOKEN}`,
+ },
+ }
+ );
+
+ if (!response.ok) throw new Error(`Failed to fetch ${collection}`);
+
+ const data = await response.json();
+ setItems(data.data || []);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Unknown error');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleBulkExport = () => {
+ const jsonStr = JSON.stringify(items, null, 2);
+ const blob = new Blob([jsonStr], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${collection}_export.json`;
+ a.click();
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
Error: {error}
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+ {icon}
+ {title}
+
+
{description}
+
+
+
+
+
+
+
+
+ {/* Stats */}
+
+
+
Total Items
+
{items.length}
+
+
+
+
+
+
+ {/* Table */}
+
+
+
+ {items.length === 0 && (
+
+
No items found. Create your first one!
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/lib/collections/config.ts b/frontend/src/lib/collections/config.ts
new file mode 100644
index 0000000..a669f45
--- /dev/null
+++ b/frontend/src/lib/collections/config.ts
@@ -0,0 +1,107 @@
+/**
+ * Collection Page Template Generator
+ * Creates standardized CRUD pages for all collections
+ */
+
+export const collectionConfigs = {
+ avatar_intelligence: {
+ title: 'Avatar Intelligence',
+ description: 'Manage persona profiles and variants',
+ icon: 'π₯',
+ fields: ['base_name', 'wealth_cluster', 'business_niches'],
+ displayField: 'base_name',
+ },
+ avatar_variants: {
+ title: 'Avatar Variants',
+ description: 'Manage gender and tone variations',
+ icon: 'π',
+ fields: ['avatar_id', 'variant_name', 'pronouns'],
+ displayField: 'variant_name',
+ },
+ campaign_masters: {
+ title: 'Campaign Masters',
+ description: 'Manage marketing campaigns',
+ icon: 'π’',
+ fields: ['campaign_name', 'status', 'site_id'],
+ displayField: 'campaign_name',
+ },
+ cartesian_patterns: {
+ title: 'Cartesian Patterns',
+ description: 'Content structure templates',
+ icon: 'π§',
+ fields: ['pattern_name', 'structure_type'],
+ displayField: 'pattern_name',
+ },
+ content_fragments: {
+ title: 'Content Fragments',
+ description: 'Reusable content blocks',
+ icon: 'π¦',
+ fields: ['fragment_type', 'content'],
+ displayField: 'fragment_type',
+ },
+ generated_articles: {
+ title: 'Generated Articles',
+ description: 'AI-generated content output',
+ icon: 'π',
+ fields: ['title', 'status', 'seo_score', 'geo_city'],
+ displayField: 'title',
+ },
+ generation_jobs: {
+ title: 'Generation Jobs',
+ description: 'Content generation queue',
+ icon: 'βοΈ',
+ fields: ['job_name', 'status', 'progress'],
+ displayField: 'job_name',
+ },
+ geo_intelligence: {
+ title: 'Geo Intelligence',
+ description: 'Location targeting data',
+ icon: 'πΊοΈ',
+ fields: ['city', 'state', 'zip', 'population'],
+ displayField: 'city',
+ },
+ headline_inventory: {
+ title: 'Headline Inventory',
+ description: 'Pre-written headlines library',
+ icon: 'π¬',
+ fields: ['headline_text', 'category'],
+ displayField: 'headline_text',
+ },
+ leads: {
+ title: 'Leads',
+ description: 'Customer lead management',
+ icon: 'π€',
+ fields: ['name', 'email', 'status'],
+ displayField: 'name',
+ },
+ offer_blocks: {
+ title: 'Offer Blocks',
+ description: 'Call-to-action templates',
+ icon: 'π―',
+ fields: ['offer_text', 'offer_type'],
+ displayField: 'offer_text',
+ },
+ pages: {
+ title: 'Pages',
+ description: 'Static page content',
+ icon: 'π',
+ fields: ['title', 'slug', 'status'],
+ displayField: 'title',
+ },
+ posts: {
+ title: 'Posts',
+ description: 'Blog posts and articles',
+ icon: 'π°',
+ fields: ['title', 'status', 'seo_score'],
+ displayField: 'title',
+ },
+ spintax_dictionaries: {
+ title: 'Spintax Dictionaries',
+ description: 'Word variation sets',
+ icon: 'π',
+ fields: ['category', 'variations'],
+ displayField: 'category',
+ },
+};
+
+export type CollectionName = keyof typeof collectionConfigs;
diff --git a/frontend/src/pages/admin/intelligence/avatars.astro b/frontend/src/pages/admin/intelligence/avatars.astro
new file mode 100644
index 0000000..cbe96e3
--- /dev/null
+++ b/frontend/src/pages/admin/intelligence/avatars.astro
@@ -0,0 +1,107 @@
+/**
+ * Avatar Intelligence Management
+ * Full CRUD for avatar_intelligence collection
+ */
+
+---
+import AdminLayout from '@/layouts/AdminLayout.astro';
+import { getDirectusClient } from '@/lib/directus/client';
+import { readItems } from '@directus/sdk';
+
+const client = getDirectusClient();
+
+let avatars = [];
+let error = null;
+
+try {
+ avatars = await client.request(readItems('avatar_intelligence', {
+ fields: ['*'],
+ sort: ['base_name'],
+ }));
+} catch (e) {
+ console.error('Error fetching avatars:', e);
+ error = e instanceof Error ? e.message : 'Unknown error';
+}
+---
+
+
+
+
+
+
+
Avatar Intelligence
+
Manage persona profiles and variants
+
+
+
+
+ {error && (
+
+ Error: {error}
+
+ )}
+
+
+
+
+
Total Avatars
+
{avatars.length}
+
+
+
+
+
+ {avatars.map((avatar: any) => (
+
+
+
{avatar.base_name}
+
+
+
+
+
+ Wealth Cluster:
+ {avatar.wealth_cluster || 'Not set'}
+
+
+ {avatar.business_niches && (
+
+
Niches:
+
+ {avatar.business_niches.slice(0, 3).map((niche: string) => (
+
+ {niche}
+
+ ))}
+ {avatar.business_niches.length > 3 && (
+
+ +{avatar.business_niches.length - 3} more
+
+ )}
+
+
+ )}
+
+
+ ))}
+
+ {avatars.length === 0 && !error && (
+
+
No avatars found. Create your first one!
+
+ )}
+
+
+