import streamlit as st from auth_token import login, initiate_signup, complete_signup, STARTUP_WARNINGS # Show critical startup warnings (missing API keys etc.) if STARTUP_WARNINGS and not __import__('streamlit').session_state.get('_warnings_shown'): for _w in STARTUP_WARNINGS: __import__('streamlit').session_state['_startup_warning'] = _w __import__('streamlit').session_state['_warnings_shown'] = True import streamlit.components.v1 as components import os st.set_page_config(page_title="FitPlan Pro", page_icon="⚡", layout="wide") # ── Already logged in → go to profile ──────────────────────────────────────── if st.session_state.get("logged_in"): # Always go through profile which loads from DB and routes correctly st.switch_page("pages/1_Profile.py") # ── State init ──────────────────────────────────────────────────────────────── for k, v in [("page_mode","landing"),("signup_step","form"),("pending_signup",{})]: if k not in st.session_state: st.session_state[k] = v # ── Hide Streamlit chrome, make iframe full screen ─────────────────────────── st.markdown(""" """, unsafe_allow_html=True) # ══════════════════════════════════════════════════════════════════════════════ # ACTION HANDLERS (query params set by JS window.location.href inside iframe) # ══════════════════════════════════════════════════════════════════════════════ params = st.query_params action = params.get("action", "") # ── STEP 5: LOGIN ───────────────────────────────────────────────────────────── if action == "login": u = params.get("u", ""); p = params.get("p", "") if u and p: ok, token, real_username, msg = login(u, p) if ok: st.session_state.logged_in = True st.session_state.username = real_username st.session_state.auth_token = token st.query_params.clear() # ── Load profile + plan from DB immediately on login ────────────── _db_err = None try: import traceback as _tb from utils.db import get_user_profile, get_active_plan # Load profile from DB prof = get_user_profile(real_username) if prof: st.session_state.user_data = prof # Load active plan from DB existing = get_active_plan(real_username) if existing and existing.get("days") and len(existing["days"]) > 0: structured = [] for d in existing["days"]: # workout_json is already parsed by get_active_plan structured.append({ "day": int(d.get("day_number", 1)), "is_rest_day": bool(d.get("is_rest_day", False)), "muscle_group": str(d.get("muscle_group") or "Full Body"), "workout": d.get("workout_json") or [], "dietary": d.get("dietary_json") or {}, "pre_stretch": d.get("pre_stretch_json") or [], "post_stretch": d.get("post_stretch_json") or [], }) structured.sort(key=lambda x: x["day"]) st.session_state.structured_days = structured st.session_state.full_plan_data = structured st.session_state.dietary_type = existing.get("dietary_type", "veg") st.session_state.plan_id = existing.get("plan_id", "") st.session_state.plan_start = existing.get("created_at_date", "") st.session_state.plan_duration = len(structured) st.session_state.workout_plan = "\n".join([ f"## Day {d['day']} - {d['muscle_group']}" for d in structured ]) st.session_state._plan_checked = True st.session_state._db_loaded_dash = True # Plan ready — go straight to dashboard st.switch_page("pages/2_Dashboard.py") except Exception as _e: _db_err = str(_e) import traceback; traceback.print_exc() # No plan or DB error — go to profile st.session_state._plan_checked = True if _db_err: st.session_state._login_db_err = _db_err st.switch_page("pages/1_Profile.py") else: st.session_state.login_error = msg st.query_params.clear(); st.rerun() # ── STEP 2a: SEND OTP ───────────────────────────────────────────────────────── if action == "send_otp": su = params.get("u",""); se = params.get("e","") sp = params.get("p",""); sp2 = params.get("p2","") if sp != sp2: st.session_state.signup_error = "Passwords don't match." elif "@" not in se or "." not in se: st.session_state.signup_error = "Enter a valid email address." elif len(sp) < 6: st.session_state.signup_error = "Password must be at least 6 characters." elif not su.strip(): st.session_state.signup_error = "Username is required." else: with st.spinner(""): ok, msg = initiate_signup(su.strip(), se.strip().lower(), sp) if ok: if msg == "__NO_OTP__": # Brevo not configured — user saved directly, redirect to login st.session_state.signup_success = "✓ Account created! Please sign in." st.session_state.page_mode = "login" st.session_state.signup_step = "form" st.session_state.pending_signup = {} else: # OTP sent — go to verification step st.session_state.pending_signup = {"username": su.strip(), "email": se.strip().lower(), "password": sp} st.session_state.signup_step = "otp" st.session_state.signup_error = "" st.session_state.page_mode = "signup" else: st.session_state.signup_error = msg st.session_state.page_mode = "signup" st.query_params.clear(); st.rerun() # ── STEP 2b: VERIFY OTP → STEP 3: CREATE ACCOUNT ──────────────────────────── if action == "verify_otp": otp_val = params.get("otp", "") # Data travels in URL — never relies on session_state (iframe boundary safe) su = params.get("u", "").strip() se = params.get("e", "").strip() sp = params.get("p", "").strip() if not su: # fallback (same-origin reload) pd = st.session_state.get("pending_signup", {}) su = pd.get("username",""); se = pd.get("email",""); sp = pd.get("password","") ok, token, msg = complete_signup(su, se, sp, otp_val) if ok: # STEP 4: redirect to login after account created st.session_state.signup_success = "✓ Account created! Please sign in." st.session_state.signup_step = "form" st.session_state.page_mode = "login" st.session_state.pending_signup = {} else: st.session_state.otp_error = msg st.session_state.signup_step = "otp" st.session_state.page_mode = "signup" st.query_params.clear(); st.rerun() # ── NAV ─────────────────────────────────────────────────────────────────────── if action == "go_signup": st.session_state.page_mode = "signup"; st.session_state.signup_step = "form" st.query_params.clear(); st.rerun() if action == "go_login": st.session_state.page_mode = "login" st.query_params.clear(); st.rerun() if action == "go_back": st.session_state.signup_step = "form" st.session_state.pending_signup = {} st.session_state.page_mode = "signup" st.query_params.clear(); st.rerun() # ── Pop flash messages ──────────────────────────────────────────────────────── login_error = st.session_state.pop("login_error", "") signup_error = st.session_state.pop("signup_error", "") otp_error = st.session_state.pop("otp_error", "") signup_success = st.session_state.pop("signup_success", "") mode = st.session_state.page_mode signup_step = st.session_state.signup_step pending = st.session_state.pending_signup pending_email = pending.get("email", "") pending_u = pending.get("username", "") pending_e = pending.get("email", "") pending_p = pending.get("password", "") is_signup = mode == "signup" is_landing = mode == "landing" def err(msg): return f"
{msg}
" if msg else "" def good(msg): return f"
{msg}
" if msg else "" # ── Config status (shown on signup page) ───────────────────────────────────── _brevo_ok = bool(os.environ.get("BREVO_API_KEY","")) and bool(os.environ.get("EMAIL_SENDER","")) _supabase_ok = bool(os.environ.get("SUPABASE_URL","")) and bool(os.environ.get("SUPABASE_KEY","")) def cfg_banner(): lines = [] if not _supabase_ok: lines.append("⚠ SUPABASE_URL/SUPABASE_KEY not set — accounts reset on restart") if not _brevo_ok: lines.append("⚠ BREVO_API_KEY/EMAIL_SENDER not set — OTP email disabled, direct signup used") if not lines: return "" return ("
" + "
".join(lines) + "
") # ══════════════════════════════════════════════════════════════════════════════ # SVG TILE ICONS # ══════════════════════════════════════════════════════════════════════════════ SVG_ICONS = { "barbell": '', "dumbbell": '', "runner": '', "pullup": '', "bicycle": '', "yoga": '', "kettlebell": '', "pushup": '', "stopwatch": '', "medal": '', "heartrate": '', "squat": '', "plank": '', "boxing": '', "flame": '', "target": '', } TILE_DATA = [ ("barbell","BARBELL","#2a1f3d","#c4b5fd"),("runner","RUNNING","#1e2d45","#93c5fd"), ("dumbbell","DUMBBELL","#2a1f3d","#e0aaff"),("heartrate","CARDIO","#3a1a2a","#fca5a5"), ("bicycle","CYCLING","#1a2e2a","#6ee7b7"),("yoga","YOGA","#2a1f3d","#d8b4fe"), ("pullup","PULL-UPS","#1a2e25","#86efac"),("kettlebell","KETTLEBELL","#1d2e36","#5eead4"), ("stopwatch","INTERVALS","#1e2d45","#7dd3fc"),("pushup","PUSH-UPS","#32210f","#fdba74"), ("squat","SQUATS","#1f2040","#a5b4fc"),("plank","PLANK","#2a1f3d","#f0abfc"), ("boxing","BOXING","#3a1a1e","#fda4af"),("medal","CHAMPION","#30280e","#fde68a"), ("flame","HIIT","#351808","#fed7aa"),("target","GOALS","#1e2d45","#7dd3fc"), ] def make_tile(i): key, label, bg, accent = TILE_DATA[i % len(TILE_DATA)] d = round((i * 0.4) % 4, 1) dur = round(3 + (i * 0.3) % 2, 1) border = "border-bottom:2px solid rgba(229,9,20,0.45);" if i % 7 == 0 else "" return ( f'
' f'' + SVG_ICONS[key] + f'
{label}
' ) tiles_html = "".join(make_tile(i) for i in range(60)) # ══════════════════════════════════════════════════════════════════════════════ # LANDING HERO (page_mode == "landing") # ══════════════════════════════════════════════════════════════════════════════ landing_html = ( "
" "
⚡ AI-POWERED FITNESS
" "
TRAIN
SMARTER.
NOT HARDER.
" "
" "Your personalised workout + diet plan — generated by AI,
" "tailored to your body, goals, and fitness level." "
" "
" "
💪Personalised Workouts
" "
🥗AI Diet Plans
" "
📊Progress Tracking
" "
🔥Streak System
" "
" "" "
Already a member? " "Sign in
" "
" ) # ══════════════════════════════════════════════════════════════════════════════ # BUILD CARD HTML (no nested f-strings — plain concatenation) # ══════════════════════════════════════════════════════════════════════════════ if not is_signup: # STEP 4 / STEP 5 — Login page card_html = ( "
Sign In
" + err(login_error) + good(signup_success) + """
""" ) elif signup_step == "otp": # STEP 2b — OTP verification card_html = ( "
Verify Email
" + err(otp_error) + "

We sent a 6-digit code to

" + pending_email + "

Check your inbox and spam folder

" + '
' + '' + '' + '' + """
Already a member? Sign in.
""" ) else: # STEP 1 — Signup form card_html = ( "
Create Account
" + cfg_banner() + err(signup_error) + """
Already a member? Sign in.
""" ) # ══════════════════════════════════════════════════════════════════════════════ # FULL HTML # ══════════════════════════════════════════════════════════════════════════════ if is_landing: page_content = landing_html else: page_content = f"
{card_html}
" HTML = f"""
{tiles_html}
{page_content}
""" components.html(HTML, height=900, scrolling=False)