Skip to content

Comments

[release] 앱 출시 팝업 릴리즈#1011

Merged
seongwon030 merged 13 commits intomainfrom
develop-fe
Jan 7, 2026
Merged

[release] 앱 출시 팝업 릴리즈#1011
seongwon030 merged 13 commits intomainfrom
develop-fe

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Jan 7, 2026

#️⃣연관된 이슈

ex) #이슈번호, #이슈번호

📝작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지/동영상 첨부 가능)

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 메인 페이지에 앱 다운로드 팝업 추가
    • 기기 플랫폼 자동 감지로 해당 앱 스토어 링크 제공
    • 팝업 7일간 숨김 기능 추가
  • Tests

    • 팝업 기능 및 앱 스토어 링크 유틸리티 단위 테스트 추가

✏️ Tip: You can customize this high-level summary in your review settings.

- Platform 타입 정의 ('iOS' | 'Android' | 'Other')
- getAppStoreLink: 사용자 플랫폼 감지 후 적절한 앱 스토어 링크 반환
- detectPlatform: iOS/Android/기타 플랫폼 감지
- APP_STORE_LINKS: 플랫폼별 앱 스토어 링크 상수
- 테스트 코드 작성 (12개 테스트, 100% 커버리지)
- APP_STORE_LINKS 상수 및 getAppStoreLink 함수 제거
- appStoreLink 유틸의 getAppStoreLink, detectPlatform 사용
- 플랫폼 감지 로직 간소화 및 코드 재사용성 향상
- 모바일에서만 표시되는 앱 출시 안내 팝업 구현
- useDevice 훅을 활용한 반응형 모바일 감지
- localStorage 기반 '다시 보지 않기' 기능
- 플랫폼별 앱 스토어 자동 연결 (iOS/Android)
- Mixpanel 이벤트 트래킹 (팝업 표시, 닫기, 다운로드 클릭)
- 이미지 클릭 시 앱 다운로드 페이지 이동
- 하단 버튼 그룹: 다시 보지 않기 / 닫기
- 배경 클릭으로 팝업 닫기 지원
- 사용자를 50/50 비율로 show_popup/no_popup 그룹에 랜덤 할당
- localStorage로 그룹 정보 영구 저장 (동일 사용자는 항상 같은 그룹)
- show_popup 그룹만 팝업 표시, no_popup 그룹은 컨트롤 그룹
- 모든 Mixpanel 이벤트에 abTestGroup 속성 추가
- MAIN_POPUP_NOT_SHOWN 이벤트 추가 (컨트롤 그룹 트래킹)
- PopupABTestGroup 타입 정의로 다른 A/B 테스트와 구분
- localStorage에 타임스탬프 저장하여 7일 후 자동 재표시
- isPopupHidden 함수로 날짜 기반 팝업 숨김 로직 구현
- POPUP_STORAGE_KEY를 'mainpage_popup_hidden_date'로 변경
- 테스트를 위해 상수 및 유틸 함수 export
- 17개 테스트 케이스 작성 (모두 통과)
  - A/B 테스트 그룹 할당 테스트
  - 7일 기반 팝업 숨김 로직 테스트
  - 통합 시나리오 테스트
- 핵심 로직 90.9% 커버리지 달성
- handleClose에 action 파라미터 추가
- 닫기 방법(close_button, backdrop_click)을 파라미터로 전달
- handleBackdropClick에서 이벤트 중복 트래킹 제거
- 각 닫기 방법이 정확히 한 번씩만 트래킹되도록 개선
- 이미지 로딩 상태(imageLoaded) 추가
- Image 객체로 팝업 이미지 사전 로드
- 이미지 로드 완료 후에만 팝업 표시
- 버튼과 이미지가 동시에 렌더링되어 UX 개선
[feature] 메인화면에 앱 출시 팝업을 추가한다
@seongwon030 seongwon030 requested a review from oesnuj January 7, 2026 02:17
@seongwon030 seongwon030 self-assigned this Jan 7, 2026
@seongwon030 seongwon030 added 💻 FE Frontend 📈 release 릴리즈 배포 labels Jan 7, 2026
@vercel
Copy link

vercel bot commented Jan 7, 2026

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

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Jan 7, 2026 2:17am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

개요

이 PR은 AB 테스트 기능이 포함된 앱 다운로드 팝업 컴포넌트를 추가하고, 플랫폼 감지 및 앱 스토어 링크 유틸리티를 구현하며, 관련 이벤트 추적 상수를 추가합니다.

변경 사항

집단 / 파일(들) 변경 요약
이벤트 추적 상수
frontend/src/constants/eventName.ts
MAIN_POPUP_VIEWED, MAIN_POPUP_NOT_SHOWN, MAIN_POPUP_CLOSED, APP_DOWNLOAD_POPUP_CLICKED 네 개의 새로운 USER_EVENT 키 추가
팝업 컴포넌트 및 스타일
frontend/src/pages/MainPage/components/Popup/Popup.tsx, Popup.styles.ts, Popup.test.tsx
Overlay, ModalContainer, Container, ImageWrapper, PopupImage, ButtonGroup, Button 등의 styled-components 추가; AB 테스트 로직(50/50 분할), localStorage 기반 7일 숨김 기능, 플랫폼별 앱 스토어 링크 열기, Mixpanel 이벤트 추적 구현; 팝업 열고 닫을 때 페이지 스크롤 잠금 관리; 유틸리티 함수(getABTestGroup, isPopupHidden) 및 상수(POPUP_STORAGE_KEY, AB_TEST_KEY, DAYS_TO_HIDE) 내보내기
앱 스토어 유틸리티
frontend/src/utils/appStoreLink.ts, appStoreLink.test.ts
Platform 타입, APP_STORE_LINKS 상수, detectPlatform(), getAppStoreLink() 함수 추가로 userAgent 기반 iOS/Android/Other 플랫폼 감지 및 해당 스토어 링크 반환
메인 페이지 통합
frontend/src/pages/MainPage/MainPage.tsx, Banner.tsx
MainPage에서 Popup 컴포넌트 임포트 및 렌더링; Banner에서 하드코딩된 플랫폼 감지 로직을 detectPlatform(), getAppStoreLink() 유틸리티로 대체

시퀀스 다이어그램

sequenceDiagram
    participant User
    participant MainPage
    participant Popup
    participant Storage as localStorage
    participant Analytics as Analytics/Mixpanel
    participant AppStore as App Store Link

    User->>MainPage: 메인 페이지 방문
    MainPage->>Popup: Popup 컴포넌트 마운트
    
    rect rgb(200, 220, 240)
        note over Popup: AB 테스트 그룹 결정
        Popup->>Storage: AB 테스트 그룹 확인
        alt 저장된 그룹 없음
            Popup->>Storage: 새 그룹 생성 및 저장<br/>(show_popup 또는 no_popup, 50/50)
        end
    end

    rect rgb(220, 240, 200)
        note over Popup: 팝업 표시 여부 결정
        Popup->>Storage: 마지막 숨김 날짜 확인
        alt 7일 이내
            Popup->>Analytics: MAIN_POPUP_NOT_SHOWN 이벤트 전송
            Popup-->>User: 팝업 표시 안 함
        else 7일 이상 또는 저장된 날짜 없음
            Popup->>Analytics: MAIN_POPUP_VIEWED 이벤트 전송
            Popup-->>User: 팝업 표시
        end
    end

    rect rgb(240, 220, 200)
        note over User,Analytics: 사용자 상호작용
        alt 닫기 또는 배경 클릭
            User->>Popup: 팝업 닫기
            Popup->>Storage: 숨김 날짜 저장
            Popup->>Analytics: MAIN_POPUP_CLOSED 이벤트 전송
        else 다운로드 버튼 클릭
            User->>Popup: 다운로드 클릭
            Popup->>Popup: 플랫폼 감지
            Popup->>AppStore: 플랫폼별 스토어 링크 요청
            AppStore-->>Popup: 스토어 URL 반환
            Popup->>Analytics: APP_DOWNLOAD_POPUP_CLICKED 이벤트<br/>(플랫폼, AB 그룹 포함)
            Popup->>User: 앱 스토어 열기
        end
    end

    Popup->>Popup: 페이지 스크롤 복원
Loading

예상 코드 리뷰 시간

🎯 4 (복잡함) | ⏱️ ~45분

관련 가능성 있는 이슈

관련 가능성 있는 PR

제안 라벨

AB TEST

제안 리뷰어

  • lepitaaar
  • suhyun113
  • oesnuj

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 '앱 출시 팝업 릴리즈'로 주요 변경사항(앱 다운로드 팝업 컴포넌트 추가)을 명확히 나타내고 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

Copy link
Member

@oesnuj oesnuj left a comment

Choose a reason for hiding this comment

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

I believe you

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @frontend/src/pages/MainPage/components/Popup/Popup.styles.ts:
- Around line 34-35: Container uses border-radius: 10px while ButtonGroup
applies 16px on bottom corners and Container has overflow: hidden, so the
ButtonGroup's 16px corners will be clipped; make the radii consistent by either
changing ButtonGroup's bottom corner radius to 10px to match Container (symbols:
Container and ButtonGroup) or removing/moving overflow: hidden to an inner
wrapper so ButtonGroup can keep 16px without being clipped; apply the same
change to the other occurrence noted (lines referencing the second
Container/ButtonGroup pair).

In @frontend/src/pages/MainPage/components/Popup/Popup.tsx:
- Around line 34-36: The expression 1000 * 60 * 60 * 24 in the daysSinceHidden
calculation is a magic number; define a named constant (e.g., MS_PER_DAY or
MILLISECONDS_PER_DAY) near the top of the module and replace the inline
multiplication with that constant so the calculation in the daysSinceHidden
expression ((Date.now() - parseInt(hiddenDate)) / MS_PER_DAY) is clear and
self-documenting; keep DAYS_TO_HIDE as-is and ensure the new constant is
exported or scoped appropriately for testing if needed.

In @frontend/src/utils/appStoreLink.test.ts:
- Around line 139-142: The iOS App Store ID in APP_STORE_LINKS is incorrect (the
stored link in frontend/src/utils/appStoreLink.ts uses '675506285' but the test
expects '6755062085'); update the iOS URL value (APP_STORE_LINKS.ios) to use the
correct App Store ID '6755062085' so the link contains both 'itms-apps://' and
the expected 10-digit ID.
🧹 Nitpick comments (5)
frontend/src/utils/appStoreLink.ts (1)

11-33: getAppStoreLinkdetectPlatform 간 중복 로직이 있습니다.

두 함수 모두 동일한 userAgent 파싱 로직과 정규식 패턴(/iphone|ipad|ipod|macintosh/, /android/)을 사용하고 있습니다. 패턴 수정이 필요할 때 한 곳만 수정하고 다른 곳을 놓칠 위험이 있습니다.

🔎 중복 제거를 위한 리팩토링 제안
+const IOS_PATTERN = /iphone|ipad|ipod|macintosh/;
+const ANDROID_PATTERN = /android/;
+
 export const getAppStoreLink = (): string => {
-  const userAgent = navigator.userAgent.toLowerCase();
-
-  if (/iphone|ipad|ipod|macintosh/.test(userAgent)) {
-    return APP_STORE_LINKS.ios;
-  }
-  if (/android/.test(userAgent)) {
-    return APP_STORE_LINKS.android;
-  }
-  return APP_STORE_LINKS.default;
+  const platform = detectPlatform();
+  if (platform === 'iOS') return APP_STORE_LINKS.ios;
+  if (platform === 'Android') return APP_STORE_LINKS.android;
+  return APP_STORE_LINKS.default;
 };
 
 export const detectPlatform = (): Platform => {
   const userAgent = navigator.userAgent.toLowerCase();
 
-  if (/iphone|ipad|ipod|macintosh/.test(userAgent)) {
+  if (IOS_PATTERN.test(userAgent)) {
     return 'iOS';
   }
-  if (/android/.test(userAgent)) {
+  if (ANDROID_PATTERN.test(userAgent)) {
     return 'Android';
   }
   return 'Other';
 };
frontend/src/pages/MainPage/components/Popup/Popup.tsx (1)

123-128: 접근성 개선: role="dialog" 속성 추가를 권장합니다.

aria-modal='true'가 설정되어 있지만, 스크린 리더 호환성을 위해 role="dialog"aria-labelledby 또는 aria-label을 함께 추가하면 좋습니다.

🔎 접근성 속성 추가 제안
     <Styled.Overlay
       isOpen={isOpen}
       onClick={handleBackdropClick}
+      role="dialog"
       aria-modal='true'
+      aria-label='모아동 앱 다운로드 팝업'
     >
frontend/src/pages/MainPage/components/Popup/Popup.styles.ts (3)

2-2: 사용되지 않는 Theme 타입 import를 제거해 주세요.

Theme 타입이 import되었지만 파일 내에서 사용되지 않습니다.

-import { theme, Theme } from '@/styles/theme';
+import { theme } from '@/styles/theme';

16-26: ModalContainerisOpen prop이 사용되지 않습니다.

isOpen prop이 타입에 선언되어 있지만 스타일에서 활용되지 않습니다. 의도된 트랜지션 효과가 있다면 구현하고, 그렇지 않다면 prop을 제거해 주세요.

🔎 prop 제거 또는 트랜지션 구현 제안

옵션 1: prop 제거

-export const ModalContainer = styled.div<{ isOpen: boolean }>`
+export const ModalContainer = styled.div`

옵션 2: 트랜지션 효과 구현

 export const ModalContainer = styled.div<{ isOpen: boolean }>`
   position: relative;
   z-index: ${Z_INDEX.modal};
   max-width: 500px;
   width: 100%;
   max-height: 90vh;
   background: transparent;
   border-radius: 16px;
   overflow: visible;
-  transition: transform 0.2s ease;
+  transform: ${({ isOpen }) => (isOpen ? 'scale(1)' : 'scale(0.95)')};
+  opacity: ${({ isOpen }) => (isOpen ? 1 : 0)};
+  transition: transform 0.2s ease, opacity 0.2s ease;
 `;

33-34: 하드코딩된 색상을 테마 변수로 교체해 주세요.

#ffffff 대신 theme.colors.base.white를 사용하면 테마 일관성을 유지할 수 있습니다.

-  background-color: #ffffff;
+  background-color: ${theme.colors.base.white};
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 43f1b69 and d4f5520.

⛔ Files ignored due to path filters (1)
  • frontend/src/assets/images/popup/app-download.svg is excluded by !**/*.svg
📒 Files selected for processing (8)
  • frontend/src/constants/eventName.ts
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/pages/MainPage/components/Banner/Banner.tsx
  • frontend/src/pages/MainPage/components/Popup/Popup.styles.ts
  • frontend/src/pages/MainPage/components/Popup/Popup.test.tsx
  • frontend/src/pages/MainPage/components/Popup/Popup.tsx
  • frontend/src/utils/appStoreLink.test.ts
  • frontend/src/utils/appStoreLink.ts
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx,js,jsx}: Replace magic numbers with named constants for clarity
Replace complex/nested ternaries with if/else or IIFEs for readability
Assign complex boolean conditions to named variables for explicit meaning
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle)
Use unique and descriptive names for custom wrappers/functions to avoid ambiguity
Define constants near related logic or ensure names link them clearly to avoid silent failures
Break down broad state management into smaller, focused hooks/contexts to reduce coupling

Files:

  • frontend/src/utils/appStoreLink.test.ts
  • frontend/src/constants/eventName.ts
  • frontend/src/pages/MainPage/components/Popup/Popup.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/pages/MainPage/components/Popup/Popup.test.tsx
  • frontend/src/utils/appStoreLink.ts
  • frontend/src/pages/MainPage/components/Banner/Banner.tsx
  • frontend/src/pages/MainPage/components/Popup/Popup.styles.ts
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

Use consistent return types for similar functions/hooks

Files:

  • frontend/src/utils/appStoreLink.test.ts
  • frontend/src/constants/eventName.ts
  • frontend/src/pages/MainPage/components/Popup/Popup.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/pages/MainPage/components/Popup/Popup.test.tsx
  • frontend/src/utils/appStoreLink.ts
  • frontend/src/pages/MainPage/components/Banner/Banner.tsx
  • frontend/src/pages/MainPage/components/Popup/Popup.styles.ts
frontend/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{tsx,jsx}: Abstract complex logic/interactions into dedicated components/HOCs
Separate significantly different conditional UI/logic into distinct components
Colocate simple, localized logic or use inline definitions to reduce context switching
Choose field-level or form-level cohesion based on form requirements when using form libraries like react-hook-form
Use Component Composition instead of Props Drilling to reduce coupling

Files:

  • frontend/src/pages/MainPage/components/Popup/Popup.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/pages/MainPage/components/Popup/Popup.test.tsx
  • frontend/src/pages/MainPage/components/Banner/Banner.tsx
🧠 Learnings (4)
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.

Applied to files:

  • frontend/src/pages/MainPage/MainPage.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{ts,tsx,js,jsx} : Define constants near related logic or ensure names link them clearly to avoid silent failures

Applied to files:

  • frontend/src/utils/appStoreLink.ts
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{ts,tsx,js,jsx} : Replace magic numbers with named constants for clarity

Applied to files:

  • frontend/src/utils/appStoreLink.ts
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Abstract complex logic/interactions into dedicated components/HOCs

Applied to files:

  • frontend/src/pages/MainPage/components/Popup/Popup.styles.ts
🧬 Code graph analysis (4)
frontend/src/utils/appStoreLink.test.ts (1)
frontend/src/utils/appStoreLink.ts (3)
  • detectPlatform (23-33)
  • getAppStoreLink (11-21)
  • APP_STORE_LINKS (1-7)
frontend/src/pages/MainPage/components/Popup/Popup.tsx (2)
frontend/src/constants/eventName.ts (1)
  • USER_EVENT (1-44)
frontend/src/utils/appStoreLink.ts (2)
  • getAppStoreLink (11-21)
  • detectPlatform (23-33)
frontend/src/pages/MainPage/components/Banner/Banner.tsx (1)
frontend/src/utils/appStoreLink.ts (1)
  • detectPlatform (23-33)
frontend/src/pages/MainPage/components/Popup/Popup.styles.ts (2)
frontend/src/styles/zIndex.ts (1)
  • Z_INDEX (1-6)
frontend/src/styles/theme/index.ts (1)
  • theme (4-7)
🔇 Additional comments (8)
frontend/src/constants/eventName.ts (1)

5-9: LGTM! 이벤트 상수가 잘 정의되었습니다.

팝업 관련 이벤트 상수들이 기존 네이밍 컨벤션을 잘 따르고 있으며, 주석으로 명확하게 구분되어 있습니다. A/B 테스트와 팝업 분석에 필요한 모든 이벤트를 포함하고 있습니다.

frontend/src/pages/MainPage/MainPage.tsx (1)

11-11: LGTM! Popup 컴포넌트 통합이 깔끔합니다.

Popup 컴포넌트를 최상단에 배치하여 오버레이/모달 동작을 보장하고 있습니다. Props drilling 없이 컴포넌트 조합 패턴을 잘 따르고 있습니다.

Also applies to: 55-55

frontend/src/utils/appStoreLink.test.ts (1)

1-153: 테스트 커버리지가 훌륭합니다.

플랫폼 감지, 앱 스토어 링크 반환, 상수 검증, 통합 시나리오까지 포괄적으로 테스트하고 있습니다. Navigator mocking 패턴도 올바르게 구현되었습니다.

frontend/src/pages/MainPage/components/Banner/Banner.tsx (1)

11-11: LGTM! 앱 스토어 링크 로직을 잘 리팩토링했습니다.

플랫폼 감지 및 링크 결정 로직을 중앙화된 유틸리티로 분리하여 단일 책임 원칙(SRP)과 DRY 원칙을 잘 따르고 있습니다. 테스트 가능성도 향상되었습니다.

Also applies to: 37-46

frontend/src/pages/MainPage/components/Popup/Popup.test.tsx (1)

1-156: LGTM! Popup 유틸리티 테스트가 매우 포괄적입니다.

A/B 테스트 그룹 할당, 팝업 숨김 상태 관리, 시간 경계 조건, 통합 시나리오까지 모든 주요 기능을 테스트하고 있습니다. 특히:

  • 50/50 분포 테스트는 통계적으로 적절한 허용 범위(40-60%)를 사용합니다
  • 7일 경계 조건을 정확히 검증합니다
  • localStorage 모킹 패턴이 올바릅니다
frontend/src/utils/appStoreLink.ts (1)

14-16: macintosh 패턴 포함 여부를 확인해 주세요.

macOS 데스크톱 Safari 사용자도 iOS로 감지되어 itms-apps:// 스킴 링크로 연결됩니다. 팝업이 모바일 전용이라면 문제없지만, 이 유틸리티가 다른 곳(예: Banner)에서도 사용되면 데스크톱 Mac 사용자에게 의도치 않은 동작이 발생할 수 있습니다.

frontend/src/pages/MainPage/components/Popup/Popup.tsx (2)

45-50: 이미지 로드 실패 시 팝업이 깨진 이미지와 함께 표시될 수 있습니다.

onerror 핸들러가 imageLoadedtrue로 설정하여 이미지 로드 실패 시에도 팝업이 표시됩니다. 의도된 동작이라면 괜찮지만, 이미지 로드 실패 시 팝업을 숨기려면 별도의 상태 관리가 필요합니다.


52-70: 트래킹 이벤트 중복 발생 우려는 실제로 발생하지 않습니다.

useMixpanelTrack 훅에서 trackEventuseCallback으로 빈 의존성 배열 []과 함께 메모이제이션되어 있어, 함수 참조가 렌더링 간에 안정적으로 유지됩니다. 따라서 effect가 trackEvent 변경으로 인해 재실행되지 않으며, 중복 추적 이벤트 발생 위험은 없습니다.

Likely an incorrect or invalid review comment.

Comment on lines +34 to +35
border-radius: 10px;
`;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

ContainerButtonGroup의 border-radius 불일치를 확인해 주세요.

Containerborder-radius: 10px를 사용하고, ButtonGroup은 하단 모서리에 16px를 사용합니다. Containeroverflow: hidden이 적용되어 있어 ButtonGroup의 16px radius가 10px로 잘릴 수 있습니다.

🔎 border-radius 통일 제안
 export const Container = styled.div`
   display: flex;
   flex-direction: column;
   width: 100%;
   overflow: hidden;
   background-color: #ffffff;
-  border-radius: 10px;
+  border-radius: 16px;
 `;

Also applies to: 60-61

🤖 Prompt for AI Agents
In @frontend/src/pages/MainPage/components/Popup/Popup.styles.ts around lines 34
- 35, Container uses border-radius: 10px while ButtonGroup applies 16px on
bottom corners and Container has overflow: hidden, so the ButtonGroup's 16px
corners will be clipped; make the radii consistent by either changing
ButtonGroup's bottom corner radius to 10px to match Container (symbols:
Container and ButtonGroup) or removing/moving overflow: hidden to an inner
wrapper so ButtonGroup can keep 16px without being clipped; apply the same
change to the other occurrence noted (lines referencing the second
Container/ButtonGroup pair).

Comment on lines +34 to +36
const daysSinceHidden =
(Date.now() - parseInt(hiddenDate)) / (1000 * 60 * 60 * 24);
return daysSinceHidden < DAYS_TO_HIDE;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

매직 넘버를 명명된 상수로 추출해 주세요.

1000 * 60 * 60 * 24의 의미가 명확하지 않습니다. 코딩 가이드라인에 따라 명명된 상수로 정의하는 것이 좋습니다.

🔎 상수 추출 제안
 export const POPUP_STORAGE_KEY = 'mainpage_popup_hidden_date';
 export const AB_TEST_KEY = 'mainpage_popup_ab_group';
 export const DAYS_TO_HIDE = 7;
 const AB_TEST_SPLIT_RATIO = 0.5;
+const MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;
 
 // ...
 
 export const isPopupHidden = (): boolean => {
   const hiddenDate = localStorage.getItem(POPUP_STORAGE_KEY);
   if (!hiddenDate) return false;
 
   const daysSinceHidden =
-    (Date.now() - parseInt(hiddenDate)) / (1000 * 60 * 60 * 24);
+    (Date.now() - parseInt(hiddenDate)) / MILLISECONDS_PER_DAY;
   return daysSinceHidden < DAYS_TO_HIDE;
 };

Based on learnings, 매직 넘버를 명명된 상수로 대체하여 명확성을 높여야 합니다.

📝 Committable suggestion

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

Suggested change
const daysSinceHidden =
(Date.now() - parseInt(hiddenDate)) / (1000 * 60 * 60 * 24);
return daysSinceHidden < DAYS_TO_HIDE;
const daysSinceHidden =
(Date.now() - parseInt(hiddenDate)) / MILLISECONDS_PER_DAY;
return daysSinceHidden < DAYS_TO_HIDE;
🤖 Prompt for AI Agents
In @frontend/src/pages/MainPage/components/Popup/Popup.tsx around lines 34 - 36,
The expression 1000 * 60 * 60 * 24 in the daysSinceHidden calculation is a magic
number; define a named constant (e.g., MS_PER_DAY or MILLISECONDS_PER_DAY) near
the top of the module and replace the inline multiplication with that constant
so the calculation in the daysSinceHidden expression ((Date.now() -
parseInt(hiddenDate)) / MS_PER_DAY) is clear and self-documenting; keep
DAYS_TO_HIDE as-is and ensure the new constant is exported or scoped
appropriately for testing if needed.

Comment on lines +139 to +142
it('iOS 앱 스토어 링크가 올바른 형식이다', () => {
expect(APP_STORE_LINKS.ios).toContain('itms-apps://');
expect(APP_STORE_LINKS.ios).toContain('6755062085');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

앱 스토어 ID에 오타가 있습니다.

Line 141에서 기대하는 앱 ID가 '6755062085' (10자리)인데, 실제 frontend/src/utils/appStoreLink.ts의 iOS 링크는 'itms-apps://itunes.apple.com/app/675506285' (9자리)입니다. '0'이 하나 더 들어가 있어 테스트가 실패할 것입니다.

🔎 수정 제안
     it('iOS 앱 스토어 링크가 올바른 형식이다', () => {
       expect(APP_STORE_LINKS.ios).toContain('itms-apps://');
-      expect(APP_STORE_LINKS.ios).toContain('6755062085');
+      expect(APP_STORE_LINKS.ios).toContain('675506285');
     });
🤖 Prompt for AI Agents
In @frontend/src/utils/appStoreLink.test.ts around lines 139 - 142, The iOS App
Store ID in APP_STORE_LINKS is incorrect (the stored link in
frontend/src/utils/appStoreLink.ts uses '675506285' but the test expects
'6755062085'); update the iOS URL value (APP_STORE_LINKS.ios) to use the correct
App Store ID '6755062085' so the link contains both 'itms-apps://' and the
expected 10-digit ID.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend 📈 release 릴리즈 배포

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants