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 { ServerSyncMutationHandler } from './servers/server-sync';
|
||||
import { SpaceChildReorderMutationHandler } from './spaces/space-child-reorder';
|
||||
import { SpaceCreateMutationHandler } from './spaces/space-create';
|
||||
import { SpaceUpdateMutationHandler } from './spaces/space-update';
|
||||
import { UserRoleUpdateMutationHandler } from './users/user-role-update';
|
||||
import { UserStorageUpdateMutationHandler } from './users/user-storage-update';
|
||||
@@ -109,7 +108,6 @@ export const buildMutationHandlerMap = (
|
||||
'server.create': new ServerCreateMutationHandler(app),
|
||||
'server.delete': new ServerDeleteMutationHandler(app),
|
||||
'server.sync': new ServerSyncMutationHandler(app),
|
||||
'space.create': new SpaceCreateMutationHandler(app),
|
||||
'user.role.update': new UserRoleUpdateMutationHandler(app),
|
||||
'users.create': new UsersCreateMutationHandler(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 './servers/server-create';
|
||||
export * from './servers/server-delete';
|
||||
export * from './spaces/space-create';
|
||||
export * from './spaces/space-update';
|
||||
export * from './spaces/space-child-reorder';
|
||||
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) => {
|
||||
const workspace = useWorkspace();
|
||||
const { mutate, isPending } = useMutation();
|
||||
const { mutate } = useMutation();
|
||||
|
||||
const canEdit = hasNodeRole(role, 'admin');
|
||||
const canDelete = hasNodeRole(role, 'admin');
|
||||
@@ -53,8 +53,7 @@ export const SpaceContainer = ({ space, role }: SpaceContainerProps) => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
isSaving={isPending}
|
||||
saveText="Update"
|
||||
submitText="Update"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
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 {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -9,7 +16,6 @@ import {
|
||||
DialogTitle,
|
||||
} from '@colanode/ui/components/ui/dialog';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
||||
|
||||
interface SpaceCreateDialogProps {
|
||||
open: boolean;
|
||||
@@ -21,7 +27,65 @@ export const SpaceCreateDialog = ({
|
||||
onOpenChange,
|
||||
}: SpaceCreateDialogProps) => {
|
||||
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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@@ -33,36 +97,9 @@ export const SpaceCreateDialog = ({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<SpaceForm
|
||||
onSubmit={(values) => {
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
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"
|
||||
onSubmit={(values) => mutate(values)}
|
||||
submitText="Create"
|
||||
onCancel={() => onOpenChange(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
FormMessage,
|
||||
} from '@colanode/ui/components/ui/form';
|
||||
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 { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
|
||||
import { cn } from '@colanode/ui/lib/utils';
|
||||
@@ -28,29 +27,27 @@ const formSchema = z.object({
|
||||
avatar: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
type formSchemaType = z.infer<typeof formSchema>;
|
||||
export type SpaceFormValues = z.infer<typeof formSchema>;
|
||||
|
||||
interface SpaceFormProps {
|
||||
values?: formSchemaType;
|
||||
onSubmit: (values: formSchemaType) => void;
|
||||
isSaving: boolean;
|
||||
values?: SpaceFormValues;
|
||||
onSubmit: (values: SpaceFormValues) => void;
|
||||
onCancel?: () => void;
|
||||
saveText: string;
|
||||
submitText: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const SpaceForm = ({
|
||||
values,
|
||||
onSubmit,
|
||||
isSaving,
|
||||
onCancel,
|
||||
saveText,
|
||||
submitText,
|
||||
readOnly = false,
|
||||
}: SpaceFormProps) => {
|
||||
const id = useRef(generateId(IdType.Space));
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const form = useForm<formSchemaType>({
|
||||
const form = useForm<SpaceFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: values?.name ?? '',
|
||||
@@ -136,7 +133,6 @@ export const SpaceForm = ({
|
||||
{onCancel && (
|
||||
<Button
|
||||
type="button"
|
||||
disabled={isSaving}
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
onCancel();
|
||||
@@ -146,9 +142,8 @@ export const SpaceForm = ({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button type="submit" disabled={isSaving} className="w-20">
|
||||
{isSaving && <Spinner className="mr-1" />}
|
||||
{saveText}
|
||||
<Button type="submit" disabled={readOnly} className="w-20">
|
||||
{submitText}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user