Fitness that fits student life.
A student fitness and sports PWA built for Smart India Hackathon Problem ID 26196. Moveup motivates students through friends, fits fitness into their timetable, and guides them to the best place and time to stay active — through friend challenges with live leaderboards, a rule-based smart quest planner (with optional Gemini-generated personal quests layered on top, never in charge of selection), a weather/AQI-aware campus activity map (FitRoute), in-app Fit Window nudges, and live, peer-verified Squad Sessions.
- Next.js (App Router) + TypeScript + Tailwind CSS
- Supabase: Auth, Postgres, Realtime, Storage
- MapLibre GL + OpenStreetMap for the campus map
- Recharts for progress/activity charts
- Open-Meteo for weather + air quality (no API key required)
- Google Gemini (optional) for personal quest content generation — see
lib/quest-generator.ts; selection logic stays rule-based either way - Installable PWA (manifest, offline cache, app icons), light/dark theme
app/ Routes (App Router)
(app)/ Authenticated student shell (bottom nav + top bar)
page.tsx Home dashboard
challenges/ Fit Circles, challenges, leaderboards
quest/ Smart Fitness Quest planner
fitroute/ Campus map + Green Window + GPS sessions
squad/ Live, peer-verified Squad Sessions
profile/ Profile, stats, privacy & settings
notifications/ Notification centre
coordinator/ Desktop-friendly coordinator dashboard (separate layout)
login/, signup/, onboarding/
api/ weather (Open-Meteo+AQI proxy), nearby-places (OSM),
quests/generate (Gemini quest content)
components/ UI split by feature area (dashboard, challenges, quests,
fitroute, leaderboard, squad, coordinator, profile, ui, layout)
lib/ supabase/ clients+types, quest-engine + quest-generator,
weather, geo, activity-stats, quest-context, time,
fit-window + nudge-rules + nudge-generator (Fit Window
nudges), squad + use-gps-tracker + use-squad-channel
(Squad Sessions), utils
supabase/
migrations/ SQL schema, RLS policies, triggers, coordinator RPCs
seed.sql Campus content (quests, locations, badges) — no fake users
npm install- Create a project at supabase.com.
- In the SQL editor, run every file in
supabase/migrations/in order (0001_init.sql,0002_..., ... through the highest-numbered file). - Run
supabase/seed.sqlto load the campus content library — the quest catalogue, FitRoute locations, and badge definitions. It creates no user accounts: students sign up through the app itself.
Never create users by inserting into
auth.usersdirectly. Several columns there (confirmation_token,recovery_token,email_change,email_change_token_new) have no default, and Supabase Auth cannot deserialize a row where they areNULL— every login for that user then fails with a generic "Database error querying schema". Use the signup flow, the Dashboard's Add user, or the Admin API instead.
If you're using the Supabase CLI instead of the dashboard SQL editor:
supabase link --project-ref <your-project-ref>
supabase db push
psql "<connection-string>" -f supabase/seed.sqlCopy .env.example to .env.local and fill in:
NEXT_PUBLIC_SUPABASE_URL= # Project Settings → API
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=# Project Settings → API (anon/publishable key)
NEXT_PUBLIC_MAPTILER_KEY= # optional — vector map style; omit to use free OSM raster tiles
AQI_API_KEY= # optional — reserved for a keyed AQI provider; Open-Meteo's free AQI API is used by default
GEMINI_API_KEY= # optional — get one free at aistudio.google.com/apikey; without it,
# personal quest generation just falls back to the static catalogue
Without Supabase configured, pages still render with proper loading/error states (there's a placeholder client so nothing crashes at build/import time) — but you need a real project to sign up, log in, and see live data.
npm run devVisit http://localhost:3000, create an account, and you'll be walked through
onboarding (campus, goal, fitness level, preferred activities, free timetable
slots) before landing on your dashboard.
For demos, turn off email confirmation so signups work instantly: Dashboard → Authentication → Providers → Email → uncheck Confirm email. Otherwise every new account has to click a link before it can log in, and Supabase's built-in SMTP is heavily rate-limited.
The login and signup screens include a "Continue with Google" button. To enable it:
- Create OAuth credentials in the Google Cloud Console (type: Web application).
- Add
https://<your-project-ref>.supabase.co/auth/v1/callbackas an authorized redirect URI. - In Supabase: Authentication → Providers → Google, enable it and paste the client ID and secret.
Until it's enabled, the button shows a clear "not enabled for this project" message instead of failing silently. Email/password works regardless.
Everyone signs up as a student. To give someone the campus coordinator dashboard
at /coordinator, promote their account after they've signed up:
update profiles set role = 'coordinator'
where id = (select id from auth.users where email = 'you@college.edu');The rule-based quest planner (lib/quest-engine.ts) and the Fit Window /
nudge engines (lib/fit-window.ts, lib/nudge-rules.ts) have unit tests:
npm testThe app spots when a student's free timetable slot, good weather/AQI, and free circle-mates line up ("Free 5:30–6:00 PM, AQI 78, Aditi & Imran are free. Group walk?") and surfaces it — but only ever as an in-app notification, generated the moment the student opens the dashboard. There is no background delivery: no push subscriptions, no OS notifications, no service-role key, no cron job. A student who never opens the app never gets a notification.
lib/fit-window.ts— which of today's free slots are actually good (pure, unit-tested).lib/nudge-rules.ts— which nudge fires given those windows plus streak/ circle context (pure, unit-tested).lib/nudge-generator.ts— called once fromapp/(app)/page.tsxon every dashboard load; runsbuildNudges()and inserts any new ones intonotifications, usingnudge_log's unique(profile_id, dedupe_key)as a same-day dedupe claim so re-opening the dashboard doesn't duplicate them. Uses the caller's own RLS-scoped session throughout — never elevated privileges — since it only ever acts on the signed-in student's own data.
The Notifications toggle in Profile → Settings controls whether this runs at all for that student. To try it: adjust your free slots/timetable so a window lines up with "now" in your testing timezone, open the dashboard, and check the Notifications tab.
A live group walk/run/cycle (or quest) where everyone's progress streams to a shared board in real time, and finishing together gets the activity stamped peer-verified — the server checks the group was actually colocated (everyone's start and end GPS fixes within 150m, ≥5 min of overlapping session time), not just that the numbers look plausible. Peer-verified activity counts more toward challenge progress than a solo GPS log.
- Schema, RLS, and every RPC (create/join/invite/start/cancel/finalize) live
in
supabase/migrations/0021_squad_sessions.sql— no client ever inserts intosquad_sessions/squad_participantsdirectly. finalize_squad_sessionlabels each participantverified_reasonwhen it isn't peer-verified:no_gps_fix,too_short(< 5 min),implausible_pace(and in that one case, no activity is logged at all — same anti-cheat pace ceilings as GPS routes),solo(nobody else had fixes either), ornot_colocated.- Realtime uses a private channel (
squad:<session-id>), authorized by RLS policies onrealtime.messages(is_squad_member(...)) — see the bottom of the migration. This requires a Supabase project with Realtime's Broadcast Authorization feature (therealtime.messagestable); if your project predates it, upgrade the Realtime service in the dashboard. lib/use-gps-tracker.tsdrives live tracking (shared with the plain GPS route dialog): fixes worse than 50m accuracy are dropped, and movement under 3m between fixes doesn't count as distance, so standing still doesn't slowly accumulate fake steps.
To try it: Squad tab (bottom nav / dashboard quick action) → Start,
pick an activity, then Invite to squad to share the join code — a second
account joins via Join by code or the shared link. On a phone, run
npx next dev --experimental-https -H 0.0.0.0 so a second device on the same
network can reach it over HTTPS (required for GPS). Worth rehearsing the
negative case too: end a session after under a minute and confirm it comes
back "Not verified — too short" instead of silently succeeding.
- Push this repo to GitHub.
- Import it into Vercel.
- Add the same environment variables from
.env.localin the Vercel project settings. - Deploy. The PWA manifest, icons, and service worker are already wired up — installable on first visit.
- Not built, by design: AI camera form-checking, medical features, parent dashboards, complaint systems, talent scouting, government integrations.
- Quest planner is a plain rule-based engine (
lib/quest-engine.ts) — no LLM/chatbot involved. Gemini only ever generates the content of a personal quest (lib/quest-generator.ts); which quest gets recommended when is still decided by the same rules for every quest, hand-written or generated. - Steps are self-reported (manual entry, completed quests, GPS-tracked
distance, or a peer-verified Squad Session) — the app never claims
browser-pedometer accuracy.
activities.source(manual/quest/gps_route/squad, the last with its ownpeer_verifiedflag) keeps that distinction, weighted accordingly in challenge progress (0013_anti_cheat.sql,0021_squad_sessions.sql). Android Health Connect can plug in as a fifth source later without changing the schema. - Coordinators only ever see anonymized, aggregated stats — enforced at the
database layer via
SECURITY DEFINERRPCs (coordinator_overview,coordinator_department_leaderboard,coordinator_route_usage), not just hidden in the UI.