@inovusmedical/org-switcher
v1.0.6
Published
Organisation switching, seat-based access gating, and role detection for the Totum ecosystem
Readme
@inovusmedical/org-switcher
Organisation switching, seat-based access gating, and role detection for the Totum ecosystem.
Installation
npm install @inovusmedical/org-switcherPeer Dependencies
The following must already be installed in your application:
react^18 or ^19react-dom^18 or ^19react-router-dom^6@supabase/supabase-js^2org.inovus.unified-auth^1lucide-react>=0.542.0
Quick Start
Simple org switcher (no seat gating)
import { OrgSwitcherProvider, OrgSwitcher } from '@inovusmedical/org-switcher';
import { AuthProvider } from 'org.inovus.unified-auth';
function App() {
return (
<AuthProvider>
<OrgSwitcherProvider config={{}}>
<Header>
<OrgSwitcher />
</Header>
<Routes>...</Routes>
</OrgSwitcherProvider>
</AuthProvider>
);
}Seat-gated app
Add env vars to your .env:
VITE_PRODUCT_ID=prod_U4eNgRQiMHpluU
VITE_COGNITO_GROUP=totum_learning_management_system_adminMultiple values are comma-separated:
VITE_PRODUCT_ID=prod_ABC,prod_XYZ
VITE_COGNITO_GROUP=totum_lms_admin,totum_lms_memberThen wrap your app:
import { OrgSwitcherProvider, SeatGate, OrgSwitcher } from '@inovusmedical/org-switcher';
import { AuthProvider } from 'org.inovus.unified-auth';
function App() {
return (
<AuthProvider>
<OrgSwitcherProvider
config={{
productId: import.meta.env.VITE_PRODUCT_ID,
cognitoGroup: import.meta.env.VITE_COGNITO_GROUP,
}}
>
<SeatGate>
<Header>
<OrgSwitcher />
</Header>
<Routes>...</Routes>
</SeatGate>
</OrgSwitcherProvider>
</AuthProvider>
);
}API
OrgSwitcherProvider
The main context provider. All shared values (Totum API URL, Supabase connections) are built into the package — only app-specific config is needed.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| productId | string | No | Stripe product ID(s), comma-separated. Enables seat gating. |
| cognitoGroup | string | No | Required Cognito group name(s), comma-separated. Enables Cognito gating. |
| adminRoles | string[] | No | Cognito group names that grant admin access |
| localStorageKey | string | No | Key for persisting selected group (default: currentGroupGuid) |
| cacheTtlMs | number | No | Cache TTL for API responses (default: 10 minutes) |
| totumApiUrl | string | No | Override the Totum API URL |
| subscriptionsSupabaseUrl | string | No | Override the subscriptions Supabase URL |
| subscriptionsSupabaseAnonKey | string | No | Override the subscriptions Supabase anon key |
| userPrefsSupabaseUrl | string | No | Override the user prefs Supabase URL |
| userPrefsSupabaseAnonKey | string | No | Override the user prefs Supabase anon key |
Hooks
useGroup()
const {
currentGroup, // GroupMembership | null
groups, // GroupMembership[]
isLoading, // boolean
hasFetchedOnce, // boolean
switchGroup, // (groupGuid: string) => void
refreshGroups, // () => Promise<void>
defaultGroupGuid, // string | null — user's persisted default org
setDefaultGroup, // (groupGuid: string) => Promise<void>
seatInfo, // GroupSeatInfo
refreshSeatInfo, // () => Promise<void>
} = useGroup();GroupMembership:
{
id: string;
group_guid: string;
group_name: string;
totumRoles: { roleId: string; roleName: string }[];
}GroupSeatInfo:
{
groupHasSubscription: boolean; // does the org have seats for the product?
userHasSeat: boolean; // does the current user have a seat?
userSeats: SeatAssignment[]; // the user's seats in this org
groupSeats: SeatAssignment[]; // all seats for this org
isFetchingSeats: boolean;
}useIsAdmin()
const {
isGlobalAdmin, // boolean — from Cognito groups claim
isGroupAdmin, // boolean — from currentGroup.totumRoles
canManage, // boolean — isGlobalAdmin || isGroupAdmin
} = useIsAdmin();Components
SeatGate
Blocks the app if the user doesn't have the required Cognito group or seat assignment. Automatically disabled when neither productId nor cognitoGroup are configured.
<SeatGate
loadingComponent={<YourSpinner />}
noAccessComponent={<YourCustomScreen />}
>
<Routes>...</Routes>
</SeatGate>The built-in no-access screen includes the org switcher so users can switch to an org with valid seats.
OrgSwitcher
Dropdown for switching between organisations. Shows role badges with hierarchy sorting.
<OrgSwitcher
variant="header" // "header" | "sidebar" | "minimal"
dropdownAlign="right" // "right" | "left"
searchThreshold={8} // show search when list exceeds this count
maxDropdownHeight="18rem"
onBeforeSwitch={(fromGuid, toGuid) => true}
onAfterSwitch={(group) => navigate('/dashboard')}
/>When only one org exists, it renders as a static label that inherits the parent's text colour.
AdminRoute
Protects admin-only routes.
<Route path="/admin/*" element={
<AdminRoute redirectTo="/dashboard">
<AdminPage />
</AdminRoute>
} />NoAccessScreen
Standalone screen shown when access is denied. Includes the org switcher and a sign-out button.
<NoAccessScreen
title="Access Unavailable"
message="Contact your organisation administrator."
onLogout={() => authService.logout()}
/>SwitchWarningModal
Confirmation modal for organisation switching.
<SwitchWarningModal
isOpen={showWarning}
onConfirm={handleConfirm}
onCancel={handleCancel}
title="Switch Organisation?"
message="Any unsaved changes may be lost."
/>Services
subscriptionService
const seats = await subscriptionService.getUserSeatAssignments(userGuid);
const groupSeats = await subscriptionService.getGroupSeatAssignments(groupGuid);
const productSeats = subscriptionService.getProductSeatAssignments(seats);
const adminSeats = subscriptionService.getAdminSeats(seats);
const memberSeats = subscriptionService.getMemberSeats(seats);totumGroupService
const groups = await totumGroupService.getUserGroups();
const user = await totumGroupService.fetchCurrentUser();
totumGroupService.clearCache();Utilities
import {
hasAdminRole,
isUserAdmin,
decodeJwtPayload,
extractUserSub,
extractUserId,
extractCognitoGroups,
} from '@inovusmedical/org-switcher';Environment Variables
Only needed for seat-gated apps. Simple org switcher apps require no env vars.
| Variable | Description |
|----------|-------------|
| VITE_PRODUCT_ID | Stripe product ID(s) for this app. Comma-separated for multiple. |
| VITE_COGNITO_GROUP | Required Cognito group name(s). Comma-separated for multiple. |
License
UNLICENSED — Internal use only.
