Skip to content

Commit

Permalink
Update analytics.tsx (#17)
Browse files Browse the repository at this point in the history
Co-authored-by: Jannik Wempe <jannik@wempe.dev>
  • Loading branch information
martyna-mindsdb and JannikWempe authored May 2, 2024
1 parent 4bb7e12 commit 45de5a1
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 58 deletions.
132 changes: 89 additions & 43 deletions packages/blog-starter-kit/themes/enterprise/components/analytics.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import Cookies from 'js-cookie';
import { useEffect } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { useAppContext } from './contexts/appContext';

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

export const Analytics = () => {
const { publication, post } = useAppContext();

useEffect(() => {
if (!isProd) return;

_sendPageViewsToHashnodeGoogleAnalytics();
_sendViewsToHashnodeInternalAnalytics();
_sendViewsToHashnodeAnalyticsDashboard();
}, []);

if (!isProd) return null;
const { publication, post, series, page } = useAppContext();

const _sendPageViewsToHashnodeGoogleAnalytics = () => {
// @ts-ignore
Expand All @@ -27,7 +17,6 @@ export const Analytics = () => {
first_party_collection: true,
});
};

const _sendViewsToHashnodeInternalAnalytics = async () => {
// Send to Hashnode's own internal analytics
const event: Record<string, string | number | object> = {
Expand All @@ -42,17 +31,14 @@ export const Analytics = () => {
referrer: window.document.referrer,
},
};

let deviceId = Cookies.get('__amplitudeDeviceID');
if (!deviceId) {
deviceId = uuidv4();
Cookies.set('__amplitudeDeviceID', deviceId, {
expires: 365 * 2,
}); // expire after two years
}

event['device_id'] = deviceId;

await fetch(`${BASE_PATH}/ping/data-event`, {
method: 'POST',
headers: {
Expand All @@ -61,7 +47,6 @@ export const Analytics = () => {
body: JSON.stringify({ events: [event] }),
});
};

const _sendViewsToHashnodeAnalyticsDashboard = async () => {
const LOCATION = window.location;
const NAVIGATOR = window.navigator;
Expand All @@ -72,21 +57,17 @@ export const Analytics = () => {
LOCATION.pathname +
LOCATION.search +
LOCATION.hash;

const query = new URL(currentFullURL).searchParams;

const utm_id = query.get('utm_id');
const utm_campaign = query.get('utm_campaign');
const utm_source = query.get('utm_source');
const utm_medium = query.get('utm_medium');
const utm_term = query.get('utm_term');
const utm_content = query.get('utm_content');

let referrer = document.referrer || '';
if (referrer.indexOf(window.location.hostname) !== -1) {
referrer = '';
}

const data = {
publicationId: publication.id,
postId: post && post.id,
Expand All @@ -107,27 +88,6 @@ export const Analytics = () => {
utm_content,
};

// send to Umami powered advanced Hashnode analytics
if (publication.integrations?.umamiWebsiteUUID) {
await fetch(`${BASE_PATH}/api/collect`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payload: {
website: publication.integrations.umamiWebsiteUUID,
url: window.location.pathname,
referrer: referrer,
hostname: window.location.hostname,
language: NAVIGATOR.language,
screen: `${window.screen.width}x${window.screen.height}`,
},
type: 'pageview',
}),
});
}

// For Hashnode Blog Dashboard Analytics
fetch(`${BASE_PATH}/ping/view`, {
method: 'POST',
Expand All @@ -138,5 +98,91 @@ export const Analytics = () => {
});
};

function _sendViewsToAdvancedAnalyticsDashboard() {
const publicationId = publication.id;
const postId = post && post.id;
const seriesId = series?.id || post?.series?.id;
const staticPageId = page && page.id;

const data = {
publicationId,
postId,
seriesId,
staticPageId,
};

if (!publicationId) {
console.warn('Publication ID is missing; could not send analytics.');
return;
}

const isBrowser = typeof window !== 'undefined';
if (!isBrowser) {
return;
}

const isLocalhost = window.location.hostname === 'localhost';
if (isLocalhost) {
console.warn(
'Analytics API call is skipped because you are running on localhost; data:',
data,
);
return;
}

const event = {
// timestamp will be added in API
payload: {
publicationId,
postId: postId || null,
seriesId: seriesId || null,
pageId: staticPageId || null,
url: window.location.href,
referrer: document.referrer || null,
language: navigator.language || null,
screen: `${window.screen.width}x${window.screen.height}`,
},
type: 'pageview',
};

const blob = new Blob(
[
JSON.stringify({
events: [event],
}),
],
{
type: 'application/json; charset=UTF-8',
},
);

let hasSentBeacon = false;
try {
if (navigator.sendBeacon) {
hasSentBeacon = navigator.sendBeacon(`/api/analytics`, blob);
}
} catch (error) {
// do nothing; in case there is an error we fall back to fetch
}

if (!hasSentBeacon) {
fetch(`/api/analytics`, {
method: 'POST',
body: blob,
credentials: 'omit',
keepalive: true,
});
}
}

useEffect(() => {
if (!isProd) return;

_sendPageViewsToHashnodeGoogleAnalytics();
_sendViewsToHashnodeInternalAnalytics();
_sendViewsToHashnodeAnalyticsDashboard();
_sendViewsToAdvancedAnalyticsDashboard();
}, []);

return null;
};
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import React, { createContext, useContext } from 'react';
import { PostFullFragment, PublicationFragment } from '../../generated/graphql';
import {
PostFullFragment,
PublicationFragment,
SeriesPostsByPublicationQuery,
StaticPageFragment,
} from '../../generated/graphql';

type AppContext = { publication: PublicationFragment; post: PostFullFragment | null };
type AppContext = {
publication: PublicationFragment;
post: PostFullFragment | null;
page: StaticPageFragment | null;
series: NonNullable<SeriesPostsByPublicationQuery['publication']>['series'];
};

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

const AppProvider = ({
children,
publication,
post,
page,
series,
}: {
children: React.ReactNode;
publication: PublicationFragment;
post?: PostFullFragment | null;
page?: StaticPageFragment | null;
series?: NonNullable<SeriesPostsByPublicationQuery['publication']>['series'];
}) => {
return (
<AppContext.Provider
value={{
publication,
post: post ?? null,
page: page ?? null,
series: series ?? null,
}}
>
{children}
Expand Down
Loading

0 comments on commit 45de5a1

Please sign in to comment.