
로그인, 회원가입 기능은 모두 모달창 안에서 수행된다.
네비게이션바에서 사람 아이콘을 누르면 로그인 모달창이 화면에 보이게 되고, 로그인 모달창에서 '회원가입' 글자를 누르면 회원가입 모달창이 화면에 보인다.
먼저, firebase의 Authentication을 사용하기 위해서 firebase.js에 auth 를 import해야 한다.
import "firebase/auth";
import firebase from "firebase/app";
const auth = firebase.auth;
export { auth };
그리고 firebase에서는 Authentication을 시작해야 한다.

우리는 Sign-in method를 이메일/비밀번호로 설정하여 사용자의 이메일 주소, 비밀번호만으로 회원가입할 수 있도록 했다.

다음은 모달창을 구현한 부분이다.
<ModalContainer> 안의 내용은 로그인, 회원가입 화면으로 나뉘기 때문에 이를 tab 과 삼항연산자 를 이용하여 모달창 안의 내용이 tab의 상태에 따라 바뀌도록 구현했다.
로그인 화면을 보여줄 때는 tab 을 1로, 회원가입 화면을 보여줄 때는 tab 을 0으로 지정했다.
처음 모달창을 눌렀을 때는 로그인 화면이 보이기 때문에 초기값을 1로 설정했다.

그리고 로그인 화면에서 회원가입 화면으로 넘어가기 위해서는 하단의 회원가입 글자를 클릭하면 된다. 이때, onClick시에 setTab(0) 이 되어 tab의 값이 0이 되기 때문에 모달창의 내용이 회원가입으로 바뀌게 된다.
삼항연산자는 (조건식) ? (true일 때 실행할 문장) : (false일 때 실행할 문장)처럼 사용하는데, 이를 true/false일 때 보여줄 태그들로 응용할 수 있다. 따라서 tab이 0일 때는 : 이후의 태그들, tab이 1일 때는 : 이전의 태그들이 화면에 렌더링 된다.
import { useState } from "react";
const LoginModal = ({ openModalHandler }) => {
const [tab, setTab] = useState(1); // 0 : signup , 1 : login
const [nickname, setNickname] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
(...로그인, 회원가입 함수 생략...)
return (
<>
<GlobalFonts />
<ModalContainer>
<XBtn src={x} onClick={() => openModalHandler(false)} />
{tab ? (
<>
<LoginContent1>이대 맛집은 커몽으로</LoginContent1>
<LoginContent2>COME-ON!</LoginContent2>
<Label1>이메일(아이디)</Label1>
<InputBox
placeholder="comong1886@ewhain.net"
name="email"
type="email"
onChange={(e) => setEmail(e.target.value)}
/>
<Label2>비밀번호</Label2>
<InputBox
placeholder="comong1234"
name="password"
type="password"
onChange={(e) => setPassword(e.target.value)}
/>
<BtnWrap>
<Button1 type="button" onClick={login}>
로그인
</Button1>
</BtnWrap>
<TextWrap>
<Minitext>회원이 아니신가요?</Minitext>
<SignupText
onClick={() => {
setTab(0);
}}
>
회원가입
</SignupText>
</TextWrap>
</>
) : (
<div>
<SignUp>회원가입</SignUp>
<SignUpWrap>
<NickNameLabel>닉네임</NickNameLabel>
<NicknameInputBox
placeholder="김코몽"
name="nickname"
type="nickname"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
/>
</SignUpWrap>
<SignUpWrap>
<EmailLabel>이메일(아이디)</EmailLabel>
<EmailInputBox
placeholder="comong1886@ewhain.net"
name="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</SignUpWrap>
<SignUpWrap>
<PWLabel1>비밀번호</PWLabel1>
<PWInputBox
placeholder="비밀번호 입력하기"
name="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</SignUpWrap>
<SignUpWrap>
<PWLabel2>비밀번호 확인</PWLabel2>
<PWInputBox
placeholder="비밀번호 확인"
name="password"
/>
</SignUpWrap>
<BtnWrap>
<Button1 onClick={signUP}>회원가입</Button1>
</BtnWrap>
</div>
)}
</ModalContainer>
</>
);
};
export default LoginModal;
다음은 회원가입과 로그인을 구현하기 위해 작성한 함수이다.
// 회원가입 시, user DB에 저장
const createUserDocument = async (user, additionalData) => {
if(!user) return;
const userRef = db.doc(`users/${user.uid}`);
const snapshot = await userRef.get();
if(!snapshot.exists){
const {email} = user;
const {nickname} = additionalData;
try{
userRef.set({
nickname,
email,
createAt: new Date()
})
}catch(error){
console.log('Error in creating user', error);
}
}
}
// 회원가입
const signUP = async () => {
try {
const {user} = await auth().createUserWithEmailAndPassword(email, password);
await createUserDocument(user, {nickname});
} catch (error) {
console.log(error.message);
};
// 로그인
const login = async () => {
try {
const user = await auth().signInWithEmailAndPassword(email, password);
} catch (error) {
console.log(error.message);
}
};
회원가입 화면에서 버튼을 누르면 함수 signUP이 실행되고, 로그인 화면에서 버튼을 누르면 함수 login이 실행된다.
회원가입은 createUserWithEmailAndPassword(email, password) 메소드를 사용하면 사용자가 입력한 이메일과 비밀번호로 회원가입이 된다.
로그인은 signInWithEmailAndPassword(email, password) 메소드를 통해 구현이 가능하다.
그리고 createUserDocument를 통해 회원가입 시 닉네임도 입력을 받아 firestore의 “user” 컬렉션에 user id를 문서명으로 하여 user 데이터가 저장되도록 했다.

user 데이터는 사진과 같이 계정 생성 날짜, 이메일, 닉네임이 저장된다.
다음은 로그인 시, 네비게이션바에 닉네임과 로그아웃이 함께 나타나도록 구현하기 위해 작성한 부분이다. 로그인을 하지 않는다면 로그인한 닉네임 확인 및 로그아웃이 불가능하다.
이를 구현하기 위해서는 먼저 로그인을 한 상태인지 검사하는 함수가 필요하다.
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
auth().onAuthStateChanged((currentUser) => {
if (currentUser) {
setIsLoggedIn(true);
closeModal();
} else {
setIsLoggedIn(false);
}
});
}, [isLoggedIn]);
onAuthStateChanged 를 통해 최근에 로그인 한 currentUser 가 있다면 isLoggedIn state를 true 로 바꾸어서 로그인을 한 상태임을 나타낸다.
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [username, setUsername] = useState({});
const [user, setUser] = useState({});
function getData() {
const bucket = db.collection("users"); // "users" 컬렉션
bucket
.doc(user.uid)
.get()
.then((doc) => {
if (doc.exists) {
setUsername(doc.data().nickname); // 닉네임 가져오기
}
});
}
useEffect(() => {
auth().onAuthStateChanged((currentUser) => {
if (currentUser) {
setIsLoggedIn(true);
closeModal(); // 로그인 완료 후, 모달창 닫기
} else {
setIsLoggedIn(false);
}
setUser(currentUser);
});
getData();
}, [isLoggedIn]);
로그인 한 사용자의 닉네임을 가져오기 위해서는 currentUser 을 user state에 넣고, getData() 함수를 실행하면 된다. getData() 함수는 “users” 컬렉션에서 현재 로그인한 사용자의 user.uid 를 문서명으로 하여 그 user의 닉네임을 가져온다.
// 로그아웃
const logout = async () => {
try {
auth().signOut();
} catch (error) {
console.log(error.message);
}
};
<Nav>
<NavBarContainer>
<NavLogo to="/">
<LogoImg src={logo} alt="logo" />
</NavLogo>
<NavUpload to="/upload"># 맛집 등록</NavUpload>
<NavList to="/list" isLoggedIn={isLoggedIn}>
# 맛집 리스트
</NavList>
{isLoggedIn ? (
<>
<UserMSG>{username + " 님"}</UserMSG>
<Logout onClick={logout}>로그아웃</Logout>
</>
) : (
<></>
)}
<NavLogin
onClick={() => {
openModalHandler(true);
}}>
<img src={userimg} alt="login" />
</NavLogin>
</NavBarContainer>
</Nav>
로그아웃 기능은 signOut() 메소드를 통해 쉽게 구현이 가능하다. 그리고 로그인 여부에 따라 네비게이션바에 내용을 다르게 보여주기 위해서 코드를 다음과 같이 작성했다.
삼항연산자를 이용하여 isLoggedIn 이 true 일 때는 사용자의 닉네임과 로그아웃 텍스트를 보여주고, false 일 때는 아무 것도 보여주지 않도록 했다.