Invite Admins (Collaboration)

Multi-Admin Organization Support Plan

Current State

The invite/collaborator infrastructure already exists:

  • invite-collaborator Edge Function handles sending invitations and adding existing users

  • UserPermissionsPanel component lets admins invite new admins with plan-based limits

  • organization_members table tracks memberships with roles

  • RLS policies on document_history, organizations, notifications, etc. allow access for org members

However, there are critical gaps preventing invited admins from actually functioning:

Problems to Fix

1. Subscription is per-user, not per-organization

The subscriptions table is keyed on user_id. When Admin B is invited, they have no subscription record. The useSubscription hook checks user_id = auth.uid(), so invited admins get no plan, no features, and see a crippled dashboard (no Request Portal, no AI Assistant, etc.).

2. useOrganizationId only finds admin memberships the user created

The hook queries role = 'admin' which works, but the get_user_organization_id RPC also filters by role = 'admin'. This actually works for invited admins since they are added with role = 'admin'. No change needed here.

3. Invited admins skip onboarding but have no profile

When an existing user is invited, they are added to organization_members directly. But they may lack a user_profiles entry or have one from their own org. The ensureUserHasOrganization in AuthContext might create a duplicate org for them on sign-in.

4. No mechanism to share branding/org data

Branding uses get_user_organization_id which returns the first admin membership. If an invited admin was previously an admin of their own org, they could see the wrong org's data.


Implementation Plan

Phase 1: Organization-Scoped Subscription Resolution

Goal: Invited admins inherit the organization owner's subscription/plan.

Database Migration:

  • Add organization_id column to the subscriptions table (nullable, for backward compatibility)

  • Create a get_organization_plan(org_id uuid) SECURITY DEFINER function that returns the active plan for an organization by finding any subscription linked to it

Alternatively (simpler, no schema change):

  • Create a get_org_active_plan(org_id uuid) SECURITY DEFINER function that looks up the subscription of the org's original admin (owner)

  • This avoids schema changes and keeps subscriptions user-scoped

Code Changes:

  • Update useSubscription hook to accept an optional organizationId parameter

  • If the user has no personal subscription, fall back to querying the organization owner's subscription via the new RPC function

  • Update usePlanFeatures to pass the org context through

Phase 2: Prevent Duplicate Org Creation for Invited Users

Code Changes in AuthContext.tsx:

  • In ensureUserHasOrganization, check if the user already has ANY active membership (not just admin). If they were invited and accepted, they already have a membership -- skip org creation entirely

  • This is already partially handled (lines 173-189 check for existing memberships), but we need to verify it covers the invited-admin case

Phase 3: Multi-Org Awareness

Problem: A user could be admin of their own org AND invited to another org. Currently useOrganizationId returns the first admin membership found. There is no org switcher.

Solution (MVP approach):

  • For now, when a user belongs to multiple organizations, show the organization they were most recently added to, or provide a simple org-selector dropdown in the sidebar

  • Add a useOrganizations hook that returns all active memberships

  • Add a small org-switcher component in the Sidebar header area (next to the logo)

  • Store the selected org in sessionStorage so it persists across navigation

Phase 4: Invited Admin Feature Parity

Ensure invited admins can:

  1. Create documents -- Already works via document_history RLS (checks org membership)

  2. View organization history -- Already works (RLS on document_history checks org membership)

  3. Access Request Portal -- Needs the subscription/plan fix from Phase 1

  4. Manage portal members -- Already works (RLS checks admin role)

  5. View/edit branding -- Already works via branding_files RLS

  6. View dashboard stats -- Already works via get_organization_statistics RPC

Phase 5: Member Display Improvements

Code Changes in UserPermissionsPanel.tsx:

  • Show the member's actual name (from user_profiles) instead of just "Active User" or their invited email

  • Add ability for the org owner to remove invited admins

  • Show the current user's own row highlighted


Technical Details

New Database Function: get_org_active_plan

CREATE OR REPLACE FUNCTION public.get_org_active_plan(p_org_id uuid)
RETURNS text
LANGUAGE sql
STABLE SECURITY DEFINER
SET search_path TO 'public'
AS $$
  SELECT s.active_plan
  FROM subscriptions s
  JOIN organization_members om ON om.user_id = s.user_id
  WHERE om.organization_id = p_org_id
    AND om.status = 'active'
    AND s.subscription_status = 'active'
    AND s.active_plan IS NOT NULL
  ORDER BY s.created_at ASC
  LIMIT 1;
$$;

Updated useSubscription Hook

Add fallback logic:

  1. Check user's own subscription (existing behavior)

  2. If no active subscription found, call get_org_active_plan RPC with the user's org ID

  3. Return whichever plan is found

New useOrganizations Hook

Returns: { organizations: [{id, name, role}], selectedOrgId, setSelectedOrgId }
  • Fetches all active memberships for the user

  • Stores selection in sessionStorage

  • Falls back to first membership if no selection

Files to Create/Modify

FileAction

src/hooks/useOrganizations.ts

Create -- multi-org membership hook

src/hooks/useSubscription.ts

Modify -- add org-level plan fallback

src/hooks/useOrganizationId.ts

Modify -- use selected org from useOrganizations

src/contexts/AuthContext.tsx

Modify -- improve ensureUserHasOrganization guard

src/components/dashboard/Sidebar.tsx

Modify -- add org switcher if multiple orgs

src/components/admin/UserPermissionsPanel.tsx

Modify -- show member names, add remove action

Database migration

Create get_org_active_plan function

Security Considerations

  • The get_org_active_plan function uses SECURITY DEFINER to bypass RLS safely -- it only returns a plan name string, no sensitive data

  • Org switcher only shows orgs the user is an active member of (enforced by RLS on organization_members)

  • No changes to existing RLS policies needed -- they already check org membership correctly

  • Subscription data remains per-user at the DB level; the org-level lookup is read-only

Please authenticate to join the conversation.

Upvoters
Status

Completed

Board

Feedback & Feature Request

Date

10 months ago

Author

Harshil S

Subscribe to post

Get notified by email when there are changes.