Skip to content

refactor: 전역 트래킹 코드를 dataLayer.push 객체 형태로 변경#48

Merged
kangdy25 merged 2 commits intomainfrom
chore/convert-to-object
Mar 2, 2026
Merged

refactor: 전역 트래킹 코드를 dataLayer.push 객체 형태로 변경#48
kangdy25 merged 2 commits intomainfrom
chore/convert-to-object

Conversation

@kangdy25
Copy link
Collaborator

@kangdy25 kangdy25 commented Mar 2, 2026

🚀 refactor: 전역 트래킹 코드를 dataLayer.push 객체 형태로 변경

📝 변경사항

  • 민아님 요청대로 sendGAEvent가 아닌 dataLayer.push 객체 형태로 변경했습니다.
  • gtm 관련한 로직을 유틸리티로 분리하여 린트 에러를 해결했습니다.

✅ 체크리스트

  • 코드 리뷰를 받았습니다
  • 테스트를 완료했습니다
  • 린터 에러가 없습니다
  • 타입 에러가 없습니다
  • 브라우저에서 테스트를 완료했습니다
  • 모바일에서 테스트를 완료했습니다 (해당되는 경우)

📸 스크린샷

UI 변경 사항이 있다면 이미지를 드래그해서 넣어주세요!

💬 리뷰어 전달사항

  • 리뷰어가 특별히 확인해야 할 사항이 있다면 적어주세요.

Summary by CodeRabbit

릴리스 노트

  • 개선사항
    • 분석 추적 인프라를 업데이트하여 더욱 안정적인 이벤트 기록을 지원합니다.
    • 브라우저 환경에서의 데이터 수집 신뢰성을 강화했습니다.

참고: 이번 업데이트는 내부 기술 개선사항으로, 사용자가 인지할 수 있는 기능 변화는 없습니다.

@vercel
Copy link

vercel bot commented Mar 2, 2026

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

Project Deployment Actions Updated (UTC)
mingling-frontend Ready Ready Preview, Comment Mar 2, 2026 0:34am

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Walkthrough

Google Analytics의 sendGAEvent 호출을 Google Tag Manager의 pushDataLayer 호출로 일관되게 교체하고, 새로운 lib/gtm.ts 모듈을 추가하여 이벤트 추적 인프라를 표준화합니다. 기능적 변화 없이 분석 방식만 변경됩니다.

Changes

Cohort / File(s) Summary
Analytics 인프라 생성
lib/gtm.ts
브라우저 환경에서 window.dataLayer 배열에 데이터를 푸시하는 pushDataLayer 함수 추가.
페이지 레벨 Analytics 교체
app/create/page.tsx, app/meeting/[id]/page.tsx, app/recommend/page.tsx, app/result/[id]/page.tsx
각 페이지에서 sendGAEvent import를 pushDataLayer로 변경하고, 모든 이벤트 추적 호출을 GTM dataLayer 형식으로 업데이트. app/result/[id]/page.tsx에서 midpoint_candidate_viewed tracking 로직 및 sessionStorage 쓰기 제거.
컴포넌트 레벨 Analytics 교체
components/join/joinForm.tsx, components/share/shareContent.tsx
양식 제출 및 공유 클릭 이벤트를 pushDataLayer로 변경하고, 브라우저 환경 확인 추가 (typeof window !== 'undefined').

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Frontend#46: 이 PR이 추가한 GA4 sendGAEvent 호출들을 본 PR에서 GTM pushDataLayer로 교체하는 직접적인 연관성.
  • Frontend#47: 동일한 이벤트 추적 코드 경로(meeting, recommend, result 페이지)를 수정하므로 관련 있음.
  • Frontend#45: app/create/page.tsx의 meeting creation analytics 로직에서 GA4 호출을 GTM 호출로 교체하는 일관된 패턴.

Suggested reviewers

  • kim3360
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경사항의 핵심을 명확하게 반영하고 있습니다. sendGAEvent에서 dataLayer.push 객체 형태로의 전환이라는 주요 변경 사항을 정확히 설명합니다.
Description check ✅ Passed PR 설명이 저장소 템플릿 구조를 따르고 있으며, 변경사항과 완료된 체크리스트를 포함하고 있습니다. 모든 필수 섹션이 작성되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/convert-to-object

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.

@kangdy25 kangdy25 requested a review from kim3360 March 2, 2026 12:34
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: 1

Caution

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

⚠️ Outside diff range comments (1)
app/recommend/page.tsx (1)

151-163: ⚠️ Potential issue | 🟡 Minor

user_cookie_id가 비어 있는 이벤트가 발생할 수 있습니다.

external_map_opened에서 browser_id 미존재 시 생성하지 않아 식별 누락이 생길 수 있습니다. 다른 화면과 동일하게 생성/저장 fallback을 맞춰주세요.

수정 예시
-      const browserId = localStorage.getItem('browser_id');
+      let browserId = localStorage.getItem('browser_id');
+      if (!browserId) {
+        browserId = `bid_${Math.random().toString(36).substring(2, 15)}${Date.now().toString(36)}`;
+        localStorage.setItem('browser_id', browserId);
+      }
@@
-        user_cookie_id: browserId,
+        user_cookie_id: browserId,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/recommend/page.tsx` around lines 151 - 163, external_map_opened 이벤트에서
localStorage.getItem('browser_id')가 없으면 user_cookie_id가 비어 전송될 수 있으니, page 컴포넌트의
해당 블록에서 browser_id를 읽는 부분을 찾아(pushDataLayer 호출 바로 앞의
localStorage.getItem('browser_id') 사용) 브라우저 아이디가 없을 경우 새로 생성해 localStorage에 저장하고
그 값을 user_cookie_id로 사용하도록 보완하세요; 즉 meetingId/ place 체크 후 browserId 생성 로직을 추가하고
pushDataLayer에 생성된 값(또는 기존 값)을 항상 전달하도록 수정하십시오.
🧹 Nitpick comments (2)
app/meeting/[id]/page.tsx (1)

79-84: 브라우저/역할 식별 로직은 공통 헬퍼로 추출을 권장합니다.

동일 로직이 반복되어 이벤트 스키마 수정 시 누락 가능성이 큽니다. lib/gtm.ts 또는 별도 util로 getOrCreateBrowserId, getUserRole를 공통화하면 안전합니다.

Also applies to: 141-149, 176-180

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/meeting/`[id]/page.tsx around lines 79 - 84, Extract the repeated
browser/role identification logic into reusable helpers (e.g.,
getOrCreateBrowserId and getUserRole) placed in lib/gtm.ts or a new util;
replace the localStorage-based inline logic in app/meeting/[id]/page.tsx (the
blocks that generate browser_id with Math.random and Date.now and the
role-detection code used in multiple places) with calls to these helpers so all
three occurrences use the same implementation and schema. Ensure
getOrCreateBrowserId returns the existing localStorage browser_id or creates,
stores, and returns the new id, and getUserRole centralizes role derivation
logic; update imports and calls in page.tsx to use these functions everywhere
the original inline logic was used.
app/create/page.tsx (1)

172-173: 변수명 purposesStr는 실제 타입과 맞지 않습니다.

Line 172의 값은 문자열이 아니라 배열(string[])이라 purposesList 또는 purposes 같은 이름이 더 정확합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/create/page.tsx` around lines 172 - 173, The variable purposesStr is
misnamed because getPurposes() returns a string[]; rename purposesStr to a more
accurate name like purposes or purposesList wherever it's declared and used
(e.g., the const currently assigned from getPurposes()) and update any
subsequent references (such as the if check "if (purposesStr.length > 0)") to
use the new identifier to reflect the actual array type returned by
getPurposes().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/join/joinForm.tsx`:
- Around line 69-88: The current pushDataLayer call (event
'participant_registration') inside joinForm.tsx runs before the API result and
thus records failures as conversions; update the logic so pushDataLayer is
invoked only after a successful registration response (move the block into the
API success callback/then branch where the registration response is handled) and
keep using meetingId, browserId, isRemembered and userRole when sending, or if
you need to track attempts separately rename the pre-call event to
'participant_registration_attempt' and send that before the API while reserving
'participant_registration' for the success path (use pushDataLayer and the same
payload keys).

---

Outside diff comments:
In `@app/recommend/page.tsx`:
- Around line 151-163: external_map_opened 이벤트에서
localStorage.getItem('browser_id')가 없으면 user_cookie_id가 비어 전송될 수 있으니, page 컴포넌트의
해당 블록에서 browser_id를 읽는 부분을 찾아(pushDataLayer 호출 바로 앞의
localStorage.getItem('browser_id') 사용) 브라우저 아이디가 없을 경우 새로 생성해 localStorage에 저장하고
그 값을 user_cookie_id로 사용하도록 보완하세요; 즉 meetingId/ place 체크 후 browserId 생성 로직을 추가하고
pushDataLayer에 생성된 값(또는 기존 값)을 항상 전달하도록 수정하십시오.

---

Nitpick comments:
In `@app/create/page.tsx`:
- Around line 172-173: The variable purposesStr is misnamed because
getPurposes() returns a string[]; rename purposesStr to a more accurate name
like purposes or purposesList wherever it's declared and used (e.g., the const
currently assigned from getPurposes()) and update any subsequent references
(such as the if check "if (purposesStr.length > 0)") to use the new identifier
to reflect the actual array type returned by getPurposes().

In `@app/meeting/`[id]/page.tsx:
- Around line 79-84: Extract the repeated browser/role identification logic into
reusable helpers (e.g., getOrCreateBrowserId and getUserRole) placed in
lib/gtm.ts or a new util; replace the localStorage-based inline logic in
app/meeting/[id]/page.tsx (the blocks that generate browser_id with Math.random
and Date.now and the role-detection code used in multiple places) with calls to
these helpers so all three occurrences use the same implementation and schema.
Ensure getOrCreateBrowserId returns the existing localStorage browser_id or
creates, stores, and returns the new id, and getUserRole centralizes role
derivation logic; update imports and calls in page.tsx to use these functions
everywhere the original inline logic was used.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 211b186 and 846ba2d.

📒 Files selected for processing (7)
  • app/create/page.tsx
  • app/meeting/[id]/page.tsx
  • app/recommend/page.tsx
  • app/result/[id]/page.tsx
  • components/join/joinForm.tsx
  • components/share/shareContent.tsx
  • lib/gtm.ts

Comment on lines +69 to 88
// ⭐️ 1. 비즈니스 로직 실행 전 GA 이벤트 선(先) 전송! (dataLayer 방식 적용)
if (typeof window !== 'undefined') {
let browserId = localStorage.getItem('browser_id');
if (!browserId) {
browserId = `bid_${Math.random().toString(36).substring(2, 15)}${Date.now().toString(36)}`;
localStorage.setItem('browser_id', browserId);
}

// ⭐️ 개발자님의 완벽한 엣지케이스 방어 로직!
// 로컬스토리지에 '이 방의 생성자(방장)'라는 징표가 있는지 확인
// 방장/참여자 구분 로직
const isHost = localStorage.getItem(`is_host_${meetingId}`) === 'true';
const userRole = isHost ? 'host' : 'participant';

sendGAEvent('event', 'participant_registration', {
pushDataLayer({
event: 'participant_registration',
meeting_url_id: meetingId,
user_cookie_id: browserId, // 생성 때 썼던 그 browserId와 일치하게 됨!
role: userRole, // 완벽하게 방장/참여자 구분 완료
user_cookie_id: browserId,
role: userRole,
remember_me: isRemembered ? 'yes' : 'no',
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

participant_registration 이벤트가 실패 케이스에도 기록됩니다.

현재는 API 성공 여부와 무관하게 전송되어 전환 지표가 과대 집계될 수 있습니다. 성공 시점으로 이동하거나, 시도 이벤트로 이름을 분리해 주세요.

수정 예시 (성공 시점 전송)
-    // ⭐️ 1. 비즈니스 로직 실행 전 GA 이벤트 선(先) 전송! (dataLayer 방식 적용)
-    if (typeof window !== 'undefined') {
-      let browserId = localStorage.getItem('browser_id');
-      if (!browserId) {
-        browserId = `bid_${Math.random().toString(36).substring(2, 15)}${Date.now().toString(36)}`;
-        localStorage.setItem('browser_id', browserId);
-      }
-
-      // 방장/참여자 구분 로직
-      const isHost = localStorage.getItem(`is_host_${meetingId}`) === 'true';
-      const userRole = isHost ? 'host' : 'participant';
-
-      pushDataLayer({
-        event: 'participant_registration',
-        meeting_url_id: meetingId,
-        user_cookie_id: browserId,
-        role: userRole,
-        remember_me: isRemembered ? 'yes' : 'no',
-      });
-    }
-
     // ⭐️ 2. 기존 참여 API 호출 로직
     try {
       // `@ts-ignore`
       const result = await participantEnter.mutateAsync({
@@
       if (result.success) {
+        if (typeof window !== 'undefined') {
+          let browserId = localStorage.getItem('browser_id');
+          if (!browserId) {
+            browserId = `bid_${Math.random().toString(36).substring(2, 15)}${Date.now().toString(36)}`;
+            localStorage.setItem('browser_id', browserId);
+          }
+          const isHost = localStorage.getItem(`is_host_${meetingId}`) === 'true';
+          const userRole = isHost ? 'host' : 'participant';
+          pushDataLayer({
+            event: 'participant_registration',
+            meeting_url_id: meetingId,
+            user_cookie_id: browserId,
+            role: userRole,
+            remember_me: isRemembered ? 'yes' : 'no',
+          });
+        }
         setMeetingUserId(meetingId, name, isRemembered);
         router.push(`/meeting/${meetingId}`);
       } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/join/joinForm.tsx` around lines 69 - 88, The current pushDataLayer
call (event 'participant_registration') inside joinForm.tsx runs before the API
result and thus records failures as conversions; update the logic so
pushDataLayer is invoked only after a successful registration response (move the
block into the API success callback/then branch where the registration response
is handled) and keep using meetingId, browserId, isRemembered and userRole when
sending, or if you need to track attempts separately rename the pre-call event
to 'participant_registration_attempt' and send that before the API while
reserving 'participant_registration' for the success path (use pushDataLayer and
the same payload keys).

@kangdy25 kangdy25 merged commit 03b8f26 into main Mar 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants