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
+40
View File
@@ -0,0 +1,40 @@
import { Building2, Network } from 'lucide-react';
import { cn } from '../_lib/utils';
interface ScopeCardProps {
organization?: { name: string } | null;
chapter?: { name: string } | null;
/** Tailwind text color for the icons, e.g. text-blue-600 */
accent?: string;
}
/**
* Shows the tenancy scope (Organisation → Chapter) the current user
* is operating within. Rendered in a portal sidebar below the brand.
*/
export function ScopeCard({ organization, chapter, accent = 'text-muted-foreground' }: ScopeCardProps) {
if (!organization && !chapter) return null;
return (
<div className="mx-3 mt-3 rounded-lg border bg-card divide-y">
{organization && (
<div className="flex items-center gap-2.5 px-3 py-2">
<Network className={cn('size-4 shrink-0', accent)} />
<div className="min-w-0">
<p className="text-[10px] uppercase tracking-wide text-muted-foreground leading-none">Organisation</p>
<p className="text-sm font-medium truncate mt-0.5">{organization.name}</p>
</div>
</div>
)}
{chapter && (
<div className="flex items-center gap-2.5 px-3 py-2">
<Building2 className={cn('size-4 shrink-0', accent)} />
<div className="min-w-0">
<p className="text-[10px] uppercase tracking-wide text-muted-foreground leading-none">Chapter</p>
<p className="text-sm font-medium truncate mt-0.5">{chapter.name}</p>
</div>
</div>
)}
</div>
);
}