{"id":51118613,"url":"https://github.com/codee-sh/payload-training-app","last_synced_at":"2026-06-25T00:01:02.509Z","repository":{"id":364803616,"uuid":"1269245319","full_name":"codee-sh/payload-training-app","owner":"codee-sh","description":"Open-source coaching app built with Payload CMS and Next.js. Coaches manage training plans in the admin; clients log workouts on mobile.","archived":false,"fork":false,"pushed_at":"2026-06-22T12:43:46.000Z","size":2280,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-22T13:25:42.115Z","etag":null,"topics":["coaching-app","fitness-app","nextjs","opensource","payload","payloadcms","postgres","postgresql","training-plan","typescript","workout-tracker"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codee-sh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-14T13:31:39.000Z","updated_at":"2026-06-22T12:14:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/codee-sh/payload-training-app","commit_stats":null,"previous_names":["codee-sh/payload-training-app"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/codee-sh/payload-training-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codee-sh%2Fpayload-training-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codee-sh%2Fpayload-training-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codee-sh%2Fpayload-training-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codee-sh%2Fpayload-training-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codee-sh","download_url":"https://codeload.github.com/codee-sh/payload-training-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codee-sh%2Fpayload-training-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34753781,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coaching-app","fitness-app","nextjs","opensource","payload","payloadcms","postgres","postgresql","training-plan","typescript","workout-tracker"],"created_at":"2026-06-25T00:01:01.537Z","updated_at":"2026-06-25T00:01:02.497Z","avatar_url":"https://github.com/codee-sh.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Training App\n\nA coach-facing admin and client-facing training tracker built with Payload CMS and Next.js. Coaches build workout plans in the admin panel; clients log their sets through a mobile-friendly web interface.\n\n## What it does\n\n**Coach (admin panel)** — creates plans, assigns them to clients, defines workout structure down to individual exercise rows with target sets and tracking parameters.\n\n**Client (web app)** — logs in, sees their active plan, works through workouts session by session, and logs each set (reps, weight, RIR, time, etc.).\n\n## Navigation\n- [Data model \u0026 flow](#data-model--flow)\n- [Tech stack](#tech-stack)\n- [Getting started](#getting-started)\n- [Project structure](#project-structure)\n- [Key scripts](#key-scripts)\n- [Development](#development)\n- [Screenshots](#screenshots)\n\n## Data model \u0026 flow\n\nThe data splits into two layers. The **plan layer** is the template a coach authors (read-only for clients). The **log layer** is what a client records while training. The two never mix: logging never writes to the plan, so the same template can be reused and audited.\n\n```\nPLAN LAYER (authored by coach)              LOG LAYER (recorded by client)\n─────────────────────────────              ──────────────────────────────\nPlan                                        WorkoutLog ............ one training session\n└─ Microcycle                               ├─ SetLog ............. one logged set       (N per exercise)\n   └─ Workout                               ├─ ExerciseLog ........ one note per exercise (1 per exercise)\n      ├─ sections[]   \"Warm-up\", \"Main\"     └─ RoundLog ........... one round of a group  (reserved)\n      └─ WorkoutGroup    → in a section\n         └─ WorkoutExerciseRow              Catalog:  Exercise, Media\n            └─ Exercise  (catalog, opt.)    Accounts: User (coach), Client (athlete)\n                                            Sharing:  ShareLink\n```\n\n\u003e `sections[]` is **not a collection** — it's an array of named headings (`title`/`subtitle`) stored on the `Workout`. A `WorkoutGroup` attaches to one section via its `sectionRowId`. So the visible nesting in the app is **Workout → Section → Group → Exercise row**, while in the database a group points back to its section by id.\n\n### How a log row points back to the plan\n\nA `WorkoutLog` references the `Workout` it belongs to and the `Client` who owns it. Each `SetLog` / `ExerciseLog` references its `session` (the `WorkoutLog`) **and** the `exerciseRow` (`WorkoutExerciseRow`) it logs against — that `exerciseRow` link is the key that ties execution back to the exact line in the plan.\n\n```mermaid\nerDiagram\n    CLIENT ||--o{ PLAN : owns\n    PLAN ||--o{ MICROCYCLE : has\n    MICROCYCLE ||--o{ WORKOUT : has\n    WORKOUT ||--o{ WORKOUT_GROUP : has\n    WORKOUT_GROUP ||--o{ WORKOUT_EXERCISE_ROW : has\n    EXERCISE ||--o{ WORKOUT_EXERCISE_ROW : \"referenced by\"\n\n    CLIENT ||--o{ WORKOUT_LOG : logs\n    WORKOUT ||--o{ WORKOUT_LOG : \"session of\"\n    WORKOUT_LOG ||--o{ SET_LOG : has\n    WORKOUT_LOG ||--o{ EXERCISE_LOG : has\n    WORKOUT_LOG ||--o{ ROUND_LOG : has\n    WORKOUT_EXERCISE_ROW ||--o{ SET_LOG : \"logged as (N per session)\"\n    WORKOUT_EXERCISE_ROW ||--o{ EXERCISE_LOG : \"noted as (1 per session)\"\n    WORKOUT_GROUP ||--o{ ROUND_LOG : \"round of\"\n\n    PLAN ||--o{ SHARE_LINK : \"shared via\"\n\n    PLAN {\n        relationship client\n        select status\n    }\n    WORKOUT {\n        relationship microcycle\n        array sections \"named headings — Group.sectionRowId points here\"\n    }\n    WORKOUT_GROUP {\n        relationship workout\n        text sectionRowId \"which section it belongs to\"\n        select protocol \"standard / emom / amrap / for_time / tabata\"\n    }\n    WORKOUT_EXERCISE_ROW {\n        relationship group\n        relationship exercise \"catalog link (optional)\"\n        text targets \"reps, kg, rir, tut, rest…\"\n    }\n    WORKOUT_LOG {\n        relationship client\n        relationship workout\n    }\n    SET_LOG {\n        relationship session\n        relationship exerciseRow\n        number setNumber\n    }\n    EXERCISE_LOG {\n        relationship session\n        relationship exerciseRow\n        textarea note\n    }\n    ROUND_LOG {\n        relationship session\n        relationship group\n        number roundNumber \"reserved — not yet written\"\n    }\n    SHARE_LINK {\n        relationship plan\n        select permissions \"plan / results\"\n        date expiresAt\n    }\n```\n\n### Collections\n\n#### Accounts\n\n| Collection | Auth | Purpose |\n|---|---|---|\n| `users` | ✅ | Coaches / staff. The only accounts allowed into `/admin`. |\n| `clients` | ✅ | Athletes. Log in to the client web app (never the admin). Holds `name`, a join to their `plans`, and admin-only `notes` (trainer notes, hidden from the client). 2h sessions, lockout after 5 failed logins. |\n\n#### Catalog\n\n| Collection | Purpose |\n|---|---|\n| `exercises` | Reusable exercise catalog (name, muscle group, equipment, video, description). `trackingType` decides **which metric fields** the client sees in the logging form (e.g. weight+reps vs distance+time). Readable by any authenticated user; only coaches edit. |\n| `media` | Image uploads (public read). |\n\n#### Training plan (template — coach writes, client reads)\n\n| Collection | Belongs to | Purpose |\n|---|---|---|\n| `plans` | a `client` | Top-level program. Status (active/paused/completed), date range, title, description. Versioned (audit trail). Client can read only their own. |\n| `microcycles` | `plan` | A block/week within the plan. Target `rpe`, `order`. |\n| `workouts` | `microcycle` | A single training day. Has an `order`, optional `rpe`, and `sections[]` (named blocks like \"Warm-up\", \"Main part\"). Edited via a custom **Structure** admin tab. Cannot be deleted once it has logged sessions. |\n| `workout-groups` | `workout` | A group of exercises sharing a `protocol` (Standard / EMOM / AMRAP / For Time / Tabata) and its parameters (rounds, durations, rest). Links to a workout section via `sectionRowId`. E.g. an \"A1/A2 superset\". Cannot be deleted if its rows have logged sets. |\n| `workout-exercise-rows` | `workout-group` | One prescribed exercise line. Optional link to a catalog `exercise`, plus targets (`reps`, `kg`, `tut`, `rir`, `rest`, duration), a plan `note`, optional per-set `setParameters[]` (drop sets/pyramids), and an `override` of the group protocol. Cannot be deleted if it has logged sets. |\n\n#### Training log (execution — client writes)\n\n| Collection | Keyed by | Cardinality | Purpose |\n|---|---|---|---|\n| `workout-logs` | `client` + `workout` | one per session | A training session. Auto-titled, holds `startedAt` / `finishedAt` and general session `notes`. Creating the first set auto-creates the session. |\n| `set-logs` | `session` + `exerciseRow` (+ `setNumber`) | **N per exercise** | One logged set: weight, reps, RIR, distance, duration, bodyweight flag, per-set `note`. A `beforeValidate` hook strips any metric not allowed by the exercise's `trackingType`. |\n| `exercise-logs` | `session` + `exerciseRow` | **1 per exercise** | A single client note for the whole exercise in that session (vs. `set-logs`, which is per set). Same relations as `set-logs` so it can grow beyond a note later. Upserted from the tracker. |\n| `round-logs` | `session` + `group` | one per round | Per-round execution of a group (round number, status, timing). **Reserved** — defined in the schema but not yet written by the app. |\n\nFor every log collection: a client may only create/read/update/delete **their own** rows (`adminOrOwnByClient`), and the owning `client` is always set server-side from the session — never trusted from the request.\n\n#### Sharing\n\n| Collection | Purpose |\n|---|---|\n| `share-links` | A tokenized, read-only link to a `plan`. `permissions` choose what is exposed (`plan` preview and/or `results` logs); `expiresAt` + `active` gate it. Only coaches manage links; the public access happens via the `share-token` cookie, which `canReadViaShareToken` validates to scope reads to that plan owner's data. |\n\n### End-to-end flow\n\n1. **Author** — coach creates a `Client`, then a `Plan` → `Microcycle` → `Workout`, and builds structure (`WorkoutGroup` → `WorkoutExerciseRow`) on the workout's **Structure** tab, linking each row to a catalog `Exercise`.\n2. **Assign** — the plan is owned by the client; they log in and see only their own active plan.\n3. **Train** — opening a workout creates a `WorkoutLog` on first save. The client logs each set as a `SetLog` (fields driven by the exercise's `trackingType`) and can attach one `ExerciseLog` note per exercise.\n4. **Review** — coach reads the client's logs in the admin; deleting plan structure that already has logs is blocked to protect history.\n5. **Share** (optional) — a `ShareLink` exposes a read-only plan and/or results to anyone with the link until it expires.\n\n## Tech stack\n\n| Layer | Technology |\n|---|---|\n| Framework | Next.js 15 (App Router) |\n| CMS / Auth | Payload CMS 3 |\n| Database | PostgreSQL (`@payloadcms/db-postgres`) |\n| Styling | Tailwind CSS |\n| i18n | next-intl (Polish / English) |\n| Forms | react-hook-form |\n| Icons | lucide-react |\n\n## Getting started\n\n### Requirements\n\n- Node.js ≥ 24\n- Yarn 4 (`corepack enable`)\n- PostgreSQL database\n\n### Setup\n\n```bash\ngit clone \u003crepo-url\u003e\ncd training-app\nyarn install\ncp .env.example .env\n```\n\nEdit `.env`:\n\n```env\nDATABASE_URL=postgresql://user:password@localhost:5432/training_app\nPAYLOAD_SECRET=your-long-random-secret-here\n```\n\nRun migrations:\n\n```bash\nyarn payload migrate\n```\n\n### Run\n\n```bash\nyarn dev\n```\n\n- App: `http://localhost:3000/pl`\n- Admin panel: `http://localhost:3000/admin`\n\n### First-time setup\n\n1. Open `/admin` and create the first user account — this becomes the super-admin\n2. (Optional) seed demo data: `yarn seed`\n3. Create a **Client** record for each athlete\n4. Build a **Plan**, link microcycles → workouts → exercise rows\n5. The client logs in at `/pl` using their email and password set in the admin\n\n## Project structure\n\n```\nsrc/\n├── access/                    # Shared access control functions (isAdmin, isAuthenticated…)\n├── app/\n│   ├── [locale]/(frontend)/   # Client-facing app (login, workout tracker)\n│   └── (payload)/             # Payload admin routes and API\n├── collections/               # Payload collection configs (one folder per collection)\n│   ├── clients/\n│   ├── exercises/\n│   ├── plans/\n│   ├── microcycles/\n│   ├── workouts/\n│   ├── workout-groups/\n│   ├── workout-exercise-rows/\n│   ├── workout-logs/\n│   ├── set-logs/\n│   ├── exercise-logs/\n│   ├── round-logs/\n│   ├── share-links/\n│   ├── media/\n│   └── users/\n├── components/\n│   ├── common/                # App-wide UI (logout button…)\n│   ├── ui/                    # Primitive components (button, input, surface…)\n│   └── workout/               # Workout tracker components\n├── data/                      # Static/seed data\n├── i18n/                      # next-intl routing and request config\n├── lib/                       # Shared utilities and SDK client\n├── loaders/                   # Server-side data fetching functions\n├── migrations/                # Payload database migrations\n├── scripts/                   # One-off CLI scripts (seed, import-plan…)\n├── types/                     # Shared TypeScript types\n├── middleware.ts\n├── payload-types.ts           # Auto-generated — do not edit manually\n└── payload.config.ts\n.claude/skills/                # AI skills for Claude Code\n.agents/skills/                # AI skills for Codex\n.ai/specs/                     # Feature specifications\n```\n\n## Key scripts\n\n| Script | Description |\n|---|---|\n| `yarn dev` | Start dev server |\n| `yarn build` | Production build |\n| `yarn start` | Start production server |\n| `yarn payload migrate` | Run pending database migrations |\n| `yarn generate:types` | Regenerate `payload-types.ts` from collection configs |\n| `yarn generate:importmap` | Regenerate Payload admin import map (run after adding custom views) |\n| `yarn seed` | Seed database with demo data |\n| `yarn seed:export` | Export current database state to seed file |\n| `yarn lint` | Run ESLint |\n| `npx skills add \u003csource\u003e` | Install AI skills into `.claude/skills/` and `.agents/skills/` |\n\n## Development\n\n### Adding a collection\n\nFollow `.ai/skills/payload-build-collections` — each collection lives in `src/collections/{kebab-case}/index.ts` and is registered in `src/collections/index.ts`.\n\nAfter changing collection configs, regenerate types:\n\n```bash\nyarn generate:types\n```\n\n### Adding an admin view or custom field UI\n\nFollow `.ai/skills/payload-build-modules`. After registering a new component path, run:\n\n```bash\nyarn generate:importmap\n```\n\n### AI skills\n\nThis project uses skill files for AI-assisted development. Skills are managed with [npx skills](https://github.com/vercel-labs/skills) and installed into `.claude/skills/` (Claude Code) and `.agents/skills/` (Codex).\n\nTo install skills from the source repository:\n\n```bash\nnpx skills add \u003csource-path-or-url\u003e -a claude-code -a codex --copy\n```\n\nSkills cover: Payload patterns, collection scaffolding, admin module structure, UI copy, and spec writing.\n\n## Screenshots\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/mockup-1.png\" alt=\"Training App mockup\" width=\"420\" /\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodee-sh%2Fpayload-training-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodee-sh%2Fpayload-training-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodee-sh%2Fpayload-training-app/lists"}