+
System roles define base-level access for your organization members
+ {roles.map(role => {
+ const roleMemberCount = members.filter(m => m.roleKey === role.key).length;
+ const roleMembers = members.filter(m => m.roleKey === role.key);
+ const isExpanded = expandedRole === role.id;
+
+ return (
+
+
+
+ {isExpanded && (
+
+
+ {roleMembers.length === 0 ? (
+
No members assigned to this role
+ ) : (
+
+ {roleMembers.map(member => (
+
+
+
+ {member.name.charAt(0)}
+
+
+ {member.name}
+ {member.email}
+
+
+
+
+ {member.status}
+
+
+
+ ))}
+
+ )}
+
+
+ )}
+
+
+ );
+ })}
+
+ );
+};
+
+// ────────────────────────────────────────────────────
+// 3. Access Control Matrix
+// ────────────────────────────────────────────────────
+const AccessControlMatrix = ({ permissions, permissionModules, roles, onUpdate }) => {
+ const togglePermission = (moduleKey, roleKey, action) => {
+ const current = permissions[moduleKey]?.[roleKey] || [];
+ const updated = current.includes(action)
+ ? current.filter(a => a !== action)
+ : [...current, action];
+ onUpdate(moduleKey, roleKey, updated);
+ };
+
+ const moduleEntries = Object.entries(permissionModules);
+
+ return (
+
+
+ Click cells to toggle permissions per role
+
+
+ {/* Desktop Matrix */}
+
+
+
+
+ | Module |
+ Action |
+ {roles.map(role => (
+
+ {role.name.split(' ')[0]}
+ |
+ ))}
+
+
+
+ {moduleEntries.map(([moduleKey, module]) =>
+ module.actions.map((action, actionIdx) => (
+
+ {actionIdx === 0 && (
+ |
+
+
+ {module.label}
+
+ |
+ )}
+ {action} |
+ {roles.map(role => {
+ const hasPermission = (permissions[moduleKey]?.[role.key] || []).includes(action);
+ const isOwner = role.key === 'OWNER';
+ return (
+
+ {isOwner ? (
+
+ ) : (
+
+ )}
+ |
+ );
+ })}
+
+ ))
+ )}
+
+
+
+
+ {/* Mobile Cards */}
+
+ {roles.filter(r => r.key !== 'OWNER').map(role => (
+
+
+ {moduleEntries.map(([moduleKey, module]) => (
+
+
{module.label}
+
+ {module.actions.map(action => {
+ const hasPermission = (permissions[moduleKey]?.[role.key] || []).includes(action);
+ return (
+
+ );
+ })}
+
+
+ ))}
+
+ ))}
+
+
+ );
+};
+
+// ────────────────────────────────────────────────────
+// 4. Commission Rules Section
+// ────────────────────────────────────────────────────
+const CommissionRulesSection = ({
+ orgDefaults, onSaveOrgDefaults,
+ roleOverrides, roles, onSaveRoleOverride, onRemoveRoleOverride,
+}) => {
+ const [localOrgDefaults, setLocalOrgDefaults] = useState({ ...orgDefaults });
+ const [editingOrg, setEditingOrg] = useState(false);
+ const [addingRole, setAddingRole] = useState(false);
+ const [newRoleOverride, setNewRoleOverride] = useState({ roleKey: '', type: 'flat', rate: 0 });
+
+ const availableRolesForOverride = useMemo(() => {
+ const overriddenKeys = new Set(roleOverrides.map(o => o.roleKey));
+ return roles.filter(r => r.key !== 'OWNER' && !overriddenKeys.has(r.key));
+ }, [roles, roleOverrides]);
+
+ const handleSaveOrg = () => {
+ onSaveOrgDefaults({ type: localOrgDefaults.type, rate: Number(localOrgDefaults.rate) || 0 });
+ setEditingOrg(false);
+ toast.success('Org commission defaults updated');
+ };
+
+ const handleAddRoleOverride = () => {
+ if (!newRoleOverride.roleKey) return;
+ onSaveRoleOverride({ ...newRoleOverride, enabled: true, rate: Number(newRoleOverride.rate) || 0 });
+ setAddingRole(false);
+ setNewRoleOverride({ roleKey: '', type: 'flat', rate: 0 });
+ toast.success('Role commission override added');
+ };
+
+ const inputClasses = "w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl px-4 py-2.5 text-sm text-zinc-900 dark:text-white outline-none focus:border-blue-400 dark:focus:border-blue-500/50 transition-colors";
+
+ return (
+
+ {/* Hierarchy Visualization */}
+
+ {[
+ { label: 'Org Default', color: '#00f0ff', icon: Building2 },
+ { label: 'Role Override', color: '#10b981', icon: Shield },
+ { label: 'Individual', color: '#a855f7', icon: User },
+ { label: 'Job Override', color: '#fda913', icon: DollarSign },
+ ].map((level, i) => (
+
+ {i > 0 && }
+
+
+ {level.label}
+
+
+ ))}
+
+
Highest priority on the right. Job override supersedes all.
+
+ {/* Org Default */}
+
+
+
+
+ Org Default Commission
+
+ {!editingOrg ? (
+
+ ) : (
+
+
+
+
+ )}
+
+ {editingOrg ? (
+
+
+
+
+
+
+
+ setLocalOrgDefaults(prev => ({ ...prev, rate: e.target.value }))} className={inputClasses} />
+
+
+ ) : (
+
+
+ {getCommissionLabel(orgDefaults.type)}
+ {formatCommission(orgDefaults.type, orgDefaults.rate)}
+
+ )}
+
+
+ {/* Role Overrides */}
+
+
+
+ {roleOverrides.length === 0 && !addingRole && (
+
No role overrides — all roles use org default
+ )}
+
+ {roleOverrides.map(ov => {
+ const role = roles.find(r => r.key === ov.roleKey);
+ return (
+
+
+
+
{role?.name || ov.roleKey}
+
+
{getCommissionLabel(ov.type)} — {formatCommission(ov.type, ov.rate)}
+
+
+
+
+
+
+ );
+ })}
+
+ {addingRole ? (
+
+
+
+
+
+
+
+
+
+
+
+
+ setNewRoleOverride(prev => ({ ...prev, rate: e.target.value }))} className={inputClasses} />
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+ {/* Info */}
+
+
+
+
+
Commission Hierarchy: Job Override → Individual Override → Role Override → Org Default
+
The most specific level takes precedence. Job-level overrides (set on each project) always win.
+
+
+
+
+ );
+};
+
+// ────────────────────────────────────────────────────
+// 5. User Overrides Section
+// ────────────────────────────────────────────────────
+const UserOverridesSection = ({ members, roles, orgDefaults, roleOverrides, userCommissionOverrides, onSaveOverride, onRemoveOverride }) => {
+ const [searchTerm, setSearchTerm] = useState('');
+ const [editingMember, setEditingMember] = useState(null);
+ const [editForm, setEditForm] = useState({ type: 'flat', rate: 0 });
+
+ const filteredMembers = useMemo(() => {
+ const term = searchTerm.toLowerCase();
+ return members.filter(m =>
+ m.name.toLowerCase().includes(term) ||
+ m.email.toLowerCase().includes(term) ||
+ m.roleKey.toLowerCase().includes(term)
+ );
+ }, [members, searchTerm]);
+
+ const getEffectiveCommission = (member) => {
+ const userOv = userCommissionOverrides.find(o => o.userId === member.userId && o.enabled);
+ if (userOv) return { source: 'individual', type: userOv.type, rate: userOv.rate };
+
+ const indOv = member.individualCommission;
+ if (indOv?.enabled) return { source: 'individual', type: indOv.type, rate: indOv.rate };
+
+ const roleOv = roleOverrides.find(o => o.roleKey === member.roleKey && o.enabled);
+ if (roleOv) return { source: 'role', type: roleOv.type, rate: roleOv.rate };
+
+ return { source: 'org', type: orgDefaults.type, rate: orgDefaults.rate };
+ };
+
+ const sourceColors = {
+ individual: { bg: 'bg-purple-50 dark:bg-purple-500/10', border: 'border-purple-200 dark:border-purple-500/20', text: 'text-purple-600 dark:text-purple-400', label: 'Individual' },
+ role: { bg: 'bg-emerald-50 dark:bg-emerald-500/10', border: 'border-emerald-200 dark:border-emerald-500/20', text: 'text-emerald-600 dark:text-emerald-400', label: 'Role' },
+ org: { bg: 'bg-cyan-50 dark:bg-cyan-500/10', border: 'border-cyan-200 dark:border-cyan-500/20', text: 'text-cyan-600 dark:text-cyan-400', label: 'Org Default' },
+ };
+
+ const startEdit = (member) => {
+ const effective = getEffectiveCommission(member);
+ setEditingMember(member.id);
+ setEditForm({ type: effective.type, rate: effective.rate });
+ };
+
+ const handleSaveIndividual = (member) => {
+ onSaveOverride({
+ userId: member.userId,
+ userName: member.name,
+ enabled: true,
+ type: editForm.type,
+ rate: Number(editForm.rate) || 0,
+ });
+ setEditingMember(null);
+ toast.success(`Individual commission set for ${member.name}`);
+ };
+
+ const handleRemoveIndividual = (member) => {
+ onRemoveOverride(member.userId);
+ setEditingMember(null);
+ toast.success(`Individual override removed for ${member.name}`);
+ };
+
+ const inputClasses = "w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl px-4 py-2.5 text-sm text-zinc-900 dark:text-white outline-none focus:border-purple-400 dark:focus:border-purple-500/50 transition-colors";
+
+ return (
+
+ {/* Search */}
+
+
+ setSearchTerm(e.target.value)}
+ className="w-full bg-zinc-50 dark:bg-[#18181b] border border-zinc-200 dark:border-white/10 rounded-xl pl-9 pr-4 py-2.5 text-sm text-zinc-900 dark:text-white placeholder-zinc-400 dark:placeholder-zinc-600 outline-none focus:border-purple-400 dark:focus:border-purple-500/50 transition-colors"
+ />
+
+
+ {/* Member Cards */}
+
+ {filteredMembers.map(member => {
+ const effective = getEffectiveCommission(member);
+ const sc = sourceColors[effective.source];
+ const role = roles.find(r => r.key === member.roleKey);
+ const hasIndividualOverride = userCommissionOverrides.some(o => o.userId === member.userId && o.enabled) || member.individualCommission?.enabled;
+ const isEditing = editingMember === member.id;
+
+ return (
+
+
+
+
+ {member.name.charAt(0)}
+
+
+
+ {member.name}
+ {role?.name || member.roleKey}
+
+
{member.email}
+
+
+
+ {/* Commission Source Badge */}
+
+ {sc.label}
+ {formatCommission(effective.type, effective.rate)}
+
+ {member.roleKey !== 'OWNER' && (
+
+ )}
+
+
+
+ {/* Edit Form */}
+
+ {isEditing && (
+
+
+
+
+
+
+
+
+
+ setEditForm(prev => ({ ...prev, rate: e.target.value }))} className={inputClasses} />
+
+
+
+ {hasIndividualOverride && (
+
+ )}
+
+
+
+
+
+
+ )}
+
+
+ );
+ })}
+
+ {filteredMembers.length === 0 && (
+
+ No members found matching "{searchTerm}"
+
+ )}
+
+
+ );
+};
+
+// ────────────────────────────────────────────────────
+// Main Page
+// ────────────────────────────────────────────────────
+const OrgSettings = () => {
+ const { user } = useAuth();
+ const store = useMockStore();
+
+ const [openSections, setOpenSections] = useState({
+ org: true,
+ roles: false,
+ access: false,
+ commission: false,
+ overrides: false,
+ });
+
+ const toggleSection = (key) => setOpenSections(prev => ({ ...prev, [key]: !prev[key] }));
+
+ const sections = [
+ {
+ id: 'org',
+ icon: Building2,
+ title: 'Organization Settings',
+ subtitle: 'Company name, contact info, and general preferences',
+ color: '#3b82f6',
+ content: (
+