diff --git a/web/src/routes/[handle]/settings/members/MembersTab.stories.svelte b/web/src/routes/[handle]/settings/members/MembersTab.stories.svelte new file mode 100644 index 000000000..9197d9afc --- /dev/null +++ b/web/src/routes/[handle]/settings/members/MembersTab.stories.svelte @@ -0,0 +1,40 @@ + + + { + const search = canvas.getByRole("searchbox", { name: "Search members" }); + await expect(canvas.getAllByRole("button", { name: /^More actions for / })).toHaveLength( + organizationMembers.length + ); + await expect(canvas.getByRole("link", { name: "Add member" })).toHaveAttribute( + "href", + "/tangled.org/settings/members/new" + ); + await userEvent.type(search, " UsEr.TLD "); + await expect(canvas.getByRole("link", { name: "user.tld" })).toBeVisible(); + await expect(canvas.getAllByRole("button", { name: /^More actions for / })).toHaveLength(1); + await userEvent.clear(search); + await userEvent.type(search, "no-such-member"); + await expect(canvas.getByText("No member matches your search")).toBeVisible(); + await expect( + canvas.queryByRole("button", { name: /^More actions for / }) + ).not.toBeInTheDocument(); + await userEvent.clear(search); + await expect(canvas.queryByText("No member matches your search")).not.toBeInTheDocument(); + for (const member of organizationMembers) { + await expect(canvas.getByRole("link", { name: member.handle })).toBeVisible(); + } + }} +/> diff --git a/web/src/routes/[handle]/settings/members/MembersTab.svelte b/web/src/routes/[handle]/settings/members/MembersTab.svelte index 870ff1052..5614fe2db 100644 --- a/web/src/routes/[handle]/settings/members/MembersTab.svelte +++ b/web/src/routes/[handle]/settings/members/MembersTab.svelte @@ -2,6 +2,10 @@ import { page } from "$app/state"; import { resolve } from "$app/paths"; import Button from "$lib/components/ui/Button.svelte"; + import ConfirmModal from "$lib/components/ui/ConfirmModal.svelte"; + import DropdownItem from "$lib/components/ui/DropdownItem.svelte"; + import Input from "$lib/components/ui/Input.svelte"; + import MoreMenu from "$lib/components/ui/MoreMenu.svelte"; import User from "$lib/components/ui/User.svelte"; import SettingsEmpty from "$lib/components/settings/SettingsEmpty.svelte"; import SettingsHeader from "$lib/components/settings/SettingsHeader.svelte"; @@ -11,15 +15,32 @@ import { organizationMembers } from "$lib/components/profile/organization/mock"; import type { PersonData } from "$lib/components/profile/types"; import Plus from "$icon/plus"; + import Search from "$icon/search"; import Trash from "$icon/trash-2"; const handle = $derived(decodeURIComponent(page.params.handle ?? "")); const addHref = $derived(resolve(`/${handle}/settings/members/new` as "/")); let members = $state([...organizationMembers]); + let query = $state(""); + const filtered = $derived( + members.filter((member) => member.handle.toLowerCase().includes(query.trim().toLowerCase())) + ); - const remove = (did: string) => { - members = members.filter((member) => member.did !== did); + let removeConfirmOpen = $state(false); + let memberPendingRemoval = $state(null); + + const confirmRemove = (member: PersonData) => { + memberPendingRemoval = member; + removeConfirmOpen = true; + }; + + const removeSelected = () => { + const pending = memberPendingRemoval; + if (!pending) return; + members = members.filter((member) => member.did !== pending.did); + memberPendingRemoval = null; + removeConfirmOpen = false; }; @@ -27,6 +48,16 @@
+ {#snippet start()} + + {/snippet} {#snippet end()} {/snippet} @@ -34,24 +65,32 @@ {#if members.length === 0} + {:else if filtered.length === 0} + {:else} - {#each members as member (member.did)} - + {#each filtered as member (member.did)} + {#snippet label()} {/snippet} - + + confirmRemove(member)}> + Remove + + {/each} {/if}
+ +