Skip to content

Commit

Permalink
feat: join and leave workspace flows
Browse files Browse the repository at this point in the history
  • Loading branch information
nikkothari22 committed Dec 13, 2024
1 parent 0b55104 commit cd26552
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 5 deletions.
13 changes: 10 additions & 3 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ const router = createBrowserRouter(
<Route path="users" lazy={() => import('./pages/settings/Users/UserList')} />
<Route path="appearance" lazy={() => import('./pages/settings/Appearance')} />
<Route path="hr" lazy={() => import('./pages/settings/Integrations/FrappeHR')} />

<Route path="workspaces" >
<Route index lazy={() => import('./pages/settings/Workspaces/WorkspaceList')} />
<Route path="create" lazy={() => import('./pages/settings/AI/CreateBot')} />
<Route path=":ID" lazy={() => import('./pages/settings/AI/ViewBot')} />
</Route>

<Route path="bots" >
<Route index lazy={() => import('./pages/settings/AI/BotList')} />
<Route path="create" lazy={() => import('./pages/settings/AI/CreateBot')} />
Expand Down Expand Up @@ -130,9 +137,9 @@ function App() {
url={import.meta.env.VITE_FRAPPE_PATH ?? ''}
socketPort={import.meta.env.VITE_SOCKET_PORT ? import.meta.env.VITE_SOCKET_PORT : undefined}
//@ts-ignore
// swrConfig={{
// provider: localStorageProvider
// }}
swrConfig={{
errorRetryCount: 2,
}}
siteName={getSiteName()}
>
<UserProvider>
Expand Down
36 changes: 36 additions & 0 deletions frontend/src/components/feature/workspaces/JoinWorkspaceButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { DropdownMenu } from '@radix-ui/themes'
import { BiLogIn } from 'react-icons/bi'
import { WorkspaceFields } from '@/hooks/fetchers/useFetchWorkspaces'
import { useFrappePostCall, useSWRConfig } from 'frappe-react-sdk'
import { toast } from 'sonner'
import { getErrorMessage } from '@/components/layout/AlertBanner/ErrorBanner'

type Props = {
workspace: WorkspaceFields
}

const JoinWorkspaceButton = ({ workspace }: Props) => {

const { call } = useFrappePostCall('raven.api.workspaces.join_workspace')

const { mutate } = useSWRConfig()

const joinWorkspace = () => {
toast.promise(call({ workspace: workspace.name }).then(() => {
mutate('workspaces_list')
mutate('channel_list')
}), {
loading: 'Joining workspace...',
success: 'You have joined the workspace.',
error: (error) => `There was an error while joining the workspace.\n${getErrorMessage(error)}`,
})
}
return (
<DropdownMenu.Item onClick={joinWorkspace}>
<BiLogIn fontSize={16} />
Join
</DropdownMenu.Item>
)
}

export default JoinWorkspaceButton
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { DropdownMenu } from '@radix-ui/themes'
import { BiLogOut } from 'react-icons/bi'
import { WorkspaceFields } from '@/hooks/fetchers/useFetchWorkspaces'
import { useFrappePostCall, useSWRConfig } from 'frappe-react-sdk'
import { toast } from 'sonner'
import { getErrorMessage } from '@/components/layout/AlertBanner/ErrorBanner'

type Props = {
workspace: WorkspaceFields
}

const LeaveWorkspaceButton = ({ workspace }: Props) => {

const { call } = useFrappePostCall('raven.api.workspaces.leave_workspace')

const { mutate } = useSWRConfig()

const leaveWorkspace = () => {
toast.promise(call({ workspace: workspace.name }).then(() => {
mutate('workspaces_list')
mutate('channel_list')
}), {
loading: 'Leaving workspace...',
success: 'You have left the workspace.',
error: (error) => `There was an error while leaving the workspace.\n${getErrorMessage(error)}`,
})
}
return (
<DropdownMenu.Item onClick={leaveWorkspace} color='red'>
<BiLogOut fontSize={16} />
Leave
</DropdownMenu.Item>
)
}

export default LeaveWorkspaceButton
31 changes: 31 additions & 0 deletions frontend/src/components/feature/workspaces/WorkspaceActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { WorkspaceFields } from '@/hooks/fetchers/useFetchWorkspaces'
import { DropdownMenu, IconButton } from '@radix-ui/themes'
import { BiDotsVerticalRounded } from 'react-icons/bi'
import LeaveWorkspaceButton from './LeaveWorkspaceButton'
import WorkspaceSettingsButton from './WorkspaceSettingsButton'
import JoinWorkspaceButton from './JoinWorkspaceButton'

type Props = {
workspace: WorkspaceFields
}

const WorkspaceActions = ({ workspace }: Props) => {
return (
<div className='flex items-center gap-2 justify-center h-full'>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<IconButton variant='ghost' color='gray' size='3'>
<BiDotsVerticalRounded fontSize={16} />
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content className='min-w-32'>
{workspace.is_admin ? <WorkspaceSettingsButton workspace={workspace} /> : null}
{workspace.workspace_member_name ? <LeaveWorkspaceButton workspace={workspace} /> : <JoinWorkspaceButton workspace={workspace} />}
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>

)
}

export default WorkspaceActions
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { WorkspaceFields } from '@/hooks/fetchers/useFetchWorkspaces'
import { DropdownMenu } from '@radix-ui/themes'
import { BiCog } from 'react-icons/bi'

type Props = {
workspace: WorkspaceFields
}

const WorkspaceSettingsButton = (props: Props) => {
return (
<DropdownMenu.Item color='gray'>
<BiCog fontSize={16} />
Settings
</DropdownMenu.Item>
)
}

export default WorkspaceSettingsButton
2 changes: 1 addition & 1 deletion frontend/src/pages/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const Settings = () => {
</header>
<Flex className="w-full">
{isDesktop && <SettingsSidebar />}
<Box className="w-full ml-64 sm:ml-0">
<Box className="w-full sm:ml-64 ml-0">
<Outlet />
</Box>
</Flex>
Expand Down
91 changes: 91 additions & 0 deletions frontend/src/pages/settings/Workspaces/WorkspaceList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { UserAvatar } from '@/components/common/UserAvatar'
import WorkspaceActions from '@/components/feature/workspaces/WorkspaceActions'
import { ErrorBanner } from '@/components/layout/AlertBanner/ErrorBanner'
import { TableLoader } from '@/components/layout/Loaders/TableLoader'
import PageContainer from '@/components/layout/Settings/PageContainer'
import SettingsContentContainer from '@/components/layout/Settings/SettingsContentContainer'
import SettingsPageHeader from '@/components/layout/Settings/SettingsPageHeader'
import { HStack } from '@/components/layout/Stack'
import useFetchWorkspaces, { WorkspaceFields } from '@/hooks/fetchers/useFetchWorkspaces'
import { hasRavenAdminRole } from '@/utils/roles'
import { Badge, Button, Table, Text, Tooltip } from '@radix-ui/themes'
import { Link } from 'react-router-dom'
import { VscVerifiedFilled } from 'react-icons/vsc'
import { BiSolidCrown } from 'react-icons/bi'

type Props = {}

const WorkspaceList = (props: Props) => {

const { data: myWorkspaces, isLoading, error } = useFetchWorkspaces()

const isRavenAdmin = hasRavenAdminRole()

return (
<PageContainer>
<SettingsContentContainer>
<SettingsPageHeader
title='Workspaces'
description='Workspaces allow you to organize your channels and teams.'
actions={<Button asChild disabled={!isRavenAdmin}>
<Link to='create'>Create</Link>
</Button>}
/>
{isLoading && !error && <TableLoader columns={2} />}
<ErrorBanner error={error} />
{myWorkspaces && <MyWorkspacesTable workspaces={myWorkspaces.message} />}
</SettingsContentContainer>
</PageContainer>
)
}

const MyWorkspacesTable = ({ workspaces }: { workspaces: WorkspaceFields[] }) => {
return (
<Table.Root variant="surface" className='rounded-sm'>
<Table.Header>
<Table.Row>
<Table.ColumnHeaderCell>Name</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell>Type</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell>Description</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell></Table.ColumnHeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{workspaces?.map((workspace) => (
<Table.Row key={workspace.name}>
<Table.Cell maxWidth={"150px"}>
<HStack align='center'>
<HStack align='center'>
<UserAvatar src={workspace.logo} alt={workspace.workspace_name} />
<Text weight='medium'>{workspace.workspace_name}</Text>
<HStack>
{workspace.workspace_member_name ? <Tooltip content='You are a member'>
<Text as='span' className='h-full flex items-center'>
<VscVerifiedFilled fontSize={18} className='text-green-9' />
</Text>
</Tooltip> : null}
{workspace.is_admin ? <Tooltip content='You are an admin'>
<Text as='span' className='h-full flex items-center'>
<BiSolidCrown fontSize={18} color='#FFC53D' />
</Text>
</Tooltip> : null}
</HStack>
</HStack>
</HStack>
</Table.Cell>
<Table.Cell>
<Badge color={workspace.type === 'Private' ? 'blue' : 'green'}>{workspace.type}</Badge>
</Table.Cell>
<Table.Cell maxWidth={"250px"}>
<Text className='line-clamp-1 text-ellipsis'>{workspace.description}</Text>
</Table.Cell>
<Table.Cell>
<WorkspaceActions workspace={workspace} />
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
)
}
export const Component = WorkspaceList
20 changes: 19 additions & 1 deletion raven/api/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,28 @@ def join_workspace(workspace: str):
"""
Joins a workspace
"""
frappe.get_doc(
member = frappe.get_doc(
{"doctype": "Raven Workspace Member", "workspace": workspace, "user": frappe.session.user}
).insert()

return member


@frappe.whitelist()
def leave_workspace(workspace: str):
"""
Leaves a workspace
"""
member = frappe.db.exists(
"Raven Workspace Member", {"workspace": workspace, "user": frappe.session.user}
)
if member:
member_doc = frappe.get_doc("Raven Workspace Member", member)
member_doc.delete()

else:
frappe.throw(_("You are not a member of this workspace."), frappe.PermissionError)


@frappe.whitelist()
def is_workspace_admin(workspace: str):
Expand Down

0 comments on commit cd26552

Please sign in to comment.