feat(04-04): slug resolution + multi-project client dashboard + slug edit

- validate-slug API route: resolves clients.slug for Edge middleware
- proxy.ts: slug-first resolution (D-06) — tries slug then falls back to token
- client-view.ts: complete rewrite — getClientWithProjectsByToken + getProjectView
  - No quote_items, no payment amounts in client API (CLAUDE.md security invariants)
- client/[token]/page.tsx: multi-project dashboard — 1 project = direct view,
  2+ projects = shadcn Tabs with project names (D-09/D-10)
- edit/page.tsx: slug field with link preview + unique constraint error handling
- actions.ts: updateClient now persists slug, redirects on success/slug_taken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 11:31:32 +02:00
parent 1d9fa10961
commit 5d785a1c1c
8 changed files with 562 additions and 175 deletions
@@ -0,0 +1,28 @@
import { NextRequest, NextResponse } from "next/server";
import { eq } from "drizzle-orm";
import { db } from "@/db";
import { clients } from "@/db/schema";
export async function GET(request: NextRequest) {
const slug = request.nextUrl.searchParams.get("slug");
if (!slug) {
return NextResponse.json({ valid: false }, { status: 400 });
}
try {
const rows = await db
.select({ id: clients.id })
.from(clients)
.where(eq(clients.slug, slug))
.limit(1);
if (rows.length === 0) {
return NextResponse.json({ valid: false }, { status: 404 });
}
return NextResponse.json({ valid: true }, { status: 200 });
} catch {
return NextResponse.json({ valid: false }, { status: 500 });
}
}