Skip to content

[Refactor/#200] 디자인 시스템 컬러 및 폰트 위계 점검 및 개선 - #207

Merged
Seojegyeong merged 36 commits into
developfrom
refactor/#200
May 14, 2026
Merged

[Refactor/#200] 디자인 시스템 컬러 및 폰트 위계 점검 및 개선#207
Seojegyeong merged 36 commits into
developfrom
refactor/#200

Conversation

@Seojegyeong

@Seojegyeong Seojegyeong commented May 14, 2026

Copy link
Copy Markdown
Collaborator

🚨 관련 이슈

#200

✨ 변경사항

  • 🐞 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.)

✏️ 작업 내용

1. 내비·헤더·카피

  • 통합 캠페인 등록
    • 캠페인 그룹 정보 설정 → 통합 캠페인 등록
      • 사용자 이해를 위해 액션 의미 자체로 페이지명 변경
      • 상단 안내 ”여러 매체 캠페인을 하나로 묶어 관리합니다.”를 [캠페인 기본 정보] 카드 제목 바로 아래로 이동
      • 주 버튼: 그룹 생성 / 로딩 그룹 생성 중...
      • 성공 모달: 그룹 생성 완료, 본문 통합 캠페인 그룹이 성공적으로 생성되었습니다.
      • 실패 토스트: 그룹 생성에 실패했습니다.
  • 메인 레이아웃 헤더
    • 브레드크럼(헤더 내 페이지 경로 표시되어있는 부분)을 Link로 클릭 시 해당 경로 이동 가능하게 개선
    • 워크스페이스 사이드바 하위 메뉴 세분화 - 페이지 내 메뉴는 중복이므로 제거

2. 캠페인·광고 목록 UI - 전체 디자인 수정

  • 캠페인 목록 / 캠페인 상세 - 광고 모아보기
  • 광고 모아보기 행 펼침 영역

3. 워크스페이스

  • 기본 정보 · 로고
    • 로고 미리보기 영역 전체 클릭 시 파일 선택 (버튼만이 아니라 박스도!!!!)
  • 워크스페이스 삭제 모달
    • GitHub식 이름 일치 입력 후에만 삭제 버튼 활성 (ModalContent + WorkspaceSetting)
    • 비활성 삭제하기도 info-red 계열 연한 배경/테두리로 어느 정도 보이게 처리

4. 설정 페이지

  • 카드
    • 다른 페이지 카드와 통일
  • 비활성 필드 가독성
    • 비활성 필드 글자 색상 변경하여 가독성 높임
  • 프로필 이미지
    • 원형 영역 그림자 제거
    • 미리보기 영역 전체 클릭으로 업로드
    • 카메라 svg - 바깥 윤곽 currentColor로 통일!!! - 이거 적용해달라고 말씀 드렸을텐데…안되어 있더라구요………..

5. 공통 컴포넌트·토큰 원칙

  • 색이나 배경은 tokens.css / @theme에 있는 유틸만 사용 (임의 hex·새 색 추가 하지 마세요)
  • 대표 예: surface-*, text-*, primary-*, info-red

6. 페이지에서 컬러 토큰 쓰는 방법

  • 페이지/컴포넌트에서는 primary-* Tailwind 유틸만 사용하면 됨
  • 예: bg-primary-400, text-primary-500, border-primary-200, ring-primary-400/30
  • CSS 변수 **var(--primary-*)**를 직접 쓰는 건 지양 (차트/인라인 스타일 등 “유틸로 못 쓰는 경우”만 예외)

7. 텍스트 컬러는 역할 토큰 사용

  • 제목/본문/보조문구는 아래처럼 통일
  • text-text-title, text-text-body, text-text-muted, text-text-auth-sub
/* STATUS —* 예산 **소진*:* 안정*=blue,* 주의*=yellow,* 위험*=red */

--color-info-red: #ff2a4b;

--color-info-blue: #0084fe;

--color-info-yellow: #facc15;

안정, 주의, 위험 의미 담은 그래프에서는 색 고정으로 사용 바람

8. Badge

  • 의미로 변수명 지어서 중복 -> infoRed 이런 식으로 색감 구분
  • 사이즈 통일
  • 더이상 뱃지 색갈 추가 지양

😅 미완성 작업

N/A

📢 논의 사항 및 참고 사항

N/A

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

Summary by CodeRabbit

업데이트 사항

  • 새로운 기능

    • 캠페인·광고 소재 개별/다중 선택 및 일괄 중단·재개 지원
    • 광고 상세에서 추적 링크 복사/모달 CTA 통합
    • 플랫폼별 캠페인 드롭다운 컴포넌트 추가
  • 디자인 개선

    • 새로운 디자인 토큰(Primary/Surface/Text)과 유틸리티 적용
    • 모달·드로어·카드·폼 등 전반적 스타일·애니메이션 개선
    • 랜딩·대시보드·사이드바 등 레이아웃 및 컴포넌트 통일
  • 제거됨

    • 일부 레거시 UI(예: 특정 확인 모달/컴포넌트) 정리
  • 기타

    • 인쇄용 스타일 및 Storybook 문서 강화

Seojegyeong added 30 commits May 9, 2026 21:01
- Split index.css into tokens, base, utilities, and print modules.
- Move @font-face to the top of base.css.
- Co-locate GuideTimeline scrollbar rules with the component.
- tokens: 텍스트/서페이스/프라이머리 팔레트 정리, 타이포 스케일 조정
- 전역 컴포넌트 반경·높이·모달 크기를 Tailwind 기본 스케일로 통일
- utilities: 미사용 CSS 애니메이션 제거, 쉬머만 유지(reduced-motion 대응)
- Modal·DropdownMenu·ComingSoon: AnimatePresence/motion으로 전환
- Storybook Colors·Typography·BorderRadius를 토큰과 맞춤
- Split index.css into tokens, base, utilities, and print modules.
- Move @font-face to the top of base.css.
- Co-locate GuideTimeline scrollbar rules with the component.
- tokens: 텍스트/서페이스/프라이머리 팔레트 정리, 타이포 스케일 조정
- 전역 컴포넌트 반경·높이·모달 크기를 Tailwind 기본 스케일로 통일
- utilities: 미사용 CSS 애니메이션 제거, 쉬머만 유지(reduced-motion 대응)
- Modal·DropdownMenu·ComingSoon: AnimatePresence/motion으로 전환
- Storybook Colors·Typography·BorderRadius를 토큰과 맞춤
- Login: 카카오 버튼에 배경색(#FEE500)·flex 중앙 정렬 적용
- kakao.svg: fill 색상 #040000 → #181600 통일
- Signup: 네이버 아이콘 크기 w-5/h-5 → w-6/h-6
- AdRow, PlatformRoasTable: 임의값 클래스를 Tailwind 유틸 클래스로 교체
@Seojegyeong
Seojegyeong requested review from YermIm and jjjsun May 14, 2026 07:02
@Seojegyeong Seojegyeong self-assigned this May 14, 2026
@Seojegyeong Seojegyeong added 🎨 Html&css 마크업 & 스타일링 🔨 Refactor 코드 리팩토링 labels May 14, 2026
@vercel

vercel Bot commented May 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
where-you-ad Ready Ready Preview, Comment May 14, 2026 8:12am

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml and included by none

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5e8207da-c022-4713-89ff-bfc0c4cb3711

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

디자인 토큰·유틸리티 도입과 전역 스타일 리팩터, 광고/캠페인 테이블의 선택·대량 작업(광고 소재/캠페인 일괄 중단·재개) 추가, 워크스페이스 경로 동적화 및 네비게이션/레이아웃 변경, 모달/드롭다운에 framer-motion 애니메이션이 도입되었습니다.

Changes

디자인 시스템 및 전역 스타일 통합

Layer / File(s) Summary
토큰·유틸리티·기본 스타일 추가
src/styles/tokens.css, src/styles/utilities.css, src/styles/base.css, src/styles/print.css, src/index.css
CSS custom properties(@theme)와 유틸리티(.font-, .shadow-, .animate-shimmer, 체크박스 등)를 추가하고 index.css에서 해당 파일들을 임포트하도록 변경. 전역 폰트(face)와 print 규칙도 이동/정의.
토큰 문서화용 Storybook 변경
src/stories/Colors.stories.tsx, src/stories/Typography.stories.tsx, src/stories/BorderRadius.stories.tsx
Storybook을 CSS 변수 기반 런타임 검사/측정 방식으로 재작성(색상/타이포그래피/라운딩 토큰 문서화).
컴포넌트 토큰 마이그레이션 샘플
여러 src/components/**src/pages/** (Card, Badge, Button, Input, Modal, Drawer 등 많은 파일)
기존 brand/status/gray 토큰을 primary/surface/text/info 계열로 교체. Badge 변형 단순화, Button/Modal/Input 등 스타일 맵 업데이트. twMerge 사용 정규화.

광고·캠페인 선택 및 대량 작업(기능 변화)

Layer / File(s) Summary
광고 상세에서 추적 링크 중심화
src/components/ads/AdDetailContent.tsx
updateAdStatus 제거, createTrackingUrl만 사용하도록 리팩터. 기존 광고 소재 중단/재개 모달 제거; UI를 추적 링크 복사/모달로 단순화.
행 선택 가능한 광고 테이블
src/components/ads/AdListTable.tsx, src/components/ads/AdRow.tsx
임베드 모드와 체크박스 선택 지원 추가. 플랫폼 정규화 로직 추가, 헤더의 select-all 체크박스(indeterminate 설정) 도입, 행 수준 선택 토글과 selectable prop 도입.
캠페인 테이블/행 선택 확장
src/components/ads/CampaignTable.tsx, src/components/ads/CampaignRow.tsx, src/components/ads/CampaignPlatformDropdown.tsx
캠페인 선택 집합(selectedProjectIds)과 onToggleProject/onToggleSelectAllVisible 핸들러 추가, embedded 레이아웃 지원, CampaignPlatformDropdown 컴포넌트 추가.
페이지 레벨 대량 작업(캠페인·광고)
src/pages/ads/list/AdsListPage.tsx, src/pages/ads/list/CampaignDetail.tsx
선택 기반 대량 중단/재개 로직 추가(범위: selection vs all). 모달을 ModalContent 기반으로 변경하고, 확인 후 API 호출(개별 또는 전체) 후 refetch 및 선택 초기화. header title 동기화(Outlet context).

네비게이션·워크스페이스 경로·레이아웃 변경

Layer / File(s) Summary
워크스페이스 경로 적용 유틸리티
src/utils/navigation/workspaceNavPaths.ts, src/utils/navigation/mainNavSidebar.ts
applyWorkspacePathsToNavnavItemMatchesPath 추가. workspaceSubpath/pathExact를 고려해 네비게이션 항목의 path를 동적으로 재작성 및 매칭 로직 개선.
사이드바·네비게이션 데이터 변경
src/components/sidebar/Sidebar.tsx, src/constants/sidebarNav.ts
사이드바에서 선택 워크스페이스 기반으로 nav 항목 생성(mainNavWithWorkspace). footer의 logout 항목 제거 및 logout modal 호출 제거.
레이아웃/라우트 변화
src/layout/main/MainLayout.tsx, src/layout/workspace/WorkspaceManageLayout.tsx, src/routes/MainRoutes.tsx, src/pages/workspace/WorkspaceBillingRedirect.tsx
TMainLayoutOutletContextcampaignDetailHeaderTitle 추가(광고 상세 breadcrumb 처리). WorkspaceManageLayout에서 route param으로 selectedOrgId 설정. Workspace billing redirect 컴포넌트 추가 및 라우트 구조 조정.

모달·드롭다운에 framer-motion 도입 및 ModalContent 확장

Layer / File(s) Summary
애니메이션 기반 모달/드롭다운
src/components/common/modal/Modal.tsx, src/components/common/dropdownmenu/DropdownMenu.tsx, src/components/common/comingSoonPlaceholder/ComingSoonPlaceholder.tsx
Modal과 DropdownMenu의 포털/패널을 AnimatePresence/motion으로 전환, useReducedMotion을 경로에 통합해 접근성 고려. Modal은 focus/scroll lock 관리 개선.
ModalContent 기능 확장
src/components/common/modal/ModalContent.tsx
상세 항목 리스트(detailItems)와 confirm-input 매칭(confirmMatchText, confirmInput, onConfirmInputChange)을 지원, 불일치 시 confirm 버튼 비활성화/변형 스타일 적용.

워크스페이스 관리 UI 현대화(스타일·구조)

Layer / File(s) Summary
Card 래핑 및 스타일 업데이트
src/components/workspace/* (MemberList, PermissionTable, MemberManagementLoading, MemberItem, InviteMemberModal 등)
멤버 관리 및 권한 UI를 Card로 래핑하고 토큰 기반 스타일로 교체. 로딩/스켈레톤 컴포넌트도 업데이트.
워크스페이스 삭제 확인 강화
src/pages/workspace/WorkspaceSetting.tsx
삭제 시 확인 입력(이름 매칭) 추가. ControlBox 기반 UI 제거, ModalContent로 교체. 로고 업로드 영역을 클릭 가능한 버튼으로 재구성.

타입 변경 및 훅 제거/문구 수정(작은 API 변화)

Layer / File(s) Summary
타입 변경
src/types/ads/campaign.ts, src/types/navigation/navItem.ts
IAd.provider를 `provider?: TProvider[]
훅·문구
src/hooks/auth/useLogout.ts, src/hooks/ads/useAdList.ts, src/hooks/ads/useCampaignGroup.ts
useLogout 훅 제거. 일부 에러 토스트 메시지 문구 소폭 변경.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Client UI
    participant Store as Workspace Store
    participant API as Backend API
    participant Nav as Navigation System

    User->>UI: 캠페인/광고 체크박스 선택
    UI->>Store: selectedIds 업데이트
    User->>UI: 대량 중단/재개 클릭
    UI->>API: updateCampaignStatus/updateAdStatus (selection 또는 all)
    API-->>UI: 성공 응답
    UI->>API: refetch 캠페인/광고
    API-->>UI: 갱신된 데이터
    UI->>Store: selectedIds 초기화
    UI->>Nav: 워크스페이스 전환
    Nav->>UI: applyWorkspacePathsToNav()로 nav 항목 갱신
Loading
sequenceDiagram
    participant Dev as Developer
    participant Tokens as Design Tokens (CSS)
    participant Component as Components

    Dev->>Tokens: tokens.css / utilities.css 작성
    Tokens->>Component: .font-*, .shadow-*, var(--color-*) 제공
    Component->>Component: tailwind class 교체, twMerge 적용
    Component-->>Dev: 컴포넌트 렌더링 (새 토큰 반영)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 분

Possibly related issues

Possibly related PRs

Suggested reviewers

  • jjjsun
  • YermIm

주의/검토 포인트(요약, 팀원에게 전달하듯 간결히)

  1. 스타일 토큰 일관성: tokens.css에서 색상/타이포/모션 값이 모든 컴포넌트 매핑과 일치하는지 샘플링(특히 대비비, info-red/yellow 접근성).
  2. 선택·대량 작업 정확성: select-all indeterminate 계산(useEffect 의존성), selection vs all 경계, 실패시 상태 보전/재시도 동작 확인.
  3. 네비게이션 매칭: workspaceSubpath/pathExact 케이스(특히 /workspace 및 /workspace/:id/billing 리다이렉트)와 정상적인 활성 상태 반영.
  4. Modal/Focus/Scroll: Modal의 focus restore와 scroll lock 동작(특히 reduced-motion 사용자), ModalContent의 confirmMatch 비교 로직(trim/정규화) 검증.
  5. 타입 호환성: IAd.provider 변경으로 인한 소비자 코드의 타입 분기(배열 vs 단일값) 누락 여부 확인.
  6. 프론트엔드 번들·성능: framer-motion 추가로 인한 번들 영향과 대량 체크 상호작용에서 불필요한 리렌더 방지(useMemo/useCallback 의존성).

간단한 테스트 시나리오: 캠페인 목록에서 일부 선택 → 중단 모달 열기(세부 리스트 확인) → 확인 → 네트워크 실패/성공 케이스 → 선택 초기화/유지 여부 확인; 워크스페이스 전환 후 사이드바 항목 경로가 즉시 변경되는지 확인.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#200

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

Caution

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

⚠️ Outside diff range comments (3)
src/components/dashboard/charts/AdStatusChart.tsx (1)

5-9: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

차트 색상 하드코딩을 토큰으로 치환해 주세요.

PLATFORM_COLORS의 hex 값("#f9ab00" 등)은 디자인 토큰 정책과 충돌합니다. tokens.css@theme 색상 토큰으로 매핑해서 사용해 주세요.

As per coding guidelines src/**/*.{css,tsx,ts}: Use only @theme tokens from tokens.css for colors. Do not rename tokens or add arbitrary colors.src/components/**/*.{tsx,ts}: Avoid adding arbitrary hex colors.

Also applies to: 39-42

🤖 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/charts/AdStatusChart.tsx` around lines 5 - 9,
PLATFORM_COLORS currently hardcodes hex values; replace those hex strings with
the corresponding `@theme` color tokens from tokens.css (e.g. use the CSS variable
form from `@theme` like "var(--theme-...)" or the project's token accessor) so the
chart uses design tokens instead of raw hex; update the PLATFORM_COLORS constant
in AdStatusChart.tsx (and the other similar entries noted around lines 39-42) to
map GOOGLE, NAVER, META to their respective `@theme` token names found in
tokens.css and ensure any usage of PLATFORM_COLORS in the component continues to
accept the token string values.
src/components/setting/PasswordSection.tsx (1)

87-90: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

aria-label이 잘못된 상태를 참조하고 있어요

새 비밀번호 입력의 토글 버튼인데 showCurrent 상태를 참조하고 있습니다. showNew로 수정이 필요합니다.

🔧 수정 제안
             <button
               type="button"
               aria-label={
-                showCurrent ? "현재 비밀번호 숨기기" : "현재 비밀번호 보기"
+                showNew ? "새 비밀번호 숨기기" : "새 비밀번호 보기"
               }
-              aria-pressed={showCurrent}
+              aria-pressed={showNew}
               onClick={() => setShowNew((p) => !p)}
             >
🤖 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/setting/PasswordSection.tsx` around lines 87 - 90, The aria
attributes for the new-password toggle are incorrectly using showCurrent; update
the toggle in PasswordSection (the new-password visibility button that currently
references showCurrent) to reference showNew instead so aria-label values ("새
비밀번호 숨기기"/"새 비밀번호 보기") and aria-pressed reflect the new-password visibility
state; ensure the button that toggles new password visibility also uses the
showNew state in its onClick handler if not already wired.
src/components/ads/AdDetailContent.tsx (1)

174-183: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

orgId 검증 없이 호출되어 API에 NaN이 전달될 수 있습니다

Line 182에서 Number(orgId)를 바로 사용하고 있어, 라우트 파라미터가 비어있는 경우 추적 링크 발급 요청이 잘못 전송됩니다. 호출 전에 유효성 검증을 넣어 주세요.

수정 예시
           onConfirm={() =>
             trackControl.handleConfirm(async () => {
+              const parsedOrgId = Number(orgId);
+              if (!orgId || Number.isNaN(parsedOrgId)) {
+                toast.error("조직 정보가 올바르지 않아 발급할 수 없습니다.");
+                throw new Error("유효하지 않은 orgId");
+              }
+
               if (!ad.landingUrl) {
                 toast.error(
                   "광고에 등록된 랜딩 URL이 없어 발급이 불가능합니다.",
                 );
                 throw new Error("랜딩 URL이 없습니다.");
               }
-              await createTrackingUrl(Number(orgId), ad.id, ad.landingUrl);
+              await createTrackingUrl(parsedOrgId, ad.id, ad.landingUrl);
             })
           }
🤖 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/ads/AdDetailContent.tsx` around lines 174 - 183, The call uses
Number(orgId) directly which can produce NaN if the route param is missing;
before invoking createTrackingUrl inside trackControl.handleConfirm (and before
using ad.landingUrl), validate orgId by parsing it (e.g., parseInt or Number)
and ensure it's a finite integer (not NaN) — if invalid, show a user-facing
error (toast) and abort (throw or return) so createTrackingUrl(orgId, ad.id,
ad.landingUrl) is never called with NaN; update the block around
trackControl.handleConfirm and createTrackingUrl to perform this check and
early-exit on invalid orgId.
🧹 Nitpick comments (15)
src/hooks/dashboard/useOverviewCampaignList.ts (1)

14-14: 💤 Low value

Non-null assertion이 약간 중복되긴 한데, 안전함

orgId!를 쓰고 있는데 enabled: !!orgId 옵션 덕분에 orgId가 없을 때는 쿼리가 실행 안 돼서 안전하다. 다만 조금 더 깔끔하게 하려면 fetcher를 다음처럼 바꿀 수도 있다:

() => (orgId ? getCampaignList(orgId) : Promise.resolve([]))

근데 현재 코드도 문제는 없으니 굳이 바꿀 필요는 없다.

🤖 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/hooks/dashboard/useOverviewCampaignList.ts` at line 14, 현재 fetcher에
orgId! 비무효(assertion) 사용이 중복되어 있어 깔끔하게 바꿀 수 있습니다; getCampaignList 호출부를 수정하여
orgId가 있을 때만 getCampaignList(orgId)를 호출하고 그렇지 않으면 빈 배열을 반환하는 Promise를 반환하도록
변경하세요(참조: fetcher 람다, getCampaignList, orgId, enabled: !!orgId).
src/components/landing/LandingFooter.tsx (2)

16-33: 💤 Low value

링크 스타일 클래스 중복 제거 고려

세 개의 링크가 모두 동일한 hover/focus-visible 스타일을 사용하고 있어서 클래스 문자열이 반복되고 있어. 유지보수성을 위해 공통 스타일을 상수로 추출하는 것도 고려해볼 만해.

♻️ 제안하는 리팩토링
+const linkClassName = "hover:text-primary-500 transition-colors rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-400/35 focus-visible:ring-offset-2 focus-visible:ring-offset-surface-200";
+
 export default function LandingFooter() {
   return (
     <footer className="bg-surface-200 text-text-title py-16 px-6 md:px-12">
       <div className="max-w-7xl mx-auto">
         <div className="flex flex-col md:flex-row items-center md:items-start justify-between gap-10">
           <div className="flex flex-col items-center md:items-start gap-6">
             <div className="text-center font-body2 text-text-auth-sub md:text-left">
               <p>Where You Ad</p>
             </div>
           </div>
           <div className="flex items-center gap-8 font-body2 text-surface-500">
             <a
               href="https://www.notion.so/Where-you-ad-351085b3a16c8040b7bef7ac311da984?source=copy_link"
               target="_blank"
               rel="noreferrer"
-              className="hover:text-primary-500 transition-colors rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-400/35 focus-visible:ring-offset-2 focus-visible:ring-offset-surface-200"
+              className={linkClassName}
             >
               이용약관
             </a>
             <a
               href="https://www.notion.so/Where-you-ad-351085b3a16c80c3b171f97238e447ca?source=copy_link"
               target="_blank"
               rel="noreferrer"
-              className="hover:text-primary-500 transition-colors rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-400/35 focus-visible:ring-offset-2 focus-visible:ring-offset-surface-200"
+              className={linkClassName}
             >
               개인정보처리방침
             </a>
             <a
               href="mailto:whereyouadofficial@gmail.com"
-              className="hover:text-primary-500 transition-colors rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-400/35 focus-visible:ring-offset-2 focus-visible:ring-offset-surface-200"
+              className={linkClassName}
             >
               고객센터
             </a>
           </div>
         </div>
🤖 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/LandingFooter.tsx` around lines 16 - 33, LandingFooter
contains three anchor elements that repeat the same hover/focus class string;
extract that repeated class string into a single constant (e.g., linkClass or
LINK_STYLE) at the top of the LandingFooter component and replace the className
values of the anchors (the links for "이용약관", "개인정보처리방침", and "고객센터") with that
constant to remove duplication and improve maintainability.

11-11: ⚡ Quick win

텍스트 색상 토큰 일관성 검토 필요

PR의 디자인 시스템 원칙에서 "텍스트 색은 역할 토큰(text-text-*) 통일"을 명시하고 있는데, 현재 링크의 기본 색상이 text-surface-500을 사용하고 있어. text-text-sub 또는 text-text-muted 같은 텍스트 역할 토큰으로 변경하는 게 디자인 시스템 원칙과 더 일관성 있을 것 같은데, 의도적으로 surface-500을 사용한 거라면 괜찮아.

♻️ 제안하는 수정안
-          <div className="flex items-center gap-8 font-body2 text-surface-500">
+          <div className="flex items-center gap-8 font-body2 text-text-sub">

As per coding guidelines: "텍스트 색은 역할 토큰(text-text-*) 통일"

🤖 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/LandingFooter.tsx` at line 11, The footer row
currently uses a surface color token in the className ("flex items-center gap-8
font-body2 text-surface-500") inside LandingFooter.tsx; change that to a text
role token (e.g., text-text-sub or text-text-muted) so it follows the design
system guideline "텍스트 색은 역할 토큰(text-text-*) 통일" — update the className on the
element that contains text-surface-500 to use the chosen text-text-* token and
run a quick visual check to confirm contrast/appearance.
src/components/common/modal/Modal.tsx (1)

53-58: 💤 Low value

requestAnimationFrame 사용의 필요성을 재검토해보면 좋을 것 같아요.

useLayoutEffect는 paint 전에 동기적으로 실행되므로, modalRef.current는 이미 DOM에 존재합니다. requestAnimationFrame을 사용하면 다음 프레임까지 포커스가 지연되는데, AnimatePresencemotion.div가 이미 마운트를 올바르게 처리하고 있어서 직접 modalRef.current?.focus()를 호출해도 안전할 것 같아요.

다만 현재 코드도 동작에 문제는 없으니, 선호도에 따라 판단하시면 됩니다.

♻️ rAF 제거 제안
  useLayoutEffect(() => {
    if (!isOpen) return;
    previousActiveElement.current = document.activeElement as HTMLElement;
-   const id = requestAnimationFrame(() => modalRef.current?.focus());
-   return () => cancelAnimationFrame(id);
+   modalRef.current?.focus();
  }, [isOpen]);
🤖 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/common/modal/Modal.tsx` around lines 53 - 58, The
useLayoutEffect currently wraps modalRef.current?.focus() in
requestAnimationFrame/cancelAnimationFrame; remove the rAF logic and call
modalRef.current?.focus() synchronously inside useLayoutEffect when isOpen is
true, keeping previousActiveElement.current assignment intact; update references
to requestAnimationFrame and cancelAnimationFrame (remove them) and ensure
modalRef, previousActiveElement, and the useLayoutEffect callback are the only
items touched.
src/components/common/dropdownmenu/DropdownMenu.tsx (1)

58-74: ⚡ Quick win

트리거의 키보드 접근성을 개선하면 좋을 것 같아요.

role="button"이 있는 div에 tabIndex={0}을 추가하면 키보드만 사용하는 사용자도 트리거에 포커스할 수 있습니다. 현재는 트리거가 실제 버튼 엘리먼트가 아닌 경우 키보드로 접근이 어려울 수 있어요.

♿ tabIndex 추가 제안
      <div
        role="button"
+       tabIndex={0}
        aria-haspopup="menu"
        aria-expanded={open}
        aria-controls={menuId}
        aria-label={ariaLabel}
        onClick={() => setOpen((v) => !v)}
🤖 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/common/dropdownmenu/DropdownMenu.tsx` around lines 58 - 74,
The trigger wrapper div in DropdownMenu lacks keyboard focusability; update the
element (the div inside the DropdownMenu component that currently has
role="button" and onKeyDown/onClick handlers) to include tabIndex={0} so
keyboard-only users can focus it, keeping the existing onKeyDown handling for
"Enter" and " " to toggle setOpen and leaving aria attributes (aria-haspopup,
aria-expanded, aria-controls, aria-label) intact; ensure any relevant tests or
accessibility docs reference the change.
src/components/ads/CampaignRow.tsx (1)

59-72: ⚡ Quick win

체크박스 컨테이너의 role="presentation"을 제거하는 게 좋을 것 같아요.

체크박스(<input type="checkbox">)는 이미 의미 있는 요소이므로, 감싸는 div에 role="presentation"을 추가하면 스크린 리더에 혼란을 줄 수 있습니다. 단순 레이아웃 컨테이너라면 role을 생략하는 게 더 명확해요.

♿ role 속성 제거 제안
      <div
        className="flex w-11 shrink-0 items-center justify-center tablet:w-10"
-       role="presentation"
        onClick={(e) => e.stopPropagation()}
        onKeyDown={(e) => e.stopPropagation()}
      >
🤖 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/ads/CampaignRow.tsx` around lines 59 - 72, Remove the
unnecessary role="presentation" from the div that wraps the checkbox in
CampaignRow (the container currently around the <input type="checkbox"> using
isSelected, onToggleSelect and name props); since the input is an interactive,
semantic element, simply omit the role on that wrapper (leave the div for layout
and keep the onClick/onKeyDown stopPropagation handlers and the input's
checked/onChange/aria-label intact) so screen readers receive the checkbox
semantics from the input itself.
src/components/setting/ProfileSection.tsx (1)

116-122: ⚡ Quick win

disabledreadOnly 속성이 중복 적용되어 있습니다.

Input 컴포넌트에 disabled={true}readOnly를 동시에 사용하고 있는데, readOnly만으로도 수정 불가 상태를 표현할 수 있습니다. disabled는 포커스 자체를 차단하고 폼 제출 시 값이 포함되지 않으므로, 읽기 전용 필드에는 readOnly만 사용하는 것이 적절합니다.

♻️ 제안하는 수정안
 <Input
   value={`${org.name}  (${org.position})`}
-  disabled
   containerClassName="bg-surface-200"
   readOnly
 />
 <Input
   label="이메일"
   value={email}
-  disabled={true}
   rightElement={<CheckIcon className="h-6 w-6 text-primary-500" />}
   readOnly
 />
 <Input
   label="전화번호"
   value={phoneNumber}
-  disabled={true}
   rightElement={<CheckIcon className="h-6 w-6 text-primary-500" />}
   readOnly
 />

Also applies to: 133-155

🤖 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/setting/ProfileSection.tsx` around lines 116 - 122, Remove the
redundant disabled prop from the Input usage and leave the field as read-only
only: update the Input component instances (e.g., the one rendering
value={`${org.name}  (${org.position})`} and the similar blocks referenced at
the other occurrences) to drop disabled={true} and keep readOnly (and existing
containerClassName) so the field remains non-editable but still focusable and
included in form submission.
src/stories/Colors.stories.tsx (1)

10-61: 💤 Low value

colorSections 데이터와 tokens.css의 수동 동기화 위험.

Storybook 문서에 표시할 색상 토큰 목록을 colorSections 배열에 하드코딩했는데, tokens.css에 새 토큰이 추가되거나 변경될 때 이 파일을 함께 업데이트해야 합니다. 토큰과 문서가 불일치할 위험이 있으므로, 가능하다면 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/stories/Colors.stories.tsx` around lines 10 - 61, colorSections is
hardcoded and can drift from tokens.css; replace the manual array with an
automated generator that parses tokens.css (or an exported tokens JSON) and
builds the same structure. Implement a helper like
generateColorSectionsFromTokens()/parseCssVariables() that reads CSS variable
names (e.g., --primary-*, --color-surface-*, --color-text-*) and groups them
into section objects matching the shape of colorSections, then export/use that
generator in Colors.stories.tsx instead of the static colorSections constant so
the story always reflects tokens.css changes.
src/components/dashboard/platform/PlatformDetailTable.tsx (1)

99-103: ⚡ Quick win

행 key에 인덱스 대신 안정 식별자를 써주세요.

현재 key={idx}는 데이터 순서가 바뀌면 잘못된 DOM 재사용으로 이어질 수 있습니다. row.date(또는 고유 ID)가 보장된다면 해당 값을 key로 쓰는 쪽이 안전합니다.

제안 diff
-            {data.map((row, idx) => (
+            {data.map((row) => (
               <tr
-                key={idx}
+                key={row.date}
                 className="hover:bg-surface-200/30 transition-colors group"
               >
🤖 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/PlatformDetailTable.tsx` around lines 99 -
103, The table rows use data.map with key={idx}, which is unstable; update the
key to use a stable unique identifier from each row (e.g., key={row.date} or
key={row.id}) in the PlatformDetailTable component so React can correctly
reconcile rows when order changes; if neither date nor id exists, add or compute
a stable id on the row objects before mapping and use that instead.
src/pages/dashboard/platform/PlatformDashboard.tsx (1)

76-89: ⚡ Quick win

텍스트/아이콘 색상에 surface 토큰 대신 text 역할 토큰을 사용해주세요.

PR 본문에서 "텍스트 색은 역할 토큰(text-text-*)로 통일"한다고 정리하셨는데, 여기서는 선택된 상태(primary 배경 위)일 때 text-surface-100을 텍스트/아이콘 색으로 쓰고 있어요. surface 계열은 배경 역할 토큰이라 의미상 어긋나고, 향후 토큰 리네이밍/리매핑 시 가독성이 깨질 위험이 있습니다. primary 배경 위의 전경색 역할을 담당하는 토큰(예: text-text-on-primary 같은 역할 토큰)을 새로 정의하거나 기존 역할 토큰으로 통일하시는 걸 권장드립니다.

As per coding guidelines: "Use only @theme tokens from tokens.css for colors. Do not rename tokens or add arbitrary colors." 그리고 PR에서 "텍스트 색은 역할 토큰(text-text-*) 통일" 원칙을 따르도록 정리되어 있습니다.

🤖 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/dashboard/platform/PlatformDashboard.tsx` around lines 76 - 89, The
span and ChevronDownIcon use the background/token `text-surface-100` for the
selected state; change these to the proper text role token (e.g.,
`text-text-on-primary` or the existing `text-text-*` token used for foregrounds)
so colors use only `@theme` role tokens; update the twMerge className entries in
PlatformDashboard (the span rendering selectedPlatform and the ChevronDownIcon)
to replace `text-surface-100` with the appropriate `text-text-*` role token
consistent with other components and token.css.
src/components/sidebar/Sidebar.tsx (1)

29-32: ⚡ Quick win

활성 상태 텍스트 색에 surface 토큰 사용 — 역할 토큰 권장.

text-surface-100은 의미상 배경/표면용 토큰입니다. PR에서 정리한 토큰 원칙(텍스트는 text-text-* 역할 토큰)에 맞춰 primary 배경 위 전경색용 역할 토큰(예: text-text-on-primary 등)을 도입하시는 게 일관성 측면에서 더 좋아 보입니다. PlatformDashboard에서 동일한 사용이 있어 함께 정리하면 유지보수가 편할 것 같아요.

As per coding guidelines: "Use only @theme tokens from tokens.css for colors. Do not rename tokens or add arbitrary colors."

🤖 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/sidebar/Sidebar.tsx` around lines 29 - 32, The active-state
class in Sidebar (the ternary that returns "bg-primary-400 text-surface-100")
uses a surface token for foreground; replace "text-surface-100" with the correct
role/text token from tokens.css (e.g., the project’s on-primary text token such
as "text-text-on-primary" or the established equivalent) so that active items
use a text-role token instead of a surface token; ensure you import/use only
existing `@theme` tokens defined in tokens.css and do not rename or add new
arbitrary color tokens; apply the same replacement pattern where
PlatformDashboard uses "text-surface-100" to keep consistency.
src/pages/ads/list/CampaignDetail.tsx (1)

143-153: 💤 Low value

openAdPauseModal / openAdResumeModaluseCallback으로 감싸는 걸 권장드립니다.

매 렌더마다 새 참조로 생성되어 하위 Button에 prop으로 전달됩니다. 지금은 큰 부담은 없지만, 동일 파일 내 다른 핸들러들(toggleAd, toggleSelectAllVisible, clearAdSelection)은 useCallback으로 정리되어 있어 일관성도 함께 챙기실 수 있습니다.

As per coding guidelines: "Hook 사용: ... useCallback, useMemo의 적절한 사용."

🤖 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/ads/list/CampaignDetail.tsx` around lines 143 - 153, Wrap
openAdPauseModal and openAdResumeModal in useCallback to avoid recreating
handlers each render; implement openAdPauseModal with useCallback(() => { const
scope = selectedOngoingIds.length > 0 ? "selection" : "all";
setPauseScope(scope); bulkAdPause.openModal(); }, [selectedOngoingIds,
setPauseScope, bulkAdPause]) and implement openAdResumeModal with useCallback(()
=> { const scope = selectedPausedIds.length > 0 ? "selection" : "all";
setResumeScope(scope); bulkAdResume.openModal(); }, [selectedPausedIds,
setResumeScope, bulkAdResume]); ensure you include the referenced symbols
(selectedOngoingIds, selectedPausedIds, setPauseScope, setResumeScope,
bulkAdPause, bulkAdResume) in the dependency arrays.
src/layout/main/MainLayout.tsx (2)

81-94: 💤 Low value

ads 캠페인 상세 경로 정규식이 두 번 반복됩니다.

/^\/ads\/[^/]+\/[^/]+$/ 패턴이 82번 줄과 94번 줄에 동일하게 사용되고 있습니다. 정의 위치를 한 곳으로 모으거나 이미 계산해 둔 isAdsCampaignDetailPath를 useMemo 내부에서 그대로 분기 조건으로 사용하시면 정의가 한 곳에 모여 추후 경로 규칙 변경 시 누락 위험이 줄어듭니다.

♻️ 제안 패치
-  const { parentLabel, currentLabel, parentTo, currentTo } = useMemo(() => {
-    const path = pathname;
-    if (/^\/ads\/[^/]+\/[^/]+$/.test(path)) {
+  const { parentLabel, currentLabel, parentTo, currentTo } = useMemo(() => {
+    const path = pathname;
+    if (isAdsCampaignDetailPath) {
       return {
         parentLabel: "",
         currentLabel: "",
         parentTo: null as string | null,
         currentTo: null as string | null,
       };
     }
-  }, [pathname, navForHeader]);
+  }, [pathname, navForHeader, isAdsCampaignDetailPath]);
🤖 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/layout/main/MainLayout.tsx` around lines 81 - 94, The regex
/^\/ads\/[^/]+\/[^/]+$/ is duplicated; replace the second inline test inside the
useMemo that computes { parentLabel, currentLabel, parentTo, currentTo } with
the already computed isAdsCampaignDetailPath value, and centralize the regex
only where isAdsCampaignDetailPath is defined (the useMemo that depends on
pathname); update the conditional in that useMemo to use isAdsCampaignDetailPath
instead of re-evaluating the regex and leave setCampaignDetailHeaderTitle logic
unchanged so all path checks reference the single isAdsCampaignDetailPath
symbol.

146-197: 🏗️ Heavy lift

브레드크럼 영역에 시맨틱/ARIA 보강을 권장드립니다.

지금 구조는 <div> 안에 <Link> / <span> 들이 슬래시 구분자로 나열되어 있어서 스크린리더 사용자에게 "브레드크럼"이라는 의미가 전달되지 않습니다. <nav aria-label="breadcrumb"> + <ol>/<li> 구조로 감싸고, 현재 페이지에 해당하는 마지막 항목에는 aria-current="page"를 주시는 게 좋습니다. 슬래시 구분자에 aria-hidden 처리하신 부분은 이미 좋은 처리이지만, 컨테이너 의미가 빠져 있어 보완이 필요합니다.

As per coding guidelines: "접근성: 시맨틱 HTML, ARIA 속성 사용 확인."

🤖 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/layout/main/MainLayout.tsx` around lines 146 - 197, Wrap the breadcrumb
markup in MainLayout with a semantic landmark by replacing the current plain div
container with a <nav aria-label="breadcrumb"> that contains an ordered list
(<ol>) of list items (<li>); render each crumb (the links rendered via Link for
parentTo/currentTo and the plain spans like parentLabel/currentLabel or the
ads-specific items using isAdsCampaignDetailPath and campaignDetailHeaderTitle)
as an li, keep the slash separators aria-hidden, and add aria-current="page" to
the final/current breadcrumb item (the element that currently renders
currentLabel || parentLabel || " " or campaignDetailHeaderTitle) so screen
readers recognize the current page; preserve existing class names like
crumbLinkBody and crumbLinkTitle and behavior of parentTo/currentTo when
converting their wrappers to list items.
src/utils/navigation/workspaceNavPaths.ts (1)

32-34: ⚡ Quick win

workspaceSubpath를 정규식에 그대로 보간하기보다 문자열 비교로 처리하시면 더 안전하고 명확합니다.

지금은 workspaceSubpath가 sidebarNav 상수("settings", "members", "billing")에서만 오기 때문에 실제 ReDoS나 잘못된 매칭 위험은 사실상 없지만, 정규식 메타문자가 섞일 여지가 생기면 의도치 않은 매칭으로 이어질 수 있습니다. 호출 빈도(렌더마다 매칭 검사)를 고려하면 단순 문자열 분해가 가독성·성능 모두 유리합니다.

♻️ 제안 패치 (문자열 매칭)
-  if (item.workspaceSubpath) {
-    return new RegExp(`^/workspace/[^/]+/${item.workspaceSubpath}$`).test(norm);
-  }
+  if (item.workspaceSubpath) {
+    const segments = norm.split("/").filter(Boolean);
+    return (
+      segments.length === 3 &&
+      segments[0] === "workspace" &&
+      segments[2] === item.workspaceSubpath
+    );
+  }
🤖 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/utils/navigation/workspaceNavPaths.ts` around lines 32 - 34, The current
code builds a RegExp with item.workspaceSubpath and tests norm, which risks
accidental regex meta-character interpretation and is less efficient; change the
logic that handles item.workspaceSubpath to perform a direct string comparison
instead (e.g., normalize and split norm by '/' or verify it startsWith
'/workspace/' + workspaceId + '/' then compare the trailing segment to
item.workspaceSubpath) so that workspaceSubpath is matched exactly without
constructing a RegExp; update the branch that checks item.workspaceSubpath to
use norm and item.workspaceSubpath string operations (referencing
item.workspaceSubpath and norm) and return the boolean result of that string
equality check.
🤖 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/card/Card.stories.tsx`:
- Line 23: The Storybook examples in Card.stories.tsx use the auth-specific
token `text-text-auth-sub`; update those instances (the <p> elements at the
occurrences referenced) to a consistent body token used by the project such as
`text-text-title` for primary card content or `text-text-muted` for
descriptive/subtle text to match Card.tsx (description uses `text-text-muted`)
and other components like InfoCard/TextareaField; replace `text-text-auth-sub`
with the chosen project-wide token across all occurrences in Card.stories.tsx.

In `@src/components/common/modal/ModalContent.tsx`:
- Around line 151-156: The Tailwind important-prefix syntax used in the
ModalContent component's className (inside the twMerge call) uses deprecated
leading "!" tokens (e.g., "!cursor-not-allowed", "!border", "!bg-info-red/12") —
update those to the Tailwind v4 suffix syntax by moving the "!" to the end of
each utility (e.g., "cursor-not-allowed!", "border!", "bg-info-red/12!") for all
occurrences within the conditional expression that uses showMutedDangerConfirm
and variant === "danger" so the utilities remain functionally important while
matching v4.1.18 syntax.

In `@src/components/common/select/SearchSelect.tsx`:
- Line 110: SearchSelect 컴포넌트의 입력 엘리먼트(className 문자열)에서 현재 outline-none과 기본/포커스
동일한 border-info-blue 조합으로 포커스 가시성이 사라지므로, focus-visible 기준의 명확한 시각 피드백을 추가해 주세요:
입력에 focus-visible:ring-2 (또는 적정 굵기)과 focus-visible:ring-info-blue(또는 더 진한
shade), focus-visible:ring-offset-2 등 링/오프셋 클래스를 추가하고 포커스 상태에서 border 색상을 더 명확히
바꾸도록 focus:border-info-blue-600(또는 적절한 대비 색) 같은 클래스를 추가해 키보드 포커스가 시각적으로 명확히
구분되도록 수정하세요 (대상 식별자: SearchSelect 컴포넌트의 해당 input/element의 className).

In `@src/components/dashboard/charts/performanceEfficiencyChart.config.ts`:
- Line 18: Replace all hardcoded hex color literals in this chart config with
the design token references: swap strokeColors, the series color array (`#0084fe`,
`#2f5bea`, `#7a9bf8`) and the other hex values referenced in this file to the
appropriate `@theme` tokens (e.g., primary-*, neutral-*, etc.). Locate the
properties named strokeColors and the series color array (and any other color
keys in this config) and import/consume the theme tokens or token helper used
across the codebase, then map each hex to the matching token (map the three
brand-like series colors to primary-* tokens). Ensure every color value in this
config uses the token reference instead of literal hex strings.

In `@src/components/dashboard/platform/AllPlatformTrafficChart.tsx`:
- Line 108: The chart component AllPlatformTrafficChart is using hardcoded hex
colors in inline style (e.g., style: { colors: "#9ca3af", fontSize: "12px" }) —
replace those hex values with the corresponding `@theme` tokens from tokens.css
(use the same token names used in PlatformTrafficChart for consistency, e.g.,
the secondary text and background tokens) and update any other occurrences in
this file (the other color uses referenced in the review) so all color values
come from `@theme` tokens; ensure you update the inline style entries and any
chart options/constants inside the AllPlatformTrafficChart component to
reference the token variables instead of literal hex strings.

In `@src/components/dashboard/platform/PlatformTrafficChart.tsx`:
- Line 103: Replace hardcoded hex color values used in the ApexCharts
configuration with CSS design tokens from tokens.css (use the `@theme` CSS
variables) instead of literal strings; locate the chart options / style objects
in PlatformTrafficChart.tsx (e.g., the style: { colors: "#9ca3af", fontSize:
"12px" } and other similar entries around the chartOptions/annotations/series
config referenced at lines ~103, ~114, ~124) and swap each "#9ca3af" and
"#f4f6fb" for the corresponding tokens.css variables (as CSS variable strings
acceptable to ApexCharts), keeping fontSize unchanged. Ensure all color usages
in functions/components like PlatformTrafficChart and its
chartOptions/annotations are updated to use the `@theme` tokens.

In `@src/components/landing/GuideTimeline.tsx`:
- Around line 50-64: The two inert controls (the button elements with
aria-label="이전 기간" and aria-label="다음 기간" that render ChevronLeftIcon and
ChevronRightIcon in GuideTimeline) must not be focusable interactive elements
when they do nothing; either add the disabled attribute and appropriate disabled
styling/classes and ensure they are non-focusable, or replace them with
non-interactive elements (e.g., span) with the same visual classes and an
appropriate aria-hidden or aria-label if needed. Update the elements that
reference ChevronLeftIcon and ChevronRightIcon accordingly so keyboard and
screen-reader users aren't presented with focusable, non-functional buttons.

In `@src/components/landing/LandingMultiDevice.tsx`:
- Around line 11-12: MOCKUP_OVERLAY_CLASS uses hardcoded colors ("from-black/70
via-black/15")—replace them with theme tokens by referencing the tokens system:
add a new overlay token (e.g., --color-overlay-dark) to tokens.css under `@theme`
or reuse an existing token like --color-text-400, then update
MOCKUP_OVERLAY_CLASS to use the token-based gradient stops (instead of
from-black/70 via-black/15) so the overlay gradient uses the project tokens
(reference symbol: MOCKUP_OVERLAY_CLASS and tokens.css/@theme).

In `@src/index.css`:
- Around line 5-8: The `@import` rules are placed after a `@custom-variant`,
violating the no-invalid-position-at-import-rule; move all `@import` statements
(e.g., `@import` "./styles/tokens.css"; `@import` "./styles/base.css"; `@import`
"./styles/utilities.css"; `@import` "./styles/print.css";) so they appear before
any `@custom-variant` declarations in the file (ensure no other at-rules precede
them) to comply with the CSS spec and the linter rule.

In `@src/layout/workspace/WorkspaceManageLayout.tsx`:
- Around line 11-13: The current parse of workspaceId in
WorkspaceManageLayout.tsx converts workspaceId to Number and only checks
Number.isFinite(id) && id > 0, which allows non-integer values like "1.5";
update the validation to ensure the parsed id is an integer (use
Number.isInteger on the parsed id) before calling setSelectedOrgId so only
positive integer org IDs are stored and used for path construction (validate
workspaceId -> id, then call setSelectedOrgId(id) only if integer && > 0).

In `@src/pages/ads/list/AdsListPage.tsx`:
- Around line 248-251: The early-return on missing orgId inside the bulk action
confirmation callback can make the confirm Promise resolve as success; in
bulkStop.handleConfirm (and the similar block at the other bulk action around
pauseScope handling) replace the silent `if (!orgId) return;` with an explicit
failure — either throw a descriptive Error (e.g., `throw new Error("orgId
missing")`) or reject the Promise so the confirm flow treats it as an error and
prevents success toasts/onSuccess; alternatively, block opening the modal or
disable the confirm button when `orgId` is falsy, but do not silently return
inside the async callback.

In `@src/pages/ads/list/CampaignDetail.tsx`:
- Around line 360-374: The current bulk pause flow inside
bulkAdPause.handleConfirm (using Promise.all over ids derived from pauseScope /
adsList / selectedOngoingIds and calling updateAdStatus) can leave server/UI out
of sync when one or more requests fail; replace Promise.all with
Promise.allSettled, count successes vs failures from the settled results, show
appropriate success/error toast/messages based on counts, and ensure refetchAds
is always invoked (in a finally or onSettled path) so UI is synchronized; apply
the same change to the corresponding resume modal flow (the similar block around
lines 401–420 that performs resume updates).

In `@src/pages/auth/RedirectPage.tsx`:
- Around line 44-45: Update the loading text container in RedirectPage (the div
that currently renders "로그인 중...") to expose its loading status to assistive
tech by adding role="status" and aria-live="polite" to that element, and if
there is a surrounding wrapper representing the ongoing operation (e.g., the
RedirectPage component root or a parent container), add aria-busy={true} while
the redirect/login is in progress; ensure these attributes are present on the
same JSX elements referenced in RedirectPage.tsx so screen readers reliably
announce the loading state.

In `@src/stories/Typography.stories.tsx`:
- Around line 170-171: Replace direct Tailwind gray color classes used in the
JSX (e.g., the div with className "grid ... border-b border-gray-200 ...
text-gray-400" and any other occurrences noted) with the project theme token
utility classes (for example use text-text-* and border-surface-* equivalents
from `@theme/tokens` instead of text-gray-* and border-gray-*). Locate the
affected JSX elements in Typography.stories.tsx (the grid header div and the
other story elements around the listed ranges) and swap every text-gray-* and
border-gray-* usage to the corresponding token utility class name, keeping the
rest of the className string intact and ensuring no new token names are
invented.

In `@src/styles/base.css`:
- Line 2: Remove the unnecessary quotes around the single-word font name in the
font-family declarations (e.g., replace the rule using "Pretendard" with
Pretendard) to satisfy Stylelint; update all occurrences including the other
instance noted (line 9) so every font-family: "Pretendard"; becomes font-family:
Pretendard;.

In `@src/styles/utilities.css`:
- Around line 59-73: Replace hardcoded RGBA values in the shadow utility classes
by using theme tokens with color-mix and CSS variables: update .shadow-Soft,
.shadow-Medium, .shadow-card and .shadow-card-hover (and the occurrences around
lines referenced) to derive the shadow color from var(--color-*) tokens via
color-mix() instead of literal rgba(...) so the project uses tokens from
tokens.css; ensure you pick the appropriate token (e.g.,
--color-foreground/--color-black or the designated shadow token) and apply
color-mix(<token> 10% , transparent) or equivalent percentages to match the
original opacities, keeping class names unchanged.

---

Outside diff comments:
In `@src/components/ads/AdDetailContent.tsx`:
- Around line 174-183: The call uses Number(orgId) directly which can produce
NaN if the route param is missing; before invoking createTrackingUrl inside
trackControl.handleConfirm (and before using ad.landingUrl), validate orgId by
parsing it (e.g., parseInt or Number) and ensure it's a finite integer (not NaN)
— if invalid, show a user-facing error (toast) and abort (throw or return) so
createTrackingUrl(orgId, ad.id, ad.landingUrl) is never called with NaN; update
the block around trackControl.handleConfirm and createTrackingUrl to perform
this check and early-exit on invalid orgId.

In `@src/components/dashboard/charts/AdStatusChart.tsx`:
- Around line 5-9: PLATFORM_COLORS currently hardcodes hex values; replace those
hex strings with the corresponding `@theme` color tokens from tokens.css (e.g. use
the CSS variable form from `@theme` like "var(--theme-...)" or the project's token
accessor) so the chart uses design tokens instead of raw hex; update the
PLATFORM_COLORS constant in AdStatusChart.tsx (and the other similar entries
noted around lines 39-42) to map GOOGLE, NAVER, META to their respective `@theme`
token names found in tokens.css and ensure any usage of PLATFORM_COLORS in the
component continues to accept the token string values.

In `@src/components/setting/PasswordSection.tsx`:
- Around line 87-90: The aria attributes for the new-password toggle are
incorrectly using showCurrent; update the toggle in PasswordSection (the
new-password visibility button that currently references showCurrent) to
reference showNew instead so aria-label values ("새 비밀번호 숨기기"/"새 비밀번호 보기") and
aria-pressed reflect the new-password visibility state; ensure the button that
toggles new password visibility also uses the showNew state in its onClick
handler if not already wired.

---

Nitpick comments:
In `@src/components/ads/CampaignRow.tsx`:
- Around line 59-72: Remove the unnecessary role="presentation" from the div
that wraps the checkbox in CampaignRow (the container currently around the
<input type="checkbox"> using isSelected, onToggleSelect and name props); since
the input is an interactive, semantic element, simply omit the role on that
wrapper (leave the div for layout and keep the onClick/onKeyDown stopPropagation
handlers and the input's checked/onChange/aria-label intact) so screen readers
receive the checkbox semantics from the input itself.

In `@src/components/common/dropdownmenu/DropdownMenu.tsx`:
- Around line 58-74: The trigger wrapper div in DropdownMenu lacks keyboard
focusability; update the element (the div inside the DropdownMenu component that
currently has role="button" and onKeyDown/onClick handlers) to include
tabIndex={0} so keyboard-only users can focus it, keeping the existing onKeyDown
handling for "Enter" and " " to toggle setOpen and leaving aria attributes
(aria-haspopup, aria-expanded, aria-controls, aria-label) intact; ensure any
relevant tests or accessibility docs reference the change.

In `@src/components/common/modal/Modal.tsx`:
- Around line 53-58: The useLayoutEffect currently wraps
modalRef.current?.focus() in requestAnimationFrame/cancelAnimationFrame; remove
the rAF logic and call modalRef.current?.focus() synchronously inside
useLayoutEffect when isOpen is true, keeping previousActiveElement.current
assignment intact; update references to requestAnimationFrame and
cancelAnimationFrame (remove them) and ensure modalRef, previousActiveElement,
and the useLayoutEffect callback are the only items touched.

In `@src/components/dashboard/platform/PlatformDetailTable.tsx`:
- Around line 99-103: The table rows use data.map with key={idx}, which is
unstable; update the key to use a stable unique identifier from each row (e.g.,
key={row.date} or key={row.id}) in the PlatformDetailTable component so React
can correctly reconcile rows when order changes; if neither date nor id exists,
add or compute a stable id on the row objects before mapping and use that
instead.

In `@src/components/landing/LandingFooter.tsx`:
- Around line 16-33: LandingFooter contains three anchor elements that repeat
the same hover/focus class string; extract that repeated class string into a
single constant (e.g., linkClass or LINK_STYLE) at the top of the LandingFooter
component and replace the className values of the anchors (the links for "이용약관",
"개인정보처리방침", and "고객센터") with that constant to remove duplication and improve
maintainability.
- Line 11: The footer row currently uses a surface color token in the className
("flex items-center gap-8 font-body2 text-surface-500") inside
LandingFooter.tsx; change that to a text role token (e.g., text-text-sub or
text-text-muted) so it follows the design system guideline "텍스트 색은 역할
토큰(text-text-*) 통일" — update the className on the element that contains
text-surface-500 to use the chosen text-text-* token and run a quick visual
check to confirm contrast/appearance.

In `@src/components/setting/ProfileSection.tsx`:
- Around line 116-122: Remove the redundant disabled prop from the Input usage
and leave the field as read-only only: update the Input component instances
(e.g., the one rendering value={`${org.name}  (${org.position})`} and the
similar blocks referenced at the other occurrences) to drop disabled={true} and
keep readOnly (and existing containerClassName) so the field remains
non-editable but still focusable and included in form submission.

In `@src/components/sidebar/Sidebar.tsx`:
- Around line 29-32: The active-state class in Sidebar (the ternary that returns
"bg-primary-400 text-surface-100") uses a surface token for foreground; replace
"text-surface-100" with the correct role/text token from tokens.css (e.g., the
project’s on-primary text token such as "text-text-on-primary" or the
established equivalent) so that active items use a text-role token instead of a
surface token; ensure you import/use only existing `@theme` tokens defined in
tokens.css and do not rename or add new arbitrary color tokens; apply the same
replacement pattern where PlatformDashboard uses "text-surface-100" to keep
consistency.

In `@src/hooks/dashboard/useOverviewCampaignList.ts`:
- Line 14: 현재 fetcher에 orgId! 비무효(assertion) 사용이 중복되어 있어 깔끔하게 바꿀 수 있습니다;
getCampaignList 호출부를 수정하여 orgId가 있을 때만 getCampaignList(orgId)를 호출하고 그렇지 않으면 빈
배열을 반환하는 Promise를 반환하도록 변경하세요(참조: fetcher 람다, getCampaignList, orgId, enabled:
!!orgId).

In `@src/layout/main/MainLayout.tsx`:
- Around line 81-94: The regex /^\/ads\/[^/]+\/[^/]+$/ is duplicated; replace
the second inline test inside the useMemo that computes { parentLabel,
currentLabel, parentTo, currentTo } with the already computed
isAdsCampaignDetailPath value, and centralize the regex only where
isAdsCampaignDetailPath is defined (the useMemo that depends on pathname);
update the conditional in that useMemo to use isAdsCampaignDetailPath instead of
re-evaluating the regex and leave setCampaignDetailHeaderTitle logic unchanged
so all path checks reference the single isAdsCampaignDetailPath symbol.
- Around line 146-197: Wrap the breadcrumb markup in MainLayout with a semantic
landmark by replacing the current plain div container with a <nav
aria-label="breadcrumb"> that contains an ordered list (<ol>) of list items
(<li>); render each crumb (the links rendered via Link for parentTo/currentTo
and the plain spans like parentLabel/currentLabel or the ads-specific items
using isAdsCampaignDetailPath and campaignDetailHeaderTitle) as an li, keep the
slash separators aria-hidden, and add aria-current="page" to the final/current
breadcrumb item (the element that currently renders currentLabel || parentLabel
|| " " or campaignDetailHeaderTitle) so screen readers recognize the current
page; preserve existing class names like crumbLinkBody and crumbLinkTitle and
behavior of parentTo/currentTo when converting their wrappers to list items.

In `@src/pages/ads/list/CampaignDetail.tsx`:
- Around line 143-153: Wrap openAdPauseModal and openAdResumeModal in
useCallback to avoid recreating handlers each render; implement openAdPauseModal
with useCallback(() => { const scope = selectedOngoingIds.length > 0 ?
"selection" : "all"; setPauseScope(scope); bulkAdPause.openModal(); },
[selectedOngoingIds, setPauseScope, bulkAdPause]) and implement
openAdResumeModal with useCallback(() => { const scope =
selectedPausedIds.length > 0 ? "selection" : "all"; setResumeScope(scope);
bulkAdResume.openModal(); }, [selectedPausedIds, setResumeScope, bulkAdResume]);
ensure you include the referenced symbols (selectedOngoingIds,
selectedPausedIds, setPauseScope, setResumeScope, bulkAdPause, bulkAdResume) in
the dependency arrays.

In `@src/pages/dashboard/platform/PlatformDashboard.tsx`:
- Around line 76-89: The span and ChevronDownIcon use the background/token
`text-surface-100` for the selected state; change these to the proper text role
token (e.g., `text-text-on-primary` or the existing `text-text-*` token used for
foregrounds) so colors use only `@theme` role tokens; update the twMerge className
entries in PlatformDashboard (the span rendering selectedPlatform and the
ChevronDownIcon) to replace `text-surface-100` with the appropriate
`text-text-*` role token consistent with other components and token.css.

In `@src/stories/Colors.stories.tsx`:
- Around line 10-61: colorSections is hardcoded and can drift from tokens.css;
replace the manual array with an automated generator that parses tokens.css (or
an exported tokens JSON) and builds the same structure. Implement a helper like
generateColorSectionsFromTokens()/parseCssVariables() that reads CSS variable
names (e.g., --primary-*, --color-surface-*, --color-text-*) and groups them
into section objects matching the shape of colorSections, then export/use that
generator in Colors.stories.tsx instead of the static colorSections constant so
the story always reflects tokens.css changes.

In `@src/utils/navigation/workspaceNavPaths.ts`:
- Around line 32-34: The current code builds a RegExp with item.workspaceSubpath
and tests norm, which risks accidental regex meta-character interpretation and
is less efficient; change the logic that handles item.workspaceSubpath to
perform a direct string comparison instead (e.g., normalize and split norm by
'/' or verify it startsWith '/workspace/' + workspaceId + '/' then compare the
trailing segment to item.workspaceSubpath) so that workspaceSubpath is matched
exactly without constructing a RegExp; update the branch that checks
item.workspaceSubpath to use norm and item.workspaceSubpath string operations
(referencing item.workspaceSubpath and norm) and return the boolean result of
that string equality check.
🪄 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: 524ca632-480c-426d-9400-934542da4bc1

📥 Commits

Reviewing files that changed from the base of the PR and between 27f8916 and 714e960.

⛔ Files ignored due to path filters (15)
  • .cursor/rules/private-always.mdc is excluded by none and included by none
  • .gitignore is excluded by none and included by none
  • eslint/import.mjs is excluded by none and included by none
  • package-lock.json is excluded by !**/package-lock.json and included by none
  • package.json is excluded by none and included by none
  • src/assets/icon/ai/ai-요약버튼.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/common/camera.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/common/eye-off.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/common/eye.svg is excluded by !**/*.svg and included by src/**
  • src/assets/icon/sidebar/logout.svg is excluded by !**/*.svg and included by src/**
  • src/assets/logo/service-logo/logo.svg is excluded by !**/*.svg and included by src/**
  • src/assets/logo/service-logo/symbol-white.svg is excluded by !**/*.svg and included by src/**
  • src/assets/logo/social-logo/circle/kakao-circle.svg is excluded by !**/*.svg and included by src/**
  • src/assets/logo/social-logo/plain/google.svg is excluded by !**/*.svg and included by src/**
  • src/assets/logo/social-logo/plain/kakao.svg is excluded by !**/*.svg and included by src/**
📒 Files selected for processing (135)
  • src/components/ads/AdDetailContent.tsx
  • src/components/ads/AdListTable.tsx
  • src/components/ads/AdRow.tsx
  • src/components/ads/CampaignInfoCard.tsx
  • src/components/ads/CampaignPlatformDropdown.tsx
  • src/components/ads/CampaignRow.tsx
  • src/components/ads/CampaignTable.tsx
  • src/components/ads/PlatformCard.tsx
  • src/components/auth/common/CommonAuthInput.tsx
  • src/components/auth/common/InputActions.tsx
  • src/components/auth/common/PasswordForm.tsx
  • src/components/auth/flows/find-email/EnterPhoneStep.tsx
  • src/components/auth/flows/find-email/ShowEmailResultStep.tsx
  • src/components/auth/flows/reset-password/EmailVerificationStep.tsx
  • src/components/auth/flows/signup/EnterEmailStep.tsx
  • src/components/auth/flows/signup/ProfileSetupStep.tsx
  • src/components/auth/skeleton/AuthFormSkeleton.tsx
  • src/components/auth/skeleton/LoginPageSkeleton.tsx
  • src/components/auth/skeleton/SignupEmailStepSkeleton.tsx
  • src/components/auth/skeleton/SignupPageSkeleton.tsx
  • src/components/common/ComingSoonPlaceholder.tsx
  • src/components/common/alert/Alert.tsx
  • src/components/common/badge/Badge.stories.tsx
  • src/components/common/badge/Badge.tsx
  • src/components/common/button/Button.tsx
  • src/components/common/card/Card.stories.tsx
  • src/components/common/card/Card.tsx
  • src/components/common/card/InfoCard.stories.tsx
  • src/components/common/card/InfoCard.tsx
  • src/components/common/card/StatCard.stories.tsx
  • src/components/common/card/StatCard.tsx
  • src/components/common/chart/ChartLegend.tsx
  • src/components/common/controlbox/ControlBox.stories.tsx
  • src/components/common/controlbox/ControlBox.tsx
  • src/components/common/drawer/Drawer.tsx
  • src/components/common/dropdownmenu/DropdownMenu.tsx
  • src/components/common/input/Input.stories.tsx
  • src/components/common/input/Input.tsx
  • src/components/common/modal/Modal.stories.tsx
  • src/components/common/modal/Modal.tsx
  • src/components/common/modal/ModalContent.tsx
  • src/components/common/progressbar/ProgressBar.tsx
  • src/components/common/select/DropdownSelect.tsx
  • src/components/common/select/SearchSelect.tsx
  • src/components/common/skeleton/Skeleton.tsx
  • src/components/common/textarea/TextareaField.tsx
  • src/components/common/toggle/Toggle.tsx
  • src/components/dashboard/charts/AdStatusChart.tsx
  • src/components/dashboard/charts/BudgetGaugeChart.tsx
  • src/components/dashboard/charts/TrafficChart.tsx
  • src/components/dashboard/charts/performanceEfficiencyChart.config.ts
  • 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/dashboard/platform/skeleton/PlatformSkeleton.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/LandingFooter.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/landing/LandingSectionHeader.tsx
  • src/components/modal/privacyModal/AgreementItem.tsx
  • src/components/modal/privacyModal/PrivacyModal.tsx
  • src/components/setting/PasswordSection.tsx
  • src/components/setting/ProfileSection.tsx
  • src/components/sidebar/LogoutConfirmModal.tsx
  • src/components/sidebar/Sidebar.tsx
  • src/components/sidebar/SubMenu.tsx
  • src/components/sidebar/WorkspaceSwitcher.tsx
  • src/components/workspace/DeleteMemberModal.tsx
  • src/components/workspace/InviteMemberModal.tsx
  • src/components/workspace/MemberItem.tsx
  • src/components/workspace/MemberList.tsx
  • src/components/workspace/MemberManagementLoading.tsx
  • src/components/workspace/MemberRoleSelect.tsx
  • src/components/workspace/MemberSearchSelect.tsx
  • src/components/workspace/PermissionTable.tsx
  • src/components/workspace/WorkspaceCard.tsx
  • src/components/workspace/WorkspaceEmptyState.tsx
  • src/components/workspace/WorkspaceListError.tsx
  • src/components/workspace/WorkspaceListLoading.tsx
  • src/components/workspace/WorkspaceSettingLoading.tsx
  • src/constants/landing/overviewChart.ts
  • src/constants/landing/timeline.ts
  • src/constants/sidebarNav.ts
  • src/hooks/ads/useAdList.ts
  • src/hooks/ads/useCampaignGroup.ts
  • src/hooks/auth/useLogout.ts
  • src/hooks/dashboard/useOverviewCampaignList.ts
  • src/index.css
  • src/layout/auth/AuthLayout.tsx
  • src/layout/main/MainLayout.tsx
  • src/layout/workspace/WorkspaceManageLayout.tsx
  • src/pages/ads/list/AdsListPage.tsx
  • src/pages/ads/list/CampaignDetail.tsx
  • src/pages/ads/new/CampaignGroup.tsx
  • src/pages/auth/Login.tsx
  • src/pages/auth/RedirectPage.tsx
  • src/pages/auth/Signup.tsx
  • src/pages/dashboard/overview/OverviewAiDrawer.tsx
  • src/pages/dashboard/overview/OverviewAiReportPanel.tsx
  • src/pages/dashboard/overview/OverviewBudgetSection.tsx
  • src/pages/dashboard/overview/OverviewCampaignSnapshotCard.tsx
  • src/pages/dashboard/overview/OverviewDashboard.tsx
  • src/pages/dashboard/overview/OverviewKpiSection.tsx
  • src/pages/dashboard/overview/OverviewPlatformSection.tsx
  • src/pages/dashboard/platform/PlatformDashboard.tsx
  • src/pages/landing/LandingPage.tsx
  • src/pages/workspace/MemberManagement.tsx
  • src/pages/workspace/Workspace.tsx
  • src/pages/workspace/WorkspaceBillingRedirect.tsx
  • src/pages/workspace/WorkspaceSetting.tsx
  • src/routes/MainRoutes.tsx
  • src/stories/BorderRadius.stories.tsx
  • src/stories/Colors.stories.tsx
  • src/stories/Typography.stories.tsx
  • src/styles/base.css
  • src/styles/print.css
  • src/styles/tokens.css
  • src/styles/utilities.css
  • src/types/ads/campaign.ts
  • src/types/navigation/navItem.ts
  • src/utils/navigation/mainNavSidebar.ts
  • src/utils/navigation/workspaceNavPaths.ts
💤 Files with no reviewable changes (5)
  • src/components/sidebar/LogoutConfirmModal.tsx
  • src/components/common/select/DropdownSelect.tsx
  • src/components/common/controlbox/ControlBox.stories.tsx
  • src/hooks/auth/useLogout.ts
  • src/components/common/controlbox/ControlBox.tsx

Comment thread src/components/common/card/Card.stories.tsx Outdated
Comment thread src/components/common/modal/ModalContent.tsx
Comment thread src/components/common/select/SearchSelect.tsx
Comment thread src/components/dashboard/charts/performanceEfficiencyChart.config.ts Outdated
Comment thread src/components/dashboard/platform/AllPlatformTrafficChart.tsx Outdated
Comment thread src/pages/ads/list/CampaignDetail.tsx
Comment thread src/pages/auth/RedirectPage.tsx
Comment thread src/stories/Typography.stories.tsx Outdated
Comment thread src/styles/base.css
Comment thread src/styles/utilities.css

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

🧹 Nitpick comments (1)
src/components/dashboard/platform/AllPlatformTrafficChart.tsx (1)

13-17: 💤 Low value

플랫폼 브랜드 컬러도 토큰화를 고려해보면 좋을 것 같아요.

코딩 가이드라인에 따르면 @theme 토큰만 사용하도록 되어 있는데, PLATFORM_COLORS 객체는 여전히 하드코딩된 hex 값을 사용하고 있습니다.

Google, Naver, Meta의 브랜드 컬러가 다른 곳에서도 재사용된다면 tokens.css에 정의하는 것이 일관성 유지에 도움이 될 것 같습니다. 예를 들어:

  • --color-platform-google
  • --color-platform-naver
  • --color-platform-meta

다만 이 컬러들이 이 컴포넌트에서만 사용되고 디자인 시스템의 일부가 아니라면 현재 구조를 유지하는 것도 합리적입니다.

🤖 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
13 - 17, PLATFORM_COLORS in AllPlatformTrafficChart.tsx is using hardcoded hex
values; move these into your design tokens and reference them instead: add CSS
variables in tokens.css like --color-platform-google, --color-platform-naver,
--color-platform-meta with the current hex values, then replace usages of
PLATFORM_COLORS (and any direct hex literals) in the AllPlatformTrafficChart
component to read from the theme tokens (e.g., via var(--color-platform-google))
or the `@theme` token accessor your codebase uses; if these colors are truly
component-scoped and not shared, keep PLATFORM_COLORS, otherwise remove the
hardcoded object and use the new tokens.
🤖 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.

Nitpick comments:
In `@src/components/dashboard/platform/AllPlatformTrafficChart.tsx`:
- Around line 13-17: PLATFORM_COLORS in AllPlatformTrafficChart.tsx is using
hardcoded hex values; move these into your design tokens and reference them
instead: add CSS variables in tokens.css like --color-platform-google,
--color-platform-naver, --color-platform-meta with the current hex values, then
replace usages of PLATFORM_COLORS (and any direct hex literals) in the
AllPlatformTrafficChart component to read from the theme tokens (e.g., via
var(--color-platform-google)) or the `@theme` token accessor your codebase uses;
if these colors are truly component-scoped and not shared, keep PLATFORM_COLORS,
otherwise remove the hardcoded object and use the new tokens.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5a4d01f7-d00f-47ae-afdd-3e275690e298

📥 Commits

Reviewing files that changed from the base of the PR and between 714e960 and ef875ed.

⛔ Files ignored due to path filters (1)
  • src/assets/logo/social-logo/circle/kakao-circle.svg is excluded by !**/*.svg and included by src/**
📒 Files selected for processing (19)
  • src/components/ads/AdRow.tsx
  • src/components/ads/CampaignRow.tsx
  • src/components/ads/PlatformCard.tsx
  • src/components/common/button/Button.tsx
  • src/components/common/card/Card.stories.tsx
  • src/components/common/modal/ModalContent.tsx
  • src/components/dashboard/charts/performanceEfficiencyChart.config.ts
  • src/components/dashboard/charts/trafficChart.config.ts
  • src/components/dashboard/platform/AllPlatformTrafficChart.tsx
  • src/components/dashboard/platform/PlatformTrafficChart.tsx
  • src/components/setting/PasswordSectionSkeleton.tsx
  • src/components/setting/ProfileSectionSkeleton.tsx
  • src/pages/ads/new/CampaignGroup.tsx
  • src/pages/auth/Login.tsx
  • src/pages/dashboard/platform/PlatformDashboard.tsx
  • src/stories/BorderRadius.stories.tsx
  • src/stories/Colors.stories.tsx
  • src/stories/Shadows.stories.tsx
  • src/stories/Typography.stories.tsx
✅ Files skipped from review due to trivial changes (3)
  • src/components/setting/PasswordSectionSkeleton.tsx
  • src/components/setting/ProfileSectionSkeleton.tsx
  • src/components/dashboard/charts/performanceEfficiencyChart.config.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/pages/dashboard/platform/PlatformDashboard.tsx
  • src/stories/Typography.stories.tsx
  • src/stories/Colors.stories.tsx
  • src/components/common/button/Button.tsx
  • src/components/common/modal/ModalContent.tsx
  • src/components/ads/AdRow.tsx
  • src/pages/ads/new/CampaignGroup.tsx

@github-actions

Copy link
Copy Markdown

📚 Storybook 배포 완료

항목 링크
📖 Storybook https://69a147b60a56365d9e2185ef-wnlhrcxmjp.chromatic.com/
🔍 Chromatic https://www.chromatic.com/build?appId=69a147b60a56365d9e2185ef&number=275

@jjjsun jjjsun left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P4: 확인했습니다! 고생하셨어요!

@Seojegyeong
Seojegyeong merged commit ae2764e into develop May 14, 2026
4 checks passed
@Seojegyeong
Seojegyeong deleted the refactor/#200 branch May 14, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎨 Html&css 마크업 & 스타일링 🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

♻️ [Refactor] 디자인 시스템 컬러 및 폰트 위계 점검 및 개선

2 participants