이번주 월요일에 적어야할 내용을 목요일에 적고 있다. ꒰( ˵¯͒ꇴ¯͒˵ )꒱
우리가 해야할 사이트는 tripadvisor로 선정되었고, 모두가 경악하였다. 〣(ºΔº)〣
1차 프로젝트보다 훨씬 어려운 사이트를 선정되었기때문이다. ٩(͡◕_͡◕
여기저기 이전 1차에 비해서 많이 어려워진 스크롤이벤트, 여러 기능들이 많아졌기에 그러하다.
해당 기능을 설명하기 위한 유튜브 링크
이중에서 내가 구현해야할 기능은 로그인, 회원가입을 맡았다.
원래는 다른것을 해야하지만, 이전 1차에서 구현하지 못한 모달을 위해서 그냥 받아들였다.
그리고 최대한 빨리 다른 페이지를 할것이다. ◝(⁰▿⁰)◜
하는 마음으로 프로젝트를 시작했다.
의외로 난항이었던 것이 구글 API였다.
카카오톡은 해당이 없는데, 구글 API의 경우 자체 스타일을 보유했기에
이것을 무력화 시키는데 있어서 !important뿐이었다.
따로 매뉴얼적으로 일일히 따오거나 하는 방법도 있는거 같은데, 그것은 내가 도저히 검색해서 찾을 수 가 없어서
손쉽게 불러오는 컴포넌트를 불러오는 방식으로 구현하다가 발생한 문제였다.
이번에 만든 카카오톡 계정 로그인 방식이다.
페이스북은 요즘 대세가 아니기도하고 잘 안쓰는 추세라서 그냥 카카오톡으로 바꿔버렸다.
리캡챠도 있는데, 이건 그냥 라이브러리 불러와서 구현하는 방식이라 깔끔하게 skip
이번에 새로 배운 stylished-component를 사용해보았는데,
처음 쓰는 것이다보니 너무 코드를 지저분하게 쓸데없는 곳에도 남발해버렸다.
왜냐하면 스타일드 컴포넌트를 구현하는데 있어 classname을 줘야하는데, 네스팅을 구현하는 방법을 몰랐기에 그랬다.
import React, { useState } from "react";
import styled from "styled-components";
import KakaoLogin from "react-kakao-login";
import { GoogleLogin } from "react-google-login";
import { withRouter } from "react-router-dom";
function SocialLogin(props, visible) {
const [ id, setId ] = useState('');
const [ name, setName ] = useState('');
const [ provider, setProvider ] = useState('');
// 구글 로그인
const clickGoogleBtn = (res) => {
fetch("http://10.58.5.52:8000/account/sign-in/google", {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
email: res.profileObj.email,
name: res.profileObj.name,
})
})
.then(res => res.json())
.then(res => {
if (res) {
localStorage.setItem('kakao-token', res.token)
alert('로그인을 환영합니다')
props.history.push('/main')
} else {
alert('아이디와 비밀번호를 확인해주세요.')
}
})
}
// 카카오 로그인
const clickKakaoBtn = (res) => {
fetch('http://10.58.5.52:8000/account/sign-in/kakao', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
access_token : res.response.access_token
})
})
.then(res => res.json())
.then(res => {
if (res.access_token) {
localStorage.setItem('kakao-token', res.access_token)
alert('로그인을 환영합니다')
props.history.push('/main')
} else {
alert('아이디와 비밀번호를 확인해주세요.')
}
})
}
const clickBackBtn = (e) => {
props.history.push('/EmailLogin')
};
const responseFail = (err) => {
console.error(err)
};
const returnMain = (e) => {
props.history.push('/Main')
};
return(
<>
<ModalOverlay visible={visible} />
<ModalWrapper tabIndex="-1" visible={visible}>
<ModalIner tabIndex="0">
<div>
<LoginImageContainer>
<LoginImgtag src="https://static.tacdn.com/img2/brand_refresh/Tripadvisor_lockup_horizontal_registered.svg" />
</LoginImageContainer>
<LogInnput>
<LoginInputMargin>
<div className="centerMargin">
<GoogleBtn
icon={false}
clientId={'구글개발자 key'}
onSuccess={clickGoogleBtn}
onFailure={responseFail}
>
<SocialImg src="https://static.tacdn.com/img2/google/G_color_40x40.png" />
<Socialspan>Google로 계속하기</Socialspan>
</GoogleBtn>
<KakaoBtn
jsKey={'카카오개발자 key'}
onSuccess={clickKakaoBtn}
onFailure={responseFail}
getProfile="true"
>
<SocialImg src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcTI2AI6Bz3gkrH3VCq4azeIg-CXoxxvsar2Og&usqp=CAU" />
<Socialspan>카카오톡 계정으로 계속하기</Socialspan>
</KakaoBtn>
<OR>
<BorderLine>
<Topspan>또는</Topspan>
</BorderLine>
</OR>
<SocialBtn onClick={clickBackBtn}>
<SocialImg src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcQvsq86NX1qN68M4GUeh5k8h6KM4pADF86mEw&usqp=CAU" />
<Socialspan>
이메일로 계속하기
</Socialspan>
</SocialBtn>
</div>
<ContiNue>계속 진행할 경우, 트립어드바이저의
<span className="textDeco">개인정보 취급방침</span>
및
<span className="textDeco">쿠키 정책</span>
에 동의한 것으로 간주됩니다.
</ContiNue>
</LoginInputMargin>
</LogInnput>
</div>
<button onClick={returnMain} className="returnmain" tabIndex="0">
X
</button>
</ModalIner>
</ModalWrapper>
</>
);
}
const ModalOverlay = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 999;
`;
const ModalWrapper = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
overflow: auto;
outline: 0;
display: flex;
justify-content: center;
align-items: center;
`;
const ModalIner = styled.div`
width: 420px;
height: 666px;
padding: 40px 45px 30px;
box-sizing: border-box;
position: relative;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
background-color: white;
.returnmain {
position: absolute;
z-index: 1;
top: 0;
right: 0;
text-align: center;
width: 48px;
height: 48px;
font-size: 25px;
cursor: pointer;
}
`;
const LoginImageContainer = styled.div`
width:328px;
`;
const LoginImgtag = styled.img`
width: 210px;
height: 32px;
margin-right: 20px;
`;
const LogInnput = styled.div`
width: 329px;
height: 467px;
`;
const LoginInputMargin = styled.div`
width:328px;
height:48px;
.centerMargin{
margin: 50px 0;
position: relative;
}
`;
const GoogleBtn = styled(GoogleLogin)`
width: 328px !important;
height: 48px !important;
margin-bottom: 16px !important;
border-radius: 12px !important;
box-shadow: none !important;
border : 2px solid rgba(0,0,0,.2) !important;
color: black !important;
background-color: 'white' !important;
display:flex !important;
align-items: center !important;
span {
padding : 0 !important;
}
`;
const KakaoBtn = styled(KakaoLogin)`
width: 328px;
height: 48px;
margin-bottom: 16px;
border-radius: 12px;
box-shadow: none;
border : 2px solid rgba(0,0,0,.2);
color: black;
background-color: 'white';
display:flex;
align-items: center;
`;
const SocialBtn = styled.div`
width: 328px;
height: 48px;
margin-bottom: 16px;
border-radius: 12px;
box-shadow: none;
border : 2px solid rgba(0,0,0,.2);
color: black;
background-color: 'white';
display:flex;
align-items: center;
`;
const SocialImg = styled.img`
width: 20px;
height: 20px;
margin-left: 24px;
`;
const Socialspan = styled.span`
color : black;
width: 100%;
font-size: 14px;
font-family: '굴림, gulim, sans-serif';
margin: 0 12px 0 50px;
`;
const OR = styled.div`
padding : 8px 0;
text-align: center;
`;
const BorderLine = styled.div`
border-top : 1px solid #e0e0e0;
content: none;
display: block;
`;
const Topspan = styled.span`
position: relative;
top: -8px;
font-weight:600;
font-size: 10px;
line-height: 14px;
color: #8c8c8c;
padding: 0 6px;
background-color: white;
`;
const ContiNue = styled.div`
width: 328px;
height: 32px;
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
.textDeco {
text-decoration: underline;
color: black;
text-decoration-style: solid;
font-weight: 600;
line-height: 16px;
}
`;
export default withRouter(SocialLogin);
email 로그인 페이지이다.
이것을 구현하였는데,
꽤 어려웠던 것이 평소에 숨어있어야 하는 경고창이 타이핑을 시작하면 경고하는 방식으로 조건을 구현하는데 있어서 그 부분이 매우 어려웠다.
만들기 힘들었던 조건문이었다.
꽤나 고민하게 만든...
1차에서 만들었다고 그래도 나름 쉬운 부분이었다.
이번에는 스타일드 컴포넌트를 네스팅을 잘 쓸 수 있어 이것저것 섞어보았다.
느낀 단점으로는 굉장히 편하다.
그런데, 무엇을 먹혔는지 찾기가 힘들다....이전에 scss에선 클래스 이름만 검색하면 되었는데,
이번건 일일히 어디에 있는지 위치를 찾아서 코드를 분석해야했다.
대신 이름 안지어서 편하고, 적용하기 매우 좋다.
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { withRouter } from "react-router-dom";
function EmailLogin(props, visible) {
const [ name, setName ] = useState({email:"", password:""});
const inputValuedetector = (e) => {
setName({...name, [e.target.name] : e.target.value})
};
const submit = (name) => {
console.log(name)
fetch('http://10.58.5.52:8000/account/sign-in', {
method: 'POST',
body: JSON.stringify({
email: name.email,
password: name.password
}),
})
.then(res => res.json())
.then(res => {
if (res.token) {
localStorage.setItem("tripadvisor-token", res.token)
alert("로그인을 환영합니다")
props.history.push('/main')
} else {
alert('이메일과 비밀번호를 확인해주십시오')
};
})
}
const goSignup = (e) => {
props.history.push('/Signup')
}
const returnSocialLogin = (e) => {
props.history.push('/SocialLogin')
}
const returnMain = (e) => {
props.history.push('/Main')
}
const word = '@';
return(
<>
<ModalOverlay visible={visible} />
<ModalWrapper tabIndex="-1" visible={visible}>
<ModalIner tabIndex="0">
<div className="modalOutline">
<LoginImageContainer>
<LoginImgtag src="https://static.tacdn.com/img2/brand_refresh/Tripadvisor_lockup_horizontal_registered.svg" />
</LoginImageContainer>
<SignInnput>
<div className="signupContainer">
<div className="welCome">환영합니다 - 로그인하세요!</div>
<label className="emAil">이메일 주소</label>
<input onChange={inputValuedetector}
className="email_input"
type='text'
name="email"
placeholder="이메일" />
<label className="emAil">패스워드</label>
<input onChange={inputValuedetector}
className="email_input"
type='password'
name="password"
placeholder="비밀번호" />
<div onClick={ () => submit(name)} className="logIntitle" type="buttton">
로그인
</div>
<div className="passWordFindertitle">
<span className="passwordfinder">
패스워드 찾기
</span>
</div>
<div className="passWordFindertitle">
계정이 없으신가요?
<span onClick={goSignup} className="passwordfinder">
가입하기
</span>
</div>
<div className="passWordFindertitle">
대신 카카오톡이나 Google을 사용하고 싶으세요?
<span onClick={returnSocialLogin} className="passwordfinder">
돌아가기
</span>
</div>
</div>
<ContiNue>계속 진행할 경우, 트립어드바이저의
<span className="textDeco">개인정보 취급방침</span>
및
<span className="textDeco">쿠키 정책</span>
에 동의한 것으로 간주됩니다.
</ContiNue>
</SignInnput>
<div className={ name.email.length && !(name.email.includes(word))
? 'inputError'
: 'noneinputError'
} >
이메일 주소로써 유효하지 않거나 저희쪽에서 메일을 발송할 수 없는 문자열을 포함하고있습니다.
</div>
</div>
<button onClick={returnMain} className="returnmain" tabIndex="0">
X
</button>
</ModalIner>
</ModalWrapper>
</>
)
}
const ModalOverlay = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 999;
`;
const ModalWrapper = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
overflow: auto;
outline: 0;
display: flex;
justify-content: center;
align-items: center;
`;
const ModalIner = styled.div`
width: 420px;
height: 666px;
padding: 40px 45px 30px;
box-sizing: border-box;
position: relative;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
background-color: white;
.modalOutline {
width: 100%;
.inputError {
position: absolute;
display: block;
color: red;
background-color: white;
font-family:굴림, gulim, sans-serif;
font-size: 12px;
border: 1px solid #e0e0e0;
box-shadow: 0 2px 12px rgba(0,0,0,.35);
padding: 18px;
line-height: 16px;
width: 316px;
height: 68px;
z-index: 5;
left: 51px;
right: auto;
top: 113px;
bottom: auto;;
}
.noneinputError {
display: none;
color: white;
}
}
.returnmain {
position: absolute;
z-index: 1;
top: 0;
right: 0;
text-align: center;
width: 48px;
height: 48px;
font-size: 25px;
cursor: pointer;
}
`;
const LoginImageContainer = styled.div`
width:328px;
`;
const LoginImgtag = styled.img`
width: 210px;
height: 32px;
margin-right: 20px;
`;
const SignInnput = styled.div`
.signupContainer {
width: 328px;
height: 440px;
display: flex;
flex-direction: column;
.welCome {
color : black;
font-size: 16px;
font-family: '굴림, gulim, sans-serif';
margin: 36px 0 25px;
}
.emAil {
color: black;
font-size: 14px;
font-weight: 600;
font-family: '굴림, gulim, sans-serif';
line-height: 18px;
text-align: left;
}
.email_input {
background-color: white;
height: 48px;
margin: 8px 0 16px;
padding : 4px 4px 4px 8px;
border: 2px solid #e0e0e0;
border-radius: 12px;
}
.logIntitle {
color : white;
background: black;
width: 100%;
height: 48px;
font-size: 14px;
font-weight: 700;
margin: 20px 0;
padding: 14px 16px 8px;
border-radius: 12px;
border: 1px solid transparent;
line-height: 18px;
text-align : center;
}
.logIntitle:hover {
color : white;
background: #525252;
width: 100%;
height: 48px;
font-size: 14px;
font-weight: 700;
margin: 20px 0;
padding: 14px 16px 8px;
border-radius: 12px;
border: 1px solid transparent;
line-height: 18px;
text-align : center;
}
.passWordFindertitle {
margin-bottom: 12px;
.passwordfinder {
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
font-weight: 600;
color: black;
line-height: 16px;
}
.passwordfinder:hover {
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
font-weight: 600;
color: black;
line-height: 16px;
text-decoration: underline;
}
}
}
`;
const ContiNue = styled.div`
width: 328px;
height: 32px;
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
.textDeco {
text-decoration: underline;
color: black;
text-decoration-style: solid;
font-weight: 600;
line-height: 16px;
}
`;
export default withRouter(EmailLogin);
마지막으로 회원가입창.
비밀번호에는 조건문이 적용이 되어있지 않았다.
백엔드의 요청으로 인스타그램 같은게 있어서 닉네임을 추가하였다.
이번 페이지들을 구현하면서 React Hook을 처음으로 사용하였는데,
비구조 할당을 하지 않는 부분에서 매우 편했으나 조건문 작성하기가 힘들었다.
이번에 전개 연산자라는 것을 배우기도 했고, 좀 어렵던...
import React, { useState } from "react";
import styled, { ThemeProvider } from "styled-components";
import { withRouter } from "react-router-dom";
function Signup(props, visible) {
const [ name, setName ] = useState({email:"",password:"",name:""});
const emailDetector = (e) => {
setName({...name, [e.target.name]:e.target.value});
};
const submit = (name) => {
console.log(name)
fetch('http://10.58.5.52:8000/account/sign-up', {
method: 'POST',
body: JSON.stringify({
email: name.email,
password: name.password,
name : name.nickname
}),
})
.then(res => res.json)
.then(res => {
if (res) {
alert("회원가입을 환영합니다")
props.history.push('/main')
} else {
alert('이메일과 비밀번호를 확인해주십시오')
};
})
}
const returnLogin = (e) => {
props.history.push('/EmailLogin')
}
const returnSocialLogin = (e) => {
props.history.push('/SocialLogin')
}
const returnMain = (e) => {
props.history.push('/Main')
}
const word = '@';
console.log(name.email.includes(word))
return(
<>
<ModalOverlay visible={visible} />
<ModalWrapper tabIndex="-1" visible={visible}>
<ModalIner tabIndex="0">
<div className="modalOutline">
<LoginImageContainer>
<LoginImgtag src="https://static.tacdn.com/img2/brand_refresh/Tripadvisor_lockup_horizontal_registered.svg" />
</LoginImageContainer>
<SignInnput>
<div className="signupContainer">
<div className="signupBody">
<div className="welCome">
바로 회원가입하기 - 무료입니다!
</div>
<label className="email">
이메일 주소
</label>
<input onChange={emailDetector}
className="email_input"
type='text'
name="email"
placeholder="이메일" />
<label className="email">
패스워드 생성하기
</label>
<input onChange={emailDetector}
className="email_input"
type='password'
name="password"
placeholder="비밀번호" />
<label className="email">
닉네임 생성하기
</label>
<input onChange={emailDetector}
className="email_input"
type='text'
name="nickname"
placeholder="닉네임" />
<div onClick={() => submit(name)}
className="logIntitle"
type="button">
가입하기
</div>
</div>
<div className="passWordFindertitle">
이미 계정이 있으신가요?
<span onClick={returnLogin}
className="passwordfinder">
로그인
</span>
</div>
<div className="passWordFindertitle">
대신 카카오톡이나 Google을 사용하고 싶으세요?
<span onClick={returnSocialLogin}
className="passwordfinder">
돌아가기
</span>
</div>
</div>
<div className="inputboxTitle">
<input className="inputbot" type="checkbox"></input>
<label>
트립어드바이저의 여행 특가, 팁, 새로운 기능에 대한 정보 메일을 수신하겠습니다. 수신을 원치 않을 때는 해지할 수 있습니다.
</label>
</div>
</SignInnput>
<div className={
name.email.length && !(name.email.includes(word))
? 'inputError'
: 'noneinputError'
} >
이메일 주소로써 유효하지 않거나 저희쪽에서 메일을 발송할 수 없는 문자열을 포함하고있습니다.
</div>
</div>
<button onClick={returnMain}
className="returnmain"
tabIndex="0">
X
</button>
</ModalIner>
</ModalWrapper>
</>
)
}
const ModalOverlay = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 999;
`;
const ModalWrapper = styled.div`
box-sizing: border-box;
display: ${(props) => (props.visible ? 'block' : 'none')};
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
overflow: auto;
outline: 0;
display: flex;
justify-content: center;
align-items: center;
`;
const ModalIner = styled.div`
width: 420px;
height: 666px;
padding: 40px 45px 30px;
box-sizing: border-box;
position: relative;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
background-color: white;
.modalOutline {
width: 100%;
.inputError {
position: absolute;
display: block;
color: red;
background-color: white;
font-family:굴림, gulim, sans-serif;
font-size: 12px;
border: 1px solid #e0e0e0;
box-shadow: 0 2px 12px rgba(0,0,0,.35);
padding: 18px;
line-height: 16px;
width: 316px;
height: 68px;
z-index: 5;
left: 51px;
right: auto;
top: 113px;
bottom: auto;;
}
.noneinputError {
display: none;
color: white;
}
}
.returnmain {
position: absolute;
z-index: 1;
top: 0;
right: 0;
text-align: center;
width: 48px;
height: 48px;
font-size: 25px;
cursor: pointer;
}
`;
const LoginImageContainer = styled.div`
width:328px;
`;
const LoginImgtag = styled.img`
width: 210px;
height: 32px;
margin-right: 20px;
`;
const SignInnput = styled.div`
.signupContainer {
width: 100%;
height: 440px;
.signupBody {
display: flex;
flex-direction: column;
width: 100%;
height: 362px;
.welCome {
color : black;
font-size: 16px;
font-family: '굴림, gulim, sans-serif';
margin: 36px 0 25px;
}
.email {
color: black;
font-size: 14px;
font-weight: 600;
font-family: '굴림, gulim, sans-serif';
line-height: 18px;
text-align: left;
}
.email_input {
background-color: white;
height: 48px;
margin: 8px 0 16px;
padding : 4px 4px 4px 8px;
border: 2px solid #e0e0e0;
border-radius: 12px;
}
.logIntitle {
color : white;
background: black;
width: 100%;
height: 48px;
font-size: 14px;
font-weight: 700;
margin: 20px 0;
padding: 14px 16px 8px;
border-radius: 12px;
border: 1px solid transparent;
line-height: 18px;
text-align : center;
}
.logIntitle:hover {
color : white;
background: #525252;
width: 100%;
height: 48px;
font-size: 14px;
font-weight: 700;
margin: 20px 0;
padding: 14px 16px 8px;
border-radius: 12px;
border: 1px solid transparent;
line-height: 18px;
text-align : center;
}
}
.passWordFindertitle {
margin-bottom: 12px;
.passwordfinder {
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
font-weight: 600;
color: black;
line-height: 16px;
}
.passwordfinder:hover {
font-size: 12px;
font-family: '굴림, gulim, sans-serif';
font-weight: 600;
color: black;
line-height: 16px;
text-decoration: underline;
}
}
}
`;
export default withRouter(Signup);
2틀만에 끝날 것으로 봤지만, 리액트 hook으로 접근한 부분때문에 로그인이 되지 않아서
고생한 것과 소셜로그인을 적용할때 자체 css를 없애는데 있어서 고생한 것 덕분에 3일 걸려서 3페이지를 만들었다는 것이 아쉽다.
'코딩 > 위코드 코딩학습' 카테고리의 다른 글
[위코드] TIL(Today I am learned) -24(위코드의 마지막 주말) (0) | 2020.08.09 |
---|---|
[위코드] TIL(Today I am learned) -23 (0) | 2020.08.07 |
위코드 10기 1차 프로젝트 클론코딩 후기.(이솝/aesop) (0) | 2020.08.03 |
[위코드] TIL(Today I am learned) -21(1차 프로젝트) (0) | 2020.08.02 |
[위코드] TIL(Today I am learned) -20 (0) | 2020.07.30 |