fix(parametres): teaser banking l'emporte sur upsell + section démo admin-only
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 42s
Build & Deploy Landing / build-and-deploy (push) Successful in 1m26s
Build & Deploy API / build-and-deploy (push) Successful in 2m10s

Deux bugs visibles dans /parametres :

1. **Banque** — un user Free voyait la carte "Plan Pro ou Business
   requis" alors que la feature est gated derrière BANKING_ENABLED=false
   en prod (Powens KYC en cours). L'upgrade n'aurait rien débloqué.
   Fix : la branche `comingSoon` court-circuite l'upsell, et le titre +
   description de la SettingsSection bascule en mode teaser ("Bientôt :
   votre banque connectée à Rubis") pour rester cohérent avec la carte
   "Bientôt disponible" en dessous.

2. **Démonstration** — la section apparaissait pour tous les users,
   alors que c'est un outil de prospection commerciale réservé aux
   admins Rubis (horloge virtuelle + capture des emails). Déroutant
   pour un user lambda.
   Fix : section gated sur `user.isAdmin` côté UI, et split des routes
   /demo côté API :
   - GET /demo/state reste accessible à tous les users authed (sinon le
     DemoClock dans AppLayout spam des 403 sur chaque page). Un user
     normal reçoit `{active: false}` — pas de leak.
   - GET /demo/inbox + POST /demo/start, /end, /tick : auth + admin.
     Mutations et lecture des emails capturés.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ordinarthur 2026-05-14 13:57:37 +02:00
parent e449b708f3
commit b81bc2609b
3 changed files with 65 additions and 26 deletions

View File

@ -365,20 +365,36 @@ router
.use(middleware.auth())
/**
* Demo auth requise. Mode démo opt-in par org (cf. CLAUDE.md
* Architecture). Routes opérantes seulement si `org.demo_mode = true`.
* Demo réservé aux admins Rubis (outil de prospection commerciale).
* Gating fin :
* - GET /state : auth seul, parce que `DemoClock` dans `AppLayout`
* fetch sur chaque page pour tous les users. Un non-admin reçoit
* `{ active: false }` (son org n'est jamais en mode démo) pas
* de leak, juste un payload neutre.
* - GET /inbox + POST /start, /end, /tick : auth + admin. Mutations
* et lecture des emails capturés (potentiellement sensibles).
*
* UI : la section "Démonstration" dans /parametres est aussi gated
* sur `user.isAdmin` (cf. parametres.tsx).
*/
router
.group(() => {
router.get('state', [controllers.Demo, 'state']).as('state')
})
.prefix('demo')
.as('demo')
.use(middleware.auth())
router
.group(() => {
router.post('start', [controllers.Demo, 'start']).as('start')
router.post('end', [controllers.Demo, 'end']).as('end')
router.post('tick', [controllers.Demo, 'tick']).as('tick')
router.get('state', [controllers.Demo, 'state']).as('state')
router.get('inbox', [controllers.Demo, 'inbox']).as('inbox')
})
.prefix('demo')
.as('demo')
.use(middleware.auth())
.as('demo.admin')
.use([middleware.auth(), middleware.admin()])
/**
* Dashboard auth requise. Calculs agrégés on-the-fly (pas de cache V1).

View File

@ -81,16 +81,20 @@ export function BankingSection({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [callbackStatus, callbackReason]);
if (!isPaid) {
return <UpsellCard />;
}
// Banking pas encore activé mais teaser ON → afficher "Bientôt disponible"
// pour annoncer la feature aux Pro/Business pendant la fenêtre KYC Powens.
// Order matters : `comingSoon` doit gagner sur `isPaid`. La feature n'est
// pas dispo pour personne pendant la fenêtre KYC Powens, donc inciter un
// Free à passer Pro pour "connecter sa banque" serait trompeur (il
// tomberait sur "Bientôt disponible" après l'upgrade). On annonce la
// feature à venir à tout le monde ; l'upsell reviendra automatiquement
// une fois `BANKING_ENABLED=true` en prod.
if (status?.comingSoon) {
return <ComingSoonCard />;
}
if (!isPaid) {
return <UpsellCard />;
}
return (
<BankingPaidView
isLoading={connectionsQuery.isLoading}

View File

@ -13,6 +13,7 @@ import { Button } from "@rubis/ui";
import { Card } from "@rubis/ui";
import { useSubscription } from "@/lib/billing";
import { useBankingStatus } from "@/lib/banking";
import { useAuth } from "@/lib/auth";
/**
* Search params optionnels :
@ -52,6 +53,12 @@ function ParametresPage() {
const { data: bankingStatus } = useBankingStatus();
const showBanking =
bankingStatus?.enabled === true || bankingStatus?.comingSoon === true;
// Mode démo réservé aux admins Rubis (outil de prospection en démo
// commerciale). Un user lambda n'a aucune raison d'y avoir accès — c'est
// une horloge virtuelle + capture des emails, donc complètement déroutant
// si l'user croit que c'est une feature produit.
const { user } = useAuth();
const isAdmin = !!user?.isAdmin;
return (
<div className="flex flex-col gap-2">
@ -188,11 +195,21 @@ function ParametresPage() {
<SettingsSection
eyebrow="Banque"
title={
bankingStatus?.comingSoon ? (
<>
Bientôt : votre <em className="text-rubis">banque</em> connectée à Rubis
</>
) : (
<>
Connecter votre <em className="text-rubis">banque</em>
</>
)
}
description={
bankingStatus?.comingSoon
? "Nous finalisons notre agrément AISP avec Powens. Une fois ouvert, Rubis lira vos virements entrants pour détecter automatiquement les factures payées — en lecture seule, sans déplacement de fonds."
: "Rubis lit vos virements entrants pour détecter automatiquement les factures payées. Lecture seule, aucun déplacement de fonds. Disponible sur les plans Pro et Business."
}
description="Rubis lit vos virements entrants pour détecter automatiquement les factures payées. Lecture seule, aucun déplacement de fonds. Disponible sur les plans Pro et Business."
>
<BankingSection
callbackStatus={search.banking}
@ -201,6 +218,7 @@ function ParametresPage() {
</SettingsSection>
)}
{isAdmin && (
<SettingsSection
eyebrow="Démonstration"
title={
@ -212,6 +230,7 @@ function ParametresPage() {
>
<DemoToggle />
</SettingsSection>
)}
<SettingsSection
eyebrow="Zone danger"