React - 로그인 페이지 만들기

Seong Ho Kim·2023년 12월 6일

React

목록 보기
17/19
post-thumbnail

Login Page

1) 로그인 페이지 UI

  • 로그인 페이지는 로고, 이메일 & 비밀번호 입력창, 로그인 & 회원가입 버튼 으로 나뉘어져 있다.

2) 필수사항 & 예외처리

(1) 필수사항

  • 이메일 입력
  • 비밀번호 입력
  • 로그인 버튼
  • 회원가입 버튼

(2) 예외처리

이메일, 비밀번호 입력후 로그인 버튼 활성화 처리(맨 처음엔 비활성화)

(3) UI만 구현

회원가입 버튼

3) 로그인 페이지 Code

  • 개인적으로 놓치고 넘어갔던 React를 다루기 위해 회원가입 페이지에 대한 로직, UI에 대한 내용을 중점적으로 다루기 위함이기에, 별도의 SCSS 코드는 첨부하지 않았다.

(1) 로그인 페이지 - 이메일, 비밀번호 input

	<div className="login">
      <div className="userFrame" onChange={handleInputChange}>
        ...생략...
        <UserInput
          type="text"
          placeholder="이메일"
          value={userInfo.email}
          name="email"
        />
        <UserInput
          type="password"
          placeholder="비밀번호"
          value={userInfo.password}
          name="password"
        />
        ...생략
      </div>
    </div>
  • 이메일, 비밀번호를 입력하는 입력창은 UserInput이라는 공용 컴포넌트를 활용해서 만들었다. 특히 공용 컴포넌트를 사용한 이유는 코드를 간결하게 유지하면서, 별도로 관리해서 활용할 수 있기 때문이다. 또한 코드의 재사용성을 높여주고, 유지보수를 훨씬 효율적으로 할 수 있다. 결론으론 공용 컴포넌트를 통해 코드를 보다 깔끔하게 관리할 수 있고, 여러 페이지에서 같은 스타일과 기능을 유지하기에도 용이하기에 이메일, 비밀번호 입력창을(input) 공용 컴포넌트로 만들어 활용하게 된것이다.

(1-1) 로그인 페이지 - UserInput 컴포넌트

import React from 'react';
import './UserInput.scss';

const UserInput = ({ type, placeholder, value, name, onChange }) => {
  return (
    <input
      className="userInput"
      type={type}
      placeholder={placeholder}
      onChange={onChange}
      value={value}
      name={name}
    />
  );
};

export default UserInput;
  • UserInput 컴포넌트는 입력 창을 만들어주는 역할을 하는 컴포넌트이다. 이 컴포넌트는 입력 창의 타입(예: 텍스트, 비밀번호 등), placeholder(입력 예시), 값(value), 이름(name), 그리고 값이 변경될 때 실행할 함수 등을 받아와 사용하고, 'userInput'이라는 클래스를 가진 입력 창을 생성하며, 사용자가 입력한 값이 value로 전달되고, 값이 변경될 때마다 onChange 함수가 실행하도록 되어 있다.

(2) 로그인 페이지 - 로그인, 회원가입 버튼

	    ...생략...
        <UserButton
          text="로그인"
          disabled={!isInvaild}
          onClick={loginProgcess}
        />
        <div className="buttonWrapper">
          <button className="actionButton" onClick={goSignupPage}>
            회원 가입
          </button>
          <div className="wall">|</div>
          <button className="actionButton">비밀번호 찾기</button>
        </div>
        ...생략...
  • 로그인 페이지에서는 로그인과 회원가입을 할 수 있는 버튼이 있다. 여기서 사용된 로그인 버튼은 UserButton 컴포넌트를 사용해서 만들어졌고, UserInput 공용 컴포넌트로 만들어 사용했다. 버튼은 로그인 조건에 따라 활성화 여부가 결정된다.
  • 회원가입과 비밀번호 찾기 버튼은 일반 button 태그를 사용하여 구현했다. 이 버튼들은 UserButton으로 공용 컴포넌트로 만들지 않고, 그때그때 적용할 필요가 없어서 일반 버튼 태그를 사용했다. 이렇게 구성함으로써 로그인 버튼과 다른 버튼들 간에 디자인이나 동작에 일관성을 유지하면서도 코드를 간결하게 유지할 수 있다는 장점이 있다.

(2-1) 로그인 페이지 - UserButton 컴포넌트

import React from 'react';
import './UserButton.scss';

const UserButton = ({ text, onClick, disabled }) => {
  return (
    <button className="userButton" onClick={onClick} disabled={disabled}>
      {text}
    </button>
  );
};

export default UserButton;
  • UserButton은 사용자가 클릭했을때 이벤트 동작이 이루어지는 버튼 컴포넌트이다. 이 컴포넌트는 버튼의 텍스트와 클릭 시 실행할 함수, 그리고 활성/비활성 상태를 결정하는 기능을 포함하고 있고 'userButton'이라는 className을 가진 버튼을 만들어내며, 이 버튼은 텍스트를 인자로 넣어서 표시하고, disabled를 통해 이메일, 비밀번호를 입력했을때 클릭 가능 여부를 조절할 수 있게 했다.

(3) 로그인 페이지 - 유저 정보 업데이트(State)

// 유저 정보(ID, PW)
  const [userInfo, setUserInfo] = useState({
    email: '',
    password: '',
  });
  // 이메일, 비밀번호
  const handleInputChange = event => {
    const { name, value } = event.target;
    setUserInfo(userInfo => ({
      ...userInfo,
      [name]: value,
    }));
  };
  
  ...
  
  <div className="userFrame" onChange={handleInputChange}>
          ...생략...
        <UserInput
          type="text"
          placeholder="이메일"
          value={userInfo.email}
          name="email"
        />
        <UserInput
          type="password"
          placeholder="비밀번호"
          value={userInfo.password}
          name="password"
        />
        <UserButton
          text="로그인"
          disabled={!isInvaild}
          onClick={loginProgcess}
        />
        ...생략...
      </div>
  • 이 로직은 사용자의 이메일과 비밀번호를 저장하고 업데이트하는 기능을 수행한다. 'userInfo' 상태에 이메일과 비밀번호를 객체 형태로 저장하여 관리하고, 'handleInputChange' 함수는 사용자가 입력한 값이 이 상태에 반영될 수 있도록 한다. 또한 사용자가 UserInput에 이메일 또는 비밀번호를 입력할 때마다 'userInfo' 상태가 업데이트되어, 이를 통해 입력된 값이 UserButton(로그인) 버튼을 활성화 시킨다.

  • (이벤트 위임이란? 이벤트가 발생하는 요소마다 이벤트(onChange)에 핸들러 함수를 전달할 필요 없이, 감싸고 있는 상위 요소에 한번만 이벤트에 핸들러 함수를 전달하면, 자식 요소의 이벤트를 전부 감지할 수 있는 기능을 수행한다.)

(4) 로그인 페이지 - 이메일, 비밀번호 유효성 검사

// 유효성 검사
  const isInvaild =
    userInfo.email.includes('@') &&
    userInfo.email.includes('.') &&
    userInfo.password.length >= 10;
    
    ...
    
    <UserButton
          text="로그인"
          disabled={!isInvaild}
          onClick={loginProgcess}
        />
  • 이 코드는 입력된 이메일과 비밀번호의 유효성을 검사하는 로직이다. isInvaild 라는 변수는 이메일에 '@'와 '.'이 모두 포함되어 있고, 비밀번호의 길이가 10자 이상인지를 확인한다. 이 모든 조건들이 충족되면 'isInvaild'는 true 상태로 바뀌어, 로그인 버튼을 활성화시킨다. 만약에 이 조건들이 충족하지 않으면 로그인 버튼의 isInvaild는 false 상태가 유지되어 활성화 되지 않아 로그인을 할 수 없다. 이를 통해 사용자가 올바른 형식의 이메일과 긴 비밀번호를 입력할 때만 로그인 버튼을 누를 수 있도록 설정했다.

(5) 로그인 페이지 - 회원가입 페이지로 이동

// 페이지 이동
  const navigate = useNavigate();
  // 회원가입 페이지 이동
  const goSignupPage = () => {
    navigate('/signup');
  };
  
  ...
  
  <div className="buttonWrapper">
     <button className="actionButton" onClick={goSignupPage}>
       회원 가입
     </button>
     ...생략...
     </div>
  • useNavigate는 다른 페이지로 이동할 수 있도록 도와주는 기능을 수행한다. 예를들어 actionButton 클래스를 가진 자식 요소중에 회원가입 하기 버튼을 누르면 onClick 이벤트가 발생해 goSignupPage 함수가 실행되어 회원가입 페이지(Signup)로 이동하게 된다.
    (이때, 이동하려는 페이지가 Router에 적용되어 있는지 반드시 확인해야 한다. 그 이유는 React는 SPA(Single Page Application) 방식이라 Router를 통해 UI를 랜더링 하기 때문이다.)

(6) 로그인 페이지 - fetch 메서드로 회원 정보 요청하기

// fetch : 로그인 버튼 클릭시 메인 페이지로 이동
  const loginProgcess = () => {
    fetch('/data/Login.json', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
      },
      body: JSON.stringify({
        email: userInfo.email,
        password: userInfo.password,
      }),
    })
      .then(response => response.json())
      .then(data => {
        if (data.message === 'LOGIN SUCCESS') {
          alert('로그인 되었습니다.');
          localStorage.setItem('token', data.message);
          navigate('/main');
        } else {
          alert('가입되지 않은 정보입니다.');
        }
      });
  };
  
  ...
  
  	<UserButton
          text="로그인"
          disabled={!isInvaild}
          onClick={loginProgcess}
        />
  • 이 로직은 로그인 버튼을 클릭했을 때 로그인의 성공/실패 여부를 동작시키는 로직이다. loginProgcess 라는 함수는 백엔드 API에 '/data/Login.json'으로 POST 요청을 보내는데, 이메일과 비밀번호 정보를 함께 request 한다. 이때, 백엔드 서버에서 받은 응답을 처리하게 되는데, response가 'LOGIN SUCCESS'라면 즉, 로그인에 성공하게 되면 로그인에 성공했다는 알림 메세지를 띄우고, 로컬 스토리지에 token을 저장한 뒤 메인 페이지로 이동한다. 만약에 로그인에 실패할 경우, '가입되지 않은 정보입니다.'라는 알림을 띄우게 된다. 이를 통해 사용자가 제공한 정보로 로그인이 성공했는지 혹은 실패했는지를 알려주고, 성공 시에만 메인 페이지로 이동하도록 구현되어 있다.

(7) 로그인 페이지 전체 Code

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import UserInput from '../../Components/UserInput';
import UserButton from '../../Components/UserButton';
import './Login.scss';

const Login = () => {
  // 유저 정보(ID, PW)
  const [userInfo, setUserInfo] = useState({
    email: '',
    password: '',
  });
  // 이메일, 비밀번호
  const handleInputChange = event => {
    const { name, value } = event.target;
    setUserInfo(userInfo => ({
      ...userInfo,
      [name]: value,
    }));
  };
  // 유효성 검사
  const isInvaild =
    userInfo.email.includes('@') &&
    userInfo.email.includes('.') &&
    userInfo.password.length >= 10;
  // 페이지 이동
  const navigate = useNavigate();
  // 회원가입 페이지 이동
  const goSignupPage = () => {
    navigate('/signup');
  };
  // fetch : 로그인 버튼 클릭시 메인 페이지로 이동
  const loginProgcess = () => {
    fetch('/data/Login.json', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
      },
      body: JSON.stringify({
        email: userInfo.email,
        password: userInfo.password,
      }),
    })
      .then(response => response.json())
      .then(data => {
        if (data.message === 'LOGIN SUCCESS') {
          alert('로그인 되었습니다.');
          localStorage.setItem('token', data.message);
          navigate('/main');
        } else {
          alert('가입되지 않은 정보입니다.');
        }
      });
  };

  return (
    <div className="login">
      <div className="userFrame" onChange={handleInputChange}>
        <div className="imageFrame">
          <img className="logo" src="/images/Logo.svg" alt="위코드 로고" />
          <img
            className="logo"
            src="/images/logo_wecode.svg"
            alt="위코드 로고"
          />
        </div>
        <UserInput
          type="text"
          placeholder="이메일"
          value={userInfo.email}
          name="email"
        />
        <UserInput
          type="password"
          placeholder="비밀번호"
          value={userInfo.password}
          name="password"
        />
        <UserButton
          text="로그인"
          disabled={!isInvaild}
          onClick={loginProgcess}
        />
        <div className="buttonWrapper">
          <button className="actionButton" onClick={goSignupPage}>
            회원 가입
          </button>
          <div className="wall">|</div>
          <button className="actionButton">비밀번호 찾기</button>
        </div>
      </div>
    </div>
  );
};

export default Login;

Reference

  • 이 프로젝트는 META의 Thread를 참조하여 만들었습니다.
  • 실무수준의 프로젝트이지만 학습 및 취업용으로 만들었기 때문에 이 코드를 활용하여 이득을 취하거나 무단 배포할 경우 법적으로 문제될 수 있습니다.
  • 이 프로젝트에서 사용하고 있는 사진 대부분은 위코드에서 구매한 것이므로 해당 프로젝트 외부인이 사용할 수 없습니다.
profile
안녕하세요 Junior UIUX Designer 입니다 😊

0개의 댓글