Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
33 changes: 33 additions & 0 deletions src/api/books/getBookDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { apiClient } from '../index';

// 책 상세 정보 타입
export interface BookDetail {
title: string;
imageUrl: string;
authorName: string;
publisher: string;
isbn: string;
description: string;
recruitingRoomCount: number;
readCount: number;
isSaved: boolean;
}

// API 응답 타입
export interface BookDetailResponse {
isSuccess: boolean;
code: number;
message: string;
data: BookDetail;
}

export const getBookDetail = async (isbn: string): Promise<BookDetailResponse> => {
try {
const response = await apiClient.get<BookDetailResponse>(`/books/${isbn}`);

return response.data;
} catch (error) {
console.error('책 상세 정보 API 오류:', error);
throw error;
}
};
Comment on lines +24 to +33
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ISBN을 URL 인코딩하고 로그에 컨텍스트 추가

특수문자(하이픈 등) 포함 가능성을 고려해 경로 파라미터는 인코딩하는 게 안전합니다. 에러 로그에 isbn을 포함해 디버깅 가시성을 높이십시오.

 export const getBookDetail = async (isbn: string): Promise<BookDetailResponse> => {
   try {
-    const response = await apiClient.get<BookDetailResponse>(`/books/${isbn}`);
+    const response = await apiClient.get<BookDetailResponse>(`/books/${encodeURIComponent(isbn)}`);
 
     return response.data;
   } catch (error) {
-    console.error('책 상세 정보 API 오류:', error);
+    console.error(`책 상세 정보 API 오류 (isbn: ${isbn}):`, error);
     throw 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
export const getBookDetail = async (isbn: string): Promise<BookDetailResponse> => {
try {
const response = await apiClient.get<BookDetailResponse>(`/books/${isbn}`);
return response.data;
} catch (error) {
console.error('책 상세 정보 API 오류:', error);
throw error;
}
};
export const getBookDetail = async (isbn: string): Promise<BookDetailResponse> => {
try {
const response = await apiClient.get<BookDetailResponse>(
`/books/${encodeURIComponent(isbn)}`
);
return response.data;
} catch (error) {
console.error(`책 상세 정보 API 오류 (isbn: ${isbn}):`, error);
throw error;
}
};
🤖 Prompt for AI Agents
In src/api/books/getBookDetail.ts around lines 24 to 33, the ISBN path parameter
isn't URL-encoded and the error log lacks context; update the request to use an
encoded ISBN (e.g., apiClient.get(`/books/${encodeURIComponent(isbn)}`)) and
modify the catch to log the isbn alongside the error (e.g., console.error('책 상세
정보 API 오류', { isbn, error })) before rethrowing so debugging has the
encoded-safe path and clear context.

32 changes: 32 additions & 0 deletions src/api/books/getMostSearchedBooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { apiClient } from '../index';

// 인기 검색 도서 타입
export interface MostSearchedBook {
rank: number;
title: string;
imageUrl: string;
isbn: string;
}

// API 응답 데이터 타입
export interface MostSearchedBooksData {
bookList: MostSearchedBook[];
}

// API 응답 타입
export interface MostSearchedBooksResponse {
isSuccess: boolean;
code: number;
message: string;
data: MostSearchedBooksData;
}

export const getMostSearchedBooks = async (): Promise<MostSearchedBooksResponse> => {
try {
const response = await apiClient.get<MostSearchedBooksResponse>('/books/most-searched');
return response.data;
} catch (error) {
console.error('인기 검색 도서 조회 API 오류:', error);
throw error;
}
};
40 changes: 40 additions & 0 deletions src/api/books/getRecruitingRooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { apiClient } from '../index';

// 모집중인 모임방 타입
export interface RecruitingRoom {
roomId: number;
bookImageUrl: string;
roomName: string;
memberCount: number;
recruitCount: number;
deadlineEndDate: string;
}

// API 응답 데이터 타입
export interface RecruitingRoomsData {
recruitingRoomList: RecruitingRoom[];
totalRoomCount: number;
nextCursor: string;
isLast: boolean;
}

// API 응답 타입
export interface RecruitingRoomsResponse {
isSuccess: boolean;
code: number;
message: string;
data: RecruitingRoomsData;
}

export const getRecruitingRooms = async (isbn: string): Promise<RecruitingRoomsResponse> => {
try {
const response = await apiClient.get<RecruitingRoomsResponse>(
`/books/${isbn}/recruiting-rooms`,
);

return response.data;
} catch (error) {
console.error('모집중인 모임방 조회 API 오류:', error);
throw error;
}
};
70 changes: 70 additions & 0 deletions src/api/books/getSearchBooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { apiClient } from '../index';

// 검색된 책 타입 (API 응답에서 받는 형태)
export interface BookSearchItem {
title: string;
imageUrl: string;
authorName: string;
publisher: string;
isbn: string;
}

// 검색된 책 타입 (컴포넌트에서 사용하는 형태)
export interface SearchedBook {
id: number;
title: string;
author: string;
publisher: string;
coverUrl: string;
isbn: string;
}
Comment on lines +12 to +20
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

SearchedBook 타입 중복 정의 제거

UI 레이어(src/pages/search/Search.tsx)에서도 같은 이름의 SearchedBook 인터페이스가 존재합니다. 중복 정의는 드리프트를 유발합니다. 공용 타입 모듈(예: src/types/book.ts)로 옮기고 단일 출처를 사용하세요.

중복 정의 및 사용처를 점검하는 스크립트:


🏁 Script executed:

#!/bin/bash
# SearchedBook 타입 선언 및 사용 위치 점검
rg -n --no-heading "interface SearchedBook|type SearchedBook" -A 2 -B 2
rg -n --no-heading "SearchedBook" -A 2 -B 2

Length of output: 8084


SearchedBook 타입 공통 모듈로 이동 및 중복 정의 제거 필요

UI 레이어와 API 레이어에서 각각 정의된 SearchedBook 인터페이스가 아래 세 곳에 중복되어 있습니다. 단일 소스에 정의를 모아두고, 각 파일에서는 해당 타입을 import 하도록 리팩터링하세요.

• src/api/books/getSearchBooks.ts
• src/pages/search/Search.tsx
• src/components/search/BookSearchResult.tsx

제안된 작업 순서:

  1. src/types/book.ts (또는 src/types/index.ts)에 SearchedBook 인터페이스 정의 추가

    // src/types/book.ts
    export interface SearchedBook {
      id: number;
      title: string;
      author: string;
      publisher: string;
      coverUrl: string;
      isbn: string;
    }
  2. 모든 파일에서 로컬 정의 제거 후 공통 타입 모듈에서 import

    • src/api/books/getSearchBooks.ts
      - // 검색된 책 타입 (컴포넌트에서 사용하는 형태)
      - export interface SearchedBook { … }
      + import type { SearchedBook } from '@/types/book';
    • src/pages/search/Search.tsx
      - export interface SearchedBook { … }
      + import type { SearchedBook } from '@/types/book';
    • src/components/search/BookSearchResult.tsx
      - import type { SearchedBook } from '@/pages/search/Search';
      + import type { SearchedBook } from '@/types/book';
  3. convertToSearchedBooks의 반환 타입도 공통 SearchedBook[]으로 유지되는지 확인

이후 빌드 에러가 없는지, 컴포넌트들이 올바르게 타입을 참조하는지 검증해 주세요.

🤖 Prompt for AI Agents
In src/api/books/getSearchBooks.ts around lines 12 to 20, the SearchedBook
interface is duplicated across multiple files; move the interface to a single
shared module (e.g., src/types/book.ts exporting SearchedBook), remove the local
SearchedBook declarations in src/api/books/getSearchBooks.ts,
src/pages/search/Search.tsx, and src/components/search/BookSearchResult.tsx, and
update those files to import { SearchedBook } from the new types module; also
ensure convertToSearchedBooks and any function signatures use the shared
SearchedBook[] type and run the build to verify no type errors remain.


// API 응답 데이터 타입
export interface SearchBooksData {
searchResult: BookSearchItem[];
page: number;
size: number;
totalElements: number;
totalPages: number;
last: boolean;
first: boolean;
}

// API 응답 타입
export interface SearchBooksResponse {
isSuccess: boolean;
code: number;
message: string;
data: SearchBooksData;
}

export const getSearchBooks = async (
query: string,
page: number = 1,
isFinalized: boolean = false,
): Promise<SearchBooksResponse> => {
try {
const response = await apiClient.get<SearchBooksResponse>('/books', {
params: {
keyword: query.trim(),
page: page,
isFinalized: isFinalized,
},
});
return response.data;
} catch (error) {
console.error('책 검색 API 오류:', error);
throw error;
}
};

export const convertToSearchedBooks = (apiBooks: BookSearchItem[]): SearchedBook[] => {
return apiBooks.map((book, index) => ({
id: index + 1,
title: book.title,
author: book.authorName,
publisher: book.publisher,
coverUrl: book.imageUrl,
isbn: book.isbn,
}));
};
Comment on lines +61 to +70
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

id로 index 사용은 불안정 — 키/식별자 용도로 ISBN 사용 권장

id를 index 기반으로 생성하면 페이지네이션/정렬 변경 시 불안정합니다. 가능하면 React key와 식별자는 ISBN을 사용하세요. 현재 UI에서 key로 id를 쓰지 않는지 확인해 주세요.

검증 스크립트(리스트 렌더링에서 book.id를 key로 쓰는지 검색):


🏁 Script executed:

#!/bin/bash
# book.id를 React key로 사용하는 구문 탐색
rg -n --no-heading "key=\{[^}]*book\.id[^}]*\}" -A 2 -B 2

Length of output: 1167


페이지네이션·정렬 안정화를 위해 id를 ISBN으로 변경하세요

현재 convertToSearchedBooks에서 idindex + 1로 생성하고,
BookSearchResult.tsx(34)·BookSearchBottomSheet.tsx(216)에서 key={book.id}로 사용하고 있습니다.
인덱스 기반 ID는 리스트가 변경될 때 React key 충돌을 일으킬 수 있으므로, 고유 불변 값인 ISBN을 사용하도록 수정하세요.

수정 제안:

// File: src/api/books/getSearchBooks.ts

-export const convertToSearchedBooks = (apiBooks: BookSearchItem[]): SearchedBook[] => {
-  return apiBooks.map((book, index) => ({
-    id: index + 1,
+export const convertToSearchedBooks = (apiBooks: BookSearchItem[]): SearchedBook[] => {
+  return apiBooks.map((book) => ({
+    id: book.isbn,           // index → ISBN으로 대체
     title: book.title,
     author: book.authorName,
     publisher: book.publisher,
     coverUrl: book.imageUrl,
-    isbn: book.isbn,
+    isbn: book.isbn,         // id와 중복되지 않도록, 필요한 경우 타입도 함께 검토
  }));
};

• BookSearchResult.tsx (34)
• BookSearchBottomSheet.tsx (216)
– 위 파일들의 key={book.id}key={book.isbn} 또는 key={book.isbn}로 변경하면 안전합니다.
– 필요 시 SearchedBook.id 타입을 string으로 조정하세요.

📝 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 const convertToSearchedBooks = (apiBooks: BookSearchItem[]): SearchedBook[] => {
return apiBooks.map((book, index) => ({
id: index + 1,
title: book.title,
author: book.authorName,
publisher: book.publisher,
coverUrl: book.imageUrl,
isbn: book.isbn,
}));
};
export const convertToSearchedBooks = (apiBooks: BookSearchItem[]): SearchedBook[] => {
return apiBooks.map((book) => ({
id: book.isbn, // index → ISBN으로 대체
title: book.title,
author: book.authorName,
publisher: book.publisher,
coverUrl: book.imageUrl,
isbn: book.isbn, // id와 중복되지 않도록, 필요한 경우 타입도 함께 검토
}));
};
🤖 Prompt for AI Agents
In src/api/books/getSearchBooks.ts around lines 61-70, the function creates id
using index+1 which can cause React key collisions; change id to use the
immutable ISBN (id: book.isbn), update the SearchedBook type so id is a string
(or remove id and rely on isbn field), and then update both BookSearchResult.tsx
(line ~34) and BookSearchBottomSheet.tsx (line ~216) to use key={book.isbn} (or
key={book.id} if you keep id as isbn) to ensure stable keys and correct typing
throughout.

32 changes: 32 additions & 0 deletions src/api/books/postSaveBook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { apiClient } from '../index';

// 북마크 요청 타입
export interface SaveBookRequest {
type: boolean;
}

// 북마크 응답 데이터 타입
export interface SaveBookData {
isbn: string;
isSaved: boolean;
}

// 북마크 응답 타입
export interface SaveBookResponse {
isSuccess: boolean;
code: number;
message: string;
data: SaveBookData;
}

export const postSaveBook = async (isbn: string, type: boolean): Promise<SaveBookResponse> => {
try {
const response = await apiClient.post<SaveBookResponse>(`/books/${isbn}/saved`, {
type: type,
});
return response.data;
Comment on lines +22 to +27
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

요청 바디 타입 적용 및 ISBN URL 인코딩

  • SaveBookRequest 타입을 실제 요청 바디에 적용해 타입 안정성을 높이세요.
  • 경로 파라미터인 ISBN은 안전하게 encodeURIComponent로 인코딩하는 것이 좋습니다.

적용 diff:

-export const postSaveBook = async (isbn: string, type: boolean): Promise<SaveBookResponse> => {
+export const postSaveBook = async (isbn: string, type: boolean): Promise<SaveBookResponse> => {
   try {
-    const response = await apiClient.post<SaveBookResponse>(`/books/${isbn}/saved`, {
-      type: type,
-    });
+    const payload: SaveBookRequest = { type };
+    const response = await apiClient.post<SaveBookResponse>(
+      `/books/${encodeURIComponent(isbn)}/saved`,
+      payload,
+    );
     return response.data;
   } catch (error) {
     console.error('책 저장 API 오류:', error);
     throw 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
export const postSaveBook = async (isbn: string, type: boolean): Promise<SaveBookResponse> => {
try {
const response = await apiClient.post<SaveBookResponse>(`/books/${isbn}/saved`, {
type: type,
});
return response.data;
export const postSaveBook = async (isbn: string, type: boolean): Promise<SaveBookResponse> => {
try {
const payload: SaveBookRequest = { type };
const response = await apiClient.post<SaveBookResponse>(
`/books/${encodeURIComponent(isbn)}/saved`,
payload,
);
return response.data;
} catch (error) {
console.error('책 저장 API 오류:', error);
throw error;
}
};
🤖 Prompt for AI Agents
In src/api/books/postSaveBook.ts around lines 22 to 27, the function currently
posts a plain object and inserts raw isbn into the URL; update it to construct a
payload typed as SaveBookRequest (e.g. const body: SaveBookRequest = { type })
and pass that to apiClient.post to ensure type safety, and encode the isbn in
the path using encodeURIComponent(isbn) so the URL is safe; keep returning
response.data as before.

} catch (error) {
console.error('책 저장 API 오류:', error);
throw error;
}
};
5 changes: 3 additions & 2 deletions src/assets/common/SaveIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/common/filledSaveIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/components/search/BookSearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function BookSearchResult({ type, searchedBookList }: BookSearchResultPro
</EmptyWrapper>
) : (
searchedBookList.map(book => (
<BookItem key={book.id}>
<BookItem key={book.id} onClick={() => navigate(`/search/book/${book.isbn}`)}>
<Cover src={book.coverUrl} alt={`${book.title} 커버`} />
<BookInfo>
<Title>{book.title}</Title>
Expand Down Expand Up @@ -63,6 +63,7 @@ const BookItem = styled.div`
display: flex;
border-bottom: 1px solid ${colors.darkgrey.dark};
padding: 12px 0;
cursor: pointer;
`;

const ResultHeader = styled.div`
Expand Down
113 changes: 68 additions & 45 deletions src/components/search/MostSearchedBooks.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,71 @@
import { colors, typography } from '@/styles/global/global';
import styled from '@emotion/styled';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { getMostSearchedBooks, type MostSearchedBook } from '@/api/books/getMostSearchedBooks';

interface Book {
id: number;
title: string;
coverUrl: string;
}
export default function MostSearchedBooks() {
const [books, setBooks] = useState<MostSearchedBook[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();

const dummyBooks: Book[] = [
{
id: 1,
title: '토마토 컵라면',
coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
},
{
id: 2,
title: '사슴',
coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
},
{
id: 3,
title: '호르몬 체인지지',
coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
},
{
id: 4,
title: '호르몬 체인지지',
coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
},
{
id: 5,
title: '호르몬 체인지지',
coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
},
{
id: 6,
title: '호르몬 체인지지',
coverUrl: 'https://cdn.imweb.me/upload/S20230204e049098f5e744/e6fd3d849546d.jpg',
},
];
useEffect(() => {
const fetchMostSearchedBooks = async () => {
try {
setIsLoading(true);
const response = await getMostSearchedBooks();

export default function MostSearchedBooks() {
if (response.isSuccess) {
setBooks(response.data.bookList);
} else {
setError(response.message);
}
} catch (error) {
console.error('인기 검색 도서 조회 오류:', error);
setError('인기 검색 도서를 불러오는데 실패했습니다.');
} finally {
setIsLoading(false);
}
};

fetchMostSearchedBooks();
}, []);

const handleBookClick = (isbn: string) => {
navigate(`/search/book/${isbn}`);
};

const getCurrentDate = () => {
const now = new Date();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${month}.${day}. 기준`;
};
return (
<Container>
<Header>
<Title>가장 많이 검색된 책</Title>
{/* 서버 응답 포맷을 모르기에 우선 하드 코딩 */}
<DateText>01.12. 기준</DateText>
<DateText>{getCurrentDate()}</DateText>
</Header>
{dummyBooks.length === 0 ? (
{isLoading ? (
<LoadingMessage>로딩 중...</LoadingMessage>
) : error ? (
<EmptyMessage>
<MainText>데이터를 불러올 수 없어요.</MainText>
<SubText>{error}</SubText>
</EmptyMessage>
) : books.length === 0 ? (
<EmptyMessage>
<MainText>아직 순위가 집계되지 않았어요.</MainText>
<SubText>조금만 기다려주세요!</SubText>
</EmptyMessage>
) : (
<BookList>
{dummyBooks.map((book, index) => (
<BookItem key={book.id}>
<Rank>{index + 1}.</Rank>
<Cover src={book.coverUrl} alt={`${book.title} 커버`} />
{books.map(book => (
<BookItem key={book.isbn} onClick={() => handleBookClick(book.isbn)}>
<Rank>{book.rank}.</Rank>
<Cover src={book.imageUrl} alt={`${book.title} 커버`} />
<BookTitle>{book.title}</BookTitle>
</BookItem>
))}
Expand Down Expand Up @@ -106,6 +113,12 @@ const BookItem = styled.li`
align-items: center;
padding: 12px 0;
border-bottom: 1px solid ${colors.darkgrey.dark};
cursor: pointer;
transition: background-color 0.2s ease;

&:hover {
background-color: ${colors.darkgrey.main};
}
`;

const Rank = styled.span`
Expand Down Expand Up @@ -151,3 +164,13 @@ const SubText = styled.div`
color: ${colors.grey[100]};
font-weight: ${typography.fontWeight.regular};
`;

const LoadingMessage = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 200px;
font-size: ${typography.fontSize.base};
color: ${colors.grey[200]};
font-weight: ${typography.fontWeight.regular};
`;
2 changes: 1 addition & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const Router = () => {
<Route path="feed/:feedId" element={<FeedDetailPage />} />
<Route path="search" element={<Search />} />
<Route path="search/applybook" element={<ApplyBook />} />
<Route path="search/book" element={<SearchBook />} />
<Route path="search/book/:isbn" element={<SearchBook />} />
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

동적 라우트 전환 LGTM. 경로 충돌 및 URL 인코딩 점검 권장

  • React Router v6는 정적 세그먼트가 파라미터보다 우선 매칭되므로, search/book/groupsearch/book/:isbn는 충돌하지 않습니다. 그래도 회귀가 없는지 한 번만 확인해 주세요.
  • 이동 시 ISBN에 특수문자가 포함될 수 있으니 encodeURIComponent 적용을 권장합니다.

다음 스크립트로 링크/네비게이션에서 인코딩 누락 가능성을 훑어볼 수 있습니다.


🏁 Script executed:

#!/bin/bash
# /search/book/${...} 사용 지점 나열
rg -n -S "/search/book/\\$\\{" src -g "src/**/*.tsx" -H

echo
echo "encodeURIComponent 사용 지점"
rg -n -S "encodeURIComponent\\(" src -g "src/**/*.tsx" -H

Length of output: 403


동적 라우트 전환 LGTM – URL 인코딩 적용 필요

  • React Router v6는 정적 세그먼트가 파라미터보다 우선 매칭하므로 search/book/groupsearch/book/:isbn는 충돌하지 않습니다. 다만 회귀가 없는지 한 번만 확인해 주세요.
  • 특수문자가 포함된 ISBN을 안전하게 전달하려면 encodeURIComponent를 적용해야 합니다.

수정 대상 위치:

  • src/components/search/MostSearchedBooks.tsx:36
  • src/components/search/BookSearchResult.tsx:34

예시 변경(diff):

- navigate(`/search/book/${isbn}`);
+ navigate(`/search/book/${encodeURIComponent(isbn)}`);
- onClick={() => navigate(`/search/book/${book.isbn}`)}
+ onClick={() => navigate(`/search/book/${encodeURIComponent(book.isbn)}`)}

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

🤖 Prompt for AI Agents
In src/pages/index.tsx around line 66 and in the two components
src/components/search/MostSearchedBooks.tsx:36 and
src/components/search/BookSearchResult.tsx:34, ensure ISBN values are
URL-encoded when constructing links or navigate calls (use
encodeURIComponent(isbn)) so special characters are passed safely to the dynamic
route /search/book/:isbn; update any <Link> or navigate(...) usages to wrap the
isbn with encodeURIComponent, and verify the static route /search/book/group
still matches (no change needed for route order), running a quick sanity check
to avoid regressions.

<Route path="search/book/group" element={<SearchBookGroup />} />
<Route path="otherfeed/:userId" element={<OtherFeedPage />} />
<Route path="follow/:type/:userId" element={<FollowerListPage />} />
Expand Down
Loading