원티드 프리온보딩 코스 사전과제 레포지토리입니다.
TODO list를 javascript로 구현했습니다.
아래 코드를 순차적으로 터미널에 입력시 프로젝트를 확인할 수 있습니다
- 프로젝트 git 클론
git clone git@github.com:so0112/so-TODO.git
- 클론 디렉토리로 이동
cd so-Todo
- 패키지 install
npm install
- 프로젝트 실행
npm start
- 로컬환경에서 실행
http://localhost:3000
// 회원가입 url
const SIGNUP_URL = `https://pre-onboarding-selection-task.shop/auth/signup`;
// 회원가입 state
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
// 유효성 검사 state
const [isEmail, setIsEmail] = useState(false);
const [ispassword, setIsPassword] = useState(false);
const [isError, setIsError] = useState(false);
useCheck(checkEmail, email, setIsEmail);
useCheck(checkPassword, password, setIsPassword);
- 유효성 검사 커스텀훅
src/hooks/useCheck.js
/** 유효성 검사 커스텀 훅
*
* checkFunction : 유효성 검사 함수
* checkedArg : checkFunction의 인자
*
* setIsState : 변경할 상태
*/
export default function useCheck(checkFunction, checkedArg, setIsState) {
useEffect(() => {
if (checkFunction(checkedArg) === true) {
setIsState(true);
} else if (checkFunction(checkedArg) === false) {
setIsState(false);
}
}, [checkFunction, checkedArg, setIsState]);
}
- 유효성 체크 함수
src/function/checkSignup.js
/** 이메일 유효성 체크 함수
*
* `@` 포함되는지 확인
*/
export function checkEmail(email) {
let emailReg = /^[a-zA-Z0-9._%+-]+@/g;
return emailReg.test(email);
}
/** 비밀번호 유효성 체크 함수
*
* 8자리 넘는지 확인
*/
export function checkPassword(password) {
return password.length >= 8;
}
// form 제출시 submitSignup 함수 호출
<form onSubmit={submitSignup}>
<h1>회원가입</h1>
<InputGroup
placeholder="이메일"
value={email}
setValue={setEmail}
setIsError={setIsError}
/>
<InputGroup
placeholder="비밀번호"
value={password}
setValue={setPassword}
type="password"
/>
// isEmail, isPassword가 true일 경우에만 submit 가능
{isEmail && isPassword ? (
<button type="submit" className="allow-signup">
가입하기
</button>
) : (
<button type="button" className="block-signup">
가입하기
</button>
)}
</form>
- form 제출시 호출되는
submitSignup
함수
/** 회원가입 axios 요청 버튼 */
const submitSignup = (event) => {
// 제출시 새로고침 방지
event.preventDefault();
// postSignup 함수 호출
postSignup(SIGNUP_URL, email, password, setIsError);
};
export const postSignup = async (SIGNUP_URL, email, password, setErrors) => {
await axios
.post(
SIGNUP_URL,
{
email,
password,
},
{
headers: {
"Content-Type": "application/json",
},
}
)
.then((res) => window.location.replace("/"))
.catch((error) => {
console.log(error.response.status);
setErrors(true);
});
};
- 성공시 login url로 이동
.then((res) => window.location.replace("/"))
- 실패시 에러 띄우기위해 error state 변경
setErrors(true)
- 에러메세지
- 이메일 에러메세지
- isError === true : 중복계정 에러
- isEmail === true : 에러 없음
- isEmail === false : 이메일 형식 에러
{isError === true ? (
<ContentCheck>중복된 계정입니다.</ContentCheck>
) : isEmail === true ? (
<></>
) : (
<ContentCheck>
올바른 형식의 이메일을 입력해주세요(@ 필수 포함)
</ContentCheck>
)}
- 비밀번호 에러메세지
- isPassword === true : 에러 x
- isPassword === false : 비밀번호 형식 에러
{isPassword === true ? (
<></>
) : (
<ContentCheck>8자 이상의 비밀번호를 입력해주세요</ContentCheck>
)}
회원가입과 같은 로직으로 작성되었습니다.
- todo form
// enter 입력시 submitTodo 함수 호출
<form onSubmit={submitTodo}>
<h1>Todo List</h1>
<div className="todo-input">
<InputGroup
className="post-input"
placeholder="todo 생성하기"
value={todo}
setValue={setTodo}
/>
// 버튼 클릭시 submitTodo 함수 호출
<button type="button" className="post-button" onClick={submitTodo}>
+
</button>
</div>
</form>
- todo form 제출시 실행되는 함수
const submitTodo = (event) => {
event.preventDefault();
// postTodo 호출
postTodo({ todo, setTodo, datas, setDatas });
};
export const postTodo = async ({ todo, setTodo, datas, setDatas }) => {
await axios
.post(
TODO_URL,
{
todo,
},
{
headers: {
"Content-Type": "application/json",
Authorization: token,
},
}
)
.then((res) => {
// todolist에 사용되는 datas state 변경
setDatas([
// 기존에 존재하는 todo
...datas,
// 추가되는 todo 값
{
id: res.data.id,
todo: res.data.todo,
isCompleted: res.data.isCompleted,
userId: res.data.userId,
},
]);
// todo form에 input에 작성되어있는 값 초기화
setTodo("");
})
.catch((err) => console.log());
};
useEffect로 화면이 새로고침될 때 getTodos호출 data state 받아온 후에 Todolist에 props로 내려줌
todo 컴포넌트의 post 영역과 get 영역을 분리하고
코드의 가독성을 높이기 위해 분리했습니다.
function Todo() {
const [datas, setDatas] = useState([]);
useEffect(() => {
getTodos({ setDatas });
}, []);
(생략)
<TodoList
datas={datas}
setDatas={setDatas}
todo={todo}
setTodo={setTodo}
/>
1.todo 값 변경, 수정에 사용되는 state
// 수정중인 todo의 id를 담을 state
const [isModifying, setIsModifying] = useState();
// todo 수정값 state
const [modifyTodo, setModifyTodo] = useState("");
- 수정 버튼 클릭시 startModify 함수 호출후 수정 시작
<BsFillPencilFill className="modify-button" onClick={() => startModify(el)} />
const startModify = (el) => {
// isModifyng state에 id값 할당
setIsModifying(el.id);
// modifyTodo에 수정전 todo값 할당
setModifyTodo(el.todo);
};
- 수정중인 id에 해당되는 todo 값 input창 띄워줌
{/* TODO PUT 수정중인 id는 input창 뜨도록 작성 */}
{isModifying === el.id ? (
<>
// update putModify 요청
<form
onSubmit={(e) => {
e.preventDefault();
putModify({
id: el.id,
modifyTodo,
isCompleted: el.isCompleted,
setIsModifying,
setModifyTodo,
setDatas,
});
}}>
<InputGroup
placeholder=""
value={modifyTodo}
setValue={setModifyTodo}
className="todo-list"
/>
</form>
<div className="modify-button-area">
<BsCheckLg
className="modify-post-button"
onClick={() =>
putModify({
id: el.id,
modifyTodo,
isCompleted: el.isCompleted,
setIsModifying,
setModifyTodo,
setDatas,
})
}
/>
// 수정 취소 버튼
<BsXLg className="modify-cancel-button" onClick={handleCancel} />
</div>
</>
- putModify 함수
src/api/axiosTodo
수정성공시 startModfiy, setModifyTodo 초기화 getTodos 호출 수정된 값 반영
export const putModify = async ({
id,
modifyTodo,
isCompleted,
setIsModifying,
setModifyTodo,
setDatas,
}) => {
await axios
.put(
`${TODO_URL}/${id}`,
{
todo: modifyTodo,
isCompleted,
},
{
headers: {
"Content-Type": "application/json",
Authorization: token,
},
}
)
.then((res) => {
setIsModifying();
setModifyTodo("");
getTodos({ setDatas });
})
.catch((err) => console.log());
};
- 수정 취소시 hanldeCancel 함수 호출
const handleCancel = () => {
setIsModifying();
};
- 클릭시 postTodoCheck 함수 호출
<BsCircle
onClick={() =>
postTodoCheck({ id: el.id, todo: el.todo, isCompleted: el.isCompleted, setDatas })
}
/>
- postTodoCheck 함수
src/api/axiosTodo
isCompleted 값을 NOT 연산자 사용해서 현재 isCompleted 값 변경 성공시 getTodos 호출 변경된 값 반영
export const postTodoCheck = async ({ id, todo, isCompleted, setDatas }) => {
await axios
.put(
`${TODO_URL}/${id}`,
{
todo,
isCompleted: !isCompleted,
},
{
headers: {
"Content-Type": "application/json",
Authorization: token,
},
}
)
.then((res) => getTodos({ setDatas }))
.catch((err) => console.log());
};
- Delete 버튼 클릭시 deleteTodo 함수 호출
<BsFillTrashFill
className="delete-button"
onClick={() => deleteTodo({ id: el.id, setDatas })}
/>
- deleteTodo 함수 성공시 해당 id Todo 삭제 후 getTodos 호출 삭제된 값 반영
export const deleteTodo = async ({ id, setDatas }) => {
await axios
.delete(`${TODO_URL}/${id}`, {
headers: {
Authorization: token,
},
})
.then((res) => getTodos({ setDatas }))
.catch((err) => console.log());
페이지 렌더링 될 때 토큰 유무 확인후 리다이렉트
const isLogin = Boolean(localStorage.getItem("token"));
const navigate = useNavigate();
// isLogin === true : todo url로 리다이렉트
useEffect(() => {
if (isLogin) {
navigate("/todo");
}
}, [isLogin, navigate]);
const isLogin = Boolean(localStorage.getItem("token"));
const navigate = useNavigate();
useEffect(() => {
if (!isLogin) {
navigate("/");
}
}, [isLogin, navigate]);
➡️ 리다이렉트 아쉬운 점
각 페이지마다 토큰 유무를 확인하고 리다이렉트가 이뤄지는데
토큰을 확인하는 페이지의 갯수가 많아질 경우 좋은 방법은 아닌 것 같다.
App.js에서 토큰 유무를 확인하고 접근할 수 있는 페이지
접근이 불가능한 페이지를 나누는 방식으로 구현하면 더 좋을 것 같아보임
원티드 온보딩 사전과제 배포링크
https://so-todo.vercel.app
- 패키지 설치
npm install
- 실행
npm run start
- http://localhost:3000 이동
open http://localhost:3000
- 로그인 / 회원가입 기능 ✅
- 이메일과 비밀번호의 유효성 검사기능 ✅
- 이메일 조건:
@
포함 - 비밀번호 조건: 8자 이상
- 입력된 이메일과 비밀번호가 위 조건을 만족할 때만 버튼이 활성화
- 이메일 조건:
- 로그인 API를 호출하고, 올바른 응답을 받았을 때
/todo
경로로 이동 ✅- 응답받은 JWT는 로컬 스토리지에 저장
- 로그인 여부에 따른 리다이렉트 처리 ✅
- 로컬 스토리지에 토큰이 있는 상태로
/
페이지에 접속한다면/todo
경로로 리다이렉트 - 로컬 스토리지에 토큰이 없는 상태로
/todo
페이지에 접속한다면/
경로로 리다이렉트
- 로컬 스토리지에 토큰이 있는 상태로
/todo
경로에 접속하면 투두 리스트의 목록 확인가능 ✅- 리스트 페이지에는 투두 리스트의 내용과 완료 여부가 표시
- 리스트 페이지에는 입력창과 추가 버튼이 있고, 추가 버튼을 누르면 입력창의 내용이 새로운 투두 리스트로 추가
- 투두 리스트의 수정, 삭제 기능 ✅
- 투두 리스트의 개별 아이템 우측에 수정버튼이 존재하고 해당 버튼을 누르면 수정모드 활성화
- 수정모드에서는 개별 아이템의 우측에 제출버튼과 취소버튼이 표시되며 해당 버튼을 통해서 수정 내용을 제출하거나 수정을 취소 가능
- 투두 리스트의 개별 아이템 우측에 삭제버튼이 존재하고 해당 버튼을 누르면 투두 리스트 삭제
Tag Name | Description |
---|---|
Add |
새로운 파일 추가 |
Feat |
새로운 기능 추가 |
Fix |
버그 수정 |
Docs |
문서 수정 |
Style |
코드 formatting, 세미콜론 누락, 코드 변경이 없는 경우 |
Design |
css 수정 |
Rename |
파일 및 폴더 구조 변경 |
Refactor |
코드 리팩토링 |
Modify |
코드 단순 수정 |
Test |
테스트 추가, 테스트 리팩토링 |
Chore |
빌드 업무 수정, 패키지 매니저 수정 |