Skip to content

[feat] 프론트엔드 주요 기능 구현 및 UI 개선 - #398

Merged
seoyoonyi merged 52 commits into
FC-DEV-FinalProject:mainfrom
seoyoonyi:main
Sep 8, 2026
Merged

[feat] 프론트엔드 주요 기능 구현 및 UI 개선#398
seoyoonyi merged 52 commits into
FC-DEV-FinalProject:mainfrom
seoyoonyi:main

Conversation

@seoyoonyi

@seoyoonyi seoyoonyi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

🚀 풀 리퀘스트 제안

📋 작업 내용

수정한 내용이나 추가한 기능에 대해 자세히 설명해 주세요.

🔧 변경 사항

  • 📃 README.md
  • 📦 package.json
  • 🔥 파일 삭제
  • 🧹 그 외 ex) .gitignore 등

주요 변경 사항을 요약해 주세요.

📸 스크린샷 (선택 사항)

수정된 화면 또는 기능을 시연할 수 있는 스크린샷을 첨부해 주세요.

📄 기타

추가적으로 전달하고 싶은 내용이나 특별한 요구 사항이 있으면 작성해 주세요.

Sourcery 요약

프론트엔드의 핵심 UI 개선, 안정적인 에셋 처리, 재사용 가능한 폼 컴포넌트, 전략 표시 업데이트 및 시각적 검토 워크플로를 구현합니다.

새로운 기능:

  • 인증 및 회원가입 흐름을 위해 레이블, 유효성 검사 메시지, 전달된 ref, 일관된 크기를 지원하는 재사용 가능한 텍스트 필드 입력을 추가합니다.
  • 로컬 대체 이미지를 사용하는 안정적인 이미지 및 전략 아이콘 렌더링, 매핑된 전략 배지, 개선된 이미지 확대 동작을 추가합니다.
  • 시각적 검토를 위해 Storybook 컴포넌트 스토리와 Chromatic 풀 리퀘스트 배포를 도입합니다.

버그 수정:

  • 손상되었거나 사용할 수 없는 원격 이미지로 인해 아바타, 전략 화면, 관리자 테이블 및 이미지 미리보기가 중단되지 않도록 합니다.
  • 전략 상세 페이지의 접근 권한 확인, 상태 처리 및 권한이 없거나 사용할 수 없는 전략에 대한 오류 탐색을 개선합니다.
  • 이미지 미리보기 URL 수명 주기를 수정하고 이미지가 로드되기 전에 확대되지 않도록 합니다.

개선 사항:

  • 더 일관된 레이아웃, 간격, 접근성 레이블, 반응형 행 크기 조정 및 최적화된 이미지 에셋을 적용하여 전략 상세, 목록, 검색, 관리자, 인증 및 회원가입 인터페이스를 개선합니다.
  • 분석 데이터 매핑 및 상태 유틸리티를 중앙화하고, 분석 쿼리 캐싱과 목 핸들러의 견고성을 개선합니다.
  • 테마에 맞게 생성된 전략 배지 에셋을 추가하고, 공유 입력 컴포넌트를 재사용할 수 있도록 업데이트합니다.

빌드:

  • Storybook, Chromatic 및 관련 개발 도구와 스크립트를 추가합니다.
  • 의존성과 생성된 lockfile 콘텐츠를 업데이트합니다.

CI:

  • 풀 리퀘스트에서 Chromatic 시각적 배포를 실행하고 Storybook URL을 풀 리퀘스트 댓글로 게시합니다.
  • Storybook 전용 ESLint 규칙을 활성화합니다.

테스트:

  • 이미지 로딩 실패, 대체 이미지, 전략 아이콘 확인, 소스 변경 및 이미지 확대 동작에 대한 테스트 커버리지를 추가합니다.

정리:

  • MSW의 생성된 서비스 워커를 업데이트하고 생성된 전략 아이콘 에셋을 추가합니다.
  • 선택한 홈페이지 이미지와 메타데이터를 새로 고치고, 사용하지 않는 not-found 레이아웃을 제거합니다.
Original summary in English

Summary by Sourcery

Implement the frontend’s core UI improvements, resilient asset handling, reusable form components, strategy presentation updates, and visual review workflow.

New Features:

  • Add reusable text-field inputs with labels, validation messaging, forwarded refs, and consistent sizing for authentication and signup flows.
  • Add resilient image and strategy-icon rendering with local fallbacks, mapped strategy badges, and improved image expansion behavior.
  • Introduce Storybook component stories and Chromatic pull-request deployments for visual review.

Bug Fixes:

  • Prevent broken or unavailable remote images from disrupting avatars, strategy views, administrative tables, and image previews.
  • Improve strategy detail access checks, status handling, and error navigation for unauthorized or unavailable strategies.
  • Fix image preview URL lifecycle management and prevent expansion before an image has loaded.

Enhancements:

  • Refresh strategy detail, list, search, admin, authentication, and signup interfaces with more consistent layouts, spacing, accessibility labels, responsive row sizing, and optimized image assets.
  • Centralize analysis data mapping and status utilities while improving analysis query caching and mock handler robustness.
  • Add generated, theme-aligned strategy badge assets and update shared input components for reuse.

Build:

  • Add Storybook, Chromatic, and related development tooling and scripts.
  • Update dependencies and generated lockfile contents.

CI:

  • Run Chromatic visual deployments on pull requests and publish the Storybook URL as a pull-request comment.
  • Enable Storybook-specific ESLint rules.

Tests:

  • Add coverage for image loading failures, fallbacks, strategy icon resolution, source changes, and image expansion behavior.

Chores:

  • Update MSW's generated service worker and add generated strategy icon assets.
  • Refresh selected homepage imagery and metadata, and remove the unused not-found layout.

seoyoonyi and others added 30 commits March 28, 2025 23:32
[refactor] 사용하지 않는 NotFoundLayout 제거 및 설정 파일 정리
[feat] 스토리북 세팅 및 chromatic 배포
[Feat] Button 컴포넌트 스토리북 스토리 추가
feat: 페이지네이션 스토리북 추가 (#37)
jizerozz and others added 22 commits May 2, 2025 00:25
[Refactor] 전략 상세페이지 리팩토링
[refactor] Input 컴포넌트 리팩토링 및 TextField 통합
[fix] S3 이미지 로딩 실패 시 대체 UI 및 로컬 아이콘 적용
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: f160bca5-741f-4a50-8d2a-6870dfd70cdb


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

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Sep 8, 2026

Copy link
Copy Markdown

리뷰어 가이드

이 PR은 입력 및 이미지 프리미티브를 통합하고, 복원력 있는 로컬 전략 아이콘을 추가하며, 전략 상세 권한 부여 및 분석 흐름을 개선하고, 에셋 및 레이아웃 동작을 최적화하고, Storybook/Chromatic 시각적 회귀 테스트 도구를 도입하여 프론트엔드 기능과 UI를 전반적으로 개선합니다.

복원력 있는 이미지 로딩을 위한 시퀀스 다이어그램

sequenceDiagram
    participant Component
    participant StrategyIcon
    participant SafeImage
    participant Browser
    participant Placeholder

    Component->>StrategyIcon: getIconSource(src, label)
    StrategyIcon->>SafeImage: render resolved src
    SafeImage->>Browser: load image
    alt image loads
        Browser-->>SafeImage: onLoad
        SafeImage-->>Component: display image
    else image fails
        Browser-->>SafeImage: onError
        SafeImage->>SafeImage: setFailed(true)
        SafeImage->>Browser: load fallbackSrc
        alt fallback loads
            Browser-->>SafeImage: onLoad
            SafeImage-->>Component: display fallback image
        else fallback fails
            Browser-->>SafeImage: onError
            SafeImage->>SafeImage: setFallbackFailed(true)
            SafeImage-->>Placeholder: render role img placeholder
        end
    end
Loading

파일 수준 변경 사항

변경 사항 세부 내용 파일
이미지 로드 실패 처리 계층과 로컬 전략 배지 확인 기능을 도입합니다.
  • 테스트와 함께 SafeImage의 대체 이미지 및 플레이스홀더 동작을 추가합니다.
  • 알려진 전략/API 아이콘 URL 및 레이블을 생성된 로컬 SVG 배지에 매핑합니다.
  • 전략 목록, 상세 보기, 관리자 테이블, 아바타, 파일 미리보기 및 차트가 복원력 있는 이미지 컴포넌트를 사용하도록 업데이트합니다.
src/components/common/SafeImage.tsx
src/components/common/SafeImage.test.tsx
src/components/common/StrategyIcon.tsx
src/assets/icons/strategy/index.ts
src/assets/icons/strategy/*.svg
src/constants/strategyIconMappings.ts
src/components/common/Avatar.tsx
src/components/common/StrategyList.tsx
src/components/page/search/SearchedStrategyList.tsx
src/components/page/strategy-detail/IconTagSection.tsx
src/components/page/strategy-detail/ImgSection.tsx
src/components/page/admin/FileInput.tsx
src/components/page/admin/TypeTable.tsx
src/pages/admin/strategy/StrategyApprovalListPage.tsx
입력 프리미티브를 재사용 가능하고 ref 전달을 지원하는 컴포넌트로 리팩터링하고 폼 피드백을 표준화합니다.
  • 정규화된 크기 이름과 중앙화된 입력 스타일을 사용하는 BaseInput ref 전달 기능을 추가합니다.
  • Input을 제어 컴포넌트로 변경하고 BaseInput으로 구성하며, 유효성 검사, 아이콘, 비밀번호 표시 전환 및 지우기 동작을 유지합니다.
  • TextField에 레이블, 필수 표시, 도움말/오류 메시지 및 가로/세로 레이아웃을 추가합니다.
  • 로그인, 회원가입 및 프로필 입력을 공유 프리미티브로 마이그레이션합니다.
src/components/common/BaseInput.tsx
src/components/common/Input.tsx
src/components/common/TextField.tsx
src/components/page/mypage/ProfileInput.tsx
src/pages/auth/SignInPage.tsx
src/components/page/signup/SignUpForm.tsx
전략 상세 동작, 권한 확인 및 분석 데이터 처리를 개선합니다.
  • 전략 상태, 소유권, 역할 및 종료 확인을 상태 유틸리티로 중앙화합니다.
  • 이름이 변경된 API 필드와 공유 권한 조건자를 사용하도록 전략 상세 props 및 헤더 동작을 업데이트합니다.
  • 비율 표시 렌더링과 재사용 가능한 일간/월간 분석 매퍼를 추출합니다.
  • 이미지 로드 완료 여부에 따른 확장 기능과 스크롤 잠금 정리를 추가하고 분석 쿼리를 무기한 캐시합니다.
src/pages/strategy/StrategyDetailPage.tsx
src/components/page/strategy-detail/StrategyHeader.tsx
src/components/page/strategy-detail/StrategyTitleSection.tsx
src/components/page/strategy-detail/StrategyIndicator.tsx
src/components/page/strategy-detail/RateItem.tsx
src/components/page/strategy-detail/ImgSection.tsx
src/components/page/strategy-detail/tabmenu/DailyAnalysis.tsx
src/components/page/strategy-detail/tabmenu/MonthlyAnalysis.tsx
src/utils/statusUtils.ts
src/utils/mappers.ts
src/types/strategyDetail.ts
src/hooks/queries/useFetchDailyAnalysis.ts
src/hooks/queries/useFetchMonthlyAnalysis.ts
Storybook 및 Chromatic 시각적 검토 인프라를 추가합니다.
  • 필수 기능 및 Chromatic 애드온과 함께 Vite React용 Storybook을 구성합니다.
  • 대화형 Button 및 Pagination 스토리를 추가합니다.
  • 풀 리퀘스트에서 Chromatic을 실행하고 생성된 Storybook URL을 댓글로 게시합니다.
.storybook/main.ts
.storybook/preview.ts
src/stories/Button.stories.tsx
src/stories/Pagination.stories.tsx
.github/workflows/chromatic.yml
package.json
.eslintrc.cjs
package-lock.json
UI, 에셋, 접근성 및 목 데이터 관련 개선 사항을 적용합니다.
  • 페이지 메타데이터, 명시적인 로고 크기, 접근 가능한 버튼 레이블, 반응형 테이블 행 및 조정된 페이지네이션 스타일을 추가합니다.
  • 홈 화면의 일부 에셋을 WebP로 전환하고 전략 배지 SVG 및 미리보기를 생성하는 스크립트를 추가합니다.
  • MSW 워커 출력, 목 라우트 매개변수 안전성 및 회원가입 상태 초기화를 업데이트하고 사용하지 않는 NotFoundLayout을 제거합니다.
index.html
src/components/common/Header.tsx
src/components/common/Pagination.tsx
src/pages/HomePage.tsx
src/components/page/home/StrategyTraderCount.tsx
src/pages/auth/signup/SignUpPage.tsx
src/pages/search/SearchInput.tsx
src/mocks/handlers/strategyDetail.handlers.ts
src/mocks/handlers/strategyStatistics.handlers.ts
public/mockServiceWorker.js
scripts/generate_strategy_icons.py
.gitignore
src/pages/test-page/InputTestPage.tsx
src/layouts/NotFoundLayout.tsx

팁 및 명령어

Sourcery 사용하기

  • 새 리뷰 요청: 풀 리퀘스트에 @sourcery-ai review를 댓글로 작성합니다.
  • 토론 계속하기: Sourcery의 리뷰 댓글에 직접 답글을 작성합니다.
  • 리뷰 댓글에서 GitHub 이슈 생성: 리뷰 댓글에 답글로 이슈를 생성해 달라고 Sourcery에 요청합니다. 리뷰 댓글에 @sourcery-ai issue라고 답글을 작성하여 이슈를 생성할 수도 있습니다.
  • 풀 리퀘스트 제목 생성: 언제든 제목을 생성하려면 풀 리퀘스트 제목의 아무 위치에나 @sourcery-ai를 작성합니다. 풀 리퀘스트에 @sourcery-ai title을 댓글로 작성하여 언제든 제목을 다시 생성할 수도 있습니다.
  • 풀 리퀘스트 요약 생성: 원하는 위치에 PR 요약을 생성하려면 풀 리퀘스트 본문의 아무 위치에나 @sourcery-ai summary를 작성합니다. 풀 리퀘스트에 @sourcery-ai summary를 댓글로 작성하여 언제든 요약을 다시 생성할 수도 있습니다.
  • 리뷰어 가이드 생성: 언제든 풀 리퀘스트에 @sourcery-ai guide를 댓글로 작성하여 리뷰어 가이드를 다시 생성합니다.
  • 모든 Sourcery 댓글 해결: 모든 Sourcery 댓글을 해결하려면 풀 리퀘스트에 @sourcery-ai resolve를 댓글로 작성합니다. 모든 댓글에 이미 대응했으며 더 이상 댓글을 보고 싶지 않을 때 유용합니다.
  • 모든 Sourcery 리뷰 닫기: 기존 Sourcery 리뷰를 모두 닫으려면 풀 리퀘스트에 @sourcery-ai dismiss를 댓글로 작성합니다. 새 리뷰로 다시 시작하고 싶을 때 특히 유용합니다. 새 리뷰를 요청하려면 @sourcery-ai review를 댓글로 작성하는 것을 잊지 마세요!

사용 환경 맞춤 설정

대시보드에 액세스하여 다음을 수행할 수 있습니다.

  • Sourcery가 생성한 풀 리퀘스트 요약, 리뷰어 가이드 등의 리뷰 기능을 활성화하거나 비활성화합니다.
  • 리뷰 언어를 변경합니다.
  • 사용자 지정 리뷰 지침을 추가, 삭제 또는 편집합니다.
  • 기타 리뷰 설정을 조정합니다.

도움말

Original review guide in English

Reviewer's Guide

This PR implements broad frontend feature and UI improvements by consolidating input and image primitives, adding resilient local strategy icons, refining strategy-detail authorization and analysis flows, optimizing assets and layout behavior, and introducing Storybook/Chromatic visual regression tooling.

Sequence diagram for resilient image loading

sequenceDiagram
    participant Component
    participant StrategyIcon
    participant SafeImage
    participant Browser
    participant Placeholder

    Component->>StrategyIcon: getIconSource(src, label)
    StrategyIcon->>SafeImage: render resolved src
    SafeImage->>Browser: load image
    alt image loads
        Browser-->>SafeImage: onLoad
        SafeImage-->>Component: display image
    else image fails
        Browser-->>SafeImage: onError
        SafeImage->>SafeImage: setFailed(true)
        SafeImage->>Browser: load fallbackSrc
        alt fallback loads
            Browser-->>SafeImage: onLoad
            SafeImage-->>Component: display fallback image
        else fallback fails
            Browser-->>SafeImage: onError
            SafeImage->>SafeImage: setFallbackFailed(true)
            SafeImage-->>Placeholder: render role img placeholder
        end
    end
Loading

File-Level Changes

Change Details Files
Introduces a shared image-failure handling layer and local strategy badge resolution.
  • Adds SafeImage fallback and placeholder behavior with tests.
  • Maps known strategy/API icon URLs and labels to generated local SVG badges.
  • Updates strategy lists, detail views, admin tables, avatars, file previews, and charts to use resilient image components.
src/components/common/SafeImage.tsx
src/components/common/SafeImage.test.tsx
src/components/common/StrategyIcon.tsx
src/assets/icons/strategy/index.ts
src/assets/icons/strategy/*.svg
src/constants/strategyIconMappings.ts
src/components/common/Avatar.tsx
src/components/common/StrategyList.tsx
src/components/page/search/SearchedStrategyList.tsx
src/components/page/strategy-detail/IconTagSection.tsx
src/components/page/strategy-detail/ImgSection.tsx
src/components/page/admin/FileInput.tsx
src/components/page/admin/TypeTable.tsx
src/pages/admin/strategy/StrategyApprovalListPage.tsx
Refactors input primitives into reusable, ref-forwarding components and standardizes form feedback.
  • Adds BaseInput ref forwarding with normalized size names and centralizes input styling.
  • Makes Input controlled, composes it from BaseInput, and preserves validation, icons, password toggling, and clear actions.
  • Adds TextField labels, required markers, helper/error messages, and horizontal/vertical layouts.
  • Migrates sign-in, sign-up, and profile inputs to the shared primitives.
src/components/common/BaseInput.tsx
src/components/common/Input.tsx
src/components/common/TextField.tsx
src/components/page/mypage/ProfileInput.tsx
src/pages/auth/SignInPage.tsx
src/components/page/signup/SignUpForm.tsx
Improves strategy detail behavior, authorization checks, and analysis data handling.
  • Centralizes strategy status, ownership, role, and termination checks in status utilities.
  • Updates strategy detail props and header actions to use the renamed API fields and shared authorization predicates.
  • Extracts rate indicator rendering and reusable daily/monthly analysis mappers.
  • Adds image-load-gated expansion with scroll-lock cleanup and caches analysis queries indefinitely.
src/pages/strategy/StrategyDetailPage.tsx
src/components/page/strategy-detail/StrategyHeader.tsx
src/components/page/strategy-detail/StrategyTitleSection.tsx
src/components/page/strategy-detail/StrategyIndicator.tsx
src/components/page/strategy-detail/RateItem.tsx
src/components/page/strategy-detail/ImgSection.tsx
src/components/page/strategy-detail/tabmenu/DailyAnalysis.tsx
src/components/page/strategy-detail/tabmenu/MonthlyAnalysis.tsx
src/utils/statusUtils.ts
src/utils/mappers.ts
src/types/strategyDetail.ts
src/hooks/queries/useFetchDailyAnalysis.ts
src/hooks/queries/useFetchMonthlyAnalysis.ts
Adds Storybook and Chromatic visual-review infrastructure.
  • Configures Storybook for Vite React with essentials and Chromatic addons.
  • Adds interactive Button and Pagination stories.
  • Runs Chromatic on pull requests and comments the generated Storybook URL.
.storybook/main.ts
.storybook/preview.ts
src/stories/Button.stories.tsx
src/stories/Pagination.stories.tsx
.github/workflows/chromatic.yml
package.json
.eslintrc.cjs
package-lock.json
Applies supporting UI, asset, accessibility, and mock-data improvements.
  • Adds page metadata, explicit logo dimensions, accessible button labels, responsive table rows, and adjusted pagination styling.
  • Switches selected home assets to WebP and adds a script for generating strategy badge SVGs and previews.
  • Updates MSW worker output, mock route parameter safety, signup state reset, and removes the unused NotFoundLayout.
index.html
src/components/common/Header.tsx
src/components/common/Pagination.tsx
src/pages/HomePage.tsx
src/components/page/home/StrategyTraderCount.tsx
src/pages/auth/signup/SignUpPage.tsx
src/pages/search/SearchInput.tsx
src/mocks/handlers/strategyDetail.handlers.ts
src/mocks/handlers/strategyStatistics.handlers.ts
public/mockServiceWorker.js
scripts/generate_strategy_icons.py
.gitignore
src/pages/test-page/InputTestPage.tsx
src/layouts/NotFoundLayout.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

안녕하세요. 5개의 이슈를 발견했습니다.

수정된 보안 이슈:

AI 에이전트용 프롬프트
이 코드 리뷰의 의견을 반영해 주세요:

## 개별 의견

### 의견 1
<location path="src/pages/strategy/StrategyDetailPage.tsx" line_range="177-178" />
<code_context>
-    )
-      navigate('/404', { replace: true });
-  }, [strategy, isOwner, isAdmin]);
+    if (user) {
+      const isOwnerOrAdmin = isStrategyOwner(strategy, user) || isAdmin(user.role);
+      if (
+        (strategy?.isApproved === 'N' && !isOwnerOrAdmin) ||
</code_context>
<issue_to_address>
**issue (bug_risk):** 초기 로딩 렌더링 중에는 `strategy`가 아직 undefined인데 권한 effect에서 `isStrategyOwner(strategy, user)`를 호출합니다. 따라서 `isStrategyOwner`가 undefined에서 `memberId`를 읽으려 하여 쿼리가 완료되기 전에 전략 상세 페이지가 충돌합니다. 또한 이 호출은 `isStrategyOwner`가 member ID 문자열을 기대하는데 `StrategyDetailProps` 객체를 전달하므로 TypeScript 타입 오류도 발생시킵니다.

**트리거:** 인증된 사용자가 상세 쿼리가 반환되기 전에 전략 상세 페이지를 열 때 발생합니다.

**권장 수정:** `strategy`가 존재하는지 확인하고 `strategy.memberId`를 전달하세요. 예: `strategy && isStrategyOwner(strategy.memberId, user)`.

```suggestion
    if (user) {
      const isOwnerOrAdmin = strategy && isStrategyOwner(strategy.memberId, user) || isAdmin(user.role);
```
</issue_to_address>

### 의견 2
<location path="src/pages/strategy/StrategyDetailPage.tsx" line_range="177-185" />
<code_context>
-    )
-      navigate('/404', { replace: true });
-  }, [strategy, isOwner, isAdmin]);
+    if (user) {
+      const isOwnerOrAdmin = isStrategyOwner(strategy, user) || isAdmin(user.role);
+      if (
+        (strategy?.isApproved === 'N' && !isOwnerOrAdmin) ||
+        (strategy?.isPosted === 'N' && !isOwnerOrAdmin)
+      )
+        navigate('/404', { replace: true });
+    }
+  }, [strategy, isStrategyOwner, isAdmin]);
+
+  if (isError) {
</code_context>
<issue_to_address>
**🚨 issue (security):** 권한 effect에서 `user`를 읽지만 dependency array에는 포함하지 않았습니다. 따라서 전략 데이터 이후 인증 상태가 로드되어도 effect가 다시 실행되지 않습니다. 인증 상태가 아직 undefined인 동안 리디렉션 검사가 실행되면 비공개 또는 승인되지 않은 전략이 계속 렌더링될 수 있습니다.

**트리거:** 인증 저장소의 hydration이 완료되기 전에 전략 쿼리가 완료될 때 발생합니다.

**권장 수정:** effect dependency array에 `user`를 포함하고, 소유권을 확인하기 전에 `strategy` 값이 존재하는지 확인하세요.
</issue_to_address>

### 의견 3
<location path="src/components/page/strategy-detail/tabmenu/DailyAnalysis.tsx" line_range="344-347" />
<code_context>
   };

+  // status prop 변경 시 동기화
   useEffect(() => {
     setInputStatus(status);
   }, [status]);
</code_context>
<issue_to_address>
**issue (broader_impact):** 페이지 변경 시 초기화 로직을 제거하면 사용자가 페이지를 변경해도 `selectedData``selectAll`에 기존 값이 남습니다. 그 결과 일괄 삭제가 이전 페이지에서 선택한 행에 대해 실행될 수 있으며, 새 페이지의 UI에는 여전히 전체 선택 상태가 표시됩니다.

**트리거:** 사용자가 분석 행을 선택한 후 일괄 작업을 실행하기 전에 다른 페이지로 이동할 때 발생합니다.

**권장 수정:** `pagination.currentPage`가 변경될 때마다 `selectedData``selectAll`을 초기화하는 effect를 복원하세요.
</issue_to_address>

### 의견 4
<location path=".github/workflows/chromatic.yml" line_range="3" />
<code_context>
+          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
+          token: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: comment PR
+        uses: thollander/actions-comment-pull-request@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          message: '🚀storybook: ${{ steps.chromatic.outputs.storybookUrl }}'
</code_context>
<issue_to_address>
**🚨 issue (security):** workflow에서 `GITHUB_TOKEN``pull-requests: write` 권한을 선언하지 않았습니다. 따라서 기본 읽기 전용 토큰을 사용하는 저장소에서는 PR 댓글을 생성하거나 업데이트할 수 없고, Chromatic이 성공하더라도 마지막 단계가 실패합니다.

**트리거:** 저장소의 workflow 토큰이 기본적으로 읽기 전용 권한을 사용할 때 발생합니다.

**권장 수정:** 최상위에 `permissions: pull-requests: write`를 선언하거나 필요한 job 수준 권한을 설정하세요.

```suggestion
on: pull_request

permissions:
  pull-requests: write
```
</issue_to_address>

### 의견 5
<location path="index.html" line_range="12" />
<code_context>
     <link rel="mask-icon" href="/src/assets/images/favicon/safari-pinned-tab.svg" color="#0D9488" />
     <link rel="manifest" href="/src/assets/images/manifest.json" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta
+      property="description"
+      content="트레들리는 체계적이고 효율적인 투자 전략을 제공합니다. 지금 바로 시작해 보세요!"
+    />
     <title>트레들리 | 체계적인 투자 전략 플랫폼</title>
</code_context>
<issue_to_address>
**nitpick:** 페이지 설명이 표준 `name="description"` 대신 `property="description"`으로 선언되어 있습니다. 따라서 브라우저와 검색 크롤러가 이를 문서의 meta description으로 인식하지 못합니다.

**권장 수정:** 속성을 `name="description"`으로 변경하세요.

```suggestion
      name="description"
```
</issue_to_address>

Sourcery는 오픈 소스 프로젝트에 무료로 제공됩니다. 리뷰가 마음에 드셨다면 공유해 주세요 ✨
Original comment in English

Hey - I've found 5 issues

Fixed security issues:

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/pages/strategy/StrategyDetailPage.tsx" line_range="177-178" />
<code_context>
-    )
-      navigate('/404', { replace: true });
-  }, [strategy, isOwner, isAdmin]);
+    if (user) {
+      const isOwnerOrAdmin = isStrategyOwner(strategy, user) || isAdmin(user.role);
+      if (
+        (strategy?.isApproved === 'N' && !isOwnerOrAdmin) ||
</code_context>
<issue_to_address>
**issue (bug_risk):** The permission effect calls `isStrategyOwner(strategy, user)` while `strategy` is still undefined during the initial loading render, so `isStrategyOwner` reads `memberId` from undefined and the strategy detail page crashes before the query completes. This call also passes a `StrategyDetailProps` object where `isStrategyOwner` expects a member ID string, producing a TypeScript type error.

**Triggers:** When an authenticated user opens the strategy detail page before the detail query has returned.

**Suggested fix:** Guard the call with `strategy` and pass `strategy.memberId`, for example `strategy && isStrategyOwner(strategy.memberId, user)`.

```suggestion
    if (user) {
      const isOwnerOrAdmin = strategy && isStrategyOwner(strategy.memberId, user) || isAdmin(user.role);
```
</issue_to_address>

### Comment 2
<location path="src/pages/strategy/StrategyDetailPage.tsx" line_range="177-185" />
<code_context>
-    )
-      navigate('/404', { replace: true });
-  }, [strategy, isOwner, isAdmin]);
+    if (user) {
+      const isOwnerOrAdmin = isStrategyOwner(strategy, user) || isAdmin(user.role);
+      if (
+        (strategy?.isApproved === 'N' && !isOwnerOrAdmin) ||
+        (strategy?.isPosted === 'N' && !isOwnerOrAdmin)
+      )
+        navigate('/404', { replace: true });
+    }
+  }, [strategy, isStrategyOwner, isAdmin]);
+
+  if (isError) {
</code_context>
<issue_to_address>
**🚨 issue (security):** The authorization effect reads `user` but omits it from its dependency array, so it does not rerun when authentication state loads after the strategy data. A private or unapproved strategy can therefore remain rendered because the redirect check ran while `user` was undefined.

**Triggers:** When the strategy query resolves before the auth store finishes hydrating.

**Suggested fix:** Include `user` in the effect dependency array and guard the strategy value before checking ownership.
</issue_to_address>

### Comment 3
<location path="src/components/page/strategy-detail/tabmenu/DailyAnalysis.tsx" line_range="344-347" />
<code_context>
   };

+  // status prop 변경 시 동기화
   useEffect(() => {
     setInputStatus(status);
   }, [status]);
</code_context>
<issue_to_address>
**issue (broader_impact):** Removing the pagination-change reset leaves `selectedData` and `selectAll` populated when the user changes pages. Bulk deletion can therefore operate on rows selected on a previous page, while the UI on the new page still reports the select-all state.

**Triggers:** When a user selects analysis rows and then navigates to another page before using the bulk action.

**Suggested fix:** Restore the effect that clears `selectedData` and `selectAll` whenever `pagination.currentPage` changes.
</issue_to_address>

### Comment 4
<location path=".github/workflows/chromatic.yml" line_range="3" />
<code_context>
+          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
+          token: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: comment PR
+        uses: thollander/actions-comment-pull-request@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          message: '🚀storybook: ${{ steps.chromatic.outputs.storybookUrl }}'
</code_context>
<issue_to_address>
**🚨 issue (security):** The workflow does not declare `pull-requests: write` permissions for `GITHUB_TOKEN`, so repositories using the default read-only token cannot create or update the PR comment and the final step fails despite Chromatic succeeding.

**Triggers:** When the repository's workflow token defaults to read-only permissions.

**Suggested fix:** Add a top-level `permissions: pull-requests: write` declaration, or configure the required job-level permission.

```suggestion
on: pull_request

permissions:
  pull-requests: write
```
</issue_to_address>

### Comment 5
<location path="index.html" line_range="12" />
<code_context>
     <link rel="mask-icon" href="/src/assets/images/favicon/safari-pinned-tab.svg" color="#0D9488" />
     <link rel="manifest" href="/src/assets/images/manifest.json" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta
+      property="description"
+      content="트레들리는 체계적이고 효율적인 투자 전략을 제공합니다. 지금 바로 시작해 보세요!"
+    />
     <title>트레들리 | 체계적인 투자 전략 플랫폼</title>
</code_context>
<issue_to_address>
**nitpick:** The page description is declared with `property="description"` instead of the standard `name="description"`, so browsers and search crawlers do not recognize it as the document meta description.

**Suggested fix:** Change the attribute to `name="description"`.

```suggestion
      name="description"
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +177 to +178
if (user) {
const isOwnerOrAdmin = isStrategyOwner(strategy, user) || isAdmin(user.role);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): 초기 로딩 렌더링 중에는 strategy가 아직 undefined인데 권한 effect에서 isStrategyOwner(strategy, user)를 호출합니다. 따라서 isStrategyOwner가 undefined에서 memberId를 읽으려 하여 쿼리가 완료되기 전에 전략 상세 페이지가 충돌합니다. 또한 이 호출은 isStrategyOwner가 member ID 문자열을 기대하는데 StrategyDetailProps 객체를 전달하므로 TypeScript 타입 오류도 발생시킵니다.

트리거: 인증된 사용자가 상세 쿼리가 반환되기 전에 전략 상세 페이지를 열 때 발생합니다.

권장 수정: strategy가 존재하는지 확인하고 strategy.memberId를 전달하세요. 예: strategy && isStrategyOwner(strategy.memberId, user).

Suggested change
if (user) {
const isOwnerOrAdmin = isStrategyOwner(strategy, user) || isAdmin(user.role);
if (user) {
const isOwnerOrAdmin = strategy && isStrategyOwner(strategy.memberId, user) || isAdmin(user.role);
Original comment in English

issue (bug_risk): The permission effect calls isStrategyOwner(strategy, user) while strategy is still undefined during the initial loading render, so isStrategyOwner reads memberId from undefined and the strategy detail page crashes before the query completes. This call also passes a StrategyDetailProps object where isStrategyOwner expects a member ID string, producing a TypeScript type error.

Triggers: When an authenticated user opens the strategy detail page before the detail query has returned.

Suggested fix: Guard the call with strategy and pass strategy.memberId, for example strategy && isStrategyOwner(strategy.memberId, user).

Suggested change
if (user) {
const isOwnerOrAdmin = isStrategyOwner(strategy, user) || isAdmin(user.role);
if (user) {
const isOwnerOrAdmin = strategy && isStrategyOwner(strategy.memberId, user) || isAdmin(user.role);

Comment on lines +177 to +185
if (user) {
const isOwnerOrAdmin = isStrategyOwner(strategy, user) || isAdmin(user.role);
if (
(strategy?.isApproved === 'N' && !isOwnerOrAdmin) ||
(strategy?.isPosted === 'N' && !isOwnerOrAdmin)
)
navigate('/404', { replace: true });
}
}, [strategy, isStrategyOwner, isAdmin]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 issue (security): 권한 effect에서 user를 읽지만 dependency array에는 포함하지 않았습니다. 따라서 전략 데이터 이후 인증 상태가 로드되어도 effect가 다시 실행되지 않습니다. 인증 상태가 아직 undefined인 동안 리디렉션 검사가 실행되면 비공개 또는 승인되지 않은 전략이 계속 렌더링될 수 있습니다.

트리거: 인증 저장소의 hydration이 완료되기 전에 전략 쿼리가 완료될 때 발생합니다.

권장 수정: effect dependency array에 user를 포함하고, 소유권을 확인하기 전에 strategy 값이 존재하는지 확인하세요.

Original comment in English

🚨 issue (security): The authorization effect reads user but omits it from its dependency array, so it does not rerun when authentication state loads after the strategy data. A private or unapproved strategy can therefore remain rendered because the redirect check ran while user was undefined.

Triggers: When the strategy query resolves before the auth store finishes hydrating.

Suggested fix: Include user in the effect dependency array and guard the strategy value before checking ownership.

Comment on lines -344 to -347
useEffect(() => {
setSelectedData([]);
setSelectAll(false);
}, [pagination.currentPage]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (broader_impact): 페이지 변경 시 초기화 로직을 제거하면 사용자가 페이지를 변경해도 selectedDataselectAll에 기존 값이 남습니다. 그 결과 일괄 삭제가 이전 페이지에서 선택한 행에 대해 실행될 수 있으며, 새 페이지의 UI에는 여전히 전체 선택 상태가 표시됩니다.

트리거: 사용자가 분석 행을 선택한 후 일괄 작업을 실행하기 전에 다른 페이지로 이동할 때 발생합니다.

권장 수정: pagination.currentPage가 변경될 때마다 selectedDataselectAll을 초기화하는 effect를 복원하세요.

Original comment in English

issue (broader_impact): Removing the pagination-change reset leaves selectedData and selectAll populated when the user changes pages. Bulk deletion can therefore operate on rows selected on a previous page, while the UI on the new page still reports the select-all state.

Triggers: When a user selects analysis rows and then navigates to another page before using the bulk action.

Suggested fix: Restore the effect that clears selectedData and selectAll whenever pagination.currentPage changes.

@@ -0,0 +1,27 @@
name: 'Chromatic Deployment'

on: pull_request

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 issue (security): workflow에서 GITHUB_TOKENpull-requests: write 권한을 선언하지 않았습니다. 따라서 기본 읽기 전용 토큰을 사용하는 저장소에서는 PR 댓글을 생성하거나 업데이트할 수 없고, Chromatic이 성공하더라도 마지막 단계가 실패합니다.

트리거: 저장소의 workflow 토큰이 기본적으로 읽기 전용 권한을 사용할 때 발생합니다.

권장 수정: 최상위에 permissions: pull-requests: write를 선언하거나 필요한 job 수준 권한을 설정하세요.

Suggested change
on: pull_request
on: pull_request
permissions:
pull-requests: write
Original comment in English

🚨 issue (security): The workflow does not declare pull-requests: write permissions for GITHUB_TOKEN, so repositories using the default read-only token cannot create or update the PR comment and the final step fails despite Chromatic succeeding.

Triggers: When the repository's workflow token defaults to read-only permissions.

Suggested fix: Add a top-level permissions: pull-requests: write declaration, or configure the required job-level permission.

Suggested change
on: pull_request
on: pull_request
permissions:
pull-requests: write

Comment thread index.html
<link rel="manifest" href="/src/assets/images/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
property="description"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nitpick: 페이지 설명이 표준 name="description" 대신 property="description"으로 선언되어 있습니다. 따라서 브라우저와 검색 크롤러가 이를 문서의 meta description으로 인식하지 못합니다.

권장 수정: 속성을 name="description"으로 변경하세요.

Suggested change
property="description"
name="description"
Original comment in English

nitpick: The page description is declared with property="description" instead of the standard name="description", so browsers and search crawlers do not recognize it as the document meta description.

Suggested fix: Change the attribute to name="description".

Suggested change
property="description"
name="description"

@seoyoonyi
seoyoonyi merged commit 0279cc9 into FC-DEV-FinalProject:main Sep 8, 2026
2 of 4 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.

3 participants