feat: UI redesign phase 1 - components + 2 pages (avatars, campaigns)
This commit is contained in:
19
src/components/admin/PageHeader.astro
Normal file
19
src/components/admin/PageHeader.astro
Normal 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>
|
||||||
23
src/components/admin/StatCard.astro
Normal file
23
src/components/admin/StatCard.astro
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
66
src/pages/admin/collections/geo-intelligence-test.astro
Normal file
66
src/pages/admin/collections/geo-intelligence-test.astro
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user