diff --git a/.eslintrc b/.eslintrc index 797783b..d1f4bde 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,5 +17,11 @@ "sourceType": "module" }, "plugins": ["react"], - "rules": {} + "rules": { + "prettier/prettier": { + "warn": { + "endOfLine": "auto" + } + } + } } diff --git a/package-lock.json b/package-lock.json index 3ac9a01..224d5be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,10 @@ "gh-pages": "^6.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.1.2", - "react-scripts": "5.0.1", + "react-icons": "^4.11.0", + "react-redux": "^8.1.3", + "react-scripts": "^5.0.1", + "redux": "^4.2.1", "web-vitals": "^2.1.4" }, "devDependencies": { @@ -14730,15 +14732,23 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-icons": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", + "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "node_modules/react-redux": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz", - "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", "dependencies": { "@babel/runtime": "^7.12.1", "@types/hoist-non-react-statics": "^3.3.1", @@ -14901,6 +14911,14 @@ "node": ">=6.0.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", diff --git a/package.json b/package.json index 363e8a1..c131384 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,10 @@ "gh-pages": "^6.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.1.2", - "react-scripts": "5.0.1", + "react-icons": "^4.11.0", + "react-redux": "^8.1.3", + "react-scripts": "^5.0.1", + "redux": "^4.2.1", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/index.html b/public/index.html index d79d7a7..4da5644 100644 --- a/public/index.html +++ b/public/index.html @@ -7,34 +7,10 @@ - - React App -
- diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/src/App.jsx b/src/App.jsx index 6ed9816..300c87e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,7 +1,15 @@ import React from 'react'; +import InputForm from 'components/InputForm'; +import TodoList from 'components/TodoList'; function App() { - return

Hello, World!

; + return ( +
+

니할일이뭔데그래서...

+ + +
+ ); } export default App; diff --git a/src/common/actions.jsx b/src/common/actions.jsx new file mode 100644 index 0000000..35e140c --- /dev/null +++ b/src/common/actions.jsx @@ -0,0 +1,30 @@ +export const ADD = 'ADD_TODO'; +export const DELETE = 'DELETE_TODO'; +export const TOGGLE = 'TOGGLE_TODO'; + +let prevId = 0; + +export const addTodo = (todo) => { + return { + type: ADD, + todo: { + id: prevId++, + text: todo.text, + done: todo.done, + }, + }; +}; + +export const deleteTodo = (id) => { + return { + type: DELETE, + id, + }; +}; + +export const toggleTodo = (id) => { + return { + type: TOGGLE, + id, + }; +}; diff --git a/src/common/reducer.jsx b/src/common/reducer.jsx new file mode 100644 index 0000000..ce67457 --- /dev/null +++ b/src/common/reducer.jsx @@ -0,0 +1,28 @@ +import { ADD, DELETE, TOGGLE } from './actions'; + +const initialState = { + todos: [], +}; + +export const reducer = (state = initialState, action) => { + switch (action.type) { + case ADD: + return { + todos: [...state.todos, action.todo], + }; + + case TOGGLE: + return { + ...state, + todos: state.todos.map((todo) => (todo.id === action.id ? { ...todo, done: !todo.done } : todo)), + }; + + case DELETE: + return { + todos: [...state.todos.filter((todo) => todo.id !== action.id)], + }; + + default: + return state; + } +}; diff --git a/src/components/InputForm.jsx b/src/components/InputForm.jsx new file mode 100644 index 0000000..f72f19e --- /dev/null +++ b/src/components/InputForm.jsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { addTodo } from 'common/actions'; + +const InputForm = () => { + const dispatch = useDispatch(); + + const [content, setContent] = useState(''); + + const handleChange = (e) => { + const { value } = e.target; + setContent(value); + }; + + const handleClick = () => { + const todo = { + text: content, + done: false, + }; + + dispatch(addTodo(todo)); + setContent(''); + }; + + const handleKeypress = (e) => { + if (e.key === 'Enter') { + handleClick(); + } + }; + + return ( +
+ + +
+ ); +}; + +export default InputForm; diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx new file mode 100644 index 0000000..ad691de --- /dev/null +++ b/src/components/TodoItem.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; +import PropTypes from 'prop-types'; +import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md'; +import { deleteTodo, toggleTodo } from 'common/actions'; + +const TodoItem = ({ todo }) => { + const dispatch = useDispatch(); + + const { id, text, done } = todo; + + const handleClick = () => { + dispatch(deleteTodo(id)); + }; + + const handleCheckboxChange = () => { + dispatch(toggleTodo(id)); + }; + + return ( +
+
+ {done ? ( + + ) : ( + + )} +
+ {text} + +
+ ); +}; + +TodoItem.propTypes = { + todo: PropTypes.shape({ + id: PropTypes.number.isRequired, + text: PropTypes.string.isRequired, + done: PropTypes.bool.isRequired, + }).isRequired, +}; + +export default TodoItem; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 0000000..fcfd03e --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import TodoItem from './TodoItem'; + +const TodoList = () => { + const todos = useSelector((state) => state.todos); + + return ( +
+ {todos.map((todo) => ( + + ))} +
+ ); +}; + +export default TodoList; diff --git a/src/index.jsx b/src/index.jsx index 793e2b8..492e5d5 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,7 +1,17 @@ import React from 'react'; +import { createStore } from 'redux'; import ReactDOM from 'react-dom/client'; + import './index.css'; +import { Provider } from 'react-redux'; +import { reducer } from './common/reducer'; import App from './App'; +const reduxStore = createStore(reducer); const root = ReactDOM.createRoot(document.getElementById('root')); -root.render(); + +root.render( + + + , +);