박민정

Merge branch 'develop' into 'main'

Develop



See merge request !5
Showing 90 changed files with 3502 additions and 0 deletions
1 +node_modules
2 +dev.js
...\ No newline at end of file ...\ No newline at end of file
1 +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 +
3 +# dependencies
4 +/node_modules
5 +/.pnp
6 +.pnp.js
7 +
8 +# testing
9 +/coverage
10 +
11 +# production
12 +/build
13 +
14 +# misc
15 +.DS_Store
16 +.env.local
17 +.env.development.local
18 +.env.test.local
19 +.env.production.local
20 +
21 +npm-debug.log*
22 +yarn-debug.log*
23 +yarn-error.log*
1 +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 +
3 +## Available Scripts
4 +
5 +In the project directory, you can run:
6 +
7 +### `npm start`
8 +
9 +Runs the app in the development mode.<br>
10 +Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 +
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
This diff could not be displayed because it is too large.
1 +{
2 + "name": "client",
3 + "version": "0.1.0",
4 + "private": true,
5 + "dependencies": {
6 + "antd": "^3.24.1",
7 + "axios": "^0.18.0",
8 + "bootstrap": "^4.6.0",
9 + "formik": "^1.5.8",
10 + "moment": "^2.24.0",
11 + "react": "^16.8.6",
12 + "react-bootstrap": "^1.6.1",
13 + "react-dom": "^16.8.6",
14 + "react-dropzone": "^10.2.1",
15 + "react-icons": "^3.7.0",
16 + "react-image-gallery": "^1.0.3",
17 + "react-paypal-express-checkout": "^1.0.5",
18 + "react-redux": "^7.1.0-rc.1",
19 + "react-router-dom": "^5.0.1",
20 + "react-scripts": "3.0.1",
21 + "redux": "^4.0.0",
22 + "redux-form": "^8.2.6",
23 + "redux-promise": "^0.6.0",
24 + "redux-thunk": "^2.3.0",
25 + "socket.io-client": "^2.2.0",
26 + "yup": "^0.27.0"
27 + },
28 + "scripts": {
29 + "start": "react-scripts start",
30 + "build": "react-scripts build",
31 + "test": "react-scripts test",
32 + "eject": "react-scripts eject"
33 + },
34 + "eslintConfig": {
35 + "extends": "react-app"
36 + },
37 + "browserslist": {
38 + "production": [
39 + ">0.2%",
40 + "not dead",
41 + "not op_mini all"
42 + ],
43 + "development": [
44 + "last 1 chrome version",
45 + "last 1 firefox version",
46 + "last 1 safari version"
47 + ]
48 + },
49 + "devDependencies": {
50 + "http-proxy-middleware": "^1.0.3"
51 + }
52 +}
No preview for this file type
1 +<!DOCTYPE html>
2 +<html lang="en">
3 + <head>
4 + <meta charset="utf-8" />
5 + <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
6 + <meta name="viewport" content="width=device-width, initial-scale=1" />
7 + <meta name="theme-color" content="#000000" />
8 + <!--
9 + manifest.json provides metadata used when your web app is installed on a
10 + user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
11 + -->
12 + <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
13 + <!--
14 + Notice the use of %PUBLIC_URL% in the tags above.
15 + It will be replaced with the URL of the `public` folder during the build.
16 + Only files inside the `public` folder can be referenced from the HTML.
17 +
18 + Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19 + work correctly both with client-side routing and a non-root public URL.
20 + Learn how to configure a non-root public URL by running `npm run build`.
21 + -->
22 + <link
23 + rel="stylesheet"
24 + href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
25 + integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
26 + crossorigin="anonymous"
27 + />
28 + <title>약 배달 서비스 : 약사</title>
29 + </head>
30 + <body>
31 + <noscript>You need to enable JavaScript to run this app.</noscript>
32 + <div id="root"></div>
33 + <!--
34 + This HTML file is a template.
35 + If you open it directly in the browser, you will see an empty page.
36 +
37 + You can add webfonts, meta tags, or analytics to this file.
38 + The build step will place the bundled scripts into the <body> tag.
39 +
40 + To begin the development, run `npm start` or `yarn start`.
41 + To create a production bundle, use `npm run build` or `yarn build`.
42 + -->
43 + </body>
44 +</html>
1 +{
2 + "short_name": "React App",
3 + "name": "Create React App Sample",
4 + "icons": [
5 + {
6 + "src": "favicon.ico",
7 + "sizes": "64x64 32x32 24x24 16x16",
8 + "type": "image/x-icon"
9 + }
10 + ],
11 + "start_url": ".",
12 + "display": "standalone",
13 + "theme_color": "#000000",
14 + "background_color": "#ffffff"
15 +}
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 +import UploadPage from './components/views/UploadPage/UploadPage'
13 +import auth from './hoc/authentication'
14 +import Navigation from './components/views/NavBar/NavBar'
15 +
16 +function App() {
17 + return (
18 + <Router>
19 + <Navigation />
20 + <div>
21 +
22 + <Switch>
23 + {/* null, false에 대한 옵션 설명은 auth로 가서 확인*/}
24 +
25 + <Route exact path="/" component={auth(LandingPage, null)} />
26 +
27 + <Route exact path="/login" component={auth(LoginPage, false)}/>
28 +
29 + <Route exact path="/register" component={auth(RegisterPage, false)}/>
30 +
31 + <Route exact path="/product/upload" component={auth(UploadPage, true)}/>
32 + {/*
33 + <Route exact path="/" component={LandingPage} />
34 +
35 + <Route exact path="/login" component={LoginPage}/>
36 +
37 + <Route exact path="/register" component={RegisterPage}/>
38 + */}
39 + </Switch>
40 + </div>
41 + </Router>
42 + );
43 +}
44 +
45 +export default App;
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';
5 +export const ADD_TO_CART_USER = 'add_to_cart_user';
6 +export const GET_CART_ITEMS_USER = 'get_cart_items_user';
7 +export const REMOVE_CART_ITEM_USER = 'remove_cart_item_user';
8 +export const ON_SUCCESS_BUY_USER = 'on_success_buy_user';
...\ No newline at end of file ...\ No newline at end of file
1 +import axios from 'axios';
2 +import {
3 + LOGIN_USER,
4 + REGISTER_USER,
5 + AUTH_USER,
6 + LOGOUT_USER,
7 + ADD_TO_CART_USER,
8 + GET_CART_ITEMS_USER,
9 + REMOVE_CART_ITEM_USER,
10 + ON_SUCCESS_BUY_USER
11 +} from './types';
12 +import { USER_SERVER } from '../components/Config.js';
13 +
14 +export function registerUser(dataToSubmit) {
15 + const request = axios.post(`${USER_SERVER}/register`, dataToSubmit)
16 + .then(response => response.data);
17 +
18 + return {
19 + type: REGISTER_USER,
20 + payload: request
21 + }
22 +}
23 +
24 +export function loginUser(dataToSubmit) {
25 + const request = axios.post(`${USER_SERVER}/login`, dataToSubmit)
26 + .then(response => response.data);
27 +
28 + return {
29 + type: LOGIN_USER,
30 + payload: request
31 + }
32 +}
33 +
34 +export function auth() {
35 + const request = axios.get(`${USER_SERVER}/auth`)
36 + .then(response => response.data);
37 +
38 + return {
39 + type: AUTH_USER,
40 + payload: request
41 + }
42 +}
43 +
44 +export function logoutUser() {
45 + const request = axios.get(`${USER_SERVER}/logout`)
46 + .then(response => response.data);
47 +
48 + return {
49 + type: LOGOUT_USER,
50 + payload: request
51 + }
52 +}
53 +
54 +
55 +export function addToCart(_id) {
56 + const request = axios.get(`${USER_SERVER}/addToCart?productId=${_id}`)
57 + .then(response => response.data);
58 +
59 + return {
60 + type: ADD_TO_CART_USER,
61 + payload: request
62 + }
63 +}
64 +
65 +
66 +
67 +export function getCartItems(cartItems, userCart) {
68 + const request = axios.get(`/api/product/products_by_id?id=${cartItems}&type=array`)
69 + .then(response => {
70 +
71 +
72 + //Make CartDetail inside Redux Store
73 + // We need to add quantity data to Product Information that come from Product Collection.
74 +
75 + userCart.forEach(cartItem => {
76 + response.data.forEach((productDetail, i) => {
77 + if (cartItem.id === productDetail._id) {
78 + response.data[i].quantity = cartItem.quantity;
79 + }
80 + })
81 + })
82 +
83 + return response.data;
84 + });
85 +
86 + return {
87 + type: GET_CART_ITEMS_USER,
88 + payload: request
89 + }
90 +}
91 +
92 +
93 +
94 +
95 +export function removeCartItem(id) {
96 + const request = axios.get(`/api/users/removeFromCart?_id=${id}`)
97 + .then(response => {
98 +
99 + response.data.cart.forEach(item => {
100 + response.data.cartDetail.forEach((k, i) => {
101 + if (item.id === k._id) {
102 + response.data.cartDetail[i].quantity = item.quantity
103 + }
104 + })
105 + })
106 + return response.data;
107 + });
108 +
109 + return {
110 + type: REMOVE_CART_ITEM_USER,
111 + payload: request
112 + }
113 +}
114 +
115 +
116 +export function onSuccessBuy(data) {
117 +
118 + const request = axios.post(`${USER_SERVER}/successBuy`, data)
119 + .then(response => response.data);
120 +
121 + return {
122 + type: ON_SUCCESS_BUY_USER,
123 + payload: request
124 + }
125 +}
126 +
127 +
128 +
129 +
130 +
1 +import { combineReducers } from "redux";
2 +import user from "./user_reducer";
3 +
4 +const rootReducer = combineReducers({
5 + user,
6 +});
7 +
8 +export default rootReducer;
1 +import {
2 + LOGIN_USER,
3 + REGISTER_USER,
4 + AUTH_USER,
5 + LOGOUT_USER,
6 + ADD_TO_CART_USER,
7 + GET_CART_ITEMS_USER,
8 + REMOVE_CART_ITEM_USER,
9 + ON_SUCCESS_BUY_USER
10 +} from '../_actions/types';
11 +
12 +
13 +export default function (state = {}, action) {
14 + switch (action.type) {
15 + case REGISTER_USER:
16 + return { ...state, register: action.payload }
17 + case LOGIN_USER:
18 + return { ...state, loginSucces: action.payload }
19 + case AUTH_USER:
20 + return { ...state, userData: action.payload }
21 + case LOGOUT_USER:
22 + return { ...state }
23 + case ADD_TO_CART_USER:
24 + return {
25 + ...state, userData: {
26 + ...state.userData,
27 + cart: action.payload
28 + }
29 + }
30 + case GET_CART_ITEMS_USER:
31 + return {
32 + ...state, cartDetail: action.payload
33 + }
34 + case REMOVE_CART_ITEM_USER:
35 + return {
36 + ...state,
37 + cartDetail: action.payload.cartDetail,
38 + userData: {
39 + ...state.userData,
40 + cart: action.payload.cart
41 + }
42 +
43 + }
44 + case ON_SUCCESS_BUY_USER:
45 + return {
46 + ...state,
47 + userData: {
48 + ...state.userData,
49 + cart: action.payload.cart
50 + },
51 + cartDetail: action.payload.cartDetail
52 + }
53 +
54 + default:
55 + return state;
56 + }
57 +}
...\ No newline at end of file ...\ No newline at end of file
1 +import React, { Suspense } from "react";
2 +import { Route, Switch } from "react-router-dom";
3 +import Auth from "../hoc/auth";
4 +// pages for this product
5 +import LandingPage from "./views/LandingPage/LandingPage.js";
6 +import LoginPage from "./views/LoginPage/LoginPage.js";
7 +import RegisterPage from "./views/RegisterPage/RegisterPage.js";
8 +import NavBar from "./views/NavBar/NavBar";
9 +import Footer from "./views/Footer/Footer";
10 +import UploadProductPage from "./views/UploadProductPage/UploadProductPage";
11 +import DetailProductPage from "./views/DetailProductPage/DetailProductPage";
12 +import CartPage from "./views/CartPage/CartPage";
13 +import HistoryPage from "./views/HistoryPage/HistoryPage";
14 +import adminPage from "./views/adminPage/adminPage";
15 +
16 +function App() {
17 + return (
18 + <Suspense fallback={<div>Loading...</div>}>
19 + <NavBar />
20 + <div style={{ paddingTop: "75px", minHeight: "calc(100vh - 80px)" }}>
21 + <Switch>
22 + <Route exact path="/" component={Auth(LandingPage, null)} />
23 + <Route exact path="/login" component={Auth(LoginPage, false)} />
24 + <Route exact path="/register" component={Auth(RegisterPage, false)} />
25 + <Route exact path="/product/upload" component={Auth(UploadProductPage, true)} />
26 + <Route exact path="/product/:productId" component={Auth(DetailProductPage, null)} />
27 + <Route exact path="/user/cart" component={Auth(CartPage, true)} />
28 + <Route exact path="/history" component={Auth(HistoryPage, true)} />
29 + <Route exact path="/admin" component={Auth(adminPage, true)} />
30 + </Switch>
31 + </div>
32 + <Footer />
33 + </Suspense>
34 + );
35 +}
36 +
37 +export default App;
1 +//SERVER ROUTES
2 +export const USER_SERVER = '/api/users';
1 +#test {
2 + width: 300px;
3 + height: 200px;
4 + border: 1px solid lightgray;
5 + border-radius: 1em;
6 + display: flex;
7 + position: relative;
8 +}
9 +#alert {
10 + width: 300px;
11 + height: 200px;
12 + border: 1px solid lightgray;
13 + border-radius: 1em;
14 + display: flex;
15 + justify-content: center;
16 + align-items: center;
17 +}
18 +
19 +#test:hover {
20 + animation: del 0.2s forwards;
21 +}
22 +
23 +@keyframes del {
24 + to {
25 + filter: brightness(50%);
26 + }
27 +}
28 +
29 +#alert {
30 + position: relative;
31 + bottom: 200px;
32 + opacity: 0;
33 +}
34 +
35 +#alert:hover {
36 + background-color: black;
37 + color: white;
38 + animation: fadeInUP 1s forwards;
39 +}
40 +
41 +@keyframes fadeInUP {
42 + to {
43 + opacity: 0.5;
44 + }
45 +}
1 +import React, { useState } from "react";
2 +import Dropzone from "react-dropzone";
3 +import { Icon } from "antd";
4 +import Axios from "axios";
5 +import "./FileUpload.css";
6 +
7 +function FileUpload(props) {
8 + const [Images, setImages] = useState([]);
9 +
10 + const onDrop = files => {
11 + let formData = new FormData();
12 + const config = {
13 + header: { "content-type": "multipart/form-data" },
14 + };
15 + formData.append("file", files[0]);
16 + Axios.post("/api/product/uploadImage", formData, config).then(response => {
17 + if (response.data.success) {
18 + setImages([...Images, response.data.image]);
19 + props.refreshFunction([...Images, response.data.image]);
20 + } else {
21 + alert("Failed to save the Image in Server");
22 + }
23 + });
24 + };
25 +
26 + const onDelete = image => {
27 + const currentIndex = Images.indexOf(image);
28 +
29 + let newImages = [...Images];
30 + newImages.splice(currentIndex, 1);
31 +
32 + setImages(newImages);
33 + props.refreshFunction(newImages);
34 + };
35 +
36 + return (
37 + <div style={{ display: "flex", justifyContent: "space-between" }}>
38 + <Dropzone onDrop={onDrop} multiple={false} maxSize={800000000}>
39 + {({ getRootProps, getInputProps }) => (
40 + <div
41 + style={{
42 + width: "300px",
43 + height: "240px",
44 + border: "1px solid lightgray",
45 + borderRadius: '15px',
46 + display: "flex",
47 + alignItems: "center",
48 + justifyContent: "center",
49 + }}
50 + {...getRootProps()}
51 + >
52 + {console.log("getRootProps", { ...getRootProps() })}
53 + {console.log("getInputProps", { ...getInputProps() })}
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 + {Images.map((image, index) => (
62 + <div onClick={() => onDelete(image)}>
63 + <img id="test" src={`http://localhost:5000/${image}`} />
64 + <div id="alert">클릭하면 삭제돼요!</div>
65 + </div>
66 + ))}
67 + </div>
68 + </div>
69 + );
70 +}
71 +
72 +export default FileUpload;
1 +import React from "react";
2 +import { 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%", maxWidth: "300px", height: "300px" }} src={`http://localhost:5000/${image}`} alt="productImage" />
11 + </div>
12 + ))}
13 + </Carousel>
14 + </div>
15 + );
16 +}
17 +
18 +export default ImageSlider;
1 +#test {
2 + width: 300px;
3 + height: 200px;
4 + border: 1px solid rgb(155, 155, 155);
5 + border-radius: 1em;
6 + display: flex;
7 + position: relative;
8 +}
9 +#alert {
10 + width: 300px;
11 + height: 200px;
12 + border: 1px solid rgb(155, 155, 155);
13 + border-radius: 1em;
14 + display: flex;
15 + justify-content: center;
16 + align-items: center;
17 +}
18 +
19 +#test:hover {
20 + animation: del 0.7s forwards;
21 +}
22 +
23 +@keyframes del {
24 + to {
25 + filter: brightness(50%);
26 + }
27 +}
28 +
29 +#alert {
30 + position: relative;
31 + bottom: 200px;
32 + opacity: 0;
33 +}
34 +
35 +#alert:hover {
36 + background-color: black;
37 + color: white;
38 + animation: fadeInUP 0.2s forwards;
39 +}
40 +
41 +@keyframes fadeInUP {
42 + to {
43 + opacity: 0.5;
44 + }
45 +}
1 +import React from 'react'
2 +import Dropzone from 'react-dropzone'
3 +import axios from 'axios';
4 +import { useState } from 'react';
5 +import './ImageUpload.css'
6 +
7 +function ImageUpload(props) {
8 +
9 + const [Images, setImages] = useState([]) // 이미지를 여러장 들어가게 하기 위해서
10 +
11 + // 이미지 서버에 저장
12 + const imageDropEvent = (files) => {
13 + let imageData = new FormData();
14 +
15 + const config = {
16 + header: {'content-type': 'multipart/image-data'}
17 + }
18 + imageData.append("file", files[0])
19 +
20 + // 이미지 전달
21 + axios.post('/api/product/image', imageData, config)
22 + .then(response => {
23 + if (response.data.success) {
24 + //console.log(response.data)
25 + setImages([...Images, response.data.filePath])
26 + props.refreshFunction([...Images, response.data.filePath])
27 + // 이 props (refreshFunction)은 UploadPage에 정의되어 있음
28 + }
29 + else {
30 + alert('파일 저장을 실패했습니다.')
31 + }
32 + })
33 +
34 + }
35 +
36 + // 이미지 삭제 위한 deleteEvent
37 + const deleteEvent = (image) => {
38 + const currentIndex = Images.indexOf(image);
39 +
40 + //console.log(currentIndex);
41 +
42 + let updateImages = [...Images];
43 + updateImages.splice(currentIndex, 1); // currentIndex부터 1개의 사진을 지움
44 +
45 + setImages(updateImages);
46 + props.refreshFunction(updateImages);
47 + }
48 +
49 +
50 + return (
51 + <div style={ {display:'flex', justifyContent:'space-between'}}>
52 + <Dropzone onDrop={imageDropEvent}>
53 + {({getRootProps, getInputProps}) => (
54 + <section>
55 + {/* Dropzone */}
56 + <div style={{
57 + width: 300, height: 200, border: '1px solid lightgray', borderRadius: '1em', display: 'flex',
58 + alignItems: 'center', textAlign: 'center', justifyContent: 'center'
59 + }}
60 + {...getRootProps()}>
61 + <input {...getInputProps()} />
62 + <p>이곳을 클릭하여<br/>상품 사진을 업로드 해주세요.</p>
63 + </div>
64 + </section>
65 + )}
66 + </Dropzone>
67 +
68 +
69 + {/* 파일 업로드하면 옆에 나오게 하도록 */}
70 + <div style={{
71 + width: '350px', height: '200px', borderRadius: '1em'
72 + , overflowX: 'scroll'
73 + }}>
74 + {Images.map((image, index) => (
75 +
76 + <div onClick={ () => deleteEvent(image) }
77 + key={index}>
78 + <img id="test" src={`http://localhost:5000/${image}`} />
79 + <div id="alert">클릭하면 삭제돼요!</div>
80 +
81 + </div>
82 + ))}
83 + </div>
84 +
85 + </div>
86 + )
87 +}
88 +
89 +export default ImageUpload
1 +import React from 'react';
2 +import PaypalExpressBtn from 'react-paypal-express-checkout';
3 +
4 +export default class Paypal extends React.Component {
5 + render() {
6 + const onSuccess = (payment) => {
7 + // Congratulation, it came here means everything's fine!
8 + console.log("The payment was succeeded!", payment);
9 + // You can bind the "payment" object's value to your state or props or whatever here, please see below for sample returned data
10 + this.props.onSuccess(payment);
11 + }
12 +
13 + const onCancel = (data) => {
14 + // User pressed "cancel" or close Paypal's popup!
15 + console.log('The payment was cancelled!', data);
16 + // You can bind the "data" object's value to your state or props or whatever here, please see below for sample returned data
17 + }
18 +
19 + const onError = (err) => {
20 + // The main Paypal's script cannot be loaded or somethings block the loading of that script!
21 + console.log("Error!", err);
22 + // Because the Paypal's main script is loaded asynchronously from "https://www.paypalobjects.com/api/checkout.js"
23 + // => sometimes it may take about 0.5 second for everything to get set, or for the button to appear
24 + }
25 +
26 + let env = 'sandbox'; // you can set here to 'production' for production
27 + let currency = 'USD'; // or you can set this value from your props or state
28 + let total = this.props.toPay; // same as above, this is the total amount (based on currency) to be paid by using Paypal express checkout
29 + // Document on Paypal's currency code: https://developer.paypal.com/docs/classic/api/currency_codes/
30 +
31 + const client = {
32 + sandbox: 'ASbCsyjZeUzpNCkVbbqseQzcXivFRRoyPfpJK24688vFvIchTR-CCK79Ao5FB6zgqIO2r5Xw-a4Xh-44',
33 + production: 'YOUR-PRODUCTION-APP-ID',
34 + }
35 + // In order to get production's app-ID, you will have to send your app to Paypal for approval first
36 + // For sandbox app-ID (after logging into your developer account, please locate the "REST API apps" section, click "Create App"):
37 + // => https://developer.paypal.com/docs/classic/lifecycle/sb_credentials/
38 + // For production app-ID:
39 + // => https://developer.paypal.com/docs/classic/lifecycle/goingLive/
40 +
41 + // NB. You can also have many Paypal express checkout buttons on page, just pass in the correct amount and they will work!
42 + return (
43 + <PaypalExpressBtn
44 + env={env}
45 + client={client}
46 + currency={currency}
47 + total={total}
48 + onError={onError}
49 + onSuccess={onSuccess}
50 + onCancel={onCancel}
51 + style={{
52 + size:'large',
53 + color:'blue',
54 + shape: 'rect',
55 + label: 'checkout'
56 + }}
57 + />
58 + );
59 + }
60 +}
...\ No newline at end of file ...\ No newline at end of file
1 +import React, { useEffect, useState } from "react";
2 +import { useDispatch } from "react-redux";
3 +import { getCartItems, removeCartItem, onSuccessBuy } from "../../../_actions/user_actions";
4 +import UserCardBlock from "./Sections/UserCardBlock";
5 +import { Result, Empty } from "antd";
6 +import Axios from "axios";
7 +import Paypal from "../../utils/Paypal";
8 +import { Container } from "react-bootstrap";
9 +
10 +function CartPage(props) {
11 + const dispatch = useDispatch();
12 + const [Total, setTotal] = useState(0);
13 + const [ShowTotal, setShowTotal] = useState(false);
14 + const [ShowSuccess, setShowSuccess] = useState(false);
15 +
16 + useEffect(() => {
17 + let cartItems = [];
18 + if (props.user.userData && props.user.userData.cart) {
19 + if (props.user.userData.cart.length > 0) {
20 + props.user.userData.cart.forEach(item => {
21 + cartItems.push(item.id);
22 + });
23 + dispatch(getCartItems(cartItems, props.user.userData.cart)).then(response => {
24 + if (response.payload.length > 0) {
25 + calculateTotal(response.payload);
26 + }
27 + });
28 + }
29 + }
30 + }, [props.user.userData]);
31 +
32 + const calculateTotal = cartDetail => {
33 + let total = 0;
34 +
35 + cartDetail.map(item => {
36 + total += parseInt(item.price, 10) * item.quantity;
37 + });
38 +
39 + setTotal(total);
40 + setShowTotal(true);
41 + };
42 +
43 + const removeFromCart = productId => {
44 + dispatch(removeCartItem(productId)).then(response => {
45 + if (response.payload.cartDetail.length <= 0) {
46 + setShowTotal(false);
47 + } else {
48 + calculateTotal(response.payload.cartDetail);
49 + }
50 + });
51 + };
52 +
53 + const transactionSuccess = data => {
54 + dispatch(
55 + onSuccessBuy({
56 + cartDetail: props.user.cartDetail,
57 + paymentData: data,
58 + })
59 + ).then(response => {
60 + if (response.payload.success) {
61 + setShowSuccess(true);
62 + setShowTotal(false);
63 + }
64 + });
65 + };
66 +
67 + const transactionError = () => {
68 + console.log("Paypal error");
69 + };
70 +
71 + const transactionCanceled = () => {
72 + console.log("Transaction canceled");
73 + };
74 +
75 + return (
76 + <div style={{ width: "85%", margin: "3rem auto" }}>
77 + <h1>장바구니</h1>
78 + <div style={{ marginTop: "30px" }}>
79 + <UserCardBlock products={props.user.cartDetail} removeItem={removeFromCart} />
80 + {ShowTotal ? (
81 + <div style={{ marginTop: "3rem", textAlign: "right" }}>
82 + <h2> 금액: ${Total} </h2>
83 + </div>
84 + ) : ShowSuccess ? (
85 + <Result status="success" title="Successfully Purchased Items" />
86 + ) : (
87 + <div
88 + style={{
89 + width: "100%",
90 + display: "flex",
91 + flexDirection: "column",
92 + justifyContent: "center",
93 + }}
94 + >
95 + <br />
96 + <Empty description={false} />
97 + <p>장바구니가 비었습니다.</p>
98 + </div>
99 + )}
100 + </div>
101 +
102 + {ShowTotal && (
103 + <Container style={{ width: "max-content", marginTop: "100px" }}>
104 + <Paypal sty toPay={Total} onSuccess={transactionSuccess} transactionError={transactionError} transactionCanceled={transactionCanceled} />
105 + </Container>
106 + )}
107 + </div>
108 + );
109 +}
110 +
111 +export default CartPage;
1 +import React from "react";
2 +
3 +function UserCardBlock(props) {
4 + const renderCartImage = images => {
5 + if (images.length > 0) {
6 + let image = images[0];
7 + return `http://localhost:5000/${image}`;
8 + }
9 + };
10 +
11 + const renderItems = () =>
12 + props.products &&
13 + props.products.map(product => (
14 + <tr key={product._id} style={{ margin: "auto", backgroundColor: "white" }}>
15 + <td style={{ margin: "auto", textAlign: "center" }}>
16 + <img style={{ width: "70px" }} alt="product" src={renderCartImage(product.images)} />
17 + </td>
18 + <td style={{ margin: "auto", textAlign: "center" }}>{product.title}</td>
19 + <td style={{ margin: "auto", textAlign: "center" }}>{product.quantity} EA</td>
20 + <td style={{ margin: "auto", textAlign: "center" }}>$ {product.price} </td>
21 + <td style={{ margin: "auto", textAlign: "center" }}>
22 + <button onClick={() => props.removeItem(product._id)}></button>
23 + </td>
24 + </tr>
25 + ));
26 +
27 + return (
28 + <div>
29 + <table>
30 + <thead>
31 + <tr style={{ textAlign: "center" }}>
32 + <th>상품 이미지</th>
33 + <th>상품명</th>
34 + <th>개수</th>
35 + <th>가격</th>
36 + <th>장바구니에서 삭제하기</th>
37 + </tr>
38 + </thead>
39 + <tbody>{renderItems()}</tbody>
40 + </table>
41 + </div>
42 + );
43 +}
44 +
45 +export default UserCardBlock;
1 +import React, { useEffect, useState } from 'react'
2 +import Axios from 'axios'
3 +import { Row, Col } from 'antd';
4 +import ProductImage from './Sections/ProductImage';
5 +import ProductInfo from './Sections/ProductInfo';
6 +import { addToCart } from '../../../_actions/user_actions';
7 +import { useDispatch } from 'react-redux';
8 +function DetailProductPage(props) {
9 + const dispatch = useDispatch();
10 + const productId = props.match.params.productId
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 +
19 + }, [])
20 +
21 + const addToCartHandler = (productId) => {
22 + dispatch(addToCart(productId))
23 +
24 + }
25 +
26 + return (
27 + <div className="postPage" style={{ width: '100%', padding: '3rem 4rem' }}>
28 +
29 + <div style={{ display: 'flex', justifyContent: 'center' }}>
30 + <h1>{Product.title}</h1>
31 + </div>
32 +
33 + <br />
34 +
35 + <Row gutter={[16, 16]} >
36 + <Col lg={12} xs={24}>
37 + <ProductImage detail={Product} style={{width :"300px"}} />
38 + </Col>
39 + <Col lg={12} xs={24}>
40 + <ProductInfo
41 + addToCart={addToCartHandler}
42 + detail={Product} />
43 + </Col>
44 + </Row>
45 + </div>
46 + )
47 +}
48 +
49 +export default DetailProductPage
1 +import React, { useEffect, useState } from 'react'
2 +import ImageGallery from 'react-image-gallery';
3 +
4 +function ProductImage(props) {
5 + const [Images, setImages] = useState([])
6 +
7 + useEffect(() => {
8 + if (props.detail.images && props.detail.images.length > 0) {
9 + let images = [];
10 +
11 + props.detail.images && props.detail.images.map(item => {
12 + images.push({
13 + original: `http://localhost:5000/${item}`,
14 + thumbnail: `http://localhost:5000/${item}`
15 + })
16 + })
17 + setImages(images)
18 + }
19 + }, [props.detail])
20 +
21 + return (
22 + <div>
23 + <ImageGallery showFullscreenButton={false} showPlayButton={false} items={Images} />
24 + </div>
25 + )
26 +}
27 +
28 +export default ProductImage
1 +import React, { useEffect, useState } from "react";
2 +import { Button, Descriptions } from "antd";
3 +import { Container } from "react-bootstrap";
4 +
5 +function ProductInfo(props) {
6 + const [Product, setProduct] = useState({});
7 +
8 + useEffect(() => {
9 + setProduct(props.detail);
10 + }, [props.detail]);
11 +
12 + const addToCarthandler = () => {
13 + props.addToCart(props.detail._id);
14 + };
15 +
16 + return (
17 +
18 + <Container style={{ paddingTop: "100px" }}>
19 + <Container style={{ textAlign: "center" }}>
20 + {/* <h7>{Product.description}</h7><br/> */}
21 + <h3>가격 : {Product.price}000 </h3>
22 + </Container>
23 +
24 + <br />
25 + <br />
26 + <div style={{ display: "flex", justifyContent: "center" }}>
27 + <Button size="large" shape="round" type="danger" onClick={addToCarthandler}>
28 + <a href="/user/cart">
29 + 장바구니에 담기
30 + </a>
31 + </Button>
32 + </div>
33 + </Container>
34 + );
35 +}
36 +
37 +export default ProductInfo;
1 +import React from 'react'
2 +import axios from 'axios'
3 +import {useEffect, useState} from 'react'
4 +import ProductImage from './Sections/ProductImage'
5 +import ProductInfo from './Sections/ProductInfo'
6 +import { Row, Col } from 'antd';
7 +function DetailProductPages(props) {
8 + const [Product, setProduct] = useState({})
9 + const productId = props.match.params.prouductID
10 + //console.log(props.match.params.prouductID) //->정상적으로 출력
11 +
12 + useEffect(() => {
13 + axios.get(`/api/product/products_by_id?id=${productId}&type=single`)
14 + .then(response => {
15 + if (response.data.success) {
16 + console.log(response.data)
17 + setProduct(response.data.goods[0]);
18 + }
19 + else {
20 + alert('Fail.');
21 + }
22 + })
23 + },[])
24 +
25 + return (
26 + <div style={{width:'100%', padding:'3rem 4rem'}}>
27 + <div style={{ display: 'flex', justifyContent: 'center' }}>
28 + <h1>{Product.title}</h1>
29 + </div>
30 +
31 + <br />
32 + <Row gutter={[16, 16]}>
33 + <Col lg={12} sm={24}>
34 + <ProductImage detail={Product} />
35 + </Col>
36 + <Col lg={12} sm={24}>
37 + <ProductImage />
38 + </Col>
39 + </Row>
40 + {/* image */}
41 +
42 +
43 + {/* info */}
44 + <ProductInfo />
45 + </div>
46 + )
47 +}
48 +
49 +export default DetailProductPages
1 +import React from 'react'
2 +import ImageGallery from 'react-image-gallery'
3 +import { useEffect, useState } from 'react'
4 +
5 +function ProductImage(props) {
6 +
7 + const [Images, setImages] = useState([])
8 +
9 +
10 +
11 + useEffect(() => {
12 + console.log('now ::::::: ', props.goods)
13 + let images = []
14 + if (props.goods.images && props.goods.images.length > 0) {
15 +
16 + props.goods.images.map(item => {
17 + images.push({
18 + original: `http://localhost:5000/${item}`,
19 + thumbnail: `http://localhost:5000/${item}`
20 + })
21 + })
22 + setImages(images)
23 + }
24 + }, [props.goods])
25 + return (
26 + <div>
27 + <ImageGallery items={Images} />
28 + </div>
29 + )
30 + // return (
31 + // <div>
32 + // ..
33 + // </div>
34 + // )
35 +
36 +
37 +}
38 +
39 +export default ProductImage
1 +import React from 'react'
2 +
3 +function ProductInfo() {
4 + return (
5 + <div>
6 + info
7 + </div>
8 + )
9 +}
10 +
11 +export default ProductInfo
1 +import React from "react";
2 +import { Icon } from "antd";
3 +
4 +function Footer() {
5 + return (
6 + <div
7 + style={{
8 + height: "80px",
9 + display: "flex",
10 + flexDirection: "column",
11 + alignItems: "center",
12 + justifyContent: "center",
13 + fontSize: "1rem",
14 + }}
15 + >
16 + <p> Copyright © 2021 Minjeong Park All rights reserved</p>
17 + </div>
18 + );
19 +}
20 +
21 +export default Footer;
1 +import React from "react";
2 +
3 +function HistoryPage(props) {
4 + return (
5 + <div style={{ width: "80%", margin: "3rem auto" }}>
6 + <div style={{ textAlign: "center" }}>
7 + <h1>구매내역</h1>
8 + </div>
9 + <br />
10 +
11 + <table>
12 + <thead>
13 + <tr>
14 + <th style={{ textAlign: "center" }}>구매 ID</th>
15 + <th style={{ textAlign: "center" }}>가격</th>
16 + <th style={{ textAlign: "center" }}>수량</th>
17 + <th style={{ textAlign: "center" }}>구매 날짜</th>
18 + </tr>
19 + </thead>
20 +
21 + <tbody>
22 + {props.user.userData &&
23 + props.user.userData.history &&
24 + props.user.userData.history.map(item => (
25 + <tr key={item.id}>
26 + <td style={{ textAlign: "center", margin: "auto" }}>{item.id}</td>
27 + <td style={{ textAlign: "center", margin: "auto" }}>{item.price}</td>
28 + <td style={{ textAlign: "center", margin: "auto" }}>{item.quantity}</td>
29 + <td style={{ textAlign: "center", margin: "auto" }}>{item.dateOfPurchase}</td>
30 + </tr>
31 + ))}
32 + </tbody>
33 + </table>
34 + </div>
35 + );
36 +}
37 +
38 +export default HistoryPage;
1 +import React, { useEffect, useState } from "react";
2 +import Axios from "axios";
3 +import { Icon, Col, Card, Row } from "antd";
4 +import ImageSlider from "../../utils/ImageSlider";
5 +import CheckBox from "./Sections/CheckBox";
6 +import { medicines, price } from "./Sections/Datas";
7 +import SearchFeature from "./Sections/SearchFeature";
8 +import { Container } from "react-bootstrap";
9 +
10 +const { Meta } = Card;
11 +
12 +function LandingPage() {
13 + const [Products, setProducts] = useState([]);
14 + const [Skip, setSkip] = useState(0);
15 + const [Limit, setLimit] = useState(8);
16 + const [PostSize, setPostSize] = useState();
17 + const [SearchTerms, setSearchTerms] = useState("");
18 +
19 + const [Filters, setFilters] = useState({
20 + medicines: [],
21 + price: [],
22 + });
23 +
24 + useEffect(() => {
25 + const variables = {
26 + skip: Skip,
27 + limit: Limit,
28 + };
29 +
30 + getProducts(variables);
31 + }, []);
32 +
33 + const getProducts = variables => {
34 + Axios.post("/api/product/getProducts", variables).then(response => {
35 + if (response.data.success) {
36 + if (variables.loadMore) {
37 + setProducts([...Products, ...response.data.products]);
38 + } else {
39 + setProducts(response.data.products);
40 + }
41 + setPostSize(response.data.postSize);
42 + } else {
43 + alert("실패하였습니다.");
44 + }
45 + });
46 + };
47 +
48 + const onLoadMore = () => {
49 + let skip = Skip + Limit;
50 +
51 + const variables = {
52 + skip: skip,
53 + limit: Limit,
54 + loadMore: true,
55 + filters: Filters,
56 + searchTerm: SearchTerms,
57 + };
58 + getProducts(variables);
59 + setSkip(skip);
60 + };
61 +
62 + const renderCards = Products.map((product, index) => {
63 + return (
64 + <Col style={{ marginTop: "30px" }} lg={6} md={8} xs={24}>
65 + <Card
66 + style={{ margin: "5px" }}
67 + hoverable={true}
68 + cover={
69 + <a href={`/product/${product._id}`}>
70 + <ImageSlider images={product.images} />
71 + </a>
72 + }
73 + >
74 + <Meta title={product.title} description={`${product.price}000원`} />
75 + </Card>
76 + </Col>
77 + );
78 + });
79 +
80 + const showFilteredResults = filters => {
81 + const variables = {
82 + skip: 0,
83 + limit: Limit,
84 + filters: filters,
85 + };
86 + getProducts(variables);
87 + setSkip(0);
88 + };
89 +
90 + const handlePrice = value => {
91 + const data = price;
92 + let array = [];
93 +
94 + for (let key in data) {
95 + if (data[key]._id === parseInt(value, 10)) {
96 + array = data[key].array;
97 + }
98 + }
99 + console.log("array", array);
100 + return array;
101 + };
102 +
103 + const handleFilters = (filters, category) => {
104 + const newFilters = { ...Filters };
105 +
106 + newFilters[category] = filters;
107 +
108 + if (category === "price") {
109 + let priceValues = handlePrice(filters);
110 + newFilters[category] = priceValues;
111 + }
112 +
113 + console.log(newFilters);
114 +
115 + showFilteredResults(newFilters);
116 + setFilters(newFilters);
117 + };
118 +
119 + const updateSearchTerms = newSearchTerm => {
120 + const variables = {
121 + skip: 0,
122 + limit: Limit,
123 + filters: Filters,
124 + searchTerm: newSearchTerm,
125 + };
126 +
127 + setSkip(0);
128 + setSearchTerms(newSearchTerm);
129 +
130 + getProducts(variables);
131 + };
132 +
133 + return (
134 + <div style={{ width: "75%", margin: "3rem auto" }}>
135 + <div style={{ textAlign: "center" }}>
136 + <img src={"/whatmedicine.png"} style={{ width: "500px" }} />
137 + {/* <h2>
138 + {" "}
139 + <Icon type="alert" />
140 + &nbsp; 어떤 약이 필요하신가요?&nbsp;<Icon type="alert" />{" "}
141 + </h2> */}
142 + </div>
143 +
144 +
145 + <Container style={{ width: "50%" }}>
146 + <CheckBox list={medicines} handleFilters={filters => handleFilters(filters, "medicines")} />
147 + </Container>
148 +
149 + {/* Search */}
150 +
151 + {Products.length === 0 ? (
152 + <div style={{ display: "flex", height: "300px", justifyContent: "center", alignItems: "center" }}>
153 +
154 +
155 + </div>
156 + ) : (
157 + <div>
158 + <Row gutter={[16, 16]}>{renderCards}</Row>
159 + </div>
160 + )}
161 + <br />
162 + <br />
163 +
164 + {PostSize >= Limit && (
165 + <div style={{ display: "flex", justifyContent: "center" }}>
166 + <button onClick={onLoadMore}>Load More</button>
167 + </div>
168 + )}
169 + </div>
170 + );
171 +}
172 +
173 +export default LandingPage;
1 +import React, { useState } from 'react'
2 +import { Checkbox, Collapse } from 'antd';
3 +
4 +const { Panel } = Collapse
5 +
6 +
7 +function CheckBox(props) {
8 +
9 + const [Checked, setChecked] = useState([])
10 +
11 + const handleToggle = (value) => {
12 +
13 + const currentIndex = Checked.indexOf(value);
14 + const newChecked = [...Checked];
15 +
16 + if (currentIndex === -1) {
17 + newChecked.push(value)
18 + } else {
19 + newChecked.splice(currentIndex, 1)
20 + }
21 +
22 + setChecked(newChecked)
23 + props.handleFilters(newChecked)
24 +
25 + }
26 +
27 + const renderCheckboxLists = () => props.list && props.list.map((value, index) => (
28 + <React.Fragment key={index}>
29 + <Checkbox
30 + onChange={() => handleToggle(value._id)}
31 + type="checkbox"
32 + checked={Checked.indexOf(value._id) === -1 ? false : true}
33 + />&nbsp;&nbsp;
34 + <span>{value.name}</span>
35 + </React.Fragment>
36 + ))
37 +
38 + return (
39 + <div>
40 + <Collapse defaultActiveKey={['0']} >
41 + <Panel header="약 종류" key="1">
42 + {renderCheckboxLists()}
43 + </Panel>
44 + </Collapse>
45 + </div>
46 + )
47 +}
48 +
49 +export default CheckBox
...\ No newline at end of file ...\ No newline at end of file
1 +const symtoms = [
2 + {
3 + "_id": 0,
4 + "name": "전체"
5 + },
6 + {
7 + "_id": 1,
8 + "name": "진통제"
9 + },
10 + {
11 + "_id": 2,
12 + "name": "소화제"
13 + },
14 + {
15 + "_id": 3,
16 + "name": "감기약"
17 + },
18 + {
19 + "_id": 4,
20 + "name": "해열제"
21 + },
22 + {
23 + "_id": 5,
24 + "name": "파스류"
25 + },
26 + {
27 + "_id": 6,
28 + "name": "상처치료"
29 + },
30 + {
31 + "_id": 7,
32 + "name": "기타"
33 + }
34 +]
35 +
36 +export {
37 + symtoms
38 +}
...\ No newline at end of file ...\ No newline at end of file
1 +
2 +
3 +const medicines = [
4 + { _id: 0, name: "전체" },
5 + {
6 + _id: 1,
7 + name: "진통제",
8 + },
9 + {
10 + _id: 2,
11 + name: "소화제",
12 + },
13 + {
14 + _id: 3,
15 + name: "감기약",
16 + },
17 + {
18 + _id: 4,
19 + name: "해열제",
20 + },
21 + {
22 + _id: 5,
23 + name: "파스류",
24 + },
25 + {
26 + _id: 6,
27 + name: "상처치료",
28 + },
29 + {
30 + _id: 7,
31 + name: "기타",
32 + },
33 +];
34 +
35 +
36 +
37 +
38 +const price = [
39 + {
40 + _id: 0,
41 + name: "Any",
42 + array: [],
43 + },
44 + {
45 + _id: 1,
46 + name: "배달비 무료",
47 + array: [0],
48 + },
49 + {
50 + _id: 2,
51 + name: "배달비 500원 ~ 1000원",
52 + array: [500, 1000],
53 + },
54 + {
55 + _id: 3,
56 + name: "배달비 1000원 ~ 2000원",
57 + array: [1001, 2000],
58 + },
59 + {
60 + _id: 4,
61 + name: "배달비 200원 ~ 3000원",
62 + array: [2001, 3000],
63 + },
64 + {
65 + _id: 5,
66 + name: "3000원 이상",
67 + array: [3000, 1500000],
68 + },
69 +];
70 +
71 +export { price, medicines };
1 +import React from 'react'
2 +
3 +function RadioBox() {
4 + return (
5 + <div>
6 +
7 + </div>
8 + )
9 +}
10 +
11 +export default RadioBox
1 +import React, { useState } from "react";
2 +import { Input } from "antd";
3 +
4 +const { Search } = Input;
5 +
6 +function SearchFeature(props) {
7 + const [SearchTerms, setSearchTerms] = useState("");
8 +
9 + const onChangeSearch = event => {
10 + setSearchTerms(event.currentTarget.value);
11 +
12 + props.refreshFunction(event.currentTarget.value);
13 + };
14 +
15 + return (
16 + <div style={{ margin: "auto" }}>
17 + <Search value={SearchTerms} onChange={onChangeSearch} placeholder="상품명" />
18 + </div>
19 + );
20 +}
21 +
22 +export default SearchFeature;
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 +
23 + return (
24 + <Formik
25 + initialValues={{
26 + email: "",
27 + password: "",
28 + }}
29 + validationSchema={Yup.object().shape({
30 + email: Yup.string().email("이메일이 유효하지 않습니다.").required("이메일을 입력해주세요."),
31 + password: Yup.string().min(5, "비밀번호가 너무 짧습니다.").required("비밀번호를 입력해주세요."),
32 + })}
33 + onSubmit={(values, { setSubmitting }) => {
34 + setTimeout(() => {
35 + let dataToSubmit = {
36 + email: values.email,
37 + password: values.password,
38 + };
39 +
40 + dispatch(loginUser(dataToSubmit))
41 + .then(response => {
42 + if (response.payload.loginSuccess) {
43 + window.localStorage.setItem("userId", response.payload.userId);
44 + if (rememberMe === true) {
45 + window.localStorage.setItem("rememberMe", values.id);
46 + } else {
47 + localStorage.removeItem("rememberMe");
48 + }
49 + props.history.push("/");
50 + } else {
51 + setFormErrorMessage("Check out your Account or Password again");
52 + }
53 + })
54 + .catch(err => {
55 + setFormErrorMessage("Check out your Account or Password again");
56 + setTimeout(() => {
57 + setFormErrorMessage("");
58 + }, 3000);
59 + });
60 + setSubmitting(false);
61 + }, 500);
62 + }}
63 + >
64 + {props => {
65 + const { values, touched, errors, dirty, isSubmitting, handleChange, handleBlur, handleSubmit, handleReset } = props;
66 + return (
67 + <div className="app">
68 + <Title level={2}>로그인</Title>
69 + <form onSubmit={handleSubmit} style={{ width: "350px" }}>
70 + <Form.Item required>
71 + <Input
72 + id="email"
73 + prefix={<Icon type="user" style={{ color: "rgba(0,0,0,.25)" }} />}
74 + placeholder="Enter your email"
75 + type="email"
76 + value={values.email}
77 + onChange={handleChange}
78 + onBlur={handleBlur}
79 + className={errors.email && touched.email ? "text-input error" : "text-input"}
80 + />
81 + {errors.email && touched.email && <div className="input-feedback">{errors.email}</div>}
82 + </Form.Item>
83 +
84 + <Form.Item required>
85 + <Input
86 + id="password"
87 + prefix={<Icon type="lock" style={{ color: "rgba(0,0,0,.25)" }} />}
88 + placeholder="Enter your password"
89 + type="password"
90 + value={values.password}
91 + onChange={handleChange}
92 + onBlur={handleBlur}
93 + className={errors.password && touched.password ? "text-input error" : "text-input"}
94 + />
95 + {errors.password && touched.password && <div className="input-feedback">{errors.password}</div>}
96 + </Form.Item>
97 +
98 + {formErrorMessage && (
99 + <label>
100 + <p style={{ color: "#ff0000bf", fontSize: "0.7rem", border: "1px solid", padding: "1rem", borderRadius: "10px" }}>{formErrorMessage}</p>
101 + </label>
102 + )}
103 +
104 + <Form.Item>
105 + <div>
106 + <Button type="primary" htmlType="submit" className="login-form-button" style={{ minWidth: "100%" }} disabled={isSubmitting} onSubmit={handleSubmit}>
107 + 로그인
108 + </Button>
109 + </div>
110 + </Form.Item>
111 + </form>
112 + </div>
113 + );
114 + }}
115 + </Formik>
116 + );
117 +}
118 +
119 +export default withRouter(LoginPage);
1 +import React, { useState } from "react";
2 +import RightMenu from "./Sections/RightMenu";
3 +import { Drawer, Button, Icon } from "antd";
4 +import "./Sections/Navbar.css";
5 +import { Container, Navbar, Nav } from "react-bootstrap";
6 +
7 +function NavBar() {
8 + const [visible, setVisible] = useState(false);
9 +
10 + const showDrawer = () => {
11 + setVisible(true);
12 + };
13 +
14 + const onClose = () => {
15 + setVisible(false);
16 + };
17 +
18 + return (
19 + <Navbar bg="white" variant="light" style={{ position: "fixed", zIndex: 5, width: "100%", border: "1px solid lightgray" }}>
20 + <Container>
21 + <Navbar.Brand href="/">
22 + <img src={"/logo.png"} style={{ width: "120px", marginLeft:"50px"}} />
23 + </Navbar.Brand>
24 + <Nav className="mx-3" style={{ position: "relative", right: "0" }}>
25 + <div className="menu__container">
26 + <div className="menu_left"></div>
27 + <div className="menu_rigth">
28 + <RightMenu mode="horizontal" />
29 + </div>
30 + <Button className="menu__mobile-button" type="primary" onClick={showDrawer}>
31 + <Icon type="align-right" />
32 + </Button>
33 + <Drawer title="Basic Drawer" placement="right" className="menu_drawer" closable={false} onClose={onClose} visible={visible}>
34 +
35 + <RightMenu mode="inline" />
36 + </Drawer>
37 + </div>
38 + </Nav>
39 + </Container>
40 + </Navbar>
41 + );
42 +}
43 +
44 +export default NavBar;
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: lightgray;
54 +}
55 +
56 +.menu_drawer .ant-drawer-body {
57 + padding: 0 !important;
58 +}
59 +
60 +/* align header of Drawer with header of page */
61 +.menu_drawer .ant-drawer-header {
62 + padding: 14px 24px !important;
63 +}
64 +
65 +@media (max-width: 767px) {
66 + .menu__mobile-button {
67 + display: inline-block !important;
68 + }
69 +
70 + .menu_left,
71 + .menu_rigth {
72 + display: none;
73 + }
74 +
75 + .menu__logo a {
76 + margin-left: -20px;
77 + }
78 +
79 + .menu__container .ant-menu-item,
80 + .menu__container .ant-menu-submenu-title {
81 + padding: 1px 20px;
82 + }
83 +
84 + .menu__logo a {
85 + padding: 10px 20px;
86 + }
87 +}
1 +/* eslint-disable jsx-a11y/anchor-is-valid */
2 +import React from "react";
3 +import { Menu, Icon, Badge } 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("로그아웃에 실패했습니다.");
18 + }
19 + });
20 + };
21 +
22 +
23 + if (user.userData && !user.userData.isAuth) {
24 +
25 + return (
26 + <Menu style={{ paddingTop: "10px" }} mode={props.mode}>
27 + <Menu.Item key="mail">
28 + <a href="/login"><h6>로그인</h6></a>
29 + </Menu.Item>
30 + <Menu.Item key="app">
31 + <a href="/register"><h6>회원가입</h6></a>
32 + </Menu.Item>
33 + </Menu>
34 + );
35 + } else {
36 + return (
37 + <Menu mode={props.mode} style={{ paddingTop: "5px" }}>
38 + <Menu.Item key="history">
39 + <a href="/history">
40 + <h6>구매내역</h6>
41 + </a>
42 + </Menu.Item>
43 +
44 + <Menu.Item key="upload">
45 + <a href="/product/upload">
46 + <h6>등록하기</h6>
47 + </a>
48 + </Menu.Item>
49 +
50 + <Menu.Item key="cart" style={{ paddingBottom: -4 }}>
51 +
52 + <a href="/user/cart" style={{ marginRight: -22, paddingbottom: "30px", color: "#667777" }}>
53 + <Icon type="shopping-cart" style={{ fontSize: 30, marginBottom: 3 }} />
54 + </a>
55 +
56 + </Menu.Item>
57 +
58 + <Menu.Item key="logout">
59 + <a onClick={logoutHandler}><h6>로그아웃</h6></a>
60 + </Menu.Item>
61 + </Menu>
62 + );
63 + }
64 +}
65 +
66 +export default withRouter(RightMenu);
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";
7 +
8 +import { Form, Input, Button } from "antd";
9 +const { TextArea } = Input;
10 +const formItemLayout = {
11 + labelCol: {
12 + xs: { span: 24 },
13 + sm: { span: 8 },
14 + },
15 + wrapperCol: {
16 + xs: { span: 24 },
17 + sm: { span: 16 },
18 + },
19 +};
20 +const tailFormItemLayout = {
21 + wrapperCol: {
22 + xs: {
23 + span: 24,
24 + offset: 0,
25 + },
26 + sm: {
27 + span: 16,
28 + offset: 8,
29 + },
30 + },
31 +};
32 +
33 +function RegisterPage(props) {
34 + const dispatch = useDispatch();
35 + return (
36 + <Formik
37 + initialValues={{
38 + email: "",
39 + name: "",
40 + address: "",
41 + password: "",
42 + confirmPassword: "",
43 + }}
44 + validationSchema={Yup.object().shape({
45 + name: Yup.string().required("이름을 입력해주세요."),
46 + email: Yup.string().email("이메일 형식이 올바르지 않습니다.").required("이메일을 입력해주세요."),
47 + password: Yup.string().min(5, "비밀번호는 5자리 이상이어야 합니다.").required("비밀번호를 입력해주세요."),
48 + confirmPassword: Yup.string()
49 + .oneOf([Yup.ref("password"), null], "비밀번호가 일치하지 않습니다.")
50 + .required("비밀번호를 한번 더 입력해주세요."),
51 + adress: Yup.string().required("주소를 입력해주세요."),
52 + })}
53 + onSubmit={(values, { setSubmitting }) => {
54 + setTimeout(() => {
55 + let dataToSubmit = {
56 + email: values.email,
57 + password: values.password,
58 + name: values.name,
59 + adress: values.adress,
60 + image: `http://gravatar.com/avatar/${moment().unix()}?d=identicon`,
61 + };
62 +
63 + dispatch(registerUser(dataToSubmit)).then(response => {
64 + if (response.payload.success) {
65 + props.history.push("/login");
66 + } else {
67 + alert(response.payload.err.errmsg);
68 + }
69 + });
70 +
71 + setSubmitting(false);
72 + }, 500);
73 + }}
74 + >
75 + {props => {
76 + const { values, touched, errors, dirty, isSubmitting, handleChange, handleBlur, handleSubmit, handleReset } = props;
77 + return (
78 + <div className="app">
79 + <h3>회원가입</h3>
80 + <Form style={{ minWidth: "375px" }} {...formItemLayout} onSubmit={handleSubmit}>
81 + <Form.Item required label="이름">
82 + <Input
83 + id="name"
84 + placeholder="이름을 입력하세요"
85 + type="text"
86 + value={values.name}
87 + onChange={handleChange}
88 + onBlur={handleBlur}
89 + className={errors.name && touched.name ? "text-input error" : "text-input"}
90 + />
91 + {errors.name && touched.name && <div className="input-feedback">{errors.name}</div>}
92 + </Form.Item>
93 +
94 +
95 +
96 + <Form.Item required label="이메일">
97 + <Input
98 + id="email"
99 + placeholder="이메일을 입력해주세요."
100 + type="email"
101 + value={values.email}
102 + onChange={handleChange}
103 + onBlur={handleBlur}
104 + className={errors.email && touched.email ? "text-input error" : "text-input"}
105 + />
106 + {errors.email && touched.email && <div className="input-feedback">{errors.email}</div>}
107 + </Form.Item>
108 +
109 + <Form.Item required label="비밀번호">
110 + <Input
111 + id="password"
112 + placeholder="비밀번호를 입력해주세요."
113 + type="password"
114 + value={values.password}
115 + onChange={handleChange}
116 + onBlur={handleBlur}
117 + className={errors.password && touched.password ? "text-input error" : "text-input"}
118 + />
119 + {errors.password && touched.password && <div className="input-feedback">{errors.password}</div>}
120 + </Form.Item>
121 +
122 + <Form.Item required label="비밀번호 확인">
123 + <Input
124 + id="confirmPassword"
125 + placeholder="비밀번호를 한번 더 입력해주세요."
126 + type="password"
127 + value={values.confirmPassword}
128 + onChange={handleChange}
129 + onBlur={handleBlur}
130 + className={errors.confirmPassword && touched.confirmPassword ? "text-input error" : "text-input"}
131 + />
132 + {errors.confirmPassword && touched.confirmPassword && <div className="input-feedback">{errors.confirmPassword}</div>}
133 + </Form.Item>
134 +
135 + <Form.Item required label="주소">
136 + <TextArea
137 + id="adress"
138 + placeholder="주소를 입력하세요"
139 + type="text"
140 + value={values.adress}
141 + onChange={handleChange}
142 + onBlur={handleBlur}
143 + className={errors.adress && touched.adress ? "text-input error" : "text-input"}
144 + />
145 + {errors.adress && touched.adress && <div className="input-feedback">{errors.adress}</div>}
146 + </Form.Item>
147 +
148 + <Form.Item {...tailFormItemLayout}>
149 + <Button onClick={handleSubmit} type="primary" disabled={isSubmitting}>
150 + 가입
151 + </Button>
152 + </Form.Item>
153 + </Form>
154 + </div>
155 + );
156 + }}
157 + </Formik>
158 + );
159 +}
160 +
161 +export default RegisterPage;
1 +import React from 'react';
2 +import { useState } from 'react';
3 +import { Typography, Button, Form, Input } from 'antd'; // css
4 +import ImageUpload from '../../utils/ImageUpload'
5 +import Axios from 'axios';
6 +
7 +const { TextArea } = Input; // 박스크기 조절을 사용자가 임의로 가능하게 함.
8 +
9 +// Select symtoms
10 +const symtoms = [{ key: 1, value: "진통제" },
11 + { key: 2, value: "소화제" },
12 + { key: 3, value: "감기약" },
13 + { key: 4, value: "해열제" },
14 + { key: 5, value: "파스류" },
15 + { key: 6, value: "상처치료" },
16 + { key: 7, value: "기타" }
17 +]
18 +
19 +function UploadPage(props) {
20 +
21 + // OnChange Function
22 +
23 + const [Image, setImage] = useState("")
24 + const [Title, setTitle] = useState("");
25 + const [Info, setInfo] = useState("");
26 + const [Cost, setCost] = useState("");
27 + const [Option, setOption] = useState(1);
28 +
29 + const titleEvent = (event) => {
30 + setTitle(event.currentTarget.value);
31 + }
32 +
33 + const infoEvent = (event) => {
34 + setInfo(event.currentTarget.value);
35 + }
36 +
37 + const costEvent = (event) => {
38 + setCost(event.currentTarget.value);
39 + }
40 +
41 +
42 + const optionEvent = (event) => {
43 + setOption(event.currentTarget.value);
44 + }
45 +
46 +
47 + const imageEvent = (event) => {
48 + setImage(event.currentTarget.value);
49 + }
50 +
51 + const updateImages = ( newImages ) => {
52 + setImage(newImages);
53 + }
54 +
55 + const submitEvent = (event) => {
56 + event.preventDefault(); // 확인버튼을 누를 때 리프레시 되지 않도록
57 +
58 + if (!Title || !Info || !Cost || !Option || !Image) {
59 + return alert("모두 입력해주세요.")
60 + }
61 +
62 + // 서버에 보낼 값들
63 + const body = {
64 + seller: props.user.userData._id,
65 + title: Title,
66 + info: Info,
67 + price: Cost,
68 + images: Image,
69 + symtoms: Option
70 + }
71 +
72 + Axios.post("/api/product", body)
73 + .then(response => {
74 + if (response.data.success) {
75 + alert("업로드가 완료되었습니다.");
76 + props.history.push('/'); // 상품 업로드가 성공하면 메인페이지로 돌아감.
77 + }
78 + else {
79 + alert("업로드에 실패했습니다.")
80 + }
81 + })
82 + }
83 +
84 +
85 + return (
86 + <div style={{ maxWidth: '700px', margin: '2rem auto' }}>
87 +
88 + <div style={{ textAlign: 'center', marginBottom:'2rem'}}>
89 + <h2> 업로드 </h2>
90 +
91 + </div>
92 +
93 + <Form onSubmit={submitEvent}>
94 + {/* 파일업로드 부분은 코드가 길어서 따로 컴포넌트로 만들어버리기~! */}
95 + <ImageUpload refreshFunction={updateImages}/>
96 + <br />
97 + <br />
98 + <label>이름</label>
99 + <Input onChange={ titleEvent} value={Title} />
100 + {/* ㄴ ant design에서 가져온 Input */}
101 + <br />
102 + <br />
103 + <label>설명</label>
104 + <TextArea onChange={ infoEvent} value={Info} />
105 + <br />
106 + <br />
107 + <label>가격</label>
108 + <Input onChange={ costEvent} value={Cost} type="number"/>
109 + <br />
110 + <br />
111 + <select onChange={optionEvent} value={ Option}>
112 + {symtoms.map(item => (
113 + <option key={item.key} value={item.key}>{ item.value}</option>
114 + ))}
115 + <option></option>
116 + </select>
117 + <br />
118 + <br />
119 + <Button onClick={submitEvent}>확인</Button>
120 +
121 + </Form>
122 +
123 + </div>
124 + )
125 +}
126 +
127 + export default UploadPage;
1 +import React, { useState } from "react";
2 +import {Button, Form, message, Input, Icon } from "antd";
3 +import FileUpload from "../../utils/FileUpload";
4 +import Axios from "axios";
5 +
6 +const { TextArea } = Input;
7 +
8 +const Medicines = [
9 + { key: 1, value: "진통제" },
10 + { key: 2, value: "소화제" },
11 + { key: 3, value: "감기약" },
12 + { key: 4, value: "해열제" },
13 + { key: 5, value: "파스류" },
14 + { key: 6, value: "상처치료" },
15 + { key: 7, value: "기타" }
16 +];
17 +
18 +function UploadProductPage(props) {
19 + const [TitleValue, setTitleValue] = useState("");
20 + const [DescriptionValue, setDescriptionValue] = useState("");
21 + const [PriceValue, setPriceValue] = useState(0);
22 + const [MedicinesValue, setMedicinesValue] = useState(1);
23 +
24 + const [Images, setImages] = useState([]);
25 +
26 + const onTitleChange = event => {
27 + setTitleValue(event.currentTarget.value);
28 + };
29 +
30 + const onDescriptionChange = event => {
31 + setDescriptionValue(event.currentTarget.value);
32 + };
33 +
34 + const onPriceChange = event => {
35 + setPriceValue(event.currentTarget.value);
36 + };
37 +
38 + const onMedicinesSelectChange = event => {
39 + setMedicinesValue(event.currentTarget.value);
40 + };
41 +
42 + const updateImages = newImages => {
43 + setImages(newImages);
44 + };
45 + const onSubmit = event => {
46 + event.preventDefault();
47 +
48 + if (!TitleValue || !DescriptionValue || !PriceValue || !MedicinesValue || !Images) {
49 + return alert("fill all the fields first!");
50 + }
51 +
52 + const variables = {
53 + writer: props.user.userData._id,
54 + title: TitleValue,
55 + description: DescriptionValue,
56 + price: PriceValue,
57 + images: Images,
58 + medicines: MedicinesValue,
59 + };
60 +
61 + Axios.post("/api/product/uploadProduct", variables).then(response => {
62 + if (response.data.success) {
63 + alert("Product Successfully Uploaded");
64 + props.history.push("/");
65 + } else {
66 + alert("Failed to upload Product");
67 + }
68 + });
69 + };
70 +
71 + return (
72 + <div style={{ maxWidth: "700px", margin: "2rem auto" }}>
73 + <div style={{ textAlign: "center", marginBottom: "2rem" }}>
74 + <h3> 등록하기</h3>
75 + </div>
76 +
77 + <Form onSubmit={onSubmit}>
78 + {/* DropZone */}
79 + <FileUpload refreshFunction={updateImages} />
80 +
81 + <br />
82 + <br />
83 + <label> 이름</label>
84 + <Input onChange={onTitleChange} value={TitleValue} />
85 + <br />
86 + <br />
87 + <label>설명</label>
88 + <TextArea onChange={onDescriptionChange} value={DescriptionValue} />
89 + <br />
90 + <br />
91 + <label>가격($)</label>
92 + <Input onChange={onPriceChange} value={PriceValue} type="number" />
93 + <br />
94 + <br />
95 + <select onChange={onMedicinesSelectChange} value={MedicinesValue}>
96 + {Medicines.map(item => (
97 + <option key={item.key} value={item.key}>{item.value} </option>
98 + ))}
99 + </select>
100 + <br />
101 + <br />
102 + <Button onClick={onSubmit}>등록</Button>
103 + </Form>
104 + </div>
105 + );
106 +}
107 +
108 +export default UploadProductPage;
1 +import React, { Component } from 'react'
2 +import { Typography, Button, Form, Input } from 'antd';
3 +import axios from 'axios';
4 +import FileUpload from '../../utils/FileUpload';
5 +
6 +const { Title } = Typography;
7 +const { TextArea } = Input;
8 +
9 +const Medicines = [
10 + { key: 1, value: "진통제" },
11 + { key: 2, value: "소화제" },
12 + { key: 3, value: "감기약" },
13 + { key: 4, value: "해열제" },
14 + { key: 5, value: "파스류" },
15 + { key: 6, value: "상처치료" },
16 + { key: 7, value: "기타" }
17 +]
18 +
19 +export class UploadProductPage extends Component {
20 +
21 + state = {
22 + title: '',
23 + description: '',
24 + medicines: 1,
25 + images: [],
26 + price: 0
27 + }
28 +
29 + handleChangeTitle = (event) => {
30 + this.setState({ title: event.currentTarget.value })
31 + }
32 +
33 + handleChangePrice = (event) => {
34 + this.setState({ price: parseInt(event.currentTarget.value, 10) })
35 + }
36 +
37 + handleChangeDecsription = (event) => {
38 + // console.log(event.currentTarget.value)
39 + this.setState({ description: event.currentTarget.value })
40 + }
41 +
42 + handleChangeMedicines = (event) => {
43 + this.setState({ medicines: event.currentTarget.value })
44 + }
45 +
46 + onSubmit = (event) => {
47 + event.preventDefault();
48 +
49 + if (this.props.user.userData && !this.props.user.userData.isAuth) {
50 + return alert('!! 접근할 수 없습니다 !!')
51 + }
52 +
53 + if (!this.state.title || !this.state.description ||
54 + !this.state.medicines || !this.state.images
55 + || !this.state.price) {
56 + return alert('모든 항목을 채워주세요.')
57 + }
58 +
59 + const variables = {
60 + writer: this.props.user.userData._id,
61 + title: this.state.title,
62 + description: this.state.description,
63 + images: this.state.images,
64 + medicines: this.state.medicines,
65 + price: this.state.price
66 + }
67 +
68 + axios.post('/api/product/uploadProduct', variables)
69 + .then(response => {
70 + if (response.data.success) {
71 + alert('성공적으로 업로드 했습니다.')
72 + setTimeout(() => {
73 + this.props.history.push('/')
74 + }, 1000);
75 + } else {
76 + alert('업로드에 실패했습니다.')
77 + }
78 + })
79 + }
80 +
81 + updateFiles = (newImages) => {
82 + this.setState({ images: newImages })
83 + }
84 +
85 +
86 + render() {
87 + return (
88 + <div style={{ maxWidth: '700px', margin: '2rem auto' }}>
89 + <div style={{ textAlign: 'center', marginBottom: '2rem' }}>
90 + <Title level={2} > 배달 서비스 : 약사</Title>
91 + </div>
92 +
93 + <Form onSubmit={this.onSubmit}>
94 +
95 + <FileUpload refreshFunction={this.updateFiles} />
96 +
97 + <br /><br />
98 + <label>제품명</label>
99 + <Input
100 + onChange={this.handleChangeTitle}
101 + value={this.state.title}
102 + />
103 + <br /><br />
104 + <label>설명</label>
105 + <TextArea
106 + onChange={this.handleChangeDecsription}
107 + value={this.state.description}
108 + />
109 + <br /><br />
110 + <label>가격</label>
111 + <Input
112 + type="number"
113 + onChange={this.handleChangePrice}
114 + value={this.state.price}
115 + />
116 + <br /><br />
117 + <select onChange={this.handleChangeMedicines}>
118 + {Medicines.map(item => (
119 + <option key={item.key} value={item.key}>{item.value}</option>
120 + ))}
121 + </select>
122 + <br /><br />
123 +
124 +
125 + <Button type="primary" size="large" onClick={this.onSubmit}>
126 + Submit
127 + </Button>
128 + </Form>
129 + </div>
130 + )
131 + }
132 +}
133 +
134 +export default UploadProductPage
1 +import React from "react";
2 +import axios from 'axios';
3 +
4 +function adminPage(props) {
5 +
6 +}
7 +
8 +export default adminPage;
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 (ComposedClass, reload, adminRoute = null) {
6 + function AuthenticationCheck(props) {
7 +
8 + let user = useSelector(state => state.user);
9 + const dispatch = useDispatch();
10 +
11 + useEffect(() => {
12 +
13 + dispatch(auth()).then(async response => {
14 + if (await !response.payload.isAuth) {
15 + if (reload) {
16 + props.history.push('/login')
17 + }
18 + } else {
19 + if (adminRoute && !response.payload.isAdmin) {
20 + props.history.push('/')
21 + }
22 + else {
23 + if (reload === false) {
24 + props.history.push('/')
25 + }
26 + }
27 + }
28 + })
29 +
30 + }, [dispatch, props.history, user.googleAuth])
31 +
32 + return (
33 + <ComposedClass {...props} user={user} />
34 + )
35 + }
36 + return AuthenticationCheck
37 +}
38 +
39 +
1 +@import "~react-image-gallery/styles/css/image-gallery.css";
2 +@font-face {
3 + font-family: "BRBA_B";
4 + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_seven@1.2/BRBA_B.woff")
5 + format("woff");
6 + font-weight: normal;
7 + font-style: normal;
8 +}
9 +
10 +@font-face {
11 + font-family: "InfinitySans-RegularA1";
12 + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/InfinitySans-RegularA1.woff")
13 + format("woff");
14 + font-weight: normal;
15 + font-style: normal;
16 +}
17 +
18 +@font-face {
19 + font-family: "GongGothicMedium";
20 + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-10@1.0/GongGothicMedium.woff")
21 + format("woff");
22 + font-weight: normal;
23 + font-style: normal;
24 +}
25 +
26 +body {
27 + margin: 0;
28 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
29 + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
30 + sans-serif;
31 + -webkit-font-smoothing: antialiased;
32 + -moz-osx-font-smoothing: grayscale;
33 +}
34 +
35 +code {
36 + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
37 + monospace;
38 +}
39 +
40 +body {
41 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
42 + sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
43 + font-size: 14px;
44 + line-height: 1.5;
45 + color: #24292e;
46 + background-color: #fff;
47 +}
48 +
49 +.app {
50 + flex-direction: column;
51 + display: flex;
52 + justify-content: center;
53 + align-items: center;
54 + height: 100vh;
55 +}
56 +
57 +input.error {
58 + border-color: red;
59 +}
60 +
61 +.input-feedback {
62 + color: red;
63 + height: 5px;
64 + margin-top: -12px;
65 +}
66 +
67 +table {
68 + font-family: arial, sans-serif;
69 + border-collapse: collapse;
70 + width: 100%;
71 +}
72 +
73 +td,
74 +th {
75 + border: 1px solid #dddddd;
76 + text-align: left;
77 + padding: 8px;
78 +}
79 +
80 +tr:nth-child(even) {
81 + background-color: #dddddd;
82 +}
83 +
84 +h1 {
85 + font-family: "GongGothicMedium";
86 +}
87 +
88 +h3 {
89 + font-family: "InfinitySans-RegularA1";
90 +}
1 +import React from "react";
2 +import ReactDOM from "react-dom";
3 +import "./index.css";
4 +import App from "./components/App";
5 +import * as serviceWorker from "./serviceWorker";
6 +import { BrowserRouter } from "react-router-dom";
7 +
8 +import Reducer from "./_reducers";
9 +import { Provider } from "react-redux";
10 +import { createStore, applyMiddleware } from "redux";
11 +import promiseMiddleware from "redux-promise";
12 +import ReduxThunk from "redux-thunk";
13 +
14 +const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore);
15 +
16 +ReactDOM.render(
17 + <Provider store={createStoreWithMiddleware(Reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())}>
18 + <BrowserRouter>
19 + <App />
20 + </BrowserRouter>
21 + </Provider>,
22 + document.getElementById("root")
23 +);
24 +// If you want your app to work offline and load faster, you can change
25 +// unregister() to register() below. Note this comes with some pitfalls.
26 +// Learn more about service workers: https://bit.ly/CRA-PWA
27 +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 +
13 +const isLocalhost = Boolean(
14 + window.location.hostname === 'localhost' ||
15 + // [::1] is the IPv6 localhost address.
16 + window.location.hostname === '[::1]' ||
17 + // 127.0.0.1/8 is considered localhost for IPv4.
18 + window.location.hostname.match(
19 + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 + )
21 +);
22 +
23 +export function register(config) {
24 + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 + // The URL constructor is available in all browsers that support SW.
26 + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 + if (publicUrl.origin !== window.location.origin) {
28 + // Our service worker won't work if PUBLIC_URL is on a different origin
29 + // from what our page is served on. This might happen if a CDN is used to
30 + // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 + return;
32 + }
33 +
34 + window.addEventListener('load', () => {
35 + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 +
37 + if (isLocalhost) {
38 + // This is running on localhost. Let's check if a service worker still exists or not.
39 + checkValidServiceWorker(swUrl, config);
40 +
41 + // Add some additional logging to localhost, pointing developers to the
42 + // service worker/PWA documentation.
43 + navigator.serviceWorker.ready.then(() => {
44 + console.log(
45 + 'This web app is being served cache-first by a service ' +
46 + 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 + );
48 + });
49 + } else {
50 + // Is not localhost. Just register service worker
51 + registerValidSW(swUrl, config);
52 + }
53 + });
54 + }
55 +}
56 +
57 +function registerValidSW(swUrl, config) {
58 + navigator.serviceWorker
59 + .register(swUrl)
60 + .then(registration => {
61 + registration.onupdatefound = () => {
62 + const installingWorker = registration.installing;
63 + if (installingWorker == null) {
64 + return;
65 + }
66 + installingWorker.onstatechange = () => {
67 + if (installingWorker.state === 'installed') {
68 + if (navigator.serviceWorker.controller) {
69 + // At this point, the updated precached content has been fetched,
70 + // but the previous service worker will still serve the older
71 + // content until all client tabs are closed.
72 + console.log(
73 + 'New content is available and will be used when all ' +
74 + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 + );
76 +
77 + // Execute callback
78 + if (config && config.onUpdate) {
79 + config.onUpdate(registration);
80 + }
81 + } else {
82 + // At this point, everything has been precached.
83 + // It's the perfect time to display a
84 + // "Content is cached for offline use." message.
85 + console.log('Content is cached for offline use.');
86 +
87 + // Execute callback
88 + if (config && config.onSuccess) {
89 + config.onSuccess(registration);
90 + }
91 + }
92 + }
93 + };
94 + };
95 + })
96 + .catch(error => {
97 + console.error('Error during service worker registration:', error);
98 + });
99 +}
100 +
101 +function checkValidServiceWorker(swUrl, config) {
102 + // Check if the service worker can be found. If it can't reload the page.
103 + fetch(swUrl)
104 + .then(response => {
105 + // Ensure service worker exists, and that we really are getting a JS file.
106 + const contentType = response.headers.get('content-type');
107 + if (
108 + response.status === 404 ||
109 + (contentType != null && contentType.indexOf('javascript') === -1)
110 + ) {
111 + // No service worker found. Probably a different app. Reload the page.
112 + navigator.serviceWorker.ready.then(registration => {
113 + registration.unregister().then(() => {
114 + window.location.reload();
115 + });
116 + });
117 + } else {
118 + // Service worker found. Proceed as normal.
119 + registerValidSW(swUrl, config);
120 + }
121 + })
122 + .catch(() => {
123 + console.log(
124 + 'No internet connection found. App is running in offline mode.'
125 + );
126 + });
127 +}
128 +
129 +export function unregister() {
130 + if ('serviceWorker' in navigator) {
131 + navigator.serviceWorker.ready.then(registration => {
132 + registration.unregister();
133 + });
134 + }
135 +}
1 +const { createProxyMiddleware } = require('http-proxy-middleware');
2 +
3 +module.exports = function (app) {
4 + app.use(
5 + '/api',
6 + createProxyMiddleware({
7 + target: 'http://localhost:5000',
8 + changeOrigin: true,
9 + })
10 + );
11 +};
...\ No newline at end of file ...\ No newline at end of file
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
1 +{
2 + "name": "react-boiler-plate",
3 + "version": "1.0.0",
4 + "description": "react boiler plate",
5 + "main": "index.js",
6 + "engine": {
7 + "node": "10.16.0",
8 + "npm": "6.9.0"
9 + },
10 + "scripts": {
11 + "start": "node server/index.js",
12 + "backend": "nodemon server/index.js",
13 + "frontend": "npm run front --prefix client",
14 + "dev": "concurrently \"npm run backend\" \"npm run start --prefix client\""
15 + },
16 + "author": "John ahn",
17 + "license": "ISC",
18 + "dependencies": {
19 + "async": "^3.1.0",
20 + "bcrypt": "^3.0.6",
21 + "body-parser": "^1.18.3",
22 + "cookie-parser": "^1.4.3",
23 + "cors": "^2.8.5",
24 + "express": "^4.17.1",
25 + "jsonwebtoken": "^8.5.1",
26 + "moment": "^2.24.0",
27 + "mongoose": "^5.4.20",
28 + "multer": "^1.4.2",
29 + "react-redux": "^5.0.7",
30 + "socket.io": "^2.2.0"
31 + },
32 + "devDependencies": {
33 + "concurrently": "^4.1.0",
34 + "nodemon": "^1.19.1"
35 + }
36 +}
1 +dev.js
...\ No newline at end of file ...\ No newline at end of file
1 +if (process.env.NODE_ENV === 'production') {
2 + module.exports = require('./prod');
3 +} else {
4 + module.exports = require('./dev');
5 +}
...\ No newline at end of file ...\ No newline at end of file
1 +module.exports = {
2 + mongoURI:process.env.MONGO_URI
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')
5 +
6 +const bodyParser = require("body-parser");
7 +const cookieParser = require("cookie-parser");
8 +
9 +const config = require("./config/key");
10 +
11 +// const mongoose = require("mongoose");
12 +// mongoose
13 +// .connect(config.mongoURI, { useNewUrlParser: true })
14 +// .then(() => console.log("DB connected"))
15 +// .catch(err => console.error(err));
16 +
17 +const mongoose = require("mongoose");
18 +const connect = mongoose.connect(config.mongoURI, { useNewUrlParser: true, useUnifiedTopology: true })
19 + .then(() => console.log('MongoDB Connected...'))
20 + .catch(err => console.log(err));
21 +
22 +app.use(cors())
23 +
24 +app.use(bodyParser.urlencoded({ extended: true }));
25 +app.use(bodyParser.json());
26 +app.use(cookieParser());
27 +
28 +app.use('/api/users', require('./routes/users'));
29 +app.use('/api/product', require('./routes/product'));
30 +
31 +
32 +//use this to show the image you have in node js server to client (react js)
33 +//https://stackoverflow.com/questions/48914987/send-image-path-from-node-js-express-server-to-react-client
34 +app.use('/uploads', express.static('uploads'));
35 +
36 +// Serve static assets if in production
37 +if (process.env.NODE_ENV === "production") {
38 +
39 + // Set static folder
40 + app.use(express.static("client/build"));
41 +
42 + // index.html for all page routes
43 + app.get("*", (req, res) => {
44 + res.sendFile(path.resolve(__dirname, "../client", "build", "index.html"));
45 + });
46 +}
47 +
48 +const port = process.env.PORT || 5000
49 +
50 +app.listen(port, () => {
51 + console.log(`Server Running at ${port}`)
52 +});
...\ 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 +
3 +const paymentSchema = mongoose.Schema({
4 + user: { // 구매자 정보
5 + type: Array,
6 + default: []
7 + },
8 + data: { // 구매일자 정보
9 + type: Array,
10 + default: []
11 + },
12 + product: { // 상품 정보
13 + type: Array,
14 + default: []
15 + }
16 +
17 +
18 +}, { timestamps: true })
19 +
20 +
21 +const Payment = mongoose.model('Payment', paymentSchema);
22 +
23 +module.exports = { Payment }
...\ No newline at end of file ...\ No newline at end of file
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 + price: {
17 + type: Number,
18 + default: 0
19 + },
20 + images: {
21 + type: Array,
22 + default: []
23 + },
24 + continents: {
25 + type: Number,
26 + default: 1
27 + },
28 + sold: {
29 + type: Number,
30 + maxlength: 100,
31 + default: 0
32 + },
33 + views: {
34 + type: Number,
35 + default: 0
36 + }
37 +}, { timestamps: true })
38 +
39 +
40 +productSchema.index({
41 + title:'text',
42 + description: 'text',
43 +}, {
44 + weights: {
45 + name: 5,
46 + description: 1,
47 + }
48 +})
49 +
50 +const Product = mongoose.model('Product', productSchema);
51 +
52 +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 + minlength: 5
20 + },
21 + adress: {
22 + type: String,
23 + maxlength: 100
24 + },
25 + role: {
26 + type: Number,
27 + default: 0
28 + },
29 + cart: {
30 + type: Array,
31 + default: []
32 + },
33 + history: {
34 + type: Array,
35 + default: []
36 + },
37 + image: String,
38 + token: {
39 + type: String,
40 + },
41 + tokenExp: {
42 + type: Number
43 + }
44 +})
45 +
46 +userSchema.pre('save', function (next) {
47 + var user = this;
48 +
49 + if (user.isModified('password')) {
50 + console.log('password changed')
51 + bcrypt.genSalt(saltRounds, function (err, salt) {
52 + if (err) return next(err);
53 +
54 + bcrypt.hash(user.password, salt, function (err, hash) {
55 + if (err) return next(err);
56 + user.password = hash
57 + next()
58 + })
59 + })
60 + } else {
61 + next()
62 + }
63 +});
64 +
65 +userSchema.methods.comparePassword = function (plainPassword, cb) {
66 + bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
67 + if (err) return cb(err);
68 + cb(null, isMatch)
69 + })
70 +}
71 +
72 +userSchema.methods.generateToken = function (cb) {
73 + var user = this;
74 + var token = jwt.sign(user._id.toHexString(), 'secret')
75 + var oneHour = moment().add(1, 'hour').valueOf();
76 +
77 + user.tokenExp = oneHour;
78 + user.token = token;
79 + user.save(function (err, user) {
80 + if (err) return cb(err)
81 + cb(null, user);
82 + })
83 +}
84 +
85 +userSchema.statics.findByToken = function (token, cb) {
86 + var user = this;
87 +
88 + jwt.verify(token, 'secret', function (err, decode) {
89 + user.findOne({ "_id": decode, "token": token }, function (err, user) {
90 + if (err) return cb(err);
91 + cb(null, user);
92 + })
93 + })
94 +}
95 +
96 +const User = mongoose.model('User', userSchema);
97 +
98 +module.exports = { User }
...\ No newline at end of file ...\ No newline at end of file
1 +const express = require('express');
2 +const router = express.Router();
3 +const { Product } = require("../models/Product");
4 +const multer = require('multer');
5 +
6 +const { auth } = require("../middleware/auth");
7 +
8 +var storage = multer.diskStorage({
9 + destination: (req, file, cb) => {
10 + cb(null, 'uploads/')
11 + },
12 + filename: (req, file, cb) => {
13 + cb(null, `${Date.now()}_${file.originalname}`)
14 + },
15 + fileFilter: (req, file, cb) => {
16 + const ext = path.extname(file.originalname)
17 + if (ext !== '.jpg' || ext !== '.png') {
18 + return cb(res.status(400).end('JPG, PNG 확장자만 가능합니다.'), false);
19 + }
20 + cb(null, true)
21 + }
22 +})
23 +
24 +var upload = multer({ storage: storage }).single("file")
25 +
26 +
27 +router.post("/uploadImage", auth, (req, res) => {
28 +
29 + upload(req, res, err => {
30 + if (err) {
31 + return res.json({ success: false, err })
32 + }
33 + return res.json({ success: true, image: res.req.file.path, fileName: res.req.file.filename })
34 + })
35 +
36 +});
37 +
38 +
39 +router.post("/uploadProduct", auth, (req, res) => {
40 +
41 + //save all the data we got from the client into the DB
42 + const product = new Product(req.body)
43 +
44 + product.save((err) => {
45 + if (err) return res.status(400).json({ success: false, err })
46 + return res.status(200).json({ success: true })
47 + })
48 +
49 +});
50 +
51 +
52 +router.post("/getProducts", (req, res) => {
53 +
54 + let order = req.body.order ? req.body.order : "desc";
55 + let sortBy = req.body.sortBy ? req.body.sortBy : "_id";
56 + let limit = req.body.limit ? parseInt(req.body.limit) : 100;
57 + let skip = parseInt(req.body.skip);
58 +
59 + let findArgs = {};
60 + let term = req.body.searchTerm;
61 +
62 + for (let key in req.body.filters) {
63 +
64 + if (req.body.filters[key].length > 0) {
65 + if (key === "price") {
66 + findArgs[key] = {
67 + $gte: req.body.filters[key][0],
68 + $lte: req.body.filters[key][1]
69 + }
70 + } else {
71 + findArgs[key] = req.body.filters[key];
72 + }
73 + }
74 + }
75 +
76 + console.log(findArgs)
77 +
78 + if (term) {
79 + Product.find(findArgs)
80 + .find({ $text: { $search: term } })
81 + .populate("writer")
82 + .sort([[sortBy, order]])
83 + .skip(skip)
84 + .limit(limit)
85 + .exec((err, products) => {
86 + if (err) return res.status(400).json({ success: false, err })
87 + res.status(200).json({ success: true, products, postSize: products.length })
88 + })
89 + } else {
90 + Product.find(findArgs)
91 + .populate("writer")
92 + .sort([[sortBy, order]])
93 + .skip(skip)
94 + .limit(limit)
95 + .exec((err, products) => {
96 + if (err) return res.status(400).json({ success: false, err })
97 + res.status(200).json({ success: true, products, postSize: products.length })
98 + })
99 + }
100 +
101 +});
102 +
103 +
104 +//?id=${productId}&type=single
105 +//id=12121212,121212,1212121 type=array
106 +router.get("/products_by_id", (req, res) => {
107 + let type = req.query.type
108 + let productIds = req.query.id
109 +
110 + console.log("req.query.id", req.query.id)
111 +
112 + if (type === "array") {
113 + let ids = req.query.id.split(',');
114 + productIds = [];
115 + productIds = ids.map(item => {
116 + return item
117 + })
118 + }
119 +
120 + console.log("productIds", productIds)
121 +
122 +
123 + //we need to find the product information that belong to product Id
124 + Product.find({ '_id': { $in: productIds } })
125 + .populate('writer')
126 + .exec((err, product) => {
127 + if (err) return res.status(400).send(err)
128 + return res.status(200).send(product)
129 + })
130 +});
131 +
132 +
133 +
134 +module.exports = router;
1 +const express = require('express');
2 +const { User } = require("../models/User");
3 +const { auth } = require("../middleware/auth");
4 +const router = express.Router();
5 +
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 })
17 +
18 +router.post('/image', (req, res) => {
19 +
20 + // 클라이언트로부터 받은 이미지 저장
21 + upload(req, res, err => {
22 + if (err) return req.json({ success: false, err })
23 + return res.json({success: true, filePath: res.req.file.path, fileName: res.req.file.filename})
24 + }
25 + )
26 +
27 +})
28 +
29 +module.exports = router;
1 +const express = require('express');
2 +const router = express.Router();
3 +const { User } = require("../models/User");
4 +const { Product } = require('../models/Product');
5 +const { auth } = require("../middleware/auth");
6 +const { Payment } = require('../models/Payment');
7 +
8 +const async = require('async');
9 +
10 +
11 +
12 +router.get("/auth", auth, (req, res) => {
13 + res.status(200).json({
14 + _id: req.user._id,
15 + isAdmin: req.user.role === 0 ? false : true,
16 + isAuth: true,
17 + email: req.user.email,
18 + name: req.user.name,
19 + address: req.user.address,
20 + role: req.user.role,
21 + image: req.user.image,
22 + cart: req.user.cart,
23 + history: req.user.history
24 + });
25 +});
26 +
27 +router.post("/register", (req, res) => {
28 +
29 + const user = new User(req.body);
30 +
31 + user.save((err, doc) => {
32 + if (err) return res.json({ success: false, err });
33 + return res.status(200).json({
34 + success: true
35 + });
36 + });
37 +});
38 +
39 +router.post("/login", (req, res) => {
40 + User.findOne({ email: req.body.email }, (err, user) => {
41 + if (!user)
42 + return res.json({
43 + loginSuccess: false,
44 + message: "존재하지 않는 계정입니다."
45 + });
46 +
47 + user.comparePassword(req.body.password, (err, isMatch) => {
48 + if (!isMatch)
49 + return res.json({ loginSuccess: false, message: "비밀번호가 다릅니다." });
50 +
51 + user.generateToken((err, user) => {
52 + if (err) return res.status(400).send(err);
53 + res.cookie("w_authExp", user.tokenExp);
54 + res
55 + .cookie("w_auth", user.token)
56 + .status(200)
57 + .json({
58 + loginSuccess: true, userId: user._id
59 + });
60 + });
61 + });
62 + });
63 +});
64 +
65 +router.get("/logout", auth, (req, res) => {
66 + User.findOneAndUpdate({ _id: req.user._id }, { token: "", tokenExp: "" }, (err, doc) => {
67 + if (err) return res.json({ success: false, err });
68 + return res.status(200).send({
69 + success: true
70 + });
71 + });
72 +});
73 +
74 +
75 +router.get('/addToCart', auth, (req, res) => {
76 +
77 + User.findOne({ _id: req.user._id }, (err, userInfo) => {
78 + let duplicate = false;
79 +
80 + console.log(userInfo)
81 +
82 + userInfo.cart.forEach((item) => {
83 + if (item.id == req.query.productId) {
84 + duplicate = true;
85 + }
86 + })
87 +
88 +
89 + if (duplicate) {
90 + User.findOneAndUpdate(
91 + { _id: req.user._id, "cart.id": req.query.productId },
92 + { $inc: { "cart.$.quantity": 1 } },
93 + { new: true },
94 + (err, userInfo) => {
95 + if (err) return res.json({ success: false, err });
96 + res.status(200).json(userInfo.cart)
97 + }
98 + )
99 + } else {
100 + User.findOneAndUpdate(
101 + { _id: req.user._id },
102 + {
103 + $push: {
104 + cart: {
105 + id: req.query.productId,
106 + quantity: 1,
107 + date: Date.now()
108 + }
109 + }
110 + },
111 + { new: true },
112 + (err, userInfo) => {
113 + if (err) return res.json({ success: false, err });
114 + res.status(200).json(userInfo.cart)
115 + }
116 + )
117 + }
118 + })
119 +});
120 +
121 +
122 +router.get('/removeFromCart', auth, (req, res) => {
123 +
124 + User.findOneAndUpdate(
125 + { _id: req.user._id },
126 + {
127 + "$pull":
128 + { "cart": { "id": req.query._id } }
129 + },
130 + { new: true },
131 + (err, userInfo) => {
132 + let cart = userInfo.cart;
133 + let array = cart.map(item => {
134 + return item.id
135 + })
136 +
137 + Product.find({ '_id': { $in: array } })
138 + .populate('writer')
139 + .exec((err, cartDetail) => {
140 + return res.status(200).json({
141 + cartDetail,
142 + cart
143 + })
144 + })
145 + }
146 + )
147 +})
148 +
149 +
150 +router.get('/userCartInfo', auth, (req, res) => {
151 + User.findOne(
152 + { _id: req.user._id },
153 + (err, userInfo) => {
154 + let cart = userInfo.cart;
155 + let array = cart.map(item => {
156 + return item.id
157 + })
158 +
159 +
160 + Product.find({ '_id': { $in: array } })
161 + .populate('writer')
162 + .exec((err, cartDetail) => {
163 + if (err) return res.status(400).send(err);
164 + return res.status(200).json({ success: true, cartDetail, cart })
165 + })
166 +
167 + }
168 + )
169 +})
170 +
171 +
172 +
173 +
174 +router.post('/successBuy', auth, (req, res) => {
175 + let history = [];
176 + let transactionData = {};
177 +
178 + req.body.cartDetail.forEach((item) => {
179 + history.push({
180 + dateOfPurchase: Date.now(),
181 + name: item.title,
182 + id: item._id,
183 + price: item.price,
184 + quantity: item.quantity,
185 + paymentId: req.body.paymentData.paymentID
186 + })
187 + })
188 +
189 + // 페이팔에서 받아온 데이터 넣음
190 + transactionData.user = {
191 + id: req.user._id,
192 + name: req.user.name,
193 + address: req.user.address,
194 + email: req.user.email
195 + }
196 +
197 + transactionData.data = req.body.paymentData;
198 + transactionData.product = history
199 +
200 +
201 + User.findOneAndUpdate(
202 + { _id: req.user._id },
203 + { $push: { history: history }, $set: { cart: [] } },
204 + { new: true },
205 + (err, user) => {
206 + if (err) return res.json({ success: false, err });
207 +
208 +
209 + const payment = new Payment(transactionData)
210 + payment.save((err, doc) => {
211 + if (err) return res.json({ success: false, err });
212 + let products = [];
213 + doc.product.forEach(item => {
214 + products.push({ id: item.id, quantity: item.quantity })
215 + })
216 +
217 + async.eachSeries(products, (item, callback) => {
218 + Product.update(
219 + { _id: item.id },
220 + {
221 + $inc: {
222 + "sold": item.quantity
223 + }
224 + },
225 + { new: false },
226 + callback
227 + )
228 + }, (err) => {
229 + if (err) return res.json({ success: false, err })
230 + res.status(200).json({
231 + success: true,
232 + cart: user.cart,
233 + cartDetail: []
234 + })
235 + })
236 +
237 + })
238 + }
239 + )
240 +})
241 +
242 +
243 +router.get('/getHistory', auth, (req, res) => {
244 + User.findOne(
245 + { _id: req.user._id },
246 + (err, doc) => {
247 + let history = doc.history;
248 + if (err) return res.status(400).send(err)
249 + return res.status(200).json({ success: true, history })
250 + }
251 + )
252 +})
253 +
254 +
255 +module.exports = router;