feat(abonnement): mode configure sur mes-infos (formule préactivée, sans cases) (#109)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ from src.subscriptions import client as frisbii_client
|
|||||||
from src.subscriptions import db as sub_db
|
from src.subscriptions import db as sub_db
|
||||||
from src.subscriptions import plans
|
from src.subscriptions import plans
|
||||||
from src.utils.data import get_annuaire_data
|
from src.utils.data import get_annuaire_data
|
||||||
|
from src.utils.frontend import format_date_french
|
||||||
|
|
||||||
register_page(
|
register_page(
|
||||||
__name__,
|
__name__,
|
||||||
@@ -32,19 +33,41 @@ def _trial_for(user_id):
|
|||||||
return lambda key: None if used else plans.trial_days(key)
|
return lambda key: None if used else plans.trial_days(key)
|
||||||
|
|
||||||
|
|
||||||
def _selectable_cards(trial_for):
|
def _mode_for(row) -> str:
|
||||||
|
if row is not None and row["status"] in ("active", "trial", "pending"):
|
||||||
|
return "configure"
|
||||||
|
return "subscribe"
|
||||||
|
|
||||||
|
|
||||||
|
def _submit_button(mode: str):
|
||||||
|
label = (
|
||||||
|
"Mettre à jour mon abonnement"
|
||||||
|
if mode == "configure"
|
||||||
|
else "Ajouter une carte de paiement"
|
||||||
|
)
|
||||||
|
return html.Button(
|
||||||
|
label,
|
||||||
|
id="inf-submit",
|
||||||
|
type="submit",
|
||||||
|
className="btn btn-primary",
|
||||||
|
disabled=(mode == "subscribe"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _selectable_cards(trial_for, selected=None):
|
||||||
cols = []
|
cols = []
|
||||||
for key in ("simple", "soutien"):
|
for key in ("simple", "soutien"):
|
||||||
meta = plans.plan_meta(key)
|
meta = plans.plan_meta(key)
|
||||||
if not meta:
|
if not meta:
|
||||||
continue
|
continue
|
||||||
|
base = "plan-selectable selected" if key == selected else "plan-selectable"
|
||||||
cols.append(
|
cols.append(
|
||||||
dbc.Col(
|
dbc.Col(
|
||||||
html.Div(
|
html.Div(
|
||||||
_plan_card(meta, trial_for(key)),
|
_plan_card(meta, trial_for(key)),
|
||||||
id=f"plan-card-{key}",
|
id=f"plan-card-{key}",
|
||||||
n_clicks=0,
|
n_clicks=0,
|
||||||
className="plan-selectable",
|
className=base,
|
||||||
),
|
),
|
||||||
md=6,
|
md=6,
|
||||||
)
|
)
|
||||||
@@ -76,6 +99,50 @@ def _select_plan(_n_simple, _n_soutien):
|
|||||||
return _selection_state(selected)
|
return _selection_state(selected)
|
||||||
|
|
||||||
|
|
||||||
|
def _legal_note():
|
||||||
|
return dcc.Markdown(
|
||||||
|
"""\\* Champ obligatoire
|
||||||
|
|
||||||
|
Si vous préférez régler par virement bancaire et une facturation annuelle plutôt qu'un réglement mensuel automatique par carte bancaire, [contactez-moi](/a-propos/contact)."""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _consent_checklists():
|
||||||
|
return [
|
||||||
|
dcc.Checklist(
|
||||||
|
id="inf-cb-retractation",
|
||||||
|
options=[
|
||||||
|
{
|
||||||
|
"label": "Je renonce à mon droit de rétractation légal de 14 jours.",
|
||||||
|
"value": "ok",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
value=[],
|
||||||
|
className="mb-2",
|
||||||
|
),
|
||||||
|
dcc.Checklist(
|
||||||
|
id="inf-cb-cgu",
|
||||||
|
options=[
|
||||||
|
{
|
||||||
|
"label": [
|
||||||
|
"J'ai lu et accepte les ",
|
||||||
|
html.A(
|
||||||
|
"conditions générales d'utilisation du service",
|
||||||
|
href="#",
|
||||||
|
id="inf-cgu-link",
|
||||||
|
style={"cursor": "pointer"},
|
||||||
|
),
|
||||||
|
".",
|
||||||
|
],
|
||||||
|
"value": "ok",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
value=[],
|
||||||
|
className="mb-4",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _cgu_modal():
|
def _cgu_modal():
|
||||||
return dbc.Modal(
|
return dbc.Modal(
|
||||||
[
|
[
|
||||||
@@ -101,6 +168,20 @@ def layout(**query):
|
|||||||
if guard is not None:
|
if guard is not None:
|
||||||
return guard
|
return guard
|
||||||
|
|
||||||
|
row = sub_db.get_current(current_user.id)
|
||||||
|
mode = _mode_for(row)
|
||||||
|
selected = row["plan"] if mode == "configure" else None
|
||||||
|
echeance = (
|
||||||
|
format_date_french(row["current_period_end"])
|
||||||
|
if mode == "configure" and row["current_period_end"]
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
sub_info = (
|
||||||
|
{"current_plan": selected, "status": row["status"], "echeance": echeance}
|
||||||
|
if mode == "configure"
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
|
||||||
prefill: dict = {}
|
prefill: dict = {}
|
||||||
try:
|
try:
|
||||||
prefill = frisbii_client.get_customer(f"colibre-{current_user.id}")
|
prefill = frisbii_client.get_customer(f"colibre-{current_user.id}")
|
||||||
@@ -232,66 +313,30 @@ def layout(**query):
|
|||||||
md=6,
|
md=6,
|
||||||
)
|
)
|
||||||
|
|
||||||
checkboxes = html.Div(
|
|
||||||
[
|
|
||||||
dcc.Markdown(
|
|
||||||
"Si vous préférez régler par virement bancaire et une facturation annuelle plutôt qu'un réglement mensuel automatique par carte bancaire, [contactez-moi](/a-propos/contact)."
|
|
||||||
),
|
|
||||||
dcc.Checklist(
|
|
||||||
id="inf-cb-retractation",
|
|
||||||
options=[
|
|
||||||
{
|
|
||||||
"label": "Je renonce à mon droit de rétractation légal de 14 jours.",
|
|
||||||
"value": "ok",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
value=[],
|
|
||||||
className="mb-2",
|
|
||||||
),
|
|
||||||
dcc.Checklist(
|
|
||||||
id="inf-cb-cgu",
|
|
||||||
options=[
|
|
||||||
{
|
|
||||||
"label": [
|
|
||||||
"J'ai lu et accepte les ",
|
|
||||||
html.A(
|
|
||||||
"conditions générales d'utilisation du service",
|
|
||||||
href="#",
|
|
||||||
id="inf-cgu-link",
|
|
||||||
style={"cursor": "pointer"},
|
|
||||||
),
|
|
||||||
".",
|
|
||||||
],
|
|
||||||
"value": "ok",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
value=[],
|
|
||||||
className="mb-4",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
form = html.Form(
|
form = html.Form(
|
||||||
method="POST",
|
method="POST",
|
||||||
action="/subscriptions/subscribe",
|
action="/subscriptions/subscribe"
|
||||||
|
if mode == "subscribe"
|
||||||
|
else "/subscriptions/update",
|
||||||
children=[
|
children=[
|
||||||
_csrf_input(),
|
_csrf_input(),
|
||||||
html.Div(
|
html.Div(
|
||||||
"Choisissez votre formule :",
|
"Choisissez votre formule :"
|
||||||
|
if mode == "subscribe"
|
||||||
|
else "Votre formule :",
|
||||||
id="inf-plan-invite",
|
id="inf-plan-invite",
|
||||||
className="fw-bold mb-2",
|
className="fw-bold mb-2",
|
||||||
),
|
),
|
||||||
_selectable_cards(_trial_for(current_user.id)),
|
_selectable_cards(_trial_for(current_user.id), selected=selected),
|
||||||
dcc.Input(type="hidden", id="inf-plan-hidden", name="plan", value=""),
|
html.Div(id="inf-change-hint", className="d-none"),
|
||||||
dbc.Row([col1, col2], className="g-4 mb-4"),
|
dcc.Store(id="inf-sub-info", data=sub_info),
|
||||||
checkboxes,
|
dcc.Input(
|
||||||
html.Button(
|
type="hidden", id="inf-plan-hidden", name="plan", value=selected or ""
|
||||||
"Ajout d'une carte de paiement",
|
|
||||||
id="inf-submit",
|
|
||||||
type="submit",
|
|
||||||
className="btn btn-primary",
|
|
||||||
disabled=True,
|
|
||||||
),
|
),
|
||||||
|
dbc.Row([col1, col2], className="g-4 mb-4"),
|
||||||
|
_legal_note(),
|
||||||
|
*(_consent_checklists() if mode == "subscribe" else []),
|
||||||
|
_submit_button(mode),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -308,7 +353,7 @@ def layout(**query):
|
|||||||
if prefill
|
if prefill
|
||||||
else None,
|
else None,
|
||||||
form,
|
form,
|
||||||
_cgu_modal(),
|
_cgu_modal() if mode == "subscribe" else None,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,3 +42,38 @@ def test_submit_disabled_without_plan():
|
|||||||
assert m._toggle_submit(["ok"], ["ok"], "") is True
|
assert m._toggle_submit(["ok"], ["ok"], "") is True
|
||||||
assert m._toggle_submit(["ok"], ["ok"], "simple") is False
|
assert m._toggle_submit(["ok"], ["ok"], "simple") is False
|
||||||
assert m._toggle_submit([], ["ok"], "simple") is True
|
assert m._toggle_submit([], ["ok"], "simple") is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_mode_for_derives_from_status():
|
||||||
|
from src.pages.compte import abonnement_mes_infos as m
|
||||||
|
|
||||||
|
for status in ("active", "trial", "pending"):
|
||||||
|
assert m._mode_for({"status": status}) == "configure"
|
||||||
|
assert m._mode_for({"status": "cancelled"}) == "subscribe"
|
||||||
|
assert m._mode_for({"status": "expired"}) == "subscribe"
|
||||||
|
assert m._mode_for(None) == "subscribe"
|
||||||
|
|
||||||
|
|
||||||
|
def test_submit_button_subscribe_mode():
|
||||||
|
from src.pages.compte import abonnement_mes_infos as m
|
||||||
|
|
||||||
|
text = str(m._submit_button("subscribe"))
|
||||||
|
assert "Ajouter une carte de paiement" in text
|
||||||
|
assert "disabled" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_submit_button_configure_mode():
|
||||||
|
from src.pages.compte import abonnement_mes_infos as m
|
||||||
|
|
||||||
|
btn = m._submit_button("configure")
|
||||||
|
text = str(btn)
|
||||||
|
assert "Mettre à jour mon abonnement" in text
|
||||||
|
assert btn.disabled is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_selectable_cards_preselects_current_plan():
|
||||||
|
from src.pages.compte import abonnement_mes_infos as m
|
||||||
|
|
||||||
|
text = str(m._selectable_cards(trial_for=lambda key: None, selected="soutien"))
|
||||||
|
# la card soutien est marquée sélectionnée, pas la card simple
|
||||||
|
assert "plan-selectable selected" in text
|
||||||
|
|||||||
Reference in New Issue
Block a user