尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證BJJ Belt Progress Tracker
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Faixa BJJ keeps one registry for everybody who trains at the gym — students and instructors alike — counts the hours they put on the mat, and tells the head coach who is getting close to their next stripe or belt. It does that one thing and stops there.
It is not a gym management system. No payments, no online enrollment, no competition management. It is meant to sit alongside whatever software the gym already uses for the business side, and cover the part those tools handle badly: technical progression, with as little extra work for the instructor as possible. One deployment hosts many gyms, each isolated from the others.
Generic gym software treats belt rank as a text field. Promotions end up decided by feel, or tracked in a notebook that lives in one person's bag. Faixa BJJ gives the instructor an objective baseline — accumulated hours, time at the current belt, attendance over the last weeks — and then gets out of the way.
[!IMPORTANT] Eligibility is always a suggestion. The app flags who has met the thresholds; the promotion itself is recorded by the head coach, never automatically, and a member can never change their own belt. A member never sees remaining hours, their next grade or a verdict.
/members/criteria) drive a count of eligible members, an
eligibility filter and a marker on each row — one computation, seen three ways./check-in; members
scan it with the phone camera or with the in-app scanner on their dashboard./members/requests./logs. Ids only, no names or emails.| Layer | Choice |
|---|---|
| Frontend | Next.js 16 (App Router), React 19, Tailwind CSS v4 |
| Database | Supabase (Postgres + Auth) |
| Permission | Postgres Row Level Security, driven by the active role and gym |
| Bot check | Cloudflare Turnstile, verified by Supabase Auth |
| Tests | Vitest (pure functions in frontend/utils/), SQL replay in Docker |
| Hosting | Vercel |
There is no API layer on purpose. The browser talks to Supabase directly, so the user's JWT reaches Postgres and RLS is what actually enforces the rules — the UI only hides what the database would refuse anyway.
Requirements: Node.js 20+, a Supabase project, and Docker if you want to replay the migrations locally.
git clone <repo-url>
cd faixabjj/frontend
npm install
cp .env.example .env.local # fill in the values below
npm run dev # http://localhost:3000
| Variable | Required | What it is |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | yes | Project URL |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | yes | Publishable key (the newer key, not the legacy anon one) |
SUPABASE_SECRET_KEY | yes | Service-role key, server-only — account management and the app log need it |
NEXT_PUBLIC_SITE_URL | yes | Absolute deployment URL, no trailing slash — canonical links, sitemap, QR |
NEXT_PUBLIC_TURNSTILE_SITEKEY | yes | Turnstile sitekey; its secret lives in Supabase Auth → Attack Protection |
TURNSTILE_SECRET_KEY | for /join | Server-only; the invite registration verifies its challenge itself |
[!WARNING]
SUPABASE_SECRET_KEYbypasses Row Level Security. It carries noNEXT_PUBLIC_prefix on purpose and must never reach the browser.
Apply the SQL in supabase/migrations/ in filename order, either with the
Supabase CLI or by pasting each file into the project's SQL Editor. Every
migration is safe to run twice. Some must be applied before or after deploying
the matching app version — docs/claude/database.md says which.
To check the whole history locally, in a throwaway Postgres:
bash supabase/tests/replay.sh --isolation # from the repo root, Docker running
It applies every migration twice and runs the tenant-isolation checks. Never
point anything in supabase/tests/ at a real project.
After the migrations nobody has a privilege. Grants are made by hand, on purpose:
supabase/scripts/bootstrap-platform-admin.sql in the SQL Editor (adjust the
email inside)./gyms — timezone, go-live date
and location included — and add its manager./members.[!NOTE] Set each gym's go-live date to the day it really starts recording attendance. Hours before it are estimated, hours from it on are counted; a date in the future counts the same weeks twice.
All of these run from frontend/:
npm run dev # dev server (Turbopack)
npm run build # production build
npm run start # serve the production build
npm run lint # ESLint
npm test # Vitest, single run
npm run test:watch
Roles come from an active assigned_role row and decide what a person can
reach inside their gym:
| Role | Members list | Courses / Attendance | Edit registry | Manage accounts | Promote |
|---|---|---|---|---|---|
student | — | Attendance only, check-in for self | — | — | — |
assistant | — | Attendance only, check-in for self | — | — | — |
instructor | read-only | full | — | — | — |
head_coach | full | full | yes | yes | yes |
admin | full | full | yes | yes | — |
admin means "runs the portal without teaching": a portal-only admin appears in
no people list and shows no belt. The platform superadmin sits outside every
gym: it reaches only the platform dashboard, /gyms, /logs and its own
account.
Each privilege is one SQL predicate, called by the frontend rather than reimplemented in it, and enforced three times over: the nav hides the link, the page checks the predicate and answers 404, and RLS refuses the query. Hiding a nav link is never the access control.
faixabjj/
├── frontend/ # Next.js app (App Router) — run every command from here
│ ├── app/ # routes: /members, /courses, /attendance, /check-in, /dashboard,
│ │ # /account, /gym, /gyms, /logs, /join, public pages
│ ├── components/ # shell, belt graphics, hand-rolled icons, no UI dependency
│ └── utils/ # pure helpers (unit-tested), Supabase clients, i18n dictionaries
├── supabase/
│ ├── migrations/ # schema, RLS policies, views, functions — plain SQL, in order
│ ├── scripts/ # hand-run SQL (bootstrap, diagnostics), not part of history
│ └── tests/ # Docker replay and tenant-isolation checks
├── docs/
│ ├── claude/ # detailed guidance per area (database, access, members, …)
│ ├── superpowers/ # design specs and implementation plans
│ └── handoff/ # session handoffs: what was decided and why
├── directives/ # SOPs (see AGENT.md)
├── execution/ # deterministic scripts (see AGENT.md)
├── AGENT.md # the 3-tier agent architecture this repo follows
├── CLAUDE.md # working notes for contributors and coding agents
├── belt-criteria.md # promotion criteria source (IBJJF + academy practice)
└── brand-guidelines.md # colours and typography
The app runs against real data end to end: registry, promotions and eligibility, courses, attendance with geofenced and QR check-in, invite registration, dashboards, multi-gym administration and three languages. What is missing before a gym relies on it is the pilot itself.