국비 지원 개발자 과정_Day83
데이터 → 상태(변하는 것) → state.js에서 관리 → 변수({}, [{}])
Action - 시그널, 구분필요 → switch문 사용 → Action의 타입필요(Action.type)
type과 payload
Dispatcher - 허브, Store에 전달 → 경우의 수에 따라 Store에 payload가 전달됨
useDispatch(함수)훅 → 콜백함수(함수 안에 또 다른 함수)
Store - payload → 꾸러미, 변수(객체, 배열객체 등), 여러 가지 타입이 있음(payload.변수)
View - Store에서 처리된 것이 즉시 화면에 반영됨
<js 콜백연습 - callback1.js>
function first(param) {
console.log(param) // [Function]
param() // 전달된 함수 호출
}
function second() {
console.log(2)
}
first(second)
// 순서대로 처리해야할 때 아래와같이 사용
function func1() { // outter함수 - 클로저
let num = 0 // 선언된 변수
return () => { // 반환형이 함수인 경우
return ++num // 여기서(내부함수) 사용 가능
}
}
let account = func1()
console.log(account) // 괄호가 없으면 [Function]
console.log(account()) // 1
console.log(account()) // 2
function one() {
console.log(1)
}
function two() {
console.log(2)
}
one()
two()
<js복습 - test.js>
let global = -99 // 전역변수
// 아무런 변화도 없음
function func1() {
let num = 0 // 지역변수
return num
}
// 파라미터로 넘어온 값과 나가는 값이 다름 - 변함
function func2(num) {
num = num + 1
return num
}
// 관전포인트는 num의 값이 바뀌지 않는 것 - 불변성
function func3() {
let num = 0 // 지역변수 불변성
global = num + 1
return global
}
console.log(func1()) // 0
console.log(func2(2)) // 3
console.log(global) // -99
console.log(func3()) // 1
console.log(global) // 1
<리덕스 연습 - app.js>
// Flux Architecture - One way binding
// 콜백함수
// document.querySelector("#root").addEventListener('click', function(){})
// 함수 선언 - 일급객체(함수를 파라미터로 넘김, 리턴으로 넘김, 할당가능)
// 함수는 어디든 갈 수 있는 권리가 있다
const createStore = () => {
console.log(worker)
// 외부함수에서 선언한 변수를 내부함수에서 사용 가능
let state; // step1에선 state.js - 상태 관리가 필요한 변수 꾸러미(묶음)
// 외부에서 구독신청한 회원들에게 알림처리 - 구독발행모델 패턴 적용
// 구독 신청한 이벤드들의 꾸러미 담기
let handlers = []
const subscribe = (handler) => { // 자바스크립트 문법 코드 분석 가치
handlers.push(handler)
}
// 위에서 선언된 상태 정보를 담은 변수를 새로운 상태정보로 치환 - 기존 참조를 끊는다 - 안전하니까
const send = (action) => {
// worker함수의 파라미터로 state를 두는 건 기존의 상태정보에 추가된 상태정보이다 - 변경사항을 담기위해
// 위에서 선언된 state변수에 새로운 상태정보가 추가된 상태정보를 갖는 주소번지로 치환
state = worker(state, action)
handlers.forEach(handler => handler())
}
// 내부함수 - 클로저검색
// https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures
const getState = () => { // react-redux에서 제공함 - 모방하기
return state // 여기서 관리하는 상태값 모두를 리턴함 - 객체{} 리터럴 사용
}
// 리턴타입에 함수이름을 반환하는 건 외부에서 호출하기 위해서이다 - API
return { // 객체리터럴을 사용하면 여러개의 함수를 외부에서 사용 가능함
getState,
send,
}
}
const worker = (state = {count: 0}, action) => { // state의 undefined방지로 객체리터럴 대입해줌
// 여기서 상태를 바꾸면 createStore에 있는 state의 참조 무결성이 깨짐
// redux에서는 반드시 이 worker, 즉 상태를 바꾸는 함수는 새로운 상태를 반환하라는 규칙을 만듦
// 새로운 상태를 반환하라는 건 기존의 참조를 끊으라는 의미
// 기존의 참조를 끊어야 예상하지 못함 side effect를 원천적으로 차단할 수 있음
switch(action.type) {
case 'increase':
return {...state, count: state.count + 1}
case 'decrease':
return {...state, count: state.count - 1}
default:
return {...state} // 원본이 지켜짐, 새로운 객체가 만들어짐(깊은복사)
}
}
// 스토어 함수 호출하기
// const store = legacy_createStore(reducer)
// 상태는 createStore함수 안에 있다(let state)
// 누가 이 상태를 변경하고 읽어가는가?
// worker함수의 switch문에서 action.type에 따라서 상태를 변경하거나 읽어낸다
// 변경되고 읽어낸 정보는 return으로 처리함
// store를 모아서 상태의 묶음을 넘겨줄 것
const store = createStore(worker)
store.subscribe =() => {
console.log(store.getState())
}
// 아래와 같이 store의 내부함수를 외부에서 호출하려면 return에 반드시 등록할 것
// action의 내용을 만드는 역할은 send를 하는쪽에서 만들어줌
store.send({type: 'increase'})
// 아래 코드로는 새로운 상태값 확인이 불가
// console.log(store.send()) -> undefined
console.log(store.getState()) // {count: 1}
store.send({type: 'increase'})
console.log(store.getState()) // {count: 2}
store.send({type: 'decrease'})
console.log(store.getState()) // {count: 1}
/*
UI한테는 직접적인 상태를 주지 않는다
그래서 여기서 return하는 것에는 state를 주지 않을 것 - 리덕스 컨셉
state를 그냥 주는 것은 자바스크립트의 컨셉
문제제기
지금은 맥락없이 1을 증가하는 컨셉
*/
<리덕스 예제 - store.js>
// 액션
export const increase = (mem_name) => ({
type: 'INCREASE',
payload: mem_name,
})
export const decrease = (empVO) => ({ type: "DECREASE", payload: empVO })
export const deptlist = (depts) => ({ type:'DEPTLIST', payload: depts })
// 초기상태 만들기
const initstate = {
number: 0,
mem_name: '이순신',
empVO: {empno: 7566, ename: '나신입'},
depts: [
{ DEPTNO: 10, DNAME: '총무부', LOC: '인천'},
{ DEPTNO: 20, DNAME: '영업부', LOC: '서울'},
{ DEPTNO: 30, DNAME: '개발부', LOC: '괌'},
],
auth: '',
googleProvider: '',
}
// worker가 reducer함수로 전환
//액션의 결과를 필터링 - 리듀서
const reducer = (state = initstate, action) => {
switch(action.type) {
case "INCREASE":
// return이되면 그걸 호출한 쪽에서 받는게 아니고, return되는 순간 바로 화면이 변경됨
return { ...state, number: state.number + 1, mem_name: action.payload }
case "DECREASE":
return { ...state, number: state.number - 1, empVO: action.payload }
case "DEPTLIST":
return {depts: action.payload}
default:
return { ...state } // 깊은 복사, 새로운 객체 생성됨
}
}
export default reducer
<리덕스 예제 - ReduxHeader.jsx>
import React from 'react'
import { useSelector } from 'react-redux'
const ReduxHeader = () => {
const empVO = useSelector((store) => store.empVO)
return (
<>
<div className='header_container'>
리덕스 헤더영역<br />
{empVO && `사원번호: ${empVO.empno}`}
</div>
</>
)
}
export default ReduxHeader
<리덕스 예제 - ReduxMainPage.jsx>
import React from 'react'
import { useSelector } from 'react-redux'
const ReduxMainPage = () => {
const number = useSelector(store => store.number)
const mem_name = useSelector(store => store.mem_name)
const depts = useSelector(store => store.depts)
return (
<>
<div className='main_container'>
컨텐츠 영역
<h4>번호: {number}</h4>
<h4>이름: {mem_name}</h4>
{
depts && depts.map((dept, index) => (
<h4 key={index}>{dept.DEPTNO} {dept.DNAME} {dept.LOC}</h4>
))
}
</div>
</>
)
}
export default ReduxMainPage
<회원가입페이지 만들기 - index.js>
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "@fortawesome/fontawesome-free/js/all.js";
import ImageUploader from "./service/imageUploader";
import { Provider } from "react-redux";
import { legacy_createStore } from "redux";
import rootReducer from "./redux/rootReducer";
import AuthLogic from "./service/authLogic";
import firebaseApp from "./service/firebase";
import { setAuth } from "./redux/userAuth/action";
// 리덕스 적용하기
const store = legacy_createStore(rootReducer)
// AuthLogic객체 생성하기
const authLogic = new AuthLogic(firebaseApp)
// store에 있는 초기 상태정보 출력하기
store.dispatch(setAuth(authLogic.getUserAuth(), authLogic.getGoogleAuthProvider()))
console.log(store.getState());
// 이미지 업로더 객체 생성
const imageUploader = new ImageUploader();
const root = ReactDOM.createRoot(document.getElementById("root"));
// 리덕스 추가 - store 생성
// createStore 호출
root.render(
<>
<Provider store={store}>
<BrowserRouter>
<App authLogic={authLogic} imageUploader={imageUploader} />
</BrowserRouter>
</Provider>
</>
);
<회원가입페이지 만들기 - App.jsx>
import { Route, Routes } 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';
function App({authLogic, imageUploader}) {
const dispatch = useDispatch()
const toastStatus = useSelector(state => state.toastStatus)
useEffect(() => {
dispatch(setToastMsg('회원가입을 해주세요.'))
}, [])
return (
<>
<div style={{height: '100vh'}}>
{toastStatus.status && <Toast />}
<Routes>
<Route path='/login' exact={true} element={<LoginPage />} />
<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='/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;
<회원가입페이지 만들기 - SignupPage.jsx>
import React from 'react'
import KhSignup from './KhSignup';
import Signuptype from './Signuptype';
const SignupPage = ({authLogic}) => {
// http://localhost:3000/auth/signup?type=member
const type = window.location.search.split('=')[1]
// console.log(window.location.search.split('&'));
// console.log(window.location.search.split('&')[0]);
// console.log(window.location.search.split('&')[0].split('='));
// console.log(window.location.search.split('&')[0].split('=')[0]);
// console.log(window.location.search.split('&')[0].split('=')[1]);
console.log(type)
const signupage = () => {
if(type){
return <KhSignup authLogic={authLogic} />
} else {
return <Signuptype/>
}
}
return (
signupage()
);
};
export default SignupPage
<회원가입페이지 만들기 - 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)
console.log(jsonDoc[0])
// 닉네임이 존재할 때
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.postNum,
MEM_ADDR: post.post,
MEM_ADDR_DTL: post.postDetail,
MEM_AUTH: (type==='member'?1:2),
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;
<회원가입페이지 만들기 - Signuptype.jsx>
import React from 'react'
import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';
const TypeForm = styled.div`
display: flex;
width: 80%;
max-width: 1200px;
height: 60vh;
justify-content: center;
align-items: center;
border: 2px solid lightgray;
border-radius: 60px;
margin: 150px 0px 150px 0px;
`;
const TypeDiv = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 50%;
height:100%;
cursor: pointer;
border-radius: 55px;
&:hover {
background-color: lightgray;
}
`;
const Signuptype = () => {
const navigate = useNavigate();
return (
<div style={{display: 'flex', justifyContent: 'center'}}>
<TypeForm>
<TypeDiv onClick={()=>{navigate('/auth/signup?type=member')}}>
<i className="fas fa-person-running" style={{width: '40%', height:'80%'}}></i>
<span style={{width: '50%', height:'20%', textAlign:'center', fontSize:'200%'}}>회원</span>
</TypeDiv>
<hr style={{height: '80%', width: '2px'}}/>
<TypeDiv onClick={()=>{navigate('/auth/signup?type=teacher')}}>
<i className="fas fa-person-chalkboard" style={{width: '50%', height:'80%'}}></i>
<span style={{width: '50%', height:'20%', textAlign:'center', fontSize:'200%'}}>강사</span>
</TypeDiv>
</TypeForm>
</div>
);
};
export default Signuptype;
<회원가입페이지 만들기 - LoginPage.jsx>
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Link, useNavigate } from 'react-router-dom'
import { setToastMsg } from '../../redux/toastStatus/action'
import { loginGoogle } from '../../service/authLogic'
import './loginpage.css'
const LoginPage = () => {
const navigate = useNavigate()
// One-Way 바인딩에서 액션을 스토어에 전달할 때 디스패치 사용함
const dispatch = useDispatch()
// store에 초기화된 state 안에 담긴 data 꺼내올 때 셀렉터 사용
const userAuth = useSelector((store) => store.userAuth)
const auth = useSelector((store) => store.userAuth.auth)
console.log(auth);
const googleProvider = useSelector((store) => store.userAuth.googleProvider)
console.log(googleProvider);
const handleGoogle = async(event) => {
event.preventDefault()
try {
const result = await loginGoogle(auth, googleProvider)
console.log(result.uid);
window.sessionStorage.setItem('userId', result.uid)
navigate('/')
} catch (error) {
dispatch(setToastMsg(error + ': 로그인 오류입니다.')) // [object, object] -> JSON.stringify()
}
} //end of handleGoogle
return (
<>
<div className="bg"></div>
<form className="login__form" action="">
<h1>로그인</h1>
<p>신규 사용자이신가요? <Link to="#">계정 만들기</Link></p>
<label htmlFor="useremail">이메일 주소
<input type="email" id="useremail" placeholder="이메일 주소를 입력해주세요."/>
</label>
<input type="submit" className="btn__submit" value="계속" />
<div className="divider">
<hr />
<span>또는</span>
</div>
<button className="btn__google" onClick={handleGoogle}>
<i className="icon icon-google"></i>Google으로 계속
</button>
<button className="btn__facebook">
<i className="icon icon-facebook"></i>Facebook으로 계속
</button>
<p className="captcha__text">
reCAPTCHA로 보호되며 Google <Link to="#">개인정보보호 정책</Link> 및
<Link to="#">서비스 약관</Link>의 적용을 받습니다.
</p>
</form>
</>
)
}
export default LoginPage
<회원가입페이지 만들기 - validateLogic.js>
export const validateEmail = (e) => {
//eslint-disable-next-line
const re = /^([0-9a-zA-Z_\\.-]+)@([0-9a-zA-Z_-]+)(\\.[0-9a-zA-Z_-]+){1,2}$/;
const email = e.target.value;
if (email === "") {
return " ";
} else if (!re.test(email)) {
return "이메일 형식이 잘못되었습니다.";
} else {
return "중복확인을 해주세요.";
}
};
export const validatePassword = (e) => {
const pw = e.target.value;
const num = pw.search(/[0-9]/g);
const eng = pw.search(/[a-z]/gi);
const spe = pw.search(/[`~!@@#$%^&*|₩₩₩'₩";:₩/?]/gi);
const hangulcheck = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/;
if (pw.length === 0) {
return " ";
} else if (pw.length < 8 || pw.length > 20) {
return "8자리 ~ 20자리 이내로 입력해주세요.";
} else if (hangulcheck.test(pw)) {
return "비밀번호에 한글을 사용 할 수 없습니다.";
} else if (pw.search(/\\s/) !== -1) {
return "비밀번호는 공백 없이 입력해주세요.";
} else if (num < 0 || eng < 0 || spe < 0) {
return "영문,숫자, 특수문자를 혼합하여 입력해주세요.";
} else {
return "";
}
};
export const checkPassword = (pw, pw2) => {
if (pw2) {
if (pw === pw2) {
return "";
} else {
return "비밀번호가 일치하지않습니다.";
}
} else {
return " ";
}
};
export const validateName = (e) => {
console.log('validateName : '+e);
const name = e.target.value;
const han = /^[가-힣]+$/;
const eng = /^[a-zA-Z]+$/;
if (name.length === 0) {
return " ";
} else if (han.test(name) || eng.test(name)) {
return "";
} else {
return "이름은 영어또는 한글로만 가능합니다.";
}
};
export const validateBirthdate = (e) => {
const birthday = e.target.value;
const day =
/^(19[0-9][0-9]|20\\d{2})(0[0-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$/;
if (birthday.length === 0) {
return " ";
} else if (day.test(birthday) && birthday.length === 8) {
return "";
} else {
return "생년월일은 YYYYMMDD형식으로 적어주세요.";
}
};
export const validateHp = (e) => {
const hp = e.target.value;
var reghp = /^01([0|1|6|7|8|9]?)([0-9]{4})([0-9]{4})$/;
if (hp.length === 0) {
return " ";
} else if (!reghp.test(hp)) {
return "'-'를 제외한 번호를 적어주세요.";
} else {
return "";
}
};
export const validateNickname = (e) => {
const nickname = e.target.value;
const check = /^[가-힣a-zA-Z0-9]+$/;
if (nickname.length === 0) {
return " ";
} else if (nickname.length < 2 || nickname.length > 10) {
return "2자리 ~ 10자리 이내로 입력해주세요.";
} else if (nickname.search(/\\s/) !== -1) {
return "닉네임은 공백 없이 입력해주세요.";
} else if (check.test(nickname)) {
return "중복확인을 해주세요.";
} else {
return "해당 닉네임은 사용할 수 없습니다.";
}
};
export const validateDname = (e) => {
// 사용자가 입력한 부서명
const name = e.target.value // input onChange
const kor = /^[ㄱ-ㅎ가-힣]+$/;
const eng = /^[a-zA-Z]+$/;
if(name.length === 0) {
return " ";
} else if (kor.test(name) || eng.test(name)) { // test의 리턴타입은 boolean
return "";
} else {
return "부서명은 영어 또는 한글만 가능합니다.";
}
}
<회원가입페이지 만들기 - authLogic.js>
import { createUserWithEmailAndPassword, EmailAuthProvider, getAuth, GoogleAuthProvider, sendEmailVerification, signInWithPopup } from 'firebase/auth'
class AuthLogic {
constructor() {
this.auth = getAuth()
this.googleProvider = new GoogleAuthProvider()
}
getUserAuth = () => {
return this.auth
}
getGoogleAuthProvider = () => {
return this.googleProvider
}
}
export default AuthLogic
export const onAuthChange = (auth) => {
return new Promise((resolve) => {
// 사용자가 바뀌었을 때 콜백함수를 받아서
auth.onAuthStateChanged((user) => {
resolve(user)
});
}) // end of Promise
} // end of onAuthChange
// 로그아웃 버튼 클릭시 호출하기
export const logout = (auth) => {
return new Promise((resolve, reject) => {
auth.signOut().catch(e => reject(e + '로그아웃 오류입니다.'))
// 로그인 성공시 세션 스토리지에 담아둔 정보를 모두 지운다
sessionStorage.clear();
// 서비스를 더 이상 사용하지 않는 경우이므로 돌려줄 값은 없다
resolve();
})
} // end of logout
// 로그인 시도시 구글인증인지 아니면 깃허브 인증인지 문자열로 넘겨받음
// 구글 인증인 경우 - 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 + ": 인증메일 오류입니다."));
});
};
<회원가입페이지 만들기 - 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
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 from member230324
<where>
<if test='mem_uid!=null and mem_uid.length()>0'>
AND mem_uid = #{mem_uid}
</if>
<if test='MEM_NICKNAME!=null and MEM_NICKNAME.length()>0'>
AND mem_nickname = #{MEM_NICKNAME}
</if>
</where>
</select>
<!-- 회원 정보 입력 -->
<insert id="memberInsert" parameterType="map">
INSERT INTO member230324(mem_no
<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>
)
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>
)
</insert>
</mapper>