BRIDGE_Modul-S_prod / src /modules /ModuleHub.tsx
BRIDGE Platform Agent
feat(platform): BRIDGE Modules hub with /modules/e and /modules/s routed workspaces
1955a0f
Raw History Blame Contribute Delete
8.48 kB
import { useEffect } from "react";
import { motion } from "motion/react";
import { ArrowRight, Boxes, CheckCircle2, XCircle, Zap } from "lucide-react";
import GlassCard from "../components/GlassCard";
import AnimatedLogo from "../components/AnimatedLogo";
import { useApiKey } from "../context/ApiKeyContext";
import { MINT_THEME } from "../context/ThemeContext";
import { useLaunch } from "./LaunchContext";
import { MODULES, MODULE_IDS } from "./registry";
import { preloadModulePages } from "./lazyPages";
const MODULE_CAPS: Record<string, string[]> = {
e: ["Sequence automation", "Unified inbox", "Warmup radar", "Deliverability analytics"],
s: ["Campaign management", "Lead database", "Webhook automation", "Email warmup"],
};
/**
* BRIDGE platform hub — the launcher. Real platform primitives only
* (GlassCard, AnimatedLogo, API-key status). Buttons trigger the module
* launch animation (re-seeded per launch) via LaunchContext.
*/
export default function ModuleHub() {
const { startLaunch } = useLaunch();
const { activeKey } = useApiKey();
useEffect(() => {
// Preload both modules' chunks so the first launch never janks.
const idle = window.requestIdleCallback ?? ((cb: () => void) => window.setTimeout(cb, 200));
const handle = idle(() => {
preloadModulePages("e");
preloadModulePages("s");
});
return () => {
if (typeof window.cancelIdleCallback === "function" && typeof handle === "number") {
window.cancelIdleCallback(handle);
}
};
}, []);
return (
<div className="min-h-screen bg-[#05070a] text-zinc-100 relative overflow-hidden">
{/* Ambient layers — platform primitives */}
<div
className="absolute inset-0 pointer-events-none"
style={{
background:
"radial-gradient(ellipse at top right, rgba(0, 229, 188, 0.10), transparent 60%), radial-gradient(ellipse at bottom left, rgba(99, 102, 241, 0.12), transparent 60%)",
}}
/>
<div className="relative z-10 max-w-5xl mx-auto px-4 md:px-8 py-12 md:py-20">
{/* Header */}
<div className="flex flex-col items-start gap-6 mb-12">
<div className="flex items-center gap-3">
<AnimatedLogo className="w-10 h-10" />
<div>
<div className="text-lg font-bold tracking-[0.08em] text-white">
BRIDGE<span className="text-[#00e5bc]">Modules</span>
</div>
<div className="text-[9px] font-bold tracking-[0.2em] text-zinc-500 uppercase font-mono">
Outbound Systems OS · Platform Hub
</div>
</div>
</div>
<div>
<h1 className="text-2xl md:text-3xl font-bold text-white tracking-tight">
Launch a workspace.
</h1>
<p className="text-sm text-zinc-400 mt-2 max-w-xl">
Two autonomous outbound engines, one command platform. Each module
assembles its own dashboard from live primitives when launched.
</p>
</div>
<div className="flex items-center gap-2">
{activeKey ? (
<div className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wider px-2.5 py-1 rounded-full border shadow-sm text-[#00e5bc] bg-[rgba(0,229,188,0.12)] border-[rgba(0,229,188,0.3)]">
<CheckCircle2 className="w-3 h-3" />
API Connected
</div>
) : (
<div className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wider text-rose-300 bg-rose-500/10 border border-rose-500/20 px-2.5 py-1 rounded-full">
<XCircle className="w-3 h-3" />
API Key Missing — set one in a module's Settings
</div>
)}
</div>
</div>
{/* Module launcher cards — real primitives */}
<div className="grid md:grid-cols-2 gap-4 md:gap-6">
{MODULE_IDS.map((id) => {
const def = MODULES[id];
const Icon = def.icon;
const theme = def.theme;
const mint = id === "e" ? MINT_THEME : null;
return (
<motion.div
key={id}
initial={{ opacity: 0, y: 18 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1], delay: id === "e" ? 0.05 : 0.15 }}
>
<GlassCard className="p-6 md:p-7 h-full flex flex-col group hover:border-white/10 transition-all duration-500">
<div className="flex items-start justify-between mb-5">
<div
className="w-11 h-11 rounded-xl flex items-center justify-center border"
style={{
backgroundColor: `${theme.accent}14`,
borderColor: `${theme.accent}44`,
boxShadow: `0 0 24px ${theme.accent}22`,
}}
>
<Icon className="w-5 h-5" style={{ color: theme.accent }} />
</div>
<span
className="text-[9px] font-bold font-mono uppercase tracking-[0.18em] px-2 py-1 rounded-md border"
style={{
color: theme.accentText,
borderColor: `${theme.accent}44`,
backgroundColor: `${theme.accent}14`,
}}
>
{theme.code}
</span>
</div>
<h2 className="text-xl font-bold text-white tracking-tight">
BRIDGE
<span style={{ color: theme.accent }}>{def.wordmark}</span>
</h2>
<p className="text-[13px] text-zinc-400 mt-2 leading-relaxed flex-1">
{def.blurb}
</p>
<ul className="mt-5 space-y-1.5">
{MODULE_CAPS[id].map((cap) => (
<li key={cap} className="flex items-center gap-2 text-xs text-zinc-300">
<span
className="w-1 h-1 rounded-full shrink-0"
style={{ backgroundColor: theme.accent, boxShadow: `0 0 6px ${theme.accent}` }}
/>
{cap}
</li>
))}
</ul>
<div className="mt-6 flex items-center justify-between gap-3">
<div className="flex items-center gap-1.5 text-[10px] font-mono text-zinc-500 uppercase tracking-wider">
<Boxes className="w-3.5 h-3.5" />
Slow-mo assembly on launch
</div>
<button
type="button"
onClick={() => startLaunch(id)}
onMouseEnter={() => preloadModulePages(id)}
className="flex items-center gap-2 px-4 py-2.5 rounded-lg text-[13px] font-bold transition-all duration-300 active:scale-[0.98] border"
style={{
backgroundColor: theme.buttonBg,
color: theme.buttonText,
borderColor: theme.buttonBg,
boxShadow: `0 0 24px ${theme.accent}33`,
}}
aria-label={`Launch ${def.label}`}
>
{id === "e" ? <Zap className="w-4 h-4" /> : <Boxes className="w-4 h-4" />}
Launch {def.label.replace(/^Module /, "")}
<ArrowRight className="w-4 h-4 group-hover:translate-x-0.5 transition-transform" />
</button>
</div>
{mint && (
<div className="mt-4 text-[10px] font-mono text-zinc-600 uppercase tracking-[0.16em]">
GreenTheme build · accent {mint.accent}
</div>
)}
</GlassCard>
</motion.div>
);
})}
</div>
<div className="mt-10 text-center text-[10px] font-mono text-zinc-600 uppercase tracking-[0.2em]">
Bridge Modules · Direct URLs — /modules/e · /modules/s
</div>
</div>
</div>
);
}