본문 바로가기
국비학원/수업기록

국비 지원 개발자 과정_Day85

by 루팽 2023. 3. 30.

localhost:3000/login → LoginPage.jsx

로그인처리

1. 이메일과 비밀번호로 인증하기

2. 구글 계정으로 인증하기

 

오라클 서버에 member230324에서 찾음

구글계정으로 발급된 useId가 있는 회원정보가 있는지 체크하기

 

반환값은 정보가

있으면 List<Map> - 회원가입 필요 없음 → sessionStorage에 저장(서비스에 이용하는데 유지해야 할 정보)

없으면 0 - 회원가입 필요

 

mem_auth: 1) member, 2) teacher

mem_status: 1) 0-비회원 2) 1-회원

 

<이메일+구글 로그인 회원가입 - App.jsx>

import { Route, Routes, useNavigate } from 'react-router-dom';
import LoginPage from './components/auth/LoginPage';
import KakaoRedirectHandler from './components/kakao/KakaoRedirectHandler';
import Profile from './components/kakao/Profile';
import MemberPage from './components/page/MemberPage';
import HomePage from './components/page/HomePage';
import DeptPage from './components/page/DeptPage';
import DeptDetail from './components/dept/DeptDetail';
import RepleBoardPage from './components/page/RepleBoardPage';
import Toast from './components/Toast';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import { setToastMsg } from './redux/toastStatus/action';
import SignupPage from './components/auth/SignupPage';
import KhLoginPage from './components/auth/KhLoginPage';
import { onAuthChange } from './service/authLogic';
import { memberListDB } from './service/dbLogic';
import EmailVerifiedPage from './components/auth/EmailVerifiedPage';
import FindEmailPage from './components/auth/FindEmailPage';
import ResetPwdPage from './components/auth/ResetPwdPage';

function App({authLogic, imageUploader}) {
  const navigate = useNavigate()
  const dispatch = useDispatch()
  const ssg = sessionStorage;
  const toastStatus = useSelector(state => state.toastStatus)
  useEffect(() => {
    const asyncDB = async() => {
      const auth = authLogic.getUserAuth()
      // 현재 인증된 사용자 정보를 가져온다
      const user = await onAuthChange(auth)
      // 사용자가 있을 경우(userId가 존재) - 구글 로그인으로 사용자 정보를 가지고 있을 때
      // user정보가 있으면 sessionStorage에 담는다 - email
      if(user) {
        console.log('user정보가 있을 때')
        ssg.setItem('email', user.email)
        const res = await memberListDB({mem_uid: user.uid, type: 'auth'})
        console.log(res.data)
        //오라클 서버의 회원집합에 uid가 존재할 경우 - 세션스토리지에 값을 담는다
        if(res.data !== 0) { // 스프링 부트 - RestMemberController - memberList() -> 0 혹은 [{}]
          const temp = JSON.stringify(res.data)
          const jsonDoc = JSON.parse(temp)
          ssg.setItem('nickname', jsonDoc[0].MEM_NICKNAME)
          ssg.setItem('status', jsonDoc[0].MEM_STATUS)
          ssg.setItem('auth', jsonDoc[0].MEM_AUTH)
          ssg.setItem('no', jsonDoc[0].MEM_NO)
          navigate("/")
          return // 렌더링이 종료됨
        }
        // 구글 계정이 아닌 다른 계정으로 로그인 시도를 했을 땐 user.emailVerified가 없다
        // -> undefined
        if(!user.emailVerified) {
          navigate('./auth/emailVerified')
        }
        //오라클 서버의 회원집합에 uid가 존재하지 않을 경우 
        else {
          console.log('가입되지 않은 구글 계정입니다.')
          //navigate('/auth/signup')
        }
      }
      // 사용자 정보가 없을 경우
      else {
        console.log('user정보가 없을 때')
        if(ssg.getItem('email')){
          // 세션 스토리지에 있는 값 모두 삭제하기
          ssg.clear()
          window.location.reload()
        } // end of inner if
      } // end of else
    }
    asyncDB()
  }, [dispatch])

  return (
    <>
      <div style={{height: '100vh'}}>
        {toastStatus.status && <Toast />}
        <Routes>
          <Route path='/login' exact={true} element={<KhLoginPage authLogic={authLogic} />} />
          <Route path='/' exact={true} element={<HomePage />} />
          <Route path='/home' exact={true} element={<HomePage />} />
          <Route path='/auth/signup' exact={true} element={<SignupPage authLogic={authLogic} />} />
          <Route path='/auth/emailVerified' exact={true} element={<EmailVerifiedPage authLogic={authLogic} />} />
          <Route path='/auth/findEmail' exact={true} element={<FindEmailPage />} />
          <Route path='/auth/resetPwd' exact={true} element={<ResetPwdPage authLogic={authLogic} />} />
          <Route path='/repleboard' element={<RepleBoardPage />} />
          <Route path='/dept/:gubun' element={<DeptPage imageUploader={imageUploader} />} />
          {/* 컴포넌트 함수를 호출하는 것이다 - 마운트(화면이 보여지는 것) - return이 호출되었다 */}
          <Route path='/deptdetail/:deptno' element={<DeptDetail imageUploader={imageUploader} />} />
          <Route path='/auth/kakao/callback' exact={true} element={<KakaoRedirectHandler />} />
          <Route path="/member" exact={true} element={<MemberPage imageUploader={imageUploader} />} />
          <Route path="/profile" exact={true} element={<Profile />} />
        </Routes>
      </div>
    </>
  );
}

export default App;

 

<이메일+구글 로그인 회원가입 - HomePage.jsx>

import React from 'react'
import { Button } from 'react-bootstrap'
import { useNavigate } from 'react-router-dom'
import BlogHeader from '../include/BlogHeader'
import KakaoMap from '../kakao/KakaoMap'
import {ContainerDiv, FormDiv, HeaderDiv} from '../styles/FormStyle'

const HomePage = () => {
  const member = window.localStorage.getItem('member')
  console.log(JSON.parse(member))
  const jsonDoc = JSON.parse(member)
  //console.log(jsonDoc.mem_id + ', ' + jsonDoc.mem_pw)
  const navigate = useNavigate()
  const handleLogin = () => {
    console.log('로그인 요청');
    navigate('/login')
  }

  return (
    <>
      <ContainerDiv>
        <BlogHeader />
        <HeaderDiv>
          <h1 style={{marginLeft:"10px"}}>터짐블로그</h1>
          <Button onClick={handleLogin}>로그인</Button>
        </HeaderDiv>
        <FormDiv>
          <div>이벤트존</div>
          <hr style={{height:"2px"}} />
          <div>추천 수업존</div>
          <hr style={{height:"2px"}} />
          <div><KakaoMap /></div>
          <div>카카오맵존</div>
          <hr style={{height:"2px"}} />
        </FormDiv>
      </ContainerDiv>
    </>
  )
}

export default HomePage

 

<이메일+구글 로그인 회원가입 - KhLoginPage.jsx>

import React, { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { loginEmail, loginGoogle } from '../../service/authLogic';
import { DividerDiv, DividerHr, DividerSpan, GoogleButton, LoginForm, MyH1, MyInput, MyLabel, MyP, PwEye, SubmitButton } from '../styles/FormStyle';

const KhLoginPage = ({authLogic}) => {
  console.log('LoginPage'); // a태그 사용하지 않기 -> Link(react-router-dom), useNavigate
  const navigate = useNavigate()
  const auth = authLogic.getUserAuth()

  const[submitBtn, setSubmitBtn] = useState({
    disabled: true,
    bgColor: 'rgb(175, 210, 244)',
    hover: false
  });

  const [tempUser, setTempUser] = useState({
    email: '',
    password: ''
  });

  const [passwordType, setPasswordType] = useState({
      type:'password',
      visible:false
  });

  useEffect(()=> {
    if(tempUser.email!==""&&tempUser.password!==""){ 
      setSubmitBtn({disabled:false, bgColor: 'rgb(105, 175, 245)'});
    } else {
      setSubmitBtn({disabled:true, bgColor: 'rgb(175, 210, 244)'});
    }
  },[tempUser]);

  const changeUser = (e) => {
    const id = e.currentTarget.id;
    const value = e.target.value;
    setTempUser({...tempUser, [id]: value});
  };

  const passwordView = (e) => {
    const id = e.currentTarget.id;
    if(id==="password") {
      if(!passwordType.visible) {
        setPasswordType({...passwordType, type: 'text', visible: true});
      } else {
        setPasswordType({...passwordType, type: 'password', visible: false});
      }
    }
  };

  const toggleHover = () => {
    if(submitBtn.hover){
      setSubmitBtn({...submitBtn, hover: false, bgColor: 'rgb(105, 175, 245)'});
    } else {
      setSubmitBtn({...submitBtn, hover: true, bgColor: 'rgb(58, 129, 200)'});
    }
  }

  const loginE = async() => {
    // 이메일 로그인 구현
    console.log(tempUser)
    try {
      const result = await loginEmail(auth, tempUser)
      console.log(result)
      console.log(result.user.uid)
      window.sessionStorage.setItem('userId', result.user.uid)
      window.localStorage.setItem('userId', result.user.uid)
      // window.localStorage.setItem('member', JSON.stringify({'mem_id': 'test', 'mem_pw': '123'}))
      // 현재 내가 바라보는 URL /login
      navigate("/") // Route path="/" HomePage
      window.location.reload()
    } catch (error) {
      console.log(error + ": 로그인 에러")
    }
  }

  const loginG = async() => {
    // 구글 로그인 구현
    try {
      const result = await loginGoogle(authLogic.getUserAuth(), authLogic.getGoogleAuthProvider())
      console.log(result)
      //window.sessionStorage.setItem('userId', result.uid)
      navigate("/")
      window.location.reload()
    } catch (error) {
      console.log(error + ': 구글 로그인 에러')
    }
  }
  return (
    <>
      <LoginForm>
        <MyH1>로그인</MyH1>
        <MyLabel htmlFor="email"> 이메일
          <MyInput type="email" id="email" name="mem_email" placeholder="이메일를 입력해주세요." 
            onChange={(e)=>changeUser(e)} />
        </MyLabel>
        <MyLabel htmlFor="password"> 비밀번호
          <MyInput type={passwordType.type} autoComplete="off" id="password" name="mem_password" placeholder="비밀번호를 입력해주세요."
            onChange={(e)=>changeUser(e)}/>
          <div id="password" onClick={(e)=> {passwordView(e)}} style={{color: `${passwordType.visible?"gray":"lightgray"}`}}>
            <PwEye className="fa fa-eye fa-lg"></PwEye>
          </div>
        </MyLabel>
        <SubmitButton type="button"  disabled={submitBtn.disabled} style={{backgroundColor:submitBtn.bgColor}}  
          onMouseEnter={toggleHover} onMouseLeave={toggleHover} onClick={()=>{loginE()}}>
          로그인
        </SubmitButton>
        <DividerDiv>
          <DividerHr />
          <DividerSpan>또는</DividerSpan>
        </DividerDiv>
        <GoogleButton type="button" onClick={()=>{loginG();}}>
          <i className= "fab fa-google-plus-g" style={{color: "red", fontSize: "18px"}}></i>&nbsp;&nbsp;Google 로그인
        </GoogleButton>
        <MyP style={{marginTop:"30px"}}>신규 사용자이신가요?&nbsp;<Link to="/auth/signup" className="text-decoration-none" style={{color: "blue"}}>계정 만들기</Link></MyP>
        <MyP>이메일를 잊으셨나요?&nbsp;<Link to="/auth/findEmail" className="text-decoration-none" style={{color: "blue"}}>이메일 찾기</Link></MyP>
        <MyP>비밀번호를 잊으셨나요?&nbsp;<Link to="/auth/resetPwd" className="text-decoration-none" style={{color: "blue"}}>비밀번호 변경</Link></MyP>
      </LoginForm>
    </>
  );
}

export default KhLoginPage;

 

<이메일+구글 로그인 회원가입 - EmailVerifiedPage.jsx>

import React from 'react'
import { useNavigate } from 'react-router-dom';
import { logout, sendEmail } from '../../service/authLogic';
import { MyButton, SignupForm } from '../styles/FormStyle';

const EmailVerifiedPage = ({authLogic}) => {

  const navigate = useNavigate();
  const auth = authLogic.getUserAuth();
  const send = async() => {
    //const user = await onAuthChange(userAuth.auth);
    const msg = await sendEmail(auth.currentUser);
    console.log(msg);
  }

  const out = async() => {
    await logout(auth);
    navigate('/');
  }

  return (
    <>
      <SignupForm style={{marginTop : '100px'}}>
        <h4>{sessionStorage.getItem('nickname')} 회원님의 이메일은 아직 사용할 수 없습니다.</h4>
        <p>
          해당 이메일함에서 인증 메일을 확인해주세요.
        </p>
        <div style={{display: 'flex', width: '50%', justifyContent: 'space-between'}}>
          <MyButton type="button" onClick={()=>{ send(); }} variant="secondary">이메일 재전송</MyButton>
          <MyButton type="button" onClick={()=>{ navigate('/'); window.location.reload();}} variant="secondary">완료</MyButton>
          <MyButton type="button" onClick={()=>{ out(); }} variant="secondary">로그아웃</MyButton>
        </div>
      </SignupForm>
    </>
  );
};

export default EmailVerifiedPage;

 

<이메일+구글 로그인 회원가입 - KhSignup.jsx>

/* global daum */
import React, { useEffect, useState } from 'react';
import { Form } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { memberInsertDB, memberListDB } from '../../service/dbLogic';
import { linkEmail, onAuthChange, signupEmail } from '../../service/authLogic';
import { checkPassword, validateBirthdate, validateEmail, validateHp, validateName, validateNickname, validatePassword } from '../../service/validateLogic';
import { SignupForm, MyH1, MyInput, MyLabel,  MyLabelAb, SubmitButton, MyButton, PwEye, WarningButton} from '../styles/FormStyle';

const KhSignup = ({authLogic}) => {
  const auth = authLogic.getUserAuth();
  const userAuth = useSelector(state => state.userAuth);
  //window.location.search는 ?뒤의 쿼리스트링을 가져옴
  //
  //http://localhost:3000/login/signup?type=member
  const type = window.location.search.split('&')[0].split('=')[1];//member담김
  //console.log(window.location.search.split('&'));//['?type=member']
  //console.log(window.location.search.split('&')[0]);//?type=member
  //console.log(window.location.search.split('&')[0].split('='));//['?type', 'member']
  //console.log(window.location.search.split('&')[0].split('=')[0]);//?type
  //console.log(window.location.search.split('&')[0].split('=')[1]);//member
  //console.log(type);
  const [reSignCheck, setReSignCheck] = useState(false);
  const navigate = useNavigate();

  const[submitBtn, setSubmitBtn] = useState({
    disabled: true,
    bgColor: 'rgb(175, 210, 244)',
    hover: false
  });

  const toggleHover = () => {
    if(submitBtn.hover){
      setSubmitBtn({...submitBtn, hover: false, bgColor: 'rgb(105, 175, 245)'});
    } else {
      setSubmitBtn({...submitBtn, hover: true, bgColor: 'rgb(72, 145, 218)'});
    }
  }


  const[post, setPost] = useState({
    zipcode: "",
    addr: "",
    addrDetail: ""
  })

  const [memInfo, setMemInfo] = useState({
    email: "",
    password: "",
    password2: "",
    name: "",
    birthday: "",
    hp: "",
    nickname: "",
    gender: "없음"
  });

  const [comment, setComment] = useState({
    email: "",
    password: "",
    password2: "",
    name: "",
    birthday: "",
    hp: "",
    nickname: ""
  });

  const [star,setStar] = useState({
    email: "*",
    password: "*",
    password2: "*",
    name: "*",
    hp: "*",
    nickname: "*",
    birthday: "*"
  })


  const [passwordType, setPasswordType] = useState([
    {
      type:'password',
      visible:false
    },
    {
      type:'password',
      visible:false
    }
  ]);


  const [googleEmail, setGoogleEmail] = useState('');


  useEffect(()=> {
    let check = true;
    Object.keys(star).forEach((key)=>{
      if(star[key]==='*') check = false;
    })
    if(check){ 
      setSubmitBtn({disabled:false, bgColor: 'rgb(105, 175, 245)'});
    } else {
      setSubmitBtn({disabled:true, bgColor: 'rgb(175, 210, 244)'});
    }
  },[star]);

  useEffect(()=> {
    const onAuth = async() => {
      const user = await onAuthChange(userAuth.auth) ;
      if(user){
        setGoogleEmail(user.email);
        setStar({
          email: "",
          password: "*",
          password2: "*",
          name: "*",
          hp: "*",
          nickname: "*",
          birthday: "*"
        });
        setMemInfo({
          email: user.email,
          password: "",
          password2: "",
          name: "",
          hp: "",
          nickname: "",
          birthday: "",
          gender:"없음"
        });
      }
    };
    onAuth();
  },[setGoogleEmail,setStar,setMemInfo, userAuth.auth]);


  const passwordView = (e) => {
    const id = e.currentTarget.id;
    if(id==="password") {
      if(!passwordType[0].visible) {
        setPasswordType([{type: 'text', visible: true},passwordType[1]]);
      } else {
        setPasswordType([{type: 'password', visible: false},passwordType[1]]);
      }
    } else if(id==="password2") {
      if(!passwordType[1].visible) {
        setPasswordType([passwordType[0],{type: 'text', visible: true}]);
      } else {
        setPasswordType([passwordType[0],{type: 'password', visible: false}]);
      }
    }
  }

  const changeMemInfo = (e) => {
    console.log('changeMemInfo');
    const id = e.currentTarget.id;
    console.log(id);
    const value = e.target.value;
    console.log(value);
    setMemInfo({...memInfo, [id]: value});
  }
  //닉네임 중복확인 
  const overlap = async(key) => {
    console.log('닉네임 중복확인');
    let params;
    if(key === 'email') {
      params = {MEM_EMAIL: memInfo[key], type: 'overlap'}
    } else if(key === 'nickname') {
      params = {MEM_NICKNAME: memInfo[key], type: 'overlap'}
    }
    console.log(params)
    let response = {data: 0}
    response = await memberListDB(params)
    console.log(response.data)
    /*
    Array(1)
    0: {MEM_UID: 'kiwi', MEM_NAME: '강감찬'}
    */
    const data = JSON.stringify(response.data)
    const jsonDoc = JSON.parse(data)
    if(jsonDoc){
      console.log(jsonDoc[0].MEM_NAME)
    } else {
      console.log('존재하지 않음')
    }
    // 닉네임이 존재할 때
    if(response.data){
      console.log('중복')
    }
    // 닉네임이 없을 때
    else {
      console.log('가능')
    }
  }

  const validate = (key, e) => {
    let result;
    if(key==='email'){
      result = validateEmail(e); 
    } else if(key==='nickname'){
      result = validateNickname(e); 
    } else if(key==='password'){
      result = validatePassword(e); 
    } else if(key==='password2'){
      result = checkPassword(memInfo.password, e); 
    } else if(key==='name'){
      result = validateName(e); 
    } else if(key==='hp'){
      result = validateHp(e); 
    } else if(key==='birthday'){
      result = validateBirthdate(e); 
    } 
    setComment({...comment, [key]: result}); 
    if(result){
      if(result===' '){
        setStar({...star, [key]:""});
      } else {
        setStar({...star, [key]:"*"});
      }
    }else {
      setStar({...star, [key]:""});
    }
  }
  //다음 우편번호 검색기 구현
  const openZipcode = () => {
    new daum.Postcode({
      oncomplete: function(data) {
        let addr = ''; 
        if (data.userSelectedType === 'R') { 
          addr = data.roadAddress;//도로명
        } else { 
          addr = data.jibunAddress;//지번
        }
        console.log(data);
        console.log(addr);
        console.log(post.postNum);
        setPost({...post, zipcode:data.zonecode, addr:addr}) ;
        document.getElementById("zipcode").value = data.zonecode;
        document.getElementById("addr").value = addr;
        document.getElementById("addrDetail").focus();
      }
    }).open();
  }
  //회원정보 수정하기 구현 예정
  const signUpdate = () => {

  }

  //회원 가입 구현
  const signup = async() => {
    console.log('회원가입 구현');
    try {
      let uid;
      console.log(googleEmail);
      if(googleEmail){
        console.log(auth); 
        console.log(memInfo); 
        uid = await linkEmail(auth, memInfo);
        console.log(uid);
      } else {
        uid = await signupEmail(auth, memInfo);
      }
      console.log(uid);
      //const pwd = pwdEncrypt(memInfo.password);
      const b = memInfo.birthday;
      let birthday = ""; 
      if(b!==""){
        birthday = b.slice(0,4) + '-' + b.slice(4, 6) + '-' + b.slice(6,8);
      }
      console.log('입력받은 생일정보 '+birthday);
      const datas = {
        MEM_UID: uid,
        MEM_NAME: memInfo.name,
        MEM_PW: memInfo.password,
        MEM_EMAIL: memInfo.email,
        MEM_BIRTHDAY: birthday,
        MEM_TEL: memInfo.hp,
        MEM_NICKNAME: memInfo.nickname,
        MEM_ZIPCODE: post.zipcode,
        MEM_ADDR: post.addr,
        MEM_ADDR_DTL: post.addrDetail,
        MEM_STATUS: 0,
        MEM_AUTH: (type === 'member' ? 'member' : 'teacher'),
        MEM_GENDER: memInfo.gender
      }
      console.log(datas)
      const response = await memberInsertDB(datas);
      console.log(response);
      if(response.data!==1) {
      return "DB 오류: 관리자에게 연락바랍니다.";
    }
      sessionStorage.clear();
      navigate('/');
      return "회원가입되었습니다. 감사합니다.";
      
    } catch (error) {
      console.log(error+" 오류: 관리자에게 연락바랍니다.");
    }
  }//end of 회원가입 구현
  
  const checkboxLable = ['없음','남자','여자']

  const Checkbox = checkboxLable.map((item, index) => (
    <Form.Check inline label={item} value={item} name="group1" type='radio' checked={memInfo.gender===item?true:false} readOnly
    id={`inline-radio-${item}`} key={index} onClick={(e)=> {setMemInfo({...memInfo, gender: e.target.value})}}/>
  ))
  
  const handleSignup = (event) => {
    console.log('가입하기 또는 수정하기 버튼클릭 이벤트');
    signup()    
  }

  return (
    <div style={{display: 'flex', justifyContent: 'center'}}>
      <div style={{display: 'flex', width:"100%"}}>
        <SignupForm  suggested={false}>
          <MyH1>{'회원가입'}</MyH1>
          <div style={{display: 'flex', flexWrap: 'wrap', justifyContent: 'center'}}>
            <div style={{padding: '30px 30px 0px 30px'}}>
              { googleEmail
                ?
                <>
                  <MyLabel> 이메일 
                    <MyInput type="email" id="email" defaultValue={googleEmail} disabled={true} />
                  </MyLabel>
                </>
                :
                <div style={{display: 'flex' , flexWrap: 'wrap'}}>
                  <MyLabel> 이메일 <span style={{color:"red"}}>{star.email}</span>
                    <MyInput type="email" id="email" placeholder="이메일를 입력해주세요." 
                    onChange={(e)=>{changeMemInfo(e); validate('email', e);}}/>
                    <MyLabelAb>{comment.email}</MyLabelAb>
                  </MyLabel>
                  <MyButton type="button" onClick={()=>{overlap('email');}}>중복확인</MyButton>
                </div>
              }
              <div style={{display: 'flex'}}>
                <MyLabel> 닉네임 <span style={{color:"red"}}>{star.nickname}</span>
                  <MyInput type="text" id="nickname" defaultValue={memInfo.nickname} placeholder="닉네임을 입력해주세요." 
                  onChange={(e)=>{changeMemInfo(e); validate('nickname', e);}}/>
                  <MyLabelAb>{comment.nickname}</MyLabelAb>
                </MyLabel>
                <MyButton type="button" onClick={()=>{overlap('nickname')}}>중복확인</MyButton>
              </div>
              <MyLabel> 비밀번호 <span style={{color:"red"}}>{star.password}</span>
                <MyInput type={passwordType[0].type} id="password" autoComplete="off" placeholder="비밀번호를 입력해주세요." 
                onKeyUp={(e)=>{setComment({...comment, password2: checkPassword(e.target.value,memInfo.password2)});}} 
                onChange={(e)=>{changeMemInfo(e);  validate('password', e);}}/>
                <div id="password" onClick={(e)=> {passwordView(e)}} style={{color: `${passwordType[0].visible?"gray":"lightgray"}`}}>
                  <PwEye className="fa fa-eye fa-lg"></PwEye>
                </div>
                <MyLabelAb>{comment.password}</MyLabelAb>
              </MyLabel>
              <MyLabel> 비밀번호 확인 <span style={{color:"red"}}>{star.password2}</span>
                <MyInput type={passwordType[1].type} id="password2"  autoComplete="off" placeholder="비밀번호를 한번 더 입력해주세요."
                onChange={(e)=>{changeMemInfo(e); validate('password2', e.target.value)}}/>
                <div id="password2" onClick={(e)=> {passwordView(e)}} style={{color: `${passwordType[1].visible?"gray":"lightgray"}`}}>
                  <PwEye className="fa fa-eye fa-lg"></PwEye>
                </div>
                <MyLabelAb>{comment.password2}</MyLabelAb>
              </MyLabel>         
            </div>

            <div style={{padding: '30px 30px 0px 30px'}}>
              <MyLabel> 이름 <span style={{color:"red"}}>{star.name}</span>
                <MyInput type="text" id="name" defaultValue={memInfo.name} placeholder="이름을 입력해주세요." 
                onChange={(e)=>{changeMemInfo(e); validate('name', e);}}/>
                <MyLabelAb>{comment.name}</MyLabelAb>
              </MyLabel>
              <MyLabel> 전화번호 <span style={{color:"red"}}>{star.hp}</span>
                <MyInput type="text" id="hp" defaultValue={memInfo.hp} placeholder="전화번호를 입력해주세요." 
                onChange={(e)=>{changeMemInfo(e); validate('hp', e);}} />
                <MyLabelAb>{comment.hp}</MyLabelAb>
              </MyLabel>

              <MyLabel> 생년월일 <span style={{color:"red"}}>{star.birthday}</span>
                <MyInput type="text" id="birthday" defaultValue={memInfo.birthday} placeholder="생년월일을 입력해주세요." 
                onChange={(e)=>{changeMemInfo(e); validate('birthday', e);}}/>
                <MyLabelAb>{comment.birthday}</MyLabelAb>
              </MyLabel>

              <MyLabel> 우편번호
                <MyInput type="text" id="zipcode" defaultValue={memInfo.zipcode} placeholder="우편번호를 입력해주세요." 
                onChange={(e)=>{changeMemInfo(e);}} />
                <MyLabelAb>{comment.zipcode}</MyLabelAb>
              </MyLabel>

              <div style={{display: 'flex'}}>
                <MyLabel> 주소
                  <MyInput type="text" id="addr" defaultValue={post.addr} readOnly placeholder="주소검색을 해주세요."/>
                </MyLabel>
                <MyButton type="button" onClick={()=>{openZipcode()}}>주소검색</MyButton>
              </div>
              <MyLabel> 상세주소
                <MyInput type="text" id="addrDetail" defaultValue={post.addrDetail} readOnly={post.addr?false:true}
                onChange={(e)=>{setPost({...post, addrDetail : e.target.value})}}/>
              </MyLabel>
            </div>
          </div>
          <MyLabel style={{margin:0}}> 성별
            <div style={{marginTop:10}} key={`inline-radio`} className="mb-3">
              {Checkbox}
            </div>
          </MyLabel>
            <SubmitButton type="button" style={{backgroundColor:submitBtn.bgColor }}
            onClick={handleSignup} onMouseEnter={toggleHover} onMouseLeave={toggleHover}>
              {'가입하기'}
            </SubmitButton>
        </SignupForm>
      </div>
    </div>
  );
};

export default KhSignup;

 

<이메일+구글 로그인 회원가입 - FindEmailPage.jsx>

import React from 'react'
import { useEffect } from 'react';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { setToastMsg } from '../../redux/toastStatus/action';
import { memberListDB } from '../../service/dbLogic';
import { LoginForm, MyH1, MyInput, MyLabel, SubmitButton } from '../styles/FormStyle';

const FindEmailPage = () => {

  const navigate = useNavigate();
  const dispatch = useDispatch();


  const[submitBtn, setSubmitBtn] = useState({
    disabled: true,
    bgColor: 'rgb(175, 210, 244)',
    hover: false
  });

  const toggleHover = () => {
    if(submitBtn.hover){
      setSubmitBtn({...submitBtn, hover: false, bgColor: 'rgb(105, 175, 245)'});
    } else {
      setSubmitBtn({...submitBtn, hover: true, bgColor: 'rgb(72, 145, 218)'});
    }
  }

  const [memInfo, setMemInfo] = useState({
    name: "",
    hp: "",
  });


  useEffect(()=> {
    if(memInfo.name&&memInfo.hp){ 
      setSubmitBtn({disabled:false, bgColor: 'rgb(105, 175, 245)'});
    } else {
      setSubmitBtn({disabled:true, bgColor: 'rgb(175, 210, 244)'});
    }
  },[memInfo]);


  const changeMemInfo = (e) => {
    const id = e.currentTarget.id;
    const value = e.target.value;
    setMemInfo({...memInfo, [id]: value});
  }


  const find = async() => {
    const member = {
      mem_name : memInfo.name,
      mem_tel : memInfo.hp,
      type : 'email',
    }
    try {
      console.log(member);
      const res = await memberListDB(member);
      console.log(res);
      if(res.data.length===0) {return dispatch(setToastMsg("일치하는 아이디가 없습니다."));}
      let msg = '회원님의 이메일 목록입니다.';
      Object.keys(res.data).forEach((key)=> {
        console.log(res.data[key]);
        console.log(res.data[key].MEM_EMAIL);
        msg+=`\n[ ${res.data[key].MEM_EMAIL} ]`;
      })
      dispatch(setToastMsg(msg));
      console.log(msg);
      navigate('/login');
    } catch (error) {
      dispatch(setToastMsg(error+": DB 오류입니다."));
    }
  }


  return (
      <LoginForm>
        <MyH1>이메일 찾기</MyH1>
        <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center',Content: 'center', marginTop: '20px', width:"100%"}}>
          <MyLabel> 이름
            <MyInput type="text" id="name" placeholder="이름을 입력해주세요." 
            onChange={(e)=>{changeMemInfo(e);}}/>
          </MyLabel>
          <MyLabel> 전화번호
            <MyInput type="number" id="hp" placeholder="전화번호를 입력해주세요." 
            onChange={(e)=>{changeMemInfo(e);}} />
          </MyLabel>
          <SubmitButton type="button"  disabled={submitBtn.disabled} style={{backgroundColor:submitBtn.bgColor }}
                onClick={()=>{find();}} onMouseEnter={toggleHover} onMouseLeave={toggleHover}>
                  찾기
          </SubmitButton>
        </div>
      </LoginForm>
  );
};

export default FindEmailPage;

 

<이메일+구글 로그인 회원가입 - ResetPwdPage.jsx>

import React, {useEffect, useState} from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { setToastMsg } from "../../redux/toastStatus/action";
import { sendResetpwEmail } from "../../service/authLogic";
import { memberListDB } from "../../service/dbLogic";
import { LoginForm, MyH1, MyInput, MyLabel, SubmitButton } from "../styles/FormStyle";

const ResetPwdPage = ({authLogic}) => {

  const navigate = useNavigate();
  const dispatch = useDispatch();
  const userAuth = useSelector(state => state.userAuth);
  const[submitBtn, setSubmitBtn] = useState({
    disabled: true,
    bgColor: 'rgb(175, 210, 244)',
    hover: false
  });

  const toggleHover = () => {
    if(submitBtn.hover){
      setSubmitBtn({...submitBtn, hover: false, bgColor: 'rgb(105, 175, 245)'});
    } else {
      setSubmitBtn({...submitBtn, hover: true, bgColor: 'rgb(72, 145, 218)'});
    }
  }

  const [memInfo, setMemInfo] = useState({
    name: "",
    hp: "",
    email: ""
  });

  useEffect(()=> {
    if(memInfo.email&&memInfo.hp&&memInfo.name){ 
      setSubmitBtn({disabled:false, bgColor: 'rgb(105, 175, 245)'});
    } else {
      setSubmitBtn({disabled:true, bgColor: 'rgb(175, 210, 244)'});
    }
  },[memInfo]);


  const changeMemInfo = (e) => {
    const id = e.currentTarget.id;
    const value = e.target.value;
    setMemInfo({...memInfo, [id]: value});
  }

  const send = async () => {
    console.log('비번찾기메일전');
    const member = {
      mem_name : memInfo.name,
      mem_tel : memInfo.hp,
      mem_email : memInfo.email,
      type : 'overlap',
    }
    console.log(member);
    const res = await memberListDB(member);
    console.log(res.data);
    const temp = JSON.stringify(res.data)
    const jsonDoc = JSON.parse(temp)
    console.log(jsonDoc[0]);
    //if(res.data!==1) return dispatch(setToastMsg("일치하는 아이디가 없습니다."));
    if(!jsonDoc[0]) return console.log("일치하는 아이디가 없습니다.");
    else console.log('일치하는 아이디가 있습니다.');
    try {
      const msg = await sendResetpwEmail(authLogic.auth, memInfo.email);
      console.log(msg);
      dispatch(setToastMsg(msg));
      navigate('/login');
    } catch (error) {
      dispatch(setToastMsg(error+": 메일전송 오류입니다."));
    }

  }

  return (
    <LoginForm>
      <MyH1>비밀번호 변경</MyH1>
      <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center',Content: 'center', marginTop: '20px', width:"100%"}}>
        <MyLabel> 이름 
          <MyInput type="text" id="name" placeholder="이름을 입력해주세요." 
          onChange={(e)=>{changeMemInfo(e);}}/>
        </MyLabel>
        <MyLabel> 전화번호
          <MyInput type="number" id="hp" placeholder="전화번호를 입력해주세요." 
          onChange={(e)=>{changeMemInfo(e);}} />
        </MyLabel>
        <MyLabel> 이메일
          <MyInput type="email" id="email" placeholder="이메일를 입력해주세요." 
          onChange={(e)=>{changeMemInfo(e);}}/>
        </MyLabel>
        <SubmitButton type="button"  disabled={submitBtn.disabled} style={{backgroundColor:submitBtn.bgColor }}
          onClick={()=>{send()}} onMouseEnter={toggleHover} onMouseLeave={toggleHover}>
            메일 전송
        </SubmitButton>
      </div>
    </LoginForm>
  );
};

export default ResetPwdPage;

 

<이메일+구글 로그인 회원가입 - authLogic.js>

import { createUserWithEmailAndPassword, EmailAuthProvider, getAuth, GoogleAuthProvider, sendEmailVerification, sendPasswordResetEmail, signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth'

class AuthLogic { // 클래스 선언
  // 생성자 - 전역변수 초기화
  constructor() {
    this.auth = getAuth()
    this.googleProvider = new GoogleAuthProvider()
    // this.kakaoProvider = new KakaoAuthProvider()
    // this.githubProvider = new GithubAuthProvider()
  }
  getUserAuth = () => {
    return this.auth
  }
  getGoogleAuthProvider = () => {
    return this.googleProvider
  }
}
export default AuthLogic // 외부 js에서 사용할 때

// 사용자가 변경되는지 지속적을 체크하영 변경될때마다 호출됨 - 구글서버에서 제공하는 서비스
// 콜백함수
export const onAuthChange = (auth) => {
  return new Promise((resolve) => { // 비동기 서비스 구현
    // 사용자가 바뀌었을 때 콜백함수를 받아서
    auth.onAuthStateChanged((user) => { // 파라미터 주입
    resolve(user) // 내보내지는 정보 - View계층 - App.jsx
    });
  }) // end of Promise
} // end of onAuthChange

// 로그아웃 버튼 클릭시 호출하기
export const logout = (auth) => {
  return new Promise((resolve, reject) => {
    auth.signOut().catch(e => reject(e + '로그아웃 오류입니다.'))
    // 우리회사가 제공하는 서비스를 누리기 위해서는 구글에서 제공하는 기본 정보 외에
    // 추가로 필요한 정보가 있다 - 테이블설계에 반영 - 세션에 담음
    // 로그인 성공시 세션 스토리지에 담아둔 정보를 모두 지운다
    window.sessionStorage.clear();
    // 서비스를 더 이상 사용하지 않는 경우이므로 돌려줄 값은 없다
    resolve(); // 그러므로 파라미터를 비움
  })
} // end of logout

// 이메일과 비밀번호로 회원가입 신청을 한 경우 로그인 처리하는 함수
// auth - AuthLogic클래스 생성자에서 getAuth()로 받아오는 전역변수 auth
// user - email, password
export const loginEmail = (auth, user) => {
  console.log(auth)
  console.log(user.email + user.password)
  return new Promise((resolve, reject) => {
    signInWithEmailAndPassword(auth, user.email, user.password)
    .then((userCredential) => {
      // Signed in
      const user = userCredential.user;
      console.log(user)
      resolve(userCredential)
    })
    .catch((error) => {
      const errorCode = error.code;
      const errorMessage = error.message;
      console.log(errorCode + ", " + errorMessage)
      reject(error)
    });
  })
}

 // 로그인 시도시 구글인증인지 아니면 깃허브 인증인지 문자열로 넘겨받음
  // 구글 인증인 경우 - Google
  // 깃허브 인증인 경우 - Github
  export const loginGoogle = (auth, googleProvider) => {
    return new Promise((resolve, reject) => {
      signInWithPopup(auth, googleProvider) // 팝업 열림
      .then((result) => { // 콜백이 진행됨
        const user = result.user; // 구글에 등록되어있는 profile정보가 담겨있음
        console.log(user);
        resolve(user) // 인증된 사용자 프로필 정보도 화면쪽으로 내보낸다
      })
      .catch(e => reject(e))
    })
  }

  export const signupEmail = (auth, user) => {
    return new Promise((resolve, reject) => {
      createUserWithEmailAndPassword(auth, user.email, user.password)
        .then((userCredential) => {
          sendEmail(userCredential.user).then(() => {
            resolve(userCredential.user.uid);
          });
        })
        .catch((e) => reject(e));
    });
  };

  export const linkEmail = (auth, user) => {
    console.log(auth);
    console.log(auth.currentUser);
    console.log(user);
    return new Promise((resolve, reject) => {
      console.log(user.email + "," + user.password);
      const credential = EmailAuthProvider.credential(user.email, user.password);
      console.log(credential);
      console.log(auth.currentUser.uid);
      resolve(auth.currentUser.uid)
      /* 인증정보가 다른 사용자 계정에 이미 연결되어 있다면 아래 코드 에러 발생함
      linkWithCredential(auth.currentUser, credential)
        .then((usercred) => {
          console.log(usercred);
          const user = usercred.user;
          console.log("Account linking success", user.uid);
          resolve(user.uid);
        })
        .catch((e) => reject(e));
      */
    });
  };

  export const sendEmail = (user) => {
    return new Promise((resolve, reject) => {
      sendEmailVerification(user)
        .then(() => {
          resolve("해당 이메일에서 인증메세지를 확인 후 다시 로그인 해주세요.");
        })
        .catch((e) => reject(e + ": 인증메일 오류입니다."));
    });
  };

  export const sendResetpwEmail = (auth, email) => {
    console.log(email)
    return new Promise((resolve, reject) => {
      sendPasswordResetEmail(auth, email)
      .then(()=> {
        resolve('비밀번호 변경이메일을 전송했습니다.')
      })
      .catch((e) => reject(e))
    })
  }

 

<이메일+구글 로그인 회원가입 - RestMemberController.java>

package com.example.demo.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.logic.BoardLogic;
import com.example.demo.logic.MemberLogic;
import com.google.gson.Gson;

@RestController
@RequestMapping("/member/*")
public class RestMemberController {
	// Logger logger = LoggerFactory.getLogger(RestMemberController.class);
	Logger logger = LogManager.getLogger(RestMemberController.class);
	
	@Autowired
	private MemberLogic memberLogic = null;
	
	/**
	 * 회원 목록
	 * http://localhost:8000/member/memberList?MEM_NICKNAME=&type=overlap
	 * 리액트 프로젝트에서 닉네임 중복검사시 사용하는 메소드 구현
	 * 리액트에서 넘기는 파라미너는 {MEM_NICKNAME: 'qwerty', type: 'overlap'}
	 * @param pMap
	 * @return
	 */
	@GetMapping("memberList")
	public String memberList(@RequestParam Map<String, Object> pMap) {
		logger.info("memberList 호출");
		logger.info(pMap);
		String temp = null;
		List<Map<String, Object>> mList = new ArrayList<>();
		mList = memberLogic.memberList(pMap);
		logger.info(mList);
		// 파라미터로 넘어온 닉네임이 회원집합에 존재하면 조회결과가 있다 -> mList.size() === 1
		// temp에 문자열이 들어있으면 자바스크립트쪽에서는 true로 판정된다 - 주의!
		if(mList.size() > 0) {
			Gson g = new Gson();
			temp = g.toJson(mList);			
		}
		// mList.size() === 0
		// uid가 없을때 - 회원가입 유도
		else {
			temp = "0";
		}
		return temp;
	}
	
	/**
	 * 회원 등록
	 * @param pMap
	 * @return
	 */
	@PostMapping("memberInsert")
	public String memberInsert(@RequestBody Map<String, Object> pMap) {
		// 리액트에서 body에 {}객체리터럴로 넘겨준 정보를 Map이나 VO에 담을 수 있다
		logger.info("memberInsert 호출");
		logger.info(pMap);
		int result = 0;
		result = memberLogic.memberInsert(pMap);
		return String.valueOf(result);
	}
	
	/**
	 * 회원 정보 수정
	 * @param pMap
	 * @return
	 */
	@PostMapping("memberUpdate")
	public String memberUpdate(@RequestBody Map<String, Object> pMap) {
		logger.info("memberUpdate 호출");
		logger.info(pMap);
		int result = 0;
		result = memberLogic.memberUpdate(pMap);
		return String.valueOf(result);
	}
	
	/**
	 * 회원 정보 삭제
	 * @param pMap
	 * @return
	 */
	@GetMapping("memberDelete")
	public String memberDelete(@RequestParam Map<String, Object> pMap) {
		logger.info("memberDelete 호출");
		logger.info(pMap);
		int result = 0;
		result = memberLogic.memberDelete(pMap);
		return String.valueOf(result);
	}
}

 

<이메일+구글 로그인 회원가입 - member.xml>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo">
	<select id="getToday" resultType="string" parameterType="string">
		SELECT
		to_char(sysdate, 'YYYY-MM-DD') FROM dual
	</select>

	<select id="login" parameterType="map" resultType="string">
		select mem_name from member230324
		<where>
			<if test='mem_uid!=null and mem_uid.length()>0'>
				AND mem_uid = #{mem_uid}
			</if>
			<if test='mem_pw!=null and mem_pw.length()>0'>
				AND mem_pw = #{mem_pw}
			</if>
		</where>
	</select>

	<!-- 회원 목록 - 닉네임 중복확인 -->
	<select id="memberList" parameterType="map" resultType="map">
		select mem_uid, mem_name, mem_email from member230324
		<where>
			<if test='mem_uid!=null and mem_uid.length()>0'>
				AND mem_uid = #{mem_uid}
			</if>
			<!-- 
				<input type=text id="mem_nickname" value="" />
				항상 무조건 빈문자열이다 - 폼전송하면 무조건 빈문자열이 있는 상태이다
				아무것도 입력하지 않아도 null에 걸리지 않는다
				잡아내려면 문자열>0까지 비교해야한다
			 -->
			<if test='MEM_NICKNAME!=null and MEM_NICKNAME.length()>0'>
				AND mem_nickname = #{MEM_NICKNAME}
			</if>
			<if test='mem_name != null and mem_name.length()>0'>
				AND mem_name = #{mem_name}
			</if>
			<if test='mem_tel != null and mem_tel.length()>0'>
				AND mem_tel = #{mem_tel}
			</if>
		</where>
	</select>

	<!-- 회원 정보 입력 -->
	<insert id="memberInsert" parameterType="map">
		INSERT INTO member230324(
						 mem_no
						,mem_uid
						,mem_pw
						,mem_name
						,mem_nickname
						,mem_email
						,mem_tel
						,mem_gender
						,mem_birthday
						,mem_zipcode
						,mem_addr
						,mem_addr_dtl
						,mem_auth
						,mem_status
					)
		VALUES (seq_member_no.nextval
					<if test="MEM_UID != null">
						,#{MEM_UID}
					</if>
					<if test="MEM_PW != null">
						,#{MEM_PW}
					</if>
					<if test="MEM_NAME != null">
						,#{MEM_NAME}
					</if>
					<if test="MEM_NICKNAME != null">
						,#{MEM_NICKNAME}
					</if>
					<if test="MEM_EMAIL != null">
						,#{MEM_EMAIL}
					</if>
					<if test="MEM_TEL != null">
						,#{MEM_TEL}
					</if>
					<if test="MEM_GENDER != null">
						,#{MEM_GENDER}
					</if>
					<if test="MEM_BIRTHDAY != null">
						,#{MEM_BIRTHDAY}
					</if>
					<if test="MEM_ZIPCODE != null">
						,#{MEM_ZIPCODE}
					</if>
					<if test="MEM_ADDR != null">
						,#{MEM_ADDR}
					</if>
					<if test="MEM_ADDR_DTL != null">
						,#{MEM_ADDR_DTL}
					</if>
					<if test="MEM_AUTH != null">
						,#{MEM_AUTH}
					</if>
					<if test="MEM_STATUS != null">
						,#{MEM_STATUS}
					</if>
					)
	</insert>
</mapper>

 

댓글형 게시판 - 컴포넌트 설계 - 화면설계 예시

page > RepleBoardPage.jsx - 게시판 전체 페이지 - /reple/board

 

board > RepleBoardList.jsx - 게시판 목록 페이지 - 라우터 해당없음 boards.map((board, index) => (<RepleBoardRow board={board}))

boardListDB

 

board > RepleBoardRow.jsx - 게시판 로우 출력 페이지 - 라우터 해당없음

한 개 로우는 RepleBoardList.jsx에서 props로 받아온다

 

board > RepleBoardDetail.jsx - 게시판 상세 보기 - /reple/boarddetail/:bm_no

boardListDB(board)

 

board > RepleBoardWriteForm.jsx - 글쓰기 페이지 - /reple/boardwrite

post방식 전송 - 첨부파일 처리(이전엔 cos.jar사용)

 

board > QuillEditor.jsx - 위지웤 지원

 

board > BoardHeader.jsx - 게시판 헤더

board > BoardFooter.jsx - 게시판 푸터

board > BoardSerch.jsx - 게시판 검색지

board > BoardPagingNavBar.jsx - 게시판 페이징 처리

댓글