From 7b7c840386b0f1375e7b5048c75b0017cb9aad0f Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 12 Apr 2020 17:08:56 +0900 Subject: [PATCH 01/14] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9D=B8?= =?UTF-8?q?=ED=92=8B=EA=B3=BC=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84,?= =?UTF-8?q?=20=EC=96=91=EC=8B=9D=EC=97=90=20=EB=A7=9E=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B2=8C=20=EC=9E=85=EB=A0=A5=ED=95=A0=EC=8B=9C=20alert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 31 +++++++++++++ package.json | 1 + pages/index.js | 1 + src/components/login-form.js | 87 +++++++++++++++++++++++++++++++++++- src/components/vote-form.js | 3 +- 5 files changed, 120 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a439258..136f0db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1920,6 +1920,37 @@ } } }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", diff --git a/package.json b/package.json index 0e22c09..817c4b9 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "start": "node server" }, "dependencies": { + "axios": "^0.19.2", "compression": "^1.7.4", "dotenv": "^8.2.0", "express": "^4.17.1", diff --git a/pages/index.js b/pages/index.js index 6474ebb..e458213 100644 --- a/pages/index.js +++ b/pages/index.js @@ -13,6 +13,7 @@ export default function Home() { } const Wrapper = styled.div` + font-size: 3rem; min-height: 100vh; padding: 10rem 40rem; background-color: Azure; diff --git a/src/components/login-form.js b/src/components/login-form.js index 418d945..731c914 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -1,8 +1,55 @@ -import React from "react"; +import React, { useState } from "react"; +import axios from "axios"; import styled from "styled-components"; export default function LoginForm() { - return 안녕 나는 로그인 폼!; + //State에 로그인에 필요한 데이터 저장 + const [loginData, setLoginData] = useState({ email: "", password: "" }); + // 변수 이름 쉽게하기 위해 + const { email, password } = loginData; + + const checkBlank = () => { + // 둘 중 하나라도 안 채워져 있을시 알림 + if (email === "" || password === "") { + alert("빈칸채워주세요!!"); + return false; + } else { + alert("로그인 화면으로 이동해야하는데 아직 라우팅 방법을 모릅니다"); + return true; + } + }; + // 로그인 시도 함수 + const tryLogin = () => { + if (checkBlank === false) { + console.log("실패"); + return; + } + }; + + // 값이 변경될 때 + const handleFormChange = (e) => { + const { name, value } = e.target; + setLoginData({ + ...loginData, + [name]: value, + }); + console.log(name, value); + }; + + return ( + + 로그인 + + EMAIL + + + + PASSWORD + + + 로그인 + + ); } const Wrapper = styled.div` @@ -12,3 +59,39 @@ const Wrapper = styled.div` font-size: 18px; padding: 3rem 4rem; `; + +const LoginTitle = styled.p` + font-weight: bold; + font-size: 2rem; + margin-bottom: 3rem; +`; + +const InputWrapper = styled.div` + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 3rem; +`; + +const InputLabel = styled.p` + font-size: 1.5rem; + padding: 0px; + margin: 0px; +`; + +const DataInput = styled.input` + border: 1px solid rgb(97, 97, 97); + padding: 0.5rem 0.8rem; + width: 70%; +`; + +const LoginBtn = styled.button` + float: right; + background: rgb(222, 222, 222); + font-size: 1.5rem; + padding: 0.5rem 1rem; + border: none; + border-radius: 0.3rem; +`; diff --git a/src/components/vote-form.js b/src/components/vote-form.js index 65bc549..15eaada 100644 --- a/src/components/vote-form.js +++ b/src/components/vote-form.js @@ -1,4 +1,5 @@ -import React from "react"; +import React, { useState } from "react"; +import axios from "axios"; import styled from "styled-components"; export default function VoteForm() { From e2ab412963b480c5f487b33afa565e7d23c97fb4 Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 12 Apr 2020 17:50:40 +0900 Subject: [PATCH 02/14] =?UTF-8?q?=EC=95=84=EC=9D=B4=EB=94=94=EC=99=80=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=ED=95=98=EB=A9=B4=20Vote-form=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/index.js | 8 ++++++-- src/components/login-form.js | 35 ++++++++++++++++++++++++++--------- src/components/vote-form.js | 3 +-- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/pages/index.js b/pages/index.js index e458213..933381c 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,13 +1,17 @@ -import React from "react"; +import React, { useState } from "react"; import styled from "styled-components"; import LoginForm from "../src/components/login-form"; +import VoteForm from "../src/components/vote-form"; export default function Home() { + const [loggedIn, setLoggedIn] = useState(false); + return ( 리액트 투-표 - + {!loggedIn && } + {loggedIn && } ); } diff --git a/src/components/login-form.js b/src/components/login-form.js index 731c914..1e6f408 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -2,38 +2,55 @@ import React, { useState } from "react"; import axios from "axios"; import styled from "styled-components"; -export default function LoginForm() { +export default function LoginForm(props) { //State에 로그인에 필요한 데이터 저장 - const [loginData, setLoginData] = useState({ email: "", password: "" }); + const [userData, setUserData] = useState({ + email: "example@ceos.or.kr", + password: "example1!", + }); + const { loginSuccess } = props; // 변수 이름 쉽게하기 위해 - const { email, password } = loginData; + const { email, password } = userData; const checkBlank = () => { // 둘 중 하나라도 안 채워져 있을시 알림 if (email === "" || password === "") { - alert("빈칸채워주세요!!"); + alert("빈칸 채워주세요!!"); return false; } else { - alert("로그인 화면으로 이동해야하는데 아직 라우팅 방법을 모릅니다"); return true; } }; + // 로그인 시도 함수 const tryLogin = () => { - if (checkBlank === false) { + if (checkBlank() === false) { console.log("실패"); return; } + axios + .post(process.env.API_HOST + "/auth/signin/", { + email: email, + password: password, + }) + .then(function (response) { + alert("로그인 되었습니다!"); + loginSuccess(true); + console.log(response); + }) + .catch(function (error) { + alert("아이디나 비밀번호 확인해주세요!"); + console.log(error); + }); }; // 값이 변경될 때 const handleFormChange = (e) => { const { name, value } = e.target; - setLoginData({ - ...loginData, + setUserData({ + ...userData, [name]: value, }); - console.log(name, value); }; return ( diff --git a/src/components/vote-form.js b/src/components/vote-form.js index 15eaada..65bc549 100644 --- a/src/components/vote-form.js +++ b/src/components/vote-form.js @@ -1,5 +1,4 @@ -import React, { useState } from "react"; -import axios from "axios"; +import React from "react"; import styled from "styled-components"; export default function VoteForm() { From 44c50c85c585db9157f25e74a12bc37cfaebe772 Mon Sep 17 00:00:00 2001 From: WooYoo Date: Thu, 16 Apr 2020 15:12:31 +0900 Subject: [PATCH 03/14] =?UTF-8?q?=ED=9B=84=EB=B3=B4=EC=9E=90=20=EB=AA=85?= =?UTF-8?q?=EB=8B=A8=EC=9D=84=20=EB=B0=9B=EC=95=84=EC=99=80=EC=84=9C=20?= =?UTF-8?q?=EB=9D=84=EC=96=B4=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/candidate-form.js | 49 ++++++++++++++++++++++++ src/components/login-form.js | 2 +- src/components/vote-form.js | 64 +++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 src/components/candidate-form.js diff --git a/src/components/candidate-form.js b/src/components/candidate-form.js new file mode 100644 index 0000000..0f9d5e4 --- /dev/null +++ b/src/components/candidate-form.js @@ -0,0 +1,49 @@ +import React from "react"; +import styled from "styled-components"; + +export default function CandidateForm(props) { + const { name, voteCount, rank } = props; + return ( + + + {rank} +
위 +
+ .. +
+ + {props.name} +
+ {props.voteCount}표 +
+ + { + alert(props.name); + }} + > + 투표 + +
+ ); +} + +const Wrapper = styled.div` + display: flex; + justify-content: space-between; + margin-bottom: 0.5rem; +`; + +const CandidateRank = styled.p``; +const CandidateDesc = styled.p` + width: 20%; +`; +const VoteBtn = styled.button` + background: blue; + color: white; + border: none; + border-radius: 0.7rem; + font-size: 2rem; + width: 4rem; + height: 6rem; +`; diff --git a/src/components/login-form.js b/src/components/login-form.js index 1e6f408..3d73789 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -22,7 +22,7 @@ export default function LoginForm(props) { } }; - // 로그인 시도 함수 + // 로그인 시도 const tryLogin = () => { if (checkBlank() === false) { console.log("실패"); diff --git a/src/components/vote-form.js b/src/components/vote-form.js index 65bc549..bb675e9 100644 --- a/src/components/vote-form.js +++ b/src/components/vote-form.js @@ -1,8 +1,59 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; +import axios from "axios"; import styled from "styled-components"; +import CandidateForm from "./candidate-form"; export default function VoteForm() { - return 안녕 나는 투표 폼!; + const [candidates, setCandidates] = useState(null); + + //useEffect는 렌더링 될때마다 실행, [] 인자에는 바뀔 떄마다 렌더링할 변수가 들어간다 + useEffect(() => { + takeCandidates(); + }, []); + console.log(takeCandidates); + // axois에서 후보자 명단 받아온다. + const takeCandidates = async () => { + const data = await axios + .get(process.env.API_HOST + "/candidates/", { + params: {}, + }) + .then(function (response) { + return response.data; + }) + .catch(function (error) { + console.log(error); + }) + .finally(function () { + // always executed + }); + setCandidates(data); + }; + if (candidates) { + return ( + +

+ 프론트앤드 개발자 인기 투표! +

+

CEOS 프론트엔드 개발자 인기 순위 및 투표 창입니다.

+ + {candidates + .sort((a, b) => { + console.log(candidates.indexOf(a)); + return b.voteCount - a.voteCount; + }) + .map((candidate) => ( + + ))} + +
+ ); + } + return <>; } const Wrapper = styled.div` @@ -12,3 +63,12 @@ const Wrapper = styled.div` font-size: 18px; padding: 3rem 4rem; `; + +const RedText = styled.span` + color: red; +`; + +const CandidatesList = styled.div` + padding: 5rem 10rem; + border: 1px solid black; +`; From 05637b0501a6f4647ec7d7a2dda94a072c520d0f Mon Sep 17 00:00:00 2001 From: WooYoo Date: Fri, 17 Apr 2020 20:22:29 +0900 Subject: [PATCH 04/14] =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=BB=A8?= =?UTF-8?q?=EB=B2=A4=EC=85=98=EC=9D=84=20=EC=9C=84=ED=95=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=ED=95=84=EC=88=98?= =?UTF-8?q?=20=EC=82=AC=ED=95=AD=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- now.json | 12 ++++++ pages/index.js | 11 ++++-- src/components/candidate-form.js | 57 ++++++++++++++++++++--------- src/components/login-form.js | 17 ++++++--- src/components/vote-form.js | 63 +++++++++++++++++++------------- 5 files changed, 110 insertions(+), 50 deletions(-) create mode 100644 now.json diff --git a/now.json b/now.json new file mode 100644 index 0000000..11ec8b2 --- /dev/null +++ b/now.json @@ -0,0 +1,12 @@ +{ + "version": 2, + "public": false, + "builds": [{ "src": "next.config.js", "use": "@now/next" }], + "build": { + "env": { + "NODE_ENV": "@react-vote-11th-node-env", + "PORT": "@react-vote-11th-port", + "API_HOST": "@react-vote-11th-api-host" + } + } +} diff --git a/pages/index.js b/pages/index.js index 933381c..cbef5bc 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,7 +1,7 @@ import React, { useState } from "react"; import styled from "styled-components"; -import LoginForm from "../src/components/login-form"; +import { MemoizedLoginForm } from "../src/components/login-form"; import VoteForm from "../src/components/vote-form"; export default function Home() { @@ -9,8 +9,8 @@ export default function Home() { return ( - 리액트 투-표 - {!loggedIn && } + 리액트 투-표 + {!loggedIn && } {loggedIn && } ); @@ -22,3 +22,8 @@ const Wrapper = styled.div` padding: 10rem 40rem; background-color: Azure; `; + +const Title = styled.p` + font-weight: bold; + margin-bottom: 2rem; +`; diff --git a/src/components/candidate-form.js b/src/components/candidate-form.js index 0f9d5e4..54ead12 100644 --- a/src/components/candidate-form.js +++ b/src/components/candidate-form.js @@ -1,25 +1,38 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import styled from "styled-components"; +import axios from "axios"; export default function CandidateForm(props) { - const { name, voteCount, rank } = props; + const { name, voteCount, rank, id } = props; + + const voteCandidate = () => { + axios + .put(process.env.API_HOST + "/candidates/" + id + "/vote/", { + params: {}, + }) + .then(function (response) { + console.log(response); + // return response.data; + }) + .catch(function (error) { + console.log(error); + alert("투표 실패!"); + }) + .finally(function () { + // always executed + }); + }; return ( - - {rank} -
위 -
- .. -
+ {rank}위: - {props.name} -
- {props.voteCount}표 + {name}[{voteCount}표]
{ - alert(props.name); + alert(name, "님에게 투표 완료!"); + voteCandidate(); }} > 투표 @@ -31,12 +44,21 @@ export default function CandidateForm(props) { const Wrapper = styled.div` display: flex; justify-content: space-between; - margin-bottom: 0.5rem; + align-items: center; + flex-direction: row; `; -const CandidateRank = styled.p``; +const CandidateRank = styled.p` + font-weight: bolder; + font-size: 2.5rem; + border: none; + margin: none; +`; const CandidateDesc = styled.p` - width: 20%; + font-size: 2.5rem; + width: 40%; + border: none; + margin: none; `; const VoteBtn = styled.button` background: blue; @@ -44,6 +66,7 @@ const VoteBtn = styled.button` border: none; border-radius: 0.7rem; font-size: 2rem; - width: 4rem; - height: 6rem; + height: 3.5rem; + width: 5.5rem; + margin: none; `; diff --git a/src/components/login-form.js b/src/components/login-form.js index 3d73789..062eda2 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, Memo } from "react"; import axios from "axios"; import styled from "styled-components"; @@ -34,13 +34,16 @@ export default function LoginForm(props) { password: password, }) .then(function (response) { - alert("로그인 되었습니다!"); + alert("로그인에 성공하셨습니다!!!"); loginSuccess(true); console.log(response); }) .catch(function (error) { - alert("아이디나 비밀번호 확인해주세요!"); - console.log(error); + alert("아이디 혹은 비밀번호를 확인해주세요!!!"); + setUserData({ + email: "", + password: "", + }); }); }; @@ -64,11 +67,15 @@ export default function LoginForm(props) { PASSWORD - 로그인 + + 로그인 +
); } +export const MemoizedLoginForm = React.memo(LoginForm); + const Wrapper = styled.div` width: 100%; min-height: 30rem; diff --git a/src/components/vote-form.js b/src/components/vote-form.js index bb675e9..e58aa7e 100644 --- a/src/components/vote-form.js +++ b/src/components/vote-form.js @@ -4,15 +4,17 @@ import styled from "styled-components"; import CandidateForm from "./candidate-form"; export default function VoteForm() { - const [candidates, setCandidates] = useState(null); + const [candidateList, setCandidateList] = useState(null); - //useEffect는 렌더링 될때마다 실행, [] 인자에는 바뀔 떄마다 렌더링할 변수가 들어간다 + // useEffect는 렌더링 될때마다 실행, [] 인자에는 바뀔 떄마다 렌더링할 변수가 들어간다 + // candidates의 값이 바뀔 때마다 렌더링한다. useEffect(() => { - takeCandidates(); - }, []); - console.log(takeCandidates); + takeCandidateList(); + }, [candidateList]); + // axois에서 후보자 명단 받아온다. - const takeCandidates = async () => { + + const takeCandidateList = async () => { const data = await axios .get(process.env.API_HOST + "/candidates/", { params: {}, @@ -26,34 +28,35 @@ export default function VoteForm() { .finally(function () { // always executed }); - setCandidates(data); + setCandidateList(data); }; - if (candidates) { - return ( - -

- 프론트앤드 개발자 인기 투표! -

-

CEOS 프론트엔드 개발자 인기 순위 및 투표 창입니다.

- - {candidates + // candidates값이 null이 아니면 + + return ( + + + 프론트앤드 인기쟁이는 누구? + + CEOS 프론트엔드 개발자 인기 순위 및 투표 창입니다. + {candidateList !== null && ( + + {candidateList .sort((a, b) => { - console.log(candidates.indexOf(a)); return b.voteCount - a.voteCount; }) .map((candidate) => ( ))} - -
- ); - } - return <>; + + )} + + ); } const Wrapper = styled.div` @@ -63,12 +66,22 @@ const Wrapper = styled.div` font-size: 18px; padding: 3rem 4rem; `; +const Title1 = styled.p` + font-size: 30px; + font-weight: bolder; +`; + +const Title2 = styled.p` + font-size: 26px; + font-weight: bolder; + color: grey; +`; const RedText = styled.span` color: red; `; -const CandidatesList = styled.div` +const CandidateListWrapper = styled.div` padding: 5rem 10rem; border: 1px solid black; `; From 94926e5a9d01597fc7e98d7a0c7fde67957fdccd Mon Sep 17 00:00:00 2001 From: WooYoo Date: Fri, 17 Apr 2020 20:35:41 +0900 Subject: [PATCH 05/14] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=83=81=ED=99=A9=EB=B3=84=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?alert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/login-form.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/components/login-form.js b/src/components/login-form.js index 062eda2..b716e28 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -39,11 +39,25 @@ export default function LoginForm(props) { console.log(response); }) .catch(function (error) { - alert("아이디 혹은 비밀번호를 확인해주세요!!!"); - setUserData({ - email: "", - password: "", - }); + if (error.response.status === 404) { + alert("이메일이 존재하지 않습니다!!!"); + setUserData({ + email: "", + password: "", + }); + } else if (error.response.status === 422) { + alert("비밀번호가 틀렸습니다!!!"); + setUserData({ + email: email, + password: "", + }); + } else { + alert("다시 시도해주세요!"); + setUserData({ + email: "", + password: "", + }); + } }); }; From 42d602b6c573b94b3518f6dccd25a9886095ad86 Mon Sep 17 00:00:00 2001 From: WooYoo Date: Fri, 17 Apr 2020 21:26:25 +0900 Subject: [PATCH 06/14] =?UTF-8?q?Memo=20=EC=B6=94=EA=B0=80,=20Readme=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=EC=84=A0=ED=83=9D=20=EC=82=AC=ED=95=AD?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- README.md | 13 +++++++------ pages/index.js | 4 ++-- src/components/candidate-form.js | 2 +- src/components/login-form.js | 6 +++--- src/components/vote-form.js | 2 ++ 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index a5ac825..f014060 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ yarn-error.log* .env.development.local .env.test.local .env.production.local -.env* \ No newline at end of file +.env* +.now \ No newline at end of file diff --git a/README.md b/README.md index 461c3ec..259ea53 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ ## 실행 방법 -``` -npm install -npm run dev -``` +## [3주차] 유현우 미션 제출합니다. -- npm install : 필요한 모든 패키지를 설치합니다. 처음 1번만 실행하면 됩니다. -- npm run dev : react(next) 웹서버를 localhost:3000에서 실행합니다. +이번 미션은 사실 스터디 때 모르는 것들을 다 여쭤보고, 구현을 어느정도 했었기 때문에 그렇게 어렵지는 않았던 것 같습니다! 너무 설명을 잘해주셔가지구 ㅎㅎ 근데 중간 고사 기간이 점점 다가오니 다른 디테일에 신경을 썼어야하는데 신경을 많이 못 쓴 것 같아 아쉽습니다 ㅠㅠ
+비동기함수나 Request, Response, axios, POST, GET 같은 개념이나 함수들을 사용하면서 이런 개념들을 더 잘 이해하게 된 것 같습니다. 개념 자체는 알고 있고 한두번씩 써보긴 했었지만 개념이 조금 모호했었는데 이번에 사용하면서 완벽하진 않지만 좀 더 잘 이해하게 된 것 같습니다.
+컴포넌트나 state 같은 것들을 사용하면 사용할수록 점점 익숙해지는 것 같습니다.
+그런데 아직 memo는 정확히 어떨 때 사용해야하는지 애매하네요..로그인 창에서 쓰는 건 알겠지만 이번에 후보자들 보여주는 창에서는 어디다 넣어야할지 고민하다가 그냥 상위 컴포넌트에 넣었는데 잘 넣었는지 모르겠네요...ㅠㅠ
+그리고 예전에 혼자서 과제나 코딩을 할 때는 컨벤션을 엄격하게 지키지 않았는데 계속 보면서 익숙해져야겠습니다...그리고 css 틈 날때마다 공부는 하는데 아직은 생각한대로 척척 짜지지는 않네요..ㅠㅠ
+혼자 개발하면 기능 구현은 해도 막개발이라는 생각이 지워지질 않았는데 이렇게 코드 리뷰해주시니까 그런 불안이 많이 없어져서 좋은 것 같습니다. 꼼꼼하게 코드 리뷰해주시느라 항상 감사합니다 ㅎㅎ diff --git a/pages/index.js b/pages/index.js index cbef5bc..e1550e6 100644 --- a/pages/index.js +++ b/pages/index.js @@ -2,7 +2,7 @@ import React, { useState } from "react"; import styled from "styled-components"; import { MemoizedLoginForm } from "../src/components/login-form"; -import VoteForm from "../src/components/vote-form"; +import { MemoizedVoteForm } from "../src/components/vote-form"; export default function Home() { const [loggedIn, setLoggedIn] = useState(false); @@ -11,7 +11,7 @@ export default function Home() { 리액트 투-표 {!loggedIn && } - {loggedIn && } + {loggedIn && } ); } diff --git a/src/components/candidate-form.js b/src/components/candidate-form.js index 54ead12..878ca51 100644 --- a/src/components/candidate-form.js +++ b/src/components/candidate-form.js @@ -31,7 +31,7 @@ export default function CandidateForm(props) { { - alert(name, "님에게 투표 완료!"); + alert(name + "님에게 투표 완료!"); voteCandidate(); }} > diff --git a/src/components/login-form.js b/src/components/login-form.js index b716e28..f1c3503 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -1,12 +1,12 @@ -import React, { useState, Memo } from "react"; +import React, { useState } from "react"; import axios from "axios"; import styled from "styled-components"; export default function LoginForm(props) { //State에 로그인에 필요한 데이터 저장 const [userData, setUserData] = useState({ - email: "example@ceos.or.kr", - password: "example1!", + email: "", + password: "", }); const { loginSuccess } = props; // 변수 이름 쉽게하기 위해 diff --git a/src/components/vote-form.js b/src/components/vote-form.js index e58aa7e..bc1611b 100644 --- a/src/components/vote-form.js +++ b/src/components/vote-form.js @@ -59,6 +59,8 @@ export default function VoteForm() { ); } +export const MemoizedVoteForm = React.memo(VoteForm); + const Wrapper = styled.div` width: 100%; min-height: 30rem; From 6a973b42d46ed1d4e9bc1c483125ef140f7f3b1f Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 19 Apr 2020 13:19:30 +0900 Subject: [PATCH 07/14] =?UTF-8?q?loggedIn=20state=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/index.js b/pages/index.js index e1550e6..f814bae 100644 --- a/pages/index.js +++ b/pages/index.js @@ -5,13 +5,13 @@ import { MemoizedLoginForm } from "../src/components/login-form"; import { MemoizedVoteForm } from "../src/components/vote-form"; export default function Home() { - const [loggedIn, setLoggedIn] = useState(false); + const [isLoggedIn, setIsLoggedIn] = useState(false); return ( 리액트 투-표 - {!loggedIn && } - {loggedIn && } + {!isLoggedIn && } + {isLoggedIn && } ); } From c80db5620ed9588c909f843ddc7e5dd6555d5654 Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 19 Apr 2020 13:24:34 +0900 Subject: [PATCH 08/14] =?UTF-8?q?put=EC=9D=98=20=EC=9D=B8=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/candidate-form.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/candidate-form.js b/src/components/candidate-form.js index 878ca51..2a1e2e6 100644 --- a/src/components/candidate-form.js +++ b/src/components/candidate-form.js @@ -7,9 +7,7 @@ export default function CandidateForm(props) { const voteCandidate = () => { axios - .put(process.env.API_HOST + "/candidates/" + id + "/vote/", { - params: {}, - }) + .put(process.env.API_HOST + `/candidates/${id}/vote/`) .then(function (response) { console.log(response); // return response.data; From 707178c0ee2ff6733bc0e1fdd6916e3a7b974c15 Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 19 Apr 2020 13:26:58 +0900 Subject: [PATCH 09/14] =?UTF-8?q?put=EC=9D=98=20finally=20=EB=B8=94?= =?UTF-8?q?=EB=A1=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/candidate-form.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/candidate-form.js b/src/components/candidate-form.js index 2a1e2e6..7441f6d 100644 --- a/src/components/candidate-form.js +++ b/src/components/candidate-form.js @@ -15,9 +15,6 @@ export default function CandidateForm(props) { .catch(function (error) { console.log(error); alert("투표 실패!"); - }) - .finally(function () { - // always executed }); }; return ( From ad0930d6c1f3277e65b9f81e1f5d7caa26996d40 Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 19 Apr 2020 13:27:40 +0900 Subject: [PATCH 10/14] =?UTF-8?q?=ED=88=AC=ED=91=9C=20=EC=84=B1=EA=B3=B5?= =?UTF-8?q?=20alert=EB=A5=BC=20axios.put.then=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/candidate-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/candidate-form.js b/src/components/candidate-form.js index 7441f6d..ce75f43 100644 --- a/src/components/candidate-form.js +++ b/src/components/candidate-form.js @@ -10,6 +10,7 @@ export default function CandidateForm(props) { .put(process.env.API_HOST + `/candidates/${id}/vote/`) .then(function (response) { console.log(response); + alert(name + "님에게 투표 완료!"); // return response.data; }) .catch(function (error) { @@ -26,7 +27,6 @@ export default function CandidateForm(props) { { - alert(name + "님에게 투표 완료!"); voteCandidate(); }} > From 547a89cf9e8aeb30db7a275d8be511d067b3c349 Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 19 Apr 2020 13:29:18 +0900 Subject: [PATCH 11/14] =?UTF-8?q?post=EC=9D=98=20=EC=9D=B8=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20=EC=83=88=EB=A1=9C=EC=9A=B4=20=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=97=90=EC=84=9C=20userData=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/login-form.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/login-form.js b/src/components/login-form.js index f1c3503..731aa38 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -29,10 +29,7 @@ export default function LoginForm(props) { return; } axios - .post(process.env.API_HOST + "/auth/signin/", { - email: email, - password: password, - }) + .post(process.env.API_HOST + "/auth/signin/", userData) .then(function (response) { alert("로그인에 성공하셨습니다!!!"); loginSuccess(true); From 44161a50797232354c810389592d3208550a9ceb Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 19 Apr 2020 13:40:35 +0900 Subject: [PATCH 12/14] =?UTF-8?q?Error=20=EC=B2=98=EB=A6=AC=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=EC=9D=84=20=ED=95=A8=EC=88=98=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC,=20Memo=20=EB=B6=80=EB=B6=84=EC=9D=84=20export=20defa?= =?UTF-8?q?ult=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/index.js | 8 +++---- src/components/login-form.js | 44 +++++++++++++++++++----------------- src/components/vote-form.js | 4 ++-- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/pages/index.js b/pages/index.js index f814bae..5d751e2 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,8 +1,8 @@ import React, { useState } from "react"; import styled from "styled-components"; -import { MemoizedLoginForm } from "../src/components/login-form"; -import { MemoizedVoteForm } from "../src/components/vote-form"; +import LoginForm from "../src/components/login-form"; +import VoteForm from "../src/components/vote-form"; export default function Home() { const [isLoggedIn, setIsLoggedIn] = useState(false); @@ -10,8 +10,8 @@ export default function Home() { return ( 리액트 투-표 - {!isLoggedIn && } - {isLoggedIn && } + {!isLoggedIn && } + {isLoggedIn && } ); } diff --git a/src/components/login-form.js b/src/components/login-form.js index 731aa38..49e5f72 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -2,7 +2,7 @@ import React, { useState } from "react"; import axios from "axios"; import styled from "styled-components"; -export default function LoginForm(props) { +function LoginForm(props) { //State에 로그인에 필요한 데이터 저장 const [userData, setUserData] = useState({ email: "", @@ -22,6 +22,26 @@ export default function LoginForm(props) { } }; + const initFormData = (status) => { + const initializedUserData = { + email: "", + password: "", + }; + + switch (status) { + case 404: + alert("이메일이 존재하지 않습니다!!!"); + break; + case 422: + alert("비밀번호가 틀렸습니다!!!"); + initializedUserData.email = email; + break; + default: + alert("로그인을 다시 시도해주세요!"); + } + + setUserData(initializedUserData); + }; // 로그인 시도 const tryLogin = () => { if (checkBlank() === false) { @@ -36,25 +56,7 @@ export default function LoginForm(props) { console.log(response); }) .catch(function (error) { - if (error.response.status === 404) { - alert("이메일이 존재하지 않습니다!!!"); - setUserData({ - email: "", - password: "", - }); - } else if (error.response.status === 422) { - alert("비밀번호가 틀렸습니다!!!"); - setUserData({ - email: email, - password: "", - }); - } else { - alert("다시 시도해주세요!"); - setUserData({ - email: "", - password: "", - }); - } + initFormData(error.response.status); }); }; @@ -84,7 +86,7 @@ export default function LoginForm(props) { ); } - +export default React.memo(LoginForm); export const MemoizedLoginForm = React.memo(LoginForm); const Wrapper = styled.div` diff --git a/src/components/vote-form.js b/src/components/vote-form.js index bc1611b..6fbb155 100644 --- a/src/components/vote-form.js +++ b/src/components/vote-form.js @@ -3,7 +3,7 @@ import axios from "axios"; import styled from "styled-components"; import CandidateForm from "./candidate-form"; -export default function VoteForm() { +function VoteForm() { const [candidateList, setCandidateList] = useState(null); // useEffect는 렌더링 될때마다 실행, [] 인자에는 바뀔 떄마다 렌더링할 변수가 들어간다 @@ -59,7 +59,7 @@ export default function VoteForm() { ); } -export const MemoizedVoteForm = React.memo(VoteForm); +export default React.memo(VoteForm); const Wrapper = styled.div` width: 100%; From d9eecd441ec00f3673857c3a8ca8da5ed3cff9df Mon Sep 17 00:00:00 2001 From: WooYoo Date: Sun, 19 Apr 2020 13:46:56 +0900 Subject: [PATCH 13/14] =?UTF-8?q?=EA=B0=80=EB=8F=85=EC=84=B1=EC=9D=84=20?= =?UTF-8?q?=EB=86=92=ED=9E=88=EA=B8=B0=20=EC=9C=84=ED=95=B4=20CandidateLis?= =?UTF-8?q?tWrapper=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/candidate-form.js | 2 +- src/components/vote-form.js | 26 +++++++++----------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/components/candidate-form.js b/src/components/candidate-form.js index ce75f43..44a4fe2 100644 --- a/src/components/candidate-form.js +++ b/src/components/candidate-form.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import styled from "styled-components"; import axios from "axios"; diff --git a/src/components/vote-form.js b/src/components/vote-form.js index 6fbb155..1a8b40a 100644 --- a/src/components/vote-form.js +++ b/src/components/vote-form.js @@ -38,23 +38,15 @@ function VoteForm() { 프론트앤드 인기쟁이는 누구? CEOS 프론트엔드 개발자 인기 순위 및 투표 창입니다. - {candidateList !== null && ( - - {candidateList - .sort((a, b) => { - return b.voteCount - a.voteCount; - }) - .map((candidate) => ( - - ))} - - )} + + {candidateList && + candidateList + .sort((a, b) => b.voteCount - a.voteCount) + .map((candidate, index) => { + const { _id: id, name, voteCount } = candidate; + return ; + })} + ); } From f12cfaf865eac8b618e6ae03892ef4d0f55fd5b8 Mon Sep 17 00:00:00 2001 From: WooYoo Date: Wed, 22 Apr 2020 16:21:34 +0900 Subject: [PATCH 14/14] Add Custom Hook and Add login& vote Pages --- pages/login.js | 32 ++++++++++++ pages/vote.js | 33 +++++++++++++ src/components/candidate-form.js | 7 +-- src/components/login-form.js | 7 ++- src/components/myHooks/candidates.js | 38 +++++++++++++++ src/components/vote-form.js | 73 ++++++++++------------------ 6 files changed, 139 insertions(+), 51 deletions(-) create mode 100644 pages/login.js create mode 100644 pages/vote.js create mode 100644 src/components/myHooks/candidates.js diff --git a/pages/login.js b/pages/login.js new file mode 100644 index 0000000..7e359ef --- /dev/null +++ b/pages/login.js @@ -0,0 +1,32 @@ +import React, { useState } from "react"; +import styled from "styled-components"; +import { useRouter } from "next/router"; +import LoginForm from "../src/components/login-form"; + +import Link from "next/link"; + +export default function Home() { + const router = useRouter(); + const { userName } = router.query; + return ( + + 리액트 투-표 + + + 투표하러가기 + + + ); +} + +const Wrapper = styled.div` + font-size: 3rem; + min-height: 100vh; + padding: 10rem 40rem; + background-color: Azure; +`; + +const Title = styled.p` + font-weight: bold; + margin-bottom: 2rem; +`; diff --git a/pages/vote.js b/pages/vote.js new file mode 100644 index 0000000..eb1f1de --- /dev/null +++ b/pages/vote.js @@ -0,0 +1,33 @@ +import React, { useState } from "react"; +import styled from "styled-components"; +import { useRouter } from "next/router"; + +import VoteForm from "../src/components/vote-form"; + +export default function Home() { + const router = useRouter(); + const { userName } = router.query; + return ( + + router.back()}>리액트 투-표 + {userName}님 안녕하세요! + + + ); +} + +const Name = styled.span` + color: blue; +`; + +const Wrapper = styled.div` + font-size: 3rem; + min-height: 100vh; + padding: 10rem 40rem; + background-color: Azure; +`; + +const Title = styled.p` + font-weight: bold; + margin-bottom: 2rem; +`; diff --git a/src/components/candidate-form.js b/src/components/candidate-form.js index 44a4fe2..2d805cb 100644 --- a/src/components/candidate-form.js +++ b/src/components/candidate-form.js @@ -3,14 +3,15 @@ import styled from "styled-components"; import axios from "axios"; export default function CandidateForm(props) { - const { name, voteCount, rank, id } = props; + const { name, voteCount, rank, id, refetch } = props; - const voteCandidate = () => { - axios + const voteCandidate = async () => { + await axios .put(process.env.API_HOST + `/candidates/${id}/vote/`) .then(function (response) { console.log(response); alert(name + "님에게 투표 완료!"); + refetch(); // return response.data; }) .catch(function (error) { diff --git a/src/components/login-form.js b/src/components/login-form.js index 49e5f72..c36be59 100644 --- a/src/components/login-form.js +++ b/src/components/login-form.js @@ -4,10 +4,12 @@ import styled from "styled-components"; function LoginForm(props) { //State에 로그인에 필요한 데이터 저장 + const [userData, setUserData] = useState({ - email: "", - password: "", + email: "example@ceos.or.kr", + password: "example1!", }); + const { loginSuccess } = props; // 변수 이름 쉽게하기 위해 const { email, password } = userData; @@ -86,6 +88,7 @@ function LoginForm(props) { ); } + export default React.memo(LoginForm); export const MemoizedLoginForm = React.memo(LoginForm); diff --git a/src/components/myHooks/candidates.js b/src/components/myHooks/candidates.js new file mode 100644 index 0000000..8508f90 --- /dev/null +++ b/src/components/myHooks/candidates.js @@ -0,0 +1,38 @@ +import { useState, useEffect } from "react"; +import axios from "axios"; + +export const useCandidates = () => { + // 로그인 데이터 + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(false); + + const fetchData = async () => { + // 로딩 시작, 에러 없음 + setIsLoading(true); + setError(false); + + try { + const data = await axios + .get(process.env.API_HOST + "/candidates/", { + params: {}, + }) + .then(function (response) { + setIsLoading(false); + return response.data; + }); + setData(data); + } catch (error) { + console.log(error); + setError(error); + } + // 로딩 끝 + setIsLoading(false); + }; + + useEffect(() => { + fetchData(); + }, []); + + return { candidateList: data, isLoading, error, refetch: fetchData }; +}; diff --git a/src/components/vote-form.js b/src/components/vote-form.js index 1a8b40a..c9ab502 100644 --- a/src/components/vote-form.js +++ b/src/components/vote-form.js @@ -1,54 +1,35 @@ import React, { useEffect, useState } from "react"; -import axios from "axios"; import styled from "styled-components"; import CandidateForm from "./candidate-form"; +import { useCandidates } from "./myHooks/candidates"; function VoteForm() { - const [candidateList, setCandidateList] = useState(null); - - // useEffect는 렌더링 될때마다 실행, [] 인자에는 바뀔 떄마다 렌더링할 변수가 들어간다 - // candidates의 값이 바뀔 때마다 렌더링한다. - useEffect(() => { - takeCandidateList(); - }, [candidateList]); - - // axois에서 후보자 명단 받아온다. - - const takeCandidateList = async () => { - const data = await axios - .get(process.env.API_HOST + "/candidates/", { - params: {}, - }) - .then(function (response) { - return response.data; - }) - .catch(function (error) { - console.log(error); - }) - .finally(function () { - // always executed - }); - setCandidateList(data); - }; - // candidates값이 null이 아니면 - - return ( - - - 프론트앤드 인기쟁이는 누구? - - CEOS 프론트엔드 개발자 인기 순위 및 투표 창입니다. - - {candidateList && - candidateList - .sort((a, b) => b.voteCount - a.voteCount) - .map((candidate, index) => { - const { _id: id, name, voteCount } = candidate; - return ; - })} - - - ); + const { candidateList, isLoading, error, refetch } = useCandidates(); + if (error) { + console.log(error); +
{error}
; + } + if (candidateList) + return ( + + + 프론트앤드 인기쟁이는 누구? + + CEOS 프론트엔드 개발자 인기 순위 및 투표 창입니다. + + {candidateList && + candidateList + .sort((a, b) => b.voteCount - a.voteCount) + .map((candidate, index) => { + const { _id: id, name, voteCount } = candidate; + return ( + + ); + })} + + + ); + return <>; } export default React.memo(VoteForm);