월요일 세션이 끝나고 과제 팀원 배정 엑셀을 봤는데 셀이 비어있었다. 오잉..? 멘토님이 이번 과제는 팀 전원이 참여하는 것이라고 하셨다. 네..? 그러면 8명이 전부다 참여한다구요..? 아이고 깜짝이야!.. 프론트엔드가 8명?! 당황했지만 당황하지 않은 척 오히려 좋아를 외치며 성황리에 과제를 끝마쳤다. 고생한 우리 팀 7ill-resource 칭찬해~
[Assignment 3] 자란다 #1, #2
Git repositories: wanted-preonboarding-subject-3
📋 1. 과제 요구사항
✅ 회원가입 페이지, 로그인/로그아웃 기능 구현
- 폼 양식 - email, password, 이름, 주소(팝업/Damu 주소 API), 신용카드 정보(팝업), 나이, 권한 설정(부모님/ 선생님)
- 유저 데이터 로컬 스토리지 저장
✅ 관리자 페이지 구현
- 권한에 따른 페이지 접근 제한
- 관리자 페이지에서 계정 생성, 권한 설정
- 유저데이터 로컬 스토리지 저장
- 유저 데이터 테이블
- 페이지네이션
- 검색 기능
👩💻 2. 내가 맡은 기능
✅ 권한
- 유저의 권한에 따른 접근 제한, 리다이렉트 구현
- 로그인 한 유저의 권한에 따른 다른 메뉴 출력 구현
- 권한에 따른 Extra 페이지 출력
✅ Utils
- Utils 영역 상수 작성
- Utils 영역 로컬 스토리지 관련 함수 작성
✅ 관리자 페이지
- 관리자 페이지 계정 생성 모달 스타일링
- 관리자 페이지 계정 생성 로직 구현
💻 3. 기능 구현
1. HOC로 사용자 권한에 따른 리다이렉트
사용자 권한에 따른 리다이렉트를 구현하기 위해 HOC(고차 컴포넌트)를 사용했다.
HOC는 컴포넌트를 받아 새로운 컴포넌트로 변환하는 함수이다.
// Utils/auth.js
1️⃣ hoc를 이용한 컴포넌트 반환
const AuthorityControl = (Components, option, authLevel) => {
const CheckAuthority = ({ history }) => {
const [render, setRender] = useState(false);
const { HOME, LOGIN } = ROUTES;
const isLoggedIn = loadLocalStorage(LOGGEDIN_USER);
2️⃣ 로그인 여부에 따른 리다이렉트
const checkLoggedIn = (isLoggedIn, option) => {
if (option === null) return;
if (!isLoggedIn && option) {
alert("로그인 해주세요.");
return history.push(LOGIN);
} else if (isLoggedIn && !option) {
alert("이미 로그인 한 유저입니다.");
return history.push(HOME);
}
};
3️⃣ 관리자 권한에 따른 리다이렉트
const checkAdmin = (isLoggedIn, authLevel) => {
const { authority } = isLoggedIn;
if (authLevel < authority) {
alert("권한이 없습니다.");
return history.push(HOME);
}
};
4️⃣ 컴포넌트 마운트 후 함수 호출
useEffect(() => {
if (!!isLoggedIn !== option) {
checkLoggedIn(isLoggedIn, option);
} else if (isLoggedIn) {
checkAdmin(isLoggedIn, authLevel);
}
setRender(true);
}, []);
return render ? <Components /> : null;
};
return CheckAuthority;
};
export default AuthorityControl;
1️⃣ hoc를 이용한 컴포넌트 반환
// src/Routes.js
<Route exact path={LOGIN} component={AuthorityControl(Login, inaccessible, unknown)} />
Routes.js에서 hoc를 사용해 AuthorityControl 함수의 인자로 각각 components(Login), option(inaccessible), authLevel(unknown)을 받는다.
components : 변환할 컴포넌트
option : 로그인 여부에 따른 페이지 접근 가능·불가능
authLevel : 유저 권한에 따른 페이지 접근 가능·불가능
2️⃣ 로그인 여부에 따른 리다이렉트
option이 unknown(null) 일 경우 비회원이 접근 가능한 페이지이기 때문에 얼리 리턴해줘 리다이렉트 하지 않는다.
로컬 스토리지의 isLoggedIn값이 null이고 로그인 한 유저가 접근 가능한 페이지(option = accessible) 일 경우 로그인 페이지로 리다이렉트 하고, isLoggedIn이 true이고 로그인 한 유저가 접근 불가능한 페이지(option = inaccessible) 일 경우 엔트리 페이지로 리다이렉트 한다.
3️⃣ 권한에 따른 리다이렉트
로컬 스토리지 isLoggedIn의 authority 속성은 권한에 따라 0(관리자), 1(선생님), 2(부모님), 3(비회원)으로 되어있는데 페이지 접근 권한 authLevel보다 authority 정수 값이 크다면 권한이 낮기 때문에 엔트리 페이지로 리다이렉트 된다.
4️⃣ 컴포넌트 마운트 후 함수 호출
로컬 스토리지에 isLoggedIn key가 존재하지 않으면 null을 반환하기 때문에 boolean 자료형으로 변경 option과 비교해 true면 2️⃣함수 호출, isLoggedIn가 true면 유저 로그인 데이터가 존재 권한에 따른 리다이렉트 하는 3️⃣함수 호출
세션에서 멘토님이 HOC를 사용 하지 않아도 된다고 하셨다. 그럼 다른방법이 무엇이 있을까?🤷♂️ 구글링해서 찾아봤는데 권한을 체크하는 커스텀 Private Router를 사용하면 간단한 로직으로도 권한별 라우팅이 가능했다. HOC도 충분히 적절하게 잘 사용했다고 생각하지만 다음엔 이 방법을 적용해봐야겠다.
2. 관리자 페이지 계정 생성
// Components/createAccount/createAccount.js
const CreateAccount = ({ toggleModal, setIsCreateAccount }) => {
const [userInput, setUserInput] = useState([]);
const [userList, setUserList] = useState([]);
const [selectValue, setSelectValue] = useState("");
1️⃣ 입력 데이터 객체, 로컬 스토리지 user data 관련
useEffect(() => {
setUserInput(USERDATA_TEMPLATE);
setUserList(loadLocalStorage(USER_STORAGE));
}, []);
2️⃣ input value에 따른 userInput state 업데이트
const inputUserData = (event) => {
const {
target: { value, name },
} = event;
return setUserInput({
...userInput,
[name]: value,
});
};
3️⃣ select option value에 따른 selectValue state 업데이트
const selectAuthority = (event) => {
const {
target: { value },
} = event;
if (value) {
setSelectValue(value);
}
};
4️⃣ user data 생성 로직
const userDataSubmit = (event) => {
event.preventDefault();
if (selectValue) {
const accountObj = {
...userInput,
id: autoIncrementUserId(),
pw: hashSync(userInput.pw),
authority: Number(selectValue),
};
saveLocalStorage(USER_STORAGE, [...userList, accountObj]);
setIsCreateAccount((prev) => !prev);
}
event.target.reset();
toggleModal();
};
}
1️⃣ 입력 데이터 객체, 로컬 스토리지 user data 관련
USERDATA_TEMPLATE상수는 input value에 따라 업데이트 될 속성들이 정의되어 있다.
loadLocalStorage(USER_STORAGE)함수는 로컬 스토리지의 모든 유저 데이터를 불러온다
2️⃣ input value에 따른 userInput state 업데이트
input의 name에 따라 업데이트 할 객체의 속성을 동적으로 지정 가능하고 spread syntax를 사용해 기존의 userInput 객체를 복사
3️⃣ select option value에 따른 selectValue state 업데이트
select box의 선택된 option에 따라 selectvalue state가 업데이트 된다.
4️⃣ user data 생성 로직
3️⃣의 함수 로직이 실행 후 selectValue state에 값이 존재하고 조건문이 실행된다.
accountObj에 userInput 객체를 spread syntax로 복사, 새로 생성되는 유저는 userList 배열에 저장된 마지막 값을 참조해 +1 된 value를 id key에 저장하는 autoIncrementUserId()함수를 호출, password는 bcrypt를 이용해 해쉬값을 넣어주고 select option의 value는 자료형이 string이기 때문에 Number로 실수자료형으로 변경한다. accountObj를 userList 배열에 넣어주기 위해 spread syntax를 사용해 기존 데이터를 넣어주고 accoutObj값을 넣는다.
export const autoIncrementUserId = (storageKey = USER_STORAGE) => {
const loadedUserListData = loadLocalStorage(storageKey);
const lastUserListData = loadedUserListData[loadedUserListData.length - 1];
return lastUserListData.id + 1;
};
🐱🏍 4. 회고
데일리 스크럼, 에자일 단어는 많이 들어봤는데 이번 과제를 진행하면서 몸소 느끼고 체험해봤다. 모두의 진행사항과 일정파악을 하니까 상황에 대한 즉각적인 피드백이 이루어져 다른 길로 빠지는 것이 아닌 과제 완성을 위한 일관적인 방향성을 잡아주었다. 또한 프로젝트 시작전에 구체적인 업무 분담과 게더타운을 통한 실시간 의사소통이 목표를 완성하는데 많은 기여를 했다고 느꼈고, 무엇보다 오랫동안 알고지낸 사람들처럼 즐거운 분위기 속에서 코딩을 하니까 의무처럼 느껴지지 않고 재미있는 놀이를 하는 기분이였다!
항상 혼자서 프로젝트를 진행하다보니 git을 사용할 때 add, commit, push만 사용했는데 git-flow를 사용함으로써 각자의 브랜치에서 기능작업을 하고 어떤식으로 git 협업이 이루어지는지 체험할 수 있는 유익한 시간이였다ㅎㅎ(git-flow 도입하자고 의견 내주신 현찬님 감사합니다👍)
'프리온보딩' 카테고리의 다른 글
[POB] 기업과제 - 원티드 #4 (2) | 2021.08.17 |
---|---|
자바스크립트 동작원리 - 이벤트 루프 (7) | 2021.08.13 |
브라우저 동작 원리 알아보기 (2) | 2021.08.13 |
[POB] 기업과제 -미스터카멜 #2 (3) | 2021.08.01 |
[POB] TIL 01 - Infinite Scroll 구현 (0) | 2021.07.28 |