Compare commits
6 Commits
chore/upgr
...
fix/api-ke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
902a539d73 | ||
|
|
7fef9be638 | ||
|
|
c7473062aa | ||
|
|
920e9b18a2 | ||
|
|
bcd20b58c6 | ||
|
|
36675604f9 |
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:"
|
||||
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,6 +0,0 @@
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||
await require("pino");
|
||||
await require("next-logger");
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:*",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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":
|
||||
@@ -2,3 +2,4 @@ export * from "./generateUID";
|
||||
export * from "./generateSlug";
|
||||
export * from "./subscriptions";
|
||||
export * from "./email";
|
||||
export * from "./dueDateFilters";
|
||||
|
||||