Skip to content

Commit

Permalink
Add members to channels in private communities FE (#6195)
Browse files Browse the repository at this point in the history
  • Loading branch information
megrogan authored Aug 12, 2024
1 parent aab6bfb commit d327695
Show file tree
Hide file tree
Showing 49 changed files with 392 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
export let permissions: ChatPermissions;
export let isPublic: boolean;
export let isCommunityPublic: boolean;
export let isChannel: boolean;
let selectedTab = 0;
let roles = [...chatRoles];
Expand Down Expand Up @@ -45,6 +46,12 @@
{roles}
label={i18nKey("permissions.updateGroup")}
bind:rolePermission={permissions.updateGroup} />
{#if isChannel && !isCommunityPublic}
<SelectPermissionRole
{roles}
label={i18nKey("permissions.addMembers")}
bind:rolePermission={permissions.addMembers} />
{/if}
<SelectPermissionRole
{roles}
label={i18nKey("permissions.inviteUsers")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
export let permissions: ChatPermissions;
export let isPublic: boolean;
export let isCommunityPublic: boolean;
export let isChannel: boolean;
let generalPartition: PermissionsByRole;
let messagePartition: PermissionsByRole;
Expand All @@ -24,6 +26,7 @@
changeRoles: permissions.changeRoles,
updateGroup: permissions.updateGroup,
inviteUsers: permissions.inviteUsers,
addMembers: permissions.addMembers,
removeMembers: permissions.removeMembers,
deleteMessages: permissions.deleteMessages,
startVideoCall: permissions.startVideoCall,
Expand Down Expand Up @@ -73,6 +76,9 @@
if (isPublic && key === "inviteUsers") {
return false;
}
if (key === "addMembers" && (!isChannel || isCommunityPublic)) {
return false;
}
return true;
}
Expand Down
1 change: 1 addition & 0 deletions frontend/app/src/components/home/Home.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@
updateGroup: "admin",
pinMessages: "admin",
inviteUsers: "admin",
addMembers: "admin",
mentionAllMembers: "member",
reactToMessages: "member",
startVideoCall: "member",
Expand Down
7 changes: 7 additions & 0 deletions frontend/app/src/components/home/RightPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
return client.searchUsersForInvite(term, 20, level, false, canInvite);
}
function searchMembers(term: string): Promise<[UserSummary[], UserSummary[]]> {
return client.searchCommunityMembersToAdd(term, 20);
}
function onChangeGroupRole(
ev: CustomEvent<{ userId: string; newRole: MemberRole; oldRole: MemberRole }>,
): void {
Expand Down Expand Up @@ -379,6 +383,7 @@
userLookup={searchUsers}
busy={invitingUsers}
closeIcon={$rightPanelHistory.length > 1 ? "back" : "close"}
isCommunityPublic={$selectedCommunity?.public ?? true}
on:inviteUsers={inviteCommunityUsers}
on:cancelInviteUsers={popRightPanelHistory} />
{:else if lastState.kind === "show_community_members" && $selectedCommunity !== undefined}
Expand All @@ -401,8 +406,10 @@
container={$multiUserChat}
{level}
userLookup={searchUsers}
memberLookup={searchMembers}
busy={invitingUsers}
closeIcon={$rightPanelHistory.length > 1 ? "back" : "close"}
isCommunityPublic={$selectedCommunity?.public ?? true}
on:inviteUsers={inviteGroupUsers}
on:cancelInviteUsers={popRightPanelHistory} />
{:else if lastState.kind === "show_group_members" && $selectedChatId !== undefined && $multiUserChat !== undefined}
Expand Down
7 changes: 5 additions & 2 deletions frontend/app/src/components/home/addgroup/NewGroup.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -351,11 +351,14 @@
{editing}
bind:permissions={candidateGroup.permissions}
isPublic={candidateGroup.public}
isCommunityPublic={$selectedCommunity?.public ?? true} />
isCommunityPublic={$selectedCommunity?.public ?? true}
isChannel={candidateGroup.id.kind === "channel"} />
{:else}
<GroupPermissionsViewer
bind:permissions={candidateGroup.permissions}
isPublic={candidateGroup.public} />
isPublic={candidateGroup.public}
isCommunityPublic={$selectedCommunity?.public ?? true}
isChannel={candidateGroup.id.kind === "channel"} />
{/if}
</div>
{#if !editing && !hideInviteUsers}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
$: canInvite =
client.canInviteUsers(chat.id) && (chat.kind !== "channel" || !client.isChatPrivate(chat));
$: avatarSrc = client.groupAvatarUrl(chat);
$: selectedCommunity = client.selectedCommunity;
$: currentChatRules = client.currentChatRules;
$: currentCommunityRules = client.currentCommunityRules;
$: combinedRulesText = canSend
Expand Down Expand Up @@ -172,7 +172,12 @@
on:toggle={groupPermissionsOpen.toggle}
open={$groupPermissionsOpen}
headerText={i18nKey("permissions.permissions")}>
<GroupPermissionsViewer bind:permissions={chat.permissions} isPublic={chat.public} />
<GroupPermissionsViewer
bind:permissions={chat.permissions}
isPublic={chat.public}
isCommunityPublic={$selectedCommunity?.public ?? true}
isChannel={chat.id.kind === "channel"} />

</CollapsibleCard>
<CollapsibleCard
on:toggle={groupStatsOpen.toggle}
Expand Down
164 changes: 142 additions & 22 deletions frontend/app/src/components/home/groupdetails/InviteUsers.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,102 @@
import { _ } from "svelte-i18n";
import SelectUsers from "../SelectUsers.svelte";
import type {
ChannelIdentifier,
CommunitySummary,
Level,
MultiUserChat,
OpenChat,
UserSummary,
} from "openchat-client";
import ArrowLeft from "svelte-material-icons/ArrowLeft.svelte";
import { createEventDispatcher, getContext } from "svelte";
import { createEventDispatcher, getContext, onMount } from "svelte";
import { iconSize } from "../../../stores/iconSize";
import InviteUsersWithLink from "../InviteUsersWithLink.svelte";
import CollapsibleCard from "../../CollapsibleCard.svelte";
import { i18nKey } from "../../../i18n/i18n";
import Translatable from "../../Translatable.svelte";
import { toastStore } from "../../../stores/toast";
import { popRightPanelHistory } from "../../../stores/rightPanel";
const client = getContext<OpenChat>("client");
const dispatch = createEventDispatcher();
export let closeIcon: "close" | "back";
export let busy = false;
export let userLookup: (searchTerm: string) => Promise<[UserSummary[], UserSummary[]]>;
export let memberLookup: ((searchTerm: string) => Promise<[UserSummary[], UserSummary[]]>) | undefined = undefined;
export let level: Level;
export let container: MultiUserChat | CommunitySummary;
export let isCommunityPublic: boolean;
type Tab = "invite_users" | "add_members" | "share";
$: canInvite = client.canInviteUsers(container.id);
$: canAdd = !isCommunityPublic && container.kind === "channel" && client.canAddMembers(container.id);
const dispatch = createEventDispatcher();
let usersToInvite: UserSummary[] = [];
let usersToAddOrInvite: UserSummary[] = [];
let selectedTab: Tab = "share";
onMount(() => {
selectedTab = canAdd ? "add_members" : canInvite ? "invite_users" : "share";
});
function cancelInviteUsers() {
dispatch("cancelInviteUsers");
}
function inviteUsers() {
dispatch("inviteUsers", usersToInvite);
dispatch("inviteUsers", usersToAddOrInvite);
}
async function addMembers() {
busy = true;
const userIds = usersToAddOrInvite.map((u) => u.userId);
await client
.addMembersToChannel(container.id as ChannelIdentifier, userIds)
.then((resp) => {
switch (resp.kind) {
case "success": {
popRightPanelHistory();
break;
}
case "add_to_channel_partial_success": {
popRightPanelHistory();
toastStore.showSuccessToast(i18nKey("group.addMembersPartialSuccess"));
break;
}
default: {
toastStore.showFailureToast(i18nKey("group.addMembersFailed"));
break;
}
}
})
.catch(() => {
toastStore.showFailureToast(
i18nKey("group.addMembersFailed"),
);
});
busy = false;
}
function deleteUser(ev: CustomEvent<UserSummary>) {
usersToInvite = usersToInvite.filter((u) => u.userId !== ev.detail.userId);
usersToAddOrInvite = usersToAddOrInvite.filter((u) => u.userId !== ev.detail.userId);
}
function selectUser(ev: CustomEvent<UserSummary>) {
usersToInvite = [...usersToInvite, ev.detail];
usersToAddOrInvite = [...usersToAddOrInvite, ev.detail];
}
function selectTab(tab: Tab) {
selectedTab = tab;
usersToAddOrInvite = [];
}
</script>

<SectionHeader border={false} flush>
<h4><Translatable resourceKey={i18nKey("group.inviteUsers", undefined, level, true)} /></h4>
<h4><Translatable resourceKey={canAdd ? i18nKey("group.addOrInviteUsers") : i18nKey("group.inviteUsers", undefined, level, true)} /></h4>
<span title={$_("close")} class="close" on:click={cancelInviteUsers}>
<HoverIcon>
{#if closeIcon === "close"}
Expand All @@ -64,40 +114,80 @@
</span>
</SectionHeader>

{#if canInvite || canAdd}
<div class="tabs">
{#if canAdd}
<div
tabindex="0"
role="button"
on:click={() => selectTab("add_members")}
class:selected={selectedTab === "add_members"}
class="tab">
<Translatable resourceKey={i18nKey("group.addMembersTab")} />
</div>
{/if}
{#if canInvite}
<div
tabindex="0"
role="button"
on:click={() => selectTab("invite_users")}
class:selected={selectedTab === "invite_users"}
class="tab">
<Translatable resourceKey={i18nKey("group.inviteUsersTab")} />
</div>
{/if}
<div
tabindex="0"
role="button"
on:click={() => selectTab("share")}
class:selected={selectedTab === "share"}
class="tab">
<Translatable resourceKey={i18nKey("group.shareTab")} />
</div>
</div>
{/if}

{#if !busy}
<div class="find-user">
{#if canInvite}
<CollapsibleCard open headerText={i18nKey("searchForUsername")}>
<div>
{#if selectedTab === "invite_users"}
<div class="subheading"><Translatable resourceKey={i18nKey("group.searchForUser")} /></div>
<SelectUsers
{userLookup}
mode={"edit"}
on:selectUser={selectUser}
on:deleteUser={deleteUser}
selectedUsers={usersToInvite} />
</CollapsibleCard>
{/if}
<CollapsibleCard
open
headerText={i18nKey("invite.inviteWithLink", undefined, container.level, true)}>
<InviteUsersWithLink {container} />
</CollapsibleCard>
selectedUsers={usersToAddOrInvite} />
{:else if selectedTab === "add_members" && memberLookup !== undefined}
<div class="subheading"><Translatable resourceKey={i18nKey("group.searchForCommunityMember")} /></div>
<SelectUsers
userLookup={memberLookup}
mode={"edit"}
on:selectUser={selectUser}
on:deleteUser={deleteUser}
selectedUsers={usersToAddOrInvite} />
{:else}
<div class="subheading"><Translatable resourceKey={i18nKey("invite.inviteWithLink", undefined, container.level, true)} /></div>
<InviteUsersWithLink {container} />
{/if}
</div>
</div>
{/if}

{#if canInvite}
{#if selectedTab !== "share"}
{#if busy}
<Loading />
{/if}

<div class="cta">
<Button
disabled={busy || usersToInvite.length === 0}
disabled={busy || usersToAddOrInvite.length === 0}
loading={busy}
square
on:click={inviteUsers}
on:click={selectedTab === "invite_users" ? inviteUsers : addMembers}
fill
><Translatable
resourceKey={i18nKey("group.inviteUsers", undefined, level, true)} /></Button>
resourceKey={selectedTab === "invite_users" ? i18nKey("group.inviteUsers", undefined, level, true) : i18nKey("group.addMembers")} /></Button>
</div>
{/if}

Expand Down Expand Up @@ -130,4 +220,34 @@
.cta {
flex: 0 0 toRem(60);
}
.subheading {
margin-bottom: $sp4;
}
.tabs {
display: flex;
align-items: center;
@include font(medium, normal, fs-90);
color: var(--txt-light);
gap: $sp5;
border-bottom: 1px solid var(--bd);
cursor: pointer;
margin: 0 $sp4 $sp5 $sp4;
@include mobile() {
gap: $sp4;
}
.tab {
padding-bottom: 10px;
margin-bottom: -2px;
border-bottom: 3px solid transparent;
white-space: nowrap;
&.selected {
color: var(--txt);
border-bottom: 3px solid var(--txt);
}
}
}
</style>
Loading

0 comments on commit d327695

Please sign in to comment.