BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2,或任何藍牙控制器搭配 MyWhoosh、Rouvy、Zwift 等。可於 iOS、Android、Windows 與 macOS 免費試用。
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 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:
Give / show / do.檢視完整 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.