From 49ae8257c745602e8af904c5091805211b083544 Mon Sep 17 00:00:00 2001 From: jeongwusi Date: Wed, 11 Oct 2023 16:29:10 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20GA4=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/index.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/index.html b/client/index.html index 9b7b8a5a..efad84ad 100644 --- a/client/index.html +++ b/client/index.html @@ -14,5 +14,17 @@
+ + + + From baa86d671889a1db0e1b030a69c7cde879ffda5b Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 12 Oct 2023 11:13:37 +0900 Subject: [PATCH 2/6] =?UTF-8?q?chore:=20gtag=20id=20=EA=B0=92=EC=9D=84=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=B0=9B?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/index.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/index.html b/client/index.html index efad84ad..33c1f3f8 100644 --- a/client/index.html +++ b/client/index.html @@ -16,15 +16,14 @@
- + From 0379edd7d597a12a5e29cc7fcb4e32cdb2d27d00 Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 12 Oct 2023 16:41:55 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20gtag.js=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20head=20=ED=83=9C=EA=B7=B8=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/index.html b/client/index.html index 33c1f3f8..d31adc28 100644 --- a/client/index.html +++ b/client/index.html @@ -11,9 +11,6 @@ - - -
@@ -25,5 +22,8 @@ gtag('js', new Date()); gtag('config', '<%= process.env.GOOGLE_ANALYTICS_TRACKING_ID %>'); + + +
From 8c704c59b5ea023358e9d4e9fe9aa144171e34e1 Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 12 Oct 2023 16:42:12 +0900 Subject: [PATCH 4/6] =?UTF-8?q?chore:=20gtag=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/package-lock.json | 7 +++++++ client/package.json | 1 + 2 files changed, 8 insertions(+) diff --git a/client/package-lock.json b/client/package-lock.json index c7177704..be3aad1a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -32,6 +32,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", @@ -6738,6 +6739,12 @@ "@types/node": "*" } }, + "node_modules/@types/gtag.js": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.16.tgz", + "integrity": "sha512-TefTCQWiQYc1F7zF7KKLOd5E645me+9CjfPwJLNxWxaTK3ITKmCxtHRVH7EhSo1dl+sVGmu2h2htCOZrjWQRrQ==", + "dev": true + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", diff --git a/client/package.json b/client/package.json index 940ad104..1bf752dc 100644 --- a/client/package.json +++ b/client/package.json @@ -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", From 0e739b9f651d37588343b3cca45a1805751a0242 Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 12 Oct 2023 16:42:51 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20GA=20config,=20event=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/CafeCard.tsx | 17 ++++---- client/src/pages/HomePage.tsx | 5 ++- client/src/pages/Root.tsx | 13 +++++- client/src/utils/GoogleAnalytics.ts | 62 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 client/src/utils/GoogleAnalytics.ts diff --git a/client/src/components/CafeCard.tsx b/client/src/components/CafeCard.tsx index 4993c613..c40e6fc3 100644 --- a/client/src/components/CafeCard.tsx +++ b/client/src/components/CafeCard.tsx @@ -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'; @@ -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); diff --git a/client/src/pages/HomePage.tsx b/client/src/pages/HomePage.tsx index 38c3f510..24daab61 100644 --- a/client/src/pages/HomePage.tsx +++ b/client/src/pages/HomePage.tsx @@ -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; @@ -24,6 +25,8 @@ const HomePage = () => { const itemRenderer = (cafe: Cafe) => ; + useEffect(withGAEvent('cafe_view', { cafeName: cafes[activeIndex].name }), [activeIndex]); + return ( { + const { identity } = useAuth(); useSilentLink(); + useEffect(() => { + if (identity) { + setGAConfig({ + user_id: identity.sub, + }); + } + }, [identity]); + return ( <> diff --git a/client/src/utils/GoogleAnalytics.ts b/client/src/utils/GoogleAnalytics.ts new file mode 100644 index 00000000..b051362d --- /dev/null +++ b/client/src/utils/GoogleAnalytics.ts @@ -0,0 +1,62 @@ +/// + +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( + ...args: Extract extends { params: infer Params extends Record } + ? [event: EventName, params: Params] + : [event: EventName] +): () => void; + +export function withGAEvent( + ...args: Extract extends { params: infer Params extends Record } + ? [event: EventName, params: Params, fn: (...args: Args) => ReturnType] + : [event: EventName, fn: (...args: Args) => ReturnType] +): (...args: Args) => ReturnType; + +/** + * Google Analytics 이벤트를 발생시킬 때 사용하는 함수 + * + * 원본 함수를 마지막 인자로 넣어 이벤트 함수와 결합하여 사용 가능합니다 + */ +export function withGAEvent( + ...args: Extract extends { params: infer Params extends Record } + ? [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) { + if (!ID) return; + window.gtag('config', ID, config); +} From 2ed522b381b5e762398170059cd4804474ca49f8 Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 12 Oct 2023 16:43:11 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20meta=20pixel=20code=20=EC=8A=AC?= =?UTF-8?q?=EB=A1=AF=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/index.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/index.html b/client/index.html index d31adc28..c942e856 100644 --- a/client/index.html +++ b/client/index.html @@ -22,6 +22,10 @@ gtag('js', new Date()); gtag('config', '<%= process.env.GOOGLE_ANALYTICS_TRACKING_ID %>'); + + + <%= process.env.META_PIXEL_CODE %> +