buildmymcpserver/apps/web/app/(marketing)/layout.tsx

122 lines
4.6 KiB
TypeScript
Raw Normal View History

feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner Legal (Swiss minimum, no individual named): - Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel, no email required, jurisdiction = Switzerland - AGB page — subscription terms, payment, cancellation, suspension on payment fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law + Swiss venue, modeled after typical Schweizer SaaS terms - Privacy: Stripe added as subprocessor with full data-flow disclosure Support panel replaces email contact entirely: - @bmm/db: support_status enum + support_tickets + support_messages tables, migration applied to prod DB - @bmm/api: support routes (user create/list/view/reply, admin list/view/reply /set-status), public /v1/contact for logged-out visitors with per-IP rate limit of 3 submissions/day to prevent spam-flood - Web: /settings/support (list + new), /settings/support/[id] (conversation), /admin/support, /admin/support/[id] - Public /contact form with email collection for guest tickets Data rights (DSG Art. 25 / GDPR Art. 15+20): - /v1/account/export returns user-scoped JSON of profile, org, servers, builds, audit, support tickets and messages — excludes hashes, encrypted secrets, other-user data - /settings/account: download button + deletion-via-ticket workflow Production-readiness gaps closed: - org.suspended now blocks /v1/servers POST and /v1/servers/preview (402); webhook flagged this state but enforcement was missing - Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG + GDPR compliant without dark-pattern consent UI), mounts on both layouts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
import { CookieBanner } from '@/components/cookie-banner';
import { Logo } from '@/components/logo';
import { MarketingAuthButtons } from '@/components/marketing-auth-buttons';
import { MarketingMobileMenu } from '@/components/marketing-mobile-menu';
import Link from 'next/link';
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-50 border-b border-[--color-border] bg-[--color-bg]/80 backdrop-blur-md">
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-5 sm:px-6">
<div className="flex items-center gap-6">
<Logo />
<nav className="hidden items-center gap-5 text-[13px] text-[--color-fg-muted] md:flex">
<Link href="/#how" className="transition-colors hover:text-[--color-fg]">
How it works
</Link>
feat(marketplace): template publish + fork + voting/ranking + admin moderation What this enables: - A user builds an MCP server. If others would benefit, they click 'Publish as template' on their server detail page. The spec + pre-rendered TypeScript snapshot is preserved. - Visitors browse /templates, filter by category, sort by trending/top/newest. Each template card shows fork count + active deployment count as natural manipulation-resistant popularity signal. - /templates/[slug] shows the full plan: tool list with input schemas, required-credential explanations (with 'how to get one' deep links), and a collapsible code preview so users can audit before forking. - Fork is one click → /servers/new?template=slug. The wizard skips Step 1 and pre-fills Step 2 with the template's parsed spec. Forker only fills in their own credentials. mcp_servers.template_id is recorded; template.fork_count is bumped atomically. Each fork gets its own isolated container with its own port, its own AES-256 secrets — the template author has zero visibility into the fork's traffic or data. - Admin /admin/templates moderation: verify quality templates (shows shield badge in marketplace), hide low-effort ones, takedown anything malicious. Takedowns cascade-pause every fork container — owners must re-deploy. Why template+fork instead of shared-container: - Shared containers would mean the publisher's quota + their secrets + their logs are exposed to forkers. Bad ergonomics, bad security, bad ownership. - Templates/forks decouple the spec (shared, vouched-for) from the runtime (isolated per user). Network-effect moat without the trust collapse. Why no 5-star voting in v1: - Manipulation-anfällig, empty lists without adoption. We use fork count + active deploys + verified badge. Trending algorithm: score = (activeDeploys * 3 + forks) / sqrt(ageDays + 1) Real signal, no brigading attack surface. Backend: - New schema: templates table (16 cols incl. tools_schema, generated_code, required_secrets, allowedDomains, status enum, verified, fork_count). - mcp_servers.template_id FK + idx for fork lookup. - @bmm/types: SpecEdit unchanged, CreateServerInput accepts optional templateId. - preview-cache.ts: new cachePrebuiltCode/loadPrebuiltCode for storing the template's full rendered server.ts alongside the spec. Generator worker detects this and skips the render step — uses the audited pre-built code verbatim. Banned-pattern re-scan at publish time. - routes/templates.ts: 5 public/auth routes + 2 admin routes. Banned-pattern re-scan before publish. Slug auto-uniqued. forkCount atomic-increment via SQL. UI: - /templates marketplace with trending/top/newest tabs, category filter, search. Cards show forks + live count + author + verified badge. - /templates/[slug] full detail with tools, credentials-with-hints, expandable code preview, fork CTA, ownership + stats sidebar, 'forking is safe' explainer. - /servers/new?template=slug — wizard auto-jumps to Step 2 with template spec pre-filled, fork banner at top with link back to template. - /servers/[id] new Publish tab with title, category, descriptions, per-secret hint fields (description + howToGetUrl per UPPER_SNAKE_CASE key). - /admin/templates moderation with verify/hide/takedown actions. - Marketing nav now includes /templates. Verified end-to-end: - Published Echo Demo Template from marco@test.local's live server - Marketplace lists it correctly with stats - Detail page renders with all sections - Fork CTA navigates to wizard with ?template= param - Wizard skips Step 1, shows fork banner, pre-fills spec - Build succeeds in ~10s (cached spec + prebuilt code path skips Claude AND render), container live on :4109 with proper OAuth 401 → token → 200 flow - DB: templates.fork_count=1, activeDeployments=1, mcp_servers.template_id populated on the fork - /admin/templates shows the new template with verify/hide/takedown controls
2026-05-19 23:22:35 +02:00
<Link href="/templates" className="transition-colors hover:text-[--color-fg]">
Templates
</Link>
<Link href="/pricing" className="transition-colors hover:text-[--color-fg]">
Pricing
</Link>
<Link href="/docs" className="transition-colors hover:text-[--color-fg]">
Docs
</Link>
<Link href="/guides" className="transition-colors hover:text-[--color-fg]">
Guides
</Link>
<Link href="/changelog" className="transition-colors hover:text-[--color-fg]">
Changelog
</Link>
</nav>
</div>
<div className="flex items-center gap-1.5 sm:gap-2">
<MarketingAuthButtons />
<MarketingMobileMenu />
</div>
</div>
</header>
<main className="flex-1">{children}</main>
<footer className="border-t border-[--color-border] py-12">
<div className="mx-auto max-w-6xl px-6">
<div className="grid gap-10 sm:grid-cols-2 md:grid-cols-4">
{/* Brand column — positioning line + live status. */}
<div className="sm:col-span-2 md:col-span-1">
<Logo />
<p className="mt-3 max-w-xs text-[12.5px] leading-relaxed text-[--color-fg-muted]">
From a prompt to a hosted, OAuth-protected MCP server for Claude, Cursor and
ChatGPT.
</p>
<Link
href="/status"
className="mt-4 flex items-center gap-2 text-[12px] text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
>
<span className="size-1.5 animate-pulse rounded-full bg-[--color-success]" />
<span>System status</span>
</Link>
</div>
<FooterColumn
title="Product"
links={[
{ href: '/templates', label: 'Templates' },
{ href: '/pricing', label: 'Pricing' },
{ href: '/changelog', label: 'Changelog' },
{ href: '/security', label: 'Security' },
]}
/>
<FooterColumn
title="Resources"
links={[
{ href: '/docs', label: 'Docs' },
{ href: '/guides', label: 'Guides' },
{ href: '/docs/faq', label: 'FAQ' },
{ href: '/contact', label: 'Contact' },
]}
/>
<FooterColumn
title="Legal"
links={[
{ href: '/privacy', label: 'Privacy' },
{ href: '/terms', label: 'Terms' },
{ href: '/agb', label: 'AGB' },
{ href: '/impressum', label: 'Impressum' },
]}
/>
</div>
<div className="mt-10 border-t border-[--color-border] pt-6 text-[12px] text-[--color-fg-subtle]">
&copy; {new Date().getFullYear()} BuildMyMCPServer
</div>
</div>
</footer>
feat: Swiss-compliant launch — Impressum/AGB/Contact, support panel, DSG exports, cookie banner Legal (Swiss minimum, no individual named): - Impressum page (UWG Art. 3 lit. s) — provider, contact via support panel, no email required, jurisdiction = Switzerland - AGB page — subscription terms, payment, cancellation, suspension on payment fail, 14-day money-back, AI-processing-per-tier disclosure, Swiss law + Swiss venue, modeled after typical Schweizer SaaS terms - Privacy: Stripe added as subprocessor with full data-flow disclosure Support panel replaces email contact entirely: - @bmm/db: support_status enum + support_tickets + support_messages tables, migration applied to prod DB - @bmm/api: support routes (user create/list/view/reply, admin list/view/reply /set-status), public /v1/contact for logged-out visitors with per-IP rate limit of 3 submissions/day to prevent spam-flood - Web: /settings/support (list + new), /settings/support/[id] (conversation), /admin/support, /admin/support/[id] - Public /contact form with email collection for guest tickets Data rights (DSG Art. 25 / GDPR Art. 15+20): - /v1/account/export returns user-scoped JSON of profile, org, servers, builds, audit, support tickets and messages — excludes hashes, encrypted secrets, other-user data - /settings/account: download button + deletion-via-ticket workflow Production-readiness gaps closed: - org.suspended now blocks /v1/servers POST and /v1/servers/preview (402); webhook flagged this state but enforcement was missing - Cookie banner: minimal, essential-cookies-only disclosure (Swiss DSG + GDPR compliant without dark-pattern consent UI), mounts on both layouts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:12:06 +02:00
<CookieBanner />
</div>
);
}
function FooterColumn({
title,
links,
}: {
title: string;
links: { href: string; label: string }[];
}) {
return (
<div>
<h3 className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[--color-fg-muted]">
{title}
</h3>
<ul className="mt-3 space-y-2 text-[12.5px] text-[--color-fg-muted]">
{links.map((l) => (
<li key={l.href}>
<Link href={l.href} className="transition-colors hover:text-[--color-fg]">
{l.label}
</Link>
</li>
))}
</ul>
</div>
);
}