Commit graph

79 commits

Author SHA1 Message Date
Claude Integration
fea55a7ddb chore(admin): Prisma.CarbetWhereInput type + cleanup options orphelines 2026-05-31 21:08:35 +00:00
00a5533bea Merge pull request 'chore: split options client/server' (#42) from chore/admin-carbet-options-split into main 2026-05-31 21:06:49 +00:00
Claude Integration
fc01144e0e chore(admin): split options enum dans fichier neutre
Le client component CarbetForm importait des options depuis lib/admin/carbets
qui contient "server-only" → erreur build turbopack. Sortie des options dans
src/lib/admin/carbet-options.ts sans server-only.
2026-05-31 21:06:47 +00:00
820f7a821b Merge pull request 'feat(admin): CRUD carbets + médias (Sprint 2)' (#41) from feat/admin-carbets-crud into main 2026-05-31 21:04:56 +00:00
Claude Integration
9aa0771001 feat(admin): CRUD complet carbets + gestion médias (Sprint 2)
Server actions (src/app/admin/carbets/actions.ts) avec validation Zod :
- createCarbetAction → INSERT + audit + redirect /admin/carbets/[id]
- updateCarbetAction → UPDATE + revalidate page publique
- updateCarbetStatusAction → DRAFT/PUBLISHED/ARCHIVED
- deleteCarbetAction → soft archive (bookings/reviews FK Restrict)
- addMediaAction(carbetId, fd) → INSERT Media + sortOrder
- removeMediaAction, reorderMediaAction (transactionnel up/down)

Helpers (src/lib/admin/carbets.ts) :
- listCarbetsAdmin avec filtres (q/river/status/accessType)
- listDistinctRivers, listOwners, listPirogueProviders
- getCarbetForEdit (include owner, provider, media, _count bookings/reviews)
- Options enum pour les selects (ACCESS_TYPE, TRANSPORT_MODE, STATUS)

Pages :
- /admin/carbets : liste tableau dense avec recherche/filtres GET, status badge,
  liens vers édition, count médias/résas
- /admin/carbets/new : page création avec CarbetForm
- /admin/carbets/[id] : header titre+badge+actions, MediaManager, CarbetForm
  d'édition. Lien public si PUBLISHED.

Composants admin réutilisables :
- StatusBadge (DRAFT/PUBLISHED/ARCHIVED + statuts Booking)
- FormField + inputCls/selectCls/textareaCls
- CarbetForm (client, 5 sections : identité, localisation, accès, séjour,
  publication) avec useTransition + erreur + succès inline
- MediaManager (client, liste + reorder ↑↓ + suppression + ajout par URL)
- StatusActions (client, publier/dépublier/archiver/réactiver avec confirm)

API :
- GET /api/admin/carbets/[id]/media pour refresh client après mutation

Audit léger en log console (JSON structuré) — Sprint 5 ajoutera la table.
2026-05-31 19:51:33 +00:00
3ec7a3ff10 Merge pull request 'feat(admin): shell + dashboard + ⌘K (Sprint 1)' (#40) from feat/admin-shell-foundation into main 2026-05-31 18:22:08 +00:00
Claude Integration
bcb93c6b29 feat(admin): shell admin + dashboard KPI + recherche ⌘K (Sprint 1)
Layout admin :
- src/app/admin/layout.tsx : route protégée requireRole(ADMIN), sidebar + topbar + breadcrumbs, data-admin sur racine pour theme sobre indépendant du theme public
- Sidebar : 12 sections groupées (Vue d'ensemble, Catalogue, Activité, Membres, Contenu, Système), highlight de la route courante
- TopBar : prompt ⌘K, lien vers site public, email admin
- Breadcrumbs : auto depuis pathname
- CommandPalette : ⌘K / Ctrl K, navigation ↑↓ + Entrée, recherche live debounced 150ms

Dashboard :
- 7 KPI cards avec tone neutral/ok/warn/info (réservations semaine, confirmées 30j, revenus reversés, occupation, nouveaux users, carbets publiés, avis à modérer)
- Section raccourcis fréquents

Theme admin :
- globals.css : [data-admin] override le background+font, neutralise les borders sépia/papier teinté du theme aquarelle, garantit lisibilité permanente

Recherche globale :
- lib/admin/search.ts : query parallèle sur Carbet, User, Booking, ContentPage, PirogueProvider (5 résultats par catégorie, LIKE insensitive)
- api/admin/search?q=… route handler avec requireRole

KPI :
- lib/admin/kpis.ts : 7 métriques live (cache 0), Promise.all, helper formatEur

Pas de dépendance externe ajoutée (cmdk, shadcn) — composants custom Tailwind pour rester léger.
2026-05-31 18:21:50 +00:00
ffb39a3bf5 Merge pull request 'feat(plugin): aquarelle seed media + upload script' (#39) from feat/aquarelle-seed-media into main 2026-05-31 12:20:56 +00:00
Claude Integration
47258bf1be feat(plugin): image-gallery-aquarelle-seed hook + upload script
Hook onEnable du plugin image-gallery-aquarelle-seed :
- Pour chaque carbet démo, crée une entrée Media qui pointe vers son aquarelle
  hébergée dans MinIO sous karbe-medias/seed/aquarelle/.
- s3Key préfixé seed/aquarelle/ pour faciliter le détachement au disable.
- Idempotent (skip si Media existe déjà).

Hook onDisable :
- Supprime tous les Media avec s3Key startsWith seed/aquarelle/.
- Les fichiers MinIO restent (pas de coût de redéploiement).

Script scripts/upload-aquarelles.sh :
- Upload depuis /tmp/karbe-aquarelles/*.{jpg,png} vers le bucket karbe-medias.
- Applique la policy public-download au bucket pour que media.karbe.cosmolan.fr
  serve les fichiers sans auth.
- À exécuter une fois après génération des illustrations.
2026-05-31 12:20:35 +00:00
93aebc4e87 Merge pull request 'feat(plugin): theme-aquarelle + hero' (#38) from feat/theme-aquarelle into main 2026-05-31 12:15:30 +00:00
Claude Integration
c69c355f90 feat(plugin): theme-aquarelle + hero variant (Phase 2.4 partie 1/2)
Registry : ajoute 2 plugins :
- theme-aquarelle (carnet naturaliste XIXᵉ, mutual exclusion avec theme-guyane)
- image-gallery-aquarelle-seed (14 aquarelles → MinIO + Media carbets démo)

Hooks :
- theme-guyane et theme-aquarelle se désactivent mutuellement au toggle ON
  via disableOtherTheme()

CSS (globals.css) :
- body[data-theme=aquarelle] : background papier teinté #faf5e9 + texture
  grain papier inline SVG + radial gradients ocres/canopy délavés
- Surcharges automatiques des borders zinc/gray vers sépia délavé

Layout :
- PT_Serif (au lieu de Cormorant) en theme aquarelle, plus dense et encrée
- data-theme = aquarelle prioritaire sur guyane si les deux sont enabled
  (défensif — le hook garantit normalement la mutual exclusion)

Hero :
- 2 versions dans le composant : guyane (existant, SVG CarbetRiver) et
  aquarelle (image MinIO 01-hero-fleuve-maroni.jpg en fond, voile crème,
  texte sépia, CTAs carrés sans rounded, hairlines, ornement de planche)
- Branchement via getActiveTheme()
- aquarelleUrl() helper qui construit l'URL MinIO publique

Partie 2/2 (PR ultérieure) : upload des 14 images dans MinIO + hook
image-gallery-aquarelle-seed + variantes aquarelle des autres composants
(CarbetCard, ExperiencesSection, HowItWorksSection, CESection, Footer).
2026-05-31 12:15:07 +00:00
bb2fee7659 Merge pull request 'chore(admin): findUnique composite key' (#37) from chore/admin-content-composite-key into main 2026-05-31 11:48:16 +00:00
Claude Integration
8196a1a3f9 chore(admin): adapter findUnique/update à la PK composite (slug, lang)
Admin édite la version FR par défaut. Édition multi-langues = future feature.
2026-05-31 11:48:14 +00:00
df9eb5fcbd Merge pull request 'feat: pages contenu bilingues' (#36) from feat/i18n-content-pages into main 2026-05-31 11:45:49 +00:00
Claude Integration
87c3e7a581 feat: ContentPage bilingue (PK composite slug+lang) + seed pages EN
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.
2026-05-31 11:45:47 +00:00
88a937f2fd Merge pull request 'feat(plugin): i18n FR + EN' (#35) from feat/i18n-fr-en into main 2026-05-31 11:38:41 +00:00
Claude Integration
cf9da94bb5 feat(plugin): i18n FR + EN (Phase 4.2)
Infrastructure i18n légère, sans deps externe :

- lib/i18n/types.ts : LOCALES, DEFAULT_LOCALE, cookie name
- lib/i18n/server.ts : getLocale (cookie > Accept-Language > FR),
  t(key) async server-side, dict(locale)
- lib/i18n/client.tsx : LocaleProvider + useLocale + useT
- messages/fr.json + messages/en.json : ~50 clés pour landing + header + footer
- LocaleSwitcher component (cookie + router.refresh)

Plugin gated :
- Quand i18n-fr-en désactivé, getLocale() force FR. Le switcher ne s'affiche
  pas dans le hero. Pas d'impact sur le rendu existant.
- Quand activé, switcher visible coin haut-droit du hero. Les composants
  landing/header/footer rendent en FR ou EN selon le cookie utilisateur.

Composants i18n-isés :
- HeroSection (eyebrow, titre, CTA)
- ExperiencesSection (route/fleuve vs expédition, tous les bullets)
- HowItWorksSection (3 étapes)
- CESection (KPIs + body + CTA)
- TestimonialsSection (eyebrow + titre, citations restent en VO)
- Footer (taglines, colonnes)
- SeasonBanner (3 saisons + messages)
- AccessTypeBadge (labels + tooltips)

Pour les ContentPage, le champ lang existait déjà. Une suite (PR ultérieure)
ajoutera le filtre lang dans getContentPage + seed pages EN.
2026-05-31 11:38:39 +00:00
efeea16467 Merge pull request 'feat(plugin): pirogue-providers' (#34) from feat/pirogue-providers into main 2026-05-31 11:29:32 +00:00
Claude Integration
a174f99eba feat(plugin): pirogue-providers (Phase 3.3)
Modèle PirogueProvider (id, name, contacts, fleuves, tarif, description)
+ enum TransportMode (OWNER_PROVIDES, SELF_ARRANGE, PARTNER_PROVIDER) sur Carbet
+ relation Carbet → PirogueProvider (nullable, ondelete:SetNull)

Composants :
- PirogueTransportBlock (server, gated par plugin) sur fiche carbet :
  affiche le mode + provider partenaire avec contacts/tarif/description
- Page publique /partenaires-pirogue : liste des partenaires actifs

Seed onEnable :
- 3 partenaires démo (Pirogues du Maroni, Approuague Aventures, Oyapock Frontière)
  avec tarifs estimatifs et fleuves desservis réels
- Attribution aux 6 carbets démo :
  · Awara (Maroni), Maripa (Approuague), Paripou (Oyapock) → PARTNER_PROVIDER
  · Wapa (Comté), Mahury CE → OWNER_PROVIDES
  · Kourou Couleuvre → SELF_ARRANGE

onDisable désactive les partenaires démo et détache les carbets démo.
2026-05-31 11:29:29 +00:00
8c0b849ad7 Merge pull request 'feat(plugins): content-pages + legal-pages' (#33) from feat/content-pages-and-legal into main 2026-05-31 10:12:28 +00:00
Claude Integration
68f37f554f feat(plugins): content-pages + legal-pages (Phase 4.1 + 4.3)
Plugin content-pages :
- Modèle Prisma ContentPage (slug PK, title, body markdown, category, published)
- lib/content-pages.ts : helpers upsert/get/list/unpublish
- lib/markdown.ts : mini-renderer markdown server-side sans deps externe
  (h1-h3, paragraphes, gras/italique, liens, listes ul/ol, hr, blockquote,
  échappement HTML)
- ContentPageRenderer server component, applique le theme Guyane (font-serif)
- 5 pages seedées : /a-propos, /faq, /comment-ca-marche,
  /pour-comites-entreprise, /devenir-loueur
- Routes publiques + force-dynamic + guard requirePluginOr404

Plugin legal-pages :
- Réutilise le même modèle ContentPage, catégorie 'legal'
- 3 pages seedées : /cgv, /mentions-legales, /politique-de-confidentialite
  (contenu de base, à valider par avocat avant prod réelle)

Admin :
- /admin/content-pages : table par catégorie, statut publié/dépublié
- /admin/content-pages/[slug] : éditeur markdown + toggle publié
- PATCH /api/admin/content-pages/[slug]

Hooks plugin :
- onEnable seed + republish toutes les pages
- onDisable dépublie toute la catégorie sans la supprimer (preserve les edits)
2026-05-31 10:12:13 +00:00
ae8f79b436 Merge pull request 'chore: wire StayConstraints' (#32) from chore/wire-stay-constraints into main 2026-05-31 08:59:48 +00:00
Claude Integration
a7761ca323 chore: wire StayConstraints + minStayNights dans carbet-card + search (oubli PR#30) 2026-05-31 08:59:46 +00:00
fdf66bfc74 Merge pull request 'chore(prisma): champs Carbet min-stay + seasonality' (#31) from chore/schema-min-stay-seasonality into main 2026-05-31 08:52:48 +00:00
Claude Integration
3405f00476 chore(prisma): ajoute minStayNights/maxStayNights/minCapacity/seasonalConstraints au modèle Carbet (oubli PR#30) 2026-05-31 08:52:46 +00:00
32410c95c7 Merge pull request 'feat(plugins): seasonality + min-stay' (#30) from feat/seasonality-and-min-stay into main 2026-05-31 08:50:28 +00:00
Claude Integration
be2391998d feat(plugins): seasonality + min-stay (Phase 3.2 + 3.4)
Plugin seasonality :
- Migration : Carbet.seasonalConstraints JSONB nullable
- lib/seasonality.ts : enum Season (DRY|LOW_WATER|WET), currentSeason() helper
  Guyane (juil-sept sèche, oct-nov étiage, déc-juin pluies), parseSeasonalConstraints,
  isCurrentlyOpen, SEASON_META (label/emoji/tone)
- Composant <SeasonBanner /> server, gated par plugin, ajouté dans layout
  au-dessus de tout le contenu — bandeau couleur+emoji+message contextuel

Plugin min-stay :
- Migration : Carbet.minStayNights, maxStayNights, minCapacity nullable
- Composant <StayConstraints /> client, gated par plugin — pill text
  '2 nuits minimum', '2-7 nuits', 'groupe 4+ recommandé'
- Carbet card et fiche enrichies avec les contraintes

Tous deux désactivables : sans le toggle, comportement legacy inchangé.
2026-05-31 08:50:26 +00:00
4842a44746 Merge pull request 'chore(prisma): enum AccessType' (#29) from chore/access-type-enum-v2 into main 2026-05-31 03:00:54 +00:00
Claude Integration
bc571b38d1 chore(prisma): déclare enum AccessType (oublié dans PR#27) 2026-05-31 03:00:52 +00:00
35080dcde1 Merge pull request 'feat(plugins): access-type + demo-carbets-seed' (#27) from feat/access-type-and-demo-carbets into main 2026-05-31 02:56:41 +00:00
Claude Integration
5e59202505 feat(plugins): access-type + demo-carbets-seed (Phase 3.1 + 2.5)
Plugin access-type :
- Migration : enum AccessType (ROAD_AND_RIVER, RIVER_ONLY), champ accessType
  sur Carbet avec default ROAD_AND_RIVER, roadAccessNote optionnel,
  pirogueDurationMin rendu nullable + index sur accessType
- Schema Prisma mis à jour
- Composant <AccessTypeBadge> client, gated par le plugin
- Carbet card et fiche enrichies : badge + texte adapté (Pirogue vs Route+pirogue
  vs Route directe), section Accès enrichie avec roadAccessNote
- formatPirogueDuration accepte null

Plugin demo-carbets-seed :
- Hook onEnable : 3 propriétaires demo (Yann/Émilie/CE Hôpital) + 6 carbets
  variés (Maroni, Approuague, Comté, Oyapock, Mahury, Kourou) avec mix
  3 RIVER_ONLY + 3 ROAD_AND_RIVER, GPS plausibles, descriptions naturelles
- Hook onDisable : archive (status=ARCHIVED) les carbets demo via slug prefix
- Toutes les fixtures idempotentes (upsert via slug + email)
2026-05-31 02:56:25 +00:00
9b9963403d Merge pull request 'chore(layout): force-dynamic' (#26) from chore/layout-dynamic into main 2026-05-30 23:36:44 +00:00
Claude Integration
049d0bb423 chore(layout): force-dynamic pour refléter l'état des plugins en live
Sans ça, le layout est rendu statiquement au build et ne re-fetch jamais
l'état des plugins, donc les toggles depuis /admin/plugins ne prennent
jamais effet sur la home jusqu'à un nouveau build.
2026-05-30 23:36:42 +00:00
de9f73246b Merge pull request 'chore(sitemap): force dynamic' (#25) from chore/sitemap-runtime into main 2026-05-30 23:32:09 +00:00
Claude Integration
b1c2877e43 chore(sitemap): force dynamic + try/catch DB
Évite que le build échoue quand la DB n'est pas joignable au prerender.
2026-05-30 23:32:07 +00:00
dc05fe118b Merge pull request 'chore(plugins): cast config en Prisma.InputJsonValue' (#24) from chore/plugin-json-type into main 2026-05-30 23:30:25 +00:00
Claude Integration
e433ebc439 chore(plugins): cast config en Prisma.InputJsonValue
Le type Record<string,unknown> ne satisfait pas le narrowing JSON Prisma.
Cast explicite pour faire passer le build TS.
2026-05-30 23:30:12 +00:00
1868b36379 Merge pull request 'chore(docker): npx prisma generate dans builder' (#23) from chore/dockerfile-prisma-generate-builder into main
Merge PR#23
2026-05-30 23:28:30 +00:00
Claude Integration
26922329d4 chore(docker): npx prisma generate dans builder stage
Le client Prisma est généré dans src/generated/prisma (cf. schema.prisma
output). Le post-install npm de deps stage le génère mais on n'embarque
que node_modules, pas le src/generated. Le builder doit donc régénérer
explicitement avant npm run build.
2026-05-30 23:28:19 +00:00
d3ce396b20 Merge pull request 'chore(docker): copier prisma/ avant npm ci' (#22) from chore/dockerfile-prisma-copy into main
Merge PR#22: fix Dockerfile prisma copy
2026-05-30 23:25:44 +00:00
Claude Integration
a564373a07 chore(docker): copier prisma/ avant npm ci + dans runner
Le postinstall hook `prisma generate` du package.json a besoin de
prisma/schema.prisma pour s'exécuter. Sans ça, npm ci échoue dès l'étape deps.

Ajoute aussi prisma/ dans l'image runner pour pouvoir exécuter
`prisma migrate deploy` depuis l'app en prod.
2026-05-30 23:25:32 +00:00
800a06afc6 Merge pull request 'chore: sync package-lock.json' (#21) from chore/lockfile-sync into main
Merge PR#21: sync lockfile
2026-05-30 23:22:38 +00:00
Claude Integration
abc3844af2 chore: sync package-lock.json (qs@6.15.2 missing) 2026-05-30 23:22:25 +00:00
b4617545d0 Merge pull request 'feat(plugins): visuels Phase 2 (theme-guyane, landing-hero, landing-sections)' (#19) from feat/plugins-visuals-phase2 into main
Merge PR#19: plugins visuels Phase 2
2026-05-30 23:19:42 +00:00
Claude Integration
d19701e275 feat(plugins-visuels): theme-guyane + landing-hero + landing-sections
Phase 2 visuals — la page d'accueil prend vie via 3 plugins activables :

- theme-guyane : palette tropicale (vert canopée, eau Maroni, ocre latérite,
  bois karbé, blanc cassé), tokens CSS, typographie display Cormorant Garamond,
  gradient ambient discret. Activé via body[data-theme=guyane].

- landing-hero : section plein écran avec illustration vectorielle SVG (carbet
  sur pilotis au crépuscule + fleuve + jungle), claim 'Le karbé qui dort vous
  attend', double CTA Découvrir / Proposer. Fallback = hero minimaliste actuel.

- landing-sections : 5 sections en cascade — 2 expériences (route+fleuve vs
  expédition fleuve), Comment ça marche (3 étapes), CE (registre coop sans
  commission), Témoignages (3 stubs), Footer riche avec navigation.

Illustrations 100% SVG inline (pas de dépendance image externe). Quand le
plugin image-gallery-seed sera activé (Phase 2.4), les photos remplaceront
progressivement les SVG. Aucune coupure sur le rendu actuel : tous les plugins
visuels sont disabled par défaut, le site garde son look minimaliste tant que
l'admin ne les a pas activés depuis /admin/plugins.
2026-05-30 23:19:24 +00:00
4454f7331d Merge pull request 'feat(plugins): foundation système Plugin Karbé' (#18) from feat/plugin-foundation into main
Merge PR#18: plugin foundation
2026-05-30 22:17:27 +00:00
Claude Integration
62cc464738 feat(plugins): foundation système Plugin Karbé
- Modèle Prisma Plugin (key, name, description, category, version, enabled,
  config JSONB, migrationsApplied, timestamps) + migration SQL
- PluginRegistry (src/lib/plugins/registry.ts) avec 12 plugins déclarés :
  visuels (theme-guyane, landing-hero, landing-sections, image-gallery-seed,
  demo-carbets-seed), métier (access-type, seasonality, pirogue-providers,
  min-stay), contenus (content-pages, legal-pages), i18n (i18n-fr-en)
- Server helpers (server.ts) : sync, isEnabled, getEnabledKeys, toggle avec
  hooks onEnable/onDisable, updateConfig, cache 5s
- Client bridge (client.tsx) : PluginProvider + useIsPluginEnabled
- Composant <IfPluginEnabled plugin=... fallback=...>
- Guard requirePluginOr404 pour pages et routes
- Page admin /admin/plugins avec table toggle par catégorie + édition config
- Route PATCH /api/admin/plugins/[key] + GET
- Layout async qui sync registry + passe enabledKeys au PluginProvider

Tous plugins en enabled=false par défaut, activation pilotée depuis l'admin.
2026-05-30 22:17:10 +00:00
d7de43a70e Merge pull request 'SYS-19: Compléter docker-compose.prod.yml avec Postgres et MinIO co-déployés' (#17) from feat/sys-19-prod-compose-postgres-minio into main
Merge PR#17: postgres + minio co-déployés (SYS-19)
2026-05-30 18:39:12 +00:00
6898649898 feat(prod): co-deploy postgres and minio in production compose 2026-05-30 18:36:38 +00:00
b8d14d8f9d Merge pull request 'SYS-18 Déployer Karbé sur karbe.cosmolan.fr (MVP, Stripe TEST)' (#16) from feat/sys-18-deploy-karbe-mvp-stripe-test into main
Merge PR#16: deployment stack (Dockerfile + Caddy + .env). À compléter avec Postgres+MinIO co-déployés via SYS-19.
2026-05-30 18:35:07 +00:00