- Deleted monolithic design-scaffolds.tsx (1154 lines, 72KB)
- New folder: components/design-scaffolds/
- types.ts — ThemeColor interface + all theme palettes
- web-app.tsx — SaaS app: Dashboard / Users / Settings with AppShell
- marketing.tsx — Landing page: hero, features, pricing, CTA
- admin.tsx — NEW unique admin: System health (servers/CPU/mem/errors),
Moderation (user table + audit log + ban/impersonate),
Config (API keys, feature flags, webhooks)
- mobile.tsx — Phone frame previews: NativeWind / Gluestack
- email.tsx — React Email welcome template preview
- docs.tsx — Nextra + shadcn docs previews
- index.ts — SCAFFOLD_REGISTRY + THEME_REGISTRY (only import needed)
- Adding a new surface = create one file + add 2 lines to index.ts
Made-with: Cursor
327 lines
19 KiB
TypeScript
327 lines
19 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { ThemeColor, TABLE_ROWS, MANTINE_THEMES, SHADCN_THEMES, TREMOR_THEMES } from "./types";
|
|
|
|
// Admin Panel surface — internal tool for operating the business.
|
|
// Context: the ops/support team, not end users.
|
|
// Shows: system health, user moderation, API config — NOT business metrics.
|
|
|
|
type AdminPage = "System" | "Moderation" | "Config";
|
|
|
|
const SERVERS = [
|
|
{ name: "api-prod-01", region: "us-east-1", status: "healthy", cpu: 34, mem: 58, uptime: "99.98%" },
|
|
{ name: "api-prod-02", region: "us-east-1", status: "healthy", cpu: 41, mem: 62, uptime: "99.97%" },
|
|
{ name: "worker-01", region: "eu-west-2", status: "warning", cpu: 89, mem: 71, uptime: "99.91%" },
|
|
{ name: "db-primary", region: "us-east-1", status: "healthy", cpu: 22, mem: 44, uptime: "100%" },
|
|
];
|
|
|
|
const AUDIT_LOG = [
|
|
{ user: "alice@co.com", action: "Banned user", target: "spammer@evil.com", time: "2m ago" },
|
|
{ user: "ben@co.com", action: "Promoted to Admin", target: "clara@co.com", time: "14m ago" },
|
|
{ user: "system", action: "Rate limit hit", target: "api/v1/search", time: "31m ago" },
|
|
{ user: "david@co.com", action: "Reset password", target: "eve@co.com", time: "1h ago" },
|
|
];
|
|
|
|
function StatusDot({ status }: { status: string }) {
|
|
const color = status === "healthy" ? "#22c55e" : status === "warning" ? "#f59e0b" : "#ef4444";
|
|
return <div style={{ width: 7, height: 7, borderRadius: "50%", background: color, flexShrink: 0 }} />;
|
|
}
|
|
|
|
function MiniBar({ value, color }: { value: number; color: string }) {
|
|
const bg = value > 80 ? "#ef444430" : "#f3f4f6";
|
|
const fill = value > 80 ? "#ef4444" : color;
|
|
return (
|
|
<div style={{ width: 52, height: 5, borderRadius: 3, background: bg, overflow: "hidden" }}>
|
|
<div style={{ width: `${value}%`, height: "100%", background: fill, borderRadius: 3 }} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Admin shell — denser than app shell, breadcrumb + secondary nav
|
|
// ---------------------------------------------------------------------------
|
|
function AdminShell({ t, page, onNav, children }: {
|
|
t: ThemeColor; page: AdminPage; onNav: (p: AdminPage) => void; children: React.ReactNode;
|
|
}) {
|
|
const NAV: { label: AdminPage; icon: string; badge?: string }[] = [
|
|
{ label: "System", icon: "◈", badge: "1 warn" },
|
|
{ label: "Moderation", icon: "◉" },
|
|
{ label: "Config", icon: "⚙" },
|
|
];
|
|
return (
|
|
<div style={{ display: "flex", height: "100%", fontFamily: "system-ui, sans-serif", fontSize: 12 }}>
|
|
{/* Left nav */}
|
|
<div style={{ width: 160, flexShrink: 0, background: "#111827", display: "flex", flexDirection: "column", padding: "12px 8px" }}>
|
|
<div style={{ display: "flex", alignItems: "center", gap: 7, padding: "0 6px", marginBottom: 20 }}>
|
|
<div style={{ width: 20, height: 20, borderRadius: 4, background: t.primary, flexShrink: 0 }} />
|
|
<span style={{ fontWeight: 700, fontSize: 10, color: "#f9fafb" }}>Acme Admin</span>
|
|
</div>
|
|
<div style={{ fontSize: 9, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.08em", padding: "0 6px", marginBottom: 6 }}>Operations</div>
|
|
{NAV.map(({ label, icon, badge }) => (
|
|
<button key={label} onClick={() => onNav(label)} style={{
|
|
display: "flex", alignItems: "center", gap: 7, width: "100%",
|
|
padding: "7px 8px", borderRadius: 6, border: "none", cursor: "pointer",
|
|
background: page === label ? `${t.primary}22` : "none",
|
|
color: page === label ? t.primary : "#9ca3af",
|
|
fontSize: 11, fontWeight: page === label ? 600 : 400,
|
|
textAlign: "left", marginBottom: 2,
|
|
}}>
|
|
<span style={{ fontSize: 10 }}>{icon}</span>
|
|
<span style={{ flex: 1 }}>{label}</span>
|
|
{badge && <span style={{ fontSize: 9, background: "#f59e0b22", color: "#f59e0b", padding: "1px 5px", borderRadius: 4, fontWeight: 600 }}>{badge}</span>}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Main */}
|
|
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
|
|
{/* Top bar */}
|
|
<div style={{ height: 40, background: "#fff", borderBottom: "1px solid #e5e7eb", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 16px", flexShrink: 0 }}>
|
|
<div style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, color: "#9ca3af" }}>
|
|
<span>Admin</span><span>/</span>
|
|
<span style={{ color: "#111827", fontWeight: 600 }}>{page}</span>
|
|
</div>
|
|
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
|
<span style={{ fontSize: 10, fontFamily: "monospace", background: "#f9fafb", border: "1px solid #e5e7eb", padding: "2px 8px", borderRadius: 4, color: "#6b7280" }}>v2.4.1-prod</span>
|
|
<div style={{ width: 24, height: 24, borderRadius: "50%", background: t.primary, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 9, fontWeight: 700, color: "#fff" }}>A</div>
|
|
</div>
|
|
</div>
|
|
<div style={{ flex: 1, overflow: "auto", background: "#f9fafb" }}>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// System tab — server health, error rate, uptime
|
|
// ---------------------------------------------------------------------------
|
|
function SystemView({ t }: { t: ThemeColor }) {
|
|
return (
|
|
<div style={{ padding: 16 }}>
|
|
{/* Summary row */}
|
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 8, marginBottom: 12 }}>
|
|
{[
|
|
{ l: "Uptime", v: "99.96%", good: true },
|
|
{ l: "Error rate", v: "0.12%", good: true },
|
|
{ l: "Avg latency", v: "84ms", good: true },
|
|
{ l: "Alerts", v: "1 active",good: false },
|
|
].map(s => (
|
|
<div key={s.l} style={{ background: "#fff", border: `1px solid ${s.good ? "#e5e7eb" : "#fde68a"}`, borderRadius: 8, padding: "10px 12px" }}>
|
|
<p style={{ fontSize: 9, color: "#6b7280", marginBottom: 3 }}>{s.l}</p>
|
|
<p style={{ fontSize: 15, fontWeight: 700, color: s.good ? "#111827" : "#d97706" }}>{s.v}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Server table */}
|
|
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
|
|
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 11, fontWeight: 600, color: "#111827", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
<span>Servers</span>
|
|
<span style={{ fontSize: 9, color: "#22c55e" }}>● Live</span>
|
|
</div>
|
|
<div>
|
|
{SERVERS.map(s => (
|
|
<div key={s.name} style={{ display: "flex", alignItems: "center", padding: "9px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
|
|
<StatusDot status={s.status} />
|
|
<div style={{ flex: 1 }}>
|
|
<p style={{ fontSize: 10, fontWeight: 600, color: "#111827", fontFamily: "monospace" }}>{s.name}</p>
|
|
<p style={{ fontSize: 9, color: "#9ca3af" }}>{s.region}</p>
|
|
</div>
|
|
<div style={{ display: "flex", flex: "column", gap: 3, alignItems: "flex-end" }}>
|
|
<div style={{ display: "flex", alignItems: "center", gap: 5 }}>
|
|
<span style={{ fontSize: 9, color: "#6b7280", width: 22 }}>CPU</span>
|
|
<MiniBar value={s.cpu} color={t.primary} />
|
|
<span style={{ fontSize: 9, color: s.cpu > 80 ? "#ef4444" : "#374151", width: 24, textAlign: "right" }}>{s.cpu}%</span>
|
|
</div>
|
|
<div style={{ display: "flex", alignItems: "center", gap: 5 }}>
|
|
<span style={{ fontSize: 9, color: "#6b7280", width: 22 }}>MEM</span>
|
|
<MiniBar value={s.mem} color={t.primary} />
|
|
<span style={{ fontSize: 9, color: s.mem > 80 ? "#ef4444" : "#374151", width: 24, textAlign: "right" }}>{s.mem}%</span>
|
|
</div>
|
|
</div>
|
|
<span style={{ fontSize: 9, fontFamily: "monospace", color: "#22c55e" }}>{s.uptime}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Error timeline */}
|
|
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8, marginTop: 10 }}>
|
|
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 11, fontWeight: 600, color: "#111827" }}>Error rate — last 24h</div>
|
|
<div style={{ padding: "12px 14px", display: "flex", alignItems: "flex-end", gap: 3, height: 56 }}>
|
|
{[2,1,3,2,1,1,0,0,1,2,8,3,2,1,0,1,2,1,1,0,1,0,1,2].map((h, i) => (
|
|
<div key={i} style={{ flex: 1, borderRadius: 2, background: h > 5 ? "#ef4444" : h > 2 ? "#f59e0b" : t.activeBg, height: `${Math.max(h * 10, 3)}%`, minHeight: 2 }} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Moderation tab — user management with admin actions
|
|
// ---------------------------------------------------------------------------
|
|
function ModerationView({ t }: { t: ThemeColor }) {
|
|
return (
|
|
<div style={{ padding: 16 }}>
|
|
{/* User table with actions */}
|
|
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8, marginBottom: 10 }}>
|
|
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
<span style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>User management</span>
|
|
<div style={{ display: "flex", gap: 6 }}>
|
|
<input style={{ height: 26, padding: "0 9px", border: "1px solid #e5e7eb", borderRadius: 5, fontSize: 10, outline: "none", width: 110 }} placeholder="Search users..." />
|
|
<select style={{ height: 26, padding: "0 8px", border: "1px solid #e5e7eb", borderRadius: 5, fontSize: 10, background: "#fff", color: "#374151" }}>
|
|
<option>All statuses</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
{TABLE_ROWS.map(r => (
|
|
<div key={r.name} style={{ display: "flex", alignItems: "center", padding: "9px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
|
|
<div style={{ width: 26, height: 26, borderRadius: "50%", background: t.activeBg, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 700, color: t.activeFg, flexShrink: 0 }}>
|
|
{r.name[0]}
|
|
</div>
|
|
<div style={{ flex: 1 }}>
|
|
<p style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>{r.name}</p>
|
|
<p style={{ fontSize: 9, color: "#9ca3af", fontFamily: "monospace" }}>{r.email}</p>
|
|
</div>
|
|
<span style={{ fontSize: 9, padding: "2px 7px", borderRadius: 4, background: r.role === "Admin" ? "#fef3c7" : "#f3f4f6", color: r.role === "Admin" ? "#92400e" : "#6b7280", fontWeight: 600 }}>{r.role}</span>
|
|
<span style={{ fontSize: 9, padding: "2px 7px", borderRadius: 4, fontWeight: 600, background: r.status === "Active" ? "#d1fae5" : r.status === "Pending" ? "#fef3c7" : "#fee2e2", color: r.status === "Active" ? "#065f46" : r.status === "Pending" ? "#92400e" : "#991b1b" }}>{r.status}</span>
|
|
{/* Admin action buttons */}
|
|
<div style={{ display: "flex", gap: 4 }}>
|
|
<button style={{ height: 22, padding: "0 8px", borderRadius: 4, border: "1px solid #e5e7eb", fontSize: 9, background: "#fff", color: "#374151", cursor: "pointer" }}>Impersonate</button>
|
|
<button style={{ height: 22, padding: "0 8px", borderRadius: 4, border: "1px solid #fca5a5", fontSize: 9, background: "#fff", color: "#dc2626", cursor: "pointer" }}>Ban</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Audit log */}
|
|
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
|
|
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 11, fontWeight: 600, color: "#111827" }}>Audit log</div>
|
|
{AUDIT_LOG.map((e, i) => (
|
|
<div key={i} style={{ display: "flex", alignItems: "center", padding: "8px 14px", borderBottom: "1px solid #f9fafb", gap: 8 }}>
|
|
<div style={{ width: 6, height: 6, borderRadius: "50%", background: t.primary, flexShrink: 0 }} />
|
|
<span style={{ fontSize: 9, fontFamily: "monospace", color: "#6b7280", minWidth: 100 }}>{e.user}</span>
|
|
<span style={{ fontSize: 10, fontWeight: 500, color: "#111827", flex: 1 }}>{e.action}</span>
|
|
<span style={{ fontSize: 9, fontFamily: "monospace", color: "#9ca3af" }}>{e.target}</span>
|
|
<span style={{ fontSize: 9, color: "#d1d5db" }}>{e.time}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Config tab — API keys, feature flags, webhooks
|
|
// ---------------------------------------------------------------------------
|
|
function ConfigView({ t }: { t: ThemeColor }) {
|
|
return (
|
|
<div style={{ padding: 16, display: "flex", flexDirection: "column", gap: 10 }}>
|
|
{/* API keys */}
|
|
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
|
|
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
<span style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>API Keys</span>
|
|
<button style={{ height: 24, padding: "0 10px", borderRadius: 5, background: t.primary, color: t.primaryFg, border: "none", fontSize: 9, fontWeight: 600, cursor: "pointer" }}>+ Generate</button>
|
|
</div>
|
|
{[
|
|
{ name: "Production", key: "sk_prod_••••••••••••••3d8f", created: "Jan 2, 2026", last: "2m ago" },
|
|
{ name: "Staging", key: "sk_test_••••••••••••••9a2c", created: "Dec 10, 2025", last: "1d ago" },
|
|
].map(k => (
|
|
<div key={k.name} style={{ display: "flex", alignItems: "center", padding: "9px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
|
|
<div style={{ flex: 1 }}>
|
|
<p style={{ fontSize: 10, fontWeight: 600, color: "#111827", marginBottom: 2 }}>{k.name}</p>
|
|
<p style={{ fontSize: 9, fontFamily: "monospace", color: "#9ca3af" }}>{k.key}</p>
|
|
</div>
|
|
<span style={{ fontSize: 9, color: "#9ca3af" }}>Created {k.created}</span>
|
|
<span style={{ fontSize: 9, color: "#22c55e" }}>Used {k.last}</span>
|
|
<button style={{ fontSize: 9, padding: "2px 8px", borderRadius: 4, border: "1px solid #fca5a5", color: "#dc2626", background: "none", cursor: "pointer" }}>Revoke</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Feature flags */}
|
|
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
|
|
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 11, fontWeight: 600, color: "#111827" }}>Feature flags</div>
|
|
{[
|
|
{ flag: "new-onboarding", on: true, env: "production" },
|
|
{ flag: "ai-suggestions", on: true, env: "production" },
|
|
{ flag: "bulk-export-v2", on: false, env: "staging" },
|
|
{ flag: "realtime-collab", on: false, env: "dev" },
|
|
].map(f => (
|
|
<div key={f.flag} style={{ display: "flex", alignItems: "center", padding: "8px 14px", borderBottom: "1px solid #f9fafb", gap: 10 }}>
|
|
<p style={{ flex: 1, fontSize: 10, fontFamily: "monospace", color: "#374151" }}>{f.flag}</p>
|
|
<span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 3, background: f.env === "production" ? "#d1fae5" : f.env === "staging" ? "#fef3c7" : "#e0e7ff", color: f.env === "production" ? "#065f46" : f.env === "staging" ? "#92400e" : "#3730a3" }}>{f.env}</span>
|
|
<div style={{ width: 32, height: 16, borderRadius: 8, background: f.on ? t.primary : "#e5e7eb", position: "relative", cursor: "pointer", flexShrink: 0 }}>
|
|
<div style={{ position: "absolute", top: 2, width: 12, height: 12, borderRadius: "50%", background: "#fff", boxShadow: "0 1px 2px rgba(0,0,0,0.2)", transform: f.on ? "translateX(18px)" : "translateX(2px)" }} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Webhooks */}
|
|
<div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 8 }}>
|
|
<div style={{ padding: "9px 14px", borderBottom: "1px solid #f3f4f6", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
<span style={{ fontSize: 11, fontWeight: 600, color: "#111827" }}>Webhooks</span>
|
|
<button style={{ height: 24, padding: "0 10px", borderRadius: 5, border: "1px solid #e5e7eb", fontSize: 9, background: "none", color: "#374151", cursor: "pointer" }}>+ Add endpoint</button>
|
|
</div>
|
|
{[
|
|
{ url: "https://hooks.slack.com/T0123/B456/••••", events: "user.created", status: "active" },
|
|
{ url: "https://api.pagerduty.com/webhooks/••••", events: "alert.fired", status: "active" },
|
|
].map((w, i) => (
|
|
<div key={i} style={{ display: "flex", alignItems: "center", padding: "8px 14px", borderBottom: "1px solid #f9fafb", gap: 8 }}>
|
|
<div style={{ width: 6, height: 6, borderRadius: "50%", background: "#22c55e", flexShrink: 0 }} />
|
|
<span style={{ flex: 1, fontSize: 9, fontFamily: "monospace", color: "#374151", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{w.url}</span>
|
|
<span style={{ fontSize: 9, padding: "1px 6px", borderRadius: 3, background: "#f3f4f6", color: "#6b7280", flexShrink: 0 }}>{w.events}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Exported scaffold components
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function AdminMantine({ themeColor }: { themeColor?: ThemeColor }) {
|
|
const [page, setPage] = useState<AdminPage>("System");
|
|
const t = themeColor ?? MANTINE_THEMES[0];
|
|
return (
|
|
<AdminShell t={t} page={page} onNav={setPage}>
|
|
{page === "System" && <SystemView t={t} />}
|
|
{page === "Moderation" && <ModerationView t={t} />}
|
|
{page === "Config" && <ConfigView t={t} />}
|
|
</AdminShell>
|
|
);
|
|
}
|
|
|
|
export function AdminShadcn({ themeColor }: { themeColor?: ThemeColor }) {
|
|
const [page, setPage] = useState<AdminPage>("System");
|
|
const t = themeColor ?? SHADCN_THEMES[0];
|
|
return (
|
|
<AdminShell t={t} page={page} onNav={setPage}>
|
|
{page === "System" && <SystemView t={t} />}
|
|
{page === "Moderation" && <ModerationView t={t} />}
|
|
{page === "Config" && <ConfigView t={t} />}
|
|
</AdminShell>
|
|
);
|
|
}
|
|
|
|
export function AdminTremor({ themeColor }: { themeColor?: ThemeColor }) {
|
|
const [page, setPage] = useState<AdminPage>("System");
|
|
const t = themeColor ?? TREMOR_THEMES[0];
|
|
return (
|
|
<AdminShell t={t} page={page} onNav={setPage}>
|
|
{page === "System" && <SystemView t={t} />}
|
|
{page === "Moderation" && <ModerationView t={t} />}
|
|
{page === "Config" && <ConfigView t={t} />}
|
|
</AdminShell>
|
|
);
|
|
}
|
|
|
|
export { MANTINE_THEMES, SHADCN_THEMES, TREMOR_THEMES };
|