npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-switcher

Peer Dependencies

The following must already be installed in your application:

  • react ^18 or ^19
  • react-dom ^18 or ^19
  • react-router-dom ^6
  • @supabase/supabase-js ^2
  • org.inovus.unified-auth ^1
  • lucide-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_admin

Multiple values are comma-separated:

VITE_PRODUCT_ID=prod_ABC,prod_XYZ
VITE_COGNITO_GROUP=totum_lms_admin,totum_lms_member

Then 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.