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 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">
|
||||
<CollectionTable
|
||||
endpoint="/api/collections/campaign_masters"
|
||||
columns={['name', 'campaign_type', 'status', 'created_at']}
|
||||
title="Campaigns"
|
||||
client:load
|
||||
<PageHeader
|
||||
icon="🎯"
|
||||
title="Content Campaigns"
|
||||
description="Manage and monitor content generation campaigns"
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
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 { getDirectusClient } from '@/lib/directus/client';
|
||||
import { readItems } from '@/lib/directus/client';
|
||||
import PageHeader from '../../../components/admin/PageHeader.astro';
|
||||
import StatCard from '../../../components/admin/StatCard.astro';
|
||||
|
||||
const client = getDirectusClient();
|
||||
const endpoint = '/api/collections/avatars';
|
||||
const columns = ['name', 'persona_type', 'tone', 'created_at'];
|
||||
---
|
||||
|
||||
let avatars = [];
|
||||
let error = null;
|
||||
<AdminLayout title="Avatars">
|
||||
<PageHeader
|
||||
icon="👤"
|
||||
title="AI Avatars"
|
||||
description="Manage AI writing personas and their characteristics"
|
||||
/>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-3 gap-6 mb-8">
|
||||
<StatCard icon="🎭" label="Total Avatars" value="0" />
|
||||
<StatCard icon="✍️" label="Active" value="0" color="green" />
|
||||
<StatCard icon="📊" label="Avg Performance" value="0%" color="blue" />
|
||||
</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 {
|
||||
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';
|
||||
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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
<AdminLayout title="Avatar Intelligence">
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<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 && (
|
||||
<div class="spark-card p-4 border-red-500 text-red-400">
|
||||
Error: {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<!-- 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>
|
||||
loadData();
|
||||
</script>
|
||||
</AdminLayout>
|
||||
|
||||
Reference in New Issue
Block a user