Files
vibn-frontend/app/[workspace]/project/[projectId]/analytics/page.tsx
Mark Henderson 3cd477c295 feat: restructure project nav to Atlas | PRD | Build | Growth | Assist | Analytics
Tab bar:
- Removed: Design, Launch, Grow, Insights, Settings tabs
- Added: Growth, Assist, Analytics as top-level tabs
- Build remains, now a full hub

Build hub (/build):
- Left sub-nav groups: Code (apps), Layouts (surfaces), Infrastructure (6 items)
- Code section: scoped file browser per selected app
- Layouts section: surface overview cards with Edit link to /design
- Infrastructure section: summary panel linking to /infrastructure?tab=

Growth (/growth):
- Left nav: Marketing Site, Communications, Channels, Pages
- Each section: description + feature item grid + feedback CTA

Assist (/assist):
- Left nav: Emails, Chat Support, Support Site, Communications
- Each section: description + feature item grid + feedback CTA

Analytics (/analytics):
- Left nav: Customers, Usage, Events, Reports
- Each section: description + feature item grid + feedback CTA

Made-with: Cursor
2026-03-06 14:36:11 -08:00

134 lines
5.9 KiB
TypeScript

"use client";
import { Suspense } from "react";
import { useParams, useSearchParams, useRouter } from "next/navigation";
const SECTIONS = [
{
id: "customers",
label: "Customers",
icon: "◉",
title: "Customer List",
desc: "Every user who has signed up, their plan, usage, last seen, and lifecycle stage. Filter, search, and act on any segment.",
items: ["User Directory", "Lifecycle Stages", "Plan & Billing", "Activity Timeline", "Segment Builder"],
},
{
id: "usage",
label: "Usage",
icon: "∿",
title: "Usage & Activity",
desc: "How users interact with your product — feature adoption, session frequency, retention curves, and activation funnels.",
items: ["Feature Adoption", "Session Metrics", "Retention Curves", "Activation Funnel", "Power Users"],
},
{
id: "events",
label: "Events",
icon: "◬",
title: "Events & Tracking",
desc: "Every event your product fires — page views, clicks, conversions, and custom events — all tagged and queryable.",
items: ["Event Stream", "Custom Events", "Page Views", "Conversion Events", "Tag Manager"],
},
{
id: "reports",
label: "Reports",
icon: "▭",
title: "Reports",
desc: "MRR, churn, DAU/MAU, cohort analysis, and revenue reports. Export or share with your team on a schedule.",
items: ["Revenue (MRR/ARR)", "Churn Report", "DAU / MAU", "Cohort Analysis", "Custom Reports", "Scheduled Exports"],
},
] as const;
type SectionId = typeof SECTIONS[number]["id"];
const NAV_GROUP: React.CSSProperties = {
fontSize: "0.6rem", fontWeight: 700, color: "#b5b0a6",
letterSpacing: "0.09em", textTransform: "uppercase",
padding: "14px 12px 6px", fontFamily: "Outfit, sans-serif",
};
function AnalyticsInner() {
const params = useParams();
const searchParams = useSearchParams();
const router = useRouter();
const workspace = params.workspace as string;
const projectId = params.projectId as string;
const activeId = (searchParams.get("section") ?? "customers") as SectionId;
const active = SECTIONS.find(s => s.id === activeId) ?? SECTIONS[0];
const setSection = (id: string) =>
router.push(`/${workspace}/project/${projectId}/analytics?section=${id}`, { scroll: false });
return (
<div style={{ display: "flex", height: "100%", fontFamily: "Outfit, sans-serif", overflow: "hidden" }}>
{/* Left nav */}
<div style={{ width: 200, flexShrink: 0, borderRight: "1px solid #e8e4dc", background: "#faf8f5", display: "flex", flexDirection: "column", overflow: "auto" }}>
<div style={NAV_GROUP}>Analytics</div>
{SECTIONS.map(s => {
const isActive = activeId === s.id;
return (
<button key={s.id} onClick={() => setSection(s.id)} style={{
display: "flex", alignItems: "center", gap: 8, width: "100%", textAlign: "left",
background: isActive ? "#f0ece4" : "transparent", border: "none", cursor: "pointer",
padding: "6px 12px", borderRadius: 5,
fontSize: "0.78rem", fontWeight: isActive ? 600 : 440,
color: isActive ? "#1a1a1a" : "#5a5550",
}}
onMouseEnter={e => { if (!isActive) (e.currentTarget as HTMLElement).style.background = "#f6f4f0"; }}
onMouseLeave={e => { if (!isActive) (e.currentTarget as HTMLElement).style.background = "transparent"; }}
>
<span style={{ fontSize: "0.65rem", opacity: 0.55, width: 14, textAlign: "center" }}>{s.icon}</span>
{s.label}
</button>
);
})}
</div>
{/* Content */}
<div style={{ flex: 1, overflow: "auto", display: "flex", flexDirection: "column" }}>
<div style={{ padding: "28px 32px", maxWidth: 800 }}>
<div style={{ marginBottom: 24 }}>
<div style={{ fontSize: "1.1rem", fontWeight: 700, color: "#1a1a1a", marginBottom: 6 }}>{active.title}</div>
<div style={{ fontSize: "0.82rem", color: "#6b6560", lineHeight: 1.65, maxWidth: 520 }}>{active.desc}</div>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", gap: 12, marginBottom: 32 }}>
{active.items.map(item => (
<div key={item} style={{
background: "#fff", border: "1px solid #e8e4dc", borderRadius: 9,
padding: "14px 16px", display: "flex", alignItems: "center", justifyContent: "space-between",
}}>
<span style={{ fontSize: "0.8rem", fontWeight: 500, color: "#1a1a1a" }}>{item}</span>
<span style={{ fontSize: "0.65rem", color: "#c5c0b8", background: "#f6f4f0", padding: "2px 7px", borderRadius: 4 }}>Soon</span>
</div>
))}
</div>
<div style={{
background: "linear-gradient(135deg, #1a1a1a 0%, #2d2820 100%)",
borderRadius: 12, padding: "24px 28px",
display: "flex", alignItems: "center", justifyContent: "space-between", gap: 20,
}}>
<div>
<div style={{ fontSize: "0.85rem", fontWeight: 600, color: "#fff", marginBottom: 4 }}>{active.title} is coming to VIBN</div>
<div style={{ fontSize: "0.75rem", color: "#8a8478", lineHeight: 1.5 }}>We&apos;re building this section next. Shape it by telling us what you need.</div>
</div>
<button style={{ background: "#d4a04a", color: "#fff", border: "none", borderRadius: 8, padding: "9px 20px", fontSize: "0.78rem", fontWeight: 600, cursor: "pointer", whiteSpace: "nowrap", flexShrink: 0 }}>
Give feedback
</button>
</div>
</div>
</div>
</div>
);
}
export default function AnalyticsPage() {
return (
<Suspense fallback={<div style={{ display: "flex", height: "100%", alignItems: "center", justifyContent: "center", color: "#a09a90", fontFamily: "Outfit, sans-serif", fontSize: "0.85rem" }}>Loading</div>}>
<AnalyticsInner />
</Suspense>
);
}