Showing
103 changed files
with
2227 additions
and
477 deletions
This diff could not be displayed because it is too large.
... | @@ -4,21 +4,25 @@ | ... | @@ -4,21 +4,25 @@ |
4 | "private": true, | 4 | "private": true, |
5 | "dependencies": { | 5 | "dependencies": { |
6 | "antd": "^3.24.1", | 6 | "antd": "^3.24.1", |
7 | - "axios": "^0.19.2", | 7 | + "axios": "^0.18.0", |
8 | - "core-js": "^3.6.4", | 8 | + "bootstrap": "^4.6.0", |
9 | "formik": "^1.5.8", | 9 | "formik": "^1.5.8", |
10 | "moment": "^2.24.0", | 10 | "moment": "^2.24.0", |
11 | "react": "^16.8.6", | 11 | "react": "^16.8.6", |
12 | - "react-app-polyfill": "^1.0.6", | 12 | + "react-bootstrap": "^1.6.1", |
13 | "react-dom": "^16.8.6", | 13 | "react-dom": "^16.8.6", |
14 | - "react-dropzone": "^11.3.2", | 14 | + "react-dropzone": "^10.2.1", |
15 | "react-icons": "^3.7.0", | 15 | "react-icons": "^3.7.0", |
16 | + "react-image-gallery": "^1.0.3", | ||
17 | + "react-paypal-express-checkout": "^1.0.5", | ||
16 | "react-redux": "^7.1.0-rc.1", | 18 | "react-redux": "^7.1.0-rc.1", |
17 | "react-router-dom": "^5.0.1", | 19 | "react-router-dom": "^5.0.1", |
18 | - "react-scripts": "3.4.1", | 20 | + "react-scripts": "3.0.1", |
19 | "redux": "^4.0.0", | 21 | "redux": "^4.0.0", |
22 | + "redux-form": "^8.2.6", | ||
20 | "redux-promise": "^0.6.0", | 23 | "redux-promise": "^0.6.0", |
21 | "redux-thunk": "^2.3.0", | 24 | "redux-thunk": "^2.3.0", |
25 | + "socket.io-client": "^2.2.0", | ||
22 | "yup": "^0.27.0" | 26 | "yup": "^0.27.0" |
23 | }, | 27 | }, |
24 | "scripts": { | 28 | "scripts": { | ... | ... |
No preview for this file type
... | @@ -19,7 +19,13 @@ | ... | @@ -19,7 +19,13 @@ |
19 | work correctly both with client-side routing and a non-root public URL. | 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`. | 20 | Learn how to configure a non-root public URL by running `npm run build`. |
21 | --> | 21 | --> |
22 | - <title>React App</title> | 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> | ||
23 | </head> | 29 | </head> |
24 | <body> | 30 | <body> |
25 | <noscript>You need to enable JavaScript to run this app.</noscript> | 31 | <noscript>You need to enable JavaScript to run this app.</noscript> | ... | ... |
We-Shop/client/public/logo.png
0 → 100644
14.7 KB
We-Shop/client/public/whatmedicine.png
0 → 100644
7.47 KB
1 | -// type들만 관리하는 곳 | ||
2 | - | ||
3 | export const LOGIN_USER = 'login_user'; | 1 | export const LOGIN_USER = 'login_user'; |
4 | export const REGISTER_USER = 'register_user'; | 2 | export const REGISTER_USER = 'register_user'; |
5 | export const AUTH_USER = 'auth_user'; | 3 | export const AUTH_USER = 'auth_user'; |
6 | export const LOGOUT_USER = 'logout_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 | ... | ... |
... | @@ -4,38 +4,36 @@ import { | ... | @@ -4,38 +4,36 @@ import { |
4 | REGISTER_USER, | 4 | REGISTER_USER, |
5 | AUTH_USER, | 5 | AUTH_USER, |
6 | LOGOUT_USER, | 6 | LOGOUT_USER, |
7 | + ADD_TO_CART_USER, | ||
8 | + GET_CART_ITEMS_USER, | ||
9 | + REMOVE_CART_ITEM_USER, | ||
10 | + ON_SUCCESS_BUY_USER | ||
7 | } from './types'; | 11 | } from './types'; |
8 | import { USER_SERVER } from '../components/Config.js'; | 12 | import { USER_SERVER } from '../components/Config.js'; |
9 | 13 | ||
10 | -export function loginUser(logInfo){ | 14 | +export function registerUser(dataToSubmit) { |
11 | - const request = axios.post(`${USER_SERVER}/login`,logInfo)// logInfo를 post로 전달 | 15 | + const request = axios.post(`${USER_SERVER}/register`, dataToSubmit) |
12 | - .then(response => response.data); // 서버에서 받은 데이터를 request에 저장 | ||
13 | - | ||
14 | -return { // return을 통해 Reducer로 보냄 | ||
15 | - // Reducer에서 previousState, action을 이용해 nextState로 만들기 때문 :: (previousState, action) => nextState | ||
16 | - // request를 reducer로 보내는 작업 | ||
17 | - | ||
18 | - // action은 type과 response을 넣어줘야 함 | ||
19 | - type: LOGIN_USER, | ||
20 | - payload: request // payroad == response | ||
21 | -} | ||
22 | -} | ||
23 | - | ||
24 | -export function registerUser(dataToSubmit){ | ||
25 | - const request = axios.post(`${USER_SERVER}/register`,dataToSubmit) | ||
26 | .then(response => response.data); | 16 | .then(response => response.data); |
27 | - | 17 | + |
28 | return { | 18 | return { |
29 | type: REGISTER_USER, | 19 | type: REGISTER_USER, |
30 | payload: request | 20 | payload: request |
31 | } | 21 | } |
32 | } | 22 | } |
33 | 23 | ||
24 | +export function loginUser(dataToSubmit) { | ||
25 | + const request = axios.post(`${USER_SERVER}/login`, dataToSubmit) | ||
26 | + .then(response => response.data); | ||
34 | 27 | ||
28 | + return { | ||
29 | + type: LOGIN_USER, | ||
30 | + payload: request | ||
31 | + } | ||
32 | +} | ||
35 | 33 | ||
36 | -export function auth(){ | 34 | +export function auth() { |
37 | const request = axios.get(`${USER_SERVER}/auth`) | 35 | const request = axios.get(`${USER_SERVER}/auth`) |
38 | - .then(response => response.data); | 36 | + .then(response => response.data); |
39 | 37 | ||
40 | return { | 38 | return { |
41 | type: AUTH_USER, | 39 | type: AUTH_USER, |
... | @@ -43,9 +41,9 @@ export function auth(){ | ... | @@ -43,9 +41,9 @@ export function auth(){ |
43 | } | 41 | } |
44 | } | 42 | } |
45 | 43 | ||
46 | -export function logoutUser(){ | 44 | +export function logoutUser() { |
47 | const request = axios.get(`${USER_SERVER}/logout`) | 45 | const request = axios.get(`${USER_SERVER}/logout`) |
48 | - .then(response => response.data); | 46 | + .then(response => response.data); |
49 | 47 | ||
50 | return { | 48 | return { |
51 | type: LOGOUT_USER, | 49 | type: LOGOUT_USER, |
... | @@ -53,3 +51,80 @@ export function logoutUser(){ | ... | @@ -53,3 +51,80 @@ export function logoutUser(){ |
53 | } | 51 | } |
54 | } | 52 | } |
55 | 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'; | 1 | +import { combineReducers } from "redux"; |
2 | -import user from './user_reducer'; | 2 | +import user from "./user_reducer"; |
3 | 3 | ||
4 | const rootReducer = combineReducers({ | 4 | const rootReducer = combineReducers({ |
5 | - user, | 5 | + user, |
6 | }); | 6 | }); |
7 | 7 | ||
8 | -export default rootReducer; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
8 | +export default rootReducer; | ... | ... |
... | @@ -3,19 +3,54 @@ import { | ... | @@ -3,19 +3,54 @@ import { |
3 | REGISTER_USER, | 3 | REGISTER_USER, |
4 | AUTH_USER, | 4 | AUTH_USER, |
5 | LOGOUT_USER, | 5 | LOGOUT_USER, |
6 | + ADD_TO_CART_USER, | ||
7 | + GET_CART_ITEMS_USER, | ||
8 | + REMOVE_CART_ITEM_USER, | ||
9 | + ON_SUCCESS_BUY_USER | ||
6 | } from '../_actions/types'; | 10 | } from '../_actions/types'; |
7 | - | ||
8 | 11 | ||
9 | -export default function(state={},action){ | 12 | + |
10 | - switch(action.type){ | 13 | +export default function (state = {}, action) { |
14 | + switch (action.type) { | ||
11 | case REGISTER_USER: | 15 | case REGISTER_USER: |
12 | - return {...state, register: action.payload } | 16 | + return { ...state, register: action.payload } |
13 | case LOGIN_USER: | 17 | case LOGIN_USER: |
14 | return { ...state, loginSucces: action.payload } | 18 | return { ...state, loginSucces: action.payload } |
15 | case AUTH_USER: | 19 | case AUTH_USER: |
16 | - return {...state, userData: action.payload } | 20 | + return { ...state, userData: action.payload } |
17 | case LOGOUT_USER: | 21 | case LOGOUT_USER: |
18 | - return {...state } | 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 | + | ||
19 | default: | 54 | default: |
20 | return state; | 55 | return state; |
21 | } | 56 | } | ... | ... |
1 | -import React, { Suspense } from 'react'; | 1 | +import React, { Suspense } from "react"; |
2 | import { Route, Switch } from "react-router-dom"; | 2 | import { Route, Switch } from "react-router-dom"; |
3 | import Auth from "../hoc/auth"; | 3 | import Auth from "../hoc/auth"; |
4 | // pages for this product | 4 | // pages for this product |
5 | import LandingPage from "./views/LandingPage/LandingPage.js"; | 5 | import LandingPage from "./views/LandingPage/LandingPage.js"; |
6 | import LoginPage from "./views/LoginPage/LoginPage.js"; | 6 | import LoginPage from "./views/LoginPage/LoginPage.js"; |
7 | import RegisterPage from "./views/RegisterPage/RegisterPage.js"; | 7 | import RegisterPage from "./views/RegisterPage/RegisterPage.js"; |
8 | -import UploadPage from './views/UploadPage/UploadPage'; | ||
9 | import NavBar from "./views/NavBar/NavBar"; | 8 | import NavBar from "./views/NavBar/NavBar"; |
10 | -import Footer from "./views/Footer/Footer" | 9 | +import Footer from "./views/Footer/Footer"; |
11 | - | 10 | +import UploadProductPage from "./views/UploadProductPage/UploadProductPage"; |
12 | -//null Anyone Can go inside | 11 | +import DetailProductPage from "./views/DetailProductPage/DetailProductPage"; |
13 | -//true only logged in user can go inside | 12 | +import CartPage from "./views/CartPage/CartPage"; |
14 | -//false logged in user can't go inside | 13 | +import HistoryPage from "./views/HistoryPage/HistoryPage"; |
14 | +import adminPage from "./views/adminPage/adminPage"; | ||
15 | 15 | ||
16 | function App() { | 16 | function App() { |
17 | return ( | 17 | return ( |
18 | - <Suspense fallback={(<div>Loading...</div>)}> | 18 | + <Suspense fallback={<div>Loading...</div>}> |
19 | <NavBar /> | 19 | <NavBar /> |
20 | - <div style={{ paddingTop: '69px', minHeight: 'calc(100vh - 80px)' }}> | 20 | + <div style={{ paddingTop: "75px", minHeight: "calc(100vh - 80px)" }}> |
21 | <Switch> | 21 | <Switch> |
22 | <Route exact path="/" component={Auth(LandingPage, null)} /> | 22 | <Route exact path="/" component={Auth(LandingPage, null)} /> |
23 | <Route exact path="/login" component={Auth(LoginPage, false)} /> | 23 | <Route exact path="/login" component={Auth(LoginPage, false)} /> |
24 | <Route exact path="/register" component={Auth(RegisterPage, false)} /> | 24 | <Route exact path="/register" component={Auth(RegisterPage, false)} /> |
25 | - <Route exact path="/upload" component={Auth(UploadPage, true)} /> | 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)} /> | ||
26 | </Switch> | 30 | </Switch> |
27 | </div> | 31 | </div> |
28 | <Footer /> | 32 | <Footer /> | ... | ... |
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 | +} |
... | @@ -2,6 +2,7 @@ import React from 'react' | ... | @@ -2,6 +2,7 @@ import React from 'react' |
2 | import Dropzone from 'react-dropzone' | 2 | import Dropzone from 'react-dropzone' |
3 | import axios from 'axios'; | 3 | import axios from 'axios'; |
4 | import { useState } from 'react'; | 4 | import { useState } from 'react'; |
5 | +import './ImageUpload.css' | ||
5 | 6 | ||
6 | function ImageUpload(props) { | 7 | function ImageUpload(props) { |
7 | 8 | ||
... | @@ -51,13 +52,14 @@ function ImageUpload(props) { | ... | @@ -51,13 +52,14 @@ function ImageUpload(props) { |
51 | <Dropzone onDrop={imageDropEvent}> | 52 | <Dropzone onDrop={imageDropEvent}> |
52 | {({getRootProps, getInputProps}) => ( | 53 | {({getRootProps, getInputProps}) => ( |
53 | <section> | 54 | <section> |
55 | + {/* Dropzone */} | ||
54 | <div style={{ | 56 | <div style={{ |
55 | width: 300, height: 200, border: '1px solid lightgray', borderRadius: '1em', display: 'flex', | 57 | width: 300, height: 200, border: '1px solid lightgray', borderRadius: '1em', display: 'flex', |
56 | alignItems: 'center', textAlign: 'center', justifyContent: 'center' | 58 | alignItems: 'center', textAlign: 'center', justifyContent: 'center' |
57 | }} | 59 | }} |
58 | {...getRootProps()}> | 60 | {...getRootProps()}> |
59 | <input {...getInputProps()} /> | 61 | <input {...getInputProps()} /> |
60 | - <p>이곳을 클릭하여 <br/> 상품 사진을 업로드 해주세요.</p> | 62 | + <p>이곳을 클릭하여<br/>상품 사진을 업로드 해주세요.</p> |
61 | </div> | 63 | </div> |
62 | </section> | 64 | </section> |
63 | )} | 65 | )} |
... | @@ -72,11 +74,10 @@ function ImageUpload(props) { | ... | @@ -72,11 +74,10 @@ function ImageUpload(props) { |
72 | {Images.map((image, index) => ( | 74 | {Images.map((image, index) => ( |
73 | 75 | ||
74 | <div onClick={ () => deleteEvent(image) } | 76 | <div onClick={ () => deleteEvent(image) } |
75 | - key={index}> | 77 | + key={index}> |
76 | - <img style={{ | 78 | + <img id="test" src={`http://localhost:5000/${image}`} /> |
77 | - width: '300px', height: '200px', border: '1px solid lightgray', | 79 | + <div id="alert">클릭하면 삭제돼요!</div> |
78 | - borderRadius: '1em', display: 'flex'}} | 80 | + |
79 | - src={`http://localhost:5000/${image}`} /> | ||
80 | </div> | 81 | </div> |
81 | ))} | 82 | ))} |
82 | </div> | 83 | </div> | ... | ... |
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' | 1 | +import React from "react"; |
2 | -import {Icon} from 'antd'; | 2 | +import { Icon } from "antd"; |
3 | 3 | ||
4 | function Footer() { | 4 | function Footer() { |
5 | - return ( | 5 | + return ( |
6 | - <div style={{ | 6 | + <div |
7 | - height: '80px', display: 'flex', | 7 | + style={{ |
8 | - flexDirection: 'column', alignItems: 'center', | 8 | + height: "80px", |
9 | - justifyContent: 'center', fontSize:'1rem' | 9 | + display: "flex", |
10 | - }}> | 10 | + flexDirection: "column", |
11 | - <p> 2018110650 박민정</p> | 11 | + alignItems: "center", |
12 | - </div> | 12 | + justifyContent: "center", |
13 | - ) | 13 | + fontSize: "1rem", |
14 | + }} | ||
15 | + > | ||
16 | + <p> Copyright © 2021 Minjeong Park All rights reserved</p> | ||
17 | + </div> | ||
18 | + ); | ||
14 | } | 19 | } |
15 | 20 | ||
16 | -export default Footer | 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 from 'react' | 1 | +import React, { useEffect, useState } from "react"; |
2 | -import { FaCode } from "react-icons/fa"; | 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; | ||
3 | 11 | ||
4 | function LandingPage() { | 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) => { | ||
5 | return ( | 63 | return ( |
6 | - <> | 64 | + <Col style={{ marginTop: "30px" }} lg={6} md={8} xs={24}> |
7 | - <div className="app"> | 65 | + <Card |
8 | - 하.. | 66 | + style={{ margin: "5px" }} |
9 | - </div> | 67 | + hoverable={true} |
10 | - | 68 | + cover={ |
11 | - </> | 69 | + <a href={`/product/${product._id}`}> |
12 | - ) | 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 | + 어떤 약이 필요하신가요? <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 | + ); | ||
13 | } | 171 | } |
14 | 172 | ||
15 | -export default LandingPage | 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 | + /> | ||
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, { 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"; | 1 | import React, { useState } from "react"; |
2 | import { withRouter } from "react-router-dom"; | 2 | import { withRouter } from "react-router-dom"; |
3 | import { loginUser } from "../../../_actions/user_actions"; | 3 | import { loginUser } from "../../../_actions/user_actions"; |
4 | -import { Formik } from 'formik'; | 4 | +import { Formik } from "formik"; |
5 | -import * as Yup from 'yup'; | 5 | +import * as Yup from "yup"; |
6 | -import { Form, Icon, Input, Button, Checkbox, Typography } from 'antd'; | 6 | +import { Form, Icon, Input, Button, Checkbox, Typography } from "antd"; |
7 | import { useDispatch } from "react-redux"; | 7 | import { useDispatch } from "react-redux"; |
8 | 8 | ||
9 | const { Title } = Typography; | 9 | const { Title } = Typography; |
10 | 10 | ||
11 | function LoginPage(props) { | 11 | function LoginPage(props) { |
12 | const dispatch = useDispatch(); | 12 | const dispatch = useDispatch(); |
13 | - //const rememberMeChecked = localStorage.getItem("rememberMe") ? true : false; | 13 | + const rememberMeChecked = localStorage.getItem("rememberMe") ? true : false; |
14 | 14 | ||
15 | - const [Error, setError] = useState('') | 15 | + const [formErrorMessage, setFormErrorMessage] = useState(""); |
16 | - //const [rememberMe, setRememberMe] = useState(rememberMeChecked) | 16 | + const [rememberMe, setRememberMe] = useState(rememberMeChecked); |
17 | 17 | ||
18 | - // const handleRememberMe = () => { | 18 | + const handleRememberMe = () => { |
19 | - // setRememberMe(!rememberMe) | 19 | + setRememberMe(!rememberMe); |
20 | - // }; | 20 | + }; |
21 | 21 | ||
22 | - //const initialEmail = localStorage.getItem("rememberMe") ? localStorage.getItem("rememberMe") : ''; | ||
23 | 22 | ||
24 | return ( | 23 | return ( |
25 | <Formik | 24 | <Formik |
26 | initialValues={{ | 25 | initialValues={{ |
27 | - email: '', | 26 | + email: "", |
28 | - password: '', | 27 | + password: "", |
29 | }} | 28 | }} |
30 | - // validationSchema={Yup.object().shape({ | 29 | + validationSchema={Yup.object().shape({ |
31 | - // email: Yup.string() | 30 | + email: Yup.string().email("이메일이 유효하지 않습니다.").required("이메일을 입력해주세요."), |
32 | - // .email('Email is invalid') | 31 | + password: Yup.string().min(5, "비밀번호가 너무 짧습니다.").required("비밀번호를 입력해주세요."), |
33 | - // .required('Email is required'), | 32 | + })} |
34 | - // password: Yup.string() | ||
35 | - // .min(6, 'Password must be at least 6 characters') | ||
36 | - // .required('Password is required'), | ||
37 | - // })} | ||
38 | onSubmit={(values, { setSubmitting }) => { | 33 | onSubmit={(values, { setSubmitting }) => { |
39 | setTimeout(() => { | 34 | setTimeout(() => { |
40 | let dataToSubmit = { | 35 | let dataToSubmit = { |
41 | email: values.email, | 36 | email: values.email, |
42 | - password: values.password | 37 | + password: values.password, |
43 | }; | 38 | }; |
44 | 39 | ||
45 | dispatch(loginUser(dataToSubmit)) | 40 | dispatch(loginUser(dataToSubmit)) |
46 | .then(response => { | 41 | .then(response => { |
47 | if (response.payload.loginSuccess) { | 42 | if (response.payload.loginSuccess) { |
48 | - window.localStorage.setItem('userId', response.payload.userId); | 43 | + window.localStorage.setItem("userId", response.payload.userId); |
49 | - // if (rememberMe === true) { | 44 | + if (rememberMe === true) { |
50 | - // window.localStorage.setItem('rememberMe', values.id); | 45 | + window.localStorage.setItem("rememberMe", values.id); |
51 | - // } else { | 46 | + } else { |
52 | - // localStorage.removeItem('rememberMe'); | 47 | + localStorage.removeItem("rememberMe"); |
53 | - // } | 48 | + } |
54 | props.history.push("/"); | 49 | props.history.push("/"); |
55 | } else { | 50 | } else { |
56 | - setError('이메일 또는 비밀번호가 올바르지 않습니다.') | 51 | + setFormErrorMessage("Check out your Account or Password again"); |
57 | } | 52 | } |
58 | }) | 53 | }) |
59 | .catch(err => { | 54 | .catch(err => { |
60 | - setError('이메일 또는 비밀번호가 올바르지 않습니다.') | 55 | + setFormErrorMessage("Check out your Account or Password again"); |
61 | setTimeout(() => { | 56 | setTimeout(() => { |
62 | - setError("") | 57 | + setFormErrorMessage(""); |
63 | }, 3000); | 58 | }, 3000); |
64 | }); | 59 | }); |
65 | setSubmitting(false); | 60 | setSubmitting(false); |
... | @@ -67,74 +62,51 @@ function LoginPage(props) { | ... | @@ -67,74 +62,51 @@ function LoginPage(props) { |
67 | }} | 62 | }} |
68 | > | 63 | > |
69 | {props => { | 64 | {props => { |
70 | - const { | 65 | + const { values, touched, errors, dirty, isSubmitting, handleChange, handleBlur, handleSubmit, handleReset } = props; |
71 | - values, | ||
72 | - touched, | ||
73 | - errors, | ||
74 | - dirty, | ||
75 | - isSubmitting, | ||
76 | - handleChange, | ||
77 | - handleBlur, | ||
78 | - handleSubmit, | ||
79 | - handleReset, | ||
80 | - } = props; | ||
81 | return ( | 66 | return ( |
82 | <div className="app"> | 67 | <div className="app"> |
83 | - | ||
84 | <Title level={2}>로그인</Title> | 68 | <Title level={2}>로그인</Title> |
85 | - <form onSubmit={handleSubmit} style={{ width: '350px' }}> | 69 | + <form onSubmit={handleSubmit} style={{ width: "350px" }}> |
86 | - | ||
87 | <Form.Item required> | 70 | <Form.Item required> |
88 | <Input | 71 | <Input |
89 | id="email" | 72 | id="email" |
90 | - prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} | 73 | + prefix={<Icon type="user" style={{ color: "rgba(0,0,0,.25)" }} />} |
91 | placeholder="Enter your email" | 74 | placeholder="Enter your email" |
92 | type="email" | 75 | type="email" |
93 | value={values.email} | 76 | value={values.email} |
94 | onChange={handleChange} | 77 | onChange={handleChange} |
95 | onBlur={handleBlur} | 78 | onBlur={handleBlur} |
96 | - className={ | 79 | + className={errors.email && touched.email ? "text-input error" : "text-input"} |
97 | - errors.email && touched.email ? 'text-input error' : 'text-input' | ||
98 | - } | ||
99 | /> | 80 | /> |
100 | - {errors.email && touched.email && ( | 81 | + {errors.email && touched.email && <div className="input-feedback">{errors.email}</div>} |
101 | - <div className="input-feedback">{errors.email}</div> | ||
102 | - )} | ||
103 | </Form.Item> | 82 | </Form.Item> |
104 | 83 | ||
105 | <Form.Item required> | 84 | <Form.Item required> |
106 | <Input | 85 | <Input |
107 | id="password" | 86 | id="password" |
108 | - prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} | 87 | + prefix={<Icon type="lock" style={{ color: "rgba(0,0,0,.25)" }} />} |
109 | placeholder="Enter your password" | 88 | placeholder="Enter your password" |
110 | type="password" | 89 | type="password" |
111 | value={values.password} | 90 | value={values.password} |
112 | onChange={handleChange} | 91 | onChange={handleChange} |
113 | onBlur={handleBlur} | 92 | onBlur={handleBlur} |
114 | - className={ | 93 | + className={errors.password && touched.password ? "text-input error" : "text-input"} |
115 | - errors.password && touched.password ? 'text-input error' : 'text-input' | ||
116 | - } | ||
117 | /> | 94 | /> |
118 | - {errors.password && touched.password && ( | 95 | + {errors.password && touched.password && <div className="input-feedback">{errors.password}</div>} |
119 | - <div className="input-feedback">{errors.password}</div> | ||
120 | - )} | ||
121 | </Form.Item> | 96 | </Form.Item> |
122 | 97 | ||
123 | - {Error && ( | 98 | + {formErrorMessage && ( |
124 | - <label ><p style={{ color: '#508DFF', fontSize: '0.8rem' }}>{Error}</p></label> | 99 | + <label> |
100 | + <p style={{ color: "#ff0000bf", fontSize: "0.7rem", border: "1px solid", padding: "1rem", borderRadius: "10px" }}>{formErrorMessage}</p> | ||
101 | + </label> | ||
125 | )} | 102 | )} |
126 | 103 | ||
127 | <Form.Item> | 104 | <Form.Item> |
128 | - {/* <Checkbox id="rememberMe" onChange={handleRememberMe} checked={rememberMe} >Remember me</Checkbox> | ||
129 | - <a className="login-form-forgot" href="/reset_user" style={{ float: 'right' }}> | ||
130 | - forgot password | ||
131 | - </a> */} | ||
132 | <div> | 105 | <div> |
133 | - <Button type="primary" htmlType="submit" className="login-form-button" style={{ minWidth: '100%' }} disabled={isSubmitting} onSubmit={handleSubmit}> | 106 | + <Button type="primary" htmlType="submit" className="login-form-button" style={{ minWidth: "100%" }} disabled={isSubmitting} onSubmit={handleSubmit}> |
134 | 로그인 | 107 | 로그인 |
135 | - </Button> | 108 | + </Button> |
136 | </div> | 109 | </div> |
137 | - <a href="/register" style={{ minWidth: '100%' }}>회원가입</a> | ||
138 | </Form.Item> | 110 | </Form.Item> |
139 | </form> | 111 | </form> |
140 | </div> | 112 | </div> |
... | @@ -142,8 +114,6 @@ function LoginPage(props) { | ... | @@ -142,8 +114,6 @@ function LoginPage(props) { |
142 | }} | 114 | }} |
143 | </Formik> | 115 | </Formik> |
144 | ); | 116 | ); |
145 | -}; | 117 | +} |
146 | 118 | ||
147 | export default withRouter(LoginPage); | 119 | export default withRouter(LoginPage); |
148 | - | ||
149 | - | ... | ... |
1 | -import React, { useState } from 'react'; | 1 | +import React, { useState } from "react"; |
2 | -import LeftMenu from './Sections/LeftMenu'; | 2 | +import RightMenu from "./Sections/RightMenu"; |
3 | -import RightMenu from './Sections/RightMenu'; | 3 | +import { Drawer, Button, Icon } from "antd"; |
4 | -import { Drawer, Button, Icon } from 'antd'; | 4 | +import "./Sections/Navbar.css"; |
5 | -import './Sections/Navbar.css'; | 5 | +import { Container, Navbar, Nav } from "react-bootstrap"; |
6 | 6 | ||
7 | function NavBar() { | 7 | function NavBar() { |
8 | - const [visible, setVisible] = useState(false) | 8 | + const [visible, setVisible] = useState(false); |
9 | 9 | ||
10 | const showDrawer = () => { | 10 | const showDrawer = () => { |
11 | - setVisible(true) | 11 | + setVisible(true); |
12 | }; | 12 | }; |
13 | 13 | ||
14 | const onClose = () => { | 14 | const onClose = () => { |
15 | - setVisible(false) | 15 | + setVisible(false); |
16 | }; | 16 | }; |
17 | 17 | ||
18 | return ( | 18 | return ( |
19 | - <nav className="menu" style={{ position: 'fixed', zIndex: 5, width: '100%' }}> | 19 | + <Navbar bg="white" variant="light" style={{ position: "fixed", zIndex: 5, width: "100%", border: "1px solid lightgray" }}> |
20 | - <div className="menu__logo"> | 20 | + <Container> |
21 | - <a href="/">Logo</a> | 21 | + <Navbar.Brand href="/"> |
22 | - </div> | 22 | + <img src={"/logo.png"} style={{ width: "120px", marginLeft:"50px"}} /> |
23 | - <div className="menu__container"> | 23 | + </Navbar.Brand> |
24 | - <div className="menu_left"> | 24 | + <Nav className="mx-3" style={{ position: "relative", right: "0" }}> |
25 | - <LeftMenu mode="horizontal" /> | 25 | + <div className="menu__container"> |
26 | - </div> | 26 | + <div className="menu_left"></div> |
27 | - <div className="menu_rigth"> | 27 | + <div className="menu_rigth"> |
28 | - <RightMenu mode="horizontal" /> | 28 | + <RightMenu mode="horizontal" /> |
29 | - </div> | 29 | + </div> |
30 | - <Button | 30 | + <Button className="menu__mobile-button" type="primary" onClick={showDrawer}> |
31 | - className="menu__mobile-button" | 31 | + <Icon type="align-right" /> |
32 | - type="primary" | 32 | + </Button> |
33 | - onClick={showDrawer} | 33 | + <Drawer title="Basic Drawer" placement="right" className="menu_drawer" closable={false} onClose={onClose} visible={visible}> |
34 | - > | 34 | + |
35 | - <Icon type="align-right" /> | 35 | + <RightMenu mode="inline" /> |
36 | - </Button> | 36 | + </Drawer> |
37 | - <Drawer | 37 | + </div> |
38 | - title="Basic Drawer" | 38 | + </Nav> |
39 | - placement="right" | 39 | + </Container> |
40 | - className="menu_drawer" | 40 | + </Navbar> |
41 | - closable={false} | 41 | + ); |
42 | - onClose={onClose} | ||
43 | - visible={visible} | ||
44 | - > | ||
45 | - <LeftMenu mode="inline" /> | ||
46 | - <RightMenu mode="inline" /> | ||
47 | - </Drawer> | ||
48 | - </div> | ||
49 | - </nav> | ||
50 | - ) | ||
51 | } | 42 | } |
52 | 43 | ||
53 | -export default NavBar | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
44 | +export default NavBar; | ... | ... |
... | @@ -50,7 +50,7 @@ | ... | @@ -50,7 +50,7 @@ |
50 | padding: 6px; | 50 | padding: 6px; |
51 | margin-top: 8px; | 51 | margin-top: 8px; |
52 | display: none !important; /* use of important to overwrite ant-btn */ | 52 | display: none !important; /* use of important to overwrite ant-btn */ |
53 | - background: #3e91f7; | 53 | + background: lightgray; |
54 | } | 54 | } |
55 | 55 | ||
56 | .menu_drawer .ant-drawer-body { | 56 | .menu_drawer .ant-drawer-body { | ... | ... |
1 | - | 1 | +/* eslint-disable jsx-a11y/anchor-is-valid */ |
2 | -import React from 'react'; | 2 | +import React from "react"; |
3 | -import { Menu } from 'antd'; | 3 | +import { Menu, Icon, Badge } from "antd"; |
4 | -import axios from 'axios'; | 4 | +import axios from "axios"; |
5 | -import { USER_SERVER } from '../../../Config'; | 5 | +import { USER_SERVER } from "../../../Config"; |
6 | -import { withRouter } from 'react-router-dom'; | 6 | +import { withRouter } from "react-router-dom"; |
7 | import { useSelector } from "react-redux"; | 7 | import { useSelector } from "react-redux"; |
8 | 8 | ||
9 | function RightMenu(props) { | 9 | function RightMenu(props) { |
10 | - const user = useSelector(state => state.user) | 10 | + const user = useSelector(state => state.user); |
11 | 11 | ||
12 | const logoutHandler = () => { | 12 | const logoutHandler = () => { |
13 | axios.get(`${USER_SERVER}/logout`).then(response => { | 13 | axios.get(`${USER_SERVER}/logout`).then(response => { |
14 | if (response.status === 200) { | 14 | if (response.status === 200) { |
15 | props.history.push("/login"); | 15 | props.history.push("/login"); |
16 | } else { | 16 | } else { |
17 | - alert('Log Out Failed') | 17 | + alert("로그아웃에 실패했습니다."); |
18 | } | 18 | } |
19 | }); | 19 | }); |
20 | }; | 20 | }; |
21 | 21 | ||
22 | - //console.log(user.userData); | ||
23 | - //console.log(!user.userData.isAuth); | ||
24 | 22 | ||
25 | if (user.userData && !user.userData.isAuth) { | 23 | if (user.userData && !user.userData.isAuth) { |
24 | + | ||
26 | return ( | 25 | return ( |
27 | - <Menu mode={props.mode}> | 26 | + <Menu style={{ paddingTop: "10px" }} mode={props.mode}> |
28 | <Menu.Item key="mail"> | 27 | <Menu.Item key="mail"> |
29 | - <a href="/login">로그인</a> | 28 | + <a href="/login"><h6>로그인</h6></a> |
30 | </Menu.Item> | 29 | </Menu.Item> |
31 | <Menu.Item key="app"> | 30 | <Menu.Item key="app"> |
32 | - <a href="/register">회원가입</a> | 31 | + <a href="/register"><h6>회원가입</h6></a> |
33 | </Menu.Item> | 32 | </Menu.Item> |
34 | </Menu> | 33 | </Menu> |
35 | - ) | 34 | + ); |
36 | } else { | 35 | } else { |
37 | return ( | 36 | return ( |
38 | - <Menu mode={props.mode}> | 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 | + | ||
39 | <Menu.Item key="upload"> | 44 | <Menu.Item key="upload"> |
40 | - <a href="/upload">업로드</a> | 45 | + <a href="/product/upload"> |
46 | + <h6>등록하기</h6> | ||
47 | + </a> | ||
41 | </Menu.Item> | 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 | + | ||
42 | <Menu.Item key="logout"> | 58 | <Menu.Item key="logout"> |
43 | - <a onClick={logoutHandler}>로그아웃</a> | 59 | + <a onClick={logoutHandler}><h6>로그아웃</h6></a> |
44 | </Menu.Item> | 60 | </Menu.Item> |
45 | </Menu> | 61 | </Menu> |
46 | - ) | 62 | + ); |
47 | } | 63 | } |
48 | } | 64 | } |
49 | 65 | ||
50 | export default withRouter(RightMenu); | 66 | export default withRouter(RightMenu); |
51 | - | ... | ... |
1 | import React from "react"; | 1 | import React from "react"; |
2 | import moment from "moment"; | 2 | import moment from "moment"; |
3 | -import { Formik } from 'formik'; | 3 | +import { Formik } from "formik"; |
4 | -import * as Yup from 'yup'; | 4 | +import * as Yup from "yup"; |
5 | import { registerUser } from "../../../_actions/user_actions"; | 5 | import { registerUser } from "../../../_actions/user_actions"; |
6 | import { useDispatch } from "react-redux"; | 6 | import { useDispatch } from "react-redux"; |
7 | 7 | ||
8 | -import { | 8 | +import { Form, Input, Button } from "antd"; |
9 | - Form, | 9 | +const { TextArea } = Input; |
10 | - Input, | ||
11 | - Button, | ||
12 | -} from 'antd'; | ||
13 | - | ||
14 | const formItemLayout = { | 10 | const formItemLayout = { |
15 | labelCol: { | 11 | labelCol: { |
16 | xs: { span: 24 }, | 12 | xs: { span: 24 }, |
... | @@ -37,139 +33,121 @@ const tailFormItemLayout = { | ... | @@ -37,139 +33,121 @@ const tailFormItemLayout = { |
37 | function RegisterPage(props) { | 33 | function RegisterPage(props) { |
38 | const dispatch = useDispatch(); | 34 | const dispatch = useDispatch(); |
39 | return ( | 35 | return ( |
40 | - | ||
41 | <Formik | 36 | <Formik |
42 | initialValues={{ | 37 | initialValues={{ |
43 | - email: '', | 38 | + email: "", |
44 | - | 39 | + name: "", |
45 | - name: '', | 40 | + address: "", |
46 | - password: '', | 41 | + password: "", |
47 | - confirmPassword: '' | 42 | + confirmPassword: "", |
48 | }} | 43 | }} |
49 | validationSchema={Yup.object().shape({ | 44 | validationSchema={Yup.object().shape({ |
50 | - name: Yup.string() | 45 | + name: Yup.string().required("이름을 입력해주세요."), |
51 | - .required('이름이 올바르게 입력되지 않았습니다.'), | 46 | + email: Yup.string().email("이메일 형식이 올바르지 않습니다.").required("이메일을 입력해주세요."), |
52 | - | 47 | + password: Yup.string().min(5, "비밀번호는 5자리 이상이어야 합니다.").required("비밀번호를 입력해주세요."), |
53 | - email: Yup.string() | ||
54 | - .required('이메일이 올바르게 입력되지 않았습니다.'), | ||
55 | - password: Yup.string() | ||
56 | - .min(3, '비밀번호는 3자리 이상이어야 합니다.') | ||
57 | - .required('비밀번호를 입력해주세요.'), | ||
58 | confirmPassword: Yup.string() | 48 | confirmPassword: Yup.string() |
59 | - .oneOf([Yup.ref('password'), null], '비밀번호를 다시 확인해주세요.') | 49 | + .oneOf([Yup.ref("password"), null], "비밀번호가 일치하지 않습니다.") |
60 | - .required('비밀번호 확인이 올바르게 입력되지 않았습니다.') | 50 | + .required("비밀번호를 한번 더 입력해주세요."), |
51 | + adress: Yup.string().required("주소를 입력해주세요."), | ||
61 | })} | 52 | })} |
62 | onSubmit={(values, { setSubmitting }) => { | 53 | onSubmit={(values, { setSubmitting }) => { |
63 | setTimeout(() => { | 54 | setTimeout(() => { |
64 | - | ||
65 | let dataToSubmit = { | 55 | let dataToSubmit = { |
66 | email: values.email, | 56 | email: values.email, |
67 | password: values.password, | 57 | password: values.password, |
68 | name: values.name, | 58 | name: values.name, |
69 | - | 59 | + adress: values.adress, |
70 | - //image: `http://gravatar.com/avatar/${moment().unix()}?d=identicon` | 60 | + image: `http://gravatar.com/avatar/${moment().unix()}?d=identicon`, |
71 | }; | 61 | }; |
72 | 62 | ||
73 | dispatch(registerUser(dataToSubmit)).then(response => { | 63 | dispatch(registerUser(dataToSubmit)).then(response => { |
74 | if (response.payload.success) { | 64 | if (response.payload.success) { |
75 | props.history.push("/login"); | 65 | props.history.push("/login"); |
76 | } else { | 66 | } else { |
77 | - alert(response.payload.err.errmsg) | 67 | + alert(response.payload.err.errmsg); |
78 | } | 68 | } |
79 | - }) | 69 | + }); |
80 | 70 | ||
81 | setSubmitting(false); | 71 | setSubmitting(false); |
82 | }, 500); | 72 | }, 500); |
83 | }} | 73 | }} |
84 | > | 74 | > |
85 | {props => { | 75 | {props => { |
86 | - const { | 76 | + const { values, touched, errors, dirty, isSubmitting, handleChange, handleBlur, handleSubmit, handleReset } = props; |
87 | - values, | ||
88 | - touched, | ||
89 | - errors, | ||
90 | - dirty, | ||
91 | - isSubmitting, | ||
92 | - handleChange, | ||
93 | - handleBlur, | ||
94 | - handleSubmit, | ||
95 | - handleReset, | ||
96 | - } = props; | ||
97 | return ( | 77 | return ( |
98 | <div className="app"> | 78 | <div className="app"> |
99 | - <h2>회원가입</h2> | 79 | + <h3>회원가입</h3> |
100 | - <Form style={{ minWidth: '375px' }} {...formItemLayout} onSubmit={handleSubmit} > | 80 | + <Form style={{ minWidth: "375px" }} {...formItemLayout} onSubmit={handleSubmit}> |
101 | - | 81 | + <Form.Item required label="이름"> |
102 | - <Form.Item required label="Name"> | ||
103 | <Input | 82 | <Input |
104 | id="name" | 83 | id="name" |
105 | - placeholder="이름을 입력하세요." | 84 | + placeholder="이름을 입력하세요" |
106 | type="text" | 85 | type="text" |
107 | value={values.name} | 86 | value={values.name} |
108 | onChange={handleChange} | 87 | onChange={handleChange} |
109 | onBlur={handleBlur} | 88 | onBlur={handleBlur} |
110 | - className={ | 89 | + className={errors.name && touched.name ? "text-input error" : "text-input"} |
111 | - errors.name && touched.name ? 'text-input error' : 'text-input' | ||
112 | - } | ||
113 | /> | 90 | /> |
114 | - {errors.name && touched.name && ( | 91 | + {errors.name && touched.name && <div className="input-feedback">{errors.name}</div>} |
115 | - <div className="input-feedback">{errors.name}</div> | 92 | + </Form.Item> |
116 | - )} | 93 | + |
117 | - </Form.Item> | 94 | + |
118 | 95 | ||
119 | - <Form.Item required label="Email" hasFeedback> | 96 | + <Form.Item required label="이메일"> |
120 | <Input | 97 | <Input |
121 | id="email" | 98 | id="email" |
122 | - placeholder="이메일을 입력하세요." | 99 | + placeholder="이메일을 입력해주세요." |
123 | type="email" | 100 | type="email" |
124 | value={values.email} | 101 | value={values.email} |
125 | onChange={handleChange} | 102 | onChange={handleChange} |
126 | onBlur={handleBlur} | 103 | onBlur={handleBlur} |
127 | - className={ | 104 | + className={errors.email && touched.email ? "text-input error" : "text-input"} |
128 | - errors.email && touched.email ? 'text-input error' : 'text-input' | ||
129 | - } | ||
130 | /> | 105 | /> |
131 | - {errors.email && touched.email && ( | 106 | + {errors.email && touched.email && <div className="input-feedback">{errors.email}</div>} |
132 | - <div className="input-feedback">{errors.email}</div> | ||
133 | - )} | ||
134 | </Form.Item> | 107 | </Form.Item> |
135 | 108 | ||
136 | - <Form.Item required label="Password" hasFeedback> | 109 | + <Form.Item required label="비밀번호"> |
137 | <Input | 110 | <Input |
138 | id="password" | 111 | id="password" |
139 | - placeholder="비밀번호를 입력하세요." | 112 | + placeholder="비밀번호를 입력해주세요." |
140 | type="password" | 113 | type="password" |
141 | value={values.password} | 114 | value={values.password} |
142 | onChange={handleChange} | 115 | onChange={handleChange} |
143 | onBlur={handleBlur} | 116 | onBlur={handleBlur} |
144 | - className={ | 117 | + className={errors.password && touched.password ? "text-input error" : "text-input"} |
145 | - errors.password && touched.password ? 'text-input error' : 'text-input' | ||
146 | - } | ||
147 | /> | 118 | /> |
148 | - {errors.password && touched.password && ( | 119 | + {errors.password && touched.password && <div className="input-feedback">{errors.password}</div>} |
149 | - <div className="input-feedback">{errors.password}</div> | ||
150 | - )} | ||
151 | </Form.Item> | 120 | </Form.Item> |
152 | 121 | ||
153 | - <Form.Item required label="Confirm" hasFeedback> | 122 | + <Form.Item required label="비밀번호 확인"> |
154 | <Input | 123 | <Input |
155 | id="confirmPassword" | 124 | id="confirmPassword" |
156 | - placeholder="비밀번호를 다시 한 번 입력해주세요." | 125 | + placeholder="비밀번호를 한번 더 입력해주세요." |
157 | type="password" | 126 | type="password" |
158 | value={values.confirmPassword} | 127 | value={values.confirmPassword} |
159 | onChange={handleChange} | 128 | onChange={handleChange} |
160 | onBlur={handleBlur} | 129 | onBlur={handleBlur} |
161 | - className={ | 130 | + className={errors.confirmPassword && touched.confirmPassword ? "text-input error" : "text-input"} |
162 | - errors.confirmPassword && touched.confirmPassword ? 'text-input error' : 'text-input' | 131 | + /> |
163 | - } | 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"} | ||
164 | /> | 144 | /> |
165 | - {errors.confirmPassword && touched.confirmPassword && ( | 145 | + {errors.adress && touched.adress && <div className="input-feedback">{errors.adress}</div>} |
166 | - <div className="input-feedback">{errors.confirmPassword}</div> | ||
167 | - )} | ||
168 | </Form.Item> | 146 | </Form.Item> |
169 | 147 | ||
170 | <Form.Item {...tailFormItemLayout}> | 148 | <Form.Item {...tailFormItemLayout}> |
171 | <Button onClick={handleSubmit} type="primary" disabled={isSubmitting}> | 149 | <Button onClick={handleSubmit} type="primary" disabled={isSubmitting}> |
172 | - Submit | 150 | + 가입 |
173 | </Button> | 151 | </Button> |
174 | </Form.Item> | 152 | </Form.Item> |
175 | </Form> | 153 | </Form> |
... | @@ -178,7 +156,6 @@ function RegisterPage(props) { | ... | @@ -178,7 +156,6 @@ function RegisterPage(props) { |
178 | }} | 156 | }} |
179 | </Formik> | 157 | </Formik> |
180 | ); | 158 | ); |
181 | -}; | 159 | +} |
182 | - | ||
183 | 160 | ||
184 | -export default RegisterPage | 161 | +export default RegisterPage; | ... | ... |
... | @@ -2,16 +2,21 @@ import React from 'react'; | ... | @@ -2,16 +2,21 @@ import React from 'react'; |
2 | import { useState } from 'react'; | 2 | import { useState } from 'react'; |
3 | import { Typography, Button, Form, Input } from 'antd'; // css | 3 | import { Typography, Button, Form, Input } from 'antd'; // css |
4 | import ImageUpload from '../../utils/ImageUpload' | 4 | import ImageUpload from '../../utils/ImageUpload' |
5 | +import Axios from 'axios'; | ||
5 | 6 | ||
6 | const { TextArea } = Input; // 박스크기 조절을 사용자가 임의로 가능하게 함. | 7 | const { TextArea } = Input; // 박스크기 조절을 사용자가 임의로 가능하게 함. |
7 | 8 | ||
8 | -// Select Options | 9 | +// Select symtoms |
9 | -const options = [{ key: 1, value: "a" }, | 10 | +const symtoms = [{ key: 1, value: "진통제" }, |
10 | - { key: 2, value: "b" }, | 11 | + { key: 2, value: "소화제" }, |
11 | - {key: 3, value : "c"} | 12 | + { key: 3, value: "감기약" }, |
13 | + { key: 4, value: "해열제" }, | ||
14 | + { key: 5, value: "파스류" }, | ||
15 | + { key: 6, value: "상처치료" }, | ||
16 | + { key: 7, value: "기타" } | ||
12 | ] | 17 | ] |
13 | 18 | ||
14 | -function UploadPage() { | 19 | +function UploadPage(props) { |
15 | 20 | ||
16 | // OnChange Function | 21 | // OnChange Function |
17 | 22 | ||
... | @@ -47,6 +52,35 @@ function UploadPage() { | ... | @@ -47,6 +52,35 @@ function UploadPage() { |
47 | setImage(newImages); | 52 | setImage(newImages); |
48 | } | 53 | } |
49 | 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 | + | ||
50 | 84 | ||
51 | return ( | 85 | return ( |
52 | <div style={{ maxWidth: '700px', margin: '2rem auto' }}> | 86 | <div style={{ maxWidth: '700px', margin: '2rem auto' }}> |
... | @@ -56,7 +90,7 @@ function UploadPage() { | ... | @@ -56,7 +90,7 @@ function UploadPage() { |
56 | 90 | ||
57 | </div> | 91 | </div> |
58 | 92 | ||
59 | - <Form> | 93 | + <Form onSubmit={submitEvent}> |
60 | {/* 파일업로드 부분은 코드가 길어서 따로 컴포넌트로 만들어버리기~! */} | 94 | {/* 파일업로드 부분은 코드가 길어서 따로 컴포넌트로 만들어버리기~! */} |
61 | <ImageUpload refreshFunction={updateImages}/> | 95 | <ImageUpload refreshFunction={updateImages}/> |
62 | <br /> | 96 | <br /> |
... | @@ -75,14 +109,14 @@ function UploadPage() { | ... | @@ -75,14 +109,14 @@ function UploadPage() { |
75 | <br /> | 109 | <br /> |
76 | <br /> | 110 | <br /> |
77 | <select onChange={optionEvent} value={ Option}> | 111 | <select onChange={optionEvent} value={ Option}> |
78 | - {options.map(item => ( | 112 | + {symtoms.map(item => ( |
79 | <option key={item.key} value={item.key}>{ item.value}</option> | 113 | <option key={item.key} value={item.key}>{ item.value}</option> |
80 | ))} | 114 | ))} |
81 | <option></option> | 115 | <option></option> |
82 | </select> | 116 | </select> |
83 | <br /> | 117 | <br /> |
84 | <br /> | 118 | <br /> |
85 | - <Button>확인</Button> | 119 | + <Button onClick={submitEvent}>확인</Button> |
86 | 120 | ||
87 | </Form> | 121 | </Form> |
88 | 122 | ||
... | @@ -90,4 +124,4 @@ function UploadPage() { | ... | @@ -90,4 +124,4 @@ function UploadPage() { |
90 | ) | 124 | ) |
91 | } | 125 | } |
92 | 126 | ||
93 | -export default UploadPage | 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 | -/* eslint-disable react-hooks/exhaustive-deps */ | ||
2 | import React, { useEffect } from 'react'; | 1 | import React, { useEffect } from 'react'; |
3 | import { auth } from '../_actions/user_actions'; | 2 | import { auth } from '../_actions/user_actions'; |
4 | import { useSelector, useDispatch } from "react-redux"; | 3 | import { useSelector, useDispatch } from "react-redux"; |
5 | 4 | ||
6 | -export default function (SpecificComponent, option, adminRoute = null) { | 5 | +export default function (ComposedClass, reload, adminRoute = null) { |
7 | function AuthenticationCheck(props) { | 6 | function AuthenticationCheck(props) { |
8 | 7 | ||
9 | let user = useSelector(state => state.user); | 8 | let user = useSelector(state => state.user); |
10 | const dispatch = useDispatch(); | 9 | const dispatch = useDispatch(); |
11 | 10 | ||
12 | useEffect(() => { | 11 | useEffect(() => { |
13 | - //To know my current status, send Auth request | 12 | + |
14 | - dispatch(auth()).then(response => { | 13 | + dispatch(auth()).then(async response => { |
15 | - //Not Loggined in Status | 14 | + if (await !response.payload.isAuth) { |
16 | - if (!response.payload.isAuth) { | 15 | + if (reload) { |
17 | - if (option) { | ||
18 | props.history.push('/login') | 16 | props.history.push('/login') |
19 | } | 17 | } |
20 | - //Loggined in Status | ||
21 | } else { | 18 | } else { |
22 | - //supposed to be Admin page, but not admin person wants to go inside | ||
23 | if (adminRoute && !response.payload.isAdmin) { | 19 | if (adminRoute && !response.payload.isAdmin) { |
24 | props.history.push('/') | 20 | props.history.push('/') |
25 | } | 21 | } |
26 | - //Logged in Status, but Try to go into log in page | ||
27 | else { | 22 | else { |
28 | - if (option === false) { | 23 | + if (reload === false) { |
29 | props.history.push('/') | 24 | props.history.push('/') |
30 | } | 25 | } |
31 | } | 26 | } |
32 | } | 27 | } |
33 | }) | 28 | }) |
34 | - | 29 | + |
35 | - }, []) | 30 | + }, [dispatch, props.history, user.googleAuth]) |
36 | 31 | ||
37 | return ( | 32 | return ( |
38 | - <SpecificComponent {...props} user={user} /> | 33 | + <ComposedClass {...props} user={user} /> |
39 | ) | 34 | ) |
40 | } | 35 | } |
41 | return AuthenticationCheck | 36 | return AuthenticationCheck | ... | ... |
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 | + | ||
1 | body { | 26 | body { |
2 | margin: 0; | 27 | margin: 0; |
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", | 28 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", |
... | @@ -38,3 +63,28 @@ input.error { | ... | @@ -38,3 +63,28 @@ input.error { |
38 | height: 5px; | 63 | height: 5px; |
39 | margin-top: -12px; | 64 | margin-top: -12px; |
40 | } | 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-app-polyfill/ie9'; | 1 | +import React from "react"; |
2 | -import 'react-app-polyfill/ie11'; | 2 | +import ReactDOM from "react-dom"; |
3 | -import 'core-js'; | 3 | +import "./index.css"; |
4 | - | 4 | +import App from "./components/App"; |
5 | -import React from 'react'; | 5 | +import * as serviceWorker from "./serviceWorker"; |
6 | -import ReactDOM from 'react-dom'; | ||
7 | -import './index.css'; | ||
8 | -import App from './components/App'; | ||
9 | -import * as serviceWorker from './serviceWorker'; | ||
10 | import { BrowserRouter } from "react-router-dom"; | 6 | import { BrowserRouter } from "react-router-dom"; |
11 | 7 | ||
12 | -import Reducer from './_reducers'; | 8 | +import Reducer from "./_reducers"; |
13 | -import { Provider } from 'react-redux'; | 9 | +import { Provider } from "react-redux"; |
14 | -import { createStore, applyMiddleware } from 'redux'; | 10 | +import { createStore, applyMiddleware } from "redux"; |
15 | -import promiseMiddleware from 'redux-promise'; | 11 | +import promiseMiddleware from "redux-promise"; |
16 | -import ReduxThunk from 'redux-thunk'; | 12 | +import ReduxThunk from "redux-thunk"; |
17 | 13 | ||
18 | const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore); | 14 | const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore); |
19 | 15 | ||
20 | ReactDOM.render( | 16 | ReactDOM.render( |
21 | - <Provider | 17 | + <Provider store={createStoreWithMiddleware(Reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())}> |
22 | - store={createStoreWithMiddleware( | 18 | + <BrowserRouter> |
23 | - Reducer, | 19 | + <App /> |
24 | - window.__REDUX_DEVTOOLS_EXTENSION__ && | 20 | + </BrowserRouter> |
25 | - window.__REDUX_DEVTOOLS_EXTENSION__() | 21 | + </Provider>, |
26 | - )} | 22 | + document.getElementById("root") |
27 | - > | 23 | +); |
28 | - <BrowserRouter> | ||
29 | - <App /> | ||
30 | - </BrowserRouter> | ||
31 | - </Provider> | ||
32 | - , document.getElementById('root')); | ||
33 | // If you want your app to work offline and load faster, you can change | 24 | // If you want your app to work offline and load faster, you can change |
34 | // unregister() to register() below. Note this comes with some pitfalls. | 25 | // unregister() to register() below. Note this comes with some pitfalls. |
35 | // Learn more about service workers: https://bit.ly/CRA-PWA | 26 | // Learn more about service workers: https://bit.ly/CRA-PWA | ... | ... |
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
... | @@ -10,25 +10,24 @@ | ... | @@ -10,25 +10,24 @@ |
10 | "scripts": { | 10 | "scripts": { |
11 | "start": "node server/index.js", | 11 | "start": "node server/index.js", |
12 | "backend": "nodemon server/index.js", | 12 | "backend": "nodemon server/index.js", |
13 | - "frontend": "npm run start --prefix client", | 13 | + "frontend": "npm run front --prefix client", |
14 | "dev": "concurrently \"npm run backend\" \"npm run start --prefix client\"" | 14 | "dev": "concurrently \"npm run backend\" \"npm run start --prefix client\"" |
15 | }, | 15 | }, |
16 | "author": "John ahn", | 16 | "author": "John ahn", |
17 | "license": "ISC", | 17 | "license": "ISC", |
18 | "dependencies": { | 18 | "dependencies": { |
19 | + "async": "^3.1.0", | ||
19 | "bcrypt": "^3.0.6", | 20 | "bcrypt": "^3.0.6", |
20 | "body-parser": "^1.18.3", | 21 | "body-parser": "^1.18.3", |
21 | "cookie-parser": "^1.4.3", | 22 | "cookie-parser": "^1.4.3", |
22 | "cors": "^2.8.5", | 23 | "cors": "^2.8.5", |
23 | - "debug": "^4.1.1", | ||
24 | "express": "^4.17.1", | 24 | "express": "^4.17.1", |
25 | "jsonwebtoken": "^8.5.1", | 25 | "jsonwebtoken": "^8.5.1", |
26 | "moment": "^2.24.0", | 26 | "moment": "^2.24.0", |
27 | "mongoose": "^5.4.20", | 27 | "mongoose": "^5.4.20", |
28 | "multer": "^1.4.2", | 28 | "multer": "^1.4.2", |
29 | "react-redux": "^5.0.7", | 29 | "react-redux": "^5.0.7", |
30 | - "saslprep": "^1.0.3", | 30 | + "socket.io": "^2.2.0" |
31 | - "supports-color": "^7.1.0" | ||
32 | }, | 31 | }, |
33 | "devDependencies": { | 32 | "devDependencies": { |
34 | "concurrently": "^4.1.0", | 33 | "concurrently": "^4.1.0", | ... | ... |
We-Shop/server/config/.gitignore
0 → 100644
1 | +dev.js | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | -// development모드이면 process.env.NODE_ENV는 development라고 나오고 | 1 | +if (process.env.NODE_ENV === 'production') { |
2 | -// production모드이면 production이라고 나옴 | 2 | + module.exports = require('./prod'); |
3 | -if(process.env.NODE_ENV === 'production') // === 는 형변환을 하지 않음. | 3 | +} else { |
4 | - // ex) true == 1 -> return true | 4 | + module.exports = require('./dev'); |
5 | - // true === 1 -> return false | ||
6 | -{ | ||
7 | - module.exports = require('./prod') | ||
8 | -} | ||
9 | - | ||
10 | -else | ||
11 | -{ | ||
12 | - module.exports = require('./dev') | ||
13 | } | 5 | } |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | -const express = require("express") | 1 | +const express = require("express"); |
2 | -const app = express() | 2 | +const app = express(); |
3 | -const port = process.env.PORT || 5000 | ||
4 | - | ||
5 | const path = require("path"); | 3 | const path = require("path"); |
6 | const cors = require('cors') | 4 | const cors = require('cors') |
7 | 5 | ||
8 | -// body-parser 가져옴 | 6 | +const bodyParser = require("body-parser"); |
9 | -const bodyParser = require('body-parser') | ||
10 | -// bodyParser option | ||
11 | -app.use(bodyParser.urlencoded({ extended: true })) //application/x-www-form-urlencoded로 된 데이터를 분석해서 가져옴 | ||
12 | -app.use(bodyParser.json()) // application/json 타입으로 된 데이터를 분석해서 가져옴 | ||
13 | const cookieParser = require("cookie-parser"); | 7 | const cookieParser = require("cookie-parser"); |
14 | -app.use(cookieParser()); | 8 | + |
15 | const config = require("./config/key"); | 9 | const config = require("./config/key"); |
16 | 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)); | ||
17 | 16 | ||
18 | const mongoose = require("mongoose"); | 17 | const mongoose = require("mongoose"); |
19 | - | 18 | +const connect = mongoose.connect(config.mongoURI, { useNewUrlParser: true, useUnifiedTopology: true }) |
20 | - | 19 | + .then(() => console.log('MongoDB Connected...')) |
21 | -const connect = mongoose.connect(config.mongoURI, | ||
22 | - { | ||
23 | - useNewUrlParser: true, useUnifiedTopology: true, | ||
24 | - useCreateIndex: true, useFindAndModify: false | ||
25 | - }) | ||
26 | - .then(() => console.log('MongoDB ---> Connected')) | ||
27 | .catch(err => console.log(err)); | 20 | .catch(err => console.log(err)); |
28 | 21 | ||
29 | app.use(cors()) | 22 | app.use(cors()) |
30 | 23 | ||
24 | +app.use(bodyParser.urlencoded({ extended: true })); | ||
25 | +app.use(bodyParser.json()); | ||
26 | +app.use(cookieParser()); | ||
31 | 27 | ||
32 | app.use('/api/users', require('./routes/users')); | 28 | app.use('/api/users', require('./routes/users')); |
33 | app.use('/api/product', require('./routes/product')); | 29 | app.use('/api/product', require('./routes/product')); |
34 | 30 | ||
35 | -// 업로드 하려고 | 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 | ||
36 | app.use('/uploads', express.static('uploads')); | 34 | app.use('/uploads', express.static('uploads')); |
37 | 35 | ||
36 | +// Serve static assets if in production | ||
38 | if (process.env.NODE_ENV === "production") { | 37 | if (process.env.NODE_ENV === "production") { |
38 | + | ||
39 | + // Set static folder | ||
39 | app.use(express.static("client/build")); | 40 | app.use(express.static("client/build")); |
41 | + | ||
42 | + // index.html for all page routes | ||
40 | app.get("*", (req, res) => { | 43 | app.get("*", (req, res) => { |
41 | res.sendFile(path.resolve(__dirname, "../client", "build", "index.html")); | 44 | res.sendFile(path.resolve(__dirname, "../client", "build", "index.html")); |
42 | }); | 45 | }); |
43 | } | 46 | } |
44 | 47 | ||
48 | +const port = process.env.PORT || 5000 | ||
45 | 49 | ||
46 | app.listen(port, () => { | 50 | app.listen(port, () => { |
47 | - console.log(`Server ---> http://localhost:${port}`) | 51 | + console.log(`Server Running at ${port}`) |
48 | -}); | 52 | +}); |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | const { User } = require('../models/User'); | 1 | const { User } = require('../models/User'); |
2 | 2 | ||
3 | let auth = (req, res, next) => { | 3 | let auth = (req, res, next) => { |
4 | - // 인증 처리 | ||
5 | - // 1. client 쿠키에서 토큰을 가져옴. | ||
6 | let token = req.cookies.w_auth; | 4 | let token = req.cookies.w_auth; |
7 | 5 | ||
8 | - // 2. 토큰을 복호화한 후 유저를 찾는다. (User.js에 findByToken(); 있음) | 6 | + User.findByToken(token, (err, user) => { |
9 | - User.findByToken(token, (err, user)=>{ | 7 | + if (err) throw err; |
10 | - // 에러가 있으면 | 8 | + if (!user) |
11 | - if(err) throw err; | 9 | + return res.json({ |
12 | - // 유저가 없으면 | 10 | + isAuth: false, |
13 | - if(!user) return res.json({ isAuth:false, error: true}) | 11 | + error: true |
14 | - // 에러도 없고 유저도 있으면 | 12 | + }); |
15 | - req.token = token; // token과 user를 request에 넣어줌으로써 index.js에서 request 사용할 수 있음 | 13 | + |
14 | + req.token = token; | ||
16 | req.user = user; | 15 | req.user = user; |
17 | next(); | 16 | next(); |
18 | -}); | 17 | + }); |
19 | - | 18 | +}; |
20 | -// 3. 유저가 있으면 인증OK, 유저가 없으면 인증No! | ||
21 | -} | ||
22 | 19 | ||
23 | -// 이 auth를 다른 파일에서도 쓸 수 있도록 | ||
24 | -module.exports = { auth }; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
20 | +module.exports = { auth }; | ... | ... |
We-Shop/server/models/Payment.js
0 → 100644
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 |
We-Shop/server/models/Product.js
0 → 100644
1 | +const mongoose = require('mongoose'); | ||
2 | +const Schema = mongoose.Schema; | ||
3 | + | ||
4 | +const productSchema = mongoose.Schema({ | ||
5 | + writer: { | ||
6 | + type: Schema.Types.ObjectId, | ||
7 | + ref: 'User' | ||
8 | + }, | ||
9 | + title: { | ||
10 | + type: String, | ||
11 | + maxlength: 50 | ||
12 | + }, | ||
13 | + description: { | ||
14 | + type: String | ||
15 | + }, | ||
16 | + 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 | -// monggoDB Model and Schema | ||
2 | const mongoose = require('mongoose'); | 1 | const mongoose = require('mongoose'); |
3 | -// bcrypt 가져옴 | 2 | +const bcrypt = require('bcrypt'); |
4 | -const bcrypt = require('bcrypt') | 3 | +const saltRounds = 10; |
5 | -// bcrypt 사용하기 위해 salt를 생성하고 그걸 이용해 암호화 시킴 | ||
6 | -const saltRounds = 10 // salt를 몇글자 할 건지 | ||
7 | - | ||
8 | const jwt = require('jsonwebtoken'); | 4 | const jwt = require('jsonwebtoken'); |
9 | -const moment = require("moment"); | 5 | +const moment = require('moment'); |
10 | 6 | ||
11 | const userSchema = mongoose.Schema({ | 7 | const userSchema = mongoose.Schema({ |
12 | name: { | 8 | name: { |
13 | - type:String, | 9 | + type: String, |
14 | - maxlength:50 | 10 | + maxlength: 50 |
15 | }, | 11 | }, |
16 | email: { | 12 | email: { |
17 | - type:String, | 13 | + type: String, |
18 | - trim:true, | 14 | + trim: true, |
19 | - unique: 1 | 15 | + unique: 1 |
20 | }, | 16 | }, |
21 | password: { | 17 | password: { |
22 | type: String, | 18 | type: String, |
23 | - minglength: 3 | 19 | + minlength: 5 |
24 | }, | 20 | }, |
25 | - lastname: { | 21 | + adress: { |
26 | - type:String, | 22 | + type: String, |
27 | - maxlength: 50 | 23 | + maxlength: 100 |
24 | + }, | ||
25 | + role: { | ||
26 | + type: Number, | ||
27 | + default: 0 | ||
28 | }, | 28 | }, |
29 | - role : { | 29 | + cart: { |
30 | - type:Number, // Number==1 이면 관리자, number==0 이면 일반 유저 | 30 | + type: Array, |
31 | - default: 0 // default는 0 | 31 | + default: [] |
32 | + }, | ||
33 | + history: { | ||
34 | + type: Array, | ||
35 | + default: [] | ||
32 | }, | 36 | }, |
33 | image: String, | 37 | image: String, |
34 | - token : { | 38 | + token: { |
35 | type: String, | 39 | type: String, |
36 | }, | 40 | }, |
37 | - tokenExp :{ | 41 | + tokenExp: { |
38 | type: Number | 42 | type: Number |
39 | } | 43 | } |
40 | }) | 44 | }) |
41 | 45 | ||
42 | -// index.js의 app.post('/register', (req, res)에 있는 | 46 | +userSchema.pre('save', function (next) { |
43 | -// user model에 user 정보를 저장하기 전에 무엇을 한다는 것 | ||
44 | -// function( next )를 해서 얘네가 끝난 다음에 다음걸 실행해라~ | ||
45 | -userSchema.pre('save', function( next ) { | ||
46 | var user = this; | 47 | var user = this; |
47 | - | 48 | + |
48 | - if(user.isModified('password')){ // password를 변경할 때만 적용되도록.. | 49 | + if (user.isModified('password')) { |
49 | - | 50 | + console.log('password changed') |
50 | - // 비밀번호 암호화 (https://www.npmjs.com/package/bcrypt 에서 가져옴) | 51 | + bcrypt.genSalt(saltRounds, function (err, salt) { |
51 | - bcrypt.genSalt(saltRounds, (err, salt) => // salt를 만드는 함수 | 52 | + if (err) return next(err); |
52 | - { | 53 | + |
53 | - if(err) return next(err) // 에러 나면 return err | 54 | + bcrypt.hash(user.password, salt, function (err, hash) { |
54 | - bcrypt.hash(user.password, salt, (err, hash) => { // bcrypt.hash(암호화되지 않은 pw, salt, function(err, 암호화된 비밀번호)) | 55 | + if (err) return next(err); |
55 | - if(err) return next(err) // 에러 나면 return err | 56 | + user.password = hash |
56 | - user.password = hash // 성공하면 user.password를 hash로 교체 | 57 | + next() |
57 | - next() | ||
58 | }) | 58 | }) |
59 | }) | 59 | }) |
60 | } else { | 60 | } else { |
... | @@ -62,44 +62,35 @@ userSchema.pre('save', function( next ) { | ... | @@ -62,44 +62,35 @@ userSchema.pre('save', function( next ) { |
62 | } | 62 | } |
63 | }); | 63 | }); |
64 | 64 | ||
65 | -userSchema.methods.comparePassword = function(plainPassword,cb){ | 65 | +userSchema.methods.comparePassword = function (plainPassword, cb) { |
66 | - | 66 | + bcrypt.compare(plainPassword, this.password, function (err, isMatch) { |
67 | - // 1. plainPassword가 1234567 암호화된 비밀번호 가 같은지 체크해야함 | 67 | + if (err) return cb(err); |
68 | - // 그러면 plainPassword도 암호화해서 비교해야함. (복호화 할 수 없기 때문에) | 68 | + cb(null, isMatch) |
69 | - bcrypt.compare(plainPassword, this.password, function(err, isMatch) | ||
70 | - { // 에러가 나면 err callback, 아니면 isMatch | ||
71 | - if(err) return cb(err); | ||
72 | - cb(null, isMatch); | ||
73 | }) | 69 | }) |
74 | } | 70 | } |
75 | 71 | ||
76 | -userSchema.methods.generateToken = function(cb) { | 72 | +userSchema.methods.generateToken = function (cb) { |
77 | var user = this; | 73 | var user = this; |
78 | - // jsonwebtoken을 이용해서 token 생성 | 74 | + var token = jwt.sign(user._id.toHexString(), 'secret') |
79 | - var token = jwt.sign(user._id.toHexString(), 'secretToken') //database에 있는 id라서 _id | ||
80 | var oneHour = moment().add(1, 'hour').valueOf(); | 75 | var oneHour = moment().add(1, 'hour').valueOf(); |
81 | 76 | ||
82 | user.tokenExp = oneHour; | 77 | user.tokenExp = oneHour; |
83 | user.token = token; | 78 | user.token = token; |
84 | - user.save(function (err, user){ | 79 | + user.save(function (err, user) { |
85 | - if(err) return cb(err)// 에러가 있다면 callback으로 에러 전달 | 80 | + if (err) return cb(err) |
86 | - cb(null, user) // 에러가 없다면 err는 없고 user정보만 전달 | 81 | + cb(null, user); |
87 | }) | 82 | }) |
88 | } | 83 | } |
89 | 84 | ||
90 | userSchema.statics.findByToken = function (token, cb) { | 85 | userSchema.statics.findByToken = function (token, cb) { |
91 | var user = this; | 86 | var user = this; |
92 | 87 | ||
93 | - // 1. 토큰을 decoding | 88 | + jwt.verify(token, 'secret', function (err, decode) { |
94 | - jwt.verify(token, 'secretToken', function(err, decoded) { | 89 | + user.findOne({ "_id": decode, "token": token }, function (err, user) { |
95 | - // 2. 유저 아이디를 이용해서 유저를 찾은 다음에 클라이언트에서 가져온 토큰과 DB에 보관된 토큰이 일치하는지 확인. | 90 | + if (err) return cb(err); |
96 | - user.findOne({"_id": decoded, "token": token}, function(err, user){ // findOne :: mongoDB에 이미 있는 method | 91 | + cb(null, user); |
97 | - // 에러가 나면 | 92 | + }) |
98 | - if(err) return cb(err); | 93 | + }) |
99 | - // 에러가 안나면 | ||
100 | - cb(null, user) | ||
101 | - }) | ||
102 | -}) | ||
103 | } | 94 | } |
104 | 95 | ||
105 | const User = mongoose.model('User', userSchema); | 96 | const User = mongoose.model('User', userSchema); | ... | ... |
1 | const express = require('express'); | 1 | const express = require('express'); |
2 | -const { User } = require("../models/User"); | ||
3 | -const { auth } = require("../middleware/auth"); | ||
4 | const router = express.Router(); | 2 | const router = express.Router(); |
3 | +const { Product } = require("../models/Product"); | ||
5 | const multer = require('multer'); | 4 | const multer = require('multer'); |
6 | 5 | ||
6 | +const { auth } = require("../middleware/auth"); | ||
7 | 7 | ||
8 | var storage = multer.diskStorage({ | 8 | var storage = multer.diskStorage({ |
9 | - destination: function (req, file, cb) { | 9 | + destination: (req, file, cb) => { |
10 | - cb(null, 'uploads/') // 어느 폴더에 저장할건지 | 10 | + cb(null, 'uploads/') |
11 | + }, | ||
12 | + filename: (req, file, cb) => { | ||
13 | + cb(null, `${Date.now()}_${file.originalname}`) | ||
11 | }, | 14 | }, |
12 | - filename: function (req, file, cb) { | 15 | + fileFilter: (req, file, cb) => { |
13 | - cb(null, Date.now() + '_' + file.originalname) // 이미지 이름 | 16 | + const ext = path.extname(file.originalname) |
14 | - } | 17 | + if (ext !== '.jpg' || ext !== '.png') { |
15 | - }) | 18 | + return cb(res.status(400).end('JPG, PNG 확장자만 가능합니다.'), false); |
16 | - | 19 | + } |
17 | -var upload = multer({ storage: storage }).single("file"); | 20 | + cb(null, true) |
18 | - | ||
19 | -router.post('/image', (req, res) => { | ||
20 | - | ||
21 | - // 클라이언트로부터 받은 이미지 저장 | ||
22 | - upload(req, res, (err) => { | ||
23 | - if (err) { | ||
24 | - return req.json({ success: false, err }) | ||
25 | - } | ||
26 | - return res.json({ success: true, filePath: res.req.file.path, fileName: res.req.file.filename }) | ||
27 | } | 21 | } |
28 | - ) | ||
29 | - | ||
30 | }) | 22 | }) |
31 | 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 | + | ||
32 | module.exports = router; | 134 | module.exports = router; | ... | ... |
1 | const express = require('express'); | 1 | const express = require('express'); |
2 | +const router = express.Router(); | ||
2 | const { User } = require("../models/User"); | 3 | const { User } = require("../models/User"); |
4 | +const { Product } = require('../models/Product'); | ||
3 | const { auth } = require("../middleware/auth"); | 5 | const { auth } = require("../middleware/auth"); |
4 | -const router = express.Router(); | 6 | +const { Payment } = require('../models/Payment'); |
7 | + | ||
8 | +const async = require('async'); | ||
9 | + | ||
10 | + | ||
5 | 11 | ||
6 | router.get("/auth", auth, (req, res) => { | 12 | router.get("/auth", auth, (req, res) => { |
7 | res.status(200).json({ | 13 | res.status(200).json({ |
... | @@ -10,9 +16,11 @@ router.get("/auth", auth, (req, res) => { | ... | @@ -10,9 +16,11 @@ router.get("/auth", auth, (req, res) => { |
10 | isAuth: true, | 16 | isAuth: true, |
11 | email: req.user.email, | 17 | email: req.user.email, |
12 | name: req.user.name, | 18 | name: req.user.name, |
13 | - lastname: req.user.lastname, | 19 | + address: req.user.address, |
14 | role: req.user.role, | 20 | role: req.user.role, |
15 | image: req.user.image, | 21 | image: req.user.image, |
22 | + cart: req.user.cart, | ||
23 | + history: req.user.history | ||
16 | }); | 24 | }); |
17 | }); | 25 | }); |
18 | 26 | ||
... | @@ -33,12 +41,12 @@ router.post("/login", (req, res) => { | ... | @@ -33,12 +41,12 @@ router.post("/login", (req, res) => { |
33 | if (!user) | 41 | if (!user) |
34 | return res.json({ | 42 | return res.json({ |
35 | loginSuccess: false, | 43 | loginSuccess: false, |
36 | - message: "Auth failed, email not found" | 44 | + message: "존재하지 않는 계정입니다." |
37 | }); | 45 | }); |
38 | 46 | ||
39 | user.comparePassword(req.body.password, (err, isMatch) => { | 47 | user.comparePassword(req.body.password, (err, isMatch) => { |
40 | if (!isMatch) | 48 | if (!isMatch) |
41 | - return res.json({ loginSuccess: false, message: "Wrong password" }); | 49 | + return res.json({ loginSuccess: false, message: "비밀번호가 다릅니다." }); |
42 | 50 | ||
43 | user.generateToken((err, user) => { | 51 | user.generateToken((err, user) => { |
44 | if (err) return res.status(400).send(err); | 52 | if (err) return res.status(400).send(err); |
... | @@ -63,4 +71,185 @@ router.get("/logout", auth, (req, res) => { | ... | @@ -63,4 +71,185 @@ router.get("/logout", auth, (req, res) => { |
63 | }); | 71 | }); |
64 | }); | 72 | }); |
65 | 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 | + | ||
66 | module.exports = router; | 255 | module.exports = router; | ... | ... |
295 KB
295 KB
295 KB
295 KB
295 KB
295 KB
295 KB
1.2 MB
295 KB
117 KB
117 KB
295 KB
295 KB
295 KB
502 KB
295 KB
295 KB
136 KB
295 KB
5.74 MB
295 KB
295 KB
136 KB
136 KB
224 KB
We-Shop/uploads/1623331722097_파스류.jpg
0 → 100644
149 KB
We-Shop/uploads/1623333149947_감기약.jpg
0 → 100644
49.7 KB
We-Shop/uploads/1623338228105_감기약.jpg
0 → 100644
49.7 KB
We-Shop/uploads/1623338369039_상처치료.png
0 → 100644
86.1 KB
We-Shop/uploads/1623339763549_소화제.jpg
0 → 100644
137 KB
We-Shop/uploads/1623341247812_진통제.png
0 → 100644
335 KB
We-Shop/uploads/1623341331089_진통제.png
0 → 100644
335 KB
We-Shop/uploads/1623346676343_파스류.jpg
0 → 100644
149 KB
We-Shop/uploads/1623351314573_해열제.jpg
0 → 100644
233 KB
We-Shop/uploads/1623351550159_진통제.png
0 → 100644
335 KB
We-Shop/uploads/1623351939639_해열제.jpeg
0 → 100644
321 KB
122 KB
23.3 KB
122 KB
-
Please register or login to post a comment