CapxulOrgMember
One root that owns what a member may do in an organization, plus the two parts a screen places in its own layout.
CapxulOrgMember answers one question: what may this member do in this
organization? It draws nothing. You place its named parts where you want them
and you draw everything yourself.
The two facts behind it are the two the backend actually enforces —
managePeople and spend. Payroll is spending. Approving an invoice is
spending. There are no other capabilities and no verb aliases.
Import
import { CapxulOrgMember } from "@capxul/sdk-react";Usage
"use client";
import { CapxulOrgMember } from "@capxul/sdk-react";
function RecipientsHeader({ orgId }: { orgId: string | undefined }) {
return (
<CapxulOrgMember orgId={orgId}>
<CapxulOrgMember.Can do="managePeople">
{({ allowed, reason }) => (
<button disabled={!allowed} title={reason === null ? undefined : DENIED[reason]}>
Invite recipient
</button>
)}
</CapxulOrgMember.Can>
</CapxulOrgMember>
);
}Every screen places a subset. A payroll page places .Can do="spend" and
nothing else. A dashboard places .Standing plus both facts. An invoice sheet
places .Can twice with different do values.
Props
orgId accepts OrgId | string | undefined. When it is undefined the root
starts no read at all and refuses immediately, so a screen rendered before an
organization is selected never spins.
.Can
Takes what the person wants to do and hands back one finished slice.
{
allowed: boolean;
isLoading: boolean;
reason: CanReason;
}It fails closed: allowed is false until the read settles.
reason is a CODE from a closed list, never a sentence. Your application owns
every refusal sentence and maps the code to it.
reason | When you get it |
|---|---|
"loading" | the read has not settled |
"no-permission" | the read settled and the member lacks the fact |
"org-unavailable" | no orgId was given, or the read failed |
null | exactly when allowed is true |
A member who is not part of the organization reads as "org-unavailable": the
backend refuses the read rather than returning a member who holds nothing.
One part serves every denial idiom. Use allowed for a disabled control, add
reason for a tooltip, branch on allowed to swap a whole surface, or return
null to make a button vanish.
.Standing
{
standing: "loading" | "unavailable" | "active" | "restricted";
roleLabel: string | null;
}Map standing to your own copy with a lookup. The classification runs in this
order, and the order matters:
| Test, in order | standing |
|---|---|
| the read has not settled | "loading" |
the read failed, or no orgId was given | "unavailable" |
| the member's role has no label | "unavailable" |
| the member holds either capability | "active" |
| the member holds neither | "restricted" |
Because the role test runs before the capability test, standing === "active"
guarantees roleLabel is not null. Your active branch never needs a fallback
word.
Rules
- A part used outside
<CapxulOrgMember>throws. - There is no companion hook. The component is the one way in, so no second surface can drift from it.
- The read is shared: React Query serves one
org(id).me()to every part on the page.
Client method
The component stands on client.org(orgId).me().