mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Improve workspace header in sidebar
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>(
|
||||
|
||||
Reference in New Issue
Block a user