-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[2주차] 조유담 미션 제출합니다 #8
base: master
Are you sure you want to change the base?
Changes from 11 commits
1a88ee1
b4672fd
9b28a7f
5ed73b8
8c140c9
9634d80
14d3ea6
26819a5
32c6473
d76cc93
a5a9cb5
ad67b54
5e897e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module.exports = { | ||
env: { | ||
browser: true, | ||
es2021: true, | ||
node: true, | ||
}, | ||
extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime'], | ||
overrides: [], | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
}, | ||
plugins: ['react'], | ||
rules: { 'react/prop-types': 'off', 'no-unused-vars': 'warn' }, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"printWidth": 120, | ||
"tabWidth": 2, | ||
"singleQuote": true, | ||
"trailingComma": "all", | ||
"semi": true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"compilerOptions": { | ||
"baseUrl": "src" | ||
} | ||
} | ||
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,18 @@ | ||
<!DOCTYPE html> | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<meta name="theme-color" content="#000000" /> | ||
<meta | ||
name="description" | ||
content="Web site created using create-react-app" | ||
/> | ||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> | ||
<!-- | ||
manifest.json provides metadata used when your web app is installed on a | ||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ | ||
--> | ||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> | ||
<!-- | ||
Notice the use of %PUBLIC_URL% in the tags above. | ||
It will be replaced with the URL of the `public` folder during the build. | ||
Only files inside the `public` folder can be referenced from the HTML. | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>TODO-LIST</title> | ||
<link rel="stylesheet" href="./style/style.css" /> | ||
|
||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will | ||
work correctly both with client-side routing and a non-root public URL. | ||
Learn how to configure a non-root public URL by running `npm run build`. | ||
--> | ||
<title>React App</title> | ||
<meta property="og:title" content="TODO-LIST" /> | ||
<meta property="og:url" content="https://vanilla-todo-19th-navy.vercel.app/" /> | ||
<meta property="og:type" content="website" /> | ||
<meta property="og:description" content="오늘의 일정을 관리해보세요." /> | ||
</head> | ||
<body> | ||
<noscript>You need to enable JavaScript to run this app.</noscript> | ||
<div id="root"></div> | ||
<!-- | ||
This HTML file is a template. | ||
If you open it directly in the browser, you will see an empty page. | ||
|
||
You can add webfonts, meta tags, or analytics to this file. | ||
The build step will place the bundled scripts into the <body> tag. | ||
|
||
To begin the development, run `npm start` or `yarn start`. | ||
To create a production bundle, use `npm run build` or `yarn build`. | ||
--> | ||
</body> | ||
</html> |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import TodoCreate from 'components/TodoCreate'; | ||
import TodoHeader from 'components/TodoHeader'; | ||
import TodoList from 'components/TodoList'; | ||
import { useEffect, useReducer } from 'react'; | ||
import todoReducer from 'reducer/todoReducer'; | ||
import styled from 'styled-components'; | ||
import { flexCenter, flexColumn } from 'styles/commonStyle'; | ||
|
||
const initialState = { | ||
todoList: JSON.parse(localStorage.getItem('todoList')) || [], | ||
doneList: JSON.parse(localStorage.getItem('doneList')) || [], | ||
}; | ||
|
||
function App() { | ||
const [state, dispatch] = useReducer(todoReducer, initialState); | ||
const { todoList, doneList } = state; | ||
|
||
useEffect(() => { | ||
localStorage.setItem('todoList', JSON.stringify(todoList)); | ||
localStorage.setItem('doneList', JSON.stringify(doneList)); | ||
}, [todoList]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 의존성 배열을 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 시간이 없어서 QA를 제대로 못한 잘못이네요 ㅠㅠㅋㅋ done<->todo로 이동할 때만 doneList가 바뀐다고 생각해서 todoList만 의존성 배열에 넣어줬네요 ! 삭제를 고려 못했어요 감사합니다! |
||
|
||
return ( | ||
<TodoAppLayout> | ||
<TodoHeader /> | ||
<TodoMain> | ||
<TodoCreate list={todoList} dispatch={dispatch} /> | ||
<TodoListContainer> | ||
<TodoList listName="todo" list={todoList} dispatch={dispatch} /> | ||
<TodoList listName="done" list={doneList} dispatch={dispatch} /> | ||
</TodoListContainer> | ||
</TodoMain> | ||
</TodoAppLayout> | ||
); | ||
} | ||
|
||
export default App; | ||
|
||
const TodoAppLayout = styled.div` | ||
padding: 4rem 0; | ||
gap: 2rem; | ||
${flexCenter} | ||
${flexColumn} | ||
`; | ||
|
||
const TodoMain = styled.main` | ||
${flexColumn} | ||
gap: 3rem; | ||
`; | ||
|
||
const TodoListContainer = styled.section` | ||
display: flex; | ||
width: 100%; | ||
justify-content: space-between; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { useState } from 'react'; | ||
import styled from 'styled-components'; | ||
import { flexCenter, flexColumn } from 'styles/commonStyle'; | ||
import { v4 as uuid } from 'uuid'; | ||
|
||
function TodoCreate({ dispatch }) { | ||
const [inputValue, setInputValue] = useState(''); | ||
const [isError, setIsError] = useState(false); | ||
|
||
const handleSubmit = (e) => { | ||
e.preventDefault(); | ||
if (!inputValue?.trim()) { | ||
setIsError(true); | ||
return; | ||
} | ||
|
||
dispatch({ | ||
type: 'ADD_TODO', | ||
payload: { | ||
id: uuid(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 Date.now()로 현재 시간을 ID로 지정해줬는데요..! |
||
text: inputValue, | ||
}, | ||
}); | ||
|
||
setIsError(false); | ||
setInputValue(''); | ||
}; | ||
|
||
const handleInputValueChange = (e) => { | ||
setInputValue(e.target.value); | ||
}; | ||
|
||
return ( | ||
<InputForm onSubmit={handleSubmit}> | ||
<InputContainer> | ||
<InputWrapper> | ||
<input className="input" placeholder="할 일 입력" value={inputValue} onChange={handleInputValueChange} /> | ||
<button className="input-button">입력</button> | ||
</InputWrapper> | ||
<ErrorMessage $isError={isError}>할 일을 입력해주세요</ErrorMessage> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 입력값이 없을 때 에러 메시지 띄우는 디테일 너무 좋네요~👍🏻 |
||
</InputContainer> | ||
</InputForm> | ||
); | ||
} | ||
|
||
export default TodoCreate; | ||
|
||
const InputForm = styled.form` | ||
display: flex; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ${flexCenter}에 |
||
width: 100%; | ||
height: 6rem; | ||
${flexCenter} | ||
`; | ||
|
||
const InputContainer = styled.div` | ||
${flexColumn} | ||
height: 100%; | ||
gap: 1rem; | ||
`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인풋 + 버튼은 가로 방향으로 , 이 둘을 감싸고 있는 InputWrapper와 ErrorMessage는 세로 방향으로 배치가 되어야해서요..!! |
||
|
||
const InputWrapper = styled.div` | ||
display: flex; | ||
height: 100%; | ||
gap: 2rem; | ||
|
||
& { | ||
font-size: 2.5rem; | ||
} | ||
|
||
.input { | ||
width: 50rem; | ||
padding: 1rem; | ||
border-radius: 1rem; | ||
} | ||
Comment on lines
+69
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
.input:focus { | ||
border: 0.1rem solid #6d6afe; | ||
} | ||
|
||
.input-button { | ||
width: 8rem; | ||
padding: 0.2rem; | ||
background: linear-gradient(91deg, #6d6afe 0.12%, #6ae3fe 101.84%); | ||
color: white; | ||
font-weight: 200; | ||
border-radius: 1rem; | ||
} | ||
`; | ||
|
||
const ErrorMessage = styled.p` | ||
color: red; | ||
font-size: 1.7rem; | ||
margin-left: 0.4rem; | ||
|
||
visibility: ${({ $isError }) => ($isError ? 'visible' : 'hidden')}; | ||
`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 조건부 연산자를 이용한 동적 스타일링 좋은 것 같아요~ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { useEffect, useState } from 'react'; | ||
import styled from 'styled-components'; | ||
import { getTodayDate } from 'util/getTodayDate'; | ||
|
||
function TodoHeader() { | ||
const [userName, setUserName] = useState(localStorage.getItem('userName') ?? ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 nullish 병합 연산자 |
||
|
||
// 사용자 이름 설정 | ||
const showUserName = () => { | ||
if (!userName) { | ||
const nameInput = prompt('이름을 알려주세요!'); | ||
localStorage.setItem('userName', nameInput); | ||
setUserName(nameInput); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용자 이름 설정 기능 신박하고 좋네요~~ |
||
} | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용자 이름 설정 기능까지 추가로 구현하시다니..! |
||
|
||
useEffect(() => { | ||
showUserName(); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 코드에서는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 실수입니다..!! 감사해요 ㅎㅎ |
||
|
||
return ( | ||
<HeaderWrapper> | ||
<p>{getTodayDate()}</p> | ||
<h1> | ||
<span>{userName}이의 </span>TODO-LIST | ||
</h1> | ||
</HeaderWrapper> | ||
); | ||
} | ||
|
||
export default TodoHeader; | ||
|
||
const HeaderWrapper = styled.header` | ||
display: flex; | ||
font-size: 2.5rem; | ||
flex-direction: column; | ||
justify-content: center; | ||
align-items: center; | ||
gap: 1rem; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import styled from 'styled-components'; | ||
import { flexCenter, flexColumn } from 'styles/commonStyle'; | ||
import { FaRegCheckCircle } from 'react-icons/fa'; | ||
import { FaRegCircle } from 'react-icons/fa'; | ||
import { FaRegTrashAlt } from 'react-icons/fa'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 항상 이미지로 사용했는데 유담님처럼 react-icons 라이브러리를 사용하는 방법도 좋은 것 같아요! |
||
|
||
function TodoList({ listName, list, dispatch }) { | ||
const handleListItemClick = ({ id, text }) => { | ||
const actionType = listName === 'todo' ? 'MOVE_TODO_TO_DONE' : 'MOVE_DONE_TO_TODO'; | ||
dispatch({ type: actionType, payload: { id, text } }); | ||
}; | ||
|
||
const handleDeleteItem = (e, { id, text }) => { | ||
e.stopPropagation(); | ||
const actionType = listName === 'todo' ? 'REMOVE_TODO' : 'REMOVE_DONE'; | ||
dispatch({ type: actionType, payload: { id, text } }); | ||
}; | ||
|
||
return ( | ||
<TodoListWrapper> | ||
<h2> | ||
{listName} <span> / {list.length}개</span> | ||
</h2> | ||
<TodoListContainer> | ||
{list.map((li) => ( | ||
<TodoListItem key={li.id} onClick={() => handleListItemClick(li)}> | ||
{listName == 'todo' ? <FaRegCircle /> : <FaRegCheckCircle />} | ||
{li.text} | ||
<FaRegTrashAlt className="trash-icon" onClick={(e) => handleDeleteItem(e, li)} /> | ||
</TodoListItem> | ||
))} | ||
</TodoListContainer> | ||
</TodoListWrapper> | ||
); | ||
} | ||
|
||
export default TodoList; | ||
|
||
const TodoListWrapper = styled.article` | ||
display: flex; | ||
width: 28rem; | ||
height: 55rem; | ||
padding: 2rem; | ||
border: 0.13rem solid #6d6afe; | ||
gap: 1rem; | ||
align-items: center; | ||
flex-direction: column; | ||
border-radius: 2rem; | ||
|
||
& { | ||
font-size: 2.5rem; | ||
} | ||
`; | ||
|
||
const TodoListContainer = styled.ul` | ||
${flexColumn} | ||
gap: 1rem; | ||
overflow: auto; | ||
`; | ||
|
||
const TodoListItem = styled.li` | ||
display: flex; | ||
${flexCenter} | ||
gap: 1rem; | ||
cursor: pointer; | ||
.trash-icon { | ||
visibility: hidden; | ||
} | ||
&:hover .trash-icon { | ||
visibility: visible; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
절대경로를 설정해주셔서 매번 상대 경로를 작성하어 import하는 것보다 편리한 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
절대 경로 설정 배워갑니다! 다음 과제부터 적용해봐야겠어요!