diff --git a/src/App.tsx b/src/App.tsx index 4888c19abf..fb300a04d6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,117 +1,30 @@ import './App.scss'; - +import { useState } from 'react'; import usersFromServer from './api/users'; import todosFromServer from './api/todos'; -import { useState } from 'react'; -import { FormData, Todo } from './types'; -import { TodoList } from './components/TodoList'; -import { getNewTodoId } from './services'; +import { getUserById } from './utils'; +import { TodoList } from './components/TodoList/TodoList'; +import { NewTodoForm } from './components/NewTodoForm'; -const initialForm: FormData = { - title: '', - userId: 0, -}; +const todoRoster = todosFromServer.map(todo => ({ + ...todo, + user: getUserById(usersFromServer, todo.userId), +})); export const App = () => { - const [todos, setTodos] = useState(todosFromServer); - const [formData, setFormData] = useState(initialForm); - const [isTitleError, setIsTitleError] = useState(false); - const [isUserError, setIsUserError] = useState(false); - - const { title, userId } = formData; - - const isDisabledAdd = !title.trim() || !userId; - - const reset = () => { - setFormData({ ...initialForm }); - }; - - const handleAddTodo = (inputData: FormData) => { - const newTodo = { - ...inputData, - id: getNewTodoId(todos), - completed: false, - }; - - setTodos(currentTodos => [...currentTodos, newTodo]); - }; - - const handleTitleChange = (event: React.ChangeEvent) => { - setFormData(prevData => ({ - ...prevData, - title: event.target.value, - })); - setIsTitleError(false); - }; - - const handleUserChange = (event: React.ChangeEvent) => { - setFormData(prevData => ({ - ...prevData, - userId: +event.target.value, - })); - setIsUserError(false); - }; - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - if (isDisabledAdd) { - setIsTitleError(!title); - setIsUserError(!userId); - - return; - } - - handleAddTodo(formData); - reset(); - }; + const [todos, setTodos] = useState(todoRoster); return (

Add todo form

-
-
- - - {isTitleError && Please enter a title} -
- -
- - - - {isUserError && Please choose a user} -
- - -
+ { + setTodos(prevTodos => [...prevTodos, newTodo]); + }} + />
diff --git a/src/components/NewTodoForm/NewTodoForm.tsx b/src/components/NewTodoForm/NewTodoForm.tsx new file mode 100644 index 0000000000..d5a391c59c --- /dev/null +++ b/src/components/NewTodoForm/NewTodoForm.tsx @@ -0,0 +1,101 @@ +import React, { useState } from 'react'; +import { Todo, User } from '../../types'; +import { createNewId, getUserById, removeInvalidCharacters } from '../../utils'; + +type Props = { + todos: Todo[]; + users: User[]; + handleAdd: (todo: Todo) => void; +}; + +export const NewTodoForm: React.FC = ({ todos, users, handleAdd }) => { + const [title, setTitle] = useState(''); + const [hasTitleInputError, setHasTitleInputError] = useState(false); + const [userId, setUserId] = useState(0); + const [hasUserSelectError, setHasUserSelectError] = useState(false); + + function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + if (!title) { + setHasTitleInputError(true); + } + + if (!userId) { + setHasUserSelectError(true); + } + + if (title && userId) { + setTitle(''); + setUserId(0); + handleAdd({ + id: createNewId(todos), + title: removeInvalidCharacters(title), + completed: false, + userId, + user: getUserById(users, userId), + }); + } + } + + function handleTitleInputChange(event: React.ChangeEvent) { + setTitle(event.target.value); + setHasTitleInputError(false); + } + + function handleUserSelect(event: React.ChangeEvent) { + setUserId(Number(event.target.value)); + setHasUserSelectError(false); + } + + return ( +
+
+ + {hasTitleInputError && ( + Please enter a title + )} +
+ +
+ + + {hasUserSelectError && ( + Please choose a user + )} +
+ + +
+ ); +}; diff --git a/src/components/NewTodoForm/index.ts b/src/components/NewTodoForm/index.ts new file mode 100644 index 0000000000..e58e9e081e --- /dev/null +++ b/src/components/NewTodoForm/index.ts @@ -0,0 +1 @@ +export * from './NewTodoForm'; diff --git a/src/components/TodoInfo/TodoInfo.tsx b/src/components/TodoInfo/TodoInfo.tsx index 523a76e0b3..9581438e8a 100644 --- a/src/components/TodoInfo/TodoInfo.tsx +++ b/src/components/TodoInfo/TodoInfo.tsx @@ -1,27 +1,20 @@ -import { getUserById } from '../../services'; +import React from 'react'; +import classNames from 'classnames'; import { Todo } from '../../types'; import { UserInfo } from '../UserInfo'; -import classNames from 'classnames'; - type Props = { todo: Todo; }; -export const TodoInfo: React.FC = ({ todo }) => { - const user = getUserById(todo.userId); - - return ( -
-

{todo.title}

- - {user && } -
- ); -}; +export const TodoInfo: React.FC = ({ todo }) => ( +
+

{todo.title}

+ {todo.user && } +
+); diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx index e32cd3969a..ac4336f6b9 100644 --- a/src/components/TodoList/TodoList.tsx +++ b/src/components/TodoList/TodoList.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { Todo } from '../../types'; import { TodoInfo } from '../TodoInfo'; @@ -5,12 +6,10 @@ type Props = { todos: Todo[]; }; -export const TodoList: React.FC = ({ todos }) => { - return ( -
- {todos.map(todo => ( - - ))} -
- ); -}; +export const TodoList: React.FC = ({ todos }) => ( +
+ {todos.map(todo => ( + + ))} +
+); diff --git a/src/components/UserInfo/UserInfo.tsx b/src/components/UserInfo/UserInfo.tsx index 460834e8e6..9da231397b 100644 --- a/src/components/UserInfo/UserInfo.tsx +++ b/src/components/UserInfo/UserInfo.tsx @@ -1,13 +1,12 @@ +import React from 'react'; import { User } from '../../types'; type Props = { user: User; }; -export const UserInfo: React.FC = ({ user }) => { - return ( - - {user.name} - - ); -}; +export const UserInfo: React.FC = ({ user }) => ( + + {user.name} + +); diff --git a/src/services.ts b/src/services.ts deleted file mode 100644 index 678c02aff6..0000000000 --- a/src/services.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Todo, User } from './types'; -import usersFromServer from './api/users'; - -export const getUserById = (userId: number): User | null => { - return usersFromServer.find(user => user.id === userId) || null; -}; - -export const getNewTodoId = (todoList: Todo[]) => { - return Math.max(...todoList.map(todo => todo.id)) + 1; -}; diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index cfc863c28e..0000000000 --- a/src/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Todo { - id: number; - title: string; - completed: boolean; - userId: number; -} - -export interface User { - id: number; - name: string; - username: string; - email: string; -} - -export type FormData = Omit; diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 0000000000..5910def44b --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,9 @@ +import { User } from './User'; + +export interface Todo { + id: number; + title: string; + completed: boolean; + userId: number; + user: User | null; +} diff --git a/src/types/User.ts b/src/types/User.ts new file mode 100644 index 0000000000..1f6908b55a --- /dev/null +++ b/src/types/User.ts @@ -0,0 +1,6 @@ +export interface User { + id: number; + name: string; + username: string; + email: string; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000000..081c59c60c --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './Todo'; +export * from './User'; diff --git a/src/utils/createNewId.ts b/src/utils/createNewId.ts new file mode 100644 index 0000000000..25bc115365 --- /dev/null +++ b/src/utils/createNewId.ts @@ -0,0 +1,9 @@ +import { Todo } from '../types'; + +export function createNewId(todos: Todo[]) { + const maxId = todos.reduce((acc: number, todo: Todo) => { + return Math.max(todo.id, acc); + }, 0); + + return maxId + 1; +} diff --git a/src/utils/getUserById.ts b/src/utils/getUserById.ts new file mode 100644 index 0000000000..2786d1dfa0 --- /dev/null +++ b/src/utils/getUserById.ts @@ -0,0 +1,5 @@ +import { User } from '../types'; + +export function getUserById(users: User[], userId: number) { + return users.find(user => user.id === userId) || null; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000000..78b26280db --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './createNewId'; +export * from './getUserById'; +export * from './removeInvalidCharacters'; diff --git a/src/utils/removeInvalidCharacters.ts b/src/utils/removeInvalidCharacters.ts new file mode 100644 index 0000000000..dd42811696 --- /dev/null +++ b/src/utils/removeInvalidCharacters.ts @@ -0,0 +1,18 @@ +const enAbc = 'abcdefghijklmnopqrstuvwxyz'; +const uaAbc = 'абвгґдеєжзиіїйклмнопрстуфхцчшщьюя'; +const digits = '123456789'; +const symbols = " '`"; +const allowedCharacters = enAbc + uaAbc + digits + symbols; + +export function removeInvalidCharacters(str: string): string { + const trimmedStr = str.trim(); + let validStr = ''; + + for (let i = 0; i < trimmedStr.length; i++) { + if (allowedCharacters.includes(trimmedStr[i].toLowerCase())) { + validStr += trimmedStr[i]; + } + } + + return validStr; +}