diff --git a/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs b/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs index 66b0368..9863fd2 100644 --- a/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs +++ b/src/backend/server/src/Todo.Api/Hubs/TodoHub.cs @@ -16,6 +16,8 @@ namespace Todo.Api.Hubs public async Task CreateTodo(string todoTitle, string projectName) { + if (todoTitle is null) + throw new ArgumentException("title cannot be null"); var _ = await _todoRepository.CreateTodoAsync(todoTitle, projectName); var todos = await _todoRepository.GetNotDoneTodos(); diff --git a/src/client/package.json b/src/client/package.json index cb7eac1..f932373 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -14,6 +14,7 @@ "next-pwa": "^5.4.0", "react": "17.0.2", "react-dom": "17.0.2", + "react-textarea-autosize": "^8.3.3", "tailwindcss": "^2.2.19" }, "devDependencies": { diff --git a/src/client/src/components/common/buttons/outlinedButton.tsx b/src/client/src/components/common/buttons/outlinedButton.tsx new file mode 100644 index 0000000..89d940f --- /dev/null +++ b/src/client/src/components/common/buttons/outlinedButton.tsx @@ -0,0 +1,15 @@ +import { ButtonHTMLAttributes, FC, forwardRef } from "react"; + +export const OutlinedButton: FC> = ( + props +) => ( + +); diff --git a/src/client/src/components/common/buttons/primaryButton.tsx b/src/client/src/components/common/buttons/primaryButton.tsx new file mode 100644 index 0000000..9925a07 --- /dev/null +++ b/src/client/src/components/common/buttons/primaryButton.tsx @@ -0,0 +1,19 @@ +import { ButtonHTMLAttributes, FC } from "react"; + +export const PrimaryButton: FC> = ( + props +) => ( + +); diff --git a/src/client/src/components/todos/add/addProjectButton.tsx b/src/client/src/components/todos/add/addProjectButton.tsx new file mode 100644 index 0000000..42df446 --- /dev/null +++ b/src/client/src/components/todos/add/addProjectButton.tsx @@ -0,0 +1,62 @@ +import { ButtonHTMLAttributes, FC, useState } from "react"; +import { OutlinedButton } from "@src/components/common/buttons/outlinedButton"; +import Tippy from "@tippyjs/react"; + +interface AddProjectButtonProps + extends ButtonHTMLAttributes { + initialProject: string; + onProjectChanged: (project: string) => void; +} + +export const AddProjectButton: FC = (props) => { + const [menuIsOpen, setMenuIsOpen] = useState(false); + const [project, setProject] = useState(props.initialProject); + return ( + { + setMenuIsOpen(false); + }} + delay={0} + interactive={true} + duration={0} + content={ +
+
+ setProject(e.target.value)} + placeholder="Project" + onKeyDown={(k) => { + if (k.key === "Enter") { + props.onProjectChanged(project); + } + }} + onBlur={() => { + props.onProjectChanged(project); + }} + /> +
+
+ } + > + + setMenuIsOpen(true)} + > + {props.children || "Add project"} + + +
+ ); +}; diff --git a/src/client/src/components/todos/add/addTodoForm.tsx b/src/client/src/components/todos/add/addTodoForm.tsx new file mode 100644 index 0000000..b646062 --- /dev/null +++ b/src/client/src/components/todos/add/addTodoForm.tsx @@ -0,0 +1,42 @@ +import { FC, useState } from "react"; +import { OutlinedButton } from "@src/components/common/buttons/outlinedButton"; +import { PrimaryButton } from "@src/components/common/buttons/primaryButton"; +import { TodoShortForm } from "@src/components/todos/collapsed/todoShortForm"; + +export const AddTodoForm: FC<{ + onAdd: (todoName: string, project: string) => void; + onClose: () => void; + project: string; +}> = ({ onAdd, onClose, ...props }) => { + const [todoName, setTodoName] = useState(""); + const [todoDescription, setTodoDescription] = useState(""); + const [project, setProject] = useState(props.project); + + return ( +
{ + e.preventDefault(); + onAdd(todoName, project); + setTodoName(""); + setTodoDescription(""); + }} + > +
+ setProject(p)} + name={todoName} + onNameChange={(e) => setTodoName(e.target.value)} + description={todoDescription} + onDescriptionChange={(e) => setTodoDescription(e.target.value)} + /> +
+ + Add todo + + Cancel +
+
+
+ ); +}; diff --git a/src/client/src/components/todos/addTodo.tsx b/src/client/src/components/todos/addTodo.tsx index 41e6250..de2e689 100644 --- a/src/client/src/components/todos/addTodo.tsx +++ b/src/client/src/components/todos/addTodo.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { CollapsedAddTodo } from "@src/components/todos/collapsed/collapsedAddTodo"; -import { AddTodoForm } from "@src/components/todos/collapsed/addTodoForm"; +import { AddTodoForm } from "@src/components/todos/add/addTodoForm"; import { CollapsedState } from "@src/components/todos/collapsed/collapsedState"; import { useCreateTodo } from "@src/presentation/hooks/socketHooks"; diff --git a/src/client/src/components/todos/collapsed/addTodoForm.tsx b/src/client/src/components/todos/collapsed/addTodoForm.tsx deleted file mode 100644 index 9a347e0..0000000 --- a/src/client/src/components/todos/collapsed/addTodoForm.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { FC, useState } from "react"; - -export const AddTodoForm: FC<{ - onAdd: (todoName: string, project: string) => void; - onClose: () => void; - project: string; -}> = ({ onAdd, onClose, ...props }) => { - const [todoName, setTodoName] = useState(""); - const [project, setProject] = useState(props.project); - - return ( -
{ - e.preventDefault(); - onAdd(todoName, project); - setTodoName(""); - }} - > -
-
-
- setTodoName(e.target.value)} - /> -
-
- setProject(e.target.value)} - /> -
-
-
- - -
-
-
- ); -}; diff --git a/src/client/src/components/todos/collapsed/todoShortForm.tsx b/src/client/src/components/todos/collapsed/todoShortForm.tsx new file mode 100644 index 0000000..d301312 --- /dev/null +++ b/src/client/src/components/todos/collapsed/todoShortForm.tsx @@ -0,0 +1,48 @@ +import TextareaAutosize from "react-textarea-autosize"; +import { AddProjectButton } from "@src/components/todos/add/addProjectButton"; + +export const TodoShortForm = (props: { + project: string; + onProjectChanged: (project: string) => void; + name: string; + onNameChange: (e) => void; + description: string; + onDescriptionChange: (e) => void; +}) => ( +
+
+
+ +
+
+
+ +
+
+ + {props.project} + +
+
+
+
+); diff --git a/src/client/src/pages/todos/[todoId]/index.tsx b/src/client/src/pages/todos/[todoId]/index.tsx index 24d8865..acd721d 100644 --- a/src/client/src/pages/todos/[todoId]/index.tsx +++ b/src/client/src/pages/todos/[todoId]/index.tsx @@ -19,7 +19,7 @@ const EditTodo: FC = ({ todo, onCancel, onSave }) => { return (
-
+
= ({ todo, onCancel, onSave }) => { />
-
+
= ({ todo }) => { const [editMode, setEditMode] = useState(false); return ( -
+
{editMode ? ( diff --git a/src/client/src/styles/tailwind.css b/src/client/src/styles/tailwind.css index e1f7b37..c15dceb 100644 --- a/src/client/src/styles/tailwind.css +++ b/src/client/src/styles/tailwind.css @@ -1,8 +1,8 @@ @tailwind base; @tailwind components; -input { - @apply shadow-none placeholder-gray-50 dark:placeholder-gray-500 border-none outline-none; +input, textarea { + @apply shadow-none placeholder-gray-50 placeholder-gray-500 border-none outline-none; background: transparent; } @@ -17,12 +17,14 @@ input { px-4 rounded-lg transition - bg-gray-700 - disabled:bg-gray-800 - active:bg-gray-600 + dark:bg-gray-700 + disabled:bg-gray-200 + active:bg-gray-300 + disabled:dark:bg-gray-800 + active:dark:bg-gray-600 border - border-gray-500 - disabled:border-gray-600 + border-gray-300 + disabled:border-gray-400 text-sm font-semibold disabled:text-gray-300 diff --git a/src/client/yarn.lock b/src/client/yarn.lock index 28ceb31..8e1c9fe 100644 --- a/src/client/yarn.lock +++ b/src/client/yarn.lock @@ -4577,6 +4577,15 @@ react-refresh@0.8.3: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== +react-textarea-autosize@^8.3.3: + version "8.3.3" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" + integrity sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ== + dependencies: + "@babel/runtime" "^7.10.2" + use-composed-ref "^1.0.0" + use-latest "^1.0.0" + react@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -5321,6 +5330,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +ts-essentials@^2.0.3: + version "2.0.12" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" + integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== + tsconfig-paths@^3.11.0, tsconfig-paths@^3.9.0: version "3.11.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" @@ -5455,6 +5469,25 @@ url-parse@^1.4.3: querystringify "^2.1.1" requires-port "^1.0.0" +use-composed-ref@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc" + integrity sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg== + dependencies: + ts-essentials "^2.0.3" + +use-isomorphic-layout-effect@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz#7bb6589170cd2987a152042f9084f9effb75c225" + integrity sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ== + +use-latest@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.0.tgz#a44f6572b8288e0972ec411bdd0840ada366f232" + integrity sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw== + dependencies: + use-isomorphic-layout-effect "^1.0.0" + use-subscription@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1"