# Phase 2: Admin Area & Interactive Features - Research **Researched:** 2026-05-15 **Domain:** Next.js authentication, admin CRUD, form handling, route protection **Confidence:** MEDIUM-HIGH ## Summary Phase 2 requires implementing Next.js 16 authentication for admin area (`/admin`), form-based CRUD operations for clients/phases/tasks/deliverables/payments, and client-facing interactive features (approval, comments). The research reveals a critical version decision: **next-auth v4 has unresolved peer dependency issues with Next.js 16**, requiring workarounds (`--legacy-peer-deps` flag). The CONTEXT.md decision to use "next-auth@4" is technically viable but carries technical debt. The phase architecture is sound: Edge middleware protection (proxy.ts) for admin routes, Server Actions for mutations, and API routes for client interactions (separate auth path). Key dependencies are not yet installed. **Primary recommendation:** Proceed with next-auth v4 as locked in CONTEXT.md, but document the workaround requirement. All other patterns (Server Actions, revalidatePath, Zod+RHF forms, tabs) are stable and production-ready on Next.js 16. ## User Constraints (from CONTEXT.md) ### Locked Decisions **D-01: Auth.js v4 (`next-auth@4`)** — versione stabile. Installare `next-auth@4` + credentials via env vars (ADMIN_EMAIL + ADMIN_PASSWORD). No DB table per utenti. **D-02: Credenziali admin via env vars** — Singolo admin hardcoded in `.env.local`. CredentialsProvider di Auth.js valida contro le env vars. **D-03: Sessione JWT** — Nessuna tabella session nel DB. JWT stateless firmato, scadenza 30 giorni. **D-04: Protezione route admin** — `src/proxy.ts` gestisce `/c/[token]`. Aggiungere matcher `/admin/:path*` → redirect a `/admin/login` se no session. Check sessione nel middleware via `getToken` da next-auth/jwt. **D-05: Server Actions** — Next.js 15/16 nativo. Nessuna API route separata per CRUD admin. Form → Server Action → Drizzle → `revalidatePath`. **D-06: API route solo per client interactions** — Approvazioni e commenti del cliente su API routes (non Server Actions). Route pattern: `POST /api/client/approve` e `POST /api/client/comment`. Validate token via header/cookie, non Auth.js. **D-07: Lista → dettaglio** — `/admin` → lista clienti con badge stato pagamenti. Click → `/admin/clients/[id]` dettaglio completo. **D-08: Tabs per sezioni cliente** — Nella pagina dettaglio cliente, tabs: Panoramica | Fasi & Task | Documenti | Pagamenti | Commenti. Aggiungere `@radix-ui/react-tabs`. **D-09: Sidebar assente in v1** — Nessuna sidebar globale. Nav minimal: logo + link "Clienti" + pulsante logout. **D-10: Approvazione deliverable** — Pulsante "Approva" visibile solo se `approved_at` è null. POST a `/api/client/approve` con `{ deliverableId }`. **D-11: Commenti inline** — Textarea + pulsante "Invia" sotto task/deliverable. POST a `/api/client/comment`. Lista commenti sopra il form, ordinati per `created_at` asc. ### Claude's Discretion - Struttura esatta delle Server Actions (file naming) - Ordine campi nei form admin - Icone lucide-react per stati - Colori badge admin ### Deferred Ideas (OUT OF SCOPE) - Brand customization panel (Phase 3+) - Sidebar globale (v1 non necessaria) - Real-time commenti (WebSocket/polling → v2) - Multi-admin / ruoli - Password change UI ## Phase Requirements | ID | Description | Research Support | |----|-------------|------------------| | ADMIN-01 | Vista di tutti i clienti con stato sintetico | Server Component + Drizzle query per lista con badge pagamenti | | ADMIN-02 | Gestione completa di ogni cliente: fasi, task, documenti, pagamenti | Server Actions per CRUD su tutte le tabelle; proxy.ts protezione route `/admin/[*]`; revalidatePath aggiorna UI | | DASH-05 | Il cliente può approvare i deliverable dalla sua area | API route `POST /api/client/approve` — token validation — UPDATE `approved_at` immutabile | | DASH-06 | Il cliente può lasciare commenti su task e deliverable | API route `POST /api/client/comment` — INSERT comments table — lista commenti con author (client/admin) | ## Architectural Responsibility Map | Capability | Primary Tier | Secondary Tier | Rationale | |------------|-------------|----------------|-----------| | Admin authentication & session | API / Backend (proxy.ts) | Frontend Server (layout redirect) | JWT validation happens at Edge; session unavailable → redirect to login before rendering | | Admin CRUD mutations | API / Backend (Server Actions) | Frontend Server (forms) | Server Actions run in Node context; Drizzle mutations; revalidatePath triggers UI refresh | | Admin UI rendering | Frontend Server (Server Components) | Browser (Client Components for interactivity) | List/detail pages are Server Components with data from Drizzle; tabs/forms use Client Components for state | | Client approval/comments | API / Backend (route handlers) | Browser (client tokens in URL/cookie) | Separate auth path from admin; token validation in route handlers; mutations persist to DB | | Client-facing UI (approval, comments) | Browser / Client | Frontend Server (token validation via middleware) | Approval button, comment form are interactive client components; middleware validates token before rendering | | Payment status display | API / Backend (Drizzle query) | Frontend Server (Server Component) | Payments table joins with clients; status badge computed server-side; client API returns `accepted_total` only | ## Standard Stack ### Core Authentication & Middleware | Library | Version | Purpose | Why Standard | |---------|---------|---------|--------------| | next-auth | 4.24.14 | JWT-based admin session, credentials provider | Industry standard for Next.js; stateless JWT avoids DB session lookups at Edge [VERIFIED: npm registry] | | next-auth/jwt | bundled with next-auth | Token signing/verification in middleware | Required for getToken() calls in proxy.ts [VERIFIED: next-auth docs] | ### Form Handling & Validation | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | react-hook-form | 7.75.0 | Client-side form state + server submission | Already installed; lightweight; pairs with Zod for validation [VERIFIED: package.json] | | @hookform/resolvers | 5.2.2 | Adapter for Zod → RHF integration | Already installed; official resolver [VERIFIED: package.json] | | zod | 4.4.3 | Schema validation (client & server) | Already installed; enables validation reuse across RHF forms and Server Actions [VERIFIED: package.json] | ### UI Components | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | @radix-ui/react-tabs | 1.0.x | Tabs primitive (Radix-based) | Not yet installed; needed for admin detail page tabs per D-08 [CITED: shadcn/ui docs] | | @radix-ui/react-dialog | 1.1.x | Modal/dialog primitive | Optional; can defer if confirm modals not needed in Wave 1 | | shadcn/ui (tabs component) | — | Pre-configured Tabs built on Radix | Install via `npx shadcn@latest add tabs` after @radix-ui/react-tabs added [CITED: shadcn/ui] | ### Existing, Reusable Components (from Phase 1) | Library | Version | Purpose | Status | |---------|---------|---------|--------| | next | 16.2.6 | App Router, Server Components, Server Actions | ✅ Installed, using in Phase 2 | | drizzle-orm | 0.45.2 | Type-safe ORM + postgres-js connection | ✅ Installed, patterns established Phase 1 | | postgres | 3.4.9 | Postgres client (Node.js only, not Edge-compatible) | ✅ Installed; used in API routes (not middleware) | | nanoid | 5.1.11 | Cryptographic token generation | ✅ Installed; schema uses for `token` field | | tailwindcss | 4.x | Styling | ✅ Installed v4 in Phase 1 | | shadcn/ui (installed) | — | badge, button, card, input, label, select, table, textarea | ✅ All used in Phase 1; reuse for admin UI | | lucide-react | 1.14.0 | Icons | ✅ Installed Phase 1; use for payment status icons | ### Installation **New packages to add:** ```bash npm install next-auth@4.24.14 @radix-ui/react-tabs npx shadcn@latest add tabs ``` **Note:** next-auth v4 with Next.js 16 requires `--legacy-peer-deps` flag due to unresolved peer dependency issue: ```bash npm install next-auth@4.24.14 --legacy-peer-deps ``` **Version verification:** ```bash npm view next-auth@4.24.14 version # Should show: 4.24.14, published ~May 2026 npm view @radix-ui/react-tabs version # Should show: 1.x.x (latest stable) ``` ## Architecture Patterns ### System Architecture Diagram ``` ┌─────────────────────────────────────────────────────────────┐ │ BROWSER / CLIENT │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ Admin Form │ │ Client Dashboard │ │ │ │ (RHF + Zod) │ │ (Approval/Comment) │ │ └────────┬─────────┘ └────────┬─────────┘ │ └───────────┼──────────────────────┼───────────────────────────┘ │ │ │ POST /api/[...] + │ POST /api/client/[...] + │ Server Action │ client token │ │ ┌───────────▼──────────────────────▼───────────────────────────┐ │ FRONTEND SERVER (Next.js App Router) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Route Handler: POST /api/client/approve │ │ │ │ Route Handler: POST /api/client/comment │ │ │ │ Server Actions: createClient, updatePhase, etc. │ │ │ └──────────────────────────────────────────────────────┘ │ └───────────┬──────────────────────────────────────────────────┘ │ │ Token validation, │ Drizzle mutation │ ┌───────────▼──────────────────────────────────────────────────┐ │ DATABASE (Hetzner Postgres) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ clients, phases, tasks, deliverables, comments, │ │ │ │ payments, documents, notes, service_catalog, │ │ │ │ quote_items │ │ │ └──────────────────────────────────────────────────────┘ │ └───────────────────────────────────────────────────────────────┘ SEPARATELY: Edge Layer (proxy.ts) ┌─────────────────────────────────────────────────────────────┐ │ EDGE (Vercel / Next.js middleware layer) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ proxy.ts: getToken() → session exists? │ │ │ │ → Route: /admin/[*] → getToken ok? → allow : 404 │ │ │ │ → Route: /c/[token]/[*] → validateToken ok? → allow │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### Route Structure & Protection **Admin Routes (Protected by Edge middleware):** - `/admin/login` — GET form, POST credentials (unprotected) - `/admin` — GET list of clients (protected, Server Component) - `/admin/clients/[id]` — GET detail + CRUD forms (protected, Server Component + Client Components) - `/admin/clients/[id]/phases` — Nested detail (protected) - `/admin/clients/[id]/payments` — Nested detail (protected) **Client Routes (Already protected in Phase 1):** - `/c/[token]/` — Client dashboard (token validation via proxy.ts) - `/c/[token]/task/[taskId]` — Task detail with comments **API Routes (Internal, not middleware-protected at Edge):** - `POST /api/client/approve` — Expects client token in body/header - `POST /api/client/comment` — Expects client token in body/header - `POST /api/internal/validate-token` — Called by proxy.ts (no auth needed, internal only) ### Recommended Project Structure ``` src/ ├── app/ │ ├── admin/ │ │ ├── login/ │ │ │ ├── page.tsx # Login form │ │ │ └── actions.ts # signIn Server Action │ │ ├── clients/ │ │ │ ├── page.tsx # List all clients │ │ │ ├── actions.ts # createClient, deleteClient │ │ │ └── [id]/ │ │ │ ├── page.tsx # Detail page with tabs │ │ │ ├── actions.ts # updateClient, createPhase, etc. │ │ │ └── layout.tsx # Detail layout (optional nested) │ │ ├── layout.tsx # Admin layout (nav, logout) │ │ └── components/ │ │ ├── ClientForm.tsx │ │ ├── PhaseForm.tsx │ │ └── PaymentBadge.tsx │ ├── c/ │ │ └── [token]/ │ │ ├── components/ │ │ │ ├── ApprovalButton.tsx │ │ │ └── CommentForm.tsx │ │ └── ... # Phase 1 routes (reuse) │ └── api/ │ ├── client/ │ │ ├── approve/ │ │ │ └── route.ts # POST /api/client/approve │ │ └── comment/ │ │ └── route.ts # POST /api/client/comment │ └── internal/ │ └── validate-token/ │ └── route.ts # (existing, Phase 1) ├── auth.ts # next-auth config (NEW) ├── proxy.ts # Edge middleware (MODIFY) ├── db/ │ └── schema.ts # (unchanged, Phase 1) └── lib/ └── client-view.ts # (unchanged, Phase 1) ``` ### Pattern 1: Server Actions for Admin CRUD **What:** Async functions marked with `'use server'` that run on the server, handle form submissions, mutate the database via Drizzle, and revalidate the cache to refresh the UI. **When to use:** Every admin form (create/edit/delete client, phase, task, payment). Never call from client-side mutation code directly. **Example:** ```typescript // src/app/admin/clients/actions.ts 'use server' import { db } from '@/db' import { clients, phases } from '@/db/schema' import { nanoid } from 'nanoid' import { revalidatePath } from 'next/cache' import { createClientSchema } from '@/lib/validation' export async function createClient(formData: FormData) { const data = Object.fromEntries(formData) const validated = createClientSchema.parse(data) const token = nanoid() // Generate secret link token const newClient = await db.insert(clients).values({ name: validated.name, brand_name: validated.brand_name, brief: validated.brief, token: token, accepted_total: '0', }).returning() revalidatePath('/admin') // Refresh client list return { success: true, clientId: newClient[0].id, token } } // src/app/admin/clients/page.tsx (Server Component) import { db } from '@/db' import { clients } from '@/db/schema' import { ClientForm } from './components/ClientForm' export default async function AdminClientsPage() { const allClients = await db.query.clients.findMany() return (

All Clients

) } // src/app/admin/clients/components/ClientForm.tsx (Client Component) 'use client' import { useFormState, useFormStatus } from 'react-dom' import { createClient } from '../actions' export function ClientForm() { const [state, formAction] = useFormState(createClient, null) return (