Files
vibn-agent-runner/vibn-frontend/app/[workspace]/agency/page.tsx

964 lines
27 KiB
TypeScript

"use client";
import React, { useState } from "react";
import { useParams } from "next/navigation";
// Minimal SVG Icons
const Icons = {
Search: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
),
Target: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="6" />
<circle cx="12" cy="12" r="2" />
</svg>
),
MessageCircle: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
),
Kanban: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
<path d="M8 7v7" />
<path d="M12 7v4" />
<path d="M16 7v9" />
</svg>
),
FileText: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<path d="M14 2v6h6" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10 9 9 9 8 9" />
</svg>
),
Users: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
),
Folder: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z" />
</svg>
),
Box: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" y1="22.08" x2="12" y2="12" />
</svg>
),
Server: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
<rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
<line x1="6" y1="6" x2="6.01" y2="6" />
<line x1="6" y1="18" x2="6.01" y2="18" />
</svg>
),
Repeat: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m17 2 4 4-4 4" />
<path d="M3 11v-1a4 4 0 0 1 4-4h14" />
<path d="m7 22-4-4 4-4" />
<path d="M21 13v1a4 4 0 0 1-4 4H3" />
</svg>
),
CreditCard: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="1" y="4" width="22" height="16" rx="2" ry="2" />
<line x1="1" y1="10" x2="23" y2="10" />
</svg>
),
TrendingDown: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="22 17 13.5 8.5 8.5 13.5 2 7" />
<polyline points="16 17 22 17 22 11" />
</svg>
),
Key: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" />
</svg>
),
Settings: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
),
ChevronDown: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m6 9 6 6 6-6" />
</svg>
),
ChevronRight: () => (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m9 18 6-6-6-6" />
</svg>
),
};
function NavItem({
icon: Icon,
label,
active,
badge,
inset = false,
}: {
icon?: React.ElementType;
label: string;
active?: boolean;
badge?: string;
inset?: boolean;
}) {
return (
<button
className="nav-item"
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
padding: "7px 10px",
paddingLeft: inset ? 32 : 10,
borderRadius: 6,
background: active ? "#f6f4f0" : "transparent",
color: active ? "#1a1a1a" : "#6b6560",
fontWeight: active ? 600 : 500,
fontSize: "13px",
transition: "background 0.15s",
border: "none",
cursor: "pointer",
}}
onMouseEnter={(e) => {
if (!active) e.currentTarget.style.background = "#f6f4f0";
}}
onMouseLeave={(e) => {
if (!active) e.currentTarget.style.background = "transparent";
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
{Icon && (
<span
style={{
opacity: active ? 0.9 : 0.5,
display: "grid",
placeItems: "center",
}}
>
<Icon />
</span>
)}
{label}
</div>
{badge && (
<span
style={{
background: "var(--accent)",
color: "#fff",
fontSize: "10px",
fontWeight: 700,
padding: "2px 6px",
borderRadius: 99,
}}
>
{badge}
</span>
)}
</button>
);
}
function SectionHeader({ children }: { children: React.ReactNode }) {
return (
<div
style={{
fontSize: "10px",
fontWeight: 600,
color: "#a09a90",
letterSpacing: "0.06em",
textTransform: "uppercase",
padding: "16px 10px 6px",
}}
>
{children}
</div>
);
}
export default function AgencyDashboard() {
const { workspace } = useParams();
const [activeTab, setActiveTab] = useState("find_clients");
const [clientOpen, setClientOpen] = useState<string | null>("apex");
const toggleClient = (id: string) => {
setClientOpen(clientOpen === id ? null : id);
};
return (
<div
style={{
display: "flex",
height: "100vh",
background: "#fcfbfa",
fontFamily: "var(--font-sans)",
}}
>
{/* ── LEFT SIDEBAR (Cadence Style) ── */}
<div
style={{
width: 240,
borderRight: "1px solid #eae6de",
background: "#f5f4ef",
display: "flex",
flexDirection: "column",
}}
>
{/* Workspace Switcher */}
<div
style={{
padding: "16px 16px 12px",
borderBottom: "1px solid #eae6de",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<div
style={{
width: 24,
height: 24,
borderRadius: 6,
background: "var(--accent)",
color: "#fff",
display: "grid",
placeItems: "center",
fontWeight: 700,
fontSize: "12px",
textTransform: "uppercase",
}}
>
{typeof workspace === "string" ? workspace.charAt(0) : "A"}
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<span
style={{
fontSize: "14px",
fontWeight: 600,
color: "#1a1a1a",
lineHeight: 1.2,
textTransform: "capitalize",
}}
>
{typeof workspace === "string"
? workspace.replace(/-/g, " ")
: "Atlas Agency"}
</span>
<span style={{ fontSize: "12px", color: "#6b6560" }}>
Pro · 2 members
</span>
</div>
</div>
</div>
{/* Global Search */}
<div style={{ padding: "12px 12px 0" }}>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
background: "#fff",
border: "1px solid #eae6de",
borderRadius: 6,
padding: "6px 10px",
color: "#a09a90",
fontSize: "13px",
}}
>
<Icons.Search />
<span>Search or jump to...</span>
<kbd
style={{
marginLeft: "auto",
fontSize: "10px",
fontFamily: "var(--font-mono)",
background: "#f5f4ef",
padding: "2px 4px",
borderRadius: 4,
}}
>
K
</kbd>
</div>
</div>
{/* Navigation */}
<div style={{ flex: 1, overflowY: "auto", padding: "8px 8px 24px" }}>
<SectionHeader>Growth</SectionHeader>
<div onClick={() => setActiveTab("find_clients")}>
<NavItem
icon={Icons.Target}
label="Find Clients"
active={activeTab === "find_clients"}
/>
</div>
<NavItem icon={Icons.MessageCircle} label="Outreach" />
<NavItem icon={Icons.Kanban} label="Pipeline" badge="3" />
<NavItem icon={Icons.FileText} label="Proposals & Contracts" />
<NavItem icon={Icons.Users} label="Contacts" />
<SectionHeader>Delivery (Projects)</SectionHeader>
{/* Nested Client Hub: Apex Plumbing */}
<div style={{ marginBottom: 4 }}>
<button
onClick={() => toggleClient("apex")}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
padding: "7px 10px",
borderRadius: 6,
background: "transparent",
color: "#1a1a1a",
fontWeight: 600,
fontSize: "13px",
border: "none",
cursor: "pointer",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Icons.Folder />
Apex Plumbing
</div>
<span
style={{
color: "#a09a90",
transform:
clientOpen === "apex" ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.15s",
}}
>
<Icons.ChevronDown />
</span>
</button>
{clientOpen === "apex" && (
<div
style={{
marginTop: 2,
display: "flex",
flexDirection: "column",
}}
>
<NavItem inset label="Projects (Live)" badge="1" />
<NavItem inset label="Infrastructure (Hosting)" />
<NavItem inset label="Secrets Manager" />
<NavItem inset label="Client Contacts" />
</div>
)}
</div>
{/* Nested Client Hub: Barton Creek */}
<div style={{ marginBottom: 4 }}>
<button
onClick={() => toggleClient("barton")}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
padding: "7px 10px",
borderRadius: 6,
background: "transparent",
color: "#1a1a1a",
fontWeight: 600,
fontSize: "13px",
border: "none",
cursor: "pointer",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Icons.Folder />
Barton Creek HVAC
</div>
<span
style={{
color: "#a09a90",
transform:
clientOpen === "barton" ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.15s",
}}
>
<Icons.ChevronDown />
</span>
</button>
{clientOpen === "barton" && (
<div
style={{
marginTop: 2,
display: "flex",
flexDirection: "column",
}}
>
<NavItem inset label="Projects (Live)" />
<NavItem inset label="Infrastructure (Hosting)" />
<NavItem inset label="Secrets Manager" />
<NavItem inset label="Client Contacts" />
</div>
)}
</div>
<SectionHeader>Finance & Ops</SectionHeader>
<NavItem icon={Icons.CreditCard} label="Invoices & Payments" />
<NavItem icon={Icons.TrendingDown} label="Expenses" />
<NavItem icon={Icons.Users} label="Users" />
<NavItem icon={Icons.Settings} label="Settings" />
</div>
</div>
{/* ── MAIN CONTENT AREA ── */}
<div style={{ flex: 1, overflowY: "auto" }}>
{activeTab === "find_clients" && <FindClientsView />}
</div>
</div>
);
}
// ── FIND CLIENTS VIEW (The Lead Gen Engine) ──
function FindClientsView() {
return (
<div style={{ padding: "40px 56px", maxWidth: 1100, margin: "0 auto" }}>
{/* Header */}
<div style={{ marginBottom: 32 }}>
<h1
style={{
fontSize: "28px",
fontWeight: 600,
color: "#1a1a1a",
letterSpacing: "-0.02em",
margin: "0 0 8px",
}}
>
Find Clients
</h1>
<p style={{ color: "#6b6560", fontSize: "15px", margin: 0 }}>
Vibn analyzes local businesses in your geofence and identifies
software gaps you can pitch and build.
</p>
</div>
{/* Geofence & Filters */}
<div
style={{
display: "flex",
gap: 12,
marginBottom: 24,
padding: "16px",
background: "#fff",
border: "1px solid #eae6de",
borderRadius: 12,
alignItems: "center",
}}
>
<div
style={{ flex: 1, display: "flex", flexDirection: "column", gap: 4 }}
>
<label
style={{
fontSize: "11px",
fontWeight: 600,
color: "#a09a90",
textTransform: "uppercase",
}}
>
Location
</label>
<div style={{ fontSize: "14px", fontWeight: 500, color: "#1a1a1a" }}>
📍 Austin, TX (50km radius)
</div>
</div>
<div style={{ width: 1, height: 32, background: "#eae6de" }} />
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
gap: 4,
paddingLeft: 12,
}}
>
<label
style={{
fontSize: "11px",
fontWeight: 600,
color: "#a09a90",
textTransform: "uppercase",
}}
>
Ideal Customer Profile
</label>
<div style={{ fontSize: "14px", fontWeight: 500, color: "#1a1a1a" }}>
&quot;Plumbers and HVAC&quot;
</div>
</div>
<button
style={{
padding: "8px 16px",
background: "var(--accent)",
color: "#fff",
borderRadius: 8,
fontWeight: 500,
fontSize: "13px",
border: "none",
cursor: "pointer",
boxShadow: "0 2px 8px var(--accent-glow)",
}}
>
Run Analysis
</button>
</div>
{/* Target Results */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
{/* Target Card 1 */}
<div
style={{
background: "#fff",
border: "1px solid #eae6de",
borderRadius: 12,
overflow: "hidden",
boxShadow: "0 4px 20px rgba(0,0,0,0.03)",
}}
>
<div
style={{ padding: "20px 24px", borderBottom: "1px solid #eae6de" }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
marginBottom: 12,
}}
>
<div>
<h3
style={{
fontSize: "18px",
fontWeight: 600,
color: "#1a1a1a",
margin: "0 0 4px",
}}
>
Apex Plumbing Services
</h3>
<a
href="#"
style={{
fontSize: "13px",
color: "var(--accent)",
textDecoration: "none",
}}
>
apexplumbingatx.com
</a>
</div>
<span
style={{
background: "#e8f5e9",
color: "#2e7d32",
padding: "4px 8px",
borderRadius: 6,
fontSize: "11px",
fontWeight: 600,
}}
>
High Intent
</span>
</div>
<div
style={{
display: "flex",
gap: 16,
fontSize: "13px",
color: "#6b6560",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 4 }}>
<Icons.Users /> 12 employees
</div>
<div>Est. Software Spend: $450/mo</div>
</div>
</div>
<div style={{ padding: "20px 24px", background: "#faf9f6" }}>
<div
style={{
fontSize: "11px",
fontWeight: 600,
color: "#a09a90",
textTransform: "uppercase",
marginBottom: 12,
}}
>
AI Software Gap Analysis
</div>
<ul
style={{
margin: 0,
padding: 0,
listStyle: "none",
display: "flex",
flexDirection: "column",
gap: 12,
}}
>
<li
style={{
display: "flex",
gap: 10,
fontSize: "14px",
color: "#1a1a1a",
}}
>
<span style={{ color: "#d32f2f" }}></span>
<div>
<strong>No online booking system.</strong>
<div
style={{ fontSize: "13px", color: "#6b6560", marginTop: 2 }}
>
They use a generic contact form. You can pitch a custom
scheduling portal.
</div>
</div>
</li>
<li
style={{
display: "flex",
gap: 10,
fontSize: "14px",
color: "#1a1a1a",
}}
>
<span style={{ color: "#d32f2f" }}></span>
<div>
<strong>Fragmented quoting.</strong>
<div
style={{ fontSize: "13px", color: "#6b6560", marginTop: 2 }}
>
Reviews indicate slow quote turnaround times.
</div>
</div>
</li>
</ul>
<button
style={{
width: "100%",
marginTop: 20,
padding: "10px",
background: "#1a1a1a",
color: "#fff",
borderRadius: 8,
fontWeight: 500,
fontSize: "14px",
border: "none",
cursor: "pointer",
}}
>
Generate Pitch & Proposal
</button>
</div>
</div>
{/* Target Card 2 */}
<div
style={{
background: "#fff",
border: "1px solid #eae6de",
borderRadius: 12,
overflow: "hidden",
boxShadow: "0 4px 20px rgba(0,0,0,0.03)",
}}
>
<div
style={{ padding: "20px 24px", borderBottom: "1px solid #eae6de" }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
marginBottom: 12,
}}
>
<div>
<h3
style={{
fontSize: "18px",
fontWeight: 600,
color: "#1a1a1a",
margin: "0 0 4px",
}}
>
Barton Creek HVAC
</h3>
<span style={{ fontSize: "13px", color: "#a09a90" }}>
No website detected
</span>
</div>
<span
style={{
background: "#fff3e0",
color: "#ef6c00",
padding: "4px 8px",
borderRadius: 6,
fontSize: "11px",
fontWeight: 600,
}}
>
Medium Intent
</span>
</div>
<div
style={{
display: "flex",
gap: 16,
fontSize: "13px",
color: "#6b6560",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 4 }}>
<Icons.Users /> 4 employees
</div>
<div>Est. Software Spend: $80/mo</div>
</div>
</div>
<div style={{ padding: "20px 24px", background: "#faf9f6" }}>
<div
style={{
fontSize: "11px",
fontWeight: 600,
color: "#a09a90",
textTransform: "uppercase",
marginBottom: 12,
}}
>
AI Software Gap Analysis
</div>
<ul
style={{
margin: 0,
padding: 0,
listStyle: "none",
display: "flex",
flexDirection: "column",
gap: 12,
}}
>
<li
style={{
display: "flex",
gap: 10,
fontSize: "14px",
color: "#1a1a1a",
}}
>
<span style={{ color: "#d32f2f" }}></span>
<div>
<strong>Missing digital presence.</strong>
<div
style={{ fontSize: "13px", color: "#6b6560", marginTop: 2 }}
>
Only operates via a Facebook page. You can pitch a
lead-capture landing page and admin dashboard.
</div>
</div>
</li>
</ul>
<button
style={{
width: "100%",
marginTop: 20,
padding: "10px",
background: "#fff",
color: "#1a1a1a",
border: "1px solid #eae6de",
borderRadius: 8,
fontWeight: 500,
fontSize: "14px",
cursor: "pointer",
}}
>
Generate Pitch & Proposal
</button>
</div>
</div>
</div>
</div>
);
}