From da7ca20228249c3521a956f3f95fada50d71fdc8 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Wed, 8 Apr 2026 11:41:56 -0400 Subject: [PATCH] v0.0.2 new updated to the vibecode-prompt --- CLAUDE.md | 86 +++++++++++--- package.json | 2 +- src/App.jsx | 26 +++++ src/components/Footer.jsx | 21 ++++ src/components/Nav.jsx | 76 +++++++++++- src/data/seedData.js | 31 ++++- src/hooks/useStore.jsx | 54 ++++++++- src/pages/AdminReports.jsx | 30 +++++ src/pages/AdminSettings.jsx | 30 +++++ src/pages/AdminUsers.jsx | 30 +++++ src/pages/BulkUpload.jsx | 29 +++++ src/pages/ClientContacts.jsx | 29 +++++ src/pages/Dashboard.jsx | 218 +++++++++++++++++++++++++++++++++++ src/pages/Login.jsx | 113 ++++++++++++++++++ src/pages/ManageAthletes.jsx | 29 +++++ src/pages/ManageTeams.jsx | 29 +++++ src/pages/MyClients.jsx | 29 +++++ src/pages/Register.jsx | 61 ++-------- src/pages/StatsEntry.jsx | 29 +++++ src/pages/TeamRoster.jsx | 29 +++++ src/version.js | 2 + vibecode-prompt.md | 39 +++++-- 22 files changed, 939 insertions(+), 83 deletions(-) create mode 100644 src/components/Footer.jsx create mode 100644 src/pages/AdminReports.jsx create mode 100644 src/pages/AdminSettings.jsx create mode 100644 src/pages/AdminUsers.jsx create mode 100644 src/pages/BulkUpload.jsx create mode 100644 src/pages/ClientContacts.jsx create mode 100644 src/pages/Dashboard.jsx create mode 100644 src/pages/Login.jsx create mode 100644 src/pages/ManageAthletes.jsx create mode 100644 src/pages/ManageTeams.jsx create mode 100644 src/pages/MyClients.jsx create mode 100644 src/pages/StatsEntry.jsx create mode 100644 src/pages/TeamRoster.jsx create mode 100644 src/version.js diff --git a/CLAUDE.md b/CLAUDE.md index 5e7a20b..646240a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ -# CLAUDE.md — PlayersEdge +# CLAUDE.md — StatSphere This file tells Claude how to work in this codebase. Read it fully before making any changes. @@ -6,13 +6,25 @@ This file tells Claude how to work in this codebase. Read it fully before making ## Current version -- `Version:` 0.0.1 +- `Version:` 0.0.2 + +### Bumping the version + +When releasing a new version, update **all three** of these together — they must stay in sync: + +1. `src/version.js` — `export const APP_VERSION = 'X.Y.Z';` +2. `package.json` — `"version": "X.Y.Z"` +3. This file — `Version:` line above + +The version is displayed in the app as a fixed bottom-right label via `src/components/Footer.jsx`. --- ## What This Project Is -PlayersEdge is a client-side Progressive Web App for tracking athlete biometric and sport-specific statistics. It supports five sports (American Football, Hockey, Baseball, Soccer, Basketball) with multi-sport athlete profiles. There is no backend, no database, and no API — all data lives in `localStorage`. The app is built with React 18 + Vite and deployed as static files behind Nginx on Ubuntu 24.04. +StatSphere is a Progressive Web App for tracking athlete biometric and sport-specific statistics. It supports five sports (American Football, Hockey, Baseball, Soccer, Basketball) with multi-sport athlete profiles. The app is built with React 18 + Vite and deployed as static files behind Nginx on Ubuntu 24.04. + +**Current architecture:** All data lives in `localStorage` — there is no backend, no database, no API, and no server-side email or scraping. Authentication is simulated client-side. This is an intermediate state; the long-term direction (per `vibecode-prompt.md`) adds Postgres, real auth, per-match stats entry, email validation, CSV upload, URL scraping, and role-based access control. Do not add those backend features unless explicitly asked — build against the current localStorage architecture. --- @@ -35,20 +47,25 @@ There are no tests. There is no linter config. There is no TypeScript. All app state flows through a single React Context defined in `src/hooks/useStore.jsx`. - `StoreProvider` wraps the entire app in `App.jsx` -- Data is loaded from `localStorage` key `PlayersEdge_users_v1` on first render; falls back to `SEED_USERS` from `seedData.js` if the key is absent or unparseable -- Every change to `users` is auto-saved to `localStorage` via a `useEffect` -- There is no per-session state beyond what's in localStorage — refreshing the page preserves all data +- Users are loaded from `localStorage` key `statsphere_users_v1` on first render; falls back to `SEED_USERS` from `seedData.js` if the key is absent or unparseable +- Auth state is loaded from `localStorage` key `statsphere_auth_v1` +- Every change to `users` or `auth` is auto-saved to `localStorage` via `useEffect` **Do not use component-level state for anything that needs to persist.** Route it through `useStore`. **The store API:** ```js -const { users, addUser, updateUser, deleteUser, getUserById, getUsersBySport, resetToSeed } = useStore(); +const { users, auth, login, logout, register, addUser, updateUser, deleteUser, getUserById, getUsersBySport, resetToSeed } = useStore(); ``` -- `addUser(user)` — generates `id` (Date.now()) and `joinDate` automatically +- `auth` — `{ currentUser: User|null, isLoggedIn: Boolean }` +- `login(email, password)` — returns `{ success, user }` or `{ success: false, error }`; sets auth state on success +- `logout()` — clears auth state +- `register(userData)` — checks for duplicate email, calls `addUser`, returns `{ success, user }` or `{ success: false, error }` +- `addUser(user)` — generates `id` (Date.now()) and `joinDate` automatically; returns new user - `updateUser(id, updates)` — shallow-merges updates into the matching user - `deleteUser(id)` — removes by id +- `getUserById(id)` — returns single user or undefined - `getUsersBySport(sport)` — filters `users` where `u.sports.includes(sport)` - `resetToSeed()` — replaces all users with `SEED_USERS` @@ -58,19 +75,34 @@ React Router v6. All routes are defined once in `App.jsx`. The Nginx config uses ``` / → Home.jsx -/leaders → Leaders.jsx (accepts ?sport= query param) +/leaders → Leaders.jsx (accepts ?sport= query param) /filter → Filter.jsx /athletes → Athletes.jsx /athletes/:id → AthleteDetail.jsx -/register → Register.jsx (accepts ?edit={id} query param) +/register → Register.jsx (accepts ?edit={id} query param) +/login → Login.jsx (redirects to /dashboard if already logged in) +/dashboard → Dashboard.jsx (redirects to /login if not authenticated; role-aware content) + +— auth-protected placeholder routes (coming soon) — +/stats-entry → StatsEntry.jsx +/manage-athletes → ManageAthletes.jsx +/bulk-upload → BulkUpload.jsx +/manage-teams → ManageTeams.jsx +/team-roster → TeamRoster.jsx +/my-clients → MyClients.jsx +/client-contacts → ClientContacts.jsx +/admin/users → AdminUsers.jsx (administrator only) +/admin/settings → AdminSettings.jsx (administrator only) +/admin/reports → AdminReports.jsx (administrator only) ``` ### Data Layer `src/data/seedData.js` is the single source of truth for: +- `USER_ROLES` — role registry object: `athlete`, `manager`, `team_manager`, `agent`, `administrator` - `SPORTS` — sport registry object - `SPORT_STATS_DEFS` — stat definitions per sport (key, label, type) -- `BIOMETRIC_FIELDS` — 12 biometric field definitions +- `BIOMETRIC_FIELDS` — 13 biometric field definitions (includes both `DOB` date field and `age` number field) - `SEED_USERS` — 125 generated fake athletes - `getUsersBySport(sport)` — exported helper (also re-implemented in the store) @@ -93,6 +125,8 @@ Stat definition shape: ```js { id: String, // Date.now() string for new users; '101'–'225' for seed users + role: String, // 'athlete' | 'manager' | 'team_manager' | 'agent' | 'administrator' + password: String, // plain text for now — no hashing in localStorage implementation firstName: String, lastName: String, name: String, // firstName + ' ' + lastName — must be kept in sync @@ -112,8 +146,10 @@ Stat definition shape: primarySport: String, // one of the SPORTS keys sports: String[], // array of sport keys — always contains primarySport biometrics: { - height_cm, weight_kg, age, reach_cm, - dominant_hand, dominant_foot, + height_cm, weight_kg, + DOB: String, // date of birth, YYYY-MM-DD format + age: Number, // computed from DOB at generation time — kept for display convenience + reach_cm, dominant_hand, dominant_foot, body_fat_pct, vo2_max, vertical_jump_cm, '40_yard_dash', bench_press_reps, years_pro }, @@ -123,6 +159,7 @@ Stat definition shape: // only present for sports in the user's sports array }, joinDate: String, // YYYY-MM-DD + lastLogin: String, // ISO datetime string, set on login } ``` @@ -302,7 +339,7 @@ If the stat should appear in seed data, also update the relevant `gen{Sport}Stat The PWA is configured in `vite.config.js` via `vite-plugin-pwa`. The service worker is auto-generated by Workbox and caches all `{js,css,html,ico,png,svg,woff2}` files. `registerType: 'autoUpdate'` means the SW updates silently on rebuild. -The install prompt (`PWABanner.jsx`) listens for `beforeinstallprompt`. Once dismissed, it sets `localStorage.getItem('pwa_dismissed')` and never shows again. This is separate from `PlayersEdge_users_v1`. +The install prompt (`PWABanner.jsx`) listens for `beforeinstallprompt`. Once dismissed, it sets `localStorage.getItem('pwa_dismissed')` and never shows again. This is separate from `statsphere_users_v1` and `statsphere_auth_v1`. PWA icons (`public/icon-192.png`, `public/icon-512.png`, `public/apple-touch-icon.png`) are placeholder PNGs. Replace them with real images for production. @@ -323,14 +360,33 @@ npm run build # dist/ is ready — rsync or cp to web root ``` +### When to rerun install.sh + +**You do not need to rerun `install.sh` for normal code changes.** Only rerun it when: + +| Change | Action required | +|---|---| +| New package added to `dependencies` or `devDependencies` in `package.json` | `npm install` on the server (or full `install.sh` rerun) | +| Node.js version requirement changes | Full `install.sh` rerun | +| Nginx config changes | Full `install.sh` rerun (or manually edit `/etc/nginx/sites-available/statsphere`) | +| New system-level dependency (e.g. ImageMagick, a native Node addon) | Full `install.sh` rerun | + +**Claude will flag this:** Any time a task adds or removes a package from `package.json`, Claude will explicitly note that `npm install` (or `install.sh`) must be rerun on the server before the next deployment. + --- ## Known Limitations / Things to Be Aware Of - **Profile images are base64 in localStorage.** Large images can approach the ~5MB localStorage limit. There is no file size validation. -- **No authentication.** Any visitor can edit or delete any athlete. +- **Passwords are stored plain text.** The localStorage auth implementation has no hashing. This is intentional for the current client-side-only phase. +- **Auth is client-side only.** Anyone who clears localStorage or inspects it can bypass auth. This is a known limitation of the localStorage architecture. - **`40_yard_dash` key has a leading digit.** Access it as `biometrics['40_yard_dash']`, not `biometrics.40_yard_dash`. This is intentional — the label reads naturally in the UI. - **`user.name` is not auto-derived.** When updating `firstName` or `lastName`, also update `name`. The `addUser` function handles this; `updateUser` does not — it takes whatever you pass. +- **`user.age` and `user.biometrics.DOB` both exist.** `DOB` is the canonical date of birth (YYYY-MM-DD); `age` is derived from it at seed-generation time. Display `DOB` for logged-in users where personal details are shown; display `age` (or compute from DOB) for public views. - **Seed data is regenerated randomly on every `npm run build`** (if localStorage is cleared). The randomness is seeded by `Math.random()` with no fixed seed. This is intentional — seed data is fake and disposable. - **The Leaders table shows the first 10 numeric stats** for the selected sport. If a sport has stats that should be prioritised in the table, order them first in `SPORT_STATS_DEFS` (after the `position` text field). - **`fmtVal` in Leaders shows `—` for `0`** — this is a display choice, not a bug. Stats that are genuinely 0 (e.g. a QB's sack count as a defender stat) display as `—` to reduce noise. +- **Dashboard placeholder routes exist but are not yet implemented.** `/stats-entry`, `/manage-athletes`, `/bulk-upload`, `/manage-teams`, `/team-roster`, `/my-clients`, `/client-contacts`, `/admin/users`, `/admin/settings`, `/admin/reports` all have stub pages that show "Coming soon." They are the starting point for the features described in `vibecode-prompt.md`. +- **Sport stats are not collected at registration.** The Register wizard (Personal Info → Biometrics → Review) skips sport stats. Athletes add stats after account setup via `/stats-entry`. +- **Football positions** in `seedData.js` (internal `positions` object): `QB, RB, WR, TE, FB, OC, OG, OT, DE, DT, LB, CB, S, P, K`. Register.jsx does not have its own positions object — sport stats are entered post-registration. +- **Hockey positions** in `seedData.js`: `C, LW, RW, RD, LD, G`. diff --git a/package.json b/package.json index 8e344c5..a632402 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "statsphere", "private": true, - "version": "1.0.0", + "version": "0.0.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.jsx b/src/App.jsx index 123d34f..f608b09 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,12 +2,25 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { StoreProvider } from './hooks/useStore.jsx'; import Nav from './components/Nav.jsx'; import PWABanner from './components/PWABanner.jsx'; +import Footer from './components/Footer.jsx'; import Home from './pages/Home.jsx'; import Leaders from './pages/Leaders.jsx'; import Filter from './pages/Filter.jsx'; import Athletes from './pages/Athletes.jsx'; import AthleteDetail from './pages/AthleteDetail.jsx'; import Register from './pages/Register.jsx'; +import Login from './pages/Login.jsx'; +import Dashboard from './pages/Dashboard.jsx'; +import StatsEntry from './pages/StatsEntry.jsx'; +import ManageAthletes from './pages/ManageAthletes.jsx'; +import BulkUpload from './pages/BulkUpload.jsx'; +import ManageTeams from './pages/ManageTeams.jsx'; +import TeamRoster from './pages/TeamRoster.jsx'; +import MyClients from './pages/MyClients.jsx'; +import ClientContacts from './pages/ClientContacts.jsx'; +import AdminUsers from './pages/AdminUsers.jsx'; +import AdminSettings from './pages/AdminSettings.jsx'; +import AdminReports from './pages/AdminReports.jsx'; export default function App() { return ( @@ -21,8 +34,21 @@ export default function App() { } /> } /> } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +