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() {