feat: show tenancy scope (Org → Chapter) in chapter & member portals

Surface where the logged-in user sits in the hierarchy via a ScopeCard
in the sidebar, below the brand mark.

API:
- Enrich chapter login/me responses with tenantName + organization
  (LoginResponse.admin, AdminProfile types updated)
- Add GET /my/scope returning member's chapter + organization

Web:
- ScopeCard component (Organisation row + Chapter row, themed icons)
- Chapter portal: scope from auth context, blue accent
- Member portal: scope fetched server-side in layout, emerald accent
- PortalShell gains a `scope` slot rendered under the brand

Org and admin portals intentionally omit it — org IS the scope, admin is
platform-wide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 18:27:23 +05:30
parent 205418fc4e
commit a3798b3059
10 changed files with 131 additions and 8 deletions
+19 -2
View File
@@ -1,14 +1,31 @@
import { getMemberToken } from '../_lib/api';
import { getMemberToken, getApiBaseUrl } from '../_lib/api';
import { redirect } from 'next/navigation';
import { MemberNav } from './member-nav';
interface MemberScope {
member: { displayName: string | null };
chapter: { id: string; slug: string; name: string };
organization: { id: string; slug: string; name: string } | null;
}
async function fetchScope(token: string): Promise<MemberScope | null> {
const res = await fetch(`${getApiBaseUrl()}/my/scope`, {
headers: { Authorization: `Bearer ${token}` },
cache: 'no-store',
}).catch(() => null);
if (!res || !res.ok) return null;
return (await res.json()) as MemberScope;
}
export default async function MemberLayout({ children }: { children: React.ReactNode }) {
const token = await getMemberToken();
if (!token) redirect('/member-login');
const scope = await fetchScope(token);
return (
<div className="flex h-screen overflow-hidden">
<MemberNav />
<MemberNav scope={scope} />
<main className="flex-1 overflow-auto bg-muted/30">
<div className="p-6 lg:p-8 max-w-6xl mx-auto w-full">{children}</div>
</main>