Skip to content

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

Merged
YermIm merged 59 commits into
mainfrom
develop
Aug 10, 2026
Merged

[Deploy] develop → main 배포 반영#447
YermIm merged 59 commits into
mainfrom
develop

Conversation

@YermIm

@YermIm YermIm commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

🚨 관련 이슈

N/A

✨ 변경사항

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

✏️ 작업 내용

N/A

😅 미완성 작업

N/A

📢 논의 사항 및 참고 사항

N/A

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

Summary by CodeRabbit

  • 새로운 기능

    • 플랫폼 트래픽 차트를 PNG, SVG, CSV 형식으로 다운로드할 수 있습니다.
    • Google 소셜 로그인이 실제 인증 흐름으로 연결되었습니다.
    • 워크스페이스 삭제 시 이름 확인 절차가 추가되었습니다.
    • 플랫폼 성과 일부 조회 실패 시 성공 데이터와 실패 항목을 함께 확인할 수 있습니다.
    • 모바일 ROAS 표에서 상세 정보를 행별로 펼쳐볼 수 있습니다.
  • 개선 사항

    • 드롭다운이 화면 공간에 맞춰 자동 배치됩니다.
    • 플랫폼 선택 상태가 URL과 동기화됩니다.
    • 설정 및 워크스페이스 화면의 태블릿·모바일 레이아웃이 개선되었습니다.
    • 연동이 필요한 메뉴에 안내 표시가 추가되었습니다.
    • 회원가입에서 카카오 로그인이 제거되었습니다.
  • 버그 수정

    • PDF 다운로드 전 콘텐츠 보안을 강화했습니다.
    • OAuth 처리와 만료일 판정이 더욱 안정적으로 동작합니다.

Seojegyeong and others added 30 commits August 8, 2026 20:44
[Refactor/#417] 플랫폼별 비교 테이블 모바일 반응형 추가
[Bugfix#419] 랜딩페이지 날짜 균등분배로 변경하여 빈공간 제거
[Refactor/#422] 카카오 소셜 로그인 코드 제거
[Feature/#416] 플랫폼 대시보드 누락 기능·상태 UX 보완
[Feature/#424] 구글 소셜 로그인 버튼 연동
YermIm and others added 24 commits August 10, 2026 01:28
[Bugfix/#420] Google OAuth result 콜백 미대응 수정
[Feature/#427] 사이드바 접힘 시 연동 필요 표시
[Refactor/#434] 회원 탈퇴 에러 메세지 매핑 제거
[Refactor/#436] XSS 방어를 위해 downloadAiSummaryPdf에 DOMPurify sanitize 적용
[Refactor/#437] 프로덕션 의존성 보안 취약점 패치 (axios, react-router, vite)
[Refactor/#440] 워크스페이스 삭제 모달 컴포넌트로 분리
@YermIm YermIm self-assigned this Aug 10, 2026
@YermIm YermIm added the 🌏 Deploy 배포 관련 label Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

플랫폼 대시보드에 부분 실패 처리, URL 기반 플랫폼 선택, 차트 다운로드를 추가했습니다. Google OAuth 반환 흐름과 워크스페이스 삭제 모달을 분리했습니다. 공통 컴포넌트와 설정·워크스페이스 화면의 반응형 레이아웃도 변경했습니다.

Changes

플랫폼 대시보드 및 차트 기능

Layer / File(s) Summary
드롭다운 배치 및 타임라인 메뉴 연결
src/components/common/dropdownmenu/DropdownMenu.tsx, src/components/timeline/*, src/pages/dashboard/timeline/Timeline.tsx
드롭다운이 클리핑 영역과 메뉴 크기를 기준으로 상하·좌우 위치를 계산합니다. 타임라인 마지막 행은 메뉴를 위쪽에 배치합니다.
플랫폼 성과 및 선택 상태 처리
src/hooks/dashboard/usePlatformPerformance.ts, src/types/dashboard/platform.ts, src/pages/dashboard/platform/PlatformDashboard.tsx, src/components/dashboard/platform/AllPlatformView.tsx, src/utils/integration/mapPlatformAccounts.ts
부분 실패 결과를 platformsfailedProviders로 분리합니다. 연결되고 만료되지 않은 플랫폼만 URL의 provider와 선택 UI에 사용합니다.
차트 다운로드
src/components/dashboard/platform/platformTrafficChartDownload.config.ts, src/components/dashboard/platform/PlatformTrafficChartDownload.tsx, src/components/dashboard/platform/*TrafficChart.tsx, src/components/dashboard/platform/*PlatformView.tsx
PNG, SVG, CSV 다운로드 항목을 추가하고 차트 및 컨테이너 식별자를 연결합니다.
모바일 ROAS 테이블
src/components/dashboard/platform/PlatformRoasTable.tsx
모바일에서 행을 펼쳐 매출, 광고비, 클릭수, 전환율을 표시합니다. 데스크톱 열 구조는 유지합니다.

Google OAuth 반환 처리

Layer / File(s) Summary
Google OAuth 결과 처리
src/hooks/integration/useGoogleOAuthReturn.ts, src/pages/integration/GoogleOAuthResultPage.tsx, src/routes/AuthRoutes.tsx, src/pages/integration/PlatformIntegrationsPage.tsx
OAuth 결과별 토스트와 중복 처리 방지를 추가합니다. 성공 시 연결 쿼리를 무효화하고 조직별 Google 동기화를 실행합니다.
소셜 로그인 정리
src/pages/auth/Login.tsx, src/pages/auth/Signup.tsx, src/types/auth/auth.ts
Google 로그인 버튼을 활성화하고 Kakao 로그인 타입과 회원가입 버튼을 제거합니다.

워크스페이스 및 공통 UI

Layer / File(s) Summary
워크스페이스 삭제 모달
src/components/workspace/DeleteWorkspaceModal.tsx, src/pages/workspace/WorkspaceSetting.tsx
워크스페이스 이름을 정확히 입력해야 삭제할 수 있습니다. 로딩 중에는 입력과 닫기 동작을 제한합니다.
반응형 설정 및 워크스페이스 화면
src/components/setting/*, src/components/workspace/*, src/pages/workspace/Workspace.tsx, src/components/landing/GuideTimeline.tsx, src/components/timeline/TimelinePerformancePanel.tsx
태블릿 패딩, 세로 배치, 가로 스크롤, 줄바꿈 제어, 동적 타임라인 너비를 적용합니다.
사이드바 연동 주의 상태
src/components/sidebar/Sidebar.tsx, src/components/sidebar/SidebarItem.tsx
연동 주의 상태를 펼침·접힘 사이드바의 배지와 접근성 라벨에 반영합니다.
공통 입력·모달 및 인쇄 처리
src/components/common/select/SearchSelect.tsx, src/components/common/modal/Modal.tsx, src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts
Input 내부 요소 사용으로 검색 UI를 정리하고, 인쇄 마크업을 DOMPurify.sanitize로 정제합니다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested labels: 🌏 Deploy

Suggested reviewers: seojegyeong, jjjsun

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 develop 브랜치의 main 배포 반영이라는 PR의 주요 목적을 명확하게 설명합니다.
Description check ✅ Passed 필수 섹션과 배포 체크 항목을 작성했으며, 이슈·미완성 작업·논의 사항도 N/A로 명시했습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@YermIm
YermIm merged commit 334201a into main Aug 10, 2026
2 of 3 checks passed

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

🧹 Nitpick comments (7)
src/components/workspace/DeleteWorkspaceModal.tsx (2)

10-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

컴포넌트 props 타입 이름을 I*Props 형식으로 변경해 주세요.

TDeleteWorkspaceModalProps는 컴포넌트 props 타입입니다. IDeleteWorkspaceModalProps로 변경해 주세요.

As per coding guidelines, “component props use I*Props.”

🤖 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/workspace/DeleteWorkspaceModal.tsx` around lines 10 - 16,
Rename the component props type TDeleteWorkspaceModalProps to
IDeleteWorkspaceModalProps and update every reference to use the new I*Props
naming convention.

Source: Coding guidelines


5-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

상대 경로 import를 @/ alias로 변경해 주세요.

ButtonInput import는 상대 경로를 사용합니다. 프로젝트의 모든 import에 @/ alias를 사용해 주세요.

As per coding guidelines, “Use @/ alias for all imports.”

🤖 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/workspace/DeleteWorkspaceModal.tsx` around lines 5 - 6, Update
the Button and Input imports in DeleteWorkspaceModal to use the project’s `@/`
alias instead of relative paths, while preserving the existing imported symbols.

Source: Coding guidelines

src/hooks/integration/useGoogleOAuthReturn.ts (2)

38-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

렌더 중에 ref를 변경하고 있습니다.

39행은 렌더 단계에서 onConnectSuccessRef.current에 값을 씁니다. 렌더 함수는 순수해야 합니다. React 19의 동시 렌더링에서는 렌더가 버려지거나 재실행될 수 있으므로 이 패턴은 권장되지 않습니다.

별도의 effect에서 할당하도록 바꿔 주세요.

♻️ 수정안
   const onConnectSuccessRef = useRef(options?.onConnectSuccess);
-  onConnectSuccessRef.current = options?.onConnectSuccess;
+
+  useEffect(() => {
+    onConnectSuccessRef.current = options?.onConnectSuccess;
+  }, [options?.onConnectSuccess]);

동작 순서는 유지됩니다. 이 대입 effect가 처리용 effect보다 먼저 등록되기 때문입니다.

🤖 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/integration/useGoogleOAuthReturn.ts` around lines 38 - 39, Move the
onConnectSuccessRef.current assignment out of render and into a separate effect
within the hook containing onConnectSuccessRef. Register this synchronization
effect before the effect that handles the connection result so the existing
callback execution order is preserved.

41-41: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

useLayoutEffect 대신 useEffect를 사용해 주세요.

이 effect는 DOM 레이아웃을 읽거나 쓰지 않습니다. 토스트 표시, 쿼리 무효화, 라우팅만 수행합니다. useLayoutEffect는 브라우저 페인트를 차단하므로 이 작업에는 불필요합니다. 서버 렌더링 환경에서는 경고도 발생합니다.

♻️ 수정안
-import { useLayoutEffect, useRef } from "react";
+import { useEffect, useRef } from "react";
-  useLayoutEffect(() => {
+  useEffect(() => {
🤖 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/integration/useGoogleOAuthReturn.ts` at line 41,
useGoogleOAuthReturn의 effect를 useLayoutEffect에서 useEffect로 변경하세요. 토스트 표시, 쿼리
무효화, 라우팅 동작과 의존성은 그대로 유지하고, import도 일반 effect 훅에 맞게 정리하세요.

Source: Path instructions

src/pages/dashboard/platform/PlatformDashboard.tsx (1)

41-128: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

URL provider 동기화 로직을 커스텀 훅으로 분리하는 것을 검토해 주세요.

현재 페이지 컴포넌트가 다음 책임을 모두 가집니다. 쿼리 파라미터 파싱, 연결 목록 조회, 사용 가능 provider 필터링, 선택 상태 파생, URL 정리 effect, 헤더 주입 effect입니다. 페이지가 렌더링보다 상태 조율에 더 많은 코드를 쓰고 있습니다.

useSelectedPlatform() 같은 훅으로 47-128행을 옮기면 페이지는 selectedPlatform, platformItems, isPlatformSelectDisabled만 소비하면 됩니다. 테스트도 훅 단위로 가능해집니다.

참고로 로직 자체는 정확합니다. 120-128행의 정리 effect는 provider 파라미터를 제거한 뒤 다음 실행에서 122행 early return으로 종료되므로 루프는 발생하지 않습니다.

🤖 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 41 - 128,
Extract the provider URL synchronization and selection-state logic from
PlatformDashboard into a dedicated useSelectedPlatform-style hook, including
query parsing, connection filtering, derived selectedPlatform, URL cleanup, and
platformItems creation. Keep the existing behavior unchanged, including the
invalid-provider cleanup effect and its loop-safe early returns, so
PlatformDashboard only consumes selectedPlatform, platformItems, and
isPlatformSelectDisabled.

Source: Path instructions

src/hooks/dashboard/usePlatformPerformance.ts (1)

32-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

부분 실패 처리 로직은 정확합니다. 실패 원인 관측만 보강하면 좋겠습니다.

Promise.allSettled는 입력 순서를 유지하므로 PROVIDERS[index] 매핑은 안전합니다. 전부 실패했을 때만 throw 하는 분기도 소비자(AllPlatformView)의 isPerformanceError 처리와 맞습니다.

다만 지금은 실패한 provider의 result.reason이 완전히 버려집니다. 운영 중 특정 플랫폼만 계속 실패할 때 원인 추적이 어렵습니다. 실패 사유를 콘솔 또는 로깅 계층으로 남기는 것을 검토해 주세요.

♻️ 실패 사유 로깅 예시
         } else {
+          if (import.meta.env.DEV) {
+            console.warn(`[platform-performance] ${provider} 조회 실패`, result.reason);
+          }
           failedProviders.push(provider);
         }
🤖 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/usePlatformPerformance.ts` around lines 32 - 48, In the
settled-results loop of the platform performance hook, preserve the existing
failedProviders mapping and all-failed error behavior while recording each
rejected result’s reason through the project’s established console or logging
mechanism, including the associated provider from PROVIDERS[index].
src/components/dashboard/platform/AllPlatformView.tsx (1)

44-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

PLATFORM_LOGOS와 카드 헤더 마크업이 PlatformDetailCard.tsx와 중복됩니다.

src/components/dashboard/platform/PlatformDetailCard.tsx에도 동일한 이름의 PLATFORM_LOGOS 상수와 동일한 헤더 블록(로고 + PLATFORM_MAP[provider] 제목)이 있습니다. 지금은 로고 매핑을 두 곳에서 관리해야 합니다. 플랫폼을 추가하거나 로고 크기를 바꿀 때 한쪽만 수정되어 화면이 어긋날 수 있습니다.

로고 매핑과 카드 헤더를 공용 모듈로 분리하는 것을 검토해 주세요. 예를 들어 platformLogos.tsx로 상수를 빼고, 헤더를 PlatformCardHeader 컴포넌트로 추출하면 두 카드가 같은 소스를 사용합니다.

🤖 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/AllPlatformView.tsx` around lines 44 - 64,
Extract the duplicated PLATFORM_LOGOS mapping from PlatformDetailErrorCard and
PlatformDetailCard into a shared platformLogos module, then update both cards to
consume it. Extract the shared logo-and-PLATFORM_MAP[provider] header markup
into a reusable PlatformCardHeader component and replace each card’s inline
header while preserving the existing styling and provider-specific rendering.
🤖 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/select/SearchSelect.tsx`:
- Around line 115-116: SearchSelect의 containerClassName과 inputClassName을 Input의
실제 클래스 매핑에 맞게 조정하세요. containerClassName에서 중복된 placeholder 스타일을 제거하거나 명시적으로
재정의하고, inputClassName 또는 wrapper의 focus-within 스타일에 키보드 포커스를 식별할 수 있는 ring 등의
표시를 추가하세요. 기존 outline-none 및 border-none과 충돌하지 않도록 focus:border-info-blue만으로
포커스를 표현하지 말고, 변경 범위는 해당 클래스 설정으로 제한하세요.

In `@src/components/dashboard/platform/AllPlatformView.tsx`:
- Around line 85-87: Stabilize the empty fallback array used by failedProviders
so its reference does not change between renders. Import and apply useMemo
around the failedProviders derivation in AllPlatformView, preserving the
existing populated-data behavior and ensuring the value passed to resetKeys
remains referentially stable.

In `@src/components/dashboard/platform/platformTrafficChartDownload.config.ts`:
- Around line 15-16: Update todayStamp so the filename date is built from the
local Date year, month, and day values rather than toISOString(), preserving the
YYYY-MM-DD format with zero-padded month and day.

In `@src/components/dashboard/platform/PlatformTrafficChartDownload.tsx`:
- Around line 14-21: Rename the component props type from
TPlatformTrafficChartDownloadProps to IPlatformTrafficChartDownloadProps, and
change platform to use the existing TProviderType instead of string. Update
getPlatformTrafficFilename’s signature if needed so it accepts the same
TProviderType, while preserving the existing optional platform behavior.

In `@src/components/dashboard/platform/PlatformViewSwitcher.tsx`:
- Around line 67-80: Update the disabled button in PlatformViewSwitcher to
provide an aria-label that includes the reason platform selection is
unavailable, such as the absence of connected platforms, alongside the existing
selected-platform context. Remove the redundant aria-disabled attribute while
preserving the native disabled state and visible label.

In `@src/components/setting/NotificationSection.tsx`:
- Line 269: Update the wrapperClassName for the Discord Webhook input to replace
the invalid tablet:w-fill utility with the valid tablet:w-full class, preserving
the existing width and shrink classes.

In `@src/components/workspace/DeleteWorkspaceModal.tsx`:
- Line 79: DeleteWorkspaceModal의 aria-label에서 잘못된 “우커스페이스” 표기를 “워크스페이스”로 수정해 스크린
리더에 올바른 레이블이 전달되도록 하세요.

In `@src/hooks/integration/useGoogleOAuthReturn.ts`:
- Around line 43-63: Update the OAuth result guard in the hook around
isTokenInitialized so both success and failure processing wait while orgId is
null, without marking processedRef or navigating to /integrations. Once orgId is
available, preserve the existing finish flow; ensure syncGoogle and query
invalidation run only for successful results, while failures can complete
without those success actions.

In `@src/pages/workspace/Workspace.tsx`:
- Around line 299-301: Update the conditional error message paragraph rendering
createErrorMsg in Workspace.tsx to include role="alert", ensuring screen readers
announce the mutation failure when the message appears.

In `@src/pages/workspace/WorkspaceSetting.tsx`:
- Around line 427-438: Remove the parent WorkspaceSetting deleteConfirmInput
state and its validation from onDelete, since DeleteWorkspaceModal owns
confirmInput and only invokes onConfirm when canDelete is satisfied. Keep
onDelete responsible for executing the deletion mutation, and preserve the
existing DeleteWorkspaceModal callbacks and loading behavior.

---

Nitpick comments:
In `@src/components/dashboard/platform/AllPlatformView.tsx`:
- Around line 44-64: Extract the duplicated PLATFORM_LOGOS mapping from
PlatformDetailErrorCard and PlatformDetailCard into a shared platformLogos
module, then update both cards to consume it. Extract the shared
logo-and-PLATFORM_MAP[provider] header markup into a reusable PlatformCardHeader
component and replace each card’s inline header while preserving the existing
styling and provider-specific rendering.

In `@src/components/workspace/DeleteWorkspaceModal.tsx`:
- Around line 10-16: Rename the component props type TDeleteWorkspaceModalProps
to IDeleteWorkspaceModalProps and update every reference to use the new I*Props
naming convention.
- Around line 5-6: Update the Button and Input imports in DeleteWorkspaceModal
to use the project’s `@/` alias instead of relative paths, while preserving the
existing imported symbols.

In `@src/hooks/dashboard/usePlatformPerformance.ts`:
- Around line 32-48: In the settled-results loop of the platform performance
hook, preserve the existing failedProviders mapping and all-failed error
behavior while recording each rejected result’s reason through the project’s
established console or logging mechanism, including the associated provider from
PROVIDERS[index].

In `@src/hooks/integration/useGoogleOAuthReturn.ts`:
- Around line 38-39: Move the onConnectSuccessRef.current assignment out of
render and into a separate effect within the hook containing
onConnectSuccessRef. Register this synchronization effect before the effect that
handles the connection result so the existing callback execution order is
preserved.
- Line 41: useGoogleOAuthReturn의 effect를 useLayoutEffect에서 useEffect로 변경하세요. 토스트
표시, 쿼리 무효화, 라우팅 동작과 의존성은 그대로 유지하고, import도 일반 effect 훅에 맞게 정리하세요.

In `@src/pages/dashboard/platform/PlatformDashboard.tsx`:
- Around line 41-128: Extract the provider URL synchronization and
selection-state logic from PlatformDashboard into a dedicated
useSelectedPlatform-style hook, including query parsing, connection filtering,
derived selectedPlatform, URL cleanup, and platformItems creation. Keep the
existing behavior unchanged, including the invalid-provider cleanup effect and
its loop-safe early returns, so PlatformDashboard only consumes
selectedPlatform, platformItems, and isPlatformSelectDisabled.
🪄 Autofix

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 Plus

Run ID: ba9f3e73-f847-47e8-99ce-22a2a4816cd7

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd3523 and 38f4154.

⛔ Files ignored due to path filters (4)
  • README.md is excluded by none and included by none
  • package.json 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/logo/social-logo/plain/kakao.svg is excluded by !**/*.svg and included by src/**
📒 Files selected for processing (44)
  • src/components/common/dropdownmenu/DropdownMenu.tsx
  • src/components/common/modal/Modal.tsx
  • src/components/common/select/SearchSelect.tsx
  • src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts
  • src/components/dashboard/platform/AllPlatformTrafficChart.tsx
  • src/components/dashboard/platform/AllPlatformView.tsx
  • src/components/dashboard/platform/PlatformRoasTable.tsx
  • src/components/dashboard/platform/PlatformTrafficChart.tsx
  • src/components/dashboard/platform/PlatformTrafficChartDownload.tsx
  • src/components/dashboard/platform/PlatformViewSwitcher.tsx
  • src/components/dashboard/platform/SinglePlatformView.tsx
  • src/components/dashboard/platform/platformTrafficChartDownload.config.ts
  • src/components/landing/GuideTimeline.tsx
  • src/components/setting/NotificationSection.tsx
  • src/components/setting/PasswordSection.tsx
  • src/components/setting/ProfileSection.tsx
  • src/components/sidebar/Sidebar.tsx
  • src/components/sidebar/SidebarItem.tsx
  • src/components/sidebar/WorkspaceSwitcher.tsx
  • src/components/timeline/TimelineBar.tsx
  • src/components/timeline/TimelinePerformancePanel.tsx
  • src/components/workspace/DeleteWorkspaceModal.tsx
  • src/components/workspace/MemberItem.tsx
  • src/components/workspace/MemberSearchSelect.tsx
  • src/components/workspace/PermissionTable.tsx
  • src/components/workspace/TransferOwnerModal.tsx
  • src/hooks/auth/useDeleteMyAccount.ts
  • src/hooks/auth/useEmailVerification.ts
  • src/hooks/dashboard/usePlatformPerformance.ts
  • src/hooks/integration/useGoogleOAuthReturn.ts
  • src/hooks/integration/useIntegrationOAuthReturn.ts
  • src/pages/auth/Login.tsx
  • src/pages/auth/Signup.tsx
  • src/pages/dashboard/platform/PlatformDashboard.tsx
  • src/pages/dashboard/timeline/Timeline.tsx
  • src/pages/integration/GoogleOAuthResultPage.tsx
  • src/pages/integration/PlatformIntegrationsPage.tsx
  • src/pages/workspace/Workspace.tsx
  • src/pages/workspace/WorkspaceSetting.tsx
  • src/routes/AuthRoutes.tsx
  • src/styles/tokens.css
  • src/types/auth/auth.ts
  • src/types/dashboard/platform.ts
  • src/utils/integration/mapPlatformAccounts.ts
💤 Files with no reviewable changes (6)
  • src/pages/auth/Signup.tsx
  • src/styles/tokens.css
  • src/hooks/integration/useIntegrationOAuthReturn.ts
  • src/components/common/modal/Modal.tsx
  • src/hooks/auth/useEmailVerification.ts
  • src/pages/integration/PlatformIntegrationsPage.tsx

Comment on lines +115 to +116
containerClassName="h-13 w-full rounded-2xl border border-info-blue font-body1 outline-none transition-colors placeholder:text-text-placeholder focus:border-info-blue"
inputClassName="truncate"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -i 'input' src -t f
rg -n -C 12 'containerClassName|inputClassName|rightElement' src --glob '*.tsx'

Repository: WhereYouAd/WhereYouAd-Frontend

Length of output: 40624


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,100p' src/components/common/input/Input.tsx
sed -n '1,130p' src/components/common/select/SearchSelect.tsx

Repository: WhereYouAd/WhereYouAd-Frontend

Length of output: 6833


SearchSelect의 포커스/placeholder 클래스 위치를 Input 매핑에 맞게 조정해 주세요.

containerClassName은 wrapper div에 적용되고, <input>에 들어가는 기본 클래스에도 outline-none, border-none, placeholder:text-text-placeholder가 이미 있습니다. 현재 placeholder:text-text-placeholder는 중복되고, focus:border-info-blue는 포커스 visible 상태를 표시하지 않습니다. placeholder:*를 없애거나 명시적으로 재지정하고, 포커스 스타일은 inputClassName이나 wrapper의 focus-within:*에서 키보드 사용자를 위한 표시(ring 등)와 함께 적용해 주세요. border-info-blue만 있으면 기존 내부 outline-none/border-none과 어울리지 않을 수 있습니다.

🤖 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/select/SearchSelect.tsx` around lines 115 - 116,
SearchSelect의 containerClassName과 inputClassName을 Input의 실제 클래스 매핑에 맞게 조정하세요.
containerClassName에서 중복된 placeholder 스타일을 제거하거나 명시적으로 재정의하고, inputClassName 또는
wrapper의 focus-within 스타일에 키보드 포커스를 식별할 수 있는 ring 등의 표시를 추가하세요. 기존 outline-none
및 border-none과 충돌하지 않도록 focus:border-info-blue만으로 포커스를 표현하지 말고, 변경 범위는 해당 클래스
설정으로 제한하세요.

Source: Path instructions

Comment on lines +85 to +87
const platformPerformance = performanceData?.platforms;
const failedProviders = performanceData?.failedProviders ?? [];
const hasPartialFailure = failedProviders.length > 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

failedProviders가 매 렌더마다 새 배열이 되어 resetKeys가 항상 바뀝니다.

86행의 performanceData?.failedProviders ?? []는 데이터가 없을 때 매 렌더마다 새로운 빈 배열을 만듭니다. 이 값이 221행 resetKeys에 그대로 들어갑니다. react-error-boundaryresetKeys를 참조 비교(Object.is)로 판단합니다. 따라서 하위 트리에서 에러가 발생하면 다음 렌더에서 곧바로 경계가 리셋되고, 같은 에러가 다시 발생하는 루프가 생길 수 있습니다. 폴백 UI가 표시되지 않고 화면이 깜빡이거나 멈출 위험이 있습니다.

useMemo로 참조를 안정화해 주세요.

🐛 참조 안정화 수정안
   const platformPerformance = performanceData?.platforms;
-  const failedProviders = performanceData?.failedProviders ?? [];
+  const failedProviders = useMemo(
+    () => performanceData?.failedProviders ?? [],
+    [performanceData],
+  );
   const hasPartialFailure = failedProviders.length > 0;

파일 상단에 useMemo import가 필요합니다.

-import type { ReactNode } from "react";
+import { type ReactNode, useMemo } from "react";

Also applies to: 221-221

🤖 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/AllPlatformView.tsx` around lines 85 - 87,
Stabilize the empty fallback array used by failedProviders so its reference does
not change between renders. Import and apply useMemo around the failedProviders
derivation in AllPlatformView, preserving the existing populated-data behavior
and ensuring the value passed to resetKeys remains referentially stable.

Source: Path instructions

Comment on lines +15 to +16
function todayStamp() {
return new Date().toISOString().slice(0, 10);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

로컬 날짜로 파일명을 생성하세요.

Line 16의 toISOString()은 UTC 날짜를 반환합니다. 한국 시간 기준 오전 9시 이전에는 파일명 날짜가 전날로 저장됩니다. 로컬 연도·월·일로 날짜 문자열을 생성하세요.

수정 예시
 function todayStamp() {
-  return new Date().toISOString().slice(0, 10);
+  const date = new Date();
+  return [
+    date.getFullYear(),
+    String(date.getMonth() + 1).padStart(2, "0"),
+    String(date.getDate()).padStart(2, "0"),
+  ].join("-");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function todayStamp() {
return new Date().toISOString().slice(0, 10);
function todayStamp() {
const date = new Date();
return [
date.getFullYear(),
String(date.getMonth() + 1).padStart(2, "0"),
String(date.getDate()).padStart(2, "0"),
].join("-");
}
🤖 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/platformTrafficChartDownload.config.ts`
around lines 15 - 16, Update todayStamp so the filename date is built from the
local Date year, month, and day values rather than toISOString(), preserving the
YYYY-MM-DD format with zero-padded month and day.

Comment on lines +14 to +21
type TPlatformTrafficChartDownloadProps = {
/** 개별 보기: 플랫폼 코드 (GOOGLE 등). 없으면 전체보기 */
platform?: string;
};

export default function PlatformTrafficChartDownload({
platform,
}: TPlatformTrafficChartDownloadProps) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

props 타입 이름과 platform 타입을 규칙에 맞춰 주세요.

두 가지를 정리해 주세요.

첫째, 코딩 가이드라인은 컴포넌트 props 타입에 I*Props 접두사를 요구합니다. 현재는 TPlatformTrafficChartDownloadProps입니다.

둘째, platform?: string은 너무 넓습니다. 호출부인 SinglePlatformView.tsx 167행은 TProviderType 값을 전달합니다. string으로 두면 오타나 잘못된 코드가 컴파일 단계에서 걸러지지 않고, getPlatformTrafficFilename(platform)에 임의 문자열이 들어갈 수 있습니다.

♻️ 타입 정리 수정안
+import type { TProviderType } from "`@/types/dashboard/provider`";
+
-type TPlatformTrafficChartDownloadProps = {
+interface IPlatformTrafficChartDownloadProps {
   /** 개별 보기: 플랫폼 코드 (GOOGLE 등). 없으면 전체보기 */
-  platform?: string;
-};
+  platform?: TProviderType;
+}
 
 export default function PlatformTrafficChartDownload({
   platform,
-}: TPlatformTrafficChartDownloadProps) {
+}: IPlatformTrafficChartDownloadProps) {

getPlatformTrafficFilename의 시그니처도 함께 확인해 주세요.

코딩 가이드라인의 "component props use I*Props" 규칙을 따랐습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type TPlatformTrafficChartDownloadProps = {
/** 개별 보기: 플랫폼 코드 (GOOGLE 등). 없으면 전체보기 */
platform?: string;
};
export default function PlatformTrafficChartDownload({
platform,
}: TPlatformTrafficChartDownloadProps) {
import type { TProviderType } from "`@/types/dashboard/provider`";
interface IPlatformTrafficChartDownloadProps {
/** 개별 보기: 플랫폼 코드 (GOOGLE 등). 없으면 전체보기 */
platform?: TProviderType;
}
export default function PlatformTrafficChartDownload({
platform,
}: IPlatformTrafficChartDownloadProps) {
🤖 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/PlatformTrafficChartDownload.tsx` around
lines 14 - 21, Rename the component props type from
TPlatformTrafficChartDownloadProps to IPlatformTrafficChartDownloadProps, and
change platform to use the existing TProviderType instead of string. Update
getPlatformTrafficFilename’s signature if needed so it accepts the same
TProviderType, while preserving the existing optional platform behavior.

Source: Coding guidelines

Comment on lines +67 to +80
{isPlatformSelectDisabled ? (
<Button
type="button"
size="small"
variant="custom"
disabled
aria-disabled="true"
className={platformTriggerClassName}
>
<span className="truncate font-body1 text-text-muted">
{selectedPlatformLabel}
</span>
<ChevronDownIcon className="ml-2 h-3 w-3 shrink-0 rotate-180 text-text-muted" />
</Button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

비활성 상태의 이유를 보조 기술에 전달해 주세요.

disabled가 설정된 native <button>은 포커스를 받지 못합니다. 따라서 스크린 리더 사용자는 이 버튼의 존재와 비활성 이유를 알기 어렵습니다. 현재 표시 텍스트는 "플랫폼 선택"뿐이라 "연동된 플랫폼이 없어서 선택할 수 없다"는 맥락이 전달되지 않습니다.

aria-label로 이유를 함께 제공해 주세요. disabledaria-disabled를 함께 두면 disabled가 우선하므로 aria-disabled는 제거해도 됩니다.

♿ 접근성 보강 수정안
           <Button
             type="button"
             size="small"
             variant="custom"
             disabled
-            aria-disabled="true"
+            aria-label="연동된 플랫폼이 없어 플랫폼을 선택할 수 없습니다"
             className={platformTriggerClassName}
           >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isPlatformSelectDisabled ? (
<Button
type="button"
size="small"
variant="custom"
disabled
aria-disabled="true"
className={platformTriggerClassName}
>
<span className="truncate font-body1 text-text-muted">
{selectedPlatformLabel}
</span>
<ChevronDownIcon className="ml-2 h-3 w-3 shrink-0 rotate-180 text-text-muted" />
</Button>
{isPlatformSelectDisabled ? (
<Button
type="button"
size="small"
variant="custom"
disabled
aria-label="연동된 플랫폼이 없어 플랫폼을 선택할 수 없습니다"
className={platformTriggerClassName}
>
<span className="truncate font-body1 text-text-muted">
{selectedPlatformLabel}
</span>
<ChevronDownIcon className="ml-2 h-3 w-3 shrink-0 rotate-180 text-text-muted" />
</Button>
🤖 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/PlatformViewSwitcher.tsx` around lines 67 -
80, Update the disabled button in PlatformViewSwitcher to provide an aria-label
that includes the reason platform selection is unavailable, such as the absence
of connected platforms, alongside the existing selected-platform context. Remove
the redundant aria-disabled attribute while preserving the native disabled state
and visible label.

Source: Path instructions

helperText={discordWebhookError}
disabled={channelDisabled || isAnyOrgPending}
wrapperClassName="w-1/4 shrink-0"
wrapperClassName="w-1/4 shrink-0 tablet:w-fill"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --glob '*.{ts,tsx,css}' '\b(?:tablet:)?w-fill\b' src || true
fd -a -g 'tailwind.config.*' -g 'package.json' -g 'vite.config.*' -x \
  rg -n -C 3 'w-fill|`@utility`' || true

Repository: WhereYouAd/WhereYouAd-Frontend

Length of output: 447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Tracked config/package files:\n'
git ls-files | rg '(^|/)(package\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|npm-shrinkwrap\.json|tailwind\.config\.(js|cjs|ts|mjs)|vite\.config\.(js|ts|mjs|cjs)|postcss\.config\.(js|cjs|ts|json)|src/styles/tokens\.css)$' || true

printf '\nRelevant NotificationSection lines:\n'
sed -n '190,275p' src/components/setting/NotificationSection.tsx

printf '\nTailwind config and utility definitions:\n'
git ls-files | rg '(^|/)(tailwind\.config\.(js|cjs|ts|mjs)|postcss\.config\.(js|cjs|ts|json)|package\.json)$' | while read -r f; do
  echo "--- $f"
  rg -n -C 4 'w-fill|`@utility`|theme|screen|screens|utility|responsive|prefix|tailwindcss|postcss|autoprefixer|daisyui' "$f" || true
done

Repository: WhereYouAd/WhereYouAd-Frontend

Length of output: 5056


🌐 Web query:

Tailwind CSS v4 width utility w-fill documentation

💡 Result:

Tailwind CSS v4 does not provide a built-in w-fill utility class [1]. The utility class you may be looking for, fill-*, is specifically intended for styling the fill color of SVG elements, not for setting element widths [2]. If you need an element to fill its container, the standard approach in Tailwind CSS is to use w-full, which sets width: 100% [1][3]. If you specifically require the behavior of width: stretch or width: -webkit-fill-available (sometimes referred to as "fill"), Tailwind does not include this as a default utility [4]. You can implement it using arbitrary values in your HTML or by adding it to your configuration: 1. Arbitrary value syntax: You can apply the style directly in your markup using square brackets: class="w-[stretch]" or class="w-[-webkit-fill-available]" [1]. 2. Custom utility: If you need to reuse this frequently, you can define it in your CSS file using the @utility directive (available in v4) [5]: css `@utility` w-fill { width: stretch; width: -webkit-fill-available; }

Citations:


tablet:w-filltablet:w-full로 바꿔 주세요.

프로젝트에 w-fill 유틸리티가 없고, Tailwind CSS에서 유효한 width 클래스도 아닙니다. 그러니까 태블릿 조건에서 디스코드 Webhook 입력이 기대한 전체 너미가 적용되지 않습니다.

수정 예시
-                    wrapperClassName="w-1/4 shrink-0 tablet:w-fill"
+                    wrapperClassName="w-1/4 shrink-0 tablet:w-full"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
wrapperClassName="w-1/4 shrink-0 tablet:w-fill"
wrapperClassName="w-1/4 shrink-0 tablet:w-full"
🤖 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/NotificationSection.tsx` at line 269, Update the
wrapperClassName for the Discord Webhook input to replace the invalid
tablet:w-fill utility with the valid tablet:w-full class, preserving the
existing width and shrink classes.

Source: Coding guidelines

placeholder="워크스페이스 이름"
autoComplete="off"
disabled={isLoading}
aria-label="우커스페이스 이름 확인 입력"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

aria-label의 워크스페이스 오타를 수정해 주세요.

현재 "우커스페이스 이름 확인 입력"이 스크린 리더에 전달됩니다. "워크스페이스 이름 확인 입력"으로 수정해 주세요.

As per path instructions, “접근성: 시맨틱 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/components/workspace/DeleteWorkspaceModal.tsx` at line 79,
DeleteWorkspaceModal의 aria-label에서 잘못된 “우커스페이스” 표기를 “워크스페이스”로 수정해 스크린 리더에 올바른
레이블이 전달되도록 하세요.

Source: Path instructions

Comment on lines +43 to +63
if (processedRef.current || !isTokenInitialized) return;

const finish = (type: TGoogleOAuthToastType, message: string) => {
if (!toastShownRef.current) {
toastShownRef.current = true;
showGoogleOAuthToast(type, message);

if (orgId != null) {
void queryClient.invalidateQueries({
queryKey: QUERY_KEYS.platform.connections(orgId),
});
}

if (type === "success" && orgId != null) {
onConnectSuccessRef.current?.(orgId);
}
}

processedRef.current = true;
navigate("/integrations", { replace: true });
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# selectedOrgId 초기화 시점과 persist 여부, isTokenInitialized 설정 위치 확인
fd -t f 'useWorkspaceStore.ts' src/store --exec cat -n
rg -n -C4 'isTokenInitialized' src --type=ts --type=tsx
rg -n -C4 'setSelectedOrgId' src --type=ts --type=tsx

Repository: WhereYouAd/WhereYouAd-Frontend

Length of output: 901


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file =="
if [ -f src/hooks/integration/useGoogleOAuthReturn.ts ]; then
  nl -ba src/hooks/integration/useGoogleOAuthReturn.ts | sed -n '1,140p'
else
  fd -t f 'useGoogleOAuthReturn' src -x sh -c 'echo "--- $1"; nl -ba "$1" | sed -n "1,140p"' sh {}
fi

echo
echo "== isTokenInitialized occurrences =="
rg -n -C5 'isTokenInitialized' src || true

echo
echo "== setSelectedOrgId occurrences =="
rg -n -C5 'setSelectedOrgId' src || true

echo
echo "== GOOGLE_OAUTH/QUERY_KEYS/connections occurrences =="
rg -n -C5 'GOOGLE_OAUTH|QUERY_KEYS|connections\(|queryKey:.*connections|platform\(\)' src || true

echo
echo "== workspace store files =="
fd -t f 'workspace|useWorkspace|store' src/store src || true

echo
echo "== TypeScript files (excluding generated/deploy) =="
git ls-files 'src/**' | grep -E '\.(ts|tsx)$' | sed -n '1,120p'

Repository: WhereYouAd/WhereYouAd-Frontend

Length of output: 226


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file =="
if [ -f src/hooks/integration/useGoogleOAuthReturn.ts ]; then
  cat -n src/hooks/integration/useGoogleOAuthReturn.ts | sed -n '1,160p'
else
  fd -t f 'useGoogleOAuthReturn' src -x sh -c 'echo "--- $1"; cat -n "$1" | sed -n "1,160p"' sh {}
fi

echo
echo "== isTokenInitialized occurrences =="
rg -n -C5 'isTokenInitialized' src || true

echo
echo "== setSelectedOrgId occurrences =="
rg -n -C5 'setSelectedOrgId' src || true

echo
echo "== GOOGLE_OAUTH/QUERY_KEYS/connections occurrences =="
rg -n -C5 'GOOGL.*OAUTH|GOOGLE_OAUTH|QUERY_KEYS|connections\(|queryKey:.*connections|platform\(' src || true

echo
echo "== workspace store files =="
fd -t f 'workspace|useWorkspace|store' src/store src || true

echo
echo "== TypeScript files (first 160) =="
git ls-files 'src/**' | grep -E '\.(ts|tsx)$' | sed -n '1,160p'

Repository: WhereYouAd/WhereYouAd-Frontend

Length of output: 50387


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== auth token initialization callers =="
rg -n -C5 'login\(|setAccessToken\(|setTokenInitialized\(' src --glob '*.ts' --glob '*.tsx' || true

echo
echo "== router auth guard =="
cat -n src/routes/Router.tsx | sed -n '1,120p'

echo
echo "== oauth return usages =="
rg -n -C8 'useGoogleOAuthReturn\(|useMetaOAuthReturn\(' src --glob '*.ts' --glob '*.tsx' || true

echo
echo "== auth store exports =="
cat -n src/store/useAuthStore.ts | sed -n '1,120p'

echo
echo "== role guard org fetching behavior =="
cat -n src/routes/RoleGuard.tsx | sed -n '1,160p'

Repository: WhereYouAd/WhereYouAd-Frontend

Length of output: 18398


orgId == null일 때는 OAuth 결과 처리를 보류하세요.

MainLayout에서 selectedOrgId를 채우는 경로는 메인 워크스페이스 목록 조회 이후이고, 이 훅의 43행 가드는 isTokenInitialized만 넘기 때문에 orgId가 아직 준비되지 않은 성공 뒤 syncGoogle과 캐시 무효화가 실행되지 않습니다. 이미 토스트를 표시하고 processedReftrue가 되면 다시 처리하지 못해 사용자는 연동되어도 목록에는 반영되지 않은 상태로 이동합니다.

orgId == null일 때는 성공 결과도 보류해 주세요. 성공 뒤 다시 도달할 수 없는 상태라 OAuth 실패만 누락될 수 있으므로 실패 처리도 함께 고려해야 합니다. workflow에는 워크스페이스가 없으면 MainLayout 자체가 계속 보류하므로, 성공만 보류하고 실패는 곧바로 처리하는 전략은 맞지 않습니다. 실패/성공 모두 orgId == null이면 /integrations 이동 없이 대기하며, 성공 뒤에만 sync/invalidate를 실행하세요.

🤖 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/integration/useGoogleOAuthReturn.ts` around lines 43 - 63, Update
the OAuth result guard in the hook around isTokenInitialized so both success and
failure processing wait while orgId is null, without marking processedRef or
navigating to /integrations. Once orgId is available, preserve the existing
finish flow; ensure syncGoogle and query invalidation run only for successful
results, while failures can complete without those success actions.

Comment on lines 299 to 301
{createErrorMsg && (
<p className="font-body2 text-info-red">{createErrorMsg}</p>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

생성 오류를 스크린 리더에도 전달해 주세요.

createErrorMsg는 mutation 실패 후 새로 표시됩니다. 그러나 Line 299-301의 <p>는 일반 텍스트로만 렌더링됩니다. 스크린 리더 사용자는 오류 상태 변경을 자동으로 알리지 못할 수 있습니다. role="alert"를 추가해 주세요.

As per path instructions: src/** 경로는 "접근성: 시맨틱 HTML, ARIA 속성 사용 확인"을 확인해야 합니다.

수정 예시
-                <p className="font-body2 text-info-red">{createErrorMsg}</p>
+                <p role="alert" className="font-body2 text-info-red">
+                  {createErrorMsg}
+                </p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{createErrorMsg && (
<p className="font-body2 text-info-red">{createErrorMsg}</p>
)}
{createErrorMsg && (
<p role="alert" className="font-body2 text-info-red">
{createErrorMsg}
</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/pages/workspace/Workspace.tsx` around lines 299 - 301, Update the
conditional error message paragraph rendering createErrorMsg in Workspace.tsx to
include role="alert", ensuring screen readers announce the mutation failure when
the message appears.

Source: Path instructions

Comment on lines +427 to +438
<DeleteWorkspaceModal
isOpen={deleteOpen}
onClose={() => {
if (!deleting) {
setDeleteOpen(false);
setDeleteNameSnapshot("");
setDeleteConfirmInput("");
}
}}
title="워크스페이스를 삭제할게요"
size="lg"
disableOverlayClick={deleting}
>
<ModalContent
icon={
<WarnIcon
className="h-7 w-7 text-info-red"
aria-hidden="true"
/>
}
title="워크스페이스를 삭제할게요"
description={
<>
<p>
삭제하면 연결된 모든 데이터가 사라지고, 다시 되돌릴 수
없어요.
</p>
<p className="mt-2.5">
아래 워크스페이스 이름을 그대로 입력해 주세요.
</p>
</>
}
confirmMatchSubheading={false}
confirmMatchText={deleteNameSnapshot}
confirmInput={deleteConfirmInput}
onConfirmInputChange={setDeleteConfirmInput}
confirmMatchInputPlaceholder="워크스페이스 이름"
buttonText="영구 삭제"
onConfirm={() => {
void onDelete();
}}
isLoading={deleting}
variant="danger"
/>
</Modal>
workspaceName={deleteNameSnapshot}
onConfirm={onDelete}
isLoading={deleting}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

삭제 확인 상태를 한 곳에서만 검증해 주세요.

Line 436의 onDelete는 Line 194에서 deleteConfirmInput을 검사합니다. 그러나 새 DeleteWorkspaceModal은 자체 confirmInput만 갱신합니다. 따라서 이름을 올바르게 입력해도 상위 상태는 빈 문자열로 남고, 삭제 mutation이 실행되지 않습니다.

상위 컴포넌트에서 deleteConfirmInput 상태와 해당 검사를 제거해 주세요. DeleteWorkspaceModalcanDelete를 만족할 때만 onConfirm을 호출하도록 이미 보호합니다.

수정 방향
 const onDelete = () => {
   if (orgId === null) return;
-  if (deleteConfirmInput.trim() !== deleteNameSnapshot) {
-    toast.error("워크스페이스 이름이 일치하지 않습니다");
-    return;
-  }
   deleteMutation.mutate(orgId);
 };
🤖 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 427 - 438, Remove the
parent WorkspaceSetting deleteConfirmInput state and its validation from
onDelete, since DeleteWorkspaceModal owns confirmInput and only invokes
onConfirm when canDelete is satisfied. Keep onDelete responsible for executing
the deletion mutation, and preserve the existing DeleteWorkspaceModal callbacks
and loading behavior.

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