feat: monorepo
This commit is contained in:
33
.env.example
33
.env.example
@@ -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=
|
||||
|
||||
@@ -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
27
.gitignore
vendored
@@ -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
4
.npmrc
Normal file
@@ -0,0 +1,4 @@
|
||||
node-linker=hoisted
|
||||
link-workspace-packages=true
|
||||
shamefully-hoist=true
|
||||
hoist=true
|
||||
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal 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
17
.vscode/launch.json
vendored
Normal 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
27
.vscode/settings.json
vendored
@@ -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
14
apps/web/eslint.config.js
Normal 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
19
apps/web/next.config.js
Normal 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
62
apps/web/package.json
Normal 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"
|
||||
}
|
||||
5
apps/web/postcss.config.cjs
Normal file
5
apps/web/postcss.config.cjs
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 433 KiB After Width: | Height: | Size: 433 KiB |
@@ -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">
|
||||
@@ -1,4 +1,5 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { getInitialsFromName, inferInitialsFromEmail } from "~/utils/helpers";
|
||||
|
||||
const Avatar = ({
|
||||
@@ -1,5 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import LoadingSpinner from "./LoadingSpinner";
|
||||
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
@@ -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 {
|
||||
@@ -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();
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Fragment } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export default function Dropdown({
|
||||
items,
|
||||
children,
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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";
|
||||
|
||||
@@ -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",
|
||||
)}
|
||||
>
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
@@ -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
37
apps/web/src/env.ts
Normal 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",
|
||||
});
|
||||
@@ -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") {
|
||||
@@ -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}`,
|
||||
@@ -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,
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -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>(
|
||||
@@ -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)}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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();
|
||||
@@ -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
Reference in New Issue
Block a user