Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useMemo, useState } from "react";
import useSWR from "swr";
import { AddMemberModal } from "./add-member-modal";
import { MembersTable } from "./members-table";
import { PermissionsReference } from "./permissions-reference";
import { PermissionsReferenceModal } from "./permissions-reference";
import { VisibilitySection } from "./visibility-section";

interface AgentAccessClientProps {
Expand Down Expand Up @@ -77,13 +77,20 @@ export function AgentAccessClient({
orgAdminsAndOwners.map((m) => m.user.id)
);

// Get regular org members (not admins/owners) - they get read access when visibility is organization
const regularOrgMembers =
orgMembers?.filter((m) => m.role === "member") || [];

// Filter out org admins and owners from the member list since they always have access
const explicitMembers = (members || []).filter(
(member) => !member.user_id || !orgAdminsAndOwnersIds.has(member.user_id)
);

// Total count includes both explicit and implicit members
const totalMembersCount = explicitMembers.length + orgAdminsAndOwners.length;
// Total count depends on visibility
const totalMembersCount =
agentVisibility === "organization"
? (orgMembers?.length || 0) // When team visible, all org members have access
: explicitMembers.length + orgAdminsAndOwners.length;

return (
<PageContainer>
Expand All @@ -99,8 +106,6 @@ export function AgentAccessClient({
organizationName={organizationName}
/>

<PermissionsReference />

<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
Expand All @@ -111,15 +116,20 @@ export function AgentAccessClient({
to this agent
</p>
</div>
<Button onClick={() => setIsAddingMember(true)}>
<UserPlus className="h-4 w-4" />
Add Member
</Button>
<div className="flex items-center gap-4">
<PermissionsReferenceModal />
<Button onClick={() => setIsAddingMember(true)}>
<UserPlus className="h-4 w-4" />
Add Member
</Button>
</div>
</div>

<MembersTable
explicitMembers={explicitMembers}
implicitMembers={orgAdminsAndOwners}
regularOrgMembers={regularOrgMembers}
agentVisibility={agentVisibility}
currentUserId={currentUserId}
onDelete={handleDelete}
onUpdatePermission={handleUpdatePermission}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import Avatar from "@/components/ui/avatar";
import type { AgentMember, OrganizationMember } from "@blink.so/api";
import { Shield } from "lucide-react";
import { Shield, Users } from "lucide-react";
import { useState } from "react";

interface MembersTableProps {
explicitMembers: AgentMember[];
implicitMembers: OrganizationMember[];
regularOrgMembers: OrganizationMember[];
agentVisibility: "private" | "public" | "organization";
currentUserId: string;
onDelete: (userId: string | null) => void;
onUpdatePermission: (
Expand All @@ -19,11 +21,25 @@ interface MembersTableProps {
export function MembersTable({
explicitMembers,
implicitMembers,
regularOrgMembers,
agentVisibility,
currentUserId,
onDelete,
onUpdatePermission,
}: MembersTableProps) {
const totalCount = explicitMembers.length + implicitMembers.length;
// Build a set of user IDs that have explicit grants (to exclude from inherited members)
const explicitUserIds = new Set(
explicitMembers.map((m) => m.user_id).filter(Boolean)
);

// Filter regular org members to exclude those with explicit grants
const inheritedTeamMembers =
agentVisibility === "organization"
? regularOrgMembers.filter((m) => !explicitUserIds.has(m.user.id))
: [];

const totalCount =
explicitMembers.length + implicitMembers.length + inheritedTeamMembers.length;

return (
<section aria-labelledby="members-heading">
Expand Down Expand Up @@ -69,6 +85,13 @@ export function MembersTable({
orgMember={orgMember}
/>
))}
{/* Inherited team members (when visibility is organization) */}
{inheritedTeamMembers.map((orgMember) => (
<InheritedTeamMemberRow
key={`team-${orgMember.user.id}`}
orgMember={orgMember}
/>
))}
{/* Explicit members */}
{explicitMembers.map((member) => (
<ExplicitMemberRow
Expand Down Expand Up @@ -123,7 +146,55 @@ function ImplicitMemberRow({ orgMember }: { orgMember: OrganizationMember }) {
<td className="px-4 py-3">
<div className="flex items-center gap-1.5 text-xs text-neutral-500 dark:text-neutral-400">
<Shield className="h-3.5 w-3.5" />
<span className="capitalize">{orgMember.role}</span>
<span className="capitalize">Team {orgMember.role}</span>
</div>
</td>
<td className="px-4 py-3 text-right">
<span className="text-xs text-neutral-400 dark:text-neutral-500">
</span>
</td>
</tr>
);
}

// Inherited team member row for regular org members when visibility is "organization"
function InheritedTeamMemberRow({
orgMember,
}: {
orgMember: OrganizationMember;
}) {
const displayName =
orgMember.user.display_name || orgMember.user.username || "Unknown";

return (
<tr className="bg-neutral-50/50 dark:bg-neutral-900/30">
<td className="px-4 py-3">
<div className="flex items-center gap-3">
<Avatar
src={orgMember.user.avatar_url}
seed={orgMember.user.id}
size={32}
/>
<div className="min-w-0 flex-1">
<div className="text-sm text-neutral-900 dark:text-white truncate">
{displayName}
</div>
<div className="text-xs text-neutral-500 dark:text-neutral-400 truncate">
@{orgMember.user.username}
</div>
</div>
</div>
</td>
<td className="px-4 py-3">
<span className="text-sm text-neutral-700 dark:text-neutral-300">
Read
</span>
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-1.5 text-xs text-neutral-500 dark:text-neutral-400">
<Users className="h-3.5 w-3.5" />
<span>Team member</span>
</div>
</td>
<td className="px-4 py-3 text-right">
Expand Down
Loading