feat: Refactor Admin Pages to use SSR for data fetching (Fixes data loading issues)
This commit is contained in:
@@ -4,52 +4,20 @@ import { getDirectusClient } from '@/lib/directus/client';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
export default function AvatarManager() {
|
interface Props {
|
||||||
const [avatars, setAvatars] = useState([]);
|
initialAvatars?: any[];
|
||||||
const [variants, setVariants] = useState([]);
|
initialVariants?: any[];
|
||||||
const [loading, setLoading] = useState(true);
|
}
|
||||||
|
|
||||||
|
export default function AvatarManager({ initialAvatars = [], initialVariants = [] }: Props) {
|
||||||
|
const [avatars, setAvatars] = useState(initialAvatars);
|
||||||
|
const [variants, setVariants] = useState(initialVariants);
|
||||||
const [selectedAvatar, setSelectedAvatar] = useState(null);
|
const [selectedAvatar, setSelectedAvatar] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadData = async () => {
|
|
||||||
try {
|
|
||||||
const directus = await getDirectusClient();
|
|
||||||
|
|
||||||
// Load avatars
|
|
||||||
const avatarData = await directus.request({
|
|
||||||
method: 'GET',
|
|
||||||
path: '/items/avatar_intelligence'
|
|
||||||
});
|
|
||||||
setAvatars(avatarData.data || []);
|
|
||||||
|
|
||||||
// Load variants
|
|
||||||
const variantData = await directus.request({
|
|
||||||
method: 'GET',
|
|
||||||
path: '/items/avatar_variants'
|
|
||||||
});
|
|
||||||
setVariants(variantData.data || []);
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading avatars:', error);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getVariantsForAvatar = (avatarKey) => {
|
const getVariantsForAvatar = (avatarKey) => {
|
||||||
return variants.filter(v => v.avatar_key === avatarKey);
|
return variants.filter(v => v.avatar_key === avatarKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center h-64">
|
|
||||||
<div className="text-white">Loading avatars...</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -4,32 +4,12 @@ import { getDirectusClient } from '@/lib/directus/client';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
export default function CartesianManager() {
|
interface Props {
|
||||||
const [patterns, setPatterns] = useState([]);
|
initialPatterns?: any[];
|
||||||
const [loading, setLoading] = useState(true);
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
export default function CartesianManager({ initialPatterns = [] }: Props) {
|
||||||
loadData();
|
const [patterns, setPatterns] = useState(initialPatterns);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadData = async () => {
|
|
||||||
try {
|
|
||||||
const directus = await getDirectusClient();
|
|
||||||
const response = await directus.request({
|
|
||||||
method: 'GET',
|
|
||||||
path: '/items/cartesian_patterns'
|
|
||||||
});
|
|
||||||
setPatterns(response.data || []);
|
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading cartesian patterns:', error);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <div className="text-white">Loading Cartesian Patterns...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -4,32 +4,12 @@ import { getDirectusClient } from '@/lib/directus/client';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
export default function GeoManager() {
|
interface Props {
|
||||||
const [clusters, setClusters] = useState([]);
|
initialClusters?: any[];
|
||||||
const [loading, setLoading] = useState(true);
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
export default function GeoManager({ initialClusters = [] }: Props) {
|
||||||
loadData();
|
const [clusters, setClusters] = useState(initialClusters);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadData = async () => {
|
|
||||||
try {
|
|
||||||
const directus = await getDirectusClient();
|
|
||||||
const response = await directus.request({
|
|
||||||
method: 'GET',
|
|
||||||
path: '/items/geo_intelligence'
|
|
||||||
});
|
|
||||||
setClusters(response.data || []);
|
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading geo clusters:', error);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <div className="text-white">Loading Geo Intelligence...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -4,32 +4,12 @@ import { getDirectusClient } from '@/lib/directus/client';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
export default function SpintaxManager() {
|
interface Props {
|
||||||
const [dictionaries, setDictionaries] = useState([]);
|
initialDictionaries?: any[];
|
||||||
const [loading, setLoading] = useState(true);
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
export default function SpintaxManager({ initialDictionaries = [] }: Props) {
|
||||||
loadData();
|
const [dictionaries, setDictionaries] = useState(initialDictionaries);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadData = async () => {
|
|
||||||
try {
|
|
||||||
const directus = await getDirectusClient();
|
|
||||||
const response = await directus.request({
|
|
||||||
method: 'GET',
|
|
||||||
path: '/items/spintax_dictionaries'
|
|
||||||
});
|
|
||||||
setDictionaries(response.data || []);
|
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading spintax dictionaries:', error);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <div className="text-white">Loading Spintax Dictionaries...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
---
|
---
|
||||||
import Layout from '@/layouts/AdminLayout.astro';
|
import Layout from '@/layouts/AdminLayout.astro';
|
||||||
import AvatarManager from '@/components/admin/content/AvatarManager';
|
import AvatarManager from '@/components/admin/content/AvatarManager';
|
||||||
|
import { getDirectusClient, readItems } from '@/lib/directus/client';
|
||||||
|
|
||||||
|
const directus = getDirectusClient();
|
||||||
|
const avatars = await directus.request(readItems('avatar_intelligence')).catch(() => []);
|
||||||
|
const variants = await directus.request(readItems('avatar_variants')).catch(() => []);
|
||||||
---
|
---
|
||||||
<Layout title="Avatars Intelligence">
|
<Layout title="Avatars Intelligence">
|
||||||
<div class="p-8">
|
<div class="p-8">
|
||||||
@@ -9,6 +14,6 @@ import AvatarManager from '@/components/admin/content/AvatarManager';
|
|||||||
<h1 class="text-3xl font-bold text-white mb-2">Avatar Intelligence</h1>
|
<h1 class="text-3xl font-bold text-white mb-2">Avatar Intelligence</h1>
|
||||||
<p class="text-gray-400">Manage your 10 base avatars, variants, and business niches.</p>
|
<p class="text-gray-400">Manage your 10 base avatars, variants, and business niches.</p>
|
||||||
</div>
|
</div>
|
||||||
<AvatarManager client:load />
|
<AvatarManager client:load initialAvatars={avatars} initialVariants={variants} />
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
import Layout from '@/layouts/AdminLayout.astro';
|
import Layout from '@/layouts/AdminLayout.astro';
|
||||||
import CartesianManager from '@/components/admin/content/CartesianManager';
|
import CartesianManager from '@/components/admin/content/CartesianManager';
|
||||||
|
import { getDirectusClient, readItems } from '@/lib/directus/client';
|
||||||
|
|
||||||
|
const directus = getDirectusClient();
|
||||||
|
const patterns = await directus.request(readItems('cartesian_patterns')).catch(() => []);
|
||||||
---
|
---
|
||||||
<Layout title="Cartesian Patterns">
|
<Layout title="Cartesian Patterns">
|
||||||
<div class="p-8">
|
<div class="p-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-3xl font-bold text-white mb-2">Cartesian Patterns</h1>
|
<h1 class="text-3xl font-bold text-white mb-2">Cartesian Patterns</h1>
|
||||||
<p class="text-gray-400">Headline and Hook Formulas for the Multiplication Engine.</p>
|
<p class="text-gray-400">Manage headline and content generation formulas.</p>
|
||||||
</div>
|
</div>
|
||||||
<CartesianManager client:load />
|
<CartesianManager client:load initialPatterns={patterns} />
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
import Layout from '@/layouts/AdminLayout.astro';
|
import Layout from '@/layouts/AdminLayout.astro';
|
||||||
import GeoManager from '@/components/admin/content/GeoManager';
|
import GeoManager from '@/components/admin/content/GeoManager';
|
||||||
|
import { getDirectusClient, readItems } from '@/lib/directus/client';
|
||||||
|
|
||||||
|
const directus = getDirectusClient();
|
||||||
|
const clusters = await directus.request(readItems('geo_intelligence')).catch(() => []);
|
||||||
---
|
---
|
||||||
<Layout title="Geo Intelligence">
|
<Layout title="Geo Clusters">
|
||||||
<div class="p-8">
|
<div class="p-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-3xl font-bold text-white mb-2">Geo Clusters</h1>
|
<h1 class="text-3xl font-bold text-white mb-2">Geo Intelligence</h1>
|
||||||
<p class="text-gray-400">Manage Geographic Intelligence (Silicon Valleys, Growth Havens) for localized content.</p>
|
<p class="text-gray-400">Manage your geographic targeting clusters.</p>
|
||||||
</div>
|
</div>
|
||||||
<GeoManager client:load />
|
<GeoManager client:load initialClusters={clusters} />
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
import Layout from '@/layouts/AdminLayout.astro';
|
import Layout from '@/layouts/AdminLayout.astro';
|
||||||
import SpintaxManager from '@/components/admin/content/SpintaxManager';
|
import SpintaxManager from '@/components/admin/content/SpintaxManager';
|
||||||
|
import { getDirectusClient, readItems } from '@/lib/directus/client';
|
||||||
|
|
||||||
|
const directus = getDirectusClient();
|
||||||
|
const dictionaries = await directus.request(readItems('spintax_dictionaries')).catch(() => []);
|
||||||
---
|
---
|
||||||
<Layout title="Spintax Dictionary">
|
<Layout title="Spintax Dictionaries">
|
||||||
<div class="p-8">
|
<div class="p-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-3xl font-bold text-white mb-2">Spintax Dictionaries</h1>
|
<h1 class="text-3xl font-bold text-white mb-2">Spintax Dictionaries</h1>
|
||||||
<p class="text-gray-400">Global Search & Replace Dictionaries for content randomization.</p>
|
<p class="text-gray-400">Manage word variations for automated content generation.</p>
|
||||||
</div>
|
</div>
|
||||||
<SpintaxManager client:load />
|
<SpintaxManager client:load initialDictionaries={dictionaries} />
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
137
full-schema-update.sql
Normal file
137
full-schema-update.sql
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
-- Spark Platform - Full Schema Update
|
||||||
|
-- Adds missing tables for SEO Engine and Cartesian Engine
|
||||||
|
|
||||||
|
-- 1. generated_articles (replacing/aliasing posts)
|
||||||
|
CREATE TABLE IF NOT EXISTS generated_articles (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||||
|
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
||||||
|
title VARCHAR(500) NOT NULL,
|
||||||
|
slug VARCHAR(500) NOT NULL,
|
||||||
|
html_content TEXT,
|
||||||
|
generation_hash VARCHAR(255),
|
||||||
|
meta_desc TEXT,
|
||||||
|
is_published BOOLEAN DEFAULT FALSE,
|
||||||
|
sync_status VARCHAR(50),
|
||||||
|
sitemap_status VARCHAR(50) DEFAULT 'ghost', -- ghost, queued, indexed
|
||||||
|
campaign_id UUID, -- Reference to campaign_masters
|
||||||
|
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
date_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 2. campaign_masters
|
||||||
|
CREATE TABLE IF NOT EXISTS campaign_masters (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||||
|
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
headline_spintax_root TEXT,
|
||||||
|
niche_variables JSONB,
|
||||||
|
location_mode VARCHAR(50),
|
||||||
|
location_target VARCHAR(255),
|
||||||
|
batch_count INTEGER DEFAULT 0,
|
||||||
|
status VARCHAR(50) DEFAULT 'active',
|
||||||
|
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3. headline_inventory
|
||||||
|
CREATE TABLE IF NOT EXISTS headline_inventory (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||||
|
campaign_id UUID REFERENCES campaign_masters (id) ON DELETE CASCADE,
|
||||||
|
final_title_text TEXT NOT NULL,
|
||||||
|
status VARCHAR(50) DEFAULT 'available',
|
||||||
|
used_on_article UUID, -- Reference to generated_articles
|
||||||
|
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 4. content_fragments
|
||||||
|
CREATE TABLE IF NOT EXISTS content_fragments (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||||
|
campaign_id UUID REFERENCES campaign_masters (id) ON DELETE CASCADE,
|
||||||
|
fragment_type VARCHAR(100),
|
||||||
|
content_body TEXT,
|
||||||
|
word_count INTEGER,
|
||||||
|
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 5. generation_jobs (Cartesian Engine)
|
||||||
|
CREATE TABLE IF NOT EXISTS generation_jobs (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||||
|
site_id UUID REFERENCES sites (id) ON DELETE CASCADE,
|
||||||
|
target_quantity INTEGER,
|
||||||
|
status VARCHAR(50) DEFAULT 'Pending',
|
||||||
|
filters JSONB,
|
||||||
|
current_offset INTEGER DEFAULT 0,
|
||||||
|
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Register collections
|
||||||
|
INSERT INTO
|
||||||
|
directus_collections (
|
||||||
|
collection,
|
||||||
|
icon,
|
||||||
|
note,
|
||||||
|
hidden,
|
||||||
|
singleton,
|
||||||
|
accountability
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'generated_articles',
|
||||||
|
'article',
|
||||||
|
'SEO Generated Articles',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'all'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'campaign_masters',
|
||||||
|
'campaign',
|
||||||
|
'SEO Campaigns',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'all'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'headline_inventory',
|
||||||
|
'title',
|
||||||
|
'Generated Headlines',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'all'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'content_fragments',
|
||||||
|
'extension',
|
||||||
|
'Content Blocks',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'all'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'generation_jobs',
|
||||||
|
'engineering',
|
||||||
|
'Generation Jobs',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'all'
|
||||||
|
) ON CONFLICT (collection) DO NOTHING;
|
||||||
|
|
||||||
|
-- Register fields for generated_articles
|
||||||
|
INSERT INTO directus_fields (collection, field, type, interface, special)
|
||||||
|
VALUES
|
||||||
|
('generated_articles', 'id', 'uuid', 'input', ARRAY['uuid']),
|
||||||
|
('generated_articles', 'site_id', 'uuid', 'select-dropdown-m2o', NULL),
|
||||||
|
('generated_articles', 'title', 'string', 'input', NULL),
|
||||||
|
('generated_articles', 'slug', 'string', 'input', NULL),
|
||||||
|
('generated_articles', 'html_content', 'text', 'input-rich-text-html', NULL),
|
||||||
|
('generated_articles', 'is_published', 'boolean', 'boolean', NULL),
|
||||||
|
('generated_articles', 'sitemap_status', 'string', 'select-dropdown', NULL),
|
||||||
|
('generated_articles', 'date_created', 'timestamp', 'datetime', ARRAY['date-created'])
|
||||||
|
ON CONFLICT (collection, field) DO NOTHING;
|
||||||
|
|
||||||
|
-- Register fields for campaign_masters
|
||||||
|
INSERT INTO directus_fields (collection, field, type, interface, special)
|
||||||
|
VALUES
|
||||||
|
('campaign_masters', 'id', 'uuid', 'input', ARRAY['uuid']),
|
||||||
|
('campaign_masters', 'name', 'string', 'input', NULL),
|
||||||
|
('campaign_masters', 'status', 'string', 'select-dropdown', NULL),
|
||||||
|
('campaign_masters', 'date_created', 'timestamp', 'datetime', ARRAY['date-created'])
|
||||||
|
ON CONFLICT (collection, field) DO NOTHING;
|
||||||
Reference in New Issue
Block a user