Skip to content

Commit

Permalink
Merge pull request #567 from woowacourse-teams/feat/566-ga-4
Browse files Browse the repository at this point in the history
Google 애널리틱스 4를 추가한다.
  • Loading branch information
solo5star authored Oct 12, 2023
2 parents 0c08d9f + 6b1d66f commit f10aa2d
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 10 deletions.
15 changes: 15 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@
<meta property="og:type" content="website" />
<meta property="og:image" content="/assets/thumbnail-image.png" />
<meta property="og:description" content="트렌디한 성수 지역의 카페를 손쉽게 탐색하는 서비스, 요즘카페" />

<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= process.env.GOOGLE_ANALYTICS_TRACKING_ID %>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', '<%= process.env.GOOGLE_ANALYTICS_TRACKING_ID %>');
</script>

<!-- Meta Pixel Code -->
<%= process.env.META_PIXEL_CODE %>
<!-- End Meta Pixel Code -->
</head>
<body>
<div id="root"></div>
Expand Down
7 changes: 7 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@storybook/react-webpack5": "^7.4.5",
"@storybook/testing-library": "0.2.1",
"@types/google.maps": "^3.54.0",
"@types/gtag.js": "^0.0.16",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@typescript-eslint/eslint-plugin": "^5.61.0",
Expand Down
17 changes: 9 additions & 8 deletions client/src/components/CafeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useCafeLikes from '../hooks/useCafeLikes';
import useClipboard from '../hooks/useClipboard';
import useUser from '../hooks/useUser';
import type { Cafe } from '../types';
import { withGAEvent } from '../utils/GoogleAnalytics';
import Resource from '../utils/Resource';
import CafeDetailBottomSheet from './CafeDetailBottomSheet';
import CafeMenuBottomSheet from './CafeMenuBottomSheet';
Expand Down Expand Up @@ -42,34 +43,34 @@ const CafeCard = (props: CardProps) => {
setCurrentImageIndex(index);
}, []);

const handleShare = async () => {
const handleShare = withGAEvent('share', { cafeName: cafe.name }, async () => {
try {
await clipboard.copyToClipboard(`https://yozm.cafe/cafes/${cafe.id}`);
showToast('success', 'URL이 복사되었습니다!');
} catch (error) {
showToast('error', `URL 복사 실패: ${error}`);
}
};
});

const handleLikeCountIncrease = () => {
const handleLikeCountIncrease = withGAEvent('click_like_button', { cafeName: cafe.name }, () => {
if (!user) {
showToast('error', '로그인이 필요합니다!');
return;
}
setLiked({ isLiked: !isLiked });
};
});

const handleDetailOpen = () => {
const handleDetailOpen = withGAEvent('click_detail_button', { cafeName: cafe.name }, () => {
setIsShowDetail(true);
};
});

const handleDetailClose = () => {
setIsShowDetail(false);
};

const handleMenuOpen = () => {
const handleMenuOpen = withGAEvent('click_menu_button', { cafeName: cafe.name }, () => {
setIsMenuOpened(true);
};
});

const handleMenuClose = () => {
setIsMenuOpened(false);
Expand Down
5 changes: 4 additions & 1 deletion client/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import CafeCard from '../components/CafeCard';
import ScrollSnap from '../components/ScrollSnap';
import useCafes from '../hooks/useCafes';
import type { Cafe } from '../types';
import { withGAEvent } from '../utils/GoogleAnalytics';
import { easeOutExpo } from '../utils/timingFunctions';

const PREFETCH_OFFSET = 2;
Expand All @@ -24,6 +25,8 @@ const HomePage = () => {

const itemRenderer = (cafe: Cafe) => <CafeCard key={cafe.id} cafe={cafe} />;

useEffect(withGAEvent('cafe_view', { cafeName: cafes[activeIndex].name }), [activeIndex]);

return (
<ScrollSnap
style={{ height: '100%' }}
Expand Down
13 changes: 12 additions & 1 deletion client/src/pages/Root.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { Suspense } from 'react';
import { Suspense, useEffect } from 'react';
import { Outlet } from 'react-router-dom';
import { styled } from 'styled-components';
import Navbar from '../components/Navbar';
import useAuth from '../hooks/useAuth';
import useSilentLink from '../hooks/useSilentLink';
import { setGAConfig } from '../utils/GoogleAnalytics';

const Root = () => {
const { identity } = useAuth();
useSilentLink();

useEffect(() => {
if (identity) {
setGAConfig({
user_id: identity.sub,
});
}
}, [identity]);

return (
<>
<Navbar />
Expand Down
62 changes: 62 additions & 0 deletions client/src/utils/GoogleAnalytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/// <reference types="@types/gtag.js" />

declare global {
interface Window {
dataLayer: unknown[];
}
}

const ID = process.env.GOOGLE_ANALYTICS_TRACKING_ID ?? null;

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};

type GAEvent =
| { name: 'share'; params: { cafeName: string } }
| { name: 'click_like_button'; params: { cafeName: string } }
| { name: 'click_menu_button'; params: { cafeName: string } }
| { name: 'click_detail_button'; params: { cafeName: string } }
| { name: 'cafe_view'; params: { cafeName: string } };

type GAConfig = {
user_id: string;
};

export function withGAEvent<EventName extends GAEvent['name']>(
...args: Extract<GAEvent, { name: EventName }> extends { params: infer Params extends Record<string, unknown> }
? [event: EventName, params: Params]
: [event: EventName]
): () => void;

export function withGAEvent<EventName extends GAEvent['name'], Args extends unknown[], ReturnType>(
...args: Extract<GAEvent, { name: EventName }> extends { params: infer Params extends Record<string, unknown> }
? [event: EventName, params: Params, fn: (...args: Args) => ReturnType]
: [event: EventName, fn: (...args: Args) => ReturnType]
): (...args: Args) => ReturnType;

/**
* Google Analytics 이벤트를 발생시킬 때 사용하는 함수
*
* 원본 함수를 마지막 인자로 넣어 이벤트 함수와 결합하여 사용 가능합니다
*/
export function withGAEvent<EventName extends GAEvent['name'], Args extends unknown[] = [], ReturnType = void>(
...args: Extract<GAEvent, { name: EventName }> extends { params: infer Params extends Record<string, unknown> }
? [event: EventName, params: Params] | [event: EventName, params: Params, fn: (...args: Args) => ReturnType]
: [event: EventName] | [event: EventName, fn: (...args: Args) => ReturnType]
) {
const [arg1, arg2, arg3] = args;
const event = arg1;
const params = typeof arg2 === 'object' ? arg2 : {};
const fn = arg3 ?? noop;

return (...args: Args) => {
window.gtag('event', event, params);

return fn(...args);
};
}

export function setGAConfig(config: Partial<GAConfig>) {
if (!ID) return;
window.gtag('config', ID, config);
}

0 comments on commit f10aa2d

Please sign in to comment.