mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 19:57:46 +01:00
Improve breadcrumbs, headers and tabs (#249)
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@colanode/ui/components/ui/button';
|
||||||
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
|
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||||
|
|
||||||
|
export const AccountLogoutContainer = () => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { mutate, isPending } = useMutation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl space-y-8">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Logout</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
||||||
|
<div className="flex-1 space-y-2">
|
||||||
|
<h3 className="font-semibold">Sign out of your account</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
All your data will be removed from this device. If there are
|
||||||
|
pending changes, they will be lost. If you login again, all the
|
||||||
|
data will be re-synced.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-auto md:shrink-0">
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
disabled={isPending}
|
||||||
|
className="w-full cursor-pointer md:w-20"
|
||||||
|
onClick={async () => {
|
||||||
|
mutate({
|
||||||
|
input: {
|
||||||
|
type: 'account.logout',
|
||||||
|
accountId: workspace.accountId,
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
navigate({
|
||||||
|
to: '/',
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isPending && <Spinner className="mr-1" />}
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const AccountLogoutHeader = () => {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem id="logout" avatar={defaultIcons.logout} name="Logout" />
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import { useNavigate } from '@tanstack/react-router';
|
|
||||||
import { LogOut } from 'lucide-react';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
import { Button } from '@colanode/ui/components/ui/button';
|
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
|
||||||
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|
||||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
|
||||||
|
|
||||||
export const AccountLogoutScreen = () => {
|
|
||||||
const workspace = useWorkspace();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { mutate, isPending } = useMutation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <LogOut className={className} />}
|
|
||||||
name="Logout"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="max-w-4xl space-y-8">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Logout</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
|
|
||||||
<div className="flex-1 space-y-2">
|
|
||||||
<h3 className="font-semibold">Sign out of your account</h3>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
All your data will be removed from this device. If there are
|
|
||||||
pending changes, they will be lost. If you login again, all the
|
|
||||||
data will be re-synced.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="w-full md:w-auto md:shrink-0">
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
disabled={isPending}
|
|
||||||
className="w-full cursor-pointer md:w-20"
|
|
||||||
onClick={async () => {
|
|
||||||
mutate({
|
|
||||||
input: {
|
|
||||||
type: 'account.logout',
|
|
||||||
accountId: workspace.accountId,
|
|
||||||
},
|
|
||||||
onSuccess() {
|
|
||||||
navigate({
|
|
||||||
to: '/',
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isPending && <Spinner className="mr-1" />}
|
|
||||||
Logout
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { LogOut } from 'lucide-react';
|
import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
export const AccountLogoutTab = () => {
|
export const AccountLogoutTab = () => {
|
||||||
return (
|
return <TabItem id="logout" avatar={defaultIcons.logout} name="Logout" />;
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<LogOut className="size-4" />
|
|
||||||
<span>Logout</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { AccountDelete } from '@colanode/ui/components/accounts/account-delete';
|
||||||
|
import { AccountUpdate } from '@colanode/ui/components/accounts/account-update';
|
||||||
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
|
|
||||||
|
export const AccountSettingsContainer = () => {
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl space-y-8">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">General</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<AccountUpdate />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Danger Zone</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<AccountDelete />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const AccountSettingsHeader = () => {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem
|
||||||
|
id="settings"
|
||||||
|
avatar={defaultIcons.settings}
|
||||||
|
name="Account Settings"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { Settings } from 'lucide-react';
|
|
||||||
|
|
||||||
import { AccountDelete } from '@colanode/ui/components/accounts/account-delete';
|
|
||||||
import { AccountUpdate } from '@colanode/ui/components/accounts/account-update';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
|
||||||
|
|
||||||
export const AccountSettingsScreen = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <Settings className={className} />}
|
|
||||||
name="Settings"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="max-w-4xl space-y-8">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">General</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<AccountUpdate />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
|
||||||
Danger Zone
|
|
||||||
</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<AccountDelete />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Settings } from 'lucide-react';
|
import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
export const AccountSettingsTab = () => {
|
export const AccountSettingsTab = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center space-x-2">
|
<TabItem
|
||||||
<Settings className="size-4" />
|
id="settings"
|
||||||
<span>Account Settings</span>
|
avatar={defaultIcons.settings}
|
||||||
</div>
|
name="Account Settings"
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { LoginForm } from '@colanode/ui/components/accounts/login-form';
|
import { LoginForm } from '@colanode/ui/components/accounts/login-form';
|
||||||
|
|
||||||
export const LoginScreen = () => {
|
export const Login = () => {
|
||||||
return (
|
return (
|
||||||
<div className="grid h-screen min-h-screen w-full grid-cols-1 lg:grid-cols-5">
|
<div className="grid h-screen min-h-screen w-full grid-cols-1 lg:grid-cols-5">
|
||||||
<div className="items-center justify-center bg-foreground hidden lg:flex lg:col-span-2">
|
<div className="items-center justify-center bg-foreground hidden lg:flex lg:col-span-2">
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import { Check, Laptop, Moon, Sun } from 'lucide-react';
|
||||||
|
|
||||||
|
import { ThemeColor, ThemeMode } from '@colanode/client/types';
|
||||||
|
import { Button } from '@colanode/ui/components/ui/button';
|
||||||
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
|
import { useMetadata } from '@colanode/ui/hooks/use-metadata';
|
||||||
|
import { cn } from '@colanode/ui/lib/utils';
|
||||||
|
|
||||||
|
interface ThemeModeOption {
|
||||||
|
key: string;
|
||||||
|
value: ThemeMode | null;
|
||||||
|
label: string;
|
||||||
|
icon: typeof Laptop;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeModeOptions: ThemeModeOption[] = [
|
||||||
|
{
|
||||||
|
key: 'system',
|
||||||
|
value: null,
|
||||||
|
label: 'System',
|
||||||
|
icon: Laptop,
|
||||||
|
title: 'Follow system',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'light',
|
||||||
|
value: 'light',
|
||||||
|
label: 'Light',
|
||||||
|
icon: Sun,
|
||||||
|
title: 'Light theme',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dark',
|
||||||
|
value: 'dark',
|
||||||
|
label: 'Dark',
|
||||||
|
icon: Moon,
|
||||||
|
title: 'Dark theme',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const themeColorOptions = [
|
||||||
|
{ value: 'default', label: 'Default', color: 'oklch(0.205 0 0)' },
|
||||||
|
{ value: 'blue', label: 'Blue', color: 'oklch(0.623 0.214 259.815)' },
|
||||||
|
{ value: 'red', label: 'Red', color: 'oklch(0.637 0.237 25.331)' },
|
||||||
|
{ value: 'rose', label: 'Rose', color: 'oklch(0.645 0.246 16.439)' },
|
||||||
|
{ value: 'orange', label: 'Orange', color: 'oklch(0.705 0.213 47.604)' },
|
||||||
|
{ value: 'green', label: 'Green', color: 'oklch(0.723 0.219 149.579)' },
|
||||||
|
{ value: 'yellow', label: 'Yellow', color: 'oklch(0.795 0.184 86.047)' },
|
||||||
|
{ value: 'violet', label: 'Violet', color: 'oklch(0.606 0.25 292.717)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AppAppearanceSettingsContainer = () => {
|
||||||
|
const [themeMode, setThemeMode] = useMetadata('app', 'theme.mode');
|
||||||
|
const [themeColor, setThemeColor] = useMetadata('app', 'theme.color');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl space-y-8">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Appearance</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||||
|
{themeModeOptions.map((option) => {
|
||||||
|
const isActive =
|
||||||
|
option.value === null ? !themeMode : themeMode === option.value;
|
||||||
|
const Icon = option.icon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={option.key}
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setThemeMode(option.value ?? undefined);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'h-10 w-full justify-start gap-2 relative',
|
||||||
|
isActive && 'ring-1 ring-ring border-primary'
|
||||||
|
)}
|
||||||
|
title={option.title}
|
||||||
|
>
|
||||||
|
<Icon className="size-5" />
|
||||||
|
{option.label}
|
||||||
|
{isActive && (
|
||||||
|
<Check className="size-5 absolute -top-2 -right-2 text-background bg-primary rounded-full p-0.5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Color</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 max-w-2xl">
|
||||||
|
{themeColorOptions.map((option) => {
|
||||||
|
const isDefault = option.value === 'default';
|
||||||
|
const isActive = isDefault
|
||||||
|
? !themeColor
|
||||||
|
: themeColor === option.value;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={option.value}
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
if (isDefault) {
|
||||||
|
setThemeColor(undefined);
|
||||||
|
} else {
|
||||||
|
setThemeColor(option.value as ThemeColor);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'h-10 justify-start gap-3 text-left relative',
|
||||||
|
isActive && 'ring-1 ring-ring border-primary'
|
||||||
|
)}
|
||||||
|
title={option.label}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="size-5 rounded-full border border-border/50 shrink-0"
|
||||||
|
style={{ backgroundColor: option.color }}
|
||||||
|
/>
|
||||||
|
{option.label}
|
||||||
|
{isActive && (
|
||||||
|
<Check className="size-5 absolute -top-2 -right-2 text-background bg-primary rounded-full p-0.5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const AppAppearanceSettingsHeader = () => {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem
|
||||||
|
id="appearance"
|
||||||
|
avatar={defaultIcons.appearance}
|
||||||
|
name="Appearance"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
import { Check, Laptop, Moon, Palette, Sun } from 'lucide-react';
|
|
||||||
|
|
||||||
import { ThemeColor, ThemeMode } from '@colanode/client/types';
|
|
||||||
import { Button } from '@colanode/ui/components/ui/button';
|
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { useMetadata } from '@colanode/ui/hooks/use-metadata';
|
|
||||||
import { cn } from '@colanode/ui/lib/utils';
|
|
||||||
|
|
||||||
interface ThemeModeOption {
|
|
||||||
key: string;
|
|
||||||
value: ThemeMode | null;
|
|
||||||
label: string;
|
|
||||||
icon: typeof Laptop;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const themeModeOptions: ThemeModeOption[] = [
|
|
||||||
{
|
|
||||||
key: 'system',
|
|
||||||
value: null,
|
|
||||||
label: 'System',
|
|
||||||
icon: Laptop,
|
|
||||||
title: 'Follow system',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'light',
|
|
||||||
value: 'light',
|
|
||||||
label: 'Light',
|
|
||||||
icon: Sun,
|
|
||||||
title: 'Light theme',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'dark',
|
|
||||||
value: 'dark',
|
|
||||||
label: 'Dark',
|
|
||||||
icon: Moon,
|
|
||||||
title: 'Dark theme',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const themeColorOptions = [
|
|
||||||
{ value: 'default', label: 'Default', color: 'oklch(0.205 0 0)' },
|
|
||||||
{ value: 'blue', label: 'Blue', color: 'oklch(0.623 0.214 259.815)' },
|
|
||||||
{ value: 'red', label: 'Red', color: 'oklch(0.637 0.237 25.331)' },
|
|
||||||
{ value: 'rose', label: 'Rose', color: 'oklch(0.645 0.246 16.439)' },
|
|
||||||
{ value: 'orange', label: 'Orange', color: 'oklch(0.705 0.213 47.604)' },
|
|
||||||
{ value: 'green', label: 'Green', color: 'oklch(0.723 0.219 149.579)' },
|
|
||||||
{ value: 'yellow', label: 'Yellow', color: 'oklch(0.795 0.184 86.047)' },
|
|
||||||
{ value: 'violet', label: 'Violet', color: 'oklch(0.606 0.25 292.717)' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const AppAppearanceSettingsScreen = () => {
|
|
||||||
const [themeMode, setThemeMode] = useMetadata('app', 'theme.mode');
|
|
||||||
const [themeColor, setThemeColor] = useMetadata('app', 'theme.color');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <Palette className={className} />}
|
|
||||||
name="Appearance"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="max-w-4xl space-y-8">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Appearance</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
||||||
{themeModeOptions.map((option) => {
|
|
||||||
const isActive =
|
|
||||||
option.value === null ? !themeMode : themeMode === option.value;
|
|
||||||
const Icon = option.icon;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={option.key}
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
setThemeMode(option.value ?? undefined);
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
'h-10 w-full justify-start gap-2 relative',
|
|
||||||
isActive && 'ring-1 ring-ring border-primary'
|
|
||||||
)}
|
|
||||||
title={option.title}
|
|
||||||
>
|
|
||||||
<Icon className="size-5" />
|
|
||||||
{option.label}
|
|
||||||
{isActive && (
|
|
||||||
<Check className="size-5 absolute -top-2 -right-2 text-background bg-primary rounded-full p-0.5" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Color</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 max-w-2xl">
|
|
||||||
{themeColorOptions.map((option) => {
|
|
||||||
const isDefault = option.value === 'default';
|
|
||||||
const isActive = isDefault
|
|
||||||
? !themeColor
|
|
||||||
: themeColor === option.value;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={option.value}
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
if (isDefault) {
|
|
||||||
setThemeColor(undefined);
|
|
||||||
} else {
|
|
||||||
setThemeColor(option.value as ThemeColor);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
'h-10 justify-start gap-3 text-left relative',
|
|
||||||
isActive && 'ring-1 ring-ring border-primary'
|
|
||||||
)}
|
|
||||||
title={option.label}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="size-5 rounded-full border border-border/50 shrink-0"
|
|
||||||
style={{ backgroundColor: option.color }}
|
|
||||||
/>
|
|
||||||
{option.label}
|
|
||||||
{isActive && (
|
|
||||||
<Check className="size-5 absolute -top-2 -right-2 text-background bg-primary rounded-full p-0.5" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Palette } from 'lucide-react';
|
import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
export const AppAppearanceSettingsTab = () => {
|
export const AppAppearanceSettingsTab = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center space-x-2">
|
<TabItem
|
||||||
<Palette className="size-4" />
|
id="appearance"
|
||||||
<span>Appearance</span>
|
avatar={defaultIcons.appearance}
|
||||||
</div>
|
name="Appearance"
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DelayedComponent } from '@colanode/ui/components/ui/delayed-component';
|
import { DelayedComponent } from '@colanode/ui/components/ui/delayed-component';
|
||||||
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
||||||
|
|
||||||
export const AppLoadingScreen = () => {
|
export const AppLoading = () => {
|
||||||
return (
|
return (
|
||||||
<div className="min-w-screen flex h-full min-h-screen w-full items-center justify-center">
|
<div className="min-w-screen flex h-full min-h-screen w-full items-center justify-center">
|
||||||
<DelayedComponent>
|
<DelayedComponent>
|
||||||
@@ -5,8 +5,8 @@ import { build } from '@colanode/core';
|
|||||||
import { collections } from '@colanode/ui/collections';
|
import { collections } from '@colanode/ui/collections';
|
||||||
import { AppAssets } from '@colanode/ui/components/app/app-assets';
|
import { AppAssets } from '@colanode/ui/components/app/app-assets';
|
||||||
import { AppLayout } from '@colanode/ui/components/app/app-layout';
|
import { AppLayout } from '@colanode/ui/components/app/app-layout';
|
||||||
import { AppLoadingScreen } from '@colanode/ui/components/app/app-loading-screen';
|
import { AppLoading } from '@colanode/ui/components/app/app-loading';
|
||||||
import { AppResetScreen } from '@colanode/ui/components/app/app-reset-screen';
|
import { AppReset } from '@colanode/ui/components/app/app-reset';
|
||||||
import { AppThemeProvider } from '@colanode/ui/components/app/app-theme-provider';
|
import { AppThemeProvider } from '@colanode/ui/components/app/app-theme-provider';
|
||||||
import { RadarProvider } from '@colanode/ui/components/app/radar-provider';
|
import { RadarProvider } from '@colanode/ui/components/app/radar-provider';
|
||||||
import { AppContext } from '@colanode/ui/contexts/app';
|
import { AppContext } from '@colanode/ui/contexts/app';
|
||||||
@@ -43,8 +43,8 @@ export const AppProvider = ({ type }: AppProviderProps) => {
|
|||||||
<AppContext.Provider value={{ type }}>
|
<AppContext.Provider value={{ type }}>
|
||||||
<AppThemeProvider init={initOutput}>
|
<AppThemeProvider init={initOutput}>
|
||||||
<AppAssets />
|
<AppAssets />
|
||||||
{initOutput === null && <AppLoadingScreen />}
|
{initOutput === null && <AppLoading />}
|
||||||
{initOutput === 'reset' && <AppResetScreen />}
|
{initOutput === 'reset' && <AppReset />}
|
||||||
{initOutput === 'success' && (
|
{initOutput === 'success' && (
|
||||||
<RadarProvider>
|
<RadarProvider>
|
||||||
<AppLayout type={type} />
|
<AppLayout type={type} />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { toast } from 'sonner';
|
|||||||
|
|
||||||
import { Button } from '@colanode/ui/components/ui/button';
|
import { Button } from '@colanode/ui/components/ui/button';
|
||||||
|
|
||||||
export const AppResetScreen = () => {
|
export const AppReset = () => {
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
return window.colanode.reset();
|
return window.colanode.reset();
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { LocalChannelNode } from '@colanode/client/types';
|
import { LocalChannelNode } from '@colanode/client/types';
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
|
|
||||||
interface ChannelBreadcrumbItemProps {
|
interface ChannelBreadcrumbItemProps {
|
||||||
channel: LocalChannelNode;
|
channel: LocalChannelNode;
|
||||||
@@ -11,14 +10,8 @@ export const ChannelBreadcrumbItem = ({
|
|||||||
}: ChannelBreadcrumbItemProps) => {
|
}: ChannelBreadcrumbItemProps) => {
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
icon={(className) => (
|
id={channel.id}
|
||||||
<Avatar
|
avatar={channel.attributes.avatar}
|
||||||
id={channel.id}
|
|
||||||
name={channel.attributes.name}
|
|
||||||
avatar={channel.attributes.avatar}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
name={channel.attributes.name}
|
name={channel.attributes.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { LocalChannelNode } from '@colanode/client/types';
|
import { LocalChannelNode } from '@colanode/client/types';
|
||||||
import { ChannelNotFound } from '@colanode/ui/components/channels/channel-not-found';
|
import { ChannelNotFound } from '@colanode/ui/components/channels/channel-not-found';
|
||||||
import { ChannelSettings } from '@colanode/ui/components/channels/channel-settings';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { ContainerSettings } from '@colanode/ui/components/workspaces/containers/container-settings';
|
|
||||||
import { Conversation } from '@colanode/ui/components/messages/conversation';
|
import { Conversation } from '@colanode/ui/components/messages/conversation';
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
||||||
|
|
||||||
@@ -28,18 +24,10 @@ export const ChannelContainer = ({ channelId }: ChannelContainerProps) => {
|
|||||||
const { node: channel, role } = data;
|
const { node: channel, role } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Conversation
|
||||||
<Breadcrumb>
|
conversationId={channel.id}
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
rootId={channel.rootId}
|
||||||
</Breadcrumb>
|
role={role}
|
||||||
<ContainerSettings>
|
/>
|
||||||
<ChannelSettings channel={channel} role={role} />
|
|
||||||
</ContainerSettings>
|
|
||||||
<Conversation
|
|
||||||
conversationId={channel.id}
|
|
||||||
rootId={channel.rootId}
|
|
||||||
role={role}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { eq, useLiveQuery } from '@tanstack/react-db';
|
|||||||
|
|
||||||
import { LocalChatNode } from '@colanode/client/types';
|
import { LocalChatNode } from '@colanode/client/types';
|
||||||
import { collections } from '@colanode/ui/collections';
|
import { collections } from '@colanode/ui/collections';
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
|
||||||
interface ChatBreadcrumbItemProps {
|
interface ChatBreadcrumbItemProps {
|
||||||
@@ -13,17 +12,17 @@ interface ChatBreadcrumbItemProps {
|
|||||||
export const ChatBreadcrumbItem = ({ chat }: ChatBreadcrumbItemProps) => {
|
export const ChatBreadcrumbItem = ({ chat }: ChatBreadcrumbItemProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
|
|
||||||
const userId =
|
const collaboratorId =
|
||||||
chat && chat.type === 'chat'
|
chat && chat.type === 'chat'
|
||||||
? (Object.keys(chat.attributes.collaborators).find(
|
? (Object.keys(chat.attributes.collaborators).find(
|
||||||
(id) => id !== workspace.userId
|
(id) => id !== workspace.userId
|
||||||
) ?? '')
|
) ?? '')
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const userQuery = useLiveQuery((q) =>
|
const collaboratorQuery = useLiveQuery((q) =>
|
||||||
q
|
q
|
||||||
.from({ users: collections.workspace(workspace.userId).users })
|
.from({ users: collections.workspace(workspace.userId).users })
|
||||||
.where(({ users }) => eq(users.id, userId))
|
.where(({ users }) => eq(users.id, collaboratorId))
|
||||||
.select(({ users }) => ({
|
.select(({ users }) => ({
|
||||||
id: users.id,
|
id: users.id,
|
||||||
name: users.name,
|
name: users.name,
|
||||||
@@ -32,22 +31,16 @@ export const ChatBreadcrumbItem = ({ chat }: ChatBreadcrumbItemProps) => {
|
|||||||
.findOne()
|
.findOne()
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = userQuery.data;
|
const collaborator = collaboratorQuery.data;
|
||||||
if (!user) {
|
if (!collaborator) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
icon={(className) => (
|
id={collaborator.id}
|
||||||
<Avatar
|
avatar={collaborator.avatar}
|
||||||
id={user.id}
|
name={collaborator.name}
|
||||||
name={user.name}
|
|
||||||
avatar={user.avatar}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
name={user.name}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { LocalChatNode } from '@colanode/client/types';
|
import { LocalChatNode } from '@colanode/client/types';
|
||||||
import { ChatNotFound } from '@colanode/ui/components/chats/chat-not-found';
|
import { ChatNotFound } from '@colanode/ui/components/chats/chat-not-found';
|
||||||
import { NodeCollaboratorsPopover } from '@colanode/ui/components/collaborators/node-collaborators-popover';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { ContainerSettings } from '@colanode/ui/components/workspaces/containers/container-settings';
|
|
||||||
import { Conversation } from '@colanode/ui/components/messages/conversation';
|
import { Conversation } from '@colanode/ui/components/messages/conversation';
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
||||||
|
|
||||||
@@ -28,14 +24,6 @@ export const ChatContainer = ({ chatId }: ChatContainerProps) => {
|
|||||||
const { node, role } = data;
|
const { node, role } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Conversation conversationId={node.id} rootId={node.rootId} role={role} />
|
||||||
<Breadcrumb>
|
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
|
||||||
</Breadcrumb>
|
|
||||||
<ContainerSettings>
|
|
||||||
<NodeCollaboratorsPopover node={node} nodes={[node]} role={role} />
|
|
||||||
</ContainerSettings>
|
|
||||||
<Conversation conversationId={node.id} rootId={node.rootId} role={role} />
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
11
packages/ui/src/components/chats/chat-settings.tsx
Normal file
11
packages/ui/src/components/chats/chat-settings.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { LocalChatNode } from '@colanode/client/types';
|
||||||
|
import { NodeRole } from '@colanode/core';
|
||||||
|
import { NodeCollaboratorsPopover } from '@colanode/ui/components/collaborators/node-collaborators-popover';
|
||||||
|
|
||||||
|
interface ChatSettingsProps {
|
||||||
|
chat: LocalChatNode;
|
||||||
|
role: NodeRole;
|
||||||
|
}
|
||||||
|
export const ChatSettings = ({ chat, role }: ChatSettingsProps) => {
|
||||||
|
return <NodeCollaboratorsPopover node={chat} nodes={[chat]} role={role} />;
|
||||||
|
};
|
||||||
@@ -24,7 +24,7 @@ export const NodeCollaboratorsPopover = ({
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<UserRoundPlus className="size-5 cursor-pointer text-muted-foreground hover:text-foreground" />
|
<UserRoundPlus className="size-5 cursor-pointer text-muted-foreground hover:text-foreground" />
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="mr-2 max-h-128 w-128 overflow-auto">
|
<PopoverContent className="mr-2 max-h-128 w-lg overflow-auto">
|
||||||
<NodeCollaborators node={node} nodes={nodes} role={role} />
|
<NodeCollaborators node={node} nodes={nodes} role={role} />
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { LocalDatabaseNode } from '@colanode/client/types';
|
import { LocalDatabaseNode } from '@colanode/client/types';
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
|
|
||||||
interface DatabaseBreadcrumbItemProps {
|
interface DatabaseBreadcrumbItemProps {
|
||||||
database: LocalDatabaseNode;
|
database: LocalDatabaseNode;
|
||||||
@@ -11,14 +10,8 @@ export const DatabaseBreadcrumbItem = ({
|
|||||||
}: DatabaseBreadcrumbItemProps) => {
|
}: DatabaseBreadcrumbItemProps) => {
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
icon={(className) => (
|
id={database.id}
|
||||||
<Avatar
|
avatar={database.attributes.avatar}
|
||||||
id={database.id}
|
|
||||||
name={database.attributes.name}
|
|
||||||
avatar={database.attributes.avatar}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
name={database.attributes.name}
|
name={database.attributes.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { LocalDatabaseNode } from '@colanode/client/types';
|
import { LocalDatabaseNode } from '@colanode/client/types';
|
||||||
import { Database } from '@colanode/ui/components/databases/database';
|
import { Database } from '@colanode/ui/components/databases/database';
|
||||||
import { DatabaseNotFound } from '@colanode/ui/components/databases/database-not-found';
|
import { DatabaseNotFound } from '@colanode/ui/components/databases/database-not-found';
|
||||||
import { DatabaseSettings } from '@colanode/ui/components/databases/database-settings';
|
|
||||||
import { DatabaseViews } from '@colanode/ui/components/databases/database-views';
|
import { DatabaseViews } from '@colanode/ui/components/databases/database-views';
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { ContainerSettings } from '@colanode/ui/components/workspaces/containers/container-settings';
|
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
||||||
|
|
||||||
@@ -29,16 +25,8 @@ export const DatabaseContainer = ({ databaseId }: DatabaseContainerProps) => {
|
|||||||
const { node: database, role } = data;
|
const { node: database, role } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Database database={database} role={role}>
|
||||||
<Breadcrumb>
|
<DatabaseViews />
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
</Database>
|
||||||
</Breadcrumb>
|
|
||||||
<ContainerSettings>
|
|
||||||
<DatabaseSettings database={database} role={role} />
|
|
||||||
</ContainerSettings>
|
|
||||||
<Database database={database} role={role}>
|
|
||||||
<DatabaseViews />
|
|
||||||
</Database>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
import { LocalFileNode } from '@colanode/client/types';
|
import { LocalFileNode } from '@colanode/client/types';
|
||||||
import { FileThumbnail } from '@colanode/ui/components/files/file-thumbnail';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|
||||||
|
|
||||||
interface FileBreadcrumbItemProps {
|
interface FileBreadcrumbItemProps {
|
||||||
file: LocalFileNode;
|
file: LocalFileNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FileBreadcrumbItem = ({ file }: FileBreadcrumbItemProps) => {
|
export const FileBreadcrumbItem = ({ file }: FileBreadcrumbItemProps) => {
|
||||||
const workspace = useWorkspace();
|
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
icon={(className) => (
|
id={file.id}
|
||||||
<FileThumbnail
|
avatar={defaultIcons.file}
|
||||||
userId={workspace.userId}
|
|
||||||
file={file}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
name={file.attributes.name}
|
name={file.attributes.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { LocalFileNode } from '@colanode/client/types';
|
import { LocalFileNode } from '@colanode/client/types';
|
||||||
import { FileBody } from '@colanode/ui/components/files/file-body';
|
import { FileBody } from '@colanode/ui/components/files/file-body';
|
||||||
import { FileNotFound } from '@colanode/ui/components/files/file-not-found';
|
import { FileNotFound } from '@colanode/ui/components/files/file-not-found';
|
||||||
import { FileSettings } from '@colanode/ui/components/files/file-settings';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { ContainerSettings } from '@colanode/ui/components/workspaces/containers/container-settings';
|
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
||||||
|
|
||||||
@@ -24,15 +20,5 @@ export const FileContainer = ({ fileId }: FileContainerProps) => {
|
|||||||
return <FileNotFound />;
|
return <FileNotFound />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <FileBody file={data.node} />;
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
|
||||||
</Breadcrumb>
|
|
||||||
<ContainerSettings>
|
|
||||||
<FileSettings file={data.node} role={data.role} />
|
|
||||||
</ContainerSettings>
|
|
||||||
<FileBody file={data.node} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const FileSettings = ({ file, role }: FileSettingsProps) => {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Settings className="size-5 cursor-pointer text-muted-foreground hover:text-foreground" />
|
<Settings className="size-4 cursor-pointer text-muted-foreground hover:text-foreground" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent side="bottom" className="mr-2 w-56">
|
<DropdownMenuContent side="bottom" className="mr-2 w-56">
|
||||||
<DropdownMenuItem className="flex items-center gap-2" disabled>
|
<DropdownMenuItem className="flex items-center gap-2" disabled>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { LocalFolderNode } from '@colanode/client/types';
|
import { LocalFolderNode } from '@colanode/client/types';
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
|
|
||||||
interface FolderBreadcrumbItemProps {
|
interface FolderBreadcrumbItemProps {
|
||||||
folder: LocalFolderNode;
|
folder: LocalFolderNode;
|
||||||
@@ -9,14 +8,8 @@ interface FolderBreadcrumbItemProps {
|
|||||||
export const FolderBreadcrumbItem = ({ folder }: FolderBreadcrumbItemProps) => {
|
export const FolderBreadcrumbItem = ({ folder }: FolderBreadcrumbItemProps) => {
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
icon={(className) => (
|
id={folder.id}
|
||||||
<Avatar
|
avatar={folder.attributes.avatar}
|
||||||
id={folder.id}
|
|
||||||
name={folder.attributes.name}
|
|
||||||
avatar={folder.attributes.avatar}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
name={folder.attributes.name}
|
name={folder.attributes.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { LocalFolderNode } from '@colanode/client/types';
|
import { LocalFolderNode } from '@colanode/client/types';
|
||||||
import { FolderBody } from '@colanode/ui/components/folders/folder-body';
|
import { FolderBody } from '@colanode/ui/components/folders/folder-body';
|
||||||
import { FolderNotFound } from '@colanode/ui/components/folders/folder-not-found';
|
import { FolderNotFound } from '@colanode/ui/components/folders/folder-not-found';
|
||||||
import { FolderSettings } from '@colanode/ui/components/folders/folder-settings';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { ContainerSettings } from '@colanode/ui/components/workspaces/containers/container-settings';
|
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
||||||
|
|
||||||
@@ -27,15 +23,5 @@ export const FolderContainer = ({ folderId }: FolderContainerProps) => {
|
|||||||
|
|
||||||
const { node: folder, role } = data;
|
const { node: folder, role } = data;
|
||||||
|
|
||||||
return (
|
return <FolderBody folder={folder} role={role} />;
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
|
||||||
</Breadcrumb>
|
|
||||||
<ContainerSettings>
|
|
||||||
<FolderSettings folder={folder} role={role} />
|
|
||||||
</ContainerSettings>
|
|
||||||
<FolderBody folder={folder} role={role} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||||
|
|
||||||
|
interface BreadcrumbItemProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
avatar?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BreadcrumbItem = ({ id, name, avatar }: BreadcrumbItemProps) => {
|
||||||
|
return (
|
||||||
|
<div className="text-muted-foreground flex items-center space-x-2 hover:text-foreground cursor-pointer text-sm">
|
||||||
|
<Avatar id={id} avatar={avatar} name={name} className="size-4" />
|
||||||
|
<span>{name}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { useRouter, useLocation } from '@tanstack/react-router';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const ContainerHeader = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const headerComponent = useMemo(() => {
|
||||||
|
const matches = router.matchRoutes(location.href);
|
||||||
|
for (let i = matches.length - 1; i >= 0; i--) {
|
||||||
|
const match = matches[i];
|
||||||
|
if (match?.context && 'header' in match.context) {
|
||||||
|
return match.context.header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [router, location.href]);
|
||||||
|
|
||||||
|
return headerComponent;
|
||||||
|
};
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Outlet } from '@tanstack/react-router';
|
import { Outlet } from '@tanstack/react-router';
|
||||||
import { useRef, useState } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
import { ContainerHeader } from '@colanode/ui/components/layouts/containers/container-header';
|
||||||
|
import { SidebarMobile } from '@colanode/ui/components/layouts/sidebars/sidebar-mobile';
|
||||||
import {
|
import {
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
ScrollBar,
|
ScrollBar,
|
||||||
ScrollViewport,
|
ScrollViewport,
|
||||||
} from '@colanode/ui/components/ui/scroll-area';
|
} from '@colanode/ui/components/ui/scroll-area';
|
||||||
import { SidebarMobile } from '@colanode/ui/components/workspaces/sidebars/sidebar-mobile';
|
|
||||||
import { useApp } from '@colanode/ui/contexts/app';
|
import { useApp } from '@colanode/ui/contexts/app';
|
||||||
import { ContainerContext } from '@colanode/ui/contexts/container';
|
import { ContainerContext } from '@colanode/ui/contexts/container';
|
||||||
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
|
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
|
||||||
@@ -15,8 +16,6 @@ import { cn } from '@colanode/ui/lib/utils';
|
|||||||
export const Container = () => {
|
export const Container = () => {
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const [settings, setSettings] = useState<React.ReactNode>(null);
|
|
||||||
const [breadcrumb, setBreadcrumb] = useState<React.ReactNode>(null);
|
|
||||||
|
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null!);
|
const scrollAreaRef = useRef<HTMLDivElement>(null!);
|
||||||
const scrollViewportRef = useRef<HTMLDivElement>(null!);
|
const scrollViewportRef = useRef<HTMLDivElement>(null!);
|
||||||
@@ -24,10 +23,6 @@ export const Container = () => {
|
|||||||
return (
|
return (
|
||||||
<ContainerContext.Provider
|
<ContainerContext.Provider
|
||||||
value={{
|
value={{
|
||||||
setSettings,
|
|
||||||
setBreadcrumb,
|
|
||||||
resetSettings: () => setSettings(null),
|
|
||||||
resetBreadcrumb: () => setBreadcrumb(null),
|
|
||||||
scrollAreaRef,
|
scrollAreaRef,
|
||||||
scrollViewportRef,
|
scrollViewportRef,
|
||||||
}}
|
}}
|
||||||
@@ -40,8 +35,9 @@ export const Container = () => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isMobile && <SidebarMobile />}
|
{isMobile && <SidebarMobile />}
|
||||||
{breadcrumb && <div className="flex-1">{breadcrumb}</div>}
|
<div className="flex-1">
|
||||||
{settings}
|
<ContainerHeader />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea ref={scrollAreaRef} className="overflow-hidden h-full">
|
<ScrollArea ref={scrollAreaRef} className="overflow-hidden h-full">
|
||||||
<ScrollViewport ref={scrollViewportRef} className="h-full">
|
<ScrollViewport ref={scrollViewportRef} className="h-full">
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ChatCreatePopover } from '@colanode/ui/components/chats/chat-create-popover';
|
import { ChatCreatePopover } from '@colanode/ui/components/chats/chat-create-popover';
|
||||||
import { ChatSidebarItem } from '@colanode/ui/components/chats/chat-sidebar-item';
|
import { ChatSidebarItem } from '@colanode/ui/components/chats/chat-sidebar-item';
|
||||||
|
import { SidebarHeader } from '@colanode/ui/components/layouts/sidebars/sidebar-header';
|
||||||
import { Link } from '@colanode/ui/components/ui/link';
|
import { Link } from '@colanode/ui/components/ui/link';
|
||||||
import { SidebarHeader } from '@colanode/ui/components/workspaces/sidebars/sidebar-header';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Resizable } from 're-resizable';
|
import { Resizable } from 're-resizable';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { Sidebar } from '@colanode/ui/components/workspaces/sidebars/sidebar';
|
import { Sidebar } from '@colanode/ui/components/layouts/sidebars/sidebar';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
import { useMetadata } from '@colanode/ui/hooks/use-metadata';
|
import { useMetadata } from '@colanode/ui/hooks/use-metadata';
|
||||||
|
|
||||||
@@ -3,9 +3,9 @@ import { LayoutGrid, MessageCircle, Settings } from 'lucide-react';
|
|||||||
|
|
||||||
import { SidebarMenuType, UploadStatus } from '@colanode/client/types';
|
import { SidebarMenuType, UploadStatus } from '@colanode/client/types';
|
||||||
import { collections } from '@colanode/ui/collections';
|
import { collections } from '@colanode/ui/collections';
|
||||||
import { SidebarMenuFooter } from '@colanode/ui/components/workspaces/sidebars/sidebar-menu-footer';
|
import { SidebarMenuFooter } from '@colanode/ui/components/layouts/sidebars/sidebar-menu-footer';
|
||||||
import { SidebarMenuHeader } from '@colanode/ui/components/workspaces/sidebars/sidebar-menu-header';
|
import { SidebarMenuHeader } from '@colanode/ui/components/layouts/sidebars/sidebar-menu-header';
|
||||||
import { SidebarMenuIcon } from '@colanode/ui/components/workspaces/sidebars/sidebar-menu-icon';
|
import { SidebarMenuIcon } from '@colanode/ui/components/layouts/sidebars/sidebar-menu-icon';
|
||||||
import { useRadar } from '@colanode/ui/contexts/radar';
|
import { useRadar } from '@colanode/ui/contexts/radar';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@ import { useLocation } from '@tanstack/react-router';
|
|||||||
import { Menu } from 'lucide-react';
|
import { Menu } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Sidebar } from '@colanode/ui/components/workspaces/sidebars/sidebar';
|
import { Sidebar } from '@colanode/ui/components/layouts/sidebars/sidebar';
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
|
|
||||||
import { UploadStatus } from '@colanode/client/types';
|
import { UploadStatus } from '@colanode/client/types';
|
||||||
import { collections } from '@colanode/ui/collections';
|
import { collections } from '@colanode/ui/collections';
|
||||||
|
import { SidebarHeader } from '@colanode/ui/components/layouts/sidebars/sidebar-header';
|
||||||
|
import { SidebarSettingsItem } from '@colanode/ui/components/layouts/sidebars/sidebar-settings-item';
|
||||||
import { Link } from '@colanode/ui/components/ui/link';
|
import { Link } from '@colanode/ui/components/ui/link';
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
import { SidebarHeader } from '@colanode/ui/components/workspaces/sidebars/sidebar-header';
|
|
||||||
import { SidebarSettingsItem } from '@colanode/ui/components/workspaces/sidebars/sidebar-settings-item';
|
|
||||||
import { useApp } from '@colanode/ui/contexts/app';
|
import { useApp } from '@colanode/ui/contexts/app';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { SidebarHeader } from '@colanode/ui/components/layouts/sidebars/sidebar-header';
|
||||||
import { SpaceCreateButton } from '@colanode/ui/components/spaces/space-create-button';
|
import { SpaceCreateButton } from '@colanode/ui/components/spaces/space-create-button';
|
||||||
import { SpaceSidebarItem } from '@colanode/ui/components/spaces/space-sidebar-item';
|
import { SpaceSidebarItem } from '@colanode/ui/components/spaces/space-sidebar-item';
|
||||||
import { SidebarHeader } from '@colanode/ui/components/workspaces/sidebars/sidebar-header';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { SidebarMenuType } from '@colanode/client/types';
|
import { SidebarMenuType } from '@colanode/client/types';
|
||||||
import { SidebarChats } from '@colanode/ui/components/workspaces/sidebars/sidebar-chats';
|
import { SidebarChats } from '@colanode/ui/components/layouts/sidebars/sidebar-chats';
|
||||||
import { SidebarMenu } from '@colanode/ui/components/workspaces/sidebars/sidebar-menu';
|
import { SidebarMenu } from '@colanode/ui/components/layouts/sidebars/sidebar-menu';
|
||||||
import { SidebarSettings } from '@colanode/ui/components/workspaces/sidebars/sidebar-settings';
|
import { SidebarSettings } from '@colanode/ui/components/layouts/sidebars/sidebar-settings';
|
||||||
import { SidebarSpaces } from '@colanode/ui/components/workspaces/sidebars/sidebar-spaces';
|
import { SidebarSpaces } from '@colanode/ui/components/layouts/sidebars/sidebar-spaces';
|
||||||
import { useApp } from '@colanode/ui/contexts/app';
|
import { useApp } from '@colanode/ui/contexts/app';
|
||||||
import { cn } from '@colanode/ui/lib/utils';
|
import { cn } from '@colanode/ui/lib/utils';
|
||||||
|
|
||||||
16
packages/ui/src/components/layouts/tabs/tab-item.tsx
Normal file
16
packages/ui/src/components/layouts/tabs/tab-item.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||||
|
|
||||||
|
interface TabItemProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
avatar?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TabItem = ({ id, name, avatar }: TabItemProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-2 cursor-pointer text-sm">
|
||||||
|
<Avatar id={id} avatar={avatar} name={name} className="size-4" />
|
||||||
|
<span>{name}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { MessageCircle } from 'lucide-react';
|
|
||||||
|
|
||||||
import { LocalMessageNode } from '@colanode/client/types';
|
import { LocalMessageNode } from '@colanode/client/types';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
interface MessageBreadcrumbItemProps {
|
interface MessageBreadcrumbItemProps {
|
||||||
message: LocalMessageNode;
|
message: LocalMessageNode;
|
||||||
@@ -11,9 +10,6 @@ export const MessageBreadcrumbItem = ({
|
|||||||
message: _,
|
message: _,
|
||||||
}: MessageBreadcrumbItemProps) => {
|
}: MessageBreadcrumbItemProps) => {
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem id="message" avatar={defaultIcons.message} name="Message" />
|
||||||
icon={(className) => <MessageCircle className={className} />}
|
|
||||||
name="Message"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { LocalMessageNode } from '@colanode/client/types';
|
import { LocalMessageNode } from '@colanode/client/types';
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { Message } from '@colanode/ui/components/messages/message';
|
import { Message } from '@colanode/ui/components/messages/message';
|
||||||
import { MessageNotFound } from '@colanode/ui/components/messages/message-not-found';
|
import { MessageNotFound } from '@colanode/ui/components/messages/message-not-found';
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { ConversationContext } from '@colanode/ui/contexts/conversation';
|
import { ConversationContext } from '@colanode/ui/contexts/conversation';
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
||||||
@@ -25,23 +23,18 @@ export const MessageContainer = ({ messageId }: MessageContainerProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ConversationContext.Provider
|
||||||
<Breadcrumb>
|
value={{
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
id: data.node.id,
|
||||||
</Breadcrumb>
|
role: data.role,
|
||||||
<ConversationContext.Provider
|
rootId: data.node.rootId,
|
||||||
value={{
|
canCreateMessage: true,
|
||||||
id: data.node.id,
|
onReply: () => {},
|
||||||
role: data.role,
|
onLastMessageIdChange: () => {},
|
||||||
rootId: data.node.rootId,
|
canDeleteMessage: () => false,
|
||||||
canCreateMessage: true,
|
}}
|
||||||
onReply: () => {},
|
>
|
||||||
onLastMessageIdChange: () => {},
|
<Message message={data.node} />
|
||||||
canDeleteMessage: () => false,
|
</ConversationContext.Provider>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Message message={data.node} />
|
|
||||||
</ConversationContext.Provider>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,20 +18,17 @@ import {
|
|||||||
import { Link } from '@colanode/ui/components/ui/link';
|
import { Link } from '@colanode/ui/components/ui/link';
|
||||||
|
|
||||||
interface NodeBreadcrumbProps {
|
interface NodeBreadcrumbProps {
|
||||||
breadcrumb: LocalNode[];
|
nodes: LocalNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NodeBreadcrumb = ({ breadcrumb }: NodeBreadcrumbProps) => {
|
export const NodeBreadcrumb = ({ nodes }: NodeBreadcrumbProps) => {
|
||||||
// Show ellipsis if we have more than 3 nodes (first + last two)
|
const showEllipsis = nodes.length > 3;
|
||||||
const showEllipsis = breadcrumb.length > 3;
|
|
||||||
|
|
||||||
// Get visible entries: first node + last two entries
|
// Get visible entries: first node + last two entries
|
||||||
const visibleItems = showEllipsis
|
const visibleItems = showEllipsis ? [nodes[0], ...nodes.slice(-2)] : nodes;
|
||||||
? [breadcrumb[0], ...breadcrumb.slice(-2)]
|
|
||||||
: breadcrumb;
|
|
||||||
|
|
||||||
// Get middle entries for ellipsis (everything except first and last two)
|
// Get middle entries for ellipsis (everything except first and last two)
|
||||||
const ellipsisItems = showEllipsis ? breadcrumb.slice(1, -2) : [];
|
const ellipsisItems = showEllipsis ? nodes.slice(1, -2) : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumb className="grow">
|
<Breadcrumb className="grow">
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useParams } from '@tanstack/react-router';
|
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { getIdType, IdType } from '@colanode/core';
|
import { getIdType, IdType } from '@colanode/core';
|
||||||
@@ -12,11 +11,11 @@ import { PageContainer } from '@colanode/ui/components/pages/page-container';
|
|||||||
import { RecordContainer } from '@colanode/ui/components/records/record-container';
|
import { RecordContainer } from '@colanode/ui/components/records/record-container';
|
||||||
import { SpaceContainer } from '@colanode/ui/components/spaces/space-container';
|
import { SpaceContainer } from '@colanode/ui/components/spaces/space-container';
|
||||||
|
|
||||||
export const NodeScreen = () => {
|
interface NodeContainerProps {
|
||||||
const { nodeId } = useParams({
|
nodeId: string;
|
||||||
from: '/workspace/$userId/$nodeId',
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
export const NodeContainer = ({ nodeId }: NodeContainerProps) => {
|
||||||
return match(getIdType(nodeId))
|
return match(getIdType(nodeId))
|
||||||
.with(IdType.Space, () => <SpaceContainer spaceId={nodeId} />)
|
.with(IdType.Space, () => <SpaceContainer spaceId={nodeId} />)
|
||||||
.with(IdType.Channel, () => <ChannelContainer channelId={nodeId} />)
|
.with(IdType.Channel, () => <ChannelContainer channelId={nodeId} />)
|
||||||
14
packages/ui/src/components/nodes/node-error-container.tsx
Normal file
14
packages/ui/src/components/nodes/node-error-container.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { BadgeAlert } from 'lucide-react';
|
||||||
|
|
||||||
|
export const NodeErrorContainer = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full p-6 text-center">
|
||||||
|
<BadgeAlert className="size-12 mb-4" />
|
||||||
|
<h1 className="text-2xl font-semibold tracking-tight">Node error</h1>
|
||||||
|
<p className="mt-2 text-sm font-medium text-muted-foreground">
|
||||||
|
The node you are looking for does not exist. It may have been deleted or
|
||||||
|
your access has been removed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { BadgeAlert } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
|
|
||||||
export const NodeErrorScreen = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <BadgeAlert className={className} />}
|
|
||||||
name="Node error"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="flex flex-col items-center justify-center h-full p-6 text-center">
|
|
||||||
<BadgeAlert className="size-12 mb-4" />
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">Node error</h1>
|
|
||||||
<p className="mt-2 text-sm font-medium text-muted-foreground">
|
|
||||||
The node you are looking for does not exist. It may have been deleted
|
|
||||||
or your access has been removed.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
27
packages/ui/src/components/nodes/node-header.tsx
Normal file
27
packages/ui/src/components/nodes/node-header.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { LocalNode } from '@colanode/client/types';
|
||||||
|
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
||||||
|
import { NodeSettings } from '@colanode/ui/components/nodes/node-settings';
|
||||||
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
|
|
||||||
|
interface NodeHeaderProps {
|
||||||
|
nodeId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NodeHeader = ({ nodeId }: NodeHeaderProps) => {
|
||||||
|
const data = useNodeContainer<LocalNode>(nodeId);
|
||||||
|
|
||||||
|
if (data.isPending) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<NodeBreadcrumb nodes={data.breadcrumb} />
|
||||||
|
<NodeSettings node={data.node} role={data.role} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
46
packages/ui/src/components/nodes/node-settings.tsx
Normal file
46
packages/ui/src/components/nodes/node-settings.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { LocalNode } from '@colanode/client/types';
|
||||||
|
import { NodeRole } from '@colanode/core';
|
||||||
|
import { ChannelSettings } from '@colanode/ui/components/channels/channel-settings';
|
||||||
|
import { NodeCollaboratorsPopover } from '@colanode/ui/components/collaborators/node-collaborators-popover';
|
||||||
|
import { DatabaseSettings } from '@colanode/ui/components/databases/database-settings';
|
||||||
|
import { FileSettings } from '@colanode/ui/components/files/file-settings';
|
||||||
|
import { FolderSettings } from '@colanode/ui/components/folders/folder-settings';
|
||||||
|
import { PageSettings } from '@colanode/ui/components/pages/page-settings';
|
||||||
|
import { RecordSettings } from '@colanode/ui/components/records/record-settings';
|
||||||
|
|
||||||
|
interface NodeSettingsProps {
|
||||||
|
node: LocalNode;
|
||||||
|
role: NodeRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NodeSettings = ({ node, role }: NodeSettingsProps) => {
|
||||||
|
if (node.type === 'channel') {
|
||||||
|
return <ChannelSettings channel={node} role={role} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'chat') {
|
||||||
|
return <NodeCollaboratorsPopover node={node} nodes={[node]} role={role} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'database') {
|
||||||
|
return <DatabaseSettings database={node} role={role} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'folder') {
|
||||||
|
return <FolderSettings folder={node} role={role} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'file') {
|
||||||
|
return <FileSettings file={node} role={role} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'page') {
|
||||||
|
return <PageSettings page={node} role={role} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'record') {
|
||||||
|
return <RecordSettings record={node} role={role} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { LocalPageNode } from '@colanode/client/types';
|
import { LocalPageNode } from '@colanode/client/types';
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
|
|
||||||
interface PageBreadcrumbItemProps {
|
interface PageBreadcrumbItemProps {
|
||||||
page: LocalPageNode;
|
page: LocalPageNode;
|
||||||
@@ -9,14 +8,8 @@ interface PageBreadcrumbItemProps {
|
|||||||
export const PageBreadcrumbItem = ({ page }: PageBreadcrumbItemProps) => {
|
export const PageBreadcrumbItem = ({ page }: PageBreadcrumbItemProps) => {
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
icon={(className) => (
|
id={page.id}
|
||||||
<Avatar
|
avatar={page.attributes.avatar}
|
||||||
id={page.id}
|
|
||||||
name={page.attributes.name}
|
|
||||||
avatar={page.attributes.avatar}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
name={page.attributes.name}
|
name={page.attributes.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { LocalPageNode } from '@colanode/client/types';
|
import { LocalPageNode } from '@colanode/client/types';
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { ContainerSettings } from '@colanode/ui/components/workspaces/containers/container-settings';
|
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { PageBody } from '@colanode/ui/components/pages/page-body';
|
import { PageBody } from '@colanode/ui/components/pages/page-body';
|
||||||
import { PageNotFound } from '@colanode/ui/components/pages/page-not-found';
|
import { PageNotFound } from '@colanode/ui/components/pages/page-not-found';
|
||||||
import { PageSettings } from '@colanode/ui/components/pages/page-settings';
|
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
||||||
|
|
||||||
@@ -26,15 +22,5 @@ export const PageContainer = ({ pageId }: PageContainerProps) => {
|
|||||||
|
|
||||||
const { node: page, role } = data;
|
const { node: page, role } = data;
|
||||||
|
|
||||||
return (
|
return <PageBody page={page} role={role} />;
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
|
||||||
</Breadcrumb>
|
|
||||||
<ContainerSettings>
|
|
||||||
<PageSettings page={page} role={role} />
|
|
||||||
</ContainerSettings>
|
|
||||||
<PageBody page={page} role={role} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { LocalRecordNode } from '@colanode/client/types';
|
import { LocalRecordNode } from '@colanode/client/types';
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
|
|
||||||
interface RecordBreadcrumbItemProps {
|
interface RecordBreadcrumbItemProps {
|
||||||
record: LocalRecordNode;
|
record: LocalRecordNode;
|
||||||
@@ -9,14 +8,8 @@ interface RecordBreadcrumbItemProps {
|
|||||||
export const RecordBreadcrumbItem = ({ record }: RecordBreadcrumbItemProps) => {
|
export const RecordBreadcrumbItem = ({ record }: RecordBreadcrumbItemProps) => {
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
icon={(className) => (
|
id={record.id}
|
||||||
<Avatar
|
avatar={record.attributes.avatar}
|
||||||
id={record.id}
|
|
||||||
name={record.attributes.name}
|
|
||||||
avatar={record.attributes.avatar}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
name={record.attributes.name}
|
name={record.attributes.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { LocalRecordNode } from '@colanode/client/types';
|
import { LocalRecordNode } from '@colanode/client/types';
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { ContainerSettings } from '@colanode/ui/components/workspaces/containers/container-settings';
|
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { RecordBody } from '@colanode/ui/components/records/record-body';
|
import { RecordBody } from '@colanode/ui/components/records/record-body';
|
||||||
import { RecordNotFound } from '@colanode/ui/components/records/record-not-found';
|
import { RecordNotFound } from '@colanode/ui/components/records/record-not-found';
|
||||||
import { RecordSettings } from '@colanode/ui/components/records/record-settings';
|
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
|
||||||
|
|
||||||
@@ -27,15 +23,5 @@ export const RecordContainer = ({ recordId }: RecordContainerProps) => {
|
|||||||
|
|
||||||
const { node: record, role } = data;
|
const { node: record, role } = data;
|
||||||
|
|
||||||
return (
|
return <RecordBody record={record} role={role} />;
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
|
||||||
</Breadcrumb>
|
|
||||||
<ContainerSettings>
|
|
||||||
<RecordSettings record={record} role={role} />
|
|
||||||
</ContainerSettings>
|
|
||||||
<RecordBody record={record} role={role} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { LocalSpaceNode } from '@colanode/client/types';
|
import { LocalSpaceNode } from '@colanode/client/types';
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
|
|
||||||
interface SpaceBreadcrumbItemProps {
|
interface SpaceBreadcrumbItemProps {
|
||||||
space: LocalSpaceNode;
|
space: LocalSpaceNode;
|
||||||
@@ -9,14 +8,8 @@ interface SpaceBreadcrumbItemProps {
|
|||||||
export const SpaceBreadcrumbItem = ({ space }: SpaceBreadcrumbItemProps) => {
|
export const SpaceBreadcrumbItem = ({ space }: SpaceBreadcrumbItemProps) => {
|
||||||
return (
|
return (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
icon={(className) => (
|
id={space.id}
|
||||||
<Avatar
|
avatar={space.attributes.avatar}
|
||||||
id={space.id}
|
|
||||||
name={space.attributes.name}
|
|
||||||
avatar={space.attributes.avatar}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
name={space.attributes.name}
|
name={space.attributes.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { LocalSpaceNode } from '@colanode/client/types';
|
import { LocalSpaceNode } from '@colanode/client/types';
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
|
|
||||||
import { SpaceBody } from '@colanode/ui/components/spaces/space-body';
|
import { SpaceBody } from '@colanode/ui/components/spaces/space-body';
|
||||||
import { SpaceNotFound } from '@colanode/ui/components/spaces/space-not-found';
|
import { SpaceNotFound } from '@colanode/ui/components/spaces/space-not-found';
|
||||||
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
|
||||||
@@ -25,12 +23,5 @@ export const SpaceContainer = ({ spaceId }: SpaceContainerProps) => {
|
|||||||
|
|
||||||
const { node, role } = data;
|
const { node, role } = data;
|
||||||
|
|
||||||
return (
|
return <SpaceBody space={node} role={role} />;
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
|
|
||||||
</Breadcrumb>
|
|
||||||
<SpaceBody space={node} role={role} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { toast } from 'sonner';
|
|||||||
import { LocalSpaceNode } from '@colanode/client/types';
|
import { LocalSpaceNode } from '@colanode/client/types';
|
||||||
import { extractNodeRole } from '@colanode/core';
|
import { extractNodeRole } from '@colanode/core';
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||||
|
import { WorkspaceSidebarItem } from '@colanode/ui/components/layouts/sidebars/sidebar-item';
|
||||||
import { SpaceSidebarDropdown } from '@colanode/ui/components/spaces/space-sidebar-dropdown';
|
import { SpaceSidebarDropdown } from '@colanode/ui/components/spaces/space-sidebar-dropdown';
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
@@ -13,7 +14,6 @@ import {
|
|||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from '@colanode/ui/components/ui/collapsible';
|
} from '@colanode/ui/components/ui/collapsible';
|
||||||
import { Link } from '@colanode/ui/components/ui/link';
|
import { Link } from '@colanode/ui/components/ui/link';
|
||||||
import { WorkspaceSidebarItem } from '@colanode/ui/components/workspaces/sidebars/sidebar-item';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
|
|||||||
<ol
|
<ol
|
||||||
data-slot="breadcrumb-list"
|
data-slot="breadcrumb-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
|
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm wrap-break-word sm:gap-2.5',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { cn } from '@colanode/ui/lib/utils';
|
|
||||||
|
|
||||||
interface BreadcrumbItemProps {
|
|
||||||
icon: (className: string) => React.ReactNode;
|
|
||||||
name: string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BreadcrumbItem = ({
|
|
||||||
icon,
|
|
||||||
name,
|
|
||||||
className,
|
|
||||||
}: BreadcrumbItemProps) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'text-muted-foreground flex items-center space-x-2 hover:text-foreground cursor-pointer text-sm',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{icon('size-4')}
|
|
||||||
<span>{name}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { useContainer } from '@colanode/ui/contexts/container';
|
|
||||||
|
|
||||||
interface BreadcrumbProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Breadcrumb = ({ children }: BreadcrumbProps) => {
|
|
||||||
const container = useContainer();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
container.setBreadcrumb(children);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
container.resetBreadcrumb();
|
|
||||||
};
|
|
||||||
}, [children]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { useContainer } from '@colanode/ui/contexts/container';
|
|
||||||
|
|
||||||
interface ContainerSettingsProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ContainerSettings = ({ children }: ContainerSettingsProps) => {
|
|
||||||
const container = useContainer();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
container.setSettings(children);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
container.resetSettings();
|
|
||||||
};
|
|
||||||
}, [children]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { useLiveQuery } from '@tanstack/react-db';
|
||||||
|
|
||||||
|
import { collections } from '@colanode/ui/collections';
|
||||||
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
|
import { WorkspaceDownloadFile } from '@colanode/ui/components/workspaces/downloads/workspace-download-file';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
|
||||||
|
export const WorkspaceDownloadsContainer = () => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
|
|
||||||
|
const downloadsQuery = useLiveQuery((q) =>
|
||||||
|
q
|
||||||
|
.from({ downloads: collections.workspace(workspace.userId).downloads })
|
||||||
|
.select(({ downloads }) => downloads)
|
||||||
|
.orderBy(({ downloads }) => downloads.id, 'desc')
|
||||||
|
);
|
||||||
|
|
||||||
|
const downloads = downloadsQuery.data ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overflow-y-auto">
|
||||||
|
<div className="max-w-4xl space-y-10">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Downloads</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4 w-full">
|
||||||
|
{downloads.map((download) => (
|
||||||
|
<WorkspaceDownloadFile key={download.id} download={download} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const WorkspaceDownloadsHeader = () => {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem
|
||||||
|
id="downloads"
|
||||||
|
avatar={defaultIcons.downloads}
|
||||||
|
name="Downloads"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { useLiveQuery } from '@tanstack/react-db';
|
|
||||||
import { Download } from 'lucide-react';
|
|
||||||
|
|
||||||
import { collections } from '@colanode/ui/collections';
|
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { WorkspaceDownloadFile } from '@colanode/ui/components/workspaces/downloads/workspace-download-file';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|
||||||
|
|
||||||
export const WorkspaceDownloadsScreen = () => {
|
|
||||||
const workspace = useWorkspace();
|
|
||||||
|
|
||||||
const downloadsQuery = useLiveQuery((q) =>
|
|
||||||
q
|
|
||||||
.from({ downloads: collections.workspace(workspace.userId).downloads })
|
|
||||||
.select(({ downloads }) => downloads)
|
|
||||||
.orderBy(({ downloads }) => downloads.id, 'desc')
|
|
||||||
);
|
|
||||||
|
|
||||||
const downloads = downloadsQuery.data ?? [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <Download className={className} />}
|
|
||||||
name="Downloads"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="overflow-y-auto">
|
|
||||||
<div className="max-w-4xl space-y-10">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Downloads</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4 w-full">
|
|
||||||
{downloads.map((download) => (
|
|
||||||
<WorkspaceDownloadFile key={download.id} download={download} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import { Download } from 'lucide-react';
|
import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
export const WorkspaceDownloadsTab = () => {
|
export const WorkspaceDownloadsTab = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center space-x-2">
|
<TabItem id="downloads" avatar={defaultIcons.downloads} name="Downloads" />
|
||||||
<Download className="size-4" />
|
|
||||||
<span>Downloads</span>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { UserStorageStats } from '@colanode/ui/components/workspaces/storage/user-storage-stats';
|
||||||
|
import { WorkspaceStorageStats } from '@colanode/ui/components/workspaces/storage/workspace-storage-stats';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
|
||||||
|
export const WorkspaceStorageContainer = () => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
|
const canManageStorage =
|
||||||
|
workspace.role === 'owner' || workspace.role === 'admin';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl space-y-10">
|
||||||
|
<UserStorageStats />
|
||||||
|
{canManageStorage && <WorkspaceStorageStats />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const WorkspaceStorageHeader = () => {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem id="storage" avatar={defaultIcons.storage} name="Storage" />
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Cylinder } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { UserStorageStats } from '@colanode/ui/components/workspaces/storage/user-storage-stats';
|
|
||||||
import { WorkspaceStorageStats } from '@colanode/ui/components/workspaces/storage/workspace-storage-stats';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|
||||||
|
|
||||||
export const WorkspaceStorageScreen = () => {
|
|
||||||
const workspace = useWorkspace();
|
|
||||||
const canManageStorage =
|
|
||||||
workspace.role === 'owner' || workspace.role === 'admin';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <Cylinder className={className} />}
|
|
||||||
name="Storage"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="max-w-4xl space-y-10">
|
|
||||||
<UserStorageStats />
|
|
||||||
{canManageStorage && <WorkspaceStorageStats />}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Cylinder } from 'lucide-react';
|
import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
export const WorkspaceStorageTab = () => {
|
export const WorkspaceStorageTab = () => {
|
||||||
return (
|
return <TabItem id="storage" avatar={defaultIcons.storage} name="Storage" />;
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Cylinder className="size-4" />
|
|
||||||
<span>Workspace Storage</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { InView } from 'react-intersection-observer';
|
||||||
|
|
||||||
|
import { UploadListQueryInput } from '@colanode/client/queries';
|
||||||
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
|
import { WorkspaceUploadFile } from '@colanode/ui/components/workspaces/uploads/workspace-upload-file';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
import { useLiveQueries } from '@colanode/ui/hooks/use-live-queries';
|
||||||
|
|
||||||
|
const UPLOADS_PER_PAGE = 100;
|
||||||
|
|
||||||
|
export const WorkspaceUploadsContainer = () => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
|
|
||||||
|
const [lastPage, setLastPage] = useState<number>(1);
|
||||||
|
const inputs: UploadListQueryInput[] = Array.from({
|
||||||
|
length: lastPage,
|
||||||
|
}).map((_, i) => ({
|
||||||
|
type: 'upload.list',
|
||||||
|
userId: workspace.userId,
|
||||||
|
count: UPLOADS_PER_PAGE,
|
||||||
|
page: i + 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const result = useLiveQueries(inputs);
|
||||||
|
const uploads = result.flatMap((data) => data.data ?? []);
|
||||||
|
|
||||||
|
const isPending = result.some((data) => data.isPending);
|
||||||
|
const hasMore = !isPending && uploads.length === lastPage * UPLOADS_PER_PAGE;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overflow-y-auto">
|
||||||
|
<div className="max-w-4xl space-y-10">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Uploads</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4 w-full">
|
||||||
|
{uploads.map((upload) => (
|
||||||
|
<WorkspaceUploadFile key={upload.fileId} upload={upload} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<InView
|
||||||
|
rootMargin="200px"
|
||||||
|
onChange={(inView) => {
|
||||||
|
if (inView && hasMore && !isPending) {
|
||||||
|
setLastPage(lastPage + 1);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const WorkspaceUploadsHeader = () => {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem id="uploads" avatar={defaultIcons.uploads} name="Uploads" />
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { Upload } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { InView } from 'react-intersection-observer';
|
|
||||||
|
|
||||||
import { UploadListQueryInput } from '@colanode/client/queries';
|
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { WorkspaceUploadFile } from '@colanode/ui/components/workspaces/uploads/workspace-upload-file';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|
||||||
import { useLiveQueries } from '@colanode/ui/hooks/use-live-queries';
|
|
||||||
|
|
||||||
const UPLOADS_PER_PAGE = 100;
|
|
||||||
|
|
||||||
export const WorkspaceUploadsScreen = () => {
|
|
||||||
const workspace = useWorkspace();
|
|
||||||
|
|
||||||
const [lastPage, setLastPage] = useState<number>(1);
|
|
||||||
const inputs: UploadListQueryInput[] = Array.from({
|
|
||||||
length: lastPage,
|
|
||||||
}).map((_, i) => ({
|
|
||||||
type: 'upload.list',
|
|
||||||
userId: workspace.userId,
|
|
||||||
count: UPLOADS_PER_PAGE,
|
|
||||||
page: i + 1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const result = useLiveQueries(inputs);
|
|
||||||
const uploads = result.flatMap((data) => data.data ?? []);
|
|
||||||
|
|
||||||
const isPending = result.some((data) => data.isPending);
|
|
||||||
const hasMore = !isPending && uploads.length === lastPage * UPLOADS_PER_PAGE;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <Upload className={className} />}
|
|
||||||
name="Uploads"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="overflow-y-auto">
|
|
||||||
<div className="max-w-4xl space-y-10">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Uploads</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4 w-full">
|
|
||||||
{uploads.map((upload) => (
|
|
||||||
<WorkspaceUploadFile key={upload.fileId} upload={upload} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<InView
|
|
||||||
rootMargin="200px"
|
|
||||||
onChange={(inView) => {
|
|
||||||
if (inView && hasMore && !isPending) {
|
|
||||||
setLastPage(lastPage + 1);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Upload } from 'lucide-react';
|
import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
export const WorkspaceUploadsTab = () => {
|
export const WorkspaceUploadsTab = () => {
|
||||||
return (
|
return <TabItem id="uploads" avatar={defaultIcons.uploads} name="Uploads" />;
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Upload className="size-4" />
|
|
||||||
<span>Uploads</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|||||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||||
|
|
||||||
export const WorkspaceCreateScreen = () => {
|
export const WorkspaceCreate = () => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutate, isPending } = useMutation();
|
const { mutate, isPending } = useMutation();
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export const WorkspaceHomeContainer = () => {
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full flex flex-col gap-1">
|
||||||
|
<div className="h-10 app-drag-region"></div>
|
||||||
|
<div className="grow flex items-center justify-center">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
What did you get done this week?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const WorkspaceHomeHeader = () => {
|
||||||
|
return <BreadcrumbItem id="home" avatar={defaultIcons.home} name="Home" />;
|
||||||
|
};
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { Home } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
|
|
||||||
export const WorkspaceHomeScreen = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <Home className={className} />}
|
|
||||||
name="Home"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="h-full w-full flex flex-col gap-1">
|
|
||||||
<div className="h-10 app-drag-region"></div>
|
|
||||||
<div className="grow flex items-center justify-center">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
What did you get done this week?
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Container } from '@colanode/ui/components/workspaces/containers/container';
|
import { Container } from '@colanode/ui/components/layouts/containers/container';
|
||||||
import { SidebarDesktop } from '@colanode/ui/components/workspaces/sidebars/sidebar-desktop';
|
import { SidebarDesktop } from '@colanode/ui/components/layouts/sidebars/sidebar-desktop';
|
||||||
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
|
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
|
||||||
|
|
||||||
export const WorkspaceLayout = () => {
|
export const WorkspaceLayout = () => {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { collections } from '@colanode/ui/collections';
|
||||||
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
|
import { WorkspaceDelete } from '@colanode/ui/components/workspaces/workspace-delete';
|
||||||
|
import { WorkspaceForm } from '@colanode/ui/components/workspaces/workspace-form';
|
||||||
|
import { WorkspaceNotFound } from '@colanode/ui/components/workspaces/workspace-not-found';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||||
|
|
||||||
|
export const WorkspaceSettingsContainer = () => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
|
const { mutate, isPending } = useMutation();
|
||||||
|
|
||||||
|
const currentWorkspaceQuery = useLiveQuery((q) =>
|
||||||
|
q
|
||||||
|
.from({ workspaces: collections.workspaces })
|
||||||
|
.where(({ workspaces }) => eq(workspaces.accountId, workspace.accountId))
|
||||||
|
.select(({ workspaces }) => ({
|
||||||
|
name: workspaces.name,
|
||||||
|
description: workspaces.description,
|
||||||
|
avatar: workspaces.avatar,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentWorkspace = currentWorkspaceQuery.data?.[0];
|
||||||
|
const canEdit = workspace.role === 'owner';
|
||||||
|
|
||||||
|
if (!currentWorkspace) {
|
||||||
|
return <WorkspaceNotFound />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl space-y-8">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">General</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<WorkspaceForm
|
||||||
|
readOnly={!canEdit}
|
||||||
|
values={{
|
||||||
|
name: currentWorkspace.name,
|
||||||
|
description: currentWorkspace.description ?? '',
|
||||||
|
avatar: currentWorkspace.avatar ?? null,
|
||||||
|
}}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
mutate({
|
||||||
|
input: {
|
||||||
|
type: 'workspace.update',
|
||||||
|
id: workspace.workspaceId,
|
||||||
|
userId: workspace.userId,
|
||||||
|
name: values.name,
|
||||||
|
description: values.description,
|
||||||
|
avatar: values.avatar ?? null,
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
toast.success('Workspace updated');
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isSaving={isPending}
|
||||||
|
saveText="Update"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Danger Zone</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<WorkspaceDelete />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const WorkspaceSettingsHeader = () => {
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem
|
||||||
|
id="settings"
|
||||||
|
avatar={defaultIcons.settings}
|
||||||
|
name="Settings"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
|
||||||
import { Settings } from 'lucide-react';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
import { collections } from '@colanode/ui/collections';
|
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { WorkspaceDelete } from '@colanode/ui/components/workspaces/workspace-delete';
|
|
||||||
import { WorkspaceForm } from '@colanode/ui/components/workspaces/workspace-form';
|
|
||||||
import { WorkspaceNotFound } from '@colanode/ui/components/workspaces/workspace-not-found';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|
||||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
|
||||||
|
|
||||||
export const WorkspaceSettingsScreen = () => {
|
|
||||||
const workspace = useWorkspace();
|
|
||||||
const { mutate, isPending } = useMutation();
|
|
||||||
|
|
||||||
const currentWorkspaceQuery = useLiveQuery((q) =>
|
|
||||||
q
|
|
||||||
.from({ workspaces: collections.workspaces })
|
|
||||||
.where(({ workspaces }) => eq(workspaces.accountId, workspace.accountId))
|
|
||||||
.select(({ workspaces }) => ({
|
|
||||||
name: workspaces.name,
|
|
||||||
description: workspaces.description,
|
|
||||||
avatar: workspaces.avatar,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentWorkspace = currentWorkspaceQuery.data?.[0];
|
|
||||||
const canEdit = workspace.role === 'owner';
|
|
||||||
|
|
||||||
if (!currentWorkspace) {
|
|
||||||
return <WorkspaceNotFound />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <Settings className={className} />}
|
|
||||||
name="Settings"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="max-w-4xl space-y-8">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">General</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<WorkspaceForm
|
|
||||||
readOnly={!canEdit}
|
|
||||||
values={{
|
|
||||||
name: currentWorkspace.name,
|
|
||||||
description: currentWorkspace.description ?? '',
|
|
||||||
avatar: currentWorkspace.avatar ?? null,
|
|
||||||
}}
|
|
||||||
onSubmit={(values) => {
|
|
||||||
mutate({
|
|
||||||
input: {
|
|
||||||
type: 'workspace.update',
|
|
||||||
id: workspace.workspaceId,
|
|
||||||
userId: workspace.userId,
|
|
||||||
name: values.name,
|
|
||||||
description: values.description,
|
|
||||||
avatar: values.avatar ?? null,
|
|
||||||
},
|
|
||||||
onSuccess() {
|
|
||||||
toast.success('Workspace updated');
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
isSaving={isPending}
|
|
||||||
saveText="Update"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
|
||||||
Danger Zone
|
|
||||||
</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<WorkspaceDelete />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Settings } from 'lucide-react';
|
import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
export const WorkspaceSettingsTab = () => {
|
export const WorkspaceSettingsTab = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center space-x-2">
|
<TabItem
|
||||||
<Settings className="size-4" />
|
id="settings"
|
||||||
<span>Workspace Settings</span>
|
avatar={defaultIcons.settings}
|
||||||
</div>
|
name="Workspace Settings"
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { InView } from 'react-intersection-observer';
|
||||||
|
|
||||||
|
import { UserListQueryInput } from '@colanode/client/queries';
|
||||||
|
import { WorkspaceRole } from '@colanode/core';
|
||||||
|
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||||
|
import { Separator } from '@colanode/ui/components/ui/separator';
|
||||||
|
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
||||||
|
import { WorkspaceUserInvite } from '@colanode/ui/components/workspaces/workspace-user-invite';
|
||||||
|
import { WorkspaceUserRoleDropdown } from '@colanode/ui/components/workspaces/workspace-user-role-dropdown';
|
||||||
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
import { useLiveQueries } from '@colanode/ui/hooks/use-live-queries';
|
||||||
|
|
||||||
|
const USERS_PER_PAGE = 50;
|
||||||
|
|
||||||
|
export const WorkspaceUsersContainer = () => {
|
||||||
|
const workspace = useWorkspace();
|
||||||
|
const canEditUsers = workspace.role === 'owner' || workspace.role === 'admin';
|
||||||
|
const [lastPage, setLastPage] = useState<number>(1);
|
||||||
|
|
||||||
|
const inputs: UserListQueryInput[] = Array.from({
|
||||||
|
length: lastPage,
|
||||||
|
}).map((_, i) => ({
|
||||||
|
type: 'user.list',
|
||||||
|
page: i + 1,
|
||||||
|
count: USERS_PER_PAGE,
|
||||||
|
userId: workspace.userId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const result = useLiveQueries(inputs);
|
||||||
|
const users = result.flatMap((data) => data.data ?? []);
|
||||||
|
const isPending = result.some((data) => data.isPending);
|
||||||
|
const hasMore = !isPending && users.length === lastPage * USERS_PER_PAGE;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl space-y-8">
|
||||||
|
{canEditUsers && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Invite</h2>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<WorkspaceUserInvite />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Users</h2>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
The list of all users on the workspace
|
||||||
|
</p>
|
||||||
|
<Separator className="mt-3" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{users.map((user) => {
|
||||||
|
const name: string = user.name ?? 'Unknown';
|
||||||
|
const email: string = user.email ?? ' ';
|
||||||
|
const avatar: string | null | undefined = user.avatar;
|
||||||
|
const role: WorkspaceRole = user.role;
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={user.id} className="flex items-center space-x-3">
|
||||||
|
<Avatar id={user.id} name={name} avatar={avatar} />
|
||||||
|
<div className="grow">
|
||||||
|
<p className="text-sm font-medium leading-none">{name}</p>
|
||||||
|
<p className="text-sm text-muted-foreground">{email}</p>
|
||||||
|
</div>
|
||||||
|
<WorkspaceUserRoleDropdown
|
||||||
|
userId={user.id}
|
||||||
|
value={role}
|
||||||
|
canEdit={canEditUsers}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div className="flex items-center justify-center space-x-3">
|
||||||
|
{isPending && <Spinner />}
|
||||||
|
</div>
|
||||||
|
<InView
|
||||||
|
rootMargin="200px"
|
||||||
|
onChange={(inView) => {
|
||||||
|
if (inView && hasMore && !isPending) {
|
||||||
|
setLastPage(lastPage + 1);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
|
export const WorkspaceUsersHeader = () => {
|
||||||
|
return <BreadcrumbItem id="users" avatar={defaultIcons.users} name="Users" />;
|
||||||
|
};
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import { Users } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { InView } from 'react-intersection-observer';
|
|
||||||
|
|
||||||
import { UserListQueryInput } from '@colanode/client/queries';
|
|
||||||
import { WorkspaceRole } from '@colanode/core';
|
|
||||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
|
||||||
import { Separator } from '@colanode/ui/components/ui/separator';
|
|
||||||
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
|
||||||
import { Breadcrumb } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb';
|
|
||||||
import { BreadcrumbItem } from '@colanode/ui/components/workspaces/breadcrumbs/breadcrumb-item';
|
|
||||||
import { WorkspaceUserInvite } from '@colanode/ui/components/workspaces/workspace-user-invite';
|
|
||||||
import { WorkspaceUserRoleDropdown } from '@colanode/ui/components/workspaces/workspace-user-role-dropdown';
|
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|
||||||
import { useLiveQueries } from '@colanode/ui/hooks/use-live-queries';
|
|
||||||
|
|
||||||
const USERS_PER_PAGE = 50;
|
|
||||||
|
|
||||||
export const WorkspaceUsersScreen = () => {
|
|
||||||
const workspace = useWorkspace();
|
|
||||||
const canEditUsers = workspace.role === 'owner' || workspace.role === 'admin';
|
|
||||||
const [lastPage, setLastPage] = useState<number>(1);
|
|
||||||
|
|
||||||
const inputs: UserListQueryInput[] = Array.from({
|
|
||||||
length: lastPage,
|
|
||||||
}).map((_, i) => ({
|
|
||||||
type: 'user.list',
|
|
||||||
page: i + 1,
|
|
||||||
count: USERS_PER_PAGE,
|
|
||||||
userId: workspace.userId,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const result = useLiveQueries(inputs);
|
|
||||||
const users = result.flatMap((data) => data.data ?? []);
|
|
||||||
const isPending = result.some((data) => data.isPending);
|
|
||||||
const hasMore = !isPending && users.length === lastPage * USERS_PER_PAGE;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbItem
|
|
||||||
icon={(className) => <Users className={className} />}
|
|
||||||
name="Users"
|
|
||||||
/>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="max-w-4xl space-y-8">
|
|
||||||
{canEditUsers && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Invite</h2>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<WorkspaceUserInvite />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Users</h2>
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
|
||||||
The list of all users on the workspace
|
|
||||||
</p>
|
|
||||||
<Separator className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
{users.map((user) => {
|
|
||||||
const name: string = user.name ?? 'Unknown';
|
|
||||||
const email: string = user.email ?? ' ';
|
|
||||||
const avatar: string | null | undefined = user.avatar;
|
|
||||||
const role: WorkspaceRole = user.role;
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={user.id} className="flex items-center space-x-3">
|
|
||||||
<Avatar id={user.id} name={name} avatar={avatar} />
|
|
||||||
<div className="grow">
|
|
||||||
<p className="text-sm font-medium leading-none">{name}</p>
|
|
||||||
<p className="text-sm text-muted-foreground">{email}</p>
|
|
||||||
</div>
|
|
||||||
<WorkspaceUserRoleDropdown
|
|
||||||
userId={user.id}
|
|
||||||
value={role}
|
|
||||||
canEdit={canEditUsers}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<div className="flex items-center justify-center space-x-3">
|
|
||||||
{isPending && <Spinner />}
|
|
||||||
</div>
|
|
||||||
<InView
|
|
||||||
rootMargin="200px"
|
|
||||||
onChange={(inView) => {
|
|
||||||
if (inView && hasMore && !isPending) {
|
|
||||||
setLastPage(lastPage + 1);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Users } from 'lucide-react';
|
import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item';
|
||||||
|
import { defaultIcons } from '@colanode/ui/lib/assets';
|
||||||
|
|
||||||
export const WorkspaceUsersTab = () => {
|
export const WorkspaceUsersTab = () => {
|
||||||
return (
|
return <TabItem id="users" avatar={defaultIcons.users} name="Users" />;
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Users className="size-4" />
|
|
||||||
<span>Workspace Users</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { eq, useLiveQuery } from '@tanstack/react-db';
|
import { eq, useLiveQuery } from '@tanstack/react-db';
|
||||||
import { useParams } from '@tanstack/react-router';
|
|
||||||
|
|
||||||
import { collections } from '@colanode/ui/collections';
|
import { collections } from '@colanode/ui/collections';
|
||||||
import { ServerProvider } from '@colanode/ui/components/servers/server-provider';
|
import { ServerProvider } from '@colanode/ui/components/servers/server-provider';
|
||||||
@@ -8,11 +7,11 @@ import { WorkspaceNotFound } from '@colanode/ui/components/workspaces/workspace-
|
|||||||
import { WorkspaceContext } from '@colanode/ui/contexts/workspace';
|
import { WorkspaceContext } from '@colanode/ui/contexts/workspace';
|
||||||
import { useLocationTracker } from '@colanode/ui/hooks/use-location-tracker';
|
import { useLocationTracker } from '@colanode/ui/hooks/use-location-tracker';
|
||||||
|
|
||||||
export const WorkspaceScreen = () => {
|
interface WorkspaceProps {
|
||||||
const { userId } = useParams({
|
userId: string;
|
||||||
from: '/workspace/$userId',
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
export const Workspace = ({ userId }: WorkspaceProps) => {
|
||||||
const workspaceQuery = useLiveQuery((q) =>
|
const workspaceQuery = useLiveQuery((q) =>
|
||||||
q
|
q
|
||||||
.from({ workspaces: collections.workspaces })
|
.from({ workspaces: collections.workspaces })
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
interface ContainerContext {
|
interface ContainerContext {
|
||||||
setSettings: (settings: React.ReactNode) => void;
|
|
||||||
resetSettings: () => void;
|
|
||||||
setBreadcrumb: (breadcrumb: React.ReactNode) => void;
|
|
||||||
resetBreadcrumb: () => void;
|
|
||||||
scrollAreaRef: React.RefObject<HTMLDivElement>;
|
scrollAreaRef: React.RefObject<HTMLDivElement>;
|
||||||
scrollViewportRef: React.RefObject<HTMLDivElement>;
|
scrollViewportRef: React.RefObject<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,15 @@ export const defaultIcons = {
|
|||||||
bookmark: '01jhzfk3g4q40x7927qcm0hrjdic',
|
bookmark: '01jhzfk3g4q40x7927qcm0hrjdic',
|
||||||
folder: '01jhzfk3jrgc276z2gdabm4cwmic',
|
folder: '01jhzfk3jrgc276z2gdabm4cwmic',
|
||||||
apps: '01jhzfk4m7djqd1pw0e1671cmric',
|
apps: '01jhzfk4m7djqd1pw0e1671cmric',
|
||||||
|
logout: '01jhzfk4pv13qxjprqgqfeqp73ic',
|
||||||
|
settings: '01jhzfk4ra4fvcay6qgrydgsf5ic',
|
||||||
|
appearance: '01jhzfk39qxa7xtr7z69fyrb2pic',
|
||||||
|
message: '01jhzfk36869zq5cp6ke5vvzd6ic',
|
||||||
|
users: '01jhzfk4tvxa8vtxwgm341w2hmic',
|
||||||
|
home: '01jhzfk2zt07xss9tqfvca1g6eic',
|
||||||
|
uploads: '01jhzfk4srtdjmd5zk0hd3fjs4ic',
|
||||||
|
downloads: '01jhzfk4n5app62xvg1m2s3nv7ic',
|
||||||
|
storage: '01jhzfk3d19d8enym86tat3ybeic',
|
||||||
|
error: '01jhzfk4naap5xt27cbkj3mbm6ic',
|
||||||
|
file: '01jhzfk3gkaz5dnfn1ew16b2pcic',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { createRoute } from '@tanstack/react-router';
|
import { createRoute } from '@tanstack/react-router';
|
||||||
|
|
||||||
import { WorkspaceCreateScreen } from '@colanode/ui/components/workspaces/workspace-create-screen';
|
import { WorkspaceCreate } from '@colanode/ui/components/workspaces/workspace-create';
|
||||||
import { WorkspaceCreateTab } from '@colanode/ui/components/workspaces/workspace-create-tab';
|
import { WorkspaceCreateTab } from '@colanode/ui/components/workspaces/workspace-create-tab';
|
||||||
import { rootRoute } from '@colanode/ui/routes/root';
|
import { rootRoute } from '@colanode/ui/routes/root';
|
||||||
|
|
||||||
export const workspaceCreateRoute = createRoute({
|
export const workspaceCreateRoute = createRoute({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
path: '/create',
|
path: '/create',
|
||||||
component: WorkspaceCreateScreen,
|
component: WorkspaceCreate,
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
tab: <WorkspaceCreateTab />,
|
tab: <WorkspaceCreateTab />,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { createRoute } from '@tanstack/react-router';
|
import { createRoute } from '@tanstack/react-router';
|
||||||
|
|
||||||
import { LoginScreen } from '@colanode/ui/components/accounts/login-screen';
|
import { Login } from '@colanode/ui/components/accounts/login';
|
||||||
import { LoginTab } from '@colanode/ui/components/accounts/login-tab';
|
import { LoginTab } from '@colanode/ui/components/accounts/login-tab';
|
||||||
import { rootRoute } from '@colanode/ui/routes/root';
|
import { rootRoute } from '@colanode/ui/routes/root';
|
||||||
|
|
||||||
export const loginRoute = createRoute({
|
export const loginRoute = createRoute({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
path: '/login',
|
path: '/login',
|
||||||
component: LoginScreen,
|
component: Login,
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
tab: <LoginTab />,
|
tab: <LoginTab />,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createRoute, redirect } from '@tanstack/react-router';
|
import { createRoute, redirect } from '@tanstack/react-router';
|
||||||
|
|
||||||
import { AccountLogoutScreen } from '@colanode/ui/components/accounts/account-logout-screen';
|
import { AccountLogoutContainer } from '@colanode/ui/components/accounts/account-logout-container';
|
||||||
|
import { AccountLogoutHeader } from '@colanode/ui/components/accounts/account-logout-header';
|
||||||
import { AccountLogoutTab } from '@colanode/ui/components/accounts/account-logout-tab';
|
import { AccountLogoutTab } from '@colanode/ui/components/accounts/account-logout-tab';
|
||||||
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
||||||
import {
|
import {
|
||||||
@@ -11,10 +12,11 @@ import {
|
|||||||
export const accountLogoutRoute = createRoute({
|
export const accountLogoutRoute = createRoute({
|
||||||
getParentRoute: () => workspaceRoute,
|
getParentRoute: () => workspaceRoute,
|
||||||
path: '/account/logout',
|
path: '/account/logout',
|
||||||
component: AccountLogoutScreen,
|
component: AccountLogoutContainer,
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
tab: <AccountLogoutTab />,
|
tab: <AccountLogoutTab />,
|
||||||
|
header: <AccountLogoutHeader />,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createRoute, redirect } from '@tanstack/react-router';
|
import { createRoute, redirect } from '@tanstack/react-router';
|
||||||
|
|
||||||
import { AccountSettingsScreen } from '@colanode/ui/components/accounts/account-settings-screen';
|
import { AccountSettingsContainer } from '@colanode/ui/components/accounts/account-settings-container';
|
||||||
|
import { AccountSettingsHeader } from '@colanode/ui/components/accounts/account-settings-header';
|
||||||
import { AccountSettingsTab } from '@colanode/ui/components/accounts/account-settings-tab';
|
import { AccountSettingsTab } from '@colanode/ui/components/accounts/account-settings-tab';
|
||||||
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
||||||
import {
|
import {
|
||||||
@@ -11,10 +12,11 @@ import {
|
|||||||
export const accountSettingsRoute = createRoute({
|
export const accountSettingsRoute = createRoute({
|
||||||
getParentRoute: () => workspaceRoute,
|
getParentRoute: () => workspaceRoute,
|
||||||
path: '/account/settings',
|
path: '/account/settings',
|
||||||
component: AccountSettingsScreen,
|
component: AccountSettingsContainer,
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
tab: <AccountSettingsTab />,
|
tab: <AccountSettingsTab />,
|
||||||
|
header: <AccountSettingsHeader />,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createRoute, redirect } from '@tanstack/react-router';
|
import { createRoute, redirect } from '@tanstack/react-router';
|
||||||
|
|
||||||
import { AppAppearanceSettingsScreen } from '@colanode/ui/components/app/app-appearance-settings-screen';
|
import { AppAppearanceSettingsContainer } from '@colanode/ui/components/app/app-appearance-settings-container';
|
||||||
|
import { AppAppearanceSettingsHeader } from '@colanode/ui/components/app/app-appearance-settings-header';
|
||||||
import { AppAppearanceSettingsTab } from '@colanode/ui/components/app/app-appearance-settings-tab';
|
import { AppAppearanceSettingsTab } from '@colanode/ui/components/app/app-appearance-settings-tab';
|
||||||
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
||||||
import {
|
import {
|
||||||
@@ -11,10 +12,11 @@ import {
|
|||||||
export const appAppearanceRoute = createRoute({
|
export const appAppearanceRoute = createRoute({
|
||||||
getParentRoute: () => workspaceRoute,
|
getParentRoute: () => workspaceRoute,
|
||||||
path: '/app/appearance',
|
path: '/app/appearance',
|
||||||
component: AppAppearanceSettingsScreen,
|
component: AppAppearanceSettingsContainer,
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
tab: <AppAppearanceSettingsTab />,
|
tab: <AppAppearanceSettingsTab />,
|
||||||
|
header: <AppAppearanceSettingsHeader />,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createRoute, redirect } from '@tanstack/react-router';
|
import { createRoute, redirect } from '@tanstack/react-router';
|
||||||
|
|
||||||
import { WorkspaceDownloadsScreen } from '@colanode/ui/components/workspaces/downloads/workspace-downloads-screen';
|
import { WorkspaceDownloadsContainer } from '@colanode/ui/components/workspaces/downloads/workspace-downloads-container';
|
||||||
|
import { WorkspaceDownloadsHeader } from '@colanode/ui/components/workspaces/downloads/workspace-downloads-header';
|
||||||
import { WorkspaceDownloadsTab } from '@colanode/ui/components/workspaces/downloads/workspace-downloads-tab';
|
import { WorkspaceDownloadsTab } from '@colanode/ui/components/workspaces/downloads/workspace-downloads-tab';
|
||||||
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
||||||
import {
|
import {
|
||||||
@@ -11,10 +12,11 @@ import {
|
|||||||
export const workspaceDownloadsRoute = createRoute({
|
export const workspaceDownloadsRoute = createRoute({
|
||||||
getParentRoute: () => workspaceRoute,
|
getParentRoute: () => workspaceRoute,
|
||||||
path: '/downloads',
|
path: '/downloads',
|
||||||
component: WorkspaceDownloadsScreen,
|
component: WorkspaceDownloadsContainer,
|
||||||
context: () => {
|
context: () => {
|
||||||
return {
|
return {
|
||||||
tab: <WorkspaceDownloadsTab />,
|
tab: <WorkspaceDownloadsTab />,
|
||||||
|
header: <WorkspaceDownloadsHeader />,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createRoute, redirect } from '@tanstack/react-router';
|
import { createRoute, redirect } from '@tanstack/react-router';
|
||||||
|
|
||||||
import { WorkspaceHomeScreen } from '@colanode/ui/components/workspaces/workspace-home-screen';
|
import { WorkspaceHomeContainer } from '@colanode/ui/components/workspaces/workspace-home-container';
|
||||||
|
import { WorkspaceHomeHeader } from '@colanode/ui/components/workspaces/workspace-home-header';
|
||||||
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
import { getWorkspaceUserId } from '@colanode/ui/routes/utils';
|
||||||
import {
|
import {
|
||||||
workspaceMaskRoute,
|
workspaceMaskRoute,
|
||||||
@@ -10,7 +11,12 @@ import {
|
|||||||
export const workspaceHomeRoute = createRoute({
|
export const workspaceHomeRoute = createRoute({
|
||||||
getParentRoute: () => workspaceRoute,
|
getParentRoute: () => workspaceRoute,
|
||||||
path: '/home',
|
path: '/home',
|
||||||
component: WorkspaceHomeScreen,
|
component: WorkspaceHomeContainer,
|
||||||
|
context: () => {
|
||||||
|
return {
|
||||||
|
header: <WorkspaceHomeHeader />,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const workspaceHomeMaskRoute = createRoute({
|
export const workspaceHomeMaskRoute = createRoute({
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user