Skip to content

Conversation

@psm1st
Copy link
Member

@psm1st psm1st commented May 29, 2025

🧐 체크리스트

  • 코드 실행 확인: yarn build 또는 npm run build 실행 후 정상적으로 동작하는지 확인했나요?
  • 테스트 통과 여부: Jest 테스트를 실행했고 모든 테스트가 통과했나요? (yarn test)
  • ESLint & Prettier 검사: yarn lintyarn prettify 실행 후 문제가 없나요?
  • PR 제목 확인: PR 제목이 feat:, fix:, chore: 등 커밋 컨벤션을 따르고 있나요?
  • 변경 사항 설명: PR 설명에 변경된 내용을 충분히 작성했나요?
  • 리뷰어가 이해하기 쉽게 작성: 코드 리뷰어가 쉽게 이해할 수 있도록 가독성 높은 코드를 작성했나요?
  • 문서 업데이트 필요 여부: 기능 추가/변경 사항이 있다면 README.md 또는 관련 문서를 업데이트했나요?

Summary by CodeRabbit

  • 신규 기능

    • 제품 제목을 가져오는 기능이 추가되었습니다.
    • 건강 정보, 식품, 화장품 성분 분석 요청을 위한 새로운 API 연동 기능이 도입되었습니다.
  • 기능 개선

    • 식품, 건강, 화장품 성분 분석 컴포넌트의 데이터 수집 및 표시 방식이 개선되어 더 안정적이고 간결한 UI를 제공합니다.
    • 알레르기 정보 저장 시 영어 코드로 변환하여 저장하도록 개선되었습니다.
    • 알레르기 섹션 기본 오픈 및 토글 버튼 제거 등 UI 사용성이 향상되었습니다.
    • 로딩 화면이 통일된 컴포넌트와 스타일로 개선되었습니다.
  • 버그 수정

    • 데이터 요청 및 응답 처리 과정에서 오류 발생 시 적절한 안내와 예외 처리가 추가되었습니다.
    • 메시지 응답 처리 로직이 개선되어 중복 호출 및 누락이 방지되었습니다.
  • 리팩터

    • 불필요한 DOM 관찰 및 수동 렌더링 로직이 제거되고, React 컴포넌트 기반으로 구조가 단순화되었습니다.
    • 내부 상태 관리 및 데이터 요청 흐름이 단순화되고 명확해졌습니다.
  • 기타

    • 내부 코드 정리 및 불필요한 파일 삭제가 이루어졌습니다.

@coderabbitai
Copy link

coderabbitai bot commented May 29, 2025

"""

Walkthrough

이 PR은 식품, 화장품, 건강 제품 관련 React 컴포넌트의 데이터 흐름을 전면적으로 리팩토링하여, 더 이상 DOM 관찰자나 수동 렌더링을 사용하지 않고, 크롬 메시지 기반의 비동기 데이터 요청 및 응답 구조로 통일하였습니다. 또한, 백그라운드와 콘텐츠 스크립트의 메시지 핸들러, API 요청 함수, 타입 선언, UI 구조가 개선되었습니다.

Changes

파일/경로 요약 변경 내용 요약
src/background/index.ts "FETCH_FOOD_DATA" 및 "FETCH_COSMETIC_DATA" 메시지 핸들러 리팩토링, URL 및 productId 검증, 에러 및 응답 처리 개선, "GET_PRODUCT_TITLE" 메시지 리스너 추가
src/components/productComponents/foodComponent.tsx
src/components/productComponents/cosmeticComponent.tsx
src/components/productComponents/healthComponent.tsx
각 제품 컴포넌트의 데이터 fetch/렌더링 로직을 MutationObserver/DOM 의존에서 크롬 메시지 기반 비동기로 변경, UI 구조 및 상태 간소화, 에러 처리 및 로딩 UI 개선
src/components/sidebar/component.tsx useRef, useEffect, observeBreadcrumb* 함수 사용 제거, 직접 React 컴포넌트 렌더링으로 전환
src/content/apiSetting/sendCosmeticDataRequest.tsx
src/content/apiSetting/sendHealthDataRequest.tsx
화장품/건강 데이터 요청 함수 및 타입 신규 추가, 크롬 메시지 기반 API 통신 구현
src/content/apiSetting/sendFoodDataRequest.tsx API 응답 구조 변경(nutrientResponse 중첩), 반환 타입 및 검증 로직 수정
src/content/index.tsx observeAndStoreCategoryType 함수 비동기화, waitForEl 유틸 추가, "GET_PRODUCT_TITLE" 메시지 리스너 추가, ".vendor-item" 엘리먼트 대기 및 HTML/productId 추출 로직 개선
src/tabs/myInfo/components/AllergySelectForm.tsx AllergyType enum 및 한영 매핑 추가, 저장 시 enum 값으로 변환하여 저장
src/content/coupang/categoryHandler/categoryHandlerFood.tsx
src/content/coupang/categoryHandler/categoryHandlerCosmetic.tsx
src/content/coupang/categoryHandler/categoryHandlerHealth.tsx
observeBreadcrumb* 함수 및 관련 React 렌더링/DOM 관찰 코드 삭제

Sequence Diagram(s)

sequenceDiagram
    participant Sidebar
    participant FoodComponent
    participant Background
    participant ContentScript

    Sidebar->>FoodComponent: 렌더링 시 mount
    FoodComponent->>ContentScript: "GET_VENDOR_HTML" 메시지 요청 (재시도 포함)
    ContentScript->>FoodComponent: { html, productId }
    FoodComponent->>Background: "FETCH_FOOD_DATA" 메시지 (payload 포함)
    Background->>FoodComponent: API 응답 (allergyTypes, nutrients 등)
    FoodComponent->>ContentScript: "GET_PRODUCT_TITLE" 메시지
    ContentScript->>FoodComponent: { title }
    FoodComponent->>FoodComponent: 상태 업데이트 및 UI 렌더링
Loading
sequenceDiagram
    participant Sidebar
    participant CosmeticComponent
    participant Background
    participant ContentScript

    Sidebar->>CosmeticComponent: 렌더링 시 mount
    CosmeticComponent->>ContentScript: "FETCH_VENDOR_HTML" 메시지 (재시도 포함)
    ContentScript->>CosmeticComponent: { html, productId }
    CosmeticComponent->>Background: "FETCH_COSMETIC_DATA" 메시지
    Background->>CosmeticComponent: { detected ingredient keys }
    CosmeticComponent->>CosmeticComponent: 상태 업데이트 및 UI 렌더링
Loading

Possibly related PRs

Poem

🥕
새벽 햇살 아래서,
코드밭을 갈아엎었죠.
DOM 감시는 안녕,
메시지로 소통해요!
건강과 영양, 화장품까지
토끼는 기뻐하며
React 밭에서 폴짝폴짝 뛰어요!
🐇✨
"""

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/components/productComponents/infoComponent.tsx

Oops! Something went wrong! :(

ESLint: 9.27.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/eslint/lib/config/config-loader.js:142:9
at async loadTypeScriptConfigFileWithJiti (/node_modules/eslint/lib/config/config-loader.js:141:25)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:259:11)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:578:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:759:23)
at async /node_modules/eslint/lib/eslint/eslint.js:760:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:757:19)
at async Object.execute (/node_modules/eslint/lib/cli.js:639:14)
at async main (/node_modules/eslint/bin/eslint.js:175:19)

src/components/productComponents/cosmeticComponent.tsx

Oops! Something went wrong! :(

ESLint: 9.27.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/eslint/lib/config/config-loader.js:142:9
at async loadTypeScriptConfigFileWithJiti (/node_modules/eslint/lib/config/config-loader.js:141:25)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:259:11)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:578:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:759:23)
at async /node_modules/eslint/lib/eslint/eslint.js:760:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:757:19)
at async Object.execute (/node_modules/eslint/lib/cli.js:639:14)
at async main (/node_modules/eslint/bin/eslint.js:175:19)

src/components/sidebar/component.tsx

Oops! Something went wrong! :(

ESLint: 9.27.0

Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.
at /node_modules/eslint/lib/config/config-loader.js:142:9
at async loadTypeScriptConfigFileWithJiti (/node_modules/eslint/lib/config/config-loader.js:141:25)
at async loadConfigFile (/node_modules/eslint/lib/config/config-loader.js:259:11)
at async ConfigLoader.calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:578:23)
at async #calculateConfigArray (/node_modules/eslint/lib/config/config-loader.js:759:23)
at async /node_modules/eslint/lib/eslint/eslint.js:760:6
at async Promise.all (index 0)
at async ESLint.lintFiles (/node_modules/eslint/lib/eslint/eslint.js:757:19)
at async Object.execute (/node_modules/eslint/lib/cli.js:639:14)
at async main (/node_modules/eslint/bin/eslint.js:175:19)

✨ 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.
    • 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 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

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

🔭 Outside diff range comments (1)
src/tabs/myInfo/components/AllergySelectForm.tsx (1)

158-176: 💡 Verification agent

🧩 Analysis chain

enum 변환 로직이 안전하게 구현되었습니다.

선택된 알레르기 이름들을 enum 값으로 변환하는 로직이 적절합니다. filter(Boolean)을 사용하여 undefined 값을 제거하는 것도 좋은 방어 코드입니다.

다른 컴포넌트에서 이 enum 타입을 올바르게 사용하는지 확인해주세요.


🏁 Script executed:

#!/bin/bash
# Description: AllergyType enum 사용 현황을 확인합니다.

# AllergyType 사용 위치 검색
rg -A 3 "AllergyType" --type tsx --type ts

Length of output: 70


다른 컴포넌트에서 AllergyType 사용 현황을 .ts 및 .tsx 파일에서 다시 검색해 보겠습니다.


🏁 Script executed:

#!/bin/bash
# Description: 모든 .ts/.tsx 파일에서 AllergyType 사용 위치를 확인합니다.
rg "AllergyType" -A 3 -g '*.ts' -g '*.tsx'

Length of output: 3647


다른 컴포넌트의 AllergyType 사용 업데이트 필요

AllergyType enum 변환 로직은 적절하지만, 다른 컴포넌트에서 여전히 문자열 배열(string[])으로 처리되고 있어 enum 타입으로 변경이 필요합니다.

  • src/components/productComponents/foodComponent.tsx
    • useState 제네릭 타입을 string[] | null 에서 AllergyType[] | null 로 변경
      - const [allergyTypes, setAllergyTypes] = useState<string[] | null>(null);
      + const [allergyTypes, setAllergyTypes] = useState<AllergyType[] | null>(null);
    • API 결과(result.allergyTypes)를 enum 배열로 변환하는 로직 추가
      import { allergyNameToEnumMap, AllergyType } from 'src/tabs/myInfo/components/AllergySelectForm';
      
      // …
      const enumList = (result.allergyTypes || [])
        .map(name => allergyNameToEnumMap[name])
        .filter(Boolean) as AllergyType[];
      setAllergyTypes(enumList);

위 항목들을 반영해 enum 타입 일관성을 유지해주세요.

🧹 Nitpick comments (6)
src/components/sidebar/component.tsx (1)

112-114: 아키텍처 개선으로 코드가 단순해졌습니다.

복잡한 DOM 관찰 로직을 제거하고 React 컴포넌트를 직접 렌더링하는 방식으로 변경한 것이 훌륭합니다. 이는 다음과 같은 이점을 제공합니다:

  • 더 예측 가능한 렌더링
  • 디버깅 용이성
  • React의 생명주기와 일관된 동작
src/content/apiSetting/sendCosmeticDataRequest.tsx (1)

12-45: 견고한 에러 처리와 데이터 변환 로직입니다.

함수가 다음과 같이 잘 구현되었습니다:

  • Promise 기반의 현대적인 API
  • 응답 형식 검증
  • 적절한 에러 메시지
  • 데이터 필터링 및 변환

작은 개선사항을 제안합니다.

 export const sendCosmeticDataRequest = (
     payload: CosmeticRequestPayload,
 ): Promise<string[]> => {
+    console.log('[sendCosmeticDataRequest] 요청 시작:', payload.productId);
     return new Promise((resolve, reject) => {

디버깅을 위해 요청 시작 로그를 추가하는 것을 고려해보세요.

src/content/apiSetting/sendHealthDataRequest.tsx (1)

14-56: 로깅과 에러 처리가 잘 구현되었습니다.

함수의 장점:

  • 상세한 디버깅 로그
  • 견고한 응답 검증
  • 명확한 에러 메시지

일관성 개선을 위한 제안:

 export const sendHealthDataRequest = (
     payload: HealthRequestPayload,
 ): Promise<string[]> => {
     return new Promise((resolve, reject) => {
-        console.log("[voim] HEALTH API 요청 payload:", payload);
+        console.log("[sendHealthDataRequest] 요청 payload:", payload);

         chrome.runtime.sendMessage(
             {
                 type: "FETCH_HEALTH_DATA",
                 payload,
             },
             (response: unknown) => {
                 if (!response || typeof response !== "object") {
                     console.error(
-                        "[voim] HEALTH API 응답 형식 오류:",
+                        "[sendHealthDataRequest] 응답 형식 오류:",
                         response,
                     );

로그 접두사를 함수명과 일치시켜 일관성을 높이는 것을 고려해보세요.

src/components/productComponents/cosmeticComponent.tsx (1)

50-111: 재시도 로직이 잘 구현되었습니다. 유지보수성을 위한 개선을 고려해보세요.

현재 구현은 잘 동작하지만, 재시도 설정을 상수로 추출하면 유지보수가 더 쉬워질 것입니다.

컴포넌트 상단에 다음 상수들을 추가하는 것을 고려해보세요:

const MAX_RETRY_ATTEMPTS = 10;
const RETRY_INTERVAL_MS = 500;

그리고 재시도 로직에서 이 상수들을 사용하세요:

-let retries = 10;
+let retries = MAX_RETRY_ATTEMPTS;
-}, 500);
+}, RETRY_INTERVAL_MS);
src/components/productComponents/foodComponent.tsx (1)

69-79: 제품 제목 가져오기 함수에 개선이 필요합니다.

현재 함수에서 reject 매개변수가 정의되었지만 사용되지 않습니다. 에러 처리를 일관성 있게 유지하는 것이 좋겠습니다.

-    const getProductTitle = (): Promise<string> => {
-        return new Promise((resolve, reject) => {
+    const getProductTitle = (): Promise<string> => {
+        return new Promise((resolve) => {
src/components/productComponents/healthComponent.tsx (1)

170-170: 오타가 있습니다.

transperenttransparent의 오타입니다.

-                        backgroundColor: "transperent",
+                        backgroundColor: "transparent",
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 754ea9d and 4400e21.

📒 Files selected for processing (13)
  • src/background/index.ts (3 hunks)
  • src/components/productComponents/cosmeticComponent.tsx (3 hunks)
  • src/components/productComponents/foodComponent.tsx (6 hunks)
  • src/components/productComponents/healthComponent.tsx (1 hunks)
  • src/components/sidebar/component.tsx (2 hunks)
  • src/content/apiSetting/sendCosmeticDataRequest.tsx (1 hunks)
  • src/content/apiSetting/sendFoodDataRequest.tsx (3 hunks)
  • src/content/apiSetting/sendHealthDataRequest.tsx (1 hunks)
  • src/content/coupang/categoryHandler/categoryHandlerCosmetic.tsx (0 hunks)
  • src/content/coupang/categoryHandler/categoryHandlerFood.tsx (0 hunks)
  • src/content/coupang/categoryHandler/categoryHandlerHealth.tsx (0 hunks)
  • src/content/index.tsx (3 hunks)
  • src/tabs/myInfo/components/AllergySelectForm.tsx (3 hunks)
💤 Files with no reviewable changes (3)
  • src/content/coupang/categoryHandler/categoryHandlerHealth.tsx
  • src/content/coupang/categoryHandler/categoryHandlerCosmetic.tsx
  • src/content/coupang/categoryHandler/categoryHandlerFood.tsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/components/sidebar/component.tsx (2)
src/components/productComponents/foodComponent.tsx (1)
  • FoodComponent (46-296)
src/components/productComponents/cosmeticComponent.tsx (1)
  • CosmeticComponent (28-176)
src/content/index.tsx (1)
src/content/coupang/categoryHandler/detectCategory.tsx (1)
  • detectCategoryType (1-32)
src/components/productComponents/cosmeticComponent.tsx (1)
src/content/apiSetting/sendCosmeticDataRequest.tsx (1)
  • sendCosmeticDataRequest (12-45)
🪛 GitHub Check: lint-and-test
src/background/index.ts

[warning] 161-161:
'err' is defined but never used


[warning] 150-150:
Unexpected console statement. Only these console methods are allowed: error, warn


[warning] 149-149:
Unexpected console statement. Only these console methods are allowed: error, warn


[warning] 285-285:
Unexpected console statement. Only these console methods are allowed: error, warn

🔇 Additional comments (25)
src/tabs/myInfo/components/AllergySelectForm.tsx (2)

10-30: 타입 안전성 개선을 위한 좋은 변경입니다!

AllergyType enum 도입은 문자열 기반 알레르기 관리에서 타입 안전성을 크게 향상시킵니다. 각 알레르기 항목에 대한 주석도 이해도를 높여줍니다.


38-58: 한국어-영어 매핑이 잘 구현되었습니다.

allergyNameToEnumMap 객체를 통한 한국어 이름과 enum 값 간의 매핑이 명확하고 완전합니다. 모든 알레르기 항목이 올바르게 매핑되어 있습니다.

src/components/sidebar/component.tsx (1)

8-9: 새로운 컴포넌트 import가 깔끔합니다.

FoodComponentCosmeticComponent를 직접 import하여 사용하는 방식이 명확하고 직관적입니다.

src/content/apiSetting/sendCosmeticDataRequest.tsx (1)

1-11: 타입 정의가 명확하고 완전합니다.

CosmeticRequestPayloadCosmeticAPIResponse 인터페이스가 잘 정의되어 있어 타입 안전성을 보장합니다.

src/content/apiSetting/sendHealthDataRequest.tsx (1)

1-13: 타입 정의가 완전하고 명확합니다.

HealthRequestPayloadHealthAPIResponse 인터페이스가 필요한 모든 필드를 포함하고 있어 좋습니다. 특히 사용자 정보(birthYear, gender, allergies)까지 포함한 것이 적절합니다.

src/content/index.tsx (3)

92-115: 잘 구현된 유틸리티 함수입니다!

DOM 요소를 비동기적으로 기다리는 함수가 타임아웃 처리와 MutationObserver 정리를 포함하여 깔끔하게 구현되었습니다.


116-125: 제품 제목 추출 리스너가 잘 구현되었습니다!

적절한 null 처리와 디버그 로깅이 포함된 깔끔한 구현입니다.


126-153: 비동기 처리와 보안이 개선되었습니다!

waitForEl을 사용한 비동기 요소 대기와 onerror 속성 제거를 통한 XSS 방지가 잘 구현되었습니다.

src/components/productComponents/cosmeticComponent.tsx (1)

146-173: UI 단순화가 잘 이루어졌습니다!

모든 감지된 성분을 하나의 섹션에 표시하는 것이 사용자 경험을 개선합니다.

src/background/index.ts (1)

452-482: 제품 제목 가져오기 핸들러가 잘 구현되었습니다!

적절한 에러 처리와 탭 통신이 포함된 견고한 구현입니다.

src/components/productComponents/foodComponent.tsx (6)

24-44: 알레르기 매핑 상수 추가는 좋은 개선사항입니다.

코드의 가독성과 유지보수성을 향상시키는 좋은 변경사항입니다. 한국어 라벨 매핑이 명확하게 정의되어 있습니다.


84-96: 스토리지에서 데이터 가져오기 로직이 개선되었습니다.

새로운 Allergies 필드 추가와 디버그 로깅이 개발 및 디버깅에 도움이 될 것입니다.


152-169: API 요청 payload 구성이 올바릅니다.

새로운 API 구조에 맞게 payload가 올바르게 구성되었습니다. 로깅 추가도 디버깅에 도움이 됩니다.


223-253: 조건부 렌더링 로직이 개선되었습니다.

nutrientAlerts.length > 0 조건을 추가하여 빈 배열일 때 불필요한 렌더링을 방지하는 것은 좋은 개선사항입니다.


267-293: 알레르기 섹션 렌더링이 올바르게 구현되었습니다.

새로운 allergyNameMap을 사용한 매핑과 조건부 렌더링이 적절하게 구현되었습니다.


167-168:

Details

❌ Incorrect review comment

API 응답 구조 변경에 대한 검증이 필요합니다.

코드에서 result.overRecommendationNutrients에 직접 접근하고 있지만, AI 요약에 따르면 이 데이터가 nutrientResponse 객체 안에 중첩되어 있다고 합니다. 실제 API 응답 구조와 일치하는지 확인이 필요합니다.

다음 스크립트를 실행하여 sendFoodDataRequest 함수의 반환 타입을 확인해보세요:


🏁 Script executed:

#!/bin/bash
# sendFoodDataRequest 함수의 반환 타입과 응답 구조 확인
ast-grep --pattern 'export const sendFoodDataRequest = ($$$): Promise<$RETURN_TYPE> => {
  $$$
}'

Length of output: 6119


API 응답 구조 확인 결과, 컴포넌트 접근 방식이 올바릅니다.

sendFoodDataRequest 함수가 이미 overRecommendationNutrientsallergyTypes를 최상위 프로퍼티로 반환하므로,

setNutrientAlerts(result.overRecommendationNutrients || []);
setAllergyTypes(result.allergyTypes || []);

현재 코드 그대로 사용하시면 됩니다. 별도의 수정이 필요 없습니다.

Likely an incorrect or invalid review comment.

src/content/apiSetting/sendFoodDataRequest.tsx (5)

20-24: API 응답 구조 변경이 올바르게 반영되었습니다.

overRecommendationNutrientsnutrientResponse 객체 안으로 중첩된 새로운 구조가 명확하게 정의되었습니다.


28-31: 함수 반환 타입 단순화가 적절합니다.

복잡한 중첩된 타입 대신 명시적인 객체 타입으로 반환 타입을 정의한 것이 가독성을 향상시킵니다.


52-65: 데이터 추출 로직이 올바르게 업데이트되었습니다.

새로운 중첩된 구조에서 데이터를 올바르게 추출하고 있으며, 변수명도 actual에서 data로 더 명확하게 변경되었습니다.


74-76: 알레르기 검증 로직 중복 제거가 효율적입니다.

이미 Array.isArray(data.allergyTypes) 검사를 통해 배열임이 확인되었으므로, 중복된 배열 검사를 제거한 것이 적절합니다.


79-83: 반환 객체 구조가 명확합니다.

명시적인 객체 구조로 반환하여 호출하는 쪽에서 데이터에 접근하기 쉽게 만들었습니다.

src/components/productComponents/healthComponent.tsx (4)

4-37: 건강 효능 매핑 상수가 포괄적으로 정의되었습니다.

다양한 건강 효능에 대한 한국어 매핑이 잘 정의되어 있어 사용자 경험을 향상시킬 것입니다.


57-67: getProductTitle 함수가 일관성 있게 구현되었습니다.

foodComponent와 동일한 패턴으로 구현되어 코드 일관성을 유지하고 있습니다.


176-194: 건강 효능 렌더링 로직이 적절합니다.

각 건강 효능을 개별 항목으로 렌더링하고 매핑된 한국어 이름을 사용하는 것이 좋습니다.


130-132:

Details

✅ Verification successful

sendHealthDataRequest 함수 호출을 확인해야 합니다.

sendHealthDataRequest 함수가 실제로 존재하고 올바른 타입을 반환하는지 확인이 필요합니다.

다음 스크립트로 함수 존재 여부를 확인해보세요:


🏁 Script executed:

#!/bin/bash
# sendHealthDataRequest 함수 존재 확인
fd "sendHealthDataRequest" --type f --extension tsx --extension ts | head -5

Length of output: 125


다음 스크립트를 실행하여 sendHealthDataRequest 함수의 시그니처와 반환 타입을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# sendHealthDataRequest 함수 정의 및 반환 타입 확인
head -n50 src/content/apiSetting/sendHealthDataRequest.tsx

Length of output: 1589


sendHealthDataRequest 함수 존재 및 반환 타입 확인 완료

  • src/content/apiSetting/sendHealthDataRequest.tsxexport const sendHealthDataRequest(payload: HealthRequestPayload): Promise<string[]>로 정의되어 있습니다.
  • 반환 타입이 Promise<string[]>로, 호출부의 result는 항상 string[]이므로 타입에 문제가 없습니다.

return;
}

const category = await detectCategoryType();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

불필요한 await 사용을 제거하세요.

detectCategoryType() 함수는 동기 함수이므로 await 키워드가 필요하지 않습니다.

다음과 같이 수정하세요:

-    const category = await detectCategoryType();
+    const category = detectCategoryType();
📝 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 category = await detectCategoryType();
const category = detectCategoryType();
🤖 Prompt for AI Agents
In src/content/index.tsx at line 58, remove the unnecessary await keyword before
the call to detectCategoryType() since it is a synchronous function. Change the
line to directly assign the result of detectCategoryType() to the category
variable without using await.

Comment on lines +148 to +150
status: res.status,
error: json?.message ?? "에러 발생",
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

console.log 대신 logger를 사용하세요.

프로덕션 환경을 위해 기존 logger 유틸리티를 사용하는 것이 좋습니다.

다음과 같이 수정하세요:

-console.log("[voim] 응답 상태 코드:", res.status);
-console.log("[voim] 응답 원문:", text);
+logger.debug("[voim] 응답 상태 코드:", res.status);
+logger.debug("[voim] 응답 원문:", text);
📝 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
console.log("[voim] 응답 상태 코드:", res.status);
console.log("[voim] 응답 원문:", text);
try {
logger.debug("[voim] 응답 상태 코드:", res.status);
logger.debug("[voim] 응답 원문:", text);
try {
🧰 Tools
🪛 GitHub Check: lint-and-test

[warning] 150-150:
Unexpected console statement. Only these console methods are allowed: error, warn


[warning] 149-149:
Unexpected console statement. Only these console methods are allowed: error, warn

🤖 Prompt for AI Agents
In src/background/index.ts around lines 148 to 150, replace the console.log
statements with calls to the existing logger utility to ensure consistent and
configurable logging in production. Identify the appropriate logger method
(e.g., logger.info or logger.debug) and use it to log the response status code
and response text instead of console.log.

}
sendResponse({ status: 500, error: err.message });
});
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

catch 블록에서 err 변수를 사용하세요.

에러 정보를 로그에 포함시켜 디버깅을 용이하게 하세요.

다음과 같이 수정하세요:

-console.error("[voim] JSON 파싱 실패", text);
+console.error("[voim] JSON 파싱 실패", text, err);
📝 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
console.error("[voim] JSON 파싱 실패", text);
console.error("[voim] JSON 파싱 실패", text, err);
🧰 Tools
🪛 GitHub Check: lint-and-test

[warning] 161-161:
'err' is defined but never used

🤖 Prompt for AI Agents
In src/background/index.ts at line 161, the catch block logs a generic error
message without including the caught error details. Modify the console.error
call to include the caught error variable (commonly named err) alongside the
message and text to provide detailed error information for easier debugging.

if (!raw || typeof raw !== "object") {
console.warn(
"[voim][background] data.data 형식 이상함:",
raw,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

console.log 대신 logger를 사용하세요.

일관성을 위해 logger 유틸리티를 사용하세요.

다음과 같이 수정하세요:

-console.log("[voim][background] 응답 원문:", json);
+logger.debug("[voim][background] 응답 원문:", json);

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

🧰 Tools
🪛 GitHub Check: lint-and-test

[warning] 285-285:
Unexpected console statement. Only these console methods are allowed: error, warn

🤖 Prompt for AI Agents
In src/background/index.ts at line 285, replace any console.log statements with
the logger utility to maintain consistency in logging. Identify where
console.log is used around this line and change it to use the logger's
appropriate logging method instead.

Comment on lines +99 to +150
const response = await new Promise<{
html: string;
productId: string;
}>((resolve, reject) => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(res) => {
if (
chrome.runtime.lastError ||
!res?.html ||
!res?.productId ||
res.html.trim() === ""
) {
console.warn(
"[voim] FETCH_VENDOR_HTML 응답 없음, 대기 중...",
);
let retries = 10;
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
if (
retryRes?.html?.trim() &&
retryRes?.productId
) {
console.log(
"[voim] FETCH_VENDOR_HTML 재시도 성공:",
retryRes,
);
clearInterval(interval);
resolve(retryRes);
} else if (--retries === 0) {
clearInterval(interval);
reject(
new Error(
"HTML 또는 productId 누락",
),
);
}
},
);
}, 500);
} else {
console.log(
"[voim] FETCH_VENDOR_HTML 성공 응답:",
res,
);
resolve(res);
}
},
);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

재시도 로직의 잠재적 메모리 누수 문제가 있습니다.

재시도 로직에서 chrome.runtime.lastError 확인 없이 setInterval이 실행될 수 있으며, 컴포넌트가 언마운트된 후에도 interval이 계속 실행될 수 있습니다.

                                const interval = setInterval(() => {
                                    chrome.runtime.sendMessage(
                                        { type: "FETCH_VENDOR_HTML" },
                                        (retryRes) => {
+                                            if (chrome.runtime.lastError) {
+                                                console.warn("[voim] 재시도 중 런타임 에러:", chrome.runtime.lastError);
+                                                if (--retries === 0) {
+                                                    clearInterval(interval);
+                                                    reject(new Error("런타임 에러로 인한 재시도 실패"));
+                                                }
+                                                return;
+                                            }
                                            if (
                                                retryRes?.html?.trim() &&
                                                retryRes?.productId

또한 컴포넌트 언마운트 시 interval 정리를 위한 cleanup 함수를 추가하는 것을 고려해보세요.

🤖 Prompt for AI Agents
In src/components/productComponents/foodComponent.tsx between lines 99 and 150,
the retry logic using setInterval lacks checks for chrome.runtime.lastError and
does not clear the interval if the component unmounts, causing potential memory
leaks. To fix this, add a check for chrome.runtime.lastError inside the retry
callback to avoid unnecessary retries, and implement a cleanup function in the
component to clear the interval when the component unmounts, ensuring the
interval does not continue running after unmount.

Comment on lines +82 to +117
const response = await new Promise<{
html: string;
productId: string;
}>((resolve, reject) => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(res) => {
if (!res?.html?.trim() || !res?.productId) {
let retries = 10;
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
if (
retryRes?.html?.trim() &&
retryRes?.productId
) {
clearInterval(interval);
resolve(retryRes);
} else if (--retries === 0) {
clearInterval(interval);
reject(
new Error(
"HTML 또는 productId 누락",
),
);
}
},
);
}, 500);
} else {
resolve(res);
}
},
);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

재시도 로직에서 동일한 메모리 누수 위험이 있습니다.

foodComponent와 동일한 문제가 있습니다. chrome.runtime.lastError 확인과 컴포넌트 언마운트 시 cleanup이 필요합니다.

                                const interval = setInterval(() => {
                                    chrome.runtime.sendMessage(
                                        { type: "FETCH_VENDOR_HTML" },
                                        (retryRes) => {
+                                            if (chrome.runtime.lastError) {
+                                                console.warn("[voim][HealthComponent] 재시도 중 런타임 에러:", chrome.runtime.lastError);
+                                                if (--retries === 0) {
+                                                    clearInterval(interval);
+                                                    reject(new Error("런타임 에러로 인한 재시도 실패"));
+                                                }
+                                                return;
+                                            }
📝 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 new Promise<{
html: string;
productId: string;
}>((resolve, reject) => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(res) => {
if (!res?.html?.trim() || !res?.productId) {
let retries = 10;
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
if (
retryRes?.html?.trim() &&
retryRes?.productId
) {
clearInterval(interval);
resolve(retryRes);
} else if (--retries === 0) {
clearInterval(interval);
reject(
new Error(
"HTML 또는 productId 누락",
),
);
}
},
);
}, 500);
} else {
resolve(res);
}
},
);
});
const response = await new Promise<{
html: string;
productId: string;
}>((resolve, reject) => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(res) => {
if (!res?.html?.trim() || !res?.productId) {
let retries = 10;
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
// Check for runtime errors before inspecting retryRes
if (chrome.runtime.lastError) {
console.warn(
"[voim][HealthComponent] 재시도 중 런타임 에러:",
chrome.runtime.lastError,
);
if (--retries === 0) {
clearInterval(interval);
reject(
new Error(
"런타임 에러로 인한 재시도 실패",
),
);
}
return;
}
if (
retryRes?.html?.trim() &&
retryRes?.productId
) {
clearInterval(interval);
resolve(retryRes);
} else if (--retries === 0) {
clearInterval(interval);
reject(
new Error(
"HTML 또는 productId 누락",
),
);
}
},
);
}, 500);
} else {
resolve(res);
}
},
);
});
🤖 Prompt for AI Agents
In src/components/productComponents/healthComponent.tsx around lines 82 to 117,
the retry logic using setInterval for chrome.runtime.sendMessage lacks cleanup
on component unmount and does not check chrome.runtime.lastError, risking memory
leaks. Fix this by adding a cleanup function to clear the interval when the
component unmounts and by checking chrome.runtime.lastError after each
sendMessage call to handle errors properly and avoid unresolved promises.

Copy link

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

♻️ Duplicate comments (1)
src/components/productComponents/healthComponent.tsx (1)

92-112: ⚠️ Potential issue

재시도 로직의 메모리 누수 위험이 여전히 존재합니다.

이전 리뷰에서 지적된 문제가 아직 해결되지 않았습니다. chrome.runtime.lastError 확인과 컴포넌트 언마운트 시 cleanup이 필요합니다.

다음 diff를 적용하여 문제를 해결하세요:

+                let intervalId: NodeJS.Timeout;
                 const interval = setInterval(() => {
                     chrome.runtime.sendMessage(
                         { type: "FETCH_VENDOR_HTML" },
                         (retryRes) => {
+                            if (chrome.runtime.lastError) {
+                                console.warn("[voim][HealthComponent] 재시도 중 런타임 에러:", chrome.runtime.lastError);
+                                if (--retries === 0) {
+                                    clearInterval(interval);
+                                    reject(new Error("런타임 에러로 인한 재시도 실패"));
+                                }
+                                return;
+                            }
                             if (
                                 retryRes?.html?.trim() &&
                                 retryRes?.productId
                             ) {
                                 clearInterval(interval);
                                 resolve(retryRes);
                             } else if (--retries === 0) {
                                 clearInterval(interval);
                                 reject(
                                     new Error(
                                         "HTML 또는 productId 누락",
                                     ),
                                 );
                             }
                         },
                     );
                 }, 500);
+                intervalId = interval;

또한 컴포넌트 언마운트 시 interval을 정리하기 위해 useEffect cleanup 함수를 추가해야 합니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4400e21 and a85082f.

📒 Files selected for processing (3)
  • src/components/productComponents/cosmeticComponent.tsx (3 hunks)
  • src/components/productComponents/foodComponent.tsx (7 hunks)
  • src/components/productComponents/healthComponent.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/productComponents/foodComponent.tsx
  • src/components/productComponents/cosmeticComponent.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/productComponents/healthComponent.tsx (2)
src/content/apiSetting/sendHealthDataRequest.tsx (1)
  • sendHealthDataRequest (14-56)
src/components/Loading/component.tsx (1)
  • Loading (5-14)
🔇 Additional comments (5)
src/components/productComponents/healthComponent.tsx (5)

2-3: 새로운 의존성 추가가 적절합니다.

sendHealthDataRequest 함수와 Loading 컴포넌트를 올바르게 import하였습니다.


5-38: 건강 효능 매핑 딕셔너리가 잘 구현되었습니다.

한국어 라벨 매핑을 위한 healthEffectMap이 포괄적이고 명확하게 정의되었습니다.


58-68: getProductTitle 함수의 에러 처리가 올바릅니다.

chrome.runtime.lastError 체크와 적절한 fallback 처리가 구현되어 있습니다.


154-166: 로딩 UI 구현이 개선되었습니다.

중앙 정렬된 Loading 컴포넌트와 명확한 로딩 텍스트가 사용자 경험을 향상시킵니다.


191-209: 건강 효능 목록 렌더링이 잘 구현되었습니다.

healthEffectMap을 활용한 한국어 라벨 표시와 적절한 스타일링이 적용되었습니다.

Comment on lines +71 to 140
const fetchData = async () => {
try {
const { birthYear, gender, Allergies } =
await chrome.storage.local.get([
"birthYear",
"gender",
"Allergies",
]);

const { birthYear, gender } = await chrome.storage.local.get([
"birthYear",
"gender",
]);
if (!birthYear || !gender) return;

const rawHtml = targetEl.outerHTML
.replace(/\sonerror=\"[^\"]*\"/g, "")
.replace(/\n/g, "")
.trim();
const title = await getProductTitle();
const response = await new Promise<{
html: string;
productId: string;
}>((resolve, reject) => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(res) => {
if (!res?.html?.trim() || !res?.productId) {
let retries = 10;
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
if (
retryRes?.html?.trim() &&
retryRes?.productId
) {
clearInterval(interval);
resolve(retryRes);
} else if (--retries === 0) {
clearInterval(interval);
reject(
new Error(
"HTML 또는 productId 누락",
),
);
}
},
);
}, 500);
} else {
resolve(res);
}
},
);
});

chrome.runtime.sendMessage(
{
type: "FETCH_HEALTH_DATA",
payload: {
productId,
title: document.title,
html: rawHtml,
birthYear: Number(birthYear),
gender: gender?.toUpperCase() || "UNKNOWN",
allergies: [],
},
},
(res) => {
const data = res?.data?.types || [];
setHealthEffects(data);
if (res?.data?.types) {
setHealthEffects(res.data.types);
}
},
);
};
const payload = {
productId: response.productId,
title,
html: response.html,
birthYear: Number(birthYear),
gender: gender.toUpperCase(),
allergies: Allergies || [],
};

const targetEl =
document.querySelector(".vendor-item") ||
document.querySelector(".product-detail-content") ||
document.querySelector(".prod-image");
console.log("[voim] HEALTH API 요청 payload:", payload);

if (targetEl) {
fetchData(targetEl);
} else {
const observer = new MutationObserver(() => {
const el =
document.querySelector(".vendor-item") ||
document.querySelector(".product-detail-content") ||
document.querySelector(".prod-image");
if (el) {
observer.disconnect();
fetchData(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
return () => observer.disconnect();
}
const result = await sendHealthDataRequest(payload);
console.log("[voim] HEALTH API 응답:", result);
setHealthTypes(result || []);
} catch (e) {
console.error("[voim] HEALTH API 실패:", e);
}
};

fetchData();
}, []);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

비동기 데이터 페칭 로직에 컴포넌트 언마운트 처리가 필요합니다.

현재 구현은 컴포넌트가 언마운트된 후에도 상태 업데이트를 시도할 수 있습니다.

useRef를 사용하여 컴포넌트 마운트 상태를 추적하고 cleanup 함수를 추가하세요:

+import React, { useEffect, useState, useRef } from "react";

 export const HealthComponent = () => {
     const [healthTypes, setHealthTypes] = useState<string[] | null>(null);
+    const isMountedRef = useRef(true);

     useEffect(() => {
+        isMountedRef.current = true;
         const fetchData = async () => {
             try {
                 // ... existing logic ...
-                setHealthTypes(result || []);
+                if (isMountedRef.current) {
+                    setHealthTypes(result || []);
+                }
             } catch (e) {
                 console.error("[voim] HEALTH API 실패:", e);
             }
         };

         fetchData();
+        
+        return () => {
+            isMountedRef.current = 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 fetchData = async () => {
try {
const { birthYear, gender, Allergies } =
await chrome.storage.local.get([
"birthYear",
"gender",
"Allergies",
]);
const { birthYear, gender } = await chrome.storage.local.get([
"birthYear",
"gender",
]);
if (!birthYear || !gender) return;
const rawHtml = targetEl.outerHTML
.replace(/\sonerror=\"[^\"]*\"/g, "")
.replace(/\n/g, "")
.trim();
const title = await getProductTitle();
const response = await new Promise<{
html: string;
productId: string;
}>((resolve, reject) => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(res) => {
if (!res?.html?.trim() || !res?.productId) {
let retries = 10;
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
if (
retryRes?.html?.trim() &&
retryRes?.productId
) {
clearInterval(interval);
resolve(retryRes);
} else if (--retries === 0) {
clearInterval(interval);
reject(
new Error(
"HTML 또는 productId 누락",
),
);
}
},
);
}, 500);
} else {
resolve(res);
}
},
);
});
chrome.runtime.sendMessage(
{
type: "FETCH_HEALTH_DATA",
payload: {
productId,
title: document.title,
html: rawHtml,
birthYear: Number(birthYear),
gender: gender?.toUpperCase() || "UNKNOWN",
allergies: [],
},
},
(res) => {
const data = res?.data?.types || [];
setHealthEffects(data);
if (res?.data?.types) {
setHealthEffects(res.data.types);
}
},
);
};
const payload = {
productId: response.productId,
title,
html: response.html,
birthYear: Number(birthYear),
gender: gender.toUpperCase(),
allergies: Allergies || [],
};
const targetEl =
document.querySelector(".vendor-item") ||
document.querySelector(".product-detail-content") ||
document.querySelector(".prod-image");
console.log("[voim] HEALTH API 요청 payload:", payload);
if (targetEl) {
fetchData(targetEl);
} else {
const observer = new MutationObserver(() => {
const el =
document.querySelector(".vendor-item") ||
document.querySelector(".product-detail-content") ||
document.querySelector(".prod-image");
if (el) {
observer.disconnect();
fetchData(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
return () => observer.disconnect();
}
const result = await sendHealthDataRequest(payload);
console.log("[voim] HEALTH API 응답:", result);
setHealthTypes(result || []);
} catch (e) {
console.error("[voim] HEALTH API 실패:", e);
}
};
fetchData();
}, []);
import React, { useEffect, useState, useRef } from "react";
export const HealthComponent = () => {
const [healthTypes, setHealthTypes] = useState<string[] | null>(null);
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
const fetchData = async () => {
try {
const { birthYear, gender, Allergies } =
await chrome.storage.local.get([
"birthYear",
"gender",
"Allergies",
]);
if (!birthYear || !gender) return;
const title = await getProductTitle();
const response = await new Promise<{
html: string;
productId: string;
}>((resolve, reject) => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(res) => {
if (!res?.html?.trim() || !res?.productId) {
let retries = 10;
const interval = setInterval(() => {
chrome.runtime.sendMessage(
{ type: "FETCH_VENDOR_HTML" },
(retryRes) => {
if (
retryRes?.html?.trim() &&
retryRes?.productId
) {
clearInterval(interval);
resolve(retryRes);
} else if (--retries === 0) {
clearInterval(interval);
reject(
new Error(
"HTML 또는 productId 누락",
),
);
}
},
);
}, 500);
} else {
resolve(res);
}
},
);
});
const payload = {
productId: response.productId,
title,
html: response.html,
birthYear: Number(birthYear),
gender: gender.toUpperCase(),
allergies: Allergies || [],
};
console.log("[voim] HEALTH API 요청 payload:", payload);
const result = await sendHealthDataRequest(payload);
console.log("[voim] HEALTH API 응답:", result);
if (isMountedRef.current) {
setHealthTypes(result || []);
}
} catch (e) {
console.error("[voim] HEALTH API 실패:", e);
}
};
fetchData();
return () => {
isMountedRef.current = false;
};
}, []);
// ...rendering logic...
};
🤖 Prompt for AI Agents
In src/components/productComponents/healthComponent.tsx around lines 71 to 140,
the asynchronous fetchData function updates state after awaiting async calls
without checking if the component is still mounted, which can cause errors if
the component unmounts during the fetch. Fix this by using a useRef hook to
track the mounted state of the component, setting it to true on mount and false
in a cleanup function inside useEffect, and conditionally update state only if
the component is still mounted.

Copy link

@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 (2)
src/components/imageCheck/imageModal.tsx (2)

3-3: 사용하지 않는 import를 제거해주세요.

Player import가 더 이상 사용되지 않는 것 같습니다. Loading 컴포넌트가 Lottie 애니메이션을 처리하므로 이 import를 제거할 수 있습니다.

-import { Player } from "@lottiefiles/react-lottie-player";

90-116: 로딩 UI 리팩토링이 잘 구현되었습니다.

새로운 Loading 컴포넌트를 사용한 로딩 UI 구조가 깔끔하고 일관성 있게 구현되었습니다. flexbox를 이용한 중앙 정렬과 고정 크기 컨테이너가 적절합니다.

다만 하나의 개선사항을 제안드립니다:

                            <div
                                style={{
                                    padding: "16px",
                                    display: "flex",
                                    flexDirection: "column",
                                    justifyContent: "center",
                                    alignItems: "center",
                                    height: "320px",
                                }}
                            >
-                                <div
-                                    style={{ width: "260px", height: "243px" }}
-                                >
-                                    <Loading />
-                                </div>
+                                <Loading />
                                <p
                                    style={{
                                        marginTop: "8px",
                                        fontFamily: "KoddiUD OnGothic",
                                        fontSize: "24px",
                                        fontWeight: 600,
                                        color: "#555",
                                    }}
                                >
                                    이미지를 분석 중입니다. 잠시만 기다려주세요.
                                </p>
                            </div>

Loading 컴포넌트가 이미 고정 크기(260x243)를 가지고 있으므로, 추가적인 래퍼 div가 불필요할 수 있습니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a85082f and f676473.

📒 Files selected for processing (2)
  • src/components/imageCheck/controlImage.tsx (1 hunks)
  • src/components/imageCheck/imageModal.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/components/imageCheck/controlImage.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/imageCheck/imageModal.tsx (1)
src/components/Loading/component.tsx (1)
  • Loading (5-14)
🔇 Additional comments (1)
src/components/imageCheck/imageModal.tsx (1)

4-4: 새로운 Loading 컴포넌트 import가 잘 적용되었습니다.

UI 일관성을 위한 Loading 컴포넌트 도입이 적절합니다.

Copy link

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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f676473 and 17558be.

📒 Files selected for processing (3)
  • src/components/productComponents/cosmeticComponent.tsx (3 hunks)
  • src/components/productComponents/infoComponent.tsx (3 hunks)
  • src/components/sidebar/component.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/sidebar/component.tsx
  • src/components/productComponents/cosmeticComponent.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/productComponents/infoComponent.tsx (1)
src/contexts/ThemeContext.tsx (1)
  • useTheme (96-102)
🪛 Biome (1.9.4)
src/components/productComponents/infoComponent.tsx

[error] 107-107: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (6)
src/components/productComponents/infoComponent.tsx (6)

9-11: 인터페이스 정의가 깔끔합니다.

InfoComponentProps 인터페이스가 잘 정의되어 있고, categoryType의 타입이 명확하게 지정되어 있습니다.


13-15: 컴포넌트 시그니처 개선이 좋습니다.

매개변수가 없던 컴포넌트에서 타입이 지정된 프롭스를 받는 함수형 컴포넌트로 개선되었습니다.


24-30: 동적 라벨링 로직이 잘 구현되어 있습니다.

categoryType에 따라 메인 라벨을 동적으로 설정하는 로직이 명확하고 효율적입니다. 음식/건강 제품과 기타 제품을 구분하여 적절한 라벨을 제공합니다.


42-42: 메시지 전송 로직이 간소화되어 좋습니다.

Chrome 런타임 메시지 전송이 더 간결하게 작성되었습니다.


95-101: 문자열 처리 로직이 효율적입니다.

문자열을 줄별로 분할하고, 앞의 하이픈을 제거하며, 빈 줄을 필터링하는 체이닝 로직이 깔끔하게 구현되어 있습니다.


77-77: 동적 렌더링으로 개선되었습니다.

정적 배열 대신 동적으로 생성된 outlineCategories 배열을 사용하여 렌더링하는 방식이 더 유연합니다.

Comment on lines +95 to +112
{info
.split("\n")
.map((item) =>
item.replace(/^-/, "").trim(),
)
.filter(Boolean)
.map((item, index) => (
<li
key={index}
className="leading-relaxed"
dangerouslySetInnerHTML={{
__html: item.replace(
/<strong>(.*?)<\/strong>/g,
`<strong style="color: #8914FF;">$1</strong>`,
),
}}
/>
))}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

보안 취약점: XSS 공격 위험성이 있습니다.

dangerouslySetInnerHTML을 사용하여 사용자 콘텐츠를 렌더링하는 것은 XSS 공격에 취약합니다. 외부에서 가져온 HTML 콘텐츠를 직접 주입하면 악성 스크립트가 실행될 수 있습니다.

다음과 같이 안전한 방식으로 수정하는 것을 권장합니다:

- <li
-     key={index}
-     className="leading-relaxed"
-     dangerouslySetInnerHTML={{
-         __html: item.replace(
-             /<strong>(.*?)<\/strong>/g,
-             `<strong style="color: #8914FF;">$1</strong>`,
-         ),
-     }}
- />
+ <li key={index} className="leading-relaxed">
+     {item.split(/<strong>(.*?)<\/strong>/g).map((part, partIndex) => {
+         if (partIndex % 2 === 1) {
+             return (
+                 <strong
+                     key={partIndex}
+                     style={{ color: "#8914FF" }}
+                 >
+                     {part}
+                 </strong>
+             );
+         }
+         return part;
+     })}
+ </li>

또는 DOMPurify 같은 HTML 새니타이제이션 라이브러리를 사용하세요.

📝 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
{info
.split("\n")
.map((item) =>
item.replace(/^-/, "").trim(),
)
.filter(Boolean)
.map((item, index) => (
<li
key={index}
className="leading-relaxed"
dangerouslySetInnerHTML={{
__html: item.replace(
/<strong>(.*?)<\/strong>/g,
`<strong style="color: #8914FF;">$1</strong>`,
),
}}
/>
))}
{info
.split("\n")
.map((item) =>
item.replace(/^-/, "").trim(),
)
.filter(Boolean)
.map((item, index) => (
<li key={index} className="leading-relaxed">
{item
.split(/<strong>(.*?)<\/strong>/g)
.map((part, partIndex) => {
if (partIndex % 2 === 1) {
return (
<strong
key={partIndex}
style={{ color: "#8914FF" }}
>
{part}
</strong>
);
}
return part;
})}
</li>
))}
🧰 Tools
🪛 Biome (1.9.4)

[error] 107-107: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🤖 Prompt for AI Agents
In src/components/productComponents/infoComponent.tsx around lines 95 to 112,
the use of dangerouslySetInnerHTML to render user content poses an XSS security
risk. To fix this, avoid using dangerouslySetInnerHTML with raw HTML strings.
Instead, sanitize the HTML content using a library like DOMPurify before
rendering, or refactor the code to render plain text or React elements safely
without injecting HTML directly.

Comment on lines +32 to +37
{ key: "MAIN", label: mainLabel },
{ key: "USAGE", label: "사용 방법 및 대상" },
{ key: "WARNING", label: "주의 및 보관" },
{ key: "SPECS", label: "구성 및 디자인" },
] as const;

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

카테고리 구조 개선이 필요할 수 있습니다.

카테고리 배열이 동적으로 생성되어 좋지만, "CERTIFICATION" 카테고리가 제거되었습니다. 이 변경이 의도적인지 확인이 필요합니다.

다음 스크립트를 실행하여 CERTIFICATION 카테고리 사용 여부를 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Description: CERTIFICATION 카테고리가 다른 곳에서 사용되는지 확인
# Expected: CERTIFICATION 관련 참조가 없어야 함

echo "=== CERTIFICATION 카테고리 사용 검색 ==="
rg -i "certification" --type typescript --type tsx

Length of output: 160


rg 명령어에서 --type typescript를 인식하지 못해 검색이 실행되지 않았습니다. 파일 확장자를 glob 패턴으로 지정하여 다시 확인해보겠습니다.


🏁 Script executed:

#!/bin/bash
echo "=== CERTIFICATION 카테고리 검색 재시도 ==="
rg -i certification -g '*.ts' -g '*.tsx'

Length of output: 365


CERTIFICATION 카테고리 재추가 필요

infoComponent.tsx에서 categories 배열에 “CERTIFICATION” 항목이 빠졌으나, 다음 위치들에서는 여전히 해당 키를 참조하고 있습니다:

  • src/components/productComponents/infoComponent.tsx (Lines 32–37): outlineCategories
  • src/components/productComponents/infoComponent.tsx: type OutlineCategory = "... | "CERTIFICATION";
  • src/content/apiSetting/sendInfoRequest.tsx: outline: "MAIN" | ... | "CERTIFICATION",

위치한 곳들이 일치하도록, 의도대로 “CERTIFICATION” 카테고리를 다음과 같이 다시 추가하거나 제거 의도를 명확히 검토해주세요.

 const outlineCategories = [
     { key: "MAIN", label: mainLabel },
     { key: "USAGE", label: "사용 방법 및 대상" },
     { key: "WARNING", label: "주의 및 보관" },
     { key: "SPECS", label: "구성 및 디자인" },
+    { key: "CERTIFICATION", label: "인증 및 허가" },
 ] as const;
📝 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 outlineCategories = [
{ key: "MAIN", label: mainLabel },
{ key: "USAGE", label: "사용 방법 및 대상" },
{ key: "WARNING", label: "주의 및 보관" },
{ key: "SPECS", label: "구성 및 디자인" },
] as const;
const outlineCategories = [
{ key: "MAIN", label: mainLabel },
{ key: "USAGE", label: "사용 방법 및 대상" },
{ key: "WARNING", label: "주의 및 보관" },
{ key: "SPECS", label: "구성 및 디자인" },
{ key: "CERTIFICATION", label: "인증 및 허가" },
] as const;
🤖 Prompt for AI Agents
In src/components/productComponents/infoComponent.tsx around lines 32 to 37, the
"CERTIFICATION" category was removed from the outlineCategories array but is
still referenced in the type OutlineCategory and other files like
sendInfoRequest.tsx. To fix this, re-add the "CERTIFICATION" category object
with the appropriate label back into the outlineCategories array to keep the
category definitions consistent across the codebase, or confirm and propagate
its removal everywhere if the deletion was intentional.

@psm1st psm1st merged commit 457e807 into main May 29, 2025
1 of 2 checks passed
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.

2 participants