Feature: expanded equipment, integration-aware sync, tooltips
1. Equipment (onboarding step 5):
- Changed from 3 radio buttons to 14 multi-select chips
- Bicycle, pool, free weights, squat rack, bench press, machines,
resistance bands, pull-up bar, kettlebell, jump rope, yoga mat,
treadmill, stationary bike, rowing machine
- Backend: equipment_list JSONB field on profile model
- Resource matcher updated to filter by specific equipment
- Legacy equipment_access auto-derived for backward compat
2. Sync Strava/Polar (dashboard):
- Fetches integration status on load
- Shows 'Connect Strava/Polar' (dashed border) when not connected
- Shows 'Sync Strava/Polar' only when connected
- Connect button triggers OAuth flow
- Sync shows loading state and result count
3. Tooltips:
- Tip component: hover/click to show explanation bubble
- Added to: goals, TTM stages, PAR-Q+, BREQ-2 items,
equipment, rewards gate, daily points, activity checklist,
weekly progress, integrations section
- Science references: TTM, PAR-Q+, BREQ-2/RAI explained
This commit is contained in:
parent
434db38f0d
commit
82c4533a96
6 changed files with 270 additions and 66 deletions
|
|
@ -42,7 +42,8 @@ class UserProfile(Base):
|
|||
|
||||
# Preferences
|
||||
activity_preferences: Mapped[dict] = mapped_column(JSONB, default=list)
|
||||
equipment_access: Mapped[str | None] = mapped_column(String(50))
|
||||
equipment_access: Mapped[str | None] = mapped_column(String(50)) # legacy — kept for compat
|
||||
equipment_list: Mapped[dict] = mapped_column(JSONB, default=list) # new: list of equipment strings
|
||||
days_per_week: Mapped[int] = mapped_column(Integer, default=3)
|
||||
minutes_per_session: Mapped[int] = mapped_column(Integer, default=30)
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ async def save_phase2(
|
|||
# Preferences
|
||||
profile.activity_preferences = req.activity_preferences
|
||||
profile.equipment_access = req.equipment_access
|
||||
profile.equipment_list = req.equipment_list
|
||||
profile.days_per_week = req.days_per_week
|
||||
profile.minutes_per_session = req.minutes_per_session
|
||||
profile.phase2_completed = True
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ class Phase2Request(BaseModel):
|
|||
|
||||
# Preferences
|
||||
activity_preferences: list[str] = []
|
||||
equipment_access: str | None = None # none, basic_home, full_gym
|
||||
equipment_access: str | None = None # legacy single value
|
||||
equipment_list: list[str] = [] # new: list of equipment tags
|
||||
days_per_week: int = 3
|
||||
minutes_per_session: int = 30
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,14 @@ from app.models.resource import Resource
|
|||
from app.models.profile import UserProfile
|
||||
|
||||
|
||||
# Equipment categories that satisfy each resource requirement
|
||||
EQUIPMENT_SATISFIES = {
|
||||
"none": set(), # bodyweight — always available
|
||||
"basic_home": {"dumbbells", "resistance_bands", "pull_up_bar", "kettlebell"},
|
||||
"full_gym": {"barbell", "squat_rack", "bench_press", "machines", "cable_machine", "dumbbells"},
|
||||
}
|
||||
|
||||
|
||||
async def get_recommendations(db: AsyncSession, user_id: uuid.UUID) -> list[dict]:
|
||||
profile_result = await db.execute(
|
||||
select(UserProfile).where(UserProfile.user_id == user_id)
|
||||
|
|
@ -17,10 +25,21 @@ async def get_recommendations(db: AsyncSession, user_id: uuid.UUID) -> list[dict
|
|||
return []
|
||||
|
||||
query = select(Resource).where(Resource.is_active == True)
|
||||
if profile.equipment_access == "none":
|
||||
|
||||
# Filter by equipment: if user has specific equipment, determine what resource levels they can do
|
||||
user_equipment = set(profile.equipment_list or [])
|
||||
if not user_equipment:
|
||||
# No equipment selected — only show bodyweight programs
|
||||
query = query.where(Resource.equipment_needed == "none")
|
||||
elif profile.equipment_access == "basic_home":
|
||||
elif user_equipment & EQUIPMENT_SATISFIES["full_gym"]:
|
||||
# Has gym-level equipment — show everything
|
||||
pass
|
||||
elif user_equipment & EQUIPMENT_SATISFIES["basic_home"]:
|
||||
# Has some home equipment — show none + basic_home
|
||||
query = query.where(Resource.equipment_needed.in_(["none", "basic_home"]))
|
||||
else:
|
||||
# Only has non-gym items (bicycle, pool, etc.) — show bodyweight
|
||||
query = query.where(Resource.equipment_needed == "none")
|
||||
|
||||
result = await db.execute(query)
|
||||
resources = result.scalars().all()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue