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:
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.).
useOrganizationId only finds admin memberships the user createdThe 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.
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.
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.
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
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
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
Ensure invited admins can:
Create documents -- Already works via document_history RLS (checks org membership)
View organization history -- Already works (RLS on document_history checks 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_files RLS
View dashboard stats -- Already works via get_organization_statistics RPC
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
get_org_active_planCREATE 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;
$$;
useSubscription HookAdd fallback logic:
Check user's own subscription (existing behavior)
If no active subscription found, call get_org_active_plan RPC with the user's org ID
Return whichever plan is found
useOrganizations HookReturns: { 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
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.
Completed
Feedback & Feature Request
10 months ago

Harshil S
Get notified by email when there are changes.
Completed
Feedback & Feature Request
10 months ago

Harshil S
Get notified by email when there are changes.