자작 블로그를 만들었던 때는 계정을 만들고 로그인 할 사람이 나 밖에 존재하지 않았다. 따라서 회원 기능을 구현하는 것도 기능 구현 연습을 목적으로 매우 간단하게만 만들었었다.
그러나 이번 굿즈 스토어는 관리자인 나 말고도 다른 사람들이 계정을 만들고 로그인할 수 있어야 한다. 여기에는 당연히 유효성 검사나 중복 검사 같은 부가기능도 포함되어야 하며, 따라서 굿즈 스토어 프로젝트에서 가장 처음으로 구현할 기능은 회원 기능. 그 중에서도 회원 가입 기능을 먼저 구현하였다.
공부용 목적으로 시작한 개인 프로젝트라고 해도 겉으로 보여지는 화면을 소홀히 할 수는 없다. 따라서 먼저 네이버나 카카오 같이 상용 서비스가 이루어지는 곳을 대상으로 UI가 어떻게 이루어졌는지 참고하였다.
그 중에서도 회원가입에 필요한 정보를 여러 개의 컴포넌트로 나누어 입력 받는 카카오의 방식이 마음에 들어, 내 프로젝트의 회원가입 UI는 카카오의 UI을 재현한다는 것을 목표로 구현을 시작하였다.
일단 가장 먼저 생각해야하는 점은 여러 개의 컴포넌트를 어떻게 전환해야하는가이다.
고민 끝에 우선 회원가입 페이지를 만들고, 페이지 아래 데이터를 입력받는 다수의 컴포넌트가 렌더링되도록 기능을 구현해보았다. 회원가입 페이지에서 플래그 변수를 이용하여 상황에 맞는 컴포넌트가 렌더링 되도록 만들었다.
const [isTermsAgreement, setIsTermsAgreement] = useState(false);
const [isEmailAndPasswordEntered, setIsEmailAndPasswordEntered] = useState(true);
const [isOtherEntered, setIsOtherEntered] = useState(true);
(...)
<BackGround>
{isTermsAgreement ?
<></>
:
<RequestTermsAgreement setIsTermsAgreement={setIsTermsAgreement} setIsEmailAndPasswordEntered={setIsEmailAndPasswordEntered} />
}
{isEmailAndPasswordEntered ?
<></>
:
<RequestEmailAndPasswordVerify userData={userData} setUserData={setUserData} getUserState={getUserState} dispatch={dispatch} setIsEmailAndPasswordEntered={setIsEmailAndPasswordEntered} setIsOtherEntered={setIsOtherEntered} />
}
{isOtherEntered ?
<></>
:
<RequestOtherVerify userData={userData} setUserData={setUserData} navigate={navigate} dispatch={dispatch} getUserState={getUserState} setIsOtherEntered={setIsOtherEntered} />
}
</BackGround>
사용자가 회원가입 페이지에 처음으로 들어오게 되면 기본적으로 약관 동의 컴포넌트가 출력된다. 여기에서 필요한 작업을 마치고 확인 버튼을 클릭하면 플래그 변수가 전환되면서 다음 컴포넌트가 렌더링 되도록 하였다.
const onSubmit = (event) => {
event.preventDefault();
if (!isAgreement) {
alert('약관 동의가 필요합니다.');
return;
}
setIsTermsAgreement(true);
setIsEmailAndPasswordEntered(false);
};
이를 위해서 submit 함수를 위와 같이 구현하였다. 필요한 데이터가 입력되었는지 혹은 필요한 작업이 모두 수행되었는지를 확인한 다음, 이를 통과했을 때 다음 컴포넌트가 렌더링되도록 하였다. 다른 컴포넌트도 동일한 방식으로 구현하였다.
평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.
-> 크로스 브라우징을 고려해야 함. CSS 미디어 쿼리의 작성 방법이 범용적이지 않음. (그 동안 반응형 웹의 구현은 모두 같은 방식으로 구현해왔고, 여기에 문제가 있을거라는건 생각해보지 않았는데 내 코드를 Chat GPT에게 보여주었더니 크로스 브라우징 문제를 지적해주었다. 생소한 개념이라 재차 물어봤더니 이렇게 답해주었다.)
@media screen and (max-width: 500px)와 같은 방식은 특정 화면 크기가 500px 이하일 때에만 해당 스타일을 적용하도록 설정한 것입니다. 하지만 이러한 방식은 특정 브라우저나 기기에서만 동작하고 다른 브라우저에서는 제대로 동작하지 않을 수 있습니다.
범용적인 방식을 사용하기 위해서는 미디어 쿼리를 브라우저의 너비를 기준으로 하는 것이 아니라, 기기의 특성이나 해상도 등과 관계없이 다양한 브라우저에서 동일한 스타일을 적용할 수 있도록 조건을 설정해야 합니다. 이를 위해서는 다음과 같은 방식들을 고려할 수 있습니다:
기기의 특성에 따라 미디어 쿼리 설정: 브라우저의 너비뿐만 아니라 기기의 종류, 해상도, 픽셀 비율 등 다양한 특성에 따라 미디어 쿼리를 설정하여 다양한 기기에서 호환성을 보장할 수 있습니다.
상대적인 단위 사용: 픽셀 단위 대신 상대적인 단위인 em, rem, % 등을 사용하여 화면 크기에 상대적인 스타일을 적용합니다. 이렇게 하면 브라우저의 확대/축소에도 유연하게 대응할 수 있습니다.
브라우저 접두사 사용: 일부 브라우저는 특정 기능을 지원하기 위해 접두사를 추가해야 할 수 있습니다. 예를 들어, -webkit-, -moz-, -ms- 등의 접두사를 사용하여 각 브라우저에 맞게 스타일을 적용할 수 있습니다.
기능 탐지 사용: 브라우저에서 지원하는 기능을 탐지하여 해당 기능을 지원하는 경우에만 스타일을 적용하도록 설정할 수 있습니다. 이를 통해 브라우저의 호환성을 개선할 수 있습니다.
같은 파이어베이스를 백엔드로 사용하고 있으니 기능 자체는 자작 블로그의 기능과 동일하다. 다만 이번에는 관리자가 회원 목록을 관리할 수 있도록 회원정보 DB에 가입자의 정보를 저장하는 기능을 추가해야 한다.
이번 프로젝트의 주요 기능은 모두 Redux 범위 내에서 이루어져야 한다. 따라서 Action Creator에 기능을 구현하였다.
정상적으로 회원가입이 이루어지면 회원의 정보를 DB에 입력하고, displayName을 설정하기 위해 프로필을 업데이트 해주고, 이메일 인증 작업도 해주고.. 필요한 작업들이 연속적으로 수행되도록 구현했다.
createUserWithEmailAndPassword(appAuth, userData.email, userData.password)
.then((userCredential) => {
// 과정이 정상적으로 진행되었는데, userCredential 값이 존재하지 않으면 과정을 중단하고 에러.
if (!userCredential.user) {
throw errorCode.userSignInError.ThereIsNoUserCredential;
}
// 계정 생성이 완료되고, userCredential 값이 정상적으로 존재하면 displayName 정보를 업데이트 해준다.
updateProfile(appAuth.currentUser, {
displayName: userData.displayname,
})
.then(() => {
// displayName 업데이트가 정상적으로 완료되면 DB에 유저 정보를 저장한다.
addData()
// 작업이 정상 완료되면 SIGN_UP_SUCCESS Action을 실행하고 완료 메시지 출력, 이후 메인 페이지로 이동.
.then(() => {
addAdditionData()
.then(() => {
emailVerifiedProcess(appAuth.currentUser)
.then(() => {
signOut(appAuth)
.then(() => {
dispatch({ type: 'COMPLETE' });
dispatch({ type: 'SIGN_UP_SUCCESS' });
alert('인증 메일이 발송되었습니다. 이메일 함을 확인해주세요.');
alert('모든 과정이 완료되었습니다. 회원가입을 환영합니다!');
})
.catch((error) => {
dispatch({ type: 'ERROR', payload: createErrorData(error) });
navigate('/', { replace: true });
});
})
// 이메일 인증 메일 발송에 문제가 생겼을 경우.
.catch((error) => {
dispatch({ type: 'ERROR', payload: createErrorData(error) });
alert('인증메일 발송에 에러가 발생했습니다.');
navigate('/', { replace: true });
});
(...)
평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.
-> 보안 강화 필요. 사용자가 입력한 비밀번호가 무방비하게 노출되어 있음. 암호화 등의 조치가 필요함.
-> 중복되는 코드 줄이기. 비슷한 기능을 하는 코드 블록들이 반복되는 부분이 존재함. 함수 등을 이용하여 중복을 줄일 필요가 있음.
-> 에러 처리 방법이 매우 미흡함. then-catch 문이 너무 중첩되어 코드 가독성이 떨어짐. 더 나은 에러 핸들링 방식을 고려해야 함.
정보가 많아서 도움이 많이 됐습니다.