1차 프로젝트 - 커뮤니티 홈페이지 클론 (hola) 로그인, 회원가입 구현 (2)_상세코드

이고운·2022년 9월 13일
0

1차 팀프로젝트

목록 보기
2/3

나의 클론코딩 페이지는 크게 로그인, 이용약관, 회원가입페이지로 나뉜다.
로그인페이지에서 계정이 없다면 회원가입을 하세요라는 버튼으로 navigate를 설정해
회원가입 페이지로 넘어가게 한다. 이 때 회원가입 상세 내역 기재 전에 이용약관 전체 동의를 선행 필수 사항으로 만들어, 전체동의를 해야만 회원가입 상세 내역 기재 페이지로 넘어가는 것이다.
일단 내가 각각의 페이지에서 구현하고자 하는 기능은 아래와 같았다.

1. 필요 기능 구현 정리

1) 로그인 페이지

  • 아이디 @필수 입력, 비밀번호 6자리 필수 입력
  • 위의 조건이 만족하면 버튼 활성화 및 버튼 색상 변경
  • 미 충족시에 버튼을 클릭하게 된다면 하단에 로그인 정보를 확인하라는 오류 메세지
  • 올바른 정보로 로그인했을 때 로그인이 성공하였습니다라는 alert창과
    main페이지로 이동

2) 이용약관 페이지

  • 전체동의 하단에 하위 동의 체크칸 3개가 있음
  • 전체동의에 체크한다면 하위 동의 3개에 자동 체크, 전체동의 체크 해제시에 하위 동의 3개에 자동 체크 해제
  • 개별 하위 동의가 전부 체크되면 전체동의에 자동 체크, 전체동의 상태에서 하나라도 미체크시 전체동의 체크 해제
  • 위의 조건들이 충족 시에 버튼 색상 변경 및 버튼 활성화
  • 미충족 시 버튼 클릭한다면 전체 동의해달라고 하단에 오류 메세지
  • 조건 충족 후 클릭 시 회원가입 페이지로 이동

3) 회원가입 페이지

  • 닉네임, 이메일, 패스워드, 패스워드 확인 칸
  • 닉네임 1자 이상, 이메일 @포함, 패스워드와 패스워드 확인이 동일
  • 위의 조건이 충족시에 버튼 활성화 및 색상 변경
  • 각각의 조건이 미 충족시에 하단에 오류 메세지 출력
    (로그인과 이용약관에서는 미충족시에 버튼 클릭하면 오류 메세지 출력하지만
    회원가입페이지에서는 즉각적으로 확인)

2. 상세 코드 내역

1) 로그인페이지

일단 로그인 페이지는 기존 학습했던 내용을 복습하는 의미가 강했던 것 같다.
먼저 email, pw, valid라는 상태값을 useState로 넣어줬다.
valid는 이메일과 패스워드 유효값을 상태값으로 관리하기 위한 값이다.

const [email, setEmail] = useState('');
const [pw, setPw] = useState('');
const [valid, setValid] = useState(false);

그리고 이메일, 패스워드 인풋 이벤트핸들러를 저장하기 위한 함수를 지정했다.
이 때 이메일과 패스워드의 조건을 넣어준다.

  const handleEmailInput = e => {
    const emailValue = e.target.value;
    setEmail(emailValue);
    emailValue.includes('@') && pw.length >= 6
      ? setValid(true)
      : setValid(false);
  };
  const handlePwInput = e => {
    const pwValue = e.target.value;
    setPw(pwValue);
    email.includes('@') && pwValue.length >= 6
      ? setValid(true)
      : setValid(false);
  };

위의 조건들이 true면 버튼 눌렀을 때 메인페이지로 이동하는 함수 만들기

const navigate = useNavigate();
  const goToMain = () => {
    if (email.includes('@') && pw.length >= 6) {
      navigate('/');
      alert('로그인 되었습니다. HALLO에 오신 것을 환영합니다.');
      setValid(true);
    } else {
      setValid(false);
    }
  };

로그인 버튼 클릭했을 때 백엔드와 통신하여 fetch로 서버로 정보 보내주는 함수를 만든다. 이 때 로그인 API 주소는 백엔드에서 지정한 주소, JSON타입으로 가져온다. 그리고 토큰값을 localstorage에 저장해준다. 로그인을 유지해야 다음 작업이 이루어짐.(새글쓰기에서 이 데이터를 getItem으로 받아와서 사용)

const onLoginBtnClick = () => {
    const body = {
      email: email,
      password: pw,
    };
    fetch('http://localhost:8000/users/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    })
      .then(res => res.json())
      .then(result => {
        // console.log(result);
        if (result.token) {
          localStorage.setItem('login-token', result.token);
          goToMain();
        } else {
          alert('로그인 정보를 확인해 주시기 바랍니다.');
        }
      });
  };

이후 return()안에 페이지에 보여주는 UI를 담는다.
각각 input, button에 위에 지정해준 함수를 넣어서 사용.
위의 이메일과 패스워드 유효값을 조건부 렌더링으로 넣어준다.
(이메일과 패스워드가 false면 아래에 오류 메세지, 버튼은 비활성화, 색상도 회색으로 비활성화 색상)

 {!valid && (
            <div
              style={{
                color: 'red',
                textAlign: 'center',
                verticalAlign: 'center',
              }}

              로그인 정보를 확인해 주시기 바랍니다.
            </div>
          )}
           <button
            disabled={!valid}
            onClick={onLoginBtnClick}
            style={{ backgroundColor: valid ? 'black' : 'rgb(201, 204, 206)' }}
            className={`${css.loginButton} ${css.button}`}

            로그인
          </button>

2) 이용약관 페이지

각 동의 칸을 useState로 상태값 저장, 에러메세지, 버튼 비활성화를 위한 상태값도 저장, 상세 이용약관 내용을 모달창으로 띄워줄꺼라 모달도 상태값 저장해줌. 이때 모달창은 true일때 보여주도록 함수도 저장.

 const [allCheck, setAllCheck] = useState(false);
 const [ageCheck, setAgeCheck] = useState(false);
 const [serviceCheck, setServiceCheck] = useState(false);
 const [personalCheck, SetPersonalCheck] = useState(false);

  const [agreementError, setAgreementError] = useState(false);
  const [disabled, setDisabled] = useState(false);

  const [modal, setModal] = useState(false);
  const modalShow = () => {
    setModal(true);
  };

그리고 전체 동의 여부에 따른 오류 메세지 생성을 위한 함수도 저장해준다. 이때 전체 동의라면 버튼도 활성화, 다음페이지로 이동시킴. (회원가입 버튼에 지정예정)

 const onCheckClick = e => {
    e.preventDefault(); //
    if (!allCheck) {
      setAgreementError(true);
    } else {
      setAgreementError(false);
      setDisabled(false);
      navigate('/signup');
    }
  };

각 동의칸을 하나씩 이벤트핸들러를 위한 함수를 저장해줌. (아래와 같이)

 const ageBtnCheck = () => {
    if (ageCheck === false) {
      setAgeCheck(true);
    } else {
      setAgeCheck(false);
    }

하위 3개의 체크 항목이 전부 체크되면 바로 전체동의에 체크될 수 있도록
useEffect를 사용해서 각 항목이 변경될 때마다 렌더링해 전체체크로 바꿔준다.

  useEffect(() => {
    if (ageCheck === true && serviceCheck === true && personalCheck === true) {
      setAllCheck(true);
    } else {
      setAllCheck(false);
    }
  }, [ageCheck, serviceCheck, personalCheck]);

그 다음 return() 안에 화면에 출력되는 실제 체크박스들을 넣어줌
이 때 누르면 이용약관 모달창으로 상세내역이 보여지는 버튼을 만들 것임.
위에서 만들어준 모달 함수를 버튼 onClick으로 넣어주고 modal === true 일때 모달창을 열어 보여줌. 모달창에서 사용하기 위해 props값으로 setModal이랑 allBtnCheck값도 넘겨줌.

  <span>
    <button className={css.detail} onClick={modalShow}>
           약관 자세히 보기
        <img className={css.nextIcon} alt="화살표" src={nexticon} />
   </button>
        {modal && (
          <Modal setModal={setModal} allBtn={allBtnCheck}>
          </Modal>
                )}
  </span>

모달창을 위한 모달 컴포넌트를 따로 만들었는데, 여기서도 전체 동의버튼을 클릭하면 전체동의에 체크되도록 만들었다. 또한 모달창 생성에서 배경 스크롤 금지, 내부 모달창 스크롤 생성 등 처음 사용하는 기능들을 구현했다.

//모달창 닫기
function Modal({ setModal, allBtn }) {
const modalClose = () => {
  setModal(false);
};
//전체동의 체크 기능
const clickBtn = () => {
  allBtn(false);
  setModal(false);
};
//배경 스크롤 방지, 글박스 내부에는 스크롤 자동
useEffect(() => {
  document.body.style.overflow = 'hidden';
  return () => {
    document.body.style.overflow = 'auto';
  };
}, []);

이용약관 첫페이지에서 전체동의 미체크시 오류 메세지가 뜨도록
조건부 렌더링 이용해서 넣어줌.

  {agreementError && (
     <div style={{ color: 'red', textAlign: 'center' }}>
        이용약관에 동의해 주시기 바랍니다.
     </div>
          )}
   <button
        disabled={disabled}
        onClick={onCheckClick}
        style={{
           backgroundColor: allCheck ? 'black' : 'rgb(201, 204, 206)',
            }}
          className={`${css.signupButton} ${css.button}`}

            다음단계 진행
  </button>

그 결과는 아래와 같다.

3. 회원가입 페이지

회원가입페이지는 시간이 제일 많이 들었던 페이지였다.
유효성 검사하여 그에 맞는 오류메세지를 나오게 하는데 어려움이 있었다.
이것저것 바꿔보고 구글 검색도 해가며 완성하긴 했는데, 더 좋은 코드가 있을 것 같아서 나중에 리팩토링 제대로 진행해봐야 할 것 같다.
이미 몇가지는 수정한 것이긴 한데..베이스는 로그인 페이지와 비슷하다.
다만 회원가입 페이지에서는 각 항목마다 조건 미충족시 오류 메세지를 띄우게 했다.
먼저 각 input, 오류 유효성 검사하는 부분을 상태값으로 저장해준다.

function Signup() {
  //닉네임, 이메일,비밀번호, 비밀번호 확인 현재값
  const [nickName, setNickName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [passwordCheck, setPasswordCheck] = useState('');

  // 유효성 검사
  const [mismatchError, setMismatchError] = useState(false);
  const [emailError, setEmailError] = useState(false);
  const [nickError, setNickError] = useState(false);

그리고 유효성 검사 이벤트 함수를 각각 저장해준다. 이때 useCallback함수를 사용해서 함수를 재 사용해주고 필요할 때만 재 생성해주도록 했다. (컴포넌트 최적화 개념)

 //이메일 주소에 @가 포함되는지 확인하고 없으면 경고메세지
 const onChangeEmail = e => {
   setEmail(e.target.value);
   setEmailError(!e.target.value.includes('@'));
 };
 //닉네임에 하나 이상 들어가는지 확인
 const onChangeNick = useCallback(e => {
   setNickName(e.target.value);
   setNickError(e.target.value.length >= 1);
 }, [nickName]);

 //비밀번호 칸이랑 비밀번호 확인칸이랑 맞는지 확인 6자리 이상 들어가지 않으면 경고 메세지
 password변경되면 함수 재생성
 const onChangePasswordCheck = useCallback(
   e => {
     setPasswordCheck(e.target.value);
     setMismatchError(
       e.target.value !== password || e.target.value.length < 6
     );
   },
   [password]
 );

 const onChangePassword = useCallback(
   e => {
     setPassword(e.target.value);
     setMismatchError(
       e.target.value !== passwordCheck || e.target.value.length < 6
     );
   },
   [passwordCheck]
 );

그리고 회원가입 버튼을 클릭했을 때 서버와 통신하여 백엔드에 데이터를 넘길 수 있게 fetch를 이용했다.
status값으로 alert팝업이나 navigate를 진행하도록 함.이 때 status값은 백엔드에서 설정한 코드를 사용했다.

const navigate = useNavigate();
  const goToLogin = () => {
    navigate('/login');
  };

  const onSignupBtnClick = () => {
    const body = {
      email: email,
      nickname: nickName,
      password: password,
    };

    fetch('http://localhost:8000/users/signup', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    })
      .then(res => {
        if (res.status === 201) {
          alert('회원가입이 완료되었습니다. 로그인 해주세요.');
          navigate('/login');
        }
        if (res.status === 400) {
          alert('이미 사용중인 이메일 입니다. 다른 이메일을 사용해주세요.');
        }
      })
      .then(res => {});
  };

그리고 return() 안에 화면에 보여지는 부분을 기재했다. 각 input에는 위에서 작성한 유효성검사 함수를 넣어서 이벤트핸들이 가능하도록 했다.

   {emailError && (
     <div style={{ color: 'red', textAlign: 'center' }}>
       이메일 양식을 확인하세요
     </div>
    )}
    </div>
    <div>
       <input
         value={password}
         onChange={onChangePassword}
         type="password"
         className={css.textInput}
         placeholder="비밀번호 (6자리 이상)"
            />
    </div>

이런식으로 넣어줌. 그리고 회원가입 버튼에는 아래와 같이 활성화, 버튼 색상 변경 스타일을 지정했는데,,nickError만 false로 지정한 이유가 원래 이름대로 에러인상태가 true이어야하는데 에러 조건 지정할 때 이상하게 닉네임 1미만인 값을 조건으로 넣을 수가 없었음..그때라도 nickError 이름을 변경했어야했는데 이름에 통일감을 주다보니 이렇게 되었다..그래서 nickError만 true, false가 반대가 되었다...

 <button
       disabled={!nickName || !password || !email || !passwordCheck}
       onClick={onSignupBtnClick}
       className={css.signupButton}
       style={{
            backgroundColor:
              !emailError && !mismatchError && nickError
              ? 'black'
              : 'rgb(201, 204, 206)',
            }}
          >

그렇게 해서 결과는 아래와 같다. 통신확인했을 때 회원가입 잘 진행되어서 백엔드로 넘어갔고 그 데이터로 로그인까지 성공했었음!!

참고로 여기서 나오는 중복확인 버튼은 역시나 백엔드에서 가져온 status코드로 검증하게 해놨는데 테스트 할 때는 잘 되었는데 발표할 때 안되어서 당황했음..
미리 이렇게 좀 저장해 놓을 껄 그랬다..ㅎㅎ

이렇게 해서 내가 구현한 페이지는 마무리되었다.
자세한 후기는 다음 페이지에서 쓰려고 한다.

profile
자 이제 시작이야~ 내 꿈을~ 내 꿈을 위한 여행~~🌈

0개의 댓글