[개인 포트폴리오] [트위터 클론 SNS Deli] 분석 Part. 9 'create-account.tsx' & 'login.tsx'

2024. 7. 8. 17:08웹개발 포트폴리오

728x90
반응형

create-account.tsx

import { createUserWithEmailAndPassword, updateProfile } from "firebase/auth";
import logo from "/logo.svg"
import { auth } from "../firebase";
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import {
    Error,
} from "../component/auth-components";
import { FirebaseError } from "firebase/app";
import GithubButton from "../component/gitdub-btn";
import { styled } from "styled-components";
import Footer from "../component/Footer";
import { media } from "../media-query-file";

// 에러 메시지 처리 객체
const errorMessages = {
    "auth/email-already-in-use": "이미 사용 중인 이메일입니다.",
    "auth/invalid-email": "유효하지 않은 이메일 주소입니다.",
    "auth/operation-not-allowed": "이 작업은 허용되지 않습니다.",
    "auth/weak-password": "비밀번호가 너무 약합니다.",
    "default": "계정 생성 중 오류가 발생했습니다. 다시 시도해 주세요."
};

// 스타일 컴포넌트 - Switcher 스타일 정의
const Switcher = styled.p`
  min-width: 0;
  font-size: 1rem;
  margin-top: 10px;
  a {
    color: #9d7ff5;
    text-decoration: none;
    &:hover {
      text-decoration: underline;
    }
  }
`;

// 스타일 컴포넌트 - 로고 이미지 스타일 정의
const Logo = styled.img`
margin-top: 20px;
width: 400px;
height: auto;
`;

// 스타일 컴포넌트 - Input 필드 스타일 정의
const Input = styled.input`
    padding: 10px 20px;
    border-radius: 50px;
    border-color: #9D80F5;
    font-size: 16px;
    font-family: pretendard-regular;
    width: 600px !important;
    align-items: center;
    justify-content: center !important;
    &[type="submit"] {
    background-color: #9d7ff5;
    color: white;
    cursor: pointer;
    &:hover,
    &:active {
      opacity: 0.9;
    }
  }
    @media (max-width: 1200px) {
    width: 100% !important;
    }

    @media (max-width: 768px) {
    width: 90% !important;
    }

    @media (max-width: 480px)
    width: 90% !important;
    }
`;

// 스타일 컴포넌트 - 전체 래퍼 스타일 정의
const Wrapper = styled.div`
  margin: 0 auto;
  padding: 0 20px;
  max-width: 1200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-wrap: wrap;
  ${media.mobile(`
    body {
      padding: 0 10px !important;
    }
  `)}

  ${media.tablet(`
    body {
      padding: 0 30px !important;
    }
  `)}

  ${media.desktop(`
    body {
      padding: 0 50px !important;
    }
  `)}
`;

// 스타일 컴포넌트 - 폼 스타일 정의
const Form = styled.form`
    margin-top: 30px;
    margin-bottom: 10px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    align-items: center;
    justify-content: center !important;
    width: 1000px;
    @media (max-width: 1200px) {
    width: 100% !important;
    }

    @media (max-width: 768px) {
    width: 90% !important;
    }

    @media (max-width: 480px)
    width: 80% !important;
    }
`;

// CreateAccount 컴포넌트 정의
export default function CreateAccount() {
    const navigate = useNavigate(); // 페이지 이동을 위한 네비게이트 훅
    const [isLoading, setLoading] = useState(false); // 로딩 상태 관리
    const [name, setName] = useState(""); // 사용자 이름 상태 관리
    const [email, setEmail] = useState(""); // 이메일 상태 관리
    const [password, setPassword] = useState(""); // 비밀번호 상태 관리
    const [error, setError] = useState(""); // 에러 메시지 상태 관리
    
    // Input 필드 값이 변경될 때 호출되는 함수
    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        if (name === "name") setName(value);
        else if (name === "email") setEmail(value);
        else if (name === "password") setPassword(value);
    };

    // 폼 제출 시 호출되는 함수
    const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        setError("");
        if (!name || !email || !password) return; // 모든 필드가 채워지지 않으면 종료
        try {
            setLoading(true); // 로딩 상태 설정
            const credentials = await createUserWithEmailAndPassword(auth, email, password); // Firebase를 통한 계정 생성
            await updateProfile(credentials.user, { displayName: name }); // 사용자 프로필 업데이트
            navigate("/"); // 메인 페이지로 이동
            //  Firebase 인증 과정에서 발생할 수 있는 오류를 처리하고, 해당 오류에 맞는 에러 메시지를 설정하는 부분입니다. 여기서 e는 예외 객체를 나타내며, FirebaseError 클래스의 인스턴스인 경우 특정 오류 코드에 맞는 메시지를 설정
        } catch (e) {
            if (e instanceof FirebaseError) {
                setError(errorMessages[e.code] || errorMessages["default"]); // 에러 메시지 설정
            }
        } finally {
            setLoading(false); // 로딩 상태 해제
        }
    };

    // 컴포넌트 렌더링
    return (
        <Wrapper>
            <Logo src={logo} alt="Logo" />
            <Form onSubmit={onSubmit}>
                <Input
                    onChange={onChange}
                    name="name"
                    value={name}
                    placeholder="이름"
                    type="text"
                    required
                />
                <Input
                    onChange={onChange}
                    name="email"
                    value={email}
                    placeholder="이메일"
                    type="email"
                    required
                />
                <Input
                    onChange={onChange}
                    name="password"
                    value={password}
                    placeholder="패스워드"
                    type="password"
                    required
                />
                <Input
                    type="submit"
                    value={isLoading ? "로딩중..." : "계정 만들기"}
                />
            </Form>
            {error && <Error>{error}</Error>} {/* 에러 메시지 표시 */}
            <Switcher>
                <Link to="/login"> 이미 계정이 있나요? &rarr; </Link>
            </Switcher>
            <GithubButton />
            <Footer />
        </Wrapper>
    );
}

 

이 코드는 Firebase를 사용하여 사용자 계정을 생성하는 React 컴포넌트입니다. 사용자가 이름, 이메일, 비밀번호를 입력하여 계정을 생성할 수 있으며, 각 필드의 상태를 관리합니다. 에러 메시지를 구체적으로 표시하고, 로딩 상태를 처리합니다. 계정 생성이 완료되면 사용자는 메인 페이지로 이동합니다.

onChange 함수는 React 컴포넌트에서 입력 필드의 변경 이벤트를 처리하는 함수입니다. 이 함수는 입력 필드가 변경될 때마다 호출되어 입력된 값을 상태(state)로 업데이트합니다.

  1. 입력 이벤트 객체(e) 처리:
    • onChange 함수는 React.ChangeEvent<HTMLInputElement> 타입의 이벤트 객체를 인자로 받습니다. 이 이벤트 객체는 입력 필드에서 발생한 이벤트에 대한 정보를 담고 있습니다.
  2. 구조 분해 할당:
    • const { name, value } = e.target;를 사용하여 이벤트 객체의 target 속성에서 name과 value를 추출합니다.
    • name: 입력 필드의 name 속성 값입니다.
    • value: 입력 필드에 입력된 현재 값입니다.

Q. e.target?

 

  • e.target은 이벤트가 발생한 DOM 요소를 참조합니다.
  • 이를 통해 이벤트가 발생한 요소의 속성 및 값을 접근할 수 있습니다.
  • e.target을 사용하면 여러 입력 필드에서 발생하는 이벤트를 하나의 이벤트 핸들러에서 처리할 수 있습니다.

 

 

  1. 조건부 상태 업데이트:
    • if (name === "name") setName(value);: 입력 필드의 name 속성이 "name"인 경우 setName 함수를 호출하여 name 상태를 업데이트합니다.
    • else if (name === "email") setEmail(value);: 입력 필드의 name 속성이 "email"인 경우 setEmail 함수를 호출하여 email 상태를 업데이트합니다.
    • else if (name === "password") setPassword(value);: 입력 필드의 name 속성이 "password"인 경우 setPassword 함수를 호출하여 password 상태를 업데이트합니다.

 

- useState 상태관리에 대해 더 알아보자!

 

 

  • useState 훅: 함수형 컴포넌트에서 상태를 관리할 수 있도록 합니다.
  • 상태 값과 업데이트 함수: useState는 현재 상태 값을 나타내는 변수와 상태를 업데이트하는 함수를 반환합니다.
  • 상태 초기화: useState의 인자는 초기 상태 값을 설정합니다.
  • 상태 업데이트: 상태를 업데이트하면 React는 해당 컴포넌트를 다시 렌더링하여 UI를 업데이트합니다.

 

  • FirebaseError 클래스:
    • Firebase SDK에서 발생하는 오류는 FirebaseError 클래스의 인스턴스입니다. 이 클래스는 오류 코드와 메시지를 포함합니다.
    • e instanceof FirebaseError: 예외 객체 e가 FirebaseError 클래스의 인스턴스인지 확인합니다. 만약 그렇다면 Firebase 관련 오류임을 나타냅니다.
  • errorMessages 객체:
    • errorMessages는 오류 코드에 대한 사용자 친화적인 메시지를 매핑하는 객체입니다. 이 객체는 오류 코드를 키로 사용하여 해당하는 메시지를 값으로 저장합니다.
  • setError 함수:
    • setError는 상태 업데이트 함수로, 현재 컴포넌트의 에러 상태를 설정합니다. 이 함수는 주로 React의 useState 훅을 통해 생성됩니다.

어려웠던 부분 상세 설명

 

 

if (e instanceof FirebaseError) {
  setError(errorMessages[e.code] || errorMessages["default"]); // 에러 메시지 설정
}
  1. if (e instanceof FirebaseError):
    • 예외 객체 e가 FirebaseError 클래스의 인스턴스인지 확인합니다. 즉, Firebase 관련 오류인지를 체크합니다.
  2. errorMessages[e.code]:
    • e.code는 발생한 오류의 코드입니다. errorMessages[e.code]는 이 코드에 대응하는 사용자 친화적인 에러 메시지를 반환합니다.
  3. || errorMessages["default"]:
    • 만약 e.code에 해당하는 에러 메시지가 errorMessages 객체에 없으면 기본 에러 메시지(errorMessages["default"])를 반환합니다. 이는 예상하지 못한 오류에 대한 일반적인 메시지를 제공하기 위해 사용됩니다.
  4. setError 함수 호출:
    • setError 함수를 호출하여 에러 메시지를 상태로 설정합니다. 이 상태는 보통 컴포넌트의 렌더링에 반영되어 사용자에게 표시됩니다.

 

login.tsx

// Firebase와 관련된 기능 및 필요한 모듈들을 임포트
import { auth } from "../firebase";
import logo from "/logo.svg"; // 로고 이미지 파일 경로
import { useState } from "react"; // 리액트의 useState 훅
import { Link, useNavigate } from "react-router-dom"; // 리액트 라우터 훅
import { FirebaseError } from "firebase/app"; // 파이어베이스 에러 처리 모듈
import { signInWithEmailAndPassword } from "firebase/auth"; // 파이어베이스 인증 모듈
import {
    Error
} from "../component/auth-components"; // 에러 메시지 컴포넌트
import GithubButton from "../component/gitdub-btn"; // 깃허브 버튼 컴포넌트
import { styled } from "styled-components"; // 스타일드 컴포넌트
import Footer from "../component/Footer"; // 푸터 컴포넌트
import { media } from "../media-query-file"; // 미디어 쿼리 파일

// 스타일 컴포넌트 정의
const Switcher = styled.p`
  min-width: 0; /* 요소가 넘치지 않도록 설정 */
  font-size: 1rem;
  margin-top: 10px;
  a {
    color: #9d7ff5;
    text-decoration: none;
    &:hover {
      text-decoration: underline;
    }
  }
`;

const Logo = styled.img`
  width: 400px;
  height: auto;
  display: flex;
  justify-content: center;
  margin: 20px 0;
  min-width: 0; /* 요소가 넘치지 않도록 설정 */
`;

const Wrapper = styled.div`
  margin: 0 auto;
  max-width: 1200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-wrap: wrap;
  ${media.mobile(`
    body {
      padding: 0 10px !important;
    }
  `)}

  ${media.tablet(`
    body {
      padding: 0 30px !important;
    }
  `)}

  ${media.desktop(`
    body {
      padding: 0 50px !important;
    }
  `)}
`;

const Input = styled.input`
  padding: 10px 20px;
  border-radius: 50px;
  border-color: #9D80F5;
  font-size: 16px;
  font-family: pretendard-regular;
  width: 600px !important;
  align-items: center;
  justify-content: center !important;
  &[type="submit"] {
    background-color: #9D7FF5;
    color: white;
    cursor: pointer;
    &:hover,
    &:active {
      opacity: 0.9;
    }
  }
    
  @media (max-width: 1200px) { /* 데스크탑 */
    width: 100% !important;
  }

  @media (max-width: 768px) { /* 태블릿 */
    width: 90% !important;
  }

  @media (max-width: 480px)
    width: 90% !important;
  }
`;

const Form = styled.form`
  margin-top: 30px;
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
  justify-content: center !important;
  width: 1000px;
  @media (max-width: 1200px) { /* 데스크탑 */
    width: 100% !important;
  }

  @media (max-width: 768px) { /* 태블릿 */
    width: 90% !important;
  }

  @media (max-width: 480px)
    width: 80% !important;
  }
`;

// CreateAccount 컴포넌트 정의
export default function CreateAccount() {
    const navigate = useNavigate(); // 페이지 이동을 위한 네비게이트 훅
    const [isLoading, setLoading] = useState(false); // 로딩 상태 관리
    const [email, setEmail] = useState(""); // 이메일 상태 관리
    const [password, setPassword] = useState(""); // 비밀번호 상태 관리
    const [error, setError] = useState(""); // 에러 메시지 상태 관리
    
    // Input 필드 값이 변경될 때 호출되는 함수
    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        if (name === "email") setEmail(value); // 이메일 상태 업데이트
        else if (name === "password") setPassword(value); // 비밀번호 상태 업데이트
    };

    // 폼 제출 시 호출되는 함수
    const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault(); // 폼 기본 동작 방지
        setError(""); // 에러 메시지 초기화
        if (email === "" || password === "") {
            setError("이메일과 패스워드를 입력해주세요."); // 이메일 또는 패스워드가 비어있을 때 에러 설정
            return;
        }
        try {
            setLoading(true); // 로딩 상태 설정
            await signInWithEmailAndPassword(auth, email, password); // Firebase를 통한 로그인
            navigate("/"); // 메인 페이지로 이동
        } catch (e) {
            if (e instanceof FirebaseError) {
                // Firebase 에러 처리
                switch (e.code) {
                    case 'auth/user-not-found':
                        setError("존재하지 않는 계정입니다.");
                        break;
                    case 'auth/wrong-password':
                        setError("비밀번호가 틀렸습니다.");
                        break;
                    case 'auth/invalid-email':
                        setError("유효하지 않은 이메일입니다.");
                        break;
                    default:
                        setError("로그인 중 오류가 발생했습니다. 다시 시도해 주세요.");
                        break;
                }
            }
        } finally {
            setLoading(false); // 로딩 상태 해제
        }
    };

    // 컴포넌트 렌더링
    return (
        <Wrapper>
            <Logo src={logo} alt="Logo" />
            <Form onSubmit={onSubmit}>
                <Input
                    onChange={onChange}
                    name="email"
                    value={email}
                    placeholder="이메일"
                    type="email"
                    required
                />
                <Input
                    onChange={onChange}
                    name="password"
                    value={password}
                    placeholder="패스워드"
                    type="password"
                    required
                />
                <Input
                    type="submit"
                    value={isLoading ? "로딩중..." : "로그인"}
                />
            </Form>
            {error !== "" ? <Error>{error}</Error> : null}
            <Switcher>
                아직 계정이 없나요? <Link to="/CreateAccount"> 계정 생성 &rarr; </Link>
            </Switcher>
            <GithubButton />
            <Footer />
        </Wrapper>
    );
}

 

이 코드는 사용자가 이메일과 비밀번호를 사용하여 로그인할 수 있는 페이지를 구현한 것입니다. Firebase를 사용하여 인증 기능을 제공하며, 사용자가 입력한 정보를 검증하고, 로그인 성공 시 메인 페이지로 리다이렉트하는 기능을 포함하고 있습니다.

  • Imports 및 Firebase 설정
    • Firebase 인증 객체, React, styled-components, React Router DOM 등을 불러와서 사용합니다.
  • 스타일 컴포넌트 정의
    • Switcher, Logo, Wrapper, Input, Form 등 스타일 컴포넌트를 정의합니다. 이 컴포넌트들은 각각 CSS 속성을 적용하여 스타일을 지정합니다.
  • CreateAccount 컴포넌트 정의
    • useNavigate 훅을 사용하여 페이지 이동 기능을 추가합니다.
    • useState 훅을 사용하여 로딩 상태, 이메일, 패스워드, 에러 메시지를 관리합니다.
    • onChange 함수는 Input 필드 값이 변경될 때 호출되며, 상태를 업데이트합니다.
    • onSubmit 함수는 폼 제출 시 호출되며, Firebase 이메일 로그인 함수를 사용하여 로그인을 시도합니다. 성공 시 메인 페이지로 이동하고, 오류 발생 시 적절한 에러 메시지를 설정합니다.
  • 렌더링
    • Wrapper, Logo, Form, Input, Switcher, GithubButton, Footer 컴포넌트들을 사용하여 화면을 구성합니다.
    • Form 컴포넌트는 제출 시 onSubmit 함수를 호출하며, Input 필드들은 onChange 함수를 통해 상태를 업데이트합니다.
    • 에러 메시지는 error 상태가 비어있지 않을 때만 표시됩니다.
    • Switcher 컴포넌트는 계정 생성 페이지로 이동할 수 있는 링크를 제공합니다.
    • GithubButton 컴포넌트는 Github 로그인 버튼을 표시합니다.
    • Footer 컴포넌트는 페이지 하단의 푸터를 표시합니다.
728x90
반응형