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

@@ -58,6 +58,44 @@ export async function fetchTemplate(slug: string): Promise<TemplateDetail | null
}
}
export interface TemplateSummary {
id: string;
slug: string;
title: string;
shortDescription: string;
category: string;
status: 'draft' | 'public' | 'hidden' | 'takedown';
verified: boolean;
forkCount: number;
activeDeployments: number;
ownerName: string | null;
ownerOrgName: string | null;
sourceServerId: string | null;
createdAt: string;
}
/**
* Full public template list for server-rendering the marketplace grid so
* crawlers see the templates in the initial HTML. Best-effort: returns empty
* lists on error so the page still renders (the client component refetches
* on filter changes anyway).
*/
export async function fetchPublicTemplates(): Promise<{
templates: TemplateSummary[];
categories: string[];
}> {
try {
const res = await fetch(`${API_BASE}/v1/templates?sort=trending`, {
next: { revalidate: 300 },
});
if (!res.ok) return { templates: [], categories: [] };
const data = (await res.json()) as { templates?: TemplateSummary[]; categories?: string[] };
return { templates: data.templates ?? [], categories: data.categories ?? [] };
} catch {
return { templates: [], categories: [] };
}
}
/** Slugs of public templates, for the sitemap. Best-effort: returns [] on error. */
export async function fetchPublicTemplateSlugs(): Promise<string[]> {
try {