feat(app): unified demo mode + public-demo build for Vercel

- Central demo-mode helper (lib/demo-mode.ts): ?demo=1 query flag plus
  VITE_PUBLIC_DEMO build flag that makes every route demo for the
  public Vercel showcase.
- Single demo fixture source (lib/demo-data.ts): projects, services,
  builds, databases, cron, HA, security, host monitoring, templates.
  Fixes cross-page ID drift that broke project navigation.
- Demo support on Projects, HA, Security, Usage, Templates, Landing,
  Service detail; DemoRestricted gate on People/Settings/Audit/Admin.
- Demo-aware PlatformShell: nav filtering, env:demo, demo account menu,
  ?demo=1 preserved through internal links.
- TemplatesPage: selection moved into ?template= URL param for deep
  links; template fixtures shared via demo-data.
- requestJson returns {} for unparseable 200 bodies (SPA fallback) so
  envelope unwraps degrade instead of dereferencing null.
- Recharts ResponsiveContainer initialDimension kills -1 warnings.
- vercel.json: vite framework + SPA rewrites for direct deep links.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-23 17:38:02 +02:00
1 parent 84b102a2c6
commit 5b0a8e0759
20 files changed
+873 -525

No files matched your search

@@ -78,7 +78,7 @@ export function LineChart({
return (
<div className="chart-wrap" style={{ height: `${height}px`, width: '100%' }}>
<ResponsiveContainer width="100%" height="100%">
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 320, height }}>
<RechartsLineChart data={chartData}>
<Line
type={smooth ? 'monotone' : 'linear'}
@@ -105,7 +105,7 @@ export function LineAreaChart({
return (
<div className="chart-wrap" style={{ height: `${height}px`, width: '100%' }}>
<ResponsiveContainer width="100%" height="100%">
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 320, height }}>
<AreaChart data={chartData}>
<defs>
<linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
@@ -142,7 +142,7 @@ export function MultiLineChart({
return (
<div className="chart-wrap" style={{ height: `${height}px`, width: '100%' }}>
<ResponsiveContainer width="100%" height="100%">
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 320, height }}>
<AreaChart data={chartData}>
<defs>
{datasets.map((dataset, index) => {
@@ -0,0 +1,25 @@
import { Link } from 'react-router-dom';
import { Sparkles } from 'lucide-react';
/**
* Shown by auth/admin-scoped pages when `?demo=1` is active — the demo only
* carries sample project/platform data, not real accounts or admin state.
*/
export function DemoRestricted({ feature }: { feature: string }) {
return (
<div className="flex min-h-[60vh] items-center justify-center p-8">
<div className="panel max-w-md p-8 text-center">
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-[var(--accent-primary-soft)]">
<Sparkles size={22} className="text-[var(--accent-primary)]" />
</div>
<p className="text-lg font-semibold text-[var(--text-primary)]">Not part of the demo</p>
<p className="mt-2 text-sm text-[var(--text-secondary)]">
{feature} needs a live Containr session. The demo covers sample projects, services, and platform views only.
</p>
<Link to="/projects?demo=1" className="v-btn mt-5 inline-flex">
Back to demo projects
</Link>
</div>
</div>
);
}
@@ -1,3 +1,4 @@
export * from './DemoGate';
export * from './ErrorBoundary';
export * from './LoadingSpinner';
export * from './Toast';