[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 함수를 사용해서 렌더링해주었다.
결과🔻
코드🔻
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가 어긋나버렸는데 빠듯한 시간동안 기능 구현에 더 집중하느라 알아채지 못했던 것 같다. 늘 사소한 부분들에 더 신경을 쓰려고 하는데도 놓친 부분들이 생겨버려서 두고두고 많이 속상했다.
또 회원가입 페이지에서 사용자가 회원가입을 끝냈을 때 자동으로 로그인된 상태에서 메인 페이지로 이동될 수 있도록 구현했다면 다시 로그인을 해야하는 번거로움을 줄일 수 있었을텐데 미처 사용자의 입장에서 먼저 생각해보지 못했던 것이 많이 아쉽다. 이제는 개발자가 되기 위해 공부하고 있지만 내가 사용자의 입장이었을 때 느꼈던 것들을 놓치지 않도록 항상 노력하려 한다.