박민정

Merge branch 'develop' into 'main'

Develop



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