diff --git a/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx b/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx index 1c59db412..ed30fb23d 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx @@ -30,6 +30,8 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => { onSuccess: async (values) => { await mutateAsync({ name: values.name, + columnCount: values.columnCount, + isPublic: values.isPublic, }); }, boardNames, diff --git a/apps/nextjs/src/components/manage/boards/add-board-modal.tsx b/apps/nextjs/src/components/manage/boards/add-board-modal.tsx index 1e1723edd..e3bf35f8a 100644 --- a/apps/nextjs/src/components/manage/boards/add-board-modal.tsx +++ b/apps/nextjs/src/components/manage/boards/add-board-modal.tsx @@ -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; + onSuccess: (props: { name: string; columnCount: number; isPublic: boolean }) => Promise; } export const AddBoardModal = createModal(({ 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 (
{ @@ -35,11 +40,21 @@ export const AddBoardModal = createModal(({ actions, innerProps }) = > + + + + + + - diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index f39a6ac1f..d4c42e44c 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -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({ diff --git a/packages/api/src/router/test/board.spec.ts b/packages/api/src/router/test/board.spec.ts index 5c121f91d..86e567965 100644 --- a/packages/api/src/router/test/board.spec.ts +++ b/packages/api/src/router/test/board.spec.ts @@ -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"); diff --git a/packages/translation/src/lang/en.ts b/packages/translation/src/lang/en.ts index 26e182939..aa851a7c6 100644 --- a/packages/translation/src/lang/en.ts +++ b/packages/translation/src/lang/en.ts @@ -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", diff --git a/packages/validation/src/board.ts b/packages/validation/src/board.ts index d8567cebb..c7eff93a6 100644 --- a/packages/validation/src/board.ts +++ b/packages/validation/src/board.ts @@ -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(),