프론트엔드로 가는 길/portfolio 제작 여정기 👩🏻‍💻

04. 자살예방웹사이트 - Authentication로 로그인 제작

woody-j 2023. 9. 1. 13:30

로그인 제공 

1) Authentication -> 빌드 -> 로그인 제공업체

2) 로그인 제공업체의 이메일/비밀번호 설정

 

 

자 그럼 이제 코드를 짜봅시다

1. 기본 설정

1) 로그인 기능을 위한 라이브러리 import 

 

2.  회원가입 

import { createUserWithEmailAndPassword, getAuth } from "firebase/auth";
import { getFirestore, doc, setDoc } from "firebase/firestore";
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import styled from "styled-components";
import { Btn, HighlightText, Paragraph } from "../../module/styled/styledFont";

// 이메일 정규식 : 영문자와 숫자만
const regexID = /^[a-zA-Z0-9]{3,16}$/;
// 비밀번호 형식
const regexPass = /^[a-zA-Z가-힣!@#$%^&*()_+|<>?:{}]*.{5,16}$/;
//  닉네임 형식
const regexNickname = /^[가-힣a-zA-Z0-9]{2,10}$/;

const Form = styled.form`
  border-radius: 5px;
`;

const Input = styled.input`
  width: 100%;
  max-width: 400px;
  margin-bottom: 10px;
  padding: 15px 10px;
  border: none;
  border: 1px solid ${({ theme }) => theme.color.mainGray};
  border-radius: 5px;
  outline: none;
  &::placeholder {
    color: ${({ theme }) => theme.color.mainGray};
  }
  &:focus {
    border-color: ${({ theme }) => theme.color.mainBlack}; /* 포커스된 상태일 때의 선 색상 변경 */
  }
`;

const Nicknamebox = styled.div`
  margin-top: 40px;
`;

const Idbox = styled.div``;

const Passwordbox = styled.div`
  margin-bottom: 20px;
`;

const Register = styled.div`
  font-size: 1.2rem;
  cursor: pointer;
`;

const SigninBox = styled(HighlightText)`
  margin-left: 5px;
  text-underline-offset: 4px;
`;

const SignUpBtn = styled(Btn)`
  margin-bottom: 20px;
`;

const ErrorTextBox = styled.div`
  margin-bottom: 10px;
  margin-left: 5px;
  text-align: left;
`;

const ErrorText = styled(Paragraph)`
  color: ${({ theme }) => theme.color.mainRed};
  font-size: 1rem;
`;

const initialIdNotice = {
  alert: false,
  message: "",
};

const SignUpForm = ({ onLogin, onChange }: any) => {
  const [nickName, setNickName] = useState("");
  const [userId, setUserId] = useState("");
  const [password, setPassword] = useState("");
  const [nickNameNotice, setNicknameNotice] = useState(initialIdNotice);
  const [idNotice, setIdNotice] = useState(initialIdNotice);
  const [passNotice, setPassNotice] = useState(initialIdNotice);
  const [error, setError] = useState("");

  const navigate = useNavigate();

  // nickname input 유효성 검사
  const onBlurNicknameHandler = async () => {
    if (nickName === "") {
      setNicknameNotice({ message: "필수항목입니다.", alert: false });
      return;
    }
  };

  const onChangeNicknameHandler = async (e: { target: { value: React.SetStateAction<string> } }) => {
    setNickName(e.target.value);
    const isValidNickname = regexNickname.test(nickName);
    if (!isValidNickname) {
      setNicknameNotice({
        message: "3 ~ 10자의 한글, 영문, 숫자 조합으로 입력해야 합니다.",
        alert: false,
      });
      return;
    }
    setNicknameNotice({
      message: "",
      alert: true,
    });
  };

  // userId input 유효성 검사
  const onBlurIdHandler = () => {
    if (userId === "") {
      setIdNotice({ message: "필수항목입니다.", alert: false });
      return;
    }
  };

  const onChangeIdHandler = (e: { target: { value: React.SetStateAction<string> } }) => {
    setUserId(e.target.value);
    const isValidID = regexID.test(userId);
    if (!isValidID) {
      setIdNotice({
        message: "4 ~ 16자의 영문, 숫자 조합으로 입력해야 합니다.",
        alert: false,
      });
      return;
    }
    setIdNotice({
      message: "",
      alert: true,
    });
  };

  // password input 유효성 검사
  const onBlurPasswordHandler = () => {
    if (password === "") {
      setPassNotice({ message: "필수항목입니다.", alert: false });
      return;
    }
  };

  const onChangePasswordHandler = (e: { target: { value: React.SetStateAction<string> } }) => {
    setPassword(e.target.value);
    const isValidPassword = regexPass.test(password);
    if (!isValidPassword) {
      setPassNotice({
        message: "한글을 제외한 6 ~ 16자의 문자로 입력해야 합니다.",
        alert: false,
      });
      return;
    }
    setPassNotice({
      message: "",
      alert: true,
    });
  };

  const auth = getAuth();
  const db = getFirestore();

  const handleSubmitSignUp = async (
     e: React.FormEvent,
    userId: string,
    password: string,
    nickName: string
  ) => {
    e.preventDefault();

    try {
      const userCredential = await createUserWithEmailAndPassword(auth, `${userId}@myapp.com`, password);
      const user = userCredential.user;

      // 사용자 정보를 Firestore에 저장
      await setDoc(doc(db, "nickName", user.uid), {
        email: `${userId}@myapp.com`,
        nickname: nickName,
      });
      alert("회원가입이 완료되었습니다.");
      navigate("/auth/signIn");
      console.log("회원가입 성공:", user);
      setError("");
      // 회원가입 성공 시에 원하는 동작을 추가해주세요.
    } catch (error) {
      console.error("회원가입 실패:", error.message);
      setError(error.message);
    }
  };

  return (
    <Form
      onSubmit={(e: { preventDefault: () => void }) => handleSubmitSignUp(e, userId, password, nickName)}
    >
      <Nicknamebox>
        <Input
          type="text"
          value={nickName}
          minLength={3}
          maxLength={10}
          onChange={onChangeNicknameHandler}
          onBlur={onBlurNicknameHandler}
          placeholder="닉네임"
        />
        <ErrorTextBox>
          {nickNameNotice.alert ? null : <ErrorText>{nickNameNotice.message}</ErrorText>}
        </ErrorTextBox>
      </Nicknamebox>
      <Idbox>
        <Input
          type="text"
          id="userId"
          value={userId}
          minLength={4}
          maxLength={16}
          onChange={onChangeIdHandler}
          onBlur={onBlurIdHandler}
          placeholder="아이디"
        />
        <ErrorTextBox>{idNotice.alert ? null : <ErrorText>{idNotice.message}</ErrorText>}</ErrorTextBox>
      </Idbox>
      <Passwordbox>
        <Input
          type="password"
          id="password"
          value={password}
          minLength={4}
          maxLength={16}
          onBlur={onBlurPasswordHandler}
          onChange={onChangePasswordHandler}
          placeholder="비밀번호"
        />
        <ErrorTextBox>
          {passNotice.alert ? null : <ErrorText>{passNotice.message}</ErrorText>}
        </ErrorTextBox>
      </Passwordbox>
      <SignUpBtn type="submit">회원가입</SignUpBtn>
      <Register>
        <Link to="/auth/signIn">
          이미 회원이신가요?
          <SigninBox showunderline>로그인</SigninBox>
        </Link>
      </Register>
    </Form>
  );
};

export default SignUpForm;

1) auth 객체를 생성

const auth = getAuth();

Firebase 인증 모듈을 사용하여 객체를 생성합니다.

이 객체를 사용해 사용자의 로그인 및 회원가입을 관리합니다.

2) db 객체를 생성

const db = getFirestore();

Firebase Firestore 데이터베이스 모듈을 사용하여 객체를 생성합니다.

이 객체를 사용해 사용자의 정보를 저장합니다.

 await setDoc(doc(db, "nickName", user.uid), {
        email: `${userId}@myapp.com`,
        nickname: nickName,
      });

: 나는 닉네임과 id,pw 3개를 동시에 보내고 싶었는데 auth인증 모듈은 그게 불가능 했다.

그래서 따로 닉네임도 저장시키기 위해 회원가입할 때 같이 db객체를 생성해서 같이 보내줬다.

 

3) 회원가입 폼의 제출 이벤트 핸들러 함수

const handleSubmitSignUp = 
async (e: React.FormEvent, userId: string, password: string, nickName: string) => {

함수의 매개변수로 이벤트 객체 e, 사용자 아이디 userId, 비밀번호 password, 닉네임 nickName이 전달된다.

 

4) createUserWithEmailAndPassword 함수를 사용하여 회원가입

const userCredential = 
await createUserWithEmailAndPassword(auth, ${userId}@myapp.com, password);

auth 객체를 사용하여 이메일 형식의 아이디와 비밀번호를 전달한다.

이때 ${userId}@myapp.com 형식으로 이메일을 만들어 사용한다. -> 나는 이메일형식말고 id로만 만들고 싶었다.

하지만 id형식만 할 수 없어서 사용자가 id만 작성하면 보내줄 때 @-를 붙여서 보내주도록 했다.

 

3.  로그인

import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import styled from "styled-components";
import { loginEmail, signupEmail } from "../../firebaseConfig";
import { Btn, HighlightText } from "../../module/styled/styledFont";

const Form = styled.form``;

const Input = styled.input`
  width: 100%;
  max-width: 400px;
  margin-bottom: 20px;
  padding: 15px 10px;
  border: none;
  border-radius: 5px;
  border: 1px solid ${({ theme }) => theme.color.mainGray};
  outline: none;
  &::placeholder {
    color: ${({ theme }) => theme.color.mainGray};
  }
  &:focus {
    border-color: ${({ theme }) => theme.color.mainBlack}; /* 포커스된 상태일 때의 선 색상 변경 */
  }
`;

const Idbox = styled.div`
  margin-top: 40px;
`;

const Passwordbox = styled.div``;

const ProcessBox = styled.div`
  max-width: 400px;
  width: 100%;
  margin-bottom: 30px;
  font-size: 1.3rem;
`;

const CheckboxLabel = styled.label`
  display: flex;
  gap: 5px;
  justify-content: center;
  align-items: center;
  cursor: pointer;
`;

const CheckboxInput = styled.input``;

const AutoLoginText = styled.div``;

const Register = styled.div`
  font-size: 1.2rem;
  cursor: pointer;
`;

const SignUpBox = styled(HighlightText)`
  margin-left: 5px;
  text-underline-offset: 4px;
`;

const SignInBtn = styled(Btn)`
  margin-bottom: 20px;
`;

const SignInForm = ({ onLogin, onChange }: any) => {
  const [userId, setUserId] = useState("");
  const [password, setPassword] = useState("");
  const [isChecked, setIsChecked] = useState(false);

  const navigate = useNavigate();

  const handleCheckboxChange = () => {
    const newChecked = !isChecked;
    setIsChecked(newChecked);
  };

  const handleLogin = async (e: { preventDefault: () => void }) => {
    e.preventDefault();
    try {
      const result = await loginEmail(`${userId}@myapp.com`, password);
      const user = result.user;
      console.log(user);
      alert("로그인되었습니다.");
      navigate("/");
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <Form onSubmit={handleLogin}>
      <Idbox>
        <Input
          type="text"
          id="username"
          value={userId}
          onChange={(e: { target: { value: React.SetStateAction<string> } }) =>
            setUserId(e.target.value)
          }
          placeholder="아이디"
        />
      </Idbox>
      <Passwordbox>
        <Input
          type="password"
          id="password"
          value={password}
          onChange={(e: { target: { value: React.SetStateAction<string> } }) =>
            setPassword(e.target.value)
          }
          placeholder="비밀번호"
        />
      </Passwordbox>
      <ProcessBox>
        <CheckboxLabel>
          <CheckboxInput type="checkbox" checked={isChecked} onChange={handleCheckboxChange} />
          <AutoLoginText>자동 로그인</AutoLoginText>
        </CheckboxLabel>
      </ProcessBox>
      <SignInBtn type="submit">로그인</SignInBtn>
      <Register>
        <Link to="/auth/signUp">
          회원이 아니신가요?
          <SignUpBox showunderline>회원가입</SignUpBox>
        </Link>
      </Register>
    </Form>
  );
};

export default SignInForm;