feat: add column count and is public options to board creation modal (#930)

* feat: add column count and is public options to board creation modal

* test: adjust board creation test to match modified schema
This commit is contained in:
Meier Lukas
2024-08-09 18:28:52 +02:00
committed by GitHub
parent 349c49462f
commit fcb72e6716
6 changed files with 36 additions and 11 deletions

View File

@@ -30,6 +30,8 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
onSuccess: async (values) => { onSuccess: async (values) => {
await mutateAsync({ await mutateAsync({
name: values.name, name: values.name,
columnCount: values.columnCount,
isPublic: values.isPublic,
}); });
}, },
boardNames, boardNames,

View File

@@ -1,31 +1,36 @@
import { Button, Group, Stack, TextInput } from "@mantine/core"; import { Button, Group, InputWrapper, Slider, Stack, Switch, TextInput } from "@mantine/core";
import { useZodForm } from "@homarr/form"; import { useZodForm } from "@homarr/form";
import { createModal } from "@homarr/modals"; import { createModal } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client"; import { useI18n } from "@homarr/translation/client";
import { validation, z } from "@homarr/validation"; import { validation } from "@homarr/validation";
import { createCustomErrorParams } from "@homarr/validation/form"; import { createCustomErrorParams } from "@homarr/validation/form";
interface InnerProps { interface InnerProps {
boardNames: string[]; boardNames: string[];
onSuccess: ({ name }: { name: string }) => Promise<void>; onSuccess: (props: { name: string; columnCount: number; isPublic: boolean }) => Promise<void>;
} }
export const AddBoardModal = createModal<InnerProps>(({ actions, innerProps }) => { export const AddBoardModal = createModal<InnerProps>(({ actions, innerProps }) => {
const t = useI18n(); const t = useI18n();
const form = useZodForm( const form = useZodForm(
z.object({ validation.board.create.refine((value) => !innerProps.boardNames.includes(value.name), {
name: validation.board.byName.shape.name.refine((value) => !innerProps.boardNames.includes(value), { params: createCustomErrorParams("boardAlreadyExists"),
params: createCustomErrorParams("boardAlreadyExists"), path: ["name"],
}),
}), }),
{ {
initialValues: { initialValues: {
name: "", name: "",
columnCount: 10,
isPublic: false,
}, },
}, },
); );
const columnCountChecks = validation.board.create.shape.columnCount._def.checks;
const minColumnCount = columnCountChecks.find((check) => check.kind === "min")?.value;
const maxColumnCount = columnCountChecks.find((check) => check.kind === "max")?.value;
return ( return (
<form <form
onSubmit={form.onSubmit((values) => { onSubmit={form.onSubmit((values) => {
@@ -35,11 +40,21 @@ export const AddBoardModal = createModal<InnerProps>(({ actions, innerProps }) =
> >
<Stack> <Stack>
<TextInput label={t("board.field.name.label")} data-autofocus {...form.getInputProps("name")} /> <TextInput label={t("board.field.name.label")} data-autofocus {...form.getInputProps("name")} />
<InputWrapper label={t("board.field.columnCount.label")} {...form.getInputProps("columnCount")}>
<Slider min={minColumnCount} max={maxColumnCount} step={1} {...form.getInputProps("columnCount")} />
</InputWrapper>
<Switch
label={t("board.field.isPublic.label")}
description={t("board.field.isPublic.description")}
{...form.getInputProps("isPublic")}
/>
<Group justify="right"> <Group justify="right">
<Button onClick={actions.closeModal} variant="subtle" color="gray"> <Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")} {t("common.action.cancel")}
</Button> </Button>
<Button disabled={!form.isValid()} type="submit" color="teal"> <Button type="submit" color="teal">
{t("common.action.create")} {t("common.action.create")}
</Button> </Button>
</Group> </Group>

View File

@@ -102,6 +102,8 @@ export const boardRouter = createTRPCRouter({
await transaction.insert(boards).values({ await transaction.insert(boards).values({
id: boardId, id: boardId,
name: input.name, name: input.name,
isPublic: input.isPublic,
columnCount: input.columnCount,
creatorId: ctx.session.user.id, creatorId: ctx.session.user.id,
}); });
await transaction.insert(sections).values({ await transaction.insert(sections).values({

View File

@@ -294,12 +294,14 @@ describe("createBoard should create a new board", () => {
}); });
// Act // Act
await caller.createBoard({ name: "newBoard" }); await caller.createBoard({ name: "newBoard", columnCount: 24, isPublic: true });
// Assert // Assert
const dbBoard = await db.query.boards.findFirst(); const dbBoard = await db.query.boards.findFirst();
expect(dbBoard).toBeDefined(); expect(dbBoard).toBeDefined();
expect(dbBoard?.name).toBe("newBoard"); expect(dbBoard?.name).toBe("newBoard");
expect(dbBoard?.columnCount).toBe(24);
expect(dbBoard?.isPublic).toBe(true);
expect(dbBoard?.creatorId).toBe(defaultCreatorId); expect(dbBoard?.creatorId).toBe(defaultCreatorId);
const dbSection = await db.query.sections.findFirst(); const dbSection = await db.query.sections.findFirst();
@@ -314,7 +316,7 @@ describe("createBoard should create a new board", () => {
const caller = boardRouter.createCaller({ db, session: defaultSession }); const caller = boardRouter.createCaller({ db, session: defaultSession });
// Act // Act
const actAsync = async () => await caller.createBoard({ name: "newBoard" }); const actAsync = async () => await caller.createBoard({ name: "newBoard", columnCount: 12, isPublic: true });
// Assert // Assert
await expect(actAsync()).rejects.toThrowError("Permission denied"); await expect(actAsync()).rejects.toThrowError("Permission denied");

View File

@@ -1201,6 +1201,10 @@ export default {
name: { name: {
label: "Name", label: "Name",
}, },
isPublic: {
label: "Public",
description: "Public boards are accessible by everyone, even without an account.",
},
}, },
content: { content: {
metaTitle: "{boardName} board", metaTitle: "{boardName} board",

View File

@@ -61,7 +61,7 @@ const saveSchema = z.object({
sections: z.array(createSectionSchema(commonItemSchema)), sections: z.array(createSectionSchema(commonItemSchema)),
}); });
const createSchema = z.object({ name: boardNameSchema }); const createSchema = z.object({ name: boardNameSchema, columnCount: z.number().min(1).max(24), isPublic: z.boolean() });
const permissionsSchema = z.object({ const permissionsSchema = z.object({
id: z.string(), id: z.string(),