Skip to content

Commit 45de5a1

Browse files
Update analytics.tsx (#17)
Co-authored-by: Jannik Wempe <[email protected]>
1 parent 4bb7e12 commit 45de5a1

File tree

8 files changed

+124
-58
lines changed

8 files changed

+124
-58
lines changed
Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
11
import Cookies from 'js-cookie';
22
import { useEffect } from 'react';
33
import { v4 as uuidv4 } from 'uuid';
4-
import { useAppContext } from './contexts/appContext';
54

5+
import { useAppContext } from './contexts/appContext';
66
const GA_TRACKING_ID = 'G-72XG3F8LNJ'; // This is Hashnode's GA tracking ID
77
const isProd = process.env.NEXT_PUBLIC_MODE === 'production';
88
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_URL || '';
99

1010
export const Analytics = () => {
11-
const { publication, post } = useAppContext();
12-
13-
useEffect(() => {
14-
if (!isProd) return;
15-
16-
_sendPageViewsToHashnodeGoogleAnalytics();
17-
_sendViewsToHashnodeInternalAnalytics();
18-
_sendViewsToHashnodeAnalyticsDashboard();
19-
}, []);
20-
21-
if (!isProd) return null;
11+
const { publication, post, series, page } = useAppContext();
2212

2313
const _sendPageViewsToHashnodeGoogleAnalytics = () => {
2414
// @ts-ignore
@@ -27,7 +17,6 @@ export const Analytics = () => {
2717
first_party_collection: true,
2818
});
2919
};
30-
3120
const _sendViewsToHashnodeInternalAnalytics = async () => {
3221
// Send to Hashnode's own internal analytics
3322
const event: Record<string, string | number | object> = {
@@ -42,17 +31,14 @@ export const Analytics = () => {
4231
referrer: window.document.referrer,
4332
},
4433
};
45-
4634
let deviceId = Cookies.get('__amplitudeDeviceID');
4735
if (!deviceId) {
4836
deviceId = uuidv4();
4937
Cookies.set('__amplitudeDeviceID', deviceId, {
5038
expires: 365 * 2,
5139
}); // expire after two years
5240
}
53-
5441
event['device_id'] = deviceId;
55-
5642
await fetch(`${BASE_PATH}/ping/data-event`, {
5743
method: 'POST',
5844
headers: {
@@ -61,7 +47,6 @@ export const Analytics = () => {
6147
body: JSON.stringify({ events: [event] }),
6248
});
6349
};
64-
6550
const _sendViewsToHashnodeAnalyticsDashboard = async () => {
6651
const LOCATION = window.location;
6752
const NAVIGATOR = window.navigator;
@@ -72,21 +57,17 @@ export const Analytics = () => {
7257
LOCATION.pathname +
7358
LOCATION.search +
7459
LOCATION.hash;
75-
7660
const query = new URL(currentFullURL).searchParams;
77-
7861
const utm_id = query.get('utm_id');
7962
const utm_campaign = query.get('utm_campaign');
8063
const utm_source = query.get('utm_source');
8164
const utm_medium = query.get('utm_medium');
8265
const utm_term = query.get('utm_term');
8366
const utm_content = query.get('utm_content');
84-
8567
let referrer = document.referrer || '';
8668
if (referrer.indexOf(window.location.hostname) !== -1) {
8769
referrer = '';
8870
}
89-
9071
const data = {
9172
publicationId: publication.id,
9273
postId: post && post.id,
@@ -107,27 +88,6 @@ export const Analytics = () => {
10788
utm_content,
10889
};
10990

110-
// send to Umami powered advanced Hashnode analytics
111-
if (publication.integrations?.umamiWebsiteUUID) {
112-
await fetch(`${BASE_PATH}/api/collect`, {
113-
method: 'POST',
114-
headers: {
115-
'Content-Type': 'application/json',
116-
},
117-
body: JSON.stringify({
118-
payload: {
119-
website: publication.integrations.umamiWebsiteUUID,
120-
url: window.location.pathname,
121-
referrer: referrer,
122-
hostname: window.location.hostname,
123-
language: NAVIGATOR.language,
124-
screen: `${window.screen.width}x${window.screen.height}`,
125-
},
126-
type: 'pageview',
127-
}),
128-
});
129-
}
130-
13191
// For Hashnode Blog Dashboard Analytics
13292
fetch(`${BASE_PATH}/ping/view`, {
13393
method: 'POST',
@@ -138,5 +98,91 @@ export const Analytics = () => {
13898
});
13999
};
140100

101+
function _sendViewsToAdvancedAnalyticsDashboard() {
102+
const publicationId = publication.id;
103+
const postId = post && post.id;
104+
const seriesId = series?.id || post?.series?.id;
105+
const staticPageId = page && page.id;
106+
107+
const data = {
108+
publicationId,
109+
postId,
110+
seriesId,
111+
staticPageId,
112+
};
113+
114+
if (!publicationId) {
115+
console.warn('Publication ID is missing; could not send analytics.');
116+
return;
117+
}
118+
119+
const isBrowser = typeof window !== 'undefined';
120+
if (!isBrowser) {
121+
return;
122+
}
123+
124+
const isLocalhost = window.location.hostname === 'localhost';
125+
if (isLocalhost) {
126+
console.warn(
127+
'Analytics API call is skipped because you are running on localhost; data:',
128+
data,
129+
);
130+
return;
131+
}
132+
133+
const event = {
134+
// timestamp will be added in API
135+
payload: {
136+
publicationId,
137+
postId: postId || null,
138+
seriesId: seriesId || null,
139+
pageId: staticPageId || null,
140+
url: window.location.href,
141+
referrer: document.referrer || null,
142+
language: navigator.language || null,
143+
screen: `${window.screen.width}x${window.screen.height}`,
144+
},
145+
type: 'pageview',
146+
};
147+
148+
const blob = new Blob(
149+
[
150+
JSON.stringify({
151+
events: [event],
152+
}),
153+
],
154+
{
155+
type: 'application/json; charset=UTF-8',
156+
},
157+
);
158+
159+
let hasSentBeacon = false;
160+
try {
161+
if (navigator.sendBeacon) {
162+
hasSentBeacon = navigator.sendBeacon(`/api/analytics`, blob);
163+
}
164+
} catch (error) {
165+
// do nothing; in case there is an error we fall back to fetch
166+
}
167+
168+
if (!hasSentBeacon) {
169+
fetch(`/api/analytics`, {
170+
method: 'POST',
171+
body: blob,
172+
credentials: 'omit',
173+
keepalive: true,
174+
});
175+
}
176+
}
177+
178+
useEffect(() => {
179+
if (!isProd) return;
180+
181+
_sendPageViewsToHashnodeGoogleAnalytics();
182+
_sendViewsToHashnodeInternalAnalytics();
183+
_sendViewsToHashnodeAnalyticsDashboard();
184+
_sendViewsToAdvancedAnalyticsDashboard();
185+
}, []);
186+
141187
return null;
142-
};
188+
};

packages/blog-starter-kit/themes/enterprise/components/contexts/appContext.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
import React, { createContext, useContext } from 'react';
2-
import { PostFullFragment, PublicationFragment } from '../../generated/graphql';
2+
import {
3+
PostFullFragment,
4+
PublicationFragment,
5+
SeriesPostsByPublicationQuery,
6+
StaticPageFragment,
7+
} from '../../generated/graphql';
38

4-
type AppContext = { publication: PublicationFragment; post: PostFullFragment | null };
9+
type AppContext = {
10+
publication: PublicationFragment;
11+
post: PostFullFragment | null;
12+
page: StaticPageFragment | null;
13+
series: NonNullable<SeriesPostsByPublicationQuery['publication']>['series'];
14+
};
515

616
const AppContext = createContext<AppContext | null>(null);
717

818
const AppProvider = ({
919
children,
1020
publication,
1121
post,
22+
page,
23+
series,
1224
}: {
1325
children: React.ReactNode;
1426
publication: PublicationFragment;
1527
post?: PostFullFragment | null;
28+
page?: StaticPageFragment | null;
29+
series?: NonNullable<SeriesPostsByPublicationQuery['publication']>['series'];
1630
}) => {
1731
return (
1832
<AppContext.Provider
1933
value={{
2034
publication,
2135
post: post ?? null,
36+
page: page ?? null,
37+
series: series ?? null,
2238
}}
2339
>
2440
{children}

0 commit comments

Comments
 (0)