Improve workspace header in sidebar

This commit is contained in:
Hakan Shehu
2024-11-14 09:20:19 +01:00
parent efe2734cd9
commit d0a45826eb
9 changed files with 95 additions and 102 deletions

View File

@@ -7,7 +7,6 @@ import { AppContext } from '@/renderer/contexts/app';
import { AccountLogout } from '@/renderer/components/accounts/account-logout';
import { AccountSettingsDialog } from '@/renderer/components/accounts/account-settings-dialog';
import { RadarProvider } from '@/renderer/radar-provider';
import { WorkspaceSettingsDialog } from '@/renderer/components/workspaces/workspace-settings-dialog';
export const App = () => {
const navigate = useNavigate();
@@ -15,10 +14,6 @@ export const App = () => {
const [accountSettingsId, setSettingsId] = React.useState<string | null>(
null
);
const [workspaceSettingsId, setWorkspaceSettingsId] = React.useState<
string | null
>(null);
const { data: servers, isPending: isPendingServers } = useQuery({
type: 'server_list',
});
@@ -65,9 +60,6 @@ export const App = () => {
navigate(`/${id}`);
},
showWorkspaceSettings: (id) => {
setWorkspaceSettingsId(id);
},
}}
>
<RadarProvider>
@@ -96,13 +88,6 @@ export const App = () => {
onOpenChange={() => setSettingsId(null)}
/>
)}
{workspaceSettingsId && (
<WorkspaceSettingsDialog
id={workspaceSettingsId}
open={true}
onOpenChange={() => setWorkspaceSettingsId(null)}
/>
)}
</AppContext.Provider>
);
};

View File

@@ -17,14 +17,8 @@ import {
SidebarMenuItem,
useSidebar,
} from '@/renderer/components/ui/sidebar';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/renderer/components/ui/tooltip';
import { ChevronsUpDown, Settings, Plus } from 'lucide-react';
import { useRadar } from '@/renderer/contexts/radar';
import { useApp } from '@/renderer/contexts/app';
interface WorkspaceStateIndicatorProps {
importantCount: number;
@@ -50,7 +44,6 @@ const WorkspaceStateIndicator = ({
};
export const LayoutSidebarHeader = () => {
const app = useApp();
const workspace = useWorkspace();
const account = useAccount();
const navigate = useNavigate();
@@ -58,9 +51,12 @@ export const LayoutSidebarHeader = () => {
const radar = useRadar();
const [open, setOpen] = React.useState(false);
const otherWorkspaceStates = account.workspaces
.filter((w) => w.id !== workspace.id)
.map((w) => radar.getWorkspaceState(w.userId));
const otherWorkspaces = account.workspaces.filter(
(w) => w.id !== workspace.id
);
const otherWorkspaceStates = otherWorkspaces.map((w) =>
radar.getWorkspaceState(w.userId)
);
const importantCount = otherWorkspaceStates.reduce(
(acc, curr) => acc + curr.importantCount,
0
@@ -101,57 +97,67 @@ export const LayoutSidebarHeader = () => {
side={sidebar.isMobile ? 'bottom' : 'right'}
sideOffset={4}
>
<DropdownMenuLabel className="mb-1">Workspace</DropdownMenuLabel>
{account.workspaces.map((workspaceItem) => {
const workspaceState = radar.getWorkspaceState(
workspaceItem.userId
);
return (
<DropdownMenuItem
key={workspaceItem.id}
className="p-0"
onClick={() => {
navigate(`/${account.id}/${workspaceItem.id}`);
}}
>
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar
className="h-8 w-8 rounded-lg"
id={workspaceItem.id}
name={workspaceItem.name}
avatar={workspaceItem.avatar}
/>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{workspaceItem.name}
</span>
<span className="truncate text-xs">Free plan</span>
</div>
<div className="ml-auto flex items-center gap-2 text-muted-foreground mr-1">
<Tooltip>
<TooltipTrigger asChild>
<Settings
className="size-4 hover:cursor-pointer hover:text-sidebar-accent-foreground"
onClick={(e) => {
e.stopPropagation();
app.showWorkspaceSettings(workspaceItem.id);
setOpen(false);
}}
/>
</TooltipTrigger>
<TooltipContent className="flex flex-row items-center gap-2">
Settings
</TooltipContent>
</Tooltip>
</div>
<WorkspaceStateIndicator
importantCount={workspaceState.importantCount}
hasUnseenChanges={workspaceState.hasUnseenChanges}
/>
</div>
</DropdownMenuItem>
);
})}
<DropdownMenuItem key={workspace.id} className="p-0">
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar
className="h-8 w-8 rounded-lg"
id={workspace.id}
name={workspace.name}
avatar={workspace.avatar}
/>
<p className="flex-1 text-left text-sm leading-tight truncate font-semibold">
{workspace.name}
</p>
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="gap-2 p-2 text-muted-foreground"
onClick={() => {
workspace.openSettings();
}}
>
<Settings className="size-4" />
<p className="font-medium">Settings</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
{otherWorkspaces.length > 0 && (
<React.Fragment>
<DropdownMenuLabel className="mb-1">
Other workspaces
</DropdownMenuLabel>
{otherWorkspaces.map((otherWorkspace) => {
const workspaceState = radar.getWorkspaceState(
otherWorkspace.userId
);
return (
<DropdownMenuItem
key={otherWorkspace.id}
className="p-0"
onClick={() => {
navigate(`/${account.id}/${otherWorkspace.id}`);
}}
>
<div className="w-full flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar
className="h-8 w-8 rounded-lg"
id={otherWorkspace.id}
name={otherWorkspace.name}
avatar={otherWorkspace.avatar}
/>
<p className="flex-1 text-left text-sm leading-tight truncate font-normal">
{otherWorkspace.name}
</p>
<WorkspaceStateIndicator
importantCount={workspaceState.importantCount}
hasUnseenChanges={workspaceState.hasUnseenChanges}
/>
</div>
</DropdownMenuItem>
);
})}
</React.Fragment>
)}
<DropdownMenuSeparator className="my-1" />
<DropdownMenuItem
className="gap-2 p-2 text-muted-foreground"

View File

@@ -21,24 +21,20 @@ import { WorkspaceUsers } from '@/renderer/components/workspaces/workspace-users
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { Info, Trash2, Users } from 'lucide-react';
import { match } from 'ts-pattern';
import { useApp } from '@/renderer/contexts/app';
import { useWorkspace } from '@/renderer/contexts/workspace';
interface WorkspaceSettingsDialogProps {
id: string;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const WorkspaceSettingsDialog = ({
id,
open,
onOpenChange,
}: WorkspaceSettingsDialogProps) => {
const app = useApp();
const workspace = useWorkspace();
const [tab, setTab] = React.useState<'info' | 'users' | 'delete'>('info');
const workspace = app.workspaces.find((w) => w.id === id);
if (!workspace) {
return null;
}
@@ -114,8 +110,8 @@ export const WorkspaceSettingsDialog = ({
</Sidebar>
<div className="flex-1 overflow-auto p-7">
{match(tab)
.with('info', () => <WorkspaceUpdate workspace={workspace} />)
.with('users', () => <WorkspaceUsers workspace={workspace} />)
.with('info', () => <WorkspaceUpdate />)
.with('users', () => <WorkspaceUsers />)
.with('delete', () => <p>Coming soon.</p>)
.exhaustive()}
</div>

View File

@@ -1,13 +1,10 @@
import { toast } from '@/renderer/hooks/use-toast';
import { useMutation } from '@/renderer/hooks/use-mutation';
import { WorkspaceForm } from './workspace-form';
import { Workspace } from '@/shared/types/workspaces';
import { useWorkspace } from '@/renderer/contexts/workspace';
interface WorkspaceUpdateProps {
workspace: Workspace;
}
export const WorkspaceUpdate = ({ workspace }: WorkspaceUpdateProps) => {
export const WorkspaceUpdate = () => {
const workspace = useWorkspace();
const { mutate, isPending } = useMutation();
const canEdit = workspace.role === 'owner';

View File

@@ -9,7 +9,7 @@ import { Spinner } from '@/renderer/components/ui/spinner';
import { useMutation } from '@/renderer/hooks/use-mutation';
import { toast } from '@/renderer/hooks/use-toast';
import { Check, ChevronDown } from 'lucide-react';
import { Workspace } from '@/shared/types/workspaces';
import { useWorkspace } from '@/renderer/contexts/workspace';
interface WorkspaceRoleItem {
name: string;
@@ -52,18 +52,17 @@ const roles: WorkspaceRoleItem[] = [
];
interface WorkspaceUserRoleDropdownProps {
workspace: Workspace;
userId: string;
value: WorkspaceRole;
canEdit: boolean;
}
export const WorkspaceUserRoleDropdown = ({
workspace,
userId,
value,
canEdit,
}: WorkspaceUserRoleDropdownProps) => {
const workspace = useWorkspace();
const { mutate, isPending } = useMutation();
const currentRole = roles.find((role) => role.value === value);

View File

@@ -8,15 +8,12 @@ import { InView } from 'react-intersection-observer';
import { WorkspaceUserRoleDropdown } from '@/renderer/components/workspaces/workspace-user-role-dropdown';
import { WorkspaceRole } from '@colanode/core';
import { WorkspaceUserListQueryInput } from '@/shared/queries/workspace-user-list';
import { Workspace } from '@/shared/types/workspaces';
import { useWorkspace } from '@/renderer/contexts/workspace';
const USERS_PER_PAGE = 50;
interface WorkspaceUsersProps {
workspace: Workspace;
}
export const WorkspaceUsers = ({ workspace }: WorkspaceUsersProps) => {
export const WorkspaceUsers = () => {
const workspace = useWorkspace();
const canEditUsers = workspace.role === 'owner' || workspace.role === 'admin';
const [lastPage, setLastPage] = React.useState<number>(1);
@@ -69,7 +66,6 @@ export const WorkspaceUsers = ({ workspace }: WorkspaceUsersProps) => {
<p className="text-sm text-muted-foreground">{email}</p>
</div>
<WorkspaceUserRoleDropdown
workspace={workspace}
userId={user.id}
value={role}
canEdit={canEditUsers}

View File

@@ -1,12 +1,17 @@
import React from 'react';
import { WorkspaceContext } from '@/renderer/contexts/workspace';
import { useParams, useSearchParams } from 'react-router-dom';
import { useAccount } from '@/renderer/contexts/account';
import { Layout } from '@/renderer/components/layouts/layout';
import { WorkspaceSettingsDialog } from '@/renderer/components/workspaces/workspace-settings-dialog';
export const Workspace = () => {
const { workspaceId } = useParams<{ workspaceId: string }>();
const account = useAccount();
const [searchParams, setSearchParams] = useSearchParams();
const account = useAccount();
const [openSettings, setOpenSettings] = React.useState(false);
const workspace = account.workspaces.find((w) => w.id === workspaceId);
if (!workspace) {
@@ -43,6 +48,9 @@ export const Workspace = () => {
return prev;
});
},
openSettings() {
setOpenSettings(true);
},
markAsSeen() {
// window.colanode.executeMutation({
// type: 'mark_node_as_seen',
@@ -54,6 +62,12 @@ export const Workspace = () => {
}}
>
<Layout main={main} modal={modal} />
{openSettings && (
<WorkspaceSettingsDialog
open={openSettings}
onOpenChange={() => setOpenSettings(false)}
/>
)}
</WorkspaceContext.Provider>
);
};

View File

@@ -11,7 +11,6 @@ interface AppContext {
setAccount: (id: string) => void;
showAccountLogout: (id: string) => void;
showAccountSettings: (id: string) => void;
showWorkspaceSettings: (id: string) => void;
}
export const AppContext = createContext<AppContext>({} as AppContext);

View File

@@ -7,6 +7,7 @@ interface WorkspaceContext extends Workspace {
openModal: (nodeId: string) => void;
closeModal: () => void;
markAsSeen: (nodeId: string, versionId: string) => void;
openSettings: () => void;
}
export const WorkspaceContext = createContext<WorkspaceContext>(