From a06376d3e0ab76a77bdfe7bbfa20729d246b6d57 Mon Sep 17 00:00:00 2001 From: yousrchive Date: Thu, 19 Feb 2026 23:45:00 +0900 Subject: [PATCH 1/2] feat: integrate GA tracking into RendingBody --- src/feature/root/components/RendingBody.tsx | 41 +++++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/feature/root/components/RendingBody.tsx b/src/feature/root/components/RendingBody.tsx index 96488a8..3d702b0 100644 --- a/src/feature/root/components/RendingBody.tsx +++ b/src/feature/root/components/RendingBody.tsx @@ -9,7 +9,8 @@ import { import { buildQuery } from '@/global/utils/buildQuery'; import Image from 'next/image'; import { useSearchParams } from 'next/navigation'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; +import { trackGaEvent } from '@/global/utils/trackGaEvent'; // GA 이벤트 추가 const KAKAO_AUTH_URL = `https://dev.say-cheese.me/oauth2/authorization/kakao`; @@ -20,6 +21,8 @@ export const RendingBody = () => { const [touchEnd, setTouchEnd] = useState(0); const searchParams = useSearchParams(); const redirect = searchParams.get('redirect'); + // 어떤 이벤트를 봤는지 집합 자료구조를 통해 표시 / state로 뒀을 때 의존성이 흔들려 ref로 관리 + const viewedSlidesRef = useRef>(new Set()); const slides = [ { @@ -60,9 +63,30 @@ export const RendingBody = () => { useEffect(() => { if (!api) return; - api.on('select', () => { - setCurrent(api.selectedScrollSnap()); - }); + const fireViewEventIfNeeded = (idx: number) => { + if (viewedSlidesRef.current.has(idx)) return; + trackGaEvent('rendering_slide_view', { + slide_index: (idx + 1).toString(), + slide_title: slides[idx]?.title ?? '', + }); + viewedSlidesRef.current.add(idx); + }; + + const initialIndex = api.selectedScrollSnap(); + setCurrent(initialIndex); + fireViewEventIfNeeded(initialIndex); + + const onSelect = () => { + const newIndex = api.selectedScrollSnap(); + setCurrent(newIndex); + fireViewEventIfNeeded(newIndex); + }; + + api.on('select', onSelect); + + return () => { + api.off?.('select', onSelect); + }; }, [api]); const handleTouchStart = (e: React.TouchEvent) => { @@ -75,6 +99,7 @@ export const RendingBody = () => { const handleTouchEnd = () => { if (!api) return; + if (touchEnd === 0) return; const swipeDistance = touchStart - touchEnd; const minSwipeDistance = 50; @@ -86,9 +111,17 @@ export const RendingBody = () => { // 오른쪽으로 스와이프 (이전) api.scrollPrev(); } + + setTouchStart(0); + setTouchEnd(0); // 이전 값이 남아 있으면 거리 계산 어려움 }; const handleKakaoLogin = async () => { + trackGaEvent('cta_click', { + button_name: 'kakao_login', + last_visible_slide: (current + 1).toString() + }); + try { const kakaoUrl = redirect ? `${KAKAO_AUTH_URL}${buildQuery({ redirect })}` From 1cda286f149f47686c96e3da3b9c7940ad2906ee Mon Sep 17 00:00:00 2001 From: yousrchive Date: Sat, 21 Feb 2026 14:58:06 +0900 Subject: [PATCH 2/2] refactor: stabilize rendering slides and centralize GA event names --- src/feature/root/components/RendingBody.tsx | 91 +++++++++++---------- src/global/constants/gaEvents.ts | 2 + 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/feature/root/components/RendingBody.tsx b/src/feature/root/components/RendingBody.tsx index 3d702b0..7239d50 100644 --- a/src/feature/root/components/RendingBody.tsx +++ b/src/feature/root/components/RendingBody.tsx @@ -10,64 +10,67 @@ import { buildQuery } from '@/global/utils/buildQuery'; import Image from 'next/image'; import { useSearchParams } from 'next/navigation'; import { useEffect, useState, useRef } from 'react'; -import { trackGaEvent } from '@/global/utils/trackGaEvent'; // GA 이벤트 추가 +import { trackGaEvent } from '@/global/utils/trackGaEvent'; +import { GA_EVENTS } from '@/global/constants/gaEvents'; // GA 이벤트 추가, 상수화 const KAKAO_AUTH_URL = `https://dev.say-cheese.me/oauth2/authorization/kakao`; +const SLIDES = [ + { + src: '/assets/rending/new-swipe/1.png', + alt: '앨범 목록 화면', + width: 260, + height: 530.31, + title: '이벤트마다 만드는 공유앨범', + description: '감튀 모임부터 찐친 여행까지', + }, + { + src: '/assets/rending/new-swipe/2.png', + alt: 'QR 코드 공유', + width: 260, + height: 530.31, + title: '모인 자리에서 바로 공유', + description: '앨범 만들고 초대까지 딱 10초', + }, + { + src: '/assets/rending/new-swipe/3.png', + alt: '베스트컷', + width: 312.56, + height: 530.31, + title: '한눈에 보는 베스트컷', + description: '사진 고르는 고민 이제 끝', + }, + { + src: '/assets/rending/new-swipe/4.png', + alt: '네컷추억', + width: 288.8, + height: 530.31, + title: '딱 네컷으로 남는 추억', + description: '앨범이 닫히면 사라지는 원본 사진들', + }, +] as const; + export const RendingBody = () => { const [api, setApi] = useState(); const [current, setCurrent] = useState(0); const [touchStart, setTouchStart] = useState(0); const [touchEnd, setTouchEnd] = useState(0); + const searchParams = useSearchParams(); const redirect = searchParams.get('redirect'); + // 어떤 이벤트를 봤는지 집합 자료구조를 통해 표시 / state로 뒀을 때 의존성이 흔들려 ref로 관리 const viewedSlidesRef = useRef>(new Set()); - const slides = [ - { - src: '/assets/rending/new-swipe/1.png', - alt: '앨범 목록 화면', - width: 260, - height: 530.31, - title: '이벤트마다 만드는 공유앨범', - description: '감튀 모임부터 찐친 여행까지', - }, - { - src: '/assets/rending/new-swipe/2.png', - alt: 'QR 코드 공유', - width: 260, - height: 530.31, - title: '모인 자리에서 바로 공유', - description: '앨범 만들고 초대까지 딱 10초', - }, - { - src: '/assets/rending/new-swipe/3.png', - alt: '베스트컷', - width: 312.56, - height: 530.31, - title: '한눈에 보는 베스트컷', - description: '사진 고르는 고민 이제 끝', - }, - { - src: '/assets/rending/new-swipe/4.png', - alt: '네컷추억', - width: 288.8, - height: 530.31, - title: '딱 네컷으로 남는 추억', - description: '앨범이 닫히면 사라지는 원본 사진들', - }, - ]; - // 캐러셀 API를 통해 현재 슬라이드 추적 useEffect(() => { if (!api) return; const fireViewEventIfNeeded = (idx: number) => { if (viewedSlidesRef.current.has(idx)) return; - trackGaEvent('rendering_slide_view', { + trackGaEvent(GA_EVENTS.rendering_slide_view, { slide_index: (idx + 1).toString(), - slide_title: slides[idx]?.title ?? '', + slide_title: SLIDES[idx]?.title ?? '', }); viewedSlidesRef.current.add(idx); }; @@ -113,13 +116,13 @@ export const RendingBody = () => { } setTouchStart(0); - setTouchEnd(0); // 이전 값이 남아 있으면 거리 계산 어려움 + setTouchEnd(0); // 초기화, 이전 값이 남아 있으면 거리 계산 어려움 }; const handleKakaoLogin = async () => { - trackGaEvent('cta_click', { + trackGaEvent(GA_EVENTS.cta_click, { button_name: 'kakao_login', - last_visible_slide: (current + 1).toString() + last_visible_slide: (current + 1).toString(), }); try { @@ -143,7 +146,7 @@ export const RendingBody = () => { onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} > - {slides.map((slide, index) => ( + {SLIDES.map((slide, index) => (
{ }} > - {slides.map((slide, index) => ( + {SLIDES.map((slide, index) => (

@@ -197,7 +200,7 @@ export const RendingBody = () => { {/* 인디케이터 점들 - 고정 */}
- {slides.map((_, idx) => ( + {SLIDES.map((_, idx) => (