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
55 changes: 55 additions & 0 deletions src/api/feeds/createFeed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { apiClient } from '../index';

/** 서버에 보낼 request JSON 페이로드 */
export interface CreateFeedBody {
isbn: string;
contentBody: string;
isPublic: boolean;
tagList?: string[];
}

/** 성공 응답 */
export interface CreateFeedSuccess {
isSuccess: true;
code: number;
message: string;
data: {
feedId: number;
};
}

/** 실패 응답 */
export interface CreateFeedFail {
isSuccess: false;
code: number;
message: string;
}

export type CreateFeedResponse = CreateFeedSuccess | CreateFeedFail;

/**
* 피드 작성 API
* - multipart/form-data
* - request: application/json (Blob로 감싸 전송)
* - images: File[] (선택값, 없으면 미첨부)
*/
export const createFeed = async (
body: CreateFeedBody,
images?: File[],
): Promise<CreateFeedResponse> => {
const form = new FormData();

// request 파트(JSON) - 필수
form.append('request', new Blob([JSON.stringify(body)], { type: 'application/json' }));

// images 파트들 - 선택
if (images && images.length > 0) {
images.forEach(file => form.append('images', file));
}

const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form, {
headers: { 'Content-Type': 'multipart/form-data' },
});
Comment on lines +50 to +52
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

Content-Type 헤더 설정 제거 필요

multipart/form-data를 사용할 때는 브라우저가 자동으로 boundary를 포함한 Content-Type을 설정합니다. 명시적으로 설정하면 boundary가 누락되어 서버에서 파싱 오류가 발생할 수 있습니다.

-  const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form, {
-    headers: { 'Content-Type': 'multipart/form-data' },
-  });
+  const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form);
📝 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 { data } = await apiClient.post<CreateFeedResponse>('/feeds', form, {
headers: { 'Content-Type': 'multipart/form-data' },
});
const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form);
🤖 Prompt for AI Agents
In src/api/feeds/createFeed.ts around lines 50 to 52, the request explicitly
sets 'Content-Type': 'multipart/form-data', which prevents the browser (or
FormData lib) from adding the required boundary and can cause server parse
errors; remove the manual Content-Type header so the client sets it
automatically, or if this is running in Node with form-data, replace the static
header with the form's generated headers (e.g., use form.getHeaders()) so the
boundary is included.

Comment on lines +50 to +52
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

Content-Type 헤더 설정 확인 필요

axios는 FormData를 전송할 때 자동으로 적절한 Content-Type과 boundary를 설정합니다. 명시적으로 'multipart/form-data'를 설정하면 boundary가 누락될 수 있습니다.

헤더를 제거하거나 axios가 자동으로 설정하도록 하는 것을 고려해보세요:

-const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form, {
-  headers: { 'Content-Type': 'multipart/form-data' },
-});
+const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form);

🏁 Script executed:

#!/bin/bash
# apiClient의 기본 헤더 설정 확인
rg -A 5 "apiClient.*create|axios.create" src/api/

Length of output: 294


FormData 전송 시 Content-Type 자동 설정 검토 필요

현재 apiClient 인스턴스의 기본 헤더로 'Content-Type': 'application/json'이 설정되어 있어, FormData 전송 시에도 이 헤더가 적용됩니다. axios는 FormData를 전송할 때 boundary를 포함한 올바른 Content-Type을 자동으로 설정하지만, 기본 헤더나 명시적 헤더가 있으면 자동 설정이 무시됩니다. 다음 중 하나의 방식을 적용해 주세요:

  • src/api/feeds/createFeed.ts (라인 50–52)
    요청할 때 default Content-Type 헤더를 제거하도록 override

    - const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form, {
    -   headers: { 'Content-Type': 'multipart/form-data' },
    - });
    + const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form, {
    +   headers: { 'Content-Type': undefined },
    + });
  • src/api/index.ts
    apiClient 기본 헤더에서 Content-Type 설정 제거 (JSON 전송 시에만 별도 명시)

    - export const apiClient = axios.create({
    -   baseURL: API_BASE_URL,
    -   timeout: 10000,
    -   headers: {
    -     'Content-Type': 'application/json',
    -   },
    - });
    + export const apiClient = axios.create({
    +   baseURL: API_BASE_URL,
    +   timeout: 10000,
    + });

위 수정 후, FormData 전송 시 브라우저(또는 axios)가 자동으로 설정하는 Content-Type과 boundary가 올바르게 포함되는지 확인해 주세요.

📝 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 { data } = await apiClient.post<CreateFeedResponse>('/feeds', form, {
headers: { 'Content-Type': 'multipart/form-data' },
});
const { data } = await apiClient.post<CreateFeedResponse>('/feeds', form, {
headers: { 'Content-Type': undefined },
});
🤖 Prompt for AI Agents
In src/api/feeds/createFeed.ts around lines 50–52, the request explicitly sets
'Content-Type': 'multipart/form-data' which conflicts with axios/FormData's
automatic header (including the boundary); remove the Content-Type header
override for this POST so axios can set the correct multipart boundary
automatically (alternatively, remove the default 'Content-Type' from apiClient
in src/api/index.ts and only set 'application/json' where needed), then verify
FormData requests include the correct Content-Type with boundary.


return data;
};
4 changes: 2 additions & 2 deletions src/api/feeds/getFeedDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface FeedDetailData {
bookTitle: string;
bookAuthor: string;
contentBody: string;
contentsUrl: string[];
contentUrls: string[];
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

API 응답 키 rename(contentUrls, isSuccess): 방향성 및 일관성 LGTM. 잔존 사용처 점검 권장

서버-클라이언트 계약 불일치로 발생하던 이미지 미노출 문제를 해결하는 바람직한 변경입니다. 동일 패턴(Other/My/Total Feed 등)으로 전역 적용도 잘 맞습니다. 남아있는 legacy 키 사용을 한번 더 스캔해 주세요.

다음 스크립트로 레거시 키 사용 여부를 전역 점검할 수 있습니다. 기대 결과는 빈 출력(= 더 이상 사용 없음)입니다.

Also applies to: 26-26


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) 레거시 'contentsUrl' 사용처 검색 (없어야 정상)"
rg -n -S '\bcontentsUrl\b' --glob '!node_modules'

echo
echo "2) 레거시 'success' 키 사용처 검색 (isSuccess 제외)"
rg -n -S -P '(?<!is)success\s*[:=]' --glob '!node_modules' | rg -v 'isSuccess' || true

echo
echo "3) 혹시 모를 snake_case 'contents_url' 검색"
rg -n -S '\bcontents_url\b' --glob '!node_modules' || true

echo
echo "4) 프로퍼티 접근 형태 '.success' 검색 ('.isSuccess'는 제외)"
rg -n -S -P '\.success\b' --glob '!node_modules' || true

Length of output: 160


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "1) 레거시 'contentsUrl' 사용처 검색 (없어야 정상)"
rg -n -S '\bcontentsUrl\b' --glob '!node_modules' || true

echo
echo "2) 레거시 'success' 키 사용처 검색 (isSuccess 제외)"
rg -n -S -P '(?<!is)success\s*[:=]' --glob '!node_modules' | rg -v 'isSuccess' || true

echo
echo "3) 혹시 모를 snake_case 'contents_url' 검색"
rg -n -S '\bcontents_url\b' --glob '!node_modules' || true

echo
echo "4) 프로퍼티 접근 형태 '.success' 검색 ('.isSuccess'는 제외)"
rg -n -S -P '\.success\b' --glob '!node_modules' | rg -v '\.isSuccess\b' || true

Length of output: 1740


남아있는 success 프로퍼티 사용처 점검 및 갱신 필요
API 응답 키를 success에서 isSuccess로 변경하셨다면, 내부 매핑과 소비 측 코드에서도 동일하게 반영해야 합니다. 아래 위치들을 확인 후 적절히 수정해 주세요.

  • src/hooks/useCreateFeed.ts
    • 26, 37, 49, 58, 68, 86, 93, 102행:

    return { success: /*…*/ }

    isSuccess를 매핑하거나 리턴 프로퍼티명을 isSuccess로 변경

  • src/pages/post/CreatePost.tsx
    • 55행의 onSuccess: 콜백명(onSuccess)은 그대로 두셔도 무방
    • 92행:

    if (result?.success) {  }

    result?.isSuccess 또는 내부 매핑 확인

  • src/pages/signup/SignupGenre.tsx
    • 94행:

    if (result.success) {  }

    result.isSuccess로 교체 검토

  • (선택) feed 외 엔드포인트
    • src/api/users/getAlias.ts, src/api/users/postSignup.ts 등
    → 해당 API들은 아직 success를 쓰는 것이 의도된 것인지 확인

위 항목들을 반영하여 잔여 legacy 키(success)를 제거 및 통일해 주세요.

likeCount: number;
commentCount: number;
isSaved: boolean;
Expand All @@ -23,7 +23,7 @@ export interface FeedDetailData {

// API 응답 타입
export interface FeedDetailResponse {
success: boolean;
isSuccess: boolean;
code: number;
message: string;
data: FeedDetailData;
Expand Down
2 changes: 1 addition & 1 deletion src/api/feeds/getMyFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface MyFeedData {

// API 응답 타입
export interface MyFeedResponse {
success: boolean;
isSuccess: boolean;
code: number;
message: string;
data: MyFeedData;
Expand Down
4 changes: 2 additions & 2 deletions src/api/feeds/getOtherFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface OtherFeedItem {
bookTitle: string;
bookAuthor: string;
contentBody: string;
contentsUrl: string[];
contentUrls: string[];
likeCount: number;
commentCount: number;
isSaved: boolean;
Expand All @@ -21,7 +21,7 @@ export interface OtherFeedData {

// API 응답 타입
export interface OtherFeedResponse {
success: boolean;
isSuccess: boolean;
code: number;
message: string;
data: OtherFeedData;
Expand Down
2 changes: 1 addition & 1 deletion src/api/feeds/getTotalFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface TotalFeedData {

// API 응답 타입
export interface TotalFeedResponse {
success: boolean;
isSuccess: boolean;
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

응답 필드 success → isSuccess로의 리네임: 전역 일관성 및 서버 스펙 확인 필요

  • 클라이언트 전반에서 .success 접근/구조분해가 남아있으면 런타임 오류가 납니다. 공용 인터셉터나 헬퍼가 .success를 참조하지 않는지 확인해 주세요.
  • 서버가 실제로 isSuccess를 반환하는지도 반드시 확인해야 합니다. 서버가 success를 그대로 돌려주면 타입만 바뀌고 런타임은 깨집니다.

다음 스크립트로 레포 내 남아있는 사용처를 점검할 수 있습니다.

추가로, 응답 스키마 중복을 줄이기 위해 공용 제네릭 타입 도입을 권장드립니다. 예:

// src/types/api.ts
export interface ApiResponse<T> {
  isSuccess: boolean;
  code: number;
  message: string;
  data: T;
}

이후 본 파일에서는:

export type TotalFeedResponse = ApiResponse<TotalFeedData>;

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[검색] contentsUrl, contentUrl, contentsUrls 잔존 레거시 키 탐지"
rg -n --hidden -S '\bcontentsUrl\b|\bcontentUrl\b|\bcontentsUrls\b' -g '!node_modules' -g '!dist' -g '!build' -g '!.next' || true

echo
echo "[검색] .success 프로퍼티 접근 탐지"
rg -n --hidden -S '\.success\b' -g '!node_modules' -g '!dist' -g '!build' -g '!.next' || true

echo
echo "[검색] 구조분해 { success } 탐지"
rg -n --hidden -S '\{\s*success\s*(,|\})' -g '!node_modules' -g '!dist' -g '!build' -g '!.next' || true

echo
echo "[검색] 타입/인터페이스에서 success: 키 정의 탐지"
rg -n --hidden -S '\bsuccess\s*:' --glob '!node_modules' --glob '!dist' --glob '!build' --glob '!.next' || true

echo
echo "[점검] PostData가 contentUrls를 포함하는지 확인"
rg -n --hidden -A 20 'interface\s+PostData' src || true

echo
echo "[점검] apiClient 인터셉터에서 success 참조 여부 확인"
fd index.ts src/api | xargs -I{} rg -n --hidden -A 3 -B 3 '\bsuccess\b' {} || true

Length of output: 2887


success → isSuccess 리네임: 남은 참조/타입 일괄 수정 필요

아래 위치들에서 여전히 success 필드를 사용하고 있어 런타임 오류가 발생합니다. 모두 isSuccess로 변경해주세요.

  • src/pages/signup/SignupGenre.tsx:94 (if (result.success))
  • src/pages/post/CreatePost.tsx:92 (if (result?.success))
  • src/hooks/useCreateFeed.ts
    • 반환 객체의 success: …isSuccess: … (26, 37, 49, 58, 68, 86, 93, 102번째 줄)

추가로:

  • 서버가 실제로 isSuccess를 반환하는지 백엔드 스펙을 확인해 주세요.
  • API 응답 타입 중복을 줄이기 위해 제네릭 ApiResponse<T> 타입 도입을 검토해 보시길 권장합니다.
🤖 Prompt for AI Agents
In src/api/feeds/getTotalFeed.ts around line 13 and the codebase, the response
field was renamed from success to isSuccess but several call sites and return
shapes still use success; update all usages to isSuccess: change
src/pages/signup/SignupGenre.tsx line ~94 (if (result.success) → if
(result.isSuccess)), src/pages/post/CreatePost.tsx line ~92 (if
(result?.success) → if (result?.isSuccess)), and update
src/hooks/useCreateFeed.ts where the hook currently returns success in multiple
places (lines ~26, 37, 49, 58, 68, 86, 93, 102) to return isSuccess instead;
additionally verify the backend actually returns isSuccess in the API spec and
consider introducing a generic ApiResponse<T> type to centralize the response
shape to avoid future mismatches.

code: number;
message: string;
data: TotalFeedData;
Expand Down
128 changes: 128 additions & 0 deletions src/api/images/uploadImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { apiClient } from '../index';

/** 단일 이미지 업로드 성공 시 데이터 */
export interface UploadImageData {
imageUrl: string;
}

/** 서버 공통 응답 타입 */
export interface UploadImageResponse {
isSuccess: boolean;
code: number;
message: string;
data?: UploadImageData; // 성공 시에만 존재
}

/** 내부 유틸: 허용 확장자 */
const IMAGE_EXT_REGEX = /\.(jpe?g|png|gif)$/i;
/** 가이드 최대 업로드 개수 (서버는 FEED 생성 시 최대 3장 제약) */
export const MAX_IMAGES = 3;

/** 파일 사전 검증: 빈 파일 / 확장자 */
function validateFile(file: File) {
if (!file || file.size === 0) {
// 서버 코드 170001과 의미 일치
throw new Error('업로드하려는 이미지가 비어있습니다.');
}
if (!IMAGE_EXT_REGEX.test(file.name)) {
// 서버 코드 170003과 의미 일치
throw new Error('파일 형식은 jpg, jpeg, png, gif만 가능합니다.');
}
}
Comment on lines +22 to +31
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

확장자만으로는 불충분 — MIME 타입 기반 검사 추가

캡처 이미지처럼 확장자가 없는 파일명 케이스가 존재합니다. file.type('image/*')로 1차 검증하고, 불명확할 때만 확장자 regex를 보조로 쓰는 게 안전합니다.

 function validateFile(file: File) {
   if (!file || file.size === 0) {
     // 서버 코드 170001과 의미 일치
     throw new Error('업로드하려는 이미지가 비어있습니다.');
   }
-  if (!IMAGE_EXT_REGEX.test(file.name)) {
+  const isImageByMime = !!file.type && file.type.startsWith('image/');
+  const isImageByName = IMAGE_EXT_REGEX.test(file.name || '');
+  if (!isImageByMime && !isImageByName) {
     // 서버 코드 170003과 의미 일치
-    throw new Error('파일 형식은 jpg, jpeg, png, gif만 가능합니다.');
+    throw new Error('이미지 파일만 업로드할 수 있습니다. (jpg, jpeg, png, gif)');
   }
 }
📝 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
function validateFile(file: File) {
if (!file || file.size === 0) {
// 서버 코드 170001과 의미 일치
throw new Error('업로드하려는 이미지가 비어있습니다.');
}
if (!IMAGE_EXT_REGEX.test(file.name)) {
// 서버 코드 170003과 의미 일치
throw new Error('파일 형식은 jpg, jpeg, png, gif만 가능합니다.');
}
}
function validateFile(file: File) {
if (!file || file.size === 0) {
// 서버 코드 170001과 의미 일치
throw new Error('업로드하려는 이미지가 비어있습니다.');
}
const isImageByMime = !!file.type && file.type.startsWith('image/');
const isImageByName = IMAGE_EXT_REGEX.test(file.name || '');
if (!isImageByMime && !isImageByName) {
// 서버 코드 170003과 의미 일치
throw new Error('이미지 파일만 업로드할 수 있습니다. (jpg, jpeg, png, gif)');
}
}
🤖 Prompt for AI Agents
In src/api/images/uploadImage.ts around lines 22-31, the current validateFile
only checks filename extension; change it to first verify MIME by checking
file.type and accept when file.type && file.type.startsWith('image/'), otherwise
fall back to the existing IMAGE_EXT_REGEX check (use a lowercased file.name when
testing) and keep the same error throws for empty files and invalid images;
ensure the size check remains first and that the logic treats missing/empty
file.type as requiring the extension check.


/** 단일 이미지 업로드 */
export const uploadImage = async (
file: File,
options?: { signal?: AbortSignal },
): Promise<UploadImageResponse> => {
// 사전 검증
validateFile(file);

const formData = new FormData();
// 서버가 단일 업로드에서 기대하는 필드명이 image라면 유지
formData.append('image', file);

const { data } = await apiClient.post<UploadImageResponse>('/images/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
signal: options?.signal,
});
Comment on lines +45 to +48
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

FormData 전송 시 Content-Type 수동 지정 금지(브라우저가 boundary 자동 설정)

Axios 브라우저 환경에서 FormData를 보낼 때 'Content-Type'을 직접 설정하면 boundary가 빠져 서버에서 파싱 실패할 수 있습니다. 명시 헤더를 제거하세요.

-  const { data } = await apiClient.post<UploadImageResponse>('/images/upload', formData, {
-    headers: { 'Content-Type': 'multipart/form-data' },
-    signal: options?.signal,
-  });
+  const { data } = await apiClient.post<UploadImageResponse>('/images/upload', formData, {
+    signal: options?.signal,
+  });
📝 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 { data } = await apiClient.post<UploadImageResponse>('/images/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
signal: options?.signal,
});
const { data } = await apiClient.post<UploadImageResponse>('/images/upload', formData, {
signal: options?.signal,
});
🤖 Prompt for AI Agents
In src/api/images/uploadImage.ts around lines 45 to 48, the code sets
'Content-Type': 'multipart/form-data' manually which prevents the browser from
adding the required boundary; remove the explicit Content-Type header (omit the
headers object or at least the Content-Type entry) so the browser/axios can set
the correct multipart boundary automatically, keeping the signal option
unchanged.


return data;
};

/**
* 다중 이미지 업로드
* - 전부 성공하면 URL 배열 반환
* - 하나라도 실패하면 실패 내역을 포함해 throw
*/
export const uploadMultipleImages = async (
files: File[],
options?: { signal?: AbortSignal; enforceMax?: boolean },
): Promise<string[]> => {
// 개수 제한(선택) – FEED 생성 정책에 맞춰 사전 차단하고 싶을 때 사용
if (options?.enforceMax && files.length > MAX_IMAGES) {
throw new Error(`이미지는 최대 ${MAX_IMAGES}장까지 업로드할 수 있습니다.`);
}

// 파일별 사전 검증
files.forEach(validateFile);

// 병렬 업로드 (각 요청 독립)
const results = await Promise.allSettled(
files.map(file => uploadImage(file, { signal: options?.signal })),
);

const successUrls: string[] = [];
const failures: { index: number; reason: string }[] = [];

results.forEach((res, idx) => {
if (res.status === 'fulfilled') {
const value = res.value;
if (value.isSuccess && value.data?.imageUrl) {
successUrls.push(value.data.imageUrl);
} else {
failures.push({
index: idx,
reason: value.message || '파일 업로드에 실패하였습니다.',
});
}
} else {
failures.push({
index: idx,
reason: (res.reason as Error)?.message || '네트워크 오류로 파일 업로드에 실패하였습니다.',
});
}
});

if (failures.length > 0) {
// 어떤 항목이 왜 실패했는지 상세 메시지
const detail = failures.map(f => `#${f.index + 1}: ${f.reason}`).join(' / ');
throw new Error(`일부 이미지 업로드에 실패했습니다. (${detail})`);
}

return successUrls;
};

/*
사용 예시:

// 단일
try {
const res = await uploadImage(file);
if (res.isSuccess) {
console.log('업로드된 URL:', res.data?.imageUrl);
} else {
console.error('실패:', res.message);
}
} catch (e) {
console.error('오류:', e);
}

// 다중
try {
const urls = await uploadMultipleImages(files, { enforceMax: true });
console.log('업로드된 URL들:', urls);
} catch (e) {
console.error('다중 업로드 실패:', e);
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface Book {
title: string;
author: string;
cover: string;
isbn: string;
}

interface BookSearchBottomSheetProps {
Expand All @@ -36,25 +37,27 @@ interface BookSearchBottomSheetProps {

type TabType = 'saved' | 'group';

// Mock Data
const mockSavedBooks: Book[] = [
{
id: 1,
title: '토마토 컵라면',
author: '작가명',
cover: '/src/assets/books/tomato.svg',
isbn: '9780374500016',
},
{
id: 2,
title: '사슴',
author: '작가명',
cover: '/src/assets/books/deer.svg',
isbn: '9781234567891',
},
{
id: 3,
title: '호르몬 체인지',
author: '작가명',
cover: '/src/assets/books/hormone.svg',
isbn: '9781234567892',
},
];

Expand All @@ -64,18 +67,21 @@ const mockGroupBooks: Book[] = [
title: '단 한번의 삶',
author: '작가명',
cover: '/src/assets/books/life.svg',
isbn: '9781234567893',
},
{
id: 5,
title: '호르몬 체인지',
author: '작가명',
cover: '/src/assets/books/hormone.svg',
isbn: '9781234567892',
},
{
id: 6,
title: '토마토 컵라면',
author: '작가명',
cover: '/src/assets/books/tomato.svg',
isbn: '9781234567890',
},
];

Expand Down
6 changes: 3 additions & 3 deletions src/components/common/Post/PostBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ const PostBody = ({
bookAuthor,
contentBody,
feedId,
contentsUrl = [],
contentUrls = [],
}: PostBodyProps) => {
const navigate = useNavigate();
const hasImage = contentsUrl.length > 0;
const hasImage = contentUrls.length > 0;

const handlePostClick = (feedId: number) => {
// if (!isClickable) return;
Expand All @@ -68,7 +68,7 @@ const PostBody = ({
<div className="content">{contentBody}</div>
{hasImage && (
<div className="imgContainer">
{contentsUrl.map((src: string, i: number) => (
{contentUrls.map((src: string, i: number) => (
<img key={i} src={src} />
))}
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/components/feed/FeedDetailPostBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ const FeedDetailPostBody = ({
isbn,
bookAuthor,
contentBody,
contentsUrl = [],
contentUrls = [],
tags = [],
}: FeedDetailPostBodyProps) => {
const [isImageViewerOpen, setIsImageViewerOpen] = useState(false);
const [selectedImageIndex, setSelectedImageIndex] = useState(0);

const hasImage = contentsUrl.length > 0;
const hasImage = contentUrls.length > 0;
const hasTag = tags.length > 0;

const handleImageClick = (index: number) => {
Expand All @@ -106,7 +106,7 @@ const FeedDetailPostBody = ({
<div className="content">{contentBody}</div>
{hasImage && (
<div className="imgContainer">
{contentsUrl.map((src: string, i: number) => (
{contentUrls.map((src: string, i: number) => (
<img key={i} src={src} alt={`이미지 ${i + 1}`} onClick={() => handleImageClick(i)} />
))}
</div>
Expand All @@ -126,7 +126,7 @@ const FeedDetailPostBody = ({
</PostContent>
{isImageViewerOpen && (
<ImageViewer
images={contentsUrl}
images={contentUrls}
initialIndex={selectedImageIndex}
isOpen={isImageViewerOpen}
onClose={handleCloseImageViewer}
Expand Down
8 changes: 4 additions & 4 deletions src/data/postData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const mockPosts: PostData[] = [
bookTitle: '제목입니다',
bookAuthor: '작가입니다',
contentBody: '내용입니다…',
contentsUrl: ['https://placehold.co/100x100', 'https://placehold.co/100x100'],
contentUrls: ['https://placehold.co/100x100', 'https://placehold.co/100x100'],
likeCount: 125,
commentCount: 125,
isSaved: false,
Expand All @@ -33,7 +33,7 @@ export const mockPosts: PostData[] = [
bookAuthor: '작가입니다',
contentBody:
'내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다',
contentsUrl: [],
contentUrls: [],
likeCount: 125,
commentCount: 125,
isSaved: true,
Expand All @@ -52,7 +52,7 @@ export const mockPosts: PostData[] = [
bookAuthor: '작가입니다',
contentBody:
'내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다내용입니다',
contentsUrl: [],
contentUrls: [],
likeCount: 125,
commentCount: 125,
isSaved: false,
Expand All @@ -74,7 +74,7 @@ export const mockFeedPost: FeedPostProps = {
bookAuthor: '한강',
contentBody:
'정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.',
contentsUrl: [test2, 'https://placehold.co/300x300', test],
contentUrls: [test2, 'https://placehold.co/300x300', test],
likeCount: 15,
commentCount: 2,
isSaved: true,
Expand Down
Loading