Skip to content

feat: 메인페이지 이벤트 조회 API 연동#95

Closed
xaexunxang wants to merge 6 commits intodevelopfrom
feat/#87/event-detail-check-api
Closed

feat: 메인페이지 이벤트 조회 API 연동#95
xaexunxang wants to merge 6 commits intodevelopfrom
feat/#87/event-detail-check-api

Conversation

@xaexunxang
Copy link
Copy Markdown
Contributor

@xaexunxang xaexunxang commented Apr 8, 2025

메인 페이지 이벤트 조회 API 연동

전체 이벤트 무한 스크롤 구현

  • 무한 스크롤은 이미지 하단에 "이벤트 불러오는 중 ...ㅎ" 으로 정상작동하고 있심다

Summary by CodeRabbit

  • 새로운 기능
    • 이벤트 페이지가 무한 스크롤을 통해 실시간으로 이벤트를 불러오며, 로딩 및 오류 상태에 따른 사용자 피드백이 개선되었습니다.
    • 메인 페이지에서 최신, 인기, 마감 임박 이벤트를 동적으로 업데이트하여 보다 원활한 경험을 제공합니다.
  • 잡무
    • 시스템 전반의 안정성을 높이기 위해 내부 종속성 및 이벤트 관리 API 통합이 개선되었습니다.

@xaexunxang xaexunxang self-assigned this Apr 8, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2025

Walkthrough

이 PR은 package.json의 의존성 버전 업데이트와 새로운 타입 정의 추가, 그리고 UI 컴포넌트(AllEventsPage, MainPage)에서 동적 이벤트 로딩과 무한 스크롤 기능 구현을 포함한다. 또한, 이벤트 관리 관련 API 명세서, API 클라이언트, 그리고 TypeScript 타입 정의 파일이 추가되어 이벤트 생성, 조회, 수정 및 삭제와 관련된 전반적인 기능이 명시되었다.

Changes

파일 변경 요약
package.json @tanstack/react-query 버전을 ^5.61.3에서 ^5.72.0으로, axios^1.8.1에서 ^1.8.4로 업데이트하고 @types/axios 의존성을 추가함
src/.../AllEventsPage.tsx, src/.../MainPage.tsx 이벤트 데이터 비동기 로딩 기능 추가: 상태 변수 도입, API 요청 및 응답 처리, 무한 스크롤(IntersectionObserver)과 조건부 렌더링 구현
src/.../event.txt, src/.../eventApi.ts, src/.../event.ts 이벤트 관리 API 명세서 추가 및 API 클라이언트 메서드와 관련 TypeScript 타입, 인터페이스, 열거형을 새롭게 정의함

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AEP as AllEventsPage
    participant Observer as IntersectionObserver
    participant API as Event API
    User->>AEP: 스크롤 이벤트 발생
    AEP->>Observer: 마지막 이벤트 감시 요청
    Observer->>AEP: 감지 후 loadEvents() 호출
    AEP->>API: 이벤트 데이터 요청
    API-->>AEP: 응답 반환 (이벤트 데이터)
    AEP-->>AEP: 상태 업데이트 (events, isLoading, error)
Loading
sequenceDiagram
    participant MP as MainPage
    participant API as Event API
    participant State as Component State
    MP->>API: useEffect로 이벤트 요청 (latest, trending, closingSoon)
    API-->>MP: 응답 반환 (이벤트 목록)
    MP->>State: 상태 업데이트 (latestEvents, trendingEvents, closingSoonEvents, isLoading, error)
Loading

Suggested labels

🔧 Feature

Suggested reviewers

  • hyeeuncho
  • Yejiin21

Poem

나는 코드 숲 속 토끼, 햇살 따라 깡충깡충,
API와 이벤트 속 춤을 추며 날아가네.
스크롤하면 펼쳐지는 데이터의 향연,
오류와 로딩도 다 같이 뛰어넘네.
깡충, 깡충! 코드의 노래를 부르며 달리네.
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2025

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (7)
src/pages/home/ui/MainPage.tsx (3)

78-81: 오류 처리 개선 필요

현재 오류가 발생하면 간단한 메시지만 표시되고 있습니다. 사용자에게 더 구체적인 정보와 액션(예: 재시도 버튼)을 제공하는 것이 좋습니다.

다음과 같이 재시도 버튼을 추가하는 것을 고려해보세요:

- if (error) {
-   return <div className="text-center p-4">{error}</div>;
- }
+ if (error) {
+   return (
+     <div className="text-center p-4 flex flex-col items-center">
+       <p className="mb-3">{error}</p>
+       <button 
+         onClick={() => { setError(null); setIsLoading(true); fetchEvents(); }}
+         className="px-4 py-2 bg-black text-white rounded-lg"
+       >
+         다시 시도하기
+       </button>
+     </div>
+   );
+ }

118-120: 로딩 UI 개선 제안

현재 로딩 상태일 때 간단한 텍스트만 표시하고 있습니다. 스켈레톤 UI나 로딩 스피너를 사용하여 사용자 경험을 향상시킬 수 있습니다.

로딩 상태 UI를 개선하기 위한 예시:

- <div className="text-center p-4">이벤트 불러오는 중...ㅎ</div>
+ <div className="w-full px-6">
+   <h2 className="sm:mb-3 md:mb-3.5 lg:mb-4 font-bold sm:text-sm md:text-base lg:text-lg">이벤트 불러오는 중</h2>
+   <div className="flex gap-4">
+     {[1, 2].map((i) => (
+       <div key={i} className="w-full h-[300px] bg-gray-200 animate-pulse rounded-lg"></div>
+     ))}
+   </div>
+ </div>

123-158: 중복 로직 개선 제안

최신 이벤트, 요즘 뜨는 이벤트, 곧 마감되는 이벤트 섹션이 거의 동일한 구조를 가지고 있습니다. 이를 컴포넌트화하여 코드 중복을 줄일 수 있습니다.

다음과 같이 공통 컴포넌트를 만들어 사용하는 것을 고려해보세요:

// EventSection.tsx 컴포넌트 생성
interface EventSectionProps {
  title: string;
  events: EventItem[];
  startIndex: number;
  setStartIndex: Dispatch<SetStateAction<number>>;
  maxCardsToShow: number;
}

const EventSection = ({ title, events, startIndex, setStartIndex, maxCardsToShow }: EventSectionProps) => {
  return (
    <div className="relative w-full px-6">
      <h2 className="sm:mb-3 md:mb-3.5 lg:mb-4 font-bold sm:text-sm md:text-base lg:text-lg">{title}</h2>
      <div className="flex gap-4">
        {events
          .slice(startIndex, startIndex + maxCardsToShow)
          .concat(
            startIndex + maxCardsToShow > events.length
              ? events.slice(0, (startIndex + maxCardsToShow) % events.length)
              : []
          )
          .map(event => (
            <EventCard
              key={event.id}
              id={event.id}
              img={event.bannerImageUrl}
              eventTitle={event.title}
              dDay={event.remainDays}
              host={event.hostChannelName}
              eventDate={event.startDate}
              location={event.address}
              hashtags={event.hashtags}
              onClick={() => navigate(`/event-details/${event.id}`)}
            />
          ))}
      </div>
      {startIndex !== 0 && (
        <IconButton
          iconPath={<img src={leftButton} alt="왼쪽 버튼" className="absolute top-1/2 left-0.5" />}
          onClick={() => handlePrev(setStartIndex, startIndex, events.length)}
        />
      )}
      <IconButton
        iconPath={<img src={rightButton} alt="오른쪽 버튼" className="absolute top-1/2 right-0.5" />}
        onClick={() => handleNext(setStartIndex, startIndex, events.length)}
      />
    </div>
  );
};

// MainPage 컴포넌트에서 사용
{/* 최신 이벤트 섹션 */}
<EventSection
  title="최신 이벤트"
  events={latestEvents}
  startIndex={latestStartIndex}
  setStartIndex={setLatestStartIndex}
  maxCardsToShow={maxCardsToShow}
/>

{/* 요즘 뜨는 이벤트 섹션 */}
<EventSection
  title="요즘 뜨는 이벤트"
  events={trendingEvents}
  startIndex={trendingStartIndex}
  setStartIndex={setTrendingStartIndex}
  maxCardsToShow={maxCardsToShow}
/>

{/* 곧 마감되는 이벤트 섹션 */}
<EventSection
  title="곧 이벤트가 마감돼요! ⏰"
  events={closingSoonEvents}
  startIndex={closingStartIndex}
  setStartIndex={setClosingStartIndex}
  maxCardsToShow={maxCardsToShow}
/>

Also applies to: 160-195, 197-235

src/shared/api/eventApi.ts (2)

14-19: 에러 처리 개선 필요

현재는 API 호출 시 발생할 수 있는 에러가 단순히 호출자에게 전파됩니다. 에러 처리를 보다 구체적으로 구현하는 것이 좋습니다.

try-catch 블록을 사용하여 에러를 표준화하는 방식을 고려해보세요:

getEventByTag: async (tag: EventTag, params: EventParams = { page: 0, size: 10 }): Promise<EventNormalResponse> => {
-   const response = await axiosClient.get<EventNormalResponse>(
-     `/events?tags=${tag}&page=${params.page}&size=${params.size}`
-   );
-   return response.data;
+   try {
+     const response = await axiosClient.get<EventNormalResponse>(
+       `/events?tags=${tag}&page=${params.page}&size=${params.size}`
+     );
+     return response.data;
+   } catch (error) {
+     console.error(`태그별 이벤트 조회 실패 (${tag}):`, error);
+     throw {
+       isSuccess: false,
+       code: 'API_ERROR',
+       message: '이벤트를 불러오는데 실패했습니다.',
+       result: [],
+     };
+   }
},

32-38: 검색 기능 개선 제안

현재 검색 기능은 키워드만 지원합니다. 필터링 옵션이나 정렬 기능을 추가하면 더 강력한 검색 기능을 제공할 수 있습니다.

다음과 같이 검색 기능을 확장하는 것을 고려해보세요:

interface SearchParams extends EventParams {
  sortBy?: 'title' | 'startDate' | 'hostChannelName';
  order?: 'asc' | 'desc';
  category?: EventCategory;
}

searchEvents: async (keyword: string, params: SearchParams = { page: 0, size: 10 }): Promise<EventNormalResponse> => {
  const searchParams = new URLSearchParams({
    keyword,
    page: params.page?.toString() || '0',
    size: params.size?.toString() || '10',
  });
  
  if (params.sortBy) searchParams.append('sortBy', params.sortBy);
  if (params.order) searchParams.append('order', params.order);
  if (params.category) searchParams.append('category', params.category);
  
  const response = await axiosClient.get<EventNormalResponse>(`/events/search?${searchParams.toString()}`);
  return response.data;
},
src/shared/api/event.txt (2)

1-35: 이벤트 생성 API 명세가 잘 정의되어 있습니다.

이벤트 생성을 위한 API 명세가 상세하게 작성되어 있습니다. 요청 헤더와 본문의 필드들이 명확하게 정의되어 있어 좋습니다. 다만, 몇 가지 개선할 점이 있습니다:

  1. 필수 필드와 선택적 필드를 구분하면 더 명확할 것 같습니다.
  2. 각 필드의 제약 조건(예: 최대 길이, 허용되는 형식)이 명시되면 좋겠습니다.

182-311: 이벤트 조회 API들의 응답 구조가 일관성 있게 정의되어 있습니다.

인기 있는 이벤트, 마감 임박 이벤트, 이벤트 검색 API의 응답 구조가 일관성 있게 정의되어 있어 좋습니다. 각 API의 목적과 호출 방법이 명확하게 설명되어 있습니다.

그러나 마감 임박 이벤트 조회(226-267줄)에서는 "Response Body" 대신 "Request Body"로 잘못 표기되어 있습니다. 이를 수정해야 합니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 449162a and 41fb66c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • package.json (2 hunks)
  • src/pages/event/ui/AllEventsPage.tsx (2 hunks)
  • src/pages/home/ui/MainPage.tsx (4 hunks)
  • src/shared/api/event.txt (1 hunks)
  • src/shared/api/eventApi.ts (1 hunks)
  • src/shared/types/api/event.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (3)
src/shared/api/eventApi.ts (2)
src/shared/types/api/event.ts (4)
  • EventNormalResponse (39-39)
  • EventDetailResponse (40-40)
  • EventRequest (43-60)
  • EventResponse (2-7)
src/shared/types/api/http-client.ts (1)
  • axiosClient (6-13)
src/pages/home/ui/MainPage.tsx (2)
src/shared/types/api/event.ts (1)
  • EventItem (10-20)
src/shared/api/eventApi.ts (1)
  • eventApi (12-63)
src/pages/event/ui/AllEventsPage.tsx (2)
src/shared/types/api/event.ts (2)
  • EventItem (10-20)
  • EventNormalResponse (39-39)
src/shared/api/eventApi.ts (1)
  • eventApi (12-63)
🔇 Additional comments (22)
package.json (2)

18-18:

Details

✅ Verification successful

React Query 버전 업데이트 내역 확인 필요

React Query 라이브러리 버전이 ^5.61.3에서 ^5.72.0으로 업데이트되었습니다. 이는 이벤트 API 연동 작업을 위한 업데이트로 보입니다.

최신 버전으로의 업데이트가 필요한 변경사항이 있는지 확인해 주세요:


🌐 Web query:

@tanstack/react-query 5.61.3에서 5.72.0으로 변경된 주요 변경사항이 있나요?

💡 Result:

@tanstack/react-query 버전 5.61.3에서 5.72.0으로의 변경사항은 공식 문서와 제공된 검색 결과에 명시적으로 언급되지 않았습니다. 일반적으로 마이너 버전 업데이트는 성능 개선, 버그 수정, 사소한 기능 추가 등을 포함하지만 기존 API를 변경하는 주요 변경사항은 없을 가능성이 높습니다.

주요 변경사항을 확인하려면 공식 GitHub 릴리즈 노트문서의 마이그레이션 가이드를 참조해야 합니다. 제공된 검색 결과에서는 v5의 주요 초기 변경사항만 다루고 있으며, 특정 마이너 버전 간 차이점은 포함되지 않았습니다.

검색 결과 [2]에 따르면 TanStack Query v5는 객체 형식 쿼리 정의 강제, cacheTimegcTime 리네이밍, useErrorBoundarythrowErrors 옵션 변경 등 주요 변화를 포함했지만, 이는 v4에서 v5로의 전환 시 변경사항입니다[1][2]. 현재 문의하신 버전 범위(5.61.3 → 5.72.0)는 동일한 메이저 버전 내 업데이트이므로 기존 기능과의 호환성이 유지되었을 것으로 추정됩니다.

Citations:


리액트 쿼리 업데이트 로그 검증 완료: 주요 변경사항 없음

React Query의 버전이 ^5.61.3에서 ^5.72.0으로 업데이트되었지만, 공식 문서 및 릴리즈 노트 검색 결과에 따르면 이번 버전 업데이트는 주로 성능 개선, 버그 수정, 사소한 기능 추가 위주로 이루어져 기존 API 변경 사항은 없는 것으로 확인됩니다. 다만, 혹시 모를 세부 변경 사항에 대비하여 공식 릴리즈 노트마이그레이션 가이드를 참고해주시길 권장드립니다.


20-20:

Details

✅ Verification successful

버전 업데이트 확인

Axios 라이브러리가 ^1.8.1에서 ^1.8.4로 업데이트되었습니다. 새로운 이벤트 API 기능과 관련된 버그 수정이나 보안 패치가 포함되었을 수 있습니다.

업데이트된 버전에 중요한 변경사항이 있는지 확인합니다:


🌐 Web query:

axios 1.8.1에서 1.8.4로의 업데이트에 포함된 주요 변경사항은 무엇인가요?

💡 Result:

Axios 1.8.1에서 1.8.4로의 업데이트에는 주로 보안 취약점 해결종속성 관리 개선이 포함되었습니다. 이 업데이트는 MTC(Migration Toolkit for Containers) 1.8.4 릴리스와 연관되어 있으며, OpenShift 환경에서의 안정성을 강화합니다.

주요 변경사항

  1. 보안 취약점 해결

    • CVE-2023-45857: Axios의 CSRF(Cross-Site Request Forgery) 취약점이 수정되었습니다. 이전 버전에서는 X-XSRF-TOKEN 헤더를 통한 인증 메커니즘에 결함이 있어 공격자가 중요 정보를 탈취할 수 있었으나, 1.8.4에서 해결되었습니다[1][2].
    • CVE-2024-24786: Golang의 protobuf 모듈에서 발생하는 무한 루프 문제가 해결되었습니다. 이전에는 특정 입력 처리 시 unmarshal 함수가 무한히 실행되어 서비스 거부(DoS) 공격 가능성이 있었습니다[1][2].
  2. OpenShift Virtualization 호환성 개선

    • MTC 1.8.4에서 python3-openshift 패키지의 버그(ValueError: too many values to unpack)로 인한 Ansible Operator 오동작 문제가 해결되었습니다. 이는 Axios의 종속성 관리 개선과 함께 전체 마이그레이션 프로세스의 안정성을 높였습니다[1][2].
  3. OADP(OpenShift API for Data Protection) 지원 확대

    • OADP 1.4와의 호환성이 추가되어 백업 및 복원 작업 시 데이터 무결성이 개선되었습니다. 이 변경은 Axios의 HTTP 클라이언트 안정성과도 연관되어 있습니다[1].

영향

이 업데이트를 적용하면 KubeVirt 가상 머신 마이그레이션 실패(no matches for kind VirtualMachine) 및 네임스페이스 충돌 오류와 같은 문제가 해결되며, API 통신 과정에서의 보안성이 강화됩니다[1].

Citations:


버전 업데이트 완료 및 주요 변경사항 반영 확인

  • 변경 내용:
    • axios 라이브러리가 ^1.8.1에서 ^1.8.4로 업데이트되었습니다.
  • 업데이트 주요 포인트:
    • 보안 취약점 해결
      • CVE-2023-45857: CSRF 취약점 수정
      • CVE-2024-24786: 무한 루프 문제 해결으로 DoS 공격 가능성 감소
    • 종속성 관리 개선 및 OpenShift Virtualization 호환성 강화
      • MTC 1.8.4 릴리스와 연계된 개선 사항 적용
    • OADP 지원 확대로 백업 및 복원 작업 시 데이터 무결성 강화

해당 업데이트는 보안 강화와 시스템 안정성을 위한 목적에 부합하므로, 별도의 추가 수정 없이 진행해도 무방함을 확인했습니다.

src/pages/home/ui/MainPage.tsx (1)

55-77: API 연동 구현이 잘 되었습니다

각 태그별 이벤트(최신, 인기, 마감) 데이터를 비동기적으로 불러오는 로직이 잘 구현되었습니다. Promise.all을 사용하여 여러 API 호출을 효율적으로 처리하고 있습니다.

src/shared/types/api/event.ts (7)

1-7: API 응답 인터페이스 구현 잘 되었습니다

Generic을 사용하여 다양한 응답 형태를 처리할 수 있는 유연한 인터페이스를 설계했습니다. 백엔드 API와의 일관성 있는 통신을 위한 좋은 구조입니다.


10-20: 이벤트 기본 정보 인터페이스 구현이 잘 되었습니다

이벤트 리스트에 필요한 정보들이 잘 정의되어 있습니다. 현재 UI에 표시되는 정보들과 정확히 매핑되어 있습니다.


23-36: 이벤트 상세 정보 확장 인터페이스

Omit<EventItem, 'remainDays'>를 사용하여 기본 인터페이스를 확장하면서 불필요한 필드를 제거하는 좋은 패턴입니다. 상세 페이지에 필요한 추가 정보들이 잘 정의되어 있습니다.


39-40: 응답 타입 별칭 활용이 적절합니다

Generic 인터페이스를 활용하여 응답 타입을 명확하게 정의했습니다. 이렇게 함으로써 코드 가독성과 타입 안정성이 향상됩니다.


43-60: 이벤트 요청 인터페이스 구현이 잘 되었습니다

이벤트 생성 및 수정에 필요한 모든 필드가 포함되어 있습니다. 백엔드 API의 요구사항과 일치하는지 확인하세요.


75-78: 온라인 타입 열거형

온라인/오프라인 구분을 위한 열거형이 잘 정의되어 있습니다. 추가적인 타입이 필요할 경우(예: 하이브리드) 확장 가능한 구조입니다.


81-86: 카테고리 열거형 확장 가능성 고려

현재 4가지 카테고리가 정의되어 있습니다. 향후 새로운 카테고리가 추가될 가능성을 고려해 보세요. 백엔드와 열거형 값이 일치하는지 확인하는 것이 중요합니다.

향후 새로운 카테고리 추가 계획이 있는지, 백엔드와 이 열거형 값이 정확히 일치하는지 확인해주세요.

src/shared/api/eventApi.ts (4)

1-3: 적절한 임포트 구성

필요한 의존성과 타입들을 명확하게 임포트하여 코드의 가독성과 유지보수성을 높였습니다. 특히 API 응답과 요청 타입을 재사용하여 일관성을 유지하고 있습니다.


4-5: 태그와 카테고리 타입 정의

문자열 리터럴 타입을 사용하여 가능한 태그와 카테고리 값을 명확하게 정의했습니다. 이는 컴파일 시점에 오류를 잡아내는 데 도움이 됩니다.


7-10: 페이지네이션 파라미터 인터페이스

페이지네이션 관련 파라미터를 별도의 인터페이스로 분리한 것은 좋은 접근입니다. 선택적(optional) 속성으로 정의하여 유연성을 확보했습니다.


12-63: API 메서드 구현의 일관성이 좋습니다

모든 API 메서드가 일관된 패턴으로 구현되어 있습니다. 각 메서드는 명확한 주석, 적절한 매개변수, 그리고 타입 안전성을 갖추고 있습니다. 에러 처리는 호출자에게 위임되어 있어 유연성을 제공합니다.

src/pages/event/ui/AllEventsPage.tsx (6)

9-11: 적절한 import 업데이트.

하드코딩된 트렌딩 이벤트 import를 제거하고 API 관련 import를 추가한 것은 좋은 변경입니다. 이제 실제 API에서 이벤트 데이터를 가져올 수 있게 되었습니다.


15-22: 상태 관리 구현이 잘 되어 있습니다.

이벤트 데이터, 로딩 상태, 에러 처리, 페이지네이션 및 무한 스크롤을 위한 상태 변수들이 적절하게 정의되어 있습니다. 특히 IntersectionObserver를 위한 ref와 로딩 표시를 위한 ref를 분리한 것이 좋습니다.


65-81: IntersectionObserver를 활용한 무한 스크롤 구현이 잘 되어 있습니다.

lastEventRef 콜백 함수가 효율적으로 구현되어 있습니다. 특히:

  1. 불필요한 observer 생성을 방지하기 위한 조건 검사
  2. 이전 observer의 연결 해제
  3. 의존성 배열이 올바르게 설정됨

이는 성능과 메모리 관리 측면에서 좋은 구현입니다.


83-90: useEffect 내 메모리 누수 방지 코드 좋습니다.

컴포넌트 마운트 시 초기 이벤트를 로드하고, 언마운트 시 observer를 정리하는 로직이 잘 구현되어 있습니다. 이는 메모리 누수를 방지하는 좋은 방법입니다.


109-122: 이벤트 리스트 렌더링 로직이 잘 구현되어 있습니다.

이벤트 카드 렌더링 로직이 적절하게 구현되어 있으며, lastEventRef를 마지막 아이템에만 연결하는 조건부 로직이 효율적입니다. 또한 EventCard 컴포넌트에 필요한 모든 속성이 올바르게 전달되고 있습니다.


125-136: 사용자 피드백 UI가 잘 구현되어 있습니다.

로딩 상태, 에러 메시지, 더 이상 표시할 이벤트가 없을 때의 메시지 등 다양한 상태에 대한 사용자 피드백이 잘 구현되어 있습니다. 이는 사용자 경험을 향상시키는 좋은 방법입니다.

src/shared/api/event.txt (2)

37-82: 이벤트 상세 조회 API 명세가 잘 정의되어 있습니다.

이벤트 상세 정보를 조회하기 위한 API 구조가 명확합니다. 응답 본문이 상세하게 작성되어 있어 프론트엔드 개발자가 쉽게 이해할 수 있습니다.

한 가지 주의할 점은 예제에서 endDatestartDate보다 이전인 것으로 보입니다(55-56줄). 실제 API에서는 날짜 유효성 검사가 필요할 것 같습니다.


121-135: 이벤트 삭제 API 명세가 간결하고 명확합니다.

이벤트 삭제 API 명세가 잘 작성되어 있습니다. 삭제 요청의 구조와 성공 응답이 명확하게 정의되어 있습니다.

Comment on lines +89 to +91
export enum EventStatus {
PROGRESS = 'PROGRESS',
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이벤트 상태 열거형 확장 필요

현재는 'PROGRESS' 상태만 정의되어 있습니다. 일반적으로 이벤트는 더 다양한 상태(예: 예정됨, 취소됨, 완료됨 등)를 가질 수 있습니다.

더 다양한 상태를 추가하는 것이 좋습니다:

export enum EventStatus {
  PROGRESS = 'PROGRESS',
+ SCHEDULED = 'SCHEDULED',
+ CANCELED = 'CANCELED',
+ COMPLETED = 'COMPLETED',
}
📝 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
export enum EventStatus {
PROGRESS = 'PROGRESS',
}
export enum EventStatus {
PROGRESS = 'PROGRESS',
SCHEDULED = 'SCHEDULED',
CANCELED = 'CANCELED',
COMPLETED = 'COMPLETED',
}

Comment on lines +15 to +17
const response = await axiosClient.get<EventNormalResponse>(
`/events?tags=${tag}&page=${params.page}&size=${params.size}`
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

URL 파라미터 처리 개선

URL 파라미터를 문자열 보간법으로 직접 구성하고 있습니다. 이는 특수 문자가 포함된 경우 문제가 될 수 있습니다.

URLSearchParams를 사용하여 안전하게 URL 매개변수를 처리하는 것이 좋습니다:

- const response = await axiosClient.get<EventNormalResponse>(
-   `/events?tags=${tag}&page=${params.page}&size=${params.size}`
- );
+ const searchParams = new URLSearchParams({
+   tags: tag,
+   page: params.page?.toString() || '0',
+   size: params.size?.toString() || '10',
+ });
+ const response = await axiosClient.get<EventNormalResponse>(`/events?${searchParams.toString()}`);
📝 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
const response = await axiosClient.get<EventNormalResponse>(
`/events?tags=${tag}&page=${params.page}&size=${params.size}`
);
const searchParams = new URLSearchParams({
tags: tag,
page: params.page?.toString() || '0',
size: params.size?.toString() || '10',
});
const response = await axiosClient.get<EventNormalResponse>(`/events?${searchParams.toString()}`);

Comment on lines +23 to +63
const loadEvents = async (pageNum: number) => {
try {
setIsLoading(true);
console.log('Fetching events with params:', { page: pageNum, size: 10 });
const response: EventNormalResponse = await eventApi.getEventByTag('current', { page: pageNum, size: 10 });
console.log('API Response:', response);

if (response?.result && Array.isArray(response.result)) {
console.log('Total events received:', response.result.length);

if (response.result.length === 0) {
console.log('No more events to load');
setHasMore(false);
} else {
if (pageNum === 0) {
console.log('Setting initial events');
setEvents(response.result);
} else {
console.log('Adding more events');
setEvents(prev => {
// 중복 제거
const newEvents = response.result.filter(
newEvent => !prev.some(existingEvent => existingEvent.id === newEvent.id)
);
console.log('New events to add:', newEvents.length);
return [...prev, ...newEvents];
});
}
setPage(pageNum);
}
} else {
console.error('Invalid response format:', response);
setError('이벤트 데이터 형식이 올바르지 않습니다.');
}
} catch (err) {
console.error('Error details:', err);
setError('이벤트를 불러오는데 실패했습니다.');
} finally {
setIsLoading(false);
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

loadEvents 함수에 몇 가지 개선 사항이 필요합니다.

이벤트를 불러오는 함수가 잘 구현되어 있지만, 몇 가지 개선할 점이 있습니다:

  1. 불필요한 console.log 문이 많이 포함되어 있습니다. 프로덕션 코드에서는 제거하는 것이 좋습니다.
  2. 에러 처리 시 더 구체적인 에러 메시지를 표시하면 디버깅에 도움이 될 것입니다.
const loadEvents = async (pageNum: number) => {
  try {
    setIsLoading(true);
-   console.log('Fetching events with params:', { page: pageNum, size: 10 });
    const response: EventNormalResponse = await eventApi.getEventByTag('current', { page: pageNum, size: 10 });
-   console.log('API Response:', response);

    if (response?.result && Array.isArray(response.result)) {
-     console.log('Total events received:', response.result.length);

      if (response.result.length === 0) {
-       console.log('No more events to load');
        setHasMore(false);
      } else {
        if (pageNum === 0) {
-         console.log('Setting initial events');
          setEvents(response.result);
        } else {
-         console.log('Adding more events');
          setEvents(prev => {
            // 중복 제거
            const newEvents = response.result.filter(
              newEvent => !prev.some(existingEvent => existingEvent.id === newEvent.id)
            );
-           console.log('New events to add:', newEvents.length);
            return [...prev, ...newEvents];
          });
        }
        setPage(pageNum);
      }
    } else {
-     console.error('Invalid response format:', response);
+     console.error('Invalid response format:', response);
      setError('이벤트 데이터 형식이 올바르지 않습니다.');
    }
  } catch (err) {
-   console.error('Error details:', err);
+   console.error('Error fetching events:', err);
+   const errorMessage = err instanceof Error ? `이벤트를 불러오는데 실패했습니다: ${err.message}` : '이벤트를 불러오는데 실패했습니다.';
-   setError('이벤트를 불러오는데 실패했습니다.');
+   setError(errorMessage);
  } finally {
    setIsLoading(false);
  }
};
📝 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
const loadEvents = async (pageNum: number) => {
try {
setIsLoading(true);
console.log('Fetching events with params:', { page: pageNum, size: 10 });
const response: EventNormalResponse = await eventApi.getEventByTag('current', { page: pageNum, size: 10 });
console.log('API Response:', response);
if (response?.result && Array.isArray(response.result)) {
console.log('Total events received:', response.result.length);
if (response.result.length === 0) {
console.log('No more events to load');
setHasMore(false);
} else {
if (pageNum === 0) {
console.log('Setting initial events');
setEvents(response.result);
} else {
console.log('Adding more events');
setEvents(prev => {
// 중복 제거
const newEvents = response.result.filter(
newEvent => !prev.some(existingEvent => existingEvent.id === newEvent.id)
);
console.log('New events to add:', newEvents.length);
return [...prev, ...newEvents];
});
}
setPage(pageNum);
}
} else {
console.error('Invalid response format:', response);
setError('이벤트 데이터 형식이 올바르지 않습니다.');
}
} catch (err) {
console.error('Error details:', err);
setError('이벤트를 불러오는데 실패했습니다.');
} finally {
setIsLoading(false);
}
};
const loadEvents = async (pageNum: number) => {
try {
setIsLoading(true);
const response: EventNormalResponse = await eventApi.getEventByTag('current', { page: pageNum, size: 10 });
if (response?.result && Array.isArray(response.result)) {
if (response.result.length === 0) {
setHasMore(false);
} else {
if (pageNum === 0) {
setEvents(response.result);
} else {
setEvents(prev => {
// 중복 제거
const newEvents = response.result.filter(
newEvent => !prev.some(existingEvent => existingEvent.id === newEvent.id)
);
return [...prev, ...newEvents];
});
}
setPage(pageNum);
}
} else {
console.error('Invalid response format:', response);
setError('이벤트 데이터 형식이 올바르지 않습니다.');
}
} catch (err) {
console.error('Error fetching events:', err);
const errorMessage = err instanceof Error ? `이벤트를 불러오는데 실패했습니다: ${err.message}` : '이벤트를 불러오는데 실패했습니다.';
setError(errorMessage);
} finally {
setIsLoading(false);
}
};

Comment on lines +85 to +118
< 이벤트 수정 >

- Path Variable
"eventId" : 1

- Request Headers
"Authorization" : "ACCESS_TOKEN",

- Request Body{
"title": "string",
"startDate": "2024-12-25",
"endDate": "2024-12-31",
"startTime": "15:30",
"endTime": "20:00",
"bannerImageUrl": "string",
"description": "string",
"referenceLinks": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
},
"onlineType": "ONLINE", [ ONLINE, OFFLINE ]
"address": "string",
"location": {
"lat": 123.00,
"lng": 123.00
},
"category": "DEVELOPMENT_STUDY", [ DEVELOPMENT_STUDY, NETWORKING, HACKATHON, CONFERENCE]
"hashtags": [
"string", "string2"
],
"hostEmail": "string",
"hostPhoneNumber": "string"
}
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 8, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

이벤트 수정 API의 referenceLinks 구조가 생성 API와 다릅니다.

이벤트 수정 API의 referenceLinks 필드 구조가 이벤트 생성 API와 다르게 정의되어 있습니다:

  • 생성 API: referenceLinkstitleurl 필드를 가진 객체의 배열
  • 수정 API: referenceLinksadditionalProp1, additionalProp2, additionalProp3 키를 가진 객체

두 API 간에 일관성이 필요합니다. 또한 이벤트 수정 API에서 hostEmailhostPhoneNumber 필드가 있는데, 생성 API에서는 organizerEmailorganizerPhoneNumber로 명명되어 있습니다. 이름을 일관되게 통일해야 합니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

아 이거는 API 연동하면서 옮겨적은거라 나중에 지우도록 할게요!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@xaexunxang 알겠습니다! 참고용 문서라면 나중에 삭제하셔도 괜찮을 것 같습니다. API 연동 작업 잘 진행되고 있는 것 같아 보입니다. 👍

ʕ •ᴥ•ʔ

Comment on lines +138 to +179
< 최신 이벤트 조회 >

- Query Variable
"tags" : "current"

- Request Headers
"Authorization" : "ACCESS_TOKEN",

- Response Body
{
"isSuccess": true,
"code": "200",
"message": "OK",
"result": [
{
"id": 21,
"bannerImageUrl": "string",
"title": "string",
"hostChannelName": "테커",
"startDate": "2025-04-02",
"address": "string",
"onlineType": "ONLINE",
"hashtags": [
"string"
],
"remainDays": "D-1"
},
{
"id": 20,
"bannerImageUrl": "string",
"title": "string",
"hostChannelName": "테커",
"startDate": "2025-04-02",
"address": "string",
"onlineType": "ONLINE",
"hashtags": [
"string"
],
"remainDays": "D-1"
}
]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

최신 이벤트 조회 API 명세가 잘 정의되어 있습니다.

최신 이벤트를 조회하기 위한 API가 명확하게 정의되어 있습니다. 쿼리 변수와 응답 본문의 구조가 상세하게 작성되어 있어 좋습니다.

다만, 페이지네이션 관련 파라미터(page, size)가 명시되어 있지 않습니다. AllEventsPage 컴포넌트에서는 이러한 파라미터를 사용하고 있으므로, API 명세에도 포함되어야 합니다.

@xaexunxang xaexunxang closed this Apr 22, 2025
@xaexunxang xaexunxang deleted the feat/#87/event-detail-check-api branch April 22, 2025 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant