feat: add template selection when creating new boards with predefined… (#42)

* feat: add template selection when creating new boards with predefined lists

* feat: extend templates with labels, update design and add translations

* feat: add validation and error states to new board form

---------

Co-authored-by: Henry <henry_ball@hotmail.co.uk>
This commit is contained in:
LovelessCodes
2025-06-29 22:53:37 +02:00
committed by GitHub
parent 67050be27f
commit 316f148663
17 changed files with 1983 additions and 72 deletions

View File

@@ -4,8 +4,10 @@ import { z } from "zod";
import * as boardRepo from "@kan/db/repository/board.repo";
import * as cardRepo from "@kan/db/repository/card.repo";
import * as activityRepo from "@kan/db/repository/cardActivity.repo";
import * as labelRepo from "@kan/db/repository/label.repo";
import * as listRepo from "@kan/db/repository/list.repo";
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { colours } from "@kan/shared/constants";
import { generateSlug, generateUID } from "@kan/shared/utils";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
@@ -149,8 +151,10 @@ export const boardRouter = createTRPCRouter({
})
.input(
z.object({
name: z.string().min(1),
name: z.string().min(1).max(100),
workspacePublicId: z.string().min(12),
lists: z.array(z.string().min(1)),
labels: z.array(z.string().min(1)),
}),
)
.output(z.custom<Awaited<ReturnType<typeof boardRepo.create>>>())
@@ -199,6 +203,30 @@ export const boardRouter = createTRPCRouter({
code: "INTERNAL_SERVER_ERROR",
});
if (input.lists.length) {
const listInputs = input.lists.map((list, index) => ({
publicId: generateUID(),
name: list,
boardId: result.id,
createdBy: userId,
index,
}));
await listRepo.bulkCreate(ctx.db, listInputs);
}
if (input.labels.length) {
const labelInputs = input.labels.map((label, index) => ({
publicId: generateUID(),
name: label,
boardId: result.id,
createdBy: userId,
colourCode: colours[index % colours.length]?.code ?? "#0d9488",
}));
await labelRepo.bulkCreate(ctx.db, labelInputs);
}
return result;
}),
update: protectedProcedure
@@ -352,40 +380,40 @@ export const boardRouter = createTRPCRouter({
return { success: true };
}),
checkSlugAvailability: publicProcedure
.meta({
openapi: {
summary: "Check if a board slug is available",
method: "GET",
path: "/boards/{boardPublicId}/check-slug-availability",
description: "Checks if a board slug is available",
tags: ["Boards"],
protect: true,
},
})
.input(
z.object({
boardSlug: z
.string()
.min(3)
.max(24)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
boardPublicId: z.string().min(12),
}),
)
.output(
z.object({
isReserved: z.boolean(),
}),
)
.query(async ({ ctx, input }) => {
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.boardSlug,
input.boardPublicId,
);
return {
isReserved: !isBoardSlugAvailable,
};
checkSlugAvailability: publicProcedure
.meta({
openapi: {
summary: "Check if a board slug is available",
method: "GET",
path: "/boards/{boardPublicId}/check-slug-availability",
description: "Checks if a board slug is available",
tags: ["Boards"],
protect: true,
},
})
.input(
z.object({
boardSlug: z
.string()
.min(3)
.max(24)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
boardPublicId: z.string().min(12),
}),
)
.output(
z.object({
isReserved: z.boolean(),
}),
)
.query(async ({ ctx, input }) => {
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.boardSlug,
input.boardPublicId,
);
return {
isReserved: !isBoardSlugAvailable,
};
}),
});