Skip to content

Commit 1d5be62

Browse files
authored
Merge pull request #129 from boostcamp-2020/dev
Dev -> master 배포
2 parents 6ef3fc6 + c7febc5 commit 1d5be62

File tree

28 files changed

+454
-77
lines changed

28 files changed

+454
-77
lines changed

client/package-lock.json

Lines changed: 11 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"react": "^17.0.1",
1818
"react-custom-scroll": "^4.2.1",
1919
"react-dom": "^17.0.1",
20+
"react-google-login": "^5.1.25",
2021
"react-redux": "^7.2.2",
2122
"react-router-dom": "^5.2.0",
2223
"redux": "^4.0.5",

client/src/api/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ instance.interceptors.response.use(
5858
console.log('요청 취소', err);
5959
} else {
6060
if (err.response.status === 401) {
61-
if (err.response.config.url !== '/api/auth/login') {
61+
const { url } = err.response.config;
62+
if (url !== '/api/auth/login' || url !== '/api/oauth/google/signup') {
6263
window.location.href = '/login';
6364
}
6465
}

client/src/components/LoginBox/LoginBox.tsx

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ import { useDispatch } from 'react-redux';
44
import { darken } from 'polished';
55
import { Link } from 'react-router-dom';
66
import isEmail from 'validator/es/lib/isEmail';
7-
import { loginRequest } from '@/store/modules/auth.slice';
7+
import { loginRequest, loginSuccess } from '@/store/modules/auth.slice';
88
import { FormButton, FormInput as Input, FormLabel as Label } from '@/styles/shared/form';
9-
import { WarningIcon } from '@/components';
9+
import { WarningIcon, GoogleLogoIcon } from '@/components';
1010
import { IconBox, WarningText } from '@/components/EmailBox/EmailBox';
1111
import { flex } from '@/styles/mixin';
1212
import { useAuthState } from '@/hooks';
1313
import LoadingSvg from '@/public/icon/loading.svg';
14+
import { GoogleLogin } from 'react-google-login';
15+
import config from '@/config';
16+
import { authService } from '@/services';
1417

1518
const Container = styled.div`
1619
width: 100%;
1720
height: 100%;
21+
${flex('center', 'flex-start', 'column')};
1822
`;
1923

2024
const Title = styled.div`
@@ -25,9 +29,15 @@ const Title = styled.div`
2529
color: #453841;
2630
`;
2731

32+
const DescText = styled.div`
33+
color: ${(props) => props.theme.color.black4};
34+
font-size: 1rem;
35+
margin-bottom: 2rem;
36+
`;
37+
2838
const Form = styled.form`
2939
width: 25rem;
30-
margin: 1.5rem auto;
40+
margin: 0 auto 1.5rem auto;
3141
`;
3242

3343
const SignupButton = styled(FormButton)`
@@ -62,6 +72,46 @@ const LoadingButton = styled(FormButton)`
6272
${flex()}
6373
`;
6474

75+
const LineWithText = styled.div`
76+
width: 25rem;
77+
${flex()};
78+
position: relative;
79+
margin: 1rem 0;
80+
`;
81+
82+
const LineText = styled.div`
83+
padding: 0 20px;
84+
padding-bottom: 3px;
85+
margin-top: 0.5rem;
86+
flex-shrink: 0;
87+
font-weight: bold;
88+
font-size: 0.9rem;
89+
color: ${(props) => props.theme.color.black8};
90+
`;
91+
92+
const Line = styled.hr`
93+
border: none;
94+
border-top: 1px solid ${(props) => props.theme.color.lightGray1};
95+
flex: 1;
96+
`;
97+
98+
const GoogleLoginBox = styled.div`
99+
${flex()};
100+
flex-shrink: 0;
101+
border: 2px solid ${(props) => props.theme.color.googleColor};
102+
border-radius: 5px;
103+
width: 25rem;
104+
height: 50px;
105+
cursor: pointer;
106+
`;
107+
108+
const GoogleText = styled.span`
109+
color: ${(props) => props.theme.color.googleColor};
110+
font-weight: 800;
111+
font-size: 1.2rem;
112+
margin-left: 0.8rem;
113+
`;
114+
65115
const LoginBox: React.FC = () => {
66116
const dispatch = useDispatch();
67117

@@ -91,6 +141,21 @@ const LoginBox: React.FC = () => {
91141
dispatch(loginRequest({ email, pw }));
92142
};
93143

144+
const handleGoogleOAuthSuccess = async (res: any) => {
145+
const { data, status } = await authService.signupWithGoogleOAuth({
146+
accessToken: res.accessToken,
147+
});
148+
const { accessToken, refreshToken, user } = data;
149+
if (status === 200) {
150+
localStorage.setItem('accessToken', accessToken);
151+
localStorage.setItem('refreshToken', refreshToken);
152+
localStorage.setItem('userId', user.id);
153+
dispatch(
154+
loginSuccess({ accessToken, refreshToken, userId: user.id ? Number(user.id) : null }),
155+
);
156+
}
157+
};
158+
94159
useEffect(() => {
95160
if (loginState.err?.response?.status === 401) {
96161
setLoginFailed(true);
@@ -99,8 +164,28 @@ const LoginBox: React.FC = () => {
99164

100165
return (
101166
<Container>
167+
<Title>로그인</Title>
168+
<DescText>로그인하려면 사용하는 Google 계정이나 이메일 주소로 계속해 주세요.</DescText>
169+
<GoogleLogin
170+
clientId={config.GOOGLE_CLIENT_ID}
171+
buttonText="Google로 계속"
172+
render={(renderProps) => (
173+
<GoogleLoginBox onClick={renderProps.onClick}>
174+
<GoogleLogoIcon size="18px" />
175+
<GoogleText>Google로 계속</GoogleText>
176+
</GoogleLoginBox>
177+
)}
178+
onSuccess={handleGoogleOAuthSuccess}
179+
onFailure={(res: any) => {
180+
alert('로그인 실패');
181+
}}
182+
/>
183+
<LineWithText>
184+
<Line />
185+
<LineText>또는</LineText>
186+
<Line />
187+
</LineWithText>
102188
<Form onSubmit={handleSubmit}>
103-
<Title>로그인</Title>
104189
<Label>
105190
이메일 주소
106191
<Input

client/src/components/common/Header/Header.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useAuthState, useUserState } from '@/hooks';
77
import { logoutRequest } from '@/store/modules/auth.slice';
88
import { DimModal, UserStateIcon, ClockIcon, Popover } from '@/components';
99
import theme from '@/styles/theme';
10+
import { useHistory } from 'react-router-dom';
1011
import { UserProfileModalHeader, UserProfileModalBody } from './UserProfileBox';
1112

1213
const Container = styled.div`
@@ -126,6 +127,7 @@ const TitleClockIcon = styled.div`
126127
const Header: React.FC = () => {
127128
const dispatch = useDispatch();
128129

130+
const history = useHistory();
129131
const { userId } = useAuthState();
130132
const { userInfo } = useUserState();
131133

@@ -144,7 +146,11 @@ const Header: React.FC = () => {
144146
useEffect(() => {
145147
if (userId) {
146148
dispatch(getUserRequest({ userId: Number(userId) }));
149+
return;
147150
}
151+
localStorage.removeItem('accessToken');
152+
localStorage.removeItem('refreshToken');
153+
history.push('/login');
148154
}, [dispatch, userId]);
149155

150156
const workspaceName = '부스트캠프 2020 멤버십';
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { PropsWithChildren } from 'react';
2+
3+
const GoogleLogoIcon = ({ size }: PropsWithChildren<{ size?: string }>) => {
4+
return (
5+
<svg width={size ?? '16px'} height={size ?? '16px'} viewBox="0 0 48 48">
6+
<g>
7+
<path
8+
fill="#EA4335"
9+
d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
10+
/>
11+
<path
12+
fill="#4285F4"
13+
d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
14+
/>
15+
<path
16+
fill="#FBBC05"
17+
d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
18+
/>
19+
<path
20+
fill="#34A853"
21+
d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
22+
/>
23+
<path fill="none" d="M0 0h48v48H0z" />
24+
</g>
25+
</svg>
26+
);
27+
};
28+
29+
export default GoogleLogoIcon;

client/src/components/common/Icon/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export { default as CommentIcon } from './CommentIcon/CommentIcon';
1414
export { default as PaperPlaneIcon } from './PaperPlaneIcon/PaperPlaneIcon';
1515
export { default as RightIcon } from './RightIcon/RightIcon';
1616
export { default as RightArrowLineIcon } from './RightArrowLineIcon/RightArrowLineIcon';
17+
export { default as GoogleLogoIcon } from './GoogleLogoIcon/GoogleLogoIcon';

client/src/components/common/LogoBox/LogoBox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const Container = styled.div`
88
width: 100%;
99
height: 5rem;
1010
${flex()};
11-
margin-top: 3rem;
11+
margin-top: 2rem;
1212
user-select: none;
1313
`;
1414

@@ -20,7 +20,7 @@ const SlackLogo = styled.img`
2020
const LogoBox: React.FC = () => {
2121
return (
2222
<Container>
23-
<Link to="/">
23+
<Link to="/login">
2424
<SlackLogo src={slackLogo} />
2525
</Link>
2626
</Container>

client/src/components/common/ThreadItem/ThreadPopup/ThreadPopup.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,16 @@ const ThreadPopup: React.FC<ThreadPopupProps> = ({
7777
const reactionBoxRef = useRef<HTMLDivElement>(null);
7878

7979
const clickDeleteMessage = () => {
80-
dispatch(
81-
sendMessageRequest({
82-
type: SOCKET_MESSAGE_TYPE.THREAD,
83-
room: current?.name as string,
84-
subType: THREAD_SUBTYPE.DELETE_THREAD,
85-
thread,
86-
}),
87-
);
80+
if (confirm('해당 스레드를 삭제 하시겠습니까?')) {
81+
dispatch(
82+
sendMessageRequest({
83+
type: SOCKET_MESSAGE_TYPE.THREAD,
84+
room: current?.name as string,
85+
subType: THREAD_SUBTYPE.DELETE_THREAD,
86+
thread,
87+
}),
88+
);
89+
}
8890
};
8991

9092
return (

client/src/config/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const config = {
22
jwtSecret: process.env.JWT_SECRET as string,
33
jwtRefreshSecret: process.env.JWT_REFRESH_SECRET as string,
4+
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID as string,
45
};
56

67
export default config;

client/src/hooks/useChannelState.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import { RootState } from '@/store/modules';
55
const selectChannelState = (state: RootState) => state.channel;
66
const selectChannel = createSelector(selectChannelState, (channels) => channels);
77

8-
const selectChannelListState = (state: RootState) => state.channel.channelList;
9-
const selectChannelList = (idx: number) =>
10-
createSelector(selectChannelListState, (channelList) => channelList[idx]);
11-
128
const selectJoinChannelListState = (state: RootState) => state.channel.myChannelList;
139
const selectJoinChannelList = (idx: number) =>
1410
createSelector(selectJoinChannelListState, (joinChannelList) => joinChannelList[idx]);
@@ -17,10 +13,6 @@ export const useChannelState = () => {
1713
return useSelector(selectChannel);
1814
};
1915

20-
export const useChannelList = (idx: number) => {
21-
return useSelector(selectChannelList(idx));
22-
};
23-
2416
export const useJoinChannelListState = (idx: number) => {
2517
return useSelector(selectJoinChannelList(idx));
2618
};

client/src/pages/LoginPage.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ import { useAuthState } from '@/hooks';
55

66
const LoginPage: React.FC = () => {
77
const { accessToken } = useAuthState();
8+
9+
if (accessToken) {
10+
return <Redirect to="/" />;
11+
}
12+
813
return (
914
<>
10-
{accessToken ? (
11-
<Redirect to="/" />
12-
) : (
13-
<>
14-
<LogoBox />
15-
<LoginBox />
16-
</>
17-
)}
15+
<LogoBox />
16+
<LoginBox />
1817
</>
1918
);
2019
};

client/src/services/auth.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import API from '@/api';
22
import { Service } from '@/types';
3+
import axios from 'axios';
34

45
interface LoginParam {
56
email: string;
@@ -29,4 +30,7 @@ export const authService: Service = {
2930
checkExistEmail({ email }: { email: string }) {
3031
return API.post('/api/auth/email/check', { email });
3132
},
33+
signupWithGoogleOAuth({ accessToken }: { accessToken: string }) {
34+
return axios.post('/api/oauth/google/signup', { accessToken });
35+
},
3236
};

0 commit comments

Comments
 (0)