회고/TIL

TIL - 20230209

k1mwnjn 2023. 2. 9. 23:00

 

 

 

React - Firebase Authentication 이메일 회원가입 & 로그인

 

로그인과 회원가입 기능을 프로젝트를 진행하며 처음 맡아 간단하게 정리해본다.

Firebase 는 여러 소셜 로그인도 제공하지만 튜터님 피드백을 따라 기본적으로 이메일 회원가입/로그인을 구현해보자.

프로젝트에 Firebase 패키지 설치 후 기본 세팅까진 완료된 상태다.

 

좌측 메뉴에서 Authentication -> Sign-in-method 로 들어가 로그인 제공업체에 이메일/비밀번호를 추가해준다.

 

// login page

import {
  browserSessionPersistence,
  setPersistence,
  signInWithEmailAndPassword,
} from "firebase/auth";
import { useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import AuthForm from "../../components/Auth/AuthForm";
import { authService } from "../../config/firebase";

const LoginPage = () => {
  const navigate = useNavigate();
  const { state } = useLocation();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const emailRef = useRef(null);
  const passwordRef = useRef(null);

  // 이메일 입력
  const changeEmail = (event) => {
    setEmail(event.target.value);
  };

  // 비밀번호 입력
  const changePassword = (event) => {
    setPassword(event.target.value);
  };

  // 이메일, 비밀번호 유효성 검사
  const checkValidation = () => {
    const emailRegex = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/g;
    const passwordRegex =
      /^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$/;
    const checkEmailValidation = email.match(emailRegex);
    const checkPasswordValidation = password.match(passwordRegex);

    if (!email || !checkEmailValidation) {
      if (!email) {
        alert("이메일을 입력해주세요.");
        emailRef?.current?.focus();
        return false;
      } else {
        alert("이메일 형식을 올바르게 입력해주세요.");
        emailRef?.current?.focus();
        return false;
      }
    }

    if (!password || !checkPasswordValidation) {
      if (!password) {
        alert("비밀번호를 입력해주세요.");
        passwordRef?.current?.focus();
        return false;
      } else {
        alert(
          "비밀번호는 대소문자, 특수문자를 포함하여 8자리 이상이어야 합니다."
        );
        passwordRef?.current?.focus();
        setPassword("");
        return false;
      }
    }
    return true;
  };

  // 로그인
  const submitLogin = () => {
    // 이메일, 비밀번호 유효성 검사 확인
    if (!checkValidation()) return;

    // setPersistence => 세션스토리지에 유저 정보 저장
    setPersistence(authService, browserSessionPersistence)
      .then(() => signInWithEmailAndPassword(authService, email, password))
      .then(() => {
        alert("환영합니다!");
        setEmail("");
        setPassword("");

        if (state) {
          navigate(state);
        } else {
          navigate("/", { replace: true });
        }
      })
      .catch((err) => {
        if (err.message.includes("user-not-found")) {
          alert("가입 정보가 없습니다. 회원가입을 먼저 진행해 주세요.");
          // navigate("/signup", { state });
          emailRef?.current?.focus();
          setEmail("");
          setPassword("");
        }

        if (err.message.includes("wrong-password")) {
          alert("잘못된 비밀번호 입니다.");
          passwordRef?.current?.focus();
          setPassword("");
        }
      });
  };
  // const socialBtn = [
  //   { title: "카카오", img: require("../../assets/kakaotalk.png") },
  //   { title: "네이버", img: require("../../assets/naver.png") },
  //   { title: "구글", img: require("../../assets/google.png") },
  //   { title: "페이스북", img: require("../../assets/facebook.png") },
  //   { title: "애플", img: require("../../assets/apple.png") },
  // ];

  return (
    <AuthForm
      title="예·적금이 필요한 순간, 목돈"
      text="아직 회원이 아니신가요?"
      linkText="회원가입하기"
      email={email}
      changeEmail={changeEmail}
      emailRef={emailRef}
      password={password}
      changePassword={changePassword}
      passwordRef={passwordRef}
      // socialBtn={socialBtn}
      submitLogin={submitLogin}
    />
  );
};

export default LoginPage;

 

// signup page

import {
  browserSessionPersistence,
  createUserWithEmailAndPassword,
  setPersistence,
} from "firebase/auth";
import React, { useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import AuthForm from "../../components/Auth/AuthForm";
import { authService } from "../../config/firebase";

const SignUpPage = () => {
  const navigate = useNavigate();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");
  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const confirmPasswordRef = useRef(null);

  // 이메일 입력
  const changeEmail = (event) => {
    setEmail(event.target.value);
  };

  // 비밀번호 입력
  const changePassword = (event) => {
    setPassword(event.target.value);
  };

  // 비밀번호 재입력
  const changeConfirmPassword = (event) => {
    setConfirmPassword(event.target.value);
  };

  // 이메일, 비밀번호 유효성 검사
  const checkValidation = () => {
    const emailRegex = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/g;
    const passwordRegex =
      /^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$/;
    const checkEmailValidation = email.match(emailRegex);
    const checkPasswordValidation = password.match(passwordRegex);

    if (!email || !checkEmailValidation) {
      if (!email) {
        alert("이메일을 입력해주세요.");
        emailRef?.current?.focus();
        return false;
      } else {
        alert("이메일 형식을 올바르게 입력해주세요.");
        emailRef?.current?.focus();
        return false;
      }
    }

    if (!password || !checkPasswordValidation) {
      if (!password) {
        alert("비밀번호를 입력해주세요.");
        passwordRef?.current?.focus();
        return false;
      } else {
        alert(
          "비밀번호는 대소문자, 특수문자를 포함하여 8자리 이상이어야 합니다."
        );
        passwordRef?.current?.focus();
        setPassword("");
        return false;
      }
    }
    return true;
  };

  // 비밀번호 일치 여부
  const checkValidationForSignUp = () => {
    if (!confirmPassword) {
      alert("비밀번호를 다시 한번 더 입력해주세요.");
      return false;
    }
    if (password !== confirmPassword) {
      alert("비밀번호가 일치하지 않습니다.");
      confirmPasswordRef?.current?.focus();
      // setPassword("");
      setConfirmPassword("");
      return false;
    }
    return true;
  };

  // 회원가입
  const submitSignUp = () => {
    // 이메일, 비밀번호 유효성 검사 확인
    if (!checkValidation()) return;

    // 비밀번호 일치여부 확인
    if (!checkValidationForSignUp()) return;

    // setPersistence => 세션스토리지에 유저 정보 저장
    setPersistence(authService, browserSessionPersistence)
      .then(() => createUserWithEmailAndPassword(authService, email, password))
      .then(() => {
        alert("회원가입이 완료 되었습니다.");
        setEmail("");
        setPassword("");
        setConfirmPassword("");
        navigate("/");
      })
      .catch((err) => {
        if (err.message.includes("already-in-use")) {
          alert("이미 가입된 계정입니다.");
          setEmail("");
          setPassword("");
          setConfirmPassword("");
          navigate("/login");
        }
      });
  };

  return (
    <AuthForm
      title="회원 가입 정보 입력"
      text="회원이신가요?"
      linkText="로그인하기"
      email={email}
      changeEmail={changeEmail}
      emailRef={emailRef}
      password={password}
      changePassword={changePassword}
      passwordRef={passwordRef}
      confirmPassword={confirmPassword}
      changeConfirmPassword={changeConfirmPassword}
      confirmPasswordRef={confirmPasswordRef}
      submitSignUp={submitSignUp}
    />
  );
};

export default SignUpPage;

 

// auth form

import React from "react";
import { Link } from "react-router-dom";
import { GoogleLogin } from "./GoogleLogin";
import { KakaoLogin } from "./KakaoLogin";
import { NaverLogin } from "./NaverLogin";
import {
  AuthBackground,
  AuthButton,
  AuthInput,
  AuthInputWrapper,
  AuthLabel,
  AuthLogo,
  AuthLogoImg,
  AuthText,
  LinkText,
  AuthTitle,
  AuthWrapper,
  DefaultLoginForm,
  SocialLoginForm,
  SocialLoginTitle,
  SocialLoginList,
} from "./style";

const AuthForm = ({
  title,
  text,
  linkText,
  email,
  changeEmail,
  emailRef,
  password,
  changePassword,
  passwordRef,
  confirmPassword,
  changeConfirmPassword,
  confirmPasswordRef,
  submitSignUp,
  submitLogin,
}) => {
  const signUp = title === "회원 가입 정보 입력";

  return (
    <AuthBackground>
      <AuthWrapper>
        <AuthLogo>
          <Link to="/">
            <AuthLogoImg src={require("../../assets/star.png")} />
          </Link>
        </AuthLogo>
        <AuthTitle>{title}</AuthTitle>
        <AuthText>
          {text}
          <Link to={`${signUp ? "/login" : "/signup"}`}>
            <LinkText>{linkText}</LinkText>
          </Link>
        </AuthText>
        <DefaultLoginForm>
          <AuthInputWrapper>
            <AuthLabel>이메일</AuthLabel>
            <AuthInput
              id="email"
              type="email"
              placeholder="example.gmail.com"
              value={email}
              onChange={changeEmail}
              ref={emailRef}
            />
            <AuthLabel>비밀번호</AuthLabel>
            <AuthInput
              id="password"
              type="password"
              placeholder="비밀번호 입력"
              value={password}
              onChange={changePassword}
              ref={passwordRef}
            />
            {signUp ? (
              <>
                <AuthLabel>비밀번호 재입력</AuthLabel>
                <AuthInput
                  id="confirm-password"
                  type="password"
                  placeholder="비밀번호 재입력"
                  value={confirmPassword}
                  onChange={changeConfirmPassword}
                  ref={confirmPasswordRef}
                />
              </>
            ) : (
              ""
            )}
          </AuthInputWrapper>

          {signUp ? (
            <AuthButton onClick={submitSignUp}>회원가입</AuthButton>
          ) : (
            <AuthButton onClick={submitLogin}>로그인</AuthButton>
          )}
        </DefaultLoginForm>
        {!signUp && (
          <SocialLoginForm>
            <SocialLoginTitle>또는</SocialLoginTitle>
            <SocialLoginList>
              <KakaoLogin />
              <NaverLogin />
              <GoogleLogin />
            </SocialLoginList>
          </SocialLoginForm>
        )}
      </AuthWrapper>
    </AuthBackground>
  );
};

export default AuthForm;


로그인과 회원가입 페이지를 먼저 만들고 공통되는 부분은 어스폼에 리팩토링했지만 아직 가야할 길이 멀다.