Compare commits

...

6 Commits

Author SHA1 Message Date
Henry
902a539d73 fix: temp ignore eslint errors on build 2025-12-13 21:56:27 +00:00
Henry
7fef9be638 chore: update lockfile 2025-12-13 21:31:17 +00:00
Henry
c7473062aa chore: remove old due date filters 2025-12-13 21:30:15 +00:00
Henry
920e9b18a2 fix: enable sessions for API keys 2025-12-13 21:15:54 +00:00
Henry
bcd20b58c6 chore: remove instrumentation (#279)
* chore: remove instrumentation

* chore: clean apk cache after installation

* chore: fix docker builds
2025-12-10 22:16:38 +00:00
Henry
36675604f9 chore: upgrade next, react-email and better-auth (#278)
* feat: upgrade next and react-email

* chore: upgrade better-auth
2025-12-10 20:34:10 +00:00
24 changed files with 1484 additions and 1229 deletions

View File

@@ -4,8 +4,9 @@ ARG PROJECT=@kan/web
# 1. Alpine image
FROM node:${NODE_VERSION}-alpine AS alpine
RUN apk update
RUN apk add --no-cache libc6-compat python3 make g++
RUN apk update && \
apk add --no-cache --virtual .build-deps libc6-compat python3 make g++ && \
rm -rf /var/cache/apk/* /tmp/* || true
# Setup pnpm and turbo on the alpine base
FROM alpine AS base

View File

@@ -26,9 +26,11 @@ const config = {
],
/** We already do linting and typechecking as separate tasks in CI */
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
// temporarily ignore eslint errors during build until we fix all the errors sigh
eslint: { ignoreDuringBuilds: true },
images: {
remotePatterns: (() => {
/** @type {Array<{protocol: "http" | "https", hostname: string}>} */
@@ -75,12 +77,13 @@ const config = {
return patterns;
})(),
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ["@svgr/webpack"],
});
return config;
turbopack: {
rules: {
"*.svg": {
loaders: ["@svgr/webpack"],
as: "*.js",
},
},
},
experimental: {
// instrumentationHook: true,

View File

@@ -4,9 +4,9 @@
"private": true,
"type": "module",
"scripts": {
"build": "pnpm with-env next build",
"build": "pnpm with-env next build --turbo",
"clean": "git clean -xdf .cache .next .turbo node_modules",
"dev": "pnpm with-env next dev",
"dev": "pnpm with-env next dev --turbo",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"start": "pnpm with-env next start",
@@ -47,12 +47,10 @@
"date-fns": "^4.1.0",
"geist": "^1.3.1",
"jose": "^6.1.2",
"next": "^15.3.4",
"next-logger": "^5.0.1",
"next": "15.5.9",
"next-runtime-env": "^1.7.2",
"next-themes": "^0.4.6",
"nextjs-cors": "^2.2.0",
"pino": "^9.6.0",
"posthog-js": "^1.254.0",
"react": "catalog:react18",
"react-beautiful-dnd": "^13.1.1",
@@ -86,7 +84,6 @@
"dotenv-cli": "^7.4.4",
"eslint": "catalog:",
"jiti": "^1.21.6",
"pino-pretty": "^13.0.0",
"prettier": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:"

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,6 +0,0 @@
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await require("pino");
await require("next-logger");
}
}

View File

@@ -29,7 +29,6 @@ import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { useWorkspace } from "~/providers/workspace";
import { api } from "~/utils/api";
import { convertDueDateFiltersToRanges } from "~/utils/dueDateFilters";
import { formatToArray } from "~/utils/helpers";
import BoardDropdown from "./components/BoardDropdown";
import Card from "./components/Card";
@@ -97,20 +96,17 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
| "no-due-date"
)[];
const queryParams: {
boardPublicId: string;
members: string[];
labels: string[];
lists: string[];
dueDate: ReturnType<typeof convertDueDateFiltersToRanges>;
type: "regular" | "template";
} = {
const boardType: "regular" | "template" = isTemplate ? "template" : "regular";
const queryParams = {
boardPublicId: boardId ?? "",
members: formatToArray(router.query.members),
labels: formatToArray(router.query.labels),
lists: formatToArray(router.query.lists),
dueDate: convertDueDateFiltersToRanges(semanticFilters),
type: isTemplate ? "template" : "regular",
...(semanticFilters.length > 0 && {
dueDateFilters: semanticFilters,
}),
type: boardType,
};
const {

View File

@@ -1,15 +1,15 @@
import type { FC, SVGProps } from "react";
import { Trans } from "@lingui/react/macro";
import AirbusLogo from "/public/logos/airbus.svg";
import BitwardenLogo from "/public/logos/bitwarden.svg";
import CouchbaseLogo from "/public/logos/couchbase.svg";
import DeloitteLogo from "/public/logos/deloitte.svg";
import FastCompanyLogo from "/public/logos/fast_company.svg";
import LegoLogo from "/public/logos/lego.svg";
import LinkedinLogo from "/public/logos/linkedin.svg";
import SanaLogo from "/public/logos/sana.svg";
import WakamLogo from "/public/logos/wakam.svg";
import AirbusLogo from "~/assets/logos/airbus.svg";
import BitwardenLogo from "~/assets/logos/bitwarden.svg";
import CouchbaseLogo from "~/assets/logos/couchbase.svg";
import DeloitteLogo from "~/assets/logos/deloitte.svg";
import FastCompanyLogo from "~/assets/logos/fast_company.svg";
import LegoLogo from "~/assets/logos/lego.svg";
import LinkedinLogo from "~/assets/logos/linkedin.svg";
import SanaLogo from "~/assets/logos/sana.svg";
import WakamLogo from "~/assets/logos/wakam.svg";
type LogoComponent = FC<SVGProps<SVGSVGElement>>;
@@ -82,7 +82,7 @@ export default function Logos() {
<div className="absolute left-0 top-0 z-10 h-full w-8 bg-gradient-to-r from-white/60 to-transparent dark:from-dark-50" />
<div className="absolute right-0 top-0 z-10 h-full w-8 bg-gradient-to-l from-white/60 to-transparent dark:from-dark-50" />
<div className="animate-scroll flex" style={{ width: "max-content" }}>
<div className="flex animate-scroll" style={{ width: "max-content" }}>
<div className="flex flex-shrink-0 items-center space-x-12">
{logos.map((logo) => {
const LogoComponent: LogoComponent = logo.component;

View File

@@ -15,7 +15,6 @@ import ThemeToggle from "~/components/ThemeToggle";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
import { convertDueDateFiltersToRanges } from "~/utils/dueDateFilters";
import { formatToArray } from "~/utils/helpers";
import Card from "~/views/board/components/Card";
import Filters from "~/views/board/components/Filters";
@@ -55,7 +54,9 @@ export default function PublicBoardView() {
members: formatToArray(router.query.members),
labels: formatToArray(router.query.labels),
lists: formatToArray(router.query.lists),
dueDate: convertDueDateFiltersToRanges(dueDateFilters),
...(dueDateFilters.length > 0 && {
dueDateFilters: dueDateFilters,
}),
},
{
enabled: router.isReady && !!boardSlug,

View File

@@ -8,17 +8,15 @@ 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 {
convertDueDateFiltersToRanges,
generateSlug,
generateUID,
} from "@kan/shared/utils";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
import { assertUserInWorkspace } from "../utils/auth";
const dueDateFilterSchema = z.object({
startDate: z.string().datetime().optional(),
endDate: z.string().datetime().optional(),
hasNoDueDate: z.boolean().optional(),
});
export const boardRouter = createTRPCRouter({
all: protectedProcedure
.meta({
@@ -85,7 +83,18 @@ export const boardRouter = createTRPCRouter({
members: z.array(z.string().min(12)).optional(),
labels: z.array(z.string().min(12)).optional(),
lists: z.array(z.string().min(12)).optional(),
dueDate: z.array(dueDateFilterSchema).optional(),
dueDateFilters: z
.array(
z.enum([
"overdue",
"today",
"tomorrow",
"next-week",
"next-month",
"no-due-date",
]),
)
.optional(),
type: z.enum(["regular", "template"]).optional(),
}),
)
@@ -112,6 +121,11 @@ export const boardRouter = createTRPCRouter({
await assertUserInWorkspace(ctx.db, userId, board.workspaceId);
// Convert semantic string filters to date ranges expected by the repo
const dueDateFilters = input.dueDateFilters
? convertDueDateFiltersToRanges(input.dueDateFilters)
: [];
const result = await boardRepo.getByPublicId(
ctx.db,
input.boardPublicId,
@@ -119,14 +133,7 @@ export const boardRouter = createTRPCRouter({
members: input.members ?? [],
labels: input.labels ?? [],
lists: input.lists ?? [],
dueDate:
input.dueDate?.map((filter) => ({
startDate: filter.startDate
? new Date(filter.startDate)
: undefined,
endDate: filter.endDate ? new Date(filter.endDate) : undefined,
hasNoDueDate: filter.hasNoDueDate,
})) ?? [],
dueDate: dueDateFilters,
type: input.type,
},
);
@@ -160,7 +167,18 @@ export const boardRouter = createTRPCRouter({
members: z.array(z.string().min(12)).optional(),
labels: z.array(z.string().min(12)).optional(),
lists: z.array(z.string().min(12)).optional(),
dueDate: z.array(dueDateFilterSchema).optional(),
dueDateFilters: z
.array(
z.enum([
"overdue",
"today",
"tomorrow",
"next-week",
"next-month",
"no-due-date",
]),
)
.optional(),
}),
)
.output(z.custom<Awaited<ReturnType<typeof boardRepo.getBySlug>>>())
@@ -176,6 +194,11 @@ export const boardRouter = createTRPCRouter({
code: "NOT_FOUND",
});
// Convert semantic string filters to date ranges expected by the repo
const dueDateFilters = input.dueDateFilters
? convertDueDateFiltersToRanges(input.dueDateFilters)
: [];
const result = await boardRepo.getBySlug(
ctx.db,
input.boardSlug,
@@ -184,14 +207,7 @@ export const boardRouter = createTRPCRouter({
members: input.members ?? [],
labels: input.labels ?? [],
lists: input.lists ?? [],
dueDate:
input.dueDate?.map((filter) => ({
startDate: filter.startDate
? new Date(filter.startDate)
: undefined,
endDate: filter.endDate ? new Date(filter.endDate) : undefined,
hasNoDueDate: filter.hasNoDueDate,
})) ?? [],
dueDate: dueDateFilters,
},
);
@@ -306,12 +322,6 @@ export const boardRouter = createTRPCRouter({
sourceBoardId: sourceBoardInfo.id,
});
if (!result)
throw new TRPCError({
message: `Failed to create board from source`,
code: "INTERNAL_SERVER_ERROR",
});
return result;
}

View File

@@ -37,7 +37,7 @@
},
"prettier": "@kan/prettier-config",
"dependencies": {
"@better-auth/stripe": "^1.3.7",
"better-auth": "^1.3.7"
"@better-auth/stripe": "^1.4.6",
"better-auth": "^1.4.6"
}
}

View File

@@ -78,8 +78,7 @@ export const configuredProviders = socialProviderList.reduce<
Object.keys(acc).includes("google") &&
acc[provider]
) {
const allowed = process.env.BETTER_AUTH_ALLOWED_DOMAINS
?.split(",")
const allowed = process.env.BETTER_AUTH_ALLOWED_DOMAINS?.split(",")
.map((d) => d.trim().toLowerCase())
.filter(Boolean);
if (allowed && allowed.length > 0) {
@@ -299,9 +298,8 @@ export const initAuth = (db: dbClient) => {
}),
]
: []),
// @todo: hasing is disabled due to a bug in the api key plugin
apiKey({
disableKeyHashing: true,
enableSessionForAPIKeys: true,
rateLimit: {
enabled: true,
timeWindow: 1000 * 60, // 1 minute
@@ -365,8 +363,7 @@ export const initAuth = (db: dbClient) => {
// Fall through to any additional checks below
}
// Enforce allowed domains (OIDC/social) if configured
const allowed = process.env.BETTER_AUTH_ALLOWED_DOMAINS
?.split(",")
const allowed = process.env.BETTER_AUTH_ALLOWED_DOMAINS?.split(",")
.map((d) => d.trim().toLowerCase())
.filter(Boolean);
if (allowed && allowed.length > 0) {

View File

@@ -24,9 +24,9 @@
},
"dependencies": {
"@novu/api": "^3.11.0",
"@react-email/components": "^0.0.25",
"@react-email/components": "^1.0.1",
"nodemailer": "^7.0.3",
"react-email": "^3.0.1"
"react-email": "^5.0.6"
},
"devDependencies": {
"@kan/eslint-config": "workspace:*",

View File

@@ -36,6 +36,7 @@
},
"prettier": "@kan/prettier-config",
"dependencies": {
"date-fns": "^4.1.0",
"jose": "^6.1.2",
"nanoid": "^5.0.9",
"next-runtime-env": "^1.7.2"

View File

@@ -1,6 +1,6 @@
import { addDays, endOfDay, startOfDay } from "date-fns";
type DueDateFilterKey =
export type DueDateFilterKey =
| "overdue"
| "today"
| "tomorrow"
@@ -9,8 +9,8 @@ type DueDateFilterKey =
| "no-due-date";
export interface DueDateFilter {
startDate?: string;
endDate?: string;
startDate?: Date;
endDate?: Date;
hasNoDueDate?: boolean;
}
@@ -21,35 +21,35 @@ export const convertDueDateFiltersToRanges = (
const today = startOfDay(new Date());
const tomorrow = addDays(today, 1);
const nextWeekEnd = addDays(today, 8); // 7 days ahead
const nextMonthEnd = addDays(today, 31); // 30 days ahead
const nextWeekEnd = addDays(today, 8);
const nextMonthEnd = addDays(today, 31);
return filters.map((filter) => {
switch (filter) {
case "overdue":
return {
endDate: today.toISOString(),
endDate: today,
};
case "today":
return {
startDate: today.toISOString(),
endDate: endOfDay(today).toISOString(),
startDate: today,
endDate: endOfDay(today),
};
case "tomorrow":
return {
startDate: tomorrow.toISOString(),
endDate: endOfDay(tomorrow).toISOString(),
startDate: tomorrow,
endDate: endOfDay(tomorrow),
};
case "next-week": {
return {
startDate: today.toISOString(),
endDate: nextWeekEnd.toISOString(),
startDate: today,
endDate: nextWeekEnd,
};
}
case "next-month": {
return {
startDate: nextWeekEnd.toISOString(),
endDate: nextMonthEnd.toISOString(),
startDate: nextWeekEnd,
endDate: nextMonthEnd,
};
}
case "no-due-date":

View File

@@ -2,3 +2,4 @@ export * from "./generateUID";
export * from "./generateSlug";
export * from "./subscriptions";
export * from "./email";
export * from "./dueDateFilters";

2514
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff