본문 바로가기

블로그 회원가입 제작 [2편. 회원가입 페이지]

by Recstasy 2022. 1. 7.

 

로그인폼에 이어 회원가입을 생성해보자.

 

 

1 페이지 생성

티스토리 블로그는 페이지를 생성할 수 있다. 페이지 관리에서 '/myPage', '/RegisterForm', 'ResetPW' 페이지를 생성한다. 

(아래 Blog_CRUD 제외)

 

 

 

 

1-1 registerForm.js

registerForm.js의 역할은 회원가입 폼을 불러오는 인터페이스 모듈이다.  chkPage모듈은 사용자가 어떠한 페이지에 접속했는지 리턴값을 돌려주고, 해당값의 라우터를 실행하는 부분이 registerForm.js의 핵심이다. 또한, 회원가입 페이지에서 사용자가 임의적으로 다른 url을 입력하는 것을 방지하기 위해서 indexScreen 모듈을 생성한다.

 


  import { chkPage } from 'https://tistory1.daumcdn.net/tistory/2784544/skin/images/wd-utils2.js';
 
   
  window.addEventListener('load', async() => {
   
    const routes = {
        'RegisterForm': registerFormScreen,
        'Index': IndexScreen,
    }
   
    let userCurrentPage = await chkPage().then((page) => {
        return page;
    });
   
    const screen = routes[userCurrentPage] ? routes[userCurrentPage] : routes['Index']
    const content = document.getElementById('content')
    content.innerHTML = await screen.render();
    if(screen.after_render) await screen.after_render();
  });
.

[ registerForm.js ]

 

 

indexScreen.js는 강제적으로 registerForm 페이지로 경로를 변경한다. 

 


  const indexScreen = {
    render: async() => {
        window.location.href = 'https://webdoli.tistory.com/pages/RegisterForm';
    }
  }
 
  export default indexScreen;
.

[ indexScreen.js ]

 

 

utils.js는 반복적으로 사용하는 기능을 모아두고, 각 기능들을 모듈로 꺼내쓸 수 있다. 가령, 아래와 같이 파이어베이스 api key를 가져온다거나 페이지의 url의 endPoint를 가져오는 식의 기능을 집어넣을 수 있다.

 


  export const parseRequestUrl = async() => {
      let siteUrl = await document.location.href;
      const request = siteUrl.split('/');
      let reqLength = request.length-1;
      let result = request[reqLength];
 
      return result;
  }
 
  export const chkPage = () => {
      let currentPage = parseRequestUrl();
      return currentPage;
  }
 
  export const firebaseConfig = () => {
      return {
      apiKey: "api key입력"
      }
  }
.

[ utils.js ]

 

 

 

1-2 registerFormScreen.js  :: render( )

registerFormScreen.js는 회원가입 폼을 랜더링하는 실질적인 구현을 한다. 일단 랜더링 부분만 구현해보자. 

 

.
  import { getAuth, createUserWithEmailAndPassword, updateProfile, sendPasswordResetEmail } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-auth.js";
  import { getFirestore, collection, doc, onSnapshot, where, setDoc, Timestamp, query } from 'https://www.gstatic.com/firebasejs/9.6.1/firebase-firestore.js';
 
  const firebaseApp = initializeApp(firebaseConfig);
  const auth = getAuth();
  const db = getFirestore();
 
  const RegisterFormScreen = {
      after_render: () =>{
   
      },
 
      render: () => {
        auth.onAuthStateChanged(async(user) => {
              if(user){
                 window.location.href = "https://webdoli.tistory.com/";  
              }
          });
       
          return `
          <form id="create-user-form">
            <h3 id="registerform-title">WebdoLi 회원가입</h3>
            <div class="form-control">
                <label for="text">아이디</label>
                <input id="create-user-id" type="text" />
                <small></small>
            </div>
            <div class="form-control">
                <label for="text">이메일</label>
                <div id="email-container">
                    <input id="create-user-email" type="text" placeholder="아이디 찾기에 사용되는 이메일을 입력해주세요."/>
                    <button id="email-chk">중복검사</button>
                </div>
                <small></small>
            </div>
            <div class="form-control">
                <label for="text">비밀번호</label>
                <input id="create-user-password" type="password" />
                <small></small>
            </div>
            <div class="form-control">
                <label for="text">비밀번호 재입력</label>
                <input id="create-user-password2" type="password" />
                <small></small>
            </div>
           
              <div class="create-user-form-btn">
                  <button id="create-user-button" class="purple-button auth-btn">Create User</button>
              </div>
          </form>
          `
      }
  }
 
  export default RegisterFormScreen;

[ registerFormScreen.js ]

 

render()메서드를 실행하면, <form>태그를 전달한다. <form>태그는 form-control 클래스의 div태그가 반복되는 코드이며, 아래처럼 css코드까지 회원가입 페이지에 연결해준다.

 

  
  .form-control.error input{
      border-color: var(--error-color)
  }
 
  .form-control small{
      color: var(--error-color);
      position: absolute;
      bottom: 30;
      left: 30;
      visibility: hidden;
  }
 
  .form-control.error small{
      visibility: visible;
  }
 
  .form-control input::placeholder{
      color: var(--bg-font-color);
      font-size: 0.8rem;
  }
 
  .create-user-form-btn{
      margin-top:20px;
      margin-bottom: 20px;
      padding:5px 30px;
  }
 
  #email-container{
      display: flex;
  }
 
  #create-user-email{
      width: 65%;
  }
 
  #email-chk{
      width:14%;
      background-color: var(--basic-color);
      color: white;
      border-radius: 5px;
      font-size: 0.9rem;
      margin-left: 12px;
      box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.4);
  }
 
  #create-user-button{
      border-radius: 5px;
      width: 100%;
      color: white;
      cursor: pointer;
      display:block;
      background-color: #48CFAD;
      padding:10px 0;
      font-size: 1.2rem;
      box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.4) ;
  }

[ registerForm.css ]

 

 

css파일은 회원가입 페이지의 <head></head>사이에 위치시켜준다. 

 

    
    <script>
        let headID = document.getElementsByTagName('head')[0];
          let link = document.createElement('link');
          link.type = 'text/css';
          link.rel = 'stylesheet';
          headID.appendChild(link);
    </script>
    

[ 티스토리 회원가입폼 페이지(HTML모드) ]

 

 

css설정까지 끝내고 회원가입 페이지(https://webdoli.tistory.com/pages/RegisterForm)에 접속해보자. 아래와 같은 회원가입 폼을 볼 수 있다. 

 

회원가입폼

 

 

 

 

1-3 registerFormScreen.js  :: 이메일 검증

회원가입 폼에는 2개의 버튼이 존재한다. 바로 이메일 중복검사와 폼 보내기 버튼이다. 이메일 검증 버튼은 현재 firebase-firestore에서 사용자가 입력한 이메일이 존재하는지 검색하는 기능을 구현한다. 

 

   
  //중략...
  const errorName = {
                'create-user-email': '이메일',
                'create-user-password': '비밀번호',
                'create-user-password2': '비밀번호 재확인',
                'create-user-id': '유저 아이디'
            }
 
  const RegisterFormScreen = {
      after_render: () =>{
       
        const content = document.getElementById('content');
        const createForm = document.getElementById('create-user-form');
        const createFormBtn = document.getElementById('create-user-button');
        const userID = document.getElementById('create-user-id');
        const email = document.getElementById('create-user-email');
        const password = document.getElementById('create-user-password');
        const password2 = document.getElementById('create-user-password2');
        const emailChkBtn = document.getElementById('email-chk');
        let emailValidChk = false;
       
       
        //이메일 검증 버튼 클릭
        emailChkBtn.addEventListener('click', async(e) => {
            e.preventDefault();
            console.log('이메일 검증');
            if(email.value.trim() === ''){
                let emailEle = email.parentElement;
                showError(emailEle, `${errorName[email.id]}을 입력하세요.` )
            }else{
               
                const queryEmail = query(collection(db, "webdoliUsers"), where("email", "==", email.value));
                const docDatas = onSnapshot(queryEmail, (querySnapshot) => {
                   
                    if(querySnapshot.empty){
                        emailValidChk = true;
                        email.parentElement.parentElement.className = "form-control success";
                        return;
                    }
                   
                    querySnapshot.forEach((doc) => {                    
                        let emailParent = email.parentElement;
                        showError(emailParent, '존재하는 이메일입니다.')
                    })
                })
            }

        })

      //폼 보내기버튼 클릭
          createFormBtn.addEventListener('click', async(e) => {
             
               
          })
     

[ registerFormScreen.js ]

 

 

이메일 검증은 파이어베이스-firestore 컬렉션의 doc를 쿼리하는 부분이 핵심이다. 위의 코드에서는 파이어베이스 v9의 'query', 'where', 'onSnapshot', 'collection' 모듈을 사용하고 있다. firebase-firestore DB쿼리문과 관련된 부분은 아래 페이지에서 확인할 수 있다.

 

https://firebase.google.com/docs/firestore/query-data/get-data

 

Cloud Firestore로 데이터 가져오기  |  Firebase Documentation

의견 보내기 Cloud Firestore로 데이터 가져오기 두 가지 방법으로 Cloud Firestore에 저장된 데이터를 검색할 수 있습니다. 문서, 문서 컬렉션 또는 쿼리 결과에 대해 이러한 방법 중 하나를 사용할 수

firebase.google.com

 

 

개인적으로는 파이어베이스 공식 문서보다 Stack-overflow에 있는 쿼리관련 답변이 더 좋은 방법인 듯 하다.(코드가 더 짧음)

 

https://stackoverflow.com/questions/50692218/how-can-i-get-specific-document-data-from-firestore-querysnapshot

 

 

 

 

1-4 registerFormScreen.js  :: 이메일 검증

after_render( )메서드는 이메일 검증버튼 이벤트와 함께 폼 전송 버튼 이벤트도 함께 구현한다. 파이어베이스 DB에 저장되기 위해서는 사용자가 입력한 값을 검증하는 2개의 함수, 'chkRequired( ), chkPasswordMatch( )'를 거쳐야하고, 'form-control error' 클래스가 없을 경우에만 파이어베이스 저장과 관련된 코드가 실행된다. v8과 달리 updateProfile( ), setDOC( )와 같은 모듈과 콜벡의 조합으로 auth와 'webdoliUsers'컬렉션에 userID, master, email 값을 저장한다.

 

  //중략...

      //폼 보내기버튼 클릭
          createFormBtn.addEventListener('click', async(e) => {
              e.preventDefault();
             
            // chk Valid
            await chkRequired([userID, email, password, password2]);
            await chkPasswordMatch(password, password2);
           
            if(document.getElementsByClassName('form-control error').length === 0){
               
                    if(emailValidChk){
                        console.log('파이어베이스 저장');
                       
                        createUserWithEmailAndPassword(auth, email.value, password.value)
                        .then( async(userCredential) => {
                            console.log('파이어베이스 FireStore 저장');
                           
                            await setDoc(doc(db, "webdoliUsers", userCredential.user.uid),{
                                    userID: userID.value,
                                    master: false,
                                    email: email.value
                                });
                           
                            console.log('DB 기록 성공!');
                           
                            content.removeChild(createForm);
                           
                            let registerPopup = document.createElement('div');
                                registerPopup.classList.add('register-popup');
                            let welcomeTxt = document.createElement('p');
                                welcomeTxt.innerText = '회원가입을 환영합니다. 재로그인 해주세요.3초후 메인페이지로 이동합니다. ';
                                registerPopup.appendChild(welcomeTxt);
                               
                            content.appendChild(registerPopup);
                           
                           
                        })
                        .catch(err => {
                            console.log('err: '+ err.message);
                        })
                       
                    }else{  
                        showError(email.parentElement, '이메일 중복 검사를 해주세요.');
                        return;
                    }
                   
                    setTimeout( () => {
                                auth.signOut();
                                document.location.href="https://webdoli.tistory.com";  
                            },3000);
                       
            }else{
                console.log('파이어베이스 보류')
            }
               
          })
       
      },    //after_render( ) End
 
      render: () => {
       //중략...
    }
 
    //함수 입력
 
  export default RegisterFormScreen;

[ registerFormScreen.js ]

 

 

 

 

1-5 registerFormScreen.js  :: 함수 추가

회원가입 폼에는 유저의 입력값을 검증할 수 있는 여러가지 함수가 필요하다. 위의 승인과 관련된 함수는 아래와 같다.

 

    
  //사용자 입력값 유효성 검사 함수
  const chkRequired = (inputArr) => {
           
    inputArr.forEach(input => {
        if(input.value.trim() === ''){
            if(input.id === 'create-user-email'){
                let emailEle = input.parentElement;
                showError(emailEle, `${errorName[input.id]} 빈칸을 채워주세요.` )
            }else{
                showError(input, `${errorName[input.id]} 빈칸을 채워주세요.`);
            }
           
        }else{
           
            if(input.id === 'create-user-id'){
                chkLength(input, 3, 15)
                chkUsrID(input)
            }else if(input.id === 'create-user-password') {
                chkLength(input, 6, 25);
            }else if(input.id === 'create-user-email'){
                chkEmail(input);
            }else{
                showSuccess(input);
            }
           
        }
    })
  }

  //에러발생 함수
  const showError = (ele, message) => {
    const formCtrl = ele.parentElement;
          formCtrl.className = 'form-control error';
    const small = formCtrl.querySelector('small');
          small.innerText = message;
  }
 
  //사용자값 승인표현 함수
  const showSuccess = (ele) => {
    const formCtrl = ele.parentElement;
    formCtrl.className = 'form-control success';
  }
 
  //이메일 유효성 검사 함수
  const chkEmail = (input) => {
     
      const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    if(re.test(input.value.trim())){
        let emailEle = input.parentElement;
        showSuccess(emailEle);
    }else{
        let emailEle = input.parentElement;
        showError(emailEle, '이메일 양식을 넣어주세요.');
    }
     
  }
 
  //아이디 중복검사 함수
  const chkUsrID = (input) => {
    let usrInputID = input.value;
    const queryUsrID = query(collection(db, "webdoliUsers"), where("userID", "==", usrInputID));
                const docDatas = onSnapshot(queryUsrID, (querySnapshot) => {
                   
                    if(querySnapshot.empty){
                        console.log('해당 ID는 사용가능합니다.');
                        return;
                    }
                   
                    querySnapshot.forEach((doc) => {
                        //console.log(`${doc.id} : ${doc.data()}`);
                        console.log('동일한 ID가 존재합니다.')
                        showError(input, '동일한 ID가 존재합니다.')
                    })
                })
  }
 
  //입력값 길이검사 함수
  const chkLength = (input, min, max) => {
   
    if(input.value.length < min){
        showError(input, `${errorName[input.id]} ${min}자리보다 길게 넣어주세요.`)
    }else if(input.value.length > max){
        showError(input, `${errorName[input.id]} ${max}자리보다 짧게 넣어주세요.`)
    }else{
        showSuccess(input);
    }
   
  }
 
  //비밀번호 재입력 검사 함수
  const chkPasswordMatch = (input1, input2) =>{
    if(input1.value !== input2.value){
        showError(input2, '비밀번호가 일치하지 않습니다.')
    }else{
        showSuccess(input2);
    }
  }
    

[ 폼가입 관련 함수 ]

 

 

 

1-6 registerFormScreen.js  :: 정리

코드량이 꽤 긴것 같지만,  크게 보자면 "after_render( )", "render( )", "함수" 세가지 부분으로 나뉘어진다. registerFormScreen.js의 전체 파일은 아래와 같다.

 

  
  import { getAuth, createUserWithEmailAndPassword, updateProfile, sendPasswordResetEmail } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-auth.js";
  import { getFirestore, collection, doc, onSnapshot, where, setDoc, Timestamp, query } from 'https://www.gstatic.com/firebasejs/9.6.1/firebase-firestore.js';
 
  const firebaseApp = initializeApp(firebaseConfig());
  const auth = getAuth();
  const db = getFirestore();
  const errorName = {
                'create-user-email': '이메일',
                'create-user-password': '비밀번호',
                'create-user-password2': '비밀번호 재확인',
                'create-user-id': '유저 아이디'
            }
 
  const RegisterFormScreen = {
      after_render: () =>{
       
        const content = document.getElementById('content');
          const createForm = document.getElementById('create-user-form');
        const createFormBtn = document.getElementById('create-user-button');
        const userID = document.getElementById('create-user-id');
          const email = document.getElementById('create-user-email');
          const password = document.getElementById('create-user-password');
        const password2 = document.getElementById('create-user-password2');
        const emailChkBtn = document.getElementById('email-chk');
        let emailValidChk = false;
       
       
       
    //이메일 검증
        emailChkBtn.addEventListener('click', async(e) => {
            e.preventDefault();
            console.log('이메일 검증');
            if(email.value.trim() === ''){
                let emailEle = email.parentElement;
                showError(emailEle, `${errorName[email.id]}을 입력하세요.` )
            }else{
                await chkEmail(email);
               
                if(chkEmailValid){
                    const queryEmail = query(collection(db, "webdoliUsers"), where("email", "==", email.value));
                    const docDatas = onSnapshot(queryEmail, (querySnapshot) => {
                        
                        if(querySnapshot.empty){
                            console.log('해당 이메일은 사용가능합니다.');
                            emailValidChk = true;
                            email.parentElement.parentElement.className = "form-control success";
                            return;
                        }
                       
                        querySnapshot.forEach((doc) => {
                           
                            console.log('이메일이 존재합니다.')
                            let emailParent = email.parentElement;
                            showError(emailParent, '존재하는 이메일입니다.')
                        })
                    })
                }
               
               
            }
         
        })
       
          createFormBtn.addEventListener('click', async(e) => {
              e.preventDefault();
             
            // chk Valid
            await chkRequired([userID, email, password, password2]);
            await chkPasswordMatch(password, password2);
              
            if(document.getElementsByClassName('form-control error').length === 0){
               
                    if(emailValidChk){
                        console.log('파이어베이스 저장');
                       
                        createUserWithEmailAndPassword(auth, email.value, password.value)
                        .then( async(userCredential) => {
                            console.log('파이어베이스 FireStore 저장');
                           
                            await setDoc(doc(db, "webdoliUsers", userCredential.user.uid),{
                                    userID: userID.value,
                                    master: false,
                                    email: email.value
                                });
                           
                            console.log('DB 기록 성공!');
                           
                            content.removeChild(createForm);
                           
                            let registerPopup = document.createElement('div');
                                registerPopup.classList.add('register-popup');
                            let welcomeTxt = document.createElement('p');
                                welcomeTxt.innerText = '회원가입을 환영합니다. 재로그인 해주세요.3초후 메인페이지로 이동합니다. ';
                                registerPopup.appendChild(welcomeTxt);
                               
                            content.appendChild(registerPopup);
                           
                           
                        })
                        .catch(err => {
                            console.log('err: '+ err.message);
                        })
                       
                    }else{  
                        showError(email.parentElement, '이메일 중복 검사를 해주세요.');
                        return;
                    }
                   
                    setTimeout( () => {
                                auth.signOut();
                                document.location.href="https://webdoli.tistory.com";  
                            },3000);
                       
            }else{
                console.log('파이어베이스 보류')
            }               
          })
 
      },
 
      render: () => {
        auth.onAuthStateChanged(async(user) => {
              if(user){
                 window.location.href = "https://webdoli.tistory.com/";  
              }
          });
       
          return `
          <form id="create-user-form">
            <h3 id="registerform-title">WebdoLi 회원가입</h3>
            <div class="form-control">
                <label for="text">아이디</label>
                <input id="create-user-id" type="text" />
                <small></small>
            </div>
            <div class="form-control">
                <label for="text">이메일</label>
                <div id="email-container">
                    <input id="create-user-email" type="text" placeholder="아이디 찾기에 사용되는 이메일을 입력해주세요."/>
                    <button id="email-chk">중복검사</button>
                </div>
                <small></small>
            </div>
            <div class="form-control">
                <label for="text">비밀번호</label>
                <input id="create-user-password" type="password" />
                <small></small>
            </div>
            <div class="form-control">
                <label for="text">비밀번호 재입력</label>
                <input id="create-user-password2" type="password" />
                <small></small>
            </div>
           
              <div class="create-user-form-btn">
                  <button id="create-user-button" class="purple-button auth-btn">Create User</button>
              </div>
          </form>
          `
      }
  }
 
 
  const chkRequired = (inputArr) => {
           
    inputArr.forEach(input => {
        if(input.value.trim() === ''){
            if(input.id === 'create-user-email'){
                let emailEle = input.parentElement;
                showError(emailEle, `${errorName[input.id]} 빈칸을 채워주세요.` )
            }else{
                showError(input, `${errorName[input.id]} 빈칸을 채워주세요.`);
            }
           
        }else{
           
            if(input.id === 'create-user-id'){
                chkLength(input, 3, 15)
                chkUsrID(input)
            }else if(input.id === 'create-user-password') {
                chkLength(input, 6, 25);
            }else if(input.id === 'create-user-email'){
                chkEmail(input);
            }else{
                showSuccess(input);
            }
           
        }
    })
  }
 
  const showError = (ele, message) => {
    const formCtrl = ele.parentElement;
          formCtrl.className = 'form-control error';
    const small = formCtrl.querySelector('small');
          small.innerText = message;
  }
 
  const showSuccess = (ele) => {
    const formCtrl = ele.parentElement;
    formCtrl.className = 'form-control success';
  }
 
  const chkEmail = (input) => {
     
      const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    if(re.test(input.value.trim())){
        let emailEle = input.parentElement;
        showSuccess(emailEle);
    }else{
        let emailEle = input.parentElement;
        showError(emailEle, '이메일 양식을 넣어주세요.');
    }
     
  }
 
  const chkUsrID = (input) => {
    let usrInputID = input.value;
    const queryUsrID = query(collection(db, "webdoliUsers"), where("userID", "==", usrInputID));
                const docDatas = onSnapshot(queryUsrID, (querySnapshot) => {
                   
                    if(querySnapshot.empty){
                        console.log('해당 ID는 사용가능합니다.');
                        return;
                    }
                   
                    querySnapshot.forEach((doc) => {
                        
                        console.log('동일한 ID가 존재합니다.')
                        showError(input, '동일한 ID가 존재합니다.')
                    })
                })
  }
 
  const chkLength = (input, min, max) => {
   
    if(input.value.length < min){
        showError(input, `${errorName[input.id]}${min}자리보다 길게 넣어주세요.`)
    }else if(input.value.length > max){
        showError(input, `${errorName[input.id]}${max}자리보다 짧게 넣어주세요.`)
    }else{
        showSuccess(input);
    }
   
  }
 
 
 
  const chkPasswordMatch = (input1, input2) =>{
    if(input1.value !== input2.value){
        showError(input2, '비밀번호가 일치하지 않습니다.')
    }else{
        showSuccess(input2);
    }
  }
 
 
 
  export default RegisterFormScreen;

[ registerFormScreen.js 전체코드 ]

 

 

 

 

 


2 테스트

테스트로 회원가입을 진행해보자.

 

 

 

 

회원가입폼 양식에 맞지 않는 내용을 입력해보자.

 

0123
[ 사용자 입력오류 테스트 ]

 

 

파이어베이스 Auth와 firestore DB에 접속했을 때, 블로그 회원가입 폼에서  입력한 정보가 그대로 저장된 것을 확인할 수 있다.

 

 

 

 

 

댓글

최신글 전체

이미지
제목
글쓴이
등록일