-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/139/gathering card UI - 모달 API 연결 #141
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,90 +1,39 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useCallback, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useCallback, useEffect, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from 'react-toastify'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ApiError } from 'next/dist/server/api-utils'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useGetGatheringDetailQuery } from '@/src/_queries/gathering/gathering-detail-queries'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import GatheringDetailModalContainer from '@/src/app/(crew)/crew/detail/[id]/_components/gathering-detail-modal/container'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { GatheringCardProps } from '@/src/types/gathering-data'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ScheduledGatheringCardPresenter from './presenter'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface ScheduledGatheringCardContainerProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| crewTitle: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| crewMainLocation: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| crewSubLocation: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dateTime: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentCount: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| totalCount: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imageUrl: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| liked: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function ScheduledGatheringCardContainer({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: ScheduledGatheringCardContainerProps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function ScheduledGatheringCardContainer({ data }: { data: GatheringCardProps }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isOpened, setIsOpened] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: modalData 연결 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // const { data: modalData } = useQuery<GatheringDetailType>(useGetGatheringQuery()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const dummyModalData = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| crewId: 7, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: '신나는 운동...즐거운..코딩..', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| introduce: '공지사항입니다. 다들 이번 약속 잊지 않으셨죠? 꼭 참여 부탁드립니다~', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dateTime: '2024-10-29T00:32:12.306Z', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location: '서울시 강남구 역삼동 오피스타워 3층', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentCount: 3, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| totalCount: 10, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imageUrl: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'https://www.dabur.com/Blogs/Doshas/Importance%20and%20Benefits%20of%20Yoga%201020x450.jpg', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| liked: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gatheringCaptain: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| participant: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| participants: [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| profileImageUrl: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUMrcQB5OJ-ETzPc6wHnjxjC-36__MGw3JcA&s', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nickname: '럽윈즈올', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email: '[email protected]', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: 2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| profileImageUrl: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'https://imgcdn.stablediffusionweb.com/2024/5/13/c0541236-e690-4dff-a27e-30a0355e5ea0.jpg', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nickname: '모닝러너', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email: '[email protected]', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: 3, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| profileImageUrl: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUMrcQB5OJ-ETzPc6wHnjxjC-36__MGw3JcA&s', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nickname: '동글동글이', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| email: '[email protected]', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: gatheringData, error } = useGetGatheringDetailQuery(data.crewId, data.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleCardClick = useCallback(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsOpened(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: retry or refetch사용해서 useQuery부분 안으로 넣기 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error instanceof ApiError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const errorData = JSON.parse(error.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (errorData.status === 'NOT_FOUND') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error('모임 정보를 찾을 수 없습니다.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error('데이터 통신에 실패했습니다.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [error]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 에러 처리 로직을 더 안전하게 개선해야 합니다 현재 구현에는 다음과 같은 잠재적인 문제가 있습니다:
다음과 같이 개선해보세요: useEffect(() => {
if (error) {
if (error instanceof ApiError) {
- const errorData = JSON.parse(error.message);
- if (errorData.status === 'NOT_FOUND') {
+ try {
+ const errorData = JSON.parse(error.message);
+ if (typeof errorData === 'object' && errorData?.status === 'NOT_FOUND') {
toast.error('모임 정보를 찾을 수 없습니다.');
+ } else {
+ toast.error('알 수 없는 에러가 발생했습니다.');
+ }
+ } catch {
+ toast.error('에러 처리 중 문제가 발생했습니다.');
}
} else {
toast.error('데이터 통신에 실패했습니다.');
}
}
}, [error]);📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleLikeToggle = useCallback(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert('좋아요 토글됨'); // TODO: 좋아요 토글 로직 구현 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleCardClick = useCallback(() => setIsOpened(true), []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleCloseModal = useCallback(() => setIsOpened(false), []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ScheduledGatheringCardPresenter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data={data} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleCardClick} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onLikeToggle={handleLikeToggle} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {dummyModalData && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ScheduledGatheringCardPresenter data={data} onClick={handleCardClick} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {gatheringData && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <GatheringDetailModalContainer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| opened={isOpened} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| close={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsOpened(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data={dummyModalData} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| close={handleCloseModal} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data={gatheringData} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
30
to
39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 로딩 상태 처리가 필요합니다. 현재 구현에서 데이터 로딩 중일 때의 UI 처리가 누락되어 있습니다. 사용자 경험 향상을 위해 다음 사항들을 고려해보세요:
다음과 같이 개선해볼 수 있습니다: + const { data: gatheringData, error, isLoading } = useGetGatheringDetailQuery(data.crewId, data.id);
return (
<div>
<ScheduledGatheringCardPresenter data={data} onClick={handleCardClick} />
+ {isLoading && <LoadingSpinner />}
{gatheringData && (
<GatheringDetailModalContainer
opened={isOpened}
close={handleCloseModal}
data={gatheringData}
/>
)}
</div>
);
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
서버 사이드 유틸리티를 클라이언트에서 사용하고 있습니다
next/dist/server/api-utils의ApiError는 서버 사이드 전용 유틸리티입니다. 클라이언트 사이드에서는 커스텀 에러 클래스를 정의하여 사용하는 것이 좋습니다.다음과 같이 수정해보세요:
📝 Committable suggestion