summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-13 15:22:48 -0500
committerBen Sima <ben@bsima.me>2025-11-13 15:22:48 -0500
commitabc8230c04a787045a599327455445585edff46a (patch)
tree8eb0593bd91c963d9a8dde52e1b5636ef13191ae
parentb3a60dc7f411dc94ef5f06af33f7b4a88011c611 (diff)
Implement full account management page
- Display account information (email, creation date) - Show subscription details (plan, status, features) - Display cancellation warning if subscription ending - Add Upgrade button for free users - Add Manage Subscription button for paid users (goes to Stripe portal) - Add logout button in Actions section - Replace Coming Soon placeholder with functional UI Addresses account management epic tasks. Amp-Thread-ID: https://ampcode.com/threads/T-8edacbeb-b343-49ca-b524-1c999272acb6 Co-authored-by: Amp <amp@ampcode.com>
-rw-r--r--.tasks/tasks.jsonl8
-rw-r--r--Biz/PodcastItLater/Web.py157
2 files changed, 152 insertions, 13 deletions
diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl
index 8eff7bc..3d0d504 100644
--- a/.tasks/tasks.jsonl
+++ b/.tasks/tasks.jsonl
@@ -55,12 +55,12 @@
{"taskCreatedAt":"2025-11-13T19:38:33.491331064Z","taskDependencies":[],"taskId":"t-1fbABoD","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Extract extract_og_metadata and send_magic_link to Core module for reusability","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:46:04.679290775Z"}
{"taskCreatedAt":"2025-11-13T19:38:33.674140035Z","taskDependencies":[],"taskId":"t-1fbBmXa","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Review and fix type: ignore comments - improve type safety","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:09.583640045Z"}
{"taskCreatedAt":"2025-11-13T19:38:33.85804778Z","taskDependencies":[],"taskId":"t-1fbC8Nq","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Remove PLR2004 magic number - use constant for month check","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:45.120428021Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.035597081Z","taskDependencies":[],"taskId":"t-1fbCSZd","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Implement cancel subscription functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.035597081Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.035597081Z","taskDependencies":[],"taskId":"t-1fbCSZd","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"InProgress","taskTitle":"Implement cancel subscription functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:28.494715433Z"}
{"taskCreatedAt":"2025-11-13T19:38:34.194926176Z","taskDependencies":[],"taskId":"t-1fbDyr2","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.194926176Z"}
{"taskCreatedAt":"2025-11-13T19:38:34.384489707Z","taskDependencies":[],"taskId":"t-1fbElKv","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Implement change email address functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.384489707Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.561871604Z","taskDependencies":[],"taskId":"t-1fbF5Tv","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Add logout button to account page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.561871604Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.777721397Z","taskDependencies":[],"taskId":"t-1fbG02X","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Replace Coming Soon placeholder with full account management UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.777721397Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.962196629Z","taskDependencies":[],"taskId":"t-1fbGM2m","taskNamespace":null,"taskParent":"t-1f9SnU7","taskStatus":"InProgress","taskTitle":"Add remove button to queue status items","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:22.346741808Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.561871604Z","taskDependencies":[],"taskId":"t-1fbF5Tv","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"InProgress","taskTitle":"Add logout button to account page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:28.242735917Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.777721397Z","taskDependencies":[],"taskId":"t-1fbG02X","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"InProgress","taskTitle":"Replace Coming Soon placeholder with full account management UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:27.967202307Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.962196629Z","taskDependencies":[],"taskId":"t-1fbGM2m","taskNamespace":null,"taskParent":"t-1f9SnU7","taskStatus":"Done","taskTitle":"Add remove button to queue status items","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:10.941908917Z"}
{"taskCreatedAt":"2025-11-13T19:38:35.119686179Z","taskDependencies":[],"taskId":"t-1fbHr0w","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Remove button classes from navbar links (make them regular nav links)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.185088389Z"}
{"taskCreatedAt":"2025-11-13T19:38:35.311151364Z","taskDependencies":[],"taskId":"t-1fbIeOF","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Remove 'Logged in as' text from navbar","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.23552934Z"}
{"taskCreatedAt":"2025-11-13T19:38:35.476139354Z","taskDependencies":[],"taskId":"t-1fbIVJL","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Left-align navbar links instead of right-aligned buttons","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.285578917Z"}
diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py
index f4c65b1..af603e2 100644
--- a/Biz/PodcastItLater/Web.py
+++ b/Biz/PodcastItLater/Web.py
@@ -1064,7 +1064,7 @@ def verify_magic_link(request: Request) -> Response:
@app.get("/account")
def account_page(request: Request) -> html.html | RedirectResponse:
- """Account management page (coming soon)."""
+ """Account management page."""
user_id = request.session.get("user_id")
if not user_id:
return RedirectResponse(url="/?error=login_required")
@@ -1073,6 +1073,12 @@ def account_page(request: Request) -> html.html | RedirectResponse:
if not user:
return RedirectResponse(url="/?error=user_not_found")
+ # Get subscription details
+ tier = user.get("plan_tier", "free")
+ tier_info = Billing.get_tier_info(tier)
+ subscription_status = user.get("subscription_status", "")
+ cancel_at_period_end = user.get("cancel_at_period_end", 0) == 1
+
return html.html(
html.head(
html.meta(charset="utf-8"),
@@ -1085,6 +1091,11 @@ def account_page(request: Request) -> html.html | RedirectResponse:
content="light dark",
),
html.title("Account - PodcastItLater"),
+ html.script(
+ src="https://unpkg.com/htmx.org@1.9.10",
+ integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC",
+ crossorigin="anonymous",
+ ),
),
html.body(
html.style(
@@ -1117,20 +1128,148 @@ def account_page(request: Request) -> html.html | RedirectResponse:
],
),
),
+ # Account info section
html.div(
+ html.h4(
+ html.i(classes=["bi", "bi-envelope-fill", "me-2"]),
+ "Account Information",
+ classes=["mb-3"],
+ ),
html.div(
+ html.div(
+ html.strong("Email: "),
+ user["email"],
+ classes=["mb-2"],
+ ),
+ html.div(
+ html.strong("Account Created: "),
+ user["created_at"],
+ classes=["mb-2"],
+ ),
+ classes=["card-body"],
+ ),
+ classes=["card", "mb-4"],
+ ),
+ # Subscription section
+ html.div(
+ html.h4(
html.i(
- classes=["bi", "bi-info-circle-fill", "me-2"],
+ classes=["bi", "bi-credit-card-fill", "me-2"],
+ ),
+ "Subscription",
+ classes=["mb-3"],
+ ),
+ html.div(
+ html.div(
+ html.strong("Plan: "),
+ tier_info["name"],
+ f" ({tier_info['price']})",
+ classes=["mb-2"],
),
- html.strong("Coming Soon"),
- html.p(
- "Account management features including "
- "subscription management, usage history, and "
- "settings will be available here.",
- classes=["mb-0", "mt-2"],
+ html.div(
+ html.strong("Status: "),
+ subscription_status.title()
+ if subscription_status
+ else "Active",
+ classes=["mb-2"],
+ )
+ if tier == "paid"
+ else html.div(),
+ html.div(
+ html.i(
+ classes=[
+ "bi",
+ "bi-info-circle",
+ "me-1",
+ ],
+ ),
+ "Your subscription will cancel at the end "
+ "of the billing period.",
+ classes=[
+ "alert",
+ "alert-warning",
+ "mt-2",
+ "mb-2",
+ ],
+ )
+ if cancel_at_period_end
+ else html.div(),
+ html.div(
+ html.strong("Features: "),
+ tier_info["description"],
+ classes=["mb-3"],
+ ),
+ # Subscription actions
+ html.div(
+ html.a(
+ html.i(
+ classes=[
+ "bi",
+ "bi-arrow-up-circle",
+ "me-1",
+ ],
+ ),
+ "Upgrade to Paid Plan",
+ href="#",
+ hx_post="/billing/checkout",
+ hx_vals='{"tier": "paid"}',
+ classes=[
+ "btn",
+ "btn-success",
+ "me-2",
+ ],
+ )
+ if tier == "free"
+ else html.a(
+ html.i(
+ classes=[
+ "bi",
+ "bi-gear-fill",
+ "me-1",
+ ],
+ ),
+ "Manage Subscription",
+ href="#",
+ hx_post="/billing/portal",
+ classes=[
+ "btn",
+ "btn-primary",
+ "me-2",
+ ],
+ ),
+ ),
+ classes=["card-body"],
+ ),
+ classes=["card", "mb-4"],
+ ),
+ # Actions section
+ html.div(
+ html.h4(
+ html.i(classes=["bi", "bi-sliders", "me-2"]),
+ "Actions",
+ classes=["mb-3"],
+ ),
+ html.div(
+ html.a(
+ html.i(
+ classes=[
+ "bi",
+ "bi-box-arrow-right",
+ "me-1",
+ ],
+ ),
+ "Logout",
+ href="/logout",
+ classes=[
+ "btn",
+ "btn-outline-secondary",
+ "mb-2",
+ "me-2",
+ ],
),
- classes=["alert", "alert-info"],
+ classes=["card-body"],
),
+ classes=["card", "mb-4"],
),
classes=["mb-4"],
),