Showing
56 changed files
with
1645 additions
and
540 deletions
1 | -# HEXA for you 개편 프로젝트 | 1 | +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). |
2 | ->20151039045 정승호 | ||
3 | 2 | ||
4 | -## 개요 | 3 | +## Available Scripts |
5 | - 현재 경희대 연행분과 클래식 기타 동아리 Hexa는 Hexa for you 라는 안드로이드 어플리케이션을 통하여 행사 사진을 업로드 하고 있는 중이다, 하지만, 앱의 기능이 사진만 보여주는 것임에도 앱 내에 모든 사진 자료들을 넣어서 앱의 용량이 300Mb 정도로 매우 크게 되어있다, 또한 제공 플랫폼이 안드로이드 하나뿐이어서 다른 플랫폼 유저들을 앱을 사용할 수 없다는 단점이 존재한다, 이러한 단점들을 개선하기 위해 동아리의 행사 사진들을 보관할 수 있는 웹페이지를 만들어서 좀 더 관리하기 쉽게 만들도록 하겠다. | ||
6 | - | ||
7 | -## 사용기술 | ||
8 | -### 1. Node.js | ||
9 | -- 웹사이트를 구동하기 위한 서버 사이드 파트는 Node.js로 작성할 예정이다. | ||
10 | 4 | ||
11 | -### 2. AWS | 5 | +In the project directory, you can run: |
12 | -- 작성한 코드를 AWS에서 구동을 할 예정 | ||
13 | 6 | ||
7 | +### `npm start` | ||
14 | 8 | ||
15 | -### 3. MongoDB | 9 | +Runs the app in the development mode.<br /> |
16 | -- 기본적으로 사진을 저장하는 웹사이트기 때문에 데이터베이스의 사용이 필요하고, 카카오 신한은행등 많은 기업들이 채택하는 중인 MongoDB를 이용해 사진을 관리 | 10 | +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. |
17 | 11 | ||
18 | -### 4. React.js | ||
19 | -- 웹사이트의 프론트엔드 부분을 다룰것이며, 깔끔한 UI를 구현하기 위해 사용 | ||
20 | - | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
12 | +The page will reload if you make edits.<br /> | ||
13 | +You will also see any lint errors in the console. | ||
14 | + | ||
15 | +### `npm test` | ||
16 | + | ||
17 | +Launches the test runner in the interactive watch mode.<br /> | ||
18 | +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. | ||
19 | + | ||
20 | +### `npm run build` | ||
21 | + | ||
22 | +Builds the app for production to the `build` folder.<br /> | ||
23 | +It correctly bundles React in production mode and optimizes the build for the best performance. | ||
24 | + | ||
25 | +The build is minified and the filenames include the hashes.<br /> | ||
26 | +Your app is ready to be deployed! | ||
27 | + | ||
28 | +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. | ||
29 | + | ||
30 | +### `npm run eject` | ||
31 | + | ||
32 | +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** | ||
33 | + | ||
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. | ||
35 | + | ||
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. | ||
37 | + | ||
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. | ||
39 | + | ||
40 | +## Learn More | ||
41 | + | ||
42 | +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). | ||
43 | + | ||
44 | +To learn React, check out the [React documentation](https://reactjs.org/). | ||
45 | + | ||
46 | +### Code Splitting | ||
47 | + | ||
48 | +This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting | ||
49 | + | ||
50 | +### Analyzing the Bundle Size | ||
51 | + | ||
52 | +This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size | ||
53 | + | ||
54 | +### Making a Progressive Web App | ||
55 | + | ||
56 | +This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app | ||
57 | + | ||
58 | +### Advanced Configuration | ||
59 | + | ||
60 | +This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration | ||
61 | + | ||
62 | +### Deployment | ||
63 | + | ||
64 | +This section has moved here: https://facebook.github.io/create-react-app/docs/deployment | ||
65 | + | ||
66 | +### `npm run build` fails to minify | ||
67 | + | ||
68 | +This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify | ... | ... |
... | @@ -6,23 +6,23 @@ In the project directory, you can run: | ... | @@ -6,23 +6,23 @@ In the project directory, you can run: |
6 | 6 | ||
7 | ### `npm start` | 7 | ### `npm start` |
8 | 8 | ||
9 | -Runs the app in the development mode.<br /> | 9 | +Runs the app in the development mode.<br> |
10 | 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. |
11 | 11 | ||
12 | -The page will reload if you make edits.<br /> | 12 | +The page will reload if you make edits.<br> |
13 | You will also see any lint errors in the console. | 13 | You will also see any lint errors in the console. |
14 | 14 | ||
15 | ### `npm test` | 15 | ### `npm test` |
16 | 16 | ||
17 | -Launches the test runner in the interactive watch mode.<br /> | 17 | +Launches the test runner in the interactive watch mode.<br> |
18 | 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. |
19 | 19 | ||
20 | ### `npm run build` | 20 | ### `npm run build` |
21 | 21 | ||
22 | -Builds the app for production to the `build` folder.<br /> | 22 | +Builds the app for production to the `build` folder.<br> |
23 | 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. |
24 | 24 | ||
25 | -The build is minified and the filenames include the hashes.<br /> | 25 | +The build is minified and the filenames include the hashes.<br> |
26 | Your app is ready to be deployed! | 26 | Your app is ready to be deployed! |
27 | 27 | ||
28 | 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. |
... | @@ -33,7 +33,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d | ... | @@ -33,7 +33,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d |
33 | 33 | ||
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. | 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. |
35 | 35 | ||
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. | 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. |
37 | 37 | ||
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. | 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. |
39 | 39 | ... | ... |
This diff could not be displayed because it is too large.
... | @@ -3,20 +3,24 @@ | ... | @@ -3,20 +3,24 @@ |
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": "^4.2.4", | 6 | + "antd": "^3.24.1", |
7 | - "@testing-library/react": "^9.5.0", | ||
8 | - "@testing-library/user-event": "^7.2.1", | ||
9 | - "antd": "^4.3.4", | ||
10 | "axios": "^0.19.2", | 7 | "axios": "^0.19.2", |
11 | - "http-proxy-middleware": "^1.0.4", | 8 | + "core-js": "^3.6.4", |
12 | - "react": "^16.13.1", | 9 | + "formik": "^1.5.8", |
13 | - "react-dom": "^16.13.1", | 10 | + "moment": "^2.24.0", |
14 | - "react-redux": "^7.2.0", | 11 | + "react": "^16.8.6", |
15 | - "react-router-dom": "^5.2.0", | 12 | + "react-app-polyfill": "^1.0.6", |
13 | + "react-dom": "^16.8.6", | ||
14 | + "react-dropzone": "^11.0.1", | ||
15 | + "react-icons": "^3.7.0", | ||
16 | + "react-image-gallery": "^1.0.7", | ||
17 | + "react-redux": "^7.1.0-rc.1", | ||
18 | + "react-router-dom": "^5.0.1", | ||
16 | "react-scripts": "3.4.1", | 19 | "react-scripts": "3.4.1", |
17 | - "redux": "^4.0.5", | 20 | + "redux": "^4.0.0", |
18 | "redux-promise": "^0.6.0", | 21 | "redux-promise": "^0.6.0", |
19 | - "redux-thunk": "^2.3.0" | 22 | + "redux-thunk": "^2.3.0", |
23 | + "yup": "^0.27.0" | ||
20 | }, | 24 | }, |
21 | "scripts": { | 25 | "scripts": { |
22 | "start": "react-scripts start", | 26 | "start": "react-scripts start", |
... | @@ -38,5 +42,8 @@ | ... | @@ -38,5 +42,8 @@ |
38 | "last 1 firefox version", | 42 | "last 1 firefox version", |
39 | "last 1 safari version" | 43 | "last 1 safari version" |
40 | ] | 44 | ] |
45 | + }, | ||
46 | + "devDependencies": { | ||
47 | + "http-proxy-middleware": "^1.0.3" | ||
41 | } | 48 | } |
42 | } | 49 | } | ... | ... |
No preview for this file type
... | @@ -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/ | ... | ... |
client/public/logo192.png
deleted
100644 → 0

5.22 KB
client/public/logo512.png
deleted
100644 → 0

9.44 KB
... | @@ -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": ".", | ... | ... |
client/public/robots.txt
deleted
100644 → 0
client/src/App.css
deleted
100644 → 0
1 | -.App { | ||
2 | - text-align: center; | ||
3 | -} | ||
4 | - | ||
5 | -.App-logo { | ||
6 | - height: 40vmin; | ||
7 | - pointer-events: none; | ||
8 | -} | ||
9 | - | ||
10 | -@media (prefers-reduced-motion: no-preference) { | ||
11 | - .App-logo { | ||
12 | - animation: App-logo-spin infinite 20s linear; | ||
13 | - } | ||
14 | -} | ||
15 | - | ||
16 | -.App-header { | ||
17 | - background-color: #282c34; | ||
18 | - min-height: 100vh; | ||
19 | - display: flex; | ||
20 | - flex-direction: column; | ||
21 | - align-items: center; | ||
22 | - justify-content: center; | ||
23 | - font-size: calc(10px + 2vmin); | ||
24 | - color: white; | ||
25 | -} | ||
26 | - | ||
27 | -.App-link { | ||
28 | - color: #61dafb; | ||
29 | -} | ||
30 | - | ||
31 | -@keyframes App-logo-spin { | ||
32 | - from { | ||
33 | - transform: rotate(0deg); | ||
34 | - } | ||
35 | - to { | ||
36 | - transform: rotate(360deg); | ||
37 | - } | ||
38 | -} |
client/src/App.js
deleted
100644 → 0
1 | -import React from "react"; | ||
2 | -import { | ||
3 | - BrowserRouter as Router, | ||
4 | - Switch, | ||
5 | - Route, | ||
6 | - Link | ||
7 | -} from "react-router-dom"; | ||
8 | - | ||
9 | -import LandingPage from './components/views/LandingPage/LandingPage'; | ||
10 | -import LoginPage from './components/views/LoginPage/LoginPage'; | ||
11 | -import RegisterPage from './components/views/RegisterPage/RegisterPage'; | ||
12 | -function App() { | ||
13 | - return ( | ||
14 | - <Router> | ||
15 | - <div> | ||
16 | - | ||
17 | - | ||
18 | - {/* | ||
19 | - A <Switch> looks through all its children <Route> | ||
20 | - elements and renders the first one whose path | ||
21 | - matches the current URL. Use a <Switch> any time | ||
22 | - you have multiple routes, but you want only one | ||
23 | - of them to render at a time | ||
24 | - */} | ||
25 | - <Switch> | ||
26 | - <Route exact path="/" component={LandingPage}/> | ||
27 | - | ||
28 | - <Route exact path="/login" component={LoginPage}/> | ||
29 | - | ||
30 | - <Route exact path="/register" component = {RegisterPage} /> | ||
31 | - | ||
32 | - </Switch> | ||
33 | - </div> | ||
34 | - </Router> | ||
35 | - ); | ||
36 | -} | ||
37 | - | ||
38 | -export default App | ||
39 | - | ||
40 | - |
client/src/Config.js
deleted
100644 → 0
File mode changed
1 | import axios from 'axios'; | 1 | import axios from 'axios'; |
2 | - | ||
3 | import { | 2 | import { |
4 | - LOGIN_USER | 3 | + LOGIN_USER, |
4 | + REGISTER_USER, | ||
5 | + AUTH_USER, | ||
6 | + LOGOUT_USER, | ||
5 | } from './types'; | 7 | } from './types'; |
8 | +import { USER_SERVER } from '../components/Config.js'; | ||
9 | + | ||
10 | +export function registerUser(dataToSubmit){ | ||
11 | + const request = axios.post(`${USER_SERVER}/register`,dataToSubmit) | ||
12 | + .then(response => response.data); | ||
13 | + | ||
14 | + return { | ||
15 | + type: REGISTER_USER, | ||
16 | + payload: request | ||
17 | + } | ||
18 | +} | ||
6 | 19 | ||
7 | -export function loginUser(dataTosubmit){ | 20 | +export function loginUser(dataToSubmit){ |
8 | - const request = axios.post('/api/users/login', dataTosubmit) | 21 | + const request = axios.post(`${USER_SERVER}/login`,dataToSubmit) |
9 | - .then(response => response.data) | 22 | + .then(response => response.data); |
10 | 23 | ||
11 | return { | 24 | return { |
12 | type: LOGIN_USER, | 25 | type: LOGIN_USER, |
13 | payload: request | 26 | payload: request |
14 | } | 27 | } |
15 | -} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
28 | +} | ||
29 | + | ||
30 | +export function auth(){ | ||
31 | + const request = axios.get(`${USER_SERVER}/auth`) | ||
32 | + .then(response => response.data); | ||
33 | + | ||
34 | + return { | ||
35 | + type: AUTH_USER, | ||
36 | + payload: request | ||
37 | + } | ||
38 | +} | ||
39 | + | ||
40 | +export function logoutUser(){ | ||
41 | + const request = axios.get(`${USER_SERVER}/logout`) | ||
42 | + .then(response => response.data); | ||
43 | + | ||
44 | + return { | ||
45 | + type: LOGOUT_USER, | ||
46 | + payload: request | ||
47 | + } | ||
48 | +} | ||
49 | + | ... | ... |
1 | -import {combineReducers} from 'redux'; | 1 | +import { combineReducers } from 'redux'; |
2 | import user from './user_reducer'; | 2 | import user from './user_reducer'; |
3 | 3 | ||
4 | const rootReducer = combineReducers({ | 4 | const rootReducer = combineReducers({ |
5 | - user | 5 | + user, |
6 | -}) | 6 | +}); |
7 | 7 | ||
8 | -export default rootReducer | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
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, | ||
4 | + AUTH_USER, | ||
5 | + LOGOUT_USER, | ||
3 | } from '../_actions/types'; | 6 | } from '../_actions/types'; |
7 | + | ||
4 | 8 | ||
5 | -export default function (state={}, action){ | 9 | +export default function(state={},action){ |
6 | - switch (action.type) { | 10 | + switch(action.type){ |
11 | + case REGISTER_USER: | ||
12 | + return {...state, register: action.payload } | ||
7 | case LOGIN_USER: | 13 | case LOGIN_USER: |
8 | - return { ...state, loginSuccess: action.payload} | 14 | + return { ...state, loginSucces: action.payload } |
9 | - break; | 15 | + case AUTH_USER: |
10 | - | 16 | + return {...state, userData: action.payload } |
17 | + case LOGOUT_USER: | ||
18 | + return {...state } | ||
11 | default: | 19 | default: |
12 | return state; | 20 | return state; |
13 | } | 21 | } | ... | ... |
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 | +import LandingPage from "./views/LandingPage/LandingPage.js"; | ||
5 | +import LoginPage from "./views/LoginPage/LoginPage.js"; | ||
6 | +import RegisterPage from "./views/RegisterPage/RegisterPage.js"; | ||
7 | +import NavBar from "./views/NavBar/NavBar"; | ||
8 | +import Footer from "./views/Footer/Footer"; | ||
9 | +import UploadProductPage from "./views/UploadProductPage/UploadProductPage.js"; | ||
10 | +import DetailProductPage from "./views/DetailProductPage/DetailProductPage.js"; | ||
11 | +//null 누구나 들어갈 수 있음 | ||
12 | +//true 로그인 하면 들어갈 수 있음 | ||
13 | +//false 로그인 유저는 못들어감 | ||
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 | + <Route exact path="/product/upload" component={Auth(UploadProductPage, true)} /> | ||
25 | + <Route exact path="/product/:productId" component={Auth(DetailProductPage, null)} /> | ||
26 | + </Switch> | ||
27 | + </div> | ||
28 | + <Footer /> | ||
29 | + </Suspense> | ||
30 | + ); | ||
31 | +} | ||
32 | + | ||
33 | +export default App; |
client/src/components/Config.js
0 → 100644
client/src/components/utils/FileUpload.js
0 → 100644
1 | +import React, { useState } from 'react' | ||
2 | +import Dropzone from 'react-dropzone' | ||
3 | +import { Icon } from 'antd'; | ||
4 | +import axios from 'axios'; | ||
5 | + | ||
6 | + | ||
7 | +function FileUpload(props) { | ||
8 | + | ||
9 | + const [Images, setImages] = useState([]) | ||
10 | + | ||
11 | + const dropHandler = (files) => { | ||
12 | + | ||
13 | + let formData = new FormData(); | ||
14 | + const config = { | ||
15 | + header: { 'content-type': 'multipart/fomr-data' } | ||
16 | + } | ||
17 | + formData.append("file", files[0]) | ||
18 | + | ||
19 | + axios.post('/api/product/image', formData, config) | ||
20 | + .then(response => { | ||
21 | + if (response.data.success) { | ||
22 | + setImages([...Images, response.data.filePath]) | ||
23 | + props.refreshFunction([...Images, response.data.filePath]) | ||
24 | + | ||
25 | + | ||
26 | + } else { | ||
27 | + alert('파일을 저장하는데 실패했습니다.') | ||
28 | + } | ||
29 | + }) | ||
30 | + } | ||
31 | + | ||
32 | +// 이미지 클릭 시 삭제 기능 추가 | ||
33 | + const deleteHandler = (image) => { | ||
34 | + const currentIndex = Images.indexOf(image); | ||
35 | + let newImages = [...Images] | ||
36 | + newImages.splice(currentIndex, 1) | ||
37 | + setImages(newImages) | ||
38 | + props.refreshFunction(newImages) | ||
39 | + | ||
40 | + | ||
41 | + } | ||
42 | + | ||
43 | + | ||
44 | + return ( | ||
45 | + <div style={{ display: 'flex', justifyContent: 'space-between' }}> | ||
46 | + <Dropzone onDrop={dropHandler}> | ||
47 | + {({ getRootProps, getInputProps }) => ( | ||
48 | + <div | ||
49 | + style={{ | ||
50 | + width: 300, height: 240, border: '1px solid lightgray', | ||
51 | + display: 'flex', alignItems: 'center', justifyContent: 'center' | ||
52 | + }} | ||
53 | + {...getRootProps()}> | ||
54 | + <input {...getInputProps()} /> | ||
55 | + <Icon type="plus" style={{ fontSize: '3rem' }} /> | ||
56 | + </div> | ||
57 | + )} | ||
58 | + </Dropzone> | ||
59 | + | ||
60 | + <div style={{ display: 'flex', width: '350px', height: '240px', overflowX: 'scroll' }}> | ||
61 | + | ||
62 | + {Images.map((image, index) => ( | ||
63 | + <div onClick={() => deleteHandler(image)} key={index}> | ||
64 | + <img style={{ minWidth: '300px', width: '300px', height: '240px' }} | ||
65 | + src={`http://localhost:5000/${image}`} | ||
66 | + /> | ||
67 | + </div> | ||
68 | + ))} | ||
69 | + | ||
70 | + | ||
71 | + </div> | ||
72 | + | ||
73 | + | ||
74 | + </div> | ||
75 | + ) | ||
76 | +} | ||
77 | + | ||
78 | +export default FileUpload; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
client/src/components/utils/ImageSlider.js
0 → 100644
1 | +import React from 'react' | ||
2 | +import { Icon, Col, Card, Row, Carousel } from 'antd'; | ||
3 | + | ||
4 | +function ImageSlider(props) { | ||
5 | + return ( | ||
6 | + <div> | ||
7 | + <Carousel autoplay > | ||
8 | + {props.images.map((image, index) => ( | ||
9 | + <div key={index}> | ||
10 | + <img style={{ width: '100%', maxHeight: '150px' }} | ||
11 | + src={`http://localhost:5000/${image}`} /> | ||
12 | + </div> | ||
13 | + ))} | ||
14 | + </Carousel> | ||
15 | + </div> | ||
16 | + ) | ||
17 | +} | ||
18 | + | ||
19 | +export default ImageSlider | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, { useEffect, useState } from 'react' | ||
2 | +import axios from 'axios'; | ||
3 | +import ProductImage from './Sections/ProductImage'; | ||
4 | +import ProductInfo from './Sections/ProductInfo'; | ||
5 | +import { Row, Col } from 'antd'; | ||
6 | + | ||
7 | +function DetailProductPage(props) { | ||
8 | + | ||
9 | + const productId = props.match.params.productId | ||
10 | + | ||
11 | + const [Product, setProduct] = useState({}) | ||
12 | + | ||
13 | + useEffect(() => { | ||
14 | + axios.get(`/api/product/products_by_id?id=${productId}&type=single`) | ||
15 | + .then(response => { | ||
16 | + setProduct(response.data[0]) | ||
17 | + }) | ||
18 | + .catch(err => alert(err)) | ||
19 | + }, []) | ||
20 | + | ||
21 | + | ||
22 | + | ||
23 | + return ( | ||
24 | + <div style={{ width: '100%', padding: '3rem 4rem' }}> | ||
25 | + | ||
26 | + <div style={{ display: 'flex', justifyContent: 'center' }}> | ||
27 | + <h1>{Product.title}</h1> | ||
28 | + </div> | ||
29 | + | ||
30 | + <br /> | ||
31 | + | ||
32 | + <Row gutter={[16, 16]} > | ||
33 | + <Col lg={12} sm={24}> | ||
34 | + {/* ProductImage */} | ||
35 | + <ProductImage detail={Product} /> | ||
36 | + </Col> | ||
37 | + <Col lg={12} sm={24}> | ||
38 | + {/* ProductInfo */} | ||
39 | + <ProductInfo detail={Product} /> | ||
40 | + </Col> | ||
41 | + </Row> | ||
42 | + | ||
43 | + | ||
44 | + | ||
45 | + | ||
46 | + | ||
47 | + </div> | ||
48 | + ) | ||
49 | +} | ||
50 | + | ||
51 | +export default DetailProductPage |
1 | +import React, { useState, useEffect } from 'react' | ||
2 | +import ImageGallery from 'react-image-gallery'; | ||
3 | + | ||
4 | +function ProductImage(props) { | ||
5 | + | ||
6 | + const [Images, setImages] = useState([]) | ||
7 | + | ||
8 | + useEffect(() => { | ||
9 | + | ||
10 | + if (props.detail.images && props.detail.images.length > 0) { | ||
11 | + let images = [] | ||
12 | + | ||
13 | + props.detail.images.map(item => { | ||
14 | + images.push({ | ||
15 | + original: `http://localhost:5000/${item}`, | ||
16 | + thumbnail: `http://localhost:5000/${item}` | ||
17 | + }) | ||
18 | + }) | ||
19 | + setImages(images) | ||
20 | + } | ||
21 | + | ||
22 | + }, [props.detail]) | ||
23 | + | ||
24 | + return ( | ||
25 | + <div> | ||
26 | + <ImageGallery items={Images} /> | ||
27 | + </div> | ||
28 | + ) | ||
29 | +} | ||
30 | + | ||
31 | +export default ProductImage | ||
32 | + |
1 | +import React from 'react' | ||
2 | +import {Descriptions } from 'antd'; | ||
3 | + | ||
4 | +function ProductInfo(props) { | ||
5 | + return ( | ||
6 | + <div> | ||
7 | + <Descriptions title="행사 정보"> | ||
8 | + <Descriptions.Item label="Events">{props.detail.continent}</Descriptions.Item> | ||
9 | + <Descriptions.Item label="View">{props.detail.views}</Descriptions.Item> | ||
10 | + <Descriptions.Item label="Description">{props.detail.description}</Descriptions.Item> | ||
11 | + </Descriptions> | ||
12 | + <br /> | ||
13 | + <br /> | ||
14 | + <br /> | ||
15 | + | ||
16 | + | ||
17 | + </div> | ||
18 | + ) | ||
19 | +} | ||
20 | + | ||
21 | +export default ProductInfo |
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> 경희대 클래식기타 동아리 HEXA </p> | ||
7 | </div> | 12 | </div> |
8 | ) | 13 | ) |
9 | } | 14 | } | ... | ... |
1 | -import React, {useEffect} from 'react' | 1 | +import React, { useEffect, useState } from 'react' |
2 | -import axios from 'axios'; | 2 | +import { FaCode } from "react-icons/fa"; |
3 | +import axios from "axios"; | ||
4 | +import { Icon, Col, Card, Row, Carousel } from 'antd'; | ||
5 | +import Meta from 'antd/lib/card/Meta'; | ||
6 | +import ImageSlider from '../../utils/ImageSlider'; | ||
3 | 7 | ||
4 | function LandingPage() { | 8 | function LandingPage() { |
9 | + const [Products, setProducts] = useState([]) | ||
10 | + const [Skip, setSkip] = useState(0) | ||
11 | + const [Limit, setLimit] = useState(8) | ||
12 | + const [PostSize, setPostSize] = useState(0) | ||
13 | + | ||
5 | useEffect(() => { | 14 | useEffect(() => { |
6 | - axios.get('/api/hello') | 15 | + let body = { |
7 | - .then(response => {console.log(response)}) | 16 | + skip: Skip, |
17 | + limit: Limit | ||
18 | + } | ||
19 | + getProducts(body) | ||
8 | }, []) | 20 | }, []) |
9 | 21 | ||
22 | + const getProducts = (body) => { | ||
23 | + axios.post('/api/product/products', body) | ||
24 | + .then(response => { | ||
25 | + if (response.data.success) { | ||
26 | + if (body.loadMore) { | ||
27 | + setProducts([...Products, ...response.data.productInfo]) | ||
28 | + } else { | ||
29 | + setProducts(response.data.productInfo) | ||
30 | + } | ||
31 | + setPostSize(response.data.postSize) | ||
32 | + } else { | ||
33 | + alert(" 상품들을 가져오는데 실패 했습니다.") | ||
34 | + } | ||
35 | + }) | ||
36 | + } | ||
37 | + | ||
38 | + | ||
39 | + const loadMoreHanlder = () => { | ||
40 | + | ||
41 | + let skip = Skip + Limit | ||
42 | + let body = { | ||
43 | + skip: skip, | ||
44 | + limit: Limit, | ||
45 | + loadMore: true | ||
46 | + } | ||
10 | 47 | ||
48 | + getProducts(body) | ||
49 | + setSkip(skip) | ||
50 | + } | ||
51 | + | ||
52 | + const renderCards = Products.map((product, index)=>{ | ||
53 | + return <Col lg ={6} md = {8} xs={24}> | ||
54 | + | ||
55 | + <Card | ||
56 | + key={index} | ||
57 | + cover = {<a href={`/product/${product._id}`} ><ImageSlider images={product.images} /></a>} | ||
58 | + > | ||
59 | + <Meta | ||
60 | + title={product.title} | ||
61 | + description={`${product.description}`} | ||
62 | + /> | ||
63 | + </Card> | ||
64 | + </Col> | ||
65 | + }) | ||
66 | + | ||
11 | return ( | 67 | return ( |
12 | - <div style = {{ | 68 | + <div style = {{ width: '75%', margin: '3rem auto' }}> |
13 | - display: 'flex', justifyContent: 'center', alignItems: 'center' | 69 | + |
14 | - , width: '100%', height: '100vh' | 70 | + <div style={{ textAlign: 'center' }}> |
15 | - }}> | 71 | + <h2>행사 사진 <Icon type = "star"/></h2> |
16 | - <h2> 시작 페이지 </h2> | 72 | + </div> |
73 | + | ||
74 | + <Row gutter = {16}> | ||
75 | + {renderCards} | ||
76 | + </Row> | ||
77 | + | ||
78 | + | ||
79 | + {PostSize >= Limit && | ||
80 | + <div style={{ display: 'flex', justifyContent: 'center' }}> | ||
81 | + <button onClick={loadMoreHanlder}>더보기</button> | ||
82 | + </div> | ||
83 | + } | ||
84 | + | ||
17 | </div> | 85 | </div> |
18 | ) | 86 | ) |
19 | } | 87 | } | ... | ... |
1 | -import React,{useState} from 'react' | ||
2 | -import Axios from 'axios' | ||
3 | -import { useDispatch} from 'react-redux'; | ||
4 | -import {loginUser} from '../../../_actions/user_actions'; | ||
5 | - | ||
6 | -function LoginPage(){ | ||
7 | - const dispatch = useDispatch(); | ||
8 | - const [Email, setEmail] = useState("") | ||
9 | - const [PassWord, setPassWord] = useState("") | ||
10 | - | ||
11 | - const onEmailHandler = (event) => { | ||
12 | - setEmail(event.currentTarget.value) | ||
13 | - } | ||
14 | - | ||
15 | - const onPassWordHandler = (event) => { | ||
16 | - setPassWord(event.currentTarget.value) | ||
17 | - } | ||
18 | - | ||
19 | - const onSubmitHandler = (event) => { | ||
20 | - event.preventDefault(); | ||
21 | - | ||
22 | - | ||
23 | - let body = { | ||
24 | - email: Email, | ||
25 | - password: PassWord | ||
26 | - } | ||
27 | - | ||
28 | - dispatch(loginUser(body)) | ||
29 | - | ||
30 | - | ||
31 | - } | ||
32 | - | ||
33 | - return ( | ||
34 | - <div style = {{ | ||
35 | - display: 'flex', justifyContent: 'center', alignItems: 'center' | ||
36 | - , width: '100%', height: '100vh' | ||
37 | - }}> | ||
38 | - <form style = {{display :'flex', flexDirection: 'column'}} | ||
39 | - onSubmit= {onSubmitHandler} | ||
40 | - > | ||
41 | - <label>Email</label> | ||
42 | - <input type = "email" value = {Email} onChange={onEmailHandler} /> | ||
43 | - <label>PassWord</label> | ||
44 | - <input type = "password" value= {PassWord} onChange = {onPassWordHandler} /> | ||
45 | - <br /> | ||
46 | - <button type = "submit"> | ||
47 | - Login | ||
48 | - </button> | ||
49 | - | ||
50 | - </form> | ||
51 | - </div> | ||
52 | - ) | ||
53 | - | ||
54 | -} | ||
55 | - | ||
56 | -export default LoginPage | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, { useState } from "react"; | ||
2 | +import { withRouter } from "react-router-dom"; | ||
3 | +import { loginUser } from "../../../_actions/user_actions"; | ||
4 | +import { Formik } from 'formik'; | ||
5 | +import * as Yup from 'yup'; | ||
6 | +import { Form, Icon, Input, Button, Checkbox, Typography } from 'antd'; | ||
7 | +import { useDispatch } from "react-redux"; | ||
8 | + | ||
9 | +const { Title } = Typography; | ||
10 | + | ||
11 | +function LoginPage(props) { | ||
12 | + const dispatch = useDispatch(); | ||
13 | + const rememberMeChecked = localStorage.getItem("rememberMe") ? true : false; | ||
14 | + | ||
15 | + const [formErrorMessage, setFormErrorMessage] = useState('') | ||
16 | + const [rememberMe, setRememberMe] = useState(rememberMeChecked) | ||
17 | + | ||
18 | + const handleRememberMe = () => { | ||
19 | + setRememberMe(!rememberMe) | ||
20 | + }; | ||
21 | + | ||
22 | + const initialEmail = localStorage.getItem("rememberMe") ? localStorage.getItem("rememberMe") : ''; | ||
23 | + | ||
24 | + return ( | ||
25 | + <Formik | ||
26 | + initialValues={{ | ||
27 | + email: initialEmail, | ||
28 | + 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 | + }; | ||
44 | + | ||
45 | + dispatch(loginUser(dataToSubmit)) | ||
46 | + .then(response => { | ||
47 | + if (response.payload.loginSuccess) { | ||
48 | + window.localStorage.setItem('userId', response.payload.userId); | ||
49 | + if (rememberMe === true) { | ||
50 | + window.localStorage.setItem('rememberMe', values.id); | ||
51 | + } else { | ||
52 | + localStorage.removeItem('rememberMe'); | ||
53 | + } | ||
54 | + props.history.push("/"); | ||
55 | + } else { | ||
56 | + setFormErrorMessage('Check out your Account or Password again') | ||
57 | + } | ||
58 | + }) | ||
59 | + .catch(err => { | ||
60 | + setFormErrorMessage('Check out your Account or Password again') | ||
61 | + setTimeout(() => { | ||
62 | + setFormErrorMessage("") | ||
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}>Log In</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> | ||
104 | + | ||
105 | + <Form.Item required> | ||
106 | + <Input | ||
107 | + id="password" | ||
108 | + prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} | ||
109 | + placeholder="Enter your password" | ||
110 | + type="password" | ||
111 | + value={values.password} | ||
112 | + onChange={handleChange} | ||
113 | + onBlur={handleBlur} | ||
114 | + className={ | ||
115 | + errors.password && touched.password ? 'text-input error' : 'text-input' | ||
116 | + } | ||
117 | + /> | ||
118 | + {errors.password && touched.password && ( | ||
119 | + <div className="input-feedback">{errors.password}</div> | ||
120 | + )} | ||
121 | + </Form.Item> | ||
122 | + | ||
123 | + {formErrorMessage && ( | ||
124 | + <label ><p style={{ color: '#ff0000bf', fontSize: '0.7rem', border: '1px solid', padding: '1rem', borderRadius: '10px' }}>{formErrorMessage}</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 | + Log in | ||
135 | + </Button> | ||
136 | + </div> | ||
137 | + Or <a href="/register">register now!</a> | ||
138 | + </Form.Item> | ||
139 | + </form> | ||
140 | + </div> | ||
141 | + ); | ||
142 | + }} | ||
143 | + </Formik> | ||
144 | + ); | ||
145 | +}; | ||
146 | + | ||
147 | +export default withRouter(LoginPage); | ||
148 | + | ||
149 | + | ... | ... |
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 = () => { |
7 | - </div> | 11 | + setVisible(true) |
8 | - ) | 12 | + }; |
13 | + | ||
14 | + const onClose = () => { | ||
15 | + setVisible(false) | ||
16 | + }; | ||
9 | 17 | ||
18 | + return ( | ||
19 | + <nav className="menu" style={{ position: 'fixed', zIndex: 5, width: '100%' }}> | ||
20 | + <div className="menu__logo"> | ||
21 | + <a href="/">Hexaforyou</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" /> | ||
29 | + </div> | ||
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 | + ) | ||
10 | } | 51 | } |
11 | 52 | ||
12 | -export default Navbar | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
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 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
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(`${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 | + if (user.userData && !user.userData.isAuth) { | ||
23 | + return ( | ||
24 | + <Menu mode={props.mode}> | ||
25 | + <Menu.Item key="mail"> | ||
26 | + <a href="/login">Signin</a> | ||
27 | + </Menu.Item> | ||
28 | + <Menu.Item key="app"> | ||
29 | + <a href="/register">Signup</a> | ||
30 | + </Menu.Item> | ||
31 | + </Menu> | ||
32 | + ) | ||
33 | + } else { | ||
34 | + return ( | ||
35 | + <Menu mode={props.mode}> | ||
36 | + <Menu.Item key="upload"> | ||
37 | + <a href = "/product/upload">Upload</a> | ||
38 | + </Menu.Item> | ||
39 | + <Menu.Item key="logout"> | ||
40 | + <a onClick={logoutHandler}>Logout</a> | ||
41 | + </Menu.Item> | ||
42 | + </Menu> | ||
43 | + ) | ||
44 | + } | ||
45 | +} | ||
46 | + | ||
47 | +export default withRouter(RightMenu); | ||
48 | + |
1 | -import React from 'react' | 1 | +import React from "react"; |
2 | +import moment from "moment"; | ||
3 | +import { Formik } from 'formik'; | ||
4 | +import * as Yup from 'yup'; | ||
5 | +import { registerUser } from "../../../_actions/user_actions"; | ||
6 | +import { useDispatch } from "react-redux"; | ||
2 | 7 | ||
3 | -function RegisterPage(){ | 8 | +import { |
4 | - return ( | 9 | + Form, |
5 | - <div> | 10 | + Input, |
6 | - RegisterPage | 11 | + Button, |
7 | - </div> | 12 | +} from 'antd'; |
8 | - ) | ||
9 | 13 | ||
10 | -} | 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 | +}; | ||
11 | 36 | ||
12 | -export default RegisterPage | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
37 | +function RegisterPage(props) { | ||
38 | + const dispatch = useDispatch(); | ||
39 | + return ( | ||
40 | + | ||
41 | + <Formik | ||
42 | + initialValues={{ | ||
43 | + email: '', | ||
44 | + lastName: '', | ||
45 | + name: '', | ||
46 | + password: '', | ||
47 | + confirmPassword: '' | ||
48 | + }} | ||
49 | + validationSchema={Yup.object().shape({ | ||
50 | + name: Yup.string() | ||
51 | + .required('Name is required'), | ||
52 | + lastName: Yup.string() | ||
53 | + .required('Last Name is required'), | ||
54 | + email: Yup.string() | ||
55 | + .email('Email is invalid') | ||
56 | + .required('Email is required'), | ||
57 | + password: Yup.string() | ||
58 | + .min(6, 'Password must be at least 6 characters') | ||
59 | + .required('Password is required'), | ||
60 | + confirmPassword: Yup.string() | ||
61 | + .oneOf([Yup.ref('password'), null], 'Passwords must match') | ||
62 | + .required('Confirm Password is required') | ||
63 | + })} | ||
64 | + onSubmit={(values, { setSubmitting }) => { | ||
65 | + setTimeout(() => { | ||
66 | + | ||
67 | + let dataToSubmit = { | ||
68 | + email: values.email, | ||
69 | + password: values.password, | ||
70 | + name: values.name, | ||
71 | + lastname: values.lastname, | ||
72 | + image: `http://gravatar.com/avatar/${moment().unix()}?d=identicon` | ||
73 | + }; | ||
74 | + | ||
75 | + dispatch(registerUser(dataToSubmit)).then(response => { | ||
76 | + if (response.payload.success) { | ||
77 | + props.history.push("/login"); | ||
78 | + } else { | ||
79 | + alert(response.payload.err.errmsg) | ||
80 | + } | ||
81 | + }) | ||
82 | + | ||
83 | + setSubmitting(false); | ||
84 | + }, 500); | ||
85 | + }} | ||
86 | + > | ||
87 | + {props => { | ||
88 | + const { | ||
89 | + values, | ||
90 | + touched, | ||
91 | + errors, | ||
92 | + dirty, | ||
93 | + isSubmitting, | ||
94 | + handleChange, | ||
95 | + handleBlur, | ||
96 | + handleSubmit, | ||
97 | + handleReset, | ||
98 | + } = props; | ||
99 | + return ( | ||
100 | + <div className="app"> | ||
101 | + <h2>Sign up</h2> | ||
102 | + <Form style={{ minWidth: '375px' }} {...formItemLayout} onSubmit={handleSubmit} > | ||
103 | + | ||
104 | + <Form.Item required label="Name"> | ||
105 | + <Input | ||
106 | + id="name" | ||
107 | + placeholder="Enter your name" | ||
108 | + type="text" | ||
109 | + value={values.name} | ||
110 | + onChange={handleChange} | ||
111 | + onBlur={handleBlur} | ||
112 | + className={ | ||
113 | + errors.name && touched.name ? 'text-input error' : 'text-input' | ||
114 | + } | ||
115 | + /> | ||
116 | + {errors.name && touched.name && ( | ||
117 | + <div className="input-feedback">{errors.name}</div> | ||
118 | + )} | ||
119 | + </Form.Item> | ||
120 | + | ||
121 | + <Form.Item required label="Last Name"> | ||
122 | + <Input | ||
123 | + id="lastName" | ||
124 | + placeholder="Enter your Last Name" | ||
125 | + type="text" | ||
126 | + value={values.lastName} | ||
127 | + onChange={handleChange} | ||
128 | + onBlur={handleBlur} | ||
129 | + className={ | ||
130 | + errors.lastName && touched.lastName ? 'text-input error' : 'text-input' | ||
131 | + } | ||
132 | + /> | ||
133 | + {errors.lastName && touched.lastName && ( | ||
134 | + <div className="input-feedback">{errors.lastName}</div> | ||
135 | + )} | ||
136 | + </Form.Item> | ||
137 | + | ||
138 | + <Form.Item required label="Email" hasFeedback validateStatus={errors.email && touched.email ? "error" : 'success'}> | ||
139 | + <Input | ||
140 | + id="email" | ||
141 | + placeholder="Enter your Email" | ||
142 | + type="email" | ||
143 | + value={values.email} | ||
144 | + onChange={handleChange} | ||
145 | + onBlur={handleBlur} | ||
146 | + className={ | ||
147 | + errors.email && touched.email ? 'text-input error' : 'text-input' | ||
148 | + } | ||
149 | + /> | ||
150 | + {errors.email && touched.email && ( | ||
151 | + <div className="input-feedback">{errors.email}</div> | ||
152 | + )} | ||
153 | + </Form.Item> | ||
154 | + | ||
155 | + <Form.Item required label="Password" hasFeedback validateStatus={errors.password && touched.password ? "error" : 'success'}> | ||
156 | + <Input | ||
157 | + id="password" | ||
158 | + placeholder="Enter your password" | ||
159 | + type="password" | ||
160 | + value={values.password} | ||
161 | + onChange={handleChange} | ||
162 | + onBlur={handleBlur} | ||
163 | + className={ | ||
164 | + errors.password && touched.password ? 'text-input error' : 'text-input' | ||
165 | + } | ||
166 | + /> | ||
167 | + {errors.password && touched.password && ( | ||
168 | + <div className="input-feedback">{errors.password}</div> | ||
169 | + )} | ||
170 | + </Form.Item> | ||
171 | + | ||
172 | + <Form.Item required label="Confirm" hasFeedback> | ||
173 | + <Input | ||
174 | + id="confirmPassword" | ||
175 | + placeholder="Enter your confirmPassword" | ||
176 | + type="password" | ||
177 | + value={values.confirmPassword} | ||
178 | + onChange={handleChange} | ||
179 | + onBlur={handleBlur} | ||
180 | + className={ | ||
181 | + errors.confirmPassword && touched.confirmPassword ? 'text-input error' : 'text-input' | ||
182 | + } | ||
183 | + /> | ||
184 | + {errors.confirmPassword && touched.confirmPassword && ( | ||
185 | + <div className="input-feedback">{errors.confirmPassword}</div> | ||
186 | + )} | ||
187 | + </Form.Item> | ||
188 | + | ||
189 | + <Form.Item {...tailFormItemLayout}> | ||
190 | + <Button onClick={handleSubmit} type="primary" disabled={isSubmitting}> | ||
191 | + Submit | ||
192 | + </Button> | ||
193 | + </Form.Item> | ||
194 | + </Form> | ||
195 | + </div> | ||
196 | + ); | ||
197 | + }} | ||
198 | + </Formik> | ||
199 | + ); | ||
200 | +}; | ||
201 | + | ||
202 | + | ||
203 | +export default RegisterPage | ... | ... |
1 | +import React, { useState } from 'react' | ||
2 | +import { Typography, Button, Form, Input } from 'antd'; | ||
3 | +import FileUpload from '../../utils/FileUpload'; | ||
4 | +import Axios from 'axios'; | ||
5 | +const { TextArea } = Input; | ||
6 | +const Continents = [ | ||
7 | + { key: 1, value: "개강총회" }, | ||
8 | + { key: 2, value: "종강총회" }, | ||
9 | + { key: 3, value: "창립제" }, | ||
10 | + { key: 4, value: "신입생환영연주회" }, | ||
11 | + { key: 5, value: "신입생연주회" }, | ||
12 | + { key: 6, value: "정기연주회" }, | ||
13 | + { key: 7, value: "기타행사" } | ||
14 | +] | ||
15 | + | ||
16 | +// 업로드 페이지 | ||
17 | +export default function UploadProductPage(props) { | ||
18 | + | ||
19 | + const [Title, setTitle] = useState("") | ||
20 | + const [Description, setDescription] = useState("") | ||
21 | + const [Continent, setContinent] = useState(1) | ||
22 | + const [Images, setImages] = useState([]) | ||
23 | + | ||
24 | + const titleChangeHandler = (event) => { | ||
25 | + setTitle(event.currentTarget.value) | ||
26 | + } | ||
27 | + | ||
28 | + const descriptionChangeHandler = (event) => { | ||
29 | + setDescription(event.currentTarget.value) | ||
30 | + } | ||
31 | + | ||
32 | + const continentChangeHandler = (event) => { | ||
33 | + setContinent(event.currentTarget.value) | ||
34 | + } | ||
35 | + | ||
36 | + const updateImages = (newImages) => { | ||
37 | + setImages(newImages) | ||
38 | + } | ||
39 | + | ||
40 | + const submitHandler = (event) => { | ||
41 | + event.preventDefault(); | ||
42 | + | ||
43 | + | ||
44 | + if (!Title || !Description || !Continent || !Images) { | ||
45 | + return alert(" 모든 값을 넣어주셔야 합니다.") | ||
46 | + } | ||
47 | + | ||
48 | + | ||
49 | + //서버에 채운 값들을 request로 보낸다. | ||
50 | + | ||
51 | + const body = { | ||
52 | + //로그인 된 사람의 ID | ||
53 | + writer: props.user.userData._id, | ||
54 | + title: Title, | ||
55 | + description: Description, | ||
56 | + images: Images, | ||
57 | + continents: Continent | ||
58 | + } | ||
59 | + | ||
60 | + Axios.post('/api/product', body) | ||
61 | + .then(response => { | ||
62 | + if (response.data.success) { | ||
63 | + alert('사진 업로드에 성공 했습니다.') | ||
64 | + props.history.push('/') | ||
65 | + } else { | ||
66 | + alert('사진 업로드에 실패 했습니다.') | ||
67 | + } | ||
68 | + }) | ||
69 | + } | ||
70 | + | ||
71 | + | ||
72 | + return ( | ||
73 | + <div style={{ maxWidth: '700px', margin: '2rem auto' }}> | ||
74 | + <div style={{ textAlign: 'center', marginBottom: '2rem' }}> | ||
75 | + <h2> 행사 사진 업로드</h2> | ||
76 | + </div> | ||
77 | + | ||
78 | + <Form onSubmit={submitHandler}> | ||
79 | + {/* DropZone */} | ||
80 | + <FileUpload refreshFunction={updateImages} /> | ||
81 | + | ||
82 | + <br /> | ||
83 | + <br /> | ||
84 | + <label>제목</label> | ||
85 | + <Input onChange={titleChangeHandler} value={Title} /> | ||
86 | + <br /> | ||
87 | + <br /> | ||
88 | + <label>설명</label> | ||
89 | + <TextArea onChange={descriptionChangeHandler} value={Description} /> | ||
90 | + <br /> | ||
91 | + <br /> | ||
92 | + <select onChange={continentChangeHandler} value={Continent}> | ||
93 | + {Continents.map(item => ( | ||
94 | + <option key={item.key} value={item.key}> {item.value}</option> | ||
95 | + ))} | ||
96 | + </select> | ||
97 | + <br /> | ||
98 | + <br /> | ||
99 | + <button type="submit"> | ||
100 | + 확인 | ||
101 | + </button> | ||
102 | + </Form> | ||
103 | + | ||
104 | + | ||
105 | + </div> | ||
106 | + | ||
107 | + ) | ||
108 | +} |
client/src/hoc/auth.js
0 → 100644
1 | +import React, { useEffect } from 'react'; | ||
2 | +import { auth } from '../_actions/user_actions'; | ||
3 | +import { useSelector, useDispatch } from "react-redux"; | ||
4 | + | ||
5 | +export default function (SpecificComponent, option, adminRoute = null) { | ||
6 | + function AuthenticationCheck(props) { | ||
7 | + | ||
8 | + let user = useSelector(state => state.user); | ||
9 | + const dispatch = useDispatch(); | ||
10 | + | ||
11 | + useEffect(() => { | ||
12 | + // 현재 나의 상태를 알기 위해서 Auth request보냄 | ||
13 | + dispatch(auth()).then(response => { | ||
14 | + // 로그인 상태가 아님 | ||
15 | + if (!response.payload.isAuth) { | ||
16 | + if (option) { | ||
17 | + props.history.push('/login') | ||
18 | + } | ||
19 | + // 로그인 상태 | ||
20 | + } else { | ||
21 | + | ||
22 | + if (adminRoute && !response.payload.isAdmin) { | ||
23 | + props.history.push('/') | ||
24 | + } | ||
25 | + else { | ||
26 | + if (option === false) { | ||
27 | + props.history.push('/') | ||
28 | + } | ||
29 | + } | ||
30 | + } | ||
31 | + }) | ||
32 | + | ||
33 | + }, []) | ||
34 | + | ||
35 | + return ( | ||
36 | + <SpecificComponent {...props} user={user} /> | ||
37 | + ) | ||
38 | + } | ||
39 | + return AuthenticationCheck | ||
40 | +} | ||
41 | + | ||
42 | + |
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 * as serviceWorker from './serviceWorker'; | 9 | import * as serviceWorker from './serviceWorker'; |
6 | -import {Provider} from 'react-redux'; | 10 | +import { BrowserRouter } from "react-router-dom"; |
7 | -import 'antd/dist/antd.css'; | 11 | + |
8 | -import { applyMiddleware, createStore} from 'redux'; | 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/index'; | ||
12 | 17 | ||
13 | -const creatStoreWithMiddleware = applyMiddleware(promiseMiddleware,ReduxThunk)(createStore) | 18 | +const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore); |
19 | + | ||
14 | ReactDOM.render( | 20 | ReactDOM.render( |
15 | - <Provider | 21 | + <Provider |
16 | - store={creatStoreWithMiddleware(Reducer, | 22 | + store={createStoreWithMiddleware( |
17 | - window.__REDUX_DEVTOOLS_EXTENTION__&& | 23 | + Reducer, |
18 | - window.__REDUX_DEVTOOLS_EXTENTION__() | 24 | + window.__REDUX_DEVTOOLS_EXTENSION__ && |
19 | - | 25 | + window.__REDUX_DEVTOOLS_EXTENSION__() |
20 | )} | 26 | )} |
21 | - > | 27 | + > |
22 | - <App /> | 28 | + <BrowserRouter> |
23 | - </Provider> | 29 | + <App /> |
24 | - | 30 | + </BrowserRouter> |
25 | - | 31 | + </Provider> |
26 | - | 32 | + , document.getElementById('root')); |
27 | - ,document.getElementById('root') | ||
28 | - | ||
29 | - | ||
30 | -); | ||
31 | 33 | ||
32 | -// If you want your app to work offline and load faster, you can change | ||
33 | -// unregister() to register() below. Note this comes with some pitfalls. | ||
34 | -// Learn more about service workers: https://bit.ly/CRA-PWA | ||
35 | serviceWorker.unregister(); | 34 | serviceWorker.unregister(); | ... | ... |
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 | 1 | ||
13 | const isLocalhost = Boolean( | 2 | const isLocalhost = Boolean( |
14 | window.location.hostname === 'localhost' || | 3 | window.location.hostname === 'localhost' || |
15 | // [::1] is the IPv6 localhost address. | 4 | // [::1] is the IPv6 localhost address. |
16 | window.location.hostname === '[::1]' || | 5 | window.location.hostname === '[::1]' || |
17 | - // 127.0.0.0/8 are considered localhost for IPv4. | 6 | + // 127.0.0.1/8 is considered localhost for IPv4. |
18 | window.location.hostname.match( | 7 | window.location.hostname.match( |
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ | 8 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ |
20 | ) | 9 | ) |
... | @@ -22,12 +11,9 @@ const isLocalhost = Boolean( | ... | @@ -22,12 +11,9 @@ const isLocalhost = Boolean( |
22 | 11 | ||
23 | export function register(config) { | 12 | export function register(config) { |
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { | 13 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { |
25 | - // The URL constructor is available in all browsers that support SW. | 14 | + |
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); | 15 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); |
27 | if (publicUrl.origin !== window.location.origin) { | 16 | 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; | 17 | return; |
32 | } | 18 | } |
33 | 19 | ||
... | @@ -35,11 +21,10 @@ export function register(config) { | ... | @@ -35,11 +21,10 @@ export function register(config) { |
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; | 21 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; |
36 | 22 | ||
37 | if (isLocalhost) { | 23 | if (isLocalhost) { |
38 | - // This is running on localhost. Let's check if a service worker still exists or not. | 24 | + |
39 | checkValidServiceWorker(swUrl, config); | 25 | checkValidServiceWorker(swUrl, config); |
40 | 26 | ||
41 | - // Add some additional logging to localhost, pointing developers to the | 27 | + |
42 | - // service worker/PWA documentation. | ||
43 | navigator.serviceWorker.ready.then(() => { | 28 | navigator.serviceWorker.ready.then(() => { |
44 | console.log( | 29 | console.log( |
45 | 'This web app is being served cache-first by a service ' + | 30 | 'This web app is being served cache-first by a service ' + |
... | @@ -47,7 +32,7 @@ export function register(config) { | ... | @@ -47,7 +32,7 @@ export function register(config) { |
47 | ); | 32 | ); |
48 | }); | 33 | }); |
49 | } else { | 34 | } else { |
50 | - // Is not localhost. Just register service worker | 35 | + |
51 | registerValidSW(swUrl, config); | 36 | registerValidSW(swUrl, config); |
52 | } | 37 | } |
53 | }); | 38 | }); |
... | @@ -66,25 +51,19 @@ function registerValidSW(swUrl, config) { | ... | @@ -66,25 +51,19 @@ function registerValidSW(swUrl, config) { |
66 | installingWorker.onstatechange = () => { | 51 | installingWorker.onstatechange = () => { |
67 | if (installingWorker.state === 'installed') { | 52 | if (installingWorker.state === 'installed') { |
68 | if (navigator.serviceWorker.controller) { | 53 | 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( | 54 | console.log( |
73 | 'New content is available and will be used when all ' + | 55 | 'New content is available and will be used when all ' + |
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' | 56 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' |
75 | ); | 57 | ); |
76 | 58 | ||
77 | - // Execute callback | 59 | + |
78 | if (config && config.onUpdate) { | 60 | if (config && config.onUpdate) { |
79 | config.onUpdate(registration); | 61 | config.onUpdate(registration); |
80 | } | 62 | } |
81 | } else { | 63 | } 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.'); | 64 | console.log('Content is cached for offline use.'); |
86 | 65 | ||
87 | - // Execute callback | 66 | + |
88 | if (config && config.onSuccess) { | 67 | if (config && config.onSuccess) { |
89 | config.onSuccess(registration); | 68 | config.onSuccess(registration); |
90 | } | 69 | } |
... | @@ -99,25 +78,23 @@ function registerValidSW(swUrl, config) { | ... | @@ -99,25 +78,23 @@ function registerValidSW(swUrl, config) { |
99 | } | 78 | } |
100 | 79 | ||
101 | function checkValidServiceWorker(swUrl, config) { | 80 | function checkValidServiceWorker(swUrl, config) { |
102 | - // Check if the service worker can be found. If it can't reload the page. | 81 | + |
103 | - fetch(swUrl, { | 82 | + fetch(swUrl) |
104 | - headers: { 'Service-Worker': 'script' }, | ||
105 | - }) | ||
106 | .then(response => { | 83 | .then(response => { |
107 | - // Ensure service worker exists, and that we really are getting a JS file. | 84 | + |
108 | const contentType = response.headers.get('content-type'); | 85 | const contentType = response.headers.get('content-type'); |
109 | if ( | 86 | if ( |
110 | response.status === 404 || | 87 | response.status === 404 || |
111 | (contentType != null && contentType.indexOf('javascript') === -1) | 88 | (contentType != null && contentType.indexOf('javascript') === -1) |
112 | ) { | 89 | ) { |
113 | - // No service worker found. Probably a different app. Reload the page. | 90 | + |
114 | navigator.serviceWorker.ready.then(registration => { | 91 | navigator.serviceWorker.ready.then(registration => { |
115 | registration.unregister().then(() => { | 92 | registration.unregister().then(() => { |
116 | window.location.reload(); | 93 | window.location.reload(); |
117 | }); | 94 | }); |
118 | }); | 95 | }); |
119 | } else { | 96 | } else { |
120 | - // Service worker found. Proceed as normal. | 97 | + |
121 | registerValidSW(swUrl, config); | 98 | registerValidSW(swUrl, config); |
122 | } | 99 | } |
123 | }) | 100 | }) |
... | @@ -130,12 +107,8 @@ function checkValidServiceWorker(swUrl, config) { | ... | @@ -130,12 +107,8 @@ function checkValidServiceWorker(swUrl, config) { |
130 | 107 | ||
131 | export function unregister() { | 108 | export function unregister() { |
132 | if ('serviceWorker' in navigator) { | 109 | if ('serviceWorker' in navigator) { |
133 | - navigator.serviceWorker.ready | 110 | + navigator.serviceWorker.ready.then(registration => { |
134 | - .then(registration => { | 111 | + registration.unregister(); |
135 | - registration.unregister(); | 112 | + }); |
136 | - }) | ||
137 | - .catch(error => { | ||
138 | - console.error(error.message); | ||
139 | - }); | ||
140 | } | 113 | } |
141 | } | 114 | } | ... | ... |
1 | -const {createProxyMiddleware} = require('http-proxy-middleware'); | 1 | +const { createProxyMiddleware } = require('http-proxy-middleware'); |
2 | -module.exports = function(app){ | 2 | +// 프록시 서버 만들어 줘서 프로트와 서버의 데이터 통신을 가능하게 해줌 |
3 | +module.exports = function (app) { | ||
3 | app.use( | 4 | app.use( |
4 | - createProxyMiddleware('/api',{ | 5 | + '/api', |
5 | - target : 'http://localhost:5000/', | 6 | + createProxyMiddleware({ |
6 | - changeOrigin: true | 7 | + target: 'http://localhost:5000', |
8 | + changeOrigin: true, | ||
7 | }) | 9 | }) |
8 | - ) | 10 | + ); |
9 | -}; | 11 | +}; |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
client/src/setupTests.js
deleted
100644 → 0
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": "hexa_for_you_renewal", | 2 | "name": "hexa_for_you_renewal", |
3 | "version": "1.0.0", | 3 | "version": "1.0.0", |
4 | - "description": ">20151039045 정승호", | 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 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 | "repository": { | 16 | "repository": { |
13 | "type": "git", | 17 | "type": "git", |
... | @@ -16,15 +20,22 @@ | ... | @@ -16,15 +20,22 @@ |
16 | "author": "seungho", | 20 | "author": "seungho", |
17 | "license": "ISC", | 21 | "license": "ISC", |
18 | "dependencies": { | 22 | "dependencies": { |
19 | - "bcrypt": "^4.0.1", | 23 | + "bcrypt": "^3.0.6", |
20 | - "body-parser": "^1.19.0", | 24 | + "body-parser": "^1.18.3", |
21 | - "concurrently": "^5.2.0", | 25 | + "cookie-parser": "^1.4.3", |
22 | - "cookie-parser": "^1.4.5", | 26 | + "cors": "^2.8.5", |
27 | + "debug": "^4.1.1", | ||
23 | "express": "^4.17.1", | 28 | "express": "^4.17.1", |
24 | "jsonwebtoken": "^8.5.1", | 29 | "jsonwebtoken": "^8.5.1", |
25 | - "mongoose": "^5.9.15" | 30 | + "moment": "^2.24.0", |
31 | + "mongoose": "^5.4.20", | ||
32 | + "multer": "^1.4.2", | ||
33 | + "react-redux": "^5.0.7", | ||
34 | + "saslprep": "^1.0.3", | ||
35 | + "supports-color": "^7.1.0" | ||
26 | }, | 36 | }, |
27 | "devDependencies": { | 37 | "devDependencies": { |
28 | - "nodemon": "^2.0.4" | 38 | + "concurrently": "^4.1.0", |
39 | + "nodemon": "^1.19.1" | ||
29 | } | 40 | } |
30 | } | 41 | } | ... | ... |
1 | -if(process.env.NODE_ENV=== 'production'){ | 1 | +if (process.env.NODE_ENV === 'production') { |
2 | - module.exports = require('./prod') | 2 | + module.exports = require('./prod'); |
3 | -} else{ | 3 | +} else { |
4 | - module.exports = require('./dev') | 4 | + module.exports = require('./dev'); |
5 | } | 5 | } |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | +const express = require("express"); | ||
2 | +const app = express(); | ||
3 | +const path = require("path"); | ||
4 | +const cors = require('cors') | ||
1 | 5 | ||
2 | -const express = require('express') | 6 | +const bodyParser = require("body-parser"); |
3 | -const app = express() | 7 | +const cookieParser = require("cookie-parser"); |
4 | -const bodyParser = require('body-parser'); | ||
5 | -const cookieParser = require('cookie-parser'); | ||
6 | -const port = 5000 | ||
7 | -const { auth } = require('./middleware/auth'); | ||
8 | -const { User } = require('./models/Users'); | ||
9 | 8 | ||
10 | -const config = require('./config/dev') | 9 | +const config = require("./config/key"); |
11 | 10 | ||
12 | -app.use(bodyParser.urlencoded({extended : true})); | ||
13 | -app.use(bodyParser.json()); | ||
14 | 11 | ||
12 | +const mongoose = require("mongoose"); | ||
13 | +const connect = mongoose.connect(config.mongoURI, | ||
14 | + { | ||
15 | + useNewUrlParser: true, useUnifiedTopology: true, | ||
16 | + useCreateIndex: true, useFindAndModify: false | ||
17 | + }) | ||
18 | + .then(() => console.log('MongoDB Connected...')) | ||
19 | + .catch(err => console.log(err)); | ||
20 | + | ||
21 | +app.use(cors()) | ||
22 | + | ||
23 | + | ||
24 | +app.use(bodyParser.urlencoded({ extended: true })); | ||
25 | +app.use(bodyParser.json()); | ||
15 | app.use(cookieParser()); | 26 | app.use(cookieParser()); |
16 | 27 | ||
17 | -const mongoose = require('mongoose') | 28 | +app.use('/api/users', require('./routes/users')); |
18 | -mongoose.connect(config.mongoURI,{ | 29 | +app.use('/api/product', require('./routes/product')); |
19 | - useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex : true, useFindAndModify: false | 30 | + |
20 | -}).then(() => console.log('MongoDb connected....')) | 31 | + |
21 | - .catch(err => console.log('Error')) | 32 | +app.use('/uploads', express.static('uploads')); |
22 | - | 33 | + |
23 | - | 34 | +if (process.env.NODE_ENV === "production") { |
24 | - | 35 | + |
25 | -app.get('/api/hello', (req,res) => res.send('Hello world!! 오늘도 지식이 쌓였당!!')) | 36 | + app.use(express.static("client/build")); |
26 | - | 37 | + |
27 | -app.post('/api/users/register', (req, res) => { | 38 | + |
28 | - // 회원 가입시 필요한 정보들을 client에서 가져오면 | 39 | + app.get("*", (req, res) => { |
29 | - // 그것들을 데이터베이스에 넣어준다. | 40 | + res.sendFile(path.resolve(__dirname, "../client", "build", "index.html")); |
30 | - const user = new User(req.body) | 41 | + }); |
31 | - | 42 | +} |
32 | - user.save((err, userInfo) => { | ||
33 | - if(err) return res.json({success : false, err}) | ||
34 | - return res.status(200).json({ | ||
35 | - success : true | ||
36 | - }) | ||
37 | - }) // MongoDb에 저징 | ||
38 | -}) | ||
39 | - | ||
40 | -app.post('/api/users/login', (req, res) => { | ||
41 | - | ||
42 | - // 요청된 이메일이 데이터 베이스에 있는지 확인 | ||
43 | - User.findOne({email : req.body.email}, (err, user) =>{ | ||
44 | - if(!user){ | ||
45 | - return res.json({ | ||
46 | - loginSuccess : false, | ||
47 | - message : "이메일에 해당하는 유저가 없습니다." | ||
48 | - }) | ||
49 | - } | ||
50 | - // 요청된 이메일이 데이터 베이스에 있다면, 비밀번호가 맞는디 확인 | ||
51 | - user.comparePassword( req.body.password,(err, isMatch) =>{ | ||
52 | - if(!isMatch) | ||
53 | - return res.json({loginSuccess : false, message : "비밀번호 오류"}) | ||
54 | - // 비밀번호까지 맞다면 그 유저에 대한 토큰을 생성한다. | ||
55 | - user.generateToken((err,user) =>{ | ||
56 | - if(err) return res.status(400).send(err); | ||
57 | - | ||
58 | - //토큰을 저장한다. | ||
59 | - res.cookie("x_auth",user.token) | ||
60 | - .status(200) | ||
61 | - .json({loginSuccess:true, userId: user._id}) | ||
62 | - | ||
63 | - | ||
64 | - }) | ||
65 | - }) | ||
66 | - }) | ||
67 | -}) | ||
68 | - | ||
69 | - | ||
70 | -app.get('/api/users/auth', auth , (req,res)=> { | ||
71 | - // 인증이 완료 | ||
72 | - | ||
73 | - res.status(200).json({ | ||
74 | - _id: req.user._id, | ||
75 | - isAdmin: req.user.role === 0 ? false : true, | ||
76 | - isAuth: true, | ||
77 | - email: req.user.name, | ||
78 | - lastname: req.user.lastname, | ||
79 | - role : req.user.role, | ||
80 | - image : req.user.image | ||
81 | - }) | ||
82 | -}) | ||
83 | - | ||
84 | -app.get('api/users/logout', auth,(req,res)=>{ | ||
85 | - | ||
86 | - User.findOneAndUpdate({_id: req.user._id}, | ||
87 | - { token: ""} | ||
88 | - , (err, user) =>{ | ||
89 | - if(err) return res.json({success: false, err}); | ||
90 | - return res.status(200).send({ | ||
91 | - success: true | ||
92 | - }) | ||
93 | - }) | ||
94 | -}) | ||
95 | - | ||
96 | - | ||
97 | - | ||
98 | -app.listen(port, () => console.log('example app listen on port %s!', port)) | ||
99 | 43 | ||
44 | +const port = process.env.PORT || 5000 | ||
100 | 45 | ||
46 | +app.listen(port, () => { | ||
47 | + console.log(`Server Listening on ${port}`) | ||
48 | +}); | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | -const {User} = require('../models/Users'); | ||
2 | - | ||
3 | - | ||
4 | -let auth = (req,res,next) => { | ||
5 | - //인증처리 하는 함수 | ||
6 | - | ||
7 | - // 클라이언트에서 cookie 가져온다 | ||
8 | - let token = req.cookies.x_auth; | ||
9 | - | ||
10 | - // 토큰을 복호화 한 후 유저 찾는다. | ||
11 | - User.findByToken(token, (err, user)=>{ | ||
12 | - if(err) throw err; | ||
13 | - if(!err) return res.json({isAuth :false, error : true}) | ||
14 | - | ||
15 | - req.token = token; | ||
16 | - req.user = user; | ||
17 | - next(); | ||
18 | - }) | ||
19 | - | ||
20 | - // 유저 없으면 인증 됨 | ||
21 | - | ||
22 | - // 유저 있으면 인증 안됨 | ||
23 | - | ||
24 | -} | ||
25 | - | ||
26 | -module.exports = {auth} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +const { User } = require('../models/User'); | ||
2 | + | ||
3 | +let auth = (req, res, next) => { | ||
4 | + let token = req.cookies.w_auth; | ||
5 | + | ||
6 | + User.findByToken(token, (err, user) => { | ||
7 | + if (err) throw err; | ||
8 | + if (!user) | ||
9 | + return res.json({ | ||
10 | + isAuth: false, | ||
11 | + error: true | ||
12 | + }); | ||
13 | + | ||
14 | + req.token = token; | ||
15 | + req.user = user; | ||
16 | + next(); | ||
17 | + }); | ||
18 | +}; | ||
19 | + | ||
20 | +module.exports = { auth }; | ... | ... |
server/models/Product.js
0 → 100644
1 | +const mongoose = require('mongoose'); | ||
2 | +const Schema = mongoose.Schema; | ||
3 | + | ||
4 | +const productSchema = mongoose.Schema({ | ||
5 | + writer: { | ||
6 | + type: Schema.Types.ObjectId, | ||
7 | + ref: 'User' | ||
8 | + }, | ||
9 | + title: { | ||
10 | + type: String, | ||
11 | + maxlength: 50 | ||
12 | + }, | ||
13 | + description: { | ||
14 | + type: String, | ||
15 | + }, | ||
16 | + images: { | ||
17 | + type: Array, | ||
18 | + default: [] | ||
19 | + }, | ||
20 | + eontinents: { | ||
21 | + type: Number, | ||
22 | + default: 1 | ||
23 | + }, | ||
24 | + views: { | ||
25 | + type: Number, | ||
26 | + default: 0 | ||
27 | + } | ||
28 | +}, { timestamps: true }) | ||
29 | + | ||
30 | +productSchema.index({ | ||
31 | + title: 'text', | ||
32 | + description: 'text' | ||
33 | +}, { | ||
34 | + weights: { | ||
35 | + title: 5, | ||
36 | + description: 1 | ||
37 | + } | ||
38 | +}) | ||
39 | + | ||
40 | + | ||
41 | +const Product = mongoose.model('Product', productSchema); | ||
42 | + | ||
43 | +module.exports = { Product } | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/models/User.js
0 → 100644
1 | +const mongoose = require('mongoose'); | ||
2 | +const bcrypt = require('bcrypt'); | ||
3 | +const saltRounds = 10; | ||
4 | +const jwt = require('jsonwebtoken'); | ||
5 | +const moment = require("moment"); | ||
6 | + | ||
7 | +const userSchema = mongoose.Schema({ | ||
8 | + name: { | ||
9 | + type:String, | ||
10 | + maxlength:50 | ||
11 | + }, | ||
12 | + email: { | ||
13 | + type:String, | ||
14 | + trim:true, | ||
15 | + unique: 1 | ||
16 | + }, | ||
17 | + password: { | ||
18 | + type: String, | ||
19 | + minglength: 5 | ||
20 | + }, | ||
21 | + lastname: { | ||
22 | + type:String, | ||
23 | + maxlength: 50 | ||
24 | + }, | ||
25 | + role : { | ||
26 | + type:Number, | ||
27 | + default: 0 | ||
28 | + }, | ||
29 | + image: String, | ||
30 | + token : { | ||
31 | + type: String, | ||
32 | + }, | ||
33 | + tokenExp :{ | ||
34 | + type: Number | ||
35 | + } | ||
36 | +}) | ||
37 | + | ||
38 | + | ||
39 | +userSchema.pre('save', function( next ) { | ||
40 | + var user = this; | ||
41 | + | ||
42 | + if(user.isModified('password')){ | ||
43 | + // console.log('password changed') | ||
44 | + bcrypt.genSalt(saltRounds, function(err, salt){ | ||
45 | + if(err) return next(err); | ||
46 | + | ||
47 | + bcrypt.hash(user.password, salt, function(err, hash){ | ||
48 | + if(err) return next(err); | ||
49 | + user.password = hash | ||
50 | + next() | ||
51 | + }) | ||
52 | + }) | ||
53 | + } else { | ||
54 | + next() | ||
55 | + } | ||
56 | +}); | ||
57 | + | ||
58 | +userSchema.methods.comparePassword = function(plainPassword,cb){ | ||
59 | + bcrypt.compare(plainPassword, this.password, function(err, isMatch){ | ||
60 | + if (err) return cb(err); | ||
61 | + cb(null, isMatch) | ||
62 | + }) | ||
63 | +} | ||
64 | + | ||
65 | +userSchema.methods.generateToken = function(cb) { | ||
66 | + var user = this; | ||
67 | + var token = jwt.sign(user._id.toHexString(),'secret') | ||
68 | + var oneHour = moment().add(1, 'hour').valueOf(); | ||
69 | + | ||
70 | + user.tokenExp = oneHour; | ||
71 | + user.token = token; | ||
72 | + user.save(function (err, user){ | ||
73 | + if(err) return cb(err) | ||
74 | + cb(null, user); | ||
75 | + }) | ||
76 | +} | ||
77 | + | ||
78 | +userSchema.statics.findByToken = function (token, cb) { | ||
79 | + var user = this; | ||
80 | + | ||
81 | + jwt.verify(token,'secret',function(err, decode){ | ||
82 | + user.findOne({"_id":decode, "token":token}, function(err, user){ | ||
83 | + if(err) return cb(err); | ||
84 | + cb(null, user); | ||
85 | + }) | ||
86 | + }) | ||
87 | +} | ||
88 | + | ||
89 | +const User = mongoose.model('User', userSchema); | ||
90 | + | ||
91 | +module.exports = { User } | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/models/Users.js
deleted
100644 → 0
1 | -const mongoose = require('mongoose'); | ||
2 | -const bcrypt = require('bcrypt'); | ||
3 | -const saltRounds = 10; | ||
4 | -const jwt = require('jsonwebtoken'); | ||
5 | - | ||
6 | -const userSchema = mongoose.Schema({ | ||
7 | - name : { | ||
8 | - type : String, | ||
9 | - maxlength : 50 | ||
10 | - }, | ||
11 | - email :{ | ||
12 | - type : String, | ||
13 | - trim : true, // trim은 이메일 주소 받을때 공백을 없애준다. | ||
14 | - unique : 1 // 중복 허용 안함 | ||
15 | - }, | ||
16 | - password :{ | ||
17 | - type : String, | ||
18 | - maxlength : 50 | ||
19 | - }, | ||
20 | - lastname:{ | ||
21 | - type : String, | ||
22 | - maxlength : 50 | ||
23 | - }, | ||
24 | - role:{ | ||
25 | - type : Number, // 1 : admin, 0 : common user | ||
26 | - default : 0 | ||
27 | - }, | ||
28 | - image: String, | ||
29 | - token: { | ||
30 | - type : String | ||
31 | - }, | ||
32 | - tokenExp :{ | ||
33 | - type: Number | ||
34 | - } | ||
35 | - | ||
36 | -}) | ||
37 | - | ||
38 | -// password 암호화 | ||
39 | -userSchema.pre('save', function(next){ | ||
40 | - var user = this; | ||
41 | - | ||
42 | - if(user.isModified('password')){ | ||
43 | - | ||
44 | - bcrypt.genSalt(saltRounds, function(err, salt){ | ||
45 | - if(err)return next(err) | ||
46 | - | ||
47 | - bcrypt.hash(user.password, salt, function(err, hash){ | ||
48 | - if(err) return next(err) | ||
49 | - user.password = hash | ||
50 | - //hash 값으로 변경해서 저장 | ||
51 | - next() | ||
52 | - }) | ||
53 | - }) | ||
54 | - } else{ | ||
55 | - next() // 비밀번호를 바꾸는 것이 아니라면, 넘어감 | ||
56 | - } | ||
57 | - | ||
58 | -}) | ||
59 | - | ||
60 | -userSchema.methods.comparePassword = function(plainPassword, cb){ | ||
61 | - bcrypt.compare(plainPassword, this.password, function(err, isMatch){ | ||
62 | - if(err) return cb(err), | ||
63 | - cb(null, isMatch) | ||
64 | - }) | ||
65 | -} | ||
66 | -userSchema.methods.generateToken = function(cb){ | ||
67 | - // token생성 | ||
68 | - var user = this; | ||
69 | - var token = jwt.sign(user._id, 'secretToken') | ||
70 | - user.token = token | ||
71 | - user.save(function(err, user){ | ||
72 | - if(err) return cb(err) | ||
73 | - cb(null, user) | ||
74 | - }) | ||
75 | -} | ||
76 | - | ||
77 | -userSchema.statics.findByToken = function(token, cb){ | ||
78 | - var user = this; | ||
79 | - // 토큰을 복호화한다. | ||
80 | - | ||
81 | - jwt.verify(token,'secretToken', function(err, decoded){ | ||
82 | - // 유저 아이디를 이용해서 유저를 찾고 | ||
83 | - // 클라이언트에서 가져온 토큰과 데이터베이스에 보관된 토큰이 | ||
84 | - //일치하는지 확인 | ||
85 | - user.findOne({"_id": decoded, "token" : token}, function(err, user){ | ||
86 | - if(err) return cb(err); | ||
87 | - cb(null, user) | ||
88 | - }) | ||
89 | - }) | ||
90 | -} | ||
91 | - | ||
92 | -const User = mongoose.model('Users', userSchema) | ||
93 | - | ||
94 | -module.exports = {User} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/routes/product.js
0 → 100644
1 | + | ||
2 | +const express = require('express'); | ||
3 | +const router = express.Router(); | ||
4 | +const multer = require('multer'); | ||
5 | +const { Product } = require("../models/Product"); | ||
6 | + | ||
7 | +var storage = multer.diskStorage({ | ||
8 | + destination: function (req, file, cb) { | ||
9 | + cb(null, 'uploads/') | ||
10 | + }, | ||
11 | + filename: function (req, file, cb) { | ||
12 | + cb(null, `${Date.now()}_${file.originalname}`) | ||
13 | + } | ||
14 | +}) | ||
15 | + | ||
16 | +var upload = multer({ storage: storage }).single("file") | ||
17 | + | ||
18 | +router.post('/image', (req, res) => { | ||
19 | + | ||
20 | + //가져온 이미지를 저장을 해주면 된다. | ||
21 | + upload(req, res, err => { | ||
22 | + if (err) { | ||
23 | + return req.json({ success: false, err }) | ||
24 | + } | ||
25 | + return res.json({ success: true, filePath: res.req.file.path, fileName: res.req.file.filename }) | ||
26 | + }) | ||
27 | + | ||
28 | +}) | ||
29 | + | ||
30 | +router.post('/', (req, res) => { | ||
31 | + | ||
32 | + //받아온 정보들을 DB에 넣어 준다. | ||
33 | + const product = new Product(req.body) | ||
34 | + | ||
35 | + product.save((err) => { | ||
36 | + if (err) return res.status(400).json({ success: false, err }) | ||
37 | + return res.status(200).json({ success: true }) | ||
38 | + }) | ||
39 | + | ||
40 | +}) | ||
41 | + | ||
42 | +router.post('/products', (req, res) => { | ||
43 | + // 정보 가져오기 | ||
44 | + let limit = req.body.limit ? parseInt(req.body.limit) : 20; | ||
45 | + let skip = req.body.skip ? parseInt(req.body.skip) : 0; | ||
46 | + | ||
47 | + Product.find() | ||
48 | + .populate("writer") | ||
49 | + .skip(skip) | ||
50 | + .limit(limit) | ||
51 | + .exec((err, productInfo)=> { | ||
52 | + if(err) return res.status(400).json({success:false, err}) | ||
53 | + return res.status(200).json({success:true, productInfo}) | ||
54 | + }) | ||
55 | +}) | ||
56 | + | ||
57 | +router.post('/', (req, res) => { | ||
58 | + | ||
59 | + //받아온 정보들을 DB에 넣어 준다. | ||
60 | + const product = new Product(req.body) | ||
61 | + | ||
62 | + product.save((err) => { | ||
63 | + if (err) return res.status(400).json({ success: false, err }) | ||
64 | + return res.status(200).json({ success: true }) | ||
65 | + }) | ||
66 | + | ||
67 | +}) | ||
68 | + | ||
69 | +router.get('/products_by_id', (req, res) => { | ||
70 | + | ||
71 | + let type = req.query.type | ||
72 | + let productIds = req.query.id | ||
73 | + | ||
74 | + if (type === "array") { | ||
75 | + //id=123123123,324234234,324234234 이거를 | ||
76 | + //productIds = ['123123123', '324234234', '324234234'] 이런식으로 바꿔주기 | ||
77 | + let ids = req.query.id.split(',') | ||
78 | + productIds = ids.map(item => { | ||
79 | + return item | ||
80 | + }) | ||
81 | + | ||
82 | + } | ||
83 | + | ||
84 | + //productId를 이용해서 DB에서 productId와 같은 상품의 정보를 가져온다. | ||
85 | + | ||
86 | + Product.find({ _id: { $in: productIds } }) | ||
87 | + .populate('writer') | ||
88 | + .exec((err, product) => { | ||
89 | + if (err) return res.status(400).send(err) | ||
90 | + return res.status(200).send(product) | ||
91 | + }) | ||
92 | + | ||
93 | +}) | ||
94 | + | ||
95 | + | ||
96 | + | ||
97 | +module.exports = router; |
server/routes/users.js
0 → 100644
1 | +const express = require('express'); | ||
2 | +const router = express.Router(); | ||
3 | +const { User } = require("../models/User"); | ||
4 | + | ||
5 | +const { auth } = require("../middleware/auth"); | ||
6 | + | ||
7 | +//================================= | ||
8 | +// User | ||
9 | +//================================= | ||
10 | + | ||
11 | +router.get("/auth", auth, (req, res) => { | ||
12 | + res.status(200).json({ | ||
13 | + _id: req.user._id, | ||
14 | + isAdmin: req.user.role === 0 ? false : true, | ||
15 | + isAuth: true, | ||
16 | + email: req.user.email, | ||
17 | + name: req.user.name, | ||
18 | + lastname: req.user.lastname, | ||
19 | + role: req.user.role, | ||
20 | + image: req.user.image, | ||
21 | + }); | ||
22 | +}); | ||
23 | + | ||
24 | +router.post("/register", (req, res) => { | ||
25 | + | ||
26 | + const user = new User(req.body); | ||
27 | + | ||
28 | + user.save((err, doc) => { | ||
29 | + if (err) return res.json({ success: false, err }); | ||
30 | + return res.status(200).json({ | ||
31 | + success: true | ||
32 | + }); | ||
33 | + }); | ||
34 | +}); | ||
35 | + | ||
36 | +router.post("/login", (req, res) => { | ||
37 | + User.findOne({ email: req.body.email }, (err, user) => { | ||
38 | + if (!user) | ||
39 | + return res.json({ | ||
40 | + loginSuccess: false, | ||
41 | + message: "Auth failed, email not found" | ||
42 | + }); | ||
43 | + | ||
44 | + user.comparePassword(req.body.password, (err, isMatch) => { | ||
45 | + if (!isMatch) | ||
46 | + return res.json({ loginSuccess: false, message: "Wrong password" }); | ||
47 | + | ||
48 | + user.generateToken((err, user) => { | ||
49 | + if (err) return res.status(400).send(err); | ||
50 | + res.cookie("w_authExp", user.tokenExp); | ||
51 | + res | ||
52 | + .cookie("w_auth", user.token) | ||
53 | + .status(200) | ||
54 | + .json({ | ||
55 | + loginSuccess: true, userId: user._id | ||
56 | + }); | ||
57 | + }); | ||
58 | + }); | ||
59 | + }); | ||
60 | +}); | ||
61 | + | ||
62 | +router.get("/logout", auth, (req, res) => { | ||
63 | + User.findOneAndUpdate({ _id: req.user._id }, { token: "", tokenExp: "" }, (err, doc) => { | ||
64 | + if (err) return res.json({ success: false, err }); | ||
65 | + return res.status(200).send({ | ||
66 | + success: true | ||
67 | + }); | ||
68 | + }); | ||
69 | +}); | ||
70 | + | ||
71 | +module.exports = router; |
uploads/1593030025779_cat-5331883_1280.jpg
0 → 100644

153 KB
uploads/1593030030261_deer-5324645_1280.jpg
0 → 100644

190 KB

705 KB

231 KB

231 KB
-
Please register or login to post a comment