From 089074d104cf0a716c3db8ce1fdb7f7e3df2ecf7 Mon Sep 17 00:00:00 2001 From: Marco Sadjadi Date: Wed, 8 Jul 2026 23:00:25 +0200 Subject: [PATCH] feat(conversion): Google-first login, truthful dashboard, SSR marketplace, template seeding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 Claude-Session: https://claude.ai/code/session_01SXUwmPVRTD8AKQtio6gCN5 --- apps/web/app/(dashboard)/dashboard/page.tsx | 17 +- apps/web/app/(dashboard)/servers/new/page.tsx | 9 - apps/web/app/login/page.tsx | 86 ++-- apps/web/app/templates/page.tsx | 357 +--------------- apps/web/app/templates/templates-browser.tsx | 395 ++++++++++++++++++ apps/web/lib/templates-server.ts | 38 ++ scripts/seed-templates.mjs | 275 ++++++++++++ 7 files changed, 775 insertions(+), 402 deletions(-) create mode 100644 apps/web/app/templates/templates-browser.tsx create mode 100644 scripts/seed-templates.mjs diff --git a/apps/web/app/(dashboard)/dashboard/page.tsx b/apps/web/app/(dashboard)/dashboard/page.tsx index 02c8d73..0e23ad9 100644 --- a/apps/web/app/(dashboard)/dashboard/page.tsx +++ b/apps/web/app/(dashboard)/dashboard/page.tsx @@ -17,12 +17,17 @@ interface ServerRow { export default function Overview() { const [servers, setServers] = useState(null); + const [plan, setPlan] = useState(null); const [err, setErr] = useState(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() {
- - + +
diff --git a/apps/web/app/(dashboard)/servers/new/page.tsx b/apps/web/app/(dashboard)/servers/new/page.tsx index fcac380..9a83f8a 100644 --- a/apps/web/app/(dashboard)/servers/new/page.tsx +++ b/apps/web/app/(dashboard)/servers/new/page.tsx @@ -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}.

- {userPlan === 'hobby' && ( -

- - Upgrade to Pro - {' '} - for ~3× faster analysis with Claude Haiku. -

- )}

{elapsedSec}s elapsed

diff --git a/apps/web/app/login/page.tsx b/apps/web/app/login/page.tsx index 9283ccf..d28227c 100644 --- a/apps/web/app/login/page.tsx +++ b/apps/web/app/login/page.tsx @@ -203,9 +203,11 @@ export default function LoginPage() { sms: false, email: false, }); - // Default to SMS — email is off by default until an SMTP/Resend provider - // is wired. The effect below flips to 'email' if the backend says it's on. - const [method, setMethod] = useState<'email' | 'phone'>('phone'); + // Phone sign-in is deliberately collapsed behind a text link whenever any + // other provider (OAuth or email) is available — a developer evaluating the + // product should never see a phone-number field as the front door. It only + // renders expanded when SMS is the sole configured provider. + const [phoneOpen, setPhoneOpen] = useState(false); const [error, setError] = useState(null); // Email magic-link @@ -226,9 +228,9 @@ export default function LoginPage() { ) .then((p) => { setProviders(p); - // Pick the most-likely method up-front: email if enabled, else SMS. - if (p.email) setMethod('email'); - else if (p.sms) setMethod('phone'); + // SMS as the only provider → show the phone form directly (no point + // hiding the sole sign-in method behind a toggle). + if (p.sms && !p.email && !p.google && !p.github) setPhoneOpen(true); }) .catch(() => undefined); const err = new URLSearchParams(window.location.search).get('error'); @@ -317,7 +319,7 @@ export default function LoginPage() {
)} - {hasOAuth && ( + {hasOAuth && (providers.email || providers.sms) && (
@@ -327,35 +329,8 @@ export default function LoginPage() {
)} - {/* Tab toggle only shown when BOTH email and SMS are enabled — if just - one is configured, that method's form renders directly without a - useless one-tab toggle. */} - {providers.sms && providers.email && ( -
- {(['email', 'phone'] as const).map((m) => ( - - ))} -
- )} -
- {method === 'email' && providers.email && emailState !== 'sent' && ( + {providers.email && !phoneOpen && emailState !== 'sent' && (
@@ -381,7 +356,7 @@ export default function LoginPage() { )} - {method === 'email' && providers.email && emailState === 'sent' && ( + {providers.email && !phoneOpen && emailState === 'sent' && (

Magic link sent to {email}. @@ -392,15 +367,11 @@ export default function LoginPage() {

)} - {method === 'phone' && smsStep === 'phone' && ( + {providers.sms && phoneOpen && smsStep === 'phone' && (
- +