design(dashboard): remove unused routes, rename existing routes to match Base44 menu structure

This commit is contained in:
2026-06-12 12:42:19 -07:00
parent 1532cb6111
commit 81994d4b6c
7 changed files with 12 additions and 444 deletions

View File

@@ -1,66 +0,0 @@
import { Suspense } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
import { Loader2, CreditCard, ArrowRight, ShieldCheck, Zap } from "lucide-react";
export default async function BillingPage(props: { params: Promise<{ projectId: string }> }) {
const { projectId } = await props.params;
return (
<div style={{ padding: "40px 48px", maxWidth: 1000, margin: "0 auto", fontFamily: "var(--font-inter), sans-serif" }}>
<div style={{ marginBottom: 32 }}>
<h1 style={{ fontFamily: "var(--font-lora), serif", fontSize: "1.8rem", color: "#1a1a1a", marginBottom: 8 }}>
Payments & Billing
</h1>
<p style={{ color: "#6b6560", fontSize: "0.95rem" }}>
Connect your bank account to start charging customers for this project.
</p>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr", gap: "24px" }}>
{/* Onboarding Card */}
<Card style={{ border: "1px solid #6366f1", boxShadow: "0 4px 14px rgba(99, 102, 241, 0.08)" }}>
<CardHeader>
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 8 }}>
<div style={{ background: "#e0e7ff", padding: 8, borderRadius: 8, color: "#4f46e5" }}>
<CreditCard style={{ width: 24, height: 24 }} />
</div>
<div>
<CardTitle style={{ fontSize: "1.2rem" }}>Accept Payments with Stripe</CardTitle>
<CardDescription>Setup takes 3 minutes. Vibn handles the code.</CardDescription>
</div>
</div>
</CardHeader>
<CardContent>
<div style={{ background: "#f8fafc", padding: 20, borderRadius: 8, marginBottom: 24 }}>
<h4 style={{ fontWeight: 600, fontSize: "0.9rem", color: "#111827", marginBottom: 12 }}>What you get immediately:</h4>
<ul style={{ display: "flex", flexDirection: "column", gap: 12, margin: 0, padding: 0, listStyle: "none" }}>
<li style={{ display: "flex", alignItems: "flex-start", gap: 8, fontSize: "0.85rem", color: "#4b5563" }}>
<Zap style={{ width: 16, height: 16, color: "#eab308", flexShrink: 0 }} />
<span><strong>AI Auto-Wiring:</strong> The Vibn AI will automatically inject your secure Stripe keys into your live Coolify application.</span>
</li>
<li style={{ display: "flex", alignItems: "flex-start", gap: 8, fontSize: "0.85rem", color: "#4b5563" }}>
<ShieldCheck style={{ width: 16, height: 16, color: "#22c55e", flexShrink: 0 }} />
<span><strong>Instant Compliance:</strong> Securely accept Apple Pay, Google Pay, and credit cards with PCI compliance handled automatically.</span>
</li>
</ul>
</div>
<p style={{ fontSize: "0.85rem", color: "#6b7280", lineHeight: 1.5 }}>
By connecting, you agree to Stripe's Services Agreement. Vibn takes a small 1% platform fee on successful transactions to keep the AI platform running.
</p>
</CardContent>
<CardFooter style={{ background: "#f9fafb", borderTop: "1px solid #f3f4f6", padding: "16px 24px" }}>
<button
className="bg-indigo-600 hover:bg-indigo-700 text-white transition-colors"
style={{ padding: "10px 20px", borderRadius: 6, fontSize: "0.9rem", fontWeight: 500, display: "flex", alignItems: "center", gap: 8 }}
>
Connect with Stripe <ArrowRight style={{ width: 16, height: 16 }} />
</button>
</CardFooter>
</Card>
</div>
</div>
);
}

View File

@@ -28,7 +28,7 @@ type Selection =
| { type: "image"; uuid: string }
| null;
export default function ProductTab() {
export default function CodeTab() {
const params = useParams();
const projectId = params.projectId as string;
const { anatomy, loading, error } = useAnatomy(projectId);

View File

@@ -1,351 +0,0 @@
import { BigQuery } from '@google-cloud/bigquery';
import { Suspense } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Loader2, Users, Target, Search, Database } from "lucide-react";
async function getMarketData(projectId: string) {
let bqOptions: any = { projectId: process.env.GCP_PROJECT_ID || 'master-ai-484822' };
if (process.env.GOOGLE_SERVICE_ACCOUNT_KEY_B64) {
try {
const saStr = Buffer.from(process.env.GOOGLE_SERVICE_ACCOUNT_KEY_B64, 'base64').toString('utf8');
bqOptions.credentials = JSON.parse(saStr);
bqOptions.projectId = bqOptions.credentials.project_id;
} catch (e) {}
}
const bigquery = new BigQuery(bqOptions);
try {
const [leads] = await bigquery.query({
query: `SELECT * FROM \`master-ai-484822.vibn_market_data.market_leads\` WHERE project_id = @projectId OR project_id = 'SYSTEM_BACKFILL' LIMIT 50`,
params: { projectId }
});
const [aggregations] = await bigquery.query({
query: `SELECT * FROM \`master-ai-484822.vibn_market_data.market_aggregations\` ORDER BY last_updated DESC LIMIT 1`
});
const [competitors] = await bigquery.query({
query: `SELECT * FROM \`master-ai-484822.vibn_market_data.software_providers_seo\` ORDER BY last_updated DESC LIMIT 10`
});
return { leads, aggregations: aggregations[0], competitors };
} catch (err) {
console.error("BigQuery Error:", err);
return { leads: [], aggregations: null, competitors: [] };
}
}
export default async function MarketPage(props: { params: Promise<{ projectId: string }> }) {
const { projectId } = await props.params;
return (
<div style={{ padding: "40px 48px", maxWidth: 1200, margin: "0 auto", fontFamily: "var(--font-inter), sans-serif" }}>
<div style={{ marginBottom: 32 }}>
<h1 style={{ fontFamily: "var(--font-lora), serif", fontSize: "1.8rem", color: "#1a1a1a", marginBottom: 8 }}>
Market Intelligence
</h1>
<p style={{ color: "#6b6560", fontSize: "0.95rem" }}>
Real-time TAM, verified leads, and competitor teardowns from the Vibn Data Co-op.
</p>
</div>
<Suspense fallback={<div className="flex justify-center p-12"><Loader2 className="animate-spin w-8 h-8 text-gray-400" /></div>}>
<MarketDataDisplay projectId={projectId} />
</Suspense>
</div>
);
}
async function MarketDataDisplay({ projectId }: { projectId: string }) {
const data = await getMarketData(projectId);
if (!data.aggregations && data.leads.length === 0) {
return (
<Card>
<CardContent className="py-16 text-center" style={{ paddingTop: '4rem', paddingBottom: '4rem', textAlign: 'center' }}>
<Database style={{ width: 48, height: 48, margin: '0 auto 16px', color: '#d0ccc4' }} />
<h3 style={{ fontSize: '1.125rem', fontWeight: 500, color: '#111827', marginBottom: '8px' }}>No Market Data Yet</h3>
<p style={{ color: '#6b7280', maxWidth: '28rem', margin: '0 auto' }}>
Ask the Vibn AI to run market research for your niche to populate this dashboard with leads, competitors, and SEO insights.
</p>
</CardContent>
</Card>
);
}
return (
<div style={{ display: "flex", flexDirection: "column", gap: "32px" }}>
{/* Overview Cards */}
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))", gap: "24px" }}>
<Card>
<CardHeader style={{ paddingBottom: '8px' }}>
<CardTitle style={{ fontSize: '0.875rem', fontWeight: 500, color: '#6b7280', display: 'flex', alignItems: 'center', gap: '8px' }}>
<Users style={{ width: 16, height: 16 }} /> Total Addressable Market
</CardTitle>
</CardHeader>
<CardContent>
<div style={{ fontSize: '1.875rem', fontWeight: 600, color: '#111827' }}>
{data.aggregations?.total_market_size?.toLocaleString() || "..."}
</div>
<p style={{ fontSize: '0.75rem', color: '#6b7280', marginTop: '4px' }}>Verified businesses in selected region</p>
</CardContent>
</Card>
<Card>
<CardHeader style={{ paddingBottom: '8px' }}>
<CardTitle style={{ fontSize: '0.875rem', fontWeight: 500, color: '#6b7280', display: 'flex', alignItems: 'center', gap: '8px' }}>
<Target style={{ width: 16, height: 16 }} /> Qualified Leads Captured
</CardTitle>
</CardHeader>
<CardContent>
<div style={{ fontSize: '1.875rem', fontWeight: 600, color: '#111827' }}>
{data.leads.length}
</div>
<p style={{ fontSize: '0.75rem', color: '#6b7280', marginTop: '4px' }}>Ready for cold outreach</p>
</CardContent>
</Card>
<Card>
<CardHeader style={{ paddingBottom: '8px' }}>
<CardTitle style={{ fontSize: '0.875rem', fontWeight: 500, color: '#6b7280', display: 'flex', alignItems: 'center', gap: '8px' }}>
<Search style={{ width: 16, height: 16 }} /> Tech Debt Indicator
</CardTitle>
</CardHeader>
<CardContent>
<div style={{ fontSize: '1.875rem', fontWeight: 600, color: '#111827' }}>
{data.aggregations ? Math.round((data.aggregations.websites_count / data.aggregations.total_market_size) * 100) : 0}%
</div>
<p style={{ fontSize: '0.75rem', color: '#6b7280', marginTop: '4px' }}>Of TAM have a website</p>
</CardContent>
</Card>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "32px" }}>
{/* Pain Points */}
{data.aggregations && (
<Card>
<CardHeader>
<CardTitle>Customer Pain Points</CardTitle>
<CardDescription>Extracted from Google Reviews</CardDescription>
</CardHeader>
<CardContent>
<div style={{ display: "flex", flexWrap: "wrap", gap: "8px" }}>
{Object.entries(typeof data.aggregations.customer_pain_points === 'string' ? JSON.parse(data.aggregations.customer_pain_points) : data.aggregations.customer_pain_points || {})
.sort(([, a], [, b]) => (b as number) - (a as number))
.slice(0, 15)
.map(([topic, count]) => (
<span key={topic} style={{ padding: "4px 12px", background: "#f0ede8", color: "#6b6560", fontSize: "0.75rem", fontWeight: 500, borderRadius: "9999px" }}>
{topic} ({(count as number).toLocaleString()})
</span>
))}
</div>
</CardContent>
</Card>
)}
{/* Sub-niches */}
{data.aggregations && (
<Card>
<CardHeader>
<CardTitle>Market Sub-Niches</CardTitle>
<CardDescription>Breakdown of primary category</CardDescription>
</CardHeader>
<CardContent>
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
{Object.entries(typeof data.aggregations.sub_niches === 'string' ? JSON.parse(data.aggregations.sub_niches) : data.aggregations.sub_niches || {})
.sort(([, a], [, b]) => (b as number) - (a as number))
.slice(0, 6)
.map(([topic, count]) => (
<div key={topic} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "0.875rem" }}>
<span style={{ color: "#374151", textTransform: "capitalize" }}>{topic.replace(/_/g, ' ')}</span>
<span style={{ fontWeight: 500 }}>{(count as number).toLocaleString()}</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
{/* Competitors */}
{data.competitors.length > 0 && (
<Card>
<CardHeader>
<CardTitle>SaaS Competitors & Ad Spend</CardTitle>
<CardDescription>Top incumbents and their Google Ads budget</CardDescription>
</CardHeader>
<div style={{ overflowX: "auto" }}>
<table style={{ width: "100%", fontSize: "0.875rem", textAlign: "left" }}>
<thead style={{ fontSize: "0.75rem", color: "#6b7280", textTransform: "uppercase", background: "#f9fafb", borderBottom: "1px solid #e5e7eb" }}>
<tr>
<th style={{ padding: "12px 24px" }}>Domain</th>
<th style={{ padding: "12px 24px" }}>Monthly Ad Spend</th>
<th style={{ padding: "12px 24px" }}>Organic Traffic</th>
<th style={{ padding: "12px 24px" }}>Top Paid Keywords</th>
</tr>
</thead>
<tbody>
{data.competitors.map((comp: any) => {
const paidKw = typeof comp.top_paid_keywords === 'string' ? JSON.parse(comp.top_paid_keywords) : comp.top_paid_keywords;
return (
<tr key={comp.domain} style={{ background: "#fff", borderBottom: "1px solid #e5e7eb" }}>
<td style={{ padding: "16px 24px", fontWeight: 500, color: "#111827" }}>{comp.domain}</td>
<td style={{ padding: "16px 24px", color: "#dc2626", fontWeight: 500 }}>
${Math.round(comp.ad_spend_usd).toLocaleString()}
</td>
<td style={{ padding: "16px 24px" }}>
{Math.round(comp.organic_traffic).toLocaleString()} /mo
</td>
<td style={{ padding: "16px 24px" }}>
<div style={{ display: "flex", flexWrap: "wrap", gap: "4px" }}>
{(paidKw || []).slice(0, 3).map((kw: string) => (
<span key={kw} style={{ padding: "2px 8px", background: "#eff6ff", color: "#1d4ed8", fontSize: "0.625rem", borderRadius: "4px" }}>
{kw}
</span>
))}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</Card>
)}
{/* Leads Table */}
{data.leads.length > 0 && (
<Card>
<CardHeader>
<CardTitle>Verified Leads</CardTitle>
<CardDescription>First {data.leads.length} contacts matching your target market</CardDescription>
</CardHeader>
<div style={{ overflowX: "auto" }}>
<table style={{ width: "100%", fontSize: "0.875rem", textAlign: "left" }}>
<thead style={{ fontSize: "0.75rem", color: "#6b7280", textTransform: "uppercase", background: "#f9fafb", borderBottom: "1px solid #e5e7eb" }}>
<tr>
<th style={{ padding: "12px 24px" }}>Business Name</th>
<th style={{ padding: "12px 24px" }}>Location</th>
<th style={{ padding: "12px 24px" }}>Rating</th>
<th style={{ padding: "12px 24px" }}>Contact</th>
</tr>
</thead>
<tbody>
{data.leads.map((lead: any) => {
const emails = typeof lead.emails === 'string' ? JSON.parse(lead.emails) : lead.emails;
return (
<tr key={lead.place_id} style={{ background: "#fff", borderBottom: "1px solid #e5e7eb" }}>
<td style={{ padding: "16px 24px", fontWeight: 500, color: "#111827" }}>
{lead.name}
{lead.website && (
<a href={lead.website.startsWith('http') ? lead.website : `https://${lead.website}`} target="_blank" rel="noreferrer" style={{ display: "block", color: "#2563eb", fontSize: "0.75rem", marginTop: "4px", textDecoration: "none" }}>
{lead.website.replace(/^https?:\/\//, '')}
</a>
)}
</td>
<td style={{ padding: "16px 24px" }}>
{lead.city}, {lead.region}
</td>
<td style={{ padding: "16px 24px" }}>
{lead.rating ? `${lead.rating} ⭐ (${lead.reviews_count})` : 'N/A'}
</td>
<td style={{ padding: "16px 24px" }}>
<div style={{ fontSize: "0.75rem", color: "#4b5563" }}>
{lead.phone && <div style={{ marginBottom: "4px" }}>{lead.phone}</div>}
{(emails || []).map((e: string) => (
<a key={e} href={`mailto:${e}`} style={{ display: "block", color: "#2563eb", textDecoration: "none" }}>
{e}
</a>
))}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</Card>
)}
{/* ───────────────────────────────────────────────────────────── */}
{/* GO-TO-MARKET (GTM) STRATEGY ENGINE */}
{/* ───────────────────────────────────────────────────────────── */}
<div style={{ marginTop: "48px", borderTop: "1px solid #e5e7eb", paddingTop: "32px" }}>
<div style={{ marginBottom: 24, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div>
<h2 style={{ fontFamily: "var(--font-lora), serif", fontSize: "1.5rem", color: "#111827", marginBottom: 4 }}>
Go-To-Market Strategy
</h2>
<p style={{ color: "#6b7280", fontSize: "0.875rem" }}>
Synthesize market data into an actionable marketing and positioning plan.
</p>
</div>
<button
style={{
background: "linear-gradient(to bottom right, #4f46e5, #312e81)",
color: "#fff",
padding: "8px 16px",
borderRadius: 6,
fontSize: "0.875rem",
fontWeight: 500,
boxShadow: "0 2px 4px rgba(79, 70, 229, 0.2)",
border: "none",
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: 8
}}
onClick={() => alert("This will deduct 500 AI Credits and generate the GTM strategy.")}
>
<span style={{ fontSize: "1.1rem", lineHeight: 1 }}></span> Generate GTM Plan (500 Credits)
</button>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "24px", opacity: 0.5, pointerEvents: "none" }}>
<Card>
<CardHeader>
<CardTitle>Brand Positioning</CardTitle>
<CardDescription>Value prop, target persona, and wedge strategy.</CardDescription>
</CardHeader>
<CardContent>
<div style={{ background: "#f9fafb", padding: 24, borderRadius: 8, textAlign: "center", border: "1px dashed #d1d5db" }}>
<p style={{ fontSize: "0.875rem", color: "#6b7280" }}>Generate a plan to reveal the positioning strategy.</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>SEO & Content Engine</CardTitle>
<CardDescription>Keyword gaps and initial blog architecture.</CardDescription>
</CardHeader>
<CardContent>
<div style={{ background: "#f9fafb", padding: 24, borderRadius: 8, textAlign: "center", border: "1px dashed #d1d5db" }}>
<p style={{ fontSize: "0.875rem", color: "#6b7280" }}>Generate a plan to reveal keyword targets.</p>
</div>
</CardContent>
</Card>
</div>
<div style={{ marginTop: "24px", opacity: 0.5, pointerEvents: "none" }}>
<Card>
<CardHeader>
<CardTitle style={{ display: "flex", alignItems: "center", gap: 8 }}>
Social Media Automation
<span style={{ fontSize: "0.65rem", background: "#f3f4f6", padding: "2px 6px", borderRadius: 4, fontWeight: 600, color: "#4b5563" }}>POWERED BY MISSINGLETTR</span>
</CardTitle>
<CardDescription>A 3-month automated drip campaign based on your positioning.</CardDescription>
</CardHeader>
<CardContent>
<div style={{ background: "#f9fafb", padding: 48, borderRadius: 8, textAlign: "center", border: "1px dashed #d1d5db" }}>
<p style={{ fontSize: "0.875rem", color: "#6b7280" }}>Generate a plan to automatically orchestrate your social media strategy via Missinglettr.</p>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@@ -44,7 +44,7 @@ type Preview = Anatomy["hosting"]["previews"][number];
// Main component
// ──────────────────────────────────────────────────
export default function HostingTab() {
export default function OverviewTab() {
const params = useParams();
const projectId = params.projectId as string;
const { anatomy, loading, error } = useAnatomy(projectId, { pollMs: 8000 });

View File

@@ -121,7 +121,7 @@ export default function PlanTab() {
<div style={railItems}>
<RailItem
id="objective"
label="Project Objective"
label="Product Brief"
icon={<Target />}
selectedId={selectedId}
onClick={setSelectedId}
@@ -282,7 +282,7 @@ function ObjectivePanel({
<div style={panel}>
<div style={panelHeader}>
<div>
<h2 style={panelTitle}>Project Objective</h2>
<h2 style={panelTitle}>Product Brief</h2>
<p style={panelDesc}>
The high-level business case and elevator pitch.
</p>

View File

@@ -112,7 +112,7 @@ function categoryDef(key: CategoryKey): CategoryDef {
return CATEGORIES.find(c => c.key === key)!;
}
export default function InfrastructureTab() {
export default function SecurityTab() {
const params = useParams();
const projectId = params.projectId as string;
const { anatomy, loading, error } = useAnatomy(projectId);

View File

@@ -6,18 +6,14 @@ import { usePathname } from "next/navigation";
import {
Search,
LayoutGrid,
Users,
ClipboardList,
Database,
BarChart2,
TrendingUp,
Globe,
Plug,
ShieldCheck,
Code2,
Bot,
Zap,
Terminal,
FileJson,
Settings,
ChevronDown,
ChevronRight,
@@ -56,29 +52,19 @@ export function DashboardSidebar({
const menuItems = [
{ segment: "overview", label: "Overview", Icon: LayoutGrid },
{ segment: "users", label: "Users", Icon: Users },
{ segment: "plan", label: "Plan & Specs", Icon: ClipboardList },
{ segment: "data", label: "Data", Icon: Database },
{
segment: "data",
label: "Data",
Icon: Database,
hasChildren: true,
},
{ segment: "analytics", label: "Analytics", Icon: BarChart2 },
{
segment: "marketing",
label: "Marketing",
Icon: TrendingUp,
badge: "New",
hasChildren: true,
segment: "analytics",
label: "Analytics",
Icon: BarChart2,
badge: "Soon",
},
{ segment: "domains", label: "Domains", Icon: Globe },
{ segment: "integrations", label: "Integrations", Icon: Plug },
{ segment: "security", label: "Security", Icon: ShieldCheck },
{ segment: "code", label: "Code", Icon: Code2 },
{ segment: "agents", label: "Agents", Icon: Bot, badge: "New" },
{ segment: "automations", label: "Automations", Icon: Zap },
{ segment: "logs", label: "Logs", Icon: Terminal },
{ segment: "api", label: "API", Icon: FileJson },
{
segment: "settings",
label: "Settings",
@@ -87,7 +73,6 @@ export function DashboardSidebar({
children: [
{ segment: "settings/app", label: "App Settings" },
{ segment: "settings/auth", label: "Authentication" },
{ segment: "settings/template", label: "App Template" },
],
},
];