mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
refactor(console): update conversion report timing (#5833)
This commit is contained in:
parent
39e239753e
commit
f8221a38db
6 changed files with 46 additions and 123 deletions
|
@ -3,16 +3,11 @@ import { Helmet } from 'react-helmet';
|
|||
|
||||
import useCurrentUser from '@/hooks/use-current-user';
|
||||
|
||||
import { useRetry } from './use-retry';
|
||||
import {
|
||||
shouldReport,
|
||||
gtagAwTrackingId,
|
||||
redditPixelId,
|
||||
hashEmail,
|
||||
type GtagConversionId,
|
||||
type RedditReportType,
|
||||
reportToGoogle,
|
||||
reportToReddit,
|
||||
plausibleDataDomain,
|
||||
} from './utils';
|
||||
|
||||
|
@ -109,25 +104,3 @@ export function GlobalScripts() {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type ReportConversionOptions = {
|
||||
transactionId?: string;
|
||||
gtagId?: GtagConversionId;
|
||||
redditType?: RedditReportType;
|
||||
};
|
||||
|
||||
export const useReportConversion = ({
|
||||
gtagId,
|
||||
redditType,
|
||||
transactionId,
|
||||
}: ReportConversionOptions) => {
|
||||
useRetry({
|
||||
precondition: Boolean(shouldReport && gtagId),
|
||||
execute: () => (gtagId ? reportToGoogle(gtagId, { transactionId }) : false),
|
||||
});
|
||||
|
||||
useRetry({
|
||||
precondition: Boolean(shouldReport && redditType),
|
||||
execute: () => (redditType ? reportToReddit(redditType) : false),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
type UseRetryOptions = {
|
||||
/** The precondition to check before executing the function. */
|
||||
precondition: boolean;
|
||||
/** The function to execute when the precondition is not met. */
|
||||
onPreconditionFailed?: () => void;
|
||||
/** The function to execute. If it returns `true`, the retry will stop. */
|
||||
execute: () => boolean;
|
||||
/**
|
||||
* The maximum number of retries.
|
||||
*
|
||||
* @default 3
|
||||
*/
|
||||
maxRetry?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook to retry a function until the condition is met. The retry interval is 1 second.
|
||||
*/
|
||||
export const useRetry = ({
|
||||
precondition,
|
||||
onPreconditionFailed,
|
||||
execute,
|
||||
maxRetry = 3,
|
||||
}: UseRetryOptions) => {
|
||||
useEffect(() => {
|
||||
if (!precondition) {
|
||||
onPreconditionFailed?.();
|
||||
}
|
||||
}, [onPreconditionFailed, precondition]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!precondition) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-let
|
||||
let retry = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (execute() || retry >= maxRetry) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
retry += 1;
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [execute, maxRetry, precondition]);
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import { cond } from '@silverhand/essentials';
|
||||
import debug from 'debug';
|
||||
|
||||
import { isProduction } from '@/consts/env';
|
||||
const log = debug('conversion');
|
||||
|
||||
export const gtagAwTrackingId = 'AW-11124811245';
|
||||
export enum GtagConversionId {
|
||||
|
@ -54,30 +55,18 @@ export const hashEmail = async (email?: string) => {
|
|||
return sha256(canonicalizedEmail);
|
||||
};
|
||||
|
||||
/** Print debug message if not in production. */
|
||||
const debug = (...args: Parameters<(typeof console)['debug']>) => {
|
||||
if (!isProduction) {
|
||||
console.debug(...args);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add more if needed: https://reddit.my.site.com/helpcenter/s/article/Install-the-Reddit-Pixel-on-your-website
|
||||
*/
|
||||
export type RedditReportType =
|
||||
| 'PageVisit'
|
||||
| 'ViewContent'
|
||||
| 'Search'
|
||||
| 'Purchase'
|
||||
| 'Lead'
|
||||
| 'SignUp';
|
||||
type RedditReportType = 'PageVisit' | 'ViewContent' | 'Search' | 'Purchase' | 'Lead' | 'SignUp';
|
||||
|
||||
export const reportToReddit = (redditType: RedditReportType) => {
|
||||
const reportToReddit = (redditType: RedditReportType) => {
|
||||
if (!window.rdt) {
|
||||
log('report:', 'window.rdt is not available');
|
||||
return false;
|
||||
}
|
||||
|
||||
debug('report:', 'redditType =', redditType);
|
||||
log('report:', 'redditType =', redditType);
|
||||
window.rdt('track', redditType);
|
||||
|
||||
return true;
|
||||
|
@ -88,13 +77,14 @@ export const reportToGoogle = (
|
|||
{ transactionId }: { transactionId?: string } = {}
|
||||
) => {
|
||||
if (!window.gtag) {
|
||||
log('report:', 'window.gtag is not available');
|
||||
return false;
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
const transaction = cond(transactionId && { transaction_id: await sha256(transactionId) });
|
||||
|
||||
debug('report:', 'gtagId =', gtagId, 'transaction =', transaction);
|
||||
log('report:', 'gtagId =', gtagId, 'transaction =', transaction);
|
||||
window.gtag?.('event', 'conversion', {
|
||||
send_to: gtagId,
|
||||
...transaction,
|
||||
|
@ -105,3 +95,25 @@ export const reportToGoogle = (
|
|||
|
||||
return true;
|
||||
};
|
||||
|
||||
type ReportConversionOptions = {
|
||||
transactionId?: string;
|
||||
gtagId?: GtagConversionId;
|
||||
redditType?: RedditReportType;
|
||||
};
|
||||
|
||||
export const reportConversion = async ({
|
||||
gtagId,
|
||||
redditType,
|
||||
transactionId,
|
||||
}: ReportConversionOptions) => {
|
||||
if (!shouldReport) {
|
||||
log('skip reporting conversion:', { gtagId, redditType, transactionId });
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
gtagId ? reportToGoogle(gtagId, { transactionId }) : undefined,
|
||||
redditType ? reportToReddit(redditType) : undefined,
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { yes } from '@silverhand/essentials';
|
||||
|
||||
export const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
export const isCloud = yes(process.env.IS_CLOUD);
|
||||
export const adminEndpoint = process.env.ADMIN_ENDPOINT;
|
||||
|
||||
|
|
|
@ -3,14 +3,11 @@ import { useContext, useEffect } from 'react';
|
|||
import { Route, Navigate, Outlet, Routes } from 'react-router-dom';
|
||||
import { SWRConfig } from 'swr';
|
||||
|
||||
import { useReportConversion } from '@/components/Conversion';
|
||||
import { GtagConversionId } from '@/components/Conversion/utils';
|
||||
import AppBoundary from '@/containers/AppBoundary';
|
||||
import ProtectedRoutes from '@/containers/ProtectedRoutes';
|
||||
import TenantAccess from '@/containers/TenantAccess';
|
||||
import { AppThemeContext } from '@/contexts/AppThemeProvider';
|
||||
import Toast from '@/ds-components/Toast';
|
||||
import useCurrentUser from '@/hooks/use-current-user';
|
||||
import useSwrOptions from '@/hooks/use-swr-options';
|
||||
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
||||
|
||||
|
@ -27,23 +24,6 @@ function Layout() {
|
|||
const { setThemeOverride } = useContext(AppThemeContext);
|
||||
const { match, getTo } = useTenantPathname();
|
||||
|
||||
// User object should be available at this point as it's rendered by the `<AppRoutes />`
|
||||
// component in `packages/console/src/App.tsx`.
|
||||
const { user } = useCurrentUser();
|
||||
|
||||
/**
|
||||
* Report a sign-up conversion.
|
||||
*
|
||||
* Note it may run multiple times (e.g. a user visit multiple times to finish the onboarding process,
|
||||
* which rarely happens). We should turn on deduplication settings in the provider's dashboard. For
|
||||
* example, in Google, we should set conversion's "Count" to "One".
|
||||
*/
|
||||
useReportConversion({
|
||||
gtagId: GtagConversionId.SignUp,
|
||||
redditType: 'SignUp',
|
||||
transactionId: user?.id,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setThemeOverride(Theme.Light);
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import useSWR from 'swr';
|
|||
|
||||
import Tools from '@/assets/icons/tools.svg';
|
||||
import ActionBar from '@/components/ActionBar';
|
||||
import { GtagConversionId, reportConversion } from '@/components/Conversion/utils';
|
||||
import PageMeta from '@/components/PageMeta';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import Button from '@/ds-components/Button';
|
||||
|
@ -18,6 +19,7 @@ import TextInput from '@/ds-components/TextInput';
|
|||
import ImageUploaderField from '@/ds-components/Uploader/ImageUploaderField';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useCurrentUser from '@/hooks/use-current-user';
|
||||
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
||||
import useUserAssetsService from '@/hooks/use-user-assets-service';
|
||||
import { CardSelector, MultiCardSelector } from '@/onboarding/components/CardSelector';
|
||||
|
@ -53,6 +55,7 @@ function SignInExperience() {
|
|||
const api = useApi();
|
||||
const { isReady: isUserAssetsServiceReady } = useUserAssetsService();
|
||||
const { update } = useUserOnboardingData();
|
||||
const { user } = useCurrentUser();
|
||||
const { navigateTenant, currentTenantId } = useContext(TenantsContext);
|
||||
|
||||
const enterAdminConsole = async () => {
|
||||
|
@ -115,11 +118,18 @@ function SignInExperience() {
|
|||
}
|
||||
}
|
||||
|
||||
const updatedData = await api
|
||||
.patch(buildUrl('api/sign-in-exp', { removeUnusedDemoSocialConnector: '1' }), {
|
||||
json: formDataParser.toUpdateOnboardingSieData(formData, signInExperience),
|
||||
})
|
||||
.json<SignInExperienceType>();
|
||||
const [updatedData] = await Promise.all([
|
||||
api
|
||||
.patch(buildUrl('api/sign-in-exp', { removeUnusedDemoSocialConnector: '1' }), {
|
||||
json: formDataParser.toUpdateOnboardingSieData(formData, signInExperience),
|
||||
})
|
||||
.json<SignInExperienceType>(),
|
||||
reportConversion({
|
||||
gtagId: GtagConversionId.SignUp,
|
||||
redditType: 'SignUp',
|
||||
transactionId: user?.id,
|
||||
}),
|
||||
]);
|
||||
|
||||
void mutate(updatedData);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue