Improve node delete handling

This commit is contained in:
Hakan Shehu
2025-11-20 14:31:50 -08:00
parent 28009f986a
commit f881e153b5
36 changed files with 149 additions and 909 deletions

View File

@@ -1,22 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
DatabaseDeleteMutationInput,
DatabaseDeleteMutationOutput,
} from '@colanode/client/mutations/databases/database-delete';
export class DatabaseDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<DatabaseDeleteMutationInput>
{
async handleMutation(
input: DatabaseDeleteMutationInput
): Promise<DatabaseDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.databaseId);
return {
success: true,
};
}
}

View File

@@ -1,22 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
FileDeleteMutationInput,
FileDeleteMutationOutput,
} from '@colanode/client/mutations';
export class FileDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<FileDeleteMutationInput>
{
async handleMutation(
input: FileDeleteMutationInput
): Promise<FileDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.fileId);
return {
success: true,
};
}
}

View File

@@ -1,23 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
FolderDeleteMutationInput,
FolderDeleteMutationOutput,
} from '@colanode/client/mutations';
export class FolderDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<FolderDeleteMutationInput>
{
async handleMutation(
input: FolderDeleteMutationInput
): Promise<FolderDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.folderId);
return {
success: true,
};
}
}

View File

@@ -20,7 +20,6 @@ import { ChannelCreateMutationHandler } from './channels/channel-create';
import { ChannelUpdateMutationHandler } from './channels/channel-update';
import { ChatCreateMutationHandler } from './chats/chat-create';
import { DatabaseCreateMutationHandler } from './databases/database-create';
import { DatabaseDeleteMutationHandler } from './databases/database-delete';
import { DatabaseNameFieldUpdateMutationHandler } from './databases/database-name-field-update';
import { DatabaseUpdateMutationHandler } from './databases/database-update';
import { FieldCreateMutationHandler } from './databases/field-create';
@@ -35,14 +34,11 @@ import { ViewNameUpdateMutationHandler } from './databases/view-name-update';
import { ViewUpdateMutationHandler } from './databases/view-update';
import { DocumentUpdateMutationHandler } from './documents/document-update';
import { FileCreateMutationHandler } from './files/file-create';
import { FileDeleteMutationHandler } from './files/file-delete';
import { FileDownloadMutationHandler } from './files/file-download';
import { TempFileCreateMutationHandler } from './files/temp-file-create';
import { FolderCreateMutationHandler } from './folders/folder-create';
import { FolderDeleteMutationHandler } from './folders/folder-delete';
import { FolderUpdateMutationHandler } from './folders/folder-update';
import { MessageCreateMutationHandler } from './messages/message-create';
import { MessageDeleteMutationHandler } from './messages/message-delete';
import { NodeCollaboratorCreateMutationHandler } from './nodes/node-collaborator-create';
import { NodeCollaboratorDeleteMutationHandler } from './nodes/node-collaborator-delete';
import { NodeCollaboratorUpdateMutationHandler } from './nodes/node-collaborator-update';
@@ -52,11 +48,9 @@ import { NodeInteractionSeenMutationHandler } from './nodes/node-interaction-see
import { NodeReactionCreateMutationHandler } from './nodes/node-reaction-create';
import { NodeReactionDeleteMutationHandler } from './nodes/node-reaction-delete';
import { PageCreateMutationHandler } from './pages/page-create';
import { PageDeleteMutationHandler } from './pages/page-delete';
import { PageUpdateMutationHandler } from './pages/page-update';
import { RecordAvatarUpdateMutationHandler } from './records/record-avatar-update';
import { RecordCreateMutationHandler } from './records/record-create';
import { RecordDeleteMutationHandler } from './records/record-delete';
import { RecordFieldValueDeleteMutationHandler } from './records/record-field-value-delete';
import { RecordFieldValueSetMutationHandler } from './records/record-field-value-set';
import { RecordNameUpdateMutationHandler } from './records/record-name-update';
@@ -65,7 +59,6 @@ import { ServerDeleteMutationHandler } from './servers/server-delete';
import { ServerSyncMutationHandler } from './servers/server-sync';
import { SpaceChildReorderMutationHandler } from './spaces/space-child-reorder';
import { SpaceCreateMutationHandler } from './spaces/space-create';
import { SpaceDeleteMutationHandler } from './spaces/space-delete';
import { SpaceUpdateMutationHandler } from './spaces/space-update';
import { UserRoleUpdateMutationHandler } from './users/user-role-update';
import { UserStorageUpdateMutationHandler } from './users/user-storage-update';
@@ -91,7 +84,6 @@ export const buildMutationHandlerMap = (
'node.delete': new NodeDeleteMutationHandler(app),
'chat.create': new ChatCreateMutationHandler(app),
'database.create': new DatabaseCreateMutationHandler(app),
'database.delete': new DatabaseDeleteMutationHandler(app),
'database.name.field.update': new DatabaseNameFieldUpdateMutationHandler(
app
),
@@ -99,20 +91,15 @@ export const buildMutationHandlerMap = (
'field.delete': new FieldDeleteMutationHandler(app),
'field.name.update': new FieldNameUpdateMutationHandler(app),
'message.create': new MessageCreateMutationHandler(app),
'file.delete': new FileDeleteMutationHandler(app),
'folder.delete': new FolderDeleteMutationHandler(app),
'node.collaborator.create': new NodeCollaboratorCreateMutationHandler(app),
'node.collaborator.delete': new NodeCollaboratorDeleteMutationHandler(app),
'node.collaborator.update': new NodeCollaboratorUpdateMutationHandler(app),
'node.interaction.opened': new NodeInteractionOpenedMutationHandler(app),
'node.interaction.seen': new NodeInteractionSeenMutationHandler(app),
'page.create': new PageCreateMutationHandler(app),
'page.delete': new PageDeleteMutationHandler(app),
'node.reaction.create': new NodeReactionCreateMutationHandler(app),
'node.reaction.delete': new NodeReactionDeleteMutationHandler(app),
'message.delete': new MessageDeleteMutationHandler(app),
'record.create': new RecordCreateMutationHandler(app),
'record.delete': new RecordDeleteMutationHandler(app),
'record.avatar.update': new RecordAvatarUpdateMutationHandler(app),
'record.name.update': new RecordNameUpdateMutationHandler(app),
'record.field.value.delete': new RecordFieldValueDeleteMutationHandler(app),
@@ -124,7 +111,6 @@ export const buildMutationHandlerMap = (
'server.delete': new ServerDeleteMutationHandler(app),
'server.sync': new ServerSyncMutationHandler(app),
'space.create': new SpaceCreateMutationHandler(app),
'space.delete': new SpaceDeleteMutationHandler(app),
'user.role.update': new UserRoleUpdateMutationHandler(app),
'users.create': new UsersCreateMutationHandler(app),
'workspace.create': new WorkspaceCreateMutationHandler(app),

View File

@@ -1,22 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
MessageDeleteMutationInput,
MessageDeleteMutationOutput,
} from '@colanode/client/mutations';
export class MessageDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<MessageDeleteMutationInput>
{
async handleMutation(
input: MessageDeleteMutationInput
): Promise<MessageDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.messageId);
return {
success: true,
};
}
}

View File

@@ -1,22 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
PageDeleteMutationInput,
PageDeleteMutationOutput,
} from '@colanode/client/mutations/pages/page-delete';
export class PageDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<PageDeleteMutationInput>
{
async handleMutation(
input: PageDeleteMutationInput
): Promise<PageDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.pageId);
return {
success: true,
};
}
}

View File

@@ -1,22 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
RecordDeleteMutationInput,
RecordDeleteMutationOutput,
} from '@colanode/client/mutations/records/record-delete';
export class RecordDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<RecordDeleteMutationInput>
{
async handleMutation(
input: RecordDeleteMutationInput
): Promise<RecordDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.recordId);
return {
success: true,
};
}
}

View File

@@ -1,22 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
SpaceDeleteMutationInput,
SpaceDeleteMutationOutput,
} from '@colanode/client/mutations/spaces/space-delete';
export class SpaceDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<SpaceDeleteMutationInput>
{
async handleMutation(
input: SpaceDeleteMutationInput
): Promise<SpaceDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.spaceId);
return {
success: true,
};
}
}

View File

@@ -1,18 +0,0 @@
export type DatabaseDeleteMutationInput = {
type: 'database.delete';
userId: string;
databaseId: string;
};
export type DatabaseDeleteMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'database.delete': {
input: DatabaseDeleteMutationInput;
output: DatabaseDeleteMutationOutput;
};
}
}

View File

@@ -1,18 +0,0 @@
export type FileDeleteMutationInput = {
type: 'file.delete';
userId: string;
fileId: string;
};
export type FileDeleteMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'file.delete': {
input: FileDeleteMutationInput;
output: FileDeleteMutationOutput;
};
}
}

View File

@@ -1,18 +0,0 @@
export type FolderDeleteMutationInput = {
type: 'folder.delete';
userId: string;
folderId: string;
};
export type FolderDeleteMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'folder.delete': {
input: FolderDeleteMutationInput;
output: FolderDeleteMutationOutput;
};
}
}

View File

@@ -13,7 +13,6 @@ export * from './channels/channel-create';
export * from './channels/channel-update';
export * from './chats/chat-create';
export * from './databases/database-create';
export * from './databases/database-delete';
export * from './databases/database-update';
export * from './databases/field-create';
export * from './databases/field-delete';
@@ -28,13 +27,10 @@ export * from './databases/view-update';
export * from './databases/database-name-field-update';
export * from './documents/document-update';
export * from './files/file-create';
export * from './files/file-delete';
export * from './files/file-download';
export * from './folders/folder-create';
export * from './folders/folder-delete';
export * from './folders/folder-update';
export * from './messages/message-create';
export * from './messages/message-delete';
export * from './nodes/node-collaborator-create';
export * from './nodes/node-collaborator-delete';
export * from './nodes/node-collaborator-update';
@@ -43,18 +39,15 @@ export * from './nodes/node-interaction-seen';
export * from './nodes/node-reaction-create';
export * from './nodes/node-reaction-delete';
export * from './pages/page-create';
export * from './pages/page-delete';
export * from './pages/page-update';
export * from './records/record-avatar-update';
export * from './records/record-create';
export * from './records/record-delete';
export * from './records/record-field-value-delete';
export * from './records/record-field-value-set';
export * from './records/record-name-update';
export * from './servers/server-create';
export * from './servers/server-delete';
export * from './spaces/space-create';
export * from './spaces/space-delete';
export * from './spaces/space-update';
export * from './spaces/space-child-reorder';
export * from './workspaces/workspace-create';

View File

@@ -1,18 +0,0 @@
export type MessageDeleteMutationInput = {
type: 'message.delete';
userId: string;
messageId: string;
};
export type MessageDeleteMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'message.delete': {
input: MessageDeleteMutationInput;
output: MessageDeleteMutationOutput;
};
}
}

View File

@@ -1,18 +0,0 @@
export type PageDeleteMutationInput = {
type: 'page.delete';
userId: string;
pageId: string;
};
export type PageDeleteMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'page.delete': {
input: PageDeleteMutationInput;
output: PageDeleteMutationOutput;
};
}
}

View File

@@ -1,18 +0,0 @@
export type RecordDeleteMutationInput = {
type: 'record.delete';
userId: string;
recordId: string;
};
export type RecordDeleteMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'record.delete': {
input: RecordDeleteMutationInput;
output: RecordDeleteMutationOutput;
};
}
}

View File

@@ -1,18 +0,0 @@
export type SpaceDeleteMutationInput = {
type: 'space.delete';
userId: string;
spaceId: string;
};
export type SpaceDeleteMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'space.delete': {
input: SpaceDeleteMutationInput;
output: SpaceDeleteMutationOutput;
};
}
}

View File

@@ -28,15 +28,24 @@ export const createNodesCollection = (userId: string) => {
});
const subscriptionId = window.eventBus.subscribe((event) => {
if (event.type === 'node.created') {
if (
event.type === 'node.created' &&
event.workspace.userId === userId
) {
begin();
write({ type: 'insert', value: event.node });
commit();
} else if (event.type === 'node.updated') {
} else if (
event.type === 'node.updated' &&
event.workspace.userId === userId
) {
begin();
write({ type: 'update', value: event.node });
commit();
} else if (event.type === 'node.deleted') {
} else if (
event.type === 'node.deleted' &&
event.workspace.userId === userId
) {
begin();
write({ type: 'delete', value: event.node });
commit();

View File

@@ -1,59 +0,0 @@
import { useNavigate } from '@tanstack/react-router';
import { collections } from '@colanode/ui/collections';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
interface ChannelDeleteDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
channelId: string;
}
export const ChannelDeleteDialog = ({
channelId,
open,
onOpenChange,
}: ChannelDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/workspace/$userId' });
const handleDelete = () => {
collections.workspace(workspace.userId).nodes.delete(channelId);
navigate({
to: 'home',
replace: true,
});
};
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this channel?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This channel will no longer be
accessible by you or others you&apos;ve shared it with.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button variant="destructive" onClick={handleDelete}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -3,9 +3,9 @@ import { Fragment, useState } from 'react';
import { LocalChannelNode } from '@colanode/client/types';
import { NodeRole, hasNodeRole } from '@colanode/core';
import { ChannelDeleteDialog } from '@colanode/ui/components/channels/channel-delete-dialog';
import { ChannelUpdateDialog } from '@colanode/ui/components/channels/channel-update-dialog';
import { NodeCollaboratorAudit } from '@colanode/ui/components/collaborators/node-collaborator-audit';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@@ -107,8 +107,10 @@ export const ChannelSettings = ({ channel, role }: ChannelSettingsProps) => {
)}
</DropdownMenuContent>
</DropdownMenu>
<ChannelDeleteDialog
channelId={channel.id}
<NodeDeleteDialog
id={channel.id}
title="Are you sure you want delete this channel?"
description="This action cannot be undone. This channel will no longer be accessible by you or others you've shared it with."
open={showDeleteDialog}
onOpenChange={setShowDeleteModal}
/>

View File

@@ -1,77 +0,0 @@
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface DatabaseDeleteDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
databaseId: string;
}
export const DatabaseDeleteDialog = ({
databaseId,
open,
onOpenChange,
}: DatabaseDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/workspace/$userId' });
const { mutate, isPending } = useMutation();
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this database?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This database will no longer be
accessible by you or others you&apos;ve shared it with.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isPending}
onClick={() => {
mutate({
input: {
type: 'database.delete',
databaseId,
userId: workspace.userId,
},
onSuccess() {
onOpenChange(false);
navigate({
to: '/',
replace: true,
});
},
onError(error) {
toast.error(error.message);
},
});
}}
>
{isPending && <Spinner className="mr-1" />}
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -4,8 +4,8 @@ import { Fragment, useState } from 'react';
import { LocalDatabaseNode } from '@colanode/client/types';
import { NodeRole, hasNodeRole } from '@colanode/core';
import { NodeCollaboratorAudit } from '@colanode/ui/components/collaborators/node-collaborator-audit';
import { DatabaseDeleteDialog } from '@colanode/ui/components/databases/database-delete-dialog';
import { DatabaseUpdateDialog } from '@colanode/ui/components/databases/database-update-dialog';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@@ -107,8 +107,10 @@ export const DatabaseSettings = ({ database, role }: DatabaseSettingsProps) => {
)}
</DropdownMenuContent>
</DropdownMenu>
<DatabaseDeleteDialog
databaseId={database.id}
<NodeDeleteDialog
id={database.id}
title="Are you sure you want delete this database?"
description="This action cannot be undone. This database will no longer be accessible by you or others you've shared it with."
open={showDeleteDialog}
onOpenChange={setShowDeleteModal}
/>

View File

@@ -2,7 +2,7 @@ import { useNavigate } from '@tanstack/react-router';
import { Folder, Trash2 } from 'lucide-react';
import { Fragment, useState } from 'react';
import { FileDeleteDialog } from '@colanode/ui/components/files/file-delete-dialog';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import {
ContextMenu,
ContextMenuContent,
@@ -51,8 +51,10 @@ export const FileContextMenu = ({ id, children }: FileContextMenuProps) => {
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<FileDeleteDialog
fileId={id}
<NodeDeleteDialog
id={id}
title="Are you sure you want delete this file?"
description="This action cannot be undone. This file will no longer be accessible and all data in the file will be lost."
open={openDelete}
onOpenChange={setOpenDelete}
/>

View File

@@ -1,76 +0,0 @@
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface FileDeleteDialogProps {
fileId: string;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const FileDeleteDialog = ({
fileId,
open,
onOpenChange,
}: FileDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/workspace/$userId' });
const { mutate, isPending } = useMutation();
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this file?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This file will no longer be accessible
and all data in the file will be lost.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isPending}
onClick={() => {
mutate({
input: {
type: 'file.delete',
fileId: fileId,
userId: workspace.userId,
},
onSuccess() {
onOpenChange(false);
navigate({
to: '/',
});
},
onError(error) {
toast.error(error.message);
},
});
}}
>
{isPending && <Spinner className="mr-1" />}
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -3,7 +3,7 @@ import { Fragment, useState } from 'react';
import { LocalFileNode } from '@colanode/client/types';
import { NodeRole, hasNodeRole } from '@colanode/core';
import { FileDeleteDialog } from '@colanode/ui/components/files/file-delete-dialog';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@@ -52,8 +52,10 @@ export const FileSettings = ({ file, role }: FileSettingsProps) => {
</DropdownMenuContent>
</DropdownMenu>
{canDelete && (
<FileDeleteDialog
fileId={file.id}
<NodeDeleteDialog
id={file.id}
title="Are you sure you want delete this file?"
description="This action cannot be undone. This file will no longer be accessible and all data in the file will be lost."
open={showDeleteModal}
onOpenChange={setShowDeleteModal}
/>

View File

@@ -1,76 +0,0 @@
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface FolderDeleteDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
folderId: string;
}
export const FolderDeleteDialog = ({
folderId,
open,
onOpenChange,
}: FolderDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/workspace/$userId' });
const { mutate, isPending } = useMutation();
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this folder?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This folder will no longer be
accessible by you or others you&apos;ve shared it with.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isPending}
onClick={() => {
mutate({
input: {
type: 'folder.delete',
folderId: folderId,
userId: workspace.userId,
},
onSuccess() {
onOpenChange(false);
navigate({
to: '/',
});
},
onError(error) {
toast.error(error.message);
},
});
}}
>
{isPending && <Spinner className="mr-1" />}
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -4,8 +4,8 @@ import { Fragment, useState } from 'react';
import { LocalFolderNode } from '@colanode/client/types';
import { NodeRole, hasNodeRole } from '@colanode/core';
import { NodeCollaboratorAudit } from '@colanode/ui/components/collaborators/node-collaborator-audit';
import { FolderDeleteDialog } from '@colanode/ui/components/folders/folder-delete-dialog';
import { FolderUpdateDialog } from '@colanode/ui/components/folders/folder-update-dialog';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@@ -104,8 +104,10 @@ export const FolderSettings = ({ folder, role }: FolderSettingsProps) => {
)}
</DropdownMenuContent>
</DropdownMenu>
<FolderDeleteDialog
folderId={folder.id}
<NodeDeleteDialog
id={folder.id}
title="Are you sure you want delete this folder?"
description="This action cannot be undone. This folder will no longer be accessible by you or others you've shared it with."
open={showDeleteDialog}
onOpenChange={setShowDeleteModal}
/>

View File

@@ -1,71 +0,0 @@
import { toast } from 'sonner';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface MessageDeleteDialogProps {
id: string;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const MessageDeleteDialog = ({
id,
open,
onOpenChange,
}: MessageDeleteDialogProps) => {
const workspace = useWorkspace();
const { mutate, isPending } = useMutation();
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this message?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This message will no longer be
accessible by you or others you&apos;ve shared it with.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isPending}
onClick={() => {
mutate({
input: {
type: 'message.delete',
messageId: id,
userId: workspace.userId,
},
onSuccess() {
onOpenChange(false);
},
onError(error) {
toast.error(error.message);
},
});
}}
>
{isPending && <Spinner className="mr-1" />}
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -31,7 +31,7 @@ export const MessageList = () => {
[workspace.userId, conversation.id]
);
const messages = messageListQuery.data.sort((a, b) =>
const messages = messageListQuery.data.toSorted((a, b) =>
compareString(a.id, b.id)
);

View File

@@ -6,11 +6,11 @@ import { MessageActions } from '@colanode/ui/components/messages/message-actions
import { MessageAuthorAvatar } from '@colanode/ui/components/messages/message-author-avatar';
import { MessageAuthorName } from '@colanode/ui/components/messages/message-author-name';
import { MessageContent } from '@colanode/ui/components/messages/message-content';
import { MessageDeleteDialog } from '@colanode/ui/components/messages/message-delete-dialog';
import { MessageMenuMobile } from '@colanode/ui/components/messages/message-menu-mobile';
import { MessageReactionCounts } from '@colanode/ui/components/messages/message-reaction-counts';
import { MessageReference } from '@colanode/ui/components/messages/message-reference';
import { MessageTime } from '@colanode/ui/components/messages/message-time';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import { useConversation } from '@colanode/ui/contexts/conversation';
import { MessageContext } from '@colanode/ui/contexts/message';
import { useRadar } from '@colanode/ui/contexts/radar';
@@ -133,8 +133,10 @@ export const Message = ({ message, previousMessage }: MessageProps) => {
/>
)}
{openDeleteDialog && (
<MessageDeleteDialog
<NodeDeleteDialog
id={message.id}
title="Are you sure you want delete this message?"
description="This action cannot be undone. This message will no longer be accessible by you or others you've shared it with."
open={openDeleteDialog}
onOpenChange={setOpenDeleteDialog}
/>

View File

@@ -0,0 +1,86 @@
import { useRouter } from '@tanstack/react-router';
import { collections } from '@colanode/ui/collections';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
interface NodeDeleteDialogProps {
id: string;
title: string;
description: string;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const NodeDeleteDialog = ({
id,
title,
description,
open,
onOpenChange,
}: NodeDeleteDialogProps) => {
const workspace = useWorkspace();
const router = useRouter();
// if the current node is opened in a modal we just navigate to the node route
// if the current node is opened in a full screen view we just navigate to the home route
const handleDelete = () => {
collections.workspace(workspace.userId).nodes.delete(id);
const matches = router.state.matches.toReversed();
for (const match of matches) {
if (
match.routeId === '/workspace/$userId/$nodeId/modal/$modalNodeId' &&
match.params.modalNodeId === id
) {
router.navigate({
to: '/workspace/$userId/$nodeId',
params: {
userId: workspace.userId,
nodeId: match.params.nodeId,
},
});
}
if (
match.routeId === '/workspace/$userId/$nodeId' &&
match.params.nodeId === id
) {
router.navigate({
to: '/workspace/$userId/home',
params: {
userId: workspace.userId,
},
});
}
}
onOpenChange(false);
};
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button variant="destructive" onClick={handleDelete}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -1,76 +0,0 @@
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface PageDeleteDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
pageId: string;
}
export const PageDeleteDialog = ({
pageId,
open,
onOpenChange,
}: PageDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/workspace/$userId' });
const { mutate, isPending } = useMutation();
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this page?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This page will no longer be accessible
by you or others you&apos;ve shared it with.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isPending}
onClick={() => {
mutate({
input: {
type: 'page.delete',
pageId,
userId: workspace.userId,
},
onSuccess() {
onOpenChange(false);
navigate({
to: '/',
});
},
onError(error) {
toast.error(error.message);
},
});
}}
>
{isPending && <Spinner className="mr-1" />}
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -4,7 +4,7 @@ import { Fragment, useState } from 'react';
import { LocalPageNode } from '@colanode/client/types';
import { NodeRole, hasNodeRole } from '@colanode/core';
import { NodeCollaboratorAudit } from '@colanode/ui/components/collaborators/node-collaborator-audit';
import { PageDeleteDialog } from '@colanode/ui/components/pages/page-delete-dialog';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import { PageUpdateDialog } from '@colanode/ui/components/pages/page-update-dialog';
import {
DropdownMenu,
@@ -107,8 +107,10 @@ export const PageSettings = ({ page, role }: PageSettingsProps) => {
)}
</DropdownMenuContent>
</DropdownMenu>
<PageDeleteDialog
pageId={page.id}
<NodeDeleteDialog
id={page.id}
title="Are you sure you want delete this page?"
description="This action cannot be undone. This page will no longer be accessible by you or others you've shared it with."
open={showDeleteDialog}
onOpenChange={setShowDeleteModal}
/>

View File

@@ -1,76 +0,0 @@
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface RecordDeleteDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
recordId: string;
}
export const RecordDeleteDialog = ({
recordId,
open,
onOpenChange,
}: RecordDeleteDialogProps) => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/workspace/$userId' });
const { mutate, isPending } = useMutation();
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this record?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This record will no longer be
accessible by you or others you&apos;ve shared it with.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isPending}
onClick={() => {
mutate({
input: {
type: 'record.delete',
recordId: recordId,
userId: workspace.userId,
},
onSuccess() {
onOpenChange(false);
navigate({
to: '/',
});
},
onError(error) {
toast.error(error.message);
},
});
}}
>
{isPending && <Spinner className="mr-1" />}
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -4,7 +4,7 @@ import { Fragment, useState } from 'react';
import { LocalRecordNode } from '@colanode/client/types';
import { NodeRole, hasNodeRole } from '@colanode/core';
import { NodeCollaboratorAudit } from '@colanode/ui/components/collaborators/node-collaborator-audit';
import { RecordDeleteDialog } from '@colanode/ui/components/records/record-delete-dialog';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
@@ -75,8 +75,10 @@ export const RecordSettings = ({ record, role }: RecordSettingsProps) => {
)}
</DropdownMenuContent>
</DropdownMenu>
<RecordDeleteDialog
recordId={record.id}
<NodeDeleteDialog
id={record.id}
title="Are you sure you want delete this record?"
description="This action cannot be undone. This record will no longer be accessible by you or others you've shared it with."
open={showDeleteDialog}
onOpenChange={setShowDeleteModal}
/>

View File

@@ -1,4 +1,3 @@
import { useNavigate } from '@tanstack/react-router';
import { toast } from 'sonner';
import { LocalSpaceNode } from '@colanode/client/types';
@@ -17,7 +16,6 @@ interface SpaceContainerProps {
export const SpaceContainer = ({ space, role }: SpaceContainerProps) => {
const workspace = useWorkspace();
const navigate = useNavigate({ from: '/workspace/$userId' });
const { mutate, isPending } = useMutation();
const canEdit = hasNodeRole(role, 'admin');
@@ -78,14 +76,7 @@ export const SpaceContainer = ({ space, role }: SpaceContainerProps) => {
</h2>
<Separator className="mt-3" />
</div>
<SpaceDelete
id={space.id}
onDeleted={() => {
navigate({
to: '/',
});
}}
/>
<SpaceDelete spaceId={space.id} />
</div>
)}
</div>

View File

@@ -1,29 +1,13 @@
import { useState } from 'react';
import { toast } from 'sonner';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@colanode/ui/components/ui/alert-dialog';
import { NodeDeleteDialog } from '@colanode/ui/components/nodes/node-delete-dialog';
import { Button } from '@colanode/ui/components/ui/button';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface SpaceDeleteProps {
id: string;
onDeleted: () => void;
spaceId: string;
}
export const SpaceDelete = ({ id, onDeleted }: SpaceDeleteProps) => {
const workspace = useWorkspace();
const { mutate, isPending } = useMutation();
export const SpaceDelete = ({ spaceId }: SpaceDeleteProps) => {
const [showDeleteModal, setShowDeleteModal] = useState(false);
return (
@@ -47,46 +31,13 @@ export const SpaceDelete = ({ id, onDeleted }: SpaceDeleteProps) => {
</Button>
</div>
</div>
<AlertDialog open={showDeleteModal} onOpenChange={setShowDeleteModal}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this space?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This space will no longer be
accessible by you or others you&apos;ve shared it with.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isPending}
onClick={() => {
mutate({
input: {
type: 'space.delete',
userId: workspace.userId,
spaceId: id,
},
onSuccess() {
setShowDeleteModal(false);
onDeleted();
toast.success('Space deleted');
},
onError(error) {
toast.error(error.message);
},
});
}}
>
{isPending && <Spinner className="mr-1" />}
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<NodeDeleteDialog
id={spaceId}
title="Are you sure you want delete this space?"
description="This action cannot be undone. This space will no longer be accessible by you or others you've shared it with."
open={showDeleteModal}
onOpenChange={setShowDeleteModal}
/>
</>
);
};