mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): let channel members add members and bots without admin (#815)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -35,12 +35,14 @@ function formatSearchUserSecondary(user: UserSearchResult) {
|
||||
}
|
||||
|
||||
export function ChannelMemberInviteCard({
|
||||
canAssignElevatedRoles,
|
||||
existingMembers,
|
||||
isPending,
|
||||
onSubmit,
|
||||
open,
|
||||
requestErrorMessage,
|
||||
}: {
|
||||
canAssignElevatedRoles: boolean;
|
||||
existingMembers: ChannelMember[];
|
||||
isPending: boolean;
|
||||
onSubmit: (input: {
|
||||
@@ -63,6 +65,28 @@ export function ChannelMemberInviteCard({
|
||||
AddChannelMembersResult["errors"]
|
||||
>([]);
|
||||
|
||||
// Only owners/admins may grant the elevated "admin" role — the relay rejects
|
||||
// it for everyone else. Hide it from the dropdown so we never offer a role
|
||||
// the server will refuse. "member", "guest", and "bot" are non-elevated and
|
||||
// any channel member may grant them.
|
||||
const availableRoles = React.useMemo<
|
||||
Exclude<ChannelMember["role"], "owner">[]
|
||||
>(
|
||||
() =>
|
||||
canAssignElevatedRoles
|
||||
? ["member", "admin", "guest", "bot"]
|
||||
: ["member", "guest", "bot"],
|
||||
[canAssignElevatedRoles],
|
||||
);
|
||||
|
||||
// Guard against a stale elevated selection if the caller's permissions change
|
||||
// while the card is mounted (e.g. the member gets demoted).
|
||||
React.useEffect(() => {
|
||||
if (!availableRoles.includes(inviteRole)) {
|
||||
setInviteRole("member");
|
||||
}
|
||||
}, [availableRoles, inviteRole]);
|
||||
|
||||
const deferredInviteQuery = React.useDeferredValue(inviteQuery.trim());
|
||||
const selectedInviteePubkeys = React.useMemo(
|
||||
() =>
|
||||
@@ -329,7 +353,7 @@ export function ChannelMemberInviteCard({
|
||||
}
|
||||
value={inviteRole}
|
||||
>
|
||||
{["member", "admin", "guest", "bot"].map((role) => (
|
||||
{availableRoles.map((role) => (
|
||||
<option key={role} value={role}>
|
||||
{role}
|
||||
</option>
|
||||
|
||||
@@ -228,9 +228,10 @@ export function MembersSidebar({
|
||||
</SheetHeader>
|
||||
|
||||
<div className="flex-1 space-y-6 overflow-y-auto px-6 py-6">
|
||||
{(canManageMembers || channel.visibility === "open") &&
|
||||
{(selfMember !== null || channel.visibility === "open") &&
|
||||
channel.channelType !== "dm" ? (
|
||||
<ChannelMemberInviteCard
|
||||
canAssignElevatedRoles={canManageMembers}
|
||||
existingMembers={rawMembers}
|
||||
isPending={addMembersMutation.isPending}
|
||||
onSubmit={(input) => addMembersMutation.mutateAsync(input)}
|
||||
|
||||
@@ -1055,6 +1055,31 @@ const mockChannels: MockChannel[] = [
|
||||
createMockMember(MOCK_IDENTITY_PUBKEY, "guest", 700),
|
||||
],
|
||||
}),
|
||||
createMockChannel({
|
||||
id: "3c2d9f0a-1b44-5e77-9a21-6f8b0c4d2e91",
|
||||
name: "secret-projects",
|
||||
channel_type: "stream",
|
||||
visibility: "private",
|
||||
description: "Private project room",
|
||||
topic: "Skunkworks",
|
||||
purpose: "Coordinate confidential project work.",
|
||||
last_message_at: null,
|
||||
archived_at: null,
|
||||
created_by: ALICE_PUBKEY,
|
||||
topic_set_by: ALICE_PUBKEY,
|
||||
topic_set_at: isoMinutesAgo(120),
|
||||
purpose_set_by: ALICE_PUBKEY,
|
||||
purpose_set_at: isoMinutesAgo(130),
|
||||
topic_required: false,
|
||||
max_members: null,
|
||||
nip29_group_id: null,
|
||||
created_minutes_ago: 600,
|
||||
updated_minutes_ago: 120,
|
||||
members: [
|
||||
createMockMember(ALICE_PUBKEY, "owner", 600),
|
||||
createMockMember(MOCK_IDENTITY_PUBKEY, "member", 540),
|
||||
],
|
||||
}),
|
||||
createMockChannel({
|
||||
id: "f48efb06-0c93-5025-aac9-2e646bb6bfa8",
|
||||
name: "alice-tyler",
|
||||
|
||||
@@ -872,6 +872,34 @@ test("open-channel members can add agents from the header", async ({
|
||||
await expect(page.getByTestId("add-channel-bot-dialog-footer")).toBeVisible();
|
||||
});
|
||||
|
||||
test("private-channel members can add members and bots without admin", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
// secret-projects is a private (non-DM) channel where the current user is a
|
||||
// plain member, not owner/admin. They should still be able to add members
|
||||
// and bots — only granting elevated roles is reserved for owners/admins.
|
||||
await openMembersSidebar(page, "secret-projects");
|
||||
|
||||
// The invite card is shown to any member, not just owners/admins.
|
||||
await expect(
|
||||
page.getByTestId("channel-management-search-users"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("channel-management-add-members"),
|
||||
).toBeVisible();
|
||||
|
||||
// The role dropdown hides the elevated "admin" option for non-admins — the
|
||||
// relay rejects it anyway — while keeping the non-elevated roles a member
|
||||
// may grant (member, guest, bot).
|
||||
const roleSelect = page.getByTestId("channel-management-add-role");
|
||||
await expect(roleSelect.locator("option")).toHaveText([
|
||||
"member",
|
||||
"guest",
|
||||
"bot",
|
||||
]);
|
||||
});
|
||||
|
||||
test("removing a channel-scoped agent preserves the managed agent record", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user