Skip to content

[feature] 동아리 지원서 페이지 방문, 체류시간 로깅한다#683

Merged
seongwon030 merged 5 commits intodevelop-fefrom
feature/#681-club-apply-page-duration-MOA-172
Aug 19, 2025
Merged

[feature] 동아리 지원서 페이지 방문, 체류시간 로깅한다#683
seongwon030 merged 5 commits intodevelop-fefrom
feature/#681-club-apply-page-duration-MOA-172

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Aug 19, 2025

#️⃣연관된 이슈

ex) #681

📝작업 내용

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

  • useTrackPageView에서 clubName을 의존성배열에서 제거 -> 중복 트래킹 방지
  • 동아리 지원서 페이지 트래킹 추가
  • 훅 호출 순서 변경 8a3113d

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

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

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

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

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

🫡 참고사항

Summary by CodeRabbit

  • 신기능
    • 신청서 페이지에 페이지뷰 분석 계측을 추가해 방문 및 체류 시간을 기록합니다.
  • 개선
    • 페이지뷰 이벤트가 경로(pathname) 변경 시에만 재전송되도록 조정해 중복 계측을 줄였습니다.
    • 클럽 식별자(clubId)가 없을 때 관련 데이터 조회가 자동 실행되지 않도록 제어해 불필요한 요청을 방지합니다.
  • 참고
    • 클럽명 변경만으로는 기존 페이지뷰 이벤트가 재전송되지 않습니다.

@vercel
Copy link

vercel bot commented Aug 19, 2025

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

Project Deployment Preview Comments Updated (UTC)
moadong Ready Ready Preview Comment Aug 19, 2025 3:25pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 19, 2025

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

Walkthrough

ApplicationFormPage에 페이지 뷰 및 체류시간 계측을 추가하고, useTrackPageView의 useEffect 의존성에서 clubName을 제거하여 pathname 변경 시에만 효과가 재실행되도록 변경했습니다. 또한 application 쿼리에 enabled: !!clubId를 추가했습니다.

Changes

Cohort / File(s) Summary
Analytics Hook
frontend/src/hooks/useTrackPageView.ts
useEffect 의존성 배열에서 clubName 제거 — 이제 효과는 location.pathname 변경 시에만 재실행됨. 이벤트 리스너, 정리, 체류시간 계산 로직에는 변경 없음.
Application Form Instrumentation
frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx
useTrackPageView를 import 및 호출하여 "ApplicationFormPage"와 clubDetail?.name으로 페이지 뷰/체류시간 계측 추가. 관련 데이터 페칭 호출 위치 정리(중복 제거).
Query Enable Guard
frontend/src/hooks/queries/application/useGetApplication.ts
React Query 옵션에 enabled: !!clubId 추가하여 clubId가 falsy일 때 쿼리 자동 실행을 방지함.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as ApplicationFormPage
  participant Hook as useTrackPageView
  participant Analytics as AnalyticsService

  User->>App: /apply/... 진입
  App->>Hook: useTrackPageView("ApplicationFormPage", clubName)
  Note right of Hook: mount 시 Visited 이벤트 전송 및 startTime 기록
  Hook->>Analytics: track("Visited", { page, clubName(captured), path })

  rect rgba(200,230,255,0.18)
  Note over Hook: 체류시간 측정(경과시간 누적)
  end

  User-->>App: 페이지 이탈 또는 경로 변경
  Hook->>Analytics: track("Duration", { page, clubName(captured), path, ms })
  Hook-->>App: cleanup 완료
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
동아리 지원서 페이지 체류시간 측정 (MOA-172)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
enabled: !!clubId 추가 (frontend/src/hooks/queries/application/useGetApplication.ts) 쿼리 실행 제어는 MOA-172의 "체류시간 측정" 목표와 직접적인 관련이 없으며 데이터-fetch 타이밍 최적화 목적임.

Possibly related issues

Possibly related PRs

Suggested labels

🔨 Refactor

Suggested reviewers

  • oesnuj
  • PororoAndFriends
  • suhyun113

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear 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 8a3113d and 26ea4f5.

📒 Files selected for processing (1)
  • frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: run
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#681-club-apply-page-duration-MOA-172

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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

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.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@seongwon030 seongwon030 linked an issue Aug 19, 2025 that may be closed by this pull request
1 task
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: 1

Caution

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

⚠️ Outside diff range comments (2)
frontend/src/hooks/useTrackPageView.ts (2)

10-29: 경로 변경 시 체류시간이 누락될 수 있음: startTime/isTracked를 이펙트 진입 시 초기화 필요

pathname이 바뀔 때 이펙트가 재실행되지만, startTime과 isTracked는 초기화되지 않습니다. 한 번 Duration을 전송한 뒤(isTracked=true) 다음 페이지에서는 Duration이 영원히 전송되지 않는 문제가 발생합니다. 또한 다음 페이지 방문의 기준시각도 갱신되지 않아 timestamp/체류시간이 부정확해집니다.

아래처럼 이펙트 시작부에서 두 값을 초기화해 주세요. 동시에 clubName은 최신값을 사용하도록 ref를 읽도록 변경했습니다(아래 다른 코멘트와 연동).

   useEffect(() => {
+    // 새 페이지 진입 시 타이머 및 1회 전송 상태 초기화
+    isTracked.current = false;
+    startTime.current = Date.now();
 
     mixpanel.track(`${pageName} Visited`, {
       url: window.location.href,
       timestamp: startTime.current,
       referrer: document.referrer || 'direct',
-      clubName,
+      clubName: latestClubNameRef?.current ?? clubName,
     });
 
     const trackPageDuration = () => {
       if (isTracked.current) return;
       const duration = Date.now() - startTime.current;
       mixpanel.track(`${pageName} Duration`, {
         url: window.location.href,
         duration: duration,
         duration_seconds: Math.round(duration / 1000),
-        clubName,
+        clubName: latestClubNameRef?.current ?? clubName,
       });
       isTracked.current = true;
     };

30-41: visibilitychange 리스너 제거가 정상 동작하지 않음(다른 핸들러 참조로 메모리 누수/중복 트래킹 가능)

등록 시 익명 함수를 사용하고, 제거 시 trackPageDuration을 전달하고 있어 removeEventListener가 효과가 없습니다. 동일한 핸들러 참조를 사용하도록 수정하세요.

-    document.addEventListener('visibilitychange', () => {
-      if (document.hidden) {
-        trackPageDuration();
-      }
-    });
+    const visibilityHandler = () => {
+      if (document.hidden) {
+        trackPageDuration();
+      }
+    };
+    document.addEventListener('visibilitychange', visibilityHandler);
 
     return () => {
       trackPageDuration();
       window.removeEventListener('beforeunload', trackPageDuration);
-      document.removeEventListener('visibilitychange', trackPageDuration);
+      document.removeEventListener('visibilitychange', visibilityHandler);
     };
🧹 Nitpick comments (1)
frontend/src/hooks/useTrackPageView.ts (1)

42-42: clubName을 의존성에서 제거한 영향 검토(의도 확인 요청) + 안전한 대안 제안

의존성에서 clubName을 제외하면 페이지 마운트 시점에 clubDetail이 아직 없을 경우 Visited/Duration 이벤트에 clubName이 누락(또는 구버전)될 수 있습니다. 중복 트래킹을 막으면서도 Duration 시점에 최신 clubName을 넣고 싶다면 ref로 최신값만 동기화하는 방식을 권장합니다.

아래 코드를 이 훅 상단에 추가하고(이 라인은 선택된 범위 밖이므로 별도 추가가 필요), 이벤트 전송 시 clubName 대신 latestClubNameRef.current를 사용하세요. 위의 diff는 payload 수정까지 포함합니다.

// 최신 clubName을 유지해 Duration 시점에 반영
const latestClubNameRef = useRef(clubName);
useEffect(() => {
  latestClubNameRef.current = clubName;
}, [clubName]);

이렇게 하면:

  • 이펙트는 pathname에만 반응(중복 트래킹 방지)
  • Duration 이벤트에는 최신 clubName 반영
  • Visited의 clubName 누락은 여전히 가능(의도라면 OK). 필요 시 clubId 같이 안정적인 식별자도 함께 전송 고려.
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear 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 6835258 and 0bd1009.

📒 Files selected for processing (2)
  • frontend/src/hooks/useTrackPageView.ts (1 hunks)
  • frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx
  • frontend/src/hooks/useTrackPageView.ts
frontend/**/*.tsx

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order 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.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.

Files:

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

[error] 44-44: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (1)
frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx (1)

6-6: 페이지 체류시간 계측 훅 도입 자체는 👍

해당 페이지에 추적을 붙인 방향성은 타당합니다. 아래 라인의 훅 호출 위치만 조정하면 좋겠습니다(다음 코멘트 참조).

Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

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

추적 추가 수고하셧습니다

Copy link
Member

@Zepelown Zepelown left a comment

Choose a reason for hiding this comment

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

고생하셨습니다~

- clubId 없으면 요청 안 함
- 조기 리턴(if !clubId) 아래로 이동하고, 데이터/트래킹 훅들을 상단으로 배치해 훅 규칙 위반 방지
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: 1

🧹 Nitpick comments (2)
frontend/src/hooks/queries/application/useGetApplication.ts (1)

4-11: 훅 시그니처를 선택적 파라미터로 완화하고 제네릭 타입 지정 통일 제안

현재 useGetApplication(clubId: string)에 호출부에서 clubId! 단언이 필요합니다. 이미 enabled: !!clubId로 런타임 가드가 있으므로, 시그니처를 clubId?: string로 완화하면 호출부 단언을 제거할 수 있고, useGetClubDetail과 함께 일관성 있는 사용 패턴을 갖출 수 있습니다. 또한 반환 타입 제네릭을 명시해 타입 안정성을 높일 것을 권장합니다.

아래와 같은 변경을 제안합니다.

-import { useQuery } from '@tanstack/react-query';
+import { useQuery } from '@tanstack/react-query';
+// import type { ApplicationForm } from '@/types/application';

-export const useGetApplication = (clubId: string) => {
-  return useQuery({
+export const useGetApplication = (clubId?: string) => {
+  return useQuery/*<ApplicationForm>*/({
     queryKey: ['applicationForm', clubId],
-    queryFn: () => getApplication(clubId),
+    // enabled가 false일 때 실행되지 않으므로 단언 사용이 안전합니다.
+    queryFn: () => getApplication(clubId as string),
     retry: false,
     enabled: !!clubId,
   });
};

참고: ApplicationForm 타입이 존재한다면 제네릭을 해제하지 말고 실제 타입을 지정해 주세요.

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

24-31: 비필수 단언 제거를 위한 훅 시그니처 정렬 제안

useGetClubDetail(clubId!), useGetApplication(clubId!)처럼 non-null 단언에 의존하고 있습니다. 두 훅 모두 내부에서 enabled: !!clubId로 안전장치를 두었으므로, 훅 시그니처를 clubId?: string로 변경하면 이 컴포넌트의 단언을 제거할 수 있어 가독성과 안정성이 개선됩니다.

변경 예시:

-  const { data: clubDetail, error: clubError } = useGetClubDetail(clubId!);
+  const { data: clubDetail, error: clubError } = useGetClubDetail(clubId);
   const {
     data: formData,
     isLoading,
     isError,
     error: applicationError,
-  } = useGetApplication(clubId!);
+  } = useGetApplication(clubId);

훅 정의부 변경은 각각 파일에서 다음과 같이 진행해 주세요.

  • frontend/src/hooks/queries/club/useGetClubDetail.ts: useGetClubDetail = (clubId?: string) => useQuery<ClubDetail>({ ..., enabled: !!clubId })
  • frontend/src/hooks/queries/application/useGetApplication.ts: 위 파일 코멘트 참고
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear 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 0bd1009 and 8a3113d.

📒 Files selected for processing (2)
  • frontend/src/hooks/queries/application/useGetApplication.ts (1 hunks)
  • frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/hooks/queries/application/useGetApplication.ts
  • frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx
frontend/**/*.tsx

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order 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.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.

Files:

  • frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
PR: Moadong/moadong#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/ApplicationFormPage/ApplicationFormPage.tsx
🧬 Code Graph Analysis (1)
frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx (2)
frontend/src/hooks/queries/club/useGetClubDetail.ts (1)
  • useGetClubDetail (6-20)
frontend/src/hooks/queries/application/useGetApplication.ts (1)
  • useGetApplication (4-11)
🔇 Additional comments (2)
frontend/src/hooks/queries/application/useGetApplication.ts (1)

9-10: enabled 게이트 추가 적절 — 불필요한 초기 요청 방지

enabled: !!clubId로 파라미터가 없을 때 쿼리 실행을 막아 중복/불필요 요청을 예방합니다. 목적에 잘 부합합니다.

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

6-6: 페이지 뷰 트래킹 훅 도입 적절

도입 및 import 위치가 최상단이라 훅 규칙을 준수합니다. 이전 커밋에서 지적되었던 early return 이후 호출 이슈도 해소되었습니다.

- ApplicationFormPage: useTrackPageView에 clubName 폴백 적용
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] MOA-172 동아리 지원서 페이지 체류시간을 측정한다

3 participants

Comments