Spaces:
Sleeping
Sleeping
| """Admin dashboard tab + sidebar subscription card for the Streamlit app. | |
| Talks to the same repositories as the FastAPI backend (in-process).""" | |
| from datetime import datetime, timedelta, timezone | |
| import streamlit as st | |
| def _subs_repo(): | |
| from apps.omyfish_api.db.engine import ensure_db | |
| from apps.omyfish_api.repositories.subscription_repository import SubscriptionRepository | |
| ensure_db() | |
| return SubscriptionRepository() | |
| # ── Sidebar subscription card (any logged-in user) ──────────────────────────── | |
| def subscription_sidebar(user: dict) -> None: | |
| from apps.omyfish_api import billing | |
| from apps.omyfish_api.billing import BillingNotConfigured | |
| repo = _subs_repo() | |
| sub = repo.get_for_user(user["id"]) or repo.start_trial(user["id"]) | |
| status = sub["status"] | |
| if status == "trialing": | |
| days_left = max(0, (sub["trial_end"] - datetime.now(timezone.utc)).days) | |
| st.info(f"Free trial — {days_left} day{'s' if days_left != 1 else ''} left") | |
| elif status == "active": | |
| st.success(f"Subscribed — {billing.PLANS[sub['plan']]['label']}" | |
| if sub.get("plan") in billing.PLANS else "Subscribed") | |
| else: | |
| st.warning("Trial ended — subscribe to keep full access") | |
| if status != "active": | |
| col1, col2 = st.columns(2) | |
| for col, plan in ((col1, "monthly"), (col2, "yearly")): | |
| with col: | |
| if st.button(billing.PLANS[plan]["label"], key=f"sub_{plan}", | |
| use_container_width=True): | |
| try: | |
| url = billing.create_checkout_session( | |
| user["id"], user["email"], plan) | |
| st.session_state["checkout_url"] = url | |
| except BillingNotConfigured: | |
| st.session_state["checkout_url"] = None | |
| if "checkout_url" in st.session_state: | |
| if st.session_state["checkout_url"]: | |
| st.link_button("Continue to secure checkout →", | |
| st.session_state.pop("checkout_url"), | |
| use_container_width=True) | |
| else: | |
| st.session_state.pop("checkout_url") | |
| st.caption("Payments are not configured on this deployment.") | |
| # ── Admin tab ───────────────────────────────────────────────────────────────── | |
| def render_admin() -> None: | |
| from apps.omyfish_api.db.engine import get_db | |
| from sqlalchemy import text | |
| repo = _subs_repo() | |
| with get_db() as db: | |
| user_count = db.execute(text("SELECT COUNT(*) FROM users")).scalar() | |
| obs_count = db.execute(text("SELECT COUNT(*) FROM observations")).scalar() | |
| stats = repo.stats() | |
| c1, c2, c3, c4, c5 = st.columns(5) | |
| c1.metric("Users", user_count) | |
| c2.metric("Observations", obs_count) | |
| c3.metric("Active subs", stats["subscriptions"].get("active", 0)) | |
| c4.metric("On trial", stats["subscriptions"].get("trialing", 0)) | |
| c5.metric("MRR (CAD)", f"${stats['mrr_cad']:.2f}") | |
| st.divider() | |
| st.subheader("Subscriptions") | |
| subs = repo.list_all() | |
| if subs: | |
| st.dataframe( | |
| [{ | |
| "Email": s["email"], | |
| "Status": s["status"], | |
| "Plan": s.get("plan") or "—", | |
| "Trial ends": s["trial_end"].strftime("%Y-%m-%d") | |
| if s.get("trial_end") else "—", | |
| "Period ends": s["current_period_end"].strftime("%Y-%m-%d") | |
| if s.get("current_period_end") else "—", | |
| } for s in subs], | |
| use_container_width=True, hide_index=True, | |
| ) | |
| else: | |
| st.caption("No subscriptions yet.") | |
| st.divider() | |
| st.subheader("Manage") | |
| if not subs: | |
| return | |
| emails = {s["email"]: s["user_id"] for s in subs} | |
| target_email = st.selectbox("User", sorted(emails)) | |
| target = emails[target_email] | |
| a1, a2, a3 = st.columns(3) | |
| with a1: | |
| if st.button("Grant 1 year (comp)", use_container_width=True): | |
| repo.set_active(target, plan="yearly", | |
| period_end=datetime.now(timezone.utc) + timedelta(days=365)) | |
| st.rerun() | |
| with a2: | |
| if st.button("Extend trial 7 days", use_container_width=True): | |
| sub = repo.get_for_user(target) | |
| base = max(sub["trial_end"] or datetime.now(timezone.utc), | |
| datetime.now(timezone.utc)) | |
| from apps.omyfish_api.db.engine import get_db as _gdb | |
| with _gdb() as db: | |
| db.execute( | |
| text("UPDATE subscriptions SET status='trialing', trial_end=:te " | |
| "WHERE user_id=:uid"), | |
| {"te": (base + timedelta(days=7)).isoformat(), "uid": target}, | |
| ) | |
| st.rerun() | |
| with a3: | |
| if st.button("Revoke", use_container_width=True, type="secondary"): | |
| repo.set_status(target, "canceled") | |
| st.rerun() | |