Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

서버 사이드 유틸리티를 클라이언트에서 사용하고 있습니다

next/dist/server/api-utilsApiError는 서버 사이드 전용 유틸리티입니다. 클라이언트 사이드에서는 커스텀 에러 클래스를 정의하여 사용하는 것이 좋습니다.

다음과 같이 수정해보세요:

-import { ApiError } from 'next/dist/server/api-utils';

+class ApiError extends Error {
+  constructor(public status: string, message: string) {
+    super(message);
+    this.name = 'ApiError';
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { ApiError } from 'next/dist/server/api-utils';
class ApiError extends Error {
constructor(public status: string, message: string) {
super(message);
this.name = 'ApiError';
}
}

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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

에러 처리 로직을 더 안전하게 개선해야 합니다

현재 구현에는 다음과 같은 잠재적인 문제가 있습니다:

  1. JSON.parse가 실패할 수 있습니다
  2. errorData 타입 검증이 없습니다

다음과 같이 개선해보세요:

  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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (error) {
if (error instanceof ApiError) {
const errorData = JSON.parse(error.message);
if (errorData.status === 'NOT_FOUND') {
toast.error('모임 정보를 찾을 수 없습니다.');
}
} else {
toast.error('데이터 통신에 실패했습니다.');
}
}
}, [error]);
useEffect(() => {
if (error) {
if (error instanceof ApiError) {
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]);


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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

로딩 상태 처리가 필요합니다.

현재 구현에서 데이터 로딩 중일 때의 UI 처리가 누락되어 있습니다. 사용자 경험 향상을 위해 다음 사항들을 고려해보세요:

  1. 로딩 상태 표시
  2. 에러 바운더리 추가

다음과 같이 개선해볼 수 있습니다:

+ 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>
  );

Committable suggestion skipped: line range outside the PR's diff.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ interface ScheduledGatheringCardPresenterProps {
liked: boolean;
};
onClick: () => void;
onLikeToggle: () => void;
}

export default function ScheduledGatheringCardPresenter({
data,
onClick,
onLikeToggle,
}: ScheduledGatheringCardPresenterProps) {
const {
id,
Expand Down Expand Up @@ -96,9 +94,6 @@ export default function ScheduledGatheringCardPresenter({
참여인원 {currentCount}/{totalCount}
</p>
</div>
<div className="absolute bottom-6 right-6">
<LikeBtn id={id} isLiked={liked} onLikeToggle={onLikeToggle} size={40} />
</div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const LikedEvent: Story = {
args: {
data: {
id: 1,
crewId: 1,
crewTitle: '풀 엔 그레이스 스튜디오',
crewMainLocation: '서울시',
crewSubLocation: '강남구 역삼동',
Expand All @@ -42,6 +43,7 @@ export const NotLikedEvent: Story = {
args: {
data: {
id: 2,
crewId: 1,
crewTitle: '산악회 모임',
crewMainLocation: '서울시',
crewSubLocation: '용산구 한강로',
Expand Down
1 change: 1 addition & 0 deletions src/types/gathering-data.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface CreateGatheringRequestTypes {

export interface GatheringCardProps {
id: number;
crewId: number;
crewTitle: string;
crewMainLocation: string;
crewSubLocation: string;
Expand Down
Loading