Invite Admins (Collaboration)
Multi-Admin Organization Support Plan
Current State
The invite/collaborator infrastructure already exists:
invite-collaboratorEdge Function handles sending invitations and adding existing usersUserPermissionsPanelcomponent lets admins invite new admins with plan-based limitsorganization_memberstable tracks memberships with rolesRLS 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_idcolumn to thesubscriptionstable (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
useSubscriptionhook to accept an optionalorganizationIdparameterIf the user has no personal subscription, fall back to querying the organization owner's subscription via the new RPC function
Update
usePlanFeaturesto 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 entirelyThis 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
useOrganizationshook that returns all active membershipsAdd a small org-switcher component in the Sidebar header area (next to the logo)
Store the selected org in
sessionStorageso it persists across navigation
Phase 4: Invited Admin Feature Parity
Ensure invited admins can:
Create documents -- Already works via
document_historyRLS (checks org membership)View organization history -- Already works (RLS on
document_historychecks org membership)Access Request Portal -- Needs the subscription/plan fix from Phase 1
Manage portal members -- Already works (RLS checks admin role)
View/edit branding -- Already works via
branding_filesRLSView dashboard stats -- Already works via
get_organization_statisticsRPC
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 emailAdd 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:
Check user's own subscription (existing behavior)
If no active subscription found, call
get_org_active_planRPC with the user's org IDReturn 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
Security Considerations
The
get_org_active_planfunction uses SECURITY DEFINER to bypass RLS safely -- it only returns a plan name string, no sensitive dataOrg 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
Log in to comment and vote
No comments yet
Be the first to share your thoughts.