diff --git a/god-mode/migrations/01_init_sites.sql b/god-mode/migrations/01_init_sites.sql new file mode 100644 index 0000000..011adff --- /dev/null +++ b/god-mode/migrations/01_init_sites.sql @@ -0,0 +1,22 @@ +-- Create sites table for Multi-Tenancy +CREATE TABLE IF NOT EXISTS sites ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid (), + domain VARCHAR(255) UNIQUE NOT NULL, + status VARCHAR(50) DEFAULT 'active', -- active, maintenance, archived + config JSONB DEFAULT '{}', -- branding, SEO settings + client_id VARCHAR(255), + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Index for fast domain lookups +CREATE INDEX IF NOT EXISTS idx_sites_domain ON sites (domain); + +-- Insert the Platform/Admin site default +INSERT INTO + sites (domain, status, config) +VALUES ( + 'spark.jumpstartscaling.com', + 'active', + '{"type": "admin"}' + ) ON CONFLICT (domain) DO NOTHING; \ No newline at end of file diff --git a/god-mode/src/components/debug/DebugToolbar.tsx b/god-mode/src/components/debug/DebugToolbar.tsx new file mode 100644 index 0000000..795cc2d --- /dev/null +++ b/god-mode/src/components/debug/DebugToolbar.tsx @@ -0,0 +1,178 @@ +import React, { useEffect, useState } from 'react'; +import { useStore } from '@nanostores/react'; +import { debugIsOpen, activeTab, logs, type LogEntry } from '../../stores/debugStore'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { getDirectusClient } from '@/lib/directus/client'; + +// Create a client for the devtools if one doesn't exist in context +// (Ideally this component is inside the main QueryClientProvider, but we'll see) +const queryClient = new QueryClient(); + +export default function DebugToolbar() { + const isOpen = useStore(debugIsOpen); + const currentTab = useStore(activeTab); + const logEntries = useStore(logs); + const [backendStatus, setBackendStatus] = useState<'checking' | 'online' | 'error'>('checking'); + const [latency, setLatency] = useState(null); + + useEffect(() => { + if (isOpen && currentTab === 'backend') { + checkBackend(); + } + }, [isOpen, currentTab]); + + const checkBackend = async () => { + setBackendStatus('checking'); + const start = performance.now(); + try { + const client = getDirectusClient(); + await client.request(() => ({ + path: '/server/ping', + method: 'GET' + })); + setLatency(Math.round(performance.now() - start)); + setBackendStatus('online'); + } catch (e) { + setBackendStatus('error'); + } + }; + + if (!isOpen) { + return ( + + ); + } + + return ( +
+ {/* Header */} +
+
+ ⚡ Spark Debug +
+ {(['console', 'backend', 'network'] as const).map(tab => ( + + ))} +
+
+ +
+ + {/* Content */} +
+ + {/* Console Tab */} + {currentTab === 'console' && ( +
+ {logEntries.length === 0 && ( +
No logs captured yet...
+ )} + {logEntries.map((log) => ( +
+ [{log.timestamp}] + + {log.type} + + + {log.messages.join(' ')} + +
+ ))} +
+ +
+
+ )} + + {/* Backend Tab */} + {currentTab === 'backend' && ( +
+
+ {backendStatus === 'online' ? '● Online' : + backendStatus === 'error' ? '✖ Error' : '● Checking...'} +
+ +
+

+ Directus URL: {import.meta.env.PUBLIC_DIRECTUS_URL} +

+ {latency && ( +

+ Latency: {latency}ms +

+ )} +
+ + +
+ )} + + {/* Network / React Query Tab */} + {currentTab === 'network' && ( +
+
+ {/* + React Query Devtools needs a QueryClientProvider context. + In Astro, components are islands. If this island doesn't share context with the main app + (which it likely won't if they are separate roots), we might see empty devtools. + However, putting it here is the best attempt. + */} +
+

React Query Devtools

+

+ (If empty, data fetching might be happening Server-Side or in a different Context) +

+
+
+ {/* We force mount devtools panel here if possible */} + + + +
+ )} + +
+
+ ); +} diff --git a/god-mode/src/components/engine/BlockRenderer.tsx b/god-mode/src/components/engine/BlockRenderer.tsx new file mode 100644 index 0000000..46acf81 --- /dev/null +++ b/god-mode/src/components/engine/BlockRenderer.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import Hero from './blocks/Hero'; +import Content from './blocks/Content'; +import Features from './blocks/Features'; + +interface Block { + id: string; + block_type: string; + block_config: any; +} + +interface BlockRendererProps { + blocks: Block[]; +} + +export default function BlockRenderer({ blocks }: BlockRendererProps) { + if (!blocks || !Array.isArray(blocks)) return null; + + return ( +
+ {blocks.map(block => { + switch (block.block_type) { + case 'hero': + return ; + case 'content': + return ; + case 'features': + return ; + case 'cta': + // reuse Hero styled as CTA or simple banner + return ; + default: + console.warn(`Unknown block type: ${block.block_type}`); + return null; + } + })} +
+ ); +} diff --git a/god-mode/src/components/engine/blocks/Content.tsx b/god-mode/src/components/engine/blocks/Content.tsx new file mode 100644 index 0000000..f1816c6 --- /dev/null +++ b/god-mode/src/components/engine/blocks/Content.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +interface ContentProps { + content: string; +} + +export default function Content({ content }: ContentProps) { + return ( +
+
+
+ ); +} diff --git a/god-mode/src/components/engine/blocks/Features.tsx b/god-mode/src/components/engine/blocks/Features.tsx new file mode 100644 index 0000000..257aa5e --- /dev/null +++ b/god-mode/src/components/engine/blocks/Features.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { CheckCircle2 } from 'lucide-react'; + +interface FeatureItem { + title: string; + desc: string; + icon?: string; +} + +interface FeaturesProps { + items: FeatureItem[]; + layout?: 'grid' | 'list'; +} + +export default function Features({ items, layout = 'grid' }: FeaturesProps) { + return ( +
+
+
+ {items?.map((item, i) => ( + + +
+ +
+ {item.title} +
+ +

+ {item.desc} +

+
+
+ ))} +
+
+
+ ); +} diff --git a/god-mode/src/components/engine/blocks/Hero.tsx b/god-mode/src/components/engine/blocks/Hero.tsx new file mode 100644 index 0000000..b1501f7 --- /dev/null +++ b/god-mode/src/components/engine/blocks/Hero.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; + +interface HeroProps { + title: string; + subtitle?: string; + bg?: string; + ctaLabel?: string; + ctaUrl?: string; +} + +export default function Hero({ title, subtitle, bg, ctaLabel, ctaUrl }: HeroProps) { + const bgClass = bg === 'dark' ? 'bg-zinc-900 text-white' : + bg === 'image' ? 'bg-zinc-800 text-white' : // Placeholder for image logic + 'bg-white text-zinc-900'; + + return ( +
+
+

+ {title} +

+ {subtitle && ( +

+ {subtitle} +

+ )} + {(ctaLabel && ctaUrl) && ( +
+ +
+ )} +
+
+ ); +} diff --git a/god-mode/src/components/testing/ContentTester.tsx b/god-mode/src/components/testing/ContentTester.tsx new file mode 100644 index 0000000..e69de29 diff --git a/god-mode/src/components/testing/DuplicateDetector.tsx b/god-mode/src/components/testing/DuplicateDetector.tsx new file mode 100644 index 0000000..e69de29 diff --git a/god-mode/src/components/testing/GrammarCheck.tsx b/god-mode/src/components/testing/GrammarCheck.tsx new file mode 100644 index 0000000..e69de29 diff --git a/god-mode/src/components/testing/LinkChecker.tsx b/god-mode/src/components/testing/LinkChecker.tsx new file mode 100644 index 0000000..e69de29 diff --git a/god-mode/src/components/testing/SEOValidator.tsx b/god-mode/src/components/testing/SEOValidator.tsx new file mode 100644 index 0000000..e69de29 diff --git a/god-mode/src/components/testing/SchemaValidator.tsx b/god-mode/src/components/testing/SchemaValidator.tsx new file mode 100644 index 0000000..e69de29 diff --git a/god-mode/src/components/testing/TestRunner.tsx b/god-mode/src/components/testing/TestRunner.tsx new file mode 100644 index 0000000..63fa12c --- /dev/null +++ b/god-mode/src/components/testing/TestRunner.tsx @@ -0,0 +1,109 @@ + +import React, { useState } from 'react'; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Input } from "@/components/ui/input"; +import { Progress } from "@/components/ui/progress"; +import { CheckCircle2, AlertTriangle, XCircle, Search, FileText } from 'lucide-react'; +// We import the analysis functions directly since this is a client component in Astro/React +import { analyzeSeo, analyzeReadability } from '@/lib/testing/seo'; + +const TestRunner = () => { + const [content, setContent] = useState(''); + const [keyword, setKeyword] = useState(''); + const [results, setResults] = useState(null); + + const runTests = () => { + const seo = analyzeSeo(content, keyword); + const read = analyzeReadability(content); + + setResults({ seo, read }); + }; + + return ( +
+ + {/* Input Column */} +
+ +

+ Content Source +

+
+ setKeyword(e.target.value)} + /> +