회원가입 기능과 마찬가지로 외부에서 볼 일이 없는 기능이다. 여기도 마찬가지로 개발 능력 증진을 위한 연습의 일환으로 로그인 기능을 구현하였다.
export default function Login() {
// useLogin 커스텀 훅으로부터 로그인 기능과 상태를 가져옴
const { error, isPending, login, anonymousLogin } = useLogin();
const [userData, setUserData] = useState({
id: '',
pwd: ''
});
const loginEvent = (event) => {
event.preventDefault();
login(userData.id, userData.pwd);
};
// eslint-disable-next-line
const anonymousLoginEvent = (event) => {
event.preventDefault();
anonymousLogin();
};
// 입력 필드 값 변경 시 호출되는 함수
const onChangeEvent = (event) => {
setUserData({
...userData,
[event.target.name]: event.target.value
})
};
// 페이지 타이틀 설정
useEffect(() => {
const titleElement = document.getElementsByTagName("title")[0];
titleElement.innerHTML = '로그인 페이지';
}, []);
return (
<div className={styles.login}>
<form className={styles.loginform} onSubmit={loginEvent}>
<p>로그인</p>
<div className={styles.input}>
<label htmlFor='id'>email : </label>
<input name='id' type='text' required placeholder='이메일을 입력해주세요' value={userData.id} onChange={onChangeEvent} />
</div>
<div className={styles.input}>
<label htmlFor='pwd'>password : </label>
<input name='pwd' type='password' required placeholder='비밀번호를 입력해주세요' value={userData.pwd} onChange={onChangeEvent} />
</div>
<div className={styles.loginbutton}>
{!isPending && <button type='submit' className={styles.submitbutton}>로그인</button>}
{/* {!isPending && <button className={styles.submitbutton2} onClick={anonymousLoginEvent}>비로그인 접속하기</button>} */}
{isPending && <><br /><strong>로그인이 진행중입니다...</strong></>}
{error && <><br /><strong>로그인 에러가 발생하였습니다...</strong></>}
</div>
</form>
</div>
);
};
UI의 기본적인 틀은 회원가입 기능과 거의 동일하다. 기능 구조는 완전 동일하고 세세한 UI만 다를 뿐이다.
사용자가 아이디와 비밀번호를 입력하고 버튼을 클릭할 경우, loginEvent 함수를 호출하고 이 함수에서는 백엔드에 해당하는 login 함수를 사용자의 입력값을 인자로 넣어 호출하여준다.
평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.
-> 보안 취약. 로그인 쪽과 동일한 문제.
-> UI 개선점. 기능의 정상동작 여부를 UI에 출력하는 수단이 alert()인데, 간편하기는 하지만 시각적 측면에서 다소 보기 안 좋을 수 있음. 개선 필요. (모달 창 또는 에러 페이지를 추가하는게 좋을지도 모르겠다.)
-> 에러 처리 보강 필요. 현재 회원 기능 UI의 에러 처리는 문제가 발생했을 때, 에러가 발생했다는 메시지만 띄워주고 있음. 발생한 에러에 대한 상세 정보를 다룰 곳이 필요함. (백엔드에서는 error 변수에 에러 메시지가 저장되도록 하고있으나 파이어베이스에서 반환하는 에러 메시지가 영문이었기에 사용자에게 불편함을 줄 수도 있다고 판단하여 에러 발생 여부만 체크하도록 하였다.)
export const useLogin = () => {
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const { dispatch } = useAuthContext();
const navigate = useNavigate();
const login = (email, password) => {
setError(null);
setIsPending(true);
setPersistence(appAuth, browserLocalPersistence)
.then(() => {
signInWithEmailAndPassword(appAuth, email, password)
.then((userCredential) => {
const user = userCredential.user;
dispatch({ type: 'login', payload: user });
setError(null);
setIsPending(false);
if (!user) {
throw new Error('로그인에 실패했습니다.');
}
alert('방문해주셔서 감사합니다.');
navigate('/main', { replace: true });
})
.catch((error) => {
setError(error.message);
setIsPending(false);
console.log(error.message);
alert('에러가 발생하였습니다.');
window.location.replace('/');
});
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.log(errorCode, errorMessage);
});
};
const anonymousLogin = () => {
setError(null);
setIsPending(true);
setPersistence(appAuth, browserSessionPersistence)
.then(() => {
signInAnonymously(appAuth)
.then(() => {
setError(null);
setIsPending(false);
alert('방문해주셔서 감사합니다.');
navigate('/main', { replace: true });
})
.catch((error) => {
setError(error.message);
setIsPending(false);
console.log(error.message);
alert('에러가 발생하였습니다.');
window.location.replace('/');
});
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.log(errorCode, errorMessage);
});
};
return { error, isPending, login, anonymousLogin };
로그인 기능은 useLogin Hook을 직접 만들어 구현하였다. 다른 곳에서 사용되거나 하지 않기 때문에 재사용성을 위해 굳이 별도로 분리할 필요성은 낮지만, 코드 모듈화에 대한 경험을 쌓을 겸 그리고 코드의 유지보수를 간편하게 하기 위해서 따로 분리하여 구현하였다.
useState를 이용하여 로딩 중, 에러 발생의 상태를 다룰 플래그 변수를 관리하고 있다. error는 오류 메시지를 저장하고, isPending은 로그인 작업이 진행 중인지 여부를 나타낸다.
기능의 동작 과정에서 발생하는 상태 변화 등은 Context API로 관리한다. 정상적으로 회원가입 절차가 마쳐질 경우 useAuthContext Hook에서 dispatch를 가져와서 필요한 플래그 변수와 데이터를 갱신한다.
로그인 기능이 호출되면, 파이어베이스에서 signInWithEmailAndPassword 함수를 사용하여 로그인 작업을 수행한다. 사용자 인증 상태 지속성을 위해 setPersistence 함수를 먼저 호출한 다음 signInWithEmailAndPassword 함수를 호출하게 된다.
기능 동작 중 에러가 발생할 경우 해당 에러 메시지를 setError 함수를 통해 저장하고, isPending 상태를 false로 변경한 뒤 동작을 마친다. 성공 시 환영 메시지를 띄우고, 페이지를 /main으로 이동시키며, 실패 시 실패 메시지를 띄우고 프로젝트의 처음 페이지로 이동시킨다.
평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.
-> 에러 처리 보강 필요. 현재 회원 기능 UI의 에러 처리는 문제가 발생했을 때, 에러가 발생했다는 메시지만 띄워주고 있음. 발생한 에러에 대한 상세 정보를 다룰 곳이 필요함. (백엔드에서는 error 변수에 에러 메시지가 저장되도록 하고있으나 파이어베이스에서 반환하는 에러 메시지가 영문이었기에 사용자에게 불편함을 줄 수도 있다고 판단하여 에러 발생 여부만 체크하도록 하였다.)
import { useState } from 'react'
import { appAuth } from '../configs/firebase'
import { useAuthContext } from './useAuthContext'
import { signOut } from 'firebase/auth';
export const useLogout = () => {
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const { dispatch } = useAuthContext();
const logout = () => {
setError(null);
setIsPending(true);
signOut(appAuth).then(() => {
dispatch({ type: 'logout' });
setError(null);
setIsPending(false);
alert('로그아웃되었습니다. 방문해주셔서 감사합니다.');
}).catch((err) => {
setError(err.message);
setIsPending(false);
console.log(err.message);
});
};
return { error, isPending, logout };
로그아웃은 그다지 복잡한 기능이 아니다. 더구나 파이어베이스에서 기능을 간편하게 제공해주고 있고, 기본적인 구성은 로그인 쪽과 동일하다.
정말 좋은 정보 감사합니다!