feat: monorepo

This commit is contained in:
Henry
2024-12-12 14:34:10 +00:00
parent b8eed7a90c
commit 0c8d17dce5
370 changed files with 10280 additions and 39805 deletions

View File

@@ -1,26 +1,11 @@
# Since the ".env" file is gitignored, you can use the ".env.example" file to
# build a new ".env" file when you clone the repo. Keep this file up-to-date
# when you add new variables to `.env`.
POSTGRES_URL=
WEBSITE_URL=
# This file will be committed to version control, so make sure not to have any
# secrets in it. If you are cloning this repo, create a copy of this file named
# ".env" and populate it with your secrets.
EMAIL_FROM=
EMAIL_URL=
EMAIL_TOKEN=
# When adding additional environment variables, the schema in "/src/env.mjs"
# should be updated accordingly.
# Drizzle
# Get the Database URL from the "prisma" dropdown selector in PlanetScale.
# Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}"
DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'
# Next Auth
# You can generate a new secret on the command line with:
# openssl rand -base64 32
# https://next-auth.js.org/configuration/options#secret
# NEXTAUTH_SECRET=""
NEXTAUTH_URL="http://localhost:3000"
# Next Auth Discord Provider
DISCORD_CLIENT_ID=""
DISCORD_CLIENT_SECRET=""
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
NEXT_PUBLIC_SUPABASE_AUTH_COOKIE_NAME=
SUPABASE_SERVICE_API_KEY=

View File

@@ -1,36 +0,0 @@
/** @type {import("eslint").Linter.Config} */
const config = {
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
plugins: ["@typescript-eslint"],
extends: [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended-type-checked",
"plugin:@typescript-eslint/stylistic-type-checked",
],
rules: {
// These opinionated rules are enabled in stylistic-type-checked above.
// Feel free to reconfigure them to your own preference.
"@typescript-eslint/array-type": "off",
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/consistent-type-imports": [
"warn",
{
prefer: "type-imports",
fixStyle: "inline-type-imports",
},
],
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-misused-promises": [
2,
{
checksVoidReturn: { attributes: false },
},
],
},
};
module.exports = config;

27
.gitignore vendored
View File

@@ -1,24 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
**/node_modules/
/.pnp
node_modules
.pnp
.pnp.js
# testing
/coverage
# database
/prisma/db.sqlite
/prisma/db.sqlite-journal
coverage
# next.js
/.next/
/out/
.next/
out/
next-env.d.ts
# nitro
.nitro/
.output/
# production
/build
build
# misc
.DS_Store
@@ -31,7 +31,6 @@ yarn-error.log*
.pnpm-debug.log*
# local env files
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
.env
.env*.local
@@ -39,4 +38,8 @@ yarn-error.log*
.vercel
# typescript
*.tsbuildinfo
dist/
.cache
# turbo
.turbo

4
.npmrc Normal file
View File

@@ -0,0 +1,4 @@
node-linker=hoisted
link-workspace-packages=true
shamefully-hoist=true
hoist=true

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
20.18

8
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"yoavbls.pretty-ts-errors",
"bradlc.vscode-tailwindcss"
]
}

17
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js",
"type": "node-terminal",
"request": "launch",
"command": "pnpm dev",
"cwd": "${workspaceFolder}/apps/nextjs",
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true,
"sourceMapPathOverrides": {
"/turbopack/[project]/*": "${webRoot}/*"
}
}
]
}

27
.vscode/settings.json vendored
View File

@@ -1,6 +1,29 @@
{
"spellright.addToSystemDictionary": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript,typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": true,
"eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }],
"eslint.runtime": "node",
"eslint.workingDirectories": [
{ "pattern": "apps/*/" },
{ "pattern": "packages/*/" },
{ "pattern": "tooling/*/" }
],
"prettier.ignorePath": ".gitignore",
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
],
"tailwindCSS.experimental.configFile": "./tooling/tailwind/web.ts",
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.preferences.autoImportFileExcludePatterns": [
"next/router.d.ts",
"next/dist/client/router.d.ts"
],
"typescript.tsdk": "node_modules/typescript/lib"
}

14
apps/web/eslint.config.js Normal file
View File

@@ -0,0 +1,14 @@
import baseConfig, { restrictEnvAccess } from "@kan/eslint-config/base";
import nextjsConfig from "@kan/eslint-config/nextjs";
import reactConfig from "@kan/eslint-config/react";
/** @type {import('typescript-eslint').Config} */
export default [
{
ignores: [".next/**"],
},
...baseConfig,
...reactConfig,
...nextjsConfig,
...restrictEnvAccess,
];

19
apps/web/next.config.js Normal file
View File

@@ -0,0 +1,19 @@
import { fileURLToPath } from "url";
import createJiti from "jiti";
// Import env files to validate at build time. Use jiti so we can load .ts files in here.
createJiti(fileURLToPath(import.meta.url))("./src/env");
/** @type {import("next").NextConfig} */
const config = {
reactStrictMode: true,
/** Enables hot reloading for local packages without a build step */
transpilePackages: ["@kan/api", "@kan/db"],
/** We already do linting and typechecking as separate tasks in CI */
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
};
export default config;

62
apps/web/package.json Normal file
View File

@@ -0,0 +1,62 @@
{
"name": "@kan/web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "pnpm with-env next build",
"clean": "git clean -xdf .cache .next .turbo node_modules",
"dev": "pnpm with-env next dev",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"start": "pnpm with-env next start",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@headlessui/react": "^2.2.0",
"@hookform/resolvers": "^3.3.4",
"@kan/api": "workspace:*",
"@kan/supabase": "workspace:^",
"@kan/utils": "workspace:^",
"@t3-oss/env-nextjs": "^0.11.1",
"@tanstack/react-query": "catalog:",
"@trpc/client": "catalog:",
"@trpc/next": "^11.0.0-rc.660",
"@trpc/react-query": "catalog:",
"@trpc/server": "catalog:",
"date-fns": "^4.1.0",
"geist": "^1.3.1",
"js-cookie": "^3.0.5",
"next": "^14.2.15",
"nextjs-cors": "^2.2.0",
"react": "catalog:react18",
"react-beautiful-dnd": "^13.1.1",
"react-contenteditable": "^3.3.7",
"react-dom": "catalog:react18",
"react-hook-form": "^7.51.1",
"react-icons": "^4.12.0",
"react-lottie-player": "^1.5.5",
"superjson": "2.2.1",
"tailwind-merge": "^2.5.2",
"zod": "catalog:"
},
"devDependencies": {
"@kan/eslint-config": "workspace:*",
"@kan/prettier-config": "workspace:*",
"@kan/tailwind-config": "workspace:*",
"@kan/tsconfig": "workspace:*",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20.17.7",
"@types/react": "catalog:react18",
"@types/react-beautiful-dnd": "^13.1.7",
"@types/react-dom": "catalog:react18",
"dotenv-cli": "^7.4.4",
"eslint": "catalog:",
"jiti": "^1.21.6",
"prettier": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:"
},
"prettier": "@kan/prettier-config"
}

View File

@@ -0,0 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
},
};

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 400 KiB

After

Width:  |  Height:  |  Size: 400 KiB

View File

Before

Width:  |  Height:  |  Size: 433 KiB

After

Width:  |  Height:  |  Size: 433 KiB

View File

@@ -1,10 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { api } from "~/utils/api";
import { FaGoogle } from "react-icons/fa";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import LoadingSpinner from "~/components/LoadingSpinner";
import { api } from "~/utils/api";
interface FormValues {
email: string;
@@ -67,7 +67,7 @@ export function Auth({ setIsMagicLinkSent }: AuthProps) {
{...register("email", { required: true })}
placeholder="Enter your email address"
autoComplete="email"
className=" block w-full rounded-md border-0 bg-dark-500 bg-white/5 py-2 text-dark-1000 shadow-sm ring-1 ring-inset ring-dark-600 focus:ring-inset focus:ring-dark-800 sm:text-sm sm:leading-6"
className="block w-full rounded-md border-0 bg-dark-500 bg-white/5 py-2 text-dark-1000 shadow-sm ring-1 ring-inset ring-dark-600 focus:ring-inset focus:ring-dark-800 sm:text-sm sm:leading-6"
/>
{!loginWithEmail.error && !loginWithOAuth.error && errors.email && (
<p className="mt-2 text-xs text-red-400">

View File

@@ -1,4 +1,5 @@
import { twMerge } from "tailwind-merge";
import { getInitialsFromName, inferInitialsFromEmail } from "~/utils/helpers";
const Avatar = ({

View File

@@ -1,5 +1,6 @@
import Link from "next/link";
import { twMerge } from "tailwind-merge";
import LoadingSpinner from "./LoadingSpinner";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {

View File

@@ -1,5 +1,5 @@
import { Fragment, useState } from "react";
import { Menu, Transition } from "@headlessui/react";
import { Fragment, useState } from "react";
import { twMerge } from "tailwind-merge";
interface Item {

View File

@@ -1,7 +1,8 @@
import Link from "next/link";
import { api } from "~/utils/api";
import SideNavigation from "./SideNavigation";
import FeedbackButton from "./FeedbackButton";
import SideNavigation from "./SideNavigation";
export default function Dashboard(props: { children: React.ReactNode }) {
const { data, isLoading } = api.auth.getUser.useQuery();

View File

@@ -1,5 +1,6 @@
import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react";
import { Fragment } from "react";
export default function Dropdown({
items,
children,

View File

@@ -1,9 +1,9 @@
import { useState } from "react";
import { useTheme } from "~/providers/theme";
import LottieIcon from "~/components/LottieIcon";
import chatIconLight from "~/assets/chat-light.json";
import chatIconDark from "~/assets/chat-dark.json";
import chatIconLight from "~/assets/chat-light.json";
import LottieIcon from "~/components/LottieIcon";
import { useTheme } from "~/providers/theme";
const FeedbackButton: React.FC = () => {
const { activeTheme } = useTheme();

View File

@@ -1,14 +1,13 @@
import { useEffect } from "react";
import { api } from "~/utils/api";
import { useForm } from "react-hook-form";
import { HiXMark } from "react-icons/hi2";
import Button from "~/components/Button";
import Input from "~/components/Input";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { useWorkspace } from "~/providers/workspace";
import Button from "~/components/Button";
import Input from "~/components/Input";
import { api } from "~/utils/api";
interface FormValues {
name: string;

View File

@@ -1,7 +1,6 @@
import { useEffect } from "react";
import { HiOutlineExclamationCircle } from "react-icons/hi2";
import { HiXMark } from "react-icons/hi2";
import { Transition } from "@headlessui/react";
import { useEffect } from "react";
import { HiOutlineExclamationCircle, HiXMark } from "react-icons/hi2";
import { usePopup } from "~/providers/popup";

View File

@@ -1,5 +1,5 @@
import { useState } from "react";
import Link from "next/link";
import { useState } from "react";
import LottieIcon from "~/components/LottieIcon";
@@ -26,7 +26,7 @@ const Button: React.FC<{
href={href}
onMouseEnter={handleMouseEnter}
className={classNames(
current ? "bg-light-200 dark:bg-dark-200" : " dark:bg-dark-50",
current ? "bg-light-200 dark:bg-dark-200" : "dark:bg-dark-50",
"group flex items-center gap-x-3 rounded-md p-1.5 text-sm font-normal leading-6 text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-200",
)}
>

View File

@@ -1,17 +1,15 @@
import { usePathname } from "next/navigation";
import { useTheme } from "~/providers/theme";
import boardsIconDark from "~/assets/boards-dark.json";
import boardsIconLight from "~/assets/boards-light.json";
import membersIconDark from "~/assets/members-dark.json";
import membersIconLight from "~/assets/members-light.json";
import settingsIconDark from "~/assets/settings-dark.json";
import settingsIconLight from "~/assets/settings-light.json";
import ReactiveButton from "~/components/ReactiveButton";
import UserMenu from "~/components/UserMenu";
import WorkspaceMenu from "~/components/WorkspaceMenu";
import { useTheme } from "~/providers/theme";
interface SideNavigationProps {
user: UserType;

View File

@@ -1,9 +1,11 @@
import { useRouter } from "next/navigation";
import { Fragment } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { Menu, Transition } from "@headlessui/react";
import { Fragment } from "react";
import { useTheme } from "~/providers/theme";
import createClient from "~/utils/supabase/client";
interface UserMenuProps {
imageUrl: string | undefined;
email: string;
@@ -126,7 +128,7 @@ export default function UserMenu({
<Menu.Item>
<button
onClick={handleLogout}
className=" flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
>
Logout
</button>

View File

@@ -1,11 +1,10 @@
import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react";
import { Fragment } from "react";
import { HiCheck } from "react-icons/hi2";
import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/providers/workspace";
import { HiCheck } from "react-icons/hi2";
export default function WorkspaceMenu() {
const { workspace, isLoading, availableWorkspaces, switchWorkspace } =
useWorkspace();

View File

@@ -1,5 +1,5 @@
import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { Fragment } from "react";
import { useModal } from "~/providers/modal";

37
apps/web/src/env.ts Normal file
View File

@@ -0,0 +1,37 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { vercel } from "@t3-oss/env-nextjs/presets";
import { z } from "zod";
export const env = createEnv({
extends: [vercel()],
shared: {
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
},
/**
* Specify your server-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
*/
server: {
POSTGRES_URL: z.string().url(),
},
/**
* Specify your client-side environment variables schema here.
* For them to be exposed to the client, prefix them with `NEXT_PUBLIC_`.
*/
client: {
NEXT_PUBLIC_SUPABASE_AUTH_COOKIE_NAME: z.string(),
},
/**
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
*/
experimental__runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
NEXT_PUBLIC_SUPABASE_AUTH_COOKIE_NAME:
process.env.NEXT_PUBLIC_SUPABASE_AUTH_COOKIE_NAME,
},
skipValidation:
!!process.env.CI || process.env.npm_lifecycle_event === "lint",
});

View File

@@ -1,10 +1,10 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { type EmailOtpType } from "@supabase/supabase-js";
import { type NextRequest, NextResponse } from "next/server";
import { createNextClient } from "~/utils/supabase/api";
import * as userRepo from "~/server/db/repository/user.repo";
import * as memberRepo from "~/server/db/repository/member.repo";
import * as memberRepo from "@kan/db/repository/member.repo";
import * as userRepo from "@kan/db/repository/user.repo";
import { createNextClient } from "@kan/supabase/clients";
export default async function handler(req: NextRequest) {
if (req.method !== "GET") {

View File

@@ -1,9 +1,8 @@
import { type NextRequest } from "next/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { env } from "~/env.mjs";
import { appRouter } from "~/server/api/root";
import { createTRPCContext } from "~/server/api/trpc";
import { appRouter } from "@kan/api/root";
import { createTRPCContext } from "@kan/api/trpc";
export default async function handler(req: NextRequest) {
return fetchRequestHandler({
@@ -12,7 +11,7 @@ export default async function handler(req: NextRequest) {
req,
createContext: createTRPCContext,
onError:
env.NODE_ENV === "development"
process.env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,

View File

@@ -1,11 +1,12 @@
import { type NextApiRequest, type NextApiResponse } from "next";
import cors from "nextjs-cors";
import { env } from "~/env.mjs";
import { appRouter } from "~/server/api/root";
import { createRESTContext } from "~/server/api/trpc";
import { createOpenApiNextHandler } from "trpc-to-openapi";
import { appRouter } from "@kan/api";
import { createRESTContext } from "@kan/api/trpc";
import { env } from "~/env";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,

View File

@@ -1,6 +1,6 @@
import { type NextApiRequest, type NextApiResponse } from "next";
import { openApiDocument } from "~/server/openapi";
import { openApiDocument } from "@kan/api/openapi";
const handler = (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).send(openApiDocument);

View File

@@ -1,13 +1,5 @@
import React, {
createContext,
useContext,
useState,
type ReactNode,
} from "react";
import { api } from "~/utils/api";
import { generateUID } from "~/utils/generateUID";
import { usePopup } from "~/providers/popup";
import type { ReactNode } from "react";
import React, { createContext, useContext, useState } from "react";
import {
type GetBoardByIdOutput,
@@ -15,7 +7,11 @@ import {
type NewListInput,
type ReorderCardInput,
type ReorderListInput,
} from "~/types/router.types";
} from "@kan/api/types";
import { generateUID } from "@kan/utils";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
interface BoardContextProps {
boardData: GetBoardByIdOutput;

View File

@@ -1,16 +1,18 @@
import type { CreateTRPCClientOptions, TRPCLink } from "@trpc/client";
import { httpBatchLink, loggerLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import superjson from "superjson";
import { type AppRouter } from "@kan/api/root";
/**
* This is the client-side entrypoint for your tRPC API. It is used to create the `api` object which
* contains the Next.js App-wrapper, as well as your type-safe React Query hooks.
*
* We also create a few inference helpers for input and output types.
*/
import { httpBatchLink, loggerLink, type TRPCLink } from "@trpc/client";
import { observable } from "@trpc/server/observable";
import { createTRPCNext } from "@trpc/next";
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
import superjson from "superjson";
import { type AppRouter } from "~/server/api/root";
const authLink: TRPCLink<AppRouter> = () => {
return ({ next, op }) => {
@@ -40,15 +42,10 @@ const getBaseUrl = () => {
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};
/** A set of type-safe react-query hooks for your tRPC API. */
// @ts-expect-error
export const api = createTRPCNext<AppRouter>({
config() {
return {
/**
* Links used to determine request flow from client to server.
*
* @see https://trpc.io/docs/links
*/
links: [
loggerLink({
enabled: (opts) =>
@@ -57,24 +54,13 @@ export const api = createTRPCNext<AppRouter>({
}),
authLink,
httpBatchLink({
/**
* Transformer used for data de-serialization from the server.
*
* @see https://trpc.io/docs/data-transformers
*/
transformer: superjson,
url: `${getBaseUrl()}/api/trpc`,
transformer: superjson,
}),
],
};
},
/**
* Whether tRPC should await queries when server rendering pages.
*
* @see https://trpc.io/docs/nextjs#ssr-boolean-default-false
*/
ssr: false,
transformer: superjson,
});
/**

View File

@@ -1,5 +1,6 @@
import { createBrowserClient } from "@supabase/ssr";
import { type Database } from "~/types/database.types";
import { type Database } from "@kan/db/types/database.types";
export default function createClient() {
const supabase = createBrowserClient<Database>(

View File

@@ -1,24 +1,22 @@
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import {
HiXMark,
HiOutlineBarsArrowDown,
HiOutlineBarsArrowUp,
HiXMark,
} from "react-icons/hi2";
import { type NewCardInput } from "@kan/api/types";
import Button from "~/components/Button";
import CheckboxDropdown from "~/components/CheckboxDropdown";
import Input from "~/components/Input";
import Toggle from "~/components/Toggle";
import { useBoard } from "~/providers/board";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
import { type NewCardInput } from "~/types/router.types";
type NewCardFormInput = NewCardInput & {
isCreateAnotherEnabled: boolean;
};
@@ -168,7 +166,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
<div>
<Input id="title" placeholder="Card title" {...register("title")} />
</div>
<div className="mt-2 ">
<div className="mt-2">
<Input
placeholder="Add description..."
onChange={(e) => setValue("description", e.target.value)}

View File

@@ -1,19 +1,16 @@
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { HiXMark } from "react-icons/hi2";
import { useBoard } from "~/providers/board";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
import { type NewListInput } from "@kan/api/types";
import Button from "~/components/Button";
import Input from "~/components/Input";
import Toggle from "~/components/Toggle";
import { type NewListInput } from "~/types/router.types";
import { useBoard } from "~/providers/board";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
type NewListFormInput = NewListInput & {
isCreateAnotherEnabled: boolean;

View File

@@ -1,35 +1,30 @@
import { useState, useEffect } from "react";
import type { DropResult } from "react-beautiful-dnd";
import Link from "next/link";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { HiOutlinePlusSmall } from "react-icons/hi2";
import {
DragDropContext,
Droppable,
type DropResult,
Draggable,
} from "react-beautiful-dnd";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import { useForm } from "react-hook-form";
import { HiOutlinePlusSmall } from "react-icons/hi2";
import { api } from "~/utils/api";
import { formatToArray } from "~/utils/helpers";
import { type UpdateBoardInput } from "@kan/api/types";
import Modal from "~/components/modal";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import { PageHead } from "~/components/PageHead";
import PatternedBackground from "~/components/PatternedBackground";
import { useBoard } from "~/providers/board";
import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/providers/workspace";
import { PageHead } from "~/components/PageHead";
import Modal from "~/components/modal";
import PatternedBackground from "~/components/PatternedBackground";
import { api } from "~/utils/api";
import { formatToArray } from "~/utils/helpers";
import BoardDropdown from "./components/BoardDropdown";
import { DeleteBoardConfirmation } from "./components/DeleteBoardConfirmation";
import { DeleteListConfirmation } from "./components/DeleteListConfirmation";
import Filters from "./components/Filters";
import List from "./components/List";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import { NewCardForm } from "./components/NewCardForm";
import { NewListForm } from "./components/NewListForm";
import Filters from "./components/Filters";
import { type UpdateBoardInput } from "~/types/router.types";
type PublicListId = string;
@@ -180,7 +175,7 @@ export default function BoardPage() {
</div>
</div>
<div className="scrollbar-w-none z-0 flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
<div className="scrollbar-w-none scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] z-0 flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
{isLoading ? (
<div className="ml-[2rem] flex">
<div className="0 mr-5 h-[500px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
@@ -215,7 +210,7 @@ export default function BoardPage() {
<div
ref={provided.innerRef}
{...provided.droppableProps}
className="z-10 h-full max-h-[calc(100vh-265px)] min-h-[2rem] overflow-y-auto pr-1 scrollbar scrollbar-track-dark-100 scrollbar-thumb-dark-600 scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px]"
className="scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-w-[8px] z-10 h-full max-h-[calc(100vh-265px)] min-h-[2rem] overflow-y-auto pr-1 scrollbar scrollbar-track-dark-100 scrollbar-thumb-dark-600"
>
{list.cards?.map((card, index) => (
<Draggable

View File

@@ -1,9 +1,11 @@
import { api } from "~/utils/api";
import { useForm } from "react-hook-form";
import { HiXMark } from "react-icons/hi2";
import { type NewBoardInput } from "@kan/api/types";
import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/providers/workspace";
import { useForm } from "react-hook-form";
import { type NewBoardInput } from "~/types/router.types";
import { api } from "~/utils/api";
export function NewBoardForm() {
const utils = api.useUtils();
@@ -36,7 +38,7 @@ export function NewBoardForm() {
<div className="text-neutral-9000 flex w-full items-center justify-between pb-4 dark:text-dark-1000">
<h2 className="text-sm font-bold">New board</h2>
<button
className="hover:bg-li ght-300 rounded p-1 focus:outline-none dark:hover:bg-dark-300"
className="hover:bg-li ght-300 rounded p-1 focus:outline-none dark:hover:bg-dark-300"
onClick={(e) => {
e.preventDefault();
closeModal();

View File

@@ -1,19 +1,19 @@
import { formatDistanceToNow } from "date-fns";
import {
HiOutlinePencil,
HiOutlineTag,
HiOutlineUserPlus,
HiOutlineUserMinus,
HiOutlineArrowRight,
HiOutlinePlus,
HiOutlineArrowLeft,
HiOutlineArrowRight,
HiOutlinePencil,
HiOutlinePlus,
HiOutlineTag,
HiOutlineUserMinus,
HiOutlineUserPlus,
} from "react-icons/hi2";
import { type GetCardByIdOutput } from "@kan/api/types";
import Avatar from "~/components/Avatar";
import Comment from "./Comment";
import { type GetCardByIdOutput } from "~/types/router.types";
type ActivityType =
NonNullable<GetCardByIdOutput>["activities"][number]["type"];

Some files were not shown because too many files have changed in this diff Show More