Skip to content

[Deploy] develop → main 배포 반영 - #243

Merged
Seojegyeong merged 67 commits into
mainfrom
develop
Jun 1, 2026
Merged

[Deploy] develop → main 배포 반영#243
Seojegyeong merged 67 commits into
mainfrom
develop

Conversation

@Seojegyeong

@Seojegyeong Seojegyeong commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

🚨 관련 이슈

#209 #213 #214 #220 #222 #225 #227 #228 #232

✨ 변경사항

  • 🐞 BugFix Something isn't working
  • 💻 CrossBrowsing Browser compatibility
  • 🌏 Deploy Deploy
  • 🎨 Design Markup & styling
  • 📃 Docs Documentation writing and editing (README.md, etc.)
  • ✨ Feature Feature
  • 🔨 Refactor Code refactoring
  • ⚙️ Setting Development environment setup
  • ✅ Test Test related (storybook, jest, etc.)

✏️ 작업 내용

✨ Feature

  • 권한 관리 시스템 도입
    • myRole 필드 추가 및 mainLayout 초기 로드 시 role 세팅, 워크스페이스 전환 시 role 갱신
    • RoleGuard 컴포넌트 구현 및 ADMIN 전용 라우터에 적용
    • NavItemrequiredRole 추가 + 사이드바 메뉴 필터링
    • useIsAdmin 훅 생성
  • 대시보드 / AI 요약
    • 통합 대시보드 AI 요약 카드 추가 및 AI 요약 분석 API 연동
    • 플랫폼별 광고 현황 상세 API 연동
    • AI 요약 PDF 저장 기능 추가
  • 설정 페이지 개편
    • 탭 형식 제거 → 단일 페이지로 섹션 통합
    • 알림 설정 UI 추가
    • 변경사항 저장 버튼이 섹션(탭)별로 독립 동작하도록 구현
  • 통합 캠페인 등록 각 섹션 설명 추가

🐞 BugFix

  • 워크스페이스 전환/마운트 시 AI 요약 상태 초기화
  • 인가 실패 시 role이 null로 떨어져 화면이 비는 문제 해결
  • 생성 모달 오류 발생 시 안 닫히던 문제 해결
  • hasChange 로직 탭 구분 버그 수정
  • PDF 인쇄 로직(토큰 적용) 및 gradient 클래스 오타 수정
  • AI 리포트 accessToken URL 인코딩 처리

🔨 Refactor

  • overview 폴더 구조 정리 (sections-ai-summary 구조로 통합)
  • AI 요약 카드 본문 단일 파일로 통합 / 공용 컴포넌트 분리
  • 대시보드 플랫폼·타입 로직 provider.ts로 통합
  • 헤더 AI 요약 버튼의 빈 드로어 제거

🎨 Design

  • 플랫폼 차트 색상을 theme 토큰으로 교체 / 통일
  • shadow-Soft 통일 및 위젯·대시보드 레이아웃 정리
  • AI 요약 카드 시각적 차별화 적용
  • 캠페인 목록·상세, 광고 목록 등 메타데이터(Meta) 정비

✅ Test

  • Playwright E2E 추가 및 dashboard 네비게이션 spec 수정

😅 미완성 작업

N/A

📢 논의 사항 및 참고 사항

💬 리뷰어 가이드 (P-Rules)
P1: 필수 반영 (Critical) - 버그 가능성, 컨벤션 위반. 해결 전 머지 불가.
P2: 적극 권장 (Recommended) - 더 나은 대안 제시. 가급적 반영 권장.
P3: 제안 (Suggestion) - 아이디어 공유. 반영 여부는 드라이버 자율.
P4: 단순 확인/칭찬 (Nit) - 사소한 오타, 칭찬 등 피드백.

Summary by CodeRabbit

주요 변경 사항

  • New Features

    • AI 광고 성과 분석 및 리포트 생성 기능 추가
    • 플랫폼별 상세 일별 지표 조회 기능 추가
    • 알림 채널 설정 UI 추가
    • 역할 기반 접근 제어 기능 추가
  • Platform Support

    • Meta(메타) 광고 플랫폼 추가
    • Kakao(카카오) 광고 플랫폼 제거
  • UI/Style

    • 일관된 그림자 스타일 적용
    • 카드 및 컴포넌트 스타일 정렬

jjjsun and others added 30 commits May 16, 2026 14:44
[Feature/#214] Meta 플랫폼 반영 및 통합 캠페인 등록 UX 개선
[Feature/#220] 플랫폼별 광고 현황 상세 API 연동
Seojegyeong and others added 19 commits May 19, 2026 18:19
[Refactor/#227] shadow-Soft 통일 및 랜딩·대시보드 레이아웃 정리
[Feature/#228] AI 요약하기 API 연동
[Feature/#232] 워크스페이스 역할 기반 접근 제어(RBAC) 도입
@Seojegyeong
Seojegyeong requested review from YermIm and jjjsun June 1, 2026 05:16
@Seojegyeong Seojegyeong self-assigned this Jun 1, 2026
@Seojegyeong Seojegyeong added the 🌏 Deploy 배포 관련 label Jun 1, 2026
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

대규모 플랫폼 통합 변경사항으로 Kakao 플랫폼을 Meta로 전환하고, AI 광고 성과 분석 기능을 새로 추가하며, 역할 기반 접근 제어 시스템을 도입하고, 대시보드 메트릭을 개선하며, UI 스타일을 통일합니다.

Changes

플랫폼 타입 중앙화 및 Kakao→Meta 전환

Layer / File(s) Summary
플랫폼 타입 및 상수 중앙화
src/types/dashboard/provider.ts, src/types/ads/campaign.ts, src/types/dashboard/overview.ts, src/types/dashboard/platform.ts
provider.tsPROVIDER_TYPES, PLATFORM_MAP, PLATFORM_CHART_COLORS 상수와 TProviderType, TAiAnalysisProvider 타입을 중앙화하여 여러 모듈에서 일관되게 플랫폼을 관리합니다. Kakao 플랫폼은 Meta로 완전 대체되고 관련 타입도 업데이트됩니다.
광고 컴포넌트 플랫폼 전환
src/components/ads/AdListTable.tsx, src/components/ads/AdRow.tsx, src/components/ads/CampaignRow.tsx, src/pages/ads/new/CampaignGroup.tsx, src/hooks/ads/useCampaignGroup.ts
광고 목록, 로고 매핑, 캠페인 그룹 선택 UI에서 Kakao를 Meta로 완전히 대체합니다. useCampaignGroup은 Kakao 상태를 Meta로 변경하고 캠페인 선택 로직도 맞춰집니다.
대시보드 플랫폼 타입 정렬
src/components/dashboard/charts/PerformanceEfficiencyChart.tsx, src/components/dashboard/platform/*.tsx, src/hooks/dashboard/use*.ts
전체 대시보드 컴포넌트의 import 경로를 provider.ts 기준으로 정렬하고 TPlatformProviderTProviderType 타입 전환으로 타입 안정성을 개선합니다.
캠페인 정보 카드 제거
src/components/ads/CampaignInfoCard.tsx, src/components/ads/PlatformCard.tsx
기존 Kakao 기반 카드 컴포넌트를 완전히 제거하여 코드 정리 및 중복 제거를 진행합니다.

역할 기반 접근 제어(RBAC) 시스템

Layer / File(s) Summary
워크스페이스 역할 상태 관리
src/store/useWorkspaceStore.ts, src/layout/main/MainLayout.tsx, src/components/sidebar/WorkspaceSwitcher.tsx
useWorkspaceStoremyRole 상태를 추가하여 사용자의 워크스페이스 역할을 중앙화합니다. 워크스페이스 선택 시 역할 정보도 함께 동기화됩니다.
라우트 권한 보호 및 훅
src/routes/RoleGuard.tsx, src/hooks/auth/useIsAdmin.ts, src/routes/MainRoutes.tsx
새로운 RoleGuard 컴포넌트로 라우트를 보호하고 useIsAdmin 훅으로 관리자 권한을 확인합니다. 워크스페이스 빌링, 멤버 관리 등 ADMIN 전용 기능을 라우트 레벨에서 보호합니다.
사이드바 네비게이션 필터링
src/components/sidebar/Sidebar.tsx, src/types/navigation/navItem.ts, src/constants/sidebarNav.ts
네비게이션 항목에 requiredRole 필드를 추가하고 filterNavByRole로 사용자 역할에 따라 메뉴를 필터링합니다.
워크스페이스 설정 권한 제어
src/pages/workspace/WorkspaceSetting.tsx, src/pages/setting/Setting.tsx, src/components/setting/ProfileSection.tsx
워크스페이스 로고, 이름, 설명 편집과 삭제/저장 기능을 ADMIN만 사용 가능하도록 제한합니다.

AI 광고 성과 분석 기능

Layer / File(s) Summary
AI 분석 API 및 타입
src/api/dashboard/aiAnalysis.ts, src/types/dashboard/aiAnalysis.ts
새로운 requestAiAnalysis, getAiReportByAccessToken API 함수와 IAnalysisRequest, IAnalysisResponse, IReportStatusResponse 타입 정의입니다.
AI 분석 리포트 관리 훅
src/hooks/dashboard/useAiAnalysisReport.ts
분석 요청, 리포트 상태 폴링, 타임아웃 처리를 통합하는 useAiAnalysisReport 훅으로 로딩/에러 상태와 메시지를 제공합니다.
AI 요약 카드 UI 컴포넌트
src/components/dashboard/ai-report/components/AiSummaryCard.tsx, src/components/dashboard/ai-report/components/DashboardAiSummarySection.tsx
분석 결과를 접힘/펼침 가능한 카드로 표시하고, 대시보드에 통합하는 섹션 컴포넌트입니다. PDF 저장 및 재시도 버튼을 포함합니다.
AI 리포트 인쇄 및 PDF
src/components/dashboard/ai-report/print/AiSummaryPrintReport.tsx, src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts, src/components/dashboard/ai-report/print/printAssets.ts
분석 데이터를 인쇄 가능한 마크업으로 변환하고 PDF로 저장하는 기능 구현입니다. Pretendard 폰트와 테마 토큰을 포함합니다.
AI 리포트 유틸 및 CSS
src/components/dashboard/ai-report/utils/aiReport.utils.ts, src/styles/aiReport.print.css, src/styles/print.css
분석 데이터를 리포트 형식으로 변환하고 인쇄 스타일을 정의합니다. 커버, 실행 요약, 섹션, 푸터 레이아웃을 포함합니다.
개요 대시보드 AI 통합
src/pages/dashboard/overview/OverviewDashboard.tsx
기존 AI 드로어를 제거하고 새로운 DashboardAiSummarySection을 대시보드 내부에 렌더링합니다.

대시보드 메트릭 및 일별 성과 개선

Layer / File(s) Summary
일별 메트릭팩트 API
src/api/dashboard/platform.ts, src/types/dashboard/platform.ts
getMetricFacts API 함수와 IMetricFactsResponse, IMetricFactsParams, IPlatformDailyPerformance 타입으로 일별 광고 성과 데이터를 조회합니다.
메트릭팩트 조회 훅
src/hooks/dashboard/usePlatformMetricFacts.ts
usePlatformMetricFacts로 일별 메트릭을 조회하고 포맷팅합니다. 날짜를 YY.MM.DD(요일) 형식으로 표시하고 합계 행을 제공합니다.
플랫폼 상세 테이블 CPA 전환
src/components/dashboard/platform/PlatformDetailTable.tsx
CPC 지표를 CPA로 변경하고 API에서 전달한 합계값을 사용하도록 개선합니다.
단일 플랫폼 뷰 메트릭 통합
src/components/dashboard/platform/SinglePlatformView.tsx
기존 목데이터를 제거하고 usePlatformMetricFacts 훅으로 실제 일별 성과 데이터를 표시합니다. 하단에 DashboardAiSummarySection을 추가합니다.
지표 범위 및 유틸
src/constants/dashboard/overviewMetricsRange.ts, src/hooks/dashboard/useOverviewRoasRankings.ts
AI 분석용 고정 지표 범위와 기간 포맷팅 유틸을 정의합니다. ROAS 랭킹도 공용 상수로 통일합니다.

UI 개선 및 스타일 통일

Layer / File(s) Summary
그림자 스타일 통일
src/components/common/card/*.tsx, src/components/common/drawer/*.tsx, src/components/common/modal/*.tsx, src/components/landing/*.tsx, src/components/workspace/*.tsx, src/styles/utilities.css, src/styles/tokens.css
전체 UI에서 여러 그림자 클래스(shadow-card, shadow-Medium, shadow-landing-* 등)를 새로운 shadow-Soft로 통일합니다.
대시보드 스켈레톤 분리
src/components/dashboard/overview/skeleton/OverviewSkeleton.tsx, src/pages/dashboard/overview/sections/*.tsx
KPI, 트래픽, 예산, 랭킹, 캠페인 스냅샷별로 로딩 UI를 별도 컴포넌트로 분리합니다.
알림 및 프로필 설정 개선
src/components/setting/NotificationSection.tsx, src/components/setting/ProfileSection.tsx, src/pages/setting/Setting.tsx
새로운 NotificationSection 컴포넌트로 알림 채널을 관리하고, 설정 페이지에서 계정 변경과 알림 변경을 분리 처리합니다.
타이포그래피 및 토큰 추가
src/styles/tokens.css, src/styles/utilities.css, src/stories/Typography.stories.tsx
플랫폼 색상 토큰, 배경 이미지, 새로운 Body 3 타이포 스케일을 추가합니다.

Sequence Diagram

sequenceDiagram
  participant User
  participant UI as Dashboard UI
  participant AiAnalysis as useAiAnalysisReport
  participant API as AI Analysis API
  participant Store as Workspace Store

  User->>UI: AI 분석 요청
  UI->>AiAnalysis: requestAnalysis()
  AiAnalysis->>API: POST /analysis
  API-->>AiAnalysis: accessToken (202)
  
  AiAnalysis->>API: GET /reports/{token}
  loop Polling (PENDING)
    API-->>AiAnalysis: status=PENDING
    AiAnalysis->>AiAnalysis: Wait refetchInterval
  end
  
  API-->>AiAnalysis: status=SUCCESS, result
  AiAnalysis->>UI: reportData
  UI->>User: 분석 결과 표시
  
  User->>UI: PDF 저장
  UI->>UI: renderToStaticMarkup
  UI->>UI: downloadAiSummaryPdf
  UI->>User: PDF 다운로드
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

✨ Feature, 📬 API, 🔨 Refactor, 🎨 Html&css

Suggested reviewers

  • jjjsun
  • YermIm
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

@coderabbitai coderabbitai 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.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/dashboard/platform/AllPlatformTrafficChart.tsx (1)

24-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

PROVIDER_TYPES와 platformTrafficMock 키를 타입 레벨로 동기화하세요

  • 지금 mock(platformTrafficMock)은 GOOGLE/NAVER/META를 모두 들고 있어서 이 코드 경로에서 즉시 크래시 가능성은 낮아요.
  • 다만 platformTrafficMockRecord<string, IClickStreamResponse>라서 TS가 “키가 항상 존재”한다고 가정합니다. 향후 PROVIDER_TYPES에 값이 추가/변경되면 실제로 platformTrafficMock[platform]undefined가 되어 data.timeSeriesData에서 런타임 에러가 날 수 있어요.
  • platformTrafficMock 타입을 Record<TProviderType, IClickStreamResponse>로 바꿔 키 불일치를 컴파일 타임에 잡는 쪽을 권장합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/platform/AllPlatformTrafficChart.tsx` around lines
24 - 41, The code assumes platformTrafficMock has every key in PROVIDER_TYPES
but its type is currently Record<string, IClickStreamResponse>; change the
mock's declaration to Record<TProviderType, IClickStreamResponse> (or a mapped
type using the same TProviderType used by PROVIDER_TYPES) so TypeScript enforces
key parity, import or reference TProviderType/IClickStreamResponse in the mock
module and update the mock to include all providers (or make missing entries
explicit), and then update usages in AllPlatformTrafficChart (PROVIDER_TYPES,
platformTrafficMock) if needed to satisfy the narrowed types; optionally add a
runtime guard (if (!data) return []) only if you prefer extra safety.
🧹 Nitpick comments (5)
src/components/landing/LandingHero.tsx (1)

46-46: 💤 Low value

배경 오버레이에 텍스트 토큰 사용 검토

bg-text-400/64를 스크림 레이어에 사용하고 있는데, text-400는 일반적으로 텍스트 색상을 위한 토큰이야. 배경 오버레이 용도라면 surface-* 계열 토큰이나 별도의 scrim 전용 토큰을 고려해볼 수 있을 것 같아.

현재 코드가 의도적으로 text-400를 사용하는 거라면 괜찮지만, tokens.css에서 해당 토큰의 용도를 확인해보는 게 좋을 것 같아.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/landing/LandingHero.tsx` at line 46, The overlay is using a
text color token (bg-text-400/64) in LandingHero's scrim element (className
"pointer-events-none absolute inset-0 z-1 bg-text-400/64"); replace it with an
appropriate background/surface or scrim token (e.g., bg-surface-*/<alpha> or a
dedicated scrim token) defined in tokens.css, or if the use of text-400 is
intentional, add a brief inline comment near the className explaining why that
token is chosen and confirm the token's intended purpose in tokens.css; ensure
you update tokens.css if you introduce a new scrim token so the style system
stays consistent.
src/components/dashboard/ai-report/components/AiSummaryCard.tsx (1)

402-406: ⚡ Quick win

펼침 토글에 aria-controls 연결을 권장합니다.

AiSummaryExpandTogglearia-expanded는 잘 지정했지만, 제어 대상 패널(${idPrefix}-panel)을 가리키는 aria-controls가 없습니다. 패널 motion.divid를 부여하고 토글 버튼에서 동일 id를 aria-controls로 참조하면 보조기술 사용자가 펼침/접힘 관계를 명확히 인지할 수 있습니다.

♿ 제안 (id/ aria-controls 전달)
             <AiSummaryExpandToggle
               cardTitle={title}
               isExpanded={isExpanded}
               onToggle={handleToggle}
+              panelId={`${idPrefix}-panel`}
             />

AiSummaryExpandTogglepanelId를 받아 aria-controls={panelId}로, motion.div에는 id={${idPrefix}-panel}을 추가해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/ai-report/components/AiSummaryCard.tsx` around lines
402 - 406, Add an explicit relationship between the toggle and the collapsible
panel by passing a panelId into AiSummaryExpandToggle and referencing it with
aria-controls; update the parent panel (the motion.div that renders the summary
details) to include id={`${idPrefix}-panel`} and pass the same id as panelId to
AiSummaryExpandToggle so the toggle button (in AiSummaryExpandToggle) sets
aria-controls={panelId} alongside the existing aria-expanded handling (keep
isExpanded / handleToggle behavior unchanged).
src/pages/workspace/WorkspaceSetting.tsx (1)

296-320: 권한 분기 처리 자체는 깔끔합니다. 다만 서버 측 검증도 함께 확인해 주세요.

isAdmin으로 저장/삭제 버튼을 숨기고 입력을 비활성화한 처리는 UX 측면에서 좋습니다. 다만 이는 클라이언트 가드일 뿐이라, updateWorkspace/deleteWorkspace API 자체에서도 ADMIN 권한을 검증하지 않으면 비관리자가 직접 요청을 보내 우회할 수 있습니다. 백엔드에서 권한 검증이 이미 보장되는지 확인해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/workspace/WorkspaceSetting.tsx` around lines 296 - 320, Client-side
isAdmin gating is fine but insufficient—ensure the server-side handlers for
updateWorkspace and deleteWorkspace enforce ADMIN permission checks: update the
API/controller functions named updateWorkspace and deleteWorkspace (and any auth
middleware used by them) to verify the requesting user is an admin for the
target workspace and return 403/unauthorized when not; ensure
openDeleteModal/onSave callers remain unchanged, add unit/integration tests for
non-admin requests hitting updateWorkspace/deleteWorkspace to confirm the server
rejects them, and log/handle authorization failures consistently.
src/components/dashboard/ai-report/print/printAssets.ts (1)

4-9: 💤 Low value

문자열 치환 방식이 SVG 포맷에 의존적입니다.

replace("<svg ", ...)는 빌드 출력에서 <svg가 줄바꿈이나 다른 첫 속성으로 시작하면 매칭에 실패합니다. 지금은 고정 자산이라 동작하지만, 로고 자산이 교체되거나 SVGO 설정이 바뀌면 속성 주입이 조용히 누락될 수 있어요. 정규식(/<svg\b/i)으로 바꾸면 포맷 변화에 더 견고해집니다.

♻️ 제안
-  .replace(
-    "<svg ",
-    '<svg aria-hidden="true" focusable="false" preserveAspectRatio="xMidYMid meet" ',
-  );
+  .replace(
+    /<svg\b/i,
+    '<svg aria-hidden="true" focusable="false" preserveAspectRatio="xMidYMid meet"',
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/ai-report/print/printAssets.ts` around lines 4 - 9,
The current replacement for injecting attributes into the logo SVG
(AI_REPORT_LOGO_PRINT_SVG) relies on literal string match replace("<svg ", ...)
which fails if the SVG tag has newlines or different spacing; update the
transformation on serviceLogoSvg to use a case-insensitive word-boundary regex
for the opening tag (e.g. /<svg\b/i) so the attribute injection is robust to
formatting changes, and keep the existing fill replacement logic intact.
src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts (1)

21-32: ⚡ Quick win

파라미터명 document가 전역 document를 섀도잉합니다.

지금은 내부에서 window.document / printWindow.document로 일관되게 접근하고 있어 동작은 정상입니다. 다만 함수 스코프에서 전역 document를 데이터 객체가 가려버리기 때문에, 이후 누군가 무심코 document.createElement 같은 코드를 추가하면 DOM API가 아닌 보고서 데이터를 참조하게 되는 함정이 생깁니다. 인자명을 reportDocument(또는 report)로 바꿔 두면 안전합니다.

♻️ 제안
-export function downloadAiSummaryPdf(document: TAiReportPrintDocument) {
+export function downloadAiSummaryPdf(reportDocument: TAiReportPrintDocument) {
   const reportMarkup = renderToStaticMarkup(
-    createElement(AiSummaryPrintReport, { document }),
+    createElement(AiSummaryPrintReport, { document: reportDocument }),
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts` around
lines 21 - 32, The parameter named document in downloadAiSummaryPdf shadows the
global DOM document; rename it (e.g., reportDocument or report) and update all
references inside the function (for example the call
createElement(AiSummaryPrintReport, { document }) and any other uses) to use the
new parameter name, while leaving all accesses to window.document /
printWindow.document unchanged so DOM calls still reference the global document.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/common/button/Button.tsx`:
- Around line 47-48: The gradient class string in Button.tsx uses the undefined
shadow token "shadow-Soft"; either add a corresponding CSS token in your tokens
file (e.g., define --shadow-Soft and map shadow-Soft in src/styles/tokens.css)
or replace "shadow-Soft" in the gradient value with an existing shadow token
name from tokens.css so the Button component's gradient class references a valid
shadow variable.

In `@src/components/common/card/Card.tsx`:
- Line 24: Card.tsx (and similar uses in Drawer.tsx and DropdownMenu.tsx)
currently use the utility class shadow-Soft which is defined only in
src/styles/utilities.css, and the expected hover/transition variants
(shadow-card, hover:shadow-card-hover, transition-shadow) are missing across the
codebase; confirm intended behavior and either (A) replace shadow-Soft in
Card.tsx/Drawer.tsx/DropdownMenu.tsx with the standardized classes (e.g.,
shadow-card plus hover:shadow-card-hover and transition-shadow) so hover shadow
changes work, or (B) move/alias the .shadow-Soft definition into the theme
tokens file (src/styles/tokens.css `@theme`) and add matching hover/transition
classes (.shadow-card, .shadow-card-hover, .transition-shadow) in CSS so
existing class names keep intended hover effect—update the three components
(Card.tsx, Drawer.tsx, DropdownMenu.tsx) to use the chosen standardized class
names.

In `@src/components/dashboard/ai-report/components/AiSummaryCard.tsx`:
- Around line 99-107: The heading levels in AiSummaryCard are semantically
skipped: replace the h5 element rendering the card title (where id, className,
isPrimary, title are used) with an h4 to restore proper document outline, and
likewise bump any h5→h4 and h6→h5 occurrences in this component (the other
headings around the same block referenced at the same file) while preserving the
visual style (keep the "font-heading4" className and existing twMerge logic and
isPrimary color logic); ensure ARIA/state props remain unchanged.
- Around line 348-351: The SparkleIcon instance uses a hard-coded fill class;
change its classes to use fill-current and the text utility for color so it
follows the project convention (e.g., update the SparkleIcon className to
include "fill-current text-primary-400" instead of "fill-primary-400"), and scan
other SVG components (icons) like the one following the same pattern to ensure
they use fill-current/stroke-current with text-* classes for color consistency.
- Around line 281-307: The collapse path can leave autoExpandOnResultRef.current
true (so future data re-expands the card); update handleToggle (the function
that calls setIsExpanded) so that when toggling to collapsed (next === false)
you always set autoExpandOnResultRef.current = false (rather than only when
isLoading), ensuring autoExpandOnResultRef is cleared whenever the user
collapses the card; keep the existing onExpand call and function dependencies
(data, isLoading, onExpand).

In `@src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts`:
- Around line 54-71: The iframe cleanup currently only runs on printWindow's
"afterprint" event; make cleanup idempotent (safe to call multiple times) and
add a safety timeout fallback so the iframe is removed and the listener detached
even if "afterprint" never fires. Specifically, modify the cleanup function used
with printWindow.addEventListener("afterprint", cleanup) to check/guard whether
iframe is already removed and ensure it can be called repeatedly, add a
setTimeout that calls cleanup after a short timeout as a fallback, and add a
.catch handler to the printDoc.fonts.ready promise chain so runPrint still
proceeds on fonts.ready rejection (and still triggers the fallback cleanup
path).

In `@src/components/landing/LandingMultiDevice.tsx`:
- Around line 11-12: MOCKUP_OVERLAY_CLASS uses the legacy Tailwind gradient
utility `bg-gradient-to-t` which is not recognized under Tailwind v4; update the
class string in MOCKUP_OVERLAY_CLASS to use the v4 canonical utility
`bg-linear-to-t` so the overlay gradient renders correctly (modify the constant
MOCKUP_OVERLAY_CLASS in LandingMultiDevice.tsx to replace `bg-gradient-to-t`
with `bg-linear-to-t`).

In `@src/components/setting/NotificationSection.tsx`:
- Around line 68-79: The Slack integration block in NotificationSection.tsx is a
non-functional placeholder: the Button (variant="outline", size="small") has no
onClick and the channel label is hardcoded as "`#채널명`"; update the UI to avoid
user confusion by making the button disabled and adding a clear placeholder
status (e.g., "준비 중" or "미연동") or implement a minimal onClick stub that opens a
setup modal/handler; locate the SlackIcon + text block and the Button element in
NotificationSection and either set the Button to disabled and replace "`#채널명`"
with a dynamic placeholder/status string, or wire the Button to an onClick like
handleOpenSlackSetup to trigger the real setup flow.

In `@src/hooks/dashboard/useAiAnalysisReport.ts`:
- Around line 123-168: pollTimedOut currently uses useMemo with Date.now(),
which won't update while reportStatus stays "PENDING", so isPolling/isLoading
never reflect the timeout; change pollTimedOut to be time-driven (either compute
the boolean inline on every render using Date.now() - pollStartedAt >
MAX_POLL_MS or set up an effect that flips a pollTimedOut state after
MAX_POLL_MS when pollStartedAt/reportStatus become active) and ensure you
clear/reset that timer on reset; update references to pollTimedOut (used by
isPolling/isLoading) accordingly so the UI updates when the timeout elapses (use
symbols pollTimedOut, pollStartedAt, reportStatus, MAX_POLL_MS, reset,
isPolling, isLoading).

In `@src/pages/ads/new/CampaignGroup.tsx`:
- Around line 126-130: The MetaIcon in CampaignGroup.tsx currently has
className="h-6 w-6 shrink-0 text-text-title" but the inline SVG asset
(meta-circle.svg) uses hardcoded fills/gradients so the text-text-title utility
has no effect and is inconsistent with other icons; fix by removing the
text-text-title utility from the MetaIcon usage to match the other social icons
(or alternatively update the meta-circle.svg to use currentColor/var(--*) fills
and then keep the utility), ensuring consistency across icons and that color
control is actually applied via CSS or the SVG itself.

In `@src/pages/setting/Setting.tsx`:
- Around line 157-160: The current branch only updates local state
(setSavedNotification(draftNotification)) when hasNotificationChanges is true,
so the UI shows "saved" but changes are not persisted; implement a call to the
notification settings API (POST/PUT using your app's API client) when
hasNotificationChanges is true, send draftNotification payload, await the
response, only call setSavedNotification and show the success toast after a
successful server response, and on failure show an error toast and avoid or
revert the local update; use the existing symbols hasNotificationChanges,
draftNotification, setSavedNotification and add proper error handling and
loading state while the request is in flight (or if you opt not to implement
persistence now, change the toast copy to indicate "Locally updated" and
create/open an issue to track adding server persistence).

In `@src/pages/workspace/Workspace.tsx`:
- Around line 119-129: The useLayoutEffect currently calls setSearchParams({}, {
replace: true }) which wipes all query params; instead preserve existing params
and only remove the "create" key: inside the effect (the block using
searchParams, openedCreateFromQueryRef, onOpenCreate) clone the current
searchParams, delete the "create" entry, then call setSearchParams with that
updated params and { replace: true } so other query keys (filters, paging, etc.)
remain intact.

In `@src/styles/aiReport.print.css`:
- Line 10: Remove unnecessary quotes around the font family name in the
font-family declaration (change "Pretendard" to Pretendard) and normalize the
`@page` size keyword to lowercase (change A4 to a4) so the rules satisfy
stylelint's font-family-name-quotes and value-keyword-case checks; update the
font-family declaration and the `@page` size usage where they appear (the lines
containing the font-family: "Pretendard"; rule and the `@page` size: A4
declaration).
- Around line 50-51: The CSS uses deprecated properties (e.g., page-break-after
and other page-break-* usages) which trigger stylelint property-no-deprecated
errors; either remove those legacy properties if your target browsers support
the modern equivalents (e.g., keep break-after/break-inside only) or explicitly
exempt the legacy lines by adding a stylelint directive
(stylelint-disable-next-line property-no-deprecated) immediately above each
legacy declaration in src/styles/aiReport.print.css (identify occurrences by the
page-break-after / page-break-inside tokens) so the build no longer fails.

In `@src/styles/print.css`:
- Line 7: Replace the deprecated clip property usage with a non-deprecated
equivalent and normalize the page size keyword: locate the rule that uses clip:
rect(0, 0, 0, 0); and replace it with clip-path: inset(50%); (or clip-path:
inset(50% round 0) if you need to preserve rounding) to keep the same
screen-hide behavior and satisfy property-no-deprecated; also find the rule that
sets size: A4 and change the value to lowercase size: a4 to satisfy
value-keyword-case.

---

Outside diff comments:
In `@src/components/dashboard/platform/AllPlatformTrafficChart.tsx`:
- Around line 24-41: The code assumes platformTrafficMock has every key in
PROVIDER_TYPES but its type is currently Record<string, IClickStreamResponse>;
change the mock's declaration to Record<TProviderType, IClickStreamResponse> (or
a mapped type using the same TProviderType used by PROVIDER_TYPES) so TypeScript
enforces key parity, import or reference TProviderType/IClickStreamResponse in
the mock module and update the mock to include all providers (or make missing
entries explicit), and then update usages in AllPlatformTrafficChart
(PROVIDER_TYPES, platformTrafficMock) if needed to satisfy the narrowed types;
optionally add a runtime guard (if (!data) return []) only if you prefer extra
safety.

---

Nitpick comments:
In `@src/components/dashboard/ai-report/components/AiSummaryCard.tsx`:
- Around line 402-406: Add an explicit relationship between the toggle and the
collapsible panel by passing a panelId into AiSummaryExpandToggle and
referencing it with aria-controls; update the parent panel (the motion.div that
renders the summary details) to include id={`${idPrefix}-panel`} and pass the
same id as panelId to AiSummaryExpandToggle so the toggle button (in
AiSummaryExpandToggle) sets aria-controls={panelId} alongside the existing
aria-expanded handling (keep isExpanded / handleToggle behavior unchanged).

In `@src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts`:
- Around line 21-32: The parameter named document in downloadAiSummaryPdf
shadows the global DOM document; rename it (e.g., reportDocument or report) and
update all references inside the function (for example the call
createElement(AiSummaryPrintReport, { document }) and any other uses) to use the
new parameter name, while leaving all accesses to window.document /
printWindow.document unchanged so DOM calls still reference the global document.

In `@src/components/dashboard/ai-report/print/printAssets.ts`:
- Around line 4-9: The current replacement for injecting attributes into the
logo SVG (AI_REPORT_LOGO_PRINT_SVG) relies on literal string match replace("<svg
", ...) which fails if the SVG tag has newlines or different spacing; update the
transformation on serviceLogoSvg to use a case-insensitive word-boundary regex
for the opening tag (e.g. /<svg\b/i) so the attribute injection is robust to
formatting changes, and keep the existing fill replacement logic intact.

In `@src/components/landing/LandingHero.tsx`:
- Line 46: The overlay is using a text color token (bg-text-400/64) in
LandingHero's scrim element (className "pointer-events-none absolute inset-0 z-1
bg-text-400/64"); replace it with an appropriate background/surface or scrim
token (e.g., bg-surface-*/<alpha> or a dedicated scrim token) defined in
tokens.css, or if the use of text-400 is intentional, add a brief inline comment
near the className explaining why that token is chosen and confirm the token's
intended purpose in tokens.css; ensure you update tokens.css if you introduce a
new scrim token so the style system stays consistent.

In `@src/pages/workspace/WorkspaceSetting.tsx`:
- Around line 296-320: Client-side isAdmin gating is fine but
insufficient—ensure the server-side handlers for updateWorkspace and
deleteWorkspace enforce ADMIN permission checks: update the API/controller
functions named updateWorkspace and deleteWorkspace (and any auth middleware
used by them) to verify the requesting user is an admin for the target workspace
and return 403/unauthorized when not; ensure openDeleteModal/onSave callers
remain unchanged, add unit/integration tests for non-admin requests hitting
updateWorkspace/deleteWorkspace to confirm the server rejects them, and
log/handle authorization failures consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d76cdd86-6d7b-44b0-be3e-5fa73d933e51

📥 Commits

Reviewing files that changed from the base of the PR and between f04029a and a321aee.

⛔ Files ignored due to path filters (12)
  • .gitignore is excluded by none and included by none
  • package.json is excluded by none and included by none
  • playwright.config.ts is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml and included by none
  • src/assets/icon/common/lightbulb.svg is excluded by !**/*.svg and included by src/**
  • src/assets/logo/social-logo/plain/slack.svg is excluded by !**/*.svg and included by src/**
  • tests/auth-guard.spec.ts is excluded by none and included by none
  • tests/find-email.spec.ts is excluded by none and included by none
  • tests/login.spec.ts is excluded by none and included by none
  • tests/navigation.spec.ts is excluded by none and included by none
  • tests/smoke.spec.ts is excluded by none and included by none
  • tsconfig.node.json is excluded by none and included by none
📒 Files selected for processing (93)
  • src/api/dashboard/aiAnalysis.ts
  • src/api/dashboard/platform.ts
  • src/components/ads/AdListTable.tsx
  • src/components/ads/AdRow.tsx
  • src/components/ads/CampaignInfoCard.tsx
  • src/components/ads/CampaignRow.tsx
  • src/components/ads/PlatformCard.tsx
  • src/components/common/ComingSoonPlaceholder.tsx
  • src/components/common/button/Button.tsx
  • src/components/common/card/Card.tsx
  • src/components/common/card/StatCard.tsx
  • src/components/common/drawer/Drawer.tsx
  • src/components/common/dropdownmenu/DropdownMenu.tsx
  • src/components/common/modal/Modal.tsx
  • src/components/common/select/SearchSelect.tsx
  • src/components/common/toggle/Toggle.tsx
  • src/components/dashboard/ai-report/components/AiSummaryCard.tsx
  • src/components/dashboard/ai-report/components/DashboardAiSummarySection.tsx
  • src/components/dashboard/ai-report/print/AiSummaryPrintReport.tsx
  • src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts
  • src/components/dashboard/ai-report/print/printAssets.ts
  • src/components/dashboard/ai-report/utils/aiReport.utils.ts
  • src/components/dashboard/charts/PerformanceEfficiencyChart.tsx
  • src/components/dashboard/overview/skeleton/OverviewSkeleton.tsx
  • src/components/dashboard/platform/AllPlatformTrafficChart.tsx
  • src/components/dashboard/platform/AllPlatformView.tsx
  • src/components/dashboard/platform/PlatformDetailCard.tsx
  • src/components/dashboard/platform/PlatformDetailTable.tsx
  • src/components/dashboard/platform/PlatformRoasTable.tsx
  • src/components/dashboard/platform/PlatformTrafficChart.tsx
  • src/components/dashboard/platform/SinglePlatformView.tsx
  • src/components/dashboard/platform/TopPerformanceList.tsx
  • src/components/landing/GuideOverviewChart.tsx
  • src/components/landing/GuidePlatform.tsx
  • src/components/landing/GuideTimeline.tsx
  • src/components/landing/LandingFAQ.tsx
  • src/components/landing/LandingFeatures.tsx
  • src/components/landing/LandingGuide.tsx
  • src/components/landing/LandingHeader.tsx
  • src/components/landing/LandingHero.tsx
  • src/components/landing/LandingMultiDevice.tsx
  • src/components/landing/LandingPricing.tsx
  • src/components/setting/NotificationSection.tsx
  • src/components/setting/ProfileSection.tsx
  • src/components/setting/ProfileSectionSkeleton.tsx
  • src/components/sidebar/Sidebar.tsx
  • src/components/sidebar/WorkspaceSwitcher.tsx
  • src/components/workspace/MemberManagementLoading.tsx
  • src/components/workspace/WorkspaceCard.tsx
  • src/components/workspace/WorkspaceListLoading.tsx
  • src/constants/dashboard/overviewMetricsRange.ts
  • src/constants/sidebarNav.ts
  • src/hooks/ads/useCampaignGroup.ts
  • src/hooks/auth/useIsAdmin.ts
  • src/hooks/dashboard/useAiAnalysisReport.ts
  • src/hooks/dashboard/useOverviewRoasRankings.ts
  • src/hooks/dashboard/usePlatformBudget.ts
  • src/hooks/dashboard/usePlatformMetricFacts.ts
  • src/hooks/dashboard/usePlatformMetrics.ts
  • src/hooks/dashboard/usePlatformPerformance.ts
  • src/layout/main/MainLayout.tsx
  • src/pages/ads/list/CampaignDetail.tsx
  • src/pages/ads/new/CampaignGroup.tsx
  • src/pages/dashboard/overview/OverviewAiDrawer.tsx
  • src/pages/dashboard/overview/OverviewAiReportPanel.tsx
  • src/pages/dashboard/overview/OverviewDashboard.tsx
  • src/pages/dashboard/overview/aiReport.mock.ts
  • src/pages/dashboard/overview/sections/OverviewBudgetSection.tsx
  • src/pages/dashboard/overview/sections/OverviewCampaignSnapshotCard.tsx
  • src/pages/dashboard/overview/sections/OverviewKpiSection.tsx
  • src/pages/dashboard/overview/sections/OverviewPlatformSection.tsx
  • src/pages/dashboard/platform/PlatformDashboard.tsx
  • src/pages/dashboard/platform/platformDashboard.mock.ts
  • src/pages/landing/LandingPage.tsx
  • src/pages/setting/Setting.tsx
  • src/pages/workspace/Workspace.tsx
  • src/pages/workspace/WorkspaceSetting.tsx
  • src/routes/MainRoutes.tsx
  • src/routes/RoleGuard.tsx
  • src/store/useWorkspaceStore.ts
  • src/stories/Shadows.stories.tsx
  • src/stories/Typography.stories.tsx
  • src/styles/aiReport.print.css
  • src/styles/print.css
  • src/styles/tokens.css
  • src/styles/utilities.css
  • src/types/ads/campaign.ts
  • src/types/dashboard/aiAnalysis.ts
  • src/types/dashboard/overview.ts
  • src/types/dashboard/platform.ts
  • src/types/dashboard/provider.ts
  • src/types/navigation/navItem.ts
  • src/vite-env.d.ts
💤 Files with no reviewable changes (6)
  • src/components/ads/CampaignInfoCard.tsx
  • src/pages/dashboard/overview/OverviewAiReportPanel.tsx
  • src/components/ads/PlatformCard.tsx
  • src/pages/dashboard/overview/aiReport.mock.ts
  • src/pages/dashboard/overview/OverviewAiDrawer.tsx
  • src/pages/dashboard/platform/platformDashboard.mock.ts

Comment thread src/components/common/button/Button.tsx
Comment thread src/components/common/card/Card.tsx
Comment thread src/components/dashboard/ai-report/components/AiSummaryCard.tsx
Comment thread src/components/dashboard/ai-report/components/AiSummaryCard.tsx
Comment thread src/components/dashboard/ai-report/components/AiSummaryCard.tsx
Comment thread src/pages/setting/Setting.tsx
Comment thread src/pages/workspace/Workspace.tsx
Comment thread src/styles/aiReport.print.css
Comment thread src/styles/aiReport.print.css
Comment thread src/styles/print.css
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🌏 Deploy 배포 관련

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants