diff --git a/.env.example b/.env.example index 56d4230e..c0e41ec1 100644 --- a/.env.example +++ b/.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= diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 79cb5118..00000000 --- a/.eslintrc.cjs +++ /dev/null @@ -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; diff --git a/.gitignore b/.gitignore index f2c14ff9..7c60a2e1 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..f5e4222e --- /dev/null +++ b/.npmrc @@ -0,0 +1,4 @@ +node-linker=hoisted +link-workspace-packages=true +shamefully-hoist=true +hoist=true \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..e8c3ab5f --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.18 \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..fd682ae2 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "yoavbls.pretty-ts-errors", + "bradlc.vscode-tailwindcss" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..67d5d1bf --- /dev/null +++ b/.vscode/launch.json @@ -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": ["/**"], + "sourceMaps": true, + "sourceMapPathOverrides": { + "/turbopack/[project]/*": "${webRoot}/*" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index ab243a37..e3c5d71a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" } \ No newline at end of file diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js new file mode 100644 index 00000000..a06b120c --- /dev/null +++ b/apps/web/eslint.config.js @@ -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, +]; diff --git a/apps/web/next.config.js b/apps/web/next.config.js new file mode 100644 index 00000000..a82d48e1 --- /dev/null +++ b/apps/web/next.config.js @@ -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; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 00000000..7cd6335c --- /dev/null +++ b/apps/web/package.json @@ -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" +} diff --git a/apps/web/postcss.config.cjs b/apps/web/postcss.config.cjs new file mode 100644 index 00000000..ee5f90b3 --- /dev/null +++ b/apps/web/postcss.config.cjs @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + tailwindcss: {}, + }, +}; diff --git a/public/favicon.ico b/apps/web/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to apps/web/public/favicon.ico diff --git a/public/hero-dark.png b/apps/web/public/hero-dark.png similarity index 100% rename from public/hero-dark.png rename to apps/web/public/hero-dark.png diff --git a/public/hero-light.png b/apps/web/public/hero-light.png similarity index 100% rename from public/hero-light.png rename to apps/web/public/hero-light.png diff --git a/src/assets/activity-logs-dark.json b/apps/web/src/assets/activity-logs-dark.json similarity index 100% rename from src/assets/activity-logs-dark.json rename to apps/web/src/assets/activity-logs-dark.json diff --git a/src/assets/activity-logs-light.json b/apps/web/src/assets/activity-logs-light.json similarity index 100% rename from src/assets/activity-logs-light.json rename to apps/web/src/assets/activity-logs-light.json diff --git a/src/assets/board-visibility-dark.json b/apps/web/src/assets/board-visibility-dark.json similarity index 100% rename from src/assets/board-visibility-dark.json rename to apps/web/src/assets/board-visibility-dark.json diff --git a/src/assets/board-visibility-light.json b/apps/web/src/assets/board-visibility-light.json similarity index 100% rename from src/assets/board-visibility-light.json rename to apps/web/src/assets/board-visibility-light.json diff --git a/src/assets/boards-dark.json b/apps/web/src/assets/boards-dark.json similarity index 100% rename from src/assets/boards-dark.json rename to apps/web/src/assets/boards-dark.json diff --git a/src/assets/boards-light.json b/apps/web/src/assets/boards-light.json similarity index 100% rename from src/assets/boards-light.json rename to apps/web/src/assets/boards-light.json diff --git a/src/assets/chat-dark.json b/apps/web/src/assets/chat-dark.json similarity index 100% rename from src/assets/chat-dark.json rename to apps/web/src/assets/chat-dark.json diff --git a/src/assets/chat-light.json b/apps/web/src/assets/chat-light.json similarity index 100% rename from src/assets/chat-light.json rename to apps/web/src/assets/chat-light.json diff --git a/src/assets/comments-dark.json b/apps/web/src/assets/comments-dark.json similarity index 100% rename from src/assets/comments-dark.json rename to apps/web/src/assets/comments-dark.json diff --git a/src/assets/comments-light.json b/apps/web/src/assets/comments-light.json similarity index 100% rename from src/assets/comments-light.json rename to apps/web/src/assets/comments-light.json diff --git a/src/assets/imports-dark.json b/apps/web/src/assets/imports-dark.json similarity index 100% rename from src/assets/imports-dark.json rename to apps/web/src/assets/imports-dark.json diff --git a/src/assets/imports-light.json b/apps/web/src/assets/imports-light.json similarity index 100% rename from src/assets/imports-light.json rename to apps/web/src/assets/imports-light.json diff --git a/src/assets/integrations-dark.json b/apps/web/src/assets/integrations-dark.json similarity index 100% rename from src/assets/integrations-dark.json rename to apps/web/src/assets/integrations-dark.json diff --git a/src/assets/integrations-light.json b/apps/web/src/assets/integrations-light.json similarity index 100% rename from src/assets/integrations-light.json rename to apps/web/src/assets/integrations-light.json diff --git a/src/assets/labels-dark.json b/apps/web/src/assets/labels-dark.json similarity index 100% rename from src/assets/labels-dark.json rename to apps/web/src/assets/labels-dark.json diff --git a/src/assets/labels-light.json b/apps/web/src/assets/labels-light.json similarity index 100% rename from src/assets/labels-light.json rename to apps/web/src/assets/labels-light.json diff --git a/src/assets/members-dark.json b/apps/web/src/assets/members-dark.json similarity index 100% rename from src/assets/members-dark.json rename to apps/web/src/assets/members-dark.json diff --git a/src/assets/members-light.json b/apps/web/src/assets/members-light.json similarity index 100% rename from src/assets/members-light.json rename to apps/web/src/assets/members-light.json diff --git a/src/assets/settings-dark.json b/apps/web/src/assets/settings-dark.json similarity index 100% rename from src/assets/settings-dark.json rename to apps/web/src/assets/settings-dark.json diff --git a/src/assets/settings-light.json b/apps/web/src/assets/settings-light.json similarity index 100% rename from src/assets/settings-light.json rename to apps/web/src/assets/settings-light.json diff --git a/src/assets/templates-dark.json b/apps/web/src/assets/templates-dark.json similarity index 100% rename from src/assets/templates-dark.json rename to apps/web/src/assets/templates-dark.json diff --git a/src/assets/templates-light.json b/apps/web/src/assets/templates-light.json similarity index 100% rename from src/assets/templates-light.json rename to apps/web/src/assets/templates-light.json diff --git a/src/components/AuthForm.tsx b/apps/web/src/components/AuthForm.tsx similarity index 93% rename from src/components/AuthForm.tsx rename to apps/web/src/components/AuthForm.tsx index 3fed355b..16efc148 100644 --- a/src/components/AuthForm.tsx +++ b/apps/web/src/components/AuthForm.tsx @@ -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 && (

diff --git a/src/components/Avatar.tsx b/apps/web/src/components/Avatar.tsx similarity index 99% rename from src/components/Avatar.tsx rename to apps/web/src/components/Avatar.tsx index 03e91eda..c7ce28d4 100644 --- a/src/components/Avatar.tsx +++ b/apps/web/src/components/Avatar.tsx @@ -1,4 +1,5 @@ import { twMerge } from "tailwind-merge"; + import { getInitialsFromName, inferInitialsFromEmail } from "~/utils/helpers"; const Avatar = ({ diff --git a/src/components/Button.tsx b/apps/web/src/components/Button.tsx similarity index 99% rename from src/components/Button.tsx rename to apps/web/src/components/Button.tsx index ff2c1168..312ff266 100644 --- a/src/components/Button.tsx +++ b/apps/web/src/components/Button.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; import { twMerge } from "tailwind-merge"; + import LoadingSpinner from "./LoadingSpinner"; interface ButtonProps extends React.ButtonHTMLAttributes { diff --git a/src/components/CheckboxDropdown.tsx b/apps/web/src/components/CheckboxDropdown.tsx similarity index 100% rename from src/components/CheckboxDropdown.tsx rename to apps/web/src/components/CheckboxDropdown.tsx index 2557e4d1..1011f612 100644 --- a/src/components/CheckboxDropdown.tsx +++ b/apps/web/src/components/CheckboxDropdown.tsx @@ -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 { diff --git a/src/components/Dashboard.tsx b/apps/web/src/components/Dashboard.tsx similarity index 99% rename from src/components/Dashboard.tsx rename to apps/web/src/components/Dashboard.tsx index 970e36d4..87d180e3 100644 --- a/src/components/Dashboard.tsx +++ b/apps/web/src/components/Dashboard.tsx @@ -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(); diff --git a/src/components/Dropdown.tsx b/apps/web/src/components/Dropdown.tsx similarity index 99% rename from src/components/Dropdown.tsx rename to apps/web/src/components/Dropdown.tsx index f6e4b920..e9a7646a 100644 --- a/src/components/Dropdown.tsx +++ b/apps/web/src/components/Dropdown.tsx @@ -1,5 +1,6 @@ -import { Fragment } from "react"; import { Menu, Transition } from "@headlessui/react"; +import { Fragment } from "react"; + export default function Dropdown({ items, children, diff --git a/src/components/FeedbackButton.tsx b/apps/web/src/components/FeedbackButton.tsx similarity index 100% rename from src/components/FeedbackButton.tsx rename to apps/web/src/components/FeedbackButton.tsx index dcf2e6cd..f100f9e3 100644 --- a/src/components/FeedbackButton.tsx +++ b/apps/web/src/components/FeedbackButton.tsx @@ -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(); diff --git a/src/components/Input.tsx b/apps/web/src/components/Input.tsx similarity index 100% rename from src/components/Input.tsx rename to apps/web/src/components/Input.tsx diff --git a/src/components/LoadingSpinner.tsx b/apps/web/src/components/LoadingSpinner.tsx similarity index 100% rename from src/components/LoadingSpinner.tsx rename to apps/web/src/components/LoadingSpinner.tsx diff --git a/src/components/LottieIcon.tsx b/apps/web/src/components/LottieIcon.tsx similarity index 100% rename from src/components/LottieIcon.tsx rename to apps/web/src/components/LottieIcon.tsx diff --git a/src/components/NewWorkspaceForm.tsx b/apps/web/src/components/NewWorkspaceForm.tsx similarity index 99% rename from src/components/NewWorkspaceForm.tsx rename to apps/web/src/components/NewWorkspaceForm.tsx index b4338ef6..609906be 100644 --- a/src/components/NewWorkspaceForm.tsx +++ b/apps/web/src/components/NewWorkspaceForm.tsx @@ -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; diff --git a/src/components/PageHead.tsx b/apps/web/src/components/PageHead.tsx similarity index 100% rename from src/components/PageHead.tsx rename to apps/web/src/components/PageHead.tsx diff --git a/src/components/PatternedBackground.tsx b/apps/web/src/components/PatternedBackground.tsx similarity index 100% rename from src/components/PatternedBackground.tsx rename to apps/web/src/components/PatternedBackground.tsx diff --git a/src/components/Popup.tsx b/apps/web/src/components/Popup.tsx similarity index 96% rename from src/components/Popup.tsx rename to apps/web/src/components/Popup.tsx index d91ef2b0..4e93ae61 100644 --- a/src/components/Popup.tsx +++ b/apps/web/src/components/Popup.tsx @@ -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"; diff --git a/src/components/ReactiveButton.tsx b/apps/web/src/components/ReactiveButton.tsx similarity index 92% rename from src/components/ReactiveButton.tsx rename to apps/web/src/components/ReactiveButton.tsx index 4b689926..89634b96 100644 --- a/src/components/ReactiveButton.tsx +++ b/apps/web/src/components/ReactiveButton.tsx @@ -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", )} > diff --git a/src/components/SideNavigation.tsx b/apps/web/src/components/SideNavigation.tsx similarity index 99% rename from src/components/SideNavigation.tsx rename to apps/web/src/components/SideNavigation.tsx index 254f24c6..284f78a4 100644 --- a/src/components/SideNavigation.tsx +++ b/apps/web/src/components/SideNavigation.tsx @@ -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; diff --git a/src/components/Toggle.tsx b/apps/web/src/components/Toggle.tsx similarity index 100% rename from src/components/Toggle.tsx rename to apps/web/src/components/Toggle.tsx diff --git a/src/components/UserMenu.tsx b/apps/web/src/components/UserMenu.tsx similarity index 97% rename from src/components/UserMenu.tsx rename to apps/web/src/components/UserMenu.tsx index f87cf560..27e53de0 100644 --- a/src/components/UserMenu.tsx +++ b/apps/web/src/components/UserMenu.tsx @@ -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({ diff --git a/src/components/WorkspaceMenu.tsx b/apps/web/src/components/WorkspaceMenu.tsx similarity index 99% rename from src/components/WorkspaceMenu.tsx rename to apps/web/src/components/WorkspaceMenu.tsx index 7cddf84c..a9e887f5 100644 --- a/src/components/WorkspaceMenu.tsx +++ b/apps/web/src/components/WorkspaceMenu.tsx @@ -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(); diff --git a/src/components/modal.tsx b/apps/web/src/components/modal.tsx similarity index 100% rename from src/components/modal.tsx rename to apps/web/src/components/modal.tsx index 7925e8ed..54e7bb40 100644 --- a/src/components/modal.tsx +++ b/apps/web/src/components/modal.tsx @@ -1,5 +1,5 @@ -import { Fragment } from "react"; import { Dialog, Transition } from "@headlessui/react"; +import { Fragment } from "react"; import { useModal } from "~/providers/modal"; diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts new file mode 100644 index 00000000..b09ee9dc --- /dev/null +++ b/apps/web/src/env.ts @@ -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", +}); diff --git a/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx similarity index 100% rename from src/pages/_app.tsx rename to apps/web/src/pages/_app.tsx diff --git a/src/pages/api/auth/confirm.ts b/apps/web/src/pages/api/auth/confirm.ts similarity index 87% rename from src/pages/api/auth/confirm.ts rename to apps/web/src/pages/api/auth/confirm.ts index 9263ac04..f17e8023 100644 --- a/src/pages/api/auth/confirm.ts +++ b/apps/web/src/pages/api/auth/confirm.ts @@ -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") { diff --git a/src/pages/api/trpc/[trpc].ts b/apps/web/src/pages/api/trpc/[trpc].ts similarity index 78% rename from src/pages/api/trpc/[trpc].ts rename to apps/web/src/pages/api/trpc/[trpc].ts index 99293098..3d6dabc5 100644 --- a/src/pages/api/trpc/[trpc].ts +++ b/apps/web/src/pages/api/trpc/[trpc].ts @@ -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 ?? ""}: ${error.message}`, diff --git a/src/pages/api/v1/[...trpc].ts b/apps/web/src/pages/api/v1/[...trpc].ts similarity index 84% rename from src/pages/api/v1/[...trpc].ts rename to apps/web/src/pages/api/v1/[...trpc].ts index 17c118a9..601ee6cc 100644 --- a/src/pages/api/v1/[...trpc].ts +++ b/apps/web/src/pages/api/v1/[...trpc].ts @@ -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, diff --git a/src/pages/api/v1/openapi.json.ts b/apps/web/src/pages/api/v1/openapi.json.ts similarity index 79% rename from src/pages/api/v1/openapi.json.ts rename to apps/web/src/pages/api/v1/openapi.json.ts index 07b03b22..4c823a2a 100644 --- a/src/pages/api/v1/openapi.json.ts +++ b/apps/web/src/pages/api/v1/openapi.json.ts @@ -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); diff --git a/src/pages/boards/[...boardId]/index.tsx b/apps/web/src/pages/boards/[...boardId]/index.tsx similarity index 100% rename from src/pages/boards/[...boardId]/index.tsx rename to apps/web/src/pages/boards/[...boardId]/index.tsx diff --git a/src/pages/boards/index.tsx b/apps/web/src/pages/boards/index.tsx similarity index 100% rename from src/pages/boards/index.tsx rename to apps/web/src/pages/boards/index.tsx diff --git a/src/pages/cards/[cardId]/index.tsx b/apps/web/src/pages/cards/[cardId]/index.tsx similarity index 100% rename from src/pages/cards/[cardId]/index.tsx rename to apps/web/src/pages/cards/[cardId]/index.tsx diff --git a/src/pages/index.tsx b/apps/web/src/pages/index.tsx similarity index 100% rename from src/pages/index.tsx rename to apps/web/src/pages/index.tsx diff --git a/src/pages/login/index.tsx b/apps/web/src/pages/login/index.tsx similarity index 100% rename from src/pages/login/index.tsx rename to apps/web/src/pages/login/index.tsx diff --git a/src/pages/members/index.tsx b/apps/web/src/pages/members/index.tsx similarity index 100% rename from src/pages/members/index.tsx rename to apps/web/src/pages/members/index.tsx diff --git a/src/pages/settings/index.tsx b/apps/web/src/pages/settings/index.tsx similarity index 100% rename from src/pages/settings/index.tsx rename to apps/web/src/pages/settings/index.tsx diff --git a/src/pages/signup/index.tsx b/apps/web/src/pages/signup/index.tsx similarity index 100% rename from src/pages/signup/index.tsx rename to apps/web/src/pages/signup/index.tsx diff --git a/src/providers/board.tsx b/apps/web/src/providers/board.tsx similarity index 96% rename from src/providers/board.tsx rename to apps/web/src/providers/board.tsx index 7cc7af4a..1adceb2f 100644 --- a/src/providers/board.tsx +++ b/apps/web/src/providers/board.tsx @@ -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; diff --git a/src/providers/modal.tsx b/apps/web/src/providers/modal.tsx similarity index 100% rename from src/providers/modal.tsx rename to apps/web/src/providers/modal.tsx diff --git a/src/providers/popup.tsx b/apps/web/src/providers/popup.tsx similarity index 100% rename from src/providers/popup.tsx rename to apps/web/src/providers/popup.tsx diff --git a/src/providers/theme.tsx b/apps/web/src/providers/theme.tsx similarity index 100% rename from src/providers/theme.tsx rename to apps/web/src/providers/theme.tsx diff --git a/src/providers/workspace.tsx b/apps/web/src/providers/workspace.tsx similarity index 100% rename from src/providers/workspace.tsx rename to apps/web/src/providers/workspace.tsx diff --git a/src/styles/globals.css b/apps/web/src/styles/globals.css similarity index 100% rename from src/styles/globals.css rename to apps/web/src/styles/globals.css diff --git a/src/types/contenteditable.d.ts b/apps/web/src/types/contenteditable.d.ts similarity index 100% rename from src/types/contenteditable.d.ts rename to apps/web/src/types/contenteditable.d.ts diff --git a/src/utils/api.ts b/apps/web/src/utils/api.ts similarity index 75% rename from src/utils/api.ts rename to apps/web/src/utils/api.ts index f2490703..1e16e22f 100644 --- a/src/utils/api.ts +++ b/apps/web/src/utils/api.ts @@ -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 = () => { 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({ 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({ }), 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, }); /** diff --git a/src/utils/helpers.ts b/apps/web/src/utils/helpers.ts similarity index 100% rename from src/utils/helpers.ts rename to apps/web/src/utils/helpers.ts diff --git a/src/utils/supabase/client.ts b/apps/web/src/utils/supabase/client.ts similarity index 80% rename from src/utils/supabase/client.ts rename to apps/web/src/utils/supabase/client.ts index f16a20d4..7e46fa62 100644 --- a/src/utils/supabase/client.ts +++ b/apps/web/src/utils/supabase/client.ts @@ -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( diff --git a/src/views/auth/login/index.tsx b/apps/web/src/views/auth/login/index.tsx similarity index 100% rename from src/views/auth/login/index.tsx rename to apps/web/src/views/auth/login/index.tsx diff --git a/src/views/auth/signup/index.tsx b/apps/web/src/views/auth/signup/index.tsx similarity index 100% rename from src/views/auth/signup/index.tsx rename to apps/web/src/views/auth/signup/index.tsx diff --git a/src/views/board/components/BoardDropdown.tsx b/apps/web/src/views/board/components/BoardDropdown.tsx similarity index 100% rename from src/views/board/components/BoardDropdown.tsx rename to apps/web/src/views/board/components/BoardDropdown.tsx diff --git a/src/views/board/components/DeleteBoardConfirmation.tsx b/apps/web/src/views/board/components/DeleteBoardConfirmation.tsx similarity index 100% rename from src/views/board/components/DeleteBoardConfirmation.tsx rename to apps/web/src/views/board/components/DeleteBoardConfirmation.tsx diff --git a/src/views/board/components/DeleteListConfirmation.tsx b/apps/web/src/views/board/components/DeleteListConfirmation.tsx similarity index 100% rename from src/views/board/components/DeleteListConfirmation.tsx rename to apps/web/src/views/board/components/DeleteListConfirmation.tsx diff --git a/src/views/board/components/Filters.tsx b/apps/web/src/views/board/components/Filters.tsx similarity index 100% rename from src/views/board/components/Filters.tsx rename to apps/web/src/views/board/components/Filters.tsx diff --git a/src/views/board/components/List.tsx b/apps/web/src/views/board/components/List.tsx similarity index 100% rename from src/views/board/components/List.tsx rename to apps/web/src/views/board/components/List.tsx diff --git a/src/views/board/components/NewCardForm.tsx b/apps/web/src/views/board/components/NewCardForm.tsx similarity index 99% rename from src/views/board/components/NewCardForm.tsx rename to apps/web/src/views/board/components/NewCardForm.tsx index 2591b046..420d2726 100644 --- a/src/views/board/components/NewCardForm.tsx +++ b/apps/web/src/views/board/components/NewCardForm.tsx @@ -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) {

-
+
setValue("description", e.target.value)} diff --git a/src/views/board/components/NewListForm.tsx b/apps/web/src/views/board/components/NewListForm.tsx similarity index 98% rename from src/views/board/components/NewListForm.tsx rename to apps/web/src/views/board/components/NewListForm.tsx index 310fd701..2a11229d 100644 --- a/src/views/board/components/NewListForm.tsx +++ b/apps/web/src/views/board/components/NewListForm.tsx @@ -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; diff --git a/src/views/board/index.tsx b/apps/web/src/views/board/index.tsx similarity index 94% rename from src/views/board/index.tsx rename to apps/web/src/views/board/index.tsx index 4cf7ef77..8912ac5b 100644 --- a/src/views/board/index.tsx +++ b/apps/web/src/views/board/index.tsx @@ -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() {
-
+
{isLoading ? (
@@ -215,7 +210,7 @@ export default function BoardPage() {
{list.cards?.map((card, index) => (

New board