18 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b0e6f83655 |
feat(billing): essai 14 j Pro avec CB à l'inscription (Stripe trial_period_days)
Implémente le chantier #6 de docs/tech/landing-optimisations.md. Le funnel signup propose maintenant un essai 14 j Pro avec carte demandée mais non prélevée — prélèvement automatique à J+14 avec rappel à J+11 (webhook customer.subscription.trial_will_end de Stripe). Couverture tests : 60 tests unitaires sur la couche billing - billing.spec.ts (25) — quota Free, bypass trial, inTrial state - stripe_billing.spec.ts (24) — handlers webhook, idempotence, dispatcher - trial_recap_job.spec.ts (11) — stats aggregation, formatRubisToHoursFr + 3 nouveaux tests vitest côté SPA (useTrialDaysRemaining, useIsAtFreeLimit bypass trial). Backend : - Migration 1779000000000_add_trial_ends_at_to_organizations - PLAN_CAPS bypass quand status=trialing AND trial_ends_at futur - getOrgSubscriptionState expose inTrial + trialEndsAt - Refactor handlers webhook en service stripe_billing.ts (pures, testables) — extraction depuis le controller. dispatchWebhookEvent routeur typé également extrait pour les tests. - createTrialCheckoutSession avec subscription_data.trial_period_days=14, garde-fou TrialAlreadyConsumedError contre re-trial. - handleTrialWillEnd → enqueue job recap (BullMQ jobId déterministe basé sur subscriptionId, idempotent contre re-delivery Stripe). - Endpoint POST /api/v1/billing/start-trial. - Email template trial_recap (React Email, branding Rubis figé) avec stats: factures importées, relances envoyées, € récupérés, rubis + heures libérées. Infra de test : - tests/helpers/stripe_mock.ts : __setStripeForTests injection + factories fakeSubscription / fakeCheckoutSession / fakeInvoice. - __setTrialRecapEnqueueForTests : permet de spy l'enqueue sans Redis. Frontend : - /onboarding/billing.tsx (opt-in, pas encore forcé dans le flow) : bouton primaire essai 14j + fallback "Free 2 factures". - PlanLimitBanner : nouveau état "Essai Pro · X jours restants" qui prime sur les autres bandeaux. Discret rubis-glow, non blocant. - useStartTrial hook + useTrialDaysRemaining (arrondi sup). - SubscriptionState typé avec inTrial + trialEndsAt. Landing : - Sous-texte CTA réactivé : « CB demandée, non prélevée avant J+14 » (Hero + FinalCTA), maintenant promesse véridique. Notes ouvertes (à décider ultérieurement) : - Tunnel /onboarding/billing FORCÉ entre signup et /onboarding/compte : guard reste à activer (risque cassage du signup actuel sinon). Pour l'instant l'écran est accessible mais opt-in. - Cron de redondance trial-recap : pas encore implémenté (le jobId déterministe BullMQ couvre déjà la double-livraison Stripe). À ajouter si on observe des trial sans recap en prod. - Tests E2E avec Stripe test mode à faire avant le go-live (cartes 3DS 4000 0027 6000 3184, declined 4000 0000 0000 0341). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
f9cba50b5e |
feat(billing,landing): plan Free 2 factures + scaffold preuves sociales/SEO
Suite des chantiers structurants de landing-optimisations.md. #5 — Plan Free : 5 → 2 factures actives (cf. ADR-023) - PLAN_CAPS.free.activeInvoicesLimit dans apps/api/app/services/billing.ts - Tests unitaires alignés (4 → 1, 5 → 2 cap, delta 3 → delta 2) - billing:scenario command : commentaires + valeur par défaut - PlanLimitBanner : copy dynamique via {limit} au lieu de "5" hardcodé - /parametres/abonnement : H1 + tile Free (3 mois → 14 jours, 5 → 2) - billing.test.tsx (fixtures + cas test) - landing copy : hero feature pill, Pricing tile, FinalCTA, CGV §5 - CLAUDE.md pricing table #7 — Scaffold <TrustedBy /> (preuve sociale) - Composant qui render null tant que copy.trustedBy.{logos,testimonials} sont vides — pas de placeholder bidon. - Structure data dans copy.ts avec commentaires sur les prérequis avant d'ajouter une entrée (accord signé, photo, citation chiffrée). - Section insérée juste avant <Pricing /> (cf. doc §4). #8 — Plan articles SEO + brouillon article 1 - docs/marketing/seo-articles.md : 5 articles ciblés, mots-clés, structure type, lead magnet, calendrier 5 semaines. - Article 1 ("Modèle d'email de relance facture impayée") en brouillon complet, prêt à valider via l'admin blog (apps/api). #6 — Plan détaillé migration Stripe trial 14 j (code reporté) - docs/tech/stripe-trial-with-card.md : état actuel vs cible, architecture (Stripe Checkout + trial_period_days), modifs DB (trial_ends_at), API (start-trial + webhook trial_will_end), SPA (onboarding/billing), 3 emails transactionnels avec contenu intégral, risques + mitigations, plan d'exécution 2,5 j. - Implémentation reportée à une session focus avec accès Stripe test mode (cartes 3DS, webhook signing secret). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cecbddc496 |
refactor(landing): mono-langue FR + quick wins optimisations conversion
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 1m1s
- Retire le système i18n EN (apps/landing/src/i18n/, pages /en/*) ajouté en 4f3417f. Source unique de copy dans src/copy.ts (FR uniquement). Switcher de langue retiré du header, sitemap nettoyé des hreflang. - Header : micro-baseline « Logiciel de relance de factures impayées » sous le wordmark pour lever l'ambiguïté du nom (§1). - CTA principal : « Lancer Rubis » → « Démarrer mon essai 14 jours » avec sous-texte sur Hero / FinalCTA / Pricing (§5). - Essai 30 j → 14 j sur landing + CGV §6.2 (§3). - Blog promu en nav primaire avec label « Ressources » (§6). Doc d'arbitrage : docs/tech/landing-optimisations.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
4f3417fcef |
feat(landing): support i18n EN avec routing /en/* (Astro i18n natif)
Active Astro i18n avec `defaultLocale: fr` et `prefixDefaultLocale: false`
— les URLs FR restent canoniques à la racine, l'EN vit sous `/en/*` pour
ne pas casser le SEO existant.
Architecture :
- `src/i18n/{types,fr,en,index}.ts` — dico FR fait foi (Dict inféré),
EN doit matcher la shape ; helpers `getTranslations(locale)` et
`getAlternateUrl()` pour le language switcher.
- `Layout.astro` lit `Astro.currentLocale`, propage `locale` aux
composants React, set `<html lang>`, og:locale + alt, hreflang.
- `SiteHeader` expose un lien switcher FR↔EN qui préserve la page.
- Toutes les sections (Hero, Stats, Promise, HowItWorks, Gamification,
AutoBanking, Legal, Pricing, FAQ, FinalCTA, Footnotes, SiteFooter)
acceptent une prop `locale` et tirent leurs chaînes du dico.
Pages EN créées :
- `/en/` — home complète
- `/en/blog`, `/en/changelog` — chrome traduit, contenu reste dans la
langue de rédaction (les .md changelog + posts API sont FR)
- `/en/cgv`, `/en/mentions-legales`, `/en/confidentialite` — résumés
courts ; la version juridiquement contraignante reste la FR (droit
français, conformité GDPR/LCEN/LME).
Sitemap mis à jour avec entrées FR/EN + `xhtml:link rel="alternate"`.
Pas de détection auto via Accept-Language pour l'instant — le switcher
header suffit en V1.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
5f88a6411e |
feat(landing): instrumentation PostHog (Astro client)
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 1m0s
Setup PostHog côté landing — loader inline dans Layout.astro + tracking de 5 events business côté browser : - blog_article_viewed / blog_cta_clicked (funnel blog → app) - pricing_pro_cta_clicked / pricing_plan_selected (intent upgrade) - signup_cta_clicked (CTA hero/header/finalCTA, location-aware) Vars PUBLIC_POSTHOG_* inlinées au build via build-arg CI (POSTHOG_PROJECT_TOKEN, partagé avec apps/web). Token public phc_*, safe à bake dans le bundle. Au passage : supprime posthog-server.ts laissé par le wizard (dead code, importait posthog-node qui n'est pas dans les deps). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
e9e0ac6abb |
update landing
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 57s
|
||
|
|
1200c549a0 |
feat(landing): section "Mode automatique bientôt" + FAQ + pricing
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 1m2s
Annonce la connexion bancaire auto sur la landing pendant la fenêtre KYC Powens. Trois touches cohérentes pour préparer le marché aux plans Pro / Business : - Nouvelle section AutoBanking entre Gamification et Legal : badge "Bientôt disponible", h2 « Plus jamais besoin de répondre "C'est payé" », 4 bénéfices clés (détection temps réel via Powens AISP, toutes banques françaises, mode validation ou auto-pilote, lecture seule), badge "Inclus sur Pro et Business", mention DSP2 / AISP ACPR pour la conformité. Mock visuel d'email "Garage Lemoine a payé F2026-0013 · 4 189,40 €" avec halo glow rubis, récap client/facture/montant + check-list "facture payée · relances annulées · remerciement envoyé · +1 rubis". - FAQ : enrichit la 1re question (paiement hors plateforme) avec une teaser vers la section AutoBanking, et ajoute une nouvelle question dédiée « La connexion bancaire, c'est sécurisé ? Vous pouvez bouger mon argent ? » avec réponse explicite sur le statut AISP (lecture seule, DSP2, révocation 1 clic). - Pricing : ajoute une feature dans la liste des inclus Pro avec mention "à venir" pour la détection bancaire automatique. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
c590b489ef |
change logo
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 58s
|
||
|
|
363caf8061 |
feat(web): UI marque blanche — page /parametres/marque + intégration
Complète la feature marque blanche initiée dans le commit précédent
(919ebfe). L'API backend est désormais consommée par une page
dédiée dans le SPA, et le changelog v1.11.0 décrit la feature complète
plutôt que la "première brique".
Livraison côté SPA :
- `apps/web/src/lib/brand.ts` — types BrandSettings/BrandTokens (miroir
serveur) + 5 hooks TanStack Query : useBrand (GET cache), useUpdateBrand
(PATCH), useUploadBrandLogo (multipart), useDeleteBrandLogo, et
useSendBrandTestEmail. Pas de retry sur le GET pour éviter de bombarder
/brand quand l'org n'est pas Business (403 définitif).
- `apps/web/src/components/settings/BrandEmailPreview.tsx` — mock fidèle
d'un email de relance qui réagit en direct aux color pickers. Copie la
structure HTML/CSS de relance_email.tsx + _layout.tsx (banner, body
pre-line, card récap, footer Rubis) pour que le user soit confiant que
son vrai email rendra pareil.
- `apps/web/src/routes/_app/parametres_.marque.tsx` — page éditeur
complète :
• Header avec retour
• Upsell card propre si l'org n'est pas Business (pas d'éditeur du tout
pour éviter de leak des controls qui throw 403 derrière)
• Form 2 colonnes desktop : zone upload logo (drop ou click) avec
preview sur le bandeau effectif, input nom expéditeur, color pickers
natifs (HTML5 + hex input) groupés en "Principales" (primary + banner)
et "Avancées" (7 autres, accordéon fermé par défaut)
• Live preview à droite (sticky desktop) qui se met à jour à chaque
keystroke / pick
• Actions : Enregistrer (diff draft → settings → PATCH), Réinitialiser
(tous les overrides à null), Envoyer un test (qui force l'enregistrement
préalable parce que le test utilise les tokens sauvegardés)
• Sémantique null/undefined respectée côté patch — undefined = pas
touché, null = reset au default Rubis sur ce champ précis
- Carte de navigation ajoutée dans `/parametres` qui linke vers
`/parametres/marque` avec libellé adaptatif (Business = "Configurer",
autres = "En savoir plus").
Changelog v1.11.0 réécrit pour décrire la feature complète et non plus
seulement la moitié backend. Un seul concept, une seule entrée changelog.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
919ebfe755 |
feat(release): v1.11.0 — marque blanche pour le plan Business (backend)
Première moitié de la feature marque blanche : la machinerie complète qui permet à un compte Business d'envoyer ses emails de relance avec son propre logo, ses propres couleurs et son nom comme expéditeur, à la place du branding Rubis. Architecture : - Nouvelle colonne JSONB `organizations.brand_settings` (12 tokens customisables : logo, senderName, et 10 couleurs — primary, banner, body bg, card bg, text, text muted, border, link, button text). Null = palette Rubis intacte. Validation hex stricte (#RRGGBB). - Service `#services/brand` avec `resolveBrandTokens(org)` qui merge defaults + overrides en respectant le plan (couleurs/logo = Business only ; senderName = cascade pour tous les plans). Mergeurs avec sémantique "null = reset au default sur ce champ précis" pour les patches partiels. - Service mutualisé `#services/media_storage` qui remplace l'ancien `blog_uploads.ts`. Scopes `blog` (4 MB, jpg/png/webp) et `brand-logo` (1 MB, + svg accepté). Cleanup automatique du logo précédent lors d'un remplacement (pas de versioning — la conv produit est "on écrase"). - Controller `BrandController` (5 endpoints) + middleware `AssertBusinessPlanMiddleware` qui throw 403 `business_plan_required` (code matché par le SPA pour l'upsell card). - Refactor des 3 templates mail (relance, payment thanks, checkin) + layout commun pour accepter `tokens: BrandTokens` en prop. Le dispatcher résout les tokens per-org pour relance + remerciement (= user → client, branded), et passe `DEFAULT_BRAND` au checkin (= Rubis → user, toujours Rubis-branded). - Routes publiques pour le logo : `/api/v1/uploads/brand-logos/:filename` (sans auth, cache immutable, X-Content-Type-Options: nosniff pour les SVG). UI self-service arrive dans la prochaine version (v1.12.0). En attendant, un compte Business peut être configuré via Bruno / API directe. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
c4910889de |
feat(landing/changelog): glow rubis sur la dernière version, retire les pills sur les anciennes
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 52s
Le contexte "Nouveauté" sur 11 cartes à la suite devenait du bruit visuel (toutes les anciennes versions ont été des nouveautés à un moment, hein). Le pill de type est maintenant réservé à la version la plus récente — sur les autres on garde seulement la chip de version + la date. Pour compenser et ancrer le regard sur le dernier état du produit, la carte "latest" gagne un glow rubis multi-couches : - Ring serré 4 px en couleur rubis-glow (contour soft) - Drop shadow proche teintée rubis (profondeur) - Bloom large diffus (halo ambiant) - Animation "respiration" 5 s ease-in-out infini (variation subtile sur l'intensité du bloom) - Désactivée si `prefers-reduced-motion: reduce` côté user. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
fc0d13e955 |
feat(landing): changelog public /changelog + flux RSS
Page Astro prerendered qui liste les versions livrées en reverse-chrono. Contenu géré en MD files versionnés dans le repo via Astro content collections (`src/content.config.ts`), pas de DB — chaque release = 1 fichier `src/content/changelog/<x.y.z>.md` ajouté en PR à côté du bump de version applicatif. 11 entrées initiales (v1.0.0 → v1.10.0) couvrant le premier mois public : lancement (OCR Mistral + plans par défaut + mode démo + Stripe), saisie manuelle, SSO Google puis Microsoft, plans custom, templates email, réécriture IA, insights, blog, marque blanche, remerciement automatique. UI : - Hero centré aligné DA landing (eyebrow rubis + h1 display + sub muted) - 2 colonnes desktop : feed cartes (gauche) + sticky rail jump-nav (droite) - Sur mobile/tablette : pas de rail, juste le feed - Sticky rail : IntersectionObserver inline qui met en surbrillance la version courante quand l'user scrolle - Anchors `#1.4.0` partageables, cliquables depuis le chip de chaque carte - Type pills colorés : feature (rubis solid), improvement (cream-2), fix (line outline) - Bullets losanges ◆ rubis cohérents avec le gem brand SEO : - `prerender = true` → HTML figé au build, LCP minimum - JSON-LD WebPage avec mainEntity[TechArticle] par version → rich snippets Google - Flux RSS 2.0 à `/changelog/rss.xml` (prerendered aussi) - Auto-discovery RSS ajoutée au Layout (à côté de celle du blog) - Lien Changelog ajouté au SiteFooter à côté de Blog Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3052a7e909 |
feat(landing): brand "Rubis.pro" + corrections copy
- Brand : suffixe `sur l'ongle` → `.pro` (attaché, muted, non italique).
Propagé partout via `<Brand withSuffix>` — header/footer landing,
topbar/sidebar SPA, login/signup/onboarding. Renforce l'identification
brand requise par Google OAuth (gem seul jugé trop générique).
- Hero : dots groupés avec leur label (inline-flex) — ne se baladent plus
en début de ligne au flex wrap. Ajout d'un topbar mock "Rubis.pro ·
Tableau de bord" en haut de la carte hero pour identifier le mock comme
un vrai dashboard. `DSO` → `DSO*`.
- Stats : "Trois chiffres qui devraient vous fâcher" → "Trois chiffres
exorbitants". Sous-titre remplacé par "Et vous faites sûrement partie
intégrante de ces enquêtes." (plus direct, moins gratuit).
- Promise : "Votre temps vaut plus que ça" → "Votre temps est plus
précieux". Réécriture de l'amorce ("votre boîte / lundis soirs" →
"votre entreprise / journées"). Ajout d'une ligne italique "Parfois
moins, si votre plan par défaut est bien réglé".
- HowItWorks : Step 01 — suppression de "à la caisse" et de ", RIB"
(l'OCR ne lit pas le RIB en V1). Step 03 — "La machine fait le reste"
→ "L'algorithme fait le reste".
- Gamification : suppression de "Pas un PDF abscons" (jargon inutile) et
de "Et oui, on garde un classement amical" (le classement n'est pas en
V1). `DSO` → `DSO*`.
- Footnotes : ajout de la définition DSO (Days Sales Outstanding) sous
celle d'OCR.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
06a3aaf468 |
feat(landing): step 04 — remerciement automatique au client
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 58s
Ajoute une 4e étape dans la section « Comment ça marche » qui matérialise la fin heureuse du cycle : le client paye, Rubis envoie automatiquement un mot court de remerciement (« Merci, paiement bien reçu »). Pourquoi c'est important côté pitch : - Aligne le produit avec le principe brand « respectueux du client final » (cf. CLAUDE.md). On n'est pas qu'un outil de pression — on est aussi celui qui sait dire merci. - Crée une attente positive de fin de cycle, qui s'enchaîne mieux vers le compteur de rubis (déplacé du step 03 vers 04 pour servir de récompense narrative à la boucle complète). Modifs : - En-tête : « Trois étapes → Quatre étapes » + ajustement du sous-titre. - Step 03 : retitré « Vous validez. La machine fait le reste. » (le punchline « Et puis c'est tout » migre implicitement sur 04). - Step 04 (nouveau) : email de remerciement + encart rubis. - Nouveau ThankYouWidget : email card stylé Apple Mail (sender, sujet, corps, badge « Envoyé automatiquement ») en tokens rubis uniquement (pas de vert — interdit par brand). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
6993d80089 |
perf(landing): inline critical CSS + preload latin woff2
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 59s
Élimine ~130 ms du critical path LCP rapportés par l'audit Lighthouse : - `build.inlineStylesheets: "always"` dans astro.config.mjs : la feuille `_astro/Layout.<hash>.css` (~42 KiB) n'est plus une requête séparée render-blocking, elle est inlinée dans le HTML. Coût : +42 KiB par page prerenderée (≈10 KiB gzippé sur la wire). Gain : 80 ms FCP. `"auto"` aurait été ignoré (Layout.css > 4 KiB du seuil interne). - `<link rel="preload">` sur les 2 woff2 latin (Inter body + Bricolage Grotesque display) dans Layout.astro. Casse la chaîne HTML→CSS→fonts du network dependency tree (~50 ms gagnés). URLs résolues via Vite `?url` import → hashing préservé entre les builds. On ne preload que latin-wght-normal — les autres subsets (latin-ext, vietnamese, cyrillic, greek) sont chargés à la demande et ne valent pas le poids upfront pour un trafic FR/latin. Vérifié build : `<link rel="stylesheet">` disparu du HTML rendu, 1 seul `<style>` inline présent, 2 `<link rel="preload">` avec les bons hashes d'asset. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
eda5436d12 |
fix(seo): smart title suffix + OG image par défaut 1200×630
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 1m1s
Audit SEO révélait deux pertes de valeur :
1. **Titres trop longs** (86 chars sur la home, 71 chars sur les articles).
Google tronque à ~60 chars dans le SERP. Le suffixe automatique
`— Rubis sur l'ongle` (20 chars) écrasait le message-clé.
Layout.astro fait maintenant un suffix smart :
* Si title <45 chars ET ne contient pas "Rubis" → suffix `— Rubis` (8 chars)
* Sinon → titre tel quel (la brand est déjà couverte par og:site_name +
JSON-LD publisher + le hostname rubis.pro visible dans la SERP).
Résultat : home 64 chars, article 51 chars, "Mentions légales" → 24 chars
avec suffix.
2. **Pas d'`og:image` sur les pages sans hero** (home, légal). Sur
LinkedIn/X/Slack, aucune preview image — perte d'engagement énorme.
Ajout d'un og-default.png 1200×630 (165 KB optimisé) dans
apps/landing/public/, monté par fallback par Layout.astro quand la
page ne fournit pas d'`ogImage` explicite. Twitter:card devient
toujours `summary_large_image`.
L'image a été générée via le nouvel outil HTML
docs/marketing/assets/og-default.html (clone de la mécanique du
linkedin-banner.html — clic sur "Télécharger PNG" et go).
Compose : brand + tagline + mock card 124 rubis + CTA rubis.pro.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
6dcae6956c |
feat(blog): admin CRUD + image upload + sidebar link
L'éditeur du blog (jusqu'ici limité au seeder) a maintenant une vraie
interface au-dessus de l'API.
Backend (apps/api) :
* Migration users.is_admin (boolean default false).
* Middleware admin (404 si user.is_admin=false après auth).
* Commande ace promote:admin --email=… [--revoke].
* AdminPostsController CRUD complet : list/show/store/update/publish/
unpublish/destroy + suggest-slug. Au save, contentHtml + wordCount +
readingTime sont re-calculés via blog_renderer. Au publish, durcit la
validation SEO (titre ≤60, excerpt 120-160, hero+alt requis, ≥600 mots),
flippe status='published' + publishedAt, ping Google+Bing pour le sitemap.
* BlogUploadsController :
- POST /api/v1/admin/uploads (multipart, JPEG/PNG/WebP, max 4MB)
→ MinIO clé uploads/blog/{uuid}.{ext}
→ renvoie URL relative /api/v1/uploads/blog/{filename}
- GET /api/v1/uploads/blog/:filename (public, cache immutable 1 an)
→ stream depuis MinIO, regex anti-traversal sur le nom.
* UserTransformer expose isAdmin (cf. shared/types/user).
* k3s/app/landing.yml : NodePort 30111 explicite (pour Traefik repo proxmox).
Frontend (apps/web) :
* Lib typée admin-blog (calls API, queryKeys, helpers URL).
* Route /admin/blog : liste filtrable avec status badge, ouverture
publique, dépublier, supprimer, "+ Nouveau brouillon".
* Route /admin/blog/:id : éditeur 2-colonnes
- Gauche : @uiw/react-md-editor (lazy import) avec preview live.
- Droite : hero image (drag&drop + alt), excerpt avec compteur
120-160, tags, aperçu Google snippet, validations bloquantes.
- Autosave debounce 2s + bouton Publier qui sauve d'abord.
- Hero image upload via MinIO (HeroImageUpload component).
* Sidebar : lien "Blog (admin)" si user.isAdmin.
* Gate côté client (beforeLoad redirect si non admin) + côté serveur
(middleware admin) — defense in depth.
Note : les requirements de publish miroir backend ↔ frontend (cf.
PUBLISH_REQUIREMENTS dans validators/post.ts et VALIDATION_RULES dans
admin.blog_.\$id.tsx). À synchroniser si un seuil bouge.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e5530930b3 |
feat: refactor frontend en stack React unifiée (Astro + packages/ui)
Trois surfaces partagent désormais le même design system, Tailwind v4
et React 19 — au lieu d'avoir landing en HTML vanilla, app en React, et
blog en Adonis SSR :
* packages/ui — design system partagé (tokens Tailwind v4 + composants
TSX) extrait depuis apps/web : Brand, Gem, Button, Card, Chip, Eyebrow,
EmptyState. apps/web migre 41 imports vers @rubis/ui.
* apps/landing — nouvelle app Astro 6 SSR (rubis.pro), remplace l'ancienne
landing nginx vanilla. Embarque :
- Landing complète portée en sections React (Hero, Stats, Promise,
HowItWorks, Gamification, Legal, Pricing, FAQ, FinalCTA, Footnotes)
- Pages légales (mentions, confidentialité, CGV) via LegalLayout.astro
- Blog SSR (/blog, /blog/:slug) qui consomme /api/v1/posts
- sitemap.xml, blog/rss.xml, robots.txt en endpoints Astro
- SEO complet (canonical, hreflang, OG, Twitter Card, JSON-LD
Article/BreadcrumbList/Blog/SoftwareApplication)
* apps/api — BlogController réduit à 2 endpoints JSON (GET /api/v1/posts
+ GET /api/v1/posts/:slug). Suppression des templates SSR Adonis
(apps/api/app/blog/), de l'alias #blog/*, des deps react-dom et
@types/react-dom. PostTransformer + PostSummaryTransformer ajoutés.
Le service blog_renderer + le seeder + les 3 articles fondateurs
restent intacts (réutilisés par futurs admin + cron IA).
* Infra :
- Dockerfile.landing (multi-stage Node 22 + tini, Astro standalone)
- k3s/app/landing.yml (Deployment + Service rubis-landing:4321 +
ConfigMap avec API_URL=http://rubis-api.rubis.svc.cluster.local:3333)
- .gitea/workflows/deploy.yml mis à jour pour build rubis-landing
- .gitea/workflows/deploy-web.yml + Dockerfile.web : prennent en
compte packages/ui/ comme dépendance
- Suppression du Dockerfile nginx legacy + k3s/{deployment,service}.yml
- Suppression de landing/ (assets favicons migrés vers
apps/landing/public/)
* Docs : architecture.md (vue d'ensemble + §4bis apps/landing complet,
§3 endpoints JSON blog, layout monorepo), CLAUDE.md (stack technique,
documents associés, déploiement).
Note infra : l'ancien Deployment "rubis" (nginx) et son Service ne sont
PAS supprimés par la CI — à nettoyer manuellement après validation que
Traefik a été repointé sur rubis-landing:4321 dans le repo proxmox.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|