Test-fitplan / app.py
saiganesh2004's picture
Update app.py
2b1a8b3 verified
import streamlit as st
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from utils.auth import initiate_signup, complete_signup, login
st.set_page_config(
page_title="FitPro AI",
page_icon="⚑",
layout="wide",
initial_sidebar_state="collapsed"
)
if st.session_state.get("logged_in"):
st.switch_page("pages/2_Dashboard.py")
for k, v in [("mode","landing"),("signup_step","form"),
("pending_email",""),("login_err",""),
("signup_err",""),("otp_err","")]:
if k not in st.session_state:
st.session_state[k] = v
login_err = st.session_state.pop("login_err", "")
signup_err = st.session_state.pop("signup_err", "")
otp_err = st.session_state.pop("otp_err", "")
mode = st.session_state.mode
step = st.session_state.signup_step
pending_email = st.session_state.get("pending_email","")
# ── Auth Pages CSS (only for login/signup) ──────────────────────────────────────────
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600&display=swap');
/* Reset all Streamlit default spacing */
#MainMenu,footer,header,
[data-testid="stHeader"],[data-testid="stToolbar"],
[data-testid="stToolbarActions"],[data-testid="stDecoration"],
[data-testid="stStatusWidget"],[data-testid="stSidebarNav"],
[data-testid="collapsedControl"],[data-testid="baseButton-header"],
.stDeployButton,button[kind="header"],
section[data-testid="stSidebar"] {
display:none!important;height:0!important;overflow:hidden!important;
position:absolute!important;
}
/* Natural document flow for auth pages */
html, body {
margin:0 !important;
padding:0 !important;
width:100% !important;
background:#07070f !important;
font-family:'DM Sans', sans-serif !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.stApp {
background:#07070f !important;
min-height: 100vh !important;
}
[data-testid="stAppViewContainer"] {
background:#07070f !important;
width:100% !important;
min-height: 100vh !important;
}
[data-testid="stAppViewContainer"] > section {
padding:0 !important;
width:100% !important;
}
[data-testid="stAppViewContainer"] > section > div.block-container {
padding:0 !important;
margin:0 !important;
max-width:100% !important;
width:100% !important;
}
/* Remove all extra gaps */
[data-testid="stVerticalBlock"] { gap:0 !important; }
[data-testid="element-container"] { margin:0 !important; padding:0 !important; }
/* Buttons */
.stButton > button {
background:linear-gradient(135deg,#6366f1,#8b5cf6) !important;
border:none !important;
color:#fff !important;
border-radius:10px !important;
font-family:'DM Sans', sans-serif !important;
font-size:0.95rem !important;
font-weight:600 !important;
padding:13px 28px !important;
width:100% !important;
box-shadow:0 4px 18px rgba(99,102,241,0.32) !important;
transition:all 0.2s !important;
cursor:pointer !important;
}
.stButton > button:hover {
opacity:0.88 !important;
transform:translateY(-2px) !important;
box-shadow:0 8px 28px rgba(99,102,241,0.50) !important;
}
/* Ghost button variant */
.ghost-btn .stButton > button {
background:transparent !important;
border:1.5px solid rgba(99,102,241,0.40) !important;
color:rgba(241,240,255,0.65) !important;
box-shadow:none !important;
}
.ghost-btn .stButton > button:hover {
border-color:#818cf8 !important;
color:#f1f0ff !important;
background:rgba(99,102,241,0.08) !important;
transform:translateY(-1px) !important;
box-shadow:none !important;
}
/* Inputs */
div[data-baseweb="input"] > div {
background:#0f0f1e !important;
border:1.5px solid rgba(99,102,241,0.22) !important;
border-radius:10px !important;
}
div[data-baseweb="input"]:focus-within > div {
border-color:#6366f1 !important;
box-shadow:0 0 0 3px rgba(99,102,241,0.12) !important;
}
div[data-baseweb="input"] input {
background:transparent !important;
color:#f1f0ff !important;
font-family:'DM Sans', sans-serif !important;
font-size:0.95rem !important;
height:48px !important;
}
[data-testid="stWidgetLabel"] p {
color:rgba(241,240,255,0.55) !important;
font-size:0.72rem !important;
font-weight:600 !important;
letter-spacing:1.5px !important;
text-transform:uppercase !important;
}
.stAlert {
border-radius:10px !important;
font-size:0.88rem !important;
}
</style>
""", unsafe_allow_html=True)
# ══════════════════════════════════════════════════════════════════
# LANDING PAGE
# ══════════════════════════════════════════════════════════════════
if mode == "landing":
# ── Topnav ─────────────────────────────────────────────────────
nav_l, nav_r = st.columns([6, 2])
with nav_l:
st.markdown("""
<div style="padding:20px 40px 16px 40px">
<span style="font-family:'Syne',sans-serif;font-size:1.55rem;font-weight:800;
background:linear-gradient(135deg,#6366f1,#a78bfa);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
letter-spacing:1px">⚑ FitPro AI</span>
</div>
""", unsafe_allow_html=True)
with nav_r:
st.markdown("<div style='padding:14px 40px 0 0;display:flex;gap:10px;justify-content:flex-end'>", unsafe_allow_html=True)
nb1, nb2 = st.columns(2)
with nb1:
st.markdown("<div class='ghost-btn'>", unsafe_allow_html=True)
if st.button("Sign In", key="nav_signin"):
st.session_state.mode = "login"; st.rerun()
st.markdown("</div>", unsafe_allow_html=True)
with nb2:
if st.button("Get Started", key="nav_start"):
st.session_state.mode = "signup"; st.session_state.signup_step = "form"; st.rerun()
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("<hr style='margin:0;border-color:rgba(99,102,241,0.12)'>", unsafe_allow_html=True)
# ── Hero ────────────────────────────────────────────────────────
st.markdown("""
<div style="
background:
radial-gradient(ellipse 55% 60% at 10% 20%, rgba(99,102,241,0.13) 0%, transparent 55%),
radial-gradient(ellipse 45% 50% at 90% 80%, rgba(139,92,246,0.10) 0%, transparent 55%),
radial-gradient(circle, rgba(255,255,255,0.025) 1px, transparent 1px) 0 0 / 30px 30px;
padding: 80px 40px 60px;
text-align: center;
">
<div style="font-size:0.62rem;font-weight:700;letter-spacing:5px;
text-transform:uppercase;color:#818cf8;margin-bottom:18px;
display:flex;align-items:center;justify-content:center;gap:10px">
<span style="width:28px;height:1px;background:rgba(99,102,241,0.4);display:inline-block"></span>
AI-Powered Personal Training
<span style="width:28px;height:1px;background:rgba(99,102,241,0.4);display:inline-block"></span>
</div>
<h1 style="font-family:'Syne',sans-serif;
font-size:clamp(2.8rem,5vw,5rem);font-weight:800;
line-height:0.92;letter-spacing:-1.5px;margin-bottom:22px;margin-top:0">
<span style="background:linear-gradient(135deg,#ffffff,rgba(255,255,255,0.75));
-webkit-background-clip:text;-webkit-text-fill-color:transparent">
TRAIN SMARTER.
</span><br>
<span style="background:linear-gradient(135deg,#6366f1,#a78bfa);
-webkit-background-clip:text;-webkit-text-fill-color:transparent">
GET REAL RESULTS.
</span>
</h1>
<p style="font-size:1.05rem;color:rgba(241,240,255,0.45);font-weight:300;
max-width:500px;margin:0 auto 44px;line-height:1.8">
Your AI coach builds a fully personalised workout &amp; nutrition plan
tailored to your body, goals and equipment β€” generated in seconds.
</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-bottom:20px">
<span style="font-size:0.60rem;font-weight:600;letter-spacing:1.8px;
text-transform:uppercase;color:rgba(241,240,255,0.22);
border:1px solid rgba(99,102,241,0.15);border-radius:20px;padding:5px 14px">Strength</span>
<span style="font-size:0.60rem;font-weight:600;letter-spacing:1.8px;
text-transform:uppercase;color:rgba(241,240,255,0.22);
border:1px solid rgba(99,102,241,0.15);border-radius:20px;padding:5px 14px">Fat Loss</span>
<span style="font-size:0.60rem;font-weight:600;letter-spacing:1.8px;
text-transform:uppercase;color:rgba(241,240,255,0.22);
border:1px solid rgba(99,102,241,0.15);border-radius:20px;padding:5px 14px">Muscle Build</span>
<span style="font-size:0.60rem;font-weight:600;letter-spacing:1.8px;
text-transform:uppercase;color:rgba(241,240,255,0.22);
border:1px solid rgba(99,102,241,0.15);border-radius:20px;padding:5px 14px">Nutrition Plans</span>
<span style="font-size:0.60rem;font-weight:600;letter-spacing:1.8px;
text-transform:uppercase;color:rgba(241,240,255,0.22);
border:1px solid rgba(99,102,241,0.15);border-radius:20px;padding:5px 14px">Progress Tracking</span>
</div>
</div>
""", unsafe_allow_html=True)
# CTA buttons
_, cta_l, cta_r, _ = st.columns([2, 1.2, 1.2, 2])
with cta_l:
if st.button("⚑ Start for Free", key="hero_start", use_container_width=True):
st.session_state.mode = "signup"; st.session_state.signup_step = "form"; st.rerun()
with cta_r:
st.markdown("<div class='ghost-btn'>", unsafe_allow_html=True)
if st.button("Sign In β†’", key="hero_signin", use_container_width=True):
st.session_state.mode = "login"; st.rerun()
st.markdown("</div>", unsafe_allow_html=True)
# ── Feature cards ───────────────────────────────────────────────
st.markdown("<div style='padding:0 40px'>", unsafe_allow_html=True)
st.markdown("<hr style='border-color:rgba(99,102,241,0.10);margin:40px 0 32px'>",
unsafe_allow_html=True)
st.markdown("""
<div style="text-align:center;font-size:0.60rem;font-weight:700;letter-spacing:3px;
text-transform:uppercase;color:rgba(241,240,255,0.25);margin-bottom:20px">
Everything you need
</div>""", unsafe_allow_html=True)
f1, f2, f3 = st.columns(3, gap="medium")
for col, icon, title, desc in [
(f1,"πŸ€–","AI-Generated Plans",
"Groq LLaMA 3 generates your full workout + diet plan in under 60 seconds, personalised to your exact stats, goals and equipment."),
(f2,"πŸ“Š","Progress Tracking",
"Track daily workouts, build streaks, visualise your weekly activity and mark sessions complete right from the dashboard."),
(f3,"πŸ₯—","Nutrition Plans",
"Full macro breakdown, sample meal plan, supplement guide and foods to prioritise β€” all tailored to your BMI and goal."),
]:
with col:
st.markdown(f"""
<div style="background:rgba(14,14,26,0.9);border:1px solid rgba(99,102,241,0.14);
border-radius:16px;padding:28px 24px;height:100%">
<div style="font-size:1.8rem;margin-bottom:14px">{icon}</div>
<div style="font-family:'Syne',sans-serif;font-size:1rem;font-weight:700;
color:#f1f0ff;margin-bottom:8px">{title}</div>
<div style="font-size:0.82rem;color:rgba(241,240,255,0.38);line-height:1.7">{desc}</div>
</div>""", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("<div style='height:60px'></div>", unsafe_allow_html=True)
# ══════════════════════════════════════════════════════════════════
# AUTH PAGES (Login / Signup / OTP) β€” simplified natural flow
# ══════════════════════════════════════════════════════════════════
else:
# Simple header
st.markdown("""
<div style="padding:20px 40px 16px;border-bottom:1px solid rgba(99,102,241,0.10);
background:#07070f">
<span style="font-family:'Syne',sans-serif;font-size:1.55rem;font-weight:800;
background:linear-gradient(135deg,#6366f1,#a78bfa);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:1px">
⚑ FitPro AI
</span>
</div>
""", unsafe_allow_html=True)
# Background with subtle gradient (no flex, just padding)
st.markdown("""
<div style="
background:
radial-gradient(ellipse 55% 45% at 15% 25%,rgba(99,102,241,0.11) 0%,transparent 60%),
radial-gradient(ellipse 45% 40% at 85% 75%,rgba(139,92,246,0.08) 0%,transparent 55%),
radial-gradient(circle,rgba(255,255,255,0.025) 1px,transparent 1px) 0 0/30px 30px,
#07070f;
width: 100%;
padding: 40px 24px;
">
""", unsafe_allow_html=True)
# Centered card using columns
_, mid, _ = st.columns([1, 1.4, 1])
with mid:
# Back home button
back_col, _ = st.columns([1, 3])
with back_col:
if st.button("← Home", key="back_home"):
st.session_state.mode = "landing"; st.rerun()
# Auth card
st.markdown("""
<div style="background:rgba(10,10,20,0.97);
border:1px solid rgba(99,102,241,0.30);border-radius:20px;
padding:36px 40px 32px;position:relative;
box-shadow:0 32px 80px rgba(0,0,0,0.60),0 0 0 1px rgba(99,102,241,0.06);
margin: 20px 0;">
<div style="position:absolute;top:0;left:0;right:0;height:2px;
background:linear-gradient(90deg,transparent,#6366f1 30%,
#a78bfa 50%,#6366f1 70%,transparent)"></div>
""", unsafe_allow_html=True)
# ── LOGIN ──────────────────────────────────────────────────
if mode == "login":
st.markdown("""
<div style="font-size:0.58rem;font-weight:700;letter-spacing:4px;
text-transform:uppercase;color:#818cf8;margin-bottom:6px">
Welcome back
</div>
<div style="font-family:'Syne',sans-serif;font-size:2.2rem;font-weight:800;
color:#f1f0ff;margin-bottom:24px;line-height:1">Sign In</div>
""", unsafe_allow_html=True)
if login_err:
st.error(login_err)
with st.form("login_form", clear_on_submit=False):
li_u = st.text_input("Email or Username", placeholder="your@email.com")
li_p = st.text_input("Password", type="password", placeholder="β€’β€’β€’β€’β€’β€’β€’β€’")
sub = st.form_submit_button("Sign In β†’", use_container_width=True)
if sub:
if not li_u.strip() or not li_p:
st.error("Please fill all fields.")
else:
ok, token, real_u, msg = login(li_u.strip(), li_p)
if ok:
st.session_state.update(logged_in=True, username=real_u, auth_token=token)
st.switch_page("pages/2_Dashboard.py")
else:
st.error(msg)
st.markdown("<div style='height:16px'></div>", unsafe_allow_html=True)
st.markdown("<div style='text-align:center;font-size:0.85rem;color:rgba(241,240,255,0.35);margin-bottom:8px'>New here?</div>", unsafe_allow_html=True)
if st.button("Create an account β†’", key="to_signup", use_container_width=True):
st.session_state.mode = "signup"; st.session_state.signup_step = "form"; st.rerun()
# ── OTP ────────────────────────────────────────────────────
elif mode == "signup" and step == "otp":
st.markdown(f"""
<div style="font-size:0.58rem;font-weight:700;letter-spacing:4px;
text-transform:uppercase;color:#818cf8;margin-bottom:6px">Almost there</div>
<div style="font-family:'Syne',sans-serif;font-size:2.2rem;font-weight:800;
color:#f1f0ff;margin-bottom:16px;line-height:1">Verify Email</div>
<div style="background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.25);
border-radius:10px;padding:14px;text-align:center;margin-bottom:20px;
font-size:0.85rem;color:rgba(241,240,255,0.45)">
6-digit code sent to
<strong style="color:#818cf8;display:block;font-size:0.95rem;margin-top:4px">
{pending_email}
</strong>
</div>
""", unsafe_allow_html=True)
if otp_err:
st.error(otp_err)
with st.form("otp_form", clear_on_submit=False):
otp_val = st.text_input("Verification Code", placeholder="000000", max_chars=6)
sub = st.form_submit_button("Verify & Create Account β†’", use_container_width=True)
if sub:
if len(otp_val.strip()) < 6:
st.error("Please enter the 6-digit code.")
else:
ok, token, result = complete_signup(pending_email, otp_val.strip())
if ok:
st.session_state.update(
logged_in=True, username=result, auth_token=token,
mode="landing", signup_step="form", pending_email=""
)
st.switch_page("pages/2_Dashboard.py")
else:
st.error(result)
st.markdown("<div style='height:8px'></div>", unsafe_allow_html=True)
if st.button("← Wrong email? Go back", key="back_otp"):
st.session_state.signup_step = "form"; st.rerun()
# ── SIGNUP ─────────────────────────────────────────────────
else:
st.markdown("""
<div style="font-size:0.58rem;font-weight:700;letter-spacing:4px;
text-transform:uppercase;color:#818cf8;margin-bottom:6px">Get started free</div>
<div style="font-family:'Syne',sans-serif;font-size:2.2rem;font-weight:800;
color:#f1f0ff;margin-bottom:24px;line-height:1">Create Account</div>
""", unsafe_allow_html=True)
if signup_err:
st.error(signup_err)
with st.form("signup_form", clear_on_submit=False):
su_u = st.text_input("Username", placeholder="yourname")
su_e = st.text_input("Email Address", placeholder="your@email.com")
c1, c2 = st.columns(2)
with c1:
su_p = st.text_input("Password", type="password", placeholder="min 6 chars")
with c2:
su_p2 = st.text_input("Confirm Password", type="password", placeholder="repeat")
sub = st.form_submit_button("Create Account β†’", use_container_width=True)
if sub:
err = ""
if not su_u.strip() or not su_e.strip() or not su_p or not su_p2:
err = "Please fill all fields."
elif "@" not in su_e:
err = "Enter a valid email address."
elif su_p != su_p2:
err = "Passwords don't match."
elif len(su_p) < 6:
err = "Password must be at least 6 characters."
if err:
st.error(err)
else:
ok, mode_flag, token = initiate_signup(su_u.strip(), su_e.strip(), su_p)
if ok and mode_flag == "__DIRECT__":
st.session_state.update(
logged_in=True, username=su_u.strip(), auth_token=token,
mode="landing", signup_step="form", pending_email=""
)
st.switch_page("pages/2_Dashboard.py")
elif ok and mode_flag == "__OTP__":
st.session_state.pending_email = su_e.strip()
st.session_state.signup_step = "otp"
st.rerun()
else:
st.error(mode_flag)
st.markdown("<div style='height:16px'></div>", unsafe_allow_html=True)
st.markdown("<div style='text-align:center;font-size:0.85rem;color:rgba(241,240,255,0.35);margin-bottom:8px'>Already have an account?</div>", unsafe_allow_html=True)
if st.button("Sign in β†’", key="to_login", use_container_width=True):
st.session_state.mode = "login"; st.rerun()
st.markdown("</div>", unsafe_allow_html=True) # close auth card
st.markdown("</div>", unsafe_allow_html=True) # close background div