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 (
);
}
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