summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-09 17:23:36 -0500
committerBen Sima <ben@bsima.me>2025-11-09 17:23:36 -0500
commit83d90f815447abc5447f6b0b4a978b2e8ce82894 (patch)
tree789c3b277680223dd771549d0180b58f198ac34e
parent380e944671add8fd5dda66d8702fc7e02b219115 (diff)
feat(PodcastItLater): Add usage limit enforcement and billing UI
- Enforce tier limits before article submission - Display current plan in user info card (Free/Personal/Pro) - Add Billing button to dashboard navigation - Show friendly upgrade prompt when limit reached - Link to /billing page for plan management Limits enforced: - Free: 10 articles/month - Personal: 50 articles/month - Pro: Unlimited Related to task t-144e7lF
-rw-r--r--Biz/PodcastItLater/Web.py54
1 files changed, 50 insertions, 4 deletions
diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py
index c469874..03d3eb7 100644
--- a/Biz/PodcastItLater/Web.py
+++ b/Biz/PodcastItLater/Web.py
@@ -678,16 +678,41 @@ class HomePage(Component[AnyChildren, HomePageAttrs]):
html.div(
html.div(
html.div(
- html.p(
+ html.div(
html.strong("Logged in as: "),
user["email"],
- classes=["mb-3"],
+ classes=["mb-2"],
+ ),
+ html.div(
+ html.i(classes=["bi", "bi-star", "me-2"]),
+ html.strong("Plan: "),
+ Billing.get_tier_info(
+ user.get("plan_tier", "free"),
+ )["name"],
+ classes=["mb-3", "text-muted", "small"],
),
html.div(
html.a(
html.i(
classes=[
"bi",
+ "bi-credit-card",
+ "me-1",
+ ],
+ ),
+ "Billing",
+ href="/billing",
+ classes=[
+ "btn",
+ "btn-outline-secondary",
+ "btn-sm",
+ "me-2",
+ ],
+ ),
+ html.a(
+ html.i(
+ classes=[
+ "bi",
"bi-gear-fill",
"me-1",
],
@@ -965,7 +990,7 @@ def logout(request: Request) -> Response:
@app.post("/submit")
-def submit_article(request: Request, data: FormData) -> html.div:
+def submit_article(request: Request, data: FormData) -> html.div: # noqa: PLR0911
"""Handle manual form submission."""
try:
# Check if user is logged in
@@ -1004,6 +1029,26 @@ def submit_article(request: Request, data: FormData) -> html.div:
classes=["alert", "alert-danger"],
)
+ # Check usage limits
+ allowed, _msg, usage = Billing.can_submit(user_id)
+ if not allowed:
+ tier = user.get("plan_tier", "free")
+ tier_info = Billing.get_tier_info(tier)
+ limit = tier_info.get("articles_limit", 0)
+ return html.div(
+ html.i(classes=["bi", "bi-exclamation-circle", "me-2"]),
+ html.strong("Limit reached: "),
+ f"You've used {usage['articles']}/{limit} articles "
+ "this period. ",
+ html.a(
+ "Upgrade your plan",
+ href="/billing",
+ classes=["alert-link"],
+ ),
+ " to continue.",
+ classes=["alert", "alert-warning"],
+ )
+
# Extract Open Graph metadata
title, author = extract_og_metadata(url)
@@ -1154,7 +1199,8 @@ def billing_checkout(request: Request, data: FormData) -> Response:
if not user_id:
return Response("Unauthorized", status_code=401)
- tier = data.get("tier", "personal")
+ tier_raw = data.get("tier", "personal")
+ tier = tier_raw if isinstance(tier_raw, str) else "personal"
if tier not in {"personal", "pro"}:
return Response("Invalid tier", status_code=400)