Public badge sur fiche carbet :
- carbet-public.ts charge les OrganizationCarbetMembership (org
approuvée uniquement) + expose `organizations: {id,name,slug}[]`.
- /carbets/[slug] affiche « Géré par le CE <name> » sous le header
si au moins 1 org liée.
Invites CE_MEMBER :
- Migration 20260603300000_org_invite_token : OrgInviteToken
(tokenHash, organizationId, email?, createdByUserId, expiresAt,
usedAt). Cascade sur Organization. Index expiresAt + organizationId.
- src/lib/ce-invites.ts : createOrgInviteToken (TTL 14j),
listOrgInviteTokens, getOrgInviteByToken (validité + expiry),
markOrgInviteConsumed, revokeOrgInviteToken. Token = 24 bytes
base64url, hash sha256.
- /espace-ce/membres : liste membres (CE_MANAGER + CE_MEMBER actifs)
+ form de génération de lien (email optionnel = lock email côté
signup) + liste des invitations avec statut actif/consommé/expiré +
bouton révoquer.
- /espace-ce/membres/actions.ts : createInviteAction +
revokeInviteAction. Audit log scope=ce.invite.
- API /api/signup étendue : zod accepte inviteToken, branche dédiée
qui crée User CE_MEMBER + organizationId du token + marquage
usedAt. Vérif email match si email fourni dans le token.
- /inscription?invite=TOKEN : récupère l'invite, pré-affiche org name,
lock email si fourni, masque les fieldsets type de compte (forcé
CE_MEMBER).
CTA marketing :
- /pour-comites-entreprise : section CTA « Créer mon espace CE » sous
le rendu content-pages, conditionnée par plugin ce-management.
Tests vitest (tests/lib/ce-access.test.ts) :
- canManageCarbet : admin always, owner direct, CE_MANAGER via org
match, refus si autre org / pas d'org / TOURIST / pas de membership.
- 9 tests, mocks next-auth + @/auth + @/lib/authorization pour éviter
next/server (incompatible vitest sans setup).
- Total tests projet : 62/62 ✓.
Dashboard /espace-ce : lien vers /espace-ce/membres en bas.
Migration prod appliquée.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schema:
- Organization gagne le workflow d'approbation (approved + approvedAt +
approvedBy + contactEmail). Backfill : toutes les orgs existantes
(CMCK) → approved=true via migration.
- Nouveau OrganizationCarbetMembership (manyToMany Org↔Carbet) pour la
co-gestion CE : un Carbet a un ownerId (créateur initial) + 0..n
memberships ; chaque CE_MANAGER d'une org liée peut gérer le carbet
en plus de l'owner. Pour un hôte individuel = pas de membership.
- RentalProvider.organizationId (nullable, SetNull on delete) : un CE
peut posséder son provider ; les CE_MANAGERs membres de l'org y ont
accès en plus du manager nominal.
Plugin ce-management ajouté au registry (catégorie business, off par
défaut). Quand off : signup CE caché + dashboard /espace-ce 404.
Admin organizations :
- Tab statut (Toutes / À valider [count] / Validées) avec compteur des
organisations pending dans l'en-tête.
- Badge statut sur la liste et la page détail.
- Bouton « Valider l'organisation » sur le détail (action
approveOrganizationAction → flip approved=true + approvedAt + audit
log organization.approve). Idempotent : un re-appel sur une org déjà
validée ne re-loggue pas.
- Détail montre les compteurs carbetMemberships + rentalProviders.
Migration appliquée à la DB prod (CMCK backfill validé).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration : ContentPage.id devient PK composite (slug, lang) au lieu de slug
seul, pour stocker une version FR et une version EN du même slug. Index sur
slug seul pour les lookups.
Schema Prisma : @@id([slug, lang]).
Helpers :
- getContentPage(slug, lang) avec fallback FR si la version dans la langue
demandée n'existe pas
- listContentPages(category?, lang?) accepte un filtre lang
- upsertContentPage : utilise le composite key
Pages publiques (a-propos, faq, comment-ca-marche, pour-comites-entreprise,
devenir-loueur, cgv, mentions-legales, politique-de-confidentialite) :
ajoutent un appel à getLocale() et le passent à getContentPage.
Seeds :
- src/lib/plugins/seeds/content-pages-en.ts : 8 pages traduites en anglais
- hook onEnable du plugin i18n-fr-en : seed EN pages au toggle on. Désactiver
i18n n'efface pas les EN pages (elles dorment, fallback FR reprend).
Résultat : quand l'utilisateur switche vers EN, /a-propos, /faq, /cgv, etc.
basculent en anglais. Le contenu hors-DB (composants UI) bascule déjà via les
dictionnaires de la PR i18n-fr-en initiale.