feat(abonnement): sélection de formule par cartes cliquables dans mes-infos
Remplace la garde ?plan= (redirection si absent) par une sélection interactive : deux cartes de formule (réutilisées de la page publique via _plan_card) cliquables, un callback recopie le choix dans un champ caché natif soumis avec le POST du formulaire. Le bouton de soumission reste désactivé tant qu'aucune formule n'est choisie (en plus des cases à cocher existantes). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1010,3 +1010,14 @@ input[type="number"] {
|
||||
font-size: 1.1rem;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.plan-selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.plan-selectable .card {
|
||||
transition: background-color 0.15s, border-color 0.15s;
|
||||
}
|
||||
.plan-selectable.selected .card {
|
||||
background-color: var(--bs-primary-bg-subtle);
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import dash_bootstrap_components as dbc
|
||||
from dash import Input, Output, State, callback, dcc, html, register_page
|
||||
from dash import Input, Output, State, callback, ctx, dcc, html, register_page
|
||||
from flask_login import current_user
|
||||
|
||||
from src.auth import db as auth_db
|
||||
from src.pages._compte_shell import account_guard, account_shell
|
||||
from src.pages.a_propos.abonnement import subscription_terms
|
||||
from src.pages.a_propos.abonnement import _plan_card, subscription_terms
|
||||
from src.subscriptions import client as frisbii_client
|
||||
from src.subscriptions import db as sub_db
|
||||
from src.subscriptions import plans
|
||||
from src.utils.data import get_annuaire_data
|
||||
|
||||
register_page(
|
||||
@@ -25,6 +27,55 @@ def _csrf_input():
|
||||
return dcc.Input(type="hidden", name="csrf_token", value=generate_csrf())
|
||||
|
||||
|
||||
def _trial_for(user_id):
|
||||
used = sub_db.has_used_trial(user_id)
|
||||
return lambda key: None if used else plans.trial_days(key)
|
||||
|
||||
|
||||
def _selectable_cards(trial_for):
|
||||
cols = []
|
||||
for key in ("simple", "soutien"):
|
||||
meta = plans.plan_meta(key)
|
||||
if not meta:
|
||||
continue
|
||||
cols.append(
|
||||
dbc.Col(
|
||||
html.Div(
|
||||
_plan_card(meta, trial_for(key)),
|
||||
id=f"plan-card-{key}",
|
||||
n_clicks=0,
|
||||
className="plan-selectable",
|
||||
),
|
||||
md=6,
|
||||
)
|
||||
)
|
||||
return dbc.Row(cols, className="g-4 mb-2")
|
||||
|
||||
|
||||
def _selection_state(selected):
|
||||
base = "plan-selectable"
|
||||
return (
|
||||
selected,
|
||||
f"{base} selected" if selected == "simple" else base,
|
||||
f"{base} selected" if selected == "soutien" else base,
|
||||
"d-none",
|
||||
)
|
||||
|
||||
|
||||
@callback(
|
||||
Output("inf-plan-hidden", "value"),
|
||||
Output("plan-card-simple", "className"),
|
||||
Output("plan-card-soutien", "className"),
|
||||
Output("inf-plan-invite", "className"),
|
||||
Input("plan-card-simple", "n_clicks"),
|
||||
Input("plan-card-soutien", "n_clicks"),
|
||||
prevent_initial_call=True,
|
||||
)
|
||||
def _select_plan(_n_simple, _n_soutien):
|
||||
selected = "simple" if ctx.triggered_id == "plan-card-simple" else "soutien"
|
||||
return _selection_state(selected)
|
||||
|
||||
|
||||
def _cgu_modal():
|
||||
return dbc.Modal(
|
||||
[
|
||||
@@ -50,10 +101,6 @@ def layout(**query):
|
||||
if guard is not None:
|
||||
return guard
|
||||
|
||||
plan = query.get("plan", "")
|
||||
if not plan:
|
||||
return dcc.Location(href="/compte/abonnement", id="inf-no-plan-redirect")
|
||||
|
||||
prefill: dict = {}
|
||||
try:
|
||||
prefill = frisbii_client.get_customer(f"colibre-{current_user.id}")
|
||||
@@ -229,7 +276,13 @@ def layout(**query):
|
||||
action="/subscriptions/subscribe",
|
||||
children=[
|
||||
_csrf_input(),
|
||||
dcc.Input(type="hidden", name="plan", value=plan),
|
||||
html.Div(
|
||||
"Choisissez votre formule :",
|
||||
id="inf-plan-invite",
|
||||
className="fw-bold mb-2",
|
||||
),
|
||||
_selectable_cards(_trial_for(current_user.id)),
|
||||
dcc.Input(type="hidden", id="inf-plan-hidden", name="plan", value=""),
|
||||
dbc.Row([col1, col2], className="g-4 mb-4"),
|
||||
checkboxes,
|
||||
html.Button(
|
||||
@@ -305,9 +358,10 @@ def _lookup_siret(_, siret):
|
||||
Output("inf-submit", "disabled"),
|
||||
Input("inf-cb-retractation", "value"),
|
||||
Input("inf-cb-cgu", "value"),
|
||||
Input("inf-plan-hidden", "value"),
|
||||
)
|
||||
def _toggle_submit(retractation, cgu):
|
||||
return not (retractation and cgu)
|
||||
def _toggle_submit(retractation, cgu, plan):
|
||||
return not (retractation and cgu and plan)
|
||||
|
||||
|
||||
@callback(
|
||||
|
||||
Reference in New Issue
Block a user