트위터(React, TypeScript, Firebase, Vite)

10. Authentication - Log In

본시모니 2025. 4. 15. 10:59

1. 최종 로그인 & 회원가입 구현

 

 

 

 

 

auth-components.ts

import { styled } from "styled-components";

export const Wrapper = styled.div`
  height: 100vh;                          /* 화면 전체 높이를 기준으로 중앙 정렬 */ 
  display: flex;
  justify-content: center;                /* 수평 중앙 */
  align-items: center;                    /* 수직 중앙 */
  flex-direction: column;
  width: 420px;
  margin: 0 auto;                         /* 화면 가운데로 */
  padding: 50px 0px;

`;

export const Title = styled.h1`
  font-size: 42px;
`;

export const Form = styled.form`
  margin-top: 50px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  margin-bottom: 10px;
`;

export const Input = styled.input`
  padding: 10px 20px;
  border-radius: 50px;
  border: none;
  width: 100%;
  font-size: 16px;
  &[type="submit"] {
    cursor: pointer;
    &:hover {
      opacity: 0.8;
    }
  }
`;

export const Error = styled.span`
  font-weight: 600;
  color: tomato;
`;

export const Switcher = styled.span`
  margin-top: 20px;
   a{
    color : #1d9bf0;
  }
`;

 

login.tsx

import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { FirebaseError } from "firebase/app";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "./firebase";
import { Error, Form, Input, Switcher, Title, Wrapper } from "../components/auth-components";

export default function Login() {
  const navigate = useNavigate();
  const [isLoading, setLoading] = useState(false);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const {
      target: { name, value },
    } = e;
    if (name === "email") {
      setEmail(value);
    } else if (name === "password") {
      setPassword(value);
    }
  };
  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setError("");
    if(isLoading === true || email === "" || password === ""){
      return;
    }
    try {
      setLoading(true);

      await signInWithEmailAndPassword(auth, email, password);

      navigate("/");                                                            
    } catch (e) {
      if(e instanceof FirebaseError){
        setError(e.message);
      }
    } finally {
      setLoading(false);
    }
  };
  return (
    <Wrapper>
      <Title>Log into 𝕏</Title>
      <Form onSubmit={onSubmit}>
        <Input
          onChange={onChange}
          name="email"
          value={email}
          placeholder="Email"
          type="email"
          required
        />
        <Input
          onChange={onChange}
          value={password}
          name="password"
          placeholder="Password"
          type="password"
          required
        />
        <Input
          type="submit"
          value={isLoading ? "Loading..." : "Log in"}
        />
      </Form>
      {error !== "" ? <Error>{error}</Error> : null}
      <Switcher>
        Don't have an accrount? <Link to="/create-account">Create one &rarr;</Link>
      </Switcher>
    </Wrapper>
  );
}

 

create-account.tsx

 

import { createUserWithEmailAndPassword, updateProfile } from "firebase/auth";
import { useState } from "react";
import { auth } from "./firebase";
import { Link, useNavigate } from "react-router-dom";
import { FirebaseError } from "firebase/app";
import { Form, Input, Switcher, Title, Wrapper } from "../components/auth-components";

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("");
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const {
      target: { name, value },
    } = e;
    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(isLoading === true ||name === "" || email === "" || password === ""){
      return;
    }
    try {
      setLoading(true);
      const credentials = await createUserWithEmailAndPassword(auth, email, password)
                                                              // createUserWithEmailAndPassword는 Firebase Auth SDK에서 제공하는 함수로 인자에 auth 인스턴스가 필요하다.
                                                              // firebase.ts 파일 내 auth 인스턴스가 생성되어 있음!
      await updateProfile(credentials.user, {displayName : name});  
      navigate("/");                                                            
    } catch (e) {
      if(e instanceof FirebaseError){
        setError(e.message);
      }
    } finally {
      setLoading(false);
    }
  };
  return (
    <Wrapper>
      <Title>Join 𝕏</Title>
      <Form onSubmit={onSubmit}>
        <Input
          onChange={onChange}
          name="name"
          value={name}
          placeholder="Name"
          type="text"
          required
        />
        <Input
          onChange={onChange}
          name="email"
          value={email}
          placeholder="Email"
          type="email"
          required
        />
        <Input
          onChange={onChange}
          value={password}
          name="password"
          placeholder="Password"
          type="password"
          required
        />
        <Input
          type="submit"
          value={isLoading ? "Loading..." : "Create Account"}
        />
      </Form>
      {error !== "" ? <Error>{error}</Error> : null}
      <Switcher>
        Already have an accrount?{""} <Link to="/login">Log in &rarr;</Link>
      </Switcher>
    </Wrapper>
  );
}

 

 

 

 

 

 

 

 

 

 

nwitter-reloaded.z01
19.53MB
nwitter-reloaded.z02
19.53MB
nwitter-reloaded.z03
19.53MB
nwitter-reloaded.z04
19.53MB
nwitter-reloaded.z05
19.53MB
nwitter-reloaded.zip
14.59MB

 

 

 

 

 

 

 

 

2. 최초 로그인 구현




 

 

 

login.tsx

import { useState } from "react";
import { styled } from "styled-components";
import { useNavigate } from "react-router-dom";
import { FirebaseError } from "firebase/app";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "./firebase";

const Wrapper = styled.div`
  height: 100vh;                          /* 화면 전체 높이를 기준으로 중앙 정렬 */ 
  display: flex;
  justify-content: center;                /* 수평 중앙 */
  align-items: center;                    /* 수직 중앙 */
  flex-direction: column;
  width: 420px;
  margin: 0 auto;                         /* 화면 가운데로 */
  padding: 50px 0px;

`;

const Title = styled.h1`
  font-size: 42px;
`;

const Form = styled.form`
  margin-top: 50px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  margin-bottom: 10px;
`;

const Input = styled.input`
  padding: 10px 20px;
  border-radius: 50px;
  border: none;
  width: 100%;
  font-size: 16px;
  &[type="submit"] {
    cursor: pointer;
    &:hover {
      opacity: 0.8;
    }
  }
`;

const Error = styled.span`
  font-weight: 600;
  color: tomato;
`;

export default function Login() {
  const navigate = useNavigate();
  const [isLoading, setLoading] = useState(false);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const {
      target: { name, value },
    } = e;
    if (name === "email") {
      setEmail(value);
    } else if (name === "password") {
      setPassword(value);
    }
  };
  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setError("");
    if(isLoading === true || email === "" || password === ""){
      return;
    }
    try {
      setLoading(true);

      await signInWithEmailAndPassword(auth, email, password);

      navigate("/");                                                            
    } catch (e) {
      if(e instanceof FirebaseError){
        setError(e.message);
      }
    } finally {
      setLoading(false);
    }
  };
  return (
    <Wrapper>
      <Title>Log into 𝕏</Title>
      <Form onSubmit={onSubmit}>
        <Input
          onChange={onChange}
          name="email"
          value={email}
          placeholder="Email"
          type="email"
          required
        />
        <Input
          onChange={onChange}
          value={password}
          name="password"
          placeholder="Password"
          type="password"
          required
        />
        <Input
          type="submit"
          value={isLoading ? "Loading..." : "Log in"}
        />
      </Form>
      {error !== "" ? <Error>{error}</Error> : null}
    </Wrapper>
  );
}

 

 

 

 

 

 

 

 

 

 

 

 

3. 최초 회원가입 시 중복된 이메일 확인

 

 

 

 

 

create-account.tsx

import { createUserWithEmailAndPassword, updateProfile } from "firebase/auth";
import { useState } from "react";
import { styled } from "styled-components";
import { auth } from "./firebase";
import { useNavigate } from "react-router-dom";
import { FirebaseError } from "firebase/app";

const Wrapper = styled.div`
  height: 100vh;                          /* 화면 전체 높이를 기준으로 중앙 정렬 */ 
  display: flex;
  justify-content: center;                /* 수평 중앙 */
  align-items: center;                    /* 수직 중앙 */
  flex-direction: column;
  width: 420px;
  margin: 0 auto;                         /* 화면 가운데로 */
  padding: 50px 0px;

`;

const Title = styled.h1`
  font-size: 42px;
`;

const Form = styled.form`
  margin-top: 50px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
`;

const Input = styled.input`
  padding: 10px 20px;
  border-radius: 50px;
  border: none;
  width: 100%;
  font-size: 16px;
  &[type="submit"] {
    cursor: pointer;
    &:hover {
      opacity: 0.8;
    }
  }
`;

const Error = styled.span`
  font-weight: 600;
  color: tomato;
`;

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("");
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const {
      target: { name, value },
    } = e;
    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();
    if(isLoading === true ||name === "" || email === "" || password === ""){
      return;
    }
    try {
      setLoading(true);
      const credentials = await createUserWithEmailAndPassword(auth, email, password)
                                                              // createUserWithEmailAndPassword는 Firebase Auth SDK에서 제공하는 함수로 인자에 auth 인스턴스가 필요하다.
                                                              // firebase.ts 파일 내 auth 인스턴스가 생성되어 있음!
      await updateProfile(credentials.user, {displayName : name});  
      navigate("/");                                                            
    } catch (e) {
      if(e instanceof FirebaseError){
        setError(e.message);
      }
    } finally {
      setLoading(false);
    }
  };
  return (
    <Wrapper>
      <Title>Join 𝕏</Title>
      <Form onSubmit={onSubmit}>
        <Input
          onChange={onChange}
          name="name"
          value={name}
          placeholder="Name"
          type="text"
          required
        />
        <Input
          onChange={onChange}
          name="email"
          value={email}
          placeholder="Email"
          type="email"
          required
        />
        <Input
          onChange={onChange}
          value={password}
          name="password"
          placeholder="Password"
          type="password"
          required
        />
        <Input
          type="submit"
          value={isLoading ? "Loading..." : "Create Account"}
        />
      </Form>
      {error !== "" ? <Error>{error}</Error> : null}
    </Wrapper>
  );
}