mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 19:57:46 +01:00
Use tanstackdb for space creation
This commit is contained in:
@@ -57,7 +57,6 @@ import { ServerCreateMutationHandler } from './servers/server-create';
|
|||||||
import { ServerDeleteMutationHandler } from './servers/server-delete';
|
import { ServerDeleteMutationHandler } from './servers/server-delete';
|
||||||
import { ServerSyncMutationHandler } from './servers/server-sync';
|
import { ServerSyncMutationHandler } from './servers/server-sync';
|
||||||
import { SpaceChildReorderMutationHandler } from './spaces/space-child-reorder';
|
import { SpaceChildReorderMutationHandler } from './spaces/space-child-reorder';
|
||||||
import { SpaceCreateMutationHandler } from './spaces/space-create';
|
|
||||||
import { SpaceUpdateMutationHandler } from './spaces/space-update';
|
import { SpaceUpdateMutationHandler } from './spaces/space-update';
|
||||||
import { UserRoleUpdateMutationHandler } from './users/user-role-update';
|
import { UserRoleUpdateMutationHandler } from './users/user-role-update';
|
||||||
import { UserStorageUpdateMutationHandler } from './users/user-storage-update';
|
import { UserStorageUpdateMutationHandler } from './users/user-storage-update';
|
||||||
@@ -109,7 +108,6 @@ export const buildMutationHandlerMap = (
|
|||||||
'server.create': new ServerCreateMutationHandler(app),
|
'server.create': new ServerCreateMutationHandler(app),
|
||||||
'server.delete': new ServerDeleteMutationHandler(app),
|
'server.delete': new ServerDeleteMutationHandler(app),
|
||||||
'server.sync': new ServerSyncMutationHandler(app),
|
'server.sync': new ServerSyncMutationHandler(app),
|
||||||
'space.create': new SpaceCreateMutationHandler(app),
|
|
||||||
'user.role.update': new UserRoleUpdateMutationHandler(app),
|
'user.role.update': new UserRoleUpdateMutationHandler(app),
|
||||||
'users.create': new UsersCreateMutationHandler(app),
|
'users.create': new UsersCreateMutationHandler(app),
|
||||||
'workspace.create': new WorkspaceCreateMutationHandler(app),
|
'workspace.create': new WorkspaceCreateMutationHandler(app),
|
||||||
|
|||||||
@@ -1,87 +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 {
|
|
||||||
SpaceCreateMutationInput,
|
|
||||||
SpaceCreateMutationOutput,
|
|
||||||
} from '@colanode/client/mutations/spaces/space-create';
|
|
||||||
import {
|
|
||||||
ChannelAttributes,
|
|
||||||
generateId,
|
|
||||||
IdType,
|
|
||||||
PageAttributes,
|
|
||||||
SpaceAttributes,
|
|
||||||
} from '@colanode/core';
|
|
||||||
|
|
||||||
export class SpaceCreateMutationHandler
|
|
||||||
extends WorkspaceMutationHandlerBase
|
|
||||||
implements MutationHandler<SpaceCreateMutationInput>
|
|
||||||
{
|
|
||||||
async handleMutation(
|
|
||||||
input: SpaceCreateMutationInput
|
|
||||||
): Promise<SpaceCreateMutationOutput> {
|
|
||||||
const workspace = this.getWorkspace(input.userId);
|
|
||||||
|
|
||||||
if (!workspace) {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.WorkspaceNotFound,
|
|
||||||
'Workspace was not found or has been deleted.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workspace.role === 'guest' || workspace.role === 'none') {
|
|
||||||
throw new MutationError(
|
|
||||||
MutationErrorCode.SpaceCreateForbidden,
|
|
||||||
"You don't have permission to create spaces in this workspace."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const spaceId = generateId(IdType.Space);
|
|
||||||
const spaceAttributes: SpaceAttributes = {
|
|
||||||
type: 'space',
|
|
||||||
name: input.name,
|
|
||||||
visibility: 'private',
|
|
||||||
collaborators: {
|
|
||||||
[workspace.userId]: 'admin',
|
|
||||||
},
|
|
||||||
description: input.description,
|
|
||||||
avatar: input.avatar,
|
|
||||||
};
|
|
||||||
|
|
||||||
await workspace.nodes.createNode({
|
|
||||||
id: spaceId,
|
|
||||||
attributes: spaceAttributes,
|
|
||||||
parentId: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pageId = generateId(IdType.Page);
|
|
||||||
const pageAttributes: PageAttributes = {
|
|
||||||
type: 'page',
|
|
||||||
name: 'Home',
|
|
||||||
parentId: spaceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
await workspace.nodes.createNode({
|
|
||||||
id: pageId,
|
|
||||||
attributes: pageAttributes,
|
|
||||||
parentId: spaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const channelId = generateId(IdType.Channel);
|
|
||||||
const channelAttributes: ChannelAttributes = {
|
|
||||||
type: 'channel',
|
|
||||||
name: 'Discussions',
|
|
||||||
parentId: spaceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
await workspace.nodes.createNode({
|
|
||||||
id: channelId,
|
|
||||||
attributes: channelAttributes,
|
|
||||||
parentId: spaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: spaceId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,7 +45,6 @@ export * from './records/record-field-value-set';
|
|||||||
export * from './records/record-name-update';
|
export * from './records/record-name-update';
|
||||||
export * from './servers/server-create';
|
export * from './servers/server-create';
|
||||||
export * from './servers/server-delete';
|
export * from './servers/server-delete';
|
||||||
export * from './spaces/space-create';
|
|
||||||
export * from './spaces/space-update';
|
export * from './spaces/space-update';
|
||||||
export * from './spaces/space-child-reorder';
|
export * from './spaces/space-child-reorder';
|
||||||
export * from './workspaces/workspace-create';
|
export * from './workspaces/workspace-create';
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
export type SpaceCreateMutationInput = {
|
|
||||||
type: 'space.create';
|
|
||||||
userId: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
avatar?: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SpaceCreateMutationOutput = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare module '@colanode/client/mutations' {
|
|
||||||
interface MutationMap {
|
|
||||||
'space.create': {
|
|
||||||
input: SpaceCreateMutationInput;
|
|
||||||
output: SpaceCreateMutationOutput;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ interface SpaceContainerProps {
|
|||||||
|
|
||||||
export const SpaceContainer = ({ space, role }: SpaceContainerProps) => {
|
export const SpaceContainer = ({ space, role }: SpaceContainerProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const { mutate, isPending } = useMutation();
|
const { mutate } = useMutation();
|
||||||
|
|
||||||
const canEdit = hasNodeRole(role, 'admin');
|
const canEdit = hasNodeRole(role, 'admin');
|
||||||
const canDelete = hasNodeRole(role, 'admin');
|
const canDelete = hasNodeRole(role, 'admin');
|
||||||
@@ -53,8 +53,7 @@ export const SpaceContainer = ({ space, role }: SpaceContainerProps) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
isSaving={isPending}
|
submitText="Update"
|
||||||
saveText="Update"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import { SpaceForm } from '@colanode/ui/components/spaces/space-form';
|
import { LocalPageNode, LocalSpaceNode } from '@colanode/client/types';
|
||||||
|
import { generateId, IdType } from '@colanode/core';
|
||||||
|
import { collections } from '@colanode/ui/collections';
|
||||||
|
import {
|
||||||
|
SpaceForm,
|
||||||
|
SpaceFormValues,
|
||||||
|
} from '@colanode/ui/components/spaces/space-form';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -9,7 +16,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 SpaceCreateDialogProps {
|
interface SpaceCreateDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -21,7 +27,65 @@ export const SpaceCreateDialog = ({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: SpaceCreateDialogProps) => {
|
}: SpaceCreateDialogProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const { mutate, isPending } = useMutation();
|
|
||||||
|
const { mutate } = useMutation({
|
||||||
|
mutationFn: async (values: SpaceFormValues) => {
|
||||||
|
const nodes = collections.workspace(workspace.userId).nodes;
|
||||||
|
|
||||||
|
const spaceId = generateId(IdType.Space);
|
||||||
|
const space: LocalSpaceNode = {
|
||||||
|
id: spaceId,
|
||||||
|
type: 'space',
|
||||||
|
attributes: {
|
||||||
|
type: 'space',
|
||||||
|
name: values.name,
|
||||||
|
description: values.description,
|
||||||
|
avatar: values.avatar,
|
||||||
|
collaborators: {
|
||||||
|
[workspace.userId]: 'admin',
|
||||||
|
},
|
||||||
|
visibility: 'private',
|
||||||
|
},
|
||||||
|
parentId: '',
|
||||||
|
rootId: spaceId,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
createdBy: workspace.userId,
|
||||||
|
updatedAt: null,
|
||||||
|
updatedBy: null,
|
||||||
|
localRevision: '0',
|
||||||
|
serverRevision: '0',
|
||||||
|
};
|
||||||
|
console.log('space', JSON.stringify(space, null, 2));
|
||||||
|
|
||||||
|
const pageId = generateId(IdType.Page);
|
||||||
|
const page: LocalPageNode = {
|
||||||
|
id: pageId,
|
||||||
|
type: 'page',
|
||||||
|
attributes: {
|
||||||
|
type: 'page',
|
||||||
|
name: 'Home',
|
||||||
|
parentId: spaceId,
|
||||||
|
},
|
||||||
|
parentId: spaceId,
|
||||||
|
rootId: spaceId,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
createdBy: workspace.userId,
|
||||||
|
updatedAt: null,
|
||||||
|
updatedBy: null,
|
||||||
|
localRevision: '0',
|
||||||
|
serverRevision: '0',
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.insert([space, page]);
|
||||||
|
return space;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
onOpenChange(false);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
@@ -33,36 +97,9 @@ export const SpaceCreateDialog = ({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<SpaceForm
|
<SpaceForm
|
||||||
onSubmit={(values) => {
|
onSubmit={(values) => mutate(values)}
|
||||||
if (isPending) {
|
submitText="Create"
|
||||||
return;
|
onCancel={() => onOpenChange(false)}
|
||||||
}
|
|
||||||
|
|
||||||
if (values.name.length < 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate({
|
|
||||||
input: {
|
|
||||||
type: 'space.create',
|
|
||||||
name: values.name,
|
|
||||||
description: values.description,
|
|
||||||
avatar: values.avatar,
|
|
||||||
userId: workspace.userId,
|
|
||||||
},
|
|
||||||
onSuccess() {
|
|
||||||
onOpenChange(false);
|
|
||||||
},
|
|
||||||
onError(error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
isSaving={isPending}
|
|
||||||
onCancel={() => {
|
|
||||||
onOpenChange(false);
|
|
||||||
}}
|
|
||||||
saveText="Create"
|
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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';
|
|
||||||
import { Textarea } from '@colanode/ui/components/ui/textarea';
|
import { Textarea } from '@colanode/ui/components/ui/textarea';
|
||||||
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
|
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
|
||||||
import { cn } from '@colanode/ui/lib/utils';
|
import { cn } from '@colanode/ui/lib/utils';
|
||||||
@@ -28,29 +27,27 @@ const formSchema = z.object({
|
|||||||
avatar: z.string().optional().nullable(),
|
avatar: z.string().optional().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type formSchemaType = z.infer<typeof formSchema>;
|
export type SpaceFormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
interface SpaceFormProps {
|
interface SpaceFormProps {
|
||||||
values?: formSchemaType;
|
values?: SpaceFormValues;
|
||||||
onSubmit: (values: formSchemaType) => void;
|
onSubmit: (values: SpaceFormValues) => void;
|
||||||
isSaving: boolean;
|
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
saveText: string;
|
submitText: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SpaceForm = ({
|
export const SpaceForm = ({
|
||||||
values,
|
values,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
isSaving,
|
|
||||||
onCancel,
|
onCancel,
|
||||||
saveText,
|
submitText,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
}: SpaceFormProps) => {
|
}: SpaceFormProps) => {
|
||||||
const id = useRef(generateId(IdType.Space));
|
const id = useRef(generateId(IdType.Space));
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const form = useForm<formSchemaType>({
|
const form = useForm<SpaceFormValues>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: values?.name ?? '',
|
name: values?.name ?? '',
|
||||||
@@ -136,7 +133,6 @@ export const SpaceForm = ({
|
|||||||
{onCancel && (
|
{onCancel && (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={isSaving}
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onCancel();
|
onCancel();
|
||||||
@@ -146,9 +142,8 @@ export const SpaceForm = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button type="submit" disabled={isSaving} className="w-20">
|
<Button type="submit" disabled={readOnly} className="w-20">
|
||||||
{isSaving && <Spinner className="mr-1" />}
|
{submitText}
|
||||||
{saveText}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user