본문 바로가기

E-commerce Vanila JS [9편] 로그인 인증(2)

by Recstasy 2021. 12. 10.

로그인 인증(1)에서 서버와 토큰을 주고받으며 사용자 인증을 진행했다. 만일 정적인 사이트(일회성 이벤트 사이트)라면 서버와 사용자 인증을 진행하는 수준에서 운영이 가능하다. 그러나 웹서비스의 기능을 이용한다면 사용자 세션이 필수다. 이번 포스팅에서는 로컬 스토러지에 사용자 정보를 저장하는 방식으로 웹에서 제공하는 서비스의 기본토대를 구현해보자. 

 

 

 

 


1 사용자 로컬스토러지

1) localStorage.js 추가

- 프론트 단에서 사용할 수 있는 사용자 데이터를 로컬스토러지와 주고받는 기능을 구현한다.

- localStorage.js는 setUserInfo( ), getUserInfo( ) 메서드를 통해 사용자 정보를 저장하고 반환한다.


   
// 중략...

    export
 const setUserInfo = ({
        _id = '',
        name = '',
        email = '',
        password = '',
        token = '',
        isAdmin = false

    }) => {
        localStorage.setItem(
            'userInfo',
            JSON.stringify({
                _id,
                name,
                email,
                password,
                token,
                isAdmin,
            })
        );
    };

    export const getUserInfo = () =>{
        return localStorage.getItem('userInfo')
        ? JSON.parse(localStorage.getItem('userInfo'))
        : { name: '', email: '', password: '' };
    };

[ root\frontend\src\localStorage.js ]  

 

 

2) SigninScreen.js 추가

- 로컬 스토러지로부터 사용자 정보를 받는 경우에는 로그인 페이지를 보여줄 필요가 없다. 사용자가 로그인 상태이므로 첫 페이지를 띄워야 한다. 이와 관련된 부분을 추가한다.    

 

  import { signin } from '../api';
  import { getUserInfo, setUserInfo } from '../localStorage' // 추가
 
  const SigninScreen = {
      after_render: () => {
          document.getElementById('signin-form')
          .addEventListener('submit', async(e) => {
              e.preventDefault();
              const data = await signin({
                  email: document.getElementById('email').value,
                  password: document.getElementById('password').value,
              });
              if(data.error){
                  alert(data.error);
              }else{
                  setUserInfo(data);   // 추가
                  document.location.hash = '/';
              }
          })
      },

      render: () => {

          if(getUserInfo().name){  // <-- 추가
              document.location.hash = '/';
          }   // 추가-->

          return `
              <div class="form-container">

                 // 중략...
  };
 
  export default SigninScreen;


[ root\frontend\src\screens\SigninScreen.js ]  

 

 

 

 

 

 

 


2 로그인 UI

1) index.html 수정

- 사용자가 로그인에 성공한다면, 우측 상단에 자신의 아이디가 보여야 한다. 이를 구현해보자. 

- index.html에서 header태그 부분에 'header-container' ID를 추가하고, 기존의 부분은 잘라둔다. 로그인에 성공했을 경우와 그렇지 못한 경우로 나눠야하기 때문에 header는 아래와 같이 공란으로 변경한다.

 

[ root\frontend\index.html ]  

 

 

2) Header.js 생성

- Header.js는 로그인 성공유무에 따라 로컬 스토러지에서 받아온 'name'값을 반환한다.

 

 
  import { getUserInfo } from "../localStorage";
 
  const Header = {
      render: () => {
          const { name } = getUserInfo();
          return `
              <div class="brand">
                  <a href="/#/">WebdoLi</a>
              </div>
              <div>
                  ${name
                      ? `<a href="/#/profile">${name}</a>`
                      : `<a href="/#/signin">Sign-In</a>`
                  }
                  <a href="/#/cart">Cart</a>
              </div>
          `;
      },
      after_render: () => {
 
      }
  }
 
  export default Header;

[ root\frontend\src\components\Header.js ]  

 

 

3) index.js 추가

- main-container항목을 랜더링하기 전에 header-container검증을 진행하고, 로그인 유무에 따라 유저 정보를 표현하도록 구현한다. 

 

[ root\frontend\src\index.js ]

 

 

여기까지 진행한 상태에서 코드를 실행해보자. 우측상단에 로그인 계정이름이 보일 것이다. (관리자로 로그인했을 경우)

 

 

 

 

 

 

 

 


3 로딩화면

1) index.html 추가

- 로그인 GUI는 대부분 아웃포커싱이 적용된다. 아웃포커싱을 구현하는 간단한 웹디자인의 방식은 '투명도' 조절이다. 

- index.html에서 'overlay' 클래스를 추가한다. 

 

  <!DOCTYPE html>

     <!-- 중략 ... -->

  </head>
  <body>
      <div class="overlay" id="loading-overlay">Loading...</div<!-- 추가 -->
      <div class="overlay" id="message-overlay"></div>    <!-- 추가 -->

      <div class="grid-container">
          <header id="header-container"></header>
          <main id="main-container"></main>
          <footer>
              CopyRight @2021
          </footer>
      </div>

      <script src="main.js"></script>

  </body>
  </html>

[ root\frontend\index.html ]  

 

 

2) style.css 추가

- 로그인 창 주변의 배경을 투명하게 조절하는 css를 지정한다. 'Form'주석 아래에 추가한다.

 

 
  /* 중략... */

    button:hover {
      border: 0.1rem #404040 solid;
    }

  /* <-- 추가 */

  button.primary {
      background-color: #f0c040;
  }
  .overlay{
    display: none;
    position: fixed;
    z-index: 1000;
    top:0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(16, 16, 16, .5);
  }
  .overlay.active{
    display:flex;
    justify-content: center;
    align-items: center;
    color: #ffffff;
  }
  .overlay > div {
    background-color: #ffffff;
    color: #000000;
    border-radius: 0.5rem;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    max-width: 40rem;
  }
  .overlay > div > * {
    margin: 2rem;
  }
  .fw {
    width: 100%;
  }

/* 추가 --> */

  .success {
    color: #40c040;

    /* 중략... */

[ root\frontend\style.css ]  

 

 

3) utils.js(frontend) 추가

- loding-overlay클래스 항목을 활성화 & 비활성화 할 수 있는 기능을 utils.js에 첨가한다.

 


  // 중략...

    export
 const rerender = async(component) => {
        document.getElementById("main-container")
        .innerHTML = await component.render();
        await component.after_render();
    }

  // <--추가
    export const showLoading = () => {
        document.getElementById('loading-overlay').classList.add('active');
    };

    export const hideLoading = () => {
        document.getElementById('loading-overlay').classList.remove('active');
    };
  // 추가-->

[ root\frontend\src\utils.js ]  

 

 

4) showLoading( ), hideLoading( ) 추가

- 서버에서 데이터를 주고받거나 로컬스토러지의 데이터를 변환하는 로직이 진행될 때, 대기상태를 의미하는 'showLoading( )'을 추가 & 삭제한다. 

- SigninScreen.js, HomeScreen.js, ProductScreen.js, index.js 파일에서 데이터의 정보를 요청하는 구문에 'showLoading( ), hideLoading( )'을 추가해준다.

 

* SigninScreen.js


 
import { signin } from '../api';
  import { getUserInfo, setUserInfo } from '../localStorage';
  import { showLoading, hideLoading } from '../utils'// 추가
 
  const SigninScreen = {
      after_render: () => {
          document.getElementById('signin-form')
          .addEventListener('submit', async(e) => {
              e.preventDefault();
              showLoading();  // 추가
 
              const data = await signin({
                  email: document.getElementById('email').value,
                  password: document.getElementById('password').value,
              });
 
              hideLoading();  // 추가
             
              if(data.error){
                  alert(data.error);
              }else{
                  setUserInfo(data);
                  document.location.hash = '/';
              }
          })
      },

   // 중략...

[ root\frontend\src\screens\SigninScreen.js ]  

 

 

* HomeScreen.js


 
import axios from 'axios';
  import Rating from '../components/Rating.js';
  import { hideLoading, showLoading } from '../utils.js'// 추가
 
  const HomeScreen = {
      render: async() => {

          showLoading();  // 추가

          const response = await axios({
              url: "http://localhost:5000/api/products",
              headers:{
                  'Content-Type': 'application/json',
              },
          });

          hideLoading();  // 추가

          if(!response || response.statusText !== 'OK'){
              return '<div> Error in getting Data </div>'
          }
         
          const products = await response.data;
          return `

     // 중략...
 
  }
 
  export default HomeScreen;

[ root\frontend\src\screens\HomeScreen.js ]  

 

 

* ProductScreen.js


 
import {getProduct} from '../api';
  import {hideLoading, parseRequestUrl, showLoading} from '../utils'// 추가
  import Rating from '../components/Rating';
 
 
  const ProductScreen = {
      after_render: () => {
          const request = parseRequestUrl();
          document.getElementById('add-button').addEventListener('click', () => {
              document.location.hash = `/cart/${request.id}`
          })
      },
      render: async() => {
          const request = parseRequestUrl();
          showLoading(); // 추가

          const product = await getProduct(request.id);
          if(product.error){
              return `<div> ${product.error} </div>`
          }

          hideLoading(); // 추가

          return `

         // 중략...
  }
 
  export default ProductScreen;

[ root\frontend\src\screens\SigninScreen.js ]  

 

 

* index.js


  import {hideLoading, parseRequestUrl, showLoading} from './utils'// showLoading( ), hideLoading( ) 추가
 
   // 중략...
 
  const router = async() => {
      showLoading();  // 추가
      const request = parseRequestUrl();
      const parseUrl = (request.resource ? `/${request.resource}` : '/') +
                       (request.id ? '/:id' : '' ) +
                       (request.verb ? `/${request.verb}` : '' );
 
      const screen = routes[parseUrl] ? routes[parseUrl] : Error404Screen;
      const header = document.getElementById('header-container');
      const mainContainer = document.getElementById('main-container');
     
      header.innerHTML = await Header.render();
      await Header.after_render();
 
      mainContainer.innerHTML = await screen.render();
      if(screen.after_render) await screen.after_render();
      hideLoading();  // 추가
  }
 
  window.addEventListener('load', router);
  window.addEventListener('hashchange', router);

[ root\frontend\src\index.js ]  

 

 

 

 

 

 


4 에러메시지 출력

1) utils.js(frontend) 수정

- 에러 메시지를 표시하는 기능을 utils.js파일에 추가한다.

 


 
// 중략...
 
  export const hideLoading = () => {
      document.getElementById('loading-overlay').classList.remove('active');
  }

  // <-- 추가 -->
  export const showMessage = (message, callback) => {
      document.getElementById('message-overlay').innerHTML = `
          <div>
              <div id="message-overlay-content"> ${message} </div>
              <button id="message-overlay-close-button"> OK </button>
          </div>
      `;
      document.getElementById('message-overlay').classList.add('active');
      document.getElementById('message-overlay-close-button')
              .addEventListener('click', () => {
                  document.getElementById('message-overlay').classList.remove('active');
                  if(callback){
                      callback();
                  }
              })
  }

[ root\frontend\src\utils.js ]  

 

 

2) SigninScreen.js 수정

- alert( )함수를 통해 에러메시지가 전달되는 구조를 showMessage( )로 대체한다.

 

  import { signin } from '../api';
  import { getUserInfo, setUserInfo } from '../localStorage';
  import { showLoading, hideLoading, showMessage } from '../utils'// showMessage 추가
 
  const SigninScreen = {
      after_render: () => {
          document.getElementById('signin-form')
          .addEventListener('submit', async(e) => {
              e.preventDefault();
              showLoading();
 
              const data = await signin({
                  email: document.getElementById('email').value,
                  password: document.getElementById('password').value,
              });
 
              hideLoading();
             
              if(data.error){
                  showMessage(data.error);  // 수정
              }else{
                  setUserInfo(data);
                  document.location.hash = '/';
              }
          })
      },
      render: () => {
          if(getUserInfo().name){
              document.location.hash = '/';
          }
          return `

         // 중략...
  };
 
  export default SigninScreen;

[ root\frontend\src\screens\SigninScreen.js ]  

 

 

3) Test

- 로그인 오류를 냈을 때, 자체적으로 구현한 오류 메시지가 나타난다. 

 

 

 

다음 포스팅에서는 회원가입을 진행해보자.

 

댓글

최신글 전체

이미지
제목
글쓴이
등록일