a3798b3059
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>
35 lines
1.1 KiB
TypeScript
35 lines
1.1 KiB
TypeScript
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 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>
|
|
</div>
|
|
);
|
|
}
|