← back to Norma

components/OrgProvider.tsx

144 lines

'use client';

import '@/lib/orgFetch'; // Monkey-patch fetch with X-Org-Id header

import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react';

/* ─── Types ──────────────────────────────────────────────────────────────── */

export interface OrgStaff {
  name: string;
  role: string;
}

export interface OrgLane {
  label: string;
  color: string;
  description: string;
}

export interface OrgProfile {
  id: string;
  org_name: string;
  short_name: string;
  ein: string;
  website: string;
  mission: string;
  focus_areas: string[];
  org_email: string;
  org_phone: string;
  brand_color: string;
  donate_url: string;
  logo_url: string | null;
  address_street: string;
  address_city: string;
  address_state: string;
  address_zip: string;
  twitter_handle: string;
  linkedin_url: string;
  instagram_handle: string;
  facebook_url: string;
  staff: OrgStaff[];
  lanes: Record<string, OrgLane>;
  voice_modes: string[];
  search_keywords: string[];
  org_size: string;
  annual_budget: string;
  year_founded: number | null;
}

interface OrgContextValue {
  org: OrgProfile | null;
  orgs: OrgProfile[];
  loading: boolean;
  switchOrg: (id: string) => void;
  refreshOrg: () => Promise<void>;
}

const LS_KEY = 'norma-active-org';

/* ─── Context ────────────────────────────────────────────────────────────── */

const OrgContext = createContext<OrgContextValue>({
  org: null,
  orgs: [],
  loading: true,
  switchOrg: () => {},
  refreshOrg: async () => {},
});

export function useOrg() {
  return useContext(OrgContext);
}

/* ─── Provider ───────────────────────────────────────────────────────────── */

export function OrgProvider({ children }: { children: ReactNode }) {
  const [orgs, setOrgs] = useState<OrgProfile[]>([]);
  const [activeId, setActiveId] = useState<string>(() => {
    if (typeof window === 'undefined') return '';
    return localStorage.getItem(LS_KEY) || '';
  });
  const [loading, setLoading] = useState(true);

  const fetchOrgs = useCallback(async () => {
    try {
      const res = await fetch('/api/accounts');
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();
      const list: OrgProfile[] = data.orgs ?? [];
      setOrgs(list);

      // Auto-select org for staff/pulse roles (org is locked to their account)
      if (typeof window !== 'undefined') {
        const userRole = localStorage.getItem('norma-user-role');
        const userOrgId = localStorage.getItem('norma-user-orgid');
        if ((userRole === 'staff' || userRole === 'pulse') && userOrgId) {
          // Always force the correct org for non-admin roles, even if activeId is stale
          setActiveId(userOrgId);
          localStorage.setItem(LS_KEY, userOrgId);
        }
      }
      // For admin: do NOT auto-select — user must pick an org from the dropdown
    } catch (err) {
      console.error('[OrgProvider] Failed to fetch orgs:', err);
    } finally {
      setLoading(false);
    }
  }, [activeId]);

  useEffect(() => {
    fetchOrgs();
  }, [fetchOrgs]);

  const switchOrg = useCallback((id: string) => {
    // Staff and pulse roles are locked to their session org — ignore switchOrg calls
    if (typeof window !== 'undefined') {
      const userRole = localStorage.getItem('norma-user-role');
      if (userRole === 'staff' || userRole === 'pulse') {
        const userOrgId = localStorage.getItem('norma-user-orgid');
        if (userOrgId && id !== userOrgId) return; // Block switching to a different org
      }
    }
    setActiveId(id);
    localStorage.setItem(LS_KEY, id);
  }, []);

  // Only return an org if one was explicitly selected (via dropdown or localStorage)
  const org = activeId ? (orgs.find((o) => o.id === activeId) ?? null) : null;

  return (
    <OrgContext.Provider
      value={{
        org,
        orgs,
        loading,
        switchOrg,
        refreshOrg: fetchOrgs,
      }}
    >
      {children}
    </OrgContext.Provider>
  );
}