Showing
36 changed files
with
1177 additions
and
468 deletions
1 | -# Getting Started with Create React App | ||
2 | - | ||
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). | 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). |
4 | 2 | ||
5 | ## Available Scripts | 3 | ## Available Scripts |
... | @@ -8,23 +6,23 @@ In the project directory, you can run: | ... | @@ -8,23 +6,23 @@ In the project directory, you can run: |
8 | 6 | ||
9 | ### `npm start` | 7 | ### `npm start` |
10 | 8 | ||
11 | -Runs the app in the development mode.\ | 9 | +Runs the app in the development mode.<br> |
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. | 10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. |
13 | 11 | ||
14 | -The page will reload if you make edits.\ | 12 | +The page will reload if you make edits.<br> |
15 | You will also see any lint errors in the console. | 13 | You will also see any lint errors in the console. |
16 | 14 | ||
17 | ### `npm test` | 15 | ### `npm test` |
18 | 16 | ||
19 | -Launches the test runner in the interactive watch mode.\ | 17 | +Launches the test runner in the interactive watch mode.<br> |
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. | 18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. |
21 | 19 | ||
22 | ### `npm run build` | 20 | ### `npm run build` |
23 | 21 | ||
24 | -Builds the app for production to the `build` folder.\ | 22 | +Builds the app for production to the `build` folder.<br> |
25 | It correctly bundles React in production mode and optimizes the build for the best performance. | 23 | It correctly bundles React in production mode and optimizes the build for the best performance. |
26 | 24 | ||
27 | -The build is minified and the filenames include the hashes.\ | 25 | +The build is minified and the filenames include the hashes.<br> |
28 | Your app is ready to be deployed! | 26 | Your app is ready to be deployed! |
29 | 27 | ||
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. |
... | @@ -35,7 +33,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d | ... | @@ -35,7 +33,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d |
35 | 33 | ||
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. |
37 | 35 | ||
38 | -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. | 36 | +Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. |
39 | 37 | ||
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. |
41 | 39 | ||
... | @@ -47,24 +45,24 @@ To learn React, check out the [React documentation](https://reactjs.org/). | ... | @@ -47,24 +45,24 @@ To learn React, check out the [React documentation](https://reactjs.org/). |
47 | 45 | ||
48 | ### Code Splitting | 46 | ### Code Splitting |
49 | 47 | ||
50 | -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) | 48 | +This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting |
51 | 49 | ||
52 | ### Analyzing the Bundle Size | 50 | ### Analyzing the Bundle Size |
53 | 51 | ||
54 | -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) | 52 | +This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size |
55 | 53 | ||
56 | ### Making a Progressive Web App | 54 | ### Making a Progressive Web App |
57 | 55 | ||
58 | -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) | 56 | +This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app |
59 | 57 | ||
60 | ### Advanced Configuration | 58 | ### Advanced Configuration |
61 | 59 | ||
62 | -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) | 60 | +This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration |
63 | 61 | ||
64 | ### Deployment | 62 | ### Deployment |
65 | 63 | ||
66 | -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) | 64 | +This section has moved here: https://facebook.github.io/create-react-app/docs/deployment |
67 | 65 | ||
68 | ### `npm run build` fails to minify | 66 | ### `npm run build` fails to minify |
69 | 67 | ||
70 | -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) | 68 | +This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify | ... | ... |
This diff could not be displayed because it is too large.
... | @@ -3,21 +3,22 @@ | ... | @@ -3,21 +3,22 @@ |
3 | "version": "0.1.0", | 3 | "version": "0.1.0", |
4 | "private": true, | 4 | "private": true, |
5 | "dependencies": { | 5 | "dependencies": { |
6 | - "@testing-library/jest-dom": "^5.12.0", | 6 | + "antd": "^3.24.1", |
7 | - "@testing-library/react": "^11.2.7", | 7 | + "axios": "^0.19.2", |
8 | - "@testing-library/user-event": "^12.8.3", | 8 | + "formik": "^1.5.8", |
9 | - "antd": "^4.16.1", | 9 | + "moment": "^2.24.0", |
10 | - "axios": "^0.21.1", | 10 | + "react": "^16.8.6", |
11 | - "http-proxy-middleware": "^2.0.0", | 11 | + "react-dom": "^16.8.6", |
12 | - "react": "^17.0.2", | 12 | + "react-icons": "^3.7.0", |
13 | - "react-dom": "^17.0.2", | 13 | + "react-redux": "^7.1.0-rc.1", |
14 | - "react-redux": "^7.2.4", | 14 | + "react-router-dom": "^5.0.1", |
15 | - "react-router-dom": "^5.2.0", | 15 | + "react-scripts": "3.4.1", |
16 | - "react-scripts": "4.0.3", | 16 | + "redux": "^4.0.0", |
17 | - "redux": "^4.1.0", | ||
18 | "redux-promise": "^0.6.0", | 17 | "redux-promise": "^0.6.0", |
19 | "redux-thunk": "^2.3.0", | 18 | "redux-thunk": "^2.3.0", |
20 | - "web-vitals": "^1.1.2" | 19 | + "yup": "^0.27.0", |
20 | + "core-js": "^3.6.4", | ||
21 | + "react-app-polyfill": "^1.0.6" | ||
21 | }, | 22 | }, |
22 | "scripts": { | 23 | "scripts": { |
23 | "start": "react-scripts start", | 24 | "start": "react-scripts start", |
... | @@ -26,10 +27,7 @@ | ... | @@ -26,10 +27,7 @@ |
26 | "eject": "react-scripts eject" | 27 | "eject": "react-scripts eject" |
27 | }, | 28 | }, |
28 | "eslintConfig": { | 29 | "eslintConfig": { |
29 | - "extends": [ | 30 | + "extends": "react-app" |
30 | - "react-app", | ||
31 | - "react-app/jest" | ||
32 | - ] | ||
33 | }, | 31 | }, |
34 | "browserslist": { | 32 | "browserslist": { |
35 | "production": [ | 33 | "production": [ |
... | @@ -42,5 +40,8 @@ | ... | @@ -42,5 +40,8 @@ |
42 | "last 1 firefox version", | 40 | "last 1 firefox version", |
43 | "last 1 safari version" | 41 | "last 1 safari version" |
44 | ] | 42 | ] |
43 | + }, | ||
44 | + "devDependencies": { | ||
45 | + "http-proxy-middleware": "^1.0.3" | ||
45 | } | 46 | } |
46 | } | 47 | } | ... | ... |
... | @@ -2,14 +2,9 @@ | ... | @@ -2,14 +2,9 @@ |
2 | <html lang="en"> | 2 | <html lang="en"> |
3 | <head> | 3 | <head> |
4 | <meta charset="utf-8" /> | 4 | <meta charset="utf-8" /> |
5 | - <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> | 5 | + <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" /> |
6 | <meta name="viewport" content="width=device-width, initial-scale=1" /> | 6 | <meta name="viewport" content="width=device-width, initial-scale=1" /> |
7 | <meta name="theme-color" content="#000000" /> | 7 | <meta name="theme-color" content="#000000" /> |
8 | - <meta | ||
9 | - name="description" | ||
10 | - content="Web site created using create-react-app" | ||
11 | - /> | ||
12 | - <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> | ||
13 | <!-- | 8 | <!-- |
14 | manifest.json provides metadata used when your web app is installed on a | 9 | manifest.json provides metadata used when your web app is installed on a |
15 | user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ | 10 | user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ | ... | ... |
... | @@ -6,16 +6,6 @@ | ... | @@ -6,16 +6,6 @@ |
6 | "src": "favicon.ico", | 6 | "src": "favicon.ico", |
7 | "sizes": "64x64 32x32 24x24 16x16", | 7 | "sizes": "64x64 32x32 24x24 16x16", |
8 | "type": "image/x-icon" | 8 | "type": "image/x-icon" |
9 | - }, | ||
10 | - { | ||
11 | - "src": "logo192.png", | ||
12 | - "type": "image/png", | ||
13 | - "sizes": "192x192" | ||
14 | - }, | ||
15 | - { | ||
16 | - "src": "logo512.png", | ||
17 | - "type": "image/png", | ||
18 | - "sizes": "512x512" | ||
19 | } | 9 | } |
20 | ], | 10 | ], |
21 | "start_url": ".", | 11 | "start_url": ".", | ... | ... |
... | @@ -9,12 +9,16 @@ import { | ... | @@ -9,12 +9,16 @@ import { |
9 | import LandingPage from './components/views/LandingPage/LandingPage' | 9 | import LandingPage from './components/views/LandingPage/LandingPage' |
10 | import LoginPage from './components/views/LoginPage/LoginPage' | 10 | import LoginPage from './components/views/LoginPage/LoginPage' |
11 | import RegisterPage from './components/views/RegisterPage/RegisterPage' | 11 | import RegisterPage from './components/views/RegisterPage/RegisterPage' |
12 | +import UploadPage from './components/views/UploadPage/UploadPage' | ||
12 | import auth from './hoc/authentication' | 13 | import auth from './hoc/authentication' |
14 | +import Navigation from './components/views/NavBar/NavBar' | ||
13 | 15 | ||
14 | function App() { | 16 | function App() { |
15 | return ( | 17 | return ( |
16 | <Router> | 18 | <Router> |
19 | + <Navigation /> | ||
17 | <div> | 20 | <div> |
21 | + | ||
18 | <Switch> | 22 | <Switch> |
19 | {/* null, false에 대한 옵션 설명은 auth로 가서 확인*/} | 23 | {/* null, false에 대한 옵션 설명은 auth로 가서 확인*/} |
20 | 24 | ||
... | @@ -24,6 +28,7 @@ function App() { | ... | @@ -24,6 +28,7 @@ function App() { |
24 | 28 | ||
25 | <Route exact path="/register" component={auth(RegisterPage, false)}/> | 29 | <Route exact path="/register" component={auth(RegisterPage, false)}/> |
26 | 30 | ||
31 | + <Route exact path="/product/upload" component={auth(UploadPage, true)}/> | ||
27 | {/* | 32 | {/* |
28 | <Route exact path="/" component={LandingPage} /> | 33 | <Route exact path="/" component={LandingPage} /> |
29 | 34 | ... | ... |
1 | // type들만 관리하는 곳 | 1 | // type들만 관리하는 곳 |
2 | 2 | ||
3 | -export const LOGIN_USER = "login_user"; | ||
4 | -export const REGISTER_USER = "resgier_user"; | ||
5 | -export const AUTH_USER = "auth_user"; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
3 | +export const LOGIN_USER = 'login_user'; | ||
4 | +export const REGISTER_USER = 'register_user'; | ||
5 | +export const AUTH_USER = 'auth_user'; | ||
6 | +export const LOGOUT_USER = 'logout_user'; | ... | ... |
We-Shop/client/src/_actions/user_actions.js
0 → 100644
1 | +import axios from 'axios'; | ||
2 | +import { | ||
3 | + LOGIN_USER, | ||
4 | + REGISTER_USER, | ||
5 | + AUTH_USER, | ||
6 | + LOGOUT_USER, | ||
7 | +} from './types'; | ||
8 | +import { USER_SERVER } from '../components/Config.js'; | ||
9 | + | ||
10 | +export function loginUser(logInfo){ | ||
11 | + const request = axios.post(`${USER_SERVER}/login`,logInfo)// logInfo를 post로 전달 | ||
12 | + .then(response => response.data); // 서버에서 받은 데이터를 request에 저장 | ||
13 | + | ||
14 | +return { // return을 통해 Reducer로 보냄 | ||
15 | + // Reducer에서 previousState, action을 이용해 nextState로 만들기 때문 :: (previousState, action) => nextState | ||
16 | + // request를 reducer로 보내는 작업 | ||
17 | + | ||
18 | + // action은 type과 response을 넣어줘야 함 | ||
19 | + type: LOGIN_USER, | ||
20 | + payload: request // payroad == response | ||
21 | +} | ||
22 | +} | ||
23 | + | ||
24 | +export function registerUser(dataToSubmit){ | ||
25 | + const request = axios.post(`${USER_SERVER}/register`,dataToSubmit) | ||
26 | + .then(response => response.data); | ||
27 | + | ||
28 | + return { | ||
29 | + type: REGISTER_USER, | ||
30 | + payload: request | ||
31 | + } | ||
32 | +} | ||
33 | + | ||
34 | + | ||
35 | + | ||
36 | +export function auth(){ | ||
37 | + const request = axios.get(`${USER_SERVER}/auth`) | ||
38 | + .then(response => response.data); | ||
39 | + | ||
40 | + return { | ||
41 | + type: AUTH_USER, | ||
42 | + payload: request | ||
43 | + } | ||
44 | +} | ||
45 | + | ||
46 | +export function logoutUser(){ | ||
47 | + const request = axios.get(`${USER_SERVER}/logout`) | ||
48 | + .then(response => response.data); | ||
49 | + | ||
50 | + return { | ||
51 | + type: LOGOUT_USER, | ||
52 | + payload: request | ||
53 | + } | ||
54 | +} | ||
55 | + |
1 | -// Redux에 있는 Store에 Reducer들이 여러가지 있을 수 있다. | ||
2 | -// -> why? : Reducer 안에서 하는 일은 | ||
3 | -// state가 어떻게 변하는지를 보여준 다음, 변한 마지막 값을 return 해주는 것. | ||
4 | -// 웹서비스를 제작하면서 user state, comment state ... 등등 다양한 기능에 대한 state들이 존재할 수 있고 | ||
5 | -// 각각 state마다 reducer가 있어서 user reducer, comment reducer ... 등등 다양한 reducer들이 존재할 수 있음. | ||
6 | -// ------------------------------------ | ||
7 | -// 이렇게 나눠진 다양한 reducer을 combineReducers을 통해 rootReducer에서 하나로 합쳐주는 기능을 만들 것임. | ||
8 | import { combineReducers } from 'redux'; | 1 | import { combineReducers } from 'redux'; |
9 | -import user from './user_reducer'; // user(회원가입, 로그인, 인증, 로그아웃 기능이 있음) reducer | 2 | +import user from './user_reducer'; |
10 | -// import comment from './comment_reducer'; // comment기능이 있을 때 reducer | ||
11 | 3 | ||
12 | -const rootReducer = combineReducers( { | 4 | +const rootReducer = combineReducers({ |
13 | - user | 5 | + user, |
14 | -}) | 6 | +}); |
15 | 7 | ||
16 | -// 다른 곳에서도 rootReducer을 쓸 수 있도록 | ||
17 | export default rootReducer; | 8 | export default rootReducer; |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | import { | 1 | import { |
2 | LOGIN_USER, | 2 | LOGIN_USER, |
3 | REGISTER_USER, | 3 | REGISTER_USER, |
4 | - AUTH_USER | 4 | + AUTH_USER, |
5 | + LOGOUT_USER, | ||
5 | } from '../_actions/types'; | 6 | } from '../_actions/types'; |
7 | + | ||
6 | 8 | ||
7 | - | 9 | +export default function(state={},action){ |
8 | -// reducer은 (previousState, action) => (nextState)로 | 10 | + switch(action.type){ |
9 | -export default function (prevState = {}, action) { | ||
10 | - switch (action.type) { | ||
11 | - case LOGIN_USER: | ||
12 | - return {...prevState, loginSuccess:action.payload} // 위의 prevState를 그대로 가져오고, | ||
13 | - // user_action.js에 있는 payload를 그대로 가져와서 return. | ||
14 | - // loginSuccess는 server/index.js 에서 login에 성공하면 json type으로 loginSuccess: true를 전달하라고 했기 때문 | ||
15 | - break; | ||
16 | case REGISTER_USER: | 11 | case REGISTER_USER: |
17 | - return {...prevState, success:action.payload} | 12 | + return {...state, register: action.payload } |
18 | - // success는 server/index.js 에서 register에 성공하면 json type으로 success: true를 전달하라고 했기 때문 | 13 | + case LOGIN_USER: |
19 | - break; | 14 | + return { ...state, loginSucces: action.payload } |
20 | - | ||
21 | case AUTH_USER: | 15 | case AUTH_USER: |
22 | - return {...prevState, user:action.payload} | 16 | + return {...state, userData: action.payload } |
23 | - break; | 17 | + case LOGOUT_USER: |
18 | + return {...state } | ||
24 | default: | 19 | default: |
25 | - return prevState; | 20 | + return state; |
26 | } | 21 | } |
27 | } | 22 | } |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
We-Shop/client/src/components/App.js
0 → 100644
1 | +import React, { Suspense } from 'react'; | ||
2 | +import { Route, Switch } from "react-router-dom"; | ||
3 | +import Auth from "../hoc/auth"; | ||
4 | +// pages for this product | ||
5 | +import LandingPage from "./views/LandingPage/LandingPage.js"; | ||
6 | +import LoginPage from "./views/LoginPage/LoginPage.js"; | ||
7 | +import RegisterPage from "./views/RegisterPage/RegisterPage.js"; | ||
8 | +import NavBar from "./views/NavBar/NavBar"; | ||
9 | +import Footer from "./views/Footer/Footer" | ||
10 | + | ||
11 | +//null Anyone Can go inside | ||
12 | +//true only logged in user can go inside | ||
13 | +//false logged in user can't go inside | ||
14 | + | ||
15 | +function App() { | ||
16 | + return ( | ||
17 | + <Suspense fallback={(<div>Loading...</div>)}> | ||
18 | + <NavBar /> | ||
19 | + <div style={{ paddingTop: '69px', minHeight: 'calc(100vh - 80px)' }}> | ||
20 | + <Switch> | ||
21 | + <Route exact path="/" component={Auth(LandingPage, null)} /> | ||
22 | + <Route exact path="/login" component={Auth(LoginPage, false)} /> | ||
23 | + <Route exact path="/register" component={Auth(RegisterPage, false)} /> | ||
24 | + </Switch> | ||
25 | + </div> | ||
26 | + <Footer /> | ||
27 | + </Suspense> | ||
28 | + ); | ||
29 | +} | ||
30 | + | ||
31 | +export default App; |
We-Shop/client/src/components/Config.js
0 → 100644
1 | import React from 'react' | 1 | import React from 'react' |
2 | +import {Icon} from 'antd'; | ||
2 | 3 | ||
3 | function Footer() { | 4 | function Footer() { |
4 | return ( | 5 | return ( |
5 | - <div> | 6 | + <div style={{ |
6 | - Footer | 7 | + height: '80px', display: 'flex', |
8 | + flexDirection: 'column', alignItems: 'center', | ||
9 | + justifyContent: 'center', fontSize:'1rem' | ||
10 | + }}> | ||
11 | + <p> 2018110650 박민정</p> | ||
7 | </div> | 12 | </div> |
8 | ) | 13 | ) |
9 | } | 14 | } |
10 | 15 | ||
11 | export default Footer | 16 | export default Footer |
12 | -Footer | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | -import React, {useEffect} from 'react' | 1 | +import React from 'react' |
2 | -import axios from 'axios' | 2 | +import { FaCode } from "react-icons/fa"; |
3 | -import { withRouter } from 'react-router-dom'; | ||
4 | - | ||
5 | -function LandingPage(props) { | ||
6 | - | ||
7 | - // 로그아웃 버튼 클릭 됐을 때 | ||
8 | - const onLogoutClickedEvent = () => { | ||
9 | - axios.get('/api/users/logout') | ||
10 | - .then(response => { | ||
11 | - // 만약 success:true이면 로그인 페이지로 가기 | ||
12 | - if(response.data.success) | ||
13 | - props.history.push("/login"); | ||
14 | - else | ||
15 | - alert("Fail to logout.") | ||
16 | - }) | ||
17 | - } | ||
18 | - // 랜딩페이지에 들어오자마자 | ||
19 | - useEffect(() => { | ||
20 | - axios.get('/api/hello') // get request를 서버로 보냄 (endpoint는 /api/hello) | ||
21 | - .then(response => console.log(response.data)) // 서버로부터 응답 받은 내용을 콘솔에 출력 | ||
22 | - }, []) | ||
23 | 3 | ||
4 | +function LandingPage() { | ||
24 | return ( | 5 | return ( |
25 | - <div style={{justifyContent:'center', alignItems: 'center', display:'flex', width:'100%'}}> | 6 | + <> |
26 | - <h1>시작 페이지</h1> | 7 | + <div className="app"> |
27 | - <button onClick ={onLogoutClickedEvent}> Logout </button> | 8 | + 하.. |
28 | - </div> | 9 | + </div> |
29 | - | 10 | + |
30 | - | 11 | + </> |
31 | ) | 12 | ) |
32 | } | 13 | } |
33 | 14 | ||
34 | - | ||
35 | -export default withRouter(LandingPage) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
15 | +export default LandingPage | ... | ... |
1 | -import axios from 'axios'; | 1 | +import React, { useState } from "react"; |
2 | -//import { response } from 'express'; | 2 | +import { withRouter } from "react-router-dom"; |
3 | -import React from 'react' | 3 | +import { loginUser } from "../../../_actions/user_actions"; |
4 | -import {useState} from 'react' | 4 | +import { Formik } from 'formik'; |
5 | -import {useDispatch} from 'react-redux'; | 5 | +import * as Yup from 'yup'; |
6 | -import {loginUser} from '../../../_actions/user_action' | 6 | +import { Form, Icon, Input, Button, Checkbox, Typography } from 'antd'; |
7 | -import { withRouter } from 'react-router-dom'; | 7 | +import { useDispatch } from "react-redux"; |
8 | + | ||
9 | +const { Title } = Typography; | ||
8 | 10 | ||
9 | function LoginPage(props) { | 11 | function LoginPage(props) { |
10 | - | 12 | + const dispatch = useDispatch(); |
13 | + //const rememberMeChecked = localStorage.getItem("rememberMe") ? true : false; | ||
14 | + | ||
15 | + const [Error, setError] = useState('') | ||
16 | + //const [rememberMe, setRememberMe] = useState(rememberMeChecked) | ||
11 | 17 | ||
12 | - // 이 로그인페이지 안에서 input에 타이핑을 함으로써 데이터를 변화시켜주므로 state 사용. | 18 | + // const handleRememberMe = () => { |
13 | - // 1-1. state을 사용하기 위해 state 만들어줌. | 19 | + // setRememberMe(!rememberMe) |
14 | - const [Email, setEmail] = useState(""); // 1-2. email을 위한 state | 20 | + // }; |
15 | - const [Password, setPassword] = useState(""); // 1-2. password를 위한 state | ||
16 | - //1-3. 아래 input value에 넣어줌 | ||
17 | 21 | ||
18 | - // 2-1. 타이핑할 때 타이핑 하는 거 보이게 하도록 핸들러를 만들어줌 | 22 | + //const initialEmail = localStorage.getItem("rememberMe") ? localStorage.getItem("rememberMe") : ''; |
19 | - const emailEvent = (event) => { | ||
20 | - setEmail(event.currentTarget.value) | ||
21 | - } | ||
22 | - const passwordEvent = (event) => { | ||
23 | - setPassword(event.currentTarget.value) | ||
24 | - | ||
25 | - } | ||
26 | 23 | ||
27 | - const dispatch = useDispatch(); | 24 | + return ( |
28 | - const submitEvent = (event) => { | 25 | + <Formik |
29 | - event.preventDefault(); // 이걸 하지 않으면 버튼을 누를 때마다 refresh돼서 데이터 처리를 할 수 없음 | 26 | + initialValues={{ |
30 | - | 27 | + email: '', |
31 | - //console.log('Email', Email); // 잘 나오는지 확인 | 28 | + password: '', |
32 | - //console.log('Password', Password); // 잘 나오는지 확인 | 29 | + }} |
30 | + // validationSchema={Yup.object().shape({ | ||
31 | + // email: Yup.string() | ||
32 | + // .email('Email is invalid') | ||
33 | + // .required('Email is required'), | ||
34 | + // password: Yup.string() | ||
35 | + // .min(6, 'Password must be at least 6 characters') | ||
36 | + // .required('Password is required'), | ||
37 | + // })} | ||
38 | + onSubmit={(values, { setSubmitting }) => { | ||
39 | + setTimeout(() => { | ||
40 | + let dataToSubmit = { | ||
41 | + email: values.email, | ||
42 | + password: values.password | ||
43 | + }; | ||
33 | 44 | ||
34 | - let logInfo = { // 보내주기 위해 저장 | 45 | + dispatch(loginUser(dataToSubmit)) |
35 | - email: Email, | ||
36 | - password: Password | ||
37 | - } | ||
38 | - | ||
39 | - dispatch(loginUser(logInfo)) // _actions폴더 user_action.js에 있음 | ||
40 | .then(response => { | 46 | .then(response => { |
41 | - if(response.payload.loginSuccess) | 47 | + if (response.payload.loginSuccess) { |
42 | - props.history.push('/'); | 48 | + window.localStorage.setItem('userId', response.payload.userId); |
43 | - | 49 | + // if (rememberMe === true) { |
44 | - else | 50 | + // window.localStorage.setItem('rememberMe', values.id); |
45 | - alert('Error'); | 51 | + // } else { |
46 | - | 52 | + // localStorage.removeItem('rememberMe'); |
53 | + // } | ||
54 | + props.history.push("/"); | ||
55 | + } else { | ||
56 | + setError('이메일 또는 비밀번호가 올바르지 않습니다.') | ||
57 | + } | ||
58 | + }) | ||
59 | + .catch(err => { | ||
60 | + setError('이메일 또는 비밀번호가 올바르지 않습니다.') | ||
61 | + setTimeout(() => { | ||
62 | + setError("") | ||
63 | + }, 3000); | ||
64 | + }); | ||
65 | + setSubmitting(false); | ||
66 | + }, 500); | ||
67 | + }} | ||
68 | + > | ||
69 | + {props => { | ||
70 | + const { | ||
71 | + values, | ||
72 | + touched, | ||
73 | + errors, | ||
74 | + dirty, | ||
75 | + isSubmitting, | ||
76 | + handleChange, | ||
77 | + handleBlur, | ||
78 | + handleSubmit, | ||
79 | + handleReset, | ||
80 | + } = props; | ||
81 | + return ( | ||
82 | + <div className="app"> | ||
83 | + | ||
84 | + <Title level={2}>로그인</Title> | ||
85 | + <form onSubmit={handleSubmit} style={{ width: '350px' }}> | ||
86 | + | ||
87 | + <Form.Item required> | ||
88 | + <Input | ||
89 | + id="email" | ||
90 | + prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} | ||
91 | + placeholder="Enter your email" | ||
92 | + type="email" | ||
93 | + value={values.email} | ||
94 | + onChange={handleChange} | ||
95 | + onBlur={handleBlur} | ||
96 | + className={ | ||
97 | + errors.email && touched.email ? 'text-input error' : 'text-input' | ||
98 | + } | ||
99 | + /> | ||
100 | + {errors.email && touched.email && ( | ||
101 | + <div className="input-feedback">{errors.email}</div> | ||
102 | + )} | ||
103 | + </Form.Item> | ||
47 | 104 | ||
48 | - }) | 105 | + <Form.Item required> |
49 | - | 106 | + <Input |
50 | - } | 107 | + id="password" |
51 | - return ( | 108 | + prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} |
52 | - <div style={{ | 109 | + placeholder="Enter your password" |
53 | - justifyContent:'center', alignItems: 'center', display:'flex', width:'100%', height:'50vh' | 110 | + type="password" |
54 | - }}> | 111 | + value={values.password} |
55 | - <form onSubmit={submitEvent}> | 112 | + onChange={handleChange} |
56 | - <label>Email</label> | 113 | + onBlur={handleBlur} |
57 | - <input type="email" value={Email} onChange={emailEvent} /> | 114 | + className={ |
58 | - {/* input type="email"이라서 '이메일 주소에 '@'를 포함해주세요'라는 경고문 뜸. */} | 115 | + errors.password && touched.password ? 'text-input error' : 'text-input' |
59 | - <label>Password</label> | 116 | + } |
60 | - <input type="password" value={Password} onChange={passwordEvent} /> | 117 | + /> |
61 | - <br/> | 118 | + {errors.password && touched.password && ( |
62 | - <button> | 119 | + <div className="input-feedback">{errors.password}</div> |
63 | - Login | 120 | + )} |
64 | - </button> | 121 | + </Form.Item> |
122 | + | ||
123 | + {Error && ( | ||
124 | + <label ><p style={{ color: '#508DFF', fontSize: '0.8rem' }}>{Error}</p></label> | ||
125 | + )} | ||
126 | + | ||
127 | + <Form.Item> | ||
128 | + {/* <Checkbox id="rememberMe" onChange={handleRememberMe} checked={rememberMe} >Remember me</Checkbox> | ||
129 | + <a className="login-form-forgot" href="/reset_user" style={{ float: 'right' }}> | ||
130 | + forgot password | ||
131 | + </a> */} | ||
132 | + <div> | ||
133 | + <Button type="primary" htmlType="submit" className="login-form-button" style={{ minWidth: '100%' }} disabled={isSubmitting} onSubmit={handleSubmit}> | ||
134 | + 로그인 | ||
135 | + </Button> | ||
136 | + </div> | ||
137 | + <a href="/register" style={{ minWidth: '100%' }}>회원가입</a> | ||
138 | + </Form.Item> | ||
65 | </form> | 139 | </form> |
66 | - </div> | 140 | + </div> |
67 | - ) | 141 | + ); |
68 | -} | 142 | + }} |
143 | + </Formik> | ||
144 | + ); | ||
145 | +}; | ||
146 | + | ||
147 | +export default withRouter(LoginPage); | ||
148 | + | ||
69 | 149 | ||
70 | -export default withRouter(LoginPage) | ... | ... |
1 | -import React from 'react' | 1 | +import React, { useState } from 'react'; |
2 | +import LeftMenu from './Sections/LeftMenu'; | ||
3 | +import RightMenu from './Sections/RightMenu'; | ||
4 | +import { Drawer, Button, Icon } from 'antd'; | ||
5 | +import './Sections/Navbar.css'; | ||
2 | 6 | ||
3 | function NavBar() { | 7 | function NavBar() { |
4 | - return ( | 8 | + const [visible, setVisible] = useState(false) |
5 | - <div> | 9 | + |
6 | - NavBar | 10 | + const showDrawer = () => { |
11 | + setVisible(true) | ||
12 | + }; | ||
13 | + | ||
14 | + const onClose = () => { | ||
15 | + setVisible(false) | ||
16 | + }; | ||
17 | + | ||
18 | + return ( | ||
19 | + <nav className="menu" style={{ position: 'fixed', zIndex: 5, width: '100%' }}> | ||
20 | + <div className="menu__logo"> | ||
21 | + <a href="/">Logo</a> | ||
22 | + </div> | ||
23 | + <div className="menu__container"> | ||
24 | + <div className="menu_left"> | ||
25 | + <LeftMenu mode="horizontal" /> | ||
26 | + </div> | ||
27 | + <div className="menu_rigth"> | ||
28 | + <RightMenu mode="horizontal" /> | ||
7 | </div> | 29 | </div> |
8 | - ) | 30 | + <Button |
31 | + className="menu__mobile-button" | ||
32 | + type="primary" | ||
33 | + onClick={showDrawer} | ||
34 | + > | ||
35 | + <Icon type="align-right" /> | ||
36 | + </Button> | ||
37 | + <Drawer | ||
38 | + title="Basic Drawer" | ||
39 | + placement="right" | ||
40 | + className="menu_drawer" | ||
41 | + closable={false} | ||
42 | + onClose={onClose} | ||
43 | + visible={visible} | ||
44 | + > | ||
45 | + <LeftMenu mode="inline" /> | ||
46 | + <RightMenu mode="inline" /> | ||
47 | + </Drawer> | ||
48 | + </div> | ||
49 | + </nav> | ||
50 | + ) | ||
9 | } | 51 | } |
10 | 52 | ||
11 | -export default NavBar | 53 | +export default NavBar |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | +import React from 'react'; | ||
2 | +import { Menu } from 'antd'; | ||
3 | +const SubMenu = Menu.SubMenu; | ||
4 | +const MenuItemGroup = Menu.ItemGroup; | ||
5 | + | ||
6 | +function LeftMenu(props) { | ||
7 | + return ( | ||
8 | + <Menu mode={props.mode}> | ||
9 | + <Menu.Item key="mail"> | ||
10 | + <a href="/">Home</a> | ||
11 | + </Menu.Item> | ||
12 | + <SubMenu title={<span>Blogs</span>}> | ||
13 | + <MenuItemGroup title="Item 1"> | ||
14 | + <Menu.Item key="setting:1">Option 1</Menu.Item> | ||
15 | + <Menu.Item key="setting:2">Option 2</Menu.Item> | ||
16 | + </MenuItemGroup> | ||
17 | + <MenuItemGroup title="Item 2"> | ||
18 | + <Menu.Item key="setting:3">Option 3</Menu.Item> | ||
19 | + <Menu.Item key="setting:4">Option 4</Menu.Item> | ||
20 | + </MenuItemGroup> | ||
21 | + </SubMenu> | ||
22 | + </Menu> | ||
23 | + ) | ||
24 | +} | ||
25 | + | ||
26 | +export default LeftMenu | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +@import "~antd/dist/antd.css"; | ||
2 | + | ||
3 | +.menu { | ||
4 | + padding: 0 20px; | ||
5 | + border-bottom: solid 1px #e8e8e8; | ||
6 | + overflow: auto; | ||
7 | + box-shadow: 0 0 30px #f3f1f1; | ||
8 | + background-color: white; | ||
9 | +} | ||
10 | + | ||
11 | +.menu__logo { | ||
12 | + width: 150px; | ||
13 | + float: left; | ||
14 | +} | ||
15 | + | ||
16 | +.menu__logo a { | ||
17 | + display: inline-block; | ||
18 | + font-size: 20px; | ||
19 | + padding: 19px 20px; | ||
20 | +} | ||
21 | + | ||
22 | +.menu__container .ant-menu-item { | ||
23 | + padding: 0px 5px; | ||
24 | +} | ||
25 | + | ||
26 | +.menu__container .ant-menu-submenu-title { | ||
27 | + padding: 10px 20px; | ||
28 | +} | ||
29 | + | ||
30 | +.menu__container .ant-menu-item a, | ||
31 | +.menu__container .ant-menu-submenu-title a { | ||
32 | + padding: 10px 15px; | ||
33 | +} | ||
34 | + | ||
35 | +.menu__container .ant-menu-horizontal { | ||
36 | + border-bottom: none; | ||
37 | +} | ||
38 | + | ||
39 | +.menu__container .menu_left { | ||
40 | + float: left; | ||
41 | +} | ||
42 | + | ||
43 | +.menu__container .menu_rigth { | ||
44 | + float: right; | ||
45 | +} | ||
46 | + | ||
47 | +.menu__mobile-button { | ||
48 | + float: right; | ||
49 | + height: 32px; | ||
50 | + padding: 6px; | ||
51 | + margin-top: 8px; | ||
52 | + display: none !important; /* use of important to overwrite ant-btn */ | ||
53 | + background: #3e91f7; | ||
54 | +} | ||
55 | + | ||
56 | +.menu_drawer .ant-drawer-body { | ||
57 | + padding: 0 !important; | ||
58 | +} | ||
59 | + | ||
60 | +/* align header of Drawer with header of page */ | ||
61 | +.menu_drawer .ant-drawer-header { | ||
62 | + padding: 14px 24px !important; | ||
63 | +} | ||
64 | + | ||
65 | +@media (max-width: 767px) { | ||
66 | + .menu__mobile-button { | ||
67 | + display: inline-block !important; | ||
68 | + } | ||
69 | + | ||
70 | + .menu_left, | ||
71 | + .menu_rigth { | ||
72 | + display: none; | ||
73 | + } | ||
74 | + | ||
75 | + .menu__logo a { | ||
76 | + margin-left: -20px; | ||
77 | + } | ||
78 | + | ||
79 | + .menu__container .ant-menu-item, | ||
80 | + .menu__container .ant-menu-submenu-title { | ||
81 | + padding: 1px 20px; | ||
82 | + } | ||
83 | + | ||
84 | + .menu__logo a { | ||
85 | + padding: 10px 20px; | ||
86 | + } | ||
87 | +} |
1 | + | ||
2 | +import React from 'react'; | ||
3 | +import { Menu } from 'antd'; | ||
4 | +import axios from 'axios'; | ||
5 | +import { USER_SERVER } from '../../../Config'; | ||
6 | +import { withRouter } from 'react-router-dom'; | ||
7 | +import { useSelector } from "react-redux"; | ||
8 | + | ||
9 | +function RightMenu(props) { | ||
10 | + const user = useSelector(state => state.user) | ||
11 | + | ||
12 | + const logoutHandler = () => { | ||
13 | + axios.get(`${USER_SERVER}/logout`).then(response => { | ||
14 | + if (response.status === 200) { | ||
15 | + props.history.push("/login"); | ||
16 | + } else { | ||
17 | + alert('Log Out Failed') | ||
18 | + } | ||
19 | + }); | ||
20 | + }; | ||
21 | + | ||
22 | + //console.log(user.userData); | ||
23 | + //console.log(!user.userData.isAuth); | ||
24 | + | ||
25 | + if (user.userData && !user.userData.isAuth) { | ||
26 | + return ( | ||
27 | + <Menu mode={props.mode}> | ||
28 | + <Menu.Item key="mail"> | ||
29 | + <a href="/login">로그인</a> | ||
30 | + </Menu.Item> | ||
31 | + <Menu.Item key="app"> | ||
32 | + <a href="/register">회원가입</a> | ||
33 | + </Menu.Item> | ||
34 | + </Menu> | ||
35 | + ) | ||
36 | + } else { | ||
37 | + return ( | ||
38 | + <Menu mode={props.mode}> | ||
39 | + <Menu.Item key="logout"> | ||
40 | + <a onClick={logoutHandler}>로그아웃</a> | ||
41 | + </Menu.Item> | ||
42 | + </Menu> | ||
43 | + ) | ||
44 | + } | ||
45 | +} | ||
46 | + | ||
47 | +export default withRouter(RightMenu); | ||
48 | + |
1 | +import React from 'react'; | ||
2 | +import { Menu } from 'antd'; | ||
3 | +const SubMenu = Menu.SubMenu; | ||
4 | +const MenuItemGroup = Menu.ItemGroup; | ||
5 | + | ||
6 | +function LeftMenu(props) { | ||
7 | + return ( | ||
8 | + <Menu mode={props.mode}> | ||
9 | + <Menu.Item key="mail"> | ||
10 | + <a href="/">Home</a> | ||
11 | + </Menu.Item> | ||
12 | + <SubMenu title={<span>Blogs</span>}> | ||
13 | + <MenuItemGroup title="Item 1"> | ||
14 | + <Menu.Item key="setting:1">Option 1</Menu.Item> | ||
15 | + <Menu.Item key="setting:2">Option 2</Menu.Item> | ||
16 | + </MenuItemGroup> | ||
17 | + <MenuItemGroup title="Item 2"> | ||
18 | + <Menu.Item key="setting:3">Option 3</Menu.Item> | ||
19 | + <Menu.Item key="setting:4">Option 4</Menu.Item> | ||
20 | + </MenuItemGroup> | ||
21 | + </SubMenu> | ||
22 | + </Menu> | ||
23 | + ) | ||
24 | +} | ||
25 | + | ||
26 | +export default LeftMenu | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
File mode changed
1 | +/* eslint-disable jsx-a11y/anchor-is-valid */ | ||
2 | +import React from 'react'; | ||
3 | +import { Menu } from 'antd'; | ||
4 | +import axios from 'axios'; | ||
5 | +//import { USER_SERVER } from '../../../Config'; | ||
6 | +import { withRouter } from 'react-router-dom'; | ||
7 | +import { useSelector } from "react-redux"; | ||
8 | + | ||
9 | +function RightMenu(props) { | ||
10 | + const user = useSelector(state => state.user) | ||
11 | + | ||
12 | + const logoutHandler = () => { | ||
13 | + axios.get('/api/users/logout').then(response => { | ||
14 | + if (response.status === 200) { | ||
15 | + props.history.push("/login"); | ||
16 | + } else { | ||
17 | + alert('Log Out Failed') | ||
18 | + } | ||
19 | + }); | ||
20 | + }; | ||
21 | + | ||
22 | + //console.log(user.userData) | ||
23 | + //console.log(!user.userData.isAuth) | ||
24 | + if (user.userData && !user.userData.isAuth) { | ||
25 | + return ( | ||
26 | + <Menu mode={props.mode}> | ||
27 | + <Menu.Item key="mail"> | ||
28 | + <a href="/login">Signin</a> | ||
29 | + </Menu.Item> | ||
30 | + <Menu.Item key="app"> | ||
31 | + <a href="/register">Signup</a> | ||
32 | + </Menu.Item> | ||
33 | + </Menu> | ||
34 | + ) | ||
35 | + } else { | ||
36 | + return ( | ||
37 | + <Menu mode={props.mode}> | ||
38 | + <Menu.Item key="logout"> | ||
39 | + <a onClick={logoutHandler}>Logout</a> | ||
40 | + </Menu.Item> | ||
41 | + </Menu> | ||
42 | + ) | ||
43 | + } | ||
44 | +} | ||
45 | + | ||
46 | +export default withRouter(RightMenu); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | -import React from 'react' | 1 | +import React from "react"; |
2 | -import {useState} from 'react' | 2 | +import moment from "moment"; |
3 | -import {useDispatch} from 'react-redux'; | 3 | +import { Formik } from 'formik'; |
4 | -import {RegisterUser} from '../../../_actions/user_action' | 4 | +import * as Yup from 'yup'; |
5 | -import { withRouter } from 'react-router-dom'; | 5 | +import { registerUser } from "../../../_actions/user_actions"; |
6 | +import { useDispatch } from "react-redux"; | ||
6 | 7 | ||
7 | -function RegisterPage(props) { | 8 | +import { |
8 | - | 9 | + Form, |
9 | - // 이 로그인페이지 안에서 input에 타이핑을 함으로써 데이터를 변화시켜주므로 state 사용. | 10 | + Input, |
10 | - // 1-1. state을 사용하기 위해 state 만들어줌. | 11 | + Button, |
11 | - const [Name, setName] = useState(""); | 12 | +} from 'antd'; |
12 | - const [Email, setEmail] = useState(""); // 1-2. email을 위한 state | ||
13 | - const [Password, setPassword] = useState(""); // 1-2. password를 위한 state | ||
14 | - const [Password2, setPassword2] = useState(""); | ||
15 | 13 | ||
16 | - //1-3. 아래 input value에 넣어줌 | 14 | +const formItemLayout = { |
15 | + labelCol: { | ||
16 | + xs: { span: 24 }, | ||
17 | + sm: { span: 8 }, | ||
18 | + }, | ||
19 | + wrapperCol: { | ||
20 | + xs: { span: 24 }, | ||
21 | + sm: { span: 16 }, | ||
22 | + }, | ||
23 | +}; | ||
24 | +const tailFormItemLayout = { | ||
25 | + wrapperCol: { | ||
26 | + xs: { | ||
27 | + span: 24, | ||
28 | + offset: 0, | ||
29 | + }, | ||
30 | + sm: { | ||
31 | + span: 16, | ||
32 | + offset: 8, | ||
33 | + }, | ||
34 | + }, | ||
35 | +}; | ||
17 | 36 | ||
18 | - // 2-1. 타이핑할 때 타이핑 하는 거 보이게 하도록 핸들러를 만들어줌 | 37 | +function RegisterPage(props) { |
19 | - const emailEvent = (event) => { | 38 | + const dispatch = useDispatch(); |
20 | - setEmail(event.currentTarget.value) | 39 | + return ( |
21 | - } | ||
22 | - const passwordEvent = (event) => { | ||
23 | - setPassword(event.currentTarget.value) | ||
24 | - | ||
25 | - } | ||
26 | - const password2Event = (event) => { | ||
27 | - setPassword2(event.currentTarget.value) | ||
28 | - | ||
29 | - } | ||
30 | - const NameEvent = (event) => { | ||
31 | - setName(event.currentTarget.value) | ||
32 | - | ||
33 | - } | ||
34 | 40 | ||
35 | - const dispatch = useDispatch(); | 41 | + <Formik |
36 | - const submitEvent = (event) => { | 42 | + initialValues={{ |
37 | - event.preventDefault(); // 이걸 하지 않으면 버튼을 누를 때마다 refresh돼서 데이터 처리를 할 수 없음 | 43 | + email: '', |
38 | - | 44 | + |
39 | - //console.log('Email', Email); // 잘 나오는지 확인 | 45 | + name: '', |
40 | - //console.log('Password', Password); // 잘 나오는지 확인 | 46 | + password: '', |
47 | + confirmPassword: '' | ||
48 | + }} | ||
49 | + validationSchema={Yup.object().shape({ | ||
50 | + name: Yup.string() | ||
51 | + .required('이름이 올바르게 입력되지 않았습니다.'), | ||
52 | + | ||
53 | + email: Yup.string() | ||
54 | + .required('이메일이 올바르게 입력되지 않았습니다.'), | ||
55 | + password: Yup.string() | ||
56 | + .min(3, '비밀번호는 3자리 이상이어야 합니다.') | ||
57 | + .required('비밀번호를 입력해주세요.'), | ||
58 | + confirmPassword: Yup.string() | ||
59 | + .oneOf([Yup.ref('password'), null], '비밀번호를 다시 확인해주세요.') | ||
60 | + .required('비밀번호 확인이 올바르게 입력되지 않았습니다.') | ||
61 | + })} | ||
62 | + onSubmit={(values, { setSubmitting }) => { | ||
63 | + setTimeout(() => { | ||
41 | 64 | ||
42 | - // 비밀번호 두개가 같아야 회원가입이 되도록 | 65 | + let dataToSubmit = { |
43 | - if(Password !== Password2) | 66 | + email: values.email, |
44 | - return alert('비밀번호가 일치하지 않습니다.') | 67 | + password: values.password, |
68 | + name: values.name, | ||
69 | + | ||
70 | + //image: `http://gravatar.com/avatar/${moment().unix()}?d=identicon` | ||
71 | + }; | ||
45 | 72 | ||
73 | + dispatch(registerUser(dataToSubmit)).then(response => { | ||
74 | + if (response.payload.success) { | ||
75 | + props.history.push("/login"); | ||
76 | + } else { | ||
77 | + alert(response.payload.err.errmsg) | ||
78 | + } | ||
79 | + }) | ||
46 | 80 | ||
47 | - let regiInfo = { // 보내주기 위해 저장 | 81 | + setSubmitting(false); |
48 | - name : Name, | 82 | + }, 500); |
49 | - email: Email, | 83 | + }} |
50 | - password: Password | 84 | + > |
51 | - } | 85 | + {props => { |
52 | - | 86 | + const { |
53 | - dispatch(RegisterUser(regiInfo)) // _actions폴더 user_action.js에 있음 | 87 | + values, |
54 | - .then(response => { | 88 | + touched, |
55 | - if(response.payload.success) | 89 | + errors, |
56 | - props.history.push('/login'); | 90 | + dirty, |
57 | - | 91 | + isSubmitting, |
58 | - else | 92 | + handleChange, |
59 | - alert('Fail to sign up'); | 93 | + handleBlur, |
60 | - }) | 94 | + handleSubmit, |
61 | - | 95 | + handleReset, |
62 | - } | 96 | + } = props; |
97 | + return ( | ||
98 | + <div className="app"> | ||
99 | + <h2>회원가입</h2> | ||
100 | + <Form style={{ minWidth: '375px' }} {...formItemLayout} onSubmit={handleSubmit} > | ||
63 | 101 | ||
64 | - return ( | 102 | + <Form.Item required label="Name"> |
65 | - <div style={{ | 103 | + <Input |
66 | - justifyContent:'center', alignItems: 'center', display:'flex', width:'100%', height:'50vh' | 104 | + id="name" |
67 | - }}> | 105 | + placeholder="이름을 입력하세요." |
68 | - <form onSubmit={submitEvent} style={{display: 'flex', flexDirection: 'column'}}> | 106 | + type="text" |
107 | + value={values.name} | ||
108 | + onChange={handleChange} | ||
109 | + onBlur={handleBlur} | ||
110 | + className={ | ||
111 | + errors.name && touched.name ? 'text-input error' : 'text-input' | ||
112 | + } | ||
113 | + /> | ||
114 | + {errors.name && touched.name && ( | ||
115 | + <div className="input-feedback">{errors.name}</div> | ||
116 | + )} | ||
117 | + </Form.Item> | ||
69 | 118 | ||
70 | - <label>Name</label> | 119 | + <Form.Item required label="Email" hasFeedback> |
71 | - <input type="text" value={Name} onChange={NameEvent} /> | 120 | + <Input |
121 | + id="email" | ||
122 | + placeholder="이메일을 입력하세요." | ||
123 | + type="email" | ||
124 | + value={values.email} | ||
125 | + onChange={handleChange} | ||
126 | + onBlur={handleBlur} | ||
127 | + className={ | ||
128 | + errors.email && touched.email ? 'text-input error' : 'text-input' | ||
129 | + } | ||
130 | + /> | ||
131 | + {errors.email && touched.email && ( | ||
132 | + <div className="input-feedback">{errors.email}</div> | ||
133 | + )} | ||
134 | + </Form.Item> | ||
72 | 135 | ||
73 | - <label>Email</label> | 136 | + <Form.Item required label="Password" hasFeedback> |
74 | - <input type="email" value={Email} onChange={emailEvent} /> | 137 | + <Input |
75 | - {/* input type="email"이라서 '이메일 주소에 '@'를 포함해주세요'라는 경고문 뜸. */} | 138 | + id="password" |
139 | + placeholder="비밀번호를 입력하세요." | ||
140 | + type="password" | ||
141 | + value={values.password} | ||
142 | + onChange={handleChange} | ||
143 | + onBlur={handleBlur} | ||
144 | + className={ | ||
145 | + errors.password && touched.password ? 'text-input error' : 'text-input' | ||
146 | + } | ||
147 | + /> | ||
148 | + {errors.password && touched.password && ( | ||
149 | + <div className="input-feedback">{errors.password}</div> | ||
150 | + )} | ||
151 | + </Form.Item> | ||
76 | 152 | ||
153 | + <Form.Item required label="Confirm" hasFeedback> | ||
154 | + <Input | ||
155 | + id="confirmPassword" | ||
156 | + placeholder="비밀번호를 다시 한 번 입력해주세요." | ||
157 | + type="password" | ||
158 | + value={values.confirmPassword} | ||
159 | + onChange={handleChange} | ||
160 | + onBlur={handleBlur} | ||
161 | + className={ | ||
162 | + errors.confirmPassword && touched.confirmPassword ? 'text-input error' : 'text-input' | ||
163 | + } | ||
164 | + /> | ||
165 | + {errors.confirmPassword && touched.confirmPassword && ( | ||
166 | + <div className="input-feedback">{errors.confirmPassword}</div> | ||
167 | + )} | ||
168 | + </Form.Item> | ||
77 | 169 | ||
78 | - <label>Password</label> | 170 | + <Form.Item {...tailFormItemLayout}> |
79 | - <input type="password" value={Password} onChange={passwordEvent} /> | 171 | + <Button onClick={handleSubmit} type="primary" disabled={isSubmitting}> |
172 | + Submit | ||
173 | + </Button> | ||
174 | + </Form.Item> | ||
175 | + </Form> | ||
176 | + </div> | ||
177 | + ); | ||
178 | + }} | ||
179 | + </Formik> | ||
180 | + ); | ||
181 | +}; | ||
80 | 182 | ||
81 | - <label>Confirm Password</label> | ||
82 | - <input type="password" value={Password2} onChange={password2Event} /> | ||
83 | - | ||
84 | - <br/> | ||
85 | - <button> | ||
86 | - Sign In | ||
87 | - </button> | ||
88 | - </form> | ||
89 | - </div> | ||
90 | - ) | ||
91 | -} | ||
92 | 183 | ||
93 | -export default withRouter(RegisterPage) | 184 | +export default RegisterPage | ... | ... |
We-Shop/client/src/hoc/auth.js
0 → 100644
1 | +/* eslint-disable react-hooks/exhaustive-deps */ | ||
2 | +import React, { useEffect } from 'react'; | ||
3 | +import { auth } from '../_actions/user_actions'; | ||
4 | +import { useSelector, useDispatch } from "react-redux"; | ||
5 | + | ||
6 | +export default function (SpecificComponent, option, adminRoute = null) { | ||
7 | + function AuthenticationCheck(props) { | ||
8 | + | ||
9 | + let user = useSelector(state => state.user); | ||
10 | + const dispatch = useDispatch(); | ||
11 | + | ||
12 | + useEffect(() => { | ||
13 | + //To know my current status, send Auth request | ||
14 | + dispatch(auth()).then(response => { | ||
15 | + //Not Loggined in Status | ||
16 | + if (!response.payload.isAuth) { | ||
17 | + if (option) { | ||
18 | + props.history.push('/login') | ||
19 | + } | ||
20 | + //Loggined in Status | ||
21 | + } else { | ||
22 | + //supposed to be Admin page, but not admin person wants to go inside | ||
23 | + if (adminRoute && !response.payload.isAdmin) { | ||
24 | + props.history.push('/') | ||
25 | + } | ||
26 | + //Logged in Status, but Try to go into log in page | ||
27 | + else { | ||
28 | + if (option === false) { | ||
29 | + props.history.push('/') | ||
30 | + } | ||
31 | + } | ||
32 | + } | ||
33 | + }) | ||
34 | + | ||
35 | + }, []) | ||
36 | + | ||
37 | + return ( | ||
38 | + <SpecificComponent {...props} user={user} /> | ||
39 | + ) | ||
40 | + } | ||
41 | + return AuthenticationCheck | ||
42 | +} | ||
43 | + | ||
44 | + |
1 | body { | 1 | body { |
2 | margin: 0; | 2 | margin: 0; |
3 | - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | 3 | + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", |
4 | - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', | 4 | + "Droid Sans", "Helvetica Neue", sans-serif; |
5 | - sans-serif; | ||
6 | -webkit-font-smoothing: antialiased; | 5 | -webkit-font-smoothing: antialiased; |
7 | -moz-osx-font-smoothing: grayscale; | 6 | -moz-osx-font-smoothing: grayscale; |
8 | } | 7 | } |
9 | 8 | ||
10 | code { | 9 | code { |
11 | - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', | 10 | + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; |
12 | - monospace; | 11 | +} |
12 | + | ||
13 | +body { | ||
14 | + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", | ||
15 | + "Segoe UI Emoji", "Segoe UI Symbol"; | ||
16 | + font-size: 14px; | ||
17 | + line-height: 1.5; | ||
18 | + color: #24292e; | ||
19 | + background-color: #fff; | ||
20 | +} | ||
21 | + | ||
22 | +.app { | ||
23 | + flex-direction: column; | ||
24 | + display: flex; | ||
25 | + justify-content: center; | ||
26 | + align-items: center; | ||
27 | + height: 100vh; | ||
28 | +} | ||
29 | + | ||
30 | +input.error { | ||
31 | + border-color: red; | ||
32 | +} | ||
33 | + | ||
34 | + | ||
35 | +.input-feedback { | ||
36 | + color: red; | ||
37 | + height: 5px; | ||
38 | + margin-top: -12px; | ||
13 | } | 39 | } | ... | ... |
1 | +import 'react-app-polyfill/ie9'; | ||
2 | +import 'react-app-polyfill/ie11'; | ||
3 | +import 'core-js'; | ||
4 | + | ||
1 | import React from 'react'; | 5 | import React from 'react'; |
2 | import ReactDOM from 'react-dom'; | 6 | import ReactDOM from 'react-dom'; |
3 | import './index.css'; | 7 | import './index.css'; |
4 | -import App from './App'; | 8 | +import App from './components/App'; |
5 | -import reportWebVitals from './reportWebVitals'; | 9 | +import * as serviceWorker from './serviceWorker'; |
6 | -import { Provider } from 'react-redux' // app에 redux를 연결시켜주기 위해 redux에서 제공하는 provider 사용 | 10 | +import { BrowserRouter } from "react-router-dom"; |
7 | -import { createStore } from 'redux'; // redux에서 createStore 가져옴. | 11 | + |
8 | -import { applyMiddleware } from 'redux'; // object만 받는 store가 promise나 functions도 받기 위해 middleware을 사용함 | 12 | +import Reducer from './_reducers'; |
13 | +import { Provider } from 'react-redux'; | ||
14 | +import { createStore, applyMiddleware } from 'redux'; | ||
9 | import promiseMiddleware from 'redux-promise'; | 15 | import promiseMiddleware from 'redux-promise'; |
10 | import ReduxThunk from 'redux-thunk'; | 16 | import ReduxThunk from 'redux-thunk'; |
11 | -import Reducer from './_reducers'; | ||
12 | 17 | ||
13 | -import 'antd/dist/antd.css'; | 18 | +const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore); |
14 | - | ||
15 | - | ||
16 | -const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore) | ||
17 | 19 | ||
18 | ReactDOM.render( | 20 | ReactDOM.render( |
19 | - // App에 Redux를 연결 | 21 | + <Provider |
20 | - <Provider | 22 | + store={createStoreWithMiddleware( |
21 | - store={createStoreWithMiddleware(Reducer, | 23 | + Reducer, |
22 | window.__REDUX_DEVTOOLS_EXTENSION__ && | 24 | window.__REDUX_DEVTOOLS_EXTENSION__ && |
23 | window.__REDUX_DEVTOOLS_EXTENSION__() | 25 | window.__REDUX_DEVTOOLS_EXTENSION__() |
24 | )} | 26 | )} |
25 | > | 27 | > |
26 | - <App /> | 28 | + <BrowserRouter> |
29 | + <App /> | ||
30 | + </BrowserRouter> | ||
27 | </Provider> | 31 | </Provider> |
28 | - , document.getElementById('root') | 32 | + , document.getElementById('root')); |
29 | -); | 33 | +// If you want your app to work offline and load faster, you can change |
30 | - | 34 | +// unregister() to register() below. Note this comes with some pitfalls. |
31 | -// If you want to start measuring performance in your app, pass a function | 35 | +// Learn more about service workers: https://bit.ly/CRA-PWA |
32 | -// to log resu lts (for example: reportWebVitals(console.log)) | 36 | +serviceWorker.unregister(); |
33 | -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | ||
34 | -reportWebVitals(); | ... | ... |
We-Shop/client/src/serviceWorker.js
0 → 100644
1 | +// This optional code is used to register a service worker. | ||
2 | +// register() is not called by default. | ||
3 | + | ||
4 | +// This lets the app load faster on subsequent visits in production, and gives | ||
5 | +// it offline capabilities. However, it also means that developers (and users) | ||
6 | +// will only see deployed updates on subsequent visits to a page, after all the | ||
7 | +// existing tabs open on the page have been closed, since previously cached | ||
8 | +// resources are updated in the background. | ||
9 | + | ||
10 | +// To learn more about the benefits of this model and instructions on how to | ||
11 | +// opt-in, read https://bit.ly/CRA-PWA | ||
12 | + | ||
13 | +const isLocalhost = Boolean( | ||
14 | + window.location.hostname === 'localhost' || | ||
15 | + // [::1] is the IPv6 localhost address. | ||
16 | + window.location.hostname === '[::1]' || | ||
17 | + // 127.0.0.1/8 is considered localhost for IPv4. | ||
18 | + window.location.hostname.match( | ||
19 | + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ | ||
20 | + ) | ||
21 | +); | ||
22 | + | ||
23 | +export function register(config) { | ||
24 | + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { | ||
25 | + // The URL constructor is available in all browsers that support SW. | ||
26 | + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); | ||
27 | + if (publicUrl.origin !== window.location.origin) { | ||
28 | + // Our service worker won't work if PUBLIC_URL is on a different origin | ||
29 | + // from what our page is served on. This might happen if a CDN is used to | ||
30 | + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 | ||
31 | + return; | ||
32 | + } | ||
33 | + | ||
34 | + window.addEventListener('load', () => { | ||
35 | + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; | ||
36 | + | ||
37 | + if (isLocalhost) { | ||
38 | + // This is running on localhost. Let's check if a service worker still exists or not. | ||
39 | + checkValidServiceWorker(swUrl, config); | ||
40 | + | ||
41 | + // Add some additional logging to localhost, pointing developers to the | ||
42 | + // service worker/PWA documentation. | ||
43 | + navigator.serviceWorker.ready.then(() => { | ||
44 | + console.log( | ||
45 | + 'This web app is being served cache-first by a service ' + | ||
46 | + 'worker. To learn more, visit https://bit.ly/CRA-PWA' | ||
47 | + ); | ||
48 | + }); | ||
49 | + } else { | ||
50 | + // Is not localhost. Just register service worker | ||
51 | + registerValidSW(swUrl, config); | ||
52 | + } | ||
53 | + }); | ||
54 | + } | ||
55 | +} | ||
56 | + | ||
57 | +function registerValidSW(swUrl, config) { | ||
58 | + navigator.serviceWorker | ||
59 | + .register(swUrl) | ||
60 | + .then(registration => { | ||
61 | + registration.onupdatefound = () => { | ||
62 | + const installingWorker = registration.installing; | ||
63 | + if (installingWorker == null) { | ||
64 | + return; | ||
65 | + } | ||
66 | + installingWorker.onstatechange = () => { | ||
67 | + if (installingWorker.state === 'installed') { | ||
68 | + if (navigator.serviceWorker.controller) { | ||
69 | + // At this point, the updated precached content has been fetched, | ||
70 | + // but the previous service worker will still serve the older | ||
71 | + // content until all client tabs are closed. | ||
72 | + console.log( | ||
73 | + 'New content is available and will be used when all ' + | ||
74 | + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' | ||
75 | + ); | ||
76 | + | ||
77 | + // Execute callback | ||
78 | + if (config && config.onUpdate) { | ||
79 | + config.onUpdate(registration); | ||
80 | + } | ||
81 | + } else { | ||
82 | + // At this point, everything has been precached. | ||
83 | + // It's the perfect time to display a | ||
84 | + // "Content is cached for offline use." message. | ||
85 | + console.log('Content is cached for offline use.'); | ||
86 | + | ||
87 | + // Execute callback | ||
88 | + if (config && config.onSuccess) { | ||
89 | + config.onSuccess(registration); | ||
90 | + } | ||
91 | + } | ||
92 | + } | ||
93 | + }; | ||
94 | + }; | ||
95 | + }) | ||
96 | + .catch(error => { | ||
97 | + console.error('Error during service worker registration:', error); | ||
98 | + }); | ||
99 | +} | ||
100 | + | ||
101 | +function checkValidServiceWorker(swUrl, config) { | ||
102 | + // Check if the service worker can be found. If it can't reload the page. | ||
103 | + fetch(swUrl) | ||
104 | + .then(response => { | ||
105 | + // Ensure service worker exists, and that we really are getting a JS file. | ||
106 | + const contentType = response.headers.get('content-type'); | ||
107 | + if ( | ||
108 | + response.status === 404 || | ||
109 | + (contentType != null && contentType.indexOf('javascript') === -1) | ||
110 | + ) { | ||
111 | + // No service worker found. Probably a different app. Reload the page. | ||
112 | + navigator.serviceWorker.ready.then(registration => { | ||
113 | + registration.unregister().then(() => { | ||
114 | + window.location.reload(); | ||
115 | + }); | ||
116 | + }); | ||
117 | + } else { | ||
118 | + // Service worker found. Proceed as normal. | ||
119 | + registerValidSW(swUrl, config); | ||
120 | + } | ||
121 | + }) | ||
122 | + .catch(() => { | ||
123 | + console.log( | ||
124 | + 'No internet connection found. App is running in offline mode.' | ||
125 | + ); | ||
126 | + }); | ||
127 | +} | ||
128 | + | ||
129 | +export function unregister() { | ||
130 | + if ('serviceWorker' in navigator) { | ||
131 | + navigator.serviceWorker.ready.then(registration => { | ||
132 | + registration.unregister(); | ||
133 | + }); | ||
134 | + } | ||
135 | +} |
1 | const { createProxyMiddleware } = require('http-proxy-middleware'); | 1 | const { createProxyMiddleware } = require('http-proxy-middleware'); |
2 | 2 | ||
3 | -module.exports = function(app) { | 3 | +module.exports = function (app) { |
4 | - app.use( | 4 | + app.use( |
5 | - '/api', | 5 | + '/api', |
6 | - createProxyMiddleware({ | 6 | + createProxyMiddleware({ |
7 | - target: 'http://localhost:5000', | 7 | + target: 'http://localhost:5000', |
8 | - changeOrigin: true, | 8 | + changeOrigin: true, |
9 | - }) | 9 | + }) |
10 | - ); | 10 | + ); |
11 | }; | 11 | }; |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
We-Shop/client/yarn.lock
0 → 100755
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
1 | { | 1 | { |
2 | - "name": "boiler-plate", | 2 | + "name": "react-boiler-plate", |
3 | "version": "1.0.0", | 3 | "version": "1.0.0", |
4 | - "description": "", | 4 | + "description": "react boiler plate", |
5 | "main": "index.js", | 5 | "main": "index.js", |
6 | + "engine": { | ||
7 | + "node": "10.16.0", | ||
8 | + "npm": "6.9.0" | ||
9 | + }, | ||
6 | "scripts": { | 10 | "scripts": { |
7 | "start": "node server/index.js", | 11 | "start": "node server/index.js", |
8 | "backend": "nodemon server/index.js", | 12 | "backend": "nodemon server/index.js", |
9 | - "test": "echo \"Error: no test specified\" && exit 1", | 13 | + "frontend": "npm run start --prefix client", |
10 | "dev": "concurrently \"npm run backend\" \"npm run start --prefix client\"" | 14 | "dev": "concurrently \"npm run backend\" \"npm run start --prefix client\"" |
11 | }, | 15 | }, |
12 | - "author": "mindyeoi", | 16 | + "author": "John ahn", |
13 | "license": "ISC", | 17 | "license": "ISC", |
14 | "dependencies": { | 18 | "dependencies": { |
15 | - "bcrypt": "^5.0.1", | 19 | + "bcrypt": "^3.0.6", |
16 | - "body-parser": "^1.19.0", | 20 | + "body-parser": "^1.18.3", |
17 | - "concurrently": "^6.2.0", | 21 | + "cookie-parser": "^1.4.3", |
18 | - "cookie-parser": "^1.4.5", | 22 | + "cors": "^2.8.5", |
23 | + "debug": "^4.1.1", | ||
19 | "express": "^4.17.1", | 24 | "express": "^4.17.1", |
20 | "jsonwebtoken": "^8.5.1", | 25 | "jsonwebtoken": "^8.5.1", |
21 | - "mongoose": "^5.12.12" | 26 | + "moment": "^2.24.0", |
27 | + "mongoose": "^5.4.20", | ||
28 | + "react-redux": "^5.0.7", | ||
29 | + "saslprep": "^1.0.3", | ||
30 | + "supports-color": "^7.1.0" | ||
22 | }, | 31 | }, |
23 | "devDependencies": { | 32 | "devDependencies": { |
24 | - "nodemon": "^2.0.7" | 33 | + "concurrently": "^4.1.0", |
34 | + "nodemon": "^1.19.1" | ||
25 | } | 35 | } |
26 | } | 36 | } | ... | ... |
1 | -const express = require('express') | 1 | +const express = require("express") |
2 | const app = express() | 2 | const app = express() |
3 | -const port = 5000 | 3 | +const port = process.env.PORT || 5000 |
4 | 4 | ||
5 | -// User.js에서 만든 model을 가져옴 | 5 | +const path = require("path"); |
6 | -const { User } = require('./models/User') | 6 | +const cors = require('cors') |
7 | -//const { User } = require('./') | ||
8 | 7 | ||
9 | // body-parser 가져옴 | 8 | // body-parser 가져옴 |
10 | const bodyParser = require('body-parser') | 9 | const bodyParser = require('body-parser') |
11 | // bodyParser option | 10 | // bodyParser option |
12 | app.use(bodyParser.urlencoded({extended: true})) //application/x-www-form-urlencoded로 된 데이터를 분석해서 가져옴 | 11 | app.use(bodyParser.urlencoded({extended: true})) //application/x-www-form-urlencoded로 된 데이터를 분석해서 가져옴 |
13 | app.use(bodyParser.json()) // application/json 타입으로 된 데이터를 분석해서 가져옴 | 12 | app.use(bodyParser.json()) // application/json 타입으로 된 데이터를 분석해서 가져옴 |
13 | +const cookieParser = require("cookie-parser"); | ||
14 | +app.use(cookieParser()); | ||
15 | +const config = require("./config/key"); | ||
14 | 16 | ||
15 | -const config = require('./config/key') | ||
16 | 17 | ||
17 | -const cookieParser = require('cookie-parser') | 18 | +const mongoose = require("mongoose"); |
18 | -app.use(cookieParser()) | ||
19 | 19 | ||
20 | -const mongoose = require('mongoose') | ||
21 | - | ||
22 | -const { auth } = require('./middleware/auth') | ||
23 | - | ||
24 | -//이 정보는 비밀임..! 몽고DB아이디랑 비밀번호를 감춰야해..! | ||
25 | -mongoose.connect(config.mongoURI, { | ||
26 | - useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false | ||
27 | -}).then(()=>console.log('MongoDB Connected...')) | ||
28 | -.catch(err => console.log(err)) | ||
29 | - | ||
30 | -app.get('/', (req, res) => { | ||
31 | - res.send('민정이짱짱맨최고최고!') | ||
32 | -}) | ||
33 | - | ||
34 | -// 회원가입 구현 | ||
35 | -// route의 endpoint는 register | ||
36 | -app.post('/api/users/register', (req, res) => { | ||
37 | - // 회원가입할 때 필요한 정보들을 client에서 가져오면 | ||
38 | - // 그것들을 데이터베이스에 넣어준다. | ||
39 | - | ||
40 | - const user = new User(req.body) // req.body에 User의 정보를 저장 | ||
41 | - | ||
42 | - // 비밀번호 암호화 | ||
43 | 20 | ||
21 | +const connect = mongoose.connect(config.mongoURI, | ||
22 | + { | ||
23 | + useNewUrlParser: true, useUnifiedTopology: true, | ||
24 | + useCreateIndex: true, useFindAndModify: false | ||
25 | + }) | ||
26 | + .then(() => console.log('MongoDB ---> Connected')) | ||
27 | + .catch(err => console.log(err)); | ||
44 | 28 | ||
45 | - // mongoDB에서 오는 메서드. 정보들이 user model에 저장 | 29 | +app.use(cors()) |
46 | - user.save((err, userInfo) => { | ||
47 | - // 만약 에러가 나면, json형식으로 success:false를 보내주고, 에러메시지를 보내줌 | ||
48 | - if(err) return res.json({success: false, err}) | ||
49 | - // 성공하면 status(200) (status(200)은 성공했다는 뜻) | ||
50 | - return res.status(200).json({ | ||
51 | - success: true | ||
52 | - }) | ||
53 | - }) | ||
54 | 30 | ||
55 | -}) | ||
56 | 31 | ||
57 | -// 로그인 구현 -> 로그인 하면 토큰 생성 | 32 | +app.use('/api/users', require('./routes/users')); |
58 | -app.post('/api/users/login', (req, res) => { | ||
59 | - // 1. 요청된 이메일이 데이터베이스에 있는지 찾기 | ||
60 | - User.findOne({ email: req.body.email }, (err, user) => { | ||
61 | - if(!user) | ||
62 | - { | ||
63 | - return res.json({ | ||
64 | - loginSuccess: false, | ||
65 | - message: "There is no user with that email." | ||
66 | - }) | ||
67 | - } | ||
68 | - // 2. email과 비밀번호가 맞는지 확인 (User.js에 comparePassword 함수 정의되어 있음) | ||
69 | - user.comparePassword(req.body.password, (err, isMatch) => { | ||
70 | - if(!isMatch) | ||
71 | - return res.json({loginSuccess: false, message: "Password is not match."}) | ||
72 | - // 3. 비밀번호까지 맞다면 유저를 위한 토큰 생성 (User.js에 generateToken 함수 정의) | ||
73 | - user.generateToken((err, user) => { // err가 없으면 user에 정보 받아옴 | ||
74 | - if(err) | ||
75 | - return res.status(400).send(err); | ||
76 | - // 4. 생성한 토큰을 저장함 -> 쿠키나 로컬 스토리지 등에 저장할 수 있는데 여기선 쿠키에 저장 | ||
77 | - res.cookie("loginCookie", user.token) | ||
78 | - .status(200) //성공했다는 표시 | ||
79 | - .json({loginSuccess: true, userId: user._id}) | ||
80 | - }) | ||
81 | - }) | ||
82 | - }) | ||
83 | -}) | ||
84 | 33 | ||
85 | 34 | ||
86 | -// 인증 구현 (이 사람이 일반유저인지 관리자인지) | 35 | +// 이미지 가져오려고 |
87 | -app.get('/api/users/auth', auth ,(req,res) => { | 36 | +app.use('/uploads', express.static('uploads')); |
88 | - // 여기까지 미들웨어(auth) 통과했으면 authentication == true 라는 뜻 | ||
89 | - res.status(200).json({ | ||
90 | - _id: req.user._id, | ||
91 | - isAdmin: req.user.role === 0 ? false : true, // role이 0이면 일반 유저, role이 0이 아니면 관리자. | ||
92 | - isAuth: true, | ||
93 | - email: req.user.email, | ||
94 | - lastname: req.user.lastname, | ||
95 | - role: req.user.role, | ||
96 | - image: req.user.image | ||
97 | - }) | ||
98 | -}) | ||
99 | - | ||
100 | -// 로그아웃 구현 (로그인 때 만든 토큰을 지워버림) | ||
101 | -app.get('/api/users/logout', auth, (req, res) => { | ||
102 | - User.findOneAndUpdate({_id: req.user._id}, // id로 User를 찾아서 업데이터 시킴 | ||
103 | - { token: "" }, (err, user) => { | ||
104 | - if(err) return res.json({success: false, err}); | ||
105 | - return res.status(200).send({ | ||
106 | - success: true | ||
107 | - }) | ||
108 | - }) | ||
109 | -}) | ||
110 | 37 | ||
111 | -// test | 38 | +if (process.env.NODE_ENV === "production") { |
112 | -app.get('/api/hello', (req, res) => { | 39 | + app.use(express.static("client/build")); |
113 | - | 40 | + app.get("*", (req, res) => { |
114 | - res.send("hello world"); | 41 | + res.sendFile(path.resolve(__dirname, "../client", "build", "index.html")); |
115 | -}) | 42 | + }); |
43 | +} | ||
116 | 44 | ||
117 | app.listen(port, () => { | 45 | app.listen(port, () => { |
118 | - console.log(`Example app listening at http://localhost:${port}`) | ||
119 | -}) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
46 | + console.log(`Server ---> http://localhost:${port}`) | ||
47 | +}); | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | -const { User } = require("../models/User"); | 1 | +const { User } = require('../models/User'); |
2 | 2 | ||
3 | let auth = (req, res, next) => { | 3 | let auth = (req, res, next) => { |
4 | - // 인증 처리 | 4 | + // 인증 처리 |
5 | - // 1. client 쿠키에서 토큰을 가져옴. | 5 | + // 1. client 쿠키에서 토큰을 가져옴. |
6 | - let token = req.cookies.loginCookie; | 6 | + let token = req.cookies.w_auth; |
7 | 7 | ||
8 | - // 2. 토큰을 복호화한 후 유저를 찾는다. (User.js에 findByToken(); 있음) | 8 | + // 2. 토큰을 복호화한 후 유저를 찾는다. (User.js에 findByToken(); 있음) |
9 | - User.findByToken(token, (err, user)=>{ | 9 | + User.findByToken(token, (err, user)=>{ |
10 | - // 에러가 있으면 | 10 | + // 에러가 있으면 |
11 | - if(err) throw err; | 11 | + if(err) throw err; |
12 | - // 유저가 없으면 | 12 | + // 유저가 없으면 |
13 | - if(!user) return res.json({ isAuth:false, error: true}) | 13 | + if(!user) return res.json({ isAuth:false, error: true}) |
14 | - // 에러도 없고 유저도 있으면 | 14 | + // 에러도 없고 유저도 있으면 |
15 | - req.token = token; // token과 user를 request에 넣어줌으로써 index.js에서 request 사용할 수 있음 | 15 | + req.token = token; // token과 user를 request에 넣어줌으로써 index.js에서 request 사용할 수 있음 |
16 | - req.user = user; | 16 | + req.user = user; |
17 | - next(); | 17 | + next(); |
18 | - }); | 18 | +}); |
19 | 19 | ||
20 | - // 3. 유저가 있으면 인증OK, 유저가 없으면 인증No! | 20 | +// 3. 유저가 있으면 인증OK, 유저가 없으면 인증No! |
21 | } | 21 | } |
22 | 22 | ||
23 | // 이 auth를 다른 파일에서도 쓸 수 있도록 | 23 | // 이 auth를 다른 파일에서도 쓸 수 있도록 | ... | ... |
... | @@ -5,35 +5,36 @@ const bcrypt = require('bcrypt') | ... | @@ -5,35 +5,36 @@ const bcrypt = require('bcrypt') |
5 | // bcrypt 사용하기 위해 salt를 생성하고 그걸 이용해 암호화 시킴 | 5 | // bcrypt 사용하기 위해 salt를 생성하고 그걸 이용해 암호화 시킴 |
6 | const saltRounds = 10 // salt를 몇글자 할 건지 | 6 | const saltRounds = 10 // salt를 몇글자 할 건지 |
7 | 7 | ||
8 | -// | 8 | +const jwt = require('jsonwebtoken'); |
9 | -const jwt = require('jsonwebtoken') | 9 | +const moment = require("moment"); |
10 | 10 | ||
11 | const userSchema = mongoose.Schema({ | 11 | const userSchema = mongoose.Schema({ |
12 | - name:{ | 12 | + name: { |
13 | - type: String, | 13 | + type:String, |
14 | - maxlength: 50 | 14 | + maxlength:50 |
15 | }, | 15 | }, |
16 | - email:{ | 16 | + email: { |
17 | - type: String, | 17 | + type:String, |
18 | - trim: true, // 'minjeong park'처럼 space가 있는 문자에 space가 없어지도록 함 | 18 | + trim:true, |
19 | - unique: 1 // 똑같은 이메일은 쓸 수 없도록 | 19 | + unique: 1 |
20 | }, | 20 | }, |
21 | password: { | 21 | password: { |
22 | type: String, | 22 | type: String, |
23 | - minlength: 5 | 23 | + minglength: 3 |
24 | }, | 24 | }, |
25 | lastname: { | 25 | lastname: { |
26 | - type: String, | 26 | + type:String, |
27 | maxlength: 50 | 27 | maxlength: 50 |
28 | }, | 28 | }, |
29 | - role: { // Number==1 이면 관리자, number==0 이면 일반 유저 | 29 | + role : { |
30 | - type: Number, | 30 | + type:Number, // Number==1 이면 관리자, number==0 이면 일반 유저 |
31 | - default: 0 // default는 0 | 31 | + default: 0 // default는 0 |
32 | }, | 32 | }, |
33 | + image: String, | ||
33 | token : { | 34 | token : { |
34 | - type : String | 35 | + type: String, |
35 | }, | 36 | }, |
36 | - tokenExp: { //토큰의 유효기간 | 37 | + tokenExp :{ |
37 | type: Number | 38 | type: Number |
38 | } | 39 | } |
39 | }) | 40 | }) |
... | @@ -41,11 +42,11 @@ const userSchema = mongoose.Schema({ | ... | @@ -41,11 +42,11 @@ const userSchema = mongoose.Schema({ |
41 | // index.js의 app.post('/register', (req, res)에 있는 | 42 | // index.js의 app.post('/register', (req, res)에 있는 |
42 | // user model에 user 정보를 저장하기 전에 무엇을 한다는 것 | 43 | // user model에 user 정보를 저장하기 전에 무엇을 한다는 것 |
43 | // function( next )를 해서 얘네가 끝난 다음에 다음걸 실행해라~ | 44 | // function( next )를 해서 얘네가 끝난 다음에 다음걸 실행해라~ |
44 | -userSchema.pre('save', function( next ){ | 45 | +userSchema.pre('save', function( next ) { |
45 | - var user = this | 46 | + var user = this; |
46 | - | 47 | + |
47 | - if(user.isModified('password')) // password를 변경할 때만 적용되도록.. | 48 | + if(user.isModified('password')){ // password를 변경할 때만 적용되도록.. |
48 | - { | 49 | + |
49 | // 비밀번호 암호화 (https://www.npmjs.com/package/bcrypt 에서 가져옴) | 50 | // 비밀번호 암호화 (https://www.npmjs.com/package/bcrypt 에서 가져옴) |
50 | bcrypt.genSalt(saltRounds, (err, salt) => // salt를 만드는 함수 | 51 | bcrypt.genSalt(saltRounds, (err, salt) => // salt를 만드는 함수 |
51 | { | 52 | { |
... | @@ -53,18 +54,15 @@ userSchema.pre('save', function( next ){ | ... | @@ -53,18 +54,15 @@ userSchema.pre('save', function( next ){ |
53 | bcrypt.hash(user.password, salt, (err, hash) => { // bcrypt.hash(암호화되지 않은 pw, salt, function(err, 암호화된 비밀번호)) | 54 | bcrypt.hash(user.password, salt, (err, hash) => { // bcrypt.hash(암호화되지 않은 pw, salt, function(err, 암호화된 비밀번호)) |
54 | if(err) return next(err) // 에러 나면 return err | 55 | if(err) return next(err) // 에러 나면 return err |
55 | user.password = hash // 성공하면 user.password를 hash로 교체 | 56 | user.password = hash // 성공하면 user.password를 hash로 교체 |
56 | - next() | 57 | + next() |
57 | - }); | 58 | + }) |
58 | - }); | 59 | + }) |
59 | - } | 60 | + } else { |
60 | - else | ||
61 | - { | ||
62 | next() | 61 | next() |
63 | } | 62 | } |
63 | +}); | ||
64 | 64 | ||
65 | -}) | 65 | +userSchema.methods.comparePassword = function(plainPassword,cb){ |
66 | - | ||
67 | -userSchema.methods.comparePassword = function(plainPassword, cb){ | ||
68 | 66 | ||
69 | // 1. plainPassword가 1234567 암호화된 비밀번호 가 같은지 체크해야함 | 67 | // 1. plainPassword가 1234567 암호화된 비밀번호 가 같은지 체크해야함 |
70 | // 그러면 plainPassword도 암호화해서 비교해야함. (복호화 할 수 없기 때문에) | 68 | // 그러면 plainPassword도 암호화해서 비교해야함. (복호화 할 수 없기 때문에) |
... | @@ -75,39 +73,35 @@ userSchema.methods.comparePassword = function(plainPassword, cb){ | ... | @@ -75,39 +73,35 @@ userSchema.methods.comparePassword = function(plainPassword, cb){ |
75 | }) | 73 | }) |
76 | } | 74 | } |
77 | 75 | ||
78 | -userSchema.methods.generateToken = function(cb) | 76 | +userSchema.methods.generateToken = function(cb) { |
79 | -{ | ||
80 | var user = this; | 77 | var user = this; |
81 | // jsonwebtoken을 이용해서 token 생성 | 78 | // jsonwebtoken을 이용해서 token 생성 |
82 | var token = jwt.sign(user._id.toHexString(), 'secretToken') //database에 있는 id라서 _id | 79 | var token = jwt.sign(user._id.toHexString(), 'secretToken') //database에 있는 id라서 _id |
80 | + var oneHour = moment().add(1, 'hour').valueOf(); | ||
83 | 81 | ||
84 | - user.token = token | 82 | + user.tokenExp = oneHour; |
85 | - user.save(function(err, user){ | 83 | + user.token = token; |
86 | - if(err) | 84 | + user.save(function (err, user){ |
87 | - return cb(err) // 에러가 있다면 callback으로 에러 전달 | 85 | + if(err) return cb(err)// 에러가 있다면 callback으로 에러 전달 |
88 | cb(null, user) // 에러가 없다면 err는 없고 user정보만 전달 | 86 | cb(null, user) // 에러가 없다면 err는 없고 user정보만 전달 |
89 | }) | 87 | }) |
90 | - | ||
91 | } | 88 | } |
92 | 89 | ||
93 | -userSchema.statics.findByToken = function(token, cb) | 90 | +userSchema.statics.findByToken = function (token, cb) { |
94 | -{ | ||
95 | var user = this; | 91 | var user = this; |
96 | 92 | ||
97 | // 1. 토큰을 decoding | 93 | // 1. 토큰을 decoding |
98 | jwt.verify(token, 'secretToken', function(err, decoded) { | 94 | jwt.verify(token, 'secretToken', function(err, decoded) { |
99 | - // 2. 유저 아이디를 이용해서 유저를 찾은 다음에 클라이언트에서 가져온 토큰과 DB에 보관된 토큰이 일치하는지 확인. | 95 | + // 2. 유저 아이디를 이용해서 유저를 찾은 다음에 클라이언트에서 가져온 토큰과 DB에 보관된 토큰이 일치하는지 확인. |
100 | - user.findOne({"_id": decoded, "token": token}, function(err, user){ // findOne :: mongoDB에 이미 있는 method | 96 | + user.findOne({"_id": decoded, "token": token}, function(err, user){ // findOne :: mongoDB에 이미 있는 method |
101 | - // 에러가 나면 | 97 | + // 에러가 나면 |
102 | - if(err) return cb(err); | 98 | + if(err) return cb(err); |
103 | - // 에러가 안나면 | 99 | + // 에러가 안나면 |
104 | - cb(null, user) | 100 | + cb(null, user) |
105 | - }) | 101 | + }) |
106 | - }) | 102 | +}) |
107 | } | 103 | } |
108 | 104 | ||
109 | -// 만든 스키마를 모델로 감싸줌 | 105 | +const User = mongoose.model('User', userSchema); |
110 | -const User = mongoose.model('User', userSchema) | ||
111 | 106 | ||
112 | -// 이 모델을 다른 파일에서도 쓰고싶으면 아래와 같이 해주면 됨 | ||
113 | -module.exports = {User} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
107 | +module.exports = { User } | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
We-Shop/server/routes/users.js
0 → 100644
1 | +const express = require('express'); | ||
2 | +const { User } = require("../models/User"); | ||
3 | +const { auth } = require("../middleware/auth"); | ||
4 | +const router = express.Router(); | ||
5 | + | ||
6 | +router.get("/auth", auth, (req, res) => { | ||
7 | + res.status(200).json({ | ||
8 | + _id: req.user._id, | ||
9 | + isAdmin: req.user.role === 0 ? false : true, | ||
10 | + isAuth: true, | ||
11 | + email: req.user.email, | ||
12 | + name: req.user.name, | ||
13 | + lastname: req.user.lastname, | ||
14 | + role: req.user.role, | ||
15 | + image: req.user.image, | ||
16 | + }); | ||
17 | +}); | ||
18 | + | ||
19 | +router.post("/register", (req, res) => { | ||
20 | + | ||
21 | + const user = new User(req.body); | ||
22 | + | ||
23 | + user.save((err, doc) => { | ||
24 | + if (err) return res.json({ success: false, err }); | ||
25 | + return res.status(200).json({ | ||
26 | + success: true | ||
27 | + }); | ||
28 | + }); | ||
29 | +}); | ||
30 | + | ||
31 | +router.post("/login", (req, res) => { | ||
32 | + User.findOne({ email: req.body.email }, (err, user) => { | ||
33 | + if (!user) | ||
34 | + return res.json({ | ||
35 | + loginSuccess: false, | ||
36 | + message: "Auth failed, email not found" | ||
37 | + }); | ||
38 | + | ||
39 | + user.comparePassword(req.body.password, (err, isMatch) => { | ||
40 | + if (!isMatch) | ||
41 | + return res.json({ loginSuccess: false, message: "Wrong password" }); | ||
42 | + | ||
43 | + user.generateToken((err, user) => { | ||
44 | + if (err) return res.status(400).send(err); | ||
45 | + res.cookie("w_authExp", user.tokenExp); | ||
46 | + res | ||
47 | + .cookie("w_auth", user.token) | ||
48 | + .status(200) | ||
49 | + .json({ | ||
50 | + loginSuccess: true, userId: user._id | ||
51 | + }); | ||
52 | + }); | ||
53 | + }); | ||
54 | + }); | ||
55 | +}); | ||
56 | + | ||
57 | +router.get("/logout", auth, (req, res) => { | ||
58 | + User.findOneAndUpdate({ _id: req.user._id }, { token: "", tokenExp: "" }, (err, doc) => { | ||
59 | + if (err) return res.json({ success: false, err }); | ||
60 | + return res.status(200).send({ | ||
61 | + success: true | ||
62 | + }); | ||
63 | + }); | ||
64 | +}); | ||
65 | + | ||
66 | +module.exports = router; |
-
Please register or login to post a comment