Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1단계 - 행운의 로또미션] 하루(김하루) 미션 제출합니다. #8

Merged
merged 42 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ded6413
docs: 1단계 구현 기능 목록 작성
365kim Apr 13, 2021
c0fde78
chore: 코드포매터, 린터 초기 설정
365kim Apr 13, 2021
66b6180
feat: 앱 전체 구조 설정
365kim Apr 13, 2021
e217a9d
fix: index.html 경로 수정
jum0 Apr 13, 2021
06c59da
chore: lottie 설치, 린터 설정 추가
jum0 Apr 14, 2021
58f4469
docs: 구현 기능 목록 수정
jum0 Apr 14, 2021
9a5cb39
feat: 구입금액 입력 컴포넌트 구현
jum0 Apr 14, 2021
4bc26b1
feat: 로또 발급 및 확인 컴포넌트 구현
jum0 Apr 14, 2021
afb3a8f
refactor: 삼항연산자 대신 단축평가 사용
365kim Apr 15, 2021
547c119
feat: 표시되는 로또 번호의 자릿수 맞춤
365kim Apr 15, 2021
95d42a2
refactor: 로또 배열의 key값 수정
365kim Apr 15, 2021
817d210
feat: 당첨번호 발표 컴포넌트 구현
365kim Apr 15, 2021
6696c1a
feat: 당첨결과 확인 컴포넌트 구현
365kim Apr 15, 2021
3a72334
refactor: 당첨번호 JSON에서 가져오도록 변경
365kim Apr 16, 2021
65e5197
feat: 로또볼 CSS 스타일링 및 메인색상 적용
365kim Apr 16, 2021
fff3536
feat: 애니메이션 구현
jum0 Apr 16, 2021
c918482
feat: 구매한 복권별 당첨 여부 확인 기능 구현
jum0 Apr 16, 2021
d023d11
chore: 회차 당첨번호 현행화
365kim Apr 16, 2021
fee90a9
refactor: 변수명 수정
365kim Apr 16, 2021
c3b7b20
fix: 콘솔 에러 수정
365kim Apr 16, 2021
4ff87ad
refactor: 컴포넌트 구조 변경
365kim Apr 18, 2021
30b056d
refactor: 재정렬 가능성 없는 배열의 key값 index로 설정
365kim Apr 18, 2021
1ee3b01
refactor: getMatchCount 함수에 reduce 활용
365kim Apr 18, 2021
911aaf7
refactor: getStatistics -> getComputedResult 이름 변경
365kim Apr 18, 2021
a35218f
refactor: shouldPlayAnimation -> isLoading 이름 변경
365kim Apr 18, 2021
c98469a
refactor: 버튼 스타일 변경
365kim Apr 18, 2021
2b9abac
refactor: 불필요한 클래스 삭제
365kim Apr 18, 2021
4551a99
refactor: HTML 태그 변경 및 삭제
365kim Apr 18, 2021
e2f6bb1
refactor: CSS 클래스 제어 및 BEM 네이밍 적용
365kim Apr 18, 2021
c7b3c81
refactor: App.js에 initialState 추가
365kim Apr 18, 2021
bb9899b
refactor: 리팩토링에 따른 네이밍 통일
365kim Apr 18, 2021
46f94bd
refactor: <Lottie /> props 스프레드로 전달
365kim Apr 18, 2021
be11367
refactor: let 대신 const 사용
365kim Apr 18, 2021
90bef34
refactor: props 중 자식요소 네이밍 변경
365kim Apr 18, 2021
208b3e5
refactor: props 중 자식요소 네이밍 변경
365kim Apr 18, 2021
e817296
refactor: 버튼의 필수 props 외에는 스프레드로 전달
365kim Apr 18, 2021
c3ae7f5
chore: 브랜치 병합
365kim Apr 18, 2021
7a3ec58
chore: 브랜치 병합
365kim Apr 18, 2021
96e6b43
refactor: Classnames 라이브러리 적용
365kim Apr 18, 2021
f0fac7f
refactor: String 내장메서드 padStart 적용
365kim Apr 18, 2021
903c080
refactor: Plain/SubmitButton -> Button컴포넌트로 통합
365kim Apr 18, 2021
61ff42a
refactor: <Record> 생성, <ResultSummary> 삭제
365kim Apr 18, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"env": {
"browser": true
},
"extends": ["airbnb", "airbnb/hooks", "plugin:prettier/recommended"],
"plugins": ["prettier"],
"parserOptions": {
"sourceType": "module"
},
"parser": "babel-eslint",
"rules": {
"no-new": "off",
"no-alert": "off",
"no-param-reassign": "off",
"no-return-assign": "off",
"import/extensions": "off",
"import/prefer-default-export": "off",
"max-depth": ["error", 1],
"react/react-in-jsx-scope": "off",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/jsx-one-expression-per-line": "off",
"react/prefer-stateless-function": "off",
"react/prop-types": "off",
"react/destructuring-assignment": "off",
"max-classes-per-file": ["error", 3],
"react/sort-comp": "off"
}
}
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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*

# editor
.vscode/
10 changes: 10 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"semi": true,
"useTabs": false,
"tabWidth": 2,
"printWidth": 120,
"endOfLine": "auto",
"singleQuote": true,
"arrowParens": "always",
"trailingComma": "all"
}
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,33 @@
- Boilerplate는 페어와 협의하여 자유롭게 선택합니다.
- CSS는 레벨1에 있는 코드를 직접 찾아서 자유롭게 재활용합니다.
- 불필요한 Third-Party 없이 React에서 제공되는 것만으로 구현합니다.
- 테스트 도구 선정부터 작성까지 일절 관여하지 않습니다. 자유롭게 즐겨보세요
- 테스트 도구 선정부터 작성까지 일절 관여하지 않습니다. 자유롭게 즐겨보세요.
<br>

## 📝 Requirements

### Step1

- [ ] `Class Component`를 사용합니다.
- [ ] 로또 구입 금액을 입력하면, 금액에 해당하는 로또를 발급해야 한다.
- [ ] 로또 1장의 가격은 1,000원이다.
- [ ] 소비자는 **자동 구매**만 할 수 있다.
- [ ] 복권 번호는 번호보기 토글 버튼을 클릭하면, 볼 수 있어야 한다.
- [ ] 결과 확인하기 버튼을 누르면 당첨 통계, 수익률을 모달로 확인할 수 있다.
- [ ] 로또 당첨 금액은 고정되어 있는 것으로 가정한다.
- [ ] 다시 시작하기 버튼을 누르면 초기화 되서 다시 구매를 시작할 수 있다.
- 리액트 제한사항
- [x] 클래스 컴포넌트를 사용한다.
- 구입금액 입력
- [x] 로또 구입 금액을 입력하면, 유효성 검사 결과를 실시간으로 표시한다.
- [x] 최소 화폐단위(1원) 미만의 자릿수가 포함된 경우
- [x] 로또 1장의 가격(1,000원) 미만일 경우
- [x] 로또 1장의 가격(1,000원)으로 나누어 떨어지지 않을 경우
- 로또 발급 및 확인
- [x] 로또 구입 금액을 제출하면, 자동구매로 금액에 해당하는 로또를 발급한다.
- [x] 번호보기 토글 버튼을 클릭하면 복권번호를 볼 수 있다.
- 당첨번호 발표
- [x] 로또 발급 후 카운트 다운(3초)후에 이번주 당첨번호와 결과확인 버튼이 표시된다.
- 당첨결과 확인
- [x] 결과 확인하기 버튼을 누르면 구매한 복권의 당첨여부 및 수익률을 모달로 표시된다.
- [x] 로또 당첨 금액은 고정되어 있는 것으로 가정한다.
- [x] 다시 시작하기 버튼을 누르면 초기화 되서 다시 구매를 시작할 수 있다.

### Step2

- [ ] Step1의 `Class Component`를 `Function Component`로 마이그레이션 합니다.

### 공통 심화

- [ ] UI를 통해 **실시간으로** 당첨 번호 발표까지 남은 시간을 제공합니다.
- [ ] 다시 시작하기 버튼을 누르면 당첨 번호 발표 시간도 사라진다.
- [ ] Step1의 클래스 컴포넌트를 함수형 컴포넌트로 마이그레이션 한다.

## 👏 Contributing

Expand Down
47 changes: 47 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "1st-react-lotto",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"babel-eslint": "^10.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-lottie": "^1.2.3",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"eslint": "^7.24.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"prettier": "^2.2.1"
}
}
12 changes: 12 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Lotto App" />
<title>🎱 행운의 로또</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
1 change: 1 addition & 0 deletions src/animations/coin.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/animations/countDown.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"v":"5.5.2","fr":60,"ip":0,"op":180,"w":750,"h":750,"nm":"countdown_3_lottie","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[361.5,394,0],"ix":2},"a":{"a":0,"k":[-5.281,-158.313,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[58.024,-374.473],[-95.374,-310.745],[-95.374,-241.377],[-10.215,-274.087],[-10.215,0],[64.228,0],[64.228,-374.473]],"c":true},"ix":2},"nm":"1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9450980392156862,0.44313725490196076,0.49019607843137253,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,375,0],"ix":2},"a":{"a":0,"k":[-1.747,-187.236,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,35.53],[73.879,0],[9.023,-65.984],[0,0],[-32.71,0],[0,-24.814],[15.227,-18.611],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[23.687,-26.506],[0,-67.112],[-73.879,0],[0,0],[4.512,-32.146],[28.762,0],[0,12.407],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-7.105,-69.368],[79.745,-169.189],[120.351,-262.808],[-1.465,-374.473],[-127.23,-263.372],[-60.118,-252.092],[-4.285,-305.669],[44.78,-263.372],[19.401,-213.179],[-124.974,-40.042],[-124.974,0],[123.735,0],[123.735,-69.368]],"c":true},"ix":2},"nm":"2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9450980392156862,0.44313725490196076,0.49019607843137253,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]},{"id":"comp_2","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,375,0],"ix":2},"a":{"a":0,"k":[-4.589,-184.698,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[40.042,14.663],[0,31.018],[64.856,0],[25.942,-30.454],[0,0],[-23.123,0],[-1.128,-22.559],[40.042,0],[0,0],[0,0],[0,0],[0,-31.018],[38.914,0],[17.483,23.687],[0,0],[-52.449,0],[0,66.548]],"o":[[29.89,-15.227],[1.128,-62.6],[-47.373,0],[0,0],[14.099,-18.047],[27.07,0],[1.692,25.942],[0,0],[0,0],[0,0],[37.786,0],[0,29.89],[-27.07,0],[0,0],[26.506,42.297],[80.083,0],[0,-43.425]],"v":[[58.857,-199.08],[107.358,-272.395],[-7.126,-374.473],[-118.792,-322.588],[-78.186,-276.343],[-15.586,-306.233],[31.787,-269.575],[-29.685,-222.202],[-50.552,-222.202],[-50.552,-165.242],[-15.022,-165.242],[48.706,-112.793],[-15.586,-62.6],[-81.57,-99.822],[-133.455,-59.78],[-12.202,5.076],[124.277,-107.153]],"c":true},"ix":2},"nm":"3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9450980392156862,0.44313725490196076,0.49019607843137253,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"3","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"number 01","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":119,"s":[3],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":125,"s":[100],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":175,"s":[100],"e":[0]},{"t":181}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,375,0],"ix":2},"a":{"a":0,"k":[375,375,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":119,"s":[50,50,100],"e":[108,108,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":131,"s":[108,108,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":141,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":161,"s":[100,100,100],"e":[106,106,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":169,"s":[106,106,100],"e":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":180,"s":[0,0,100],"e":[50,50,100]},{"t":181}],"ix":6}},"ao":0,"w":750,"h":750,"ip":119,"op":181,"st":119,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"number 02","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[3],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":66,"s":[100],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":116,"s":[100],"e":[0]},{"t":122}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,375,0],"ix":2},"a":{"a":0,"k":[375,375,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[50,50,100],"e":[108,108,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":72,"s":[108,108,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":82,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":102,"s":[100,100,100],"e":[106,106,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":110,"s":[106,106,100],"e":[50,50,100]},{"t":122}],"ix":6}},"ao":0,"w":750,"h":750,"ip":60,"op":122,"st":60,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"number 03","refId":"comp_2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[3],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[100],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":56,"s":[100],"e":[0]},{"t":62}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[375,375,0],"ix":2},"a":{"a":0,"k":[375,375,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[50,50,100],"e":[108,108,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[108,108,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":22,"s":[100,100,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":42,"s":[100,100,100],"e":[106,106,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":50,"s":[106,106,100],"e":[50,50,100]},{"t":62}],"ix":6}},"ao":0,"w":750,"h":750,"ip":0,"op":62,"st":0,"bm":0}],"markers":[]}
17 changes: 17 additions & 0 deletions src/components/Animation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component } from 'react';
import Lottie from 'react-lottie';

export default class Animation extends Component {
render() {
return (
<Lottie
height={this.props.height}
speed={this.props.speed}
options={{
animationData: this.props.animationData,
loop: false,
}}
/>
);
}
}
JaeYeopHan marked this conversation as resolved.
Show resolved Hide resolved
97 changes: 97 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Component } from 'react';
import PurchaseAmount from './PurchaseAmount.js';
import PurchaseLotto from './PurchaseLotto.js';
import DrawNumbers from './DrawNumbers.js';
import WinningResult from './WinningResult.js';
import getRandomNumber from '../utils/getRandomNumber.js';
import { LOTTO_MIN_NUMBER, LOTTO_MAX_NUMBER, LOTTO_NUMBERS_LENGTH } from '../constants/lottoRules.js';
import '../css/app.css';

export default class App extends Component {
constructor() {
super();
this.state = {
lottoBundle: [],
drawNumber: {},
isShowingWinningResult: false,
shouldReset: false,
};

this.onPurchaseLotto = this.onPurchaseLotto.bind(this);
this.setDrawNumber = this.setDrawNumber.bind(this);
this.onShowWinningResult = this.onShowWinningResult.bind(this);
this.onCloseWinningResult = this.onCloseWinningResult.bind(this);
this.onReset = this.onReset.bind(this);
this.didReset = this.didReset.bind(this);
}

onPurchaseLotto({ numOfLotto }) {
this.setState({ lottoBundle: [...Array(numOfLotto)].map(() => this.createLotto()) });
}

setDrawNumber({ drawNumber }) {
this.setState({ drawNumber });
}

onShowWinningResult() {
this.setState({ isShowingWinningResult: true });
}

onCloseWinningResult() {
this.setState({ isShowingWinningResult: false });
}

onReset() {
this.setState({ lottoBundle: [], isShowingWinningResult: false, shouldReset: true });
}

didReset() {
this.setState({ shouldReset: false });
}

createLotto(array = []) {
const number = getRandomNumber({ min: LOTTO_MIN_NUMBER, max: LOTTO_MAX_NUMBER });

if (array.length === LOTTO_NUMBERS_LENGTH) {
return array.sort((a, b) => a - b);
}
if (!array.includes(number)) {
array.push(number);
}

return this.createLotto(array);
}

render() {
const { lottoBundle, drawNumber, isShowingWinningResult, shouldReset } = this.state;
const isPurchased = Boolean(lottoBundle.length);

return (
<>
<div className="app">
<h1 className="header">행운의 로또</h1>
<main>
<PurchaseAmount
lottoBundle={lottoBundle}
onPurchaseLotto={this.onPurchaseLotto}
shouldReset={shouldReset}
didReset={this.didReset}
/>
{isPurchased && <PurchaseLotto lottoBundle={this.state.lottoBundle} />}
{isPurchased && (
<DrawNumbers setDrawNumber={this.setDrawNumber} onShowWinningResult={this.onShowWinningResult} />
)}
</main>
</div>
{isShowingWinningResult && (
<WinningResult
lottoBundle={lottoBundle}
drawNumber={drawNumber}
onCloseWinningResult={this.onCloseWinningResult}
onReset={this.onReset}
/>
)}
</>
);
}
}
97 changes: 97 additions & 0 deletions src/components/DrawNumbers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Component } from 'react';
import { LOTTO_NUMBERS_LENGTH } from '../constants/lottoRules';
import Animation from './Animation.js';
import '../css/draw-number.css';
import '../css/lotto-ball.css';
import dummyDrawNumber from '../constants/dummyData.json';
import countDown from '../animations/countDown.json';

const WINNING_NUMBER_KEY = (i) => `drwtNo${i}`;
const BONUS_NUMBER_KEY = 'bnusNo';
const DRAW_NTH_KEY = 'drwNo';
const DRAW_DATE_KEY = 'drwNoDate';
const COUNT_DOWN_ANIMATION_DURATION = 3000;

export default class DrawNumbers extends Component {
constructor(props) {
super(props);

this.state = {
shouldPlayAnimation: true,
};
this.destroyAnimation = this.destroyAnimation.bind(this);
this.drawNumber = this.getDrawNumber();
this.props.setDrawNumber({ drawNumber: this.drawNumber });
}

componentDidMount() {
setTimeout(this.destroyAnimation, COUNT_DOWN_ANIMATION_DURATION);
}

destroyAnimation() {
this.setState({ shouldPlayAnimation: false });
}

// eslint-disable-next-line class-methods-use-this
getDrawNumber() {
return {
winningNumbers: [...Array(LOTTO_NUMBERS_LENGTH)].map((_, i) =>
Number(dummyDrawNumber[WINNING_NUMBER_KEY(i + 1)]),
),
bonusNumber: Number(dummyDrawNumber[BONUS_NUMBER_KEY]),
};
}

render() {
return this.state.shouldPlayAnimation ? (
<Animation animationData={countDown} speed={1} height="140px" />
) : (
<DrawNumber drawNumber={this.drawNumber} onShowWinningResult={this.props.onShowWinningResult} />
);
}
}

class DrawNumber extends Component {
render() {
const { winningNumbers, bonusNumber } = this.props.drawNumber;
return (
<div className="draw-number-wrapper">
<h2 className="draw-number-title">
{dummyDrawNumber[DRAW_NTH_KEY]}회차 당첨번호 {dummyDrawNumber[DRAW_DATE_KEY].split('-').join('.')}
</h2>
<section className="draw-number-section">
{winningNumbers.map((v) => (
<LottoBall key={v} number={v} />
))}
<span className="plus-sign">+</span>
<span className="bonus-number-title">보너스번호</span>
<LottoBall key={bonusNumber} number={bonusNumber} />
</section>
<button type="button" className="open-result-button" onClick={this.props.onShowWinningResult}>
당첨결과 확인하기
</button>
</div>
);
}
}

class LottoBall extends Component {
render() {
const { number } = this.props;
let ballColorClassName;

if (number < 10) {
ballColorClassName = 'zeros';
} else if (number < 20) {
ballColorClassName = 'tens';
} else if (number < 30) {
ballColorClassName = 'twenties';
} else if (number < 40) {
ballColorClassName = 'thirties';
} else {
ballColorClassName = 'forties';
}

return <span className={`lotto-ball ${ballColorClassName}`}>{number < 10 ? `0${number}` : number}</span>;
}
}