feat: activer « Obtenir Base » (écrans verrouillés, carte Mises à jour, page d'erreur) + note Premium + texte de la carte Compte #346

Closed
opened 2026-09-14 00:19:30 +00:00 by maximus · 0 comments
Owner

En édition gratuite, les écrans verrouillés montrent « Obtenir {tier} » désactivé, avec la note « Achat en ligne bientôt disponible ». C'était la décision de l'ADR 0017 en attendant #270, mergée le 2026-09-27 (PR #359).

Avec le helper purchaseUrl livré par #270 :

  • le bouton Base devient actif partout ;
  • Premium reçoit une note honnête ;
  • la carte Mises à jour et la page d'erreur gagnent un chemin d'achat et un lien vers la dernière version ;
  • la carte Compte cesse de promettre des fonctionnalités inexistantes. Un testeur s'est connecté pour rien le 2026-09-12 et a reçu un code Logto par courriel.

Analyse du 2026-09-27 (/analyze) : prémisse revérifiée sur main de274ec, deux oublis corrigés (le bouton Annuler de ProfileFormModal, le onNavigate du modal), deux décisions ajoutées (A1, A2).

Contexte vérifié (2026-09-27, main de274ec)

  • src/shared/purchaseUrl.ts (#270) : purchaseUrl(language) → https://lacompagniemaximus.com/{fr|en}/produits/simpl-resultat. Le chemin est un contrat avec le site.
  • Volet site clos (maximus/la-compagnie-maximus#153) : la page produit sert les téléchargements directs de la dernière release (…_x64-setup.exe, …_amd64.deb, v0.15.0 au 2026-09-27, vérifié en prod en FR et en EN), et /simpl-resultat y redirige.
  • src/components/shared/UpsellGate.tsx :
    • l.33 : useNavigate est appelé sans condition ;
    • l.50-63 : bouton « Obtenir {tier} » disabled en dur, avec la note upsell.ctaGetSoon (l.61) ;
    • l.65-75 : « J'ai déjà une clé » appelle navigate("/settings/users") puis onNavigate?.().
    • Appelants : RequireFeature.tsx:39 (routes Budget, Ajustements, rapports avancés, Bilan) et ProfileSwitcher.tsx:154-158 (modal multi-profils, onNavigate={() => setShowUpsell(false)}).
  • src/components/profile/ProfileFormModal.tsx, l.178-213 : panneau upsell compact (mode upsell). Il contient un bouton « Obtenir » désactivé, la note upsell.ctaGetSoon (l.205) et un bouton Annuler qui revient à la liste des profils (setMode("list"), l.207-212). Le titre du modal reste profile.create en mode upsell. Le modal a deux hôtes :
    • ProfileSelectionPage.tsx:108, rendue hors BrowserRouter (App.tsx:97, alors que le routeur est monté en l.109) ;
    • ProfileSwitcher.tsx:139, dans le routeur (onClose={() => setShowManage(false)}).
  • Matrice (src/shared/entitlements.ts:22-26) : Base et plus = budget, adjustments, reports-advanced, multi-profile ; Premium seul = balance (Bilan). requiredTierFor(feature) donne le tier.
  • src/components/settings/UpdateCard.tsx:75-80, monté dans SystemsSettingsPage donc dans le routeur : l'état notEntitled n'affiche que le texte settings.updates.notEntitled.
  • src/components/shared/ErrorPage.tsx:100-104 : même état, avec la clé error.updateNotEntitled. ErrorPage est rendue hors du routeur (App.tsx:93, ErrorBoundary.tsx:29). L'état n'apparaît qu'après un clic sur « Vérifier les mises à jour » (l.18-40).
  • En Gratuite, carte comme page d'erreur s'arrêtent avant check() (useUpdater.ts:79-86, ErrorPage.tsx:22-28) : l'utilisateur n'apprend jamais qu'une version corrige une faille.
  • src/components/settings/AccountCard.tsx:58 affiche account.description, faux depuis v0.15.0. Le compte sert réellement à deux choses : joindre le courriel à la rétroaction (FeedbackDialog.tsx:26-35), et la page /compte du site, qui liste les licences.
  • La carte Licence (LicenseCard.tsx) a son propre bouton « Acheter Simpl'Résultat », qui passe par purchaseUrl depuis #270 : il ne change pas (décision A2).
  • src/services/entitlements.test.ts : l'interface UpsellMessages type upsell.ctaGetSoon (l.116), attendue en l.145.
  • Documentation à amender :
    • ADR 0017, l.66, l.111 et l.129 ; convention d'amendement : blockquote de tête > **Amendement du AAAA-MM-JJ (#N)** — … (ADR 0018, l.8) ;
    • docs/architecture.md:299 (ligne UpsellGate) et l.301 (ligne « Gate multi-profils », qui cite le « mode upsell compact ») ;
    • docs/guide-utilisateur.md §12 (l.519), puce l.545 ;
    • docs.editions.steps[3] FR/EN (« l'achat en ligne arrive bientôt… »).
  • useInRouterContext est exporté par react-router (installé, 7.x).
  • Pas de jsdom ni de Testing Library : seules des fonctions pures se testent en vitest.

Tâches

  1. Nouvelle fonction pure src/shared/upsellCta.ts : upsellCtaFor(tier: "base" | "premium") → { purchasable: boolean; noteKey: string | null }. Base donne { purchasable: true, noteKey: null }, Premium donne { purchasable: false, noteKey: "upsell.premiumNotForSale" }. Test dans src/shared/upsellCta.test.ts (décision A1).
  2. Nouveau composant src/components/shared/PurchaseButton.tsx, sans routeur : un bouton « Obtenir Base » (upsell.ctaGet, avec tier = license.editions.base) qui appelle openUrl(purchaseUrl(i18n.language)). Il sert à UpsellGate, à la carte Mises à jour et à la page d'erreur.
  3. UpsellGate.tsx :
    • la règle vient de upsellCtaFor(requiredTier) : si le palier est achetable, PurchaseButton actif, sans note ; sinon, bouton désactivé avec la note noteKey ;
    • le lien externe n'appelle pas onNavigate, pour que le modal hôte reste ouvert ;
    • « J'ai déjà une clé » passe dans un sous-composant qui appelle useNavigate, rendu seulement si useInRouterContext() est vrai.
  4. ProfileFormModal.tsx, mode upsell :
    • remplacer le panneau compact par UpsellGate (feature="multi-profile", requiredTier={requiredTierFor("multi-profile")}, onNavigate={onClose}) ;
    • garder sous UpsellGate le bouton Annuler qui revient à la liste des profils ;
    • ouvert depuis ProfileSwitcher (dans le routeur), « J'ai déjà une clé » apparaît et ferme le modal après la navigation ; ouvert depuis ProfileSelectionPage (hors routeur), il n'apparaît pas, comme aujourd'hui.
  5. UpdateCard.tsx, état notEntitled : le texte settings.updates.notEntitled, puis PurchaseButton, puis « J'ai déjà une clé » (navigate("/settings/users"), clé upsell.ctaHaveKey), puis un lien « Télécharger la dernière version » vers purchaseUrl(i18n.language) (clé settings.updates.downloadLatest).
  6. ErrorPage.tsx, état notEntitled : PurchaseButton et le lien « Télécharger la dernière version » (même clé), sans « J'ai déjà une clé » puisque la page est hors routeur.
  7. i18n FR/EN :
    • account.description :
      • FR « Le compte sert à retrouver vos licences sur lacompagniemaximus.com et à vous identifier dans vos rétroactions. Les éditions Base et Premium viennent de la clé de licence, pas du compte. »
      • EN « Your account lets you find your licenses on lacompagniemaximus.com and identifies you in the feedback you send. Base and Premium editions come from your license key, not from the account. »
    • upsell.premiumNotForSale : FR « Premium n'est pas encore offert en ligne. » / EN « Premium isn't sold online yet. »
    • settings.updates.notEntitled : FR « Les mises à jour automatiques sont incluses dans l'édition Base. » / EN « Automatic updates are included in the Base edition. »
    • settings.updates.downloadLatest : FR « Télécharger la dernière version » / EN « Download the latest version ».
    • error.updateNotEntitled : inchangé.
    • Retirer upsell.ctaGetSoon des deux langues, ainsi que de l'interface UpsellMessages et des attentes de entitlements.test.ts.
    • docs.editions.steps[3] FR/EN : retirer la mention « l'achat en ligne arrive bientôt ».
    • docs.editions.tips FR/EN : un conseil de plus, qui reprend celui du guide : en édition gratuite, la carte Mises à jour propose de télécharger la dernière version depuis la page produit.
  8. Tests : entitlements.test.ts couvre en FR et en EN upsell.premiumNotForSale, settings.updates.downloadLatest et account.description à la place de ctaGetSoon.
  9. Documentation :
    • ADR 0017 : blockquote > **Amendement du <date du commit> (#346)** — … en tête. Il indique que le CTA Base est actif via purchaseUrl, que Premium reste désactivé avec la note « pas encore offert en ligne » tant qu'aucune offre Premium n'est vendable, et que la Gratuite a un lien « Télécharger la dernière version ». Ajouter le renvoi « (voir l'amendement en tête) » en l.66 et en l.111 ; en l.129, remplacer « #270 (câblage du CTA d'achat, à venir) » par « #270 (URL d'achat localisée) ; #346 (CTA d'achat actif, voir l'amendement en tête) ».
    • docs/architecture.md : mettre à jour la ligne UpsellGate (l.299), ajouter une ligne PurchaseButton et une ligne upsellCtaFor, et remplacer « mode upsell compact » par « mode upsell, qui réutilise UpsellGate » dans la ligne « Gate multi-profils » (l.301).
    • docs/guide-utilisateur.md §12 : puce l.545, et mention du lien de téléchargement de la carte Mises à jour.
  10. CHANGELOG.md et CHANGELOG.fr.md, sous [Unreleased] / [Non publié], dans l'ordre Added / Changed avant les sections Fixed et Security existantes : Added / Ajouté pour l'achat Base depuis les écrans verrouillés et le lien de téléchargement en Gratuite ; Changed / Modifié pour le texte de la carte Compte et la note Premium.

Fichiers concernés

  • src/shared/upsellCta.ts (nouveau)
  • src/shared/upsellCta.test.ts (nouveau)
  • src/components/shared/PurchaseButton.tsx (nouveau)
  • src/components/shared/UpsellGate.tsx
  • src/components/profile/ProfileFormModal.tsx
  • src/components/settings/UpdateCard.tsx
  • src/components/shared/ErrorPage.tsx
  • src/i18n/locales/fr.json
  • src/i18n/locales/en.json
  • src/services/entitlements.test.ts
  • docs/adr/0017-feature-gating-par-tier.md
  • docs/architecture.md
  • docs/guide-utilisateur.md
  • CHANGELOG.md
  • CHANGELOG.fr.md

Depends on

Critères d'acceptation

  • upsellCtaFor est testée : Base achetable sans note, Premium non achetable avec upsell.premiumNotForSale.
  • En édition gratuite, chaque écran verrouillé Base a un « Obtenir Base » actif qui ouvre la page produit dans la langue de l'app : Budget, Ajustements, rapports avancés, et multi-profils via ProfileSwitcher comme via ProfileFormModal.
  • Le Bilan (Premium) garde un bouton désactivé, avec « Premium n'est pas encore offert en ligne. » / « Premium isn't sold online yet. ».
  • En Gratuite, la carte Mises à jour offre « Obtenir Base », « J'ai déjà une clé » et « Télécharger la dernière version ». La page d'erreur offre « Obtenir Base » et « Télécharger la dernière version ».
  • ProfileFormModal n'a plus de panneau upsell propre, garde son bouton Annuler, et se ferme après « J'ai déjà une clé ». UpsellGate n'appelle useNavigate que dans un sous-composant rendu sous useInRouterContext().
  • La carte Compte Maximus affiche le texte exact ci-dessus, en FR et en EN.
  • upsell.ctaGetSoon n'existe plus dans aucune langue ni dans entitlements.test.ts. npm test et npm run build verts ; CHANGELOG FR/EN.

Review caveats

  • 🟡 Revue du 2026-09-24. Sans PurchaseButton partagé (tâche 2), le CTA serait recopié sur trois surfaces. Le panneau compact de ProfileFormModal n'existait qu'à cause du useNavigate inconditionnel d'UpsellGate (tâches 3 et 4).
  • 🟡 En Gratuite, l'app ne vérifie jamais les mises à jour. Le lien « Télécharger la dernière version » est le seul chemin vers une version corrective : l'installation manuelle reste gratuite, et la page produit sert désormais les téléchargements directs (maximus/la-compagnie-maximus#153). Ne pas appeler check() en Gratuite : aucun appel réseau de plus, décision de Max.
  • 🟢 Le chemin /{fr|en}/produits/simpl-resultat est un contrat avec le site (voir le commentaire de src/shared/purchaseUrl.ts).
  • 🟢 Analyse du 2026-09-27 : l'onboarding (#355, #357) touche aussi le CHANGELOG, le guide, architecture.md et les fichiers i18n. Ces conflits textuels se règlent au merge ; #355 ne touche pas docs.editions.

Décisions prises en planification

  • D3 — Premium : bouton désactivé avec « Premium n'est pas encore offert en ligne. ». La clé upsell.ctaGetSoon est retirée.
  • D4 — Texte exact de la carte Compte, donné plus haut.
  • D11 — État notEntitled : lien « Télécharger la dernière version » vers la page produit, sans appel réseau (pas de check() en Gratuite).
  • Le lien d'achat externe n'appelle pas onNavigate.
  • ErrorPage n'a pas de « J'ai déjà une clé », puisqu'elle est hors routeur.
  • A1 (analyse du 2026-09-27) — La règle palier → bouton vit dans une fonction pure testée, upsellCtaFor, comme les sélecteurs de #270.
  • A2 (analyse du 2026-09-27) — La carte Licence garde son bouton « Acheter Simpl'Résultat » (libellé et style propres, URL déjà commune via purchaseUrl).

Hors périmètre

  • Vente de Premium : aucune offre, le site ne vend que Base.
  • Texte « gratuit/bêta » de la page produit du site, « Gratuit » écrit en dur sur /compte.
  • Bouton d'achat de la carte Licence (A2).

Spec source

spec-plan-paiements.md (séance /plan-run du 2026-09-24), complété par l'analyse du 2026-09-27. C'est un fichier de travail ignoré par git : ce corps se suffit à lui-même.

En édition gratuite, les écrans verrouillés montrent « Obtenir {tier} » désactivé, avec la note « Achat en ligne bientôt disponible ». C'était la décision de l'ADR 0017 en attendant #270, mergée le 2026-09-27 (PR #359). Avec le helper `purchaseUrl` livré par #270 : - le bouton Base devient actif partout ; - Premium reçoit une note honnête ; - la carte Mises à jour et la page d'erreur gagnent un chemin d'achat et un lien vers la dernière version ; - la carte Compte cesse de promettre des fonctionnalités inexistantes. Un testeur s'est connecté pour rien le 2026-09-12 et a reçu un code Logto par courriel. Analyse du 2026-09-27 (`/analyze`) : prémisse revérifiée sur `main` `de274ec`, deux oublis corrigés (le bouton Annuler de `ProfileFormModal`, le `onNavigate` du modal), deux décisions ajoutées (A1, A2). ## Contexte vérifié (2026-09-27, `main` `de274ec`) - `src/shared/purchaseUrl.ts` (#270) : `purchaseUrl(language)` → `https://lacompagniemaximus.com/{fr|en}/produits/simpl-resultat`. Le chemin est un contrat avec le site. - Volet site clos (maximus/la-compagnie-maximus#153) : la page produit sert les téléchargements directs de la dernière release (`…_x64-setup.exe`, `…_amd64.deb`, v0.15.0 au 2026-09-27, vérifié en prod en FR et en EN), et `/simpl-resultat` y redirige. - `src/components/shared/UpsellGate.tsx` : - l.33 : `useNavigate` est appelé sans condition ; - l.50-63 : bouton « Obtenir {tier} » `disabled` en dur, avec la note `upsell.ctaGetSoon` (l.61) ; - l.65-75 : « J'ai déjà une clé » appelle `navigate("/settings/users")` puis `onNavigate?.()`. - Appelants : `RequireFeature.tsx:39` (routes Budget, Ajustements, rapports avancés, Bilan) et `ProfileSwitcher.tsx:154-158` (modal multi-profils, `onNavigate={() => setShowUpsell(false)}`). - `src/components/profile/ProfileFormModal.tsx`, l.178-213 : panneau upsell compact (mode `upsell`). Il contient un bouton « Obtenir » désactivé, la note `upsell.ctaGetSoon` (l.205) et un bouton **Annuler** qui revient à la liste des profils (`setMode("list")`, l.207-212). Le titre du modal reste `profile.create` en mode `upsell`. Le modal a deux hôtes : - `ProfileSelectionPage.tsx:108`, rendue **hors `BrowserRouter`** (`App.tsx:97`, alors que le routeur est monté en l.109) ; - `ProfileSwitcher.tsx:139`, **dans** le routeur (`onClose={() => setShowManage(false)}`). - Matrice (`src/shared/entitlements.ts:22-26`) : Base et plus = `budget`, `adjustments`, `reports-advanced`, `multi-profile` ; Premium seul = `balance` (Bilan). `requiredTierFor(feature)` donne le tier. - `src/components/settings/UpdateCard.tsx:75-80`, monté dans `SystemsSettingsPage` donc dans le routeur : l'état `notEntitled` n'affiche que le texte `settings.updates.notEntitled`. - `src/components/shared/ErrorPage.tsx:100-104` : même état, avec la clé `error.updateNotEntitled`. `ErrorPage` est rendue **hors du routeur** (`App.tsx:93`, `ErrorBoundary.tsx:29`). L'état n'apparaît qu'après un clic sur « Vérifier les mises à jour » (l.18-40). - En Gratuite, carte comme page d'erreur s'arrêtent avant `check()` (`useUpdater.ts:79-86`, `ErrorPage.tsx:22-28`) : l'utilisateur n'apprend jamais qu'une version corrige une faille. - `src/components/settings/AccountCard.tsx:58` affiche `account.description`, faux depuis v0.15.0. Le compte sert réellement à deux choses : joindre le courriel à la rétroaction (`FeedbackDialog.tsx:26-35`), et la page `/compte` du site, qui liste les licences. - La carte Licence (`LicenseCard.tsx`) a son propre bouton « Acheter Simpl'Résultat », qui passe par `purchaseUrl` depuis #270 : il ne change pas (décision A2). - `src/services/entitlements.test.ts` : l'interface `UpsellMessages` type `upsell.ctaGetSoon` (l.116), attendue en l.145. - Documentation à amender : - ADR 0017, l.66, l.111 et l.129 ; convention d'amendement : blockquote de tête `> **Amendement du AAAA-MM-JJ (#N)** — …` (ADR 0018, l.8) ; - `docs/architecture.md:299` (ligne `UpsellGate`) et l.301 (ligne « Gate multi-profils », qui cite le « mode upsell compact ») ; - `docs/guide-utilisateur.md` §12 (l.519), puce l.545 ; - `docs.editions.steps[3]` FR/EN (« l'achat en ligne arrive bientôt… »). - `useInRouterContext` est exporté par `react-router` (installé, 7.x). - Pas de jsdom ni de Testing Library : seules des fonctions pures se testent en vitest. ## Tâches 1. Nouvelle fonction pure `src/shared/upsellCta.ts` : `upsellCtaFor(tier: "base" | "premium")` → `{ purchasable: boolean; noteKey: string | null }`. Base donne `{ purchasable: true, noteKey: null }`, Premium donne `{ purchasable: false, noteKey: "upsell.premiumNotForSale" }`. Test dans `src/shared/upsellCta.test.ts` (décision A1). 2. Nouveau composant `src/components/shared/PurchaseButton.tsx`, sans routeur : un bouton « Obtenir Base » (`upsell.ctaGet`, avec `tier` = `license.editions.base`) qui appelle `openUrl(purchaseUrl(i18n.language))`. Il sert à `UpsellGate`, à la carte Mises à jour et à la page d'erreur. 3. `UpsellGate.tsx` : - la règle vient de `upsellCtaFor(requiredTier)` : si le palier est achetable, `PurchaseButton` actif, sans note ; sinon, bouton désactivé avec la note `noteKey` ; - le lien externe n'appelle pas `onNavigate`, pour que le modal hôte reste ouvert ; - « J'ai déjà une clé » passe dans un sous-composant qui appelle `useNavigate`, rendu seulement si `useInRouterContext()` est vrai. 4. `ProfileFormModal.tsx`, mode `upsell` : - remplacer le panneau compact par `UpsellGate` (`feature="multi-profile"`, `requiredTier={requiredTierFor("multi-profile")}`, `onNavigate={onClose}`) ; - **garder** sous `UpsellGate` le bouton Annuler qui revient à la liste des profils ; - ouvert depuis `ProfileSwitcher` (dans le routeur), « J'ai déjà une clé » apparaît et ferme le modal après la navigation ; ouvert depuis `ProfileSelectionPage` (hors routeur), il n'apparaît pas, comme aujourd'hui. 5. `UpdateCard.tsx`, état `notEntitled` : le texte `settings.updates.notEntitled`, puis `PurchaseButton`, puis « J'ai déjà une clé » (`navigate("/settings/users")`, clé `upsell.ctaHaveKey`), puis un lien « Télécharger la dernière version » vers `purchaseUrl(i18n.language)` (clé `settings.updates.downloadLatest`). 6. `ErrorPage.tsx`, état `notEntitled` : `PurchaseButton` et le lien « Télécharger la dernière version » (même clé), sans « J'ai déjà une clé » puisque la page est hors routeur. 7. i18n FR/EN : - `account.description` : - FR « Le compte sert à retrouver vos licences sur lacompagniemaximus.com et à vous identifier dans vos rétroactions. Les éditions Base et Premium viennent de la clé de licence, pas du compte. » - EN « Your account lets you find your licenses on lacompagniemaximus.com and identifies you in the feedback you send. Base and Premium editions come from your license key, not from the account. » - `upsell.premiumNotForSale` : FR « Premium n'est pas encore offert en ligne. » / EN « Premium isn't sold online yet. » - `settings.updates.notEntitled` : FR « Les mises à jour automatiques sont incluses dans l'édition Base. » / EN « Automatic updates are included in the Base edition. » - `settings.updates.downloadLatest` : FR « Télécharger la dernière version » / EN « Download the latest version ». - `error.updateNotEntitled` : inchangé. - Retirer `upsell.ctaGetSoon` des deux langues, ainsi que de l'interface `UpsellMessages` et des attentes de `entitlements.test.ts`. - `docs.editions.steps[3]` FR/EN : retirer la mention « l'achat en ligne arrive bientôt ». - `docs.editions.tips` FR/EN : un conseil de plus, qui reprend celui du guide : en édition gratuite, la carte Mises à jour propose de télécharger la dernière version depuis la page produit. 8. Tests : `entitlements.test.ts` couvre en FR et en EN `upsell.premiumNotForSale`, `settings.updates.downloadLatest` et `account.description` à la place de `ctaGetSoon`. 9. Documentation : - ADR 0017 : blockquote `> **Amendement du <date du commit> (#346)** — …` en tête. Il indique que le CTA Base est actif via `purchaseUrl`, que Premium reste désactivé avec la note « pas encore offert en ligne » tant qu'aucune offre Premium n'est vendable, et que la Gratuite a un lien « Télécharger la dernière version ». Ajouter le renvoi « (voir l'amendement en tête) » en l.66 et en l.111 ; en l.129, remplacer « #270 (câblage du CTA d'achat, à venir) » par « #270 (URL d'achat localisée) ; #346 (CTA d'achat actif, voir l'amendement en tête) ». - `docs/architecture.md` : mettre à jour la ligne `UpsellGate` (l.299), ajouter une ligne `PurchaseButton` et une ligne `upsellCtaFor`, et remplacer « mode upsell compact » par « mode upsell, qui réutilise `UpsellGate` » dans la ligne « Gate multi-profils » (l.301). - `docs/guide-utilisateur.md` §12 : puce l.545, et mention du lien de téléchargement de la carte Mises à jour. 10. `CHANGELOG.md` et `CHANGELOG.fr.md`, sous `[Unreleased]` / `[Non publié]`, dans l'ordre Added / Changed avant les sections Fixed et Security existantes : Added / Ajouté pour l'achat Base depuis les écrans verrouillés et le lien de téléchargement en Gratuite ; Changed / Modifié pour le texte de la carte Compte et la note Premium. ## Fichiers concernés - `src/shared/upsellCta.ts` (nouveau) - `src/shared/upsellCta.test.ts` (nouveau) - `src/components/shared/PurchaseButton.tsx` (nouveau) - `src/components/shared/UpsellGate.tsx` - `src/components/profile/ProfileFormModal.tsx` - `src/components/settings/UpdateCard.tsx` - `src/components/shared/ErrorPage.tsx` - `src/i18n/locales/fr.json` - `src/i18n/locales/en.json` - `src/services/entitlements.test.ts` - `docs/adr/0017-feature-gating-par-tier.md` - `docs/architecture.md` - `docs/guide-utilisateur.md` - `CHANGELOG.md` - `CHANGELOG.fr.md` ## Depends on - #270 ## Critères d'acceptation - [ ] `upsellCtaFor` est testée : Base achetable sans note, Premium non achetable avec `upsell.premiumNotForSale`. - [ ] En édition gratuite, chaque écran verrouillé Base a un « Obtenir Base » actif qui ouvre la page produit dans la langue de l'app : Budget, Ajustements, rapports avancés, et multi-profils via `ProfileSwitcher` comme via `ProfileFormModal`. - [ ] Le Bilan (Premium) garde un bouton désactivé, avec « Premium n'est pas encore offert en ligne. » / « Premium isn't sold online yet. ». - [ ] En Gratuite, la carte Mises à jour offre « Obtenir Base », « J'ai déjà une clé » et « Télécharger la dernière version ». La page d'erreur offre « Obtenir Base » et « Télécharger la dernière version ». - [ ] `ProfileFormModal` n'a plus de panneau upsell propre, garde son bouton Annuler, et se ferme après « J'ai déjà une clé ». `UpsellGate` n'appelle `useNavigate` que dans un sous-composant rendu sous `useInRouterContext()`. - [ ] La carte Compte Maximus affiche le texte exact ci-dessus, en FR et en EN. - [ ] `upsell.ctaGetSoon` n'existe plus dans aucune langue ni dans `entitlements.test.ts`. `npm test` et `npm run build` verts ; CHANGELOG FR/EN. ## Review caveats - 🟡 Revue du 2026-09-24. Sans `PurchaseButton` partagé (tâche 2), le CTA serait recopié sur trois surfaces. Le panneau compact de `ProfileFormModal` n'existait qu'à cause du `useNavigate` inconditionnel d'`UpsellGate` (tâches 3 et 4). - 🟡 En Gratuite, l'app ne vérifie jamais les mises à jour. Le lien « Télécharger la dernière version » est le seul chemin vers une version corrective : l'installation manuelle reste gratuite, et la page produit sert désormais les téléchargements directs (maximus/la-compagnie-maximus#153). Ne pas appeler `check()` en Gratuite : aucun appel réseau de plus, décision de Max. - 🟢 Le chemin `/{fr|en}/produits/simpl-resultat` est un contrat avec le site (voir le commentaire de `src/shared/purchaseUrl.ts`). - 🟢 Analyse du 2026-09-27 : l'onboarding (#355, #357) touche aussi le CHANGELOG, le guide, `architecture.md` et les fichiers i18n. Ces conflits textuels se règlent au merge ; #355 ne touche pas `docs.editions`. ## Décisions prises en planification - D3 — Premium : bouton désactivé avec « Premium n'est pas encore offert en ligne. ». La clé `upsell.ctaGetSoon` est retirée. - D4 — Texte exact de la carte Compte, donné plus haut. - D11 — État `notEntitled` : lien « Télécharger la dernière version » vers la page produit, sans appel réseau (pas de `check()` en Gratuite). - Le lien d'achat externe n'appelle pas `onNavigate`. - `ErrorPage` n'a pas de « J'ai déjà une clé », puisqu'elle est hors routeur. - A1 (analyse du 2026-09-27) — La règle palier → bouton vit dans une fonction pure testée, `upsellCtaFor`, comme les sélecteurs de #270. - A2 (analyse du 2026-09-27) — La carte Licence garde son bouton « Acheter Simpl'Résultat » (libellé et style propres, URL déjà commune via `purchaseUrl`). ## Hors périmètre - Vente de Premium : aucune offre, le site ne vend que Base. - Texte « gratuit/bêta » de la page produit du site, « Gratuit » écrit en dur sur `/compte`. - Bouton d'achat de la carte Licence (A2). ## Spec source `spec-plan-paiements.md` (séance `/plan-run` du 2026-09-24), complété par l'analyse du 2026-09-27. C'est un fichier de travail ignoré par git : ce corps se suffit à lui-même.
maximus added this to the spec-paiements milestone 2026-09-14 00:19:30 +00:00
maximus added the
status:ready
type:feature
source:analyste
labels 2026-09-14 00:19:30 +00:00
maximus changed title from feat: activer le bouton « Obtenir Base » des UpsellGate + lien d'achat carte Mises à jour + texte carte Compte Maximus to feat: activer « Obtenir Base » (écrans verrouillés, carte Mises à jour, page d'erreur) + note Premium + texte de la carte Compte 2026-09-25 00:49:56 +00:00
maximus modified the milestone from spec-paiements to planned-2026-09-24-paiements 2026-09-25 00:49:56 +00:00
maximus added
status:review
and removed
status:ready
labels 2026-09-27 19:51:17 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-09-28 00:31:02 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#346
No description provided.