정승호

사진 업로드 작업 완료

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/
......
...@@ -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": ".",
......
1 -# https://www.robotstxt.org/robotstxt.html
2 -User-agent: *
3 -Disallow:
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 -}
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 -
1 -export const LOGIN_USER = "login_user";
...\ No newline at end of file ...\ No newline at end of file
1 +export const LOGIN_USER = 'login_user';
2 +export const REGISTER_USER = 'register_user';
3 +export const AUTH_USER = 'auth_user';
4 +export const LOGOUT_USER = 'logout_user';
......
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 }
......
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;
1 +//서버 라우트
2 +export const USER_SERVER = '/api/users';
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
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 +}
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
......
1 -// jest-dom adds custom jest matchers for asserting on DOM nodes.
2 -// allows you to do things like:
3 -// expect(element).toHaveTextContent(/react/i)
4 -// learn more: https://github.com/testing-library/jest-dom
5 -import '@testing-library/jest-dom/extend-expect';
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 module.exports = { 1 module.exports = {
2 - mongoURI : process.env.MONGO_URI 2 + mongoURI:process.env.MONGO_URI
3 } 3 }
...\ 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 };
......
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
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
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
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;
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;