Compare commits

..

20 Commits

Author SHA1 Message Date
Henry
a7062f8cfb chore: update translations 2025-07-23 22:43:26 +01:00
Henry
40f40a0329 chore: update hero images for docs 2025-07-23 22:29:58 +01:00
Henry
4bc1cc1eda chore: update hero images 2025-07-23 22:28:19 +01:00
Henry
3743fcd88d fix: hide side nav right border on desktop 2025-07-23 22:27:51 +01:00
Henry
8fb1da8224 feat: extend list max height 2025-07-23 22:06:20 +01:00
Henry
a6946094f3 feat: tweak skeleton loaders for board list 2025-07-23 22:00:46 +01:00
Henry
e3b25abccb feat: tweak nav styling for light mode 2025-07-23 21:57:55 +01:00
Henry
74a0dc2fc0 fix: hide pro badge when collapsed 2025-07-22 23:08:00 +01:00
Henry
6c6fd4bd32 feat: extend mobile layout support 2025-07-22 22:55:42 +01:00
Henry
09066fa47d feat: adjust page max width 2025-07-20 10:38:04 +01:00
Henry
089723d748 feat: add feedback button and modal 2025-07-18 23:11:46 +01:00
Henry
18c7faac19 feat: reduce max width for settings and members pages 2025-07-18 16:49:26 +01:00
Henry
076fe1d922 fix: use getLayout to persist layout state across pages 2025-07-18 16:48:45 +01:00
Henry
c58c8c753c fix: use correct colour for description placeholder 2025-07-17 22:15:42 +01:00
Henry
789b48b715 feat: adjust dashboard border colour scheme and radius 2025-07-17 22:15:01 +01:00
Henry
9feb2d3d07 feat: adjust colour scheme for card view 2025-07-17 22:12:07 +01:00
Henry
54479764f6 feat: set max width for main card view 2025-07-17 21:29:29 +01:00
Henry
5c12221f09 fix: prevent patterned background from overlaying buttons 2025-07-17 21:27:57 +01:00
Henry
b9c3ffe7ab feat: setup dashboard 2.0 2025-07-16 22:55:04 +01:00
Henry
63d948cc06 feat: add patterned background to boards 2025-07-16 22:46:08 +01:00
5 changed files with 29 additions and 69 deletions

View File

@@ -167,8 +167,7 @@ export function UpdateBoardSlugForm({
!isDirty ||
updateBoardSlug.isPending ||
errors.slug?.message !== undefined ||
isBoardSlugAvailable?.isReserved ||
checkBoardSlugAvailability.isLoading
isBoardSlugAvailable?.isReserved
}
>
{t`Update`}

View File

@@ -276,21 +276,6 @@ export const boardRouter = createTRPCRouter({
await assertUserInWorkspace(ctx.db, userId, board.workspaceId);
if (input.slug) {
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.slug,
board.workspaceId,
);
if (!isBoardSlugAvailable) {
throw new TRPCError({
message: `Board slug ${input.slug} is not available`,
code: "BAD_REQUEST",
});
}
}
const result = await boardRepo.update(ctx.db, {
name: input.name,
slug: input.slug,
@@ -422,23 +407,11 @@ export const boardRouter = createTRPCRouter({
}),
)
.query(async ({ ctx, input }) => {
const board = await boardRepo.getWorkspaceAndBoardIdByBoardPublicId(
ctx.db,
input.boardPublicId,
);
if (!board)
throw new TRPCError({
message: `Board with public ID ${input.boardPublicId} not found`,
code: "NOT_FOUND",
});
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.boardSlug,
board.workspaceId,
input.boardPublicId,
);
return {
isReserved: !isBoardSlugAvailable,
};

View File

@@ -166,19 +166,9 @@ export const initAuth = (db: dbClient) => {
databaseHooks: {
user: {
create: {
async before(user) {
before() {
if (env("NEXT_PUBLIC_DISABLE_SIGN_UP")?.toLowerCase() === "true") {
const pendingInvitation = await memberRepo.getByEmailAndStatus(
db,
user.email,
"invited",
);
if (!pendingInvitation) {
return Promise.resolve(false);
}
return Promise.resolve(true);
return Promise.resolve(false);
}
return Promise.resolve(true);
},

View File

@@ -1,4 +1,4 @@
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
import { and, asc, desc, eq, exists, inArray, isNull, or, sql } from "drizzle-orm";
import type { dbClient } from "@kan/db/client";
import type { BoardVisibilityStatus } from "@kan/db/schema";
@@ -481,18 +481,30 @@ export const getWorkspaceAndBoardIdByBoardPublicId = async (
export const isBoardSlugAvailable = async (
db: dbClient,
boardSlug: string,
workspaceId: number,
boardPublicId: string,
) => {
const result = await db.query.boards.findFirst({
columns: {
id: true,
},
where: and(
eq(boards.slug, boardSlug),
eq(boards.workspaceId, workspaceId),
isNull(boards.deletedAt),
),
});
const result = await db
.select({ id: boards.id })
.from(boards)
.where(
and(
eq(boards.publicId, boardPublicId),
exists(
db
.select({ id: boards.id })
.from(boards)
.where(
and(
eq(boards.slug, boardSlug),
eq(boards.workspaceId, sql`${boards.workspaceId}`), // Reference outer query's workspaceId
isNull(boards.deletedAt),
),
)
.limit(1),
),
),
)
.limit(1);
return result === undefined;
return result.length === 0;
};

View File

@@ -41,20 +41,6 @@ export const getByPublicId = async (db: dbClient, publicId: string) => {
});
};
export const getByEmailAndStatus = async (
db: dbClient,
email: string,
status: MemberStatus,
) => {
return db.query.workspaceMembers.findFirst({
where: and(
eq(workspaceMembers.email, email),
eq(workspaceMembers.status, status),
isNull(workspaceMembers.deletedAt),
),
});
};
export const acceptInvite = async (
db: dbClient,
args: { memberId: number; userId: string },