[Lululemon 클론 프로젝트] 로그인/회원가입 페이지

목표

JWT 인증방식을 활용한 로그인/회원가입 페이지 구현


과정

1. 로그인/회원가입 페이지 UI

1) lululemon의 사이트는 로그인 페이지에서 회원가입 버튼 클릭시 양식이 슬라이드로 올라와 한 페이지에서 로그인과 회원가입을 모두 진행할 수 있는 방식으로 되어 있었다. 이를 구현하기 위해 로그인 양식과 회원가입 양식을 각각 컴포넌트로 만든 뒤 'modal' 변수의 상태값이 true인지 false인지에 따라 적절한 컴포넌트가 렌더링될 수 있게 했다. modal 변수의 값을 반전시키는 handleModal 함수를 로그인, 회원가입 컴포넌트에 넘겨준 후 각각의 컴포넌트에 있는 로그인하기, 회원가입하기 버튼에 달아주었다.

결과🔻


코드🔻

const SignIn = () => {
//(...)
    const [modal, setModal] = useState(true);
    const handleModal = () => {
      setModal(!modal);
    };

    return (
        //(...)
              {modal ? (
                <SignInComponent
                  id={id}
                  pw={pw}
                  handleIdInput={handleIdInput}
                  handlePwInput={handlePwInput}
                  handleModal={handleModal}
                  isValidSignIn={isValidSignIn}
                  goToMain={goToMain}
                />
              ) : (
                <SignUp handleModal={handleModal} />
              )}
    );
  };



2) 로그인 페이지 왼쪽의 홍보 문구 부분은 아이콘과 문장 한 줄이 반복되는 형태로 되어있어 문구 목록을 상수데이터로 만든 뒤 map 함수를 사용해서 렌더링해주었다.

결과🔻

Screenshot 2022-06-06 at 11.45.29.png
코드🔻


const SignIn = () => {
//(...)
  return (
      //(...)
            <ul>
              {PERKS_LIST.map(list => {
                const { id, imgSrc, alt, text } = list;
                return (
                  <li key={id}>
                    <img src={imgSrc} alt={alt} />
                    {text}
                  </li>
                );
              })}
            </ul>
      //(...)    
  );
};
export default SignIn;

const PERKS_LIST = [
  {
    id: 1,
    imgSrc: '/signInImages/refund.png',
    alt: 'refund',
    text: 'Fast Track Refunds',
  },
  //(...)
  {
    id: 5,
    imgSrc: '/signInImages/shopping-bag.png',
    alt: 'shoppingbag',
    text: 'Tailored Suggestions',
  },
];




2. JWT 로그인 방식 구현
로그인시 사용자가 생성했던 계정의 ID/PW와 비교한다음, 일치할 경우 access token을 발급하여 로컬스토리지에 저장해두었다. 처음 로그인을 한 다음부터는 저장했던 토큰을 request의 헤더에 첨부해서 전송함으로써 사용자가 기능에 접근할때마다 로그인을 매번 하지 않아도 되도록 구현했다.


코드🔻

const SignIn = () => {
  const navigate = useNavigate();
  const goToMain = e => {
    e.preventDefault();

    fetch('http://10.58.3.71:8000/users/signin', {
      method: 'POST',
      body: JSON.stringify({
        email: id,
        password: pw,
      }),
    })
      .then(res => {
        if (res.ok) {
          return res.json();
        } else {
          alert('Please check your email and password');
          setId('');
          setPw('');
        }
      })
      .then(result => {
        localStorage.setItem('token', result.Access_token);
        alert('WELCOME');
        navigate('/');
      });
  };
  //(...)
};




3. 로그인/회원가입 유효성 검사
1) 로그인시 ID에 @와 .이 포함되어있고 PW를 8자 이상 입력해야만 로그인 버튼이 활성화되며, 회원가입한 이력이 있어 서버에서 확인 가능한 계정일 경우에만 로그인된 상태에서 메인페이지로 이동할 수 있도록 구현했다.
2) 회원가입의 경우 로그인과 동일하게 ID/PW를 검사하고, 주소를 10자 이상 입력한 경우에만 회원 정보가 저장된다.


결과🔻


코드🔻
SignIn.js👇

const SignIn = () => {
//(...)
  const [id, setId] = useState('');
  const [pw, setPw] = useState('');

  const isValidEmail = id.includes('@') && id.includes('.');
  const isValidPassword = pw.length >= 8;
  const isValidSignIn = isValidEmail && isValidPassword;

  const handleIdInput = e => {
    setId(e.target.value);
  };
  const handlePwInput = e => {
    setPw(e.target.value);
  };

  return (
      //(...)
            {modal ? (
              <SignInComponent
                id={id}
                pw={pw}
                handleIdInput={handleIdInput}
                handlePwInput={handlePwInput}
                handleModal={handleModal}
                isValidSignIn={isValidSignIn}
                goToMain={goToMain}
              />
            ) : (
              <SignUp handleModal={handleModal} />
            )}
  );
};

SignInComponent.js👇

const SignInComponent = ({
  id,
  pw,
  handleIdInput,
  handlePwInput,
  isValidSignIn,
  goToMain,
}) => {
  const scrollToTop = useEffect(() => {
    window.scrollTo(0, 0);
  }, []);

  return (
    //(...)    
      <div className="signInWrapper">
        <form>
          <label>
            Email address
            <input type="text" onChange={handleIdInput} value={id} required />
          </label>
          <label>
            Password
            <input
              type="password"
              onChange={handlePwInput}
              value={pw}
              required
            />
          </label>
          <a href="#!">Forgot your password?</a>
          <button
            className={
              isValidSignIn ? 'signInBtnActivated' : 'signInBtnDeActivated'
            }
            onClick={goToMain}
          >
            SIGN IN
          </button>
        </form>
      </div>
      //(...)
  );
};




회고

로그인/회원가입 페이지를 구현하면서 그동안 프론트엔드를 공부하면서도 미처 신경쓰지 못했던 개발자도구의 network 패널과 인증, 인가의 개념에 익숙해질 수 있어 개인적으로 정말 소중한 경험이었다. 다만 시연 영상을 촬영할 당시에 로그인 양식의 border-bottom 부분이 내 의도와 다르게 적용되었는데도 수정하지 못했던 것이 끝까지 아쉬움으로 남는다. 로그인과 회원가입 양식을 별개의 컴포넌트로 분리하는 과정에서 작성해두었던 css가 어긋나버렸는데 빠듯한 시간동안 기능 구현에 더 집중하느라 알아채지 못했던 것 같다. 늘 사소한 부분들에 더 신경을 쓰려고 하는데도 놓친 부분들이 생겨버려서 두고두고 많이 속상했다.
또 회원가입 페이지에서 사용자가 회원가입을 끝냈을 때 자동으로 로그인된 상태에서 메인 페이지로 이동될 수 있도록 구현했다면 다시 로그인을 해야하는 번거로움을 줄일 수 있었을텐데 미처 사용자의 입장에서 먼저 생각해보지 못했던 것이 많이 아쉽다. 이제는 개발자가 되기 위해 공부하고 있지만 내가 사용자의 입장이었을 때 느꼈던 것들을 놓치지 않도록 항상 노력하려 한다.