Showing
10 changed files
with
373 additions
and
38 deletions
1 | +import React from 'react'; | ||
2 | +import styled from 'styled-components'; | ||
3 | +import { Link } from 'react-router-dom'; | ||
4 | +import Button from '../common/Button'; | ||
5 | +import palette from '../../lib/styles/palette'; | ||
6 | +/* | ||
7 | +Display Auth Form(Register, Login) | ||
8 | +*/ | ||
9 | + | ||
10 | +const AuthFormBlock = styled.div` | ||
11 | + h3 { | ||
12 | + margin: 0; | ||
13 | + color: ${palette.gray[8]}; | ||
14 | + margin-bottom: 1rem; | ||
15 | + } | ||
16 | +`; | ||
17 | + | ||
18 | +const StyledInput = styled.input` | ||
19 | + font-size: 1rem; | ||
20 | + border: none; | ||
21 | + border-bottom: 1px solid ${palette.gray[5]}; | ||
22 | + padding-bottom: 0.5rem; | ||
23 | + outline: none; | ||
24 | + width: 100%; | ||
25 | + &:focus { | ||
26 | + color: $oc-teal-7; | ||
27 | + border-bottom: 1px solid ${palette.gray[7]}; | ||
28 | + } | ||
29 | + & + & { | ||
30 | + margin-top: 1rem; | ||
31 | + } | ||
32 | +`; | ||
33 | + | ||
34 | +const Footer = styled.div` | ||
35 | + margin-top: 2rem; | ||
36 | + text-align: right; | ||
37 | + a { | ||
38 | + color: ${palette.gray[6]}; | ||
39 | + text-decoration: underline; | ||
40 | + &:hover { | ||
41 | + color: ${palette.gray[9]}; | ||
42 | + } | ||
43 | + } | ||
44 | +`; | ||
45 | + | ||
46 | +const ButtonWithMarginTop = styled(Button)` | ||
47 | + margin-top: 1rem; | ||
48 | +`; | ||
49 | + | ||
50 | +/** | ||
51 | + * Show Error message | ||
52 | + */ | ||
53 | +const ErrorMessage = styled.div` | ||
54 | + color: red; | ||
55 | + text-align: center; | ||
56 | + font-size: 0.875rem; | ||
57 | + margin-top: 1rem; | ||
58 | +`; | ||
59 | + | ||
60 | +const textMap = { | ||
61 | + login: '로그인', | ||
62 | + register: '회원가입', | ||
63 | +}; | ||
64 | + | ||
65 | +const AuthForm = ({ type, form, onChange, onSubmit, error }) => { | ||
66 | + const text = textMap[type]; | ||
67 | + return ( | ||
68 | + <AuthFormBlock> | ||
69 | + <h3>{text}</h3> | ||
70 | + <form onSubmit={onSubmit}> | ||
71 | + <StyledInput | ||
72 | + autoComplete="username" | ||
73 | + name="username" | ||
74 | + placeholder="아이디" | ||
75 | + onChange={onChange} | ||
76 | + value={form.username} | ||
77 | + /> | ||
78 | + <StyledInput | ||
79 | + autoComplete="new-password" | ||
80 | + name="password" | ||
81 | + placeholder="비밀번호" | ||
82 | + type="password" | ||
83 | + onChange={onChange} | ||
84 | + value={form.password} | ||
85 | + /> | ||
86 | + {type === 'register' && ( | ||
87 | + <StyledInput | ||
88 | + autoComplete="new-password" | ||
89 | + name="passwordConfirm" | ||
90 | + placeholder="비밀번호 확인" | ||
91 | + type="password" | ||
92 | + onChange={onChange} | ||
93 | + value={form.passwordConfirm} | ||
94 | + /> | ||
95 | + )} | ||
96 | + {error && <ErrorMessage>{error}</ErrorMessage>} | ||
97 | + <ButtonWithMarginTop cyan fullWidth> | ||
98 | + {text} | ||
99 | + </ButtonWithMarginTop> | ||
100 | + </form> | ||
101 | + <Footer> | ||
102 | + {type === 'login' ? ( | ||
103 | + <Link to="/register">회원가입</Link> | ||
104 | + ) : ( | ||
105 | + <Link to="/login">로그인</Link> | ||
106 | + )} | ||
107 | + </Footer> | ||
108 | + </AuthFormBlock> | ||
109 | + ); | ||
110 | +}; | ||
111 | + | ||
112 | +export default AuthForm; |
1 | +import React from 'react'; | ||
2 | +import styled from 'styled-components'; | ||
3 | +import palette from '../../lib/styles/palette'; | ||
4 | +import { Link } from 'react-router-dom'; | ||
5 | +/* | ||
6 | +register/login Layout | ||
7 | +*/ | ||
8 | +const AuthTemplateBlock = styled.div` | ||
9 | + position: absolute; | ||
10 | + left: 0; | ||
11 | + top: 0; | ||
12 | + bottom: 0; | ||
13 | + right: 0; | ||
14 | + background: ${palette.gray[2]}; | ||
15 | + display: flex; | ||
16 | + flex-direction: column; | ||
17 | + justify-content: center; | ||
18 | + align-items: center; | ||
19 | +`; | ||
20 | + | ||
21 | +const WhiteBox = styled.div` | ||
22 | + .logo-area { | ||
23 | + display: block; | ||
24 | + padding-bottom: 2rem; | ||
25 | + text-align: center; | ||
26 | + font-weight: bold; | ||
27 | + letter-spacing: 2px; | ||
28 | + } | ||
29 | + box-shadow: 0 0 8px rgba(0, 0, 0, 0.025); | ||
30 | + padding: 2rem; | ||
31 | + width: 360px; | ||
32 | + background: white; | ||
33 | + border-radius: 2px; | ||
34 | +`; | ||
35 | + | ||
36 | +const AuthTemplate = ({ children }) => { | ||
37 | + return ( | ||
38 | + <AuthTemplateBlock> | ||
39 | + <WhiteBox> | ||
40 | + <div className="logo-area"> | ||
41 | + <Link to="/">Jaksimsamil</Link> | ||
42 | + </div> | ||
43 | + {children} | ||
44 | + </WhiteBox> | ||
45 | + </AuthTemplateBlock> | ||
46 | + ); | ||
47 | +}; | ||
48 | + | ||
49 | +export default AuthTemplate; |
1 | import React from 'react'; | 1 | import React from 'react'; |
2 | -import styled from 'styled-components'; | 2 | +import styled, { css } from 'styled-components'; |
3 | import palette from '../../lib/styles/palette'; | 3 | import palette from '../../lib/styles/palette'; |
4 | +import { withRouter } from 'react-router-dom'; | ||
4 | 5 | ||
5 | const StyledButton = styled.button` | 6 | const StyledButton = styled.button` |
6 | border: none; | 7 | border: none; |
... | @@ -12,13 +13,38 @@ const StyledButton = styled.button` | ... | @@ -12,13 +13,38 @@ const StyledButton = styled.button` |
12 | outline: none; | 13 | outline: none; |
13 | cursor: pointer; | 14 | cursor: pointer; |
14 | 15 | ||
15 | - background: ${palette.gray[7]}; | 16 | + background: ${palette.gray[8]}; |
16 | - | ||
17 | &:hover { | 17 | &:hover { |
18 | - background: ${palette.gray[5]}; | 18 | + background: ${palette.gray[6]}; |
19 | } | 19 | } |
20 | -`; | 20 | + ${props => |
21 | + props.fullWidth && | ||
22 | + css` | ||
23 | + padding-top: 0.75rem; | ||
24 | + padding-bottom: 0.75rem; | ||
25 | + width: 100%; | ||
26 | + font-size: 1.125rem; | ||
27 | + `} | ||
21 | 28 | ||
22 | -const Button = (props) => <StyledButton {...props} />; | 29 | + ${props => |
30 | + props.cyan && | ||
31 | + css` | ||
32 | + background: ${palette.cyan[5]}; | ||
33 | + &:hover { | ||
34 | + background: ${palette.cyan[4]}; | ||
35 | + } | ||
36 | + `} | ||
37 | +`; | ||
23 | 38 | ||
24 | -export default Button; | 39 | +const Button = ({ to, history, ...rest }) => { |
40 | + const onClick = e => { | ||
41 | + if (to) { | ||
42 | + history.push(to); | ||
43 | + } | ||
44 | + if (rest.onClick) { | ||
45 | + rest.onClick(e); | ||
46 | + } | ||
47 | + }; | ||
48 | + return <StyledButton {...rest} onClick={onClick} />; | ||
49 | +}; | ||
50 | +export default withRouter(Button); | ... | ... |
1 | -import React from 'react'; | ||
2 | -import styled from 'styled-components'; | ||
3 | -/* | ||
4 | -register/login Layout | ||
5 | -*/ | ||
6 | - | ||
7 | -const AuthTemplateBlock = styled.div``; | ||
8 | - | ||
9 | -const AuthTemplate = ({ children }) => { | ||
10 | - return <AuthTemplateBlock>{children}</AuthTemplateBlock>; | ||
11 | -}; | ||
12 | - | ||
13 | -export default AuthTemplate; |
1 | +import React, { useEffect, useState } from 'react'; | ||
2 | +import { useDispatch, useSelector } from 'react-redux'; | ||
3 | +import { withRouter } from 'react-router-dom'; | ||
4 | +import { changeField, initializeForm, login } from '../../modules/auth'; | ||
5 | +import AuthForm from './AuthForm'; | ||
6 | + | ||
7 | +const LoginForm = ({ history }) => { | ||
8 | + const dispatch = useDispatch(); | ||
9 | + const [error, setError] = useState(null); | ||
10 | + const { form, auth, authError, user } = useSelector(({ auth, user }) => ({ | ||
11 | + form: auth.login, | ||
12 | + auth: auth.auth, | ||
13 | + authError: auth.authError, | ||
14 | + user: user.user, | ||
15 | + })); | ||
16 | + | ||
17 | + const onChange = (e) => { | ||
18 | + const { value, name } = e.target; | ||
19 | + dispatch( | ||
20 | + changeField({ | ||
21 | + form: 'login', | ||
22 | + key: name, | ||
23 | + value, | ||
24 | + }), | ||
25 | + ); | ||
26 | + }; | ||
27 | + | ||
28 | + const onSubmit = (e) => { | ||
29 | + e.preventDefault(); | ||
30 | + const { username, password } = form; | ||
31 | + dispatch(login({ username, password })); | ||
32 | + }; | ||
33 | + | ||
34 | + useEffect(() => { | ||
35 | + dispatch(initializeForm('login')); | ||
36 | + }, [dispatch]); | ||
37 | + | ||
38 | + useEffect(() => { | ||
39 | + if (authError) { | ||
40 | + console.log('Error Occured'); | ||
41 | + console.log(authError); | ||
42 | + setError('로그인 실패'); | ||
43 | + return; | ||
44 | + } | ||
45 | + if (auth) { | ||
46 | + console.log('Login Success'); | ||
47 | + dispatch(check()); | ||
48 | + } | ||
49 | + }, [auth, authError, dispatch]); | ||
50 | + | ||
51 | + useEffect(() => { | ||
52 | + if (user) { | ||
53 | + history.push('/'); | ||
54 | + try { | ||
55 | + localStorage.setItem('user', JSON.stringify(user)); | ||
56 | + } catch (e) { | ||
57 | + console.log('localStorage is not working'); | ||
58 | + } | ||
59 | + console.log(user); | ||
60 | + } | ||
61 | + }, [history, user]); | ||
62 | + | ||
63 | + return <AuthForm type="login"></AuthForm>; | ||
64 | +}; | ||
65 | + | ||
66 | +export default withRouter(LoginForm); |
1 | +import React, { useEffect, useState } from 'react'; | ||
2 | +import { useDispatch, useSelector } from 'react-redux'; | ||
3 | +import AuthForm from '../../components/auth/AuthForm'; | ||
4 | +import { withRouter } from 'react-router-dom'; | ||
5 | + | ||
6 | +const RegisterForm = ({ history }) => { | ||
7 | + const [error, setError] = useState(null); | ||
8 | + const dispatch = useDispatch(); | ||
9 | + const { form, auth, authError, user } = useSelector(({ auth, user }) => ({ | ||
10 | + form: auth.register, | ||
11 | + auth: auth.auth, | ||
12 | + authError: auth.authError, | ||
13 | + user: user.user, | ||
14 | + })); | ||
15 | + | ||
16 | + const onChange = (e) => { | ||
17 | + const { value, name } = e.target; | ||
18 | + dispatch( | ||
19 | + changeField({ | ||
20 | + form: 'register', | ||
21 | + key: name, | ||
22 | + value, | ||
23 | + }), | ||
24 | + ); | ||
25 | + }; | ||
26 | + | ||
27 | + const onSubmit = (e) => { | ||
28 | + e.preventDefault(); | ||
29 | + const { username, password, passwordConfirm } = form; | ||
30 | + if ([username, password, passwordConfirm].includes('')) { | ||
31 | + setError('빈 칸을 모두 입력하세요'); | ||
32 | + return; | ||
33 | + } | ||
34 | + if (password !== passwordConfirm) { | ||
35 | + //Todo Handle Error | ||
36 | + setError('비밀번호가 일치하지 않습니다.'); | ||
37 | + changeField({ form: 'register', key: 'password', value: '' }); | ||
38 | + changeField({ form: 'register', key: 'passwordConfirm', value: '' }); | ||
39 | + return; | ||
40 | + } | ||
41 | + dispatch(register({ username, password })); | ||
42 | + }; | ||
43 | + | ||
44 | + useEffect(() => { | ||
45 | + dispatch(initializeForm('register')); | ||
46 | + }, [dispatch]); | ||
47 | + useEffect(() => { | ||
48 | + if (authError) { | ||
49 | + if (authError.response.status === 409) { | ||
50 | + setError('이미 존재하는 계정명입니다.'); | ||
51 | + return; | ||
52 | + } | ||
53 | + setError('회원가입 실패'); | ||
54 | + return; | ||
55 | + } | ||
56 | + | ||
57 | + if (auth) { | ||
58 | + console.log('Register Success!'); | ||
59 | + console.log(auth); | ||
60 | + dispatch(check()); | ||
61 | + } | ||
62 | + }, [auth, authError, dispatch]); | ||
63 | + | ||
64 | + useEffect(() => { | ||
65 | + if (user) { | ||
66 | + console.log('SUCCESS check API'); | ||
67 | + history.push('/'); | ||
68 | + try { | ||
69 | + localStorage.setItem('user', JSON.stringify(user)); | ||
70 | + } catch (e) { | ||
71 | + console.log('localStorage is not working'); | ||
72 | + } | ||
73 | + } | ||
74 | + }, [history, user]); | ||
75 | + | ||
76 | + return <AuthForm type="register" form={form}></AuthForm>; | ||
77 | +}; | ||
78 | + | ||
79 | +export default withRouter(RegisterForm); |
1 | import { createAction, handleActions } from 'redux-actions'; | 1 | import { createAction, handleActions } from 'redux-actions'; |
2 | +import produce from 'immer'; | ||
2 | 3 | ||
3 | -const SAMPLE_ACTION = 'auth/SAMPLE_ACTION'; | 4 | +const CHANGE_FIELD = 'auth/CHANGE_FIELD'; |
5 | +const INITIALIZE_FORM = 'auth/INITIALIZE_FORM'; | ||
4 | 6 | ||
5 | export const sampleAction = createAction(SAMPLE_ACTION); | 7 | export const sampleAction = createAction(SAMPLE_ACTION); |
8 | +export const cahngeField = createAction( | ||
9 | + CHANGE_FIELD, | ||
10 | + ({ form, key, value }) => { | ||
11 | + form, key, value; | ||
12 | + }, | ||
13 | +); | ||
14 | +export const initializeForm = createAction(INITIALIZE_FORM, (form) => form); | ||
6 | 15 | ||
7 | -const initialState = {}; | 16 | +const initialState = { |
17 | + register: { | ||
18 | + username: '', | ||
19 | + password: '', | ||
20 | + passwordConfirm: '', | ||
21 | + }, | ||
22 | + login: { | ||
23 | + username: '', | ||
24 | + password: '', | ||
25 | + }, | ||
26 | +}; | ||
8 | 27 | ||
9 | const auth = handleActions( | 28 | const auth = handleActions( |
10 | { | 29 | { |
11 | - [SAMPLE_ACTION]: (state, action) => state, | 30 | + [CHANGE_FIELD]: (state, { payload: { form, key, value } }) => |
31 | + produce(state, (draft) => { | ||
32 | + draft[form][key] = value; | ||
33 | + }), | ||
34 | + [INITIALIZE_FORM]: (state, { payload: form }) => ({ | ||
35 | + ...state, | ||
36 | + [form]: initialState[form], | ||
37 | + }), | ||
12 | }, | 38 | }, |
13 | initialState, | 39 | initialState, |
14 | ); | 40 | ); | ... | ... |
1 | import React from 'react'; | 1 | import React from 'react'; |
2 | import AuthTemplate from '../components/auth/AuthTemplate'; | 2 | import AuthTemplate from '../components/auth/AuthTemplate'; |
3 | -import AuthForm from '../components/auth/AuthForm'; | 3 | +import LoginForm from '../containers/auth/LoginForm'; |
4 | 4 | ||
5 | const LoginPage = () => { | 5 | const LoginPage = () => { |
6 | return ( | 6 | return ( |
7 | <AuthTemplate> | 7 | <AuthTemplate> |
8 | - <AuthForm /> | 8 | + <LoginForm type="login" /> |
9 | </AuthTemplate> | 9 | </AuthTemplate> |
10 | ); | 10 | ); |
11 | }; | 11 | }; | ... | ... |
1 | import React from 'react'; | 1 | import React from 'react'; |
2 | +import AuthTemplate from '../components/auth/AuthTemplate'; | ||
3 | +import RegisterForm from '../containers/auth/RegisterForm'; | ||
2 | 4 | ||
3 | const RegisterPage = () => { | 5 | const RegisterPage = () => { |
4 | return ( | 6 | return ( |
5 | <AuthTemplate> | 7 | <AuthTemplate> |
6 | - <AuthForm /> | 8 | + <RegisterForm type="register" /> |
7 | </AuthTemplate> | 9 | </AuthTemplate> |
8 | ); | 10 | ); |
9 | }; | 11 | }; | ... | ... |
-
Please register or login to post a comment