mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 11:47:47 +01:00
Use tanstackdb for channel creation
This commit is contained in:
@@ -1,50 +0,0 @@
|
|||||||
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
|
||||||
import { MutationHandler } from '@colanode/client/lib/types';
|
|
||||||
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
|
|
||||||
import {
|
|
||||||
ChannelCreateMutationInput,
|
|
||||||
ChannelCreateMutationOutput,
|
|
||||||
} from '@colanode/client/mutations/channels/channel-create';
|
|
||||||
import { ChannelAttributes, generateId, IdType } from '@colanode/core';
|
|
||||||
|
|
||||||
export class ChannelCreateMutationHandler
|
|
||||||
extends WorkspaceMutationHandlerBase
|
|
||||||
implements MutationHandler<ChannelCreateMutationInput>
|
|
||||||
{
|
|
||||||
async handleMutation(
|
|
||||||
input: ChannelCreateMutationInput
|
|
||||||
): Promise<ChannelCreateMutationOutput> {
|
|
||||||
const workspace = this.getWorkspace(input.userId);
|
|
||||||
|
|
||||||
const space = await workspace.database
|
|
||||||
.selectFrom('nodes')
|
|
||||||
.selectAll()
|
|
||||||
.where('id', '=', input.spaceId)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
if (!space) {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.SpaceNotFound,
|
|
||||||
'Space not found or has been deleted.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = generateId(IdType.Channel);
|
|
||||||
const attributes: ChannelAttributes = {
|
|
||||||
type: 'channel',
|
|
||||||
name: input.name,
|
|
||||||
avatar: input.avatar,
|
|
||||||
parentId: input.spaceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
await workspace.nodes.createNode({
|
|
||||||
id,
|
|
||||||
attributes,
|
|
||||||
parentId: input.spaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ import { EmailRegisterMutationHandler } from './auth/email-register';
|
|||||||
import { EmailVerifyMutationHandler } from './auth/email-verify';
|
import { EmailVerifyMutationHandler } from './auth/email-verify';
|
||||||
import { GoogleLoginMutationHandler } from './auth/google-login';
|
import { GoogleLoginMutationHandler } from './auth/google-login';
|
||||||
import { AvatarUploadMutationHandler } from './avatars/avatar-upload';
|
import { AvatarUploadMutationHandler } from './avatars/avatar-upload';
|
||||||
import { ChannelCreateMutationHandler } from './channels/channel-create';
|
|
||||||
import { ChannelUpdateMutationHandler } from './channels/channel-update';
|
import { ChannelUpdateMutationHandler } from './channels/channel-update';
|
||||||
import { ChatCreateMutationHandler } from './chats/chat-create';
|
import { ChatCreateMutationHandler } from './chats/chat-create';
|
||||||
import { DatabaseCreateMutationHandler } from './databases/database-create';
|
import { DatabaseCreateMutationHandler } from './databases/database-create';
|
||||||
@@ -42,6 +41,7 @@ import { MessageCreateMutationHandler } from './messages/message-create';
|
|||||||
import { NodeCollaboratorCreateMutationHandler } from './nodes/node-collaborator-create';
|
import { NodeCollaboratorCreateMutationHandler } from './nodes/node-collaborator-create';
|
||||||
import { NodeCollaboratorDeleteMutationHandler } from './nodes/node-collaborator-delete';
|
import { NodeCollaboratorDeleteMutationHandler } from './nodes/node-collaborator-delete';
|
||||||
import { NodeCollaboratorUpdateMutationHandler } from './nodes/node-collaborator-update';
|
import { NodeCollaboratorUpdateMutationHandler } from './nodes/node-collaborator-update';
|
||||||
|
import { NodeCreateMutationHandler } from './nodes/node-create';
|
||||||
import { NodeDeleteMutationHandler } from './nodes/node-delete';
|
import { NodeDeleteMutationHandler } from './nodes/node-delete';
|
||||||
import { NodeInteractionOpenedMutationHandler } from './nodes/node-interaction-opened';
|
import { NodeInteractionOpenedMutationHandler } from './nodes/node-interaction-opened';
|
||||||
import { NodeInteractionSeenMutationHandler } from './nodes/node-interaction-seen';
|
import { NodeInteractionSeenMutationHandler } from './nodes/node-interaction-seen';
|
||||||
@@ -80,8 +80,8 @@ export const buildMutationHandlerMap = (
|
|||||||
'email.verify': new EmailVerifyMutationHandler(app),
|
'email.verify': new EmailVerifyMutationHandler(app),
|
||||||
'google.login': new GoogleLoginMutationHandler(app),
|
'google.login': new GoogleLoginMutationHandler(app),
|
||||||
'view.create': new ViewCreateMutationHandler(app),
|
'view.create': new ViewCreateMutationHandler(app),
|
||||||
'channel.create': new ChannelCreateMutationHandler(app),
|
|
||||||
'node.delete': new NodeDeleteMutationHandler(app),
|
'node.delete': new NodeDeleteMutationHandler(app),
|
||||||
|
'node.create': new NodeCreateMutationHandler(app),
|
||||||
'chat.create': new ChatCreateMutationHandler(app),
|
'chat.create': new ChatCreateMutationHandler(app),
|
||||||
'database.create': new DatabaseCreateMutationHandler(app),
|
'database.create': new DatabaseCreateMutationHandler(app),
|
||||||
'database.name.field.update': new DatabaseNameFieldUpdateMutationHandler(
|
'database.name.field.update': new DatabaseNameFieldUpdateMutationHandler(
|
||||||
|
|||||||
22
packages/client/src/handlers/mutations/nodes/node-create.ts
Normal file
22
packages/client/src/handlers/mutations/nodes/node-create.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
|
||||||
|
import { MutationHandler } from '@colanode/client/lib/types';
|
||||||
|
import {
|
||||||
|
NodeCreateMutationInput,
|
||||||
|
NodeCreateMutationOutput,
|
||||||
|
} from '@colanode/client/mutations/nodes/node-create';
|
||||||
|
|
||||||
|
export class NodeCreateMutationHandler
|
||||||
|
extends WorkspaceMutationHandlerBase
|
||||||
|
implements MutationHandler<NodeCreateMutationInput>
|
||||||
|
{
|
||||||
|
async handleMutation(
|
||||||
|
input: NodeCreateMutationInput
|
||||||
|
): Promise<NodeCreateMutationOutput> {
|
||||||
|
const workspace = this.getWorkspace(input.userId);
|
||||||
|
await workspace.nodes.insertNode(input.node);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
export type ChannelCreateMutationInput = {
|
|
||||||
type: 'channel.create';
|
|
||||||
userId: string;
|
|
||||||
spaceId: string;
|
|
||||||
name: string;
|
|
||||||
avatar?: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChannelCreateMutationOutput = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare module '@colanode/client/mutations' {
|
|
||||||
interface MutationMap {
|
|
||||||
'channel.create': {
|
|
||||||
input: ChannelCreateMutationInput;
|
|
||||||
output: ChannelCreateMutationOutput;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ export * from './auth/google-login';
|
|||||||
export * from './apps/metadata-delete';
|
export * from './apps/metadata-delete';
|
||||||
export * from './apps/metadata-update';
|
export * from './apps/metadata-update';
|
||||||
export * from './avatars/avatar-upload';
|
export * from './avatars/avatar-upload';
|
||||||
export * from './channels/channel-create';
|
|
||||||
export * from './channels/channel-update';
|
export * from './channels/channel-update';
|
||||||
export * from './chats/chat-create';
|
export * from './chats/chat-create';
|
||||||
export * from './databases/database-create';
|
export * from './databases/database-create';
|
||||||
@@ -62,6 +61,7 @@ export * from './apps/tab-update';
|
|||||||
export * from './servers/server-sync';
|
export * from './servers/server-sync';
|
||||||
export * from './apps/tab-delete';
|
export * from './apps/tab-delete';
|
||||||
export * from './nodes/node-delete';
|
export * from './nodes/node-delete';
|
||||||
|
export * from './nodes/node-create';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
export interface MutationMap {}
|
export interface MutationMap {}
|
||||||
|
|||||||
20
packages/client/src/mutations/nodes/node-create.ts
Normal file
20
packages/client/src/mutations/nodes/node-create.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { LocalNode } from '@colanode/client/types';
|
||||||
|
|
||||||
|
export type NodeCreateMutationInput = {
|
||||||
|
type: 'node.create';
|
||||||
|
userId: string;
|
||||||
|
node: LocalNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NodeCreateMutationOutput = {
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module '@colanode/client/mutations' {
|
||||||
|
interface MutationMap {
|
||||||
|
'node.create': {
|
||||||
|
input: NodeCreateMutationInput;
|
||||||
|
output: NodeCreateMutationOutput;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from '@colanode/client/lib/mentions';
|
} from '@colanode/client/lib/mentions';
|
||||||
import { deleteNodeRelations, fetchNodeTree } from '@colanode/client/lib/utils';
|
import { deleteNodeRelations, fetchNodeTree } from '@colanode/client/lib/utils';
|
||||||
import { WorkspaceService } from '@colanode/client/services/workspaces/workspace-service';
|
import { WorkspaceService } from '@colanode/client/services/workspaces/workspace-service';
|
||||||
import { DownloadStatus } from '@colanode/client/types';
|
import { DownloadStatus, LocalNode } from '@colanode/client/types';
|
||||||
import {
|
import {
|
||||||
generateId,
|
generateId,
|
||||||
IdType,
|
IdType,
|
||||||
@@ -225,6 +225,173 @@ export class NodeService {
|
|||||||
return createdNode;
|
return createdNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async insertNode(input: LocalNode): Promise<LocalNode> {
|
||||||
|
debug(`Inserting node ${input.id} with type ${input.attributes.type}`);
|
||||||
|
|
||||||
|
const tree = input.parentId
|
||||||
|
? await fetchNodeTree(this.workspace.database, input.parentId)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const model = getNodeModel(input.attributes.type);
|
||||||
|
const canCreateNodeContext: CanCreateNodeContext = {
|
||||||
|
user: {
|
||||||
|
id: this.workspace.userId,
|
||||||
|
role: this.workspace.role,
|
||||||
|
workspaceId: this.workspace.workspaceId,
|
||||||
|
accountId: this.workspace.accountId,
|
||||||
|
},
|
||||||
|
tree: tree,
|
||||||
|
attributes: input.attributes,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!model.canCreate(canCreateNodeContext)) {
|
||||||
|
throw new Error('Insufficient permissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ydoc = new YDoc();
|
||||||
|
const update = ydoc.update(model.attributesSchema, input.attributes);
|
||||||
|
|
||||||
|
if (!update) {
|
||||||
|
throw new Error('Invalid attributes');
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateId = generateId(IdType.Update);
|
||||||
|
const createdAt = new Date().toISOString();
|
||||||
|
const rootId = tree[0]?.id ?? input.id;
|
||||||
|
const nodeText = model.extractText(input.id, input.attributes);
|
||||||
|
const mentions = model.extractMentions(input.id, input.attributes);
|
||||||
|
const nodeReferencesToCreate: CreateNodeReference[] = mentions.map(
|
||||||
|
(mention) => ({
|
||||||
|
node_id: input.id,
|
||||||
|
reference_id: mention.target,
|
||||||
|
inner_id: mention.id,
|
||||||
|
type: 'mention',
|
||||||
|
created_at: createdAt,
|
||||||
|
created_by: this.workspace.userId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { createdNode, createdMutation, createdNodeReferences } =
|
||||||
|
await this.workspace.database.transaction().execute(async (trx) => {
|
||||||
|
const createdNode = await trx
|
||||||
|
.insertInto('nodes')
|
||||||
|
.returningAll()
|
||||||
|
.values({
|
||||||
|
id: input.id,
|
||||||
|
root_id: rootId,
|
||||||
|
attributes: JSON.stringify(input.attributes),
|
||||||
|
created_at: createdAt,
|
||||||
|
created_by: this.workspace.userId,
|
||||||
|
local_revision: '0',
|
||||||
|
server_revision: '0',
|
||||||
|
})
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
if (!createdNode) {
|
||||||
|
throw new Error('Failed to create node');
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdNodeUpdate = await trx
|
||||||
|
.insertInto('node_updates')
|
||||||
|
.returningAll()
|
||||||
|
.values({
|
||||||
|
id: updateId,
|
||||||
|
node_id: input.id,
|
||||||
|
data: update,
|
||||||
|
created_at: createdAt,
|
||||||
|
})
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
if (!createdNodeUpdate) {
|
||||||
|
throw new Error('Failed to create node update');
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutationData: CreateNodeMutationData = {
|
||||||
|
nodeId: input.id,
|
||||||
|
updateId: updateId,
|
||||||
|
data: encodeState(update),
|
||||||
|
createdAt: createdAt,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createdMutation = await trx
|
||||||
|
.insertInto('mutations')
|
||||||
|
.returningAll()
|
||||||
|
.values({
|
||||||
|
id: generateId(IdType.Mutation),
|
||||||
|
type: 'node.create',
|
||||||
|
data: JSON.stringify(mutationData),
|
||||||
|
created_at: createdAt,
|
||||||
|
retries: 0,
|
||||||
|
})
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
if (!createdMutation) {
|
||||||
|
throw new Error('Failed to create mutation');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeText) {
|
||||||
|
await trx
|
||||||
|
.insertInto('node_texts')
|
||||||
|
.values({
|
||||||
|
id: input.id,
|
||||||
|
name: nodeText.name,
|
||||||
|
attributes: nodeText.attributes,
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
let createdNodeReferences: SelectNodeReference[] = [];
|
||||||
|
if (nodeReferencesToCreate.length > 0) {
|
||||||
|
createdNodeReferences = await trx
|
||||||
|
.insertInto('node_references')
|
||||||
|
.values(nodeReferencesToCreate)
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createdNode,
|
||||||
|
createdMutation,
|
||||||
|
createdNodeReferences,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!createdNode) {
|
||||||
|
throw new Error('Failed to create node');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!createdMutation) {
|
||||||
|
throw new Error('Failed to create mutation');
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(`Created node ${createdNode.id} with type ${createdNode.type}`);
|
||||||
|
|
||||||
|
eventBus.publish({
|
||||||
|
type: 'node.created',
|
||||||
|
workspace: {
|
||||||
|
workspaceId: this.workspace.workspaceId,
|
||||||
|
userId: this.workspace.userId,
|
||||||
|
accountId: this.workspace.accountId,
|
||||||
|
},
|
||||||
|
node: mapNode(createdNode),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const createdNodeReference of createdNodeReferences) {
|
||||||
|
eventBus.publish({
|
||||||
|
type: 'node.reference.created',
|
||||||
|
workspace: {
|
||||||
|
workspaceId: this.workspace.workspaceId,
|
||||||
|
userId: this.workspace.userId,
|
||||||
|
accountId: this.workspace.accountId,
|
||||||
|
},
|
||||||
|
nodeReference: mapNodeReference(createdNodeReference),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.workspace.mutations.scheduleSync();
|
||||||
|
return mapNode(createdNode);
|
||||||
|
}
|
||||||
|
|
||||||
public async updateNode<T extends NodeAttributes>(
|
public async updateNode<T extends NodeAttributes>(
|
||||||
nodeId: string,
|
nodeId: string,
|
||||||
updater: (attributes: T) => T
|
updater: (attributes: T) => T
|
||||||
|
|||||||
@@ -60,15 +60,15 @@ export const createNodesCollection = (userId: string) => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// onInsert: async ({ transaction }) => {
|
onInsert: async ({ transaction }) => {
|
||||||
// for (const mutation of transaction.mutations) {
|
for (const mutation of transaction.mutations) {
|
||||||
// await window.colanode.executeMutation({
|
await window.colanode.executeMutation({
|
||||||
// type: 'node.create',
|
type: 'node.create',
|
||||||
// userId,
|
userId,
|
||||||
// node: mutation.modified,
|
node: mutation.modified,
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// onUpdate: async ({ transaction }) => {
|
// onUpdate: async ({ transaction }) => {
|
||||||
// for (const mutation of transaction.mutations) {
|
// for (const mutation of transaction.mutations) {
|
||||||
// const attributes = cloneDeep(mutation.modified.attributes);
|
// const attributes = cloneDeep(mutation.modified.attributes);
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useNavigate } from '@tanstack/react-router';
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { LocalChannelNode } from '@colanode/client/types';
|
||||||
import { generateId, IdType } from '@colanode/core';
|
import { generateId, IdType } from '@colanode/core';
|
||||||
import { ChannelForm } from '@colanode/ui/components/channels/channel-form';
|
import { collections } from '@colanode/ui/collections';
|
||||||
|
import {
|
||||||
|
ChannelForm,
|
||||||
|
ChannelFormValues,
|
||||||
|
} from '@colanode/ui/components/channels/channel-form';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -11,7 +17,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@colanode/ui/components/ui/dialog';
|
} from '@colanode/ui/components/ui/dialog';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
|
||||||
|
|
||||||
interface ChannelCreateDialogProps {
|
interface ChannelCreateDialogProps {
|
||||||
spaceId: string;
|
spaceId: string;
|
||||||
@@ -26,7 +31,46 @@ export const ChannelCreateDialog = ({
|
|||||||
}: ChannelCreateDialogProps) => {
|
}: ChannelCreateDialogProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const navigate = useNavigate({ from: '/workspace/$userId' });
|
const navigate = useNavigate({ from: '/workspace/$userId' });
|
||||||
const { mutate, isPending } = useMutation();
|
|
||||||
|
const { mutate } = useMutation({
|
||||||
|
mutationFn: async (values: ChannelFormValues) => {
|
||||||
|
const channelId = generateId(IdType.Channel);
|
||||||
|
const nodes = collections.workspace(workspace.userId).nodes;
|
||||||
|
|
||||||
|
const channel: LocalChannelNode = {
|
||||||
|
id: channelId,
|
||||||
|
type: 'channel',
|
||||||
|
attributes: {
|
||||||
|
type: 'channel',
|
||||||
|
name: values.name,
|
||||||
|
parentId: spaceId,
|
||||||
|
},
|
||||||
|
parentId: spaceId,
|
||||||
|
rootId: spaceId,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
createdBy: workspace.userId,
|
||||||
|
updatedAt: null,
|
||||||
|
updatedBy: null,
|
||||||
|
localRevision: '0',
|
||||||
|
serverRevision: '0',
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.insert(channel);
|
||||||
|
return channel;
|
||||||
|
},
|
||||||
|
onSuccess: (channel) => {
|
||||||
|
navigate({
|
||||||
|
to: '$nodeId',
|
||||||
|
params: {
|
||||||
|
nodeId: channel.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
onOpenChange(false);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
@@ -42,38 +86,11 @@ export const ChannelCreateDialog = ({
|
|||||||
values={{
|
values={{
|
||||||
name: '',
|
name: '',
|
||||||
}}
|
}}
|
||||||
isPending={isPending}
|
|
||||||
submitText="Create"
|
submitText="Create"
|
||||||
handleCancel={() => {
|
onCancel={() => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
}}
|
}}
|
||||||
handleSubmit={(values) => {
|
onSubmit={(values) => mutate(values)}
|
||||||
if (isPending) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate({
|
|
||||||
input: {
|
|
||||||
type: 'channel.create',
|
|
||||||
spaceId: spaceId,
|
|
||||||
name: values.name,
|
|
||||||
avatar: values.avatar,
|
|
||||||
userId: workspace.userId,
|
|
||||||
},
|
|
||||||
onSuccess(output) {
|
|
||||||
onOpenChange(false);
|
|
||||||
navigate({
|
|
||||||
to: '$nodeId',
|
|
||||||
params: {
|
|
||||||
nodeId: output.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -14,30 +14,29 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@colanode/ui/components/ui/form';
|
} from '@colanode/ui/components/ui/form';
|
||||||
import { Input } from '@colanode/ui/components/ui/input';
|
import { Input } from '@colanode/ui/components/ui/input';
|
||||||
import { Spinner } from '@colanode/ui/components/ui/spinner';
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(3, 'Name must be at least 3 characters long.'),
|
name: z.string().min(3, 'Name must be at least 3 characters long.'),
|
||||||
avatar: z.string().optional().nullable(),
|
avatar: z.string().optional().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type ChannelFormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
interface ChannelFormProps {
|
interface ChannelFormProps {
|
||||||
id: string;
|
id: string;
|
||||||
values: z.infer<typeof formSchema>;
|
values: z.infer<typeof formSchema>;
|
||||||
isPending: boolean;
|
|
||||||
submitText: string;
|
submitText: string;
|
||||||
handleCancel: () => void;
|
onCancel: () => void;
|
||||||
handleSubmit: (values: z.infer<typeof formSchema>) => void;
|
onSubmit: (values: z.infer<typeof formSchema>) => void;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChannelForm = ({
|
export const ChannelForm = ({
|
||||||
id,
|
id,
|
||||||
values,
|
values,
|
||||||
isPending,
|
|
||||||
submitText,
|
submitText,
|
||||||
handleCancel,
|
onCancel,
|
||||||
handleSubmit,
|
onSubmit,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
}: ChannelFormProps) => {
|
}: ChannelFormProps) => {
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
@@ -60,10 +59,7 @@ export const ChannelForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form className="flex flex-col" onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
className="flex flex-col"
|
|
||||||
onSubmit={form.handleSubmit(handleSubmit)}
|
|
||||||
>
|
|
||||||
<div className="grow flex flex-row items-end gap-2 py-2 pb-4">
|
<div className="grow flex flex-row items-end gap-2 py-2 pb-4">
|
||||||
{readOnly ? (
|
{readOnly ? (
|
||||||
<Button type="button" variant="outline" size="icon">
|
<Button type="button" variant="outline" size="icon">
|
||||||
@@ -72,7 +68,6 @@ export const ChannelForm = ({
|
|||||||
) : (
|
) : (
|
||||||
<AvatarPopover
|
<AvatarPopover
|
||||||
onPick={(avatar) => {
|
onPick={(avatar) => {
|
||||||
if (isPending) return;
|
|
||||||
if (avatar === values.avatar) return;
|
if (avatar === values.avatar) return;
|
||||||
|
|
||||||
form.setValue('avatar', avatar);
|
form.setValue('avatar', avatar);
|
||||||
@@ -102,16 +97,10 @@ export const ChannelForm = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<Button
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
disabled={isPending}
|
|
||||||
onClick={handleCancel}
|
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" disabled={isPending || readOnly}>
|
<Button type="submit" disabled={readOnly}>
|
||||||
{isPending && <Spinner className="mr-1" />}
|
|
||||||
{submitText}
|
{submitText}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,13 +45,12 @@ export const ChannelUpdateDialog = ({
|
|||||||
name: channel.attributes.name,
|
name: channel.attributes.name,
|
||||||
avatar: channel.attributes.avatar,
|
avatar: channel.attributes.avatar,
|
||||||
}}
|
}}
|
||||||
isPending={isPending}
|
|
||||||
submitText="Update"
|
submitText="Update"
|
||||||
readOnly={!canEdit}
|
readOnly={!canEdit}
|
||||||
handleCancel={() => {
|
onCancel={() => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
}}
|
}}
|
||||||
handleSubmit={(values) => {
|
onSubmit={(values) => {
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user