-
Notifications
You must be signed in to change notification settings - Fork 44
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
경북대 FE_이정민 6주차 과제 Step1,2 #76
base: userjmmm
Are you sure you want to change the base?
경북대 FE_이정민 6주차 과제 Step1,2 #76
Conversation
안녕하세요~ |
06f4aa6
to
d168557
Compare
충돌 이슈를 해결 못 한 걸 모르고 PR을 올렸네요 죄송합니다ㅠㅠ. 현재 충돌 해결 후 step3 브랜치도 rebase한 상태입니다. �알려주셔서 감사합니다! |
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.
�수고하셨습니다~! 👍
리뷰 남깁니다!
let BASE_URL = localStorage.getItem('baseURL') || 'https://api.example.com'; | ||
|
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.
let으로 선언하는 이유가 무엇일까요?
BASE_URL이 변경될 수 있는건가요?
해당 파일 내에서 let이 자주 사용되는데, 변경의 추적이 어려우므로 const를 사용해 주세요!
export const getCategories = async (): Promise<CategoryData[]> => { | ||
let allCategories: CategoryData[] = []; | ||
let currentPage = 0; | ||
let hasNextPage = true; | ||
|
||
export const getCategoriesPath = () => `${BASE_URL}/api/categories`; | ||
const categoriesQueryKey = [getCategoriesPath()]; | ||
while (hasNextPage) { | ||
const response = await fetchInstance.get<CategoryResponseData>( | ||
`${getCategoriesPath()}?page=${currentPage}&size=100` | ||
); | ||
allCategories = [...allCategories, ...response.data.content]; | ||
hasNextPage = !response.data.last; | ||
currentPage++; | ||
} |
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.
currentPage, hasNextPage가 getCategories 함수의 책임일까요?
const [counts, setCounts] = useState<CountState>(() => { | ||
const initialCounts: CountState = {}; | ||
if (options) { | ||
options.forEach(option => { | ||
initialCounts[option.id] = '0'; | ||
}); | ||
} | ||
return initialCounts; | ||
}); | ||
|
||
useEffect(() => { | ||
if (options) { | ||
setCounts(prevCounts => { | ||
const newCounts = { ...prevCounts }; | ||
options.forEach(option => { | ||
if (!newCounts[option.id]) { | ||
newCounts[option.id] = '0'; | ||
} | ||
}); | ||
return newCounts; | ||
}); | ||
} | ||
}, [options]); | ||
|
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.
해당 작업이 어떤 역할인지 잘 모르겠네요!
알아볼 수 있도록 적절한 함수명을 가진 함수로 분리하여 사용하거나, 셀프 리뷰를 통해 미리 어떤 역할의 함수인지 코멘트를 남겨두는 방법도 있으니 사용해 보시면 좋을 것 같습니다~
try { | ||
const response = await fetch('/api/wishes', { | ||
const requestBody = { | ||
productId: parseInt(productId, 10), | ||
quantity: null, | ||
}; | ||
|
||
console.log('Sending request to add wish', requestBody); // 잘 받아오는지 확인 - 추후 삭제 예정 | ||
|
||
const response = await fetch(`${getBaseUrl()}/api/wishes`, { | ||
method: 'POST', | ||
headers: { | ||
Authorization: `Bearer ${authInfo.token}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ | ||
productId: parseInt(productId, 10), | ||
name: detail?.name, | ||
price: detail?.price, | ||
imageUrl: detail?.imageUrl, | ||
}), | ||
body: JSON.stringify(requestBody), | ||
}); |
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.
이 부분은 왜 react-query, axios를 쓰지 않고 fetch를 쓰셨나요?
const [selectedApi, setSelectedApi] = useState('default'); | ||
|
||
useEffect(() => { | ||
const storedBaseUrl = localStorage.getItem('baseURL'); | ||
if (storedBaseUrl) { | ||
setSelectedApi(storedBaseUrl); | ||
updateBaseUrl(storedBaseUrl); | ||
} | ||
}, []); | ||
|
||
const handleLogin = () => { | ||
navigate(getDynamicPath.login()); | ||
}; | ||
|
||
const handleApiChange = (event: ChangeEvent<HTMLSelectElement>) => { | ||
const newApi = event.target.value; | ||
setSelectedApi(newApi); | ||
localStorage.setItem('baseURL', newApi); | ||
updateBaseUrl(newApi); | ||
queryClient.invalidateQueries(); | ||
}; |
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.
아래 코드를 보니 여러 명이 개발한 api를 사용하기 위해서 작성해두신 것으로 보이네요.
다만 제가 느끼기에는 그냥 instance에 base url을 선언하고 쓰지 않고. 전체 url을 그때그때 받아서 사용하는 방식으로 쓰는게 나을 것 같습니다.
base url이 변경되는 것에 영향을 받는 코드가 프로젝트 여기저기에 퍼져있어 좋지 않을 것 같아요
const orders = orderHistorySessionStorage.get() as OrderHistory[]; | ||
|
||
const requests = orders.map(order => { | ||
const requestBody = { | ||
optionId: order.optionId, | ||
quantity: order.quantity, | ||
message: values.message, | ||
}; | ||
|
||
console.log('서버로 전송하는 요청 데이터:', JSON.stringify(requestBody, null, 2)); | ||
|
||
return fetch(`${getBaseUrl()}/api/orders`, { | ||
method: 'POST', | ||
headers: { | ||
Authorization: `Bearer ${authInfo?.token}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify(requestBody), | ||
credentials: 'include', | ||
}) | ||
.then(async response => { | ||
if (!response.ok) { | ||
const errorBody = await response.text(); | ||
console.error(`옵션 ID ${order.optionId} 주문 실패:`, response.status, errorBody); | ||
return { success: false, optionId: order.optionId, error: errorBody }; | ||
} | ||
const data = await response.json(); | ||
console.log(`옵션 ID ${order.optionId} 주문 성공:`, data); | ||
return { success: true, optionId: order.optionId, data }; | ||
}) | ||
.catch(error => { | ||
console.error(`옵션 ID ${order.optionId} 주문 에러:`, error); | ||
return { success: false, optionId: order.optionId, error: error.message }; | ||
}); | ||
}); | ||
|
||
try { | ||
const results = await Promise.all(requests); | ||
const successfulOrders = results.filter(result => result.success); | ||
const failedOrders = results.filter(result => !result.success); | ||
|
||
if (successfulOrders.length > 0) { | ||
console.log('성공한 주문:', successfulOrders); | ||
alert(`${successfulOrders.length}개의 주문이 완료되었습니다.`); | ||
} | ||
|
||
if (failedOrders.length > 0) { | ||
console.error('실패한 주문:', failedOrders); | ||
alert(`${failedOrders.length}개의 주문 중 오류가 발생했습니다.`); | ||
} | ||
} catch (error) { | ||
console.error('주문 처리 중 예외 발생:', error); | ||
alert('주문 처리 중 예기치 않은 오류가 발생했습니다.'); | ||
} | ||
}; |
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.
해당 함수의 가독성이 좋지 않네요...
분기가 매우 많고, 명령형으로 처리되고 있으니 개선해 보시면 좋을 것 같아요~
const [userName, setUserName] = useState<string>("사용자"); | ||
|
||
useEffect(() => { | ||
const fetchWishlist = async () => { | ||
if (authInfo) { | ||
try { | ||
const response = await fetch(`/api/wishes?page=${page}&size=10&sort=createdDate,desc`, { | ||
const [headerEncoded, payloadEncoded, signature] = authInfo.split('.'); | ||
const payload = JSON.parse(base64.decode(payloadEncoded)); | ||
|
||
console.log('Token Header:', JSON.parse(base64.decode(headerEncoded))); | ||
console.log('Token Payload:', payload); | ||
console.log('Token Signature:', signature); | ||
console.log('Payload Keys:', Object.keys(payload)); | ||
|
||
if (payload.sub) { | ||
const email = payload.sub; | ||
const displayName = email.split('@')[0]; | ||
setUserName(displayName); | ||
} | ||
} catch (error) { | ||
console.error('토큰 디코딩 에러:', error); | ||
} | ||
} else { | ||
setUserName("사용자"); | ||
} | ||
}, [authInfo]); |
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.
토큰을 클라이언트에서 디코드하여 유저 정보를 확인하는 것으로 보여요.
이건 백엔드와 협의했다면 어쩔 수 없는 부분이니 넘어가고...
디코딩하는 부분을 추상화하면 어떨까 싶네요!
const getWishlistPath = () => `${getBaseUrl()}/api/wishes`; | ||
|
||
const fetchWishlist = async (page: number) => { | ||
try { | ||
const response = await fetchInstance.get<WishlistResponseData>( | ||
`${getWishlistPath()}?page=${page}&size=10&sort=createdDate,desc`, | ||
{ | ||
headers: { | ||
Authorization: `Bearer ${authInfo.token}`, | ||
Authorization: `Bearer ${authInfo}`, | ||
}, | ||
}); | ||
|
||
if (response.status === 200) { | ||
const data = await response.json(); | ||
setWishlist(data.content); | ||
setTotalPages(data.totalPages); | ||
} else if (response.status === 401) { | ||
alert('토큰이 유효하지 않습니다. 다시 로그인해주세요.'); | ||
} else { | ||
alert('위시 리스트를 불러오는데 실패했습니다.'); | ||
withCredentials: true, | ||
} | ||
} catch (error) { | ||
console.error('위시 리스트 조회 에러:', error); | ||
alert('위시 리스트 조회 에러가 발생했습니다.'); | ||
); | ||
|
||
if (response.status === 200) { | ||
const data = response.data; | ||
console.log('Response data:', data); | ||
setWishlist(data.content); | ||
setTotalPages(data.totalPages); | ||
} else if (response.status === 403) { | ||
alert('토큰이 유효하지 않습니다. 다시 로그인해주세요.'); | ||
} else { | ||
alert('위시 리스트를 불러오는데 실패했습니다.'); | ||
} | ||
}; | ||
} catch (error) { | ||
console.error('위시 리스트 조회 에러:', error); | ||
alert('위시 리스트 조회 에러가 발생했습니다.'); | ||
} | ||
}; |
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.
여기는 왜 react-query를 사용하지 않았나요?
useEffect(() => { | ||
fetchWishlist(page); | ||
}, [page, authInfo]); |
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.
useEffect의 의존성 배열 규칙을 지켜주세요~!!
안녕하세요 멘토님,
백엔드와 API를 맞추면서 기능과 코드가 많이 수정되었습니다.
https://zzoe2346.github.io/api API 명세를 정리한 문서를 함께 첨부합니다!
배포는 네이버 클라우드 서버를 사용해서 진행했고, 배포된 사이트는 http://101.101.216.221:3000/ 에서 확인 가능합니다.
6주 동안 소중한 시간 내어 코드 리뷰를 제공해주셔서 감사합니다 :)