From 9f5c45b6f46102050b6b8a4ff7a475fe343e5e44 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Fri, 11 Dec 2020 10:30:50 +0900 Subject: [PATCH 01/86] =?UTF-8?q?fix:=EC=BA=98=EB=A6=B0=EB=8D=94=20date?= =?UTF-8?q?=EC=97=90=20=EC=BB=A4=EC=84=9C=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/container/SelectMonth/styles.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/container/SelectMonth/styles.tsx b/client/src/container/SelectMonth/styles.tsx index 9d7529f..b1b2679 100644 --- a/client/src/container/SelectMonth/styles.tsx +++ b/client/src/container/SelectMonth/styles.tsx @@ -4,7 +4,6 @@ const MonthDiv = styled.div` display: inline-flex; width: 100%; justify-content: center; - cursor: pointer; margin: 10px 0px 0px 0px; font-size: 1.5rem; margin-bottom: 10px; @@ -12,5 +11,6 @@ const MonthDiv = styled.div` const ArrowDiv = styled.div` margin: 0px 10px; + cursor: pointer; `; export { ArrowDiv, MonthDiv }; From 69b674b1425aacf8545025dc66b8397ff0b1a1b4 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Fri, 11 Dec 2020 10:34:42 +0900 Subject: [PATCH 02/86] =?UTF-8?q?fix:=EC=BA=98=EB=A6=B0=EB=8D=94=EC=97=90?= =?UTF-8?q?=20=EB=AA=A8=EB=8B=AC=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/views/CalendarPage/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/views/CalendarPage/index.tsx b/client/src/views/CalendarPage/index.tsx index 43d1647..4d004e2 100644 --- a/client/src/views/CalendarPage/index.tsx +++ b/client/src/views/CalendarPage/index.tsx @@ -1,12 +1,18 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; import MainLayout from '@/components/common/layouts/MainLayout'; import Calendar from '@container/Calendar'; import TransactionUpdateModal from '@/container/TransactionUpdateModal'; +import TransactionModal from '@container/TransactionModal'; +import ModalToggleButton from '@components/common/buttons/ModalToggleButton'; const CalendarPage = (): JSX.Element => { + const [showModal, setShowModal] = useState(false); + const toggleModal = useCallback(() => setShowModal(!showModal), [showModal]); return ( + + ); From 96f8ba0d37f2cac58d2a8863fdda709d78aad3b5 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Fri, 11 Dec 2020 19:14:53 +0900 Subject: [PATCH 03/86] =?UTF-8?q?feat:=EC=9D=BC=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EC=8B=9C=20=EC=88=98=EC=A0=95=20=EA=B0=80=EB=8A=A5=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../container/TransactionSelectList/index.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/client/src/container/TransactionSelectList/index.tsx b/client/src/container/TransactionSelectList/index.tsx index 3c20104..e53e505 100644 --- a/client/src/container/TransactionSelectList/index.tsx +++ b/client/src/container/TransactionSelectList/index.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; +import React, { useCallback } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '@/modules/index'; +import { toggleModalOn } from '@/modules/updateModal'; import TransactionListItem from '@/components/transaction/ListItem'; import { TransactionListItemWrapper } from '@/container/TransactionList/styles'; import { TransactionModel } from '@/commons/types/transaction'; @@ -13,7 +14,14 @@ const TransactionSelectList = (): JSX.Element => { const transactionList = transactionData.get( Number(calendarDaySelector.day), ) as TransactionModel[]; + const dispatch = useDispatch(); + const toggleModal = useCallback( + (t: TransactionModel) => { + dispatch(toggleModalOn(t)); + }, + [dispatch], + ); return ( <> @@ -22,8 +30,12 @@ const TransactionSelectList = (): JSX.Element => { transactionList.map((transactionDay) => ( { + toggleModal(transactionDay); + }} key={`transaction_${transactionDay.tid}`} transaction={transactionDay} + editable /> )) From 70a322858acb2b70fd1fe22b6a1466b08b30246a Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Fri, 11 Dec 2020 19:22:43 +0900 Subject: [PATCH 04/86] =?UTF-8?q?refactor:ArrowDiv=20=EC=BB=A4=EC=84=9C?= =?UTF-8?q?=EB=B3=B4=EC=9D=B8=ED=84=B0=20=EC=A4=91=EB=B3=B5=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/container/SelectMonth/styles.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/container/SelectMonth/styles.tsx b/client/src/container/SelectMonth/styles.tsx index ad06686..f0f802d 100644 --- a/client/src/container/SelectMonth/styles.tsx +++ b/client/src/container/SelectMonth/styles.tsx @@ -12,6 +12,5 @@ const MonthDiv = styled.div` const ArrowDiv = styled.div` cursor: pointer; margin: 0px 10px; - cursor: pointer; `; export { ArrowDiv, MonthDiv }; From a4383b5beea2c822a3f81c64c3dc295ec72cdbfd Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Fri, 11 Dec 2020 21:37:49 +0900 Subject: [PATCH 05/86] =?UTF-8?q?docs:=20=EA=B8=B0=EC=88=A0=EC=8A=A4?= =?UTF-8?q?=ED=83=9D,=20=EC=95=84=ED=82=A4=ED=85=8D=EC=B2=98=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cf8a1c6..7edfa9b 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,10 @@ ### 기술스택 -![스크린샷 2020-11-20 오전 11 12 06](https://user-images.githubusercontent.com/17294694/99751186-0b66c180-2b25-11eb-8ab4-bac628a7bc22.png) +![스크린샷 2020-12-11 오후 9 36 26](https://user-images.githubusercontent.com/17294694/101904293-f5ff2780-3bf8-11eb-8775-52034f850fcb.png) + + ### Architecture -![README%20md%20455ebaf5dd964ea5aa9b3015e5a19dbd/Untitled.png](https://user-images.githubusercontent.com/60877502/99751015-c17ddb80-2b24-11eb-8e1c-fbb29330f24d.png) +![스크린샷 2020-12-11 오후 9 33 31](https://user-images.githubusercontent.com/17294694/101904084-a882ba80-3bf8-11eb-8679-78145643e0c1.png) From f3181f2ac802de1e9460fe7b56798f27367d6c98 Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Fri, 11 Dec 2020 22:37:56 +0900 Subject: [PATCH 06/86] =?UTF-8?q?fix=20:=20=EA=B3=A0=EC=A0=95=20=EC=A7=80?= =?UTF-8?q?=EC=B6=9C=20API=20=EC=BF=BC=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 잘못된 결과를 반환하는 쿼리 조건문 변경 --- .../src/domain/fixed-expenditure/fixed-expenditure.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/domain/fixed-expenditure/fixed-expenditure.service.ts b/server/src/domain/fixed-expenditure/fixed-expenditure.service.ts index 73915b0..23e5755 100644 --- a/server/src/domain/fixed-expenditure/fixed-expenditure.service.ts +++ b/server/src/domain/fixed-expenditure/fixed-expenditure.service.ts @@ -38,7 +38,7 @@ export default class FixedExpenditureService { const fixedDatas: ResultType[] = await this.fixedExpenditureRepository .query(`select f1.fid, f1.trade_at as tradeAt, f1.amount as estimatedAmount, f1.description, t1.amount as paidAmount from (select * from fixed_expenditure where uid=${uid} and trade_at between '${startDate}' and '${endDate}') f1 - left outer join (select * from transaction where uid=${uid} and trade_at between '${startDate}' and '${endDate}') t1 on f1.uid = t1.uid and f1.trade_at = t1.trade_at + left outer join (select * from transaction where uid=${uid} and trade_at between '${startDate}' and '${endDate}') t1 on f1.uid = t1.uid and f1.trade_at = t1.trade_at and t1.description = f1.description order by f1.trade_at ASC;`); const paid: FixedType[] = []; From 7335740d26b4c05c2c68919a6c2218adc04b00fd Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Fri, 11 Dec 2020 22:40:25 +0900 Subject: [PATCH 07/86] =?UTF-8?q?fix=20:=20=EB=82=A0=EC=A7=9C=EA=B0=80=20?= =?UTF-8?q?=ED=95=98=EB=A3=A8=20=EC=A0=84=EC=9C=BC=EB=A1=9C=20=EB=82=98?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 날짜가 하루 전으로 나오는 버그 수정 --- client/src/components/fixedExpenditure/Item/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/fixedExpenditure/Item/index.tsx b/client/src/components/fixedExpenditure/Item/index.tsx index 4eba381..814c067 100644 --- a/client/src/components/fixedExpenditure/Item/index.tsx +++ b/client/src/components/fixedExpenditure/Item/index.tsx @@ -13,7 +13,7 @@ const FixedExpenditureItem = ({ fixedItem, isPaid }: FixedExpenditureItemPropTyp {numberUtils.numberWithCommas(fixedItem.amount)} 원 - {fixedItem.tradeAt.toString().substring(8, 10)}일 + {new Date(fixedItem.tradeAt).toString().substring(8, 10)}일 ); }; From 42cf70554a7e8a69df7431f976f60bc126352426 Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Fri, 11 Dec 2020 22:41:21 +0900 Subject: [PATCH 08/86] =?UTF-8?q?fix=20:=20=EA=B1=B0=EB=9E=98=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=B6=94=EA=B0=80=EC=8B=9C=20=EA=B3=A0=EC=A0=95=20?= =?UTF-8?q?=EC=A7=80=EC=B6=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 거래 내역 추가시 고정 지출 업데이트 되지 않는 버그 수정 --- client/src/container/FixedExpenditure/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/container/FixedExpenditure/index.tsx b/client/src/container/FixedExpenditure/index.tsx index 348d4c8..87d72a0 100644 --- a/client/src/container/FixedExpenditure/index.tsx +++ b/client/src/container/FixedExpenditure/index.tsx @@ -9,7 +9,7 @@ import numberUtils from '@libs/numberUtils'; import * as S from './styles'; const FixedExpenditure = (): JSX.Element => { - const { datePicker, fixedExpenditure } = useSelector((state: RootState) => state); + const { datePicker, fixedExpenditure, transaction } = useSelector((state: RootState) => state); const dispatch = useDispatch(); const getFixedExpenditure = useCallback(() => { @@ -40,7 +40,7 @@ const FixedExpenditure = (): JSX.Element => { useEffect(() => { getFixedExpenditure(); - }, [dispatch, datePicker]); + }, [dispatch, datePicker, transaction]); return ( <> From 9a1e48853f8c1efdaec556cd309c0484890fc47b Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Fri, 11 Dec 2020 23:54:41 +0900 Subject: [PATCH 09/86] =?UTF-8?q?docs:=20=EB=A1=9C=EA=B3=A0=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7edfa9b..02bb66b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Project16-F-Account-Book -![스크린샷 2020-11-20 오전 10 47 13](https://user-images.githubusercontent.com/17294694/99751194-0f92df00-2b25-11eb-89f9-b41313bf3c27.png) +![Logo](https://user-images.githubusercontent.com/17294694/101917945-1c7a8e00-3c0c-11eb-828d-03e127a4d883.png) + 배포링크 - [http://tess.kro.kr](http://tess.kro.kr) From a4040e28e1afacd57ca1ee58bcb578b816904fb5 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Fri, 11 Dec 2020 23:56:57 +0900 Subject: [PATCH 10/86] =?UTF-8?q?docs:=20=ED=8C=80=EC=9B=90=20=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 02bb66b..264096e 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,29 @@ ## 팀원 소개 -- **최창희**([@changheedev](https://github.com/changheedev)) : "여기 이거는.. 저기 저거는.." -- **정상우**([@sangw3433](https://github.com/sangw3433)) : "음.. 저는 다른 생각이에요" -- **박진용**([@namda-on](https://github.com/namda-on)) : " 근데 이 부분에서는 ... 아 아닙니다" -- **박동현**([@parkdit94](https://github.com/parkdit94)) : "조금만 쉬다가 할까요?" +- **박동현**([@parkdit94](https://github.com/parkdit94)) + - 매사에 웃음기가 많습니다. + - 꾸준히 성장하기 위해 계획을 세우는 것을 좋아합니다. + - 계획을 수정하는 것도 좋아합니다. + +- **박진용**([@namda-on](https://github.com/namda-on)) + - 재미있는 서비스 개발 자체에 관심이 많습니다. + - 새로운 것에 도전하고 배우는 것을 좋아합니다. + - 팀원들과 소통하기를 좋아하며 협업을 좋아합니다! + +- **정상우**([@sangw3433](https://github.com/sangw3433)) + - 저와 다른 의견을 가진 분과 이야기하는 것을 좋아합니다. + - 성능적인 측면에서 관심이 많습니다. + - 게임, 배드민턴을 좋아합니다. + + +- **최창희**([@changheedev](https://github.com/changheedev)) + - 서버개발, 인프라 및 자동화에 관심이 많은 개발자입니다. + - 기술에 대해 얘기 나누고 배우는 것을 좋아합니다. + - 몸은 가볍지만 엉덩이는 무겁습니다. 👨‍💻 + + +
From 79fc3b7a31d5fb9be2d8c12de2504bb2275226da Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sat, 12 Dec 2020 01:57:29 +0900 Subject: [PATCH 11/86] =?UTF-8?q?docs:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=A4=ED=96=89=EB=B0=A9=EB=B2=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EC=8B=A4=ED=96=89=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 + server/.dummy.env | 3 +-- server/package.json | 2 +- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 264096e..be7e0b1 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,43 @@ ### Architecture ![스크린샷 2020-12-11 오후 9 33 31](https://user-images.githubusercontent.com/17294694/101904084-a882ba80-3bf8-11eb-8679-78145643e0c1.png) + + +## 프로젝트 세팅 및 실행 + +### 환경변수 세팅 + +프로젝트를 실행하기 위해서 환경 변수를 설정해주어야 합니다. + +``` +cp server/dummy.env server/.env +``` + +**환경변수** +``` +TYPEORM_CONNECTION : 연동할 DB 타입 ex) mysql +TYPEORM_HOST : 연결할 DB Host +TYPEORM_PORT : 연결할 DB Port +TYPEORM_USERNAME : DB연결에 사용할 계정 +TYPEORM_PASSWORD : 계정 패스워드 +TYPEORM_DATABASE : 연결할 Database +TYPEORM_SYNCHRONIZE : 엔티티와 테이블 sync 설정 ex) true +TYPEORM_LOGGING : Logging 설정 ex) true + +CLIENT_URI = 프론트엔드 URI ex) http://localhost:3000 + +JWT_SECRET : 토큰 생성에 사용할 Secret +JWT_TOKEN_EXPIRES_IN** : 토큰의 유효기간 ex) 1d +JWT_COOKIE_EXPIRES_IN** = 인증 쿠키의 유효기간 (ms) ex) 86400000 + +XXX_CLIENT_ID** = 개발자 센터에서 발급받은 client id +XXX_CLIENT_SECRET** = 개발자 센터에서 발급받은 client secret +XXX_CALLBACK_URI** = 개발자 센터에서 설정한 callback uri ex) http://localhost:4000/api/auth/callback/xxx (xxx = kakao | naver | google) +``` + +### 프로젝트 실행 +루트 디렉토리에서 아래 명령어를 실행합니다. + +``` +yarn && yarn dev +``` \ No newline at end of file diff --git a/package.json b/package.json index 92c3463..942576d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "precommit": "run-p precommit:client precommit:server", "precommit:client": "yarn workspace client precommit", "precommit:server": "yarn workspace server precommit", + "dev": "run-p dev:client dev:server", "dev:client": "yarn workspace client start", "dev:server": "yarn workspace server dev", "test": "run-p test:server test:client", diff --git a/server/.dummy.env b/server/.dummy.env index c317dad..f8624f5 100644 --- a/server/.dummy.env +++ b/server/.dummy.env @@ -4,9 +4,8 @@ TYPEORM_PORT = TYPEORM_USERNAME = TYPEORM_PASSWORD = TYPEORM_DATABASE = -TYPEORM_SYNCHRONIZE = +TYPEORM_SYNCHRONIZE = TYPEORM_LOGGING = -TYPEORM_ENTITIES = CLIENT_URI = diff --git a/server/package.json b/server/package.json index a03f495..5bbc6fe 100644 --- a/server/package.json +++ b/server/package.json @@ -10,7 +10,7 @@ "lint:fix": "eslint --fix src/**/*.ts", "dev": "nodemon", "build": "NODE_ENV=production ttsc", - "test": "NODE_ENV=test jest", + "test": "NODE_ENV=test jest --runInBand", "seed:up": "ts-node -r tsconfig-paths/register src/seed/up.ts", "seed:clear": "ts-node -r tsconfig-paths/register src/seed/clear.ts" }, From 440bdba65e94e22000e08d2d88d60915da548cc3 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sat, 12 Dec 2020 02:02:42 +0900 Subject: [PATCH 12/86] =?UTF-8?q?fix:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bold 표시(**)가 제거되지 않고 남아있던 것을 제거함 --- README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index be7e0b1..e775961 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ -# Project16-F-Account-Book +# Project16-F-Account-Book ([Link](http://tess.kro.kr)) ![Logo](https://user-images.githubusercontent.com/17294694/101917945-1c7a8e00-3c0c-11eb-828d-03e127a4d883.png) -배포링크 - [http://tess.kro.kr](http://tess.kro.kr) -
## 팀원 소개 @@ -74,15 +72,15 @@ TYPEORM_DATABASE : 연결할 Database TYPEORM_SYNCHRONIZE : 엔티티와 테이블 sync 설정 ex) true TYPEORM_LOGGING : Logging 설정 ex) true -CLIENT_URI = 프론트엔드 URI ex) http://localhost:3000 +CLIENT_URI : 프론트엔드 URI ex) http://localhost:3000 JWT_SECRET : 토큰 생성에 사용할 Secret -JWT_TOKEN_EXPIRES_IN** : 토큰의 유효기간 ex) 1d -JWT_COOKIE_EXPIRES_IN** = 인증 쿠키의 유효기간 (ms) ex) 86400000 +JWT_TOKEN_EXPIRES_IN : 토큰의 유효기간 ex) 1d +JWT_COOKIE_EXPIRES_IN : 인증 쿠키의 유효기간 (ms) ex) 86400000 -XXX_CLIENT_ID** = 개발자 센터에서 발급받은 client id -XXX_CLIENT_SECRET** = 개발자 센터에서 발급받은 client secret -XXX_CALLBACK_URI** = 개발자 센터에서 설정한 callback uri ex) http://localhost:4000/api/auth/callback/xxx (xxx = kakao | naver | google) +XXX_CLIENT_ID : 개발자 센터에서 발급받은 client id +XXX_CLIENT_SECRET : 개발자 센터에서 발급받은 client secret +XXX_CALLBACK_URI : 개발자 센터에서 설정한 callback uri ex) http://localhost:4000/api/auth/callback/xxx (xxx = kakao | naver | google) ``` ### 프로젝트 실행 From ca67cd3e918dc32436d91e1f2a6aa9c143a008af Mon Sep 17 00:00:00 2001 From: Changhee Choi Date: Sat, 12 Dec 2020 02:05:04 +0900 Subject: [PATCH 13/86] =?UTF-8?q?fix:=20env=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=B3=B5=EC=82=AC=20=EB=AA=85=EB=A0=B9=EC=96=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e775961..34bd2a8 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ 프로젝트를 실행하기 위해서 환경 변수를 설정해주어야 합니다. ``` -cp server/dummy.env server/.env +cp server/.dummy.env server/.env ``` **환경변수** @@ -88,4 +88,4 @@ XXX_CALLBACK_URI : 개발자 센터에서 설정한 callback uri ex) http:/ ``` yarn && yarn dev -``` \ No newline at end of file +``` From 9b0620a63fab06115e69edca810b036fd705534f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sat, 12 Dec 2020 15:42:00 +0900 Subject: [PATCH 14/86] =?UTF-8?q?fix=20:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EA=B4=80=EB=A6=AC=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수입 카테고리에서 추가시 지출 카테고리로 생성되는 버그 수정 --- client/src/container/CategoryManageContainer/index.tsx | 6 ++++-- client/src/container/CategoryManageMain/index.tsx | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/src/container/CategoryManageContainer/index.tsx b/client/src/container/CategoryManageContainer/index.tsx index b7d918d..b3be0f5 100644 --- a/client/src/container/CategoryManageContainer/index.tsx +++ b/client/src/container/CategoryManageContainer/index.tsx @@ -36,7 +36,7 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS (e: React.ChangeEvent) => { setCategoryData({ ...categoryData, name: e.target.value }); }, - [categoryData], + [categoryData, isIncome], ); const postNewCategory = useCallback(() => { @@ -44,7 +44,7 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS dispatch(postCategoryThunk(newCategory)); toggleAddCategory(); setCategoryData({ isIncome } as CategoryRequest); - }, [dispatch, categoryData]); + }, [dispatch, categoryData, isIncome]); const updateCategory = useCallback( (cid) => { @@ -55,6 +55,8 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS [dispatch, categoryData], ); + console.log('isIncome : ', isIncome); + return ( <> diff --git a/client/src/container/CategoryManageMain/index.tsx b/client/src/container/CategoryManageMain/index.tsx index 0defaed..0ff58f1 100644 --- a/client/src/container/CategoryManageMain/index.tsx +++ b/client/src/container/CategoryManageMain/index.tsx @@ -4,8 +4,8 @@ import CategoryManageContainer from '../CategoryManageContainer'; const CategoryManageMain = (): JSX.Element => { const tabMenu = [ - { tabName: '지출', children: }, - { tabName: '수입', children: }, + { tabName: '지출', children: }, + { tabName: '수입', children: }, ]; return ; }; From 3988150b8e0607c4bb9bf1abeea343e7eccd5f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sat, 12 Dec 2020 15:46:24 +0900 Subject: [PATCH 15/86] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20deps=EC=99=80=20console=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/container/CategoryManageContainer/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/container/CategoryManageContainer/index.tsx b/client/src/container/CategoryManageContainer/index.tsx index b3be0f5..84b3585 100644 --- a/client/src/container/CategoryManageContainer/index.tsx +++ b/client/src/container/CategoryManageContainer/index.tsx @@ -36,7 +36,7 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS (e: React.ChangeEvent) => { setCategoryData({ ...categoryData, name: e.target.value }); }, - [categoryData, isIncome], + [categoryData], ); const postNewCategory = useCallback(() => { @@ -55,8 +55,6 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS [dispatch, categoryData], ); - console.log('isIncome : ', isIncome); - return ( <> From 3646baf23acb7ada8ed0cbe40540dd57f30695ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sat, 12 Dec 2020 21:10:38 +0900 Subject: [PATCH 16/86] =?UTF-8?q?feat=20:=20=EA=BA=BD=EC=9D=80=EC=84=A0=20?= =?UTF-8?q?=EA=B7=B8=EB=9E=98=ED=94=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.tsx | 4 +++- .../transaction/LineGraph/index.tsx | 22 ++++++++----------- .../transaction/LineGraph/styles.tsx | 7 ------ .../components/transaction/LineGraph/types.ts | 2 -- .../container/DashboardContainer/index.tsx | 2 +- .../container/LineGraphContainer/index.tsx | 16 +++++++++----- .../container/LineGraphContainer/style.tsx | 9 -------- .../container/LineGraphContainer/styles.ts | 21 ++++++++++++++++++ .../container/LineGraphContainer/types.tsx | 4 ---- .../src/views/AggregatePeriodPage/index.tsx | 13 +++++++++++ 10 files changed, 57 insertions(+), 43 deletions(-) delete mode 100644 client/src/container/LineGraphContainer/style.tsx create mode 100644 client/src/container/LineGraphContainer/styles.ts delete mode 100644 client/src/container/LineGraphContainer/types.tsx create mode 100644 client/src/views/AggregatePeriodPage/index.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 72a4a6d..bb3540f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -9,6 +9,7 @@ import PaymentManagePage from '@/views/PaymentManagePage'; import AggregateCategoryPage from '@/views/AggregateCategoryPage'; import GlobalStyled from './GlobalStyled'; import CategoryManagePage from './views/CategoryManagePage'; +import AggregatePeriodPage from './views/AggregatePeriodPage'; function App(): JSX.Element { return ( @@ -21,8 +22,9 @@ function App(): JSX.Element { - + + diff --git a/client/src/components/transaction/LineGraph/index.tsx b/client/src/components/transaction/LineGraph/index.tsx index e20b977..4d0dc43 100644 --- a/client/src/components/transaction/LineGraph/index.tsx +++ b/client/src/components/transaction/LineGraph/index.tsx @@ -8,6 +8,7 @@ import { Tooltip, Legend, TooltipProps, + ResponsiveContainer, } from 'recharts'; import dateUtil from '@libs/dateUtils'; import { useSelector } from 'react-redux'; @@ -22,10 +23,10 @@ const customTooltip = ({ active, payload, label }: TooltipProps) => { {`${label}일`} - {`수입 : ${payload ? payload[0].value : 0}원`} + {`수입 : ${payload ? payload[0].value : 0}원`} - {`지출 : ${payload ? payload[1].value : 0}원`} + {`지출 : ${payload ? payload[1].value : 0}원`} )} @@ -33,28 +34,23 @@ const customTooltip = ({ active, payload, label }: TooltipProps) => { ); }; -const LineGraph = ({ width, height, data }: LineGraphProps): JSX.Element => { +const LineGraph = ({ data }: LineGraphProps): JSX.Element => { const { datePicker } = useSelector((state: RootState) => state); const { year, month } = datePicker; const { maxTotal, graphData } = dateUtil.makeDataForLineGraph(data, year, month); return ( - - + + - - + + - + ); }; diff --git a/client/src/components/transaction/LineGraph/styles.tsx b/client/src/components/transaction/LineGraph/styles.tsx index 5f25b32..6696dbf 100644 --- a/client/src/components/transaction/LineGraph/styles.tsx +++ b/client/src/components/transaction/LineGraph/styles.tsx @@ -1,12 +1,5 @@ import styled from 'styled-components'; -export const StyledLineGraph = styled.div` - padding: 10px; - width: auto; - height: auto; - background-color: #fff; -`; - export const CustomTooltipContainer = styled.div` display: flex; flex-direction: column; diff --git a/client/src/components/transaction/LineGraph/types.ts b/client/src/components/transaction/LineGraph/types.ts index 6be8fc7..4fbedf6 100644 --- a/client/src/components/transaction/LineGraph/types.ts +++ b/client/src/components/transaction/LineGraph/types.ts @@ -1,7 +1,5 @@ export type AggregateInfo = [number, { totalIn: number; totalOut: number }][]; export type LineGraphProps = { - width: number; - height: number; data: AggregateInfo; }; diff --git a/client/src/container/DashboardContainer/index.tsx b/client/src/container/DashboardContainer/index.tsx index 1a01ca6..dea5360 100644 --- a/client/src/container/DashboardContainer/index.tsx +++ b/client/src/container/DashboardContainer/index.tsx @@ -114,7 +114,7 @@ const DashboardContainer = (): JSX.Element => { 기간별 통계 - 자세히 보기 + 자세히 보기 {transactionState.mostOutDateDetail.date}일에 가장 많은 돈을 쓰셨어요 diff --git a/client/src/container/LineGraphContainer/index.tsx b/client/src/container/LineGraphContainer/index.tsx index 3f2198b..a7c4c44 100644 --- a/client/src/container/LineGraphContainer/index.tsx +++ b/client/src/container/LineGraphContainer/index.tsx @@ -2,17 +2,21 @@ import LineGraph from '@/components/transaction/LineGraph'; import { RootState } from '@/modules'; import React from 'react'; import { useSelector } from 'react-redux'; -import StyledLineGraphContainer from './style'; -import { LineGraphContainerProps } from './types'; +import SelectMonth from '../SelectMonth'; +import * as S from './styles'; -const LineGraphContainer = ({ width, height }: LineGraphContainerProps) => { +const LineGraphContainer = () => { const { transaction } = useSelector((state: RootState) => state); return ( <> {transaction && ( - - - + <> + + + 기간별 통계 + + + )} ); diff --git a/client/src/container/LineGraphContainer/style.tsx b/client/src/container/LineGraphContainer/style.tsx deleted file mode 100644 index df133ce..0000000 --- a/client/src/container/LineGraphContainer/style.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import styled from 'styled-components'; - -const StyledLineGraphContainer = styled.div` - display: flex; - align-items: center; - justify-content: center; -`; - -export default StyledLineGraphContainer; diff --git a/client/src/container/LineGraphContainer/styles.ts b/client/src/container/LineGraphContainer/styles.ts new file mode 100644 index 0000000..d8a9de2 --- /dev/null +++ b/client/src/container/LineGraphContainer/styles.ts @@ -0,0 +1,21 @@ +import styled from 'styled-components'; + +export const StyledLineGraphContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 80%; + margin: 1rem auto; + .div { + display: flex; + } + border-radius: 0.3rem; + box-shadow: 1px 2px 4px 1px rgba(0, 0, 0, 0.2); + padding: 1rem; +`; + +export const LineGraphText = styled.div` + margin: 0.5rem 0rem 1.5rem 0rem; + font-weight: bold; +`; diff --git a/client/src/container/LineGraphContainer/types.tsx b/client/src/container/LineGraphContainer/types.tsx deleted file mode 100644 index e9dbf98..0000000 --- a/client/src/container/LineGraphContainer/types.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export type LineGraphContainerProps = { - width: number; - height: number; -}; diff --git a/client/src/views/AggregatePeriodPage/index.tsx b/client/src/views/AggregatePeriodPage/index.tsx new file mode 100644 index 0000000..9cc702b --- /dev/null +++ b/client/src/views/AggregatePeriodPage/index.tsx @@ -0,0 +1,13 @@ +import MainLayout from '@/components/common/layouts/MainLayout'; +import LineGraphContainer from '@/container/LineGraphContainer'; +import React from 'react'; + +const AggregatePeriodPage = (): JSX.Element => { + return ( + + + + ); +}; + +export default AggregatePeriodPage; From 539bec74ab65cc035d22af0fcdd2456f6e2c6018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sat, 12 Dec 2020 21:21:34 +0900 Subject: [PATCH 17/86] =?UTF-8?q?feat=20:=20categoryPage=EC=97=90=20?= =?UTF-8?q?=EB=8B=AC=EC=84=A0=ED=83=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/container/LineGraphContainer/styles.ts | 4 ++-- client/src/views/AggregateCategoryPage/index.tsx | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/container/LineGraphContainer/styles.ts b/client/src/container/LineGraphContainer/styles.ts index d8a9de2..844c707 100644 --- a/client/src/container/LineGraphContainer/styles.ts +++ b/client/src/container/LineGraphContainer/styles.ts @@ -5,14 +5,14 @@ export const StyledLineGraphContainer = styled.div` flex-direction: column; justify-content: center; align-items: center; - width: 80%; + width: 95%; margin: 1rem auto; .div { display: flex; } border-radius: 0.3rem; box-shadow: 1px 2px 4px 1px rgba(0, 0, 0, 0.2); - padding: 1rem; + padding: 0.6rem; `; export const LineGraphText = styled.div` diff --git a/client/src/views/AggregateCategoryPage/index.tsx b/client/src/views/AggregateCategoryPage/index.tsx index 2add9a9..b4c2acd 100644 --- a/client/src/views/AggregateCategoryPage/index.tsx +++ b/client/src/views/AggregateCategoryPage/index.tsx @@ -1,10 +1,12 @@ import React from 'react'; import MainLayout from '@/components/common/layouts/MainLayout'; import AggregateCategory from '@/container/AggregateCategory'; +import SelectMonth from '@/container/SelectMonth'; const AggregateCategoryPage = (): JSX.Element => { return ( + ); From d37519bca2ebce79f6af7fa3185601559965d3d4 Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sat, 12 Dec 2020 22:22:18 +0900 Subject: [PATCH 18/86] =?UTF-8?q?refactor=20:=20date=20=ED=8C=8C=EC=8B=B1?= =?UTF-8?q?=ED=95=B4=EC=A3=BC=EB=8A=94=20=ED=95=A8=EC=88=98=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit date 파싱해주는 함수 이용 --- client/src/components/fixedExpenditure/Item/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/fixedExpenditure/Item/index.tsx b/client/src/components/fixedExpenditure/Item/index.tsx index 814c067..6a41f5d 100644 --- a/client/src/components/fixedExpenditure/Item/index.tsx +++ b/client/src/components/fixedExpenditure/Item/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import numberUtils from '@libs/numberUtils'; +import dateUtils from '@libs/dateUtils'; import { FixedExpenditureItemPropType } from './types'; import * as S from './styles'; @@ -13,7 +14,7 @@ const FixedExpenditureItem = ({ fixedItem, isPaid }: FixedExpenditureItemPropTyp {numberUtils.numberWithCommas(fixedItem.amount)} 원 - {new Date(fixedItem.tradeAt).toString().substring(8, 10)}일 + {dateUtils.parseDate(fixedItem.tradeAt).date}일 ); }; From 16dc39a1b531c0f64a93fd3276cacf12bb2ac9d0 Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:08:36 +0900 Subject: [PATCH 19/86] =?UTF-8?q?refactor=20:=20types=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit types 분리 --- client/src/components/common/buttons/OAuthLoginButton/types.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 client/src/components/common/buttons/OAuthLoginButton/types.ts diff --git a/client/src/components/common/buttons/OAuthLoginButton/types.ts b/client/src/components/common/buttons/OAuthLoginButton/types.ts new file mode 100644 index 0000000..0f17143 --- /dev/null +++ b/client/src/components/common/buttons/OAuthLoginButton/types.ts @@ -0,0 +1,3 @@ +export type PropsType = { + provider: 'google' | 'naver' | 'kakao'; +}; From 97a18a686529c506c85702ace3d78a2f904d451e Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:10:44 +0900 Subject: [PATCH 20/86] =?UTF-8?q?docs=20:=20=EB=B9=84=EC=96=B4=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=ED=8F=B4=EB=8D=94=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 비어있는 폴더 삭제 --- client/src/components/category/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 client/src/components/category/.gitkeep diff --git a/client/src/components/category/.gitkeep b/client/src/components/category/.gitkeep deleted file mode 100644 index e69de29..0000000 From 1007d3d69a6cde339c0fd439181dbf880153e4ea Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:13:03 +0900 Subject: [PATCH 21/86] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A1=B0=EA=B1=B4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 조건 제거 --- client/src/components/calendar/TableCell/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/calendar/TableCell/index.tsx b/client/src/components/calendar/TableCell/index.tsx index 103bb0c..86e993d 100644 --- a/client/src/components/calendar/TableCell/index.tsx +++ b/client/src/components/calendar/TableCell/index.tsx @@ -14,10 +14,10 @@ const getRem = (n: number): string => { return rem; }; -function TableCell({ day, totalInOut }: TableCellTypes): JSX.Element { +const TableCell = ({ day, totalInOut }: TableCellTypes): JSX.Element => { const { calendarDaySelector } = useSelector((state: RootState) => state); const isBold = (): 'isBold' | '' => { - if (Number(calendarDaySelector.day) === Number(day) && day !== ' ') return 'isBold'; + if (Number(calendarDaySelector.day) === Number(day)) return 'isBold'; return ''; }; const dispatch = useDispatch(); @@ -63,6 +63,6 @@ function TableCell({ day, totalInOut }: TableCellTypes): JSX.Element { ); -} +}; export default TableCell; From 36525d88cc00864ef9c1711188d5d609cd16d39b Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:17:03 +0900 Subject: [PATCH 22/86] =?UTF-8?q?refactor=20:=20=ED=99=94=EC=82=B4?= =?UTF-8?q?=ED=91=9C=20=ED=95=A8=EC=88=98=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 코드 통일을 위해 화살표 함수로 변경 --- .../components/calendar/CalendarView/index.tsx | 16 +++++++--------- .../src/components/calendar/MatrixView/index.tsx | 4 ++-- client/src/components/common/ArrowIcon/index.tsx | 4 ++-- client/src/components/common/Dropdown/index.tsx | 6 ++++-- client/src/components/common/Logo/index.tsx | 4 ++-- client/src/components/common/Modal/index.tsx | 4 ++-- .../common/buttons/CustomButton/index.tsx | 4 ++-- .../common/buttons/ModalToggleButton/index.tsx | 4 ++-- .../common/buttons/OAuthLoginButton/index.tsx | 9 +++------ .../common/forms/CustomSelectInput/index.tsx | 4 ++-- .../components/common/layouts/Header/index.tsx | 4 ++-- .../components/transaction/AmountText/index.tsx | 4 ++-- .../transaction/ModalHeaderText/index.tsx | 4 ++-- .../transaction/ModalRadioButton/index.tsx | 4 ++-- .../transaction/ModalXButton/index.tsx | 4 ++-- 15 files changed, 38 insertions(+), 41 deletions(-) diff --git a/client/src/components/calendar/CalendarView/index.tsx b/client/src/components/calendar/CalendarView/index.tsx index 022893c..1b464c9 100644 --- a/client/src/components/calendar/CalendarView/index.tsx +++ b/client/src/components/calendar/CalendarView/index.tsx @@ -5,16 +5,14 @@ import getDayMatrix from '@libs/calendarUtils'; import { getWeekDays } from '@libs/nationalCalendarUtils'; import { CalendarViewType } from './types'; -function CalendarView({ totalInOut, lang, year, month }: CalendarViewType): JSX.Element { +const CalendarView = ({ totalInOut, lang, year, month }: CalendarViewType): JSX.Element => { return ( - <> - } - /> - + } + /> ); -} +}; export default CalendarView; diff --git a/client/src/components/calendar/MatrixView/index.tsx b/client/src/components/calendar/MatrixView/index.tsx index 3fa1144..2228162 100644 --- a/client/src/components/calendar/MatrixView/index.tsx +++ b/client/src/components/calendar/MatrixView/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { MatrixViewTypes } from './types'; import * as S from './styles'; -function MatrixView({ matrix, headers, cell }: MatrixViewTypes): JSX.Element { +const MatrixView = ({ matrix, headers, cell }: MatrixViewTypes): JSX.Element => { return ( @@ -23,6 +23,6 @@ function MatrixView({ matrix, headers, cell }: MatrixViewTypes): JSX.Element { ); -} +}; export default MatrixView; diff --git a/client/src/components/common/ArrowIcon/index.tsx b/client/src/components/common/ArrowIcon/index.tsx index 375587c..8bfd112 100644 --- a/client/src/components/common/ArrowIcon/index.tsx +++ b/client/src/components/common/ArrowIcon/index.tsx @@ -3,12 +3,12 @@ import ArrowSVG from '@/assets/svg/Arrow.svg'; import StyledArrow from './styles'; import { ArrowPropType } from './types'; -function ArrowIcon({ height, width, rotate }: ArrowPropType): JSX.Element { +const ArrowIcon = ({ height, width, rotate }: ArrowPropType): JSX.Element => { return ( arrow ); -} +}; export default ArrowIcon; diff --git a/client/src/components/common/Dropdown/index.tsx b/client/src/components/common/Dropdown/index.tsx index b4759de..945562f 100644 --- a/client/src/components/common/Dropdown/index.tsx +++ b/client/src/components/common/Dropdown/index.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { DropdownType } from './types'; import * as S from './styles'; -export default function Dropdown({ icon, isRight, children }: DropdownType): JSX.Element { +const Dropdown = ({ icon, isRight, children }: DropdownType): JSX.Element => { const [isShow, setDisplay] = useState(false); const position = isRight ? 'right' : ''; @@ -22,4 +22,6 @@ export default function Dropdown({ icon, isRight, children }: DropdownType): JSX )} ); -} +}; + +export default Dropdown; diff --git a/client/src/components/common/Logo/index.tsx b/client/src/components/common/Logo/index.tsx index dc2b533..487356b 100644 --- a/client/src/components/common/Logo/index.tsx +++ b/client/src/components/common/Logo/index.tsx @@ -3,12 +3,12 @@ import LogoSVG from '@/assets/svg/Logo.svg'; import { LogoPropType } from './types'; import StyledLogo from './styles'; -function Logo({ height }: LogoPropType): JSX.Element { +const Logo = ({ height }: LogoPropType): JSX.Element => { return ( logo ); -} +}; export default Logo; diff --git a/client/src/components/common/Modal/index.tsx b/client/src/components/common/Modal/index.tsx index e52dfc0..db7ef7b 100644 --- a/client/src/components/common/Modal/index.tsx +++ b/client/src/components/common/Modal/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import * as S from './styles'; import { ModalProps } from './types'; -function Modal({ children, show, toggleModal }: ModalProps): JSX.Element { +const Modal = ({ children, show, toggleModal }: ModalProps): JSX.Element => { return ( ); -} +}; export default Modal; diff --git a/client/src/components/common/buttons/CustomButton/index.tsx b/client/src/components/common/buttons/CustomButton/index.tsx index 0d43d22..92313b3 100644 --- a/client/src/components/common/buttons/CustomButton/index.tsx +++ b/client/src/components/common/buttons/CustomButton/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import * as S from './styles'; import { CustomButtonProps } from './types'; -function CustomButton(props: CustomButtonProps): JSX.Element { +const CustomButton = (props: CustomButtonProps): JSX.Element => { const { image, children, color, size, onClickEvent } = props; return ( @@ -17,6 +17,6 @@ function CustomButton(props: CustomButtonProps): JSX.Element { )} ); -} +}; export default CustomButton; diff --git a/client/src/components/common/buttons/ModalToggleButton/index.tsx b/client/src/components/common/buttons/ModalToggleButton/index.tsx index 0d3ded6..70a8efc 100644 --- a/client/src/components/common/buttons/ModalToggleButton/index.tsx +++ b/client/src/components/common/buttons/ModalToggleButton/index.tsx @@ -2,12 +2,12 @@ import React from 'react'; import * as S from './styles'; import { ModalToggleButtonProps } from './types'; -function ModalToggleButton({ setToggle }: ModalToggleButtonProps): JSX.Element { +const ModalToggleButton = ({ setToggle }: ModalToggleButtonProps): JSX.Element => { return ( + ); -} +}; export default ModalToggleButton; diff --git a/client/src/components/common/buttons/OAuthLoginButton/index.tsx b/client/src/components/common/buttons/OAuthLoginButton/index.tsx index 5b83949..51bab05 100644 --- a/client/src/components/common/buttons/OAuthLoginButton/index.tsx +++ b/client/src/components/common/buttons/OAuthLoginButton/index.tsx @@ -1,18 +1,15 @@ import React from 'react'; import endpoints from '@/libs/endpoints'; import LoginButtonResourceFactory from './buttonResourceFactory'; +import { PropsType } from './types'; -type PropsType = { - provider: 'google' | 'naver' | 'kakao'; -}; - -function OAuthLoginButton({ provider }: PropsType): JSX.Element { +const OAuthLoginButton = ({ provider }: PropsType): JSX.Element => { const { Button, text } = LoginButtonResourceFactory.getButtonResource(provider); const clickHandler = () => { window.location.href = `${endpoints.API_BASE_URL}${endpoints.AUTH_API}/${provider}`; }; return ; -} +}; export default OAuthLoginButton; diff --git a/client/src/components/common/forms/CustomSelectInput/index.tsx b/client/src/components/common/forms/CustomSelectInput/index.tsx index 5abdc39..f9e58eb 100644 --- a/client/src/components/common/forms/CustomSelectInput/index.tsx +++ b/client/src/components/common/forms/CustomSelectInput/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { SelectInputProps } from './types'; import * as S from './style'; -function CustomSelectInput(props: SelectInputProps): JSX.Element { +const CustomSelectInput = (props: SelectInputProps): JSX.Element => { const { placeholder, children, name, onChange, value } = props; const initialValue = value?.id || 0; const [inputValue, setInputValue] = useState(initialValue); @@ -40,6 +40,6 @@ function CustomSelectInput(props: SelectInputProps): JSX.Element { ))} ); -} +}; export default CustomSelectInput; diff --git a/client/src/components/common/layouts/Header/index.tsx b/client/src/components/common/layouts/Header/index.tsx index 4366fbd..bf65ed5 100644 --- a/client/src/components/common/layouts/Header/index.tsx +++ b/client/src/components/common/layouts/Header/index.tsx @@ -11,7 +11,7 @@ import * as S from './styles'; const HeaderDropdownIcon = ( settings-button ); -function Header(): JSX.Element { +const Header = (): JSX.Element => { const list = ['결제수단 관리', '카테고리 관리']; const linkPageList = ['manage-payment', 'manage-category']; const dropdonwList = list.map((v: string, i: number) => ( @@ -45,6 +45,6 @@ function Header(): JSX.Element { ); -} +}; export default React.memo(Header); diff --git a/client/src/components/transaction/AmountText/index.tsx b/client/src/components/transaction/AmountText/index.tsx index 0a4a812..860394e 100644 --- a/client/src/components/transaction/AmountText/index.tsx +++ b/client/src/components/transaction/AmountText/index.tsx @@ -3,7 +3,7 @@ import NumberUtils from '@libs/numberUtils'; import StyledAmountText from './styles'; import { AmountTextProps } from './types'; -function AmountText({ isIncome, size = 'md', amount }: AmountTextProps): JSX.Element { +const AmountText = ({ isIncome, size = 'md', amount }: AmountTextProps): JSX.Element => { return ( {isIncome @@ -11,6 +11,6 @@ function AmountText({ isIncome, size = 'md', amount }: AmountTextProps): JSX.Ele : `-${NumberUtils.numberWithCommas(amount)}`} ); -} +}; export default AmountText; diff --git a/client/src/components/transaction/ModalHeaderText/index.tsx b/client/src/components/transaction/ModalHeaderText/index.tsx index 5de4f04..d7473e5 100644 --- a/client/src/components/transaction/ModalHeaderText/index.tsx +++ b/client/src/components/transaction/ModalHeaderText/index.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { ModalHeaderProps } from './types'; import ModalHeaderTextStyle from './styles'; -function ModalHeaderText({ children }: ModalHeaderProps): JSX.Element { +const ModalHeaderText = ({ children }: ModalHeaderProps): JSX.Element => { return {children}; -} +}; export default ModalHeaderText; diff --git a/client/src/components/transaction/ModalRadioButton/index.tsx b/client/src/components/transaction/ModalRadioButton/index.tsx index 1620a97..9eae1ee 100644 --- a/client/src/components/transaction/ModalRadioButton/index.tsx +++ b/client/src/components/transaction/ModalRadioButton/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import * as S from './styles'; import { ModalRadioButtonProps } from './types'; -function ModalRadioButton({ setIsIncome, onChange, value }: ModalRadioButtonProps): JSX.Element { +const ModalRadioButton = ({ setIsIncome, onChange, value }: ModalRadioButtonProps): JSX.Element => { const onChangeRadioButton = (event: React.ChangeEvent) => { const { value: targetValue } = event.target; const isIncome = targetValue === 'true'; @@ -37,6 +37,6 @@ function ModalRadioButton({ setIsIncome, onChange, value }: ModalRadioButtonProp ); -} +}; export default ModalRadioButton; diff --git a/client/src/components/transaction/ModalXButton/index.tsx b/client/src/components/transaction/ModalXButton/index.tsx index e4bd132..25f4e31 100644 --- a/client/src/components/transaction/ModalXButton/index.tsx +++ b/client/src/components/transaction/ModalXButton/index.tsx @@ -2,8 +2,8 @@ import React from 'react'; import XButton from './styles'; import { ModalXButtonProps } from './types'; -function ModalXButton({ onClickEvent }: ModalXButtonProps): JSX.Element { +const ModalXButton = ({ onClickEvent }: ModalXButtonProps): JSX.Element => { return X; -} +}; export default ModalXButton; From 004c498a0d5f738d5c1aa4ddc9fb2c70ba48dec8 Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:36:40 +0900 Subject: [PATCH 23/86] =?UTF-8?q?refactor=20:=20=EA=B2=B0=EC=A0=9C?= =?UTF-8?q?=EC=88=98=EB=8B=A8=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 결제수단이 없을 때 비어있는 박스를 보이지 않도록 수정 --- .../src/container/PaymentManageMain/index.tsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/client/src/container/PaymentManageMain/index.tsx b/client/src/container/PaymentManageMain/index.tsx index 0d6926e..59633d9 100644 --- a/client/src/container/PaymentManageMain/index.tsx +++ b/client/src/container/PaymentManageMain/index.tsx @@ -63,17 +63,19 @@ const PaymentManageContainer = (): JSX.Element => { border /> )} - - {paymentList.map((payment) => ( - - ))} - + {paymentList.length !== 0 && ( + + {paymentList.map((payment) => ( + + ))} + + )} ); }; From 3eb2d19ef2446580cb48f84f5daef0787dd34ba7 Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:37:47 +0900 Subject: [PATCH 24/86] =?UTF-8?q?refactor=20:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 카테고리가 없을 때 비어있는 박스를 보이지 않도록 수정 --- .../CategoryManageContainer/index.tsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/client/src/container/CategoryManageContainer/index.tsx b/client/src/container/CategoryManageContainer/index.tsx index 84b3585..2c79a43 100644 --- a/client/src/container/CategoryManageContainer/index.tsx +++ b/client/src/container/CategoryManageContainer/index.tsx @@ -67,17 +67,19 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS border /> )} - - {categoryList.map((category) => ( - - ))} - + {categoryList.length !== 0 && ( + + {categoryList.map((category) => ( + + ))} + + )} ); }; From d30e5766cb3e73f3a61c4b5bf853c328269a23a3 Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:38:53 +0900 Subject: [PATCH 25/86] =?UTF-8?q?refactor=20:=20=EA=B3=A0=EC=A0=95?= =?UTF-8?q?=EC=A7=80=EC=B6=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 코드 삭제 --- client/src/container/DetailedFixedExpenditure/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client/src/container/DetailedFixedExpenditure/index.tsx b/client/src/container/DetailedFixedExpenditure/index.tsx index b627127..c9b335f 100644 --- a/client/src/container/DetailedFixedExpenditure/index.tsx +++ b/client/src/container/DetailedFixedExpenditure/index.tsx @@ -50,7 +50,7 @@ const DetailedFixedExpenditure = (): JSX.Element => { 고정적인 지출 총 {getAmount('both')} 원
- {fixedExpenditure.data.paid.length !== 0 ? ( + {fixedExpenditure.data.paid.length !== 0 && ( 지출 완료 {fixedExpenditure.data.paid.map((fixedItem) => ( @@ -63,10 +63,8 @@ const DetailedFixedExpenditure = (): JSX.Element => {

{getAmount('paid')}원

- ) : ( - <> )} - {fixedExpenditure.data.estimated.length !== 0 ? ( + {fixedExpenditure.data.estimated.length !== 0 && ( 지출 예정 {fixedExpenditure.data.estimated.map((fixedItem) => ( @@ -79,8 +77,6 @@ const DetailedFixedExpenditure = (): JSX.Element => {

{getAmount('estimated')}원

- ) : ( - <> )} ); From 7cee6013c22bae91476bf1c55d480f5e22f3bc63 Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:48:40 +0900 Subject: [PATCH 26/86] =?UTF-8?q?feat=20:=20=EB=9D=BC=EC=9D=B8=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=ED=94=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=B9=84=EC=96=B4=EC=9E=88=EC=9D=84=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 데이터가 없을 경우 내역이 없다고 표시 --- client/src/container/LineGraphContainer/index.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/client/src/container/LineGraphContainer/index.tsx b/client/src/container/LineGraphContainer/index.tsx index a7c4c44..edc880c 100644 --- a/client/src/container/LineGraphContainer/index.tsx +++ b/client/src/container/LineGraphContainer/index.tsx @@ -2,20 +2,25 @@ import LineGraph from '@/components/transaction/LineGraph'; import { RootState } from '@/modules'; import React from 'react'; import { useSelector } from 'react-redux'; +import EmptyStateComponent from '@/components/transaction/EmptyState'; import SelectMonth from '../SelectMonth'; import * as S from './styles'; -const LineGraphContainer = () => { +const LineGraphContainer = (): JSX.Element => { const { transaction } = useSelector((state: RootState) => state); return ( <> {transaction && ( <> - - 기간별 통계 - - + {transaction.aggregationByDate.length === 0 ? ( + + ) : ( + + 기간별 통계 + + + )} )} From b1b0e7425e6550dc51911e4c827dbf2e8ef72e6a Mon Sep 17 00:00:00 2001 From: Sangwoo <66261552+sangw3433@users.noreply.github.com> Date: Sun, 13 Dec 2020 00:49:51 +0900 Subject: [PATCH 27/86] =?UTF-8?q?feat=20:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=86=B5=EA=B3=84=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=86=EC=9D=84=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 데이터가 없을 경우 내역이 없다고 표시 --- .../aggregateCategory/Main/index.tsx | 65 +++++++++++-------- .../aggregateCategory/Main/styles.tsx | 4 ++ 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/client/src/components/aggregateCategory/Main/index.tsx b/client/src/components/aggregateCategory/Main/index.tsx index 260c223..969e3d6 100644 --- a/client/src/components/aggregateCategory/Main/index.tsx +++ b/client/src/components/aggregateCategory/Main/index.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import AggregateCategoryItem from '@/components/aggregateCategory/Item'; import { AggregateCategoryPropType } from '@/commons/types/aggregateCategory'; import { Doughnut } from 'react-chartjs-2'; +import EmptyStateComponent from '@/components/transaction/EmptyState'; import * as S from './styles'; const AggregateCategoryMain = ({ list, isIncome }: AggregateCategoryPropType): JSX.Element => { @@ -32,32 +33,44 @@ const AggregateCategoryMain = ({ list, isIncome }: AggregateCategoryPropType): J }, [list, isIncome]); return ( <> - - - - - {isIncome ? 카테고리별 수입 : 카테고리별 지출} - {list.length !== 0 ? ( - list.map((item) => ( - - )) - ) : ( - <> - )} - + {list.length === 0 ? ( + + + + ) : ( + <> + + + + + {isIncome ? 카테고리별 수입 : 카테고리별 지출} + {list.length !== 0 ? ( + list.map((item) => ( + + )) + ) : ( + <> + )} + + + )} ); }; diff --git a/client/src/components/aggregateCategory/Main/styles.tsx b/client/src/components/aggregateCategory/Main/styles.tsx index feb0afc..f680c26 100644 --- a/client/src/components/aggregateCategory/Main/styles.tsx +++ b/client/src/components/aggregateCategory/Main/styles.tsx @@ -23,3 +23,7 @@ export const Chart = styled.div` margin: 1.4rem 0 1.4rem 0; box-sizing: border-box; `; + +export const EmptyStateBox = styled.div` + margin-top: 2rem; +`; From 08932793bae2750e69d9ad158b1e2b1542ed2508 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Sun, 13 Dec 2020 02:56:10 +0900 Subject: [PATCH 28/86] =?UTF-8?q?feat:validation=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/libs/checkValidation.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 client/src/libs/checkValidation.ts diff --git a/client/src/libs/checkValidation.ts b/client/src/libs/checkValidation.ts new file mode 100644 index 0000000..97d4008 --- /dev/null +++ b/client/src/libs/checkValidation.ts @@ -0,0 +1,8 @@ +const checkValidation = (name: string, target: string): boolean => { + if (name === 'amount' && (Number.isNaN(Number(target)) === true || target === '0')) return false; + if (name === 'tradeAt' && target === '') return false; + if (target === '0' || target === '0') return false; + return true; +}; + +export default checkValidation; From 7aa832fb38bd051407c1115fefb36bca4d51f086 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Sun, 13 Dec 2020 02:57:07 +0900 Subject: [PATCH 29/86] =?UTF-8?q?feat:=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EB=B0=8F=20=EB=AA=A8=EB=8B=AC=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/forms/CustomSelectInput/index.tsx | 3 +- .../src/container/TransactionModal/index.tsx | 37 +++++++++++++----- .../TransactionUpdateModal/index.tsx | 38 +++++++++++++++---- client/src/views/CalendarPage/index.tsx | 2 +- client/src/views/DashBoardPage/index.tsx | 2 +- 5 files changed, 61 insertions(+), 21 deletions(-) diff --git a/client/src/components/common/forms/CustomSelectInput/index.tsx b/client/src/components/common/forms/CustomSelectInput/index.tsx index 5abdc39..3f5df32 100644 --- a/client/src/components/common/forms/CustomSelectInput/index.tsx +++ b/client/src/components/common/forms/CustomSelectInput/index.tsx @@ -6,7 +6,6 @@ function CustomSelectInput(props: SelectInputProps): JSX.Element { const { placeholder, children, name, onChange, value } = props; const initialValue = value?.id || 0; const [inputValue, setInputValue] = useState(initialValue); - const selectInput = useRef(null); const getMatchedOptionValue = () => { @@ -20,7 +19,7 @@ function CustomSelectInput(props: SelectInputProps): JSX.Element { useEffect(() => { if (!value) return; const matchedValue = getMatchedOptionValue(); - if (matchedValue) setInputValue(matchedValue); + setInputValue(matchedValue); }, [value]); const handleChange = (e: React.ChangeEvent) => { diff --git a/client/src/container/TransactionModal/index.tsx b/client/src/container/TransactionModal/index.tsx index 8647ebe..f5acdbe 100644 --- a/client/src/container/TransactionModal/index.tsx +++ b/client/src/container/TransactionModal/index.tsx @@ -16,12 +16,14 @@ import SMSParser from '@/libs/smsParser/parser'; import DateUtils from '@/libs/dateUtils'; import ModalInput from '@/components/transaction/ModalInput'; import ModalHeader from '@/components/transaction/ModalHeader'; +import checkValidation from '@/libs/checkValidation'; +import { pid } from 'process'; import * as S from './styles'; import { TransactionModalProps } from './types'; const TransactionModal = ({ show, toggleModal }: TransactionModalProps): JSX.Element => { const [isIncome, setIsIncome] = useState(false); - + const [validation, setValidation] = useState(new Set(['isIncome'])); const { payment, category } = useSelector((state: RootState) => state); const categoryList = category.data.map((c) => new CategoryDTO(c)); const paymentList = payment.data.map((p) => new PaymentDTO(p)); @@ -42,16 +44,31 @@ const TransactionModal = ({ show, toggleModal }: TransactionModalProps): JSX.Ele const onChangeReducer = (state: PostTransactionRequest, action: PostTransactionRequest) => { return action; }; - const [newTransaction, infoDispatch] = useReducer(onChangeReducer, {} as PostTransactionRequest); + const onChangeInput = (e: React.ChangeEvent) => { - infoDispatch({ ...newTransaction, [e.target.name]: e.target.value }); + if (checkValidation(e.target.name, e.target.value)) { + infoDispatch({ ...newTransaction, [e.target.name]: e.target.value }); + validation.add(e.target.name); + setValidation(new Set([...validation])); + } else { + validation.delete(e.target.name); + setValidation(new Set([...validation])); + } + if ((e.target.name === 'description' || e.target.name === 'amount') && e.target.value === '') { + validation.delete(e.target.name); + setValidation(new Set([...validation])); + } + }; + const toggleModalOption = () => { + toggleModal(); + setValidation(new Set(['isIncome'])); }; const postNewTransaction = useCallback(() => { const newTransactionDTO = new TransactionRequestDTO(newTransaction); dispatch(postTransactionThunk(newTransactionDTO)); - toggleModal(); + toggleModalOption(); }, [dispatch, newTransaction]); const parseClipboardText = useCallback(() => { @@ -84,8 +101,8 @@ const TransactionModal = ({ show, toggleModal }: TransactionModalProps): JSX.Ele return ( <> {paymentList && categoryList && ( - - + + 복사 - - 저장 - + {validation.size > 5 && ( + + 저장 + + )} )} diff --git a/client/src/container/TransactionUpdateModal/index.tsx b/client/src/container/TransactionUpdateModal/index.tsx index 6ca073e..b6ed855 100644 --- a/client/src/container/TransactionUpdateModal/index.tsx +++ b/client/src/container/TransactionUpdateModal/index.tsx @@ -11,20 +11,24 @@ import { UpdateTransactionRequest } from '@/commons/types/transaction'; import ModalInput from '@/components/transaction/ModalInput'; import CustomSelectInput from '@/components/common/forms/CustomSelectInput'; import CustomButton from '@/components/common/buttons/CustomButton'; +import checkValidation from '@/libs/checkValidation'; import TransactionRequestDTO from '@/commons/dto/transaction-request'; import { deleteTransactionThunk, updateTransactionThunk } from '@/modules/transaction'; import * as S from './styles'; +const MODALLSITARR = ['tradeAt', 'description', 'amount', 'pid', 'cid', 'isIncome']; + const TransactionUpdateModal = (): JSX.Element => { const [isIncome, setIsIncome] = useState(false); + const [validation, setValidation] = useState(new Set(MODALLSITARR)); const { payment, category } = useSelector((state: RootState) => state); const { toggle, data } = useSelector((state: RootState) => state.updateModal); const categoryList = category.data.map((c) => new CategoryDTO(c)); const paymentList = payment.data.map((p) => new PaymentDTO(p)); - const dispatch = useDispatch(); const toggleModal = useCallback(() => { dispatch(toggleModalOff()); + setValidation(new Set(MODALLSITARR)); }, [dispatch]); const onChangeReducer = (state: UpdateTransactionRequest, action: UpdateTransactionRequest) => { @@ -34,8 +38,20 @@ const TransactionUpdateModal = (): JSX.Element => { const [updatedTransaction, infoDispatch] = useReducer(onChangeReducer, { tid: data?.tid, } as UpdateTransactionRequest); + const onChangeInput = (e: React.ChangeEvent) => { - infoDispatch({ ...updatedTransaction, [e.target.name]: e.target.value }); + if (checkValidation(e.target.name, e.target.value)) { + infoDispatch({ ...updatedTransaction, [e.target.name]: e.target.value }); + validation.add(e.target.name); + setValidation(new Set([...validation])); + } else { + validation.delete(e.target.name); + setValidation(new Set([...validation])); + } + if ((e.target.name === 'description' || e.target.name === 'amount') && e.target.value === '') { + validation.delete(e.target.name); + setValidation(new Set([...validation])); + } }; useEffect(() => { @@ -59,9 +75,13 @@ const TransactionUpdateModal = (): JSX.Element => { }, [dispatch, updatedTransaction, data]); const deleteTransaction = useCallback(() => { - if (!data) return; - dispatch(deleteTransactionThunk(data.tid)); - dispatch(toggleModalOff()); + if (window.confirm('삭제 하시겠습니까?')) { + if (!data) return; + dispatch(deleteTransactionThunk(data.tid)); + dispatch(toggleModalOff()); + } else { + toggleModalOff(); + } }, [dispatch, data]); return ( @@ -117,9 +137,11 @@ const TransactionUpdateModal = (): JSX.Element => { 삭제 - - 저장 - + {validation.size > 5 && ( + + 저장 + + )} )} diff --git a/client/src/views/CalendarPage/index.tsx b/client/src/views/CalendarPage/index.tsx index 4d004e2..6250b95 100644 --- a/client/src/views/CalendarPage/index.tsx +++ b/client/src/views/CalendarPage/index.tsx @@ -12,7 +12,7 @@ const CalendarPage = (): JSX.Element => { - + {showModal && } ); diff --git a/client/src/views/DashBoardPage/index.tsx b/client/src/views/DashBoardPage/index.tsx index d9e39bf..3392f23 100644 --- a/client/src/views/DashBoardPage/index.tsx +++ b/client/src/views/DashBoardPage/index.tsx @@ -11,7 +11,7 @@ const DashBoardPage = (): JSX.Element => { - + {showModal && } ); }; From a642c5faf86fc20cbf9b3da8ce61c4bb7bd860b2 Mon Sep 17 00:00:00 2001 From: Changhee Choi Date: Sun, 13 Dec 2020 03:20:44 +0900 Subject: [PATCH 30/86] =?UTF-8?q?docs:=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=20=EA=B5=AC=EC=A1=B0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34bd2a8..612208f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ ### Architecture -![스크린샷 2020-12-11 오후 9 33 31](https://user-images.githubusercontent.com/17294694/101904084-a882ba80-3bf8-11eb-8679-78145643e0c1.png) +![스크린샷 2020-12-13 오전 3 18 13](https://user-images.githubusercontent.com/17294694/101991738-f3ccc400-3cf1-11eb-9af5-0842f99efecd.png) ## 프로젝트 세팅 및 실행 From 3bb9208da3bb84080d3cf9b923bfff12efc28729 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Sun, 13 Dec 2020 03:22:35 +0900 Subject: [PATCH 31/86] =?UTF-8?q?fix:=20=EB=8B=AC=EB=A0=A5=EC=97=90=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=20=ED=81=B4=EB=A6=AD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/calendar/TableCell/index.tsx | 8 ++++++-- client/src/components/calendar/TableCell/styles.ts | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/src/components/calendar/TableCell/index.tsx b/client/src/components/calendar/TableCell/index.tsx index 103bb0c..a948507 100644 --- a/client/src/components/calendar/TableCell/index.tsx +++ b/client/src/components/calendar/TableCell/index.tsx @@ -20,11 +20,15 @@ function TableCell({ day, totalInOut }: TableCellTypes): JSX.Element { if (Number(calendarDaySelector.day) === Number(day) && day !== ' ') return 'isBold'; return ''; }; + const isCursor = (): 'isCursor' | '' => { + if (Number(day)) return 'isCursor'; + return ''; + }; const dispatch = useDispatch(); const onClick = (e: any) => { const value = e.currentTarget.innerText.split('\n')[0]; - if (value === calendarDaySelector.day) { + if (value === calendarDaySelector.day || Number(day[0]) === 0) { dispatch(changeDay({ day: 0 })); return; } @@ -32,7 +36,7 @@ function TableCell({ day, totalInOut }: TableCellTypes): JSX.Element { }; return ( <> - +
{day}
{totalInOut.get(day) && totalInOut.get(day).totalIn > 0 ? ( diff --git a/client/src/components/calendar/TableCell/styles.ts b/client/src/components/calendar/TableCell/styles.ts index 7b4a1e9..a4e1219 100644 --- a/client/src/components/calendar/TableCell/styles.ts +++ b/client/src/components/calendar/TableCell/styles.ts @@ -10,7 +10,9 @@ const TotalOut = styled.div` `; const CellButton = styled.td` position: relative; - cursor: pointer; + &.isCursor { + cursor: pointer; + } text-align: center; &.isBold { font-weight: bold; From e7207b1b5b5851298877d68c35cd4f5d908c51f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 03:41:41 +0900 Subject: [PATCH 32/86] =?UTF-8?q?refactor=20:=20custom=20error=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit string -> 객체로 변경 및 추가 --- server/src/common/error.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/server/src/common/error.ts b/server/src/common/error.ts index 7be7852..55d89b1 100644 --- a/server/src/common/error.ts +++ b/server/src/common/error.ts @@ -1,16 +1,26 @@ -const BAD_REQUEST = JSON.stringify({ +const BAD_REQUEST = { status: 400, - body: 'Bad request', -}); + message: 'Bad request', +}; -const ACCESS_DENIED = JSON.stringify({ +const ACCESS_DENIED = { status: 401, - body: 'Unauthorized', -}); + message: 'Unauthorized', +}; -const DATABASE_ERROR = JSON.stringify({ +const FORBIDDEN = { + stats: 403, + message: 'Does not havee access rights', +}; + +const DATABASE_ERROR = { status: 500, - body: 'Internal server error', -}); + message: 'Internal server error', +}; + +const NOT_FOUND_ERROR = { + status: 404, + message: 'cannot find requested resource', +}; -export { BAD_REQUEST, ACCESS_DENIED, DATABASE_ERROR }; +export { BAD_REQUEST, ACCESS_DENIED, DATABASE_ERROR, NOT_FOUND_ERROR, FORBIDDEN }; From b5a37bba55d2da36a397f2bbec60e64f8b281ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 03:48:20 +0900 Subject: [PATCH 33/86] =?UTF-8?q?refactor=20:=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EB=82=B4=EC=97=90=EC=84=9C=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EC=97=90=EB=9F=AC=20custom=20error=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 해당하는 error status 와 message로 반환하도록 변경 --- server/src/domain/auth/auth.router.ts | 4 ++-- server/src/domain/category/category.service.ts | 16 +++++++++++++--- server/src/domain/payment/payment.service.ts | 15 ++++++++++++--- .../domain/transaction/transaction.service.ts | 16 +++++++++++----- server/src/loader/index.ts | 7 +++---- server/src/middleware/jwt-authorize.ts | 18 ++++++------------ 6 files changed, 47 insertions(+), 29 deletions(-) diff --git a/server/src/domain/auth/auth.router.ts b/server/src/domain/auth/auth.router.ts index e06b071..0046dc1 100644 --- a/server/src/domain/auth/auth.router.ts +++ b/server/src/domain/auth/auth.router.ts @@ -37,10 +37,10 @@ class AuthRouter extends Router { this.get('/callback/:provider', async (ctx: Context) => { const { provider } = ctx.params; const { code, state, error } = ctx.request.query; - if (error || !code) throw new Error(ACCESS_DENIED); + if (error || !code) throw ACCESS_DENIED; const oAuthClient = new OAuthClient(provider); - const { profile, token } = await oAuthClient.authorize(code, state); + const { profile } = await oAuthClient.authorize(code, state); const uid = await this.userService.getOrCreateUid(profile); const jwtToken = JwtUtils.generateToken(uid); diff --git a/server/src/domain/category/category.service.ts b/server/src/domain/category/category.service.ts index bc6a4e5..ba675c3 100644 --- a/server/src/domain/category/category.service.ts +++ b/server/src/domain/category/category.service.ts @@ -1,6 +1,6 @@ import CategoryEntity from '@/entity/category.entity'; import { Repository } from 'typeorm'; -import { BAD_REQUEST } from '@/common/error'; +import { DATABASE_ERROR, NOT_FOUND_ERROR } from '@/common/error'; export default class CategoryService { private categoryRepository: Repository; @@ -16,6 +16,10 @@ export default class CategoryService { ): Promise { const category = this.categoryRepository.create({ name, isIncome, uid }); const newCategory = await this.categoryRepository.save(category); + + if (!newCategory) { + throw DATABASE_ERROR; + } return newCategory; } @@ -34,7 +38,10 @@ export default class CategoryService { uid: number, ): Promise { const category = await this.categoryRepository.findOne({ where: { cid, uid } }); - if (!category) throw new Error(BAD_REQUEST); + + if (!category) { + throw NOT_FOUND_ERROR; + } const mergedCategory = this.categoryRepository.merge(category, { name, isIncome }); const updatedCategory = await this.categoryRepository.save(mergedCategory); return updatedCategory; @@ -42,7 +49,10 @@ export default class CategoryService { public async deleteCategory(cid: number, uid: number): Promise { const category = await this.categoryRepository.findOne({ where: { cid, uid } }); - if (!category) throw new Error(BAD_REQUEST); + + if (!category) { + throw NOT_FOUND_ERROR; + } await this.categoryRepository.softDelete(category); return category; } diff --git a/server/src/domain/payment/payment.service.ts b/server/src/domain/payment/payment.service.ts index 4bb962a..a6cba5f 100644 --- a/server/src/domain/payment/payment.service.ts +++ b/server/src/domain/payment/payment.service.ts @@ -1,6 +1,6 @@ import PaymentEntity from '@/entity/payment.entity'; import { Repository } from 'typeorm'; -import { BAD_REQUEST } from '@/common/error'; +import { DATABASE_ERROR, NOT_FOUND_ERROR } from '@/common/error'; export default class PaymentService { private paymentRepository: Repository; @@ -12,6 +12,10 @@ export default class PaymentService { public async createPayment(name: string, uid: number): Promise { const payment = this.paymentRepository.create({ name, uid }); const newPayment = await this.paymentRepository.save(payment); + + if (!payment) { + throw DATABASE_ERROR; + } return newPayment; } @@ -25,7 +29,10 @@ export default class PaymentService { public async updatePayment(pid: number, name: string, uid: number): Promise { const payment = await this.paymentRepository.findOne({ where: { pid, uid } }); - if (!payment) throw new Error(BAD_REQUEST); + + if (!payment) { + throw NOT_FOUND_ERROR; + } const mergedPayment = await this.paymentRepository.merge(payment, { name }); const updatedPayment = await this.paymentRepository.save(mergedPayment); return updatedPayment; @@ -34,7 +41,9 @@ export default class PaymentService { public async deletePayment(pid: number, uid: number): Promise { const payment = await this.paymentRepository.findOne({ where: { pid, uid } }); - if (!payment) throw new Error(BAD_REQUEST); + if (!payment) { + throw NOT_FOUND_ERROR; + } await this.paymentRepository.softDelete(payment); return payment; } diff --git a/server/src/domain/transaction/transaction.service.ts b/server/src/domain/transaction/transaction.service.ts index 3b468f3..f9f500b 100644 --- a/server/src/domain/transaction/transaction.service.ts +++ b/server/src/domain/transaction/transaction.service.ts @@ -2,7 +2,7 @@ import TranscationEntity from '@/entity/transaction.entity'; import TransactionRepository from '@/domain/transaction/transaction.repository'; import { Between } from 'typeorm'; -import { BAD_REQUEST, DATABASE_ERROR } from '@/common/error'; +import { BAD_REQUEST, DATABASE_ERROR, NOT_FOUND_ERROR } from '@/common/error'; import { Transactional } from 'typeorm-transactional-cls-hooked'; import { MonthlyTransactionDetailsQueryParams, @@ -42,7 +42,7 @@ export default class TransactionService { relations: ['payment', 'category'], }); if (!newTransaction) { - throw new Error(DATABASE_ERROR); + throw DATABASE_ERROR; } return newTransaction; } @@ -55,7 +55,10 @@ export default class TransactionService { ): Promise { const { amount, tradeAt, description, isIncome, cid, pid } = data; const target = await this.transactionRepository.findOne({ where: { tid, uid } }); - if (!target) throw new Error(BAD_REQUEST); + + if (!target) { + throw NOT_FOUND_ERROR; + } const mergedTransaction = this.transactionRepository.merge(target, { amount, tradeAt, @@ -70,7 +73,7 @@ export default class TransactionService { relations: ['payment', 'category'], }); if (!updatedTransaction) { - throw new Error(DATABASE_ERROR); + throw DATABASE_ERROR; } return updatedTransaction; } @@ -78,7 +81,10 @@ export default class TransactionService { @Transactional() public async deleteTransaction(tid: number, uid: number): Promise { const transaction = await this.transactionRepository.findOne({ where: { tid, uid } }); - if (!transaction) throw new Error(BAD_REQUEST); + + if (!transaction) { + throw NOT_FOUND_ERROR; + } await this.transactionRepository.delete(transaction); return transaction; } diff --git a/server/src/loader/index.ts b/server/src/loader/index.ts index cccbb12..76aa0b5 100644 --- a/server/src/loader/index.ts +++ b/server/src/loader/index.ts @@ -1,5 +1,4 @@ -import Koa, { Next } from 'koa'; -import { Context } from 'vm'; +import Koa, { Context, Next } from 'koa'; import Router from 'koa-router'; import morgan from 'koa-morgan'; import cors from '@koa/cors'; @@ -23,9 +22,9 @@ export default async (app: Koa): Promise => { const token = ctx.cookies.get('jwt'); if (!token) { - throw new Error('unauthorized'); + throw FORBIDDEN; } - try { const decoded = JwtUtils.verifyToken(token); const userRepository = getRepository(UserEntity); const user = await userRepository.findOne({ where: { uid: decoded.uid } }); - if (!user) throw new Error('no user'); - ctx.state.user = new UserDTO(user); - - const now: number = Math.floor(Date.now() / 1000); - + const now = Math.floor(Date.now() / 1000); if (decoded.exp - now < 60 * 60 * 4) { const newToken = JwtUtils.generateToken(decoded.uid); ctx.cookies.set('jwt', newToken, { @@ -30,11 +25,10 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { httpOnly: true, }); } - - await next(); } catch (e) { - throw new Error(e.message); + throw ACCESS_DENIED; } + await next(); }; export default jwtAuthorize; From 447dcef58bff826061d9ddd8dc706390292b093c Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Sun, 13 Dec 2020 16:41:27 +0900 Subject: [PATCH 34/86] =?UTF-8?q?fix:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=8B=9C=20day=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/calendar/TableCell/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/components/calendar/TableCell/index.tsx b/client/src/components/calendar/TableCell/index.tsx index a948507..bb1b8d4 100644 --- a/client/src/components/calendar/TableCell/index.tsx +++ b/client/src/components/calendar/TableCell/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '@/modules'; import { changeDay } from '@/modules/calendarDaySelector/action'; @@ -34,6 +34,9 @@ function TableCell({ day, totalInOut }: TableCellTypes): JSX.Element { } dispatch(changeDay({ day: value })); }; + useEffect(() => { + dispatch(changeDay({ day: 0 })); + }, []); return ( <> From 6eb0f87b7cc730fb391808cebf7816cc3032e477 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 17:03:43 +0900 Subject: [PATCH 35/86] =?UTF-8?q?fix:=20case=20=EB=AC=B8=EC=A0=9C=EB=A1=9C?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CalendarUtils.ts 파일이 중복 존재하여 제거함. --- client/src/libs/CalendarUtils.ts | 35 -------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 client/src/libs/CalendarUtils.ts diff --git a/client/src/libs/CalendarUtils.ts b/client/src/libs/CalendarUtils.ts deleted file mode 100644 index ec5456c..0000000 --- a/client/src/libs/CalendarUtils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import dayjs from 'dayjs'; - -const getDayMatrix = (year: number, month: number): string[][] => { - let setMonth: number = month - 1; - let setYear: number = year; - if (setMonth < 0) { - setMonth = 12; - setYear -= 1; - } - const date = dayjs().year(setYear).month(setMonth); - const weekSlice = 7; - const startOfMonth = date.startOf('month').date(); - const endOfMonth = date.endOf('month').date(); - - const startDay = date.startOf('month').day(); - const remain = (startDay + endOfMonth) % 7; - const dataArr: any = [ - ...' '.repeat(startDay).split(''), - ...Array.from({ length: endOfMonth - startOfMonth + 1 }, (v, i) => String(i + 1)), - 7 - remain === 7 ? [] : ' '.repeat(7 - remain).split(''), - ]; - const resultMatrix: any = (): string[][] => { - const result = []; - let i = 0; - while (i < dataArr.length / weekSlice) { - result.push(dataArr.slice(i * weekSlice, i * weekSlice + weekSlice)); - i += 1; - } - if (result.length > 5) result.pop(); - return result; - }; - return resultMatrix(); -}; - -export default getDayMatrix; From de3f94da8f39f5acce72ad098107a1173df8cbb9 Mon Sep 17 00:00:00 2001 From: Changhee Choi Date: Sun, 13 Dec 2020 17:25:48 +0900 Subject: [PATCH 36/86] =?UTF-8?q?docs:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B0=9C=EC=9A=94=20=EB=82=B4=EC=9A=A9=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/README.md b/README.md index 612208f..c487acd 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,43 @@ 웹 기반으로 동작하는 개인 입출금 및 가계 재정을 시각화 및 분석할 수 있는 가계부 구현 +``` +🗓 가계부 관리 +``` + +- 내역 작성/삭제/수정 기능 +- 월별/ 일별 내역 달력 조회 기능 +- 카테고리, 결제수단 관리 기능 + +``` +📋 대시보드 +``` + +- 선택된 월에 해당하는 사용자 데이터를 집계/분석하여 보여줍니다. +- 월평균 수입과 이번 달 소비량을 이용해 소비 지수를 계산하고 소비 습관 상태 메세지를 보여줍니다. + +``` +📌 고정 지출 항목 분석 +``` + +- 지난 3개월 데이터를 비교해 고정적으로 발생하는 소비 내역을 고정 지출 항목으로 생성합니다. +- 새로 추가되는 데이터와 비교하여 완료된 고정지출과 예정된 고정지출로 분리하여 보여줍니다. + +``` +📊 통계 +``` + +- 카테고리 별, 기간 별 수입/지출 데이터를 집계하여 파이 그래프와 꺾은선 그래프로 시각화하여 보여줍니다. + +``` +📩 SMS 파싱 +``` + +- SMS 문자 내역을 파싱하여 가계부를 작성 할 수 있습니다. +- 붙여넣기를 위한 별도의 공간 없이도 복사한 내역을 파싱할 수 있게 Clipboard API를 이용하여 구현했습니다. + +
+ ### 기술스택 ![스크린샷 2020-12-11 오후 9 36 26](https://user-images.githubusercontent.com/17294694/101904293-f5ff2780-3bf8-11eb-8775-52034f850fcb.png) @@ -50,6 +87,37 @@ ![스크린샷 2020-12-13 오전 3 18 13](https://user-images.githubusercontent.com/17294694/101991738-f3ccc400-3cf1-11eb-9af5-0842f99efecd.png) +
+ +### 기술 특장점 + +``` +👨‍💻 쿼리 최적화 +``` + +- 대량의 더미 데이터를 생성하는 Seeder 모듈을 구현하고 생성된 데이터를 이용하여 Select 성능을 비교하여 쿼리를 최적화 + +``` +🔧 리덕스를 활용한 상태관리 +``` +- 페이지, 컴포넌트 간의 공유되는 상태를 새로 불러오기보다는 전역적으로 관리하기 위하여 Redux 사용 +- 대부분의 상태관리에 필요한 데이터를 비동기적으로 실행되는 API 호출로 받아오게 되는데 이 과정을 Redux-Thunk 미들웨어로 구현 + +``` +🔐 OAuth 로그인 구현 및 모듈화 +``` + +- Kakao, Naver, Google OAuth 로그인 구현 +- 다양한 서비스의 OAuth 로그인과 연동할 수 있도록 OAuth 인증 로직을 모듈화 + +``` +🚥 트래픽 분산 처리 +``` + +- Docker Swarm을 이용하여 클러스터 환경을 구축하고 필요에 따라 Docker 컨테이너를 스케일링 할 수 있도록 구현 +- JWT 인증을 이용하여 분산 세션에 대한 문제를 해결 + +
## 프로젝트 세팅 및 실행 From 6879318218acd9040e4cde68533c4ca9178e4315 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 17:40:05 +0900 Subject: [PATCH 37/86] =?UTF-8?q?refactor:=20=EB=A6=AC=EC=95=A1=ED=8A=B8?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20=EB=A1=9C=EA=B3=A0=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/public/logo192.png | Bin 5347 -> 0 bytes client/public/logo512.png | Bin 9664 -> 0 bytes client/public/manifest.json | 14 ++------------ 3 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 client/public/logo192.png delete mode 100644 client/public/logo512.png diff --git a/client/public/logo192.png b/client/public/logo192.png deleted file mode 100644 index fc44b0a3796c0e0a64c3d858ca038bd4570465d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN diff --git a/client/public/manifest.json b/client/public/manifest.json index 080d6c7..a4e663c 100644 --- a/client/public/manifest.json +++ b/client/public/manifest.json @@ -1,21 +1,11 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Tess", + "name": "개인 가계부 서비스 - Tess", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" } ], "start_url": ".", From d0cebfe1bfc4f411e1546560d6a1f91a6ef1ab36 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 20:25:43 +0900 Subject: [PATCH 38/86] =?UTF-8?q?refactor:=20=ED=8C=8C=EB=B9=84=EC=BD=98,?= =?UTF-8?q?=20=EB=A1=9C=EA=B3=A0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=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 --- client/public/favicon.ico | Bin 3870 -> 1150 bytes client/src/assets/svg/Logo.svg | 582 +++++++++++++++++++++++++++++++-- 2 files changed, 547 insertions(+), 35 deletions(-) diff --git a/client/public/favicon.ico b/client/public/favicon.ico index a11777cc471a4344702741ab1c8a588998b1311a..b0a24236ebb803f6f9b984496ff33e88a952df37 100644 GIT binary patch literal 1150 zcmcJOy-EW?6ot>KphPf1Q3xph6p~Wx)!rZ(pF%62LB&qr!Iw}F3kg_Q`qx=VS~~>` z1!es1j0*$FnoVKCw=;Y1J!fxnXQhm9ttPGu+8vQ*BsG8#kr-!@$W4w%!u>y8d`t3M z&{D-a&B8oPaGzPO=o54fozpbz!4*712X5dHmi%_`4mN0pa$j<$U4{$z2H%4_VjE!n zS^XUBfb}>9XV6QR#vZA$=a{|YVY8q@v&7~F^r literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= z!sii`1giyH7EKL_+Wi0ab<)&E_0KD!3Rp2^HNB*K2@PHCs4PWSA32*-^7d{9nH2_E zmC{C*N*)(vEF1_aMamw2A{ZH5aIDqiabnFdJ|y0%aS|64E$`s2ccV~3lR!u<){eS` z#^Mx6o(iP1Ix%4dv`t@!&Za-K@mTm#vadc{0aWDV*_%EiGK7qMC_(`exc>-$Gb9~W!w_^{*pYRm~G zBN{nA;cm^w$VWg1O^^<6vY`1XCD|s_zv*g*5&V#wv&s#h$xlUilPe4U@I&UXZbL z0)%9Uj&@yd03n;!7do+bfixH^FeZ-Ema}s;DQX2gY+7g0s(9;`8GyvPY1*vxiF&|w z>!vA~GA<~JUqH}d;DfBSi^IT*#lrzXl$fNpq0_T1tA+`A$1?(gLb?e#0>UELvljtQ zK+*74m0jn&)5yk8mLBv;=@}c{t0ztT<v;Avck$S6D`Z)^c0(jiwKhQsn|LDRY&w(Fmi91I7H6S;b0XM{e zXp0~(T@k_r-!jkLwd1_Vre^v$G4|kh4}=Gi?$AaJ)3I+^m|Zyj#*?Kp@w(lQdJZf4 z#|IJW5z+S^e9@(6hW6N~{pj8|NO*>1)E=%?nNUAkmv~OY&ZV;m-%?pQ_11)hAr0oAwILrlsGawpxx4D43J&K=n+p3WLnlDsQ$b(9+4 z?mO^hmV^F8MV{4Lx>(Q=aHhQ1){0d*(e&s%G=i5rq3;t{JC zmgbn5Nkl)t@fPH$v;af26lyhH!k+#}_&aBK4baYPbZy$5aFx4}ka&qxl z$=Rh$W;U)>-=S-0=?7FH9dUAd2(q#4TCAHky!$^~;Dz^j|8_wuKc*YzfdAht@Q&ror?91Dm!N03=4=O!a)I*0q~p0g$Fm$pmr$ zb;wD;STDIi$@M%y1>p&_>%?UP($15gou_ue1u0!4(%81;qcIW8NyxFEvXpiJ|H4wz z*mFT(qVx1FKufG11hByuX%lPk4t#WZ{>8ka2efjY`~;AL6vWyQKpJun2nRiZYDij$ zP>4jQXPaP$UC$yIVgGa)jDV;F0l^n(V=HMRB5)20V7&r$jmk{UUIe zVjKroK}JAbD>B`2cwNQ&GDLx8{pg`7hbA~grk|W6LgiZ`8y`{Iq0i>t!3p2}MS6S+ zO_ruKyAElt)rdS>CtF7j{&6rP-#c=7evGMt7B6`7HG|-(WL`bDUAjyn+k$mx$CH;q2Dz4x;cPP$hW=`pFfLO)!jaCL@V2+F)So3}vg|%O*^T1j>C2lx zsURO-zIJC$^$g2byVbRIo^w>UxK}74^TqUiRR#7s_X$e)$6iYG1(PcW7un-va-S&u zHk9-6Zn&>T==A)lM^D~bk{&rFzCi35>UR!ZjQkdSiNX*-;l4z9j*7|q`TBl~Au`5& z+c)*8?#-tgUR$Zd%Q3bs96w6k7q@#tUn`5rj+r@_sAVVLqco|6O{ILX&U-&-cbVa3 zY?ngHR@%l{;`ri%H*0EhBWrGjv!LE4db?HEWb5mu*t@{kv|XwK8?npOshmzf=vZA@ zVSN9sL~!sn?r(AK)Q7Jk2(|M67Uy3I{eRy z_l&Y@A>;vjkWN5I2xvFFTLX0i+`{qz7C_@bo`ZUzDugfq4+>a3?1v%)O+YTd6@Ul7 zAfLfm=nhZ`)P~&v90$&UcF+yXm9sq!qCx3^9gzIcO|Y(js^Fj)Rvq>nQAHI92ap=P z10A4@prk+AGWCb`2)dQYFuR$|H6iDE8p}9a?#nV2}LBCoCf(Xi2@szia7#gY>b|l!-U`c}@ zLdhvQjc!BdLJvYvzzzngnw51yRYCqh4}$oRCy-z|v3Hc*d|?^Wj=l~18*E~*cR_kU z{XsxM1i{V*4GujHQ3DBpl2w4FgFR48Nma@HPgnyKoIEY-MqmMeY=I<%oG~l!f<+FN z1ZY^;10j4M4#HYXP zw5eJpA_y(>uLQ~OucgxDLuf}fVs272FaMxhn4xnDGIyLXnw>Xsd^J8XhcWIwIoQ9} z%FoSJTAGW(SRGwJwb=@pY7r$uQRK3Zd~XbxU)ts!4XsJrCycrWSI?e!IqwqIR8+Jh zlRjZ`UO1I!BtJR_2~7AbkbSm%XQqxEPkz6BTGWx8e}nQ=w7bZ|eVP4?*Tb!$(R)iC z9)&%bS*u(lXqzitAN)Oo=&Ytn>%Hzjc<5liuPi>zC_nw;Z0AE3Y$Jao_Q90R-gl~5 z_xAb2J%eArrC1CN4G$}-zVvCqF1;H;abAu6G*+PDHSYFx@Tdbfox*uEd3}BUyYY-l zTfEsOqsi#f9^FoLO;ChK<554qkri&Av~SIM*{fEYRE?vH7pTAOmu2pz3X?Wn*!ROX ztd54huAk&mFBemMooL33RV-*1f0Q3_(7hl$<#*|WF9P!;r;4_+X~k~uKEqdzZ$5Al zV63XN@)j$FN#cCD;ek1R#l zv%pGrhB~KWgoCj%GT?%{@@o(AJGt*PG#l3i>lhmb_twKH^EYvacVY-6bsCl5*^~L0 zonm@lk2UvvTKr2RS%}T>^~EYqdL1q4nD%0n&Xqr^cK^`J5W;lRRB^R-O8b&HENO||mo0xaD+S=I8RTlIfVgqN@SXDr2&-)we--K7w= zJVU8?Z+7k9dy;s;^gDkQa`0nz6N{T?(A&Iz)2!DEecLyRa&FI!id#5Z7B*O2=PsR0 zEvc|8{NS^)!d)MDX(97Xw}m&kEO@5jqRaDZ!+%`wYOI<23q|&js`&o4xvjP7D_xv@ z5hEwpsp{HezI9!~6O{~)lLR@oF7?J7i>1|5a~UuoN=q&6N}EJPV_GD`&M*v8Y`^2j zKII*d_@Fi$+i*YEW+Hbzn{iQk~yP z>7N{S4)r*!NwQ`(qcN#8SRQsNK6>{)X12nbF`*7#ecO7I)Q$uZsV+xS4E7aUn+U(K baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/client/src/assets/svg/Logo.svg b/client/src/assets/svg/Logo.svg index bc2720e..555f7ab 100644 --- a/client/src/assets/svg/Logo.svg +++ b/client/src/assets/svg/Logo.svg @@ -1,36 +1,548 @@ - - - - - - - - - - - + + + + + + + + image/svg+xml + + + + + + + + + From 2f6b5ba75660517ed5424cd248e2a87f1eeacf8c Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 20:26:57 +0900 Subject: [PATCH 39/86] =?UTF-8?q?refactor:=20=ED=97=A4=EB=8D=94,=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 헤더에서 로고 위치를 중앙이 아닌 좌측 정렬로 변경함. 로그인 페이지의 로그 크기 줄임. --- .../src/components/common/layouts/Header/index.tsx | 4 ++-- .../src/components/common/layouts/Header/styles.ts | 12 ++++-------- client/src/views/LoginPage/index.tsx | 12 ++++++------ client/src/views/LoginPage/styles.ts | 1 + 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/client/src/components/common/layouts/Header/index.tsx b/client/src/components/common/layouts/Header/index.tsx index 4366fbd..55527f3 100644 --- a/client/src/components/common/layouts/Header/index.tsx +++ b/client/src/components/common/layouts/Header/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Link, useHistory } from 'react-router-dom'; -import Logo from '@components/common/Logo'; +import Logo from '@/components/common/Logo'; import Dropdown from '@/components/common/Dropdown'; import authorizationAPI from '@/libs/api/Authorization'; import CircleUserSVG from '@/assets/svg/CircleUser.svg'; @@ -31,7 +31,7 @@ function Header(): JSX.Element { - + diff --git a/client/src/components/common/layouts/Header/styles.ts b/client/src/components/common/layouts/Header/styles.ts index 456d2d9..39853c2 100644 --- a/client/src/components/common/layouts/Header/styles.ts +++ b/client/src/components/common/layouts/Header/styles.ts @@ -12,22 +12,18 @@ const HeaderDiv = styled.div` `; const HeaderContentDiv = styled.div` - text-align: center; + display: flex; height: 40px; + align-items: center; `; const HeaderLogo = styled.div` + flex: 1 1 auto; display: inline-block; - margin-top: 5px; - margin-right: -48px; `; const DropDiv = styled.div` - float: right; - margin-top: 3px; - position: relative; - top: 50%; - transform: translateY(-50%); + flex: 0 0 auto; `; const Item = styled.li` diff --git a/client/src/views/LoginPage/index.tsx b/client/src/views/LoginPage/index.tsx index a529f3f..7c23f09 100644 --- a/client/src/views/LoginPage/index.tsx +++ b/client/src/views/LoginPage/index.tsx @@ -5,7 +5,7 @@ import Logo from '@/components/common/Logo'; import LoginButton from '@/container/LoginButton'; import { useDispatch } from 'react-redux'; import { login } from '@/modules/authorization/actions'; -import { Box, LogoBox } from './styles'; +import * as S from './styles'; const LoginPage = (props: RouteComponentProps): JSX.Element => { const dispatch = useDispatch(); @@ -22,12 +22,12 @@ const LoginPage = (props: RouteComponentProps): JSX.Element => { checkLogin(); }, [props]); return ( - - - - + + + + - + ); }; diff --git a/client/src/views/LoginPage/styles.ts b/client/src/views/LoginPage/styles.ts index 109d8bc..7824f46 100644 --- a/client/src/views/LoginPage/styles.ts +++ b/client/src/views/LoginPage/styles.ts @@ -12,4 +12,5 @@ export const Box = styled.div` export const LogoBox = styled.div` text-align: center; + margin-left: -0.8rem; `; From babd217fa8deaa753f0c33239f9433bf9e1a4df9 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 20:28:02 +0900 Subject: [PATCH 40/86] =?UTF-8?q?fix:=20CalendarView=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20import=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit case 오류가 있었던 calendarUtils import 부분을 수정함. --- client/src/components/calendar/CalendarView/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/calendar/CalendarView/index.tsx b/client/src/components/calendar/CalendarView/index.tsx index 022893c..0bf40f1 100644 --- a/client/src/components/calendar/CalendarView/index.tsx +++ b/client/src/components/calendar/CalendarView/index.tsx @@ -1,8 +1,8 @@ import React from 'react'; import MatrixView from '@/components/calendar/MatrixView'; import TableCell from '@/components/calendar/TableCell'; -import getDayMatrix from '@libs/calendarUtils'; -import { getWeekDays } from '@libs/nationalCalendarUtils'; +import getDayMatrix from '@/libs/calendarUtils'; +import { getWeekDays } from '@/libs/nationalCalendarUtils'; import { CalendarViewType } from './types'; function CalendarView({ totalInOut, lang, year, month }: CalendarViewType): JSX.Element { From 38169017dcf464f00ae91ff4a4f59ecb133d7170 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Sun, 13 Dec 2020 20:28:08 +0900 Subject: [PATCH 41/86] =?UTF-8?q?fix:=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80?= =?UTF-8?q?=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20,=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/container/TransactionModal/index.tsx | 1 - client/src/container/TransactionUpdateModal/index.tsx | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/client/src/container/TransactionModal/index.tsx b/client/src/container/TransactionModal/index.tsx index f5acdbe..f7471ca 100644 --- a/client/src/container/TransactionModal/index.tsx +++ b/client/src/container/TransactionModal/index.tsx @@ -17,7 +17,6 @@ import DateUtils from '@/libs/dateUtils'; import ModalInput from '@/components/transaction/ModalInput'; import ModalHeader from '@/components/transaction/ModalHeader'; import checkValidation from '@/libs/checkValidation'; -import { pid } from 'process'; import * as S from './styles'; import { TransactionModalProps } from './types'; diff --git a/client/src/container/TransactionUpdateModal/index.tsx b/client/src/container/TransactionUpdateModal/index.tsx index b6ed855..f20b44f 100644 --- a/client/src/container/TransactionUpdateModal/index.tsx +++ b/client/src/container/TransactionUpdateModal/index.tsx @@ -16,11 +16,11 @@ import TransactionRequestDTO from '@/commons/dto/transaction-request'; import { deleteTransactionThunk, updateTransactionThunk } from '@/modules/transaction'; import * as S from './styles'; -const MODALLSITARR = ['tradeAt', 'description', 'amount', 'pid', 'cid', 'isIncome']; +const MODAL_LSIT_ARR = ['tradeAt', 'description', 'amount', 'pid', 'cid', 'isIncome']; const TransactionUpdateModal = (): JSX.Element => { const [isIncome, setIsIncome] = useState(false); - const [validation, setValidation] = useState(new Set(MODALLSITARR)); + const [validation, setValidation] = useState(new Set(MODAL_LSIT_ARR)); const { payment, category } = useSelector((state: RootState) => state); const { toggle, data } = useSelector((state: RootState) => state.updateModal); const categoryList = category.data.map((c) => new CategoryDTO(c)); @@ -28,7 +28,7 @@ const TransactionUpdateModal = (): JSX.Element => { const dispatch = useDispatch(); const toggleModal = useCallback(() => { dispatch(toggleModalOff()); - setValidation(new Set(MODALLSITARR)); + setValidation(new Set(MODAL_LSIT_ARR)); }, [dispatch]); const onChangeReducer = (state: UpdateTransactionRequest, action: UpdateTransactionRequest) => { From 374109fcb41036bc51bcf9901dba3e70a863c73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 20:36:19 +0900 Subject: [PATCH 42/86] =?UTF-8?q?refacotr=20:=20custom=20Error=20=EC=9E=AC?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit class 기반의 Error 정의 message를 외부에서 주입받을 수 있도록 설정 Error interface 상속 --- server/src/common/error.ts | 26 ------------------------ server/src/common/error/access-deined.ts | 8 ++++++++ server/src/common/error/bad-request.ts | 8 ++++++++ server/src/common/error/database.ts | 8 ++++++++ server/src/common/error/forbidden.ts | 8 ++++++++ server/src/common/error/not-found.ts | 8 ++++++++ 6 files changed, 40 insertions(+), 26 deletions(-) delete mode 100644 server/src/common/error.ts create mode 100644 server/src/common/error/access-deined.ts create mode 100644 server/src/common/error/bad-request.ts create mode 100644 server/src/common/error/database.ts create mode 100644 server/src/common/error/forbidden.ts create mode 100644 server/src/common/error/not-found.ts diff --git a/server/src/common/error.ts b/server/src/common/error.ts deleted file mode 100644 index 55d89b1..0000000 --- a/server/src/common/error.ts +++ /dev/null @@ -1,26 +0,0 @@ -const BAD_REQUEST = { - status: 400, - message: 'Bad request', -}; - -const ACCESS_DENIED = { - status: 401, - message: 'Unauthorized', -}; - -const FORBIDDEN = { - stats: 403, - message: 'Does not havee access rights', -}; - -const DATABASE_ERROR = { - status: 500, - message: 'Internal server error', -}; - -const NOT_FOUND_ERROR = { - status: 404, - message: 'cannot find requested resource', -}; - -export { BAD_REQUEST, ACCESS_DENIED, DATABASE_ERROR, NOT_FOUND_ERROR, FORBIDDEN }; diff --git a/server/src/common/error/access-deined.ts b/server/src/common/error/access-deined.ts new file mode 100644 index 0000000..35f07fd --- /dev/null +++ b/server/src/common/error/access-deined.ts @@ -0,0 +1,8 @@ +export default class AccessDeniedError extends Error { + status; + + constructor(message: string) { + super(message); + this.status = 401; + } +} diff --git a/server/src/common/error/bad-request.ts b/server/src/common/error/bad-request.ts new file mode 100644 index 0000000..70f7dc0 --- /dev/null +++ b/server/src/common/error/bad-request.ts @@ -0,0 +1,8 @@ +export default class BadRequest extends Error { + status; + + constructor(message: string) { + super(message); + this.status = 400; + } +} diff --git a/server/src/common/error/database.ts b/server/src/common/error/database.ts new file mode 100644 index 0000000..27211d4 --- /dev/null +++ b/server/src/common/error/database.ts @@ -0,0 +1,8 @@ +export default class DatabaseError extends Error { + status; + + constructor(message: string) { + super(message); + this.status = 500; + } +} diff --git a/server/src/common/error/forbidden.ts b/server/src/common/error/forbidden.ts new file mode 100644 index 0000000..ee1e90a --- /dev/null +++ b/server/src/common/error/forbidden.ts @@ -0,0 +1,8 @@ +export default class ForbideenError extends Error { + status; + + constructor(message: string) { + super(message); + this.status = 403; + } +} diff --git a/server/src/common/error/not-found.ts b/server/src/common/error/not-found.ts new file mode 100644 index 0000000..1e408d5 --- /dev/null +++ b/server/src/common/error/not-found.ts @@ -0,0 +1,8 @@ +export default class NotFoundError extends Error { + status; + + constructor(message: string) { + super(message); + this.status = 404; + } +} From 37129a65c32b7e64893c968ff84690cd9ec7e59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 20:56:47 +0900 Subject: [PATCH 43/86] =?UTF-8?q?refactor=20:=20error=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 401 error forbidden -> unauthorized 로 변경 --- server/src/common/error/{access-deined.ts => unauthorized.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename server/src/common/error/{access-deined.ts => unauthorized.ts} (62%) diff --git a/server/src/common/error/access-deined.ts b/server/src/common/error/unauthorized.ts similarity index 62% rename from server/src/common/error/access-deined.ts rename to server/src/common/error/unauthorized.ts index 35f07fd..34a8924 100644 --- a/server/src/common/error/access-deined.ts +++ b/server/src/common/error/unauthorized.ts @@ -1,4 +1,4 @@ -export default class AccessDeniedError extends Error { +export default class UnauthorizedError extends Error { status; constructor(message: string) { From 8af1fe2a4344fec1ddf9eb6c5c0f6330558dab60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 20:57:24 +0900 Subject: [PATCH 44/86] =?UTF-8?q?refactor=20:=20custom=20error=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/domain/auth/auth.router.ts | 4 ++-- server/src/domain/category/category.service.ts | 14 ++++++++++---- server/src/domain/payment/payment.service.ts | 14 ++++++++++---- .../src/domain/transaction/transaction.service.ts | 11 ++++++----- server/src/middleware/jwt-authorize.ts | 6 +++--- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/server/src/domain/auth/auth.router.ts b/server/src/domain/auth/auth.router.ts index 0046dc1..63de755 100644 --- a/server/src/domain/auth/auth.router.ts +++ b/server/src/domain/auth/auth.router.ts @@ -1,4 +1,3 @@ -import { ACCESS_DENIED } from 'common/error'; import Router from 'koa-router'; import { Context } from 'koa'; import userRepository from '@/domain/user/user.repository'; @@ -7,6 +6,7 @@ import { JwtConfig } from '@/config/index'; import UserService from '@/domain/user/user.service'; import jwtAuthorize from '@/middleware/jwt-authorize'; import OAuthClient from '@/lib/oauth-client'; +import BadRequest from '@/common/error/bad-request'; import JwtUtils from './utils/jwt-utils'; class AuthRouter extends Router { @@ -37,7 +37,7 @@ class AuthRouter extends Router { this.get('/callback/:provider', async (ctx: Context) => { const { provider } = ctx.params; const { code, state, error } = ctx.request.query; - if (error || !code) throw ACCESS_DENIED; + if (error || !code) throw new BadRequest('Bad request'); const oAuthClient = new OAuthClient(provider); const { profile } = await oAuthClient.authorize(code, state); diff --git a/server/src/domain/category/category.service.ts b/server/src/domain/category/category.service.ts index ba675c3..852add4 100644 --- a/server/src/domain/category/category.service.ts +++ b/server/src/domain/category/category.service.ts @@ -1,6 +1,7 @@ +import DatabaseError from '@/common/error/database'; +import NotFoundError from '@/common/error/not-found'; import CategoryEntity from '@/entity/category.entity'; import { Repository } from 'typeorm'; -import { DATABASE_ERROR, NOT_FOUND_ERROR } from '@/common/error'; export default class CategoryService { private categoryRepository: Repository; @@ -18,7 +19,7 @@ export default class CategoryService { const newCategory = await this.categoryRepository.save(category); if (!newCategory) { - throw DATABASE_ERROR; + throw new DatabaseError('Fail to create new category'); } return newCategory; } @@ -40,10 +41,15 @@ export default class CategoryService { const category = await this.categoryRepository.findOne({ where: { cid, uid } }); if (!category) { - throw NOT_FOUND_ERROR; + throw new NotFoundError('Requested category resource does not exist'); } const mergedCategory = this.categoryRepository.merge(category, { name, isIncome }); const updatedCategory = await this.categoryRepository.save(mergedCategory); + + if (!updatedCategory) { + throw new DatabaseError('Fail to update category'); + } + return updatedCategory; } @@ -51,7 +57,7 @@ export default class CategoryService { const category = await this.categoryRepository.findOne({ where: { cid, uid } }); if (!category) { - throw NOT_FOUND_ERROR; + throw new NotFoundError('Requested category resource does not exist'); } await this.categoryRepository.softDelete(category); return category; diff --git a/server/src/domain/payment/payment.service.ts b/server/src/domain/payment/payment.service.ts index a6cba5f..0df61ab 100644 --- a/server/src/domain/payment/payment.service.ts +++ b/server/src/domain/payment/payment.service.ts @@ -1,6 +1,7 @@ +import DatabaseError from '@/common/error/database'; +import NotFoundError from '@/common/error/not-found'; import PaymentEntity from '@/entity/payment.entity'; import { Repository } from 'typeorm'; -import { DATABASE_ERROR, NOT_FOUND_ERROR } from '@/common/error'; export default class PaymentService { private paymentRepository: Repository; @@ -14,7 +15,7 @@ export default class PaymentService { const newPayment = await this.paymentRepository.save(payment); if (!payment) { - throw DATABASE_ERROR; + throw new DatabaseError('Fail to create new payment'); } return newPayment; } @@ -31,10 +32,15 @@ export default class PaymentService { const payment = await this.paymentRepository.findOne({ where: { pid, uid } }); if (!payment) { - throw NOT_FOUND_ERROR; + throw new NotFoundError('Requested payment resource does not exist'); } const mergedPayment = await this.paymentRepository.merge(payment, { name }); const updatedPayment = await this.paymentRepository.save(mergedPayment); + + if (!updatedPayment) { + throw new DatabaseError('Fail to updated payment'); + } + return updatedPayment; } @@ -42,7 +48,7 @@ export default class PaymentService { const payment = await this.paymentRepository.findOne({ where: { pid, uid } }); if (!payment) { - throw NOT_FOUND_ERROR; + throw new NotFoundError('Requested payment resource does not exist'); } await this.paymentRepository.softDelete(payment); return payment; diff --git a/server/src/domain/transaction/transaction.service.ts b/server/src/domain/transaction/transaction.service.ts index f9f500b..0f9145a 100644 --- a/server/src/domain/transaction/transaction.service.ts +++ b/server/src/domain/transaction/transaction.service.ts @@ -2,8 +2,9 @@ import TranscationEntity from '@/entity/transaction.entity'; import TransactionRepository from '@/domain/transaction/transaction.repository'; import { Between } from 'typeorm'; -import { BAD_REQUEST, DATABASE_ERROR, NOT_FOUND_ERROR } from '@/common/error'; import { Transactional } from 'typeorm-transactional-cls-hooked'; +import DatabaseError from '@/common/error/database'; +import NotFoundError from '@/common/error/not-found'; import { MonthlyTransactionDetailsQueryParams, TransactionDetail, @@ -42,7 +43,7 @@ export default class TransactionService { relations: ['payment', 'category'], }); if (!newTransaction) { - throw DATABASE_ERROR; + throw new DatabaseError('Fail to create new transaction'); } return newTransaction; } @@ -57,7 +58,7 @@ export default class TransactionService { const target = await this.transactionRepository.findOne({ where: { tid, uid } }); if (!target) { - throw NOT_FOUND_ERROR; + throw new NotFoundError('Requested transaction resource does not exist'); } const mergedTransaction = this.transactionRepository.merge(target, { amount, @@ -73,7 +74,7 @@ export default class TransactionService { relations: ['payment', 'category'], }); if (!updatedTransaction) { - throw DATABASE_ERROR; + throw new DatabaseError('Fail to update transaction'); } return updatedTransaction; } @@ -83,7 +84,7 @@ export default class TransactionService { const transaction = await this.transactionRepository.findOne({ where: { tid, uid } }); if (!transaction) { - throw NOT_FOUND_ERROR; + throw new NotFoundError('Requested transaction resource does not exist'); } await this.transactionRepository.delete(transaction); return transaction; diff --git a/server/src/middleware/jwt-authorize.ts b/server/src/middleware/jwt-authorize.ts index 2d62165..a1f1984 100644 --- a/server/src/middleware/jwt-authorize.ts +++ b/server/src/middleware/jwt-authorize.ts @@ -4,12 +4,12 @@ import JwtUtils from '@/domain/auth/utils/jwt-utils'; import { JwtConfig } from '@config/index'; import UserEntity from '@/entity/user.entity'; import UserDTO from '@/domain/auth/types/user-dto'; -import { ACCESS_DENIED, FORBIDDEN } from '@/common/error'; +import UnauthorizedError from '@/common/error/unauthorized'; const jwtAuthorize = async (ctx: Context, next: Next): Promise => { const token = ctx.cookies.get('jwt'); if (!token) { - throw FORBIDDEN; + throw new UnauthorizedError('Request need authorization'); } try { const decoded = JwtUtils.verifyToken(token); @@ -26,7 +26,7 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { }); } } catch (e) { - throw ACCESS_DENIED; + throw new UnauthorizedError('Invalid authorization token'); } await next(); }; From add3532163831b09bd90e6510d4bc7b1f534a96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 21:07:42 +0900 Subject: [PATCH 45/86] =?UTF-8?q?refactor=20:=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20error=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/middleware/jwt-authorize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/middleware/jwt-authorize.ts b/server/src/middleware/jwt-authorize.ts index a1f1984..8541189 100644 --- a/server/src/middleware/jwt-authorize.ts +++ b/server/src/middleware/jwt-authorize.ts @@ -26,7 +26,7 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { }); } } catch (e) { - throw new UnauthorizedError('Invalid authorization token'); + throw new UnauthorizedError('Invalid authentication token'); } await next(); }; From 11f7a998dc7492b314477ba83179c1ea26ec230f Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 21:09:18 +0900 Subject: [PATCH 46/86] =?UTF-8?q?refactor:=20UserDTO=EC=97=90=20socialId?= =?UTF-8?q?=20=EB=A9=A4=EB=B2=84=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit socialId 값을 저장하기 위해 멤버변수 추가함. --- server/src/domain/auth/types/user-dto.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/domain/auth/types/user-dto.ts b/server/src/domain/auth/types/user-dto.ts index f23f709..0833362 100644 --- a/server/src/domain/auth/types/user-dto.ts +++ b/server/src/domain/auth/types/user-dto.ts @@ -4,6 +4,7 @@ export default class UserDTO { constructor(user: UserEntity) { this.uid = user.uid; this.name = user.name; + this.socialId = user.socialId; this.socialType = user.socialType; this.updateAt = user.updateAt; this.createAt = user.createAt; @@ -13,6 +14,8 @@ export default class UserDTO { name: string; + socialId: string; + socialType: string; updateAt: Date | undefined; From 3555159b64844b18ebca0371e99f3ca68eb3e8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 21:09:24 +0900 Subject: [PATCH 47/86] =?UTF-8?q?refacotr=20:=20=EC=98=A4=ED=83=80?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit forbidden 오타 수정 --- server/src/common/error/forbidden.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/common/error/forbidden.ts b/server/src/common/error/forbidden.ts index ee1e90a..859c1d9 100644 --- a/server/src/common/error/forbidden.ts +++ b/server/src/common/error/forbidden.ts @@ -1,4 +1,4 @@ -export default class ForbideenError extends Error { +export default class ForbiddenError extends Error { status; constructor(message: string) { From 3c2a12322d967bd07cf04acb5f6e4ba1b2e34698 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 21:12:24 +0900 Subject: [PATCH 48/86] =?UTF-8?q?refactor:=20JWT=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EA=B0=92=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 토큰에 저장되는 값에 socialId, socialType 항목을 추가함. --- server/src/domain/auth/auth.router.ts | 6 +++--- server/src/domain/auth/utils/jwt-utils.ts | 5 ++++- server/src/domain/user/user.service.ts | 13 +++++++------ server/src/middleware/jwt-authorize.ts | 10 ++++++---- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/server/src/domain/auth/auth.router.ts b/server/src/domain/auth/auth.router.ts index e06b071..5b920d8 100644 --- a/server/src/domain/auth/auth.router.ts +++ b/server/src/domain/auth/auth.router.ts @@ -40,10 +40,10 @@ class AuthRouter extends Router { if (error || !code) throw new Error(ACCESS_DENIED); const oAuthClient = new OAuthClient(provider); - const { profile, token } = await oAuthClient.authorize(code, state); + const { profile } = await oAuthClient.authorize(code, state); - const uid = await this.userService.getOrCreateUid(profile); - const jwtToken = JwtUtils.generateToken(uid); + const user = await this.userService.getOrCreateUser(profile); + const jwtToken = JwtUtils.generateToken(user); ctx.cookies.set('jwt', jwtToken, { maxAge: JwtConfig.cookieExpiresIn, diff --git a/server/src/domain/auth/utils/jwt-utils.ts b/server/src/domain/auth/utils/jwt-utils.ts index 0413b77..699e7af 100644 --- a/server/src/domain/auth/utils/jwt-utils.ts +++ b/server/src/domain/auth/utils/jwt-utils.ts @@ -1,11 +1,14 @@ import jwt from 'jsonwebtoken'; import { JwtConfig } from '@/config/index'; import decodedJWT from '@/domain/auth/types/decoded-jwt'; +import UserDTO from '@/domain/auth/types/user-dto'; -const generateToken = (uid: number): string => { +const generateToken = ({ uid, socialId, socialType }: UserDTO): string => { const token = jwt.sign( { uid, + socialId, + socialType, }, JwtConfig.tokenSecret, { diff --git a/server/src/domain/user/user.service.ts b/server/src/domain/user/user.service.ts index 673fc56..f2b3a8a 100644 --- a/server/src/domain/user/user.service.ts +++ b/server/src/domain/user/user.service.ts @@ -1,6 +1,7 @@ import SocialUserDTO from '@/lib/oauth-client/userinfo/default'; import UserEntity from '@/entity/user.entity'; import { Repository } from 'typeorm'; +import UserDTO from '../auth/types/user-dto'; export default class UserService { private userRepository: Repository; @@ -9,13 +10,13 @@ export default class UserService { this.userRepository = userRepository; } - public async getUserById(id: number): Promise { + public async getUserById(id: number): Promise { const user = await this.userRepository.findOne({ where: { uid: id } }); - return user; + return user ? new UserDTO(user) : undefined; } - public async getOrCreateUid(data: SocialUserDTO): Promise { + public async getOrCreateUser(data: SocialUserDTO): Promise { let user = await this.userRepository.findOne({ where: { socialId: data.socialId, socialType: data.socialType }, }); @@ -24,10 +25,10 @@ export default class UserService { user = await this.createNewUser(data); } - return user.uid; + return new UserDTO(user); } - public async createNewUser(data: SocialUserDTO): Promise { + public async createNewUser(data: SocialUserDTO): Promise { const newUser = this.userRepository.create({ name: data.name, socialId: data.socialId, @@ -35,6 +36,6 @@ export default class UserService { }); const savedUser = await this.userRepository.save(newUser); - return savedUser; + return new UserDTO(savedUser); } } diff --git a/server/src/middleware/jwt-authorize.ts b/server/src/middleware/jwt-authorize.ts index 1a460b8..2a8695a 100644 --- a/server/src/middleware/jwt-authorize.ts +++ b/server/src/middleware/jwt-authorize.ts @@ -19,12 +19,14 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { if (!user) throw new Error('no user'); - ctx.state.user = new UserDTO(user); + const userDTO = new UserDTO(user); + ctx.state.user = new UserDTO(userDTO); - const now: number = Math.floor(Date.now() / 1000); + const now: number = new Date().getTime(); + const FOUR_HOUR = 1000 * 60 * 60 * 4; - if (decoded.exp - now < 60 * 60 * 4) { - const newToken = JwtUtils.generateToken(decoded.uid); + if (decoded.exp - now < FOUR_HOUR) { + const newToken = JwtUtils.generateToken(userDTO); ctx.cookies.set('jwt', newToken, { maxAge: Number(JwtConfig.cookieExpiresIn), httpOnly: true, From c63db18c9b03730e54eb34bd9dfb798ae257d9a7 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 21:14:25 +0900 Subject: [PATCH 49/86] =?UTF-8?q?refacor:=20=EC=BF=A0=ED=82=A4=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20=EB=AA=A8=EB=93=88=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 쿠키 발급 코드를 JwtUtils 모듈에 함수로 추가. --- server/src/domain/auth/auth.router.ts | 5 +---- server/src/domain/auth/utils/jwt-utils.ts | 10 +++++++++- server/src/middleware/jwt-authorize.ts | 9 ++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/server/src/domain/auth/auth.router.ts b/server/src/domain/auth/auth.router.ts index 5b920d8..3ab7b82 100644 --- a/server/src/domain/auth/auth.router.ts +++ b/server/src/domain/auth/auth.router.ts @@ -45,10 +45,7 @@ class AuthRouter extends Router { const user = await this.userService.getOrCreateUser(profile); const jwtToken = JwtUtils.generateToken(user); - ctx.cookies.set('jwt', jwtToken, { - maxAge: JwtConfig.cookieExpiresIn, - httpOnly: true, - }); + JwtUtils.setCookie(ctx, jwtToken); const clientUri = process.env.CLIENT_URI as string; ctx.redirect(clientUri); diff --git a/server/src/domain/auth/utils/jwt-utils.ts b/server/src/domain/auth/utils/jwt-utils.ts index 699e7af..a4a5e5a 100644 --- a/server/src/domain/auth/utils/jwt-utils.ts +++ b/server/src/domain/auth/utils/jwt-utils.ts @@ -1,4 +1,5 @@ import jwt from 'jsonwebtoken'; +import { Context } from 'koa'; import { JwtConfig } from '@/config/index'; import decodedJWT from '@/domain/auth/types/decoded-jwt'; import UserDTO from '@/domain/auth/types/user-dto'; @@ -23,4 +24,11 @@ const verifyToken = (token: string): decodedJWT => { return decodedToken; }; -export default { generateToken, verifyToken }; +const setCookie = (ctx: Context, token: string): void => { + ctx.cookies.set('jwt', token, { + maxAge: Number(JwtConfig.cookieExpiresIn), + httpOnly: true, + }); +}; + +export default { generateToken, verifyToken, setCookie }; diff --git a/server/src/middleware/jwt-authorize.ts b/server/src/middleware/jwt-authorize.ts index 2a8695a..66249db 100644 --- a/server/src/middleware/jwt-authorize.ts +++ b/server/src/middleware/jwt-authorize.ts @@ -1,8 +1,6 @@ -import { Next } from 'koa'; -import { Context } from 'vm'; +import { Next, Context } from 'koa'; import { getRepository } from 'typeorm'; import JwtUtils from '@/domain/auth/utils/jwt-utils'; -import { JwtConfig } from '@config/index'; import UserEntity from '@/entity/user.entity'; import UserDTO from '@/domain/auth/types/user-dto'; @@ -27,10 +25,7 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { if (decoded.exp - now < FOUR_HOUR) { const newToken = JwtUtils.generateToken(userDTO); - ctx.cookies.set('jwt', newToken, { - maxAge: Number(JwtConfig.cookieExpiresIn), - httpOnly: true, - }); + JwtUtils.setCookie(ctx, newToken); } await next(); From d391c4ff18dc7b5d23f69d80f40f0ba152a6d44a Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 21:34:04 +0900 Subject: [PATCH 50/86] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EA=B8=B0=EC=A4=80=EA=B0=92=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 토큰 재발급 기준값을 토큰 설정에 따라 같이 변경할 수 있도록 환경변수 로 분리함. --- server/.dummy.env | 43 +++++++++++++------------- server/src/config/index.ts | 3 +- server/src/middleware/jwt-authorize.ts | 6 ++-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/server/.dummy.env b/server/.dummy.env index f8624f5..1ef2c88 100644 --- a/server/.dummy.env +++ b/server/.dummy.env @@ -1,26 +1,27 @@ -TYPEORM_CONNECTION = -TYPEORM_HOST = -TYPEORM_PORT = -TYPEORM_USERNAME = -TYPEORM_PASSWORD = -TYPEORM_DATABASE = -TYPEORM_SYNCHRONIZE = -TYPEORM_LOGGING = +TYPEORM_CONNECTION= #DB 타입(mysql) +TYPEORM_HOST= #DB HOST +TYPEORM_PORT= #DB PORT +TYPEORM_USERNAME= #DB 계정 +TYPEORM_PASSWORD= #DB PASSWORD +TYPEORM_DATABASE= #DB 이름 +TYPEORM_SYNCHRONIZE= #엔티티와 테이블 sync 여부 +TYPEORM_LOGGING= #쿼리 실행 로깅 여부 -CLIENT_URI = +CLIENT_URI= #클라이언트 URI -JWT_SECRET = -JWT_TOKEN_EXPIRES_IN = -JWT_COOKIE_EXPIRES_IN = +JWT_SECRET= #토큰 Secret +JWT_TOKEN_EXPIRES_IN= #토큰 유효시간 (string - '1h','1d') +JWT_TOKEN_REFERSH_THRESHOLD= #토큰 재발급 기준 남은 유효시간 (ms) +JWT_COOKIE_EXPIRES_IN= #쿠키 유효시간 (ms) -KAKAO_CLIENT_ID = -KAKAO_CLIENT_SECRET = -KAKAO_CALLBACK_URI = +KAKAO_CLIENT_ID= #발급받은 CLIENT ID +KAKAO_CLIENT_SECRET= #발급받은 CLIENT SECRET +KAKAO_CALLBACK_URI= #개발자센터에 설정한 콜백 URI -NAVER_CLIENT_ID = -NAVER_CLIENT_SECRET = -NAVER_CALLBACK_URI = +NAVER_CLIENT_ID= +NAVER_CLIENT_SECRET= +NAVER_CALLBACK_URI= -GOOGLE_CLIENT_ID = -GOOGLE_CLIENT_SECRET = -GOOGLE_CALLBACK_URI = +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +GOOGLE_CALLBACK_URI= diff --git a/server/src/config/index.ts b/server/src/config/index.ts index c26c689..d7958c9 100644 --- a/server/src/config/index.ts +++ b/server/src/config/index.ts @@ -52,10 +52,9 @@ export const OAuthConfig = { }, }; -export default OAuthConfig; - export const JwtConfig = { tokenSecret: process.env.JWT_SECRET || 'token-secret', tokenExpiresIn: process.env.JWT_TOKEN_EXPIRES_IN || '1d', + refreshThreshold: Number(process.env.JWT_TOKEN_REFERSH_THRESHOLD || 14400000), cookieExpiresIn: Number(process.env.JWT_COOKIE_EXPIRES_IN || 86400000), }; diff --git a/server/src/middleware/jwt-authorize.ts b/server/src/middleware/jwt-authorize.ts index 22e25e1..9ec88e7 100644 --- a/server/src/middleware/jwt-authorize.ts +++ b/server/src/middleware/jwt-authorize.ts @@ -1,5 +1,6 @@ import { Context, Next } from 'koa'; import { getRepository } from 'typeorm'; +import { JwtConfig } from '@/config'; import JwtUtils from '@/domain/auth/utils/jwt-utils'; import UserEntity from '@/entity/user.entity'; import UserDTO from '@/domain/auth/types/user-dto'; @@ -14,15 +15,14 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { const decoded = JwtUtils.verifyToken(token); const userRepository = getRepository(UserEntity); const user = await userRepository.findOne({ where: { uid: decoded.uid } }); - if (!user) throw new Error('no user'); + if (!user) throw new Error('Not found user'); const userDTO = new UserDTO(user); ctx.state.user = userDTO; const now: number = new Date().getTime(); - const FOUR_HOUR = 1000 * 60 * 60 * 4; - if (decoded.exp - now < FOUR_HOUR) { + if (decoded.exp - now < JwtConfig.refreshThreshold) { const newToken = JwtUtils.generateToken(userDTO); JwtUtils.setCookie(ctx, newToken); } From 7c1bda83cda6e17594fd31b729d8b1e1d275599f Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 21:36:37 +0900 Subject: [PATCH 51/86] =?UTF-8?q?fix:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20impo?= =?UTF-8?q?rt=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/domain/auth/auth.router.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/domain/auth/auth.router.ts b/server/src/domain/auth/auth.router.ts index 28666b6..babbb77 100644 --- a/server/src/domain/auth/auth.router.ts +++ b/server/src/domain/auth/auth.router.ts @@ -2,7 +2,6 @@ import Router from 'koa-router'; import { Context } from 'koa'; import userRepository from '@/domain/user/user.repository'; import { v4 } from 'uuid'; -import { JwtConfig } from '@/config/index'; import UserService from '@/domain/user/user.service'; import jwtAuthorize from '@/middleware/jwt-authorize'; import OAuthClient from '@/lib/oauth-client'; From ac882820bb37eb9f26f51de4acb66ebc4ac216c3 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 21:39:32 +0900 Subject: [PATCH 52/86] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=ED=95=AD=EB=AA=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DB가 변경됐을때 변경된 DB에서 같은 uid를 가진 다른 유저로 로그인되는 현상을 막기 위해 토큰을 검증할때 socialId, socialType도 같이 비교하도록 수정함. --- server/src/domain/auth/types/decoded-jwt.ts | 2 ++ server/src/middleware/jwt-authorize.ts | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/domain/auth/types/decoded-jwt.ts b/server/src/domain/auth/types/decoded-jwt.ts index e18a045..2d7ba41 100644 --- a/server/src/domain/auth/types/decoded-jwt.ts +++ b/server/src/domain/auth/types/decoded-jwt.ts @@ -1,4 +1,6 @@ export default interface decodedJWT { uid: number; + socialId: string; + socialType: string; exp: number; } diff --git a/server/src/middleware/jwt-authorize.ts b/server/src/middleware/jwt-authorize.ts index 9ec88e7..a9e48de 100644 --- a/server/src/middleware/jwt-authorize.ts +++ b/server/src/middleware/jwt-authorize.ts @@ -12,9 +12,9 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { throw new UnauthorizedError('Request need authorization'); } try { - const decoded = JwtUtils.verifyToken(token); + const { uid, socialId, socialType, exp } = JwtUtils.verifyToken(token); const userRepository = getRepository(UserEntity); - const user = await userRepository.findOne({ where: { uid: decoded.uid } }); + const user = await userRepository.findOne({ where: { uid, socialId, socialType } }); if (!user) throw new Error('Not found user'); const userDTO = new UserDTO(user); @@ -22,7 +22,7 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { const now: number = new Date().getTime(); - if (decoded.exp - now < JwtConfig.refreshThreshold) { + if (exp - now < JwtConfig.refreshThreshold) { const newToken = JwtUtils.generateToken(userDTO); JwtUtils.setCookie(ctx, newToken); } From e6b60f9d927d727a33c18ec8520b066aae5b22c8 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 22:32:49 +0900 Subject: [PATCH 53/86] =?UTF-8?q?test:=20Jwt=20Authorize=20Middleware=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/package.json | 1 + server/src/domain/auth/auth.router.ts | 2 +- .../jwt-utils.ts => lib/jwt-utils/index.ts} | 0 .../index.ts} | 7 ++- .../jwt-authorize-middleware.test.ts | 49 +++++++++++++++++++ yarn.lock | 44 +++++++++++++---- 6 files changed, 88 insertions(+), 15 deletions(-) rename server/src/{domain/auth/utils/jwt-utils.ts => lib/jwt-utils/index.ts} (100%) rename server/src/middleware/{jwt-authorize.ts => jwt-authorize/index.ts} (83%) create mode 100644 server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts diff --git a/server/package.json b/server/package.json index 5bbc6fe..403472a 100644 --- a/server/package.json +++ b/server/package.json @@ -30,6 +30,7 @@ "license": "ISC", "dependencies": { "@koa/cors": "^3.1.0", + "@shopify/jest-koa-mocks": "^2.2.4", "axios": "^0.21.0", "dotenv": "^8.2.0", "jsonwebtoken": "^8.5.1", diff --git a/server/src/domain/auth/auth.router.ts b/server/src/domain/auth/auth.router.ts index babbb77..4b3706c 100644 --- a/server/src/domain/auth/auth.router.ts +++ b/server/src/domain/auth/auth.router.ts @@ -6,7 +6,7 @@ import UserService from '@/domain/user/user.service'; import jwtAuthorize from '@/middleware/jwt-authorize'; import OAuthClient from '@/lib/oauth-client'; import BadRequest from '@/common/error/bad-request'; -import JwtUtils from './utils/jwt-utils'; +import JwtUtils from '@/lib/jwt-utils'; class AuthRouter extends Router { private userService; diff --git a/server/src/domain/auth/utils/jwt-utils.ts b/server/src/lib/jwt-utils/index.ts similarity index 100% rename from server/src/domain/auth/utils/jwt-utils.ts rename to server/src/lib/jwt-utils/index.ts diff --git a/server/src/middleware/jwt-authorize.ts b/server/src/middleware/jwt-authorize/index.ts similarity index 83% rename from server/src/middleware/jwt-authorize.ts rename to server/src/middleware/jwt-authorize/index.ts index a9e48de..c1a6903 100644 --- a/server/src/middleware/jwt-authorize.ts +++ b/server/src/middleware/jwt-authorize/index.ts @@ -1,8 +1,7 @@ import { Context, Next } from 'koa'; -import { getRepository } from 'typeorm'; import { JwtConfig } from '@/config'; -import JwtUtils from '@/domain/auth/utils/jwt-utils'; -import UserEntity from '@/entity/user.entity'; +import JwtUtils from '@/lib/jwt-utils'; +import UserRepository from '@/domain/user/user.repository'; import UserDTO from '@/domain/auth/types/user-dto'; import UnauthorizedError from '@/common/error/unauthorized'; @@ -13,7 +12,7 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { } try { const { uid, socialId, socialType, exp } = JwtUtils.verifyToken(token); - const userRepository = getRepository(UserEntity); + const userRepository = UserRepository.getUserRepository(); const user = await userRepository.findOne({ where: { uid, socialId, socialType } }); if (!user) throw new Error('Not found user'); diff --git a/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts b/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts new file mode 100644 index 0000000..b207bd5 --- /dev/null +++ b/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts @@ -0,0 +1,49 @@ +import { createMockContext } from '@shopify/jest-koa-mocks'; +import JwtUtils from '@/lib/jwt-utils'; +import UserRepository from '@/domain/user/user.repository'; +import UserDTO from '@/domain/auth/types/user-dto'; +import UnauthorizedError from '@/common/error/unauthorized'; +import { fail } from 'assert'; +import JwtAuthorizeMiddleware from '.'; + +describe('Jwt Authorize Middleware', () => { + it('토큰이 유효하면 context.state에 user가 등록된다', async () => { + const userRepository = UserRepository.getUserRepository(); + const user = await userRepository.save({ + socialId: '123456789', + socialType: 'naver', + name: 'TestUser', + }); + const userDTO = new UserDTO(user); + const token = JwtUtils.generateToken(userDTO); + const cookies = { jwt: token }; + const ctx = createMockContext({ cookies }); + + await JwtAuthorizeMiddleware(ctx, () => Promise.resolve()); + + expect(ctx.state.user).toStrictEqual(userDTO); + + await userRepository.remove(user); + }); + + it('등록된 유저가 아니면 UnauthorizedError가 던져진다', async () => { + const userDTO = new UserDTO({ + uid: 1, + socialId: '123456789', + socialType: 'naver', + name: 'TestUser', + updateAt: undefined, + createAt: new Date(), + }); + const token = JwtUtils.generateToken(userDTO); + const cookies = { jwt: token }; + const ctx = createMockContext({ cookies }); + + try { + await JwtAuthorizeMiddleware(ctx, () => Promise.resolve()); + fail(); + } catch (err) { + expect(err.constructor).toEqual(UnauthorizedError); + } + }); +}); diff --git a/yarn.lock b/yarn.lock index aa9e12c..dfd94ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1749,6 +1749,15 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@shopify/jest-koa-mocks@^2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@shopify/jest-koa-mocks/-/jest-koa-mocks-2.2.4.tgz#ad762ad63f0a69c3f4f288aceb44caadd814af57" + integrity sha512-Dhzi8HSxni7vL7WP3qLZ9Wot7JkeWPOiFf/Yhie2aaBlHNB6o26C1W7a2JbWz5nhhZNVOvILT4H9BqOt//7WdA== + dependencies: + koa "^2.5.0" + node-mocks-http "^1.5.8" + tslib "^1.14.1" + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -3908,7 +3917,7 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@^1.3.5, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: +accepts@^1.3.5, accepts@^1.3.7, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -6806,7 +6815,7 @@ denque@^1.4.1: resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== -depd@^1.1.2, depd@~1.1.2: +depd@^1.1.0, depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -8308,7 +8317,7 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fresh@0.5.2, fresh@~0.5.2: +fresh@0.5.2, fresh@^0.5.2, fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= @@ -10796,7 +10805,7 @@ koa-router@^10.0.0: methods "^1.1.2" path-to-regexp "^6.1.0" -koa@^2.13.0: +koa@^2.13.0, koa@^2.5.0: version "2.13.0" resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.0.tgz#25217e05efd3358a7e5ddec00f0a380c9b71b501" integrity sha512-i/XJVOfPw7npbMv67+bOeXr3gPqOAw6uh5wFyNs3QvJ47tUx3M3V9rIE0//WytY42MKz4l/MXKyGkQ2LQTfLUQ== @@ -11388,7 +11397,7 @@ merge-deep@^3.0.2: clone-deep "^0.2.4" kind-of "^3.0.2" -merge-descriptors@1.0.1: +merge-descriptors@1.0.1, merge-descriptors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= @@ -11465,7 +11474,7 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.27, mime-types@~2.1.17, dependencies: mime-db "1.44.0" -mime@1.6.0: +mime@1.6.0, mime@^1.3.4: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -11851,6 +11860,21 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" +node-mocks-http@^1.5.8: + version "1.9.0" + resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.9.0.tgz#6000c570fc4b809603782309be81c73a71d85b71" + integrity sha512-ILf7Ws8xyX9Rl2fLZ7xhZBovrRwgaP84M13esndP6V17M/8j25TpwNzb7Im8U9XCo6fRhdwqiQajWXpsas/E6w== + dependencies: + accepts "^1.3.7" + depd "^1.1.0" + fresh "^0.5.2" + merge-descriptors "^1.0.1" + methods "^1.1.2" + mime "^1.3.4" + parseurl "^1.3.3" + range-parser "^1.2.0" + type-is "^1.6.18" + node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" @@ -12473,7 +12497,7 @@ parse5@^6.0.0, parse5@^6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== -parseurl@^1.3.2, parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@^1.3.2, parseurl@^1.3.3, parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -13693,7 +13717,7 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@^1.2.1, range-parser@~1.2.1: +range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== @@ -16515,7 +16539,7 @@ tsconfig-paths@^3.4.0, tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.10.0, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.10.0, tslib@^1.13.0, tslib@^1.14.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -16600,7 +16624,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: +type-is@^1.6.16, type-is@^1.6.18, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== From 7931d3b26231455e16c1930b5b41812f0b8b133f Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Sun, 13 Dec 2020 22:45:33 +0900 Subject: [PATCH 54/86] =?UTF-8?q?refactor:=20JwtUtils=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config를 주입받을 수 있도록 구조를 변경함. --- server/src/domain/auth/auth.router.ts | 6 +- server/src/lib/jwt-utils/index.ts | 62 +++++++++++-------- server/src/lib/jwt-utils/types.ts | 0 server/src/middleware/jwt-authorize/index.ts | 7 ++- .../jwt-authorize-middleware.test.ts | 13 +++- 5 files changed, 55 insertions(+), 33 deletions(-) create mode 100644 server/src/lib/jwt-utils/types.ts diff --git a/server/src/domain/auth/auth.router.ts b/server/src/domain/auth/auth.router.ts index 4b3706c..00a064f 100644 --- a/server/src/domain/auth/auth.router.ts +++ b/server/src/domain/auth/auth.router.ts @@ -7,6 +7,7 @@ import jwtAuthorize from '@/middleware/jwt-authorize'; import OAuthClient from '@/lib/oauth-client'; import BadRequest from '@/common/error/bad-request'; import JwtUtils from '@/lib/jwt-utils'; +import { JwtConfig } from '@/config'; class AuthRouter extends Router { private userService; @@ -34,6 +35,7 @@ class AuthRouter extends Router { }); this.get('/callback/:provider', async (ctx: Context) => { + const jwtUtils = new JwtUtils(JwtConfig); const { provider } = ctx.params; const { code, state, error } = ctx.request.query; if (error || !code) throw new BadRequest('Bad request'); @@ -42,9 +44,9 @@ class AuthRouter extends Router { const { profile } = await oAuthClient.authorize(code, state); const user = await this.userService.getOrCreateUser(profile); - const jwtToken = JwtUtils.generateToken(user); + const jwtToken = jwtUtils.generateToken(user); - JwtUtils.setCookie(ctx, jwtToken); + jwtUtils.setCookie(ctx, jwtToken); const clientUri = process.env.CLIENT_URI as string; ctx.redirect(clientUri); diff --git a/server/src/lib/jwt-utils/index.ts b/server/src/lib/jwt-utils/index.ts index a4a5e5a..5bad317 100644 --- a/server/src/lib/jwt-utils/index.ts +++ b/server/src/lib/jwt-utils/index.ts @@ -1,34 +1,44 @@ import jwt from 'jsonwebtoken'; import { Context } from 'koa'; -import { JwtConfig } from '@/config/index'; import decodedJWT from '@/domain/auth/types/decoded-jwt'; import UserDTO from '@/domain/auth/types/user-dto'; -const generateToken = ({ uid, socialId, socialType }: UserDTO): string => { - const token = jwt.sign( - { - uid, - socialId, - socialType, - }, - JwtConfig.tokenSecret, - { - expiresIn: JwtConfig.tokenExpiresIn, - }, - ); - return token; -}; +export default class JwtUtils { + private config; -const verifyToken = (token: string): decodedJWT => { - const decodedToken = jwt.verify(token, JwtConfig.tokenSecret) as decodedJWT; - return decodedToken; -}; + constructor(config: { + tokenSecret: string; + tokenExpiresIn: string; + refreshThreshold: number; + cookieExpiresIn: number; + }) { + this.config = config; + } -const setCookie = (ctx: Context, token: string): void => { - ctx.cookies.set('jwt', token, { - maxAge: Number(JwtConfig.cookieExpiresIn), - httpOnly: true, - }); -}; + generateToken = ({ uid, socialId, socialType }: UserDTO): string => { + const token = jwt.sign( + { + uid, + socialId, + socialType, + }, + this.config.tokenSecret, + { + expiresIn: this.config.tokenExpiresIn, + }, + ); + return token; + }; -export default { generateToken, verifyToken, setCookie }; + verifyToken = (token: string): decodedJWT => { + const decodedToken = jwt.verify(token, this.config.tokenSecret) as decodedJWT; + return decodedToken; + }; + + setCookie = (ctx: Context, token: string): void => { + ctx.cookies.set('jwt', token, { + maxAge: Number(this.config.cookieExpiresIn), + httpOnly: true, + }); + }; +} diff --git a/server/src/lib/jwt-utils/types.ts b/server/src/lib/jwt-utils/types.ts new file mode 100644 index 0000000..e69de29 diff --git a/server/src/middleware/jwt-authorize/index.ts b/server/src/middleware/jwt-authorize/index.ts index c1a6903..55d59dd 100644 --- a/server/src/middleware/jwt-authorize/index.ts +++ b/server/src/middleware/jwt-authorize/index.ts @@ -6,12 +6,13 @@ import UserDTO from '@/domain/auth/types/user-dto'; import UnauthorizedError from '@/common/error/unauthorized'; const jwtAuthorize = async (ctx: Context, next: Next): Promise => { + const jwtUtils = new JwtUtils(JwtConfig); const token = ctx.cookies.get('jwt'); if (!token) { throw new UnauthorizedError('Request need authorization'); } try { - const { uid, socialId, socialType, exp } = JwtUtils.verifyToken(token); + const { uid, socialId, socialType, exp } = jwtUtils.verifyToken(token); const userRepository = UserRepository.getUserRepository(); const user = await userRepository.findOne({ where: { uid, socialId, socialType } }); if (!user) throw new Error('Not found user'); @@ -22,8 +23,8 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { const now: number = new Date().getTime(); if (exp - now < JwtConfig.refreshThreshold) { - const newToken = JwtUtils.generateToken(userDTO); - JwtUtils.setCookie(ctx, newToken); + const newToken = jwtUtils.generateToken(userDTO); + jwtUtils.setCookie(ctx, newToken); } } catch (e) { throw new UnauthorizedError('Invalid authentication token'); diff --git a/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts b/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts index b207bd5..0f64b5a 100644 --- a/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts +++ b/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts @@ -6,8 +6,16 @@ import UnauthorizedError from '@/common/error/unauthorized'; import { fail } from 'assert'; import JwtAuthorizeMiddleware from '.'; +const baseMockJwtConfig = { + tokenSecret: 'token-secret', + tokenExpiresIn: '1d', + refreshThreshold: 14400000, + cookieExpiresIn: 86400000, +}; + describe('Jwt Authorize Middleware', () => { it('토큰이 유효하면 context.state에 user가 등록된다', async () => { + const jwtUtils = new JwtUtils(baseMockJwtConfig); const userRepository = UserRepository.getUserRepository(); const user = await userRepository.save({ socialId: '123456789', @@ -15,7 +23,7 @@ describe('Jwt Authorize Middleware', () => { name: 'TestUser', }); const userDTO = new UserDTO(user); - const token = JwtUtils.generateToken(userDTO); + const token = jwtUtils.generateToken(userDTO); const cookies = { jwt: token }; const ctx = createMockContext({ cookies }); @@ -27,6 +35,7 @@ describe('Jwt Authorize Middleware', () => { }); it('등록된 유저가 아니면 UnauthorizedError가 던져진다', async () => { + const jwtUtils = new JwtUtils(baseMockJwtConfig); const userDTO = new UserDTO({ uid: 1, socialId: '123456789', @@ -35,7 +44,7 @@ describe('Jwt Authorize Middleware', () => { updateAt: undefined, createAt: new Date(), }); - const token = JwtUtils.generateToken(userDTO); + const token = jwtUtils.generateToken(userDTO); const cookies = { jwt: token }; const ctx = createMockContext({ cookies }); From ba6ab8c1cea3cfcda4840a499f0edcc1255b43fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 23:03:35 +0900 Subject: [PATCH 55/86] =?UTF-8?q?feat=20:=20loading=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/assets/svg/Spinner.svg | 52 +++++++++++++++++++ .../common/LoadingSpinner/index.tsx | 13 +++++ 2 files changed, 65 insertions(+) create mode 100644 client/src/assets/svg/Spinner.svg create mode 100644 client/src/components/common/LoadingSpinner/index.tsx diff --git a/client/src/assets/svg/Spinner.svg b/client/src/assets/svg/Spinner.svg new file mode 100644 index 0000000..2ca805e --- /dev/null +++ b/client/src/assets/svg/Spinner.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/common/LoadingSpinner/index.tsx b/client/src/components/common/LoadingSpinner/index.tsx new file mode 100644 index 0000000..51b4c46 --- /dev/null +++ b/client/src/components/common/LoadingSpinner/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import Spinner from '@/assets/svg/Spinner.svg'; +import StyledLoadingSpinner from './styles'; + +const LoadingSpinner = (): JSX.Element => { + return ( + + loading... + + ); +}; + +export default LoadingSpinner; From 12f94d48ce2660fe66ac31425e7ccba0956c4c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Sun, 13 Dec 2020 23:04:49 +0900 Subject: [PATCH 56/86] =?UTF-8?q?feat=20:=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20loading=20component?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/LoadingSpinner/styles.ts | 14 +++ .../container/DashboardContainer/index.tsx | 115 ++++++++++-------- 2 files changed, 78 insertions(+), 51 deletions(-) create mode 100644 client/src/components/common/LoadingSpinner/styles.ts diff --git a/client/src/components/common/LoadingSpinner/styles.ts b/client/src/components/common/LoadingSpinner/styles.ts new file mode 100644 index 0000000..f863e0f --- /dev/null +++ b/client/src/components/common/LoadingSpinner/styles.ts @@ -0,0 +1,14 @@ +import styled from 'styled-components'; + +const StyledLoadingSpinner = styled.div` + display: flex; + height: 100%; + width: 100%; + justify-content: center; + align-items: center; + .img { + height: 30%; + } +`; + +export default StyledLoadingSpinner; diff --git a/client/src/container/DashboardContainer/index.tsx b/client/src/container/DashboardContainer/index.tsx index dea5360..cd65b57 100644 --- a/client/src/container/DashboardContainer/index.tsx +++ b/client/src/container/DashboardContainer/index.tsx @@ -10,6 +10,7 @@ import FixedExpenditure from '@container/FixedExpenditure'; import aggregateAPI from '@/libs/api/aggregate'; import NumberUtils from '@/libs/numberUtils'; import EmptyStateComponent from '@/components/transaction/EmptyState'; +import LoadingSpinner from '@/components/common/LoadingSpinner'; import * as S from './styles'; const RECENT_TRANSACTION_LIMIT = 3; @@ -70,57 +71,69 @@ const DashboardContainer = (): JSX.Element => { - - - {datePicker.month}월 소비/수입 - - 소비 습관{getSpendingStatus(overspendingIndexState.overspendingIndex)} - - - - - - - - - - - - 최근 내역 - 자세히 보기 - - {transactionState.transactions.length !== 0 ? ( - transactionState.transactions.slice(0, RECENT_TRANSACTION_LIMIT).map((transaction) => ( - - - - )) - ) : ( - - )} - - - - - 카테고리 통계 - 자세히 보기 - - {mostSpendingCategoryState.name}에 가장 많은 돈을 쓰셨어요 - - 사용한 금액 :{' '} - {NumberUtils.numberWithCommas(Number(mostSpendingCategoryState.aggregate || 0))}원 - - - - - 기간별 통계 - 자세히 보기 - - {transactionState.mostOutDateDetail.date}일에 가장 많은 돈을 쓰셨어요 - - 사용한 금액 : {NumberUtils.numberWithCommas(transactionState.mostOutDateDetail.amount)}원 - - + {transactionState.loading ? ( + + ) : ( + <> + + + {datePicker.month}월 소비/수입 + + 소비 습관{getSpendingStatus(overspendingIndexState.overspendingIndex)} + + + + + + + + + + + + 최근 내역 + 자세히 보기 + + {transactionState.transactions.length !== 0 ? ( + transactionState.transactions + .slice(0, RECENT_TRANSACTION_LIMIT) + .map((transaction) => ( + + + + )) + ) : ( + + )} + + + + + 카테고리 통계 + 자세히 보기 + + {mostSpendingCategoryState.name}에 가장 많은 돈을 쓰셨어요 + + 사용한 금액 :{' '} + {NumberUtils.numberWithCommas(Number(mostSpendingCategoryState.aggregate || 0))}원 + + + + + + 기간별 통계 + 자세히 보기 + + + {transactionState.mostOutDateDetail.date}일에 가장 많은 돈을 쓰셨어요 + + + 사용한 금액 :{' '} + {NumberUtils.numberWithCommas(transactionState.mostOutDateDetail.amount)}원 + + + + )} ); }; From 4a16e4873157cbda102b56844b8dffcaf8e98f60 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 00:22:51 +0900 Subject: [PATCH 57/86] =?UTF-8?q?refactor:=20JWT=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EB=B0=8F=20config=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 토큰 만료시간과 쿠키 만료시간을 하나의 환경변수로 관리되도록 변경함. 토큰 만료시간의 경우 number 타입으로 입력할 경우 초 단위로 적용이 되는 문제가 있어서 환경변수 입력을 초단위로 변경하고 쿠키생성시에는 1000을 곱해주도록 수정함. --- server/.dummy.env | 5 ++--- server/src/config/index.ts | 5 ++--- server/src/lib/jwt-utils/index.ts | 9 ++------- server/src/middleware/jwt-authorize/index.ts | 5 ++--- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/server/.dummy.env b/server/.dummy.env index 1ef2c88..e876940 100644 --- a/server/.dummy.env +++ b/server/.dummy.env @@ -10,9 +10,8 @@ TYPEORM_LOGGING= #쿼리 실행 로깅 여부 CLIENT_URI= #클라이언트 URI JWT_SECRET= #토큰 Secret -JWT_TOKEN_EXPIRES_IN= #토큰 유효시간 (string - '1h','1d') -JWT_TOKEN_REFERSH_THRESHOLD= #토큰 재발급 기준 남은 유효시간 (ms) -JWT_COOKIE_EXPIRES_IN= #쿠키 유효시간 (ms) +JWT_TOKEN_EXPIRES_IN= #토큰 유효시간 (seconds) +JWT_TOKEN_REFERSH_THRESHOLD= #토큰 재발급 기준 남은 유효시간 (seconds) KAKAO_CLIENT_ID= #발급받은 CLIENT ID KAKAO_CLIENT_SECRET= #발급받은 CLIENT SECRET diff --git a/server/src/config/index.ts b/server/src/config/index.ts index d7958c9..6e96f19 100644 --- a/server/src/config/index.ts +++ b/server/src/config/index.ts @@ -54,7 +54,6 @@ export const OAuthConfig = { export const JwtConfig = { tokenSecret: process.env.JWT_SECRET || 'token-secret', - tokenExpiresIn: process.env.JWT_TOKEN_EXPIRES_IN || '1d', - refreshThreshold: Number(process.env.JWT_TOKEN_REFERSH_THRESHOLD || 14400000), - cookieExpiresIn: Number(process.env.JWT_COOKIE_EXPIRES_IN || 86400000), + tokenExpiresIn: Number(process.env.JWT_TOKEN_EXPIRES_IN || 86400), + refreshThreshold: Number(process.env.JWT_TOKEN_REFERSH_THRESHOLD || 14400), }; diff --git a/server/src/lib/jwt-utils/index.ts b/server/src/lib/jwt-utils/index.ts index 5bad317..4c935b2 100644 --- a/server/src/lib/jwt-utils/index.ts +++ b/server/src/lib/jwt-utils/index.ts @@ -6,12 +6,7 @@ import UserDTO from '@/domain/auth/types/user-dto'; export default class JwtUtils { private config; - constructor(config: { - tokenSecret: string; - tokenExpiresIn: string; - refreshThreshold: number; - cookieExpiresIn: number; - }) { + constructor(config: { tokenSecret: string; tokenExpiresIn: number; refreshThreshold: number }) { this.config = config; } @@ -37,7 +32,7 @@ export default class JwtUtils { setCookie = (ctx: Context, token: string): void => { ctx.cookies.set('jwt', token, { - maxAge: Number(this.config.cookieExpiresIn), + maxAge: this.config.tokenExpiresIn * 1000, httpOnly: true, }); }; diff --git a/server/src/middleware/jwt-authorize/index.ts b/server/src/middleware/jwt-authorize/index.ts index 55d59dd..ad1dadb 100644 --- a/server/src/middleware/jwt-authorize/index.ts +++ b/server/src/middleware/jwt-authorize/index.ts @@ -20,9 +20,8 @@ const jwtAuthorize = async (ctx: Context, next: Next): Promise => { const userDTO = new UserDTO(user); ctx.state.user = userDTO; - const now: number = new Date().getTime(); - - if (exp - now < JwtConfig.refreshThreshold) { + const nowToSec: number = new Date().getTime() / 1000; + if (exp - nowToSec < JwtConfig.refreshThreshold) { const newToken = jwtUtils.generateToken(userDTO); jwtUtils.setCookie(ctx, newToken); } From c0cda7983cc4757a6b56939c4ebc47eb0867154f Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 00:25:07 +0900 Subject: [PATCH 58/86] =?UTF-8?q?test:=20JWT=20Authorize=20Middleware=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토큰 재발급 상황에 대한 케이스 추가 - 토큰이 유효하지 않은 상황에 대한 케이스 추가 - DB가 변경됐을때 같은 uid를 가진 유저로 로그인되던 상황에 대한 케이스 추가 --- server/package.json | 2 +- .../jwt-authorize-middleware.test.ts | 100 ++++++++++++++++-- server/src/seed/test.seed.ts | 39 +++---- yarn.lock | 8 +- 4 files changed, 116 insertions(+), 33 deletions(-) diff --git a/server/package.json b/server/package.json index 403472a..1eb88d3 100644 --- a/server/package.json +++ b/server/package.json @@ -40,7 +40,7 @@ "koa-router": "^10.0.0", "mysql2": "^2.2.5", "typeorm": "^0.2.29", - "typeorm-transactional-cls-hooked": "^0.1.17", + "typeorm-transactional-cls-hooked": "^0.1.19", "uuid": "^8.3.1" }, "devDependencies": { diff --git a/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts b/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts index 0f64b5a..63dae99 100644 --- a/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts +++ b/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts @@ -4,18 +4,12 @@ import UserRepository from '@/domain/user/user.repository'; import UserDTO from '@/domain/auth/types/user-dto'; import UnauthorizedError from '@/common/error/unauthorized'; import { fail } from 'assert'; +import { JwtConfig } from '@/config'; import JwtAuthorizeMiddleware from '.'; -const baseMockJwtConfig = { - tokenSecret: 'token-secret', - tokenExpiresIn: '1d', - refreshThreshold: 14400000, - cookieExpiresIn: 86400000, -}; - describe('Jwt Authorize Middleware', () => { it('토큰이 유효하면 context.state에 user가 등록된다', async () => { - const jwtUtils = new JwtUtils(baseMockJwtConfig); + const jwtUtils = new JwtUtils(JwtConfig); const userRepository = UserRepository.getUserRepository(); const user = await userRepository.save({ socialId: '123456789', @@ -35,7 +29,7 @@ describe('Jwt Authorize Middleware', () => { }); it('등록된 유저가 아니면 UnauthorizedError가 던져진다', async () => { - const jwtUtils = new JwtUtils(baseMockJwtConfig); + const jwtUtils = new JwtUtils(JwtConfig); const userDTO = new UserDTO({ uid: 1, socialId: '123456789', @@ -55,4 +49,92 @@ describe('Jwt Authorize Middleware', () => { expect(err.constructor).toEqual(UnauthorizedError); } }); + + it('토큰이 만료된 경우 UnauthorizedError가 던져진다', async () => { + const jwtUtils = new JwtUtils({ ...JwtConfig, tokenExpiresIn: 0 }); + const userRepository = UserRepository.getUserRepository(); + const user = await userRepository.save({ + socialId: '123456789', + socialType: 'naver', + name: 'TestUser', + }); + const userDTO = new UserDTO(user); + const token = jwtUtils.generateToken(userDTO); + const cookies = { jwt: token }; + const ctx = createMockContext({ cookies }); + + try { + await JwtAuthorizeMiddleware(ctx, () => Promise.resolve()); + fail(); + } catch (err) { + expect(err.constructor).toEqual(UnauthorizedError); + } + + await userRepository.remove(user); + }); + + it('동일한 uid이지만 socialId, socialType이 다르면 UnauthorizedError가 던져진다 (DB 변경에 대한 오류상황 케이스)', async () => { + const jwtUtils = new JwtUtils(JwtConfig); + const userRepository = UserRepository.getUserRepository(); + const user = await userRepository.save({ + socialId: '123456789', + socialType: 'naver', + name: 'TestUser', + }); + + const token = jwtUtils.generateToken({ ...user, socialId: '987654321', socialType: 'google' }); + const cookies = { jwt: token }; + const ctx = createMockContext({ cookies }); + + try { + await JwtAuthorizeMiddleware(ctx, () => Promise.resolve()); + fail(); + } catch (err) { + expect(err.constructor).toEqual(UnauthorizedError); + } + + await userRepository.remove(user); + }); + + it('토큰의 유효시간이 threshold 미만으로 남은 경우 재발급 된다', async () => { + // default threshold = 4hour + const HOUR = 3600; + const jwtUtils = new JwtUtils({ ...JwtConfig, tokenExpiresIn: HOUR }); + const userRepository = UserRepository.getUserRepository(); + const user = await userRepository.save({ + socialId: '123456789', + socialType: 'naver', + name: 'TestUser', + }); + const userDTO = new UserDTO(user); + const token = jwtUtils.generateToken(userDTO); + const cookies = { jwt: token }; + const ctx = createMockContext({ cookies }); + + await JwtAuthorizeMiddleware(ctx, () => Promise.resolve()); + expect(ctx.cookies.set).toBeCalledTimes(1); + + await userRepository.remove(user); + }); + + it('토큰의 유효시간이 threshold 이상으로 남은 경우 재발급 되지 않는다', async () => { + // default threshold = 4hour + const HOUR = 3600; + const jwtUtils = new JwtUtils({ ...JwtConfig, tokenExpiresIn: 5 * HOUR }); + const userRepository = UserRepository.getUserRepository(); + const user = await userRepository.save({ + socialId: '123456789', + socialType: 'naver', + name: 'TestUser', + }); + const userDTO = new UserDTO(user); + const token = jwtUtils.generateToken(userDTO); + const cookies = { jwt: token }; + const ctx = createMockContext({ cookies }); + + await JwtAuthorizeMiddleware(ctx, () => Promise.resolve()); + expect(ctx.cookies.set).toBeCalledTimes(0); + + await userRepository.remove(user); + }); }); diff --git a/server/src/seed/test.seed.ts b/server/src/seed/test.seed.ts index 1718424..745c3fc 100644 --- a/server/src/seed/test.seed.ts +++ b/server/src/seed/test.seed.ts @@ -6,6 +6,25 @@ import TransactionEntity from '@entity/transaction.entity'; import { Connection } from 'typeorm'; import SeedGenerator from './seed-generator'; +const clear = async (connection: Connection): Promise => { + const queryRunner = connection.createQueryRunner(); + const { manager } = queryRunner; + await queryRunner.startTransaction(); + try { + await manager.query('SET FOREIGN_KEY_CHECKS = 0'); + await manager.clear(TransactionEntity); + await manager.clear(PaymentEntity); + await manager.clear(CategoryEntity); + await manager.clear(UserEntity); + await manager.query('SET FOREIGN_KEY_CHECKS = 1'); + await queryRunner.commitTransaction(); + } catch (err) { + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } +}; + const up = async ({ connection, numOfUsers, @@ -23,6 +42,7 @@ const up = async ({ const { manager } = queryRunner; await queryRunner.startTransaction(); try { + await clear(connection); const { users, categories, payments, transactions } = SeedGenerator.generateSeedData({ numOfUsers, numOfTransactionsPerMonth, @@ -41,23 +61,4 @@ const up = async ({ } }; -const clear = async (connection: Connection): Promise => { - const queryRunner = connection.createQueryRunner(); - const { manager } = queryRunner; - await queryRunner.startTransaction(); - try { - await manager.query('SET FOREIGN_KEY_CHECKS = 0'); - await manager.clear(TransactionEntity); - await manager.clear(PaymentEntity); - await manager.clear(CategoryEntity); - await manager.clear(UserEntity); - await manager.query('SET FOREIGN_KEY_CHECKS = 1'); - await queryRunner.commitTransaction(); - } catch (err) { - await queryRunner.rollbackTransaction(); - } finally { - await queryRunner.release(); - } -}; - export default { up, clear }; diff --git a/yarn.lock b/yarn.lock index dfd94ea..1d38d1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16659,10 +16659,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeorm-transactional-cls-hooked@^0.1.17: - version "0.1.17" - resolved "https://registry.yarnpkg.com/typeorm-transactional-cls-hooked/-/typeorm-transactional-cls-hooked-0.1.17.tgz#7d7743836f487c3ba6fc777780e6c0e9a9101326" - integrity sha512-iuDh0/vsQBVvQmjZ5KKZHhlbsgQLD8uUKZypzaMFOh9/N99j9fxgz4NCLBCDl4K30vCi7SROpiuEXAPtMxy+ew== +typeorm-transactional-cls-hooked@^0.1.19: + version "0.1.19" + resolved "https://registry.yarnpkg.com/typeorm-transactional-cls-hooked/-/typeorm-transactional-cls-hooked-0.1.19.tgz#6b055ec944ab2b588d3dec4b7aa3ce7c0ef7e3c9" + integrity sha512-UZIRwOOVwzGfh81j8I0heyqP1XxXfUKGkrJkR4+tGtIKF/aki/ZHD1n/DNVrZtNYoz4kXcwEYqNH/FBpPsXbKw== dependencies: "@types/cls-hooked" "^4.2.1" cls-hooked "^4.2.2" From 92ef66a181b98cd0190fc669402a4abec7268758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 00:44:00 +0900 Subject: [PATCH 59/86] =?UTF-8?q?fix=20:=20login=20=EC=8B=9C=20login=20pag?= =?UTF-8?q?e=20=EC=9E=AC=EB=9E=9C=EB=8D=94=EB=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.tsx | 6 ++-- .../common/layouts/Header/index.tsx | 8 ++--- client/src/views/LoginPage/index.tsx | 32 ++++++++++--------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index bb3540f..321a0ad 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -17,15 +17,15 @@ function App(): JSX.Element { - - + - + + ); diff --git a/client/src/components/common/layouts/Header/index.tsx b/client/src/components/common/layouts/Header/index.tsx index 4366fbd..c457b8d 100644 --- a/client/src/components/common/layouts/Header/index.tsx +++ b/client/src/components/common/layouts/Header/index.tsx @@ -21,23 +21,23 @@ function Header(): JSX.Element { )); const dispatch = useDispatch(); const history = useHistory(); - const onClick = async () => { + const doLogout = async () => { await authorizationAPI.logout(); - history.push('/'); + history.push('/login'); dispatch(logout({ createAt: null })); }; return ( - + {dropdonwList} - + 로그아웃 diff --git a/client/src/views/LoginPage/index.tsx b/client/src/views/LoginPage/index.tsx index a529f3f..7e3cb42 100644 --- a/client/src/views/LoginPage/index.tsx +++ b/client/src/views/LoginPage/index.tsx @@ -1,33 +1,35 @@ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import authorizationAPI from '@/libs/api/Authorization'; import Logo from '@/components/common/Logo'; import LoginButton from '@/container/LoginButton'; import { useDispatch } from 'react-redux'; import { login } from '@/modules/authorization/actions'; -import { Box, LogoBox } from './styles'; +import * as S from './styles'; const LoginPage = (props: RouteComponentProps): JSX.Element => { const dispatch = useDispatch(); + + const checkLogin = useCallback(async () => { + try { + const createAt = await authorizationAPI.isLogin(); + dispatch(login({ createAt })); + props.history.push('/'); + } catch (e) { + console.log(e); + } + }, []); + useEffect(() => { - const checkLogin = async () => { - try { - const createAt = await authorizationAPI.isLogin(); - dispatch(login({ createAt })); - props.history.push('/dashboard'); - } catch (error) { - console.log(error); - } - }; checkLogin(); }, [props]); return ( - - + + - + - + ); }; From 6c87db80b43ccfccffeb0af2a00014907addd299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 00:45:12 +0900 Subject: [PATCH 60/86] =?UTF-8?q?fix=20:=20url=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=8B=9C=20component=20=EB=9E=9C=EB=8D=94=EB=A7=81=20=ED=9B=84?= =?UTF-8?q?=20redirect=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main layout 에 loading 상태를 추가하여 login 상태를 판별한 후 component 랜더링으로 변경 --- .../common/LoadingSpinner/styles.ts | 6 +- .../common/layouts/MainLayout/index.tsx | 66 ++++++++++++++----- client/src/container/Authorization/index.tsx | 30 --------- 3 files changed, 52 insertions(+), 50 deletions(-) delete mode 100644 client/src/container/Authorization/index.tsx diff --git a/client/src/components/common/LoadingSpinner/styles.ts b/client/src/components/common/LoadingSpinner/styles.ts index f863e0f..70761d3 100644 --- a/client/src/components/common/LoadingSpinner/styles.ts +++ b/client/src/components/common/LoadingSpinner/styles.ts @@ -1,9 +1,11 @@ import styled from 'styled-components'; const StyledLoadingSpinner = styled.div` + position: relative; display: flex; - height: 100%; - width: 100%; + min-height: 100vh; + padding-top: calc(51px + 1rem); + padding-bottom: 150px; justify-content: center; align-items: center; .img { diff --git a/client/src/components/common/layouts/MainLayout/index.tsx b/client/src/components/common/layouts/MainLayout/index.tsx index 40a69b2..e8b78e6 100644 --- a/client/src/components/common/layouts/MainLayout/index.tsx +++ b/client/src/components/common/layouts/MainLayout/index.tsx @@ -1,29 +1,59 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import Header from '@/components/common/layouts/Header'; -import Authorization from '@container/Authorization'; import GitHubSVG from '@/assets/svg/GitHub.svg'; +import { useHistory } from 'react-router-dom'; +import { useSelector, useDispatch } from 'react-redux'; +import { RootState } from '@/modules'; +import authorizationAPI from '@/libs/api/Authorization'; +import { login } from '@/modules/authorization/actions'; import * as S from './styles'; import { MainLayoutPropsType } from './types'; +import LoadingSpinner from '../../LoadingSpinner'; const MainLayout = ({ children }: MainLayoutPropsType): JSX.Element => { + const [loading, setLoading] = useState(true); + const history = useHistory(); + const dispatch = useDispatch(); + const createAt = useSelector((state: RootState) => state.authorization.createAt); + + const checkLogin = useCallback(async () => { + if (!createAt) { + try { + const inputCreateAt = await authorizationAPI.isLogin(); + dispatch(login({ createAt: inputCreateAt })); + setLoading(false); + } catch (error) { + history.push('/login'); + } + } + setLoading(false); + }, []); + + useEffect(() => { + checkLogin(); + }, []); + return ( <> - - -
- {children} - - © 2020 Boostcamp, AccountBook-F{' '} - - github-logo - - - + {loading ? ( + + ) : ( + +
+ {children} + + © 2020 Boostcamp, AccountBook-F{' '} + + github-logo + + + + )} ); }; diff --git a/client/src/container/Authorization/index.tsx b/client/src/container/Authorization/index.tsx deleted file mode 100644 index 8a3d0e7..0000000 --- a/client/src/container/Authorization/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React, { useCallback, useEffect } from 'react'; -import { useHistory } from 'react-router-dom'; -import { useSelector, useDispatch } from 'react-redux'; -import { RootState } from '@/modules'; -import authorizationAPI from '@/libs/api/Authorization'; -import { login } from '@/modules/authorization/actions'; - -const Authorization = (): JSX.Element => { - const history = useHistory(); - const dispatch = useDispatch(); - const createAt = useSelector((state: RootState) => state.authorization.createAt); - const checkLogin = useCallback(async () => { - if (!createAt) { - try { - const inputCreateAt = await authorizationAPI.isLogin(); - dispatch(login({ createAt: inputCreateAt })); - } catch (error) { - console.log(error); - history.push('/'); - } - } - }, []); - - useEffect(() => { - checkLogin(); - }, []); - return <>; -}; - -export default Authorization; From fe9062998141be02e8660f19b1673b15763b4f86 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 01:09:11 +0900 Subject: [PATCH 61/86] =?UTF-8?q?refactor:=20UserDTO=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UserDTO의 디렉토리가 domain/auth -> domain/user로 변경되었습니다. --- server/src/domain/aggregate/aggregate.service.ts | 2 +- server/src/domain/{auth => user}/types/user-dto.ts | 0 server/src/domain/user/user.service.ts | 2 +- server/src/lib/jwt-utils/index.ts | 2 +- server/src/middleware/jwt-authorize/index.ts | 2 +- .../middleware/jwt-authorize/jwt-authorize-middleware.test.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename server/src/domain/{auth => user}/types/user-dto.ts (100%) diff --git a/server/src/domain/aggregate/aggregate.service.ts b/server/src/domain/aggregate/aggregate.service.ts index 6b3ec8e..b713fe8 100644 --- a/server/src/domain/aggregate/aggregate.service.ts +++ b/server/src/domain/aggregate/aggregate.service.ts @@ -1,6 +1,6 @@ import TransactionRepository from '@/domain/transaction/transaction.repository'; import DateUtils from '@/lib/date-utils'; -import UserDTO from '@/domain/auth/types/user-dto'; +import UserDTO from '@/domain/user/types/user-dto'; import { AggregateData, AggregateValue, AggregateResponse, MaxCategory } from './types'; export default class AggregateService { diff --git a/server/src/domain/auth/types/user-dto.ts b/server/src/domain/user/types/user-dto.ts similarity index 100% rename from server/src/domain/auth/types/user-dto.ts rename to server/src/domain/user/types/user-dto.ts diff --git a/server/src/domain/user/user.service.ts b/server/src/domain/user/user.service.ts index f2b3a8a..82ca65d 100644 --- a/server/src/domain/user/user.service.ts +++ b/server/src/domain/user/user.service.ts @@ -1,7 +1,7 @@ import SocialUserDTO from '@/lib/oauth-client/userinfo/default'; import UserEntity from '@/entity/user.entity'; import { Repository } from 'typeorm'; -import UserDTO from '../auth/types/user-dto'; +import UserDTO from './types/user-dto'; export default class UserService { private userRepository: Repository; diff --git a/server/src/lib/jwt-utils/index.ts b/server/src/lib/jwt-utils/index.ts index 4c935b2..6af59ed 100644 --- a/server/src/lib/jwt-utils/index.ts +++ b/server/src/lib/jwt-utils/index.ts @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken'; import { Context } from 'koa'; import decodedJWT from '@/domain/auth/types/decoded-jwt'; -import UserDTO from '@/domain/auth/types/user-dto'; +import UserDTO from '@/domain/user/types/user-dto'; export default class JwtUtils { private config; diff --git a/server/src/middleware/jwt-authorize/index.ts b/server/src/middleware/jwt-authorize/index.ts index ad1dadb..8dd3ccc 100644 --- a/server/src/middleware/jwt-authorize/index.ts +++ b/server/src/middleware/jwt-authorize/index.ts @@ -2,7 +2,7 @@ import { Context, Next } from 'koa'; import { JwtConfig } from '@/config'; import JwtUtils from '@/lib/jwt-utils'; import UserRepository from '@/domain/user/user.repository'; -import UserDTO from '@/domain/auth/types/user-dto'; +import UserDTO from '@/domain/user/types/user-dto'; import UnauthorizedError from '@/common/error/unauthorized'; const jwtAuthorize = async (ctx: Context, next: Next): Promise => { diff --git a/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts b/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts index 63dae99..5ba4b12 100644 --- a/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts +++ b/server/src/middleware/jwt-authorize/jwt-authorize-middleware.test.ts @@ -1,7 +1,7 @@ import { createMockContext } from '@shopify/jest-koa-mocks'; import JwtUtils from '@/lib/jwt-utils'; import UserRepository from '@/domain/user/user.repository'; -import UserDTO from '@/domain/auth/types/user-dto'; +import UserDTO from '@/domain/user/types/user-dto'; import UnauthorizedError from '@/common/error/unauthorized'; import { fail } from 'assert'; import { JwtConfig } from '@/config'; From 92eacab1bcb9bba0902da57db4a596a149f2f20e Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 01:23:36 +0900 Subject: [PATCH 62/86] =?UTF-8?q?fix:=20test=20ggiven=20data=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/domain/aggregate/agreegate.service.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/domain/aggregate/agreegate.service.test.ts b/server/src/domain/aggregate/agreegate.service.test.ts index b162bf3..8b15224 100644 --- a/server/src/domain/aggregate/agreegate.service.test.ts +++ b/server/src/domain/aggregate/agreegate.service.test.ts @@ -29,7 +29,8 @@ describe('TransactionService Tests', () => { }); const user = { uid: 1, - name: 'testUser', + name: 'user1', + socialId: 'user1-123456789', socialType: 'google', updateAt: new Date('2020-05-12'), createAt: new Date('2020-05-12'), @@ -61,7 +62,8 @@ describe('TransactionService Tests', () => { const registeredAt = new Date('2020-03-15'); const user = { uid: 1, - name: 'testUser', + name: 'user1', + socialId: 'user1-123456789', socialType: 'google', updateAt: registeredAt, createAt: registeredAt, From fa360e49043052e50f44341b8d2f3dc0f3772ed7 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 01:30:07 +0900 Subject: [PATCH 63/86] =?UTF-8?q?docs:=20README.md=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EB=82=B4=EC=9A=A9=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 46 +++++++++++++++++++++++++++------------------- server/.dummy.env | 2 +- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c487acd..c588dda 100644 --- a/README.md +++ b/README.md @@ -130,25 +130,33 @@ cp server/.dummy.env server/.env ``` **환경변수** -``` -TYPEORM_CONNECTION : 연동할 DB 타입 ex) mysql -TYPEORM_HOST : 연결할 DB Host -TYPEORM_PORT : 연결할 DB Port -TYPEORM_USERNAME : DB연결에 사용할 계정 -TYPEORM_PASSWORD : 계정 패스워드 -TYPEORM_DATABASE : 연결할 Database -TYPEORM_SYNCHRONIZE : 엔티티와 테이블 sync 설정 ex) true -TYPEORM_LOGGING : Logging 설정 ex) true - -CLIENT_URI : 프론트엔드 URI ex) http://localhost:3000 - -JWT_SECRET : 토큰 생성에 사용할 Secret -JWT_TOKEN_EXPIRES_IN : 토큰의 유효기간 ex) 1d -JWT_COOKIE_EXPIRES_IN : 인증 쿠키의 유효기간 (ms) ex) 86400000 - -XXX_CLIENT_ID : 개발자 센터에서 발급받은 client id -XXX_CLIENT_SECRET : 개발자 센터에서 발급받은 client secret -XXX_CALLBACK_URI : 개발자 센터에서 설정한 callback uri ex) http://localhost:4000/api/auth/callback/xxx (xxx = kakao | naver | google) +```bash +TYPEORM_CONNECTION= #DB 타입(mysql) +TYPEORM_HOST= #DB HOST +TYPEORM_PORT= #DB PORT +TYPEORM_USERNAME= #DB 계정 +TYPEORM_PASSWORD= #DB PASSWORD +TYPEORM_DATABASE= #DB 이름 +TYPEORM_SYNCHRONIZE= #엔티티와 테이블 sync 여부 +TYPEORM_LOGGING= #쿼리 실행 로깅 여부 + +CLIENT_URI= #클라이언트 URI + +JWT_SECRET= #토큰 Secret +JWT_TOKEN_EXPIRES_IN= #토큰 유효시간 (seconds) +JWT_TOKEN_REFERSH_THRESHOLD= #토큰이 재발급되는 남은시간 (seconds) + +KAKAO_CLIENT_ID= #발급받은 CLIENT ID +KAKAO_CLIENT_SECRET= #발급받은 CLIENT SECRET +KAKAO_CALLBACK_URI= #개발자센터에 설정한 콜백 URI + +NAVER_CLIENT_ID= +NAVER_CLIENT_SECRET= +NAVER_CALLBACK_URI= + +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +GOOGLE_CALLBACK_URI= ``` ### 프로젝트 실행 diff --git a/server/.dummy.env b/server/.dummy.env index e876940..059511c 100644 --- a/server/.dummy.env +++ b/server/.dummy.env @@ -11,7 +11,7 @@ CLIENT_URI= #클라이언트 URI JWT_SECRET= #토큰 Secret JWT_TOKEN_EXPIRES_IN= #토큰 유효시간 (seconds) -JWT_TOKEN_REFERSH_THRESHOLD= #토큰 재발급 기준 남은 유효시간 (seconds) +JWT_TOKEN_REFERSH_THRESHOLD= #토큰이 재발급되는 남은시간 (seconds) KAKAO_CLIENT_ID= #발급받은 CLIENT ID KAKAO_CLIENT_SECRET= #발급받은 CLIENT SECRET From a73d14b4e5587289441a03b85379b6909a7ed9ce Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 01:33:42 +0900 Subject: [PATCH 64/86] =?UTF-8?q?docs:=20README.md=20Logo=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c588dda..4da0fb2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Project16-F-Account-Book ([Link](http://tess.kro.kr)) +
+ +![Logo](https://user-images.githubusercontent.com/17294694/102017772-3eab1200-3dac-11eb-8e95-8435e44eb16a.png) -![Logo](https://user-images.githubusercontent.com/17294694/101917945-1c7a8e00-3c0c-11eb-828d-03e127a4d883.png)
From 7d5c86584eeac2e0c62065e3dbe7c9c67258025c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 01:58:38 +0900 Subject: [PATCH 65/86] =?UTF-8?q?style=20:=20global=20scrollbar=20?= =?UTF-8?q?=EC=97=86=EC=95=A0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/GlobalStyled.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/GlobalStyled.ts b/client/src/GlobalStyled.ts index 4ada067..feb632a 100644 --- a/client/src/GlobalStyled.ts +++ b/client/src/GlobalStyled.ts @@ -10,6 +10,11 @@ const GlobalStyled = createGlobalStyle` color: #363B40; height: 100%; + ::-webkit-scrollbar { + width: 0px; + background: transparent; + } + @media (max-width: 575.98px) { font-size: 14px; } From f946251a99b2e93db3a73b824792f338cd59ab67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 02:27:44 +0900 Subject: [PATCH 66/86] =?UTF-8?q?feat=20:=20=EA=B3=A0=EC=A0=95=EC=A7=80?= =?UTF-8?q?=EC=B6=9C=20=EC=84=A0=ED=98=95=20=EA=B7=B8=EB=9E=98=ED=94=84=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20loading=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/container/FixedExpenditure/index.tsx | 51 ++++++++++--------- .../container/LineGraphContainer/index.tsx | 5 +- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/client/src/container/FixedExpenditure/index.tsx b/client/src/container/FixedExpenditure/index.tsx index 87d72a0..068a197 100644 --- a/client/src/container/FixedExpenditure/index.tsx +++ b/client/src/container/FixedExpenditure/index.tsx @@ -6,6 +6,7 @@ import { RootState } from '@/modules'; import { Link } from 'react-router-dom'; import { GoCalendar } from 'react-icons/go'; import numberUtils from '@libs/numberUtils'; +import LoadingSpinner from '@/components/common/LoadingSpinner'; import * as S from './styles'; const FixedExpenditure = (): JSX.Element => { @@ -43,29 +44,33 @@ const FixedExpenditure = (): JSX.Element => { }, [dispatch, datePicker, transaction]); return ( <> - - - -

예정된 고정지출이

-

{fixedExpenditure.data.estimated.length}개 있어요

-
- 자세히 보기 -
- - - - 완료된 고정 지출 - {getAmount('paid')}원 - - - - - - 예정된 고정 지출 - {getAmount('estimated')}원 - - -
+ {fixedExpenditure.loading ? ( + + ) : ( + + + +

예정된 고정지출이

+

{fixedExpenditure.data.estimated.length}개 있어요

+
+ 자세히 보기 +
+ + + + 완료된 고정 지출 + {getAmount('paid')}원 + + + + + + 예정된 고정 지출 + {getAmount('estimated')}원 + + +
+ )} ); }; diff --git a/client/src/container/LineGraphContainer/index.tsx b/client/src/container/LineGraphContainer/index.tsx index a7c4c44..395a0b6 100644 --- a/client/src/container/LineGraphContainer/index.tsx +++ b/client/src/container/LineGraphContainer/index.tsx @@ -1,3 +1,4 @@ +import LoadingSpinner from '@/components/common/LoadingSpinner'; import LineGraph from '@/components/transaction/LineGraph'; import { RootState } from '@/modules'; import React from 'react'; @@ -9,7 +10,9 @@ const LineGraphContainer = () => { const { transaction } = useSelector((state: RootState) => state); return ( <> - {transaction && ( + {transaction.loading ? ( + + ) : ( <> From 65ec624c4e32102c6b44baabc285f77a91456c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 03:13:24 +0900 Subject: [PATCH 67/86] =?UTF-8?q?style=20:=20header=20logo=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/common/layouts/Header/index.tsx | 2 +- client/src/views/LoginPage/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/common/layouts/Header/index.tsx b/client/src/components/common/layouts/Header/index.tsx index de9fde0..2fcc05a 100644 --- a/client/src/components/common/layouts/Header/index.tsx +++ b/client/src/components/common/layouts/Header/index.tsx @@ -31,7 +31,7 @@ const Header = (): JSX.Element => { - + diff --git a/client/src/views/LoginPage/index.tsx b/client/src/views/LoginPage/index.tsx index 7e3cb42..47afac7 100644 --- a/client/src/views/LoginPage/index.tsx +++ b/client/src/views/LoginPage/index.tsx @@ -26,7 +26,7 @@ const LoginPage = (props: RouteComponentProps): JSX.Element => { return ( - + From 5044328562c9a5e16fd51e6823f3752027d5d5a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 10:52:56 +0900 Subject: [PATCH 68/86] =?UTF-8?q?refactor=20:=20layout=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/views/AggregateCategoryPage/index.tsx | 2 +- client/src/views/AggregatePeriodPage/index.tsx | 2 +- client/src/views/CalendarPage/index.tsx | 2 +- client/src/views/CategoryManagePage/index.tsx | 2 +- client/src/views/DashBoardPage/index.tsx | 2 +- client/src/views/DetailedFixedExpenditurePage/index.tsx | 2 +- client/src/views/PaymentManagePage/index.tsx | 2 +- .../common => views}/layouts/Header/index.stories.tsx | 0 .../src/{components/common => views}/layouts/Header/index.tsx | 0 .../src/{components/common => views}/layouts/Header/styles.ts | 0 .../{components/common => views}/layouts/MainLayout/index.tsx | 4 ++-- .../{components/common => views}/layouts/MainLayout/styles.ts | 0 .../{components/common => views}/layouts/MainLayout/types.ts | 0 13 files changed, 9 insertions(+), 9 deletions(-) rename client/src/{components/common => views}/layouts/Header/index.stories.tsx (100%) rename client/src/{components/common => views}/layouts/Header/index.tsx (100%) rename client/src/{components/common => views}/layouts/Header/styles.ts (100%) rename client/src/{components/common => views}/layouts/MainLayout/index.tsx (93%) rename client/src/{components/common => views}/layouts/MainLayout/styles.ts (100%) rename client/src/{components/common => views}/layouts/MainLayout/types.ts (100%) diff --git a/client/src/views/AggregateCategoryPage/index.tsx b/client/src/views/AggregateCategoryPage/index.tsx index b4c2acd..2d11ef4 100644 --- a/client/src/views/AggregateCategoryPage/index.tsx +++ b/client/src/views/AggregateCategoryPage/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import MainLayout from '@/components/common/layouts/MainLayout'; +import MainLayout from '@/views/layouts/MainLayout'; import AggregateCategory from '@/container/AggregateCategory'; import SelectMonth from '@/container/SelectMonth'; diff --git a/client/src/views/AggregatePeriodPage/index.tsx b/client/src/views/AggregatePeriodPage/index.tsx index 9cc702b..8796183 100644 --- a/client/src/views/AggregatePeriodPage/index.tsx +++ b/client/src/views/AggregatePeriodPage/index.tsx @@ -1,4 +1,4 @@ -import MainLayout from '@/components/common/layouts/MainLayout'; +import MainLayout from '@/views/layouts/MainLayout'; import LineGraphContainer from '@/container/LineGraphContainer'; import React from 'react'; diff --git a/client/src/views/CalendarPage/index.tsx b/client/src/views/CalendarPage/index.tsx index 4d004e2..d63ecf9 100644 --- a/client/src/views/CalendarPage/index.tsx +++ b/client/src/views/CalendarPage/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react'; -import MainLayout from '@/components/common/layouts/MainLayout'; +import MainLayout from '@/views/layouts/MainLayout'; import Calendar from '@container/Calendar'; import TransactionUpdateModal from '@/container/TransactionUpdateModal'; import TransactionModal from '@container/TransactionModal'; diff --git a/client/src/views/CategoryManagePage/index.tsx b/client/src/views/CategoryManagePage/index.tsx index e212dec..8b28407 100644 --- a/client/src/views/CategoryManagePage/index.tsx +++ b/client/src/views/CategoryManagePage/index.tsx @@ -1,4 +1,4 @@ -import MainLayout from '@/components/common/layouts/MainLayout'; +import MainLayout from '@/views/layouts/MainLayout'; import CategoryManageMain from '@/container/CategoryManageMain'; import React from 'react'; diff --git a/client/src/views/DashBoardPage/index.tsx b/client/src/views/DashBoardPage/index.tsx index d9e39bf..1aa1efc 100644 --- a/client/src/views/DashBoardPage/index.tsx +++ b/client/src/views/DashBoardPage/index.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState } from 'react'; import TransactionModal from '@container/TransactionModal'; import ModalToggleButton from '@components/common/buttons/ModalToggleButton'; -import MainLayout from '@/components/common/layouts/MainLayout'; +import MainLayout from '@/views/layouts/MainLayout'; import DashboardContainer from '@/container/DashboardContainer'; const DashBoardPage = (): JSX.Element => { diff --git a/client/src/views/DetailedFixedExpenditurePage/index.tsx b/client/src/views/DetailedFixedExpenditurePage/index.tsx index a257d65..0146055 100644 --- a/client/src/views/DetailedFixedExpenditurePage/index.tsx +++ b/client/src/views/DetailedFixedExpenditurePage/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import MainLayout from '@/components/common/layouts/MainLayout'; +import MainLayout from '@/views/layouts/MainLayout'; import DetailedFixedExpenditure from '@/container/DetailedFixedExpenditure'; const DetailedFixedExpenditurePage = (): JSX.Element => { diff --git a/client/src/views/PaymentManagePage/index.tsx b/client/src/views/PaymentManagePage/index.tsx index 5004205..d472535 100644 --- a/client/src/views/PaymentManagePage/index.tsx +++ b/client/src/views/PaymentManagePage/index.tsx @@ -1,4 +1,4 @@ -import MainLayout from '@/components/common/layouts/MainLayout'; +import MainLayout from '@/views/layouts/MainLayout'; import PaymentManageContainer from '@/container/PaymentManageMain'; import React from 'react'; diff --git a/client/src/components/common/layouts/Header/index.stories.tsx b/client/src/views/layouts/Header/index.stories.tsx similarity index 100% rename from client/src/components/common/layouts/Header/index.stories.tsx rename to client/src/views/layouts/Header/index.stories.tsx diff --git a/client/src/components/common/layouts/Header/index.tsx b/client/src/views/layouts/Header/index.tsx similarity index 100% rename from client/src/components/common/layouts/Header/index.tsx rename to client/src/views/layouts/Header/index.tsx diff --git a/client/src/components/common/layouts/Header/styles.ts b/client/src/views/layouts/Header/styles.ts similarity index 100% rename from client/src/components/common/layouts/Header/styles.ts rename to client/src/views/layouts/Header/styles.ts diff --git a/client/src/components/common/layouts/MainLayout/index.tsx b/client/src/views/layouts/MainLayout/index.tsx similarity index 93% rename from client/src/components/common/layouts/MainLayout/index.tsx rename to client/src/views/layouts/MainLayout/index.tsx index e8b78e6..8c1a492 100644 --- a/client/src/components/common/layouts/MainLayout/index.tsx +++ b/client/src/views/layouts/MainLayout/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; -import Header from '@/components/common/layouts/Header'; +import Header from '@/views/layouts/Header'; import GitHubSVG from '@/assets/svg/GitHub.svg'; import { useHistory } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; @@ -8,7 +8,7 @@ import authorizationAPI from '@/libs/api/Authorization'; import { login } from '@/modules/authorization/actions'; import * as S from './styles'; import { MainLayoutPropsType } from './types'; -import LoadingSpinner from '../../LoadingSpinner'; +import LoadingSpinner from '../../../components/common/LoadingSpinner'; const MainLayout = ({ children }: MainLayoutPropsType): JSX.Element => { const [loading, setLoading] = useState(true); diff --git a/client/src/components/common/layouts/MainLayout/styles.ts b/client/src/views/layouts/MainLayout/styles.ts similarity index 100% rename from client/src/components/common/layouts/MainLayout/styles.ts rename to client/src/views/layouts/MainLayout/styles.ts diff --git a/client/src/components/common/layouts/MainLayout/types.ts b/client/src/views/layouts/MainLayout/types.ts similarity index 100% rename from client/src/components/common/layouts/MainLayout/types.ts rename to client/src/views/layouts/MainLayout/types.ts From bcb9023611495ad74e3cac8ce08402552f067517 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Sun, 13 Dec 2020 20:28:08 +0900 Subject: [PATCH 69/86] =?UTF-8?q?fix:=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80?= =?UTF-8?q?=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20,=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/container/TransactionModal/index.tsx | 1 - client/src/container/TransactionUpdateModal/index.tsx | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/client/src/container/TransactionModal/index.tsx b/client/src/container/TransactionModal/index.tsx index f5acdbe..f7471ca 100644 --- a/client/src/container/TransactionModal/index.tsx +++ b/client/src/container/TransactionModal/index.tsx @@ -17,7 +17,6 @@ import DateUtils from '@/libs/dateUtils'; import ModalInput from '@/components/transaction/ModalInput'; import ModalHeader from '@/components/transaction/ModalHeader'; import checkValidation from '@/libs/checkValidation'; -import { pid } from 'process'; import * as S from './styles'; import { TransactionModalProps } from './types'; diff --git a/client/src/container/TransactionUpdateModal/index.tsx b/client/src/container/TransactionUpdateModal/index.tsx index b6ed855..f20b44f 100644 --- a/client/src/container/TransactionUpdateModal/index.tsx +++ b/client/src/container/TransactionUpdateModal/index.tsx @@ -16,11 +16,11 @@ import TransactionRequestDTO from '@/commons/dto/transaction-request'; import { deleteTransactionThunk, updateTransactionThunk } from '@/modules/transaction'; import * as S from './styles'; -const MODALLSITARR = ['tradeAt', 'description', 'amount', 'pid', 'cid', 'isIncome']; +const MODAL_LSIT_ARR = ['tradeAt', 'description', 'amount', 'pid', 'cid', 'isIncome']; const TransactionUpdateModal = (): JSX.Element => { const [isIncome, setIsIncome] = useState(false); - const [validation, setValidation] = useState(new Set(MODALLSITARR)); + const [validation, setValidation] = useState(new Set(MODAL_LSIT_ARR)); const { payment, category } = useSelector((state: RootState) => state); const { toggle, data } = useSelector((state: RootState) => state.updateModal); const categoryList = category.data.map((c) => new CategoryDTO(c)); @@ -28,7 +28,7 @@ const TransactionUpdateModal = (): JSX.Element => { const dispatch = useDispatch(); const toggleModal = useCallback(() => { dispatch(toggleModalOff()); - setValidation(new Set(MODALLSITARR)); + setValidation(new Set(MODAL_LSIT_ARR)); }, [dispatch]); const onChangeReducer = (state: UpdateTransactionRequest, action: UpdateTransactionRequest) => { From 2378a46c9c0a5bf66c28bc0c374afbd3968d5e4b Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 11:37:06 +0900 Subject: [PATCH 70/86] =?UTF-8?q?refactor:=20=EB=8B=AC=EB=A0=A5=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 달력생성 로직에서 any 타입을 제거하고 타입을 명시적으로변경하여 발생할 수 있는 오류를 수정함. 달력의 빈칸을 클릭했을때 클릭 이벤트가 발생하지 않도록 처리. --- .../components/calendar/TableCell/index.tsx | 18 +++++++----- client/src/libs/calendarUtils.test.ts | 29 +++++++++++++++++++ client/src/libs/calendarUtils.ts | 6 ++-- 3 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 client/src/libs/calendarUtils.test.ts diff --git a/client/src/components/calendar/TableCell/index.tsx b/client/src/components/calendar/TableCell/index.tsx index bb1b8d4..cc323ea 100644 --- a/client/src/components/calendar/TableCell/index.tsx +++ b/client/src/components/calendar/TableCell/index.tsx @@ -14,21 +14,25 @@ const getRem = (n: number): string => { return rem; }; -function TableCell({ day, totalInOut }: TableCellTypes): JSX.Element { +const TableCell = ({ day, totalInOut }: TableCellTypes): JSX.Element => { + const dayToNumber = Number(day); const { calendarDaySelector } = useSelector((state: RootState) => state); const isBold = (): 'isBold' | '' => { - if (Number(calendarDaySelector.day) === Number(day) && day !== ' ') return 'isBold'; + if (calendarDaySelector.day === dayToNumber) return 'isBold'; return ''; }; const isCursor = (): 'isCursor' | '' => { - if (Number(day)) return 'isCursor'; + if (dayToNumber) return 'isCursor'; return ''; }; const dispatch = useDispatch(); - const onClick = (e: any) => { - const value = e.currentTarget.innerText.split('\n')[0]; - if (value === calendarDaySelector.day || Number(day[0]) === 0) { + const onClick = (e: React.MouseEvent) => { + const value = Number(e.currentTarget.innerText.split('\n')[0]); + if (!value) { + return; + } + if (value === calendarDaySelector.day) { dispatch(changeDay({ day: 0 })); return; } @@ -70,6 +74,6 @@ function TableCell({ day, totalInOut }: TableCellTypes): JSX.Element { ); -} +}; export default TableCell; diff --git a/client/src/libs/calendarUtils.test.ts b/client/src/libs/calendarUtils.test.ts new file mode 100644 index 0000000..d635a97 --- /dev/null +++ b/client/src/libs/calendarUtils.test.ts @@ -0,0 +1,29 @@ +import getDayMatrix from './calendarUtils'; + +it('2020년 11월 달력 매트릭스 생성 테스트', () => { + const matrix = getDayMatrix(2020, 11); + const flattedMatrix = matrix.flat(); + for (let i = 0; i < 30; i += 1) { + expect(Number(flattedMatrix[i])).toBe(i + 1); + } + + for (let i = 2; i < 7; i += 1) { + expect(matrix[4][i]).toBe(' '); + } +}); + +it('2020년 12월 달력 매트릭스 생성 테스트', () => { + const matrix = getDayMatrix(2020, 12); + const flattedMatrix = matrix.flat(); + + expect(matrix.every((row) => row.length === 7)).toBe(true); + expect(matrix[0][0]).toBe(' '); + expect(matrix[0][1]).toBe(' '); + expect(matrix[4][5]).toBe(' '); + expect(matrix[4][6]).toBe(' '); + + const startIdx = 2; + for (let i = 0; i < 31; i += 1) { + expect(Number(flattedMatrix[startIdx + i])).toBe(i + 1); + } +}); diff --git a/client/src/libs/calendarUtils.ts b/client/src/libs/calendarUtils.ts index ec5456c..de46f05 100644 --- a/client/src/libs/calendarUtils.ts +++ b/client/src/libs/calendarUtils.ts @@ -14,12 +14,12 @@ const getDayMatrix = (year: number, month: number): string[][] => { const startDay = date.startOf('month').day(); const remain = (startDay + endOfMonth) % 7; - const dataArr: any = [ + const dataArr = [ ...' '.repeat(startDay).split(''), ...Array.from({ length: endOfMonth - startOfMonth + 1 }, (v, i) => String(i + 1)), - 7 - remain === 7 ? [] : ' '.repeat(7 - remain).split(''), + ...(7 - remain === 7 ? [] : ' '.repeat(7 - remain).split('')), ]; - const resultMatrix: any = (): string[][] => { + const resultMatrix = (): string[][] => { const result = []; let i = 0; while (i < dataArr.length / weekSlice) { From 568cc3f7ebc5af38ddf9b8f5eecfe535c42cc9ee Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 16:28:59 +0900 Subject: [PATCH 71/86] =?UTF-8?q?fix:=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/libs/checkValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/libs/checkValidation.ts b/client/src/libs/checkValidation.ts index 97d4008..afe102f 100644 --- a/client/src/libs/checkValidation.ts +++ b/client/src/libs/checkValidation.ts @@ -1,7 +1,7 @@ const checkValidation = (name: string, target: string): boolean => { if (name === 'amount' && (Number.isNaN(Number(target)) === true || target === '0')) return false; if (name === 'tradeAt' && target === '') return false; - if (target === '0' || target === '0') return false; + if (target === '0') return false; return true; }; From bde1c320b772649005a47efe4c358b2146dc74b0 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 16:30:55 +0900 Subject: [PATCH 72/86] =?UTF-8?q?feat:CustomButton=EC=97=90=20disabled=20?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/common/buttons/CustomButton/index.tsx | 4 ++-- client/src/components/common/buttons/CustomButton/types.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/components/common/buttons/CustomButton/index.tsx b/client/src/components/common/buttons/CustomButton/index.tsx index 92313b3..ede2221 100644 --- a/client/src/components/common/buttons/CustomButton/index.tsx +++ b/client/src/components/common/buttons/CustomButton/index.tsx @@ -3,10 +3,10 @@ import * as S from './styles'; import { CustomButtonProps } from './types'; const CustomButton = (props: CustomButtonProps): JSX.Element => { - const { image, children, color, size, onClickEvent } = props; + const { image, children, color, size, onClickEvent, validation } = props; return ( - + {image ? ( <> diff --git a/client/src/components/common/buttons/CustomButton/types.tsx b/client/src/components/common/buttons/CustomButton/types.tsx index d8c290a..f0f581d 100644 --- a/client/src/components/common/buttons/CustomButton/types.tsx +++ b/client/src/components/common/buttons/CustomButton/types.tsx @@ -4,6 +4,7 @@ export type CustomButtonProps = { children: React.ReactNode; size?: 'sm' | 'md'; onClickEvent?: any; + validation: boolean; }; export type ButtonProps = { From 47bf760628dd573622a9dc99c5bcfc83491fd0d2 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 16:35:40 +0900 Subject: [PATCH 73/86] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20isValid=20manageIteminput=EC=97=90=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/common/buttons/CustomButton/index.tsx | 4 ++-- client/src/components/common/buttons/CustomButton/types.tsx | 2 +- client/src/components/manage/ManageItemInput/index.tsx | 3 ++- client/src/components/manage/ManageItemInput/types.tsx | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/src/components/common/buttons/CustomButton/index.tsx b/client/src/components/common/buttons/CustomButton/index.tsx index ede2221..bfbab8d 100644 --- a/client/src/components/common/buttons/CustomButton/index.tsx +++ b/client/src/components/common/buttons/CustomButton/index.tsx @@ -3,10 +3,10 @@ import * as S from './styles'; import { CustomButtonProps } from './types'; const CustomButton = (props: CustomButtonProps): JSX.Element => { - const { image, children, color, size, onClickEvent, validation } = props; + const { image, children, color, size, onClickEvent, isValid } = props; return ( - + {image ? ( <> diff --git a/client/src/components/common/buttons/CustomButton/types.tsx b/client/src/components/common/buttons/CustomButton/types.tsx index f0f581d..ea41701 100644 --- a/client/src/components/common/buttons/CustomButton/types.tsx +++ b/client/src/components/common/buttons/CustomButton/types.tsx @@ -4,7 +4,7 @@ export type CustomButtonProps = { children: React.ReactNode; size?: 'sm' | 'md'; onClickEvent?: any; - validation: boolean; + isValid?: boolean; }; export type ButtonProps = { diff --git a/client/src/components/manage/ManageItemInput/index.tsx b/client/src/components/manage/ManageItemInput/index.tsx index 6d96e2b..f242014 100644 --- a/client/src/components/manage/ManageItemInput/index.tsx +++ b/client/src/components/manage/ManageItemInput/index.tsx @@ -10,6 +10,7 @@ const ManageItemInput = ({ saveHandler, onChangeInput, border, + isValid, }: ManageItemInputProps): JSX.Element => { const inputComponent = useRef(null); useEffect(() => { @@ -31,7 +32,7 @@ const ManageItemInput = ({ 취소 - + 저장 diff --git a/client/src/components/manage/ManageItemInput/types.tsx b/client/src/components/manage/ManageItemInput/types.tsx index d1f30e1..8f87e0b 100644 --- a/client/src/components/manage/ManageItemInput/types.tsx +++ b/client/src/components/manage/ManageItemInput/types.tsx @@ -4,6 +4,7 @@ export type ManageItemInputProps = { saveHandler?: (e: React.MouseEvent) => void; onChangeInput?: (e: React.ChangeEvent) => void; border: boolean; + isValid?: boolean; }; export type ManageItemInputContainerProps = { From 287bdf9d04009988d03d681cab7061a2310718fe Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 16:40:13 +0900 Subject: [PATCH 74/86] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EA=B4=80=EB=A6=AC=20=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CategoryManageContainer/index.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/client/src/container/CategoryManageContainer/index.tsx b/client/src/container/CategoryManageContainer/index.tsx index 2c79a43..a19f325 100644 --- a/client/src/container/CategoryManageContainer/index.tsx +++ b/client/src/container/CategoryManageContainer/index.tsx @@ -18,6 +18,7 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS .map((category) => new CategoryDTO(category)); const [categoryData, setCategoryData] = useState({ isIncome } as CategoryRequest); const [addCategory, setAddCategory] = useState(false); + const checkValidation = checkOverlap(categoryData.name, categoryList); const dispatch = useDispatch(); @@ -40,17 +41,21 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS ); const postNewCategory = useCallback(() => { - const newCategory = new CategoryRequestDTO(categoryData); - dispatch(postCategoryThunk(newCategory)); - toggleAddCategory(); - setCategoryData({ isIncome } as CategoryRequest); + if (checkValidation) { + const newCategory = new CategoryRequestDTO(categoryData); + dispatch(postCategoryThunk(newCategory)); + toggleAddCategory(); + setCategoryData({ isIncome } as CategoryRequest); + } }, [dispatch, categoryData, isIncome]); const updateCategory = useCallback( (cid) => { - const updateCategoryData = new CategoryRequestDTO({ cid, ...categoryData }); - dispatch(updateCategoryThunk(updateCategoryData)); - setCategoryData({ isIncome } as CategoryRequest); + if (checkValidation) { + const updateCategoryData = new CategoryRequestDTO({ cid, ...categoryData }); + dispatch(updateCategoryThunk(updateCategoryData)); + setCategoryData({ isIncome } as CategoryRequest); + } }, [dispatch, categoryData], ); @@ -65,6 +70,7 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS saveHandler={postNewCategory} onChangeInput={onChangeCategoryName} border + overlap={checkValidation} /> )} {categoryList.length !== 0 && ( From c4d83974a62441de1cb6ea6bb41e83e3ffdf46f5 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 16:41:57 +0900 Subject: [PATCH 75/86] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=EC=88=98?= =?UTF-8?q?=EB=8B=A8=20=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CategoryManageContainer/index.tsx | 2 +- .../src/container/PaymentManageMain/index.tsx | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/client/src/container/CategoryManageContainer/index.tsx b/client/src/container/CategoryManageContainer/index.tsx index a19f325..d848116 100644 --- a/client/src/container/CategoryManageContainer/index.tsx +++ b/client/src/container/CategoryManageContainer/index.tsx @@ -70,7 +70,7 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS saveHandler={postNewCategory} onChangeInput={onChangeCategoryName} border - overlap={checkValidation} + isValid={checkValidation} /> )} {categoryList.length !== 0 && ( diff --git a/client/src/container/PaymentManageMain/index.tsx b/client/src/container/PaymentManageMain/index.tsx index 59633d9..56cd79b 100644 --- a/client/src/container/PaymentManageMain/index.tsx +++ b/client/src/container/PaymentManageMain/index.tsx @@ -8,6 +8,7 @@ import ManageItemInput from '@/components/manage/ManageItemInput'; import { deletePaymentThunk, postPaymentThunk, updatePaymentThunk } from '@/modules/payment'; import PaymentRequestDTO from '@/commons/dto/payment-request'; import { PaymentRequest } from '@/commons/types/payment'; +import checkOverlap from '@/libs/checkOverlap'; import * as S from './styles'; const PaymentManageContainer = (): JSX.Element => { @@ -16,6 +17,7 @@ const PaymentManageContainer = (): JSX.Element => { const [paymentData, setPaymentData] = useState({} as PaymentRequest); const [addPayment, setAddPayment] = useState(false); const dispatch = useDispatch(); + const checkValidation = checkOverlap(paymentData.name, paymentList); const toggleAddPayment = useCallback(() => { setAddPayment(!addPayment); @@ -36,17 +38,21 @@ const PaymentManageContainer = (): JSX.Element => { ); const postNewPayment = useCallback(() => { - const newPayment = new PaymentRequestDTO(paymentData); - dispatch(postPaymentThunk(newPayment)); - toggleAddPayment(); - setPaymentData({} as PaymentRequest); + if (checkValidation) { + const newPayment = new PaymentRequestDTO(paymentData); + dispatch(postPaymentThunk(newPayment)); + toggleAddPayment(); + setPaymentData({} as PaymentRequest); + } }, [dispatch, paymentData]); const updatePayment = useCallback( (pid) => { - const updatePaymentData = new PaymentRequestDTO({ pid, ...paymentData }); - dispatch(updatePaymentThunk(updatePaymentData)); - setPaymentData({} as PaymentRequest); + if (checkValidation) { + const updatePaymentData = new PaymentRequestDTO({ pid, ...paymentData }); + dispatch(updatePaymentThunk(updatePaymentData)); + setPaymentData({} as PaymentRequest); + } }, [dispatch, paymentData], ); @@ -60,6 +66,7 @@ const PaymentManageContainer = (): JSX.Element => { cancelHandler={toggleAddPayment} saveHandler={postNewPayment} onChangeInput={onChangePaymentName} + isValid={checkValidation} border /> )} From 5ea8c1015c495fc783b2b140dd9ec75c8ef6f1a3 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 16:50:12 +0900 Subject: [PATCH 76/86] =?UTF-8?q?feat:=20=EA=B8=B0=EB=B3=B8=20=ED=8F=B0?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=80=EA=B2=BD=20->=20Noto=20Sans?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.css | 1 + client/src/App.tsx | 3 +-- client/src/GlobalStyled.ts | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 client/src/App.css diff --git a/client/src/App.css b/client/src/App.css new file mode 100644 index 0000000..d1b6ae1 --- /dev/null +++ b/client/src/App.css @@ -0,0 +1 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700;900&display=swap'); diff --git a/client/src/App.tsx b/client/src/App.tsx index bb3540f..3f8b758 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Reset } from 'styled-reset'; import { Redirect, Route, Switch } from 'react-router-dom'; import DashBoardPage from '@/views/DashBoardPage'; import LoginPage from '@/views/LoginPage'; @@ -10,11 +9,11 @@ import AggregateCategoryPage from '@/views/AggregateCategoryPage'; import GlobalStyled from './GlobalStyled'; import CategoryManagePage from './views/CategoryManagePage'; import AggregatePeriodPage from './views/AggregatePeriodPage'; +import './App.css'; function App(): JSX.Element { return ( <> - diff --git a/client/src/GlobalStyled.ts b/client/src/GlobalStyled.ts index 4ada067..bfd90e6 100644 --- a/client/src/GlobalStyled.ts +++ b/client/src/GlobalStyled.ts @@ -1,6 +1,9 @@ import { createGlobalStyle } from 'styled-components'; +import reset from 'styled-reset'; const GlobalStyled = createGlobalStyle` + ${reset} + * { box-sizing: border-box; } @@ -10,6 +13,8 @@ const GlobalStyled = createGlobalStyle` color: #363B40; height: 100%; + font-family: 'Noto Sans KR', sans-serif; + @media (max-width: 575.98px) { font-size: 14px; } From 92fde355de16fde61b043b7b91cf71891aee91f8 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 17:00:15 +0900 Subject: [PATCH 77/86] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC,=EA=B2=B0=EC=A0=9C=EC=88=98=EB=8B=A8=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=EA=B2=80=EC=82=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/buttons/CustomButton/index.tsx | 2 +- .../CategoryManageContainer/index.tsx | 1 + client/src/libs/checkOverlap.ts | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 client/src/libs/checkOverlap.ts diff --git a/client/src/components/common/buttons/CustomButton/index.tsx b/client/src/components/common/buttons/CustomButton/index.tsx index bfbab8d..95b47a5 100644 --- a/client/src/components/common/buttons/CustomButton/index.tsx +++ b/client/src/components/common/buttons/CustomButton/index.tsx @@ -6,7 +6,7 @@ const CustomButton = (props: CustomButtonProps): JSX.Element => { const { image, children, color, size, onClickEvent, isValid } = props; return ( - + {image ? ( <> diff --git a/client/src/container/CategoryManageContainer/index.tsx b/client/src/container/CategoryManageContainer/index.tsx index d848116..17ad814 100644 --- a/client/src/container/CategoryManageContainer/index.tsx +++ b/client/src/container/CategoryManageContainer/index.tsx @@ -3,6 +3,7 @@ import CategoryRequestDTO from '@/commons/dto/category-request'; import { CategoryRequest } from '@/commons/types/category'; import ManageHeader from '@/components/manage/ManageHeader'; import ManageItem from '@/components/manage/ManageItem'; +import checkOverlap from '@/libs/checkOverlap'; import ManageItemInput from '@/components/manage/ManageItemInput'; import { RootState } from '@/modules'; import { deleteCategoryThunk, postCategoryThunk, updateCategoryThunk } from '@/modules/category'; diff --git a/client/src/libs/checkOverlap.ts b/client/src/libs/checkOverlap.ts new file mode 100644 index 0000000..8201935 --- /dev/null +++ b/client/src/libs/checkOverlap.ts @@ -0,0 +1,18 @@ +import CategoryDTO from '@/commons/dto/category'; +import PaymentDTO from '@/commons/dto/payment'; + +const checkOverlap = (target: string, dataList: Array) => { + if (target === undefined || target === '') return false; + return !dataList.some((e) => e.name.trim() === target.trim()); +}; + +export default checkOverlap; + +// if (dataList.length > 0) { +// const nameList: string[] = dataList.map((value: CategoryDTO | PaymentDTO) => +// value.name === target; +// ); +// return !nameList.includes(target.replaceAll(' ', '')); +// } +// return true; +// retr From f206a442ff195071ee84a2bdd6249fa391088fab Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 17:31:15 +0900 Subject: [PATCH 78/86] =?UTF-8?q?fix:=20=EC=9B=94=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=EC=8B=9C=20=EB=B2=84=ED=8A=BC=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EB=90=98=EB=8A=94=20=ED=98=84=EC=83=81=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 년,월이 표시되는 영역의 공간을 fix 시키고 중앙 정렬하여 버튼의 위치가 변경되지 않도록 처리함. --- client/src/container/SelectMonth/index.tsx | 11 ++++++++--- client/src/container/SelectMonth/styles.tsx | 10 +++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/client/src/container/SelectMonth/index.tsx b/client/src/container/SelectMonth/index.tsx index 70625c9..4afce80 100644 --- a/client/src/container/SelectMonth/index.tsx +++ b/client/src/container/SelectMonth/index.tsx @@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '@/modules'; import { changeDate } from '@modules/datePicker/actions'; import { changeDay } from '@/modules/calendarDaySelector/action'; +import { IoIosArrowBack, IoIosArrowForward } from 'react-icons/io'; import * as S from './styles'; export default function SelectMonth(): JSX.Element { @@ -35,9 +36,13 @@ export default function SelectMonth(): JSX.Element { return ( <> - onChangeLeftMonth()}>{'<'} - {`${datePicker.year}-${datePicker.month}`} - onChangeRightMonth()}>{'>'} + onChangeLeftMonth()}> + + + {`${datePicker.year}-${datePicker.month}`} + onChangeRightMonth()}> + + ); diff --git a/client/src/container/SelectMonth/styles.tsx b/client/src/container/SelectMonth/styles.tsx index f0f802d..faf34cb 100644 --- a/client/src/container/SelectMonth/styles.tsx +++ b/client/src/container/SelectMonth/styles.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components'; -const MonthDiv = styled.div` +export const MonthDiv = styled.div` display: inline-flex; width: 100%; justify-content: center; @@ -9,8 +9,12 @@ const MonthDiv = styled.div` margin-bottom: 10px; `; -const ArrowDiv = styled.div` +export const ArrowDiv = styled.div` cursor: pointer; +`; + +export const MonthText = styled.div` + width: 100px; + text-align: center; margin: 0px 10px; `; -export { ArrowDiv, MonthDiv }; From d071fa0d796b230317d47386add890884f14906c Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 17:36:12 +0900 Subject: [PATCH 79/86] =?UTF-8?q?feat:=20=EA=B1=B0=EB=9E=98=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=88=98=EC=A0=95,=EC=B6=94=EA=B0=80=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=EA=B2=80=EC=82=AC=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/forms/CustomSelectInput/index.tsx | 4 +-- .../src/container/TransactionModal/index.tsx | 28 ++++++++-------- .../TransactionUpdateModal/index.tsx | 32 ++++++++++--------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/client/src/components/common/forms/CustomSelectInput/index.tsx b/client/src/components/common/forms/CustomSelectInput/index.tsx index db149db..6039d3a 100644 --- a/client/src/components/common/forms/CustomSelectInput/index.tsx +++ b/client/src/components/common/forms/CustomSelectInput/index.tsx @@ -19,8 +19,8 @@ const CustomSelectInput = (props: SelectInputProps): JSX.Element => { useEffect(() => { if (!value) return; const matchedValue = getMatchedOptionValue(); - setInputValue(matchedValue); - }, [value]); + if (matchedValue) setInputValue(matchedValue); + }, []); const handleChange = (e: React.ChangeEvent) => { setInputValue(Number(e.target.value)); diff --git a/client/src/container/TransactionModal/index.tsx b/client/src/container/TransactionModal/index.tsx index f7471ca..f7db8ce 100644 --- a/client/src/container/TransactionModal/index.tsx +++ b/client/src/container/TransactionModal/index.tsx @@ -30,15 +30,15 @@ const TransactionModal = ({ show, toggleModal }: TransactionModalProps): JSX.Ele const getCategoryList = useCallback(() => { dispatch(getCategoryThunk()); - }, [dispatch]); + }, []); const getPaymentList = useCallback(() => { dispatch(getPaymentThunk()); - }, [dispatch]); + }, []); useEffect(() => { getCategoryList(); getPaymentList(); - }, [dispatch]); + }, []); const onChangeReducer = (state: PostTransactionRequest, action: PostTransactionRequest) => { return action; @@ -59,15 +59,11 @@ const TransactionModal = ({ show, toggleModal }: TransactionModalProps): JSX.Ele setValidation(new Set([...validation])); } }; - const toggleModalOption = () => { - toggleModal(); - setValidation(new Set(['isIncome'])); - }; const postNewTransaction = useCallback(() => { const newTransactionDTO = new TransactionRequestDTO(newTransaction); dispatch(postTransactionThunk(newTransactionDTO)); - toggleModalOption(); + toggleModal(); }, [dispatch, newTransaction]); const parseClipboardText = useCallback(() => { @@ -100,8 +96,8 @@ const TransactionModal = ({ show, toggleModal }: TransactionModalProps): JSX.Ele return ( <> {paymentList && categoryList && ( - - + + 복사 - {validation.size > 5 && ( - - 저장 - - )} + 5} + color="blue" + onClickEvent={postNewTransaction} + > + 저장 + )} diff --git a/client/src/container/TransactionUpdateModal/index.tsx b/client/src/container/TransactionUpdateModal/index.tsx index f20b44f..b6fb855 100644 --- a/client/src/container/TransactionUpdateModal/index.tsx +++ b/client/src/container/TransactionUpdateModal/index.tsx @@ -2,7 +2,7 @@ import Modal from '@/components/common/Modal'; import ModalHeader from '@/components/transaction/ModalHeader'; import { RootState } from '@/modules'; import { toggleModalOff } from '@/modules/updateModal'; -import React, { useCallback, useState, useReducer, useEffect } from 'react'; +import React, { useCallback, useState, useReducer, useEffect, useMemo } from 'react'; import CategoryDTO from '@/commons/dto/category'; import PaymentDTO from '@/commons/dto/payment'; import { useDispatch, useSelector } from 'react-redux'; @@ -16,20 +16,20 @@ import TransactionRequestDTO from '@/commons/dto/transaction-request'; import { deleteTransactionThunk, updateTransactionThunk } from '@/modules/transaction'; import * as S from './styles'; -const MODAL_LSIT_ARR = ['tradeAt', 'description', 'amount', 'pid', 'cid', 'isIncome']; +const MODAL_LIST_ARR = ['tradeAt', 'description', 'amount', 'pid', 'cid', 'isIncome']; const TransactionUpdateModal = (): JSX.Element => { const [isIncome, setIsIncome] = useState(false); - const [validation, setValidation] = useState(new Set(MODAL_LSIT_ARR)); + const [validation, setValidation] = useState(new Set(MODAL_LIST_ARR)); const { payment, category } = useSelector((state: RootState) => state); const { toggle, data } = useSelector((state: RootState) => state.updateModal); - const categoryList = category.data.map((c) => new CategoryDTO(c)); - const paymentList = payment.data.map((p) => new PaymentDTO(p)); + const categoryList = useMemo(() => category.data.map((c) => new CategoryDTO(c)), [category]); + const paymentList = useMemo(() => payment.data.map((p) => new PaymentDTO(p)), [payment]); const dispatch = useDispatch(); const toggleModal = useCallback(() => { dispatch(toggleModalOff()); - setValidation(new Set(MODAL_LSIT_ARR)); - }, [dispatch]); + setValidation(new Set(MODAL_LIST_ARR)); + }, []); const onChangeReducer = (state: UpdateTransactionRequest, action: UpdateTransactionRequest) => { return action; @@ -72,17 +72,17 @@ const TransactionUpdateModal = (): JSX.Element => { const newTransactionDTO = new TransactionRequestDTO(updatedTransaction); dispatch(updateTransactionThunk(newTransactionDTO)); dispatch(toggleModalOff()); - }, [dispatch, updatedTransaction, data]); + }, [updatedTransaction, data]); const deleteTransaction = useCallback(() => { + if (!data) return; if (window.confirm('삭제 하시겠습니까?')) { - if (!data) return; dispatch(deleteTransactionThunk(data.tid)); dispatch(toggleModalOff()); } else { toggleModalOff(); } - }, [dispatch, data]); + }, [data]); return ( <> @@ -137,11 +137,13 @@ const TransactionUpdateModal = (): JSX.Element => { 삭제 - {validation.size > 5 && ( - - 저장 - - )} + 5} + color="blue" + onClickEvent={updateTransaction} + > + 저장 + )} From 5681e9b997ca50b4af86b6c43bf31d7b6c8a9a89 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 17:46:39 +0900 Subject: [PATCH 80/86] =?UTF-8?q?feat:GlobalStyled=EC=97=90=20button=20dis?= =?UTF-8?q?abled=EC=8B=9C=20style=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/GlobalStyled.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/GlobalStyled.ts b/client/src/GlobalStyled.ts index feb632a..af04ae6 100644 --- a/client/src/GlobalStyled.ts +++ b/client/src/GlobalStyled.ts @@ -19,6 +19,11 @@ const GlobalStyled = createGlobalStyle` font-size: 14px; } } + button:disabled { + opacity: 0.6; + cursor: not-allowed; + } + a { &:link { From c5518b76660026a5f30ee95558f5129231f9e909 Mon Sep 17 00:00:00 2001 From: parkdit94 Date: Mon, 14 Dec 2020 17:48:17 +0900 Subject: [PATCH 81/86] =?UTF-8?q?fix:=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/libs/checkOverlap.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/src/libs/checkOverlap.ts b/client/src/libs/checkOverlap.ts index 8201935..80fbd2f 100644 --- a/client/src/libs/checkOverlap.ts +++ b/client/src/libs/checkOverlap.ts @@ -7,12 +7,3 @@ const checkOverlap = (target: string, dataList: Array) }; export default checkOverlap; - -// if (dataList.length > 0) { -// const nameList: string[] = dataList.map((value: CategoryDTO | PaymentDTO) => -// value.name === target; -// ); -// return !nameList.includes(target.replaceAll(' ', '')); -// } -// return true; -// retr From 7817f4da3e531238cb5e814674f61507cb7a92b8 Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 18:56:35 +0900 Subject: [PATCH 82/86] =?UTF-8?q?fix:=20=EB=93=9C=EB=9E=98=EA=B7=B8=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80=20css=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 월 변경 버튼을 중복적으로 클릭했을때 다른 영역이 드래그가 되는 현상을 방지하기 위해 드래그 방지 css 설정을 추가함. --- client/src/GlobalStyled.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/GlobalStyled.ts b/client/src/GlobalStyled.ts index ca622f5..69e7dcd 100644 --- a/client/src/GlobalStyled.ts +++ b/client/src/GlobalStyled.ts @@ -43,6 +43,14 @@ const GlobalStyled = createGlobalStyle` button { cursor: pointer; } + + div { + -ms-user-select: none; + -moz-user-select: -moz-none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; + } `; export default GlobalStyled; From 03fd922dccf7ef334fb2a6c87d6578c81e330f0f Mon Sep 17 00:00:00 2001 From: Changehee Choi Date: Mon, 14 Dec 2020 18:57:28 +0900 Subject: [PATCH 83/86] =?UTF-8?q?fix:=20=EA=B8=B0=EA=B0=84=EB=B3=84=20?= =?UTF-8?q?=ED=86=B5=EA=B3=84=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=AC=B4?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EB=94=A9=ED=98=84=EC=83=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/container/LineGraphContainer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/container/LineGraphContainer/index.tsx b/client/src/container/LineGraphContainer/index.tsx index e66cd7e..785a044 100644 --- a/client/src/container/LineGraphContainer/index.tsx +++ b/client/src/container/LineGraphContainer/index.tsx @@ -11,11 +11,11 @@ const LineGraphContainer = (): JSX.Element => { const { transaction } = useSelector((state: RootState) => state); return ( <> + {transaction.loading ? ( ) : ( <> - {transaction.aggregationByDate.length === 0 ? ( ) : ( From 721e535b66d25b1503bdeb7a9c8bd4b73a5e0cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 21:33:26 +0900 Subject: [PATCH 84/86] =?UTF-8?q?=20fix=20:=20line=20graph=20container=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 반복적으로 api 호출하는 문제 해결 --- client/src/container/LineGraphContainer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/container/LineGraphContainer/index.tsx b/client/src/container/LineGraphContainer/index.tsx index e66cd7e..785a044 100644 --- a/client/src/container/LineGraphContainer/index.tsx +++ b/client/src/container/LineGraphContainer/index.tsx @@ -11,11 +11,11 @@ const LineGraphContainer = (): JSX.Element => { const { transaction } = useSelector((state: RootState) => state); return ( <> + {transaction.loading ? ( ) : ( <> - {transaction.aggregationByDate.length === 0 ? ( ) : ( From b0e28710809cf226d350a7b23898f3c659b8b479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 21:44:25 +0900 Subject: [PATCH 85/86] =?UTF-8?q?refactor=20:=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20data=20patching=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 새로고침시 state가 초기화되는 것을 고려하여 해당 data를 사용하는 container에서도 dispatch를 사용 --- .../container/CategoryManageContainer/index.tsx | 17 +++++++++++++++-- .../src/container/PaymentManageMain/index.tsx | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/client/src/container/CategoryManageContainer/index.tsx b/client/src/container/CategoryManageContainer/index.tsx index 2c79a43..041f798 100644 --- a/client/src/container/CategoryManageContainer/index.tsx +++ b/client/src/container/CategoryManageContainer/index.tsx @@ -5,8 +5,13 @@ import ManageHeader from '@/components/manage/ManageHeader'; import ManageItem from '@/components/manage/ManageItem'; import ManageItemInput from '@/components/manage/ManageItemInput'; import { RootState } from '@/modules'; -import { deleteCategoryThunk, postCategoryThunk, updateCategoryThunk } from '@/modules/category'; -import React, { useCallback, useState } from 'react'; +import { + deleteCategoryThunk, + getCategoryThunk, + postCategoryThunk, + updateCategoryThunk, +} from '@/modules/category'; +import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { CategoryManageContainerProps } from './types'; import CategoryListContainer from './styles'; @@ -21,6 +26,14 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS const dispatch = useDispatch(); + const getCategoryList = useCallback(() => { + dispatch(getCategoryThunk()); + }, []); + + useEffect(() => { + getCategoryList(); + }, []); + const toggleAddCategory = useCallback(() => { setAddCategory(!addCategory); }, [addCategory]); diff --git a/client/src/container/PaymentManageMain/index.tsx b/client/src/container/PaymentManageMain/index.tsx index 59633d9..a0ae2a8 100644 --- a/client/src/container/PaymentManageMain/index.tsx +++ b/client/src/container/PaymentManageMain/index.tsx @@ -1,11 +1,16 @@ import ManageHeader from '@/components/manage/ManageHeader'; import { RootState } from '@/modules'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import ManageItem from '@/components/manage/ManageItem'; import PaymentDTO from '@/commons/dto/payment'; import ManageItemInput from '@/components/manage/ManageItemInput'; -import { deletePaymentThunk, postPaymentThunk, updatePaymentThunk } from '@/modules/payment'; +import { + deletePaymentThunk, + getPaymentThunk, + postPaymentThunk, + updatePaymentThunk, +} from '@/modules/payment'; import PaymentRequestDTO from '@/commons/dto/payment-request'; import { PaymentRequest } from '@/commons/types/payment'; import * as S from './styles'; @@ -17,6 +22,14 @@ const PaymentManageContainer = (): JSX.Element => { const [addPayment, setAddPayment] = useState(false); const dispatch = useDispatch(); + const getPaymentList = useCallback(() => { + dispatch(getPaymentThunk()); + }, []); + + useEffect(() => { + getPaymentList(); + }, []); + const toggleAddPayment = useCallback(() => { setAddPayment(!addPayment); }, [addPayment]); From a3463cb542291b51d80a8d74e40bae336940aee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=84=EC=9A=A9?= Date: Mon, 14 Dec 2020 21:48:57 +0900 Subject: [PATCH 86/86] =?UTF-8?q?refactor=20:=20useCallback=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20dispatch=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dispatch는 변화 하지않으므로 deps에 포함하는 것이 무의미하다 판단하여 리팩토링 --- .../src/container/CategoryManageContainer/index.tsx | 13 +++++-------- client/src/container/DashboardContainer/index.tsx | 2 +- .../container/DetailedFixedExpenditure/index.tsx | 4 ++-- client/src/container/FixedExpenditure/index.tsx | 4 ++-- client/src/container/PaymentManageMain/index.tsx | 13 +++++-------- client/src/container/SelectMonth/index.tsx | 2 +- client/src/container/TransactionList/index.tsx | 9 +++------ client/src/container/TransactionModal/index.tsx | 8 ++++---- .../src/container/TransactionSelectList/index.tsx | 9 +++------ .../src/container/TransactionUpdateModal/index.tsx | 6 +++--- 10 files changed, 29 insertions(+), 41 deletions(-) diff --git a/client/src/container/CategoryManageContainer/index.tsx b/client/src/container/CategoryManageContainer/index.tsx index 041f798..c387b11 100644 --- a/client/src/container/CategoryManageContainer/index.tsx +++ b/client/src/container/CategoryManageContainer/index.tsx @@ -38,12 +38,9 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS setAddCategory(!addCategory); }, [addCategory]); - const deleteCategory = useCallback( - (cid) => { - dispatch(deleteCategoryThunk(cid)); - }, - [dispatch], - ); + const deleteCategory = useCallback((cid) => { + dispatch(deleteCategoryThunk(cid)); + }, []); const onChangeCategoryName = useCallback( (e: React.ChangeEvent) => { @@ -57,7 +54,7 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS dispatch(postCategoryThunk(newCategory)); toggleAddCategory(); setCategoryData({ isIncome } as CategoryRequest); - }, [dispatch, categoryData, isIncome]); + }, [categoryData, isIncome]); const updateCategory = useCallback( (cid) => { @@ -65,7 +62,7 @@ const CategoryManageContainer = ({ isIncome }: CategoryManageContainerProps): JS dispatch(updateCategoryThunk(updateCategoryData)); setCategoryData({ isIncome } as CategoryRequest); }, - [dispatch, categoryData], + [categoryData], ); return ( diff --git a/client/src/container/DashboardContainer/index.tsx b/client/src/container/DashboardContainer/index.tsx index cd65b57..f2558fe 100644 --- a/client/src/container/DashboardContainer/index.tsx +++ b/client/src/container/DashboardContainer/index.tsx @@ -20,7 +20,7 @@ const DashboardContainer = (): JSX.Element => { const dispatch = useDispatch(); const getMonthlyTransactions = useCallback(() => { dispatch(getMonthlyTransactionThunk(datePicker)); - }, [dispatch, datePicker]); + }, [datePicker]); const [overspendingIndexState, setOverspendingIndexState] = useState({ overspendingIndex: 0, averageIncome: 0, diff --git a/client/src/container/DetailedFixedExpenditure/index.tsx b/client/src/container/DetailedFixedExpenditure/index.tsx index c9b335f..ef77256 100644 --- a/client/src/container/DetailedFixedExpenditure/index.tsx +++ b/client/src/container/DetailedFixedExpenditure/index.tsx @@ -13,7 +13,7 @@ const DetailedFixedExpenditure = (): JSX.Element => { const getFixedExpenditure = useCallback(() => { dispatch(getFixedExpenditureThunk(datePicker.year, datePicker.month)); - }, [dispatch, datePicker]); + }, [datePicker]); const getAmount = useCallback( (type: string) => { @@ -43,7 +43,7 @@ const DetailedFixedExpenditure = (): JSX.Element => { useEffect(() => { getFixedExpenditure(); - }, [dispatch, datePicker]); + }, [datePicker]); return ( <> diff --git a/client/src/container/FixedExpenditure/index.tsx b/client/src/container/FixedExpenditure/index.tsx index 068a197..40e7656 100644 --- a/client/src/container/FixedExpenditure/index.tsx +++ b/client/src/container/FixedExpenditure/index.tsx @@ -15,7 +15,7 @@ const FixedExpenditure = (): JSX.Element => { const getFixedExpenditure = useCallback(() => { dispatch(getFixedExpenditureThunk(datePicker.year, datePicker.month)); - }, [dispatch, datePicker]); + }, [datePicker]); const getAmount = useCallback( (type: string) => { @@ -41,7 +41,7 @@ const FixedExpenditure = (): JSX.Element => { useEffect(() => { getFixedExpenditure(); - }, [dispatch, datePicker, transaction]); + }, [datePicker, transaction]); return ( <> {fixedExpenditure.loading ? ( diff --git a/client/src/container/PaymentManageMain/index.tsx b/client/src/container/PaymentManageMain/index.tsx index a0ae2a8..a9b4d50 100644 --- a/client/src/container/PaymentManageMain/index.tsx +++ b/client/src/container/PaymentManageMain/index.tsx @@ -34,12 +34,9 @@ const PaymentManageContainer = (): JSX.Element => { setAddPayment(!addPayment); }, [addPayment]); - const deletePayment = useCallback( - (pid) => { - dispatch(deletePaymentThunk(pid)); - }, - [dispatch], - ); + const deletePayment = useCallback((pid) => { + dispatch(deletePaymentThunk(pid)); + }, []); const onChangePaymentName = useCallback( (e: React.ChangeEvent) => { @@ -53,7 +50,7 @@ const PaymentManageContainer = (): JSX.Element => { dispatch(postPaymentThunk(newPayment)); toggleAddPayment(); setPaymentData({} as PaymentRequest); - }, [dispatch, paymentData]); + }, [paymentData]); const updatePayment = useCallback( (pid) => { @@ -61,7 +58,7 @@ const PaymentManageContainer = (): JSX.Element => { dispatch(updatePaymentThunk(updatePaymentData)); setPaymentData({} as PaymentRequest); }, - [dispatch, paymentData], + [paymentData], ); return ( diff --git a/client/src/container/SelectMonth/index.tsx b/client/src/container/SelectMonth/index.tsx index 70625c9..0ddec15 100644 --- a/client/src/container/SelectMonth/index.tsx +++ b/client/src/container/SelectMonth/index.tsx @@ -26,7 +26,7 @@ export default function SelectMonth(): JSX.Element { const getMonthlyTransactions = useCallback(() => { dispatch(getMonthlyTransactionThunk(datePicker)); - }, [dispatch, datePicker]); + }, [datePicker]); useEffect(() => { getMonthlyTransactions(); diff --git a/client/src/container/TransactionList/index.tsx b/client/src/container/TransactionList/index.tsx index 0a903d8..fa8460c 100644 --- a/client/src/container/TransactionList/index.tsx +++ b/client/src/container/TransactionList/index.tsx @@ -11,12 +11,9 @@ import { TransactionListContainerProps } from './types'; const TransactionListContainer = ({ editable }: TransactionListContainerProps): JSX.Element => { const { transaction } = useSelector((state: RootState) => state); const dispatch = useDispatch(); - const toggleModal = useCallback( - (t: TransactionModel) => { - dispatch(toggleModalOn(t)); - }, - [dispatch], - ); + const toggleModal = useCallback((t: TransactionModel) => { + dispatch(toggleModalOn(t)); + }, []); return ( <> diff --git a/client/src/container/TransactionModal/index.tsx b/client/src/container/TransactionModal/index.tsx index f7471ca..5deda03 100644 --- a/client/src/container/TransactionModal/index.tsx +++ b/client/src/container/TransactionModal/index.tsx @@ -30,15 +30,15 @@ const TransactionModal = ({ show, toggleModal }: TransactionModalProps): JSX.Ele const getCategoryList = useCallback(() => { dispatch(getCategoryThunk()); - }, [dispatch]); + }, []); const getPaymentList = useCallback(() => { dispatch(getPaymentThunk()); - }, [dispatch]); + }, []); useEffect(() => { getCategoryList(); getPaymentList(); - }, [dispatch]); + }, []); const onChangeReducer = (state: PostTransactionRequest, action: PostTransactionRequest) => { return action; @@ -68,7 +68,7 @@ const TransactionModal = ({ show, toggleModal }: TransactionModalProps): JSX.Ele const newTransactionDTO = new TransactionRequestDTO(newTransaction); dispatch(postTransactionThunk(newTransactionDTO)); toggleModalOption(); - }, [dispatch, newTransaction]); + }, [newTransaction]); const parseClipboardText = useCallback(() => { navigator.clipboard.readText().then((clipText) => { diff --git a/client/src/container/TransactionSelectList/index.tsx b/client/src/container/TransactionSelectList/index.tsx index e53e505..6f9cadd 100644 --- a/client/src/container/TransactionSelectList/index.tsx +++ b/client/src/container/TransactionSelectList/index.tsx @@ -16,12 +16,9 @@ const TransactionSelectList = (): JSX.Element => { ) as TransactionModel[]; const dispatch = useDispatch(); - const toggleModal = useCallback( - (t: TransactionModel) => { - dispatch(toggleModalOn(t)); - }, - [dispatch], - ); + const toggleModal = useCallback((t: TransactionModel) => { + dispatch(toggleModalOn(t)); + }, []); return ( <> diff --git a/client/src/container/TransactionUpdateModal/index.tsx b/client/src/container/TransactionUpdateModal/index.tsx index f20b44f..ca07523 100644 --- a/client/src/container/TransactionUpdateModal/index.tsx +++ b/client/src/container/TransactionUpdateModal/index.tsx @@ -29,7 +29,7 @@ const TransactionUpdateModal = (): JSX.Element => { const toggleModal = useCallback(() => { dispatch(toggleModalOff()); setValidation(new Set(MODAL_LSIT_ARR)); - }, [dispatch]); + }, []); const onChangeReducer = (state: UpdateTransactionRequest, action: UpdateTransactionRequest) => { return action; @@ -72,7 +72,7 @@ const TransactionUpdateModal = (): JSX.Element => { const newTransactionDTO = new TransactionRequestDTO(updatedTransaction); dispatch(updateTransactionThunk(newTransactionDTO)); dispatch(toggleModalOff()); - }, [dispatch, updatedTransaction, data]); + }, [updatedTransaction, data]); const deleteTransaction = useCallback(() => { if (window.confirm('삭제 하시겠습니까?')) { @@ -82,7 +82,7 @@ const TransactionUpdateModal = (): JSX.Element => { } else { toggleModalOff(); } - }, [dispatch, data]); + }, [data]); return ( <>