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> Google 로그인
</GoogleButton>
<MyP style={{marginTop:"30px"}}>신규 사용자이신가요? <Link to="/auth/signup" className="text-decoration-none" style={{color: "blue"}}>계정 만들기</Link></MyP>
<MyP>이메일를 잊으셨나요? <Link to="/auth/findEmail" className="text-decoration-none" style={{color: "blue"}}>이메일 찾기</Link></MyP>
<MyP>비밀번호를 잊으셨나요? <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 - 게시판 페이징 처리
'국비학원 > 수업기록' 카테고리의 다른 글
국비 지원 개발자 과정_Day87 (0) | 2023.04.03 |
---|---|
국비 지원 개발자 과정_Day86 (0) | 2023.03.31 |
국비 지원 개발자 과정_Day84 (0) | 2023.03.29 |
국비 지원 개발자 과정_Day83 (1) | 2023.03.28 |
국비 지원 개발자 과정_Day82 (1) | 2023.03.27 |
댓글