0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

Merge pull request #5732 from logto-io/gao-add-plausible

feat(console): add plausible
This commit is contained in:
Gao Sun 2024-04-18 00:18:38 +08:00 committed by GitHub
commit 26c215fbdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 128 additions and 19 deletions

View file

@ -54,6 +54,7 @@
"@swc/jest": "^0.2.26",
"@testing-library/react": "^15.0.0",
"@types/color": "^3.0.3",
"@types/debug": "^4.1.7",
"@types/jest": "^29.4.0",
"@types/mdx": "^2.0.1",
"@types/mdx-js__react": "^1.5.5",
@ -71,6 +72,7 @@
"csstype": "^3.0.11",
"date-fns": "^2.29.3",
"dayjs": "^1.10.5",
"debug": "^4.3.4",
"deep-object-diff": "^1.1.9",
"deepmerge": "^4.2.2",
"dnd-core": "^16.0.0",

View file

@ -13,6 +13,7 @@ import {
type RedditReportType,
reportToGoogle,
reportToReddit,
plausibleDataDomain,
} from './utils';
type ScriptProps = {
@ -61,6 +62,20 @@ function RedditScripts({ userEmailHash }: ScriptProps) {
);
}
function PlausibleScripts() {
return (
<Helmet>
<script
async
defer
data-domain={plausibleDataDomain}
src="https://plausible.io/js/plausible.manual.js"
/>
<script>{`window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }`}</script>
</Helmet>
);
}
/**
* Renders global scripts for conversion tracking.
*/
@ -88,6 +103,7 @@ export function GlobalScripts() {
return (
<>
<PlausibleScripts />
<GoogleScripts userEmailHash={userEmailHash} />
<RedditScripts userEmailHash={userEmailHash} />
</>

View file

@ -13,6 +13,7 @@ export enum GtagConversionId {
}
export const redditPixelId = 't2_ggt11omdo';
export const plausibleDataDomain = 'cloud.logto.io';
const logtoProductionHostname = 'logto.io';

View file

@ -1,7 +1,8 @@
import { useOutletContext } from 'react-router-dom';
import { useOutletContext, useRoutes } from 'react-router-dom';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import { useConsoleRoutes } from '@/hooks/use-console-routes';
import { usePlausiblePageview } from '@/hooks/use-plausible-pageview';
import type { AppContentOutletContext } from '../AppContent/types';
@ -11,9 +12,11 @@ import * as styles from './index.module.scss';
function ConsoleContent() {
const { scrollableContent } = useOutletContext<AppContentOutletContext>();
const routes = useConsoleRoutes();
const routeObjects = useConsoleRoutes();
const routes = useRoutes(routeObjects);
// Use this hook here to make sure console listens to user tenant scope changes.
useTenantScopeListener();
usePlausiblePageview(routeObjects);
return (
<div className={styles.content}>

View file

@ -1,6 +1,6 @@
import { condArray } from '@silverhand/essentials';
import { useMemo } from 'react';
import { type RouteObject, useRoutes } from 'react-router-dom';
import { type RouteObject } from 'react-router-dom';
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
import Dashboard from '@/pages/Dashboard';
@ -54,7 +54,6 @@ export const useConsoleRoutes = () => {
),
[tenantSettings]
);
const routes = useRoutes(routeObjects);
return routes;
return routeObjects;
};

View file

@ -0,0 +1,26 @@
import { appendPath } from '@silverhand/essentials';
import debug from 'debug';
import { useEffect } from 'react';
import { type RouteObject, useLocation } from 'react-router-dom';
import { plausibleDataDomain } from '@/components/Conversion/utils';
import { getRoutePattern } from '@/utils/route';
const log = debug('usePlausiblePageview');
export const usePlausiblePageview = (routes: RouteObject[]) => {
const { pathname } = useLocation();
useEffect(() => {
const routePattern = getRoutePattern(pathname, routes);
log('pageview', routePattern);
// https://plausible.io/docs/custom-locations#3-specify-a-custom-location
window.plausible?.('pageview', {
u:
appendPath(new URL('https://' + plausibleDataDomain), routePattern).href +
window.location.search,
});
}, [pathname, routes]);
};

View file

@ -3,4 +3,6 @@ declare interface Window {
gtag?: (...args: unknown[]) => void;
// Reddit
rdt?: (...args: unknown[]) => void;
// Plausible
plausible?: (...args: unknown[]) => void;
}

View file

@ -1,16 +1,47 @@
import { Outlet } from 'react-router-dom';
import { useRoutes, type RouteObject, Navigate } from 'react-router-dom';
import { usePlausiblePageview } from '@/hooks/use-plausible-pageview';
import Topbar from '@/onboarding/components/Topbar';
import SignInExperience from '@/onboarding/pages/SignInExperience';
import Welcome from '@/onboarding/pages/Welcome';
import { OnboardingPage, OnboardingRoute } from '@/onboarding/types';
import NotFound from '@/pages/NotFound';
import * as styles from './index.module.scss';
const routeObjects: RouteObject[] = [
{
path: OnboardingRoute.Onboarding,
children: [
{
index: true,
element: <Navigate replace to={OnboardingPage.Welcome} />,
},
{
path: OnboardingPage.Welcome,
element: <Welcome />,
},
{
path: OnboardingPage.SignInExperience,
element: <SignInExperience />,
},
],
},
{
path: '*',
element: <NotFound />,
},
];
function AppContent() {
const routes = useRoutes(routeObjects);
usePlausiblePageview(routeObjects);
return (
<div className={styles.app}>
<Topbar />
<div className={styles.content}>
<Outlet />
</div>
<div className={styles.content}>{routes}</div>
</div>
);
}

View file

@ -15,13 +15,10 @@ 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';
import NotFound from '@/pages/NotFound';
import AppContent from './containers/AppContent';
import useUserOnboardingData from './hooks/use-user-onboarding-data';
import * as styles from './index.module.scss';
import SignInExperience from './pages/SignInExperience';
import Welcome from './pages/Welcome';
import { OnboardingPage, OnboardingRoute } from './types';
import { getOnboardingPage } from './utils';
@ -88,12 +85,7 @@ export function OnboardingRoutes() {
<Route element={<TenantAccess />}>
<Route element={<Layout />}>
<Route index element={<Navigate replace to={OnboardingRoute.Onboarding} />} />
<Route path={OnboardingRoute.Onboarding} element={<AppContent />}>
<Route index element={<Navigate replace to={OnboardingPage.Welcome} />} />
<Route path={OnboardingPage.Welcome} element={<Welcome />} />
<Route path={OnboardingPage.SignInExperience} element={<SignInExperience />} />
</Route>
<Route path="*" element={<NotFound />} />
<Route path="*" element={<AppContent />} />
</Route>
</Route>
</Route>

View file

@ -0,0 +1,31 @@
import { matchRoutes, type RouteObject } from 'react-router-dom';
export const getRoutePattern = (pathname: string, routes: RouteObject[]) => {
// Remove the first segment of the pathname, which is the tenant ID.
const normalized = pathname.replace(/^\/[^/]+/, '');
const matches = matchRoutes(routes, normalized) ?? [];
return (
'/' +
matches
.filter((match) => !match.route.index)
.flatMap(({ route: { path }, params }) => {
// Path could have multiple segments, e.g. 'api-resources/:id/*'.
const segments = path?.split('/') ?? [];
return segments.map((segment) => {
if (segment === '*') {
return params['*'] ?? segment;
}
// If the path is not a parameter, or it's an ID parameter, use the path as is.
if (!segment.startsWith(':') || segment.endsWith('Id') || segment.endsWith('id')) {
return segment;
}
// Otherwise, use the parameter value.
return params[segment.slice(1)] ?? segment;
});
})
.join('/')
);
};

View file

@ -2792,6 +2792,9 @@ importers:
'@types/color':
specifier: ^3.0.3
version: 3.0.3
'@types/debug':
specifier: ^4.1.7
version: 4.1.7
'@types/jest':
specifier: ^29.4.0
version: 29.4.0
@ -2843,6 +2846,9 @@ importers:
dayjs:
specifier: ^1.10.5
version: 1.11.6
debug:
specifier: ^4.3.4
version: 4.3.4
deep-object-diff:
specifier: ^1.1.9
version: 1.1.9
@ -15656,7 +15662,7 @@ packages:
jest: ^28.1.0 || ^29.1.2
react: ^17.0.0 || ^18.0.0
dependencies:
jest: 29.7.0(@types/node@20.11.20)
jest: 29.7.0(@types/node@20.11.20)(ts-node@10.9.2)
react: 18.2.0
dev: true