fix(parametres): teaser banking l'emporte sur upsell + section démo admin-only
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:
parent
e449b708f3
commit
b81bc2609b
@ -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).
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user