---
phase: "03"
plan: "02"
type: execute
wave: 2
depends_on:
- "03-01"
files_modified:
- src/app/admin/catalog/page.tsx
- src/app/admin/catalog/actions.ts
- src/components/admin/catalog/ServiceTable.tsx
- src/components/admin/catalog/ServiceForm.tsx
- src/components/admin/NavBar.tsx
autonomous: true
requirements:
- CAT-01
must_haves:
truths:
- "Admin can navigate to /admin/catalog from the NavBar ('Catalogo' link visible between Statistiche and Esci)"
- "Admin can see a table of all services with columns Nome | Descrizione | Prezzo | Stato | Azioni"
- "Admin can add a new service via an inline form (name, optional description, unit price) — it appears in the table after save"
- "Admin can click 'Modifica' on a row and edit name, description, price inline — changes persist after save"
- "Admin can click 'Disattiva' to soft-delete a service (active=false) — row shows 'Disattivato' badge at 50% opacity"
- "Admin can click 'Riattiva' on a disabled service to re-enable it"
- "Inactive services remain visible in the table (with badge) but are excluded from the quote builder dropdown"
artifacts:
- path: "src/app/admin/catalog/page.tsx"
provides: "Service catalog page — server component, fetches all services, renders table"
contains: "getAllServices"
- path: "src/app/admin/catalog/actions.ts"
provides: "Server Actions: createService, updateService, toggleServiceActive"
exports: ["createService", "updateService", "toggleServiceActive"]
- path: "src/components/admin/catalog/ServiceTable.tsx"
provides: "Table with per-row inline edit and active toggle"
contains: "ServiceTable"
- path: "src/components/admin/catalog/ServiceForm.tsx"
provides: "Add-new-service form rendered above table"
contains: "ServiceForm"
- path: "src/components/admin/NavBar.tsx"
provides: "NavBar with Catalogo link added"
contains: "/admin/catalog"
key_links:
- from: "src/components/admin/catalog/ServiceForm.tsx"
to: "src/app/admin/catalog/actions.ts createService"
via: "form action"
pattern: "createService"
- from: "src/components/admin/catalog/ServiceTable.tsx"
to: "src/app/admin/catalog/actions.ts updateService / toggleServiceActive"
via: "Server Action calls in useTransition"
pattern: "updateService|toggleServiceActive"
- from: "src/app/admin/catalog/page.tsx"
to: "src/lib/admin-queries.ts getAllServices (new function)"
via: "await getAllServices()"
pattern: "getAllServices"
---
Deliver the complete `/admin/catalog` page: NavBar link, page layout, table with inline edit, add-service form, and soft-delete toggle. This is a self-contained vertical slice — after this plan executes, the admin can manage the service catalog end-to-end.
Purpose: Fulfills CAT-01 (service database with prices). Provides the catalog data that Wave 2's Quote Builder (plan 03-03) will query for its dropdown.
Output: 5 new/modified files — a fully functional service catalog page.
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@/Users/simonecavalli/IAMCAVALLI/.planning/ROADMAP.md
@/Users/simonecavalli/IAMCAVALLI/.planning/phases/03-service-catalog-quote-builder/03-01-SUMMARY.md
```typescript
// Server component, fetches data, renders table + header
export default async function AdminDashboard() {
const clients = await getAllClientsWithPayments();
return (
);
}
```
**Modify `src/components/admin/NavBar.tsx`** — add Catalogo link after the Statistiche link:
```typescript
Catalogo
```
Insert this line immediately after the existing `Statistiche` line.
cd /Users/simonecavalli/IAMCAVALLI && grep -c '/admin/catalog' src/components/admin/NavBar.tsx
Expected: 1
cd /Users/simonecavalli/IAMCAVALLI && grep -c 'export function ServiceTable' src/components/admin/catalog/ServiceTable.tsx
Expected: 1
cd /Users/simonecavalli/IAMCAVALLI && grep -c 'export function ServiceForm' src/components/admin/catalog/ServiceForm.tsx
Expected: 1
cd /Users/simonecavalli/IAMCAVALLI && grep -c 'getAllServices' src/app/admin/catalog/page.tsx
Expected: 1
cd /Users/simonecavalli/IAMCAVALLI && npx tsc --noEmit 2>&1 | head -20
Expected: no output (zero errors)
cd /Users/simonecavalli/IAMCAVALLI && npm run build 2>&1 | tail -10
Expected: "Compiled successfully" or "Route (app)" output with no errors
NavBar shows "Catalogo" link. `/admin/catalog` page renders. ServiceTable and ServiceForm compile. Full `npm run build` passes. Admin can navigate to `/admin/catalog` and see the table.
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Admin browser → Server Actions (catalog/actions.ts) | FormData from admin form crosses to server; must be validated before DB write |
| /admin/catalog route → Auth.js session | All catalog routes inherit the `/admin/*` middleware session check from Phase 2; no additional guard needed at page level |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-03-02-01 | Spoofing | createService / updateService / toggleServiceActive | mitigate | `requireAdmin()` calls `getServerSession(authOptions)` at the top of every Server Action — rejects if no valid session |
| T-03-02-02 | Tampering | serviceSchema Zod validation | mitigate | `unit_price` validated as `z.coerce.number().min(0.01)` — prevents zero/negative prices; `name` requires min length 1 |
| T-03-02-03 | Tampering | updateService serviceId parameter | mitigate | serviceId is bound at call site in the Server Action closure — admin can only modify the row ID passed from the server-rendered page |
| T-03-02-04 | Information Disclosure | /admin/catalog page | accept | Page is behind Auth.js `/admin/*` middleware (enforced in Phase 2); service prices are admin-internal data, not client-facing |
| T-03-02-05 | Tampering | XSS in service name / description | accept | React JSX auto-escapes all string output; no `dangerouslySetInnerHTML` used; UI-SPEC forbids it |
After both tasks complete:
1. `grep '/admin/catalog' src/components/admin/NavBar.tsx` returns 1 match
2. `npx tsc --noEmit` exits clean
3. `npm run build` succeeds
4. Navigating to `/admin/catalog` (dev server) shows the catalog page with table headers and "Aggiungi servizio" button
5. Adding a service via the form makes it appear in the table
6. Clicking "Disattiva" changes badge to "Disattivato" and reduces row opacity
- `/admin/catalog` route is accessible from NavBar and renders without error
- All three Server Actions (createService, updateService, toggleServiceActive) are exported from `catalog/actions.ts` with Zod validation and `requireAdmin()` guard
- ServiceTable renders per-row inline edit using the DocumentRow pattern
- Inactive services show "Disattivato" badge; active services show "Attivo" badge
- TypeScript and build both pass clean