diff --git a/e2e/USER_STORIES.md b/e2e/USER_STORIES.md index a529a1f1..798b67eb 100644 --- a/e2e/USER_STORIES.md +++ b/e2e/USER_STORIES.md @@ -1451,6 +1451,8 @@ Acceptance: - Searching the directory for the role's name filters to members holding it. - Deleting a role warns that members keep their space membership, and confirming removes it from the list; bob sees no "Manage Roles" option. +- A member's "Edit roles" in a space with no roles opens the roles list rather + than ending there. ### US-094 — Invite people to a space diff --git a/e2e/specs/admin.spec.ts b/e2e/specs/admin.spec.ts index 2a0609e1..cc9665fb 100644 --- a/e2e/specs/admin.spec.ts +++ b/e2e/specs/admin.spec.ts @@ -290,6 +290,18 @@ test("US-093 create roles and assign them", async ({seed, as}) => { await expect(memberCard(admin, "Bob Barnacle")).toBeVisible() await expect(memberCard(admin, "Bob Barnacle").locator(".role-badge")).toHaveCount(0) + // A space with no roles left offers the roles list from the member's own dialog + await memberCard(admin, "Bob Barnacle").getByRole("button").last().click() + await menuItem(admin, "Edit roles").click() + + const bobsRoles = dialog(admin, "Edit Member") + + await expect(bobsRoles.getByText("This space has no roles yet.")).toBeVisible() + + await bobsRoles.getByRole("button", {name: "Manage Roles"}).click() + + await expect(emptyState).toBeVisible() + const bob = await as(users.bob, spacePath(url) + "/directory") await expect(memberCard(bob, "Carol Cutter")).toBeVisible() diff --git a/src/app/components/SpaceMemberRoles.svelte b/src/app/components/SpaceMemberRoles.svelte index f51a57bd..2679f253 100644 --- a/src/app/components/SpaceMemberRoles.svelte +++ b/src/app/components/SpaceMemberRoles.svelte @@ -3,6 +3,7 @@ import {sortBy} from "@welshman/lib" import {RelayRoles} from "@welshman/app" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" + import UsersGroup from "@assets/icons/users-group-rounded.svg?dataurl" import Spinner from "@lib/components/Spinner.svelte" import Button from "@lib/components/Button.svelte" import Icon from "@lib/components/Icon.svelte" @@ -13,8 +14,11 @@ import ModalSubtitle from "@lib/components/ModalSubtitle.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte" import RoleItem from "@app/components/RoleItem.svelte" + import SpaceRoles from "@app/components/SpaceRoles.svelte" import {app, profiles, relayManagement} from "@app/core" + import {deriveUserCanManageRoles} from "@app/management" import {deriveSpaceMemberRoles} from "@app/roles" + import {pushModal} from "@app/modal" import {pushToast} from "@app/toast" type Props = { @@ -30,9 +34,12 @@ const profileDisplay = $profiles.display(pubkey, [url]).$ const memberRoles = deriveSpaceMemberRoles(url) const initial = new Set(get(memberRoles).get(pubkey) ?? []) + const canManageRoles = deriveUserCanManageRoles(url) const back = () => history.back() + const manageRoles = () => pushModal(SpaceRoles, {url}) + const toggle = (id: string) => { const next = new Set(selected) @@ -114,8 +121,15 @@ Go back - + {#if roles.length > 0} + + {:else if $canManageRoles} + + {/if} diff --git a/src/app/management.ts b/src/app/management.ts index d3cfd250..01a20374 100644 --- a/src/app/management.ts +++ b/src/app/management.ts @@ -150,6 +150,11 @@ export const deriveUserIsSpaceOwner = (url: string) => ([$user, $relay]) => $user.pubkey === $relay?.pubkey, ) +export const deriveUserCanManageRoles = (url: string) => + derived(deriveSpaceSupportedMethods(url), $methods => + ["createrole", "editrole", "deleterole"].some(method => $methods.includes(method)), + ) + export const deriveUserCanCreateRoom = (url: string) => derived( [ diff --git a/src/routes/spaces/[relay]/directory/+page.svelte b/src/routes/spaces/[relay]/directory/+page.svelte index a502febc..bdaf7daa 100644 --- a/src/routes/spaces/[relay]/directory/+page.svelte +++ b/src/routes/spaces/[relay]/directory/+page.svelte @@ -18,7 +18,7 @@ import SpaceAdmins from "@app/components/SpaceAdmins.svelte" import SpaceRoles from "@app/components/SpaceRoles.svelte" import SpaceMembersBanned from "@app/components/SpaceMembersBanned.svelte" - import {deriveSpaceSupportedMethods} from "@app/management" + import {deriveSpaceSupportedMethods, deriveUserCanManageRoles} from "@app/management" import {deriveSpaceMemberRoles} from "@app/roles" import {relayMemberLists, relayRoles} from "@app/core" import {deriveDisplaysByPubkey} from "@app/social" @@ -33,9 +33,7 @@ const members = $relayMemberLists.forUrl(url) const memberRoles = deriveSpaceMemberRoles(url) const supportedMethods = deriveSpaceSupportedMethods(url) - const canManageRoles = $derived( - ["createrole", "editrole", "deleterole"].some(method => $supportedMethods.includes(method)), - ) + const canManageRoles = deriveUserCanManageRoles(url) const canListBans = $derived($supportedMethods.includes("listbannedpubkeys")) const canListAdmins = $derived($supportedMethods.includes("listmethodassignees")) @@ -116,7 +114,7 @@ Invite people - {#if canManageRoles || canListBans || canListAdmins} + {#if $canManageRoles || canListBans || canListAdmins}