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({
|
export function ChannelMemberInviteCard({
|
||||||
|
canAssignElevatedRoles,
|
||||||
existingMembers,
|
existingMembers,
|
||||||
isPending,
|
isPending,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
open,
|
open,
|
||||||
requestErrorMessage,
|
requestErrorMessage,
|
||||||
}: {
|
}: {
|
||||||
|
canAssignElevatedRoles: boolean;
|
||||||
existingMembers: ChannelMember[];
|
existingMembers: ChannelMember[];
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
onSubmit: (input: {
|
onSubmit: (input: {
|
||||||
@@ -63,6 +65,28 @@ export function ChannelMemberInviteCard({
|
|||||||
AddChannelMembersResult["errors"]
|
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 deferredInviteQuery = React.useDeferredValue(inviteQuery.trim());
|
||||||
const selectedInviteePubkeys = React.useMemo(
|
const selectedInviteePubkeys = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -329,7 +353,7 @@ export function ChannelMemberInviteCard({
|
|||||||
}
|
}
|
||||||
value={inviteRole}
|
value={inviteRole}
|
||||||
>
|
>
|
||||||
{["member", "admin", "guest", "bot"].map((role) => (
|
{availableRoles.map((role) => (
|
||||||
<option key={role} value={role}>
|
<option key={role} value={role}>
|
||||||
{role}
|
{role}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -228,9 +228,10 @@ export function MembersSidebar({
|
|||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
|
|
||||||
<div className="flex-1 space-y-6 overflow-y-auto px-6 py-6">
|
<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" ? (
|
channel.channelType !== "dm" ? (
|
||||||
<ChannelMemberInviteCard
|
<ChannelMemberInviteCard
|
||||||
|
canAssignElevatedRoles={canManageMembers}
|
||||||
existingMembers={rawMembers}
|
existingMembers={rawMembers}
|
||||||
isPending={addMembersMutation.isPending}
|
isPending={addMembersMutation.isPending}
|
||||||
onSubmit={(input) => addMembersMutation.mutateAsync(input)}
|
onSubmit={(input) => addMembersMutation.mutateAsync(input)}
|
||||||
|
|||||||
@@ -1055,6 +1055,31 @@ const mockChannels: MockChannel[] = [
|
|||||||
createMockMember(MOCK_IDENTITY_PUBKEY, "guest", 700),
|
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({
|
createMockChannel({
|
||||||
id: "f48efb06-0c93-5025-aac9-2e646bb6bfa8",
|
id: "f48efb06-0c93-5025-aac9-2e646bb6bfa8",
|
||||||
name: "alice-tyler",
|
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();
|
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 ({
|
test("removing a channel-scoped agent preserves the managed agent record", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user