글 작성자: bbangson
반응형

개요

2021.08.02 (월) ~ 2021.08.06(금)

 원티드 프리온보딩 프론트 엔드 코스 교육에서 진행한 Jaranda기업의 기업 과제 입니다. 

총 8명에서 약 5일 동안 진행한 프로젝트입니다. 큰 규모의 프로젝트는 아니었지만 많은 인원과 함께 프로젝트를 진행했습니다. 다수 인원이 함께한 프로젝트는 장단점이 존재했습니다. 개인적으로는 구현해보고 싶은 기능에 더 집중할 수 있고, 여유롭게 협업할 수 있어서 리팩토링과 피드백도 주어진 시간 내에 가능했다는 부분에서 장점을 느꼈습니다. 

 

총 4편으로 나누어 이번 기업 과제를 리뷰하고자 합니다. 

 

1편은 전체적인 프로젝트 소개와 저의 역할을 중점으로 작성한 회고.

2편과 3편은 각 팀원이 구현한 기능에 대한 공부와 설명.

https://bbangson.tistory.com/85

 

[기업 과제] Jaranda 팀 프로젝트 리뷰 ②

개요 1편에 이어 2편 작성하겠습니다. 1편은 전체적인 프로젝트 소개와 저의 역할을 중점으로 작성한 회고. https://bbangson.tistory.com/83 [기업 과제] Jaranda 팀 프로젝트 리뷰 ① 개요 2021.08.02 (월) ~ 20..

bbangson.tistory.com

https://bbangson.tistory.com/86

 

[기업 과제] Jaranda 팀 프로젝트 리뷰 ③

개요 3편 작성하겠습니다. 1편은 전체적인 프로젝트 소개와 저의 역할을 중점으로 작성한 회고. https://bbangson.tistory.com/83 [기업 과제] Jaranda 팀 프로젝트 리뷰 ① 개요 2021.08.02 (월) ~ 2021.08.06(금..

bbangson.tistory.com

4편은 팀 프로젝트 셋업 내용과 리팩토링 및 최종 회고.

 

약 5일동안 주어진 미션에만 집중한 프론트 엔드 주니어 개발자들의 기업 과제이므로

프로젝트의 퀄리티는 떨어질 수 있는 점, 양해 바랍니다. 

 

시작하겠습니다.

 

 

 


프로젝트 소개

저희 프로젝트 배포 링크 Github 주소입니다.

 

https://jaranda.netlify.app/

 

자란다 - 돌봄과 배움을 함께, 우리 아이 단짝쌤 찾기

 

jaranda.netlify.app

https://github.com/six-sense/jaranda

 

GitHub - six-sense/jaranda

Contribute to six-sense/jaranda development by creating an account on GitHub.

github.com

 

 기업 과제에서 요구한 내용은 아래 '더보기' 기능으로 두겠습니다.

간단하게 정리하자면 로그인, 회원가입 페이지가 존재하는 관리자 페이지 구현하기입니다.

그 안에 세부 기능은 아래  '과제 요구 사항 보기' 버튼을 클릭하여 확인해주시기 바랍니다. 

 

 저희는 크게 로그인, 회원가입, 관리자 페이지로 나누어 프로젝트를 진행하고자 하였습니다. 상대적으로 빠르게 마무리되는 기능의 팀원들이 미흡한 기능에 가서 도와주는 형식으로 프로젝트를 진행했습니다.  

 

 인원이 많은 만큼 최대한 애자일스럽게 개발하고자 했습니다.

 

 로그인 2, 회원가입 3, 관리자 페이지 3으로 인원수를 나눴고 하나의 기능을 세분화하여 최대한 신속하게 개발하고 서로가 고객이 되어 서로의 기능을 피드백하고자 했습니다. 또한, 매일 회의를 통하여 미흡한 기능은 짝 프로그래밍을 통해 문제를 같이 해결했습니다. (용훈 님 감사합니다.)

더보기

1. 아래 정보를 입력받아 회원가입 페이지를 구현하고 로그인/로그아웃 기능을 구현해주세요.

- 이름
- 주소 (팝업을 이용해서 입력 받음)
- 신용카드 정보 (팝업을 이용해서 입력 받음)
- 나이

1.1 관리자 로그인을 하면 등록한 계정 정보를 아래 방법을 이용하여 시각화해 주세요.
- 테이블 Component 페이지 만들기
- Data Table 구현
- 페이지 네이션 구현
- 검색 기능 구현

1.2 정보는 로컬 저장소 등 자유롭게 저장해도 됩니다.

1.3 주소는 다음에서 제공하는 입력창을 사용해도 무방합니다.

1.4 관리자 계정은 임의로 정의해도 됩니다.

2. 다양한 메뉴를 가지고 있는 홈페이지 관리자 페이지를 구현해 주세요.

2.1 계정, 비밀번호만 입력하면 로그인이 되어야 합니다.

2.2 로그인된 계정은 자신에게 허용된 메뉴만 보여야 합니다.

2.3 관리자는 계정을 임의로 생성할 수 있고 계정별로 볼 수 있는 메뉴를 설정할 수 있습니다.

2.4 관리자 계정은 임의로 정의해도 됩니다.

2.5 정보는 로컬 저장소 등 자유롭게 저장해도 됩니다.

2.6 메뉴는 임의대로 정의해도 되며 메뉴를 선택했을 때 나오는 화면에는 메뉴명이 출력되면 됩니다.

2.7 관리자 로그인을 하면 등록한 계정 정보를 아래 방법을 이용하여 시각화해 주세요.
- 테이블 Component 페이지 만들기
- Data Table 구현
- 페이지 네이션 구현
- 검색 기능 구현

 

 

 


회고

저는 회원 가입 페이지를 주로 담당하고 기능을 구현하였습니다. 

 

□ Style | 회원가입 페이지 UI

 

Styled-Components를 사용했습니다.

자란다 홈페이지를 참고하여 구현하였습니다.

회원가입 페이지 UI

 

 

 

□ Feat | 아이디, 비밀번호 유효성 검사

 

아이디를 입력받으면 매 키보드 입력 순간마다 유효성 검사를 시작합니다.

아이디 입력 폼 밑에 있는 아이콘 UI를 통해 현재 사용자가 자신이 올바르게 입력하고 있는지 확인할 수 있습니다. 

 

초기 구현 계획에는 토스트 메시지로 매 순간 사용자에게 보이게 구현하고자 했으나, 반응형 아이콘 UI가 더 직관적일 것 같아 이것으로 선택했습니다. 

입력값에 따라 아이콘 색이 변한다.

 

아래 코드는 유효성을 검증하는 정규표현식입니다. 

아이디는 4자리 이상, 숫자 또는 영문자를 필수로 입력하게 하였고, 특수문자는 금지했습니다.

비밀번호는 8자리 이상 숫자와 영문자, 특수문자를 필수로 포함했습니다.

 

메소드를 굳이 test를 쓰지 않고 match를 사용한 이유는 어차피 반환되는 문자열은 Truthy 한 값이기 때문에 수정할 필요를 못 느껴 수정하지 않았습니다. test 메소드를 사용해도 무방합니다.

// 아이디 유효성 검사
function checkIdSignUp(id) {
  const idValid = /^[a-zA-Z0-9_-]{4,26}$/;
  return id.match(idValid);
}

// 비밀번호 유효성 검사
function checkPasswordSignUp(password) {
  const pwValid = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,25}$/;
  return password.match(pwValid);
}

 

다음은 아이디, 비밀번호 유효성 검사를 위해 필요한 코드입니다.

// ID 입력시 onChange()
const onChangeID = (e) => {
    const id = e.target.value;
    // 영문자 
    const regex1 = /[A-Za-z0-9]+/g;
    
    // 특수문자 금지
    const regex2 = /[^가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z0-9]+/gi;
    
    // 한글 금지
    const regex3 = /[가-힣ㄱ-ㅎㅏ-ㅣ]/;
    
    if (regex1.test(id)) {
      setIsEngNum(true);
    } else {
      setIsEngNum(false);
    }

    if (regex2.test(id)) {
      setIsSpecialId(true);
    } else {
      setIsSpecialId(false);
    }
    
    if (regex3.test(id)) {
      setIsEngNum(false);
    } else {
      setIsEngNum(true);
    }

    if (id.length >= 4) {
      setIsLenId(true);
    } else {
      setIsLenId(false);
    }

// ID를 저장하는 State에 등록.
    setUserInfo({
      ...userInfo,
      userId: e.target.value,
    });
  };
  
  // 비밀번호 입력 시 onChange() 
  const onChangePW = (e) => {
   
    // 비밀 번호 입력 폼 전의 입력 폼들이 미흡 입력 상태인지 확인하는 메소드.
    inputCheck(1);

    const pw = e.target.value;
    // 영문자 최소 1개 이상
    const regex1 = /[A-Za-z]+/;
    // 숫자 최소 1개 이상
    const regex2 = /[0-9]+/;
    // 특수문자 최소 1개 이상
    const regex3 = /[!@#$%^*+=-]+/;

    if (regex1.test(pw)) {
      setIsEnglish(true);
    } else {
      setIsEnglish(false);
    }
    if (regex2.test(pw)) {
      setIsNumber(true);
    } else {
      setIsNumber(false);
    }
    if (regex3.test(pw)) {
      setIsSpecial(true);
    } else {
      setIsSpecial(false);
    }
    if (pw.length >= 8) {
      setIsLength(true);
    } else {
      setIsLength(false);
    }

    setUserInfo({
      ...userInfo,
      password: e.target.value,
    });

    HandleValidatePW(e.target.value);
  };

  // 비밀번호 유효성 검사
  const HandleValidatePW = (value) => {
    const checkValidPw = checkPasswordSignUp(value);

    if (checkValidPw) {
      setInputChk({
        ...inputChk,
        password: true,
      });
    } else if (!checkValidPw) {
      setInputChk({
        ...inputChk,
        password: false,
      });
    }
  };

onChangeID 함수는 위에 작성한 정규표현식을 사용하지 않고 반응형 아이콘 UI를 구현하기 위해 함수 안에서 분리 작성한 정규 표현식을 사용합니다. 참고로 위에 작성한 checkIdSignUp 함수는 아이디 중복 확인 버튼에서 사용합니다.

 

비밀번호 또한 onChangePW 함수를 통하여 반응형 아이콘 UI를 구현하기 위해 함수 안에서 분리 작성된 정규 표현식을 사용합니다. 하지만 ID와 차이점이 있다면 비밀번호는 잘 작성하고 있는지 매 키보드 입력마다 HandleValidatePW 함수를 호출하여 checkPasswordSignUp 함수를 통해 비밀번호 유효성 검사를 진행합니다.

 

 

 

□ Feat | 아이디 중복 확인

 

사용자가 아이디를 입력하면 아이디 중복 확인 버튼을 통해 중복 확인이 가능합니다.

사용자는 자신이 아이디 입력을 올바르게 하고 있는지 입력 폼 밑에 있는 아이콘 UI의 색깔 변화로 확인할 수 있습니다.  

 

아이디 중복 확인 함수에서는 아이디 유효성 검사와 로컬 스토리지에 저장되어 있는 기존 ID와 비교하는 중복 검사를 같이 수행합니다. 

중복된 아이디, 사용할 수 있는 아이디, 사용할 수 없는 아이디를 구분하여 그에 맞는 토스트 메시지를 출력합니다.

 

 

조건에 따라 토스트 메세지가 뜬다.

 

  // 아이디 중복 확인
  const onClickIdValid = () => {
    const checkValidId = checkIdSignUp(userInfo.userId);
    let userData = LOCAL_STORAGE.get('userData');

    const reduplication = userData.find(
      (data) => data.userId === userInfo.userId,
    );
    if (checkValidId && reduplication === undefined) {
      setToast({ ...toast, status: true, msg: '사용가능한 아이디입니다.' });

      setInputChk({
        ...inputChk,
        userId: true,
      });
      setIsOverlap(userInfo.userId);
      return;
    } else if (!checkValidId) {
      setToast({
        ...toast,
        status: true,
        msg: '4자리 이상, 숫자 혹은 영문자만 사용해주시기 바랍니다.',
      });
      setInputChk({
        ...inputChk,
        userId: false,
      });
      return;
    } else {
      setToast({ ...toast, status: true, msg: '중복된 아이디입니다.' });
      setInputChk({
        ...inputChk,
        userId: false,
      });
    }
  };

 

 

 

□ Feat | 비밀번호 확인

 

 비밀번호를 확인하는 로직은 매 키보드 입력마다 위에서 입력한 비밀번호와 비교합니다. 

비밀번호가 일치하지 않으면 비밀번호가 일치하지 않다는 토스트 메시지를 띄우고 비밀번호가 일치하는 순간 비밀번호가 일치하다는 토스트 메시지를 띄웁니다.

 

 즉, 매 키보드 입력마다 토스트 메세지를 띄우게 됩니다.

일치하지 않을 때
일치할 때

 

// 비밀번호 확인 onChange
const onChangePwConfirm = (e) => {
    inputCheck(2);
    setUserPwConfirm(e.target.value);
    MatchPW(e.target.value);
  };

// 비밀번호와 일치하는지 확인.
  const MatchPW = (value) => {
    if (value !== userInfo.password) {
      setToast({
        ...toast,
        status: true,
        msg: '비밀번호가 일치하지 않습니다.',
      });

      setInputChk({
        ...inputChk,
        password_confirm: false,
      });
    } else if (value === userInfo.password) {
      setToast({
        ...toast,
        status: true,
        msg: '비밀번호가 일치합니다.',
      });

      setInputChk({
        ...inputChk,
        password_confirm: true,
      });
    }
  };

 

 

 

□ Feat | 성명, 나이

 

 성명과 나이는 유효성 검사를 진행하지 않았습니다. 사용자의 입력을 그대로 입력받았습니다.

 

 

 

□ Feat | 주소, 신용 카드 등록

 

 주소와 신용 카드는 제가 구현한 것이 아니기 때문에 자세히는 2편에서 다루도록 하겠습니다.

간단하게 설명하자면, 주소는 다음 API를 활용하여 팝업 창을 띄워 등록하고, 신용 카드는 모달을 띄워 입력과 유효성 검사를 진행하도록 구현하였습니다.

 

 저는 주소와 신용카드 컴포넌트를 가져와 사용자의 입력을 받고 등록을 하여 등록이 되었다는 토스트 메시지를 띄우는 것을 구현하였습니다. 여기서 어려움이 있었습니다. 

 

 신용 카드 정보와 주소를 입력받고 등록 버튼을 누르면 그 후에 토스트 메시지를 띄우는 것이 목표였지만 계속해서 이 과정이 까다로웠습니다. 주소 입력 폼은 readOnly로 되어 있어서, onChange 함수가 소용이 없었고, onChange 함수가 가능하다고 해도  최종 입력 후에 등록 버튼을 누른 순간 토스트 메시지를 띄우게 하는 로직은 쉽사리 떠올르지 않았습니다. 

 

 물론 가장 간단한 방법은 모달 창 안에 따로 등록 버튼이 있는데, 그 등록 버튼에 onClick 함수를 적용하여 해결하는 방법이 있겠지만, 모달 창 안에 구현되어 있는 등록 버튼에 접근하는 방법을 알지 못하여 이 간단하고 가장 획기적인 방법을 그때 당시에 떠올리질 못했습니다.

 

 결국에는 useEffect 함수를 이용해서 해결했습니다. 주소 팝업 창에는 우편 번호 입력 폼을, 카드 모달 창에는 카드 번호 입력 폼을 각각 useEffectdeps 배열 안에 선언하였습니다. 

 

 완벽하지 않은 해결법이었습니다. 해당 State만 수정될 때 모달 창을 띄우고 싶었습니다. 하지만 컴포넌트가 리렌더링 될 때 토스트 메시지가 매 번 띄워진다는 것이 흠이었습니다. 이 부분은 조금 더 공부가 필요해 보입니다. 

 

 문제를 해결한 코드입니다. 

  // 주소 팝업창의 우편 번호 입력 폼을 기준.
  useEffect(() => {
    if (userInfo.zcode) {
      setInputChk((prevInputChk) => ({
        ...prevInputChk,
        zcode: true,
      }));
      setToast((prevToast) => ({
        ...prevToast,
        status: true,
        msg: '주소가 등록되었습니다.',
      }));
    }
  }, [userInfo.zcode]);
 
  // 카드 모달창의 카드 번호 입력 폼을 기준.
  useEffect(() => {
    if (userInfo.creditCard.cardNumber) {
      setInputChk((prevInputChk) => ({
        ...prevInputChk,
        creditCard: {
          cardNumber: true,
        },
      }));
      setToast((prevToast) => ({
        ...prevToast,
        status: true,
        msg: '카드가 등록되었습니다.',
      }));
    }
  }, [userInfo.creditCard.cardNumber]);

 

 

 

□ Feat | 가입하기 버튼 클릭 시 입력 폼 검사.

 

 제가 가장 시간을 많이 쏟은 기능이라고 생각합니다.

가입하기 버튼 하나를 클릭하면 직전 입력 폼들을 전부 검사하여, 미흡 또는 완벽 입력인지 판단 후 사용자의 가입을 결정합니다. 

 

 만약 모든 입력 폼들이 빠짐없이 완벽 입력이라면 사용자의 정보를 저장하고 로그인 페이지로 이동시킵니다. 

하지만 미흡 입력 폼이 하나라도 존재하면 토스트 메시지를 띄우고 그 입력 폼으로 커서를 이동시켰습니다. 

 

 가장 시간이 많이 걸렸던 부분은 미흡 입력 폼을 검사하는 로직을 구현하는 것이었습니다. 머릿속으로는 가입 버튼을 클릭하는 순간 모든 입력 폼을 하나하나 검사해야겠다는 로직을 떠올리는 것은 금방이었으나, 이를 코드로 구현하는 것이 문제였습니다. 처음에는 Switch문을 활용하여 각 해당 입력 폼이 빈칸 이거나 미흡 입력이면 false를 반환하게끔 하였으나 Switch 문은 순서가 불안정하여 완벽하지 않았습니다. 결국에는 for문을 사용하여 문제를 해결하였습니다. 

 

 for을 사용한 것은 좋은 선택이었습니다.

이 로직을 활용하여 추가로 직전 입력 폼을 미흡 입력 상태로 다음 입력 폼으로 넘어간다면 미흡 입력 폼에 대한 토스트 메시지를 출력하는 기능도 함께 구현할 수 있었습니다.

 

이름을 입력하지 않고 가입하기 버튼을 클릭했을 경우

 

 

코드입니다.

 // 각 입력 폼을 확인하는 함수 
 const inputCheck = (limit) => {
    // 각 입력 폼들이 올바르게 입력되었는지 판단하는 boolean state
    const { userId, password, password_confirm, name, age, zcode, creditCard } =
      inputChk;
    const { cardNumber } = creditCard;
    let result = true;
    for (let i = 0; i < limit; i++) {
      if (i === 0 && !userId) {
        setToast({
          ...toast,
          status: true,
          msg: '중복 확인 버튼을 눌러주세요.',
        });
        result = false;
      } else if (i === 1 && !password) {
        setToast({
          ...toast,
          status: true,
          msg: '비밀번호를 다시 입력해주세요.',
        });
        result = false;
      } else if (i === 2 && !password_confirm) {
        setToast({
          ...toast,
          status: true,
          msg: '비밀번호 확인을 해주세요.',
        });
        result = false;
      } else if (i === 3 && !name) {
        setToast({
          ...toast,
          status: true,
          msg: '이름을 입력해주세요.',
        });
        result = false;
      } else if (i === 4 && !age) {
        setToast({
          ...toast,
          status: true,
          msg: '나이를 입력해주세요.',
        });
        result = false;
      } else if (i === 5 && !zcode) {
        setToast({
          ...toast,
          status: true,
          msg: '주소를 입력해주세요',
        });
        result = false;
      } else if (i === 6 && !cardNumber) {
        setToast({
          ...toast,
          status: true,
          msg: '카드를 등록해주세요.',
        });
        result = false;
      }
      if (!result) break;
    }
    return result;
  };

 

 

 


 1편은 여기서 마무리하도록 하겠습니다. 미흡한 부분이 많을 거라 생각합니다.

현재 이 모습이 제 실력이 아닌 더 발전할 수 있는 잠재력을 보여주는 것이 이 포스팅의 목적이며, 저 또한 이 포스팅으로 스스로를 점검하는 기회로 만들고자 합니다.  

 

 기업 과제를 경험할 수 있는 기회를 준 wecode, Wanted, Jaranda에 감사의 말씀드리며, 여러 의견과 피드백을 댓글로 남겨주시면 감사하겠습니다. 

 

 

 

 

관련 링크

 

https://wecode.co.kr/?gclid=CjwKCAjw3riIBhAwEiwAzD3TieM0Y43D-7_T8g2QOIR2WYaykoQxaKxTgyySC8gFOwW7IMjXg_qStBoCa34QAvD_BwE 

 

WeCode | 위코드 | 코딩 부트캠프 | 코딩교육 | 온라인 부트캠프

wecode(위코드)의 부트캠프를 통해 개발자로서 커리어를 시작하세요.

wecode.co.kr

 

https://www.wanted.co.kr/events/pre_onboarding_course_2

 

프리온보딩 코스_ 프론트엔드

커리어 성장과 행복을 위한 여정, 지금 원티드에서 시작하세요.

www.wanted.co.kr

 

https://jaranda.kr/

 

jaranda | 자란다

아이에게 딱 맞는 프로그램과 단짝선생님, 교구재까지 추천받으세요. 국내 최초, 유일 통합 교육 플랫폼. 아이의 흥미와 성향까지 모두 고려한 완벽한 AI 매칭과 꼼꼼한 수업 관리로 만족도 1위!

jaranda.kr

 

반응형