feat(conversion): Google-first login, truthful dashboard, SSR marketplace, template seeding

- login: OAuth/email on top, phone collapsed behind 'Sign in with phone
  instead' (prod providers: google on, email off, sms on — a developer
  should never see a phone field as the front door)
- dashboard: plan card wired to GET /v1/billing/status; calls card shows
  '—' + pointer to per-server metrics (no user-facing usage endpoint
  exists; previous card showed invented '0 of 100,000 / Hobby')
- templates: server-rendered grid (revalidate 300) via fetchPublicTemplates,
  client browser hydrates with initial data; inviting empty state with
  labeled starter ideas instead of 'No templates yet'
- servers/new: removed upgrade nag from first analyze wait
- scripts/seed-templates.mjs: idempotent dry-run-by-default seeder driving
  the real preview->create->live->publish flow for 6 first-party templates

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXUwmPVRTD8AKQtio6gCN5
This commit is contained in:
Marco Sadjadi
2026-07-08 23:00:25 +02:00
parent 17056d0b30
commit 089074d104
7 changed files with 775 additions and 402 deletions

View File

@@ -17,12 +17,17 @@ interface ServerRow {
export default function Overview() {
const [servers, setServers] = useState<ServerRow[] | null>(null);
const [plan, setPlan] = useState<string | null>(null);
const [err, setErr] = useState<string | null>(null);
useEffect(() => {
apiFetch<{ servers: ServerRow[] }>('/v1/servers')
.then((r) => setServers(r.servers))
.catch((e) => setErr((e as Error).message));
// Real plan from billing status — never render a hardcoded tier.
apiFetch<{ plan: string }>('/v1/billing/status')
.then((r) => setPlan(r.plan))
.catch(() => setPlan(null));
}, []);
if (err?.includes('401')) {
@@ -46,8 +51,16 @@ export default function Overview() {
<div className="mt-6 grid gap-3 md:grid-cols-3">
<Card label="Servers" value={total.toString()} sub={`${live} live`} />
<Card label="Calls this period" value="0" sub="of 100,000" />
<Card label="Plan" value="Hobby" sub="Upgrade in Settings" />
<Card
label="Calls this period"
value="—"
sub="Per-server metrics live on each server page"
/>
<Card
label="Plan"
value={plan ? plan.charAt(0).toUpperCase() + plan.slice(1) : '—'}
sub="Manage in Settings → Billing"
/>
</div>
<div className="mt-10">

View File

@@ -8,7 +8,6 @@ import { Button } from '@/components/ui/button';
import { apiFetch, apiSseStream, humanizeError } from '@/lib/api';
import { findSecretInPrompt } from '@bmm/types';
import { Loader2, RotateCcw, X } from 'lucide-react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useEffect, useState } from 'react';
@@ -558,14 +557,6 @@ function NewServerPageInner() {
drafting the tool spec. Usually{' '}
{(userPlan ? PREVIEW_MODEL_BY_PLAN[userPlan] : PREVIEW_MODEL_BY_PLAN.hobby).estimate}.
</p>
{userPlan === 'hobby' && (
<p className="mt-2 text-[11px] text-[--color-fg-muted]">
<Link href="/pricing" className="text-[--color-accent] hover:underline">
Upgrade to Pro
</Link>{' '}
for ~3× faster analysis with Claude Haiku.
</p>
)}
<p className="mono mt-3 text-[11px] tabular-nums text-[--color-fg-muted]">
{elapsedSec}s elapsed
</p>