Commit graph

5 commits

Author SHA1 Message Date
Ubuntu
3a557b6de5 feat(ce): Sprint L — email auto invites + admin memberships UI
All checks were successful
CI / test (pull_request) Successful in 2m39s
Email automatique pour les invites CE_MEMBER :
- sendCeInviteEmail(to, orgName, inviteUrl, inviterName?) : template
  best-effort (dry-run sans Resend), bouton CTA + lien direct en plain
  text. Mentionne TTL 14j + warning si pas le destinataire attendu.
- createInviteAction branche l'envoi automatique quand un email est
  renseigné dans le formulaire. Audit log gagne emailedAutomatically.
- InviteForm UI : affiche « lien généré · email envoyé » quand un
  email était fourni. Texte d'aide mis à jour.
- Sans email → comportement inchangé : lien à copier manuellement.

Admin /admin/carbets/[id] gagne section memberships :
- src/lib/admin/carbets.ts : getCarbetForEdit inclut organizations +
  listOrganizationsForLink helper (toutes orgs triées approved desc).
- 2 actions admin : linkCarbetToOrganizationAction (idempotent) +
  unlinkCarbetFromOrganizationAction. Audit scope=admin.carbets,
  events carbet.org.link / carbet.org.unlink.
- CarbetMemberships client component : liste les orgs liées (badge
  pending si org non approuvée) + select des orgs disponibles + boutons
  Lier/Délier. Désactive le select quand toutes les orgs sont déjà
  liées.

Le link admin permet de :
- Lier rétroactivement un carbet existant à un CE (cas où l'orga
  intègre un carbet d'un hôte individuel).
- Délier un carbet quand un CE part ou que le carbet repasse en
  gestion individuelle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 01:59:18 +00:00
Ubuntu
ea0e606735 feat(ce): Sprint K — public badge + invites CE_MEMBER + tests
All checks were successful
CI / test (pull_request) Successful in 2m45s
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>
2026-06-03 00:03:03 +00:00
Ubuntu
caa3d5214f feat(ce): Sprint J — matériel rental côté CE
All checks were successful
CI / test (pull_request) Successful in 2m41s
src/lib/rental-access.ts CE-aware :
- requireRentalProviderSession accepte CE_MANAGER (en plus de
  RENTAL_PROVIDER et ADMIN).
- getCurrentRentalProvider : CE_MANAGER → findFirst par
  organizationId ; RENTAL_PROVIDER → par managedByUserId.
- getCurrentRentalProviderForCe(organizationId) helper explicite.
- canManageRentalProvider gagne un userOrgId? optionnel : vrai si
  manager nominal OU CE_MANAGER + provider.organizationId === userOrgId.
- Callers existants (5 sites : actions.ts + 4 routes API rental)
  passent désormais session.user.organizationId.

Actions /espace-prestataire/actions.ts role-aware :
- requireOwnedProvider() dérive basePath selon le rôle :
  CE_MANAGER → /espace-ce/materiel ; sinon → /espace-prestataire.
- Tous les redirect/revalidatePath utilisent basePath, donc
  createHostItemAction, updateHostItemAction, deleteHostItemAction,
  addItemBlockAction, removeItemBlockAction, updateBookingStatusAction
  emmènent le user vers son espace contextuel après chaque opération.

/espace-ce/materiel/page.tsx — onboarding :
- Plugin gear-rental disabled → message d'info.
- Pas de provider activé → CTA « Activer la location matériel pour
  <org> » (bouton bloqué si org pending, message bannière).
- Provider existant → dashboard avec KPIs (items actifs, résa pending,
  confirmées à venir, revenu 30j) + 2 ActionCards Items + Réservations.

actions.ts (CE) :
- activateRentalProviderForCeAction → crée RentalProvider(organizationId,
  name="Matériel <org>", managedByUserId=session.user.id, approved=true)
  + audit + redirect /espace-ce/materiel.

Pages CE clonées (réutilisent les composants, actions, helpers
existants — zéro duplication de logique métier) :
- /espace-ce/materiel/items/page.tsx (liste)
- /espace-ce/materiel/items/new/page.tsx (HostItemForm)
- /espace-ce/materiel/items/[itemId]/page.tsx (MediaUploader +
  HostItemForm + ItemBlocksManager + ItemInlineDelete)
- /espace-ce/materiel/reservations/page.tsx (BookingDecision)

Tous importent depuis /espace-prestataire/{actions, items, reservations}
pour rester DRY. Les breadcrumbs et links sont adaptés au contexte CE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 23:47:57 +00:00
Ubuntu
74ea280f28 feat(ce): Sprint I — CRUD carbets côté CE
All checks were successful
CI / test (pull_request) Successful in 2m36s
Session étendue :
- Ajout session.user.organizationId (typedef + auth callbacks JWT
  & session). Permet à canManageCarbet de check membership sans
  refetch DB.

src/lib/carbet-access.ts :
- MANAGER_ROLES inclut désormais CE_MANAGER → /espace-hote ET /espace-ce
  sont gardés par requireOwnerSession (CE_MANAGER passe, OWNER passe,
  ADMIN passe).
- canManageCarbet(session, carbetOwnerId, linkedOrgIds=[]) :
  - ADMIN → toujours vrai
  - OWNER + session.user.id === carbetOwnerId → vrai
  - CE_MANAGER + session.user.organizationId ∈ linkedOrgIds → vrai
  - sinon faux.
- Callers historiques (qui ne passent pas linkedOrgIds) restent sûrs :
  CE_MANAGER ne peut rien gérer par défaut.

createCarbet étendu : si role=CE_MANAGER + organizationId présent,
crée OrganizationCarbetMembership dans la même transaction. Redirige
ensuite vers /espace-ce/carbets/[id] au lieu de /espace-hote/.

Sweep des callers canManageCarbet (8 sites) : chargent désormais
`Carbet.organizations` + passent linkedOrgIds. Includes :
- updateCarbet, setCarbetStatus, deleteCarbet, reorderMedia, deleteMedia
  dans espace-hote/carbets/actions.ts
- espace-hote/carbets/[carbetId]/page.tsx
- API POST /api/carbets/[carbetId]/media

Pages /espace-ce/carbets/* :
- page.tsx : liste les carbets co-gérés via OrganizationCarbetMembership,
  forms Publier/Dépublier/Supprimer pointent vers les actions
  existantes de /espace-hote (réutilisation totale)
- nouveau/page.tsx : requireApprovedOrg (redirect dashboard si pending),
  CarbetForm + createCarbet (même action que /espace-hote — détecte
  CE_MANAGER et crée membership)
- [carbetId]/page.tsx : vérif que le carbet est lié à l'org du user
  + MediaUploader + CarbetForm (updateCarbet partagé)

Dashboard /espace-ce/page.tsx : ActionCard « Mes carbets » devient
active (le lien marche même en pending — l'org peut préparer des
brouillons, c'est juste la publication qui est bloquée).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 23:34:17 +00:00
Ubuntu
63a29d9ade feat(ce): Sprint H — signup CE public + /espace-ce shell
All checks were successful
CI / test (pull_request) Successful in 2m33s
src/lib/ce-access.ts (NEW) :
- requireCeManagerSession (redirect connexion ou / si rôle insuffisant)
- getCurrentCeOrganization (CE_MANAGER → son org via organizationId,
  ADMIN → org ciblée par paramètre ou null)
- canManageCarbetForCe (owner direct OU membre d'une org liée)
- requireApprovedOrg (redirect /espace-ce?pending=1 si non validée)

Emails best-effort :
- sendNewCeRequest → admin (contact@karbe) avec lien filtré
  /admin/organizations?status=pending
- sendCeApproved → CE_MANAGERs actifs de l'org après validation
- Branchement dans approveOrganizationAction : envoie le mail à tous
  les CE_MANAGERs actifs de l'org en best-effort.

Signup CE public :
- SignupForm 4e tuile « Comité d'Entreprise » avec champ orgName.
  Layout grid 4 colonnes sur lg, 2 sur sm.
- /api/signup étendu :
  - zod accepte CE_MANAGER + orgName
  - transaction $tx atomique : Organization (approved=false, slug
    auto-unique via slugify + suffix) + User (role=CE_MANAGER,
    organizationId lié)
  - sendNewCeRequest best-effort
  - réponse étendue avec organizationId
- Pattern slug : retry avec suffix -2, -3… jusqu'à libre

Dashboard /espace-ce :
- layout.tsx : requirePluginOr404("ce-management") +
  requireCeManagerSession
- page.tsx : 4 KPIs (carbets co-gérés, items rental, bookings 30j,
  revenu 30j), bannière « En attente de validation » si pending,
  2 ActionCards (Mes carbets, Matériel rental) marquées « Bientôt »
  jusqu'aux sprints I et J
- ce-dashboard.ts : getCeOrgKpis (agrège bookings carbets via
  membership + rentalBookings via provider.organizationId) +
  listCeCarbets pour Sprint I

SiteHeader : lien « Espace CE » conditionné par role + plugin
(mirror du lien Espace prestataire).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 23:12:46 +00:00