import streamlit as st from auth_token import (login, initiate_signup, complete_signup, reset_password_request, reset_password_confirm, 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"): 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", "") # ── 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() _db_err = None try: from utils.db import get_user_profile, get_active_plan prof = get_user_profile(real_username) if prof: st.session_state.user_data = prof existing = get_active_plan(real_username) if existing and existing.get("days") and len(existing["days"]) > 0: structured = [] for d in existing["days"]: 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 st.switch_page("pages/2_Dashboard.py") except Exception as _e: _db_err = str(_e) import traceback; traceback.print_exc() 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() # ── SEND OTP (signup) ───────────────────────────────────────────────────────── 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__": 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: 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() # ── VERIFY OTP (signup) ─────────────────────────────────────────────────────── if action == "verify_otp": otp_val = params.get("otp", "") su = params.get("u", "").strip() se = params.get("e", "").strip() sp = params.get("p", "").strip() if not su: 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: 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() # ── FORGOT PASSWORD — STEP 1: request OTP ──────────────────────────────────── if action == "forgot_request": fp_id = params.get("id", "").strip() if not fp_id: st.session_state.forgot_error = "Please enter your email or username." st.session_state.page_mode = "forgot" else: ok, msg = reset_password_request(fp_id) if ok: st.session_state.forgot_identity = fp_id st.session_state.forgot_skip_otp = (msg == "__NO_OTP__") st.session_state.page_mode = "forgot_otp" st.session_state.forgot_error = "" if msg != "__NO_OTP__": st.session_state.forgot_sent_msg = msg else: st.session_state.forgot_error = msg st.session_state.page_mode = "forgot" st.query_params.clear(); st.rerun() # ── FORGOT PASSWORD — STEP 2: confirm new password ─────────────────────────── if action == "forgot_confirm": fp_id = params.get("id", "").strip() fp_otp = params.get("otp", "").strip() fp_pw = params.get("pw", "").strip() fp_pw2 = params.get("pw2", "").strip() skip = st.session_state.get("forgot_skip_otp", False) if not fp_pw or len(fp_pw) < 6: st.session_state.forgot_otp_error = "Password must be at least 6 characters." st.session_state.page_mode = "forgot_otp" elif fp_pw != fp_pw2: st.session_state.forgot_otp_error = "Passwords don't match." st.session_state.page_mode = "forgot_otp" else: otp_to_use = "000000" if skip else fp_otp ok, msg = reset_password_confirm(fp_id, otp_to_use, fp_pw) if ok: st.session_state.signup_success = "✓ Password updated! Please sign in." st.session_state.page_mode = "login" for k in ["forgot_identity","forgot_skip_otp","forgot_sent_msg","forgot_error","forgot_otp_error"]: st.session_state.pop(k, None) else: st.session_state.forgot_otp_error = msg st.session_state.page_mode = "forgot_otp" st.query_params.clear(); st.rerun() # ── FORGOT: resend OTP ──────────────────────────────────────────────────────── if action == "forgot_resend": fp_id = st.session_state.get("forgot_identity", params.get("id","").strip()) if fp_id: ok, msg = reset_password_request(fp_id) if ok and msg != "__NO_OTP__": st.session_state.forgot_sent_msg = msg 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" for k in ["forgot_identity","forgot_skip_otp","forgot_sent_msg","forgot_error","forgot_otp_error"]: st.session_state.pop(k, None) 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() if action == "go_forgot": st.session_state.page_mode = "forgot" st.session_state.forgot_error = "" 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", "") forgot_error = st.session_state.get("forgot_error", "") forgot_otp_error = st.session_state.get("forgot_otp_error", "") forgot_sent_msg = st.session_state.pop("forgot_sent_msg", "") 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", "") forgot_identity = st.session_state.get("forgot_identity", "") forgot_skip_otp = st.session_state.get("forgot_skip_otp", False) is_signup = mode == "signup" is_landing = mode == "landing" is_forgot = mode == "forgot" is_forgot_otp = mode == "forgot_otp" 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 (unchanged from original) # ══════════════════════════════════════════════════════════════════════════════ 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 # ══════════════════════════════════════════════════════════════════════════════ 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
" "
" "" "" "
" ) # ══════════════════════════════════════════════════════════════════════════════ # BUILD CARD HTML # ══════════════════════════════════════════════════════════════════════════════ # ── FORGOT PASSWORD STEP 1: enter email/username ────────────────────────────── if is_forgot: card_html = ( "
Reset Password
" + err(forgot_error) + """

Enter your email or username and we'll send a 6-digit reset code.

""" ) # ── FORGOT PASSWORD STEP 2: enter OTP + new password ───────────────────────── elif is_forgot_otp: sent_notice = good(forgot_sent_msg) if forgot_sent_msg else ( "

Reset code sent to your email

" "" + forgot_identity + "" "

Check your inbox and spam folder

" if not forgot_skip_otp else "" ) otp_field = "" if forgot_skip_otp else """
""" resend_row = "" if forgot_skip_otp else ( '' ) fp_id_val = forgot_identity.replace('"', '"') card_html = ( "
New Password
" + err(forgot_otp_error) + sent_notice + '
' + '' + otp_field + """
""" + resend_row + '' ) elif not is_signup: # ── LOGIN ───────────────────────────────────────────────────────────────── card_html = ( "
Sign In
" + err(login_error) + good(signup_success) + """
""" ) elif signup_step == "otp": # ── SIGNUP OTP ──────────────────────────────────────────────────────────── 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: # ── 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)