feat(frontend): SPA Vue 3 + Vite + Tailwind v4 consommant l'API

Nouveau dossier /frontend avec un SPA complet qui couvre les écrans de la
maquette (cf. docs/Capture*.png) : public + admin token. Backend inchangé.

Stack
  - Vue 3.5 + Vite 8 + TypeScript 6 + Tailwind v4 (via @tailwindcss/vite)
  - Vue Router 4 en mode history, code-split par page (lazy import)
  - Aucune lib UI, aucun store (pas de Pinia) — état local + fetch direct
  - Bundle initial 50 kB gzip, total dist 240 kB

Pages
  - /                 redirige vers /donut
  - /dashboard        4 KPIs + mini donut + flux activité (via /api/stats + /api/activity)
  - /donut            viz SVG custom : 6 secteurs catégories, projets en points
                      colorés, anneaux plancher social / plafond écologique,
                      tooltip + click vers la fiche projet
  - /projects         grille de cartes paginée + filtres (catégorie, statut, recherche
                      avec debounce, query string comme source de vérité)
  - /projects/new     formulaire complet de création (token admin requis)
  - /projects/:slug   fiche : header méta + tabs Roadmap/Discussions/Ressources
                      + panneau droit Progression + Équipe, modal "Rejoindre"
  - /archives         projets status=archived
  - /categories       6 cartes coloured-top-border + count via /api/stats
  - /admin/login      saisie du token Bearer, validé via POST /api/members (vide)
  - 404               fallback

Structure
  - src/api/client.ts        : wrapper fetch typé + token Bearer auto + ApiError
  - src/composables/useAuth  : token localStorage + reactivité Vue
  - src/types/api.ts         : types miroirs des réponses PHP
  - src/utils/formatDate.ts  : ISO/MySQL/DATE -> JJ mois AAAA, relatif, etc.
  - src/components/          : Avatar, ProjectCard, StatusBadge, CategoryBadge,
                               ProgressBar, LoadingState, ErrorState, JoinProjectModal
  - src/layouts/AppLayout    : sidebar nav + main scrollable
  - src/components/AppSidebar: logo donut SVG, CTA, nav, statut admin en bas
  - src/pages/               : 10 pages ci-dessus

Intégration & déploiement
  - vite.config.ts proxy /api/* vers http://donut-gestprojet.test (Laragon)
  - caddy.conf prod : /api/* -> reverse_proxy PHP, le reste -> static dist/
    avec fallback /index.html (history mode), cache long sur /assets/*
  - DEPLOY.md mis à jour avec build local + rsync dist/ vers
    /srv/donut/web/infolab-front/dist
  - README.md mis à jour avec section "Frontend (SPA Vue 3)"

Build & types
  - npm run build : vue-tsc strict (TS 6 + erasableSyntaxOnly) -> OK
  - paths alias @/* configuré dans tsconfig.app.json

Vérifié localement
  - npm run dev sur :5173, proxy /api -> Apache Laragon -> PHP
  - Toutes les routes répondent 200 (curl http://localhost:5173/...)
  - Routes API consommées correctement (categories, stats, projects, ...)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 20:40:39 +02:00
parent 3a32e93ab1
commit 7fb22bcb29
49 changed files with 4754 additions and 13 deletions
+59 -6
View File
@@ -8,6 +8,7 @@ Procédure exacte de déploiement sur le serveur **Donut Marseille**.
- Conteneur PHP : `donut-infolab` (UID `33` = www-data côté Apache)
- BDD : conteneur existant `donut-mariadb`, réseau Docker `donut-net`
- Reverse proxy : Caddy natif sur l'hôte
- Front : SPA Vue 3 buildé (statique) servi par Caddy depuis `/srv/donut/web/infolab-front/dist/`
> ⚠️ Pré-requis DNS : créer chez OVH un record `A infolab.ledonut-marseille.org → 82.66.40.209`
> **avant** le reload Caddy (sinon échec ACME challenge).
@@ -31,16 +32,17 @@ getent group donut-admins
---
## 1. Copier le projet vers le serveur
## 1. Copier le backend vers le serveur
Depuis ta machine de dev (à la racine du repo) :
```bash
# Code app -> /srv/donut/web/infolab/ (bind-mount dans le conteneur, UID 33)
# Code app PHP -> /srv/donut/web/infolab/ (bind-mount dans le conteneur, UID 33)
rsync -avz --delete \
--exclude '.git' --exclude '.idea' --exclude '.env' --exclude 'tests' \
--exclude 'docker-compose.yml' --exclude 'Dockerfile' --exclude 'caddy.conf' \
--exclude 'migrations' --exclude 'DEPLOY.md' --exclude 'INSTRUCTIONS.md' \
--exclude 'frontend' --exclude 'docs' \
./ arnaud.donut@82.66.40.209:/tmp/infolab-app/
# Fichiers d'orchestration -> /srv/donut/compose/infolab/
@@ -75,6 +77,40 @@ rm -rf /tmp/infolab-app /tmp/infolab-compose
---
## 1bis. Builder & copier le frontend (SPA Vue)
Le SPA est compilé localement (Node 22+ requis) et déployé en statique. Pas de Node sur le serveur prod.
```bash
# Sur la machine de dev, à la racine du repo
cd frontend
npm ci # ou `npm install` la 1re fois
npm run build # produit frontend/dist/
ls -la dist/ # index.html + assets/ avec hash
# Copie de l'artifact vers le serveur (rsync direct, fichiers statiques)
rsync -avz --delete dist/ arnaud.donut@82.66.40.209:/tmp/infolab-front-dist/
cd ..
```
Sur le serveur :
```bash
ssh arnaud.donut@82.66.40.209
sudo mkdir -p /srv/donut/web/infolab-front/dist
sudo rsync -a --delete /tmp/infolab-front-dist/ /srv/donut/web/infolab-front/dist/
# Caddy tourne en `caddy` user — donner l'accès en lecture est suffisant
sudo chown -R caddy:caddy /srv/donut/web/infolab-front
sudo find /srv/donut/web/infolab-front -type d -exec chmod 755 {} \;
sudo find /srv/donut/web/infolab-front -type f -exec chmod 644 {} \;
rm -rf /tmp/infolab-front-dist
```
> ⚠️ Le build embarque l'URL de l'API en relatif (`/api/...`) — donc le SPA et l'API
> **doivent** être servis depuis le même domaine (ce que fait notre `caddy.conf`).
---
## 2. Créer les secrets `/srv/donut/secrets/infolab.env`
```bash
@@ -143,10 +179,11 @@ docker logs --tail 100 donut-infolab
---
## 5. Vhost Caddy
## 5. Vhost Caddy (sert le SPA + reverse_proxy l'API)
Le fichier `caddy.conf` a été copié à `/srv/donut/compose/infolab/caddy.conf`.
Vérifier qu'il est bien `import`-é par `/etc/caddy/Caddyfile` :
Le `caddy.conf` fait deux choses :
- `/api/*` -> reverse_proxy vers le conteneur PHP `127.0.0.1:8003`
- tout le reste -> sert `/srv/donut/web/infolab-front/dist/` (SPA), fallback `index.html`
```bash
grep -n 'infolab' /etc/caddy/Caddyfile
@@ -162,6 +199,14 @@ sudo systemctl reload caddy
Caddy va négocier le certificat Let's Encrypt (à condition que le DNS A
record soit en place — voir avertissement en tête de fichier).
Vérifier qu'il sert bien le SPA et l'API :
```bash
curl -sI https://infolab.ledonut-marseille.org/ # SPA -> 200 text/html
curl -sI https://infolab.ledonut-marseille.org/api/health # API -> 200 application/json
curl -sI https://infolab.ledonut-marseille.org/whatever-fake # SPA fallback -> 200 index.html
```
---
## 6. Vérification finale
@@ -186,10 +231,15 @@ Si tout est vert : `OK : 11 / FAIL : 0`.
## 7. Cycle de mise à jour (pour plus tard)
```bash
# Code app
# Backend PHP
rsync -avz --delete --exclude '.git' --exclude '.env' \
public/ src/ arnaud.donut@82.66.40.209:/srv/donut/web/infolab/
# Frontend Vue (build à chaque release)
(cd frontend && npm ci && npm run build)
rsync -avz --delete frontend/dist/ \
arnaud.donut@82.66.40.209:/srv/donut/web/infolab-front/dist/
# Si changement Dockerfile / docker-compose / migrations :
rsync -avz docker-compose.yml Dockerfile migrations/ \
arnaud.donut@82.66.40.209:/srv/donut/compose/infolab/
@@ -199,6 +249,7 @@ ssh arnaud.donut@82.66.40.209 << 'EOF'
cd /srv/donut/compose/infolab
sudo docker compose --env-file /srv/donut/secrets/infolab.env up -d --build
sudo chown -R 33:33 /srv/donut/web/infolab
sudo chown -R caddy:caddy /srv/donut/web/infolab-front
EOF
```
@@ -210,5 +261,7 @@ EOF
- [ ] Certificat Let's Encrypt négocié OK (`caddy.log` côté hôte)
- [ ] `ADMIN_TOKEN` archivé dans le gestionnaire de mots de passe partagé
- [ ] `tests/smoke.sh` vert depuis l'extérieur
- [ ] SPA accessible : `https://infolab.ledonut-marseille.org/` renvoie la page d'accueil
- [ ] SPA routing OK : `https://infolab.ledonut-marseille.org/projects` aussi (pas de 404 — fallback Caddy)
- [ ] Conteneur `donut-infolab` visible dans Dozzle (logs Apache + PHP via stderr)
- [ ] Pas de fuite : `curl -I https://infolab.ledonut-marseille.org/.env` → 403/404