BikeControl (SwiftControl)
Use Zwift Click, Zwift Play, Shimano Di2, or any Bluetooth controller with MyWhoosh, Rouvy, Zwift, and more. Free to try on iOS, Android, Windows, and macOS.
Pro Placement Player Hub — the private place where a player keeps everything about himself.
Translation pending
Sports & fitnessJavaScript · CSSGitHub @kokkinispanosOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
The private app where a signed player keeps everything about himself: his match footage, and
(from stage 3) his passport, CV, finished highlight reel, measured numbers and documents.
It is not the public page and it is not the portal. Three surfaces, one job each:
Give / show / do.Read the full README ↗The private app where a signed player keeps everything about himself: his match footage, and (from stage 3) his passport, CV, finished highlight reel, measured numbers and documents.
It is not the public page and it is not the portal. Three surfaces, one job each:
| Surface | What it is | Who sees it |
|---|---|---|
| This app — the Player Hub | what the player gives us | the player and admins, nobody else |
proplacement.cloud/p/<slug> — the proof page | what clubs see | anyone with the link |
proplacement.cloud/portal — the campaign portal | what is happening now: DMs to send, club replies, trials | the player |
Give / show / do.
firestore.rules THE SECURITY OF THE APP. Read this before changing anything.
firebase.json So `firebase deploy --only firestore:rules` works if the CLI is installed.
MIGRATION.md The console steps to switch sign-in on and close the database.
rules-tests/ 93 tests that PROVE firestore.rules does what it says. `npm test`.
AUDIT_2026-09-08.md Where this all came from: what was broken and what is planned.
frontend/
dev.mjs Starts Vite with the right working directory (the path has spaces).
src/
services/firebase.js The Firebase handles + the on-device cache that makes it work offline.
services/auth.js WHO the user is. Passwords never come near our database.
services/db.js The player's RECORD. Found by authUid, never by email.
context/AuthContext The signed-in user, from Firebase on every load.
context/PlayerContext The record, kept live with onSnapshot, saved one section at a time.
hooks/useOnlineStatus Whether to tell him he is offline. Never used to decide what to save.
utils/catalog.js Positions, skill categories, photo types. ONE source of truth.
utils/completion.js ONE completion formula + the single "do this next" instruction.
utils/activity.js When he last added something, and when he last just looked.
utils/export.js The link sheet for the editor, and his raw data.
utils/links.js Link tidying and the mistakes we can actually catch.
pages/ Login, SignUp, ForgotPassword, Dashboard, Account, Admin*
utils/hubCatalog.js Every question the hub asks: countries, consents, platforms, slots.
utils/hubCompletion.js What "done" means for the five sections about the player.
utils/nested.js Reading and writing `identity.passportOne.country` safely.
services/files.js The four uploads. Shrunk on the phone, stored in Firestore.
components/dashboard/ The five footage sections + next-step card, phone nav, shell, row
components/hub/ The five sections about the player + the coach's readiness panel
components/ui/ Buttons, inputs, cards, modal, brand marks
cd frontend
npm install
npm run dev
Then http://localhost:5173. (npm run dev and node dev.mjs do the same thing; the second one
works from any directory.)
Build: npm run build. Lint: npm run lint. Deploying the frontend is a push to main —
Vercel builds frontend/.
Rules are not deployed by a push. They live in the Firebase console. See MIGRATION.md.
Firebase Spark, the free one. Nothing in this app needs billing and nothing should be added that does. That rules out Firebase Storage, Cloud Functions and scheduled jobs. If a feature seems to need one of them, look for the version that does not before you look at the invoice.
cd rules-tests
npm install
npm test
Starts the Firestore emulator (needs Java), runs firestore.rules against 93 cases and exits
non-zero if any of them lets something through. Run this before changing the rules and after.
The rules are the only thing standing between a signed-in player and everyone else's data; a
comment saying they work is not evidence.
firestore.rules, not the React code. Any check written only in the
browser is a suggestion. If a player must not be able to do something, the rules must be what
stops him. Before this was true, the whole database was readable by anyone on the internet.updatedAt and profile.lastSeenAt are separate. The first means he added something, the
second that he opened the app. "He looks every day and adds nothing" and "he has vanished" are
different problems and the admin list distinguishes them. Opening the app must never count as
progress.profile.lastSeenAt lives inside profile. It would read better as a top-level field, but
the rules only let a player write the named sections — a new top-level key would be refused.
Nested keeps it legal without redeploying rules.passportOne, passportTwo, cv, headshot, in playerFiles/<uid>__<slot>, one document
each so the coach's list never drags a passport photo along with a row of names.authEmail is not the same field as profile.email, and the difference matters. The
player writes profile.email; authEmail can only be written to the address Firebase says he
is signed in as and has confirmed. Anything that decides who gets access to what — carrying an
old record across, above all — must use authEmail. Matching on the one he can type is how one
player ends up with another player's footage.verifyBeforeUpdateEmail, not updateEmail. The confirmation goes
to the new address, so a typo cannot lock a player out of his own account. It is also the only
variant that still works with email-enumeration protection switched on.Two tabs. My footage is the five sections of links he sends us. About me is the five about him: who he is, his numbers, how we reach him, his finished work, and where he is already online. Ten sections in one scroll is a wall, and a wall is where a sixteen year old on a phone gives up.
Every word he reads is written short and plain, on purpose. Assume it is being read on a phone, at night, by someone tired. No em dashes anywhere in rendered text.
Whoever has a document at admins/{their Firebase Auth uid}. That collection is writable by
nobody — only the Firebase console can create one. There is no admin password and no admin
login screen: an admin signs in like everyone else and the app notices.