Skip to content
This repository was archived by the owner on Sep 19, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ yarn-error.log*
.env.development.local
.env.test.local
.env.production.local
.env*
.env*
.now
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# react-vote-11th

## 실행 방법
## 구현 방법

```
npm install
npm run dev
```
1. login form과 vote form을 로그인 결과에 따라 보여주는 페이지 index를 만든다.
2. 로그인시 서버에 리퀘스트를 보내 이메일과 비밀번호를 확인한다
3. vote form 내부 리스트 정렬을 vote list를 구현하여 진행한다

- npm install : 필요한 모든 패키지를 설치합니다. 처음 1번만 실행하면 됩니다.
- npm run dev : react(next) 웹서버를 localhost:3000에서 실행합니다.
## 어려웠던 점

개인적으로 시간을 너무 촉박하게 잡고 과제를 진행해서 아쉬운 부분이 많이 남습니다. 그리고 이상한 부분에서 계속 삽질을 해서,, 흑흑 시간이 조금 많이 모자랐던 것 같습니다. 서버 통신을 axios로 하는게 되게 간단하고 효율적인 것 같아 좋았습니다! memo는 어느 곳에 써야할지 아직은 감이 잘 안잡혀서 이부분도 더 공부하고 싶습니다.
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"start": "node server"
},
"dependencies": {
"axios": "^0.19.2",
"compression": "^1.7.4",
"dotenv": "^8.2.0",
"express": "^4.17.1",
Expand Down
14 changes: 11 additions & 3 deletions pages/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import React from "react";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

import LoginForm from "../src/components/login-form";
import VoteForm from "../src/components/vote-form";

export default function Home() {
const [isLoggedIn, setIsLoggedIn] = useState(false);

return (
<Wrapper>
리액트 투-표
<LoginForm />
<Title>리액트 투-표</Title>
{!isLoggedIn && <LoginForm {...{ setIsLoggedIn }} />}
{isLoggedIn && <VoteForm />}
</Wrapper>
);
}
Expand All @@ -17,3 +22,6 @@ const Wrapper = styled.div`
padding: 10rem 40rem;
background-color: Azure;
`;
const Title = styled.h1`
font-size: 4rem;
`;
102 changes: 97 additions & 5 deletions src/components/login-form.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,106 @@
import React from "react";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

export default function LoginForm() {
return <Wrapper>안녕 나는 로그인 폼!</Wrapper>;
}
export default function LoginForm({ setIsLoggedIn }) {
const [form, setForm] = useState({ email: "", password: "" });

const handleFormChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value,
});
};
const resetForm = () => {
setForm({ email: "", password: "" });
};
const validCheck = () => {
if (form.email.length === 0 || form.password.length === 0) {
alert("모든 항목을 입력하세요");
return false;
}
return true;
};
const handleSubmit = () => {
console.log(form);
if (!validCheck) return;
axios
.post(process.env.API_HOST + "/auth/signin/", form)
.then(function (response) {
console.log(response);
alert("로그인 성공!");
setIsLoggedIn(true);
})
.catch(function (error) {
resetForm();
alert("로그인 실패!");
console.log(error);
});
};

return (
<Wrapper>
<Title>로그인</Title>
<InputWrapper>
<Row>
<Label>EMAIL</Label>
<Input
name="email"
type="text"
value={form.email}
onChange={handleFormChange}
/>
</Row>
<Row>
<Label>PASSWORD</Label>
<Input
name="password"
value={form.password}
onChange={handleFormChange}
type="password"
/>
</Row>
</InputWrapper>
<SubmitButton onClick={handleSubmit}>로그인</SubmitButton>
</Wrapper>
);
}
export const MemoizedLoginForm = React.memo(LoginForm);
const Wrapper = styled.div`
width: 100%;
width: 100
min-height: 30rem;
background-color: white;
font-size: 18px;
padding: 3rem 4rem;
display: flex;
flex-direction: column;
`;
const Title = styled.p`
font-weight: bold;
`;
const InputWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: space-around;
`;
const Row = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
const Input = styled.input`
width: 50rem;
height: 3rem;
`;
const Label = styled.label`
font-size: 12px;
`;
const SubmitButton = styled.button`
display: block;
margin-left: auto;
font-size: 1.8rem;
cursor: pointer;
padding: 0.5rem 1rem;
border: 1rem none;
outline: none;
`;
49 changes: 47 additions & 2 deletions src/components/vote-form.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,55 @@
import React from "react";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

import CandidateCard from "./vote-list";
import { useCandidates } from "../hooks/Candidates";

export default function VoteForm() {
return <Wrapper>안녕 나는 투표 폼!</Wrapper>;
const { candidates, loading, error, fetchData } = useCandidates();
console.log(candidates);

return (
<Wrapper>
<Title>
<Red>프론트엔드 인기쟁이</Red>는 누구?
</Title>
<SubTitle>CEOS 프론트엔드 개발자 인기순위 및 투표창입니다.</SubTitle>
<VoteSection>
{candidates &&
candidates
.sort((a, b) => {
return b.voteCount - a.voteCount;
})
.map((candidate, index) => {
const { _id: id } = candidate;
return (
<CandidateCard
rank={index + 1}
key={id}
{...candidate}
getCandidateList={fetchData}
/>
);
})}
</VoteSection>
</Wrapper>
);
}
export const MemoizedVoteForm = React.memo(VoteForm);

const Title = styled.h2``;
const Red = styled.strong`
color: red;
`;
const SubTitle = styled.h3`
color: grey;
`;
const VoteSection = styled.div`
width: 100%;
padding: 5rem 10rem;
border: 1px solid black;
`;
const Wrapper = styled.div`
width: 100%;
min-height: 30rem;
Expand Down
55 changes: 55 additions & 0 deletions src/components/vote-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

export default function VoteList({
name,
voteCount,
_id: id,
rank,
getCandidateList,
}) {
const handleVote = () => {
axios
.put(process.env.API_HOST + `/candidates/${id}/vote/`, {
params: {},
})
.then(function (response) {
console.log(response);
getCandidateList();
alert(name + "님께 투표 했습니다!");
})
.catch(function (error) {
console.log(error);
alert("투표 실패했습니다!");
});
};

return (
<Wrapper>
<Candidate>
<Rank>{rank}위:</Rank> {name} [{voteCount}]표
</Candidate>
<VoteButton onClick={handleVote}>투표</VoteButton>
</Wrapper>
);
}
const Wrapper = styled.div`
width: 100%;
background-color: white;
font-size: 18px;
display: flex;
flex-direction: row;
align-items: center;
`;
const Rank = styled.strong``;
const Candidate = styled.div``;
const VoteButton = styled.button`
background-color: navy;
color: white;
font-size: 2rem;
padding: 0.5rem 1rem;
border-radius: 1rem;
display: block;
margin-left: auto;
`;
32 changes: 32 additions & 0 deletions src/hooks/Candidates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import axios from "axios";

export const useCandidates = () => {
const [candidates, setCandidateList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const fetchData = async () => {
setError(false);
setLoading(true);

try {
await axios
.get(process.env.API_HOST + "/candidates/", candidates)
.then(({ data }) => {
setCandidateList(data);
})
.catch(function (error) {
console.log(error);
});
} catch (error) {
setError(true);
}
setLoading(false);
};
useEffect(() => {
fetchData();
}, []);

return { candidates, loading, error, fetchData };
};