"use client";
import { use, useState, useEffect } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import {
Monitor, Globe, Settings, Smartphone, Mail, BookOpen,
Lock, CheckCircle2, Loader2, ChevronRight, Pencil,
} from "lucide-react";
import { SCAFFOLD_REGISTRY } from "@/components/design-scaffolds";
// ---------------------------------------------------------------------------
// Surface definitions
// ---------------------------------------------------------------------------
interface Surface {
id: string;
name: string;
description: string;
icon: React.ElementType;
themes: Theme[];
}
interface Theme {
id: string;
name: string;
description: string;
tags: string[];
url: string;
preview: React.ReactNode;
}
// Mini UI mockups — each styled to feel like the library
const ShadcnPreview = () => (
);
const MantiinePreview = () => (
);
const HeroUIPreview = () => (
);
const DaisyPreview = () => (
);
const AcernityPreview = () => (
);
const TailwindPreview = () => (
className="flex…"
Custom →
);
const TremorPreview = () => (
{[60, 80, 45, 90, 70, 55].map((h, i) => (
))}
);
const NativewindPreview = () => (
);
const GluestackPreview = () => (
);
const ReactEmailPreview = () => (
);
const NextraPreview = () => (
{['Getting started', 'Installation', 'API', 'Examples'].map(l => (
))}
);
const ALL_SURFACES: Surface[] = [
{
id: "web-app",
name: "Web App",
description: "The core product your users log into — dashboards, features, settings",
icon: Monitor,
themes: [
{ id: "shadcn", name: "shadcn/ui", description: "Copy-paste components on Radix primitives. You own the code, fully customisable.", tags: ["Tailwind", "Radix", "Copy-paste"], url: "https://ui.shadcn.com", preview: },
{ id: "mantine", name: "Mantine", description: "100+ components with hooks, forms, charts. Best for data-heavy apps.", tags: ["React", "Charts", "Forms"], url: "https://mantine.dev", preview: },
{ id: "hero-ui", name: "HeroUI", description: "Beautiful, accessible components with smooth animations and dark mode.", tags: ["Tailwind", "Animations", "Accessible"], url: "https://heroui.com", preview: },
{ id: "tremor", name: "Tremor", description: "Dashboard components — charts, KPIs, tables — designed for analytics UIs.", tags: ["Charts", "Dashboard", "Analytics"], url: "https://tremor.so", preview: },
],
},
{
id: "marketing",
name: "Marketing Site",
description: "Public-facing landing page, blog, pricing — brand expression and conversion",
icon: Globe,
themes: [
{ id: "daisy-ui", name: "DaisyUI", description: "Tailwind plugin with 48 built-in themes. Fastest path to a beautiful site.", tags: ["Tailwind", "Themes", "Plugin"], url: "https://daisyui.com", preview: },
{ id: "hero-ui", name: "HeroUI", description: "Beautiful components with gradients and smooth animations.", tags: ["Tailwind", "Animations", "Modern"], url: "https://heroui.com", preview: },
{ id: "aceternity", name: "Aceternity UI", description: "Animated, visually striking components for premium landing pages.", tags: ["Animations", "Dark", "Premium"], url: "https://ui.aceternity.com", preview: },
{ id: "tailwind-only", name: "Tailwind only", description: "No component library — full creative control with pure Tailwind CSS.", tags: ["Custom", "Flexible", "Minimal"], url: "https://tailwindcss.com", preview: },
],
},
{
id: "admin",
name: "Admin Panel",
description: "Internal tool for managing your business — users, support, billing, analytics",
icon: Settings,
themes: [
{ id: "mantine", name: "Mantine", description: "The best choice for admin — comprehensive tables, forms, and data components.", tags: ["Tables", "Forms", "Charts"], url: "https://mantine.dev", preview: },
{ id: "shadcn", name: "shadcn/ui", description: "Clean, neutral components. Great if you want the admin to match the main app.", tags: ["Tailwind", "Consistent", "Clean"], url: "https://ui.shadcn.com", preview: },
{ id: "tremor", name: "Tremor", description: "Analytics-first — built for KPI dashboards, charts, and data tables.", tags: ["Analytics", "Charts", "KPIs"], url: "https://tremor.so", preview: },
],
},
{
id: "mobile",
name: "Mobile App",
description: "iOS and Android companion app — touch-first, native feel",
icon: Smartphone,
themes: [
{ id: "nativewind", name: "NativeWind", description: "Use Tailwind CSS in React Native. Consistent style across web and mobile.", tags: ["Tailwind", "React Native", "Expo"], url: "https://nativewind.dev", preview: },
{ id: "gluestack", name: "Gluestack UI", description: "Universal components for React Native — accessible, well-tested, comprehensive.", tags: ["Universal", "Accessible", "Expo"], url: "https://gluestack.io", preview: },
],
},
{
id: "email",
name: "Email",
description: "Transactional and marketing emails — welcome, billing, notifications",
icon: Mail,
themes: [
{ id: "react-email", name: "React Email", description: "Build emails with React components. Works with any email provider.", tags: ["React", "Resend", "Cross-client"], url: "https://react.email", preview: },
],
},
{
id: "docs",
name: "Docs / Content",
description: "Documentation, knowledge base, or blog for your product",
icon: BookOpen,
themes: [
{ id: "nextra", name: "Nextra", description: "Next.js-based docs site. Markdown-first, fast, with great search.", tags: ["Next.js", "Markdown", "Search"], url: "https://nextra.site", preview: },
{ id: "shadcn", name: "shadcn/ui + custom", description: "Build a fully custom docs site that matches your product exactly.", tags: ["Custom", "Tailwind", "Flexible"], url: "https://ui.shadcn.com", preview: },
],
},
];
// ---------------------------------------------------------------------------
// Surface section — tab toggle + scaffold preview + lock in
// ---------------------------------------------------------------------------
function SurfaceSection({
surface,
selectedThemeId,
lockedThemeId,
onSelect,
onLock,
onUnlock,
saving,
}: {
surface: Surface;
selectedThemeId: string | null;
lockedThemeId: string | null;
onSelect: (themeId: string) => void;
onLock: () => void;
onUnlock: () => void;
saving: boolean;
}) {
// Active preview tab — if locked show that, otherwise the selected/first
const previewId = lockedThemeId ?? selectedThemeId ?? surface.themes[0]?.id ?? null;
const activeTheme = surface.themes.find(t => t.id === previewId);
const ScaffoldComponent = previewId
? SCAFFOLD_REGISTRY[surface.id]?.[previewId]
: null;
return (
{/* Tab bar */}
{surface.themes.map(theme => {
const isActive = theme.id === previewId;
const isLocked = theme.id === lockedThemeId;
return (
);
})}
{/* Spacer + actions */}
{/* Scaffold preview — browser chrome frame */}
{/* Browser chrome */}
{activeTheme ? `/${surface.id} — ${activeTheme.name}` : ""}
{/* Scaffold */}
{ScaffoldComponent
?
: (
Select a library above to preview
)
}
{/* Theme description */}
{activeTheme && (
{activeTheme.description}
{activeTheme.tags.map(t => (
{t}
))}
)}
);
}
// ---------------------------------------------------------------------------
// Phase 1 — Surface picker
// ---------------------------------------------------------------------------
function SurfacePicker({
onConfirm,
saving,
}: {
onConfirm: (ids: string[]) => void;
saving: boolean;
}) {
const [selected, setSelected] = useState>(new Set());
const toggle = (id: string) => {
setSelected(prev => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
};
return (
Design surfaces
Which surfaces does your product need? Select all that apply — you can always add more later.
{ALL_SURFACES.map(surface => {
const Icon = surface.icon;
const isSelected = selected.has(surface.id);
return (
);
})}
{selected.size === 0 && (
Select at least one surface to continue
)}
);
}
// ---------------------------------------------------------------------------
// Page
// ---------------------------------------------------------------------------
export default function DesignPage({
params,
}: {
params: Promise<{ workspace: string; projectId: string }>;
}) {
const { projectId } = use(params);
const [surfaces, setSurfaces] = useState([]);
const [surfaceThemes, setSurfaceThemes] = useState>({});
const [selectedThemes, setSelectedThemes] = useState>({});
const [activeSurfaceId, setActiveSurfaceId] = useState(null);
const [savingLock, setSavingLock] = useState(null);
const [savingSurfaces, setSavingSurfaces] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/projects/${projectId}/design-surfaces`)
.then(r => r.json())
.then(d => {
const loaded = (d.surfaces ?? []) as string[];
setSurfaces(loaded);
setSurfaceThemes(d.surfaceThemes ?? {});
setSelectedThemes(d.surfaceThemes ?? {});
if (loaded.length > 0) setActiveSurfaceId(loaded[0]);
})
.catch(() => toast.error("Failed to load design data"))
.finally(() => setLoading(false));
}, [projectId]);
const handleConfirmSurfaces = async (ids: string[]) => {
setSavingSurfaces(true);
try {
const res = await fetch(`/api/projects/${projectId}/design-surfaces`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ surfaces: ids }),
});
if (!res.ok) throw new Error();
setSurfaces(ids);
setActiveSurfaceId(ids[0] ?? null);
toast.success("Surfaces saved");
} catch {
toast.error("Failed to save surfaces");
} finally {
setSavingSurfaces(false);
}
};
const handleLock = async (surfaceId: string) => {
const themeId = selectedThemes[surfaceId];
if (!themeId) return;
setSavingLock(surfaceId);
try {
const res = await fetch(`/api/projects/${projectId}/design-surfaces`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ surface: surfaceId, theme: themeId }),
});
if (!res.ok) throw new Error();
setSurfaceThemes(prev => ({ ...prev, [surfaceId]: themeId }));
const surface = ALL_SURFACES.find(s => s.id === surfaceId);
const theme = surface?.themes.find(t => t.id === themeId);
toast.success(`${surface?.name} → ${theme?.name} locked in`);
} catch {
toast.error("Failed to lock in theme");
} finally {
setSavingLock(null);
}
};
const handleUnlock = (surfaceId: string) => {
setSurfaceThemes(prev => {
const next = { ...prev };
delete next[surfaceId];
return next;
});
};
if (loading) {
return (
);
}
// Phase 1 — no surfaces set yet
if (surfaces.length === 0) {
return (
);
}
// Phase 2 — left nav + main content
const activeSurfaces = ALL_SURFACES.filter(s => surfaces.includes(s.id));
const currentSurface = activeSurfaces.find(s => s.id === activeSurfaceId) ?? activeSurfaces[0];
const lockedCount = Object.keys(surfaceThemes).length;
return (
{/* Left nav */}
{lockedCount === activeSurfaces.length && lockedCount > 0 && (
All surfaces locked
)}
{/* Main content */}
{currentSurface && (
setSelectedThemes(prev => ({ ...prev, [currentSurface.id]: themeId }))}
onLock={() => handleLock(currentSurface.id)}
onUnlock={() => handleUnlock(currentSurface.id)}
saving={savingLock === currentSurface.id}
/>
)}
);
}