infra(04-00): route /c/ → /client/, Dockerfile, Gitea deploy

- Rename src/app/c/[token] → src/app/client/[token]
- Update proxy.ts, ClientRow, admin client detail with /client/ path
- Add output: "standalone" to next.config.ts for Docker build
- Add Dockerfile (multi-stage, node:20-alpine) and .dockerignore
- Push schema to Coolify Postgres via SSH tunnel (drizzle-kit push ✓)
- Update CLAUDE.md constraint 4 to reflect /client/ route
- Add Phase 4 planning artifacts (04-00, 04-RESEARCH, 04-PATTERNS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 16:12:05 +02:00
parent 49ef45da83
commit 5bf5dfce71
21 changed files with 3164 additions and 63 deletions
+61
View File
@@ -0,0 +1,61 @@
import { cache } from 'react';
import { getClientView } from '@/lib/client-view';
import { ClientDashboard } from '@/components/client-dashboard';
import { notFound } from 'next/navigation';
import { db } from '@/db';
import { comments } from '@/db/schema';
import { inArray } from 'drizzle-orm';
import type { Comment } from '@/db/schema';
export const revalidate = 0; // Always revalidate — comments and approvals must be fresh
// React cache deduplicates DB calls within the same render
const getCachedClientView = cache(getClientView);
export async function generateMetadata({
params,
}: {
params: Promise<{ token: string }>;
}) {
const { token } = await params;
const view = await getCachedClientView(token);
if (!view) {
return { title: 'Not Found' };
}
return {
title: `${view.client.brand_name} — Stato Progetto | iamcavalli`,
description: view.client.brief || 'Dashboard stato progetto',
};
}
export default async function ClientPage({
params,
}: {
params: Promise<{ token: string }>;
}) {
const { token } = await params;
const view = await getCachedClientView(token);
if (!view) {
notFound();
}
// Fetch comments: tasks, deliverables, and general (entity_id = clientId)
const allTaskIds = view.phases.flatMap((p) => p.tasks.map((t) => t.id));
const allDeliverableIds = view.phases.flatMap((p) =>
p.tasks.flatMap((t) => t.deliverables.map((d) => d.id))
);
const allEntityIds = [view.client.id, ...allTaskIds, ...allDeliverableIds];
const allComments: Comment[] =
allEntityIds.length > 0
? await db
.select()
.from(comments)
.where(inArray(comments.entity_id, allEntityIds))
: [];
return <ClientDashboard view={view} token={token} comments={allComments} />;
}