Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[YS-384] 믹스패널 도입 #86

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open

[YS-384] 믹스패널 도입 #86

wants to merge 8 commits into from

Conversation

eeeyooon
Copy link
Member

@eeeyooon eeeyooon commented Mar 9, 2025

Issue Number

close #81

As-Is

믹스 패널 도입 x

To-Be

믹스 패널 도입

Check List

  • 테스트가 전부 통과되었나요?
  • 모든 commit이 push 되었나요?
  • merge할 branch를 확인했나요?
  • Assignee를 지정했나요?
  • Label을 지정했나요?

Test Screenshot

image

(Optional) Additional Description

믹스 패널 도입 및 PM님의 요청사항 중 일부 trackingEvent만 추가하여 테스트 했습니다.

  • 페이지별 조회수
  • 유저 플로우 (페이지 기준)
  • 권한별 유저 수
  • 참여 방법 조회 수 / 링크 클릭 수 / 링크 복사 수
  • 회원가입 날짜 (이미 회원가입한 경우, 믹스 패널 도입 후 최초 로그인 날짜)

이외에도 믹스 패널에서 기본으로 제공하는 트래킹(최초 방문 시 유입 경로 등)을 확인하였습니다.

서버에선 실행이 되지 않도록 window 객체 접근 가능한지 여부로 분기 처리를 하였고, pathname (ex. /edit/AD31D, /upload 등)으로 page 이름을 등록할 수 있으나 좀 더 명확히 구분하기 위해 metadata 추가 및 document.tilte을 가져와 page 이름으로 기록 되게 하였습니다.

머지 이후 배포 환경에서의 트래킹도 잘 되는지 확인할 예정입니다. 확인이 끝나면 아직 도입 못한 유저 트래킹 목록 중 구현 가능한 트래킹을 추가하겠습니다. 믹스 패널을 처음 사용해봐서 조금 더 공부가 필요합니당,, 수집 중인 데이터 및 조회 방법은 따로 정리해서 팀 전체 공유 드리겠습니다 🙌

📍 .env에 mix panel 토큰을 추가하였습니다. 노션 확인해주세요!

Summary by CodeRabbit

  • 신규 기능
    • 각 페이지(공고 조회, 회원가입, 로그인, 작성 글 목록, 등)의 제목 및 설명을 개선하여 SEO 및 브라우저 표시를 강화했습니다.
    • 콘텐츠 로딩 시 로딩 인디케이터를 추가해 사용자 경험을 향상시켰습니다.
    • 로그인, 로그아웃 및 주요 버튼 클릭 시 인터랙션 이벤트 추적 기능을 강화했습니다.
  • 작업 내역
    • 최신 분석 도구 관련 라이브러리를 업데이트했습니다.
  • 스타일
    • SVG 요소의 속성 네이밍을 개선해 코드 일관성을 확보했습니다.

@eeeyooon eeeyooon added feat New feature or request chore 설정 파일, 라이브러리 설치 labels Mar 9, 2025
@eeeyooon eeeyooon requested a review from rbgksqkr March 9, 2025 09:09
@eeeyooon eeeyooon self-assigned this Mar 9, 2025
Copy link

vercel bot commented Mar 9, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
25th-web-team-2-fe ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 9, 2025 9:09am

Copy link

coderabbitai bot commented Mar 9, 2025

Walkthrough

이번 풀 리퀘스트는 Mixpanel 분석 통합과 페이지 메타데이터 정의를 확장하는 변경 사항을 포함합니다. 여러 레이아웃 파일에 SEO와 페이지 정보를 위한 metadata 상수를 추가했으며, 로그인 및 로그아웃 플로우에 Mixpanel 트래킹 함수(identifyUser, setUserProperties, trackEvent, logoutUser)를 도입하여 사용자 상호작용을 기록합니다. 또한, ExperimentPostCardListContainer 컴포넌트에 로딩 상태 표시를 추가하고, SVG 컴포넌트의 속성 네이밍을 JSX 표준으로 수정하는 등의 변경이 이루어졌습니다.

Changes

파일들 변경 요약
package.json Mixpanel 관련 타입 및 라이브러리(@types/mixpanel-browser, mixpanel-browser) 의존성 추가
src/app/{edit/[post_id], join, login, my-posts, post/[post_id], upload, user/profile}/layout.tsx 각 레이아웃에 SEO/페이지 정보를 위한 metadata 상수(export) 추가 (제목 및 설명 포함)
src/app/login/hooks/useGoogleLoginMutation.ts, src/app/login/hooks/useNaverLoginMutation.ts, src/app/post/[post_id]/components/{ExperimentPostOutline, ParticipationGuideModal}.tsx, src/app/providers.tsx, src/components/Header/Menu.tsx, src/lib/mixpanelClient.ts Mixpanel 초기화 및 트래킹 함수(initMixpanel, trackEvent, identifyUser, setUserProperties, logoutUser) 추가 및 로그인, 로그아웃, 모달 및 페이지 뷰 트래킹 로직 통합
src/app/home/components/ExperimentPostContainer/ExperimentPostCardListContainer/ExperimentPostCardListContainer.tsx 로딩 스피너 및 조건부 렌더링을 통한 컴포넌트 로딩 상태 추가
src/components/Icon/icons/AlertOutlined.tsx SVG 속성 clip-path를 JSX 규칙에 맞게 clipPath로 변경

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LoginHook
    participant Mixpanel
    User->>LoginHook: 로그인 요청
    LoginHook-->>User: 로그인 성공
    LoginHook->>Mixpanel: identifyUser(사용자 이메일) 호출
    LoginHook->>Mixpanel: setUserProperties({ email, role }) 호출
Loading
sequenceDiagram
    participant User
    participant HeaderMenu
    participant Mixpanel
    User->>HeaderMenu: 로그아웃 클릭
    HeaderMenu->>Mixpanel: logoutUser() 호출
    HeaderMenu-->>User: 홈으로 리다이렉션
Loading

Suggested reviewers

  • rbgksqkr

Poem

나는 토끼, 코드 정원 속을 달리네,
메타데이터 꽃피며 길을 밝혀주고,
믹스패널 별빛에 추적의 춤을 추며,
스피너처럼 반짝이는 로딩의 순간,
변화의 바람 속에 함께 뛰노는 우리,
귀여운 발자국 따라 새로운 꿈을~ 🐇✨
hop, hop, 코드와 즐거움이 공존하는 날!

✨ 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

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

🧹 Nitpick comments (8)
src/app/edit/[post_id]/layout.tsx (1)

5-8: 메타데이터 구현이 좋습니다만 설명을 조금 더 구체적으로 개선하세요.

제목과 설명이 동일한 값('그라밋 | 공고 수정')으로 설정되어 있습니다. SEO 최적화를 위해서는 description을 더 구체적으로 작성하는 것이 좋습니다.

export const metadata: Metadata = {
  title: '그라밋 | 공고 수정',
-  description: '그라밋 | 공고 수정',
+  description: '그라밋에서 등록한 실험 공고를 수정할 수 있는 페이지입니다.',
};
src/app/post/[post_id]/components/ExperimentPostOutline/ExperimentPostOutline.tsx (1)

128-136: 이벤트 추적 구현이 잘 되었습니다만 컨텍스트를 더 추가하면 좋을 것 같습니다.

버튼 클릭 시 Mixpanel 이벤트 추적이 적절히 구현되었습니다. 하지만 이벤트 추적 시 현재 조회 중인 포스트 ID와 같은 추가 컨텍스트를 포함하면 더 유용한 분석 데이터를 얻을 수 있을 것입니다.

<button
  className={checkButton()}
  onClick={() => {
    trackEvent('ApplyMethod Interaction', {
      action: 'Click ApplyMethod Modal',
+     post_id: postDetailData.id,
+     post_title: postDetailData.title,
    });
    setIsModalOpen(true);
  }}
>
src/app/login/hooks/useNaverLoginMutation.ts (1)

18-21: 로그인 성공 시 사용자 추적 구현이 잘 되었습니다.

로그인 성공 후 사용자를 식별하고 속성을 설정하는 구현이 적절합니다. 다만, Mixpanel 추적 기능에 오류가 발생하더라도 로그인 기능에 영향을 주지 않도록 에러 처리를 추가하는 것이 좋을 것 같습니다.

if (isRegistered) {
  API.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
  sessionStorage.setItem('refreshToken', refreshToken);
  sessionStorage.setItem('role', memberInfo.role);

-  identifyUser(memberInfo.oauthEmail);
-  setUserProperties({ email: memberInfo.oauthEmail, role: memberInfo.role });
+  try {
+    identifyUser(memberInfo.oauthEmail);
+    setUserProperties({ email: memberInfo.oauthEmail, role: memberInfo.role });
+  } catch (error) {
+    console.error('Mixpanel tracking failed:', error);
+    // 추적 실패해도 로그인 흐름은 계속 진행
+  }

  router.push('/');
  return;
}
src/app/login/hooks/useGoogleLoginMutation.ts (1)

19-21: 사용자 식별 및 속성 설정이 잘 구현되었습니다.

로그인 성공 후 사용자를 식별하고 속성을 설정하는 코드가 적절하게 배치되었습니다. 토큰 설정 후 페이지 리디렉션 전에 Mixpanel 추적 코드를 실행하는 위치가 최적의 위치입니다.

고려해볼 점: 현재는 등록된 사용자만 추적하고 있는데, 미등록 사용자의 회원가입 흐름도 추적하는 것이 분석에 도움이 될 수 있습니다. 필요하다면 26~28라인 사이에 추적 코드를 추가하는 것을 고려해보세요.

src/app/home/components/ExperimentPostContainer/ExperimentPostCardListContainer/ExperimentPostCardListContainer.tsx (1)

28-28: 로딩 상태 변수 이름 명확화 필요

isLoadingisListLoading으로 재명명한 것은 혼동을 방지하는 좋은 시도이지만, 프로퍼티로 받는 isLoading과 구분이 여전히 명확하지 않을 수 있습니다.

보다 명확한 변수명을 고려해보세요:

- isLoading: isListLoading,
+ isLoading: isPostListLoading, // 또는 isFetchingPosts
src/lib/mixpanelClient.ts (3)

1-2: ESLint 규칙 비활성화에 대한 검토가 필요합니다.

전역적으로 ESLint 규칙을 비활성화하는 것은 코드 품질에 영향을 줄 수 있습니다. 특히 @typescript-eslint/no-explicit-any는 TypeScript의 타입 안전성을 저하시킵니다. 필요한 경우에만 특정 라인에 대해 규칙을 비활성화하는 것이 좋습니다.

-/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable no-console */
+// 필요한 경우에만 특정 라인에 대해 규칙 비활성화

1-73: 유틸리티 함수를 추가하여 코드 중복을 줄이는 것이 좋습니다.

각 함수에서 반복되는 조건 검사를 하나의 유틸리티 함수로 추출하여 코드의 가독성과 유지보수성을 높일 수 있습니다.

다음과 같은 유틸리티 함수를 파일 상단에 추가할 수 있습니다:

const isClient = typeof window !== 'undefined';

/**
 * Mixpanel 작업을 안전하게 실행하는 유틸리티 함수
 * @param callback Mixpanel 관련 작업을 수행하는 콜백 함수
 */
const safelyRunMixpanel = (callback: () => void) => {
  if (!isClient) return;
  if (!MIXPANEL_TOKEN) return;
  
  try {
    if (!isMixpanelInitialized) {
      initMixpanel();
    }
    callback();
  } catch (error) {
    console.error('Mixpanel operation error:', error);
  }
};

이후 각 함수에서 이 유틸리티를 사용하여 코드를 간소화할 수 있습니다.


1-73: 자동 페이지 추적 기능 추가를 고려해보세요.

현재 구현은 수동으로 이벤트를 추적하는 방식입니다. Next.js의 라우터 이벤트를 활용하여 페이지 전환을 자동으로 추적하는 기능을 추가하면 유용할 것입니다.

다음과 같은 함수를 추가하여 Next.js 라우터와 통합할 수 있습니다:

/**
 * Next.js 라우터와 Mixpanel을 통합하여 페이지 뷰를 자동으로 추적
 */
export const setupPageViewTracking = () => {
  if (!isClient) return;
  
  // Next.js 13 App Router
  if (typeof window !== 'undefined') {
    const handleRouteChange = (url: string) => {
      trackEvent('Page View', {
        'Page URL': url,
        'Page Title': document.title
      });
    };
    
    // pathname 변경 감지
    let lastPathname = window.location.pathname;
    const observer = new MutationObserver(() => {
      const pathname = window.location.pathname;
      if (pathname !== lastPathname) {
        lastPathname = pathname;
        handleRouteChange(pathname);
      }
    });
    
    observer.observe(document, { subtree: true, childList: true });
    
    // 초기 페이지 로드 시 이벤트 발생
    handleRouteChange(window.location.pathname);
  }
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db5214e and b93aad9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • package.json (1 hunks)
  • src/app/edit/[post_id]/layout.tsx (1 hunks)
  • src/app/home/components/ExperimentPostContainer/ExperimentPostCardListContainer/ExperimentPostCardListContainer.tsx (2 hunks)
  • src/app/join/layout.tsx (1 hunks)
  • src/app/login/hooks/useGoogleLoginMutation.ts (2 hunks)
  • src/app/login/hooks/useNaverLoginMutation.ts (2 hunks)
  • src/app/login/layout.tsx (1 hunks)
  • src/app/my-posts/layout.tsx (1 hunks)
  • src/app/post/[post_id]/components/ExperimentPostOutline/ExperimentPostOutline.tsx (2 hunks)
  • src/app/post/[post_id]/components/ParticipationGuideModal/ParticipationGuideModal.tsx (3 hunks)
  • src/app/post/[post_id]/layout.tsx (1 hunks)
  • src/app/providers.tsx (2 hunks)
  • src/app/upload/layout.tsx (1 hunks)
  • src/app/user/profile/layout.tsx (1 hunks)
  • src/components/Header/Menu.tsx (2 hunks)
  • src/components/Icon/icons/AlertOutlined.tsx (1 hunks)
  • src/lib/mixpanelClient.ts (1 hunks)
🔇 Additional comments (20)
src/components/Icon/icons/AlertOutlined.tsx (1)

15-15: JSX 속성명 표기법 수정 적절함

JSX에서 SVG 속성을 사용할 때 clip-path와 같은 하이픈 형식 대신 clipPath와 같은 카멜케이스를 사용해야 합니다. 이 변경은 React 컴포넌트 내에서 SVG 요소의 표기법을 표준화하는 올바른 접근입니다.

src/app/join/layout.tsx (1)

1-1: SEO 최적화를 위한 메타데이터 추가

메타데이터를 추가하여 SEO 최적화와 페이지 정보를 명확히 한 것은 좋은 변경입니다. 이는 Mixpanel 도입과 함께 사용자 트래킹 개선을 위한 중요한 일부이며, 페이지의 식별과 분석에 도움이 됩니다.

Also applies to: 6-9

src/app/upload/layout.tsx (1)

1-2: SEO 최적화를 위한 메타데이터 추가

실험 공고 등록 페이지에 메타데이터를 추가한 것은 SEO 최적화와 페이지 식별에 도움이 됩니다. Mixpanel과 함께 사용하면 페이지별 사용자 행동을 더 정확히 추적할 수 있어 분석 데이터의 질을 향상시킵니다.

Also applies to: 5-8

package.json (1)

25-25: Mixpanel 라이브러리 의존성 추가

Mixpanel 브라우저 라이브러리와 해당 TypeScript 타입 정의를 추가한 것은 적절합니다. 이를 통해 PR 목표인 사용자 트래킹 및 분석 기능을 구현할 수 있습니다.

한 가지 고려할 점은 보안입니다. Mixpanel과 같은 외부 분석 도구는 사용자 데이터를 수집하므로 개인정보 보호법(GDPR, CCPA 등)을 준수해야 합니다. 추후 사용자 동의 메커니즘이 구현되어 있는지 확인하세요.

Also applies to: 33-33

src/app/post/[post_id]/layout.tsx (1)

1-8: SEO를 위한 메타데이터 추가 적절합니다.

Next.js의 Metadata 타입을 활용하여 페이지의 SEO 메타데이터를 적절하게 정의했습니다. 공고 조회 페이지에 맞는 제목과 설명을 추가한 것은 좋은 접근법입니다.

src/app/login/layout.tsx (1)

1-8: 로그인 페이지 메타데이터 구성이 잘 되었습니다.

Next.js의 Metadata 타입을 사용하여 로그인 페이지에 적합한 메타데이터를 정의했습니다. 제목과 설명이 동일하게 설정되어 있는데, 이는 로그인 페이지의 간단한 성격을 고려하면 적절합니다.

src/app/user/profile/layout.tsx (1)

1-10: 회원 정보 페이지 메타데이터 구성이 잘 되었습니다.

Next.js의 Metadata 타입을 활용하여 회원 정보 페이지의 메타데이터를 적절하게 정의했습니다. 제목과 설명이 페이지 목적에 맞게 설정되어 있습니다.

src/app/my-posts/layout.tsx (2)

1-11: 작성 글 목록 페이지 메타데이터 구성이 잘 되었습니다.

Next.js의 Metadata 타입을 사용하여 작성 글 목록 페이지에 적합한 메타데이터를 정의했습니다. 이는 SEO 최적화에 도움이 될 것입니다.


8-11:

❓ Verification inconclusive

Mixpanel 트래킹 구현을 고려해보세요.

PR 목표에서 언급된 Mixpanel 트래킹 기능이 이 파일에는 구현되어 있지 않습니다. 페이지 뷰와 같은 이벤트를 트래킹하는 코드를 추가하는 것을 고려해보세요.

다른 파일에 Mixpanel 구현이 되어 있는지 확인이 필요합니다:


🏁 Script executed:

#!/bin/bash
# Mixpanel 관련 구현 코드 검색
echo "Searching for Mixpanel imports and usage..."
rg -i "mixpanel" --type ts --type tsx

Length of output: 162


Mixpanel 트래킹 구현 검토 필요

현재 src/app/my-posts/layout.tsx 파일에는 페이지 뷰 등 이벤트 트래킹을 위한 Mixpanel 관련 코드가 구현되어 있지 않습니다. PR 목표에 언급된 Mixpanel 트래킹 기능 도입과 관련하여, 코드베이스 내 다른 파일에서도 Mixpanel 구현을 확인할 필요가 있습니다.

단, 이전 검색 시 tsx 파일 타입 인식 문제로 인해 정확한 결과를 얻지 못했습니다. 수동 재검증을 위해 아래 명령어로 .ts.tsx 파일에서 Mixpanel 관련 사용 여부를 다시 확인해 보시기 바랍니다:

#!/bin/bash
# Mixpanel 관련 구현 재검증: .ts 및 .tsx 파일 검색
echo "Searching for Mixpanel imports and usage in .ts and .tsx files..."
rg -i mixpanel --glob '*.ts' --glob '*.tsx'

검증 결과에 따라 페이지 뷰 등의 이벤트 트래킹 추가 구현이 필요하시면 해당 사항을 반영해 주세요.

src/app/edit/[post_id]/layout.tsx (1)

1-1: Metadata 타입 임포트 적절합니다.

Next.js의 Metadata 타입을 올바르게 가져와서 타입 안전성을 확보했습니다.

src/app/post/[post_id]/components/ExperimentPostOutline/ExperimentPostOutline.tsx (1)

28-28: Mixpanel 이벤트 추적 기능 추가가 잘 되었습니다.

Mixpanel의 trackEvent 함수를 올바르게 가져와 사용자 상호작용을 추적할 준비가 되었습니다.

src/app/providers.tsx (1)

5-7: 필요한 함수와 훅을 올바르게, 간결하게 임포트했습니다.

Mixpanel 기능과 React 훅을 효과적으로 가져왔습니다.

src/app/login/hooks/useNaverLoginMutation.ts (1)

6-6: Mixpanel 사용자 추적 함수 임포트가 올바르게 되었습니다.

Mixpanel 사용자 식별 및 속성 설정 함수를 적절하게 가져왔습니다.

src/components/Header/Menu.tsx (2)

10-10: 로그아웃 추적 기능 구현이 잘 되었네요!

Mixpanel 로그아웃 기능을 가져오는 import 문이 잘 추가되었습니다.


23-23: 로그아웃 시 사용자 추적 구현이 적절합니다.

로그아웃 함수에서 세션 스토리지를 지우기 전에 logoutUser()를 호출하여 사용자의 로그아웃 이벤트를 추적하는 것은 좋은 구현입니다. 리다이렉션 전에 추적 이벤트를 발생시키는 위치가 적절합니다.

src/app/login/hooks/useGoogleLoginMutation.ts (1)

6-6: Mixpanel 사용자 식별 함수 가져오기 적절합니다.

로그인 과정에서 사용자를 식별하고 속성을 설정하기 위한 함수들을 적절히 import 했습니다.

src/app/post/[post_id]/components/ParticipationGuideModal/ParticipationGuideModal.tsx (2)

22-22: Mixpanel 이벤트 추적 함수 가져오기 적절합니다.

사용자 상호작용을 추적하기 위한 trackEvent 함수를 적절히 import 했습니다.


96-101: 링크 클릭 추적 구현이 잘 되었습니다.

외부 링크 클릭을 추적하는 코드가 적절하게 구현되었으며, 이벤트 속성에 링크 URL을 포함시켜 더 구체적인 분석이 가능하도록 한 점이 좋습니다.

src/app/home/components/ExperimentPostContainer/ExperimentPostCardListContainer/ExperimentPostCardListContainer.tsx (2)

12-14: 로딩 상태 UI 구현을 위한 컴포넌트 가져오기 적절합니다.

로딩 상태를 표시하기 위한 UI 컴포넌트와 스타일을 재사용하여 가져오는 것은 좋은 접근 방식입니다.


31-38: 로딩 상태 UI 구현이 잘 되었습니다.

로딩 중 스피너와 텍스트를 표시하는 UI가 깔끔하게 구현되었습니다. 사용자 경험을 향상시키는 좋은 개선사항입니다.

Comment on lines +27 to +34
useEffect(() => {
if (typeof window !== 'undefined') {
const pageTitle = document.title;

trackEvent('Page Viewed', { page: pageTitle });
setUserProperties({ last_visited_page: pageTitle });
}
}, []); //
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

페이지 조회 추적 구현에 개선이 필요합니다.

페이지 조회 이벤트 추적의 기본 구현은 좋지만, 몇 가지 개선이 필요합니다:

  1. 빈 의존성 배열([])을 사용하면 컴포넌트가 마운트될 때만 효과가 실행됩니다. SPA에서 페이지 간 이동 시 컴포넌트는 다시 마운트되지 않으므로 페이지 변경이 추적되지 않습니다.
  2. 34번 줄의 주석(//)은 불필요합니다.

Next.js의 usePathname을 사용하여 경로 변경 시마다 이벤트를 추적하도록 수정하세요:

import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useEffect } from 'react';
+import { usePathname } from 'next/navigation';

import { setUserProperties, trackEvent } from '@/lib/mixpanelClient';
import MSWProvider from '@/mocks/MSWProvider';

function makeQueryClient() {
  return new QueryClient();
}

let browserQueryClient: QueryClient | undefined = undefined;

function getQueryClient() {
  if (isServer) {
    return makeQueryClient();
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
  }
}

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = getQueryClient();
+  const pathname = usePathname();
  
  useEffect(() => {
    if (typeof window !== 'undefined') {
      const pageTitle = document.title;

      trackEvent('Page Viewed', { page: pageTitle, path: pathname });
      setUserProperties({ last_visited_page: pageTitle });
    }
-  }, []); //
+  }, [pathname]);
📝 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
useEffect(() => {
if (typeof window !== 'undefined') {
const pageTitle = document.title;
trackEvent('Page Viewed', { page: pageTitle });
setUserProperties({ last_visited_page: pageTitle });
}
}, []); //
import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { setUserProperties, trackEvent } from '@/lib/mixpanelClient';
import MSWProvider from '@/mocks/MSWProvider';
function makeQueryClient() {
return new QueryClient();
}
let browserQueryClient: QueryClient | undefined = undefined;
function getQueryClient() {
if (isServer) {
return makeQueryClient();
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient();
const pathname = usePathname();
useEffect(() => {
if (typeof window !== 'undefined') {
const pageTitle = document.title;
trackEvent('Page Viewed', { page: pageTitle, path: pathname });
setUserProperties({ last_visited_page: pageTitle });
}
}, [pathname]);
return (
<QueryClientProvider client={queryClient}>
<MSWProvider>{children}</MSWProvider>
<ReactQueryDevtools />
</QueryClientProvider>
);
}

Comment on lines +44 to 47
trackEvent('ApplyMethod Interaction', {
action: 'Link Copied',
});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

클립보드 복사 이벤트 추적 개선 필요

클립보드 복사가 성공적으로 완료된 후에만 이벤트를 추적하도록 코드 위치를 조정하는 것이 좋겠습니다. 현재 구현에서는 복사 작업이 실패해도 이벤트가 기록될 수 있습니다.

다음과 같이 수정해보세요:

  const handleCopyContent = (text: string) => {
    navigator.clipboard.writeText(text).then(() => {
      setIsCopyToastOpen(true);
+     trackEvent('ApplyMethod Interaction', {
+       action: 'Link Copied',
+     });
    });
-
-    trackEvent('ApplyMethod Interaction', {
-      action: 'Link Copied',
-    });
  };

Comment on lines +67 to +72
export const logoutUser = () => {
if (!MIXPANEL_TOKEN) return;

mixpanel.reset();
// console.log('Mixpanel user data reset');
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

logoutUser 함수에 SSR 대응 및 불필요한 주석 제거가 필요합니다.

서버 사이드 체크 및 주석 처리된 콘솔 로그 제거가 필요합니다.

export const logoutUser = () => {
+  if (!isClient) return;
  if (!MIXPANEL_TOKEN) return;

+  try {
    mixpanel.reset();
-  // console.log('Mixpanel user data reset');
+  } catch (error) {
+    console.error('Mixpanel reset error:', error);
+  }
};
📝 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
export const logoutUser = () => {
if (!MIXPANEL_TOKEN) return;
mixpanel.reset();
// console.log('Mixpanel user data reset');
};
export const logoutUser = () => {
if (!isClient) return;
if (!MIXPANEL_TOKEN) return;
try {
mixpanel.reset();
} catch (error) {
console.error('Mixpanel reset error:', error);
}
};

Comment on lines +47 to +53
export const identifyUser = (userId: string) => {
if (!MIXPANEL_TOKEN) return;
mixpanel.identify(userId);
mixpanel.people.set_once({
signup_date: new Date().toISOString(), // 최초 가입 시점 기록
});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

identifyUser 함수에 SSR 대응과 예외 처리가 필요합니다.

서버 사이드 렌더링과 잠재적인 오류 처리를 위한 개선이 필요합니다.

export const identifyUser = (userId: string) => {
+  if (!isClient) return;
  if (!MIXPANEL_TOKEN) return;
+  
+  try {
    mixpanel.identify(userId);
    mixpanel.people.set_once({
      signup_date: new Date().toISOString(), // 최초 가입 시점 기록
    });
+  } catch (error) {
+    console.error('Mixpanel identify error:', error);
+  }
};
📝 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
export const identifyUser = (userId: string) => {
if (!MIXPANEL_TOKEN) return;
mixpanel.identify(userId);
mixpanel.people.set_once({
signup_date: new Date().toISOString(), // 최초 가입 시점 기록
});
};
export const identifyUser = (userId: string) => {
if (!isClient) return;
if (!MIXPANEL_TOKEN) return;
try {
mixpanel.identify(userId);
mixpanel.people.set_once({
signup_date: new Date().toISOString(), // 최초 가입 시점 기록
});
} catch (error) {
console.error('Mixpanel identify error:', error);
}
};

Comment on lines +8 to +23
export const initMixpanel = () => {
if (!MIXPANEL_TOKEN) {
console.warn('Mixpanel token is missing! Check your .env file.');
return;
}

if (!isMixpanelInitialized) {
mixpanel.init(MIXPANEL_TOKEN, {
debug: process.env.NODE_ENV === 'development',
track_pageview: false,
persistence: 'localStorage',
});
isMixpanelInitialized = true;
// console.log('Mixpanel initialized');
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

initMixpanel 함수에 SSR 대응 로직이 필요합니다.

서버 사이드 렌더링 환경에서 안전하게 실행되도록 클라이언트 측 체크가 필요합니다. 또한 주석 처리된 콘솔 로그는 제거하는 것이 좋습니다.

export const initMixpanel = () => {
+  if (!isClient) return;
  
  if (!MIXPANEL_TOKEN) {
    console.warn('Mixpanel token is missing! Check your .env file.');
    return;
  }

  if (!isMixpanelInitialized) {
    mixpanel.init(MIXPANEL_TOKEN, {
      debug: process.env.NODE_ENV === 'development',
      track_pageview: false,
      persistence: 'localStorage',
    });
    isMixpanelInitialized = true;
-    // console.log('Mixpanel initialized');
  }
};
📝 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
export const initMixpanel = () => {
if (!MIXPANEL_TOKEN) {
console.warn('Mixpanel token is missing! Check your .env file.');
return;
}
if (!isMixpanelInitialized) {
mixpanel.init(MIXPANEL_TOKEN, {
debug: process.env.NODE_ENV === 'development',
track_pageview: false,
persistence: 'localStorage',
});
isMixpanelInitialized = true;
// console.log('Mixpanel initialized');
}
};
export const initMixpanel = () => {
if (!isClient) return;
if (!MIXPANEL_TOKEN) {
console.warn('Mixpanel token is missing! Check your .env file.');
return;
}
if (!isMixpanelInitialized) {
mixpanel.init(MIXPANEL_TOKEN, {
debug: process.env.NODE_ENV === 'development',
track_pageview: false,
persistence: 'localStorage',
});
isMixpanelInitialized = true;
}
};

Comment on lines +31 to +42
export const trackEvent = (event: string, properties?: Record<string, string>) => {
if (!MIXPANEL_TOKEN) {
console.warn('Mixpanel Token is missing.');
return;
}

if (!isMixpanelInitialized) {
// console.warn('Mixpanel is not initialized. Initializing now...');
initMixpanel();
}
mixpanel.track(event, properties);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

trackEvent 함수의 타입 제한과 SSR 대응이 필요합니다.

현재 properties는 문자열 값만 허용하나, Mixpanel은 숫자, 불리언, 객체 등 다양한 타입을 지원합니다. 또한 서버 사이드 체크가 필요합니다.

-export const trackEvent = (event: string, properties?: Record<string, string>) => {
+export const trackEvent = (event: string, properties?: Record<string, any>) => {
+  if (!isClient) return;
+
  if (!MIXPANEL_TOKEN) {
    console.warn('Mixpanel Token is missing.');
    return;
  }

  if (!isMixpanelInitialized) {
-    // console.warn('Mixpanel is not initialized. Initializing now...');
    initMixpanel();
  }
  mixpanel.track(event, properties);
};
📝 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
export const trackEvent = (event: string, properties?: Record<string, string>) => {
if (!MIXPANEL_TOKEN) {
console.warn('Mixpanel Token is missing.');
return;
}
if (!isMixpanelInitialized) {
// console.warn('Mixpanel is not initialized. Initializing now...');
initMixpanel();
}
mixpanel.track(event, properties);
};
export const trackEvent = (event: string, properties?: Record<string, any>) => {
if (!isClient) return;
if (!MIXPANEL_TOKEN) {
console.warn('Mixpanel Token is missing.');
return;
}
if (!isMixpanelInitialized) {
initMixpanel();
}
mixpanel.track(event, properties);
};

Comment on lines +59 to +62
export const setUserProperties = (properties: Record<string, string>) => {
if (!MIXPANEL_TOKEN) return;
mixpanel.people.set(properties);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

setUserProperties 함수의 타입 및 에러 처리 개선이 필요합니다.

타입 확장 및 예외 처리, SSR 대응이 필요합니다.

-export const setUserProperties = (properties: Record<string, string>) => {
+export const setUserProperties = (properties: Record<string, any>) => {
+  if (!isClient) return;
  if (!MIXPANEL_TOKEN) return;
+  
+  try {
    mixpanel.people.set(properties);
+  } catch (error) {
+    console.error('Mixpanel set properties error:', error);
+  }
};
📝 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
export const setUserProperties = (properties: Record<string, string>) => {
if (!MIXPANEL_TOKEN) return;
mixpanel.people.set(properties);
};
export const setUserProperties = (properties: Record<string, any>) => {
if (!isClient) return;
if (!MIXPANEL_TOKEN) return;
try {
mixpanel.people.set(properties);
} catch (error) {
console.error('Mixpanel set properties error:', error);
}
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
chore 설정 파일, 라이브러리 설치 feat New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[YS-384] 믹스패널 도입
1 participant