Commit cccc30fb authored by Lee SeoYeon's avatar Lee SeoYeon
Browse files

0111

parent 939f2b23
This diff is collapsed.
...@@ -39,5 +39,6 @@ ...@@ -39,5 +39,6 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
} },
"proxy": "http://localhost:3001"
} }
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Link, Redirect } from 'react-router-dom'; import { Link, Redirect } from 'react-router-dom';
import ohuh from './ohuh.PNG'; import ohuh from './ohuh.PNG';
import { Col, FormControl, Container, Row, Form, Image, InputGroup, Button } from 'react-bootstrap'; import { Col, FormControl, Container, Row, Form, Image, InputGroup, Button, Navbar, Nav } from 'react-bootstrap';
import { handleLogout, isAuthenticated } from './utils/auth'
function App() { function App() {
const [state, setState] = useState(false); const [state, setState] = useState(false);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const user = isAuthenticated()
if (state !== false) { if (state !== false) {
return <Redirect to={{ return <Redirect to={{
...@@ -33,6 +34,19 @@ function App() { ...@@ -33,6 +34,19 @@ function App() {
<Col md={6} className="mt-5"> <Col md={6} className="mt-5">
<Image src={ohuh} fluid /> <Image src={ohuh} fluid />
</Col> </Col>
<Col md={9} className='mr-6'>
<Navbar bg="#fff" variant="light">
<Nav className="mr-auto">
{user ? <Nav.Link onClick={() => handleLogout()}>로그아웃</Nav.Link>
: (
<>
<Nav.Link href="/signup">회원가입</Nav.Link>
<Nav.Link href="/login">로그인</Nav.Link>
</>
)}
</Nav>
</Navbar>
</Col>
{/* </Row> */} {/* </Row> */}
<Col lg={{ span: 10, offset: 1 }} > {/*xs={{ span: 12, offset: 3 }} */} <Col lg={{ span: 10, offset: 1 }} > {/*xs={{ span: 12, offset: 3 }} */}
<InputGroup size="lg" lg={6} xs={4} fluid> <InputGroup size="lg" lg={6} xs={4} fluid>
......
import React, { useState, useEffect } from 'react'
import { Alert, Col, Container, Form, Row, Button, Spinner } from "react-bootstrap";
import axios from "axios";
import catchErrors from '../utils/catchErrors';
import { Redirect } from 'react-router-dom';
import { handleLogin } from '../utils/auth';
const INIT_USER = {
email: '',
password: ''
}
function Login() {
const [user, setUser] = useState(INIT_USER)
const [disabled, setDisabled] = useState(true)
const [error, setError] = useState('')
const [success, setSuccess] = useState(false)
const [loading, setLoading] = useState(false)
useEffect(() => {
const isUser = Object.values(user).every(el => Boolean(el))
isUser ? setDisabled(false) : setDisabled(true)
}, [user])
function handleChange(event) {
const {name, value} = event.target
setUser({...user, [name]: value})
}
async function handleSubmit(event) {
event.preventDefault()
try {
setLoading(true)
setError('')
const response = await axios.post('/api/auth/login', user)
console.log(response.data)
handleLogin(response.data.userId)
setSuccess(true)
} catch (error) {
catchErrors(error, setError)
} finally {
setLoading(false)
}
}
if (success) {
console.log('success', success)
return <Redirect to= '/'/>
}
return (
<Container>
<Row className= 'vh-100 flex-column align-items-center justify-content-center'>
<h2>로그인</h2>
<Col md={6}>
{error && <Alert variant='danger'>
{error}
</Alert>}
<Form onSubmit={handleSubmit}>
<Form.Group>
<Form.Label>이메일</Form.Label>
<Form.Control name='email' type='email'value={user.email} onChange={handleChange}/>
</Form.Group>
<Form.Group>
<Form.Label>비밀번호</Form.Label>
<Form.Control name='password' type='password' value={user.password} onChange={handleChange}/>
</Form.Group>
<Button disabled={disabled || setLoading} type='submit'
block>
{loading && <Spinner as= 'span' animation= 'border' size='sm'
role= 'status' aria-hidden= 'true' />}{' '}확인</Button>
</Form>
</Col>
</Row>
</Container>
)
}
export default Login
import React, { useState, useEffect } from 'react'
import { Alert, Col, Container, Form, Row, Button } from "react-bootstrap";
import axios from "axios";
import catchErrors from '../utils/catchErrors';
import { Link, Redirect} from 'react-router-dom';
const INIT_USER = {
name: '',
email: '',
password: ''
}
function Signup() {
const [user, setUser] = useState(INIT_USER)
const [disabled, setDisabled] = useState(true)
const [error, setError] = useState('')
const [success, setSuccess] = useState(false)
useEffect(() => {
const isUser = Object.values(user).every(el => Boolean(el))
isUser ? setDisabled(false) : setDisabled(true)
}, [user])
function handleChange(event) {
const {name, value} = event.target
setUser({...user, [name]: value})
}
async function handleSubmit(event) {
event.preventDefault()
try {
setError('')
const response = await axios.post('/api/users/signup', user)
console.log(response.data)
setUser(INIT_USER)
} catch (error) {
catchErrors(error, setError)
}
}
if (success) {
return <Redirect to='/'/>
}
return (
<Container>
<Row className= 'vh-100 flex-column align-items-center justify-content-center'>
<h2>회원가입</h2>
<Col md={5}>
{error && <Alert variant='danger'>
{error}
</Alert>}
<Form onSubmit={handleSubmit}>
<Form.Group>
<Form.Label>이름</Form.Label>
<Form.Control name='name' value={user.name} onChange={handleChange}/>
</Form.Group>
<Form.Group>
<Form.Label>이메일</Form.Label>
<Form.Control name='email' type='email'value={user.email} onChange={handleChange}/>
</Form.Group>
<Form.Group>
<Form.Label>비밀번호</Form.Label>
<Form.Control name='password' type='password' value={user.password} onChange={handleChange}/>
</Form.Group>
<Button disabled={disabled} type='submit' block>확인</Button>
</Form>
</Col>
</Row>
</Container>
)
}
export default Signup
\ No newline at end of file
...@@ -2,7 +2,7 @@ import React, { useState } from 'react'; ...@@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Link, Redirect } from 'react-router-dom'; import { Link, Redirect } from 'react-router-dom';
import ohuh from './ohuh-sm.PNG'; import ohuh from './ohuh-sm.PNG';
import Place from './Components/Place'; import Place from './Components/Place';
import { Container, Form, Row, Col, Card, Image, InputGroup, FormControl, Button, Pagination } from 'react-bootstrap'; import { Container, Form, Row, Col, Card, Image, InputGroup, FormControl, Button, Pagination, Nav, Navbar } from 'react-bootstrap';
import Paginations from './pagination.js'; import Paginations from './pagination.js';
function Search(props) { function Search(props) {
...@@ -443,16 +443,24 @@ function Search(props) { ...@@ -443,16 +443,24 @@ function Search(props) {
return ( return (
<Container > <Container >
<Link to="/" className="d-flex justify-content-center"><Image src={ohuh} /></Link> <Link to="/" className="d-flex justify-content-center"><Image src={ohuh} /></Link>
{/* <Row className='d-flex justify-content-flex-start'>
<Navbar bg="#fff" variant="light">
<Nav className="mr-auto">
<Nav.Link href="#home">회원가입</Nav.Link>
<Nav.Link href="#features">로그인</Nav.Link>
<Nav.Link href="#pricing">로그아웃</Nav.Link>
</Nav>
</Navbar>
</Row> */}
<Row className="mb-2" className="d-flex justify-content-center"> <Row className="mb-2" className="d-flex justify-content-center">
<Form style={{width:"90vw"}} onSubmit={handleSubmit}> <Form style={{ width: "90vw" }} onSubmit={handleSubmit}>
<InputGroup size="lg"> <InputGroup size="lg">
<Link to="/"> <Link to="/">
<Col> <Col>
<Image src={ohuh} fluid width="130px" /> {/* <Image src={ohuh} fluid width="130px" /> */}
</Col> </Col>
</Link> </Link>
<Col>
<FormControl <FormControl
placeholder="검색어를 입력하세요." placeholder="검색어를 입력하세요."
value={search} value={search}
...@@ -461,9 +469,8 @@ function Search(props) { ...@@ -461,9 +469,8 @@ function Search(props) {
onChange={handleChange} onChange={handleChange}
/> />
<InputGroup.Append> <InputGroup.Append>
<Button type="submit" variant="outline-secondary" style={{maxHeight: "8vh", maxWidth: "14vh" }} >검색</Button> <Button type="submit" variant="outline-secondary" style={{ maxHeight: "8vh", maxWidth: "14vh" }} >검색</Button>
</InputGroup.Append> </InputGroup.Append>
</Col>
</InputGroup> </InputGroup>
</Form> </Form>
</Row> </Row>
...@@ -471,11 +478,11 @@ function Search(props) { ...@@ -471,11 +478,11 @@ function Search(props) {
{pagePlace.map((place, index) => { {pagePlace.map((place, index) => {
return ( return (
<Col key={index} md={6} > <Col key={index} md={6} >
<Card align="center" border="info" style={{margin:"3%"}}> <Card align="center" border="info" style={{ margin: "3%" }}>
<Card.Title style={{margin:"3%", fontSize:'200%', fontWeight:'bold'}} >{place.name}</Card.Title> <Card.Title style={{ margin: "3%", fontSize: '200%', fontWeight: 'bold' }} >{place.name}</Card.Title>
<Card.Img variant="top" style={{padding:"5%" ,width:"100%", height:"340px"}} src={place.img} /> <Card.Img variant="top" style={{ padding: "5%", width: "100%", height: "340px" }} src={place.img} />
<Card.Body > <Card.Body >
<Card.Text style={{overflow:'auto', fontSize:'25px', width: '100%', height:"80px"}} > <Card.Text style={{ overflow: 'auto', fontSize: '25px', width: '100%', height: "80px" }} >
{place.address} </Card.Text> {place.address} </Card.Text>
<Button variant="primary" onClick={() => setShow(true)}>{place.name} 자세히 살펴보기</Button> <Button variant="primary" onClick={() => setShow(true)}>{place.name} 자세히 살펴보기</Button>
<Place search={place} show={show} onHide={() => setShow(false)} /> <Place search={place} show={show} onHide={() => setShow(false)} />
......
...@@ -5,6 +5,8 @@ import 'bootstrap/dist/css/bootstrap.min.css'; ...@@ -5,6 +5,8 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import App from './App'; import App from './App';
import Search from './Search'; import Search from './Search';
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals';
import Signup from './Components/Signup'
import Login from './Components/Login'
import { import {
BrowserRouter as Router, BrowserRouter as Router,
Switch, Switch,
...@@ -14,7 +16,7 @@ import { ...@@ -14,7 +16,7 @@ import {
import axios from 'axios'; import axios from 'axios';
axios.defaults.validateStatus = function (status){ axios.defaults.validateStatus = function (status){
return status < 500; //default return status < 500;
} }
ReactDOM.render( ReactDOM.render(
...@@ -24,6 +26,8 @@ ReactDOM.render( ...@@ -24,6 +26,8 @@ ReactDOM.render(
<Route exact path="/" component={App} /> <Route exact path="/" component={App} />
<Route path="/search" component={Search} /> <Route path="/search" component={Search} />
<Redirect path="/search/:id" to="/search" /> <Redirect path="/search/:id" to="/search" />
<Route path='/signup' component={Signup}/>
<Route path='/login' component={Login} />
</Switch> </Switch>
</Router> </Router>
</React.StrictMode>, </React.StrictMode>,
......
import axios from "axios"
export function handleLogin(userId) {
localStorage.setItem('loginStatus', userId)
}
export async function handleLogout() {
localStorage.removeItem('loginStatus')
await axios.get('/api/auth/logout')
window.location.href='/'
}
export function isAuthenticated() {
const userId = localStorage.getItem('loginStatus')
if (userId) {
return userId
}else{
return false
}
}
\ No newline at end of file
function catchErrors(error, displayError) {
let errorMsg
if (error.response) {
errorMsg = error.response.data
console.log(errorMsg)
}else if (error.requset) {
errorMsg = error.requset
console.log(errorMsg)
} else {
errorMsg = error.message
console.log(errorMsg)
}
displayError(errorMsg)
}
export default catchErrors
\ No newline at end of file
This diff is collapsed.
...@@ -5,10 +5,18 @@ ...@@ -5,10 +5,18 @@
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"express": "^4.17.1" "axios": "^0.21.1",
"bcryptjs": "^2.4.3",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.11.11",
"multer": "^1.4.2",
"nodemon": "^2.0.7",
"validator": "^13.5.2"
}, },
"devDependencies": {}, "devDependencies": {},
"scripts": { "scripts": {
"dev": "nodemon server/server.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
......
const config = {
env: process.env.NODE_ENV || 'development',
port: process.env.PORT || 3001,
jwtSecret: process.env.JWT_SECRET || 'My_Secret_Key',
mongoDbUri: process.env.MONGODB_URI || 'mongodb://localhost/react_test',
cookieMaxAge: 60 * 60 * 24 * 7 * 1000
}
export default config
\ No newline at end of file
import User from "../models/User.js"
import bcrypt from "bcryptjs"
import jwt from "jsonwebtoken";
import config from "../config.js";
const login = async (req, res) => {
const { email, password } = req.body //구조분해해서 하나씩
console.log( email, password)
try { //적어도 3개 맥시멈 10개 가 아니면
// 1) 사용자 확인
const user = await User.findOne({email}).select('+password')
// 2) 이메일 사용자가 없으면 에러 반환
if (!user) {
return res.status(404).send(`${email}이 없습니다`)
}
// 3) 비밀번호 일치 확인
const passwordMatch = await bcrypt.compare(password, user.password)
// 4) 비밀번호가 맞으면 토큰 생성 후 쿠키에 저장
if (passwordMatch) { //비밀스럽게 한다 노출되면 안된다 문자열
const token = jwt.sign({userId: user._id}, config.jwtSecret,{
expiresIn: '7d' //만기날짜
})
// 쿠키의 이름, 쿠키의 value
res.cookie('token', token, {
maxAge: config.cookieMaxAge, //쿠키 얼마동안 살아있을지 생성일이 지나면 자동으로 사라짐
httpOnly: true, //client쪽에서 자바스크립트에서 쿠키 접근 불가
secure: config.env === 'production' //true가되면 https로만 접근가능
})
res.json({userId: user._id})
// 5) 비밀번호가 틀리면 에러 반환
} else {
res.status(401).send('비밀번호가 일치하지 않습니다')
}
} catch (error) { //다른 과정에서 에러가 나면 실행
console.log(error)
res.status(500).send('로그인 에러')
}
}
const logout = (req, res) => {
res.clearCookie('token')
res.send('Logout Successful')
}
export default { login, logout}
\ No newline at end of file
import User from "../models/User.js"
import isLength from 'validator/lib/isLength.js'
import isEmail from "validator/lib/isEmail.js"
import bcrypt from "bcryptjs";
import multer from "multer";
const upload = multer({ dest: 'uploads/' })
const profileUpload = upload.fields([
{name: 'avatar', maxCount: 1},
{name: 'gallery', maxCount: 10},
])
const signup = async (req, res) => {
const { name, email, password } = req.body
console.log(name, email, password)
try {
if (!isLength(name, {min: 3, max: 10})) {
return res.status(422).send('이름은 3-10자 사이입니다')
}else if (!isLength(password, {min: 6})) {
return res.status(422).send('비밀번호는 6자 이상입니다')
}else if (!isEmail(email)) {
return res.status(422).send('유효하지 않은 이메일 형식입니다')
}
const user = await User.findOne({email})
if (user) {
return res.status(422).send(`${email}이 이미 사용중입니다`)
}
const hash = await bcrypt.hash(password, 10)
const newUser = await new User ({
name,
email,
password: hash
}).save()
console.log(newUser)
res.json(newUser)
} catch (error) {
console.log(error)
res.status(500).send('회원가입 에러')
}
}
const update = async (req, res) => {
try {
const {name} = req.body
console.log(req.files)
const avatar = req.files['avatar'][0]
const gallery = req.files['gallery']
const user = req.profile
user.avatarUrl = avatar.filename
gallery.forEach(file => {
user.galleryUrls.push(file.filename)
});
const updatedUser = await user.save()
res.json(updatedUser)
} catch (error) {
console.log(error)
res.status(500).send('프로파일 업데이트 실패')
}
}
const getProfile = (req, res) => {
res.json(req.profile)
}
const userById = async (req, res, next, id) => {
try {
const user = await User.findById(id)
if (!user) {
res.status(404).send('사용자를 찾을 수 없습니다')
}
req.profile = user
next()
} catch (error) {
console.log(error)
res.status(500).send('사용자 아이디 검색 실패')
}
}
export default { signup, profileUpload, update, getProfile, userById }
\ No newline at end of file
import mongoose from "mongoose";
const { String } = mongoose.Schema.Types
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
select: false,
},
role: {
type: String,
required: true,
default: 'user',
enum: ['user', 'admin', 'root']
},
avatarUrl: {
type: String
},
galleryUrls: {
type: [String]
}
}, {
timestamps: true
})
export default mongoose.models.User || mongoose.model('User', UserSchema)
import express from "express"
import authCtrl from "../controllers/auth.controller.js";
const router = express.Router()
router.route('/api/auth/login')
.post(authCtrl.login)
router.route('/api/auth/logout')
.get(authCtrl.logout)
export default router
\ No newline at end of file
import express from "express";
import userCtrl from "../controllers/user.controller.js";
const router = express.Router()
router.route('/api/users/signup')
.post(userCtrl.signup)
router.route('/api/users/profile/:userId')
.get(userCtrl.getProfile)
.put(userCtrl.profileUpload, userCtrl.update)
router.param('userId', userCtrl.userById)
export default router
\ No newline at end of file
import express from 'express' import express from 'express'
import connectDb from "./utils/connectDb.js"
import userRouter from "./routes/user.routes.js"
import authRouter from "./routes/auth.routes.js"
connectDb()
const app = express() const app = express()
// app.use(express.static('../client/build')) app.use('/images', express.static('uploads/'))
app.get('/', (req,res) => { app.use(express.json())
res.send('Hello')
})
app.get('/home', (req,res) => { app.use(userRouter)
res.send('Home page') app.use(authRouter)
})
app.put('/signin', (req,res) => { app.get('/', (req,res) => {
res.send('Sign in') res.send('Hello world, 안녕하세요.')
}) })
app.listen(3001, () =>{ app.listen(3001, () => {
console.log(('Server is listening on port 3001')) console.log('Listening on port 3001')
}) })
\ No newline at end of file
...@@ -8,7 +8,7 @@ async function connectDb() { ...@@ -8,7 +8,7 @@ async function connectDb() {
return return
} }
const db = await mongoose.connect(config.mongoDburi, { const db = await mongoose.connect(config.mongoDbUri, {
useNewUrlParser: true, useNewUrlParser: true,
useUnifiedTopology: true, useUnifiedTopology: true,
useFindAndModify: false, useFindAndModify: false,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment