# Design — Page de management des projets

Date : 2026-06-04
Branche : `feature/domain-schema` (proto SEO Insight)

## Contexte

Un **site = un projet** (`sites` table, Drizzle). Champs : `id, orgId, ownerUserId,
name, url, primaryLanguage, languages[], targetCountries[], createdAt`.
Supprimer un site **cascade** vers ses `keywords` et ses `backlinks`
(`onDelete: "cascade"` dans `domain-schema.ts`).

État actuel :
- Action existante : `createSiteAction` seulement. Pas d'update/delete.
- Query existante : `getSitesForOrg` → `Site` minimal `{ id, name, plan(url) }`.
- Section sidebar **« Projects »** = données demo hardcodées (`NavProjects`).
- Les vrais sites vivent dans le `TeamSwitcher` via le contexte client
  `useActiveSite()` (`sites`, `activeSite`, `setActiveSiteId`, `addSite`).
- `getActiveOrgId()` / `getActiveUserId()` sont stubbés (`"demo-org"` /
  `"seed-owner"`) en attendant Better Auth.

## Objectif

Permettre de **gérer les projets** (éditer les réglages, supprimer) depuis une
page dédiée, accessible via la section « Projects » de la sidebar.

## Routes (`app/(app)/projects/`)

- **`/projects`** — liste des projets de l'org (rows). Bouton **+ Ajouter**
  (réutilise `AddSiteDialog`). Chaque ligne affiche **domaine, langue principale,
  langues additionnelles, pays cibles, date de création** et lie vers
  `/projects/[id]`. Pas de compteurs keywords/backlinks pour l'instant (YAGNI).
- **`/projects/[id]`** — page **Paramètres** du projet :
  - form d'édition (mêmes champs que l'ajout, pré-remplis) → `updateSiteAction`.
  - **zone Danger** en bas → suppression.
  - `notFound()` si l'id n'appartient pas à l'org active.

Les deux pages sont des **Server Components** lisant les données serveur ; les
mutations passent par des composants client appelant les server actions.

## Data (`src/lib/sites/`)

- **Type `SiteDetail`** (ligne complète) :
  `{ id, name, url, primaryLanguage, languages, targetCountries, createdAt }`.
  Le `Site` minimal reste inchangé pour le switcher/contexte.
- **`queries.ts`** :
  - `getProjectsForOrg(orgId): SiteDetail[]` — liste complète, triée par nom.
  - `getSiteById(id, orgId): SiteDetail | null` — **org-scopé** (où `id` ET
    `orgId` matchent), retourne `null` sinon.
- **`actions.ts`** :
  - `updateSiteAction(id, raw: SiteInput): CreateSiteResult-like` — valide via
    `siteInputSchema` (réutilisé), **vérifie que le site appartient à l'org
    active** avant l'update, `revalidatePath("/", "layout")`.
  - `deleteSiteAction(id): { ok: true } | { ok: false, error }` — vérifie
    l'org-scope, supprime la ligne (cascade DB keywords + backlinks),
    `revalidatePath("/", "layout")`.
  - Les deux refusent un `id` hors org active (sécurité : pas d'édition d'un id
    arbitraire).

## Form partagé (`src/components/site/`)

Extraire le corps du form add-site en **`SiteForm`** réutilisable :
- props : `defaultValues` (name, scheme, domain, primaryLanguage, languages,
  targetCountries), `submitLabel`, `pending`, `error`, `onSubmit(input)`.
- contient toute la logique champs : `cleanDomain`, split schéma/domaine,
  langue principale exclue des additionnelles (statique + dynamique),
  `SingleCombobox` / `MultiCombobox`.
- `AddSiteDialog` = `SiteForm` dans un `Dialog` (create, defaults vides).
- Page settings = `SiteForm` **inline** (edit) : l'`url` stockée est re-splittée
  en `scheme` + `domain` pour pré-remplir (matcher le préfixe parmi
  `https://`, `http://`, `https://www.`, `http://www.`).

## Suppression

- Zone **Danger** sur la page settings → `DeleteProjectDialog` (shadcn
  `AlertDialog`) qui **avertit explicitement** :
  > Supprimer « <nom> » ? Cela supprime aussi tous ses keywords et tous ses
  > backlinks. Irréversible.
- Au succès de `deleteSiteAction` :
  - `removeSite(id)` sur le contexte ;
  - si l'id supprimé était le **site actif**, le contexte bascule l'actif sur le
    premier site restant ;
  - redirection vers `/projects`.

## Sidebar (`NavProjects` + `app-sidebar`)

- Supprimer les données demo. `NavProjects` consomme les **vrais** sites via
  `useActiveSite().sites`.
- Chaque projet : nom → `/projects/[id]` ; menu **⋯** → « Paramètres »
  (→ `/projects/[id]`) + « Définir comme actif » (`setActiveSiteId`).
  **Pas** de suppression dans la sidebar — un seul endroit dangereux (page
  settings).
- Entrée footer **« Gérer les projets »** → `/projects`.

## Contexte site (`site-context.tsx`)

Étendre `SiteContextValue` :
- `updateSite(site: Site)` — remplace l'entrée par id (garde switcher + sidebar
  synchros après edit).
- `removeSite(id: string)` — retire l'entrée ; réassigne `activeSiteId` au
  premier restant si l'actif est supprimé.

## A11y / style

- Composants shadcn (via le skill shadcn). `AlertDialog` pour la confirmation.
- Focus visible, navigation clavier, form clavier-accessible.
- Réutilise les comboboxes base-ui déjà corrigées (`pointer-events-auto` dans le
  Dialog radix).

## Hors scope (YAGNI)

- Compteurs keywords/backlinks sur la liste.
- Transfert de propriété / partage.
- Suppression depuis la sidebar.
- Réglages par-projet autres que les champs actuels (intégrations, etc.).
