투자 관리 서비스의 관리자 기능 서비스
프로젝트 기간 : 2022년 11월 12일 ~ 2022년 11월 18일
아래의 아이디와 비밀번호로 접속해주세요.
아이디:[email protected]
비밀번호:12341234
🚨 사용자 목록에서 계좌수를 json-server api를 이용해 불러오려 했으나 실패하여 결국 Math.random
으로 구현했으니 참고바랍니다. 나중에 이 부분은 꼭 구현해서 수정하도록 하겠습니다!
🚨 배포 사이트가 열리지 않습니다. heroku에 배포하였으나 일정기간만 서비스 운영을 하기 때문입니다. aws의 ec2로 배포를 진행하고자 합니다
로그인 | 계좌정보 - 네비게이션 | 사용자 상세페이지 - 수정 |
---|---|---|
사용자목록 - 추가 | 사용자목록 - 삭제 |
---|---|
$ git clone https://github.com/pre-onboarding-frontend-7-team-3/pre-onboarding-7th-3-2-3.git
$ npm install
$ npm run dev
-
비동기적 소통을 위해 노션 워크스페이스에서 프로젝트를 페이지와 컴포넌트로 나누고 미팅 로그와 주요 코드를 공유하여 개발 효율을 높이고자 노력했습니다.
-
본 프로젝트는 동료학습에 최적화된 과정을 찾아가며 진행했습니다. VSC Live Code extension을 활용하여 라이브 코드 리뷰를 진행하고 각자 구현한 코드에 대한 피드백을 진행하여 Best Practice를 추가해 나가는 과정을 거쳤습니다. 후의 리팩토링도 동일한 과정을 거쳐 진행하였습니다.
-
소통 플랫폼으로 게더타운과 디스코드를 활용해서 협업을 진행했습니다.
- TypeScript는 정적 타입을 지원하므로 컴파일 단계에서 오류를 포착할 수 있는 장점이 있습니다. 코드의
가독성
을 높이고 예측할 수 있게 하며디버깅
이 쉽다는 장점에 모두 공감해서 채택했습니다. 명시적인 정적타입 지정
은 팀 단위로 협업 시에 의도를 명확하게 코드로 기술할 수 있다는 점에서도 의견을 모았습니다.
-
서버 상태와 비동기 호출을 react query로 관리하였고 데이터의 형태와 적합성 그리고 앱의 동작 흐름을 고려하여
stale time
과cache time
을 설정했습니다. -
계좌목록에서는 금액과 같이 변동성이 큰 데이터는 staleTime과 cacheTime을 짧게(2000ms) 설정해서 최신 데이터를 받아오도록 했습니다.
-
사용자 목록에서는 민감한 정보를 다루지 않기 때문에 staleTime과 cacheTime 을 길게 설정하고 사용자 추가, 수정, 삭제가 될 때
invalidateQueries
를 사용, 데이터를 비교해 최신의 데이터를 UI로 출력하게 했습니다. -
캐싱 기능을 제공하는 react query의 장점을 살리기 위해, 다음 페이지에 대한 data를 prefetch하여, 페이지 이동 시, prefetch된 데이터를 바로 보여줄 수 있게 하였습니다.
-
계좌 정보와 유저 정보 관련 api 코드들을 분리하여 작성하여 추상화했습니다. api코드와 query코드를 추상화하여 관심사를 분리하고 재사용성을 높였습니다.
-
react-query는 캐시받아 저장하고 다루기 때문에 DB 형태와 비슷하다고 판단했습니다. 기본적으로 스키마를 정의하고, 불러오는 레파지토리를 만들고, DB 에 넣는 쿼리를 가지는 형태의 데이터 저장방식을 모방해 현재의 아키텍쳐를 적용 반영했습니다.
-
레파지토리를 class 객체로 만들게 된 이유는 서로 비슷한 기능을 하는 api 들이기 때문에 parameter 설정이나 기타 axios 설정 값을 공유할 수 있을 거라는 예측에 기반했습니다. 각 api 호출 기능들을 멤버함수로써 다뤄 api안에 api를 호출될 수 있는 상황을 대비했습니다.
-
계좌목록 페이지 내 검색을 포함한 필터링 데이터 상태 관리에 Jotai를 사용했습니다.
-
내장 hook useAtom을 사용해 accountQueryParams state를 관리하며 페이지의 이동과 새로고침에도 유지되도록 구현하였습니다.
-
Log in 페이지에 있는 input과 사용자추가시 팝업으로 뜨는 modal 의 input에 react-hook-form을 적용하여 input에 입력되는 value의 validation을 체크했습니다.
-
validation이 맞지 않으면 form이 submit되지 못하도록 했습니다.
-
그리고 validation이 맞아서 submit에 성공하여 서버로 정보가 전송되었다 하더라도 서버에서 에러가 발생했을 경우, 에러 modal이 팝업되도록 했습니다.
-
검색창에 검색어를 입력했을 때 onChange 이벤트가 발생할 때마다 서버에 GET 요청을 보내는 것은 비효율적인 프로세스라고 공통된 의견을 나누었습니다.
-
따라서 첫 onChange 이벤트의 발생 시점으로부터 의도적인
지연시간
을 두어 API 호출 횟수를 줄였습니다. -
검색창의 onChange 이벤트가 비동기적으로 input의 상태 값을 업데이트하되, 사용자가 입력한 검색 결과에 대한 비동기 요청은
디바운싱 함수
에서 설정한 시간(600ms)이 지난 뒤에 최종적으로 업데이트된 상태 값을 쿼리 스트링으로 보내 호출되게 구현했습니다.pre-onboarding-7th-3-2-3/src/hooks/useDebounce.ts
Lines 1 to 17 in 1a2a664
-
라우팅 페이지에서 Route상단에 RequireAuth컴포넌트를 두어 페이지별 인가를 구현하였습니다.
-
RequireAuth 컴포넌트 내부에서 props를 통해 전달받은 isAuthRequire와, 로그인 성공 후 cookies에 저장하는 access_token 값의 유무를 기준으로 4단계로 나누어 리다이렉팅 하였습니다.
-
server에 존재하는 authorize token의 만료 시간이 지나면 관련한 모든 api의 호출에서 401.
-
따라서 기존에 로그인 후 localStorage 저장하던 access_token을 서버의 authorize token 만료시간(1시간)과 일치시켜 로그아웃 될 수 있도록 하였습니다.
pre-onboarding-7th-3-2-3/src/utils/auth/RequireAuth.tsx
Lines 1 to 24 in 0c03f6a
- git commit message
커밋명 | 내용 |
---|---|
feat | 파일, 폴더, 새로운 기능 추가 |
fix | 버그 수정 |
docs | 제품 코드 수정 없음 |
style | 코드 형식, 정렬, 주석 등의 변경 |
refactor | 코드 리팩토링 |
test | 테스트 코드 추가 |
chore | 환경설정, 빌드 업무, 패키지 매니저 설정등.. |
hotfix | 치명적이거나 급한 버그 수정 |
remove | 사용하지 않는 변수, 파일 etc 삭제 |
working | 이미 만들어진 기능, 함수 작업중 |
merge | branch merge |
- branch
브랜치명 | 내용 |
---|---|
develop | 파일, 폴더, 새로운 기능 추가 |
fix | 버그 수정 |
docs | 제품 코드 수정 없음 |
refactor | 코드 리팩토링 |
hotfix | 치명적이거나 급한 버그 수정 |
feat | 새로운 기능 추가 |
📂 src
│ ├─ App.tsx
│ ├─ apis
│ │ ├─ httpClient.ts
│ │ ├─ index.ts
│ │ └─ investmentService.ts
│ ├─ assets
│ │ └─ December&Company.jpeg
│ ├─ components
│ │ ├─ InvestmentAccountList
│ │ │ ├─ Account-query
│ │ │ │ ├─ InvestmentAccount.model.ts
│ │ │ │ ├─ InvestmentAccount.query.ts
│ │ │ │ └─ InvestmentAccount.repository.ts
│ │ │ ├─ InvestmentAccountItem
│ │ │ │ └─ InvestmentAccountItem.tsx
│ │ │ ├─ InvestmentAccountList.style.ts
│ │ │ ├─ InvestmentAccountList.tsx
│ │ │ ├─ atoms
│ │ │ │ └─ index.ts
│ │ │ └─ index.ts
│ │ ├─ LoginForm
│ │ │ ├─ Login-query
│ │ │ │ ├─ Login.query.ts
│ │ │ │ └─ Login.repository.ts
│ │ │ ├─ LoginErrorModal
│ │ │ │ ├─ LoginErrorModal.style.ts
│ │ │ │ └─ LoginErrorModal.tsx
│ │ │ ├─ LoginForm.style.ts
│ │ │ ├─ LoginForm.tsx
│ │ │ ├─ LoginInput
│ │ │ │ ├─ LoginInput.style.tsx
│ │ │ │ └─ LoginInput.tsx
│ │ │ └─ index.ts
│ │ ├─ NewUserModal
│ │ │ ├─ FileInput
│ │ │ │ ├─ FileInput.style.ts
│ │ │ │ └─ FileInput.tsx
│ │ │ ├─ FunnelButton
│ │ │ │ ├─ FunnelButton.style.ts
│ │ │ │ └─ FunnelButton.tsx
│ │ │ ├─ NewUserModal.style.ts
│ │ │ ├─ NewUserModal.tsx
│ │ │ ├─ UserInput
│ │ │ │ ├─ UserInput.style.ts
│ │ │ │ └─ UserInput.tsx
│ │ │ └─ index.ts
│ │ ├─ UserDetail
│ │ │ ├─ UserDetail.tsx
│ │ │ ├─ UserDetailTableItem.tsx
│ │ │ ├─ UserInfoTable.tsx
│ │ │ ├─ index.ts
│ │ │ └─ types.ts
│ │ ├─ UserList
│ │ │ ├─ DeleteModal
│ │ │ │ ├─ DeleteModal.tsx
│ │ │ │ └─ index.ts
│ │ │ ├─ UserList.style.ts
│ │ │ ├─ UserList.tsx
│ │ │ ├─ UserTableItem
│ │ │ │ └─ UserTableItem.tsx
│ │ │ ├─ atoms
│ │ │ │ └─ index.ts
│ │ │ └─ index.ts
│ │ └─ common
│ │ ├─ Dropdown
│ │ │ ├─ Dropdown.style.ts
│ │ │ └─ Dropdown.tsx
│ │ ├─ Header
│ │ │ ├─ Header.style.ts
│ │ │ └─ Header.tsx
│ │ ├─ Icons
│ │ │ ├─ Lock.tsx
│ │ │ ├─ Logo.tsx
│ │ │ ├─ User.tsx
│ │ │ └─ index.ts
│ │ ├─ Layout
│ │ │ ├─ Layout.style.ts
│ │ │ └─ Layout.tsx
│ │ ├─ Loader
│ │ │ ├─ Loader.style.ts
│ │ │ └─ Loader.tsx
│ │ ├─ PageContainer
│ │ │ ├─ PageContainer.style.ts
│ │ │ └─ PageContainer.tsx
│ │ ├─ PagenationButton
│ │ │ └─ PagenationButton.tsx
│ │ ├─ SEO
│ │ │ └─ SEO.tsx
│ │ ├─ SearchInput
│ │ │ ├─ SearchInput.style.ts
│ │ │ └─ SearchInput.tsx
│ │ ├─ Sider
│ │ │ ├─ Sider.style.ts
│ │ │ └─ Sider.tsx
│ │ └─ Table
│ │ ├─ CustomTableBody.style.ts
│ │ ├─ CustomTableBody.tsx
│ │ └─ CustomTableHead.tsx
│ ├─ constants
│ │ ├─ NewUserInputData.ts
│ │ ├─ dropDownData.ts
│ │ ├─ funnelButtonData.ts
│ │ ├─ routes.ts
│ │ ├─ siderData.ts
│ │ └─ tableData.ts
│ ├─ hooks
│ │ ├─ useDebounce.ts
│ │ ├─ useSignForm.ts
│ │ └─ useUnmountIfClickedOutside.ts
│ ├─ libs
│ │ └─ api
│ │ ├─ auth.ts
│ │ ├─ client.ts
│ │ └─ user.ts
│ ├─ main.tsx
│ ├─ pages
│ │ ├─ InvestmentAccounts
│ │ │ ├─ InvestmentAccounts.tsx
│ │ │ └─ index.ts
│ │ ├─ NotFound
│ │ │ ├─ NotFound.style.ts
│ │ │ ├─ NotFound.tsx
│ │ │ └─ index.ts
│ │ ├─ UserDetail
│ │ │ ├─ UserDetail.tsx
│ │ │ └─ index.ts
│ │ ├─ UserList
│ │ │ ├─ UserList.tsx
│ │ │ └─ index.ts
│ │ └─ login
│ │ ├─ Login.tsx
│ │ └─ index.ts
│ ├─ shared
│ │ └─ User-query
│ │ ├─ User.model.ts
│ │ ├─ User.query.ts
│ │ └─ User.repository.ts
│ ├─ store
│ │ └─ sider.ts
│ ├─ styled.d.ts
│ ├─ styles
│ │ ├─ GlobalStyles.ts
│ │ └─ Theme.ts
│ ├─ utils
│ │ ├─ assetsColorDecider.ts
│ │ ├─ auth
│ │ │ ├─ RequireAuth.tsx
│ │ │ └─ httpResponseUtils.ts
│ │ ├─ convertDate.ts
│ │ ├─ formatBoolean.ts
│ │ ├─ processData.ts
│ │ └─ validator.ts
│ └─ vite-env.d.ts
├─ tsconfig.json
├─ tsconfig.node.json
└─ vite.config.ts
고영훈 (팀장) |
조은지 (팀원) |
김창희 (팀원) |
박정민 (팀원) |
---|---|---|---|
YeonghunKO | Joeunji0119 | PiperChang | ono212 |
문지원 (팀원) |
이상민 (공지) |
이지원 (팀원) |
조수진 (팀원) |
---|---|---|---|
moonkorea00 | dltkdals224 | 365support | suzz-in |