mirror of
https://github.com/docmost/docmost.git
synced 2025-11-16 03:41:09 +10:00
feat: delete space and edit space slug (#307)
* feat: make space slug editable * feat: delete space * client
This commit is contained in:
@ -0,0 +1,86 @@
|
||||
import { Button, Divider, Group, Modal, Text, TextInput } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { useDeleteSpaceMutation } from '../queries/space-query';
|
||||
import { useField } from '@mantine/form';
|
||||
import { ISpace } from '../types/space.types';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import APP_ROUTE from '@/lib/app-route';
|
||||
|
||||
interface DeleteSpaceModalProps {
|
||||
space: ISpace;
|
||||
}
|
||||
|
||||
export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const deleteSpaceMutation = useDeleteSpaceMutation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const confirmNameField = useField({
|
||||
initialValue: '',
|
||||
validateOnChange: true,
|
||||
validate: (value) =>
|
||||
value.trim().toLowerCase() === space.name.trim().toLocaleLowerCase()
|
||||
? null
|
||||
: 'Names do not match',
|
||||
});
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (
|
||||
confirmNameField.getValue().trim().toLowerCase() !==
|
||||
space.name.trim().toLowerCase()
|
||||
) {
|
||||
confirmNameField.validate();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// pass slug too so we can clear the local cache
|
||||
await deleteSpaceMutation.mutateAsync({ id: space.id, slug: space.slug });
|
||||
navigate(APP_ROUTE.HOME);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete space', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={open} variant="light" color="red">
|
||||
Delete
|
||||
</Button>
|
||||
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={close}
|
||||
title="Are you sure you want to delete this space?"
|
||||
>
|
||||
<Divider size="xs" mb="xs" />
|
||||
<Text>
|
||||
All pages, comments, attachments and permissions in this space will be
|
||||
deleted irreversibly.
|
||||
</Text>
|
||||
<Text mt="sm">
|
||||
Type the space name{' '}
|
||||
<Text span fw={500}>
|
||||
'{space.name}'
|
||||
</Text>{' '}
|
||||
to confirm your action.
|
||||
</Text>
|
||||
<TextInput
|
||||
{...confirmNameField.getInputProps()}
|
||||
variant="filled"
|
||||
placeholder="Confirm space name"
|
||||
py="sm"
|
||||
data-autofocus
|
||||
/>
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button onClick={close} variant="default">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleDelete} color="red">
|
||||
Confirm
|
||||
</Button>
|
||||
</Group>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -8,6 +8,14 @@ import { ISpace } from "@/features/space/types/space.types.ts";
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(2).max(50),
|
||||
description: z.string().max(250),
|
||||
slug: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(50)
|
||||
.regex(
|
||||
/^[a-zA-Z0-9]+$/,
|
||||
"Space slug must be alphanumeric. No special characters",
|
||||
),
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
@ -23,12 +31,14 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
|
||||
initialValues: {
|
||||
name: space?.name,
|
||||
description: space?.description || "",
|
||||
slug: space.slug,
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = async (values: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
slug?: string;
|
||||
}) => {
|
||||
const spaceData: Partial<ISpace> = {
|
||||
spaceId: space.id,
|
||||
@ -40,6 +50,10 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
|
||||
spaceData.description = values.description;
|
||||
}
|
||||
|
||||
if (form.isDirty("slug")) {
|
||||
spaceData.slug = values.slug;
|
||||
}
|
||||
|
||||
await updateSpaceMutation.mutateAsync(spaceData);
|
||||
form.resetDirty();
|
||||
};
|
||||
@ -62,8 +76,8 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
|
||||
id="slug"
|
||||
label="Slug"
|
||||
variant="filled"
|
||||
readOnly
|
||||
value={space.slug}
|
||||
readOnly={readOnly}
|
||||
{...form.getInputProps("slug")}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { useSpaceQuery } from "@/features/space/queries/space-query.ts";
|
||||
import { EditSpaceForm } from "@/features/space/components/edit-space-form.tsx";
|
||||
import { Text } from "@mantine/core";
|
||||
import React from 'react';
|
||||
import { useSpaceQuery } from '@/features/space/queries/space-query.ts';
|
||||
import { EditSpaceForm } from '@/features/space/components/edit-space-form.tsx';
|
||||
import { Divider, Group, Text } from '@mantine/core';
|
||||
import DeleteSpaceModal from './delete-space-modal';
|
||||
|
||||
interface SpaceDetailsProps {
|
||||
spaceId: string;
|
||||
@ -18,6 +19,23 @@ export default function SpaceDetails({ spaceId, readOnly }: SpaceDetailsProps) {
|
||||
Details
|
||||
</Text>
|
||||
<EditSpaceForm space={space} readOnly={readOnly} />
|
||||
|
||||
{!readOnly && (
|
||||
<>
|
||||
<Divider my="lg" />
|
||||
|
||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
||||
<div>
|
||||
<Text size="md">Delete space</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
Delete this space with all its pages and data.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<DeleteSpaceModal space={space} />
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user