Skip to content

Commit

Permalink
Feat: suspend users from team management page (#360)
Browse files Browse the repository at this point in the history
* added search bar and three dots option and also fixed global search in labels

* added user icon for style in add user to admin text

* Feat: UI to suspend users

---------

Co-authored-by: Dhruv218 <[email protected]>
  • Loading branch information
harshithmullapudi and Dhruv218 authored Jan 9, 2025
1 parent 20ad1fa commit 2b53c82
Show file tree
Hide file tree
Showing 21 changed files with 234 additions and 35 deletions.
12 changes: 12 additions & 0 deletions apps/server/src/modules/workspaces/workspaces.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ export class WorkspacesController {
);
}

@Post('suspend')
@UseGuards(AuthGuard, AdminGuard)
async suspendUser(
@WorkspaceD() workspaceId: string,
@Body() userBody: UserBody,
) {
return await this.workspacesService.suspendUser(
workspaceId,
userBody.userId,
);
}

@Post('add_users')
@UseGuards(AuthGuard)
async addUserToWorkspace(
Expand Down
21 changes: 21 additions & 0 deletions apps/server/src/modules/workspaces/workspaces.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,4 +367,25 @@ export default class WorkspacesService {
);
res.status(200).json(invite);
}

async suspendUser(workspaceId: string, userId: string) {
const userOnWorkspace =
await this.prisma.usersOnWorkspaces.findUniqueOrThrow({
where: {
userId_workspaceId: {
workspaceId,
userId,
},
},
});

await this.prisma.usersOnWorkspaces.update({
where: {
id: userOnWorkspace.id,
},
data: {
status: 'SUSPENDED',
},
});
}
}
1 change: 1 addition & 0 deletions apps/webapp/src/common/types/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface UsersOnWorkspaceType {
createdAt: string;
updatedAt: string;
role: string;
status: string;

userId: string;
workspaceId: string;
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/src/modules/ai/conversation-item.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UserTypeEnum } from '@tegonhq/types';
import { Button } from '@tegonhq/ui/components/button';
import { defaultExtensions } from '@tegonhq/ui/components/ui/editor/editor-extensions';
import { defaultExtensions } from '@tegonhq/ui/components/editor/editor-extensions';
import { AI } from '@tegonhq/ui/icons';
import { Editor } from '@tiptap/core';
import { observer } from 'mobx-react-lite';
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/src/modules/ai/conversation-textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AdjustableTextArea } from '@tegonhq/ui/components/ui/adjustable-textarea';
import { Button } from '@tegonhq/ui/components/ui/button';
import { AdjustableTextArea } from '@tegonhq/ui/components/adjustable-textarea';
import { Button } from '@tegonhq/ui/components/button';
import { SendLine } from '@tegonhq/ui/icons';
import { useState } from 'react';

Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/src/modules/ai/conversation.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UserTypeEnum } from '@tegonhq/types';
import { ScrollArea } from '@tegonhq/ui/components/ui/scroll-area';
import { ScrollArea } from '@tegonhq/ui/components/scroll-area';
import { cn } from '@tegonhq/ui/lib/utils';
import { observer } from 'mobx-react-lite';
import getConfig from 'next/config';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Separator } from '@tegonhq/ui/components/ui/separator';
import { Separator } from '@tegonhq/ui/components/separator';
import { observer } from 'mobx-react-lite';

import type { IssueType, TeamType } from 'common/types';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button } from '@tegonhq/ui/components/button';
import { useEditor } from '@tegonhq/ui/components/editor/editor';
import { uploadFileFn, uploadFn } from '@tegonhq/ui/components/editor/utils';
import { useEditor } from '@tegonhq/ui/components/ui/editor/editor';
import { Paperclip } from 'lucide-react';

interface FileUploadProps {
Expand Down
10 changes: 9 additions & 1 deletion apps/webapp/src/modules/settings/team-settings/members.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useCurrentTeam } from 'hooks/teams';
import { useUsersData } from 'hooks/users';

import { useContextStore } from 'store/global-context-provider';
import { UserContext } from 'store/user-context';

import { ShowMembersDropdown } from './show-members-dropdown';
import { SettingSection } from '../setting-section';
Expand All @@ -19,10 +20,16 @@ export const Members = observer(() => {
const team = useCurrentTeam();
const { workspaceStore } = useContextStore();
const usersOnWorkspace = workspaceStore.usersOnWorkspaces;
const currentUser = React.useContext(UserContext);
const userRole = workspaceStore.getUserData(currentUser.id).role;

const userIds = usersOnWorkspace
.filter((uOW: UsersOnWorkspaceType) => {
return uOW.teamIds.includes(team.id) && uOW.role !== RoleEnum.BOT;
return (
uOW.teamIds.includes(team.id) &&
uOW.role !== RoleEnum.BOT &&
uOW.status !== 'SUSPENDED'
);
})
.map((uOW: UsersOnWorkspaceType) => uOW.userId);

Expand Down Expand Up @@ -56,6 +63,7 @@ export const Members = observer(() => {
id={userData.id}
name={userData.fullname}
email={userData.email}
isAdmin={userRole === 'ADMIN'}
teamId={team.id}
className={
index === users.length - 1 && 'pb-0 !border-b-0'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RiClipboardLine } from '@remixicon/react';
import { Button } from '@tegonhq/ui/components/button';
import { Input } from '@tegonhq/ui/components/input';
import { useToast } from '@tegonhq/ui/components/ui/use-toast';
import { useToast } from '@tegonhq/ui/components/use-toast';
import copy from 'copy-to-clipboard';
import { observer } from 'mobx-react-lite';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export const Labels = observer(() => {
{labelsStore.labels
.filter(
(label: LabelType) =>
label.name.includes(searchValue) && !label.teamId,
label.name.toLowerCase().includes(searchValue.toLowerCase()) &&
!label.teamId,
)
.map((label: LabelType) => {
if (editLabelState === label.id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@ interface MemberItemProps {
email: string;
id: string;
teamId?: string;
isAdmin?: boolean;
isSuspended?: boolean;
}

export const MemberItem = observer(
({ name, className, email, id, teamId }: MemberItemProps) => {
({
name,
className,
email,
id,
teamId,
isAdmin,
isSuspended,
}: MemberItemProps) => {
const { workspaceStore } = useContextStore();

const userOnWorkspace = workspaceStore.usersOnWorkspaces.find(
Expand All @@ -29,7 +39,8 @@ export const MemberItem = observer(
<div
className={cn(
className,
'flex items-center justify-between bg-background-3 p-3 rounded-lg',
'flex items-center justify-between bg-background-3 px-4 py-2 rounded-lg',
isSuspended && 'bg-grayAlpha-100',
)}
>
<div className="flex gap-2 items-center">
Expand All @@ -44,12 +55,12 @@ export const MemberItem = observer(
<div className="text-sm flex gap-2 items-center">
<div>{userOnWorkspace?.role}</div>

{teamId && (
<MemberOptionsDropdown
userId={userOnWorkspace.userId}
teamId={teamId}
/>
)}
<MemberOptionsDropdown
userId={userOnWorkspace.userId}
teamId={teamId}
isAdmin={isAdmin}
isSuspended={isSuspended}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@ import {
DropdownMenuTrigger,
} from '@tegonhq/ui/components/dropdown-menu';
import { useToast } from '@tegonhq/ui/components/use-toast';
import { DeleteLine, MoreLine } from '@tegonhq/ui/icons';
import { CanceledLine, DeleteLine, MoreLine } from '@tegonhq/ui/icons';
import React from 'react';

import { useRemoveTeamMemberMutation } from 'services/team';
import { useSuspendUserMutation } from 'services/workspace';

interface MemberOptionsDropdownProps {
userId: string;
teamId: string;
isAdmin: boolean;
isSuspended: boolean;
}

export function MemberOptionsDropdown({
userId,
teamId,
isAdmin,
isSuspended,
}: MemberOptionsDropdownProps) {
const { toast } = useToast();
const { mutate: removeMember } = useRemoveTeamMemberMutation({
Expand All @@ -32,6 +37,19 @@ export function MemberOptionsDropdown({
},
});

const { mutate: suspendUser } = useSuspendUserMutation({
onSuccess: () => {
toast({
title: 'Success',
description: 'User has been suspended',
});
},
});

if (!isAdmin || isSuspended) {
return null;
}

return (
<div>
<DropdownMenu>
Expand All @@ -48,18 +66,35 @@ export function MemberOptionsDropdown({
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => {
removeMember({
userId,
teamId,
});
}}
>
<div className="flex items-center gap-1">
<DeleteLine size={16} /> Remove from team
</div>
</DropdownMenuItem>
{isAdmin && (
<>
<DropdownMenuItem
onClick={() => {
suspendUser({
userId,
});
}}
>
<div className="flex items-center gap-1">
<CanceledLine size={16} /> Suspend
</div>
</DropdownMenuItem>
</>
)}
{teamId && isAdmin && (
<DropdownMenuItem
onClick={() => {
removeMember({
userId,
teamId,
});
}}
>
<div className="flex items-center gap-1">
<DeleteLine size={16} /> Remove from team
</div>
</DropdownMenuItem>
)}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button } from '@tegonhq/ui/components/button';
import { Input } from '@tegonhq/ui/components/input';
import { Loader } from '@tegonhq/ui/components/loader';
import { observer } from 'mobx-react-lite';
import React from 'react';
Expand All @@ -9,12 +10,37 @@ import type { User } from 'common/types';

import { useUsersData } from 'hooks/users';

import { useContextStore } from 'store/global-context-provider';
import { UserContext } from 'store/user-context';

import { AddMemberDialog } from './add-member-dialog';
import { MemberItem } from './member-item';

export const Members = observer(() => {
const { users, isLoading } = useUsersData(false);
const { workspaceStore } = useContextStore();
const [newMemberDialog, setNewMemberDialog] = React.useState(false);
const [searchValue, setSearchValue] = React.useState('');
const currentUser = React.useContext(UserContext);
const userRole = workspaceStore.getUserData(currentUser.id)?.role;

const getUsers = (isSuspened: boolean = false) => {
const nonSuspendedUsers = users.filter((user) =>
isSuspened
? workspaceStore.getUserData(user.id).status === 'SUSPENDED'
: workspaceStore.getUserData(user.id).status !== 'SUSPENDED',
);

if (searchValue) {
return nonSuspendedUsers.filter(
(user) =>
user.fullname.toLowerCase().includes(searchValue.toLowerCase()) ||
user.email.toLowerCase().includes(searchValue.toLowerCase()),
);
}

return nonSuspendedUsers;
};

return (
<>
Expand All @@ -34,20 +60,47 @@ export const Members = observer(() => {
>
Add member
</Button>
<h3 className="text-xs">{users.length} Members </h3>

<div className="flex">
<Input
placeholder="Filter by name"
onChange={(e) => setSearchValue(e.currentTarget.value)}
/>
</div>
</div>

<div className="mt-4 flex flex-col gap-2">
{users.map((userData: User, index) => (
<div className="mt-4 flex flex-col gap-1">
{getUsers().map((userData: User, index) => (
<MemberItem
key={userData.id}
id={userData.id}
name={userData.fullname}
email={userData.email}
isAdmin={userRole === 'ADMIN'}
className={index === users.length - 1 && 'pb-0 !border-b-0'}
/>
))}
</div>

{getUsers(true).length > 0 && (
<div className="mt-4">
<h2 className="mb-1"> Suspended </h2>

{getUsers(true).map((userData: User, index) => (
<MemberItem
key={userData.id}
id={userData.id}
name={userData.fullname}
email={userData.email}
isSuspended
isAdmin={userRole === 'ADMIN'}
className={
index === users.length - 1 && 'pb-0 !border-b-0'
}
/>
))}
</div>
)}
</div>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/src/services/workspace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './invite-action';
export * from './update-workspace';
export * from './create-initial-resources';
export * from './update-workspace-preferences';
export * from './suspend-user';
Loading

0 comments on commit 2b53c82

Please sign in to comment.