박민정

Merge branch 'client' into develop

......@@ -2312,6 +2312,11 @@
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"attr-accept": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
},
"autoprefixer": {
"version": "9.7.5",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.5.tgz",
......@@ -5530,6 +5535,21 @@
"schema-utils": "^2.5.0"
}
},
"file-selector": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz",
"integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==",
"requires": {
"tslib": "^2.0.3"
},
"dependencies": {
"tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
}
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
......@@ -11692,6 +11712,16 @@
"scheduler": "^0.19.1"
}
},
"react-dropzone": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.3.2.tgz",
"integrity": "sha512-Z0l/YHcrNK1r85o6RT77Z5XgTARmlZZGfEKBl3tqTXL9fZNQDuIdRx/J0QjvR60X+yYu26dnHeaG2pWU+1HHvw==",
"requires": {
"attr-accept": "^2.2.1",
"file-selector": "^0.2.2",
"prop-types": "^15.7.2"
}
},
"react-error-overlay": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
......
......@@ -5,10 +5,13 @@
"dependencies": {
"antd": "^3.24.1",
"axios": "^0.19.2",
"core-js": "^3.6.4",
"formik": "^1.5.8",
"moment": "^2.24.0",
"react": "^16.8.6",
"react-app-polyfill": "^1.0.6",
"react-dom": "^16.8.6",
"react-dropzone": "^11.3.2",
"react-icons": "^3.7.0",
"react-redux": "^7.1.0-rc.1",
"react-router-dom": "^5.0.1",
......@@ -16,9 +19,7 @@
"redux": "^4.0.0",
"redux-promise": "^0.6.0",
"redux-thunk": "^2.3.0",
"yup": "^0.27.0",
"core-js": "^3.6.4",
"react-app-polyfill": "^1.0.6"
"yup": "^0.27.0"
},
"scripts": {
"start": "react-scripts start",
......
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
import axios from 'axios';
import {
LOGIN_USER,
REGISTER_USER,
AUTH_USER
} from './types';
export function loginUser(logInfo) {
const request = axios.post('/api/users/login', logInfo) // logInfo를 post로 전달
.then(response => response.data); // 서버에서 받은 데이터를 request에 저장
return { // return을 통해 Reducer로 보냄
// Reducer에서 previousState, action을 이용해 nextState로 만들기 때문 :: (previousState, action) => nextState
// request를 reducer로 보내는 작업
// action은 type과 response을 넣어줘야 함
type: "LOGIN_USER",
payload: request // payroad == response
}
}
export function RegisterUser(regInfo) {
const request = axios.post('/api/users/register', regInfo) // logInfo를 post로 전달
.then(response => response.data); // 서버에서 받은 데이터를 request에 저장
return { // return을 통해 Reducer로 보냄.
// Reducer에서 previousState, action을 이용해 nextState로 만들기 때문 :: (previousState, action) => nextState
// request를 reducer로 보내는 작업
// action은 type과 response을 넣어줘야 함
type: "REGISTER_USER",
payload: request // payroad == response
}
}
export function auth() {
const request = axios.get('/api/users/auth') // logInfo를 post로 전달
.then(response => response.data); // 서버에서 받은 데이터를 request에 저장
return {
type: "AUTH_USER",
payload: request // payroad == response
}
}
\ No newline at end of file
......@@ -5,6 +5,7 @@ import Auth from "../hoc/auth";
import LandingPage from "./views/LandingPage/LandingPage.js";
import LoginPage from "./views/LoginPage/LoginPage.js";
import RegisterPage from "./views/RegisterPage/RegisterPage.js";
import UploadPage from './views/UploadPage/UploadPage';
import NavBar from "./views/NavBar/NavBar";
import Footer from "./views/Footer/Footer"
......@@ -21,6 +22,7 @@ function App() {
<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="/upload" component={Auth(UploadPage, true)} />
</Switch>
</div>
<Footer />
......
import React from 'react'
import Dropzone from 'react-dropzone'
import axios from 'axios';
function ImageUpload() {
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)
}
else {
alert('파일 저장을 실패했습니다.')
}
})
}
return (
<div style={ {display:'flex', justifyContent:'space-between'}}>
<Dropzone onDrop={imageDropEvent}>
{({getRootProps, getInputProps}) => (
<section>
<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>
)
}
export default ImageUpload
......@@ -36,6 +36,9 @@ function RightMenu(props) {
} else {
return (
<Menu mode={props.mode}>
<Menu.Item key="upload">
<a href="/upload">업로드</a>
</Menu.Item>
<Menu.Item key="logout">
<a onClick={logoutHandler}>로그아웃</a>
</Menu.Item>
......
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
/* eslint-disable jsx-a11y/anchor-is-valid */
import React from 'react';
import { Menu } 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('/api/users/logout').then(response => {
if (response.status === 200) {
props.history.push("/login");
} else {
alert('Log Out Failed')
}
});
};
//console.log(user.userData)
//console.log(!user.userData.isAuth)
if (user.userData && !user.userData.isAuth) {
return (
<Menu mode={props.mode}>
<Menu.Item key="mail">
<a href="/login">Signin</a>
</Menu.Item>
<Menu.Item key="app">
<a href="/register">Signup</a>
</Menu.Item>
</Menu>
)
} else {
return (
<Menu mode={props.mode}>
<Menu.Item key="logout">
<a onClick={logoutHandler}>Logout</a>
</Menu.Item>
</Menu>
)
}
}
export default withRouter(RightMenu);
\ No newline at end of file
import React from 'react'
import React from 'react';
import { useState } from 'react';
import { Typography, Button, Form, Input } from 'antd'; // css
import ImageUpload from '../../utils/ImageUpload'
const { TextArea } = Input; // 박스크기 조절을 사용자가 임의로 가능하게 함.
// Select Options
const options = [{ key: 1, value: "a" },
{ key: 2, value: "b" },
{key: 3, value : "c"}
]
function UploadPage() {
// 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);
}
return (
<div>
uploadpage
<div style={{ maxWidth: '700px', margin: '2rem auto' }}>
<div style={{ textAlign: 'center', marginBottom:'2rem'}}>
<h2> 업로드 </h2>
</div>
<Form>
{/* 파일업로드 부분은 코드가 길어서 따로 컴포넌트로 만들었습니다. */}
<ImageUpload />
<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}>
{options.map(item => (
<option key={item.key} value={item.key}>{ item.value}</option>
))}
<option></option>
</select>
<br />
<br />
<Button>확인</Button>
</Form>
</div>
)
}
......
// 사용자의 상태를 보고
// 해당 페이지에 들어갈 수 있게 할지 안할지 결정해 줌. hoc 이용.
import axios from 'axios';
import React, {useEffect} from 'react';
//import {useEffect} from 'react';
import {useDispatch} from 'react-redux';
import {auth} from '../_actions/user_action'
export default function (SpecificComponent, option, adminRoute = null){
// ㄴ option 종류
// - null 아무나 출입 가능한 페이지
// - true 로그인한 유저만 출입 가능
// - false 로그인한 유저 출입 불가능
function AuthenticationCheck(props) {
//1. backend에 request를 날려서 사용자의 상태를 확인
const dispatch = useDispatch(); // 1-1. dispatch 사용
useEffect(() => {
dispatch(auth()) // 페이지가 이동할 때마다 dispatch가 작동해서 backend에 request를 줌
.then(response => { // 받은 response
console.log(response);
// 로그인 안했다면
if(!response.payload.isAuth) {
if(option) { // 만약 위 option이 true이면 (로그인한 유저만 출입 가능한 페이지로 가게 하려면)
props.history.push('/login'); // 로그인 하게 함
}
}
// 로그인 했다면
else {
if(adminRoute && !response.payload.isAdmin) { // admin만 갈 수 있는 페이지를 admin이 false 사람이 들어가려 한다면
props.history.push('/'); // 홈페이지로 돌아가게 함
}
else {
if(option===false) {// 로그인한 유저가 출입 불가능한 곳을 가려고 한다면
props.history.push('/'); // 홈페이지로 돌아가게 함
}
}
}
})
},[])
return (
<SpecificComponent />
)
}
return AuthenticationCheck
}
\ No newline at end of file
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
"Droid Sans", "Helvetica Neue", sans-serif;
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;
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-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;
......@@ -31,7 +33,6 @@ input.error {
border-color: red;
}
.input-feedback {
color: red;
height: 5px;
......
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
......@@ -103,6 +103,11 @@
}
}
},
"append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
......@@ -397,6 +402,43 @@
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
"requires": {
"dicer": "0.2.5",
"readable-stream": "1.1.x"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
......@@ -604,6 +646,17 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
}
},
"concurrently": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.2.tgz",
......@@ -834,6 +887,38 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
"integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
"requires": {
"readable-stream": "1.1.x",
"streamsearch": "0.1.2"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"dot-prop": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
......@@ -2644,6 +2729,21 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"multer": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz",
"integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==",
"requires": {
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.1",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
}
},
"nan": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
......@@ -3717,6 +3817,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
......@@ -3913,6 +4018,11 @@
"mime-types": "~2.1.24"
}
},
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"undefsafe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
......@@ -4178,6 +4288,11 @@
"integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
"dev": true
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
......
......@@ -25,6 +25,7 @@
"jsonwebtoken": "^8.5.1",
"moment": "^2.24.0",
"mongoose": "^5.4.20",
"multer": "^1.4.2",
"react-redux": "^5.0.7",
"saslprep": "^1.0.3",
"supports-color": "^7.1.0"
......
......@@ -30,6 +30,7 @@ app.use(cors())
app.use('/api/users', require('./routes/users'));
app.use('/api/product', require('./routes/product'));
// 이미지 가져오려고
......
const express = require('express');
const { User } = require("../models/User");
const { auth } = require("../middleware/auth");
const router = express.Router();
const multer = require('multer');
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 }).single("file");
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;