mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console): report LinkedIn conversion (#4251)
* feat(console): report linkedin conversion * refactor(console): clean up code
This commit is contained in:
parent
5a1c9d3a7b
commit
db91da19bd
7 changed files with 113 additions and 57 deletions
57
packages/console/src/components/ReportConversion/index.tsx
Normal file
57
packages/console/src/components/ReportConversion/index.tsx
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
|
import {
|
||||||
|
shouldReport,
|
||||||
|
lintrk,
|
||||||
|
gtagAwTrackingId,
|
||||||
|
linkedInConversionId,
|
||||||
|
gtag,
|
||||||
|
gtagSignUpConversionId,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In cloud production environment, this component initiates gtag.js and LinkedIn
|
||||||
|
* Insight Tag, then reports a sign-up conversion to them.
|
||||||
|
*/
|
||||||
|
export default function ReportConversion() {
|
||||||
|
/**
|
||||||
|
* This `useEffect()` initiates Google Tag and report a sign-up conversion to it.
|
||||||
|
* It may run multiple times (e.g. a user visit multiple times to finish the onboarding process,
|
||||||
|
* which rarely happens), but it'll be okay since we've set conversion's "Count" to "One"
|
||||||
|
* which means only the first interaction is valuable.
|
||||||
|
*
|
||||||
|
* Track this conversion in the backend has been considered, but Google does not provide
|
||||||
|
* a clear guideline for it and marks the [Node library](https://developers.google.com/tag-platform/tag-manager/api/v2/libraries)
|
||||||
|
* as "alpha" which looks unreliable.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldReport) {
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', gtagAwTrackingId);
|
||||||
|
gtag('event', 'conversion', {
|
||||||
|
send_to: gtagSignUpConversionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
lintrk('track', { conversion_id: linkedInConversionId });
|
||||||
|
console.debug('Have a good day!');
|
||||||
|
} else {
|
||||||
|
console.debug("Not reporting conversion because it's not production");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (shouldReport) {
|
||||||
|
return (
|
||||||
|
<Helmet>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${gtagAwTrackingId}`}
|
||||||
|
/>
|
||||||
|
<script async src="https://snap.licdn.com/li.lms-analytics/insight.min.js" />
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
44
packages/console/src/components/ReportConversion/utils.ts
Normal file
44
packages/console/src/components/ReportConversion/utils.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
export const gtagAwTrackingId = 'AW-11124811245';
|
||||||
|
/** This ID indicates a user has truly signed up for Logto Cloud. */
|
||||||
|
export const gtagSignUpConversionId = `AW-11192640559/ZuqUCLvNpasYEK_IiNkp`;
|
||||||
|
const logtoProductionHostname = 'logto.io';
|
||||||
|
const linkedInPartnerId = '5096172';
|
||||||
|
export const linkedInConversionId = '13374828';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to the special of conversion reporting, it should be `true` only in the
|
||||||
|
* Logto Cloud production environment.
|
||||||
|
* Add the leading '.' to make it safer (ignore hostnames like "foologto.io").
|
||||||
|
*/
|
||||||
|
export const shouldReport = window.location.hostname.endsWith('.' + logtoProductionHostname);
|
||||||
|
|
||||||
|
/* eslint-disable @silverhand/fp/no-mutation, @silverhand/fp/no-mutating-methods */
|
||||||
|
|
||||||
|
/** This function is edited from the Google Tag official code snippet. */
|
||||||
|
export function gtag(..._: unknown[]) {
|
||||||
|
if (!window.dataLayer) {
|
||||||
|
window.dataLayer = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot use rest params here since gtag has some internal logic about `arguments` for data transpiling
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
window.dataLayer.push(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This function is edited from the LinkedIn Tag official code snippet. */
|
||||||
|
export function lintrk(..._: unknown[]) {
|
||||||
|
// Init LinkedIn tag if needed
|
||||||
|
if (!window._linkedin_data_partner_ids) {
|
||||||
|
window._linkedin_data_partner_ids = [];
|
||||||
|
window._linkedin_data_partner_ids.push(linkedInPartnerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.lintrk) {
|
||||||
|
window.lintrk = { q: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
window.lintrk.q.push(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-enable @silverhand/fp/no-mutation, @silverhand/fp/no-mutating-methods */
|
3
packages/console/src/include.d/gtag.d.ts
vendored
3
packages/console/src/include.d/gtag.d.ts
vendored
|
@ -1,3 +0,0 @@
|
||||||
declare interface Window {
|
|
||||||
dataLayer?: unknown[];
|
|
||||||
}
|
|
9
packages/console/src/include.d/tags.d.ts
vendored
Normal file
9
packages/console/src/include.d/tags.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
declare interface Window {
|
||||||
|
// `gtag.js`
|
||||||
|
dataLayer?: unknown[];
|
||||||
|
// LinkedIn
|
||||||
|
_linkedin_data_partner_ids?: unknown[];
|
||||||
|
lintrk?: {
|
||||||
|
q: unknown[];
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
export const gtagAwTrackingId = 'AW-11124811245';
|
|
||||||
/** This ID indicates a user has truly signed up for Logto Cloud. */
|
|
||||||
export const gtagSignUpConversionId = `AW-11192640559/ZuqUCLvNpasYEK_IiNkp`;
|
|
||||||
export const logtoProductionHostname = 'logto.io';
|
|
|
@ -2,10 +2,10 @@ import { Component, ConsoleEvent } from '@logto/app-insights/custom-event';
|
||||||
import { TrackOnce } from '@logto/app-insights/react';
|
import { TrackOnce } from '@logto/app-insights/react';
|
||||||
import { Theme } from '@logto/schemas';
|
import { Theme } from '@logto/schemas';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
import { Route, Navigate, Outlet, Routes } from 'react-router-dom';
|
import { Route, Navigate, Outlet, Routes } from 'react-router-dom';
|
||||||
import { SWRConfig } from 'swr';
|
import { SWRConfig } from 'swr';
|
||||||
|
|
||||||
|
import ReportConversion from '@/components/ReportConversion';
|
||||||
import AppBoundary from '@/containers/AppBoundary';
|
import AppBoundary from '@/containers/AppBoundary';
|
||||||
import ProtectedRoutes from '@/containers/ProtectedRoutes';
|
import ProtectedRoutes from '@/containers/ProtectedRoutes';
|
||||||
import TenantAccess from '@/containers/TenantAccess';
|
import TenantAccess from '@/containers/TenantAccess';
|
||||||
|
@ -15,21 +15,15 @@ import useSwrOptions from '@/hooks/use-swr-options';
|
||||||
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
||||||
import NotFound from '@/pages/NotFound';
|
import NotFound from '@/pages/NotFound';
|
||||||
|
|
||||||
import { gtagAwTrackingId, gtagSignUpConversionId, logtoProductionHostname } from './constants';
|
|
||||||
import AppContent from './containers/AppContent';
|
import AppContent from './containers/AppContent';
|
||||||
import useUserOnboardingData from './hooks/use-user-onboarding-data';
|
import useUserOnboardingData from './hooks/use-user-onboarding-data';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
import SignInExperience from './pages/SignInExperience';
|
import SignInExperience from './pages/SignInExperience';
|
||||||
import Welcome from './pages/Welcome';
|
import Welcome from './pages/Welcome';
|
||||||
import { OnboardingPage, OnboardingRoute } from './types';
|
import { OnboardingPage, OnboardingRoute } from './types';
|
||||||
import { getOnboardingPage, gtag } from './utils';
|
import { getOnboardingPage } from './utils';
|
||||||
|
|
||||||
const welcomePathname = getOnboardingPage(OnboardingPage.Welcome);
|
const welcomePathname = getOnboardingPage(OnboardingPage.Welcome);
|
||||||
/**
|
|
||||||
* Due to the special of Google Tag, it should be `true` only in the Logto Cloud production environment.
|
|
||||||
* Add the leading '.' to make it safer (ignore hostnames like "foologto.io").
|
|
||||||
*/
|
|
||||||
const shouldReportToGtag = window.location.hostname.endsWith('.' + logtoProductionHostname);
|
|
||||||
|
|
||||||
function Layout() {
|
function Layout() {
|
||||||
const swrOptions = useSwrOptions();
|
const swrOptions = useSwrOptions();
|
||||||
|
@ -44,27 +38,6 @@ function Layout() {
|
||||||
};
|
};
|
||||||
}, [setThemeOverride]);
|
}, [setThemeOverride]);
|
||||||
|
|
||||||
/**
|
|
||||||
* This `useEffect()` initiates Google Tag and report a sign-up conversion to it.
|
|
||||||
* It may run multiple times (e.g. a user visit multiple times to finish the onboarding process,
|
|
||||||
* which rarely happens), but it'll be okay since we've set conversion's "Count" to "One"
|
|
||||||
* which means only the first interaction is valuable.
|
|
||||||
*
|
|
||||||
* Track this conversion in the backend has been considered, but Google does not provide
|
|
||||||
* a clear guideline for it and marks the [Node library](https://developers.google.com/tag-platform/tag-manager/api/v2/libraries)
|
|
||||||
* as "alpha" which looks unreliable.
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
if (shouldReportToGtag) {
|
|
||||||
gtag('js', new Date());
|
|
||||||
gtag('config', gtagAwTrackingId);
|
|
||||||
gtag('event', 'conversion', {
|
|
||||||
send_to: gtagSignUpConversionId,
|
|
||||||
});
|
|
||||||
console.debug('Google Tag event fires');
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { questionnaire },
|
data: { questionnaire },
|
||||||
} = useUserOnboardingData();
|
} = useUserOnboardingData();
|
||||||
|
@ -80,15 +53,7 @@ function Layout() {
|
||||||
<div className={styles.app}>
|
<div className={styles.app}>
|
||||||
<SWRConfig value={swrOptions}>
|
<SWRConfig value={swrOptions}>
|
||||||
<AppBoundary>
|
<AppBoundary>
|
||||||
{shouldReportToGtag && (
|
<ReportConversion />
|
||||||
<Helmet>
|
|
||||||
<script
|
|
||||||
async
|
|
||||||
crossOrigin="anonymous"
|
|
||||||
src={`https://www.googletagmanager.com/gtag/js?id=${gtagAwTrackingId}`}
|
|
||||||
/>
|
|
||||||
</Helmet>
|
|
||||||
)}
|
|
||||||
<Toast />
|
<Toast />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</AppBoundary>
|
</AppBoundary>
|
||||||
|
|
|
@ -5,15 +5,3 @@ import { OnboardingRoute } from './types';
|
||||||
|
|
||||||
export const getOnboardingPage = (page: OnboardingPage) =>
|
export const getOnboardingPage = (page: OnboardingPage) =>
|
||||||
joinPath(OnboardingRoute.Onboarding, page);
|
joinPath(OnboardingRoute.Onboarding, page);
|
||||||
|
|
||||||
/** This function is updated from the Google Tag official code snippet. */
|
|
||||||
export function gtag(..._: unknown[]) {
|
|
||||||
if (!window.dataLayer) {
|
|
||||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
|
||||||
window.dataLayer = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// We cannot use rest params here since gtag has some internal logic about `arguments` for data transpiling
|
|
||||||
// eslint-disable-next-line @silverhand/fp/no-mutating-methods, prefer-rest-params
|
|
||||||
window.dataLayer.push(arguments);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue