diff --git a/CLAUDE.md b/CLAUDE.md
index 409646d..e9f5b4a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -190,6 +190,7 @@ week_plan_days
- **Claude API proxy:** `app/api/claude.js` verifies incoming Supabase JWTs via `GET /auth/v1/user`. Requires `ANTHROPIC_API_KEY`, `SUPABASE_URL`, and `SUPABASE_ANON_KEY` as Azure app settings. Use `SUPABASE_ANON_KEY` (no `VITE_` prefix) — the `VITE_` prefix is Vite build-time only and is invisible to the Azure Functions runtime.
- **CI/CD build split:** the frontend is pre-built in the GitHub Actions runner (`npm ci && npm run build` with `VITE_*` in `env:`), then the Azure SWA action uploads `app/dist/` directly (`app_location: "app/dist"`). This bypasses Oryx for the frontend — Oryx strips `VITE_*` env vars before spawning Vite and they never reach the bundle. Oryx still handles the API (`app/api`). `vite.config.js` has a build-time assertion that fails immediately if the required vars are missing.
- **Supabase client explicit apikey header:** `createClient` is called with `global: { headers: { apikey: supabaseKey } }` in `app/src/lib/supabase.js`. The Supabase JS v2 fetch interceptor should add this automatically, but it was not reaching browser requests — passing it in `global.headers` puts it directly on `PostgrestClient`'s base headers, bypassing the interceptor. Do not remove this option.
+- **Multi-instruktør gym membership:** `user_gyms` table links each user to a Sporty business unit (`sporty_business_unit_id`). Primary users are instruktører; sharing default is opt-out scoped to the same gym. `ensureGymMembership(buId)` in `db.js` does an idempotent upsert on sign-in (called in `App.jsx`). The `role` column is a text placeholder (`'instruktor'`) — it will be replaced by a FK to a dedicated temporal `roles` table in a later issue. `DEFAULT_SPORTY_BUSINESS_UNIT_ID = 8` mirrors the hardcoded BU in `sportySync.js`; both must move to a DB config when multi-gym support lands. Backfilled rows exist for both current users.
## Known limitations
- SVG body is improved but still geometrically simplified — not anatomically precise; key muscles (traps, lats) use path shapes, rest are ellipses
diff --git a/app/api/sportySync.js b/app/api/sportySync.js
index 5922e24..d317892 100644
--- a/app/api/sportySync.js
+++ b/app/api/sportySync.js
@@ -1,5 +1,7 @@
import { app } from '@azure/functions';
+// BU 8 is hardcoded for the single-gym MVP (Sporty Thon Senter Ski).
+// When multi-gym support lands, replace with a DB lookup via user_gyms.sporty_business_unit_id.
const SPORTY_BASE_URL =
'https://sporty.no/api/v1/businessunits/8/groupactivities';
diff --git a/app/public/locales/en/translation.json b/app/public/locales/en/translation.json
index 69310dc..17a68aa 100644
--- a/app/public/locales/en/translation.json
+++ b/app/public/locales/en/translation.json
@@ -274,7 +274,10 @@
"language": "Language",
"languageNorwegian": "Norsk",
"languageEnglish": "English",
- "languagePersian": "فارسی"
+ "languagePersian": "فارسی",
+ "myGym": "My gym",
+ "myGymMembership": "Sporty Thon Senter Ski",
+ "myGymFutureHint": "Coming soon: choose your gym and see shared session history with other instructors."
},
"report": {
"heroMuscles_one": "{{count}} muscle",
diff --git a/app/public/locales/fa/translation.json b/app/public/locales/fa/translation.json
index 99ce9d0..5e3fea5 100644
--- a/app/public/locales/fa/translation.json
+++ b/app/public/locales/fa/translation.json
@@ -274,7 +274,10 @@
"language": "زبان",
"languageNorwegian": "Norsk",
"languageEnglish": "English",
- "languagePersian": "فارسی"
+ "languagePersian": "فارسی",
+ "myGym": "باشگاه من",
+ "myGymMembership": "Sporty Thon Senter Ski",
+ "myGymFutureHint": "به زودی: انتخاب باشگاه و مشاهده تاریخچه مشترک با سایر مربیان."
},
"report": {
"heroMuscles_one": "{{count}} عضله",
diff --git a/app/public/locales/nb/translation.json b/app/public/locales/nb/translation.json
index 29e247d..1d9282c 100644
--- a/app/public/locales/nb/translation.json
+++ b/app/public/locales/nb/translation.json
@@ -274,7 +274,10 @@
"language": "Språk",
"languageNorwegian": "Norsk",
"languageEnglish": "English",
- "languagePersian": "فارسی"
+ "languagePersian": "فارسی",
+ "myGym": "Min gym",
+ "myGymMembership": "Sporty Thon Senter Ski",
+ "myGymFutureHint": "Fremtidig: velg gym og se felles økthistorikk med andre instruktører."
},
"report": {
"heroMuscles_one": "{{count}} muskel",
diff --git a/app/src/App.jsx b/app/src/App.jsx
index d1af07b..04977d6 100644
--- a/app/src/App.jsx
+++ b/app/src/App.jsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { supabase } from "./lib/supabase";
+import { ensureGymMembership } from "./lib/db";
import { NavContext } from "./lib/NavContext";
import Login from "./components/Login";
import Home from "./components/Home";
@@ -22,9 +23,13 @@ function App() {
const [reportPrefill, setReportPrefill] = useState(null);
useEffect(() => {
- supabase.auth.getSession().then(({ data: { session } }) => setSession(session));
+ supabase.auth.getSession().then(({ data: { session } }) => {
+ setSession(session);
+ if (session) ensureGymMembership().catch(() => {});
+ });
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
+ if (session) ensureGymMembership().catch(() => {});
});
return () => subscription.unsubscribe();
}, []);
diff --git a/app/src/components/Settings.jsx b/app/src/components/Settings.jsx
index fddaee4..3ba4be5 100644
--- a/app/src/components/Settings.jsx
+++ b/app/src/components/Settings.jsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { Toggle, Button, RadioButtonGroup, RadioButton } from "@carbon/react";
+import { Toggle, Button, RadioButtonGroup, RadioButton, Tag } from "@carbon/react";
import { useTranslation } from "react-i18next";
import PageShell, { SectionLabel, PageHeading } from "./PageShell";
import BodyPanel from "./BodyPanel";
@@ -79,6 +79,23 @@ export default function Settings() {
/>
+
+ {t("settings.myGymFutureHint")} +
+