My Boundary As Much As I Experienced

패스트캠퍼스X야놀자 프론트엔드 개발 부트캠프) 사진 관리자 백오피스 서비스 만들기 본문

FrontEnd/Javascript(Vanilla)

패스트캠퍼스X야놀자 프론트엔드 개발 부트캠프) 사진 관리자 백오피스 서비스 만들기

Bumang 2023. 8. 28. 18:21

과제 목표:

직원들의 프로필과 사진을 관리할 수 있는 사진 관리자 서비스 만들기.

  • “AWS S3 / Firebase 같은 서비스”를 이용하여 사진을 관리할 수 있는 페이지를 구현하세요.
  • 프로필 페이지를 개발하세요.스크롤이 가능한 형태의 리스팅 페이지를 개발하세요.
  • 전체 페이지 데스크탑-모바일 반응형 페이지를 개발하세요.사진을 등록, 수정, 삭제가 가능해야 합니다.
  • 유저 플로우를 제작하여 리드미에 추가하세요.
  • CSS애니메이션 구현상대수치 사용(rem, em)
  • JavaScriptDOM event 조작

 

 

결과물 주소:

https://profilebase-bm0729.firebaseapp.com/

 

 

깃허브 주소에서 기능 설명 자세히 보기

 

ProfileBase, 직원 사진 관리 백오피스 (KDT0_JeongBeomHwan) by Bumang-Cyber · Pull Request #41 · KDT1-FE/Y_FE_JAVASCRI

Profile Base 프로필 베이스 직원 사진 관리용 백오피스 서비스 회사 내 직원들의 프로필 정보를 관리할 수 있습니다. 사진, 한글이름, 영어이름, 팀, 이메일, 연락처, 관리자 여부 등을 설정할 수 있

github.com

 

 

과제에서 진행한 부분:

연장을 다루는 법을 배우고 바로 집을 하나 만들어보라는 느낌이었다. 하나의 서비스를 직접 구현하라니.. 근데 어떻게 또 됐네?! 신기하다. 

 

1. 구글로그인 인증을 구현하였다.

 

2. 로컬스토리지에 프로필 정보들을 저장, 불러오기, 수정, 삭제등을 구현하였다.

 

3. 동시에 파이어베이스 서버에 직원정보를 저장, 불러오기, 수정하기 등을 구현하였다.

 

4. 로컬스토리지에서 데이터를 우선적으로 가져오지만, 정상적이지 않은 방식으로 로컬스토리지 데이터가 삭제된 경우(사용자가 서비스 내에서 삭제한게 아니라면) 그때서야 파이어베이스를 호출하여 데이터를 가져오도록 구현하였다.

 

5. 그러나 이미지는 용량 상 로컬스토리지에 저장하기 힘드니 리로드 될때마다 파이어베이스에서 정보를 가져온다.

 

6. 모바일까지 고려한 반응형 디자인을 진행하였다.

 

7. 각 데이터 포맷에 맞는 유효성 검사를 모두 진행하였다. (한글이름은 한글만 받고, 영어이름은 영어만 받고, 이메일 형식, 연락처 등등..)

 

 

 

 

리팩토링할 부분

 

문제:

새로운 프로필을 생성할 때, 파이어베이스에 이미지 저장과 이미지 불러오기를 동시에 하는데, 이미지 저장이 조금 오래걸리면 불러오기가 실패해버려서 생성 직후에는 프로필 이미지을 불러오지 못하는 사태가 벌어졌다.

    => 저장하기가 비동기처리이기 때문에 불러오기도 비동기처리로 해서 순서를 보장하였다.( setTimeout을 이용해 3초 지연시간을 가지 게 하는 비동기처리를 함, 이렇게하면 콜백 큐에서 저장 다음 불러오기를 할 수 있게 된다.)

    => 순서는 보장되었지만, 이미지가 3초 뒤 불러와지기 때문에 처음 생성하고 빈 이미지만 보여서 순간 추가가 안 된줄 아는 경우가 발생

 

 

목표:

이미지를 불러오기 전까지 프로필 이미지 부분에 로딩 스피너를 넣어 서비스가 정상 작동중이라는 것을 알려준다.

전체 페이지에 로딩 애니메이션을 띄울까 싶었지만, 워크샵 때 로딩 애니메이션은 작은 범위에서 작동하는 것이 유리하다는 인사이트를 얻어, 프로필 이미지 부분에 로딩 스피너을 넣기로 하였다.

 

 

 

 

CSS 수정 코드

로딩 스피너 애니메이션 적당한걸 리서치해서 가져옴. 색상은 이 서비스의 컬러로 변경. 

(애니메이션을 infinite로 계속 반복할 수 있었음을 다시 깨달았다..!)

.loading {
  position: absolute;
  width: 16px;
  height: 16px;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
  border: 4px solid transparent;
  border-top-color: var(--color-blue500);
  border-radius: 50%;
  animation: button-loading-spinner 1s ease infinite; //무한 반복
  z-index: 600;
}

@keyframes button-loading-spinner {
  from {
    transform: rotate(0turn);
  }

  to {
    transform: rotate(1turn);
  }
}

 

 

자바스크립트 수정 코드

1. 이미지 입력이 있었으면 서버 호출 성공 시 로딩 애니메이션을 없애고 이미지 src를 수정한다. (신규 생성, 변경 모두 포함)

2. 이미지 입력이 없으면 추가했던 로딩 애니메이션을 없앤다.

/*
수정 전
*/
const profileCol = document.createElement("img"); //이미지 요소 생성
if (employeeProfileCard.employeeFile && newType) { // 새로 만들면서 이미지파일 입력이 있는 경우
  ...
} else if (employeeProfileCard.employeeFile){ //이미지 파일 입력이 있지만 새로 생성하는 프로필이 아닌 경우
  ...
} else {
  profileCol.src = "../images/noimage.png"; //이미지 파일 입력이 없는 경우 noimage 이미지 설정
}

profileCheckEl.append(profileCol); // 입력


/*
수정 후
*/
const profileCol = document.createElement("img"); //이미지 요소 생성
if (employeeProfileCard.employeeFile && newType) { // 새로 만들면서 이미지파일 입력이 있는 경우
  ...
  setTimeout(() => {
    getDownloadURL(ref(storage, `images/${employeeProfileCard.employeeID}.${extension}`))
      .then((url) => {
        loading.remove();
        profileCol.src = url;
      })
} else if (employeeProfileCard.employeeFile){ //이미지 파일 입력이 있지만 새로 생성하는 프로필이 아닌 경우
  ...
  getDownloadURL(ref(storage, `images/${employeeProfileCard.employeeID}.${extension}`))
    .then((url) => {
      loading.remove(); // 호출에 성공하면 로딩 이미지를 제거
      profileCol.src = url; // 프로필 이미지에 가져온 url을 넣음
    }) else {
    ...
    }
} else {
  profileCol.src = "../images/noimage.png"; //이미지 파일 입력이 없는 경우 noimage 이미지 설정
}

const imgContainer = document.createElement("div"); //로딩용 요소와 프로필 이미지를 다 담을 컨테이너 생성
const loading = document.createElement("div"); // 로딩용 요소 생성
loading.classList.add("loading"); // 로딩용 요소에 로딩 애니메이션 css 입력
if (!employeeProfileCard.employeeFile) { // 이미지 입력이 없다면
    loading.remove(); // loading 애니메이션 삭제하고 넣지 않음
    imgContainer.append(profileCol);
    imgContainer.style.position = "relative";
    profileCheckEl.append(imgContainer); // 프로필 요소들 tree에 추가
} else { // 이미지 입력이 있다면 loading 애니메이션도 append.
    imgContainer.append(profileCol, loading);
    imgContainer.style.position = "relative";
    profileCheckEl.append(imgContainer);
}

수정 전

 

 

 

수정 후

 

과제 수행 후기

5조 '잘 동작하죠?' 팀원들의 내부투표로 5조 대표 프로젝트로 선정되었습니다. (야호~)

 

UX디자이너 시절에 프론트 개발자분에게 예외처리 이런저런거 되야한다고 정책 써주던걸 입장 바꿔서 내가 구현한다고 생각하고 진행했다. 디자이너의 까다로운 인터랙션 요구가 얼마나 많은 프레셔가 될지 새삼 다시 느꼈다ㅋㅋ

 

또한, 과제가 주어질때마다 내가 익숙하게 해낼 수 있는 부분은 30%이고 나머지 부분은 어떻게든 리서치를 통해서 부딪혀봐야 한다는 느낌을 받는다. 다음엔 또 어떤 도전적인 과제가 주어질지.. 리액트가 아직 안 익숙한데 빨리 연습해야겠다.

 

 

'FrontEnd > Javascript(Vanilla)' 카테고리의 다른 글

호이스팅의 이유  (0) 2023.08.29
이벤트 위임  (0) 2023.08.29
js) 자바스크립트로 체크박스에서 체크된 요소만 모두 찾기  (0) 2023.08.28
JS) some, every, find  (0) 2023.08.28
버블링&캡처링  (0) 2023.08.22