feat: UI redesign phase 1 - components + 2 pages (avatars, campaigns)

This commit is contained in:
cawcenter
2025-12-15 00:16:46 -05:00
parent 21fe0766be
commit 6ec1dc34d5
5 changed files with 273 additions and 106 deletions

View File

@@ -0,0 +1,19 @@
---
interface Props {
icon: string;
title: string;
description: string;
}
const { icon, title, description } = Astro.props;
---
<div class="mb-8">
<div class="flex items-center gap-4 mb-2">
<span class="text-6xl">{icon}</span>
<div>
<h1 class="text-4xl font-bold text-gold-500">{title}</h1>
<p class="text-gray-300 mt-1">{description}</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,23 @@
---
interface Props {
icon: string;
label: string;
value: string | number;
color?: 'gold' | 'green' | 'blue' | 'red';
}
const { icon, label, value, color = 'gold' } = Astro.props;
const colorClasses = {
gold: 'text-gold-500',
green: 'text-green-400',
blue: 'text-blue-400',
red: 'text-red-400'
};
---
<div class="bg-titanium border border-edge-normal rounded-xl p-6 hover:border-gold-500/30 transition-colors">
<div class="text-4xl mb-3">{icon}</div>
<div class={`text-3xl font-bold ${colorClasses[color]}`}>{value}</div>
<div class="text-gray-400 text-sm mt-1">{label}</div>
</div>

View File

@@ -1,13 +1,80 @@
--- ---
import AdminLayout from '../../../layouts/AdminLayout.astro'; import AdminLayout from '../../../layouts/AdminLayout.astro';
import CollectionTable from '../../../components/admin/CollectionTable'; import PageHeader from '../../../components/admin/PageHeader.astro';
import StatCard from '../../../components/admin/StatCard.astro';
const endpoint = '/api/collections/campaign_masters';
const columns = ['name', 'campaign_type', 'status', 'created_at'];
--- ---
<AdminLayout title="Campaigns"> <AdminLayout title="Campaigns">
<CollectionTable <PageHeader
endpoint="/api/collections/campaign_masters" icon="🎯"
columns={['name', 'campaign_type', 'status', 'created_at']} title="Content Campaigns"
title="Campaigns" description="Manage and monitor content generation campaigns"
client:load
/> />
<div class="grid grid-cols-4 gap-6 mb-8">
<StatCard icon="📊" label="Total Campaigns" value="0" />
<StatCard icon="🟢" label="Active" value="0" color="green" />
<StatCard icon="⏸️" label="Paused" value="0" color="blue" />
<StatCard icon="✅" label="Completed" value="0" color="gold" />
</div>
<div class="bg-titanium border border-edge-normal rounded-xl overflow-hidden">
<div class="p-6 border-b border-edge-subtle">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold text-white">All Campaigns</h2>
<button class="px-4 py-2 bg-gold-500 text-black rounded-lg font-medium hover:bg-gold-400 transition-colors">
+ New Campaign
</button>
</div>
</div>
<table class="w-full">
<thead class="bg-graphite border-b border-edge-subtle">
<tr>
{columns.map(col => (
<th class="px-6 py-4 text-left text-sm font-semibold text-gray-300 uppercase">
{col.replace(/_/g, ' ')}
</th>
))}
<th class="px-6 py-4 text-right text-sm font-semibold text-gray-300 uppercase">Actions</th>
</tr>
</thead>
<tbody id="table-body" class="divide-y divide-edge-subtle">
<tr>
<td colspan={columns.length + 1} class="px-6 py-12 text-center text-gray-400">Loading...</td>
</tr>
</tbody>
</table>
</div>
<script define:vars={{ endpoint, columns }}>
async function loadData() {
try {
const token = localStorage.getItem('godToken') || 'jmQXoeyxWoBsB7eHzG7FmnH90f22JtaYBxXHoorhfZ-v4tT3VNEr9vvmwHqYHCDoWXHSU4DeZXApCP-Gha-YdA';
const response = await fetch(endpoint, { headers: { 'X-God-Token': token } });
if (!response.ok) throw new Error('Failed to fetch');
const result = await response.json();
const data = result.data || [];
const tbody = document.getElementById('table-body');
tbody.innerHTML = data.length === 0
? `<tr><td colspan="${columns.length + 1}" class="px-6 py-12 text-center text-gray-400">No campaigns yet</td></tr>`
: data.map(item => `
<tr class="hover:bg-graphite/50 transition-colors">
${columns.map(col => `<td class="px-6 py-4 text-sm text-gray-200">${item[col] || '-'}</td>`).join('')}
<td class="px-6 py-4 text-right">
<button class="text-gold-500 hover:text-gold-400 text-sm font-medium">View</button>
</td>
</tr>
`).join('');
} catch (err) {
document.getElementById('table-body').innerHTML = `<tr><td colspan="${columns.length + 1}" class="px-6 py-12 text-center text-red-400">Error: ${err.message}</td></tr>`;
}
}
loadData();
</script>
</AdminLayout> </AdminLayout>

View File

@@ -0,0 +1,66 @@
---
import AdminLayout from '../../../layouts/AdminLayout.astro';
---
<AdminLayout title="Geo Intelligence Test">
<div class="p-8">
<h1 class="text-3xl font-bold text-gold-500 mb-4">Geo Intelligence - Simple Test</h1>
<div class="bg-titanium border border-edge-normal rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold text-white mb-4">Page Status</h2>
<p class="text-green-400">✅ Page is loading!</p>
<p class="text-gray-400 mt-2">If you see this, the Astro page is working.</p>
</div>
<div class="bg-titanium border border-edge-normal rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold text-white mb-4">API Test</h2>
<button
onclick="testAPI()"
class="px-4 py-2 bg-gold-500 text-black rounded font-medium hover:bg-gold-400"
>
Test API Endpoint
</button>
<div id="api-result" class="mt-4 text-gray-300"></div>
</div>
<div class="bg-titanium border border-edge-normal rounded-xl p-6">
<h2 class="text-xl font-semibold text-white mb-4">Manual Data Fetch</h2>
<div id="geo-data" class="text-gray-300">Loading...</div>
</div>
</div>
<script>
async function testAPI() {
const result = document.getElementById('api-result');
try {
const token = 'jmQXoeyxWoBsB7eHzG7FmnH90f22JtaYBxXHoorhfZ-v4tT3VNEr9vvmwHqYHCDoWXHSU4DeZXApCP-Gha-YdA';
const response = await fetch('/api/collections/geo_locations', {
headers: { 'X-God-Token': token }
});
const data = await response.json();
result.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
} catch (err) {
result.innerHTML = `<span class="text-red-400">Error: ${err.message}</span>`;
}
}
// Auto-test on load
window.addEventListener('DOMContentLoaded', async () => {
const container = document.getElementById('geo-data');
try {
const token = 'jmQXoeyxWoBsB7eHzG7FmnH90f22JtaYBxXHoorhfZ-v4tT3VNEr9vvmwHqYHCDoWXHSU4DeZXApCP-Gha-YdA';
const response = await fetch('/api/collections/geo_locations', {
headers: { 'X-God-Token': token }
});
const data = await response.json();
container.innerHTML = `
<p class="text-green-400">✅ API Connected!</p>
<p class="mt-2">Total items: ${data.meta?.total || 0}</p>
<p>Data count: ${data.data?.length || 0}</p>
`;
} catch (err) {
container.innerHTML = `<p class="text-red-400">❌ API Error: ${err.message}</p>`;
}
});
</script>
</AdminLayout>

View File

@@ -1,107 +1,99 @@
/**
* Avatar Intelligence Management
* Full CRUD for avatar_intelligence collection
*/
--- ---
import AdminLayout from '@/layouts/AdminLayout.astro'; import PageHeader from '../../../components/admin/PageHeader.astro';
import { getDirectusClient } from '@/lib/directus/client'; import StatCard from '../../../components/admin/StatCard.astro';
import { readItems } from '@/lib/directus/client';
const client = getDirectusClient(); const endpoint = '/api/collections/avatars';
const columns = ['name', 'persona_type', 'tone', 'created_at'];
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';
}
--- ---
<AdminLayout title="Avatar Intelligence"> <AdminLayout title="Avatars">
<div class="space-y-6"> <PageHeader
<!-- Header --> icon="👤"
<div class="flex justify-between items-center"> title="AI Avatars"
<div> description="Manage AI writing personas and their characteristics"
<h1 class="spark-heading text-3xl">Avatar Intelligence</h1> />
<p class="text-silver mt-1">Manage persona profiles and variants</p>
</div>
<div class="flex gap-3">
<button class="spark-btn-secondary text-sm">
📥 Import CSV
</button>
<button class="spark-btn-secondary text-sm">
📤 Export
</button>
<a href="/admin/intelligence/avatars/new" class="spark-btn-primary text-sm">
✨ New Avatar
</a>
</div>
</div>
{error && ( <!-- Stats Cards -->
<div class="spark-card p-4 border-red-500 text-red-400"> <div class="grid grid-cols-3 gap-6 mb-8">
Error: {error} <StatCard icon="🎭" label="Total Avatars" value="0" />
</div> <StatCard icon="✍️" label="Active" value="0" color="green" />
)} <StatCard icon="📊" label="Avg Performance" value="0%" color="blue" />
<!-- Stats -->
<div class="grid grid-cols-4 gap-4">
<div class="spark-card p-6">
<div class="spark-label mb-2">Total Avatars</div>
<div class="spark-data text-3xl">{avatars.length}</div>
</div>
</div>
<!-- Avatars Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{avatars.map((avatar: any) => (
<div class="spark-card spark-card-hover p-6">
<div class="flex items-start justify-between mb-4">
<h3 class="text-white font-semibold text-lg">{avatar.base_name}</h3>
<button class="spark-btn-ghost text-xs px-2 py-1">
Edit
</button>
</div>
<div class="space-y-2 text-sm">
<div>
<span class="spark-label">Wealth Cluster:</span>
<span class="text-silver ml-2">{avatar.wealth_cluster || 'Not set'}</span>
</div>
{avatar.business_niches && (
<div>
<span class="spark-label">Niches:</span>
<div class="flex flex-wrap gap-1 mt-1">
{avatar.business_niches.slice(0, 3).map((niche: string) => (
<span class="px-2 py-0.5 bg-graphite border border-edge-subtle rounded text-xs text-silver">
{niche}
</span>
))}
{avatar.business_niches.length > 3 && (
<span class="px-2 py-0.5 text-xs text-silver/50">
+{avatar.business_niches.length - 3} more
</span>
)}
</div>
</div>
)}
</div>
</div>
))}
{avatars.length === 0 && !error && (
<div class="col-span-full spark-card p-12 text-center">
<p class="text-silver/50">No avatars found. Create your first one!</p>
</div>
)}
</div>
</div> </div>
<!-- Data Table -->
<div class="bg-titanium border border-edge-normal rounded-xl overflow-hidden">
<div class="p-6 border-b border-edge-subtle">
<h2 class="text-xl font-semibold text-white">All Avatars</h2>
</div>
<table class="w-full">
<thead class="bg-graphite border-b border-edge-subtle">
<tr>
{columns.map(col => (
<th class="px-6 py-4 text-left text-sm font-semibold text-gray-300 uppercase tracking-wider">
{col.replace(/_/g, ' ')}
</th>
))}
<th class="px-6 py-4 text-right text-sm font-semibold text-gray-300 uppercase">Actions</th>
</tr>
</thead>
<tbody id="table-body" class="divide-y divide-edge-subtle">
<tr>
<td colspan={columns.length + 1} class="px-6 py-12 text-center text-gray-400">
Loading data...
</td>
</tr>
</tbody>
</table>
</div>
<script define:vars={{ endpoint, columns }}>
async function loadData() {
try {
const token = localStorage.getItem('godToken') || 'jmQXoeyxWoBsB7eHzG7FmnH90f22JtaYBxXHoorhfZ-v4tT3VNEr9vvmwHqYHCDoWXHSU4DeZXApCP-Gha-YdA';
const response = await fetch(endpoint, {
headers: { 'X-God-Token': token }
});
if (!response.ok) throw new Error('Failed to fetch');
const result = await response.json();
const data = result.data || [];
const tbody = document.getElementById('table-body');
if (data.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="${columns.length + 1}" class="px-6 py-12 text-center text-gray-400">
No avatars found. Create your first AI persona!
</td>
</tr>
`;
} else {
tbody.innerHTML = data.map(item => `
<tr class="hover:bg-graphite/50 transition-colors">
${columns.map(col => `
<td class="px-6 py-4 text-sm text-gray-200">
${item[col] || '-'}
</td>
`).join('')}
<td class="px-6 py-4 text-right">
<button class="text-gold-500 hover:text-gold-400 text-sm font-medium">Edit</button>
</td>
</tr>
`).join('');
}
} catch (err) {
document.getElementById('table-body').innerHTML = `
<tr>
<td colspan="${columns.length + 1}" class="px-6 py-12 text-center text-red-400">
Error loading data: ${err.message}
</td>
</tr>
`;
}
}
loadData();
</script>
</AdminLayout> </AdminLayout>