From a2bbc250cad619e129b1d637f139ba4c50973fed Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Wed, 17 Apr 2024 14:21:08 +0800 Subject: [PATCH] feat(console): add plausible --- packages/console/package.json | 2 + .../src/components/Conversion/index.tsx | 16 ++++++++ .../src/components/Conversion/utils.ts | 1 + .../src/containers/ConsoleContent/index.tsx | 7 +++- .../src/hooks/use-console-routes/index.tsx | 5 +-- .../src/hooks/use-plausible-pageview.ts | 26 +++++++++++++ packages/console/src/include.d/tags.d.ts | 2 + .../containers/AppContent/index.tsx | 39 +++++++++++++++++-- packages/console/src/onboarding/index.tsx | 10 +---- packages/console/src/utils/route.ts | 31 +++++++++++++++ pnpm-lock.yaml | 8 +++- 11 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 packages/console/src/hooks/use-plausible-pageview.ts create mode 100644 packages/console/src/utils/route.ts diff --git a/packages/console/package.json b/packages/console/package.json index efb833743..54b901769 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -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", diff --git a/packages/console/src/components/Conversion/index.tsx b/packages/console/src/components/Conversion/index.tsx index 450249c1c..2278dc74a 100644 --- a/packages/console/src/components/Conversion/index.tsx +++ b/packages/console/src/components/Conversion/index.tsx @@ -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 ( + + + + ); +} + /** * Renders global scripts for conversion tracking. */ @@ -88,6 +103,7 @@ export function GlobalScripts() { return ( <> + diff --git a/packages/console/src/components/Conversion/utils.ts b/packages/console/src/components/Conversion/utils.ts index 16b73f467..d63b052da 100644 --- a/packages/console/src/components/Conversion/utils.ts +++ b/packages/console/src/components/Conversion/utils.ts @@ -13,6 +13,7 @@ export enum GtagConversionId { } export const redditPixelId = 't2_ggt11omdo'; +export const plausibleDataDomain = 'cloud.logto.io'; const logtoProductionHostname = 'logto.io'; diff --git a/packages/console/src/containers/ConsoleContent/index.tsx b/packages/console/src/containers/ConsoleContent/index.tsx index 3449daa41..651982374 100644 --- a/packages/console/src/containers/ConsoleContent/index.tsx +++ b/packages/console/src/containers/ConsoleContent/index.tsx @@ -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(); - 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 (
diff --git a/packages/console/src/hooks/use-console-routes/index.tsx b/packages/console/src/hooks/use-console-routes/index.tsx index a3c4df02a..61d5db43e 100644 --- a/packages/console/src/hooks/use-console-routes/index.tsx +++ b/packages/console/src/hooks/use-console-routes/index.tsx @@ -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; }; diff --git a/packages/console/src/hooks/use-plausible-pageview.ts b/packages/console/src/hooks/use-plausible-pageview.ts new file mode 100644 index 000000000..f4135a950 --- /dev/null +++ b/packages/console/src/hooks/use-plausible-pageview.ts @@ -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]); +}; diff --git a/packages/console/src/include.d/tags.d.ts b/packages/console/src/include.d/tags.d.ts index e315ba04b..09550516d 100644 --- a/packages/console/src/include.d/tags.d.ts +++ b/packages/console/src/include.d/tags.d.ts @@ -3,4 +3,6 @@ declare interface Window { gtag?: (...args: unknown[]) => void; // Reddit rdt?: (...args: unknown[]) => void; + // Plausible + plausible?: (...args: unknown[]) => void; } diff --git a/packages/console/src/onboarding/containers/AppContent/index.tsx b/packages/console/src/onboarding/containers/AppContent/index.tsx index c31d7aa07..69f35bb7a 100644 --- a/packages/console/src/onboarding/containers/AppContent/index.tsx +++ b/packages/console/src/onboarding/containers/AppContent/index.tsx @@ -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: , + }, + { + path: OnboardingPage.Welcome, + element: , + }, + { + path: OnboardingPage.SignInExperience, + element: , + }, + ], + }, + { + path: '*', + element: , + }, +]; + function AppContent() { + const routes = useRoutes(routeObjects); + + usePlausiblePageview(routeObjects); + return (
-
- -
+
{routes}
); } diff --git a/packages/console/src/onboarding/index.tsx b/packages/console/src/onboarding/index.tsx index 1c60a282f..4f3bf45c9 100644 --- a/packages/console/src/onboarding/index.tsx +++ b/packages/console/src/onboarding/index.tsx @@ -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() { }> }> } /> - }> - } /> - } /> - } /> - - } /> + } /> diff --git a/packages/console/src/utils/route.ts b/packages/console/src/utils/route.ts new file mode 100644 index 000000000..1afaa57a9 --- /dev/null +++ b/packages/console/src/utils/route.ts @@ -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('/') + ); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54e091fc3..9796db70c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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