-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
1조 과제 제출 #1
base: main
Are you sure you want to change the base?
1조 과제 제출 #1
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
총평
정말 멋진 프로젝트입니다.
수강생들의 결과물이라니 믿기지 않을 정도입니다.
useMemo의 사용, 일관된 api return 등 프로젝트의 여러 부분에서 완성도가 높은 것이 티가 납니다.
아쉬운 점은 딱히 없고, 가독성이나 유지 보수 측면에서 더 개선될 사항 정도가 있겠습니다.
fetch를 모두 useEffect에서 사용하고 로직을 대부분 컴포넌트 안에서 사용하다보니 컴포넌트 자체가 복잡해지는 경향이 있습니다.
컴포넌트에서 분리할 수 있는 로직들은 재사용성과 가독성을 위해서 유틸 함수로 만들어 분리해보세요.(API 처럼)
수고하셨습니다.
{userInfo.isAdmin ? ( | ||
// 관리자인 경우 | ||
<Link to="/admin/clients">관리자</Link> | ||
) : ( | ||
// 관리자가 아닌경우 | ||
<></> | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, false 중 하나의 경우에만 렌더가 필요할 경우 삼항연산자의 사용보단 &&의 사용이 코드를 조금 더 깔끔하게 만들어 줍니다.
@@ -0,0 +1,44 @@ | |||
import { useState } from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
간단한 form은 비제어 컴포넌트로도 구성할 수 있습니다.
비제어 컴포넌트가 좋다는 의미는 아니고, 유연하게 비제어 컴포넌트의 사용도 생각해보세요.
{spentMoney >= 300000 ? ( | ||
spentMoney >= 500000 ? ( | ||
<span className="font-bold text-accent">💰VVIP💰</span> | ||
) : ( | ||
<span className="font-bold text-accent">💰VIP</span> | ||
) | ||
) : ( | ||
'일반회원' | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금의 경우 많이 복잡하진 않은데, 2가지의 방법으로 리팩터링을 해볼 수 있습니다.
- vvip와 vip를 감싸는 tag가 동일하므로 span까지 반복하지 말고 그 안의 텍스트만 분리하는 식으로 코드의 양 자체를 줄일 수 있습니다.
- 3항 연산자가 중첩으로 사용될 경우 차라리 위에서 변수로 선언하고 return문을 간단하게 유지하는 것도 좋은 방법입니다.
|
||
// 로컬 저장소에 카테고리별 상품 갯수를 가져옴 / 없는 경우 10개 | ||
const skeletonLength = new Array( | ||
JSON.parse(localStorage.getItem(category ? category : 'all') ?? '10') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
삼항연산자가 자기 자신을 포함하면 or로 처리할 수 있습니다.
JSON.parse(localStorage.getItem(category ? category : 'all') ?? '10') | |
JSON.parse(localStorage.getItem(category || 'all') ?? '10') |
if (res.statusCode === 200) { | ||
// 카테고리에 따라서 products을 setting | ||
const categoryFilteredProducts = (res.data as Product[]).filter( | ||
(product) => (category ? product.tags[0] === category : product) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
category가 false인 경우 굳이 filter를 돌리지 않고 res.data를 바로 사용해도 좋아보입니다.
const files = (event.target as HTMLInputElement).files as FileList; | ||
const reader = new FileReader(); | ||
reader.readAsDataURL(files[0]); | ||
reader.onloadend = () => { | ||
setProductInputData((prevData) => ({ | ||
...prevData, | ||
[name]: reader.result as string, | ||
})); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 로직은 따로 함수로 만들어 재사용가능하게 만드는 것이 좋아보입니다.
!Number(productInputData.discountRate) || | ||
Number(productInputData.discountRate) <= 0 || | ||
Number(productInputData.discountRate) >= 100 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
조건이 and를 사용하는 것이 더 깔끔해 보입니다.
!Number(productInputData.discountRate) || | |
Number(productInputData.discountRate) <= 0 || | |
Number(productInputData.discountRate) >= 100 | |
!(Number(productInputData.discountRate) >= 0 && | |
Number(productInputData.discountRate) < 100) |
toast.error(res.message, { id: 'getBankList' }); | ||
} | ||
fetchData(); | ||
}, [userInfo?.accessToken]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
accessToken이 바뀌지 않아도 bankList가 바뀔 가능성이 있지 않을까요?
<table className="table-zebra table table-fixed text-center"> | ||
<thead className="text-sm text-black"> | ||
<tr> | ||
<th>상품 이미지</th> | ||
<th>고객</th> | ||
<th>상품 이름</th> | ||
<th>가격(원)</th> | ||
<th>거래 시간</th> | ||
<th>거래 취소</th> | ||
<th>거래 완료</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{filteredTransactions.map((transaction, index) => ( | ||
<tr key={index}> | ||
<td> | ||
<img | ||
src={transaction.product.thumbnail || '/defaultThumb.jpg'} | ||
alt="thumbnail" | ||
/> | ||
</td> | ||
<td>{transaction.user.displayName}</td> | ||
<td>{transaction.product.title}</td> | ||
<td>{transaction.product.price.toLocaleString('ko-KR')}</td> | ||
<td>{convertToHumanReadable(transaction.timePaid)}</td> | ||
<td>{transaction.isCanceled ? '취소함' : '취소하지 않음'}</td> | ||
<td>{transaction.done ? '🔘' : '❌'}</td> | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
굉장히 동일한 DOM의 반복은, 스타일만 가진 컴포넌트로 재사용성을 높일 수 있습니다.
if ( | ||
Number(detailProduct.discountRate) < 0 || | ||
Number(detailProduct.discountRate) >= 100 | ||
) { | ||
toast.error('할인율은 0 ~ 99를 입력해주세요.', { id: 'updateProduct' }); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
할인율이 NaN인 경우를 체크하지 못하고 있습니다.
명백한 범위가 있는 경우 and와 not으로 체크를 더 확실히 하는 것이 좋습니다.
🤝 패스트캠퍼스 FE5 쇼핑몰 팀프로젝트
배포주소
https://kdt-5-m5-crazy11.vercel.app
개발팀 소개
상품 상세페이지
구매확정
구매내역
구매취소
상품 추가
상품 수정
거래내역
상품 검색
상품 배치
스타일링
시작 가이드
Installation
사용한 기술, 라이브러리
Environment
Config
Development
: 전역 상태관리
: 팝업 안내 메시지
: 이미지 슬라이더
화면 구성
고찰, 느낀점
상태관리 툴
팀원 내 입문자를 배려하여 상대적으로 사용이 쉬운 ZUSTAND를 사용
context wrapping하는 과정이 필요하지 않음
src/store.ts
관리자 확인
로그인 시 서버로 부터 받는 데이터는 아래와 같으며 해당 정보로는 관리자 여부를 알 수 없다.
따라서 클라이언트 단에서 관리자 여부를 확인하고 isAdmin property를 추가하여 전역상태와 로컬저장소에 저장한다.
이 방법은 보안상 위험하지만 다음과 같은 대응 전략을 취할 수 있다.
비건전한 사용자가 local storage에 접근하여 isAdmin을 true로 바꿀 경우
👉 관리자만 접근 할 수 있는 route 분기점에 인증 api를 사용하여 사용자의 신원을 확인한다.
src/routes/admin/Admin.tsx
비건전한 사용자가 파일에 저장된 관리자 이메일 주소를 보는 경우
👉 관리자의 메일 주소를 알더라도 비밀번호는 모르기 때문에 괜찮다. 관리자 메일 주소를 환경변수에 저장하는 방법도 있다.
부족한 상품 정보
상품의 스키마는 아래와 같으며 본 프로젝트에서 필요한 'category'와 'brand' 항목이 없다.
tags 항목에서 배열의 첫번째 요소를 category, 두번째 요소를 brand로 지정하였다.
라우트 보호
로그인 상태, 관리자 여부에 따라서 접근할 수 있는 페이지를 제한해야 한다.
ProdtectedRoute에서 전역 User 상태와 adminRequired props 속성에 따라서 접근을 제한하게 하였다.
src/routes/ProtectedRoute.tsx
상태에 따른 UI의 동적 변화
첫 협업 프로젝트
디렉토리 구조