mirror of
https://github.com/ajnart/homarr.git
synced 2026-01-29 10:49:14 +01:00
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:
@@ -30,6 +30,8 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
|
||||
onSuccess: async (values) => {
|
||||
await mutateAsync({
|
||||
name: values.name,
|
||||
columnCount: values.columnCount,
|
||||
isPublic: values.isPublic,
|
||||
});
|
||||
},
|
||||
boardNames,
|
||||
|
||||
@@ -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 { createModal } from "@homarr/modals";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { validation, z } from "@homarr/validation";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { createCustomErrorParams } from "@homarr/validation/form";
|
||||
|
||||
interface InnerProps {
|
||||
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 }) => {
|
||||
const t = useI18n();
|
||||
const form = useZodForm(
|
||||
z.object({
|
||||
name: validation.board.byName.shape.name.refine((value) => !innerProps.boardNames.includes(value), {
|
||||
params: createCustomErrorParams("boardAlreadyExists"),
|
||||
}),
|
||||
validation.board.create.refine((value) => !innerProps.boardNames.includes(value.name), {
|
||||
params: createCustomErrorParams("boardAlreadyExists"),
|
||||
path: ["name"],
|
||||
}),
|
||||
{
|
||||
initialValues: {
|
||||
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 (
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
@@ -35,11 +40,21 @@ export const AddBoardModal = createModal<InnerProps>(({ actions, innerProps }) =
|
||||
>
|
||||
<Stack>
|
||||
<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">
|
||||
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||
{t("common.action.cancel")}
|
||||
</Button>
|
||||
<Button disabled={!form.isValid()} type="submit" color="teal">
|
||||
<Button type="submit" color="teal">
|
||||
{t("common.action.create")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -102,6 +102,8 @@ export const boardRouter = createTRPCRouter({
|
||||
await transaction.insert(boards).values({
|
||||
id: boardId,
|
||||
name: input.name,
|
||||
isPublic: input.isPublic,
|
||||
columnCount: input.columnCount,
|
||||
creatorId: ctx.session.user.id,
|
||||
});
|
||||
await transaction.insert(sections).values({
|
||||
|
||||
@@ -294,12 +294,14 @@ describe("createBoard should create a new board", () => {
|
||||
});
|
||||
|
||||
// Act
|
||||
await caller.createBoard({ name: "newBoard" });
|
||||
await caller.createBoard({ name: "newBoard", columnCount: 24, isPublic: true });
|
||||
|
||||
// Assert
|
||||
const dbBoard = await db.query.boards.findFirst();
|
||||
expect(dbBoard).toBeDefined();
|
||||
expect(dbBoard?.name).toBe("newBoard");
|
||||
expect(dbBoard?.columnCount).toBe(24);
|
||||
expect(dbBoard?.isPublic).toBe(true);
|
||||
expect(dbBoard?.creatorId).toBe(defaultCreatorId);
|
||||
|
||||
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 });
|
||||
|
||||
// Act
|
||||
const actAsync = async () => await caller.createBoard({ name: "newBoard" });
|
||||
const actAsync = async () => await caller.createBoard({ name: "newBoard", columnCount: 12, isPublic: true });
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).rejects.toThrowError("Permission denied");
|
||||
|
||||
@@ -1201,6 +1201,10 @@ export default {
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
isPublic: {
|
||||
label: "Public",
|
||||
description: "Public boards are accessible by everyone, even without an account.",
|
||||
},
|
||||
},
|
||||
content: {
|
||||
metaTitle: "{boardName} board",
|
||||
|
||||
@@ -61,7 +61,7 @@ const saveSchema = z.object({
|
||||
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({
|
||||
id: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user