Skip to content

[API] axios API 클라이언트 및 토큰 관리 유틸 추가#68

Merged
heeeeyong merged 8 commits into
developfrom
feat/api-auth
Aug 3, 2025
Merged

[API] axios API 클라이언트 및 토큰 관리 유틸 추가#68
heeeeyong merged 8 commits into
developfrom
feat/api-auth

Conversation

@heeeeyong
Copy link
Copy Markdown
Collaborator

@heeeeyong heeeeyong commented Aug 3, 2025

#️⃣연관된 이슈

이 부분은 따로 등록하지 않았습니다.

📝작업 내용

  1. axios 기반 공통 API 클라이언트(apiClient)를 생성했습니다.
  2. baseURL은 보안문제를 고려하여 .env.local 파일을 .gitignore로 관리하도록 했습니다. 환경변수 파일을 각자 루트폴더 바로 아래에 만든 후 VITE_API_BASE_URL 환경변수로 관리하시면 되겠습니다.
  3. 일단은 소셜로그인 부분 서버측 코드 수정이 필요한 상황이라 요청 인터셉터에 토큰이 있을 경우 Authorization 헤더 자동 주입 (없으면 하드코딩된 개발용 토큰 사용하도록 구현해놓았습니다.)
  4. 응답 인터셉터는 토큰만료 혹은 공통적인 에러 처리에만 사용하도록 하면 될 것 같습니다. 이 부분은 추후에 코드수정이 필요할듯 합니다.
  5. getTotalFeed.ts와 getMyFeed.ts를 구현해보았는데 별 이상없이 잘 작동되는 것을 확인했습니다. 만료된 코드를 넣었을 때, 로그인 페이지로 리다이렉션 되는 것까지도 확인 완료했습니다.
  6. 현재 서비스에서는 리프레시 토큰을 사용하지 않을 예정이라고 들어서 해당부분도 코드 삭제하겠습니다.
  7. API 디렉토리 구조는 API 명세서상 도메인 기준으로 분류했습니다.

스크린샷

image image

💬리뷰 요구사항

  1. API 디렉토리 구조에 수정해야할 사항이 있으면 알려주시면 적극 반영하겠습니다.
  2. 무한스크롤이 필요한 화면의 경우, 무한스크롤을 hook으로 빼내는게 맞을 것 같다는 생각이 드는데 여러분들의 생각은 어떨지 궁금합니다.

Summary by CodeRabbit

  • New Features

    • 피드(전체/내 피드)에서 무한 스크롤 및 API 기반 동적 데이터 로딩이 지원됩니다.
    • 내 피드 및 전체 피드 API 연동 기능이 추가되었습니다.
  • Bug Fixes

    • 피드, 포스트, 댓글 등에서 사용자 및 포스트 관련 속성명이 일관성 있게 변경되었습니다.
  • Refactor

    • Post, Feed 관련 데이터 타입과 컴포넌트 prop 명칭이 일괄적으로 변경되어 코드 일관성과 가독성이 향상되었습니다.
    • API 클라이언트가 토큰 관리 및 오류 처리 기능을 포함하도록 개선되었습니다.
    • ISBN 데이터 타입이 숫자에서 문자열로 변경되어 일관성이 향상되었습니다.
    • 피드 및 포스트 컴포넌트에서 prop 이름과 기본값이 수정되었습니다.
  • Chores

    • 환경설정 파일(.env.local)이 버전 관리에서 자동으로 제외됩니다.
  • Style

    • 피드 컴포넌트 컨테이너 높이가 뷰포트 전체 높이(100vh)로 조정되어 레이아웃이 개선되었습니다.

@vercel
Copy link
Copy Markdown

vercel Bot commented Aug 3, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
thip ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 3, 2025 2:47pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 3, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

이번 변경에서는 피드 관련 API 클라이언트 모듈이 새로 추가되고, 기존의 목(mock) 데이터 기반 피드 페이지가 실제 API로부터 데이터를 동적으로 불러오도록 리팩터링되었습니다. 포스트 관련 타입과 컴포넌트의 prop 명칭이 대대적으로 일관성 있게 변경되었으며, 무한 스크롤 및 페이지네이션 로직이 Feed 페이지에 구현되었습니다.

Changes

Cohort / File(s) Change Summary
API 클라이언트 및 토큰 관리
src/api/index.ts
Axios 기반 API 클라이언트 모듈 및 토큰 관리 유틸리티(TokenManager) 추가. 요청/응답 인터셉터 구현 및 인증 헤더 자동 주입, 에러 핸들링, 로그인 리다이렉트 등 포함.
피드 API 모듈
src/api/feeds/getMyFeed.ts, src/api/feeds/getTotalFeed.ts
"내 피드" 및 "전체 피드"를 페이징 처리하여 불러오는 API 함수와 응답 타입 정의. 각 함수는 커서 기반 페이지네이션 지원, 예시 주석 포함.
Feed 페이지 동적 데이터화
src/pages/feed/Feed.tsx
Feed 컴포넌트가 API에서 실제 데이터를 받아오도록 리팩터링. 각 탭별로 별도 상태, 로딩, 커서, 마지막 페이지 플래그 관리. 무한 스크롤 및 에러 핸들링 구현.
타입 및 목데이터 구조 변경
src/types/post.ts, src/data/postData.ts, src/mocks/searchBook.mock.ts
PostData 및 관련 타입, 목데이터 구조를 API 응답 및 컴포넌트 prop 명칭과 일치하도록 대대적으로 리팩터링. 필드명 변경, 불필요 필드 제거 및 추가, 타입 정비.
Post 컴포넌트 prop 명칭 통일 및 기본값 추가
src/components/common/Post/PostBody.tsx, src/components/common/Post/PostFooter.tsx, src/components/common/Post/PostHeader.tsx, src/components/common/Post/Reply.tsx, src/components/common/Post/SubReply.tsx, src/components/feed/FeedDetailPostBody.tsx
포스트 관련 컴포넌트들의 prop 명칭을 API 및 타입 정의와 일치하도록 일괄 변경. 일부 prop은 선택적(optional)으로 변경, 기본값 추가, 내부 상태 초기화 로직 조정 등.
기타 설정
.gitignore
.env.local 파일을 git 추적에서 제외하도록 ignore 패턴 추가.
isbn 타입 변경 및 관련 수정
src/types/book.ts, src/components/feed/BookInfoCard.tsx, src/pages/mypage/SavePage.tsx, src/data/bookData.ts
ISBN 필드 타입을 숫자에서 문자열로 변경. 관련 상태, 함수 파라미터, 컴포넌트 prop 타입도 일관되게 수정.
스타일 수정
src/components/feed/MyFeed.tsx, src/components/feed/TotalFeed.tsx
각 컴포넌트의 Container에 높이 100vh 스타일 추가.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FeedPage
  participant APIClient
  participant Server

  User->>FeedPage: 페이지 진입/탭 변경/스크롤 하단 도달
  FeedPage->>APIClient: getTotalFeeds(cursor) 또는 getMyFeeds(cursor)
  APIClient->>Server: GET /feeds 또는 /feeds/mine?cursor=...
  Server-->>APIClient: 피드 데이터, nextCursor, isLast
  APIClient-->>FeedPage: 응답 데이터 반환
  FeedPage-->>User: 피드 목록 렌더링 및 무한 스크롤 처리
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇
새 이름으로 피드가 춤추네,
API에서 데이터가 쏟아지네!
무한 스크롤, 커서도 척척,
목데이터는 안녕, 진짜가 왔지.
타입도 정비, 컴포넌트도 새단장,
토끼는 오늘도 코드를 뛰논다!
🌱

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8712bb4 and 262da2b.

📒 Files selected for processing (2)
  • src/components/feed/MyFeed.tsx (1 hunks)
  • src/components/feed/TotalFeed.tsx (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/api-auth

🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @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.

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: 7

🔭 Outside diff range comments (2)
src/types/post.ts (1)

57-59: 중복된 날짜 필드가 있습니다

ReplyDataProps 인터페이스에 createdAtpostDate 두 개의 날짜 필드가 있습니다. 일관성을 위해 하나로 통일하는 것이 좋습니다.

-  createdAt: string;
   postDate: string;
src/data/postData.ts (1)

94-95: 댓글 목 데이터에 중복된 날짜 필드가 있습니다

createdAtpostDate가 동일한 값으로 중복되어 있습니다. 타입 정의와 일관성을 맞춰주세요.

-    createdAt: '2025.01.12',
     postDate: '2025.01.12',
🧹 Nitpick comments (3)
src/api/index.ts (2)

24-24: 프로덕션용 로깅 제거가 필요합니다.

환경변수 확인용 console.log가 포함되어 있습니다. 프로덕션 환경에서는 제거되어야 합니다.

-// 환경변수 확인용
-console.log('API_BASE_URL:', API_BASE_URL);
+// 개발 환경에서만 로깅
+if (import.meta.env.DEV) {
+  console.log('API_BASE_URL:', API_BASE_URL);
+}

62-68: 사용자 경험 개선을 위한 에러 처리 방식 개선을 제안합니다.

현재 alert() 사용과 window.location.href를 통한 리다이렉트는 사용자 경험을 저해할 수 있습니다.

더 나은 UX를 위해 다음과 같은 개선을 고려해보세요:

  1. Toast 알림 시스템 사용
  2. React Router의 navigate 함수 활용
  3. 에러 상태 관리를 통한 조건부 렌더링
// 예시: 토스트 알림과 라우터 기반 네비게이션
import { toast } from 'react-toastify';

// 401 에러 처리
if (status === 401) {
  TokenManager.clearTokens();
  toast.error('로그인이 필요합니다.');
  // 라우터 컨텍스트에서 네비게이션 처리
}

// 403 에러 처리  
if (status === 403) {
  toast.warn('접근 권한이 없습니다.');
}

// 서버 에러 처리
if (status && status >= 500) {
  toast.error('서버에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.');
}

Also applies to: 72-75, 78-81

src/pages/feed/Feed.tsx (1)

44-91: 로드 함수들의 중복 코드를 줄일 수 있습니다

loadTotalFeedsloadMyFeeds 함수의 로직이 거의 동일합니다. 공통 로직을 추출하면 유지보수성이 향상됩니다.

const loadFeeds = async (
  type: 'total' | 'my',
  cursor?: string
) => {
  const isTotal = type === 'total';
  const apiCall = isTotal ? getTotalFeeds : getMyFeeds;
  const setLoading = isTotal ? setTotalLoading : setMyLoading;
  const setPosts = isTotal ? setTotalFeedPosts : setMyFeedPosts;
  const setNextCursor = isTotal ? setTotalNextCursor : setMyNextCursor;
  const setIsLast = isTotal ? setTotalIsLast : setMyIsLast;
  
  try {
    setLoading(true);
    const response = await apiCall(cursor ? { cursor } : undefined);
    
    if (cursor) {
      setPosts(prev => [...prev, ...response.data.feedList]);
    } else {
      setPosts(response.data.feedList);
    }
    
    setNextCursor(response.data.nextCursor);
    setIsLast(response.data.isLast);
  } catch (error) {
    console.error(`${isTotal ? '전체' : '내'} 피드 로드 실패:`, error);
    setPosts(mockPosts);
  } finally {
    setLoading(false);
  }
};
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4b958d7 and d13e70e.

📒 Files selected for processing (13)
  • .gitignore (1 hunks)
  • src/api/feeds/getMyFeed.ts (1 hunks)
  • src/api/feeds/getTotalFeed.ts (1 hunks)
  • src/api/index.ts (1 hunks)
  • src/components/common/Post/PostBody.tsx (2 hunks)
  • src/components/common/Post/PostFooter.tsx (1 hunks)
  • src/components/common/Post/PostHeader.tsx (1 hunks)
  • src/components/common/Post/Reply.tsx (1 hunks)
  • src/components/common/Post/SubReply.tsx (1 hunks)
  • src/components/feed/FeedDetailPostBody.tsx (3 hunks)
  • src/data/postData.ts (1 hunks)
  • src/pages/feed/Feed.tsx (3 hunks)
  • src/types/post.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/components/feed/FeedDetailPostBody.tsx (1)
src/types/post.ts (1)
  • PostBodyProps (31-34)
src/components/common/Post/PostBody.tsx (1)
src/types/post.ts (1)
  • PostBodyProps (31-34)
src/api/feeds/getTotalFeed.ts (2)
src/types/post.ts (1)
  • PostData (1-18)
src/api/index.ts (1)
  • apiClient (27-33)
src/api/feeds/getMyFeed.ts (2)
src/types/post.ts (1)
  • PostData (1-18)
src/api/index.ts (1)
  • apiClient (27-33)
src/data/postData.ts (1)
src/types/post.ts (1)
  • FeedPostProps (26-29)
🔇 Additional comments (16)
.gitignore (1)

14-14: 환경 변수 파일 제외 처리가 적절합니다.

.env.local 파일을 gitignore에 추가한 것은 민감한 환경 변수(API URL, 토큰 등)가 버전 관리에 포함되지 않도록 하는 표준적이고 필수적인 보안 관행입니다.

src/api/index.ts (1)

8-18: API 클라이언트 아키텍처가 잘 설계되었습니다.

TokenManager 유틸리티와 axios 인터셉터를 활용한 토큰 관리 로직이 깔끔하게 구현되었습니다. 토큰 자동 주입과 fallback 로직이 적절합니다.

Also applies to: 27-49

src/components/common/Post/Reply.tsx (1)

39-44: props 명명 규칙 통일이 잘 적용되었습니다.

PostHeader 컴포넌트로 전달되는 props 이름이 더 명확하고 일관성 있게 변경되었습니다. creator 접두사를 통해 작성자 관련 정보임을 명확히 표현하고 있습니다.

src/components/common/Post/SubReply.tsx (1)

44-49: 일관된 props 명명 규칙이 적용되었습니다.

Reply 컴포넌트와 동일한 명명 규칙으로 PostHeader props가 업데이트되어 컴포넌트 간 일관성이 확보되었습니다.

src/components/common/Post/PostBody.tsx (1)

51-53: 컨텐츠 관련 props 명명이 개선되었습니다.

postContentcontentBody, imagescontentsUrl로 변경된 props 이름이 더 명확하고 직관적입니다. 내부 로직에서도 일관되게 적용되었습니다.

Also applies to: 56-56, 68-68, 71-71

src/components/feed/FeedDetailPostBody.tsx (2)

75-77: 인터페이스 확장이 잘 구현되었습니다.

PostBodyProps를 확장하여 tags 속성을 추가한 방식이 적절합니다. API에 없는 선택적 필드를 컴포넌트에서 사용할 때 이런 패턴이 유용합니다.


83-86: prop 이름 변경이 일관성 있게 적용되었습니다.

postContentcontentBody, imagescontentsUrl로의 변경이 타입 정의와 일치하며, 기본값 설정도 적절합니다.

src/api/feeds/getMyFeed.ts (1)

5-22: 타입 정의가 잘 구성되어 있습니다.

API 응답과 요청 파라미터에 대한 타입 정의가 명확하고 완전합니다. 페이지네이션을 위한 nextCursorisLast 필드도 적절하게 포함되어 있습니다.

src/api/feeds/getTotalFeed.ts (1)

5-22: 타입 정의가 명확합니다.

페이지네이션과 API 응답 구조에 대한 타입 정의가 잘 되어 있습니다.

src/components/common/Post/PostHeader.tsx (2)

4-11: prop 타입 정의가 잘 개선되었습니다.

creator 관련 필드들을 선택적으로 만들고 명명을 일관성 있게 변경한 것이 좋습니다. 특히 titleColor에 기본값을 제공한 것도 적절합니다.


24-28: 안전한 네비게이션 로직입니다.

creatorId가 존재할 때만 네비게이션하는 방어적 프로그래밍이 잘 적용되었습니다. 선택적 prop과 잘 어울리는 구현입니다.

src/components/common/Post/PostFooter.tsx (2)

46-54: prop 인터페이스 개선이 적절합니다.

likeCount로 이름을 통일하고 isSaved, isLiked 같은 초기 상태 prop을 추가한 것이 좋습니다. 기본값 설정도 합리적입니다.


68-70: 상태 초기화가 올바르게 구현되었습니다.

들어오는 prop 값으로 상태를 초기화하는 것이 올바른 패턴입니다. 이제 컴포넌트가 서버에서 받은 초기 상태를 반영할 수 있습니다.

src/types/post.ts (1)

1-18: PostData 인터페이스 리팩토링이 적절합니다

API 응답 구조에 맞춰 필드명을 일관성 있게 변경한 것이 좋습니다. 특히 creator* 필드로의 통일과 saved/liked 상태 추가가 적절합니다.

src/pages/feed/Feed.tsx (1)

107-132: 무한 스크롤 구현이 적절합니다

스크롤 이벤트 리스너의 정리와 조건부 로딩 로직이 잘 구현되어 있습니다. 성능 최적화를 위해 throttle을 추가하는 것도 고려해보세요.

src/data/postData.ts (1)

5-83: 목 데이터가 새로운 인터페이스 구조와 일치합니다

PostData 인터페이스 변경사항에 맞춰 목 데이터가 적절히 업데이트되었습니다. API 실패 시 폴백으로 사용되는 데이터로 적합합니다.

Comment on lines +25 to +37
export const getMyFeeds = async (params?: GetMyFeedParams): Promise<MyFeedResponse> => {
const queryParams = new URLSearchParams();

// cursor가 있을 때만 쿼리 파라미터에 추가
if (params?.cursor) {
queryParams.append('cursor', params.cursor);
}

const url = `/feeds/mine${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;

const response = await apiClient.get<MyFeedResponse>(url);
return response.data;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

코드 중복 가능성을 확인해보세요.

getTotalFeed.ts와 거의 동일한 구조를 가지고 있습니다. 공통 유틸리티 함수나 제네릭 함수로 중복을 제거할 수 있을지 검토해보세요.

다음 스크립트로 두 API 함수 간의 유사성을 확인할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# 두 feed API 파일의 구조적 유사성 확인
echo "=== getMyFeed.ts ==="
cat src/api/feeds/getMyFeed.ts | head -40

echo -e "\n=== getTotalFeed.ts ==="
cat src/api/feeds/getTotalFeed.ts | head -40

echo -e "\n=== 차이점 비교 ==="
diff src/api/feeds/getMyFeed.ts src/api/feeds/getTotalFeed.ts

Length of output: 3173


중복 피드 API 함수 리팩토링 제안

getMyFeeds(/feeds/mine)와 getTotalFeeds(/feeds)는 URLSearchParams 처리, 쿼리 문자열 생성, apiClient.get 호출 등 거의 동일한 로직을 갖고 있습니다. 아래 파일들을 점검하여 공통 유틸 함수 또는 제네릭 헬퍼로 추출해 중복을 제거해 보세요.

수정 대상:

  • src/api/feeds/getMyFeed.ts
  • src/api/feeds/getTotalFeed.ts

리팩토링 예시:

// src/api/feeds/fetchFeeds.ts
import { apiClient } from '../index';

export async function fetchFeeds<Params extends { cursor?: string }, Response>(
  endpoint: string,
  params?: Params,
): Promise<Response> {
  const qp = new URLSearchParams();
  if (params?.cursor) qp.append('cursor', params.cursor);
  const url = `${endpoint}${qp.toString() ? `?${qp}` : ''}`;
  const res = await apiClient.get<Response>(url);
  return res.data;
}

// 기존 함수들은 이렇게 변경
export const getMyFeeds = (params?: GetMyFeedParams) =>
  fetchFeeds<GetMyFeedParams, MyFeedResponse>('/feeds/mine', params);

export const getTotalFeeds = (params?: GetTotalFeedParams) =>
  fetchFeeds<GetTotalFeedParams, TotalFeedResponse>('/feeds', params);
🤖 Prompt for AI Agents
In src/api/feeds/getMyFeed.ts lines 25 to 37, refactor the getMyFeeds function
to remove duplication with getTotalFeeds by extracting the shared logic of
building query parameters, constructing the URL, and calling apiClient.get into
a generic utility function (e.g., fetchFeeds) in a new file like
src/api/feeds/fetchFeeds.ts. Then update getMyFeeds and getTotalFeeds to call
this utility with their specific endpoints and types, thereby centralizing the
common code and reducing redundancy.

Comment on lines +24 to +36
export const getTotalFeeds = async (params?: GetTotalFeedParams): Promise<TotalFeedResponse> => {
const queryParams = new URLSearchParams();

// cursor가 있을 때만 쿼리 파라미터에 추가
if (params?.cursor) {
queryParams.append('cursor', params.cursor);
}

const url = `/feeds${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;

const response = await apiClient.get<TotalFeedResponse>(url);
return response.data;
};
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

코드 중복을 개선할 수 있습니다.

getMyFeed.ts와 구조가 거의 동일합니다. 다음과 같은 제네릭 함수로 리팩터링을 고려해보세요:

// api/feeds/common.ts
interface FeedApiParams {
  cursor?: string;
}

interface FeedResponse<T> {
  success: boolean;
  code: number;
  message: string;
  data: T;
}

const createFeedApi = <T>(endpoint: string) => {
  return async (params?: FeedApiParams): Promise<FeedResponse<T>> => {
    const queryParams = new URLSearchParams();
    if (params?.cursor) {
      queryParams.append('cursor', params.cursor);
    }
    const url = `${endpoint}${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
    const response = await apiClient.get<FeedResponse<T>>(url);
    return response.data;
  };
};
🤖 Prompt for AI Agents
In src/api/feeds/getTotalFeed.ts around lines 24 to 36, the getTotalFeeds
function duplicates code similar to getMyFeed.ts. Refactor by creating a generic
function in a new file like api/feeds/common.ts that accepts an endpoint string
and returns an async function handling the cursor parameter and API call. Then
replace getTotalFeeds with a call to this generic function, passing the
appropriate endpoint, to eliminate duplication and improve maintainability.

Comment thread src/api/index.ts
Comment on lines +3 to +5
// 하드코딩된 액세스 토큰
const ACCESS_TOKEN =
'Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc1NDIwMTY4OCwiZXhwIjoxNzU2NzkzNjg4fQ.oOyJ7JI_t2-Xq1-gfAv4ZaYNrbyplvqdxhCk76-Txe4';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

개발용 하드코딩된 토큰의 보안 위험성을 주의하세요.

하드코딩된 액세스 토큰이 소스코드에 포함되어 있습니다. 이는 보안상 위험할 수 있습니다.

프로덕션 배포 전에 다음과 같이 개선하는 것을 권장합니다:

-// 하드코딩된 액세스 토큰
-const ACCESS_TOKEN =
-  'Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc1NDIwMTY4OCwiZXhwIjoxNzU2NzkzNjg4fQ.oOyJ7JI_t2-Xq1-gfAv4ZaYNrbyplvqdxhCk76-Txe4';
+// 개발용 기본 토큰 (환경변수로 관리)
+const ACCESS_TOKEN = import.meta.env.VITE_DEV_ACCESS_TOKEN || '';
📝 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 ACCESS_TOKEN =
'Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc1NDIwMTY4OCwiZXhwIjoxNzU2NzkzNjg4fQ.oOyJ7JI_t2-Xq1-gfAv4ZaYNrbyplvqdxhCk76-Txe4';
// 개발용 기본 토큰 (환경변수로 관리)
const ACCESS_TOKEN = import.meta.env.VITE_DEV_ACCESS_TOKEN || '';
🤖 Prompt for AI Agents
In src/api/index.ts around lines 3 to 5, the access token is hardcoded, posing a
security risk. Remove the hardcoded token and instead load the access token from
a secure environment variable or configuration file. Ensure the token is not
committed to source control and is injected securely during runtime or
deployment.

Comment thread src/data/postData.ts Outdated
Comment thread src/pages/feed/Feed.tsx
Comment on lines +59 to +63
} catch (error) {
console.error('전체 피드 로드 실패:', error);
// 에러 시 mockPosts 사용 (fallback)
setTotalFeedPosts(mockPosts);
} finally {
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 호출 실패 시 콘솔에만 로그를 남기고 있습니다. 사용자에게 에러 상태를 알려주는 UI가 필요합니다.

에러 상태를 관리하고 사용자에게 표시하는 로직을 추가하세요:

+  const [error, setError] = useState<string | null>(null);

   } catch (error) {
     console.error('전체 피드 로드 실패:', error);
+    setError('피드를 불러오는데 실패했습니다. 다시 시도해주세요.');
     // 에러 시 mockPosts 사용 (fallback)
     setTotalFeedPosts(mockPosts);

Also applies to: 85-87

🤖 Prompt for AI Agents
In src/pages/feed/Feed.tsx around lines 59 to 63 and similarly at lines 85 to
87, the catch blocks only log errors to the console without informing the user.
Add state management for error status using React's useState, update the error
state in the catch blocks, and render an error message or UI component to notify
the user about the failure to load the feed.

Comment thread src/pages/feed/Feed.tsx
Comment on lines +138 to +146
useEffect(() => {
// 탭별로 API 호출
if (activeTab === '피드') {
loadTotalFeeds();
} else if (activeTab === '내 피드') {
loadMyFeeds();
}
}, [activeTab]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

탭 변경 중 진행 중인 API 호출을 취소해야 합니다

사용자가 빠르게 탭을 전환할 때 이전 API 호출이 완료되면 잘못된 데이터가 표시될 수 있습니다.

AbortController를 사용하여 진행 중인 요청을 취소하세요:

useEffect(() => {
  const abortController = new AbortController();
  
  if (activeTab === '피드') {
    loadTotalFeeds(undefined, abortController.signal);
  } else if (activeTab === '내 피드') {
    loadMyFeeds(undefined, abortController.signal);
  }
  
  return () => {
    abortController.abort();
  };
}, [activeTab]);
🤖 Prompt for AI Agents
In src/pages/feed/Feed.tsx around lines 138 to 146, the current useEffect does
not cancel ongoing API calls when the activeTab changes, which can cause
outdated data to display. Modify the useEffect to create an AbortController
instance, pass its signal to loadTotalFeeds or loadMyFeeds calls, and abort the
controller in the cleanup function to cancel any in-progress requests when the
tab changes.

Comment thread src/types/post.ts Outdated
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: 0

🧹 Nitpick comments (1)
src/mocks/searchBook.mock.ts (1)

4-5: 소개 텍스트의 데이터 품질 문제를 수정하세요.

소개 텍스트에 깨진 한글 문자(엥ㄹㅁㄴㅇㄹㄹㅁㅇ)와 반복적인 내용이 포함되어 있습니다. 이는 목 데이터의 품질을 저하시키고 테스트 시 부정확한 결과를 초래할 수 있습니다.

다음과 같이 수정하는 것을 제안합니다:

-    '인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국엥ㄹㅁㄴㅇㄹㄹㅁㅇ인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국엥ㄹㅁㄴㅇㄹㄹㅁㅇ인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국엥ㄹㅁㄴㅇㄹㄹㅁㅇ인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국엥ㄹㅁㄴㅇㄹㄹㅁㅇ인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국엥ㄹㅁㄴㅇㄹㄹㅁㅇ인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국',
+    '인터내셔널 부커상, 산클레멘테 문학상 수상작 전세계가 주목한 한강의 역작을 다시 만나다. 2016년 인터내셔널 부커상을 수상하며 한국 문학의 새로운 지평을 열었다. 폭력과 억압에 맞서는 한 여성의 이야기를 통해 인간 내면의 깊은 갈등과 해방 의지를 그려낸 작품이다.',
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d13e70e and e65b876.

📒 Files selected for processing (1)
  • src/mocks/searchBook.mock.ts (1 hunks)
🔇 Additional comments (2)
src/mocks/searchBook.mock.ts (2)

39-54: 포스트 데이터 구조 업데이트가 잘 적용되었습니다.

새로운 API 명세와 PostData 인터페이스에 맞춘 필드명 변경이 적절히 이루어졌습니다. 사용자 상호작용 상태를 나타내는 isSaved, isLiked 필드 추가도 좋습니다.


57-74: 두 번째 포스트 객체도 일관성 있게 업데이트되었습니다.

첫 번째 포스트와 동일한 구조로 업데이트되어 일관성이 유지되고 있으며, 다양한 상태값(isSaved: true, isLiked: false, contentsUrl: [])으로 테스트 시나리오를 풍부하게 만들어 줍니다.

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