Link a member's empty role list to the space's roles (#653)
This commit is contained in:
parent
a7cb5fe4d6
commit
5c0617fbf1
5 changed files with 40 additions and 9 deletions
|
|
@ -1451,6 +1451,8 @@ Acceptance:
|
||||||
- Searching the directory for the role's name filters to members holding it.
|
- 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
|
- Deleting a role warns that members keep their space membership, and confirming
|
||||||
removes it from the list; bob sees no "Manage Roles" option.
|
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
|
### US-094 — Invite people to a space
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")).toBeVisible()
|
||||||
await expect(memberCard(admin, "Bob Barnacle").locator(".role-badge")).toHaveCount(0)
|
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")
|
const bob = await as(users.bob, spacePath(url) + "/directory")
|
||||||
|
|
||||||
await expect(memberCard(bob, "Carol Cutter")).toBeVisible()
|
await expect(memberCard(bob, "Carol Cutter")).toBeVisible()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import {sortBy} from "@welshman/lib"
|
import {sortBy} from "@welshman/lib"
|
||||||
import {RelayRoles} from "@welshman/app"
|
import {RelayRoles} from "@welshman/app"
|
||||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
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 Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
|
@ -13,8 +14,11 @@
|
||||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import RoleItem from "@app/components/RoleItem.svelte"
|
import RoleItem from "@app/components/RoleItem.svelte"
|
||||||
|
import SpaceRoles from "@app/components/SpaceRoles.svelte"
|
||||||
import {app, profiles, relayManagement} from "@app/core"
|
import {app, profiles, relayManagement} from "@app/core"
|
||||||
|
import {deriveUserCanManageRoles} from "@app/management"
|
||||||
import {deriveSpaceMemberRoles} from "@app/roles"
|
import {deriveSpaceMemberRoles} from "@app/roles"
|
||||||
|
import {pushModal} from "@app/modal"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -30,9 +34,12 @@
|
||||||
const profileDisplay = $profiles.display(pubkey, [url]).$
|
const profileDisplay = $profiles.display(pubkey, [url]).$
|
||||||
const memberRoles = deriveSpaceMemberRoles(url)
|
const memberRoles = deriveSpaceMemberRoles(url)
|
||||||
const initial = new Set(get(memberRoles).get(pubkey) ?? [])
|
const initial = new Set(get(memberRoles).get(pubkey) ?? [])
|
||||||
|
const canManageRoles = deriveUserCanManageRoles(url)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const manageRoles = () => pushModal(SpaceRoles, {url})
|
||||||
|
|
||||||
const toggle = (id: string) => {
|
const toggle = (id: string) => {
|
||||||
const next = new Set(selected)
|
const next = new Set(selected)
|
||||||
|
|
||||||
|
|
@ -114,8 +121,15 @@
|
||||||
<Icon icon={AltArrowLeft} />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
|
{#if roles.length > 0}
|
||||||
<Button class="button button-primary" onclick={submit} disabled={loading}>
|
<Button class="button button-primary" onclick={submit} disabled={loading}>
|
||||||
<Spinner {loading}>Save changes</Spinner>
|
<Spinner {loading}>Save changes</Spinner>
|
||||||
</Button>
|
</Button>
|
||||||
|
{:else if $canManageRoles}
|
||||||
|
<Button class="button button-primary" onclick={manageRoles}>
|
||||||
|
<Icon icon={UsersGroup} />
|
||||||
|
Manage Roles
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,11 @@ export const deriveUserIsSpaceOwner = (url: string) =>
|
||||||
([$user, $relay]) => $user.pubkey === $relay?.pubkey,
|
([$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) =>
|
export const deriveUserCanCreateRoom = (url: string) =>
|
||||||
derived(
|
derived(
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
import SpaceAdmins from "@app/components/SpaceAdmins.svelte"
|
import SpaceAdmins from "@app/components/SpaceAdmins.svelte"
|
||||||
import SpaceRoles from "@app/components/SpaceRoles.svelte"
|
import SpaceRoles from "@app/components/SpaceRoles.svelte"
|
||||||
import SpaceMembersBanned from "@app/components/SpaceMembersBanned.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 {deriveSpaceMemberRoles} from "@app/roles"
|
||||||
import {relayMemberLists, relayRoles} from "@app/core"
|
import {relayMemberLists, relayRoles} from "@app/core"
|
||||||
import {deriveDisplaysByPubkey} from "@app/social"
|
import {deriveDisplaysByPubkey} from "@app/social"
|
||||||
|
|
@ -33,9 +33,7 @@
|
||||||
const members = $relayMemberLists.forUrl(url)
|
const members = $relayMemberLists.forUrl(url)
|
||||||
const memberRoles = deriveSpaceMemberRoles(url)
|
const memberRoles = deriveSpaceMemberRoles(url)
|
||||||
const supportedMethods = deriveSpaceSupportedMethods(url)
|
const supportedMethods = deriveSpaceSupportedMethods(url)
|
||||||
const canManageRoles = $derived(
|
const canManageRoles = deriveUserCanManageRoles(url)
|
||||||
["createrole", "editrole", "deleterole"].some(method => $supportedMethods.includes(method)),
|
|
||||||
)
|
|
||||||
const canListBans = $derived($supportedMethods.includes("listbannedpubkeys"))
|
const canListBans = $derived($supportedMethods.includes("listbannedpubkeys"))
|
||||||
const canListAdmins = $derived($supportedMethods.includes("listmethodassignees"))
|
const canListAdmins = $derived($supportedMethods.includes("listmethodassignees"))
|
||||||
|
|
||||||
|
|
@ -116,7 +114,7 @@
|
||||||
<Icon icon={AddCircle} />
|
<Icon icon={AddCircle} />
|
||||||
Invite people
|
Invite people
|
||||||
</Button>
|
</Button>
|
||||||
{#if canManageRoles || canListBans || canListAdmins}
|
{#if $canManageRoles || canListBans || canListAdmins}
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<Button
|
<Button
|
||||||
class="button button-neutral button-sm button-square"
|
class="button button-neutral button-sm button-square"
|
||||||
|
|
@ -129,7 +127,7 @@
|
||||||
<ul
|
<ul
|
||||||
transition:fly
|
transition:fly
|
||||||
class="menu bg-surface absolute right-0 z-popover mt-2 w-48 gap-1 rounded-2xl p-2">
|
class="menu bg-surface absolute right-0 z-popover mt-2 w-48 gap-1 rounded-2xl p-2">
|
||||||
{#if canManageRoles}
|
{#if $canManageRoles}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={manageRoles}>
|
<Button onclick={manageRoles}>
|
||||||
<Icon icon={UsersGroup} />
|
<Icon icon={UsersGroup} />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue