Improve routing

This commit is contained in:
Hakan Shehu
2025-09-19 19:35:03 +02:00
parent 9a288eabda
commit 4fdc1fe8e4
44 changed files with 179 additions and 329 deletions

View File

@@ -1,92 +0,0 @@
import { useMatch, useNavigate } from '@tanstack/react-router';
import { useEffect, useState } from 'react';
import { Account } from '@colanode/client/types';
import { AccountContext } from '@colanode/ui/contexts/account';
import { useAppMetadata } from '@colanode/ui/contexts/app-metadata';
const fetchAccountForWorkspace = async (
workspaceId?: string
): Promise<Account[] | null> => {
const accounts = await window.colanode.executeQuery({
type: 'account.list',
});
if (accounts.length === 0) {
return null;
}
if (!workspaceId) {
return [accounts[0]!];
}
const accountsForWorkspace: Account[] = [];
for (const account of accounts) {
const workspaces = await window.colanode.executeQuery({
type: 'workspace.list',
accountId: account.id,
});
if (workspaces.some((workspace) => workspace.id === workspaceId)) {
accountsForWorkspace.push(account);
}
}
if (accountsForWorkspace.length === 0) {
return [accounts[0]!];
}
return accountsForWorkspace;
};
export const AccountProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const appMetadata = useAppMetadata();
const navigate = useNavigate();
const match = useMatch({ from: '/$workspaceId', shouldThrow: false });
const workspaceId = match?.params.workspaceId;
const accountId = appMetadata.get('account');
const [account, setAccount] = useState<Account | null>(null);
useEffect(() => {
(async () => {
const accounts = await fetchAccountForWorkspace(workspaceId);
if (!accounts) {
navigate({ to: '/login' });
return;
}
if (accounts.length === 1) {
if (accountId !== accounts[0]!.id) {
appMetadata.set('account', accounts[0]!.id);
}
setAccount(accounts[0]!);
return;
}
const account = accounts.find((account) => account.id === accountId);
if (account) {
setAccount(account);
return;
}
//TODO: Show account selection
setAccount(accounts[0]!);
})();
}, [workspaceId]);
if (!account) {
return null;
}
return (
<AccountContext.Provider value={account}>
{children}
</AccountContext.Provider>
);
};

View File

@@ -0,0 +1,28 @@
import { Outlet, useParams } from '@tanstack/react-router';
import { AccountContext } from '@colanode/ui/contexts/account';
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
export const AccountScreen = () => {
const { accountId } = useParams({ from: '/acc/$accountId' });
const accountQuery = useLiveQuery({
type: 'account.get',
accountId: accountId,
});
if (accountQuery.isPending) {
return null;
}
const account = accountQuery.data;
if (!account) {
return <p>Account not found</p>;
}
return (
<AccountContext.Provider value={account}>
<Outlet />
</AccountContext.Provider>
);
};

View File

@@ -1,9 +1,11 @@
import { useRouter } from '@tanstack/react-router';
import { HouseIcon } from 'lucide-react';
import { useState, Fragment, useEffect } from 'react';
import { useState, Fragment, useEffect, useCallback } from 'react';
import { match } from 'ts-pattern';
import { isFeatureSupported } from '@colanode/client/lib';
import { Account, ServerDetails } from '@colanode/client/types';
import { LoginSuccessOutput } from '@colanode/core';
import { EmailLogin } from '@colanode/ui/components/accounts/email-login';
import { EmailPasswordResetComplete } from '@colanode/ui/components/accounts/email-password-reset-complete';
import { EmailPasswordResetInit } from '@colanode/ui/components/accounts/email-password-reset-init';
@@ -12,7 +14,6 @@ import { EmailVerify } from '@colanode/ui/components/accounts/email-verify';
import { ServerDropdown } from '@colanode/ui/components/servers/server-dropdown';
import { Button } from '@colanode/ui/components/ui/button';
import { Separator } from '@colanode/ui/components/ui/separator';
import { useApp } from '@colanode/ui/contexts/app';
import { ServerContext } from '@colanode/ui/contexts/server';
interface LoginFormProps {
@@ -52,11 +53,12 @@ type PanelState =
| PasswordResetCompletePanelState;
export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
const app = useApp();
const router = useRouter();
const [serverDomain, setServerDomain] = useState<string | null>(
servers[0]?.domain ?? null
);
const [panel, setPanel] = useState<PanelState>({
type: 'login',
});
@@ -73,6 +75,24 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
? servers.find((s) => s.domain === serverDomain)
: null;
const handleLoginSuccess = useCallback(
(output: LoginSuccessOutput) => {
const workspace = output.workspaces[0];
if (workspace) {
router.navigate({
to: '/acc/$accountId/$workspaceId',
params: { accountId: output.account.id, workspaceId: workspace.id },
});
} else {
router.navigate({
to: '/acc/$accountId/create',
params: { accountId: output.account.id },
});
}
},
[router]
);
return (
<div className="flex flex-col gap-4">
<ServerDropdown
@@ -98,7 +118,7 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
<EmailLogin
onSuccess={(output) => {
if (output.type === 'success') {
app.openAccount(output.account.id);
handleLoginSuccess(output);
} else if (output.type === 'verify') {
setPanel({
type: 'verify',
@@ -123,7 +143,7 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
<EmailRegister
onSuccess={(output) => {
if (output.type === 'success') {
app.openAccount(output.account.id);
handleLoginSuccess(output);
} else if (output.type === 'verify') {
setPanel({
type: 'verify',
@@ -145,7 +165,7 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
expiresAt={p.expiresAt}
onSuccess={(output) => {
if (output.type === 'success') {
app.openAccount(output.account.id);
handleLoginSuccess(output);
}
}}
onBack={() => {
@@ -195,7 +215,14 @@ export const LoginForm = ({ accounts, servers }: LoginFormProps) => {
className="w-full text-muted-foreground"
type="button"
onClick={() => {
app.closeLogin();
if (router.history.canGoBack()) {
router.history.back();
} else {
router.navigate({
to: '/acc/$accountId',
params: { accountId: accounts[0]!.id },
});
}
}}
>
<HouseIcon className="mr-1 size-4" />

View File

@@ -1,32 +1,28 @@
import { useNavigate } from '@tanstack/react-router';
import { useEffect } from 'react';
import { useAccount } from '@colanode/ui/contexts/account';
export const AppHomeScreen = () => {
const navigate = useNavigate();
const account = useAccount();
useEffect(() => {
(async () => {
const workspaces = await window.colanode.executeQuery({
type: 'workspace.list',
accountId: account.id,
const accounts = await window.colanode.executeQuery({
type: 'account.list',
});
if (workspaces.length === 0) {
navigate({ to: '/create', replace: true });
if (accounts.length === 0) {
navigate({ to: '/login', replace: true });
return;
}
const workspace = workspaces[0]!;
const account = accounts[0]!;
navigate({
to: '/$workspaceId',
params: { workspaceId: workspace.id },
to: '/acc/$accountId',
params: { accountId: account.id },
replace: true,
});
})();
}, [account.id]);
}, []);
return null;
};

View File

@@ -25,7 +25,7 @@ export const ChannelCreateDialog = ({
onOpenChange,
}: ChannelCreateDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -64,9 +64,8 @@ export const ChannelCreateDialog = ({
onSuccess(output) {
onOpenChange(false);
navigate({
to: '/$workspaceId/$nodeId',
to: '$nodeId',
params: {
workspaceId: workspace.id,
nodeId: output.id,
},
});

View File

@@ -27,7 +27,7 @@ export const ChannelDeleteDialog = ({
onOpenChange,
}: ChannelDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -58,8 +58,7 @@ export const ChannelDeleteDialog = ({
onSuccess() {
onOpenChange(false);
navigate({
to: '/$workspaceId',
params: { workspaceId: workspace.id },
to: '/',
replace: true,
});
},

View File

@@ -14,8 +14,8 @@ import { useMutation } from '@colanode/ui/hooks/use-mutation';
export const ChatCreatePopover = () => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
const navigate = useNavigate();
const [open, setOpen] = useState(false);
@@ -39,9 +39,8 @@ export const ChatCreatePopover = () => {
},
onSuccess(output) {
navigate({
to: '/$workspaceId/$nodeId',
to: '$nodeId',
params: {
workspaceId: workspace.id,
nodeId: output.id,
},
});

View File

@@ -35,7 +35,11 @@ export const BoardViewRecordCard = () => {
key={record.id}
className="animate-fade-in flex cursor-pointer flex-col gap-1 rounded-md border p-2 text-left hover:bg-accent"
>
<Link from="/$workspaceId" to="$nodeId" params={{ nodeId: record.id }}>
<Link
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: record.id }}
>
<p className={hasName ? '' : 'text-muted-foreground'}>
{hasName ? name : 'Unnamed'}
</p>

View File

@@ -33,7 +33,7 @@ export const CalendarViewNoValueList = ({
const name = record.attributes.name ?? 'Unnamed';
return (
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: record.id }}
key={record.id}

View File

@@ -13,7 +13,7 @@ export const CalendarViewRecordCard = () => {
return (
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: record.id }}
key={record.id}

View File

@@ -25,7 +25,7 @@ export const DatabaseCreateDialog = ({
onOpenChange,
}: DatabaseCreateDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -64,9 +64,8 @@ export const DatabaseCreateDialog = ({
onSuccess(output) {
onOpenChange(false);
navigate({
to: '/$workspaceId/$nodeId',
to: '$nodeId',
params: {
workspaceId: workspace.id,
nodeId: output.id,
},
});

View File

@@ -27,7 +27,7 @@ export const DatabaseDeleteDialog = ({
onOpenChange,
}: DatabaseDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -58,8 +58,7 @@ export const DatabaseDeleteDialog = ({
onSuccess() {
onOpenChange(false);
navigate({
to: '/$workspaceId',
params: { workspaceId: workspace.id },
to: '/',
replace: true,
});
},

View File

@@ -106,7 +106,7 @@ export const TableViewNameCell = ({ record }: TableViewNameCellProps) => {
)}
</div>
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: record.id }}
className="absolute right-2 flex h-6 cursor-pointer flex-row items-center gap-1 rounded-md border p-1 text-sm text-muted-foreground opacity-0 hover:bg-accent group-hover:opacity-100"

View File

@@ -14,7 +14,7 @@ export const ViewFullscreenButton = () => {
return (
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: database.id }}
className="flex cursor-pointer items-center rounded-md p-1.5 hover:bg-accent"

View File

@@ -34,7 +34,7 @@ interface ViewProps {
export const View = ({ view }: ViewProps) => {
const workspace = useWorkspace();
const database = useDatabase();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const fields: ViewField[] = database.fields
.map((field) => {
@@ -512,8 +512,8 @@ export const View = ({ view }: ViewProps) => {
toast.error(result.error.message);
} else {
navigate({
to: '/$workspaceId/$nodeId',
params: { workspaceId: workspace.id, nodeId: result.output.id },
to: '$nodeId',
params: { nodeId: result.output.id },
});
}
},

View File

@@ -31,7 +31,7 @@ export const FileBlock = ({ id }: FileBlockProps) => {
if (canPreview) {
return (
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: id }}
className="flex h-72 max-h-72 max-w-128 w-full cursor-pointer overflow-hidden rounded-md p-2 hover:bg-muted/50"
@@ -43,7 +43,7 @@ export const FileBlock = ({ id }: FileBlockProps) => {
return (
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: id }}
className="flex flex-row gap-4 items-center w-full cursor-pointer overflow-hidden rounded-md p-2 pl-0 hover:bg-accent"

View File

@@ -28,7 +28,7 @@ export const FileContextMenu = ({ id, children }: FileContextMenuProps) => {
<ContextMenuItem
onSelect={() => {
navigate({
from: '/$workspaceId',
from: '/acc/$accountId/$workspaceId',
to: '$nodeId',
params: { nodeId: id },
});

View File

@@ -27,7 +27,7 @@ export const FileDeleteDialog = ({
onOpenChange,
}: FileDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -58,7 +58,6 @@ export const FileDeleteDialog = ({
onSuccess() {
onOpenChange(false);
navigate({
from: '/$workspaceId',
to: '/',
});
},

View File

@@ -18,7 +18,7 @@ export const FileSaveButton = ({ file }: FileSaveButtonProps) => {
const app = useApp();
const workspace = useWorkspace();
const mutation = useMutation();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const [isSaving, setIsSaving] = useState(false);
const handleDownloadDesktop = async () => {
@@ -40,7 +40,6 @@ export const FileSaveButton = ({ file }: FileSaveButtonProps) => {
},
onSuccess: () => {
navigate({
from: '/$workspaceId',
to: 'downloads',
});
},

View File

@@ -25,7 +25,7 @@ export const FolderCreateDialog = ({
onOpenChange,
}: FolderCreateDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -65,8 +65,8 @@ export const FolderCreateDialog = ({
onSuccess(output) {
onOpenChange(false);
navigate({
to: '/$workspaceId/$nodeId',
params: { workspaceId: workspace.id, nodeId: output.id },
to: '$nodeId',
params: { nodeId: output.id },
});
},
onError(error) {

View File

@@ -27,7 +27,7 @@ export const FolderDeleteDialog = ({
onOpenChange,
}: FolderDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -58,7 +58,6 @@ export const FolderDeleteDialog = ({
onSuccess() {
onOpenChange(false);
navigate({
from: '/$workspaceId',
to: '/',
});
},

View File

@@ -25,7 +25,7 @@ export const FolderFiles = ({
layout: folderLayout,
}: FolderFilesProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const [lastPage] = useState<number>(1);
const inputs: FileListQueryInput[] = Array.from({
@@ -53,7 +53,6 @@ export const FolderFiles = ({
},
onDoubleClick: (_, id) => {
navigate({
from: '/$workspaceId',
to: '$nodeId',
params: { nodeId: id },
});

View File

@@ -50,7 +50,7 @@ export const ContainerBreadcrumb = ({
{!isFirst && <BreadcrumbSeparator />}
<BreadcrumbItem className="cursor-pointer hover:text-foreground">
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: item.id }}
>
@@ -70,7 +70,7 @@ export const ContainerBreadcrumb = ({
return (
<DropdownMenuItem key={ellipsisItem.id}>
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: ellipsisItem.id }}
>

View File

@@ -1,88 +0,0 @@
import { useRef } from 'react';
import { useDrop } from 'react-dnd';
import { ContainerTab } from '@colanode/client/types';
import { ContainerTabContent } from '@colanode/ui/components/layouts/containers/container-tab-content';
import { ContainerTabTrigger } from '@colanode/ui/components/layouts/containers/container-tab-trigger';
import {
ScrollArea,
ScrollBar,
ScrollViewport,
} from '@colanode/ui/components/ui/scroll-area';
import { Tabs, TabsList } from '@colanode/ui/components/ui/tabs';
import { cn } from '@colanode/ui/lib/utils';
interface ContainerTabsProps {
tabs: ContainerTab[];
onTabChange: (value: string) => void;
onFocus: () => void;
onClose: (value: string) => void;
onOpen: (value: string) => void;
onMove: (tab: string, before: string | null) => void;
}
export const ContainerTabs = ({
tabs,
onTabChange,
onFocus,
onClose,
onOpen,
onMove,
}: ContainerTabsProps) => {
const activeTab = tabs.find((t) => t.active)?.path;
const [dropMonitor, dropRef] = useDrop({
accept: 'container-tab',
drop: () => ({
before: null,
}),
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
});
const buttonRef = useRef<HTMLDivElement>(null);
const dragDropRef = dropRef(buttonRef);
return (
<Tabs
defaultValue={tabs[0]?.path}
value={activeTab}
onValueChange={onTabChange}
onFocus={onFocus}
className="h-full min-h-full w-full min-w-full max-h-full max-w-full flex flex-col overflow-hidden"
>
<ScrollArea className="h-10 min-h-10 w-full">
<ScrollViewport>
<TabsList className="h-10 w-full justify-start p-0 app-drag-region rounded-none">
{tabs.map((tab) => (
<ContainerTabTrigger
key={tab.path}
tab={tab}
onClose={() => onClose(tab.path)}
onOpen={() => onOpen(tab.path)}
onMove={(before) => onMove(tab.path, before)}
/>
))}
<div
ref={dragDropRef as React.LegacyRef<HTMLDivElement>}
className={cn(
'h-full w-10',
dropMonitor.isOver &&
dropMonitor.canDrop &&
'border-l-2 border-blue-300'
)}
/>
</TabsList>
<ScrollBar orientation="horizontal" className="z-10" />
</ScrollViewport>
</ScrollArea>
<div className="flex-grow overflow-hidden">
{tabs.map((tab) => (
<ContainerTabContent key={tab.path} tab={tab} />
))}
</div>
</Tabs>
);
};

View File

@@ -1,11 +0,0 @@
import { Outlet } from '@tanstack/react-router';
import { AccountProvider } from '@colanode/ui/components/accounts/account-provider';
export const RootLayout = () => {
return (
<AccountProvider>
<Outlet />
</AccountProvider>
);
};

View File

@@ -13,7 +13,9 @@ import { RecordContainer } from '@colanode/ui/components/records/record-containe
import { SpaceContainer } from '@colanode/ui/components/spaces/space-container';
export const NodeScreen = () => {
const { nodeId } = useParams({ from: '/$workspaceId/$nodeId' });
const { nodeId } = useParams({
from: '/acc/$accountId/$workspaceId/$nodeId',
});
return match(getIdType(nodeId))
.with(IdType.Space, () => <SpaceContainer spaceId={nodeId} />)

View File

@@ -25,7 +25,7 @@ export const PageCreateDialog = ({
onOpenChange,
}: PageCreateDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -65,9 +65,8 @@ export const PageCreateDialog = ({
onSuccess(output) {
onOpenChange(false);
navigate({
to: '/$workspaceId/$nodeId',
to: '$nodeId',
params: {
workspaceId: workspace.id,
nodeId: output.id,
},
});

View File

@@ -27,7 +27,7 @@ export const PageDeleteDialog = ({
onOpenChange,
}: PageDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -58,7 +58,6 @@ export const PageDeleteDialog = ({
onSuccess() {
onOpenChange(false);
navigate({
from: '/$workspaceId',
to: '/',
});
},

View File

@@ -27,7 +27,7 @@ export const RecordDeleteDialog = ({
onOpenChange,
}: RecordDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
return (
@@ -58,7 +58,6 @@ export const RecordDeleteDialog = ({
onSuccess() {
onOpenChange(false);
navigate({
from: '/$workspaceId',
to: '/',
});
},

View File

@@ -23,7 +23,7 @@ interface SpaceBodyProps {
export const SpaceBody = ({ space, role }: SpaceBodyProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const { mutate, isPending } = useMutation();
const canEdit = hasNodeRole(role, 'admin');
@@ -95,7 +95,6 @@ export const SpaceBody = ({ space, role }: SpaceBodyProps) => {
id={space.id}
onDeleted={() => {
navigate({
from: '/$workspaceId',
to: '/',
});
}}

View File

@@ -29,7 +29,7 @@ interface SpaceSidebarDropdownProps {
}
export const SpaceSidebarDropdown = ({ space }: SpaceSidebarDropdownProps) => {
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const [openCreatePage, setOpenCreatePage] = useState(false);
const [openCreateChannel, setOpenCreateChannel] = useState(false);
@@ -81,7 +81,6 @@ export const SpaceSidebarDropdown = ({ space }: SpaceSidebarDropdownProps) => {
<DropdownMenuItem
onClick={() =>
navigate({
from: '/$workspaceId',
to: '$nodeId',
params: { nodeId: space.id },
})
@@ -94,7 +93,6 @@ export const SpaceSidebarDropdown = ({ space }: SpaceSidebarDropdownProps) => {
<DropdownMenuItem
onClick={() =>
navigate({
from: '/$workspaceId',
to: '$nodeId',
params: { nodeId: space.id },
})

View File

@@ -105,9 +105,9 @@ export const SpaceSidebarItem = ({ space }: SpaceSidebarItemProps) => {
{children.map((child) => (
<li key={child.id}>
<Link
to={'/$workspaceId/$nodeId'}
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{
workspaceId: workspace.id,
nodeId: child.id,
}}
className="cursor-pointer select-none"

View File

@@ -22,7 +22,7 @@ export const WorkspaceDownloadFile = ({
download,
}: WorkspaceDownloadFileProps) => {
const workspace = useWorkspace();
const navigate = useNavigate();
const navigate = useNavigate({ from: '/acc/$accountId/$workspaceId' });
const fileQuery = useLiveQuery({
type: 'node.get',
@@ -39,7 +39,6 @@ export const WorkspaceDownloadFile = ({
onClick={() => {
if (file) {
navigate({
from: '/$workspaceId',
to: '$nodeId',
params: { nodeId: file.id },
});

View File

@@ -26,7 +26,7 @@ export const WorkspaceSidebarChats = () => {
{chats.map((item) => (
<Link
key={item.id}
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: item.id }}
className="px-2 flex w-full items-center gap-2 overflow-hidden rounded-md text-left text-sm h-7 cursor-pointer text-foreground hover:bg-muted"

View File

@@ -78,7 +78,10 @@ export function WorkspaceSidebarMenuFooter() {
key={accountItem.id}
className="p-0"
onClick={() => {
app.openAccount(accountItem.id);
navigate({
to: '/acc/$accountId',
params: { accountId: accountItem.id },
});
}}
>
<AccountContext.Provider value={accountItem}>

View File

@@ -75,8 +75,11 @@ export const WorkspaceSidebarMenuHeader = () => {
className="p-0 cursor-pointer"
onClick={() => {
navigate({
to: '/$workspaceId',
params: { workspaceId: workspaceItem.id },
to: '/acc/$accountId/$workspaceId',
params: {
accountId: workspaceItem.accountId,
workspaceId: workspaceItem.id,
},
});
}}
>
@@ -106,7 +109,10 @@ export const WorkspaceSidebarMenuHeader = () => {
<DropdownMenuItem
className="gap-2 p-2 text-muted-foreground hover:text-foreground cursor-pointer"
onClick={() => {
navigate({ to: '/create' });
navigate({
to: '/acc/$accountId/create',
params: { accountId: account.id },
});
}}
>
<Plus className="size-4" />

View File

@@ -35,10 +35,7 @@ export const WorkspaceSidebarSettings = () => {
<div className="flex flex-col gap-4 h-full px-2 group/sidebar">
<div className="flex w-full min-w-0 flex-col gap-1">
<WorkspaceSidebarHeader title="Workspace settings" />
<Link
to="/$workspaceId/settings"
params={{ workspaceId: workspace.id }}
>
<Link from="/acc/$accountId/$workspaceId" to="settings">
{({ isActive }) => (
<WorkspaceSidebarSettingsItem
title="General"
@@ -48,7 +45,7 @@ export const WorkspaceSidebarSettings = () => {
)}
</Link>
<Link to="/$workspaceId/users" params={{ workspaceId: workspace.id }}>
<Link from="/acc/$accountId/$workspaceId" to="users">
{({ isActive }) => (
<WorkspaceSidebarSettingsItem
title="Users"
@@ -57,7 +54,7 @@ export const WorkspaceSidebarSettings = () => {
/>
)}
</Link>
<Link to="/$workspaceId/storage" params={{ workspaceId: workspace.id }}>
<Link from="/acc/$accountId/$workspaceId" to="storage">
{({ isActive }) => (
<WorkspaceSidebarSettingsItem
title="Storage"
@@ -66,7 +63,7 @@ export const WorkspaceSidebarSettings = () => {
/>
)}
</Link>
<Link to="/$workspaceId/uploads" params={{ workspaceId: workspace.id }}>
<Link from="/acc/$accountId/$workspaceId" to="uploads">
{({ isActive }) => (
<WorkspaceSidebarSettingsItem
title="Uploads"
@@ -82,10 +79,7 @@ export const WorkspaceSidebarSettings = () => {
)}
</Link>
{app.type === 'desktop' && (
<Link
to="/$workspaceId/downloads"
params={{ workspaceId: workspace.id }}
>
<Link from="/acc/$accountId/$workspaceId" to="downloads">
{({ isActive }) => (
<WorkspaceSidebarSettingsItem
title="Downloads"
@@ -98,10 +92,7 @@ export const WorkspaceSidebarSettings = () => {
</div>
<div className="flex w-full min-w-0 flex-col gap-1">
<WorkspaceSidebarHeader title="Account settings" />
<Link
to="/$workspaceId/account/settings"
params={{ workspaceId: workspace.id }}
>
<Link from="/acc/$accountId/$workspaceId" to="account/settings">
{({ isActive }) => (
<WorkspaceSidebarSettingsItem
title="General"
@@ -113,10 +104,7 @@ export const WorkspaceSidebarSettings = () => {
</div>
<div className="flex w-full min-w-0 flex-col gap-1">
<WorkspaceSidebarHeader title="App settings" />
<Link
to="/$workspaceId/app/appearance"
params={{ workspaceId: workspace.id }}
>
<Link from="/acc/$accountId/$workspaceId" to="app/appearance">
{({ isActive }) => (
<WorkspaceSidebarSettingsItem
title="Appearance"
@@ -128,10 +116,7 @@ export const WorkspaceSidebarSettings = () => {
</div>
<div className="flex w-full min-w-0 flex-col gap-1">
<Separator className="my-2" />
<Link
to="/$workspaceId/account/logout"
params={{ workspaceId: workspace.id }}
>
<Link from="/acc/$accountId/$workspaceId" to="account/logout">
{({ isActive }) => (
<WorkspaceSidebarSettingsItem
title="Logout"

View File

@@ -51,7 +51,7 @@ export const WorkspaceUploadFile = ({ upload }: WorkspaceUploadFileProps) => {
return (
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: file.id }}
className="border rounded-lg p-4 bg-card hover:bg-accent/50 transition-colors flex items-center gap-6 cursor-pointer"

View File

@@ -1,4 +1,4 @@
import { useCanGoBack, useNavigate, useRouter } from '@tanstack/react-router';
import { useRouter } from '@tanstack/react-router';
import { toast } from 'sonner';
import { WorkspaceForm } from '@colanode/ui/components/workspaces/workspace-form';
@@ -8,11 +8,8 @@ import { useMutation } from '@colanode/ui/hooks/use-mutation';
export const WorkspaceCreateScreen = () => {
const account = useAccount();
const { mutate, isPending } = useMutation();
const canGoBack = useCanGoBack();
const router = useRouter();
const navigate = useNavigate();
const { mutate, isPending } = useMutation();
const workspacesQuery = useLiveQuery({
type: 'workspace.list',
@@ -20,13 +17,13 @@ export const WorkspaceCreateScreen = () => {
});
const workspaces = workspacesQuery.data ?? [];
const handleCancel = canGoBack
const handleCancel = router.history.canGoBack()
? () => router.history.back()
: workspaces.length > 0
? () =>
navigate({
to: '/$workspaceId',
params: { workspaceId: workspaces[0]!.id },
router.navigate({
to: '/acc/$accountId/$workspaceId',
params: { accountId: account.id, workspaceId: workspaces[0]!.id },
})
: undefined;
@@ -50,7 +47,10 @@ export const WorkspaceCreateScreen = () => {
avatar: values.avatar ?? null,
},
onSuccess(output) {
navigate({ to: `/${output.id}` });
router.navigate({
to: '/acc/$accountId/$workspaceId',
params: { accountId: account.id, workspaceId: output.id },
});
},
onError(error) {
toast.error(error.message);

View File

@@ -9,7 +9,7 @@ import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
export const WorkspaceScreen = () => {
const account = useAccount();
const { workspaceId } = useParams({ from: '/$workspaceId' });
const { workspaceId } = useParams({ from: '/acc/$accountId/$workspaceId' });
const workspaceQuery = useLiveQuery({
type: 'workspace.get',

View File

@@ -49,7 +49,7 @@ export const DatabaseNodeView = ({ node }: NodeViewProps) => {
return (
<NodeViewWrapper data-id={node.attrs.id}>
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: id }}
className="my-0.5 flex h-10 w-full cursor-pointer flex-row items-center gap-1 rounded-md p-1 hover:bg-accent"

View File

@@ -37,7 +37,7 @@ export const FolderNodeView = ({ node }: NodeViewProps) => {
return (
<NodeViewWrapper data-id={node.attrs.id}>
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: id }}
className="my-0.5 flex h-10 w-full cursor-pointer flex-row items-center gap-1 rounded-md p-1 hover:bg-accent"

View File

@@ -37,7 +37,7 @@ export const PageNodeView = ({ node }: NodeViewProps) => {
return (
<NodeViewWrapper data-id={node.attrs.id}>
<Link
from="/$workspaceId"
from="/acc/$accountId/$workspaceId"
to="$nodeId"
params={{ nodeId: id }}
className="my-0.5 flex h-10 w-full cursor-pointer flex-row items-center gap-1 rounded-md p-1 hover:bg-accent"

View File

@@ -5,11 +5,11 @@ import {
} from '@tanstack/react-router';
import { AccountLogoutScreen } from '@colanode/ui/components/accounts/account-logout-screen';
import { AccountScreen } from '@colanode/ui/components/accounts/account-screen';
import { AccountSettingsScreen } from '@colanode/ui/components/accounts/account-settings-screen';
import { LoginScreen } from '@colanode/ui/components/accounts/login-screen';
import { AppAppearanceSettingsScreen } from '@colanode/ui/components/app/app-appearance-settings-screen';
import { AppHomeScreen } from '@colanode/ui/components/app/app-home-screen';
import { RootLayout } from '@colanode/ui/components/layouts/root-layout';
import { NodeScreen } from '@colanode/ui/components/nodes/node-screen';
import { WorkspaceDownloadsScreen } from '@colanode/ui/components/workspaces/downloads/workspace-downloads-screen';
import { WorkspaceStorageScreen } from '@colanode/ui/components/workspaces/storage/workspace-storage-screen';
@@ -20,8 +20,12 @@ import { WorkspaceScreen } from '@colanode/ui/components/workspaces/workspace-sc
import { WorkspaceSettingsScreen } from '@colanode/ui/components/workspaces/workspace-settings-screen';
import { WorkspaceUsersScreen } from '@colanode/ui/components/workspaces/workspace-users-screen';
export const rootRoute = createRootRoute({
component: RootLayout,
export const rootRoute = createRootRoute();
export const appHomeRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: AppHomeScreen,
});
export const loginRoute = createRoute({
@@ -30,14 +34,14 @@ export const loginRoute = createRoute({
component: LoginScreen,
});
export const appHomeRoute = createRoute({
export const accountRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: AppHomeScreen,
path: '/acc/$accountId',
component: AccountScreen,
});
export const workspaceRoute = createRoute({
getParentRoute: () => rootRoute,
getParentRoute: () => accountRoute,
path: '/$workspaceId',
component: WorkspaceScreen,
});
@@ -49,7 +53,7 @@ export const workspaceHomeRoute = createRoute({
});
export const workspaceCreateRoute = createRoute({
getParentRoute: () => rootRoute,
getParentRoute: () => accountRoute,
path: '/create',
component: WorkspaceCreateScreen,
});
@@ -111,18 +115,20 @@ export const appAppearanceRoute = createRoute({
export const routeTree = rootRoute.addChildren([
appHomeRoute,
loginRoute,
workspaceCreateRoute,
workspaceRoute.addChildren([
workspaceHomeRoute,
nodeRoute,
workspaceDownloadsRoute,
workspaceUploadsRoute,
workspaceStorageRoute,
workspaceUsersRoute,
workspaceSettingsRoute,
accountSettingsRoute,
accountLogoutRoute,
appAppearanceRoute,
accountRoute.addChildren([
workspaceCreateRoute,
workspaceRoute.addChildren([
workspaceHomeRoute,
nodeRoute,
workspaceDownloadsRoute,
workspaceUploadsRoute,
workspaceStorageRoute,
workspaceUsersRoute,
workspaceSettingsRoute,
accountSettingsRoute,
accountLogoutRoute,
appAppearanceRoute,
]),
]),
]);