SaaS platform for running coaches and independent athletes.
- What is TrainingTrack
- Features
- Tech Stack
- Project Structure
- Getting Started
- Environment Variables
- Edge Functions
- Database Overview
- Deployment
- Development Notes
- License
TrainingTrack has two audiences:
- Coaches — a dashboard to manage a roster of athletes: assign weekly trainings, build multi-phase plans, track competitions, message athletes, review Strava activity, and read AI-generated performance reports.
- Independent athletes — a self-service mode (no coach required) with an AI-generated training plan, an AI training assistant, competition tracking, and gamification.
Both roles share the same authenticated app; role and plan (free / premium) are resolved at
login and gate which routes and features are available.
- Weekly agenda: sessions grouped by title/type across all athletes, with per-athlete drill-down
- Calendar with drag-and-drop (
@dnd-kit); sessions and competitions shown as grouped pills - Athlete roster management, athlete profile and metrics views
- Competition creation for multiple athletes in a single operation
- Team health overview (
TeamHealthTable,useTeamHealth) - AI performance reports per athlete, generated on demand or on a weekly schedule
- Multi-phase training plans with mesocycles (base, build, peak, taper, recovery) and weekly microcycles
- Weekly plan editor: 7-day grid, free-text per day, auto-calculated weekly volume
- Plan assignment to multiple athletes from a start date, generating
training_sessionsrows - Periodization manager and reusable week/mesocycle templates
- Gym file uploads (PDF, 10 MB max, 14-day auto-expiry) shared with assigned athletes
- Shared running and gym exercise banks, browsable by both coach and athlete (
/libraryroutes)
- Interactive charts (Chart.js) for pace, volume, and training load
- Conconi and VAM field-test modals with CSV/XLSX import (
exceljs+papaparse) to derive pace/heart-rate series - RPE logging, readiness score, PMC (performance management chart), HR zone breakdown, race time predictions
- AI-assisted metric chart analysis (
analyze-metric-chartEdge Function)
All AI features call Google's Gemini API (gemini-2.5-flash) from Supabase Edge Functions —
not DeepSeek, despite what older internal notes may say:
- AI training plan generation: onboarding wizard collects athlete profile data, an Edge Function generates a structured plan, the coach (or independent athlete) reviews before saving
- AI weekly/on-demand reports per athlete
- AI chat: an assistant with access to the athlete's training context (coach-managed and independent-athlete variants)
- Self-service AI training plan (
MyPlan) with no coach relationship required - Own competitions list, AI training assistant, gamification elements
- OAuth-linked devices; a proxy Edge Function fetches Strava data server-side so access tokens never reach the browser
- Activity stream fetch (GPS/HR/pace series) and a webhook that auto-completes matching
training_sessions - Route maps rendered from Strava polylines via Mapbox GL (
useMapboxhook) on activity detail pages
- Plan selection, hosted Checkout, and Customer Portal via
stripe-checkout/stripe-portalEdge Functions - Subscription state surfaced in the coach's account settings (
useSubscription,subscriptionService.js) - Account deletion (GDPR-style) cancels any active subscription, purges storage objects, and logs an audit row before removing the user
- Real-time coach-athlete chat via Supabase Realtime
- PWA with a custom service worker (
src/sw.js, WorkboxinjectManifest) and Web Push notifications (VAPID); DB triggers on sessions/messages/competitions fire pushes automatically - Transactional email (password reset, notifications) via Resend, sent from the
send-emailEdge Function using shared templates
- Themed PDF documents (weekly plans, AI reports) built with the pdfx component library
(
src/components/pdfx/,@react-pdf/renderer) — seepdfx.jsonandCLAUDE.mdat the repo root for the component reference - Ad-hoc report exports via
jspdf+jspdf-autotable(src/lib/reportPdfExport.js)
- Public marketing site: landing page, pricing, use cases, blog, legal pages
- Admin panel: user management, subscription/plan overrides, waitlist review
- Dark/light mode (
ThemeContext), error tracking (Sentry), toast notifications (sileo)
| Library | Purpose |
|---|---|
| React 19 | UI — functional components and hooks |
| Vite 7 | Build tool with HMR |
| React Router 7 | Client-side routing, lazy-loaded routes |
| Tailwind CSS 3.4 | Utility-first styling |
| Framer Motion / GSAP | Animations (Framer Motion is the default; GSAP powers a few effect components) |
| Chart.js + react-chartjs-2 | Metric and performance charts |
| Mapbox GL + Leaflet/react-leaflet | Route maps from Strava polylines |
| React Hook Form + Zod | Form state and validation (auth flows) |
| @dnd-kit | Drag-and-drop calendar |
@react-pdf/renderer (via pdfx) + jsPDF/jspdf-autotable |
PDF generation |
| exceljs + papaparse | XLSX/CSV parsing for field-test imports |
| react-easy-crop | Profile image cropping |
| sileo | Toast notifications |
| Sentry | Error tracking |
| Workbox / vite-plugin-pwa | PWA / service worker |
| Service | Purpose |
|---|---|
| Supabase | PostgreSQL, Auth, Storage, Realtime, Edge Functions |
| Supabase Edge Functions | Serverless Deno functions |
| Google Gemini API | LLM for AI plan generation, reports, chat, and metric analysis |
| Strava API | Activity data, OAuth, webhook |
| Stripe | Checkout, billing portal, subscription webhooks |
| Resend | Transactional email |
Frontend/
├── public/ # Static assets, manifest.json, robots.txt, sitemap.xml
├── src/
│ ├── components/
│ │ ├── landing/ # Public marketing site components
│ │ ├── dashboard/ # Coach dashboard components (modals, planning wizard, tests)
│ │ ├── athlete/ # Athlete area components (charts, onboarding, reports)
│ │ ├── admin/ # Admin panel components
│ │ ├── calendar/ # Drag-and-drop weekly calendar view
│ │ ├── library/ # Exercise library modals/components (shared coach+athlete)
│ │ ├── auth/ · register/ # Auth and signup flow components
│ │ ├── shared/ # Small components shared across areas (e.g. ACWRGauge)
│ │ ├── common/ # Cross-app components (image crop, PWA prompt, Strava map)
│ │ └── pdfx/ # PDF component library (React components → @react-pdf/renderer)
│ │
│ ├── contexts/
│ │ ├── AuthContext.jsx # Auth session, profile, role/plan resolution
│ │ ├── ThemeContext.jsx # Dark / light mode
│ │ └── NotificationContext.jsx # Push state, active chat partner
│ │
│ ├── hooks/ # One hook per data/UI concern (dashboard, calendar, metrics,
│ │ Strava, AI quota, forms, Mapbox, subscription, etc.)
│ │
│ ├── layouts/
│ │ ├── DashboardLayout.jsx # Coach route guard + shell
│ │ ├── AthleteDashboardLayout.jsx # Athlete route guard + shell
│ │ └── AdminLayout.jsx # Admin route guard + shell
│ │
│ ├── pages/
│ │ ├── Landing.jsx, PricingPage.jsx, UseCases.jsx, PrivacyPolicy.jsx, ...
│ │ ├── blog/ # Public blog (list + article pages)
│ │ ├── dashboard/ # Coach pages (Dashboard, Athletes, Planning, Library, ...)
│ │ ├── athlete/ # Athlete pages (Training, MyPlan, Competitions, ...)
│ │ └── admin/ # Admin pages (Users, Waitlist, ...)
│ │
│ ├── services/ # All Supabase queries — one file per domain
│ │ (athlete, coach, planning, gamification, Strava, Stripe
│ │ subscriptions, conconi tests, exercise library, ...)
│ │
│ ├── lib/
│ │ ├── supabase.js # Supabase client
│ │ ├── dateUtils.js # toLocalDateStr() and other date helpers
│ │ ├── pdfExport.js / reportPdfExport.js # PDF generation entry points
│ │ ├── pdfx-theme.ts # Theme consumed by src/components/pdfx
│ │ ├── pdf/ # Standalone @react-pdf/renderer documents (plans, reports)
│ │ ├── pushNotifications.js # Web Push subscribe/unsubscribe
│ │ └── toast.js # Toast helpers (sileo)
│ │
│ ├── utils/ # (reserved; currently empty)
│ ├── sw.js # Custom service worker (Workbox injectManifest)
│ ├── App.jsx # Router and lazy-loaded routes
│ └── main.jsx # React entry point
│
├── supabase/ # SQL migrations, RLS policies, seeds, Edge Functions
├── react-email-starter/ # Standalone project for authoring Resend email templates
├── .env.example # Environment variable template
├── vite.config.js, tailwind.config.js, eslint.config.js, tsconfig.json
├── components.json, pdfx.json # Registries for shadcn-style and pdfx component CLIs
└── package.json
Note: this is a mostly-JS(X) codebase; TypeScript is used only for the
pdfxcomponent library and the standalone documents insrc/lib/pdf/.
/ — Landing page (public)
/login · /register
/forgot-password · /reset-password
/casos-de-uso · /privacidad · /terminos-y-condiciones
/blog · /blog/:slug
/pricing · /select-plan · /checkout/success
/auth/callback
/dashboard — Coach area (protected)
/dashboard/athletes
/dashboard/athletes/:athleteId
/dashboard/athletes/:athleteId/metrics
/dashboard/athletes/:athleteId/activity/:activityId
/dashboard/planning
/dashboard/library
/dashboard/metrics
/dashboard/calendar
/dashboard/profile
/dashboard/messages
/dashboard/ai-reports
/athlete/dashboard — Athlete area (protected)
/athlete/training · /athlete/calendar · /athlete/metrics
/athlete/devices · /athlete/messages · /athlete/gym-files
/athlete/library · /athlete/my-reports · /athlete/analysis-history
/athlete/activity/:activityId
/athlete/my-plan · /athlete/my-plan/activity/:activityId — independent athlete only
/athlete/competitions · /athlete/ai-assistant — independent athlete only
/admin/login — Admin login (public)
/admin — Admin panel (protected)
/admin/users · /admin/users/:userId
/admin/waitlist
- Node.js >= 18, npm >= 9
- A Supabase project (free tier is sufficient for development)
- (Optional) Google Gemini API key, Strava API credentials, Stripe test keys, Resend API key — needed only if you're working on those specific features
git clone <repo-url>
cd TrainingTrack/Frontend
npm installnpm run devThe app is available at http://localhost:5173.
| Command | Description |
|---|---|
npm run dev |
Start development server with HMR |
npm run build |
Production build to /dist |
npm run preview |
Preview the production build locally |
npm run lint |
Run ESLint |
Create a .env file in Frontend/ based on .env.example:
# Supabase (required)
VITE_SUPABASE_URL=https://your-project-id.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key-here
# Optional: Google OAuth Client ID (if using Google login)
# VITE_GOOGLE_CLIENT_ID=your-google-client-idThe client code also reads these variables (not currently listed in .env.example — add them if
you're working on the corresponding feature):
VITE_VAPID_PUBLIC_KEY=... # required for Web Push subscriptions
VITE_GA_TRACKING_ID=G-XXXXXXXXXX # optional, Google Analytics
VITE_SENTRY_DSN=https://... # optional, error trackingDo not commit .env to version control — it is listed in .gitignore.
Set these via the Supabase Dashboard (Settings → Edge Functions → Secrets) or the CLI:
supabase secrets set GEMMA4_API_KEY=... # Gemini API key (AI features)
supabase secrets set STRAVA_CLIENT_ID=...
supabase secrets set STRAVA_CLIENT_SECRET=...
supabase secrets set STRIPE_SECRET_KEY=...
supabase secrets set STRIPE_WEBHOOK_SECRET=...
supabase secrets set RESEND_API_KEY=...
supabase secrets set VAPID_PUBLIC_KEY=...
supabase secrets set VAPID_PRIVATE_KEY=...
supabase secrets set VAPID_SUBJECT=mailto:...Edge Functions live in supabase/functions/ and are written in Deno, deployed to the Supabase
project (lusirdkixfliydimemre).
| Function | Purpose |
|---|---|
admin-api |
Admin operations, protected by the service role |
generate-ai-plan |
Generates a training plan from athlete profile data (Gemini) |
generate-ai-report |
Generates a single-athlete performance report (Gemini) |
weekly-ai-reports |
Scheduled job that runs reports for all athletes weekly (Gemini) |
list-ai-reports |
Lists previously generated AI reports |
athlete-ai-chat |
AI assistant chat endpoint with training context (Gemini) |
analyze-metric-chart |
AI-assisted analysis of a metric chart (Gemini) |
strava-proxy |
Proxies Strava API calls so access tokens stay server-side |
strava-fetch-streams |
Fetches GPS/HR/pace streams for a Strava activity |
strava-webhook |
Receives Strava activity events, auto-completes matching sessions |
stripe-checkout |
Creates a Stripe Checkout session for plan purchase |
stripe-portal |
Creates a Stripe Customer Portal session |
stripe-webhook |
Handles Stripe subscription lifecycle events |
send-email |
Sends transactional email via Resend |
delete-account |
GDPR account deletion: cancels subscription, purges storage, audit-logs, deletes the user |
cleanup-gym-files |
Deletes expired gym file records and storage objects |
_shared |
Shared helpers (CORS, etc.) — not an invokable function |
Key tables in the Supabase PostgreSQL database:
| Table | Description |
|---|---|
users |
Base user accounts (id, email, role, is_admin, is_active) |
coaches / athletes |
Role-specific profiles |
athlete_profile |
Onboarding data used for AI plan generation |
coach_athlete_relationship |
Many-to-many between coaches and athletes |
training_sessions / training_session_exercises |
Assigned sessions and their exercises |
training_plans / mesocycles / microcycles / plan_assignments |
Long-term planning |
competitions |
Races and events (one row per athlete) |
chat_messages |
Coach-athlete messages |
push_subscriptions |
Web Push subscription objects per user/device |
strava_activities / devices |
Cached Strava activity data and OAuth tokens |
gym_files |
PDF metadata for gym strength files (14-day expiry) |
running_exercises_bank / gym_exercises_bank |
Shared exercise libraries |
waitlist |
Pre-launch email signups |
All tables use Row Level Security (RLS). Policies use (select auth.uid()) for performance. The
coach-athlete access pattern is always resolved through coach_athlete_relationship, not a
direct coach_id foreign key on athletes.
- User signs in via Supabase Auth (email/password)
AuthContextloads the user's profile and resolves role, plan, andis_active/is_admin- Layout guards (
DashboardLayout,AthleteDashboardLayout,AdminLayout) enforce role-based access; an additionalIndependentRouteguard gates independent-athlete-only routes - RLS policies enforce data isolation at the database level on every query
The application is deployed on Vercel at trainingtrack.es.
masterbranch is production; auto-deployed on every pushdevbranch is the integration branch; feature branches are merged here first- DNS is managed on Hostinger (A record and CNAME pointing to Vercel)
A
netlify.tomlstill exists in the repo from before the migration to Vercel — it is not used for the current deployment.
| Setting | Value |
|---|---|
| Framework Preset | Vite |
| Build Command | npm run build |
| Output Directory | dist |
| Install Command | npm install |
| Root Directory | Frontend |
Set the environment variables listed above in the Vercel project settings.
Never use date.toISOString().split('T')[0] to format dates for database queries.
toISOString() converts to UTC and shifts the date back by one day in UTC+ timezones (e.g.,
Spain CET/CEST).
Always use toLocalDateStr() from src/lib/dateUtils.js:
import { toLocalDateStr } from '../lib/dateUtils';
const formatted = toLocalDateStr(new Date()); // "2026-03-24" in local timezoneThis project enforces conventions via a pre-commit hook. See AGENTS.md for the full list,
including:
constby default,letonly when reassignment is needed, nevervar- Functional components with hooks only; no class components
- Tailwind CSS utilities only — no inline styles or CSS-in-JS
async/awaitover.then()chains- No
console.login production code - Business logic in services or hooks, not in components
- Lazy loading for all page components
- UI language is Spanish (es-ES)
AGENTS.md also indexes a set of project skills (SDD workflow, issue/PR creation) used by
AI coding agents working in this repo — load the relevant skill before starting a task.
Supabase does not support automatic rollbacks. Migrations are one-way SQL. When a branch touches the database schema, prepare both an "up" and a "down" migration SQL file before applying.
Proprietary — All rights reserved © 2026 TrainingTrack
This software is private property. Reproduction, distribution, or commercial use without explicit written permission from the owner is prohibited.
