feat: convert to pages router

This commit is contained in:
Henry
2024-04-13 16:21:38 +01:00
parent 6178b4f309
commit e048e243d4
74 changed files with 518 additions and 575 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -16,23 +16,21 @@
"src/email" "src/email"
], ],
"dependencies": { "dependencies": {
"@auth/drizzle-adapter": "^0.3.12",
"@headlessui/react": "^1.7.17", "@headlessui/react": "^1.7.17",
"@planetscale/database": "^1.11.0",
"@react-email/render": "^0.0.10", "@react-email/render": "^0.0.10",
"@supabase/ssr": "^0.3.0",
"@supabase/supabase-js": "^2.42.0", "@supabase/supabase-js": "^2.42.0",
"@t3-oss/env-nextjs": "^0.7.0", "@t3-oss/env-nextjs": "^0.9.2",
"@tailwindcss/forms": "^0.5.6", "@tailwindcss/forms": "^0.5.6",
"@tanstack/react-query": "^4.32.6", "@tanstack/react-query": "^5.25.0",
"@trpc/client": "^10.37.1", "@trpc/client": "next",
"@trpc/next": "^10.37.1", "@trpc/next": "next",
"@trpc/react-query": "^10.37.1", "@trpc/react-query": "next",
"@trpc/server": "^10.37.1", "@trpc/server": "next",
"@vercel/postgres": "^0.7.2", "@vercel/postgres": "^0.7.2",
"drizzle-orm": "^0.28.5", "drizzle-orm": "^0.28.5",
"formik": "^2.4.5", "formik": "^2.4.5",
"next": "^13.5.4", "next": "^14.1.3",
"next-auth": "0.0.0-manual.cfb42ae7",
"postgres": "^3.4.4", "postgres": "^3.4.4",
"react": "18.2.0", "react": "18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
@@ -45,27 +43,27 @@
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@types/eslint": "^8.44.2", "@types/eslint": "^8.56.2",
"@types/node": "^18.16.0", "@types/node": "^20.11.20",
"@types/react": "^18.2.20", "@types/react": "^18.2.57",
"@types/react-beautiful-dnd": "^13.1.7", "@types/react-beautiful-dnd": "^13.1.7",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^6.3.0", "@typescript-eslint/parser": "^7.1.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"dotenv-cli": "^7.3.0", "dotenv-cli": "^7.3.0",
"drizzle-kit": "^0.19.13", "drizzle-kit": "^0.19.13",
"eslint": "^8.47.0", "eslint": "^8.57.0",
"eslint-config-next": "^13.5.4", "eslint-config-next": "^14.1.3",
"mysql2": "^3.6.1", "pg": "^8.11.3",
"postcss": "^8.4.27", "postcss": "^8.4.34",
"prettier": "^3.0.0", "prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.1", "prettier-plugin-tailwindcss": "^0.5.11",
"supabase": "^1.153.4", "supabase": "^1.153.4",
"tailwind": "^4.0.0", "tailwind": "^4.0.0",
"tailwind-scrollbar": "^3.0.5", "tailwind-scrollbar": "^3.0.5",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.4.1",
"typescript": "^5.1.6" "typescript": "^5.4.2"
}, },
"ct3aMetadata": { "ct3aMetadata": {
"initVersion": "7.22.0" "initVersion": "7.22.0"

View File

@@ -1,5 +0,0 @@
export { GET, POST } from "~/server/auth";
export const runtime = "edge";
export const preferredRegion = 'lhr1';
export const dynamic = 'force-dynamic'

View File

@@ -1,28 +0,0 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { type NextRequest } from "next/server";
import { env } from "~/env.mjs";
import { appRouter } from "~/server/api/root";
import { createTRPCContext } from "~/server/api/trpc";
const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createTRPCContext({ req }),
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});
export { handler as GET, handler as POST };
export const runtime = "edge";
export const preferredRegion = 'lhr1';
export const dynamic = 'force-dynamic'

View File

@@ -1,10 +0,0 @@
import { WorkspaceProvider } from "~/app/providers/workspace";
import Dashboard from "~/app/components/dashboard";
export default function Layout(props: { children: React.ReactNode }) {
return (
<WorkspaceProvider>
<Dashboard>{props.children}</Dashboard>
</WorkspaceProvider>
);
}

View File

@@ -1,10 +0,0 @@
import { WorkspaceProvider } from "~/app/providers/workspace";
import Dashboard from "~/app/components/dashboard";
export default function Layout(props: { children: React.ReactNode }) {
return (
<WorkspaceProvider>
<Dashboard>{props.children}</Dashboard>;
</WorkspaceProvider>
);
}

View File

@@ -1,41 +0,0 @@
import "~/styles/globals.css";
import { Plus_Jakarta_Sans } from "next/font/google";
import { headers } from "next/headers";
import { TRPCReactProvider } from "~/trpc/react";
import { ModalProvider } from "~/app/providers/modal";
import { BoardProvider } from "~/app/providers/board";
import { ThemeProvider } from "./providers/theme";
const jakarta = Plus_Jakarta_Sans({
subsets: ["latin"],
display: "swap",
variable: "--font-plus-jakarta-sans",
});
export const metadata = {
title: "Kan",
description: "The open source Trello alternative",
icons: [{ rel: "icon", url: "/favicon.ico" }],
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${jakarta.variable}`}>
<body className="h-screen overflow-hidden font-sans">
<ThemeProvider>
<TRPCReactProvider headers={headers()}>
<ModalProvider>
<BoardProvider>{children}</BoardProvider>
</ModalProvider>
</TRPCReactProvider>
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,10 +0,0 @@
import { WorkspaceProvider } from "~/app/providers/workspace";
import Dashboard from "~/app/components/dashboard";
export default function Layout(props: { children: React.ReactNode }) {
return (
<WorkspaceProvider>
<Dashboard>{props.children}</Dashboard>;
</WorkspaceProvider>
);
}

View File

@@ -1,3 +0,0 @@
export default function Home() {
return <main></main>;
}

View File

@@ -1,10 +0,0 @@
import { WorkspaceProvider } from "~/app/providers/workspace";
import Dashboard from "~/app/components/dashboard";
export default function Layout(props: { children: React.ReactNode }) {
return (
<WorkspaceProvider>
<Dashboard>{props.children}</Dashboard>;
</WorkspaceProvider>
);
}

View File

@@ -1,5 +1,3 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
@@ -35,13 +33,13 @@ export default function CheckboxDropdown({
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="bg-light-50 border-light-200 absolute left-0 top-[26px] z-10 mt-2 w-56 origin-top-left rounded-md border-[1px] shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200"> <Menu.Items className="absolute left-0 top-[26px] z-10 mt-2 w-56 origin-top-left rounded-md border-[1px] border-light-200 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="p-1"> <div className="p-1">
{items?.map((item) => ( {items?.map((item) => (
<Menu.Item key={item.key}> <Menu.Item key={item.key}>
<div <div
key={item.key} key={item.key}
className="hover:bg-light-200 flex items-center rounded-[5px] p-2 dark:hover:bg-dark-300" className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
handleSelect({ key: item.key }); handleSelect({ key: item.key });

View File

@@ -1,11 +1,9 @@
"use client";
import { useState } from "react"; import { useState } from "react";
import { useTheme } from "~/app/providers/theme"; import { useTheme } from "~/providers/theme";
import LottieIcon from "~/app/components/LottieIcon"; import LottieIcon from "~/components/LottieIcon";
import chatIconLight from "~/app/assets/chat-light.json"; import chatIconLight from "~/assets/chat-light.json";
import chatIconDark from "~/app/assets/chat-dark.json"; import chatIconDark from "~/assets/chat-dark.json";
const FeedbackButton: React.FC = () => { const FeedbackButton: React.FC = () => {
const { theme } = useTheme(); const { theme } = useTheme();
@@ -21,7 +19,7 @@ const FeedbackButton: React.FC = () => {
<button <button
type="button" type="button"
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
className="bg-light-50 border-light-600 flex items-center rounded-md border-[1px] px-2.5 py-1.5 text-sm font-normal text-neutral-900 shadow-sm dark:border-dark-600 dark:bg-dark-50 dark:text-dark-1000" className="flex items-center rounded-md border-[1px] border-light-600 bg-light-50 px-2.5 py-1.5 text-sm font-normal text-neutral-900 shadow-sm dark:border-dark-600 dark:bg-dark-50 dark:text-dark-1000"
> >
<LottieIcon <LottieIcon
index={index} index={index}

View File

@@ -1,5 +1,3 @@
"use client";
import Lottie from "react-lottie-player"; import Lottie from "react-lottie-player";
type IconProps = { type IconProps = {

View File

@@ -1,11 +1,9 @@
"use client"; import { api } from "~/utils/api";
import { api } from "~/trpc/react";
import { HiXMark } from "react-icons/hi2"; import { HiXMark } from "react-icons/hi2";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/app/providers/workspace"; import { useWorkspace } from "~/providers/workspace";
import { Formik, Form, Field } from "formik"; import { Formik, Form, Field } from "formik";

View File

@@ -1,9 +1,7 @@
"use client";
import { useState } from "react"; import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import LottieIcon from "~/app/components/LottieIcon"; import LottieIcon from "~/components/LottieIcon";
function classNames(...classes: string[]): string { function classNames(...classes: string[]): string {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(" ");
@@ -29,7 +27,7 @@ const Button: React.FC<{
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
className={classNames( 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",
"hover:bg-light-200 group flex items-center gap-x-3 rounded-md p-1.5 text-sm font-normal leading-6 text-neutral-900 dark:text-dark-1000 dark:hover:bg-dark-200", "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",
)} )}
> >
<LottieIcon index={index} json={json} isPlaying={isHovered} /> <LottieIcon index={index} json={json} isPlaying={isHovered} />

View File

@@ -1,19 +1,17 @@
"use client";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useTheme } from "~/app/providers/theme"; import { useTheme } from "~/providers/theme";
import boardsIconDark from "~/app/assets/boards-dark.json"; import boardsIconDark from "~/assets/boards-dark.json";
import boardsIconLight from "~/app/assets/boards-light.json"; import boardsIconLight from "~/assets/boards-light.json";
import membersIconDark from "~/app/assets/members-dark.json"; import membersIconDark from "~/assets/members-dark.json";
import membersIconLight from "~/app/assets/members-light.json"; import membersIconLight from "~/assets/members-light.json";
import settingsIconDark from "~/app/assets/settings-dark.json"; import settingsIconDark from "~/assets/settings-dark.json";
import settingsIconLight from "~/app/assets/settings-light.json"; import settingsIconLight from "~/assets/settings-light.json";
import ReactiveButton from "~/app/components/ReactiveButton"; import ReactiveButton from "~/components/ReactiveButton";
import UserMenu from "~/app/components/UserMenu"; import UserMenu from "~/components/UserMenu";
import WorkspaceMenu from "~/app/components/WorkspaceMenu"; import WorkspaceMenu from "~/components/WorkspaceMenu";
interface SideNavigationProps { interface SideNavigationProps {
user: UserType; user: UserType;
@@ -48,7 +46,7 @@ export default function SideNavigation({ user }: SideNavigationProps) {
return ( return (
<> <>
<nav className="border-light-600 flex w-72 flex-col justify-between border-r px-3 pb-3 pt-5 dark:border-dark-600"> <nav className="flex w-72 flex-col justify-between border-r border-light-600 px-3 pb-3 pt-5 dark:border-dark-600">
<div> <div>
<WorkspaceMenu /> <WorkspaceMenu />
<ul role="list" className="space-y-1"> <ul role="list" className="space-y-1">

View File

@@ -1,10 +1,7 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import Image from "next/image"; import Image from "next/image";
import { signOut } from "next-auth/react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { useTheme } from "~/app/providers/theme"; import { useTheme } from "~/providers/theme";
interface UserMenuProps { interface UserMenuProps {
imageUrl: string | undefined; imageUrl: string | undefined;
@@ -21,7 +18,7 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
return ( return (
<Menu as="div" className="relative inline-block w-full text-left"> <Menu as="div" className="relative inline-block w-full text-left">
<div> <div>
<Menu.Button className="hover:bg-light-200 flex w-full items-center rounded-md p-1.5 text-neutral-900 dark:text-dark-900 dark:hover:bg-dark-200 dark:hover:text-dark-1000"> <Menu.Button className="flex w-full items-center rounded-md p-1.5 text-neutral-900 hover:bg-light-200 dark:text-dark-900 dark:hover:bg-dark-200 dark:hover:text-dark-1000">
{imageUrl ? ( {imageUrl ? (
<Image <Image
src={imageUrl ?? ""} src={imageUrl ?? ""}
@@ -31,7 +28,7 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
alt="" alt=""
/> />
) : ( ) : (
<span className="bg-light-400 inline-block h-6 w-6 overflow-hidden rounded-full dark:bg-dark-400"> <span className="inline-block h-6 w-6 overflow-hidden rounded-full bg-light-400 dark:bg-dark-400">
<svg <svg
className="h-full w-full text-dark-700" className="h-full w-full text-dark-700"
fill="currentColor" fill="currentColor"
@@ -54,7 +51,7 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="border-light-600 bg-light-50 absolute bottom-[40px] left-0 z-10 mt-2 w-full origin-top-left rounded-md border shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-600 dark:bg-dark-300"> <Menu.Items className="absolute bottom-[40px] left-0 z-10 mt-2 w-full origin-top-left rounded-md border border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-600 dark:bg-dark-300">
<div className="flex flex-col text-neutral-900 dark:text-dark-1000"> <div className="flex flex-col text-neutral-900 dark:text-dark-1000">
<div className="p-1"> <div className="p-1">
<div className="flex w-full items-center px-3 py-2 text-left text-xs"> <div className="flex w-full items-center px-3 py-2 text-left text-xs">
@@ -63,12 +60,12 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
<Menu.Item> <Menu.Item>
<button <button
onClick={() => switchTheme("system")} onClick={() => switchTheme("system")}
className="hover:bg-light-200 flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs 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"
> >
<span <span
className={classNames( className={classNames(
theme === "system" ? "visible" : "invisible", theme === "system" ? "visible" : "invisible",
"bg-light-900 mr-4 h-[6px] w-[6px] rounded-full dark:bg-dark-900", "mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
)} )}
/> />
System System
@@ -77,12 +74,12 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
<Menu.Item> <Menu.Item>
<button <button
onClick={() => switchTheme("dark")} onClick={() => switchTheme("dark")}
className="hover:bg-light-200 flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs 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"
> >
<span <span
className={classNames( className={classNames(
theme === "dark" ? "visible" : "invisible", theme === "dark" ? "visible" : "invisible",
"bg-light-900 mr-4 h-[6px] w-[6px] rounded-full dark:bg-dark-900", "mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
)} )}
/> />
Dark Dark
@@ -91,12 +88,12 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
<Menu.Item> <Menu.Item>
<button <button
onClick={() => switchTheme("light")} onClick={() => switchTheme("light")}
className="hover:bg-light-200 flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs 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"
> >
<span <span
className={classNames( className={classNames(
theme === "light" ? "visible" : "invisible", theme === "light" ? "visible" : "invisible",
"bg-light-900 mr-4 h-[6px] w-[6px] rounded-full dark:bg-dark-900", "mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
)} )}
/> />
Light Light
@@ -106,8 +103,8 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
<div className="light-border-600 border-t-[1px] p-1 dark:border-dark-600"> <div className="light-border-600 border-t-[1px] p-1 dark:border-dark-600">
<Menu.Item> <Menu.Item>
<button <button
onClick={() => signOut({ callbackUrl: "/boards" })} // onClick={() => signOut({ callbackUrl: "/boards" })}
className=" hover:bg-light-200 flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs 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 Logout
</button> </button>

View File

@@ -1,10 +1,8 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/app/providers/workspace"; import { useWorkspace } from "~/providers/workspace";
import { HiCheck } from "react-icons/hi2"; import { HiCheck } from "react-icons/hi2";
@@ -15,7 +13,7 @@ export default function WorkspaceMenu() {
return ( return (
<Menu as="div" className="relative inline-block w-full pb-3 text-left"> <Menu as="div" className="relative inline-block w-full pb-3 text-left">
<div> <div>
<Menu.Button className="hover:bg-light-200 mb-1 flex w-full items-center rounded-[5px] p-1.5 dark:hover:bg-dark-200"> <Menu.Button className="mb-1 flex w-full items-center rounded-[5px] p-1.5 hover:bg-light-200 dark:hover:bg-dark-200">
<span className="inline-flex h-6 w-6 items-center justify-center rounded-[5px] bg-indigo-700"> <span className="inline-flex h-6 w-6 items-center justify-center rounded-[5px] bg-indigo-700">
<span className="text-xs font-bold leading-none text-white"> <span className="text-xs font-bold leading-none text-white">
{workspace?.name.charAt(0).toUpperCase()} {workspace?.name.charAt(0).toUpperCase()}
@@ -36,14 +34,14 @@ export default function WorkspaceMenu() {
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="bg-light-50 border-light-600 absolute left-0 z-10 w-full origin-top-left rounded-md border shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-600 dark:bg-dark-300"> <Menu.Items className="absolute left-0 z-10 w-full origin-top-left rounded-md border border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-600 dark:bg-dark-300">
<div className="p-1"> <div className="p-1">
{availableWorkspaces.map((availableWorkspace) => ( {availableWorkspaces.map((availableWorkspace) => (
<div key={availableWorkspace.publicId} className="flex"> <div key={availableWorkspace.publicId} className="flex">
<Menu.Item> <Menu.Item>
<button <button
onClick={() => switchWorkspace(availableWorkspace)} onClick={() => switchWorkspace(availableWorkspace)}
className="hover:bg-light-200 flex w-full items-center justify-between rounded-[5px] px-3 py-2 text-left text-sm text-neutral-900 dark:text-dark-1000 dark:hover:bg-dark-400" className="flex w-full items-center justify-between rounded-[5px] px-3 py-2 text-left text-sm text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-400"
> >
<div> <div>
<span className="inline-flex h-5 w-5 items-center justify-center rounded-[5px] bg-indigo-700"> <span className="inline-flex h-5 w-5 items-center justify-center rounded-[5px] bg-indigo-700">
@@ -65,11 +63,11 @@ export default function WorkspaceMenu() {
</div> </div>
))} ))}
</div> </div>
<div className="border-light-600 border-t-[1px] p-1 dark:border-dark-500"> <div className="border-t-[1px] border-light-600 p-1 dark:border-dark-500">
<Menu.Item> <Menu.Item>
<button <button
onClick={() => openModal("NEW_WORKSPACE")} onClick={() => openModal("NEW_WORKSPACE")}
className="hover:bg-light-200 flex w-full items-center justify-between rounded-[5px] px-3 py-2 text-left text-xs text-neutral-900 dark:text-dark-1000 dark:hover:bg-dark-400" className="flex w-full items-center justify-between rounded-[5px] px-3 py-2 text-left text-xs text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-400"
> >
Create workspace Create workspace
</button> </button>

View File

@@ -1,7 +1,5 @@
"use client";
import { Formik, Form, Field } from "formik"; import { Formik, Form, Field } from "formik";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
interface FormValues { interface FormValues {
email: string; email: string;

View File

@@ -1,20 +1,18 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { auth } from "~/server/auth";
import SideNavigation from "./SideNavigation"; import SideNavigation from "./SideNavigation";
import FeedbackButton from "./FeedbackButton"; import FeedbackButton from "./FeedbackButton";
export default async function Dashboard(props: { children: React.ReactNode }) { export default function Dashboard(props: { children: React.ReactNode }) {
const session = await auth(); // const session = await auth();
if (!session?.user) { // if (!session?.user) {
redirect("/api/auth/signin"); // redirect("/api/auth/signin");
} // }
return ( return (
<main className="bg-light-100 flex h-screen flex-col items-center dark:bg-dark-50"> <div className="flex h-screen flex-col items-center bg-light-100 dark:bg-dark-50">
<div className="border-light-600 m-auto flex h-16 w-full justify-between border-b px-5 py-2 align-middle dark:border-dark-600"> <div className="m-auto flex h-16 w-full justify-between border-b border-light-600 px-5 py-2 align-middle dark:border-dark-600">
<div className="my-auto flex w-full items-center justify-between"> <div className="my-auto flex w-full items-center justify-between">
<h1 className="text-lg font-bold tracking-tight text-neutral-900 dark:text-dark-1000"> <h1 className="text-lg font-bold tracking-tight text-neutral-900 dark:text-dark-1000">
kan.bn kan.bn
@@ -24,9 +22,9 @@ export default async function Dashboard(props: { children: React.ReactNode }) {
</div> </div>
<div className="flex h-full w-full"> <div className="flex h-full w-full">
<SideNavigation user={session.user} /> <SideNavigation user={{ email: "henry_ball@hotmail.co.uk" }} />
<div className="w-full overflow-hidden">{props.children}</div> <div className="w-full overflow-hidden">{props.children}</div>
</div> </div>
</main> </div>
); );
} }

View File

@@ -1,9 +1,7 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
@@ -24,7 +22,7 @@ const Modal: React.FC<Props> = ({ children }) => {
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<div className="bg-light-50 fixed inset-0 bg-opacity-40 transition-opacity dark:bg-dark-50 dark:bg-opacity-40" /> <div className="fixed inset-0 bg-light-50 bg-opacity-40 transition-opacity dark:bg-dark-50 dark:bg-opacity-40" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 w-screen overflow-y-auto"> <div className="fixed inset-0 z-10 w-screen overflow-y-auto">
@@ -38,7 +36,7 @@ const Modal: React.FC<Props> = ({ children }) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<Dialog.Panel className="shadow-3xl-light dark:shadow-3xl-dark bg-light-50 border-light-600 relative mt-[25vh] transform rounded-lg border px-4 pb-4 pt-5 text-left transition-all dark:border-dark-600 dark:bg-dark-100/20 dark:backdrop-blur-lg sm:mb-8 sm:w-full sm:max-w-sm sm:p-6"> <Dialog.Panel className="relative mt-[25vh] transform rounded-lg border border-light-600 bg-light-50 px-4 pb-4 pt-5 text-left shadow-3xl-light transition-all dark:border-dark-600 dark:bg-dark-100/20 dark:shadow-3xl-dark dark:backdrop-blur-lg sm:mb-8 sm:w-full sm:max-w-sm sm:p-6">
{children} {children}
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>

View File

@@ -17,17 +17,6 @@ export const env = createEnv({
NODE_ENV: z NODE_ENV: z
.enum(["development", "test", "production"]) .enum(["development", "test", "production"])
.default("development"), .default("development"),
NEXTAUTH_SECRET:
process.env.NODE_ENV === "production"
? z.string()
: z.string().optional(),
NEXTAUTH_URL: z.preprocess(
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
// Since NextAuth.js automatically uses the VERCEL_URL if present.
(str) => process.env.VERCEL_URL ?? str,
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
process.env.VERCEL ? z.string() : z.string().url(),
),
}, },
/** /**
@@ -46,8 +35,7 @@ export const env = createEnv({
runtimeEnv: { runtimeEnv: {
POSTGRES_URL: process.env.POSTGRES_URL, POSTGRES_URL: process.env.POSTGRES_URL,
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
}, },
/** /**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
@@ -55,8 +43,8 @@ export const env = createEnv({
*/ */
skipValidation: !!process.env.SKIP_ENV_VALIDATION, skipValidation: !!process.env.SKIP_ENV_VALIDATION,
/** /**
* Makes it so that empty strings are treated as undefined. * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
* `SOME_VAR: z.string()` and `SOME_VAR=''` will throw an error. * `SOME_VAR=''` will throw an error.
*/ */
emptyStringAsUndefined: true, emptyStringAsUndefined: true,
}); });

38
src/pages/_app.tsx Normal file
View File

@@ -0,0 +1,38 @@
import "~/styles/globals.css";
import { type AppType } from "next/app";
import { Plus_Jakarta_Sans } from "next/font/google";
import { ModalProvider } from "~/providers/modal";
import { BoardProvider } from "~/providers/board";
import { ThemeProvider } from "~/providers/theme";
import { api } from "~/utils/api";
const jakarta = Plus_Jakarta_Sans({
subsets: ["latin"],
display: "swap",
variable: "--font-plus-jakarta-sans",
});
export const metadata = {
title: "Kan",
description: "The open source Trello alternative",
icons: [{ rel: "icon", url: "/favicon.ico" }],
};
const MyApp: AppType = ({ Component, pageProps }) => {
return (
<main className={`${jakarta.variable} h-screen overflow-hidden font-sans`}>
<ThemeProvider>
<ModalProvider>
<BoardProvider>
<Component {...pageProps} />
</BoardProvider>
</ModalProvider>
</ThemeProvider>
</main>
);
};
export default api.withTRPC(MyApp);

View File

@@ -0,0 +1,36 @@
import { type EmailOtpType } from '@supabase/supabase-js'
import type { NextApiRequest, NextApiResponse } from 'next'
import createClient from '~/utils/supabase/api'
function stringOrFirstString(item: string | string[] | undefined) {
return Array.isArray(item) ? item[0] : item
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
res.status(405).appendHeader('Allow', 'GET').end()
return
}
const queryParams = req.query
const token_hash = stringOrFirstString(queryParams.token_hash)
const type = stringOrFirstString(queryParams.type)
let next = '/error'
if (token_hash && type) {
const supabase = createClient(req, res)
const { error } = await supabase.auth.verifyOtp({
type: type as EmailOtpType,
token_hash,
})
if (error) {
console.error(error)
} else {
next = stringOrFirstString(queryParams.next) || '/'
}
}
res.redirect(next)
}

View File

@@ -0,0 +1,33 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { createNextApiHandler, CreateNextContextOptions } from "@trpc/server/adapters/next";
import { env } from "~/env.mjs";
import { appRouter } from "~/server/api/root";
import { createTRPCContext } from "~/server/api/trpc";
const nextApiHandler = createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
return nextApiHandler(req, res);
}
// export const runtime = "edge";
// export const preferredRegion = 'lhr1';
// export const dynamic = 'force-dynamic'

View File

@@ -0,0 +1,13 @@
import { WorkspaceProvider } from "~/providers/workspace";
import Dashboard from "~/components/dashboard";
import BoardView from "~/views/board";
export default function BoardPage() {
return (
<WorkspaceProvider>
<Dashboard>
<BoardView />
</Dashboard>
</WorkspaceProvider>
);
}

View File

@@ -0,0 +1,13 @@
import { WorkspaceProvider } from "~/providers/workspace";
import Dashboard from "~/components/dashboard";
import BoardsView from "~/views/boards";
export default function BoardsPage() {
return (
<WorkspaceProvider>
<Dashboard>
<BoardsView />
</Dashboard>
</WorkspaceProvider>
);
}

View File

@@ -0,0 +1,13 @@
import { WorkspaceProvider } from "~/providers/workspace";
import Dashboard from "~/components/dashboard";
import CardView from "~/views/card";
export default function CardPage() {
return (
<WorkspaceProvider>
<Dashboard>
<CardView />
</Dashboard>
</WorkspaceProvider>
);
}

View File

@@ -0,0 +1,5 @@
import LoginView from "~/views/auth/login";
export default function LoginPage() {
return <LoginView />;
}

View File

@@ -0,0 +1,13 @@
import { WorkspaceProvider } from "~/providers/workspace";
import Dashboard from "~/components/dashboard";
import MembersView from "~/views/members";
export default function MembersPage() {
return (
<WorkspaceProvider>
<Dashboard>
<MembersView />
</Dashboard>
</WorkspaceProvider>
);
}

View File

@@ -0,0 +1,13 @@
import { WorkspaceProvider } from "~/providers/workspace";
import Dashboard from "~/components/dashboard";
import SettingsView from "~/views/settings";
export default function SettingsPage() {
return (
<WorkspaceProvider>
<Dashboard>
<SettingsView />
</Dashboard>
</WorkspaceProvider>
);
}

View File

@@ -1,5 +1,3 @@
"use client";
import React, { import React, {
createContext, createContext,
useContext, useContext,
@@ -7,7 +5,7 @@ import React, {
type ReactNode, type ReactNode,
} from "react"; } from "react";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
interface BoardContextProps { interface BoardContextProps {
boardData: BoardData; boardData: BoardData;

View File

@@ -1,5 +1,3 @@
"use client";
import { createContext, useContext, useState } from "react"; import { createContext, useContext, useState } from "react";
type ModalContextType = { type ModalContextType = {

View File

@@ -1,5 +1,3 @@
"use client";
import React, { import React, {
createContext, createContext,
useContext, useContext,

View File

@@ -1,5 +1,3 @@
"use client";
import React, { import React, {
createContext, createContext,
useContext, useContext,
@@ -8,7 +6,7 @@ import React, {
type ReactNode, type ReactNode,
} from "react"; } from "react";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";

View File

@@ -24,3 +24,13 @@ export const appRouter = createTRPCRouter({
// export type definition of API // export type definition of API
export type AppRouter = typeof appRouter; export type AppRouter = typeof appRouter;
/**
* Create a server-side caller for the tRPC API.
* @example
* const trpc = createCaller(createContext);
* const res = await trpc.post.all();
* ^? Post[]
*/
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
// export const createCaller = createCallerFactory(appRouter);

View File

@@ -6,14 +6,15 @@
* TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
* need to use are documented accordingly near the end. * need to use are documented accordingly near the end.
*/ */
import { initTRPC } from "@trpc/server";
import { initTRPC, TRPCError } from "@trpc/server"; import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
import { type NextRequest } from "next/server";
import superjson from "superjson"; import superjson from "superjson";
import { ZodError } from "zod"; import { ZodError } from "zod";
import { auth } from "~/server/auth"; import { db } from "~/server/db";
import { db, supabase } from "~/server/db"; import createClient from "~/utils/supabase/api";
import { type Database } from "~/types/database.types";
import { SupabaseClient } from '@supabase/supabase-js';
/** /**
* 1. CONTEXT * 1. CONTEXT
@@ -23,8 +24,14 @@ import { db, supabase } from "~/server/db";
* These allow you to access things when processing a request, like the database, the session, etc. * These allow you to access things when processing a request, like the database, the session, etc.
*/ */
type User = {
id: string;
};
interface CreateContextOptions { interface CreateContextOptions {
headers: Headers; user: User | null;
supabase: SupabaseClient<Database>
db: any;
} }
/** /**
@@ -37,14 +44,12 @@ interface CreateContextOptions {
* *
* @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts * @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts
*/ */
export const createInnerTRPCContext = async (opts: CreateContextOptions) => {
const session = await auth();
export const createInnerTRPCContext = (opts: CreateContextOptions) => {
return { return {
session, user: opts.user,
headers: opts.headers, supabase: opts.supabase,
db, db,
supabase
}; };
}; };
@@ -54,12 +59,12 @@ export const createInnerTRPCContext = async (opts: CreateContextOptions) => {
* *
* @see https://trpc.io/docs/context * @see https://trpc.io/docs/context
*/ */
export const createTRPCContext = async (opts: { req: NextRequest }) => { export const createTRPCContext = async (_opts: CreateNextContextOptions) => {
// Fetch stuff that depends on the request const supabase = createClient(_opts.req, _opts.res);
return await createInnerTRPCContext({ const { data: { user } } = await supabase.auth.getUser()
headers: opts.req.headers,
}); return createInnerTRPCContext({ user, supabase });
}; };
/** /**
@@ -84,6 +89,13 @@ const t = initTRPC.context<typeof createTRPCContext>().create({
}, },
}); });
/**
* Create a server-side caller.
*
* @see https://trpc.io/docs/server/server-side-calls
*/
// export const createCallerFactory = t.createCallerFactory;
/** /**
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
* *
@@ -106,26 +118,3 @@ export const createTRPCRouter = t.router;
* are logged in. * are logged in.
*/ */
export const publicProcedure = t.procedure; export const publicProcedure = t.procedure;
/** Reusable middleware that enforces users are logged in before running the procedure. */
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});
/**
* Protected (authenticated) procedure
*
* If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies
* the session is valid and guarantees `ctx.session.user` is not null.
*
* @see https://trpc.io/docs/procedures
*/
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);

View File

@@ -1,47 +0,0 @@
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { useState } from "react";
import { type AppRouter } from "~/server/api/root";
import { getUrl, transformer } from "./shared";
export const api = createTRPCReact<AppRouter>();
export function TRPCReactProvider(props: {
children: React.ReactNode;
headers: Headers;
}) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
api.createClient({
transformer,
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
url: getUrl(),
headers() {
const heads = new Map(props.headers);
heads.set("x-trpc-source", "react");
return Object.fromEntries(heads);
},
}),
],
}),
);
return (
<QueryClientProvider client={queryClient}>
<api.Provider client={trpcClient} queryClient={queryClient}>
{props.children}
</api.Provider>
</QueryClientProvider>
);
}

View File

@@ -1,28 +0,0 @@
import {
createTRPCProxyClient,
loggerLink,
unstable_httpBatchStreamLink,
} from "@trpc/client";
import { headers } from "next/headers";
import { type AppRouter } from "~/server/api/root";
import { getUrl, transformer } from "./shared";
export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
url: getUrl(),
headers() {
const heads = new Map(headers());
heads.set("x-trpc-source", "rsc");
return Object.fromEntries(heads);
},
}),
],
});

View File

@@ -1,30 +0,0 @@
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
import superjson from "superjson";
import { type AppRouter } from "~/server/api/root";
export const transformer = superjson;
function getBaseUrl() {
if (typeof window !== "undefined") return "";
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return `http://localhost:${process.env.PORT ?? 3000}`;
}
export function getUrl() {
return getBaseUrl() + "/api/trpc";
}
/**
* Inference helper for inputs.
*
* @example type HelloInput = RouterInputs['example']['hello']
*/
export type RouterInputs = inferRouterInputs<AppRouter>;
/**
* Inference helper for outputs.
*
* @example type HelloOutput = RouterOutputs['example']['hello']
*/
export type RouterOutputs = inferRouterOutputs<AppRouter>;

68
src/utils/api.ts Normal file
View File

@@ -0,0 +1,68 @@
/**
* 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 } from "@trpc/client";
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 getBaseUrl = () => {
if (typeof window !== "undefined") return ""; // browser should use relative url
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};
/** A set of type-safe react-query hooks for your tRPC API. */
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) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
httpBatchLink({
/**
* Transformer used for data de-serialization from the server.
*
* @see https://trpc.io/docs/data-transformers
*/
transformer: superjson,
url: `${getBaseUrl()}/api/trpc`,
}),
],
};
},
/**
* Whether tRPC should await queries when server rendering pages.
*
* @see https://trpc.io/docs/nextjs#ssr-boolean-default-false
*/
ssr: false,
transformer: superjson,
});
/**
* Inference helper for inputs.
*
* @example type HelloInput = RouterInputs['example']['hello']
*/
export type RouterInputs = inferRouterInputs<AppRouter>;
/**
* Inference helper for outputs.
*
* @example type HelloOutput = RouterOutputs['example']['hello']
*/
export type RouterOutputs = inferRouterOutputs<AppRouter>;

View File

@@ -1,4 +1,4 @@
import { Login } from "~/app/components/auth"; import { Login } from "~/components/auth";
export default function LoginPage() { export default function LoginPage() {
return ( return (

View File

@@ -1,9 +1,7 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { HiEllipsisHorizontal } from "react-icons/hi2"; import { HiEllipsisHorizontal } from "react-icons/hi2";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
export default function BoardDropdown() { export default function BoardDropdown() {
const { openModal } = useModal(); const { openModal } = useModal();
@@ -11,7 +9,7 @@ export default function BoardDropdown() {
return ( return (
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
<div> <div>
<Menu.Button className="hover:bg-light-200 flex h-8 w-8 items-center justify-center rounded-[5px] dark:hover:bg-dark-200"> <Menu.Button className="flex h-8 w-8 items-center justify-center rounded-[5px] hover:bg-light-200 dark:hover:bg-dark-200">
<HiEllipsisHorizontal <HiEllipsisHorizontal
size={25} size={25}
className="text-light-900 dark:text-dark-900" className="text-light-900 dark:text-dark-900"
@@ -28,12 +26,12 @@ export default function BoardDropdown() {
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="bg-light-50 border-light-200 absolute right-0 z-30 mt-2 w-56 origin-top-right rounded-md border shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-400 dark:bg-dark-300"> <Menu.Items className="absolute right-0 z-30 mt-2 w-56 origin-top-right rounded-md border border-light-200 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-400 dark:bg-dark-300">
<div className="flex"> <div className="flex">
<Menu.Item> <Menu.Item>
<button <button
onClick={() => openModal("DELETE_BOARD")} onClick={() => openModal("DELETE_BOARD")}
className="hover:bg-light-200 m-1 w-full rounded-[5px] px-3 py-2 text-left text-sm text-neutral-900 dark:text-dark-1000 dark:hover:bg-dark-400" className="m-1 w-full rounded-[5px] px-3 py-2 text-left text-sm text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-400"
> >
Delete board Delete board
</button> </button>

View File

@@ -1,10 +1,8 @@
"use client";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { useBoard } from "~/app/providers/board"; import { useBoard } from "~/providers/board";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
export function DeleteBoardConfirmation() { export function DeleteBoardConfirmation() {
const router = useRouter(); const router = useRouter();
@@ -24,13 +22,13 @@ export function DeleteBoardConfirmation() {
<h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000"> <h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000">
Are you sure you want to delete this board? Are you sure you want to delete this board?
</h2> </h2>
<p className="text-light-900 text-sm font-medium dark:text-dark-900"> <p className="text-sm font-medium text-light-900 dark:text-dark-900">
{"This action can't be undone."} {"This action can't be undone."}
</p> </p>
</div> </div>
<div className="mt-5 flex justify-end sm:mt-6"> <div className="mt-5 flex justify-end sm:mt-6">
<button <button
className="bg-light-50 border-light-600 mr-4 inline-flex justify-center rounded-md border-[1px] px-3 py-2 text-sm font-semibold text-neutral-900 shadow-sm focus-visible:outline-none dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000" className="mr-4 inline-flex justify-center rounded-md border-[1px] border-light-600 bg-light-50 px-3 py-2 text-sm font-semibold text-neutral-900 shadow-sm focus-visible:outline-none dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000"
onClick={() => closeModal()} onClick={() => closeModal()}
> >
Cancel Cancel
@@ -41,7 +39,7 @@ export function DeleteBoardConfirmation() {
boardPublicId: boardData.publicId, boardPublicId: boardData.publicId,
}) })
} }
className="bg-light-1000 text-light-50 inline-flex justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Delete Delete
</button> </button>

View File

@@ -1,8 +1,6 @@
"use client"; import { api } from "~/utils/api";
import { useBoard } from "~/providers/board";
import { api } from "~/trpc/react"; import { useModal } from "~/providers/modal";
import { useBoard } from "~/app/providers/board";
import { useModal } from "~/app/providers/modal";
interface DeleteListConfirmationProps { interface DeleteListConfirmationProps {
listPublicId: string; listPublicId: string;
@@ -31,13 +29,13 @@ export function DeleteListConfirmation({
<h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000"> <h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000">
Are you sure you want to delete this list? Are you sure you want to delete this list?
</h2> </h2>
<p className="text-light-900 text-sm font-medium dark:text-dark-900"> <p className="text-sm font-medium text-light-900 dark:text-dark-900">
{"This action can't be undone."} {"This action can't be undone."}
</p> </p>
</div> </div>
<div className="mt-5 flex justify-end sm:mt-6"> <div className="mt-5 flex justify-end sm:mt-6">
<button <button
className="bg-light-50 border-light-600 mr-4 inline-flex justify-center rounded-md border-[1px] px-3 py-2 text-sm font-semibold text-neutral-900 shadow-sm focus-visible:outline-none dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000" className="mr-4 inline-flex justify-center rounded-md border-[1px] border-light-600 bg-light-50 px-3 py-2 text-sm font-semibold text-neutral-900 shadow-sm focus-visible:outline-none dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000"
onClick={() => closeModal()} onClick={() => closeModal()}
> >
Cancel Cancel
@@ -48,7 +46,7 @@ export function DeleteListConfirmation({
listPublicId, listPublicId,
}) })
} }
className="bg-light-1000 text-light-50 inline-flex justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Delete Delete
</button> </button>

View File

@@ -1,12 +1,10 @@
"use client";
import { type ReactNode } from "react"; import { type ReactNode } from "react";
import { HiOutlinePlusSmall } from "react-icons/hi2"; import { HiOutlinePlusSmall } from "react-icons/hi2";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import ListDropdown from "./ListDropdown"; import ListDropdown from "./ListDropdown";
@@ -66,7 +64,7 @@ export default function List({
ref={provided.innerRef} ref={provided.innerRef}
{...provided.draggableProps} {...provided.draggableProps}
{...provided.dragHandleProps} {...provided.dragHandleProps}
className="bg-light-300 border-light-400 dark-text-dark-1000 mr-5 h-fit min-w-[18rem] max-w-[18rem] rounded-md border py-2 pl-2 pr-1 text-neutral-900 dark:border-dark-400 dark:bg-dark-200" className="dark-text-dark-1000 mr-5 h-fit min-w-[18rem] max-w-[18rem] rounded-md border border-light-400 bg-light-300 py-2 pl-2 pr-1 text-neutral-900 dark:border-dark-400 dark:bg-dark-200"
> >
<div className="flex justify-between"> <div className="flex justify-between">
<form <form
@@ -85,7 +83,7 @@ export default function List({
</form> </form>
<div> <div>
<button <button
className="hover:bg-light-400 mx-1 inline-flex h-fit items-center rounded-md p-1 px-1 text-sm font-semibold text-dark-50 dark:hover:bg-dark-400" className="mx-1 inline-flex h-fit items-center rounded-md p-1 px-1 text-sm font-semibold text-dark-50 hover:bg-light-400 dark:hover:bg-dark-400"
onClick={() => openNewCardForm(list.publicId)} onClick={() => openNewCardForm(list.publicId)}
> >
<HiOutlinePlusSmall <HiOutlinePlusSmall

View File

@@ -1,9 +1,7 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { HiEllipsisHorizontal } from "react-icons/hi2"; import { HiEllipsisHorizontal } from "react-icons/hi2";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
interface ListDropdownProps { interface ListDropdownProps {
setSelectedPublicListId: () => void; setSelectedPublicListId: () => void;
@@ -22,7 +20,7 @@ export default function ListDropdown({
return ( return (
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
<div> <div>
<Menu.Button className="hover:bg-light-400 mr-1 inline-flex h-fit items-center rounded-md p-1 px-1 text-sm font-semibold text-dark-50 dark:hover:bg-dark-400"> <Menu.Button className="mr-1 inline-flex h-fit items-center rounded-md p-1 px-1 text-sm font-semibold text-dark-50 hover:bg-light-400 dark:hover:bg-dark-400">
<HiEllipsisHorizontal className="h-5 w-5 text-dark-900" /> <HiEllipsisHorizontal className="h-5 w-5 text-dark-900" />
</Menu.Button> </Menu.Button>
</div> </div>
@@ -36,12 +34,12 @@ export default function ListDropdown({
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="bg-light-50 dark-text-dark-1000 border-light-400 absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md border text-neutral-900 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-400 dark:bg-dark-300"> <Menu.Items className="dark-text-dark-1000 absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md border border-light-400 bg-light-50 text-neutral-900 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-400 dark:bg-dark-300">
<div className="flex"> <div className="flex">
<Menu.Item> <Menu.Item>
<button <button
onClick={handleOpenDeleteListConfirmation} onClick={handleOpenDeleteListConfirmation}
className="hover:bg-light-400 m-1 w-full rounded-[5px] px-3 py-2 text-left text-sm dark:hover:bg-dark-400" className="m-1 w-full rounded-[5px] px-3 py-2 text-left text-sm hover:bg-light-400 dark:hover:bg-dark-400"
> >
Delete list Delete list
</button> </button>

View File

@@ -1,15 +1,13 @@
"use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { HiXMark } from "react-icons/hi2"; import { HiXMark } from "react-icons/hi2";
import { Switch } from "@headlessui/react"; import { Switch } from "@headlessui/react";
import CheckboxDropdown from "~/app/components/CheckboxDropdown"; import CheckboxDropdown from "~/components/CheckboxDropdown";
import { useBoard } from "~/app/providers/board"; import { useBoard } from "~/providers/board";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
interface FormData { interface FormData {
title: string; title: string;
@@ -142,7 +140,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
New card New card
</h2> </h2>
<button <button
className="hover:bg-light-200 rounded p-1 focus:outline-none dark:hover:bg-dark-300" className="rounded p-1 hover:bg-light-200 focus:outline-none dark:hover:bg-dark-300"
onClick={() => closeModal()} onClick={() => closeModal()}
> >
<HiXMark size={18} className="text-light-900 dark:text-dark-900" /> <HiXMark size={18} className="text-light-900 dark:text-dark-900" />
@@ -161,7 +159,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
id="title" id="title"
type="text" type="text"
{...register("title")} {...register("title")}
className="ring-light-600 focus:ring-light-600 block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6" className="block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-600 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"
/> />
</div> </div>
<div className="mt-2 flex space-x-1"> <div className="mt-2 flex space-x-1">
@@ -172,7 +170,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
handleSelectList(list.key) handleSelectList(list.key)
} }
> >
<div className="border-light-600 bg-light-200 text-light-800 hover:bg-light-300 flex h-full w-full items-center rounded-[5px] border-[1px] px-2 py-1 text-left text-xs dark:border-dark-600 dark:bg-dark-400 dark:text-dark-1000 dark:hover:bg-dark-500"> <div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-600 bg-light-200 px-2 py-1 text-left text-xs text-light-800 hover:bg-light-300 dark:border-dark-600 dark:bg-dark-400 dark:text-dark-1000 dark:hover:bg-dark-500">
{selectedList?.value} {selectedList?.value}
</div> </div>
</CheckboxDropdown> </CheckboxDropdown>
@@ -184,7 +182,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
handleSelectMembers(list.key) handleSelectMembers(list.key)
} }
> >
<div className="border-light-600 bg-light-200 text-light-800 hover:bg-light-300 flex h-full w-full items-center rounded-[5px] border-[1px] px-2 py-1 text-left text-xs dark:border-dark-600 dark:bg-dark-400 dark:text-dark-1000 dark:hover:bg-dark-500"> <div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-600 bg-light-200 px-2 py-1 text-left text-xs text-light-800 hover:bg-light-300 dark:border-dark-600 dark:bg-dark-400 dark:text-dark-1000 dark:hover:bg-dark-500">
{!memberPublicIds.length ? ( {!memberPublicIds.length ? (
"Members" "Members"
) : ( ) : (
@@ -197,7 +195,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
return ( return (
<span <span
key={member?.key} key={member?.key}
className="ring-light-200 inline-flex h-4 w-4 items-center justify-center rounded-full bg-gray-400 ring-1 dark:ring-dark-500" className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-gray-400 ring-1 ring-light-200 dark:ring-dark-500"
> >
<span className="text-[8px] font-medium leading-none text-white"> <span className="text-[8px] font-medium leading-none text-white">
{member?.value {member?.value
@@ -222,7 +220,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
handleSelectLabels(list.key) handleSelectLabels(list.key)
} }
> >
<div className="border-light-600 bg-light-200 text-light-800 hover:bg-light-300 flex h-full w-full items-center rounded-[5px] border-[1px] px-2 py-1 text-left text-xs dark:border-dark-600 dark:bg-dark-400 dark:text-dark-1000 dark:hover:bg-dark-500"> <div className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-600 bg-light-200 px-2 py-1 text-left text-xs text-light-800 hover:bg-light-300 dark:border-dark-600 dark:bg-dark-400 dark:text-dark-1000 dark:hover:bg-dark-500">
{!labelPublicIds.length ? ( {!labelPublicIds.length ? (
"Labels" "Labels"
) : ( ) : (
@@ -267,7 +265,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
</div> </div>
<div className="mt-3 flex items-center justify-end"> <div className="mt-3 flex items-center justify-end">
<span className="text-light-900 mr-2 text-xs dark:text-dark-900"> <span className="mr-2 text-xs text-light-900 dark:text-dark-900">
Create more Create more
</span> </span>
<Switch <Switch
@@ -294,7 +292,7 @@ export function NewCardForm({ listPublicId }: NewCardFormProps) {
<div className="mt-5 sm:mt-6"> <div className="mt-5 sm:mt-6">
<button <button
type="submit" type="submit"
className="bg-light-1000 text-light-50 inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Create card Create card
</button> </button>

View File

@@ -1,13 +1,11 @@
"use client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { HiXMark } from "react-icons/hi2"; import { HiXMark } from "react-icons/hi2";
import { Switch } from "@headlessui/react"; import { Switch } from "@headlessui/react";
import { useBoard } from "~/app/providers/board"; import { useBoard } from "~/providers/board";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import { Formik, Form, Field } from "formik"; import { Formik, Form, Field } from "formik";
@@ -56,7 +54,7 @@ export function NewListForm({ boardPublicId }: boardPublicId) {
New list New list
</h2> </h2>
<button <button
className="hover:bg-light-200 rounded p-1 focus:outline-none dark:hover:bg-dark-300" className="rounded p-1 hover:bg-light-200 focus:outline-none dark:hover:bg-dark-300"
onClick={() => closeModal()} onClick={() => closeModal()}
> >
<HiXMark size={18} className="text-light-900 dark:text-dark-900" /> <HiXMark size={18} className="text-light-900 dark:text-dark-900" />
@@ -85,10 +83,10 @@ export function NewListForm({ boardPublicId }: boardPublicId) {
<Field <Field
id="list-name" id="list-name"
name="name" name="name"
className="ring-light-600 focus:ring-light-600 block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6" className="block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-600 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"
/> />
<div className="mt-3 flex items-center justify-end"> <div className="mt-3 flex items-center justify-end">
<span className="text-light-900 mr-2 text-xs dark:text-dark-900"> <span className="mr-2 text-xs text-light-900 dark:text-dark-900">
Create more Create more
</span> </span>
<Switch <Switch
@@ -114,7 +112,7 @@ export function NewListForm({ boardPublicId }: boardPublicId) {
<div className="mt-5 sm:mt-6"> <div className="mt-5 sm:mt-6">
<button <button
type="submit" type="submit"
className="bg-light-1000 text-light-50 inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Create list Create list
</button> </button>

View File

@@ -1,5 +1,3 @@
"use client";
import { useState } from "react"; import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
@@ -12,17 +10,17 @@ import {
} from "react-beautiful-dnd"; } from "react-beautiful-dnd";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { useBoard } from "~/app/providers/board"; import { useBoard } from "~/providers/board";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import Modal from "~/app/components/modal"; import Modal from "~/components/modal";
import BoardDropdown from "./components/BoardDropdown"; import BoardDropdown from "./components/BoardDropdown";
import { DeleteBoardConfirmation } from "./components/DeleteBoardConfirmation"; import { DeleteBoardConfirmation } from "./components/DeleteBoardConfirmation";
import { DeleteListConfirmation } from "./components/DeleteListConfirmation"; import { DeleteListConfirmation } from "./components/DeleteListConfirmation";
import List from "./components/List"; import List from "./components/List";
import { NewWorkspaceForm } from "~/app/components/NewWorkspaceForm"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import { NewCardForm } from "./components/NewCardForm"; import { NewCardForm } from "./components/NewCardForm";
import { NewListForm } from "./components/NewListForm"; import { NewListForm } from "./components/NewListForm";

View File

@@ -1,9 +1,7 @@
"use client";
import Link from "next/link"; import Link from "next/link";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { useWorkspace } from "~/app/providers/workspace"; import { useWorkspace } from "~/providers/workspace";
export function BoardsList() { export function BoardsList() {
const { workspace } = useWorkspace(); const { workspace } = useWorkspace();

View File

@@ -1,14 +1,12 @@
"use client";
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
import { FaTrello } from "react-icons/fa"; import { FaTrello } from "react-icons/fa";
import { HiChevronUpDown, HiXMark } from "react-icons/hi2"; import { HiChevronUpDown, HiXMark } from "react-icons/hi2";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/app/providers/workspace"; import { useWorkspace } from "~/providers/workspace";
import { useFormik } from "formik"; import { useFormik } from "formik";
@@ -41,7 +39,7 @@ const SelectSource = ({ handleNextStep }: { handleNextStep: () => void }) => {
{({ open }) => ( {({ open }) => (
<> <>
<div className="relative"> <div className="relative">
<Listbox.Button className="ring-light-600 focus-ring-light-700 block w-full rounded-md border-0 bg-dark-300 bg-white/5 px-4 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"> <Listbox.Button className="focus-ring-light-700 block w-full rounded-md border-0 bg-dark-300 bg-white/5 px-4 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6">
<span className="flex items-center"> <span className="flex items-center">
<FaTrello /> <FaTrello />
<span className="ml-2 block truncate"> <span className="ml-2 block truncate">
@@ -63,14 +61,14 @@ const SelectSource = ({ handleNextStep }: { handleNextStep: () => void }) => {
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="bg-light-50 ring-light-600 absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md py-1 text-base text-neutral-900 shadow-lg ring-1 ring-opacity-5 focus:outline-none dark:bg-dark-300 dark:text-dark-1000 sm:text-sm"> <Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-light-50 py-1 text-base text-neutral-900 shadow-lg ring-1 ring-light-600 ring-opacity-5 focus:outline-none dark:bg-dark-300 dark:text-dark-1000 sm:text-sm">
{sources.map(({ source }, index) => ( {sources.map(({ source }, index) => (
<Listbox.Option <Listbox.Option
key={`source_${index}`} key={`source_${index}`}
className="relative cursor-default select-none px-1" className="relative cursor-default select-none px-1"
value={source} value={source}
> >
<div className="hover:bg-light-200 flex items-center rounded-[5px] p-1 dark:hover:bg-dark-400"> <div className="flex items-center rounded-[5px] p-1 hover:bg-light-200 dark:hover:bg-dark-400">
<FaTrello className="ml-1" /> <FaTrello className="ml-1" />
<span className="ml-2 block truncate font-normal"> <span className="ml-2 block truncate font-normal">
{source} {source}
@@ -87,7 +85,7 @@ const SelectSource = ({ handleNextStep }: { handleNextStep: () => void }) => {
<div className="mt-5 sm:mt-6"> <div className="mt-5 sm:mt-6">
<button <button
type="submit" type="submit"
className="bg-light-1000 text-light-50 inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Select source Select source
</button> </button>
@@ -219,7 +217,7 @@ const ImportTrello: React.FC = () => {
<input <input
id="apiKey" id="apiKey"
name="apiKey" name="apiKey"
className="ring-light-600 focus:ring-light-700 mb-2 block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6" className="mb-2 block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-700 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"
value={formik.values.apiKey} value={formik.values.apiKey}
onChange={formik.handleChange} onChange={formik.handleChange}
/> />
@@ -232,14 +230,14 @@ const ImportTrello: React.FC = () => {
<input <input
id="token" id="token"
name="token" name="token"
className="ring-light-600 focus:ring-light-700 mb-2 block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6" className="mb-2 block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-700 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"
value={formik.values.token} value={formik.values.token}
onChange={formik.handleChange} onChange={formik.handleChange}
/> />
<div className="mt-5 sm:mt-6"> <div className="mt-5 sm:mt-6">
<button <button
type="submit" type="submit"
className="bg-light-1000 text-light-50 inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Fetch boards Fetch boards
</button> </button>
@@ -259,7 +257,7 @@ export function ImportBoardsForm() {
New import New import
</h2> </h2>
<button <button
className="hover:bg-light-200 rounded p-1 dark:hover:bg-dark-300" className="rounded p-1 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={() => closeModal()} onClick={() => closeModal()}
> >
<HiXMark size={18} className="text-dark-900" /> <HiXMark size={18} className="text-dark-900" />

View File

@@ -1,11 +1,9 @@
"use client"; import { api } from "~/utils/api";
import { api } from "~/trpc/react";
import { HiXMark } from "react-icons/hi2"; import { HiXMark } from "react-icons/hi2";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/app/providers/workspace"; import { useWorkspace } from "~/providers/workspace";
import { Formik, Form, Field } from "formik"; import { Formik, Form, Field } from "formik";
@@ -32,7 +30,7 @@ export function NewBoardForm() {
<div className="flex w-full items-center justify-between pb-4 text-neutral-900 dark:text-dark-1000"> <div className="flex w-full items-center justify-between pb-4 text-neutral-900 dark:text-dark-1000">
<h2 className="text-sm font-bold">New board</h2> <h2 className="text-sm font-bold">New board</h2>
<button <button
className="hover:bg-light-300 rounded p-1 focus:outline-none dark:hover:bg-dark-300" className="rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300"
onClick={() => closeModal()} onClick={() => closeModal()}
> >
<HiXMark size={18} className="text-light-900 dark:text-dark-900" /> <HiXMark size={18} className="text-light-900 dark:text-dark-900" />
@@ -60,12 +58,12 @@ export function NewBoardForm() {
<Field <Field
id="name" id="name"
name="name" name="name"
className="ring-light-600 focus:ring-light-600 block w-full rounded-md border-0 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset dark:bg-dark-300 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6" className="block w-full rounded-md border-0 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-600 dark:bg-dark-300 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"
/> />
<div className="mt-5 sm:mt-6"> <div className="mt-5 sm:mt-6">
<button <button
type="submit" type="submit"
className="bg-light-1000 text-light-50 inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Create board Create board
</button> </button>

View File

@@ -1,14 +1,12 @@
"use client";
import { HiArrowDownTray, HiOutlinePlusSmall } from "react-icons/hi2"; import { HiArrowDownTray, HiOutlinePlusSmall } from "react-icons/hi2";
import { BoardsList } from "./components/BoardsList"; import { BoardsList } from "./components/BoardsList";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import Modal from "~/app/components/modal"; import Modal from "~/components/modal";
import { ImportBoardsForm } from "./components/ImportBoardsForm"; import { ImportBoardsForm } from "./components/ImportBoardsForm";
import { NewBoardForm } from "./components/NewBoardForm"; import { NewBoardForm } from "./components/NewBoardForm";
import { NewWorkspaceForm } from "~/app/components/NewWorkspaceForm"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
export default function BoardsPage() { export default function BoardsPage() {
const { openModal, modalContentType } = useModal(); const { openModal, modalContentType } = useModal();
@@ -22,7 +20,7 @@ export default function BoardsPage() {
<div className="flex"> <div className="flex">
<button <button
type="button" type="button"
className="bg-dark-3000 border-light-600 mr-2 flex items-center gap-x-1.5 rounded-md border-[1px] px-3 py-2 text-sm text-neutral-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:border-dark-600 dark:text-dark-1000" className="bg-dark-3000 mr-2 flex items-center gap-x-1.5 rounded-md border-[1px] border-light-600 px-3 py-2 text-sm text-neutral-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:border-dark-600 dark:text-dark-1000"
onClick={() => openModal("IMPORT_BOARDS")} onClick={() => openModal("IMPORT_BOARDS")}
> >
<div className="flex h-5 w-5 items-center"> <div className="flex h-5 w-5 items-center">
@@ -32,7 +30,7 @@ export default function BoardsPage() {
</button> </button>
<button <button
type="button" type="button"
className="bg-light-1000 text-light-50 flex items-center gap-x-1.5 rounded-md px-3 py-2 text-sm font-semibold focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50" className="flex items-center gap-x-1.5 rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50"
onClick={() => openModal("NEW_BOARD")} onClick={() => openModal("NEW_BOARD")}
> >
<div className="h-5 w-5 items-center"> <div className="h-5 w-5 items-center">

View File

@@ -1,8 +1,6 @@
"use client";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
interface DeleteCardConfirmationProps { interface DeleteCardConfirmationProps {
cardPublicId: string; cardPublicId: string;
@@ -29,13 +27,13 @@ export function DeleteCardConfirmation({
<h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000"> <h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000">
Are you sure you want to delete this card? Are you sure you want to delete this card?
</h2> </h2>
<p className="text-light-900 text-sm font-medium dark:text-dark-900"> <p className="text-sm font-medium text-light-900 dark:text-dark-900">
{"This action can't be undone."} {"This action can't be undone."}
</p> </p>
</div> </div>
<div className="mt-5 flex justify-end sm:mt-6"> <div className="mt-5 flex justify-end sm:mt-6">
<button <button
className="bg-light-50 border-light-600 mr-4 inline-flex justify-center rounded-md border-[1px] px-3 py-2 text-sm font-semibold text-neutral-900 shadow-sm focus-visible:outline-none dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000" className="mr-4 inline-flex justify-center rounded-md border-[1px] border-light-600 bg-light-50 px-3 py-2 text-sm font-semibold text-neutral-900 shadow-sm focus-visible:outline-none dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000"
onClick={() => closeModal()} onClick={() => closeModal()}
> >
Cancel Cancel
@@ -46,7 +44,7 @@ export function DeleteCardConfirmation({
cardPublicId, cardPublicId,
}) })
} }
className="bg-light-1000 text-light-50 inline-flex justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Delete Delete
</button> </button>

View File

@@ -1,9 +1,7 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { HiEllipsisHorizontal } from "react-icons/hi2"; import { HiEllipsisHorizontal } from "react-icons/hi2";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
export default function Dropdown() { export default function Dropdown() {
const { openModal } = useModal(); const { openModal } = useModal();
@@ -11,7 +9,7 @@ export default function Dropdown() {
return ( return (
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
<div> <div>
<Menu.Button className="hover:bg-light-200 flex h-8 w-8 items-center justify-center rounded-[5px] dark:hover:bg-dark-200"> <Menu.Button className="flex h-8 w-8 items-center justify-center rounded-[5px] hover:bg-light-200 dark:hover:bg-dark-200">
<HiEllipsisHorizontal <HiEllipsisHorizontal
size={25} size={25}
className="text-light-900 dark:text-dark-900" className="text-light-900 dark:text-dark-900"
@@ -28,12 +26,12 @@ export default function Dropdown() {
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="bg-light-50 border-light-200 absolute right-0 z-30 mt-2 w-56 origin-top-right rounded-md border shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-400 dark:bg-dark-300"> <Menu.Items className="absolute right-0 z-30 mt-2 w-56 origin-top-right rounded-md border border-light-200 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-400 dark:bg-dark-300">
<div className="flex"> <div className="flex">
<Menu.Item> <Menu.Item>
<button <button
onClick={() => openModal("DELETE_CARD")} onClick={() => openModal("DELETE_CARD")}
className="hover:bg-light-200 m-1 w-full rounded-[5px] px-3 py-2 text-left text-sm text-neutral-900 dark:text-dark-1000 dark:hover:bg-dark-400" className="m-1 w-full rounded-[5px] px-3 py-2 text-left text-sm text-neutral-900 hover:bg-light-200 dark:text-dark-1000 dark:hover:bg-dark-400"
> >
Delete card Delete card
</button> </button>

View File

@@ -1,12 +1,10 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { HiMiniPlus } from "react-icons/hi2"; import { HiMiniPlus } from "react-icons/hi2";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
interface LabelSelectorProps { interface LabelSelectorProps {
cardPublicId: string; cardPublicId: string;
@@ -58,7 +56,7 @@ export default function LabelSelector({
{selectedLabels.map((label) => ( {selectedLabels.map((label) => (
<Menu.Button <Menu.Button
key={label.publicId} key={label.publicId}
className="text-light-800 ring-light-600 my-1 mr-2 inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[12px] font-medium ring-1 ring-inset dark:text-dark-1000 dark:ring-dark-800" className="my-1 mr-2 inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[12px] font-medium text-light-800 ring-1 ring-inset ring-light-600 dark:text-dark-1000 dark:ring-dark-800"
> >
<svg <svg
fill={label.colourCode} fill={label.colourCode}
@@ -71,13 +69,13 @@ export default function LabelSelector({
<div>{label.name}</div> <div>{label.name}</div>
</Menu.Button> </Menu.Button>
))} ))}
<Menu.Button className="hover:bg-light-400 my-1 inline-flex w-fit items-center gap-x-1.5 rounded-full py-1 pl-2 pr-4 text-[12px] font-medium text-dark-800 ring-inset ring-dark-800 dark:hover:bg-dark-200"> <Menu.Button className="my-1 inline-flex w-fit items-center gap-x-1.5 rounded-full py-1 pl-2 pr-4 text-[12px] font-medium text-dark-800 ring-inset ring-dark-800 hover:bg-light-400 dark:hover:bg-dark-200">
<HiMiniPlus size={16} /> <HiMiniPlus size={16} />
Add label Add label
</Menu.Button> </Menu.Button>
</> </>
) : ( ) : (
<Menu.Button className="border-light-200 hover:bg-light-300 flex h-full w-full items-center rounded-[5px] border-[1px] pl-2 text-left text-sm text-neutral-900 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200"> <Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
<HiMiniPlus size={22} className="pr-2" /> <HiMiniPlus size={22} className="pr-2" />
Add label Add label
</Menu.Button> </Menu.Button>
@@ -92,7 +90,7 @@ export default function LabelSelector({
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="bg-light-50 border-light-600 absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200"> <Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="p-2"> <div className="p-2">
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
{labels?.map((label) => ( {labels?.map((label) => (
@@ -100,7 +98,7 @@ export default function LabelSelector({
{() => ( {() => (
<div <div
key={label.publicId} key={label.publicId}
className="hover:bg-light-200 flex items-center rounded-[5px] p-2 dark:hover:bg-dark-300" className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
await formik.setFieldValue( await formik.setFieldValue(
@@ -137,7 +135,7 @@ export default function LabelSelector({
))} ))}
<button <button
onClick={() => openModal("NEW_LABEL")} onClick={() => openModal("NEW_LABEL")}
className="hover:bg-light-200 flex w-full items-center rounded-[5px] p-1.5 px-2 text-sm dark:hover:bg-dark-300" className="flex w-full items-center rounded-[5px] p-1.5 px-2 text-sm hover:bg-light-200 dark:hover:bg-dark-300"
> >
<HiMiniPlus size={22} className="pr-2" /> <HiMiniPlus size={22} className="pr-2" />
Create new label Create new label

View File

@@ -1,7 +1,5 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { useFormik } from "formik"; import { useFormik } from "formik";
@@ -48,7 +46,7 @@ export default function ListSelector({
as="div" as="div"
className="relative flex w-full flex-wrap items-center text-left" className="relative flex w-full flex-wrap items-center text-left"
> >
<Menu.Button className="border-light-200 hover:border-light-300 hover:bg-light-300 flex h-full w-full items-center rounded-[5px] border-[1px] pl-2 text-left text-sm text-neutral-900 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200"> <Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:border-light-300 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
{selectedList?.name} {selectedList?.name}
</Menu.Button> </Menu.Button>
@@ -61,14 +59,14 @@ export default function ListSelector({
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="bg-light-50 border-light-600 absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200"> <Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="p-2"> <div className="p-2">
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
{lists?.map((list) => ( {lists?.map((list) => (
<Menu.Item key={list.publicId}> <Menu.Item key={list.publicId}>
<div <div
key={list.publicId} key={list.publicId}
className="hover:bg-light-200 flex items-center rounded-[5px] p-2 dark:hover:bg-dark-300" className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
await formik.setFieldValue( await formik.setFieldValue(

View File

@@ -1,7 +1,5 @@
"use client";
import { Fragment } from "react"; import { Fragment } from "react";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { HiMiniPlus } from "react-icons/hi2"; import { HiMiniPlus } from "react-icons/hi2";
import { useFormik } from "formik"; import { useFormik } from "formik";
@@ -52,13 +50,13 @@ export default function LabelSelector({
as="div" as="div"
className="relative flex w-full flex-wrap items-center text-left" className="relative flex w-full flex-wrap items-center text-left"
> >
<Menu.Button className="border-light-200 hover:bg-light-300 flex h-full w-full items-center rounded-[5px] border-[1px] pl-2 text-left text-sm text-neutral-900 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200"> <Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
{selectedMembers.length ? ( {selectedMembers.length ? (
<div className="isolate flex -space-x-1 overflow-hidden"> <div className="isolate flex -space-x-1 overflow-hidden">
{selectedMembers.map((member) => ( {selectedMembers.map((member) => (
<span <span
key={member.publicId} key={member.publicId}
className="ring-light-200 relative z-30 inline-flex h-6 w-6 items-center justify-center rounded-full bg-gray-500 ring-1 dark:ring-dark-100" className="relative z-30 inline-flex h-6 w-6 items-center justify-center rounded-full bg-gray-500 ring-1 ring-light-200 dark:ring-dark-100"
> >
<span className="text-[10px] font-medium leading-none text-white"> <span className="text-[10px] font-medium leading-none text-white">
{member.user?.name {member.user?.name
@@ -88,14 +86,14 @@ export default function LabelSelector({
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="bg-light-50 border-light-600 absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200"> <Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="p-2"> <div className="p-2">
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
{members?.map((member) => ( {members?.map((member) => (
<Menu.Item key={member.publicId}> <Menu.Item key={member.publicId}>
<div <div
key={member.publicId} key={member.publicId}
className="hover:bg-light-200 flex items-center rounded-[5px] p-2 dark:hover:bg-dark-300" className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
await formik.setFieldValue( await formik.setFieldValue(

View File

@@ -1,12 +1,10 @@
"use client";
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
import { HiChevronUpDown, HiXMark } from "react-icons/hi2"; import { HiChevronUpDown, HiXMark } from "react-icons/hi2";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import { Formik, Form, Field } from "formik"; import { Formik, Form, Field } from "formik";
@@ -58,7 +56,7 @@ export function NewLabelForm({ cardPublicId }: cardPublicId) {
<div className="flex w-full items-center justify-between pb-4 text-neutral-900 dark:text-dark-1000"> <div className="flex w-full items-center justify-between pb-4 text-neutral-900 dark:text-dark-1000">
<h2 className="text-sm font-medium">New label</h2> <h2 className="text-sm font-medium">New label</h2>
<button <button
className="hover:bg-light-300 rounded p-1 focus:outline-none dark:hover:bg-dark-300" className="rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300"
onClick={() => closeModal()} onClick={() => closeModal()}
> >
<HiXMark size={18} className="text-light-900 dark:text-dark-900" /> <HiXMark size={18} className="text-light-900 dark:text-dark-900" />
@@ -90,13 +88,13 @@ export function NewLabelForm({ cardPublicId }: cardPublicId) {
<Field <Field
id="name" id="name"
name="name" name="name"
className="ring-light-600 focus:ring-light-600 block w-full rounded-md border-0 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset dark:bg-dark-300 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6" className="block w-full rounded-md border-0 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-600 dark:bg-dark-300 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"
/> />
<Listbox value={selected} onChange={setSelected}> <Listbox value={selected} onChange={setSelected}>
{({ open }) => ( {({ open }) => (
<> <>
<div className="relative mt-4"> <div className="relative mt-4">
<Listbox.Button className="ring-light-600 focus:ring-light-600 block w-full rounded-md border-0 bg-white/5 px-4 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset dark:bg-dark-300 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"> <Listbox.Button className="block w-full rounded-md border-0 bg-white/5 px-4 py-1.5 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-600 dark:bg-dark-300 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6">
<span className="flex items-center"> <span className="flex items-center">
<span <span
style={{ backgroundColor: selected?.code }} style={{ backgroundColor: selected?.code }}
@@ -121,7 +119,7 @@ export function NewLabelForm({ cardPublicId }: cardPublicId) {
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="bg-light-50 absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md py-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-dark-300 sm:text-sm"> <Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-light-50 py-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-dark-300 sm:text-sm">
{colours.map((colour, index) => ( {colours.map((colour, index) => (
<Listbox.Option <Listbox.Option
key={`colours_${index}`} key={`colours_${index}`}
@@ -130,7 +128,7 @@ export function NewLabelForm({ cardPublicId }: cardPublicId) {
> >
{() => ( {() => (
<> <>
<div className="hover:bg-light-200 flex items-center rounded-[5px] p-2 dark:hover:bg-dark-400"> <div className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-400">
<span <span
style={{ backgroundColor: colour?.code }} style={{ backgroundColor: colour?.code }}
className="ml-2 inline-block h-2 w-2 flex-shrink-0 rounded-full" className="ml-2 inline-block h-2 w-2 flex-shrink-0 rounded-full"
@@ -153,7 +151,7 @@ export function NewLabelForm({ cardPublicId }: cardPublicId) {
<div className="mt-5 sm:mt-6"> <div className="mt-5 sm:mt-6">
<button <button
type="submit" type="submit"
className="bg-light-1000 text-light-50 inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50" className="inline-flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
> >
Create label Create label
</button> </button>

View File

@@ -1,5 +1,3 @@
"use client";
import Link from "next/link"; import Link from "next/link";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useFormik } from "formik"; import { useFormik } from "formik";
@@ -12,12 +10,12 @@ import LabelSelector from "./components/LabelSelector";
import ListSelector from "./components/ListSelector"; import ListSelector from "./components/ListSelector";
import MemberSelector from "./components/MemberSelector"; import MemberSelector from "./components/MemberSelector";
import { NewLabelForm } from "./components/NewLabelForm"; import { NewLabelForm } from "./components/NewLabelForm";
import { NewWorkspaceForm } from "~/app/components/NewWorkspaceForm"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import Modal from "~/app/components/modal"; import Modal from "~/components/modal";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
interface FormValues { interface FormValues {
cardId: string; cardId: string;
@@ -98,14 +96,14 @@ export default function CardPage() {
<div className="w-full p-8"> <div className="w-full p-8">
<div className="mb-8 flex w-full items-center justify-between"> <div className="mb-8 flex w-full items-center justify-between">
<Link <Link
className="text-light-900 whitespace-nowrap font-medium leading-[2.3rem] tracking-tight dark:text-dark-900 sm:text-[1.2rem]" className="whitespace-nowrap font-medium leading-[2.3rem] tracking-tight text-light-900 dark:text-dark-900 sm:text-[1.2rem]"
href={`/boards/${board?.publicId}`} href={`/boards/${board?.publicId}`}
> >
{board?.name} {board?.name}
</Link> </Link>
<IoChevronForwardSharp <IoChevronForwardSharp
size={18} size={18}
className="text-light-900 mx-2 dark:text-dark-900" className="mx-2 text-light-900 dark:text-dark-900"
/> />
<form onSubmit={formik.handleSubmit} className="w-full space-y-6"> <form onSubmit={formik.handleSubmit} className="w-full space-y-6">
<div> <div>
@@ -135,13 +133,13 @@ export default function CardPage() {
formik.setFieldValue("description", e.target.value) formik.setFieldValue("description", e.target.value)
} }
onBlur={formik.submitForm} onBlur={formik.submitForm}
className="text-light-900 block w-full border-0 bg-transparent py-1.5 focus-visible:outline-none dark:text-dark-1000 sm:text-sm sm:leading-6" className="block w-full border-0 bg-transparent py-1.5 text-light-900 focus-visible:outline-none dark:text-dark-1000 sm:text-sm sm:leading-6"
/> />
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<div className="bg-light-200 border-light-600 text-light-900 min-w-[325px] border-l-[1px] p-8 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-900"> <div className="min-w-[325px] border-l-[1px] border-light-600 bg-light-200 p-8 text-light-900 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-900">
<div className="mb-4 flex w-full"> <div className="mb-4 flex w-full">
<p className="my-2 w-[100px] text-sm">List</p> <p className="my-2 w-[100px] text-sm">List</p>
<ListSelector cardPublicId={cardId} lists={formattedLists} /> <ListSelector cardPublicId={cardId} lists={formattedLists} />

View File

@@ -1,13 +1,11 @@
"use client";
import { HiOutlinePlusSmall } from "react-icons/hi2"; import { HiOutlinePlusSmall } from "react-icons/hi2";
import { useModal } from "~/app/providers/modal"; import { useModal } from "~/providers/modal";
import { useWorkspace } from "~/app/providers/workspace"; import { useWorkspace } from "~/providers/workspace";
import Modal from "~/app/components/modal"; import Modal from "~/components/modal";
import { NewWorkspaceForm } from "~/app/components/NewWorkspaceForm"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import { api } from "~/trpc/react"; import { api } from "~/utils/api";
export default function MembersPage() { export default function MembersPage() {
const { openModal, modalContentType } = useModal(); const { openModal, modalContentType } = useModal();
@@ -27,7 +25,7 @@ export default function MembersPage() {
<div className="flex"> <div className="flex">
<button <button
type="button" type="button"
className="bg-light-1000 text-light-50 flex items-center gap-x-1.5 rounded-md px-3 py-2 text-sm font-semibold focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50" className="flex items-center gap-x-1.5 rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 dark:bg-dark-1000 dark:text-dark-50"
// onClick={() => openModal("")} // onClick={() => openModal("")}
> >
<div className="h-5 w-5 items-center"> <div className="h-5 w-5 items-center">
@@ -45,30 +43,30 @@ export default function MembersPage() {
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg"> <div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
<table className="divide-light-600 min-w-full divide-y dark:divide-dark-600"> <table className="min-w-full divide-y divide-light-600 dark:divide-dark-600">
<thead className="bg-light-300 dark:bg-dark-200"> <thead className="bg-light-300 dark:bg-dark-200">
<tr> <tr>
<th <th
scope="col" scope="col"
className="text-light-900 py-3.5 pl-4 pr-3 text-left text-sm font-semibold dark:text-dark-900 sm:pl-6" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-light-900 dark:text-dark-900 sm:pl-6"
> >
User User
</th> </th>
<th <th
scope="col" scope="col"
className="text-light-900 px-3 py-3.5 text-left text-sm font-semibold dark:text-dark-900" className="px-3 py-3.5 text-left text-sm font-semibold text-light-900 dark:text-dark-900"
> >
Role Role
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-light-50 divide-light-600 divide-y dark:divide-dark-600 dark:bg-dark-100"> <tbody className="divide-y divide-light-600 bg-light-50 dark:divide-dark-600 dark:bg-dark-100">
{data?.members.map((member) => ( {data?.members.map((member) => (
<tr key={member.publicId}> <tr key={member.publicId}>
<td> <td>
<div className="flex items-center p-4"> <div className="flex items-center p-4">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<span className="bg-light-1000 inline-flex h-9 w-9 items-center justify-center rounded-full dark:bg-dark-400"> <span className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-light-1000 dark:bg-dark-400">
<span className="text-sm font-medium leading-none text-white"> <span className="text-sm font-medium leading-none text-white">
{member.user.name {member.user.name
?.split(" ") ?.split(" ")

View File

@@ -1,9 +1,7 @@
"use client"; import { useModal } from "~/providers/modal";
import Modal from "~/components/modal";
import { useModal } from "~/app/providers/modal"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
import Modal from "~/app/components/modal";
import { NewWorkspaceForm } from "~/app/components/NewWorkspaceForm";
export default function SettingsPage() { export default function SettingsPage() {
const { openModal, modalContentType } = useModal(); const { openModal, modalContentType } = useModal();

View File

@@ -1,26 +1,33 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2017", /* Base Options: */
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"checkJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "skipLibCheck": true,
"moduleResolution": "node", "target": "es2022",
"allowJs": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve",
"incremental": true, /* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"checkJs": true,
/* Bundled projects */
"lib": ["dom", "dom.iterable", "ES2022"],
"noEmit": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"plugins": [{ "name": "next" }],
"incremental": true,
/* Path Aliases */
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"~/*": ["./src/*"] "~/*": ["./src/*"]
}, }
"plugins": [{ "name": "next" }]
}, },
"include": [ "include": [
".eslintrc.cjs", ".eslintrc.cjs",
@@ -28,8 +35,9 @@
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
"**/*.cjs", "**/*.cjs",
"**/*.mjs", "**/*.js",
".next/types/**/*.ts" ".next/types/**/*.ts"
], , "src/env.mjs" ],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }