굿즈 스토어 프로젝트 02-2 - 회원가입 세부.

이유승·2023년 7월 18일
0

회원가입 기능은 단순히 아이디와 비밀번호를 입력받아서 이를 서버에 넘겨주기만 해서는 안된다. 사용자가 입력한 값이 유효한 값인지, 값을 제대로 입력했는지, 입력한 아이디가 이미 서버에 존재하는지 등을 검사해주어야 한다.

자작 블로그때와 달리 이번 프로젝트에서는 이런 부가기능들을 모두 구현하기로 하였다.

그리고 검사 결과를 이런 식으로 시각적으로 보여주어야 한다.



1. 기능을 구현하자.

어떤 기능이 필요한가?

대략 필수 입력값이 없으면 경고 등을 출력하는 기능, 유효성 검사 혹은 중복 검사 결과를 출력하는 기능, 비밀번호의 경우 2차 입력까지 받아서 두 입력값이 일치하는지 확인하는 기능 정도를 구현하기로 하였다.

이번에는 UI보다 기능을 먼저 구현하였다. 우선 유효성 검사와 중복 검사 기능.

어떻게 기능을 구현해야 하는가?

// 유효성 검사.
const checkValidate = (inputdata, checktype) => {

  // 이메일, 비밀번호 정규 표현식.
  // 이메일은 이메일 형식 기준.
  // 비밀번호는 8에서 25자리, 영문소대문자와 숫자 그리고 특수문자가 모두 혼합되어야함.
  // eslint-disable-next-line
  const emailValidatePattern = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;
  const passwordValidatePattern = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,25}$/;

  // 검사 타입에 따라서 적절한 검사를 실행.
  if (checktype === 'email') {

      // 검사를 통과하면 true 반환, 그렇지 않으면 false를 반환.
      if (emailValidatePattern.test(inputdata)) {
          return true;
      }
      else {
          return false;
      };
  }
  else if (checktype === 'password') {
      if (passwordValidatePattern.test(inputdata)) {
          return true;
      }
      else {
          return false;
      };
  };  
};

유효성 검사. 검사 대상값을 인자로 함수를 호출하게 되면 미리 지정된 정규 표현식을 이용하여 test 함수를 호출, 결과에 따라 true 혹은 false를 반환하게 된다.

// 중복 검사.
const checkDuplication = async (inputdata, checktype) => {
 	// 반환되는 결과값을 미리 선언.
 	let result = false;

    // 관리자 이메일과, 닉네임을 밴 리스트로 작성하여 우선 판별.
    const prohibitionList = ['admin@admin.com', 'admin', 'Admin', 'administrator', 'Administrator', 'manager', 'Manager', '관리자'];

    // 관리자와 중복될 경우 false를 반환하고 함수 종료.
    if (prohibitionList.includes(inputdata)) {
        result = true;
        return result;
    };

    // 그렇지 않을 경우, 파이어스토어 유저 DB에서 중복 검사 실행.
    // 검사종류와 해당값을 인자로 검색 실행.
    const querys = query(userCollectionRef, where(checktype, '==', inputdata));
    const querySnap = await getDocs(querys);

    // querySnap은 data()를 사용할 수 없다. 반환되는 결과값은 forEach 함수로 풀어서 받아야한다.
    querySnap.forEach((doc) => {

        if (doc.data().email === inputdata) {
            result = true
        };

        if (doc.data().displayName === inputdata) {
            result = true
        };

        console.log(doc.id, " => ", doc.data());
    });

    // 작업을 마치고 결과값을 반환.
    return result;
};

중복 검사. 내 프로젝트에서 가입자의 이메일 주소와 닉네임은 기존에 존재하는 값과 중복되면 안되지만, 관리자의 것을 사용하지도 말아야한다.

따라서 먼저 관리자의 이메일 주소와 닉네임을 배열에 담아 밴 리스트를 만들고, 여기에 대한 중복검사를 먼저 실행하도록 하였다. 검사에 통과하지 못하면 false를 반환하고 함수를 종료.

관리자 검증을 통과하면 query 함수를 이용하여 파이어베이스의 유저 데이터 DB와 입력값을 비교하여 중복 검사를 실행하도록 구현하였다.

코드 평가.

평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.

-> 코드 리팩토링 필요.
이 함수들은 전용 js 파일에서 정의되어 있다. 정규 표현식 같은 고정값은 함수 내부에서 선언하는 것보다는 외부에서 선언하여 재사용성을 높일 수도 있다. 또한 test 함수의 경우, 결과값을 true 혹은 false의 boolean 값으로 반환하기 때문에 굳이 조건식을 사용하여 true 혹은 false를 따로 반환하도록 코드를 짤 필요가 없다. 따라서 아래와 같이 코드를 리팩토링 할 수 있다.

const emailValidatePattern = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;
const passwordValidatePattern = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,25}$/;

const checkValidate = (inputdata, checktype) => {
    // 검사 타입에 따라서 적절한 검사를 실행.
    if (checktype === 'email') {
        return emailValidatePattern.test(inputdata);
    } 
    else if (checktype === 'password') {
        return passwordValidatePattern.test(inputdata);
    }
    return false; // 검사 타입이 잘못된 경우 기본적으로 false를 반환.
};


const checkDuplication = async (inputdata, checktype) => {
    // 관리자 이메일과 닉네임을 밴 리스트로 작성하여 우선 판별.
    const prohibitionList = ['admin@admin.com', 'admin', 'Admin', 'administrator', 'Administrator', 'manager', 'Manager', '관리자'];

    // 관리자와 중복될 경우 바로 false를 반환하고 함수 종료.
    if (prohibitionList.includes(inputdata)) {
        return true;
    }

    // 파이어스토어 유저 DB에서 중복 검사 실행.
    // email 또는 displayName을 checktype에 따라 검색 실행.
    const queryField = checktype === 'email' ? 'email' : 'displayName';
    const querySnap = await getDocs(query(userCollectionRef, where(queryField, '==', inputdata)));

    // 검색 결과를 확인하고 중복 여부를 반환.
    return !querySnap.empty;
};
  • 리팩토링의 출처는 Chat GPT.



2. UI와 기능을 연결하자.

기능을 구현했으니 다음 순서는 UI를 제작하는 것. 서버에서는 검사 결과값을 반환하고 프론트에서는 이를 받아 플래그 변수로 제어하도록 구현하였다.

// 각 Input 태그의 빈 값을 체크하는 State.
const [isEmailEmpty, setIsEmailEmpty] = useState(false);
const [isPasswordEmpty, setIsPasswordEmpty] = useState(false);
const [isPasswordRewriteEmpty, setIsPasswordRewriteEmpty] = useState(false);

// 이메일 유효성 여부를 제어하는 State.
const [isEmailValidate, setIsEmailValidate] = useState(false);
const [isPasswordValidate, setIsPasswordValidate] = useState(false);

// 이메일 중복성 여부를 제어하는 State
const [isEmailDuplication, setIsEmailDuplication] = useState(true);

// 비밀번호를 2번 입력시키고, 입력한 값들이 일치하는지 여부를 제어하는 State.
const [isPasswordSame, setIsPasswordSame] = useState(false);

각각의 플래그 변수는 결과값에 따라 변화하여 화면에 출력할 문구나 CSS 속성을 변경하는 역할을 한다.

그런데 UI를 구현하고 보니 사용자가 맨 처음 페이지에 진입하여 아무것도 입력하지 않았음에도 검사가 실행되어 그 결과를 화면에 출력하고 있는 문제를 발견하였다. 여기에 대한 해결 방법을 고민하다가 이 부분도 플래그 변수로 관리할 수 있겠다는 생각이 들었다.

// 각 Input 태그의 결과 메시지 출력여부를 제어하는 State.
const [isFirstRenderingEmail, setIsFirstRenderingEmail] = useState(true);
const [isFirstRenderingPassword, setIsFirstRenderingPassword] = useState(true);
const [isFirstRenderingPasswordRewrite, setIsFirstRenderingPasswordRewrite] = useState(true);

프론트 화면에 검사 결과를 렌더링 할지 여부를 제어하는 플래그 변수를 만들었다. 페이지가 처음 렌더링 되었을 때는 false로 결과값을 화면에 출력하지 않고, 값을 입력했을 때 true로 전환되어 화면에 출력되도록 구현하였다. 또한 사용자가 한 번 입력한 값을 모두 삭제했을 때에도 false로 전환되어 화면이 깔끔해지도록 하였다.

// 이메일 주소, 비밀번호 Input 태그의 입력값을 감지하는 onChange 함수.
const onChange = (event) => {
    setUserData({ ...userData, [event.target.id]: event.target.value });

    if (event.target.id === 'email') {
        setIsEmailDuplication(true);
        setIsFirstRenderingEmail(false);
        setIsEmailEmpty(false);

        setIsEmailValidate(checkValidate(event.target.value, 'email'));
    };

    if (event.target.id === 'password') {
        setIsFirstRenderingPassword(false);
        setIsPasswordEmpty(false);

        setIsPasswordValidate(checkValidate(event.target.value, 'password'));
    };
};

사용자가 input 태그에서 값을 입력했을 경우, event 객체의 id 값을 이용해서 어떤 종류의 값을 입력했는지 감지한다.

그리고 플래그 변수를 조정하여 검사 결과가 화면에 출력되도록 하고, 값이 입력되었으니 빈 값 여부를 제어하는 플래그 변수도 변경해준다.

마지막으로 유효성 검사를 실행하여 입력값의 유효성을 검증한다.

초기 화면은 깨끗하지만, 값이나 입력하면..

검사 기능들이 동작하고 그 결과값을 화면에 출력하게 된다.

그리고 검사가 실행되고 결과값이 프론트로 반환되기 전까지는 다른 기능이 실행되어서는 안된다. 따라서 여기서도 isLoading 플래그 변수를 이용하여 어떤 기능이 동작 중인 상태에서는 isLoading이 true가 되고, 다른 기능이 동작하지 않도록 구현하였다.

// 중복검사 함수.
const onCheckDuplication = () => {
    setIsLoading(true);

    if (!userData.email) {
        alert('이메일 주소를 입력해주세요.');
        return;
    };

    if (!isEmailValidate) {
        alert('유효한 이메일 주소를 입력해주세요.');
        return;
    };

    checkDuplication(userData.email, 'email', dispatch)
        .then((result) => {
            setIsEmailDuplication(result);

            if (result) {
                alert('사용할 수 없는 이메일 주소 입니다.');
                setIsLoading(false);
            }
            else {
                alert('사용가능한 이메일 주소 입니다.');
                setIsLoading(false);
            }
        });
};

사용자가 입력한 값이 유효하며, 중복 검사도 통과했다면..

위와 같은 화면이 출력된다. 비밀번호도 마찬가지의 원리로 동작한다.

회원가입 기능이 동작하는데 이메일 주소나 비밀번호를 입력하지 않으면? 기능이 동작해서는 안된다. 경고창이 출력되고 다음 과정으로 넘어가지 않도록 구현하였다.

여기서 비밀번호를 다시 입력하는 부분은 굳이 경고창까지 출력할 필요가 없다고 판단하였다. 따라서 여기서는 styled-components의 기능을 사용하여 UI를 구현해보았다.

const Input = styled.input`
    width: 100%;
    height: 40px;

    border: none;
    border-bottom: 1px solid black;

    font-size: 18px;
    font-family: 'GIFont';

    border-color: ${(props) => props.isEmpty ? 'red' : 'gray'};

    &::placeholder {
        color: ${(props) => props.isEmpty ? 'red' : 'gray'};
    };
`;

styled-components의 장점 중 하나는 js의 변수를 가져다가 사용할 수 있다는 것이다. 입력값이 존재하지 않을 경우 isEmpty 변수의 값을 사용하여 UI의 스타일이 다르게 적용되도록 구현하였다.

원하는데로 잘 동작한다. 이렇게 회원 가입 기능이 완성되었다!

코드 평가.

평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.

-> 코드 중복성 줄이기. 비슷한 기능을 하는 코드가 반복되는 부분이 많다. 함수 등을 활용하는 방법을 생각해볼 것.

-> 컴포넌트 분리. 하나의 파일에 너무 많은 코드들이 작성되어 있다. 유지보수의 편리함 및 가독성 향상을 위해 적절하게 컴포넌트를 분리하는 방법을 구상하는 것이 좋다.

profile
프론트엔드 개발자를 준비하고 있습니다.

1개의 댓글

comment-user-thumbnail
2023년 7월 18일

글 잘 봤습니다, 감사합니다.

답글 달기