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

refactor: update tracking components (#3792)

* refactor: update tracking components

* refactor: track once for the same session
This commit is contained in:
Gao Sun 2023-04-29 12:02:52 +08:00 committed by GitHub
parent 61aaf7d98d
commit 5a8442712f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 230 additions and 35 deletions

View file

@ -0,0 +1,9 @@
---
"@logto/app-insights": minor
---
add custom events and new component
- implement `getEventName()` to create standard event name with the format `<component>/<event>[/data]`. E.g. `core/sign_in`.
- four new event components `core`, `console`, `blog`, and `website`.
- implement `<TrackOnce />` for tracking a custom event once

View file

@ -20,7 +20,8 @@
}, },
"//": "This field is for parcel. Remove after https://github.com/parcel-bundler/parcel/pull/8807 published.", "//": "This field is for parcel. Remove after https://github.com/parcel-bundler/parcel/pull/8807 published.",
"alias": { "alias": {
"./react": "./lib/react/index.js" "./react": "./lib/react/index.js",
"./custom-event": "./lib/custom-event.js"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View file

@ -0,0 +1,59 @@
/** The app (either frontend or backend) for a bunch of events. */
export enum Component {
/** Logto core service. */
Core = 'core',
/** Logto Console. */
Console = 'console',
/** Logto blog. */
Blog = 'blog',
/** Logto official website. */
Website = 'website',
}
/** General event enums that for frontend apps. */
export enum GeneralEvent {
/** A user visited the current app, it should be recorded once per session. */
Visit = 'visit',
}
export enum CoreEvent {
/** An existing user signed in via an interaction. */
SignIn = 'sign_in',
/** A new user has created in an interaction. */
Register = 'register',
}
export enum ConsoleEvent {
/** A user started the onboarding process. */
Onboard = 'onboard',
}
export enum BlogEvent {
/** A user viewed a blog post. */
ViewPost = 'view_post',
}
export const eventsMap = Object.freeze({
[Component.Core]: CoreEvent,
[Component.Console]: { ...ConsoleEvent, ...GeneralEvent },
[Component.Blog]: { ...BlogEvent, ...GeneralEvent },
[Component.Website]: GeneralEvent,
}) satisfies Record<Component, unknown>;
export type EventsMap = typeof eventsMap;
export type EventType<C extends Component> = EventsMap[C][keyof EventsMap[C]];
export function getEventName(
component: Component.Blog,
event: EventType<Component.Blog>,
postUrl?: string
): string;
export function getEventName<C extends Component>(component: C, event: EventType<C>): string;
export function getEventName<C extends Component>(
component: C,
event: EventType<C>,
data?: string
): string {
return [component, event, data].filter(Boolean).join('/');
}

View file

@ -3,19 +3,27 @@ import { type ReactNode, useContext, useEffect } from 'react';
import { AppInsightsContext, AppInsightsProvider } from './context.js'; import { AppInsightsContext, AppInsightsProvider } from './context.js';
import { getPrimaryDomain } from './utils.js'; import { getPrimaryDomain } from './utils.js';
const AppInsights = () => { type AppInsightsProps = {
cloudRole: string;
};
const AppInsights = ({ cloudRole }: AppInsightsProps) => {
const { needsSetup, setup } = useContext(AppInsightsContext); const { needsSetup, setup } = useContext(AppInsightsContext);
useEffect(() => { useEffect(() => {
const run = async () => {
await setup(cloudRole, { cookieDomain: getPrimaryDomain() });
};
if (needsSetup) { if (needsSetup) {
void setup('console', { cookieDomain: getPrimaryDomain() }); void run();
} }
}, [needsSetup, setup]); }, [cloudRole, needsSetup, setup]);
return null; return null;
}; };
type Props = { type Props = AppInsightsProps & {
children: ReactNode; children: ReactNode;
}; };
@ -28,10 +36,10 @@ type Props = {
* cause issues for some context providers. For example, `useHandleSignInCallback` will be * cause issues for some context providers. For example, `useHandleSignInCallback` will be
* called twice if you use this component to wrap a `<LogtoProvider />`. * called twice if you use this component to wrap a `<LogtoProvider />`.
*/ */
const AppInsightsBoundary = ({ children }: Props) => { const AppInsightsBoundary = ({ children, ...rest }: Props) => {
return ( return (
<AppInsightsProvider> <AppInsightsProvider>
<AppInsights /> <AppInsights {...rest} />
{children} {children}
</AppInsightsProvider> </AppInsightsProvider>
); );

View file

@ -12,6 +12,12 @@ export type SetupConfig = {
}; };
export class AppInsightsReact { export class AppInsightsReact {
/**
* URL search parameters that start with `utm_`. It is an empty object until you call `.setup()`,
* which will read the URL search string and store parameters in this property.
*/
utmParameters: Record<string, string> = {};
protected reactPlugin?: ReactPlugin; protected reactPlugin?: ReactPlugin;
protected clickAnalyticsPlugin?: ClickAnalyticsPlugin; protected clickAnalyticsPlugin?: ClickAnalyticsPlugin;
protected withAITracking?: typeof withAITracking; protected withAITracking?: typeof withAITracking;
@ -84,18 +90,17 @@ export class AppInsightsReact {
}, },
}); });
// Extract UTM parameters
const searchParams = [...new URLSearchParams(window.location.search).entries()];
this.utmParameters = Object.fromEntries(
searchParams.filter(([key]) => key.startsWith('utm_'))
);
this.appInsights.addTelemetryInitializer((item) => { this.appInsights.addTelemetryInitializer((item) => {
// @see https://github.com/microsoft/ApplicationInsights-JS#example-setting-cloud-role-name // @see https://github.com/microsoft/ApplicationInsights-JS#example-setting-cloud-role-name
// @see https://github.com/microsoft/ApplicationInsights-node.js/blob/a573e40fc66981c6a3106bdc5b783d1d94f64231/Schema/PublicSchema/ContextTagKeys.bond#L83 // @see https://github.com/microsoft/ApplicationInsights-node.js/blob/a573e40fc66981c6a3106bdc5b783d1d94f64231/Schema/PublicSchema/ContextTagKeys.bond#L83
/* eslint-disable @silverhand/fp/no-mutation */ /* eslint-disable @silverhand/fp/no-mutation */
item.tags = [...(item.tags ?? []), { 'ai.cloud.role': cloudRole }]; item.tags = [...(item.tags ?? []), { 'ai.cloud.role': cloudRole }];
// Extract UTM parameters
const searchParams = [...new URLSearchParams(window.location.search).entries()];
item.data = {
...item.data,
...Object.fromEntries(searchParams.filter(([key]) => key.startsWith('utm_'))),
};
/* eslint-enable @silverhand/fp/no-mutation */ /* eslint-enable @silverhand/fp/no-mutation */
}); });

View file

@ -0,0 +1,47 @@
import { type ICustomProperties } from '@microsoft/applicationinsights-web';
import { yes } from '@silverhand/essentials';
import { useContext, useEffect } from 'react';
import { getEventName, type Component, type EventType } from '../custom-event.js';
import { AppInsightsContext } from './context.js';
type Props<C extends Component> = {
component: C;
event: EventType<C>;
customProperties?: ICustomProperties;
};
const storageKeyPrefix = 'logto:insights:';
/** Track an event after AppInsights SDK is setup, but only once during the current session. */
const TrackOnce = <C extends Component>({ component, event, customProperties }: Props<C>) => {
const { isSetupFinished, appInsights } = useContext(AppInsightsContext);
useEffect(() => {
const eventName = getEventName(component, event);
const storageKey = `${storageKeyPrefix}${eventName}`;
const tracked = yes(sessionStorage.getItem(storageKey));
if (isSetupFinished && !tracked) {
appInsights.instance?.trackEvent(
{
name: getEventName(component, event),
},
{ ...appInsights.utmParameters, ...customProperties }
);
sessionStorage.setItem(storageKey, '1');
}
}, [
appInsights.instance,
appInsights.utmParameters,
component,
customProperties,
event,
isSetupFinished,
]);
return null;
};
export default TrackOnce;

View file

@ -2,3 +2,4 @@ export { AppInsightsReact, type SetupConfig, withAppInsights } from './AppInsigh
export * from './context.js'; export * from './context.js';
export * from './utils.js'; export * from './utils.js';
export { default as AppInsightsBoundary } from './AppInsightsBoundary.js'; export { default as AppInsightsBoundary } from './AppInsightsBoundary.js';
export { default as TrackOnce } from './TrackOnce.js';

View file

@ -71,7 +71,7 @@ function Content() {
}} }}
> >
<AppThemeProvider> <AppThemeProvider>
<AppInsightsBoundary> <AppInsightsBoundary cloudRole="console">
<Helmet titleTemplate={`%s - ${mainTitle}`} defaultTitle={mainTitle} /> <Helmet titleTemplate={`%s - ${mainTitle}`} defaultTitle={mainTitle} />
<ErrorBoundary> <ErrorBoundary>
{!isCloud || isSettle ? ( {!isCloud || isSettle ? (

View file

@ -4,8 +4,6 @@ import { useContext, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { mainTitle } from '@/consts/tenants';
export type Props = { export type Props = {
titleKey: AdminConsoleKey | AdminConsoleKey[]; titleKey: AdminConsoleKey | AdminConsoleKey[];
// eslint-disable-next-line react/boolean-prop-naming // eslint-disable-next-line react/boolean-prop-naming
@ -23,7 +21,7 @@ function PageMeta({ titleKey, trackPageView = true }: Props) {
useEffect(() => { useEffect(() => {
// Only track once for the same page // Only track once for the same page
if (isSetupFinished && trackPageView && !pageViewTracked) { if (isSetupFinished && trackPageView && !pageViewTracked) {
appInsights.trackPageView?.({ name: [rawTitle, mainTitle].join(' - ') }); appInsights.trackPageView?.({ name: `Console: ${rawTitle}` });
setPageViewTracked(true); setPageViewTracked(true);
} }
}, [appInsights, isSetupFinished, pageViewTracked, rawTitle, trackPageView]); }, [appInsights, isSetupFinished, pageViewTracked, rawTitle, trackPageView]);

View file

@ -5,9 +5,8 @@ export * from './resources';
export * from './tenants'; export * from './tenants';
export * from './page-tabs'; export * from './page-tabs';
export * from './external-links'; export * from './external-links';
export * from './storage';
export const appearanceModeStorageKey = 'logto:admin_console:appearance_mode';
export const profileSocialLinkingKeyPrefix = 'logto:admin_console:linking_social_connector';
export const requestTimeout = 20_000; export const requestTimeout = 20_000;
export const defaultPageSize = 20; export const defaultPageSize = 20;

View file

@ -0,0 +1,13 @@
export type CamelCase<T> = T extends `${infer A}_${infer B}`
? `${A}${Capitalize<CamelCase<B>>}`
: T;
export type StorageType = 'appearance_mode' | 'linking_social_connector';
export const getStorageKey = <T extends StorageType>(forType: T) =>
`logto:admin_console:${forType}` as const;
export const storageKeys = Object.freeze({
appearanceMode: getStorageKey('appearance_mode'),
linkingSocialConnector: getStorageKey('linking_social_connector'),
} satisfies Record<CamelCase<StorageType>, string>);

View file

@ -3,7 +3,7 @@ import { conditionalString, noop, trySafe } from '@silverhand/essentials';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useEffect, useMemo, useState, createContext } from 'react'; import { useEffect, useMemo, useState, createContext } from 'react';
import { appearanceModeStorageKey } from '@/consts'; import { storageKeys } from '@/consts';
import type { AppearanceMode } from '@/types/appearance-mode'; import type { AppearanceMode } from '@/types/appearance-mode';
import { appearanceModeGuard, DynamicAppearanceMode } from '@/types/appearance-mode'; import { appearanceModeGuard, DynamicAppearanceMode } from '@/types/appearance-mode';
@ -24,7 +24,7 @@ const getThemeBySystemConfiguration = (): Theme =>
darkThemeWatchMedia.matches ? Theme.Dark : Theme.Light; darkThemeWatchMedia.matches ? Theme.Dark : Theme.Light;
export const buildDefaultAppearanceMode = (): AppearanceMode => export const buildDefaultAppearanceMode = (): AppearanceMode =>
trySafe(() => appearanceModeGuard.parse(localStorage.getItem(appearanceModeStorageKey))) ?? trySafe(() => appearanceModeGuard.parse(localStorage.getItem(storageKeys.appearanceMode))) ??
DynamicAppearanceMode.System; DynamicAppearanceMode.System;
const defaultAppearanceMode = buildDefaultAppearanceMode(); const defaultAppearanceMode = buildDefaultAppearanceMode();
@ -47,7 +47,7 @@ export function AppThemeProvider({ children }: Props) {
const setAppearanceMode = (mode: AppearanceMode) => { const setAppearanceMode = (mode: AppearanceMode) => {
setMode(mode); setMode(mode);
localStorage.setItem(appearanceModeStorageKey, mode); localStorage.setItem(storageKeys.appearanceMode, mode);
}; };
useEffect(() => { useEffect(() => {

View file

@ -1,3 +1,5 @@
import { Component, ConsoleEvent } from '@logto/app-insights/custom-event';
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 { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
@ -46,6 +48,7 @@ function App() {
return ( return (
<BrowserRouter basename={getBasename()}> <BrowserRouter basename={getBasename()}>
<TrackOnce component={Component.Console} event={ConsoleEvent.Onboard} />
<div className={styles.app}> <div className={styles.app}>
<SWRConfig value={swrOptions}> <SWRConfig value={swrOptions}>
<AppBoundary> <AppBoundary>

View file

@ -56,7 +56,7 @@ function About() {
return ( return (
<div className={pageLayout.page}> <div className={pageLayout.page}>
<PageMeta titleKey="cloud.about.page_title" /> <PageMeta titleKey={['cloud.about.page_title', 'cloud.general.onboarding']} />
<OverlayScrollbar className={pageLayout.contentContainer}> <OverlayScrollbar className={pageLayout.contentContainer}>
<div className={pageLayout.content}> <div className={pageLayout.content}>
<Case /> <Case />

View file

@ -31,7 +31,7 @@ function Congrats() {
return ( return (
<div className={pageLayout.page}> <div className={pageLayout.page}>
<PageMeta titleKey="cloud.congrats.page_title" /> <PageMeta titleKey={['cloud.congrats.page_title', 'cloud.general.onboarding']} />
<OverlayScrollbar className={pageLayout.contentContainer}> <OverlayScrollbar className={pageLayout.contentContainer}>
<div className={classNames(pageLayout.content, styles.content)}> <div className={classNames(pageLayout.content, styles.content)}>
<CongratsImage className={styles.congratsImage} /> <CongratsImage className={styles.congratsImage} />

View file

@ -107,7 +107,7 @@ function SignInExperience() {
return ( return (
<div className={pageLayout.page}> <div className={pageLayout.page}>
<PageMeta titleKey="cloud.sie.page_title" /> <PageMeta titleKey={['cloud.sie.page_title', 'cloud.general.onboarding']} />
<OverlayScrollbar className={pageLayout.contentContainer}> <OverlayScrollbar className={pageLayout.contentContainer}>
<div className={styles.content}> <div className={styles.content}>
<div className={styles.config}> <div className={styles.config}>

View file

@ -53,7 +53,7 @@ function Welcome() {
return ( return (
<div className={pageLayout.page}> <div className={pageLayout.page}>
<PageMeta titleKey="cloud.welcome.page_title" /> <PageMeta titleKey={['cloud.welcome.page_title', 'cloud.general.onboarding']} />
<OverlayScrollbar className={pageLayout.contentContainer}> <OverlayScrollbar className={pageLayout.contentContainer}>
<div className={classNames(pageLayout.content, styles.content)}> <div className={classNames(pageLayout.content, styles.content)}>
<WelcomeImage className={styles.congrats} /> <WelcomeImage className={styles.congrats} />

View file

@ -1,3 +1,5 @@
import { Component, GeneralEvent } from '@logto/app-insights/custom-event';
import { TrackOnce } from '@logto/app-insights/react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { import {
Route, Route,
@ -41,6 +43,7 @@ function Main() {
return ( return (
<SWRConfig value={swrOptions}> <SWRConfig value={swrOptions}>
<AppBoundary> <AppBoundary>
<TrackOnce component={Component.Console} event={GeneralEvent.Visit} />
<Toast /> <Toast />
<RouterProvider router={router} /> <RouterProvider router={router} />
</AppBoundary> </AppBoundary>

View file

@ -14,7 +14,7 @@ import FormCard from '@/components/FormCard';
import ImageWithErrorFallback from '@/components/ImageWithErrorFallback'; import ImageWithErrorFallback from '@/components/ImageWithErrorFallback';
import UnnamedTrans from '@/components/UnnamedTrans'; import UnnamedTrans from '@/components/UnnamedTrans';
import UserInfoCard from '@/components/UserInfoCard'; import UserInfoCard from '@/components/UserInfoCard';
import { adminTenantEndpoint, getBasename, meApi, profileSocialLinkingKeyPrefix } from '@/consts'; import { adminTenantEndpoint, getBasename, meApi, storageKeys } from '@/consts';
import { useStaticApi } from '@/hooks/use-api'; import { useStaticApi } from '@/hooks/use-api';
import { useConfirmModal } from '@/hooks/use-confirm-modal'; import { useConfirmModal } from '@/hooks/use-confirm-modal';
import useTheme from '@/hooks/use-theme'; import useTheme from '@/hooks/use-theme';
@ -48,7 +48,7 @@ function LinkAccountSection({ user, connectors, onUpdate }: Props) {
.post('me/social/authorization-uri', { json: { connectorId, state, redirectUri } }) .post('me/social/authorization-uri', { json: { connectorId, state, redirectUri } })
.json<{ redirectTo: string }>(); .json<{ redirectTo: string }>();
sessionStorage.setItem(profileSocialLinkingKeyPrefix, connectorId); sessionStorage.setItem(storageKeys.linkingSocialConnector, connectorId);
return redirectTo; return redirectTo;
}, },

View file

@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import AppLoading from '@/components/AppLoading'; import AppLoading from '@/components/AppLoading';
import { adminTenantEndpoint, meApi, profileSocialLinkingKeyPrefix } from '@/consts'; import { adminTenantEndpoint, meApi, storageKeys } from '@/consts';
import { useStaticApi } from '@/hooks/use-api'; import { useStaticApi } from '@/hooks/use-api';
import { useConfirmModal } from '@/hooks/use-confirm-modal'; import { useConfirmModal } from '@/hooks/use-confirm-modal';
@ -19,8 +19,8 @@ function HandleSocialCallback() {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const connectorId = sessionStorage.getItem(profileSocialLinkingKeyPrefix); const connectorId = sessionStorage.getItem(storageKeys.linkingSocialConnector);
sessionStorage.removeItem(profileSocialLinkingKeyPrefix); sessionStorage.removeItem(storageKeys.linkingSocialConnector);
if (connectorId) { if (connectorId) {
const queries = new URLSearchParams(search); const queries = new URLSearchParams(search);

View file

@ -1,3 +1,5 @@
import { Component, CoreEvent, getEventName } from '@logto/app-insights/custom-event';
import { appInsights } from '@logto/app-insights/node';
import type { User, Profile } from '@logto/schemas'; import type { User, Profile } from '@logto/schemas';
import { import {
AdminTenantRole, AdminTenantRole,
@ -196,6 +198,7 @@ export default async function submitInteraction(
await assignInteractionResults(ctx, provider, { login: { accountId: id } }); await assignInteractionResults(ctx, provider, { login: { accountId: id } });
log?.append({ userId: id }); log?.append({ userId: id });
appInsights.client?.trackEvent({ name: getEventName(Component.Core, CoreEvent.Register) });
return; return;
} }
@ -208,9 +211,10 @@ export default async function submitInteraction(
const upsertProfile = await parseUserProfile(connectors, interaction, user); const upsertProfile = await parseUserProfile(connectors, interaction, user);
await updateUserById(accountId, upsertProfile); await updateUserById(accountId, upsertProfile);
await assignInteractionResults(ctx, provider, { login: { accountId } }); await assignInteractionResults(ctx, provider, { login: { accountId } });
appInsights.client?.trackEvent({ name: getEventName(Component.Core, CoreEvent.SignIn) });
return; return;
} }

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Einführung',
},
welcome: { welcome: {
page_title: 'Willkommen', page_title: 'Willkommen',
title: 'Willkommen, lass uns deine eigene Logto Cloud Preview erstellen', title: 'Willkommen, lass uns deine eigene Logto Cloud Preview erstellen',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Onboarding',
},
welcome: { welcome: {
page_title: 'Welcome', page_title: 'Welcome',
title: 'Welcome and lets create your own Logto Cloud Preview', title: 'Welcome and lets create your own Logto Cloud Preview',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Integración',
},
welcome: { welcome: {
page_title: 'Bienvenido', page_title: 'Bienvenido',
title: 'Bienvenido y creemos su propia vista previa de Logto Cloud', title: 'Bienvenido y creemos su propia vista previa de Logto Cloud',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Intégration',
},
welcome: { welcome: {
page_title: 'Bienvenue', page_title: 'Bienvenue',
title: 'Bienvenue et créons votre propre aperçu cloud de Logto', title: 'Bienvenue et créons votre propre aperçu cloud de Logto',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Inizio',
},
welcome: { welcome: {
page_title: 'Benvenuto', page_title: 'Benvenuto',
title: 'Benvenuto e creiamo insieme la tua anteprima di Logto Cloud', title: 'Benvenuto e creiamo insieme la tua anteprima di Logto Cloud',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'オンボーディング',
},
welcome: { welcome: {
page_title: 'ようこそ', page_title: 'ようこそ',
title: 'ようこそ、あなたのLogto Cloud Previewを作成しましょう', title: 'ようこそ、あなたのLogto Cloud Previewを作成しましょう',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: '온보딩',
},
welcome: { welcome: {
page_title: '환영합니다', page_title: '환영합니다',
title: '환영합니다. 당신 만의 Logto Cloud Preview를 만들어 보세요', title: '환영합니다. 당신 만의 Logto Cloud Preview를 만들어 보세요',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Wdrażanie',
},
welcome: { welcome: {
page_title: 'Witaj', page_title: 'Witaj',
title: 'Witaj i stwórz własny podgląd chmury Logto', title: 'Witaj i stwórz własny podgląd chmury Logto',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Integração',
},
welcome: { welcome: {
page_title: 'Boas-vindas', page_title: 'Boas-vindas',
title: 'Boas-vindas e vamos criar sua própria visualização de nuvem Logto', title: 'Boas-vindas e vamos criar sua própria visualização de nuvem Logto',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Introdução',
},
welcome: { welcome: {
page_title: 'Bem-vindo', page_title: 'Bem-vindo',
title: 'Bem-vindo e vamos criar a sua própria visualização da Logto Cloud', title: 'Bem-vindo e vamos criar a sua própria visualização da Logto Cloud',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Вводный курс',
},
welcome: { welcome: {
page_title: 'Добро пожаловать', page_title: 'Добро пожаловать',
title: title:

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: 'Başlatma',
},
welcome: { welcome: {
page_title: 'Hoşgeldiniz', page_title: 'Hoşgeldiniz',
title: 'Hoşgeldiniz ve kendi Logto Cloud Önizlemenizi oluşturalım', title: 'Hoşgeldiniz ve kendi Logto Cloud Önizlemenizi oluşturalım',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: '入门',
},
welcome: { welcome: {
page_title: '欢迎', page_title: '欢迎',
title: '欢迎来到 Logto Cloud预览版让我们一起创建独属于你的体验', title: '欢迎来到 Logto Cloud预览版让我们一起创建独属于你的体验',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: '入門',
},
welcome: { welcome: {
page_title: '歡迎', page_title: '歡迎',
title: '歡迎來到 Logto Cloud預覽版讓我們一起創建獨屬於你的體驗', title: '歡迎來到 Logto Cloud預覽版讓我們一起創建獨屬於你的體驗',

View file

@ -1,4 +1,7 @@
const cloud = { const cloud = {
general: {
onboarding: '入門',
},
welcome: { welcome: {
page_title: '歡迎', page_title: '歡迎',
title: '歡迎來到 Logto Cloud預覽版讓我們一起創建獨屬於你的體驗', title: '歡迎來到 Logto Cloud預覽版讓我們一起創建獨屬於你的體驗',

View file

@ -33,7 +33,7 @@ const App = () => {
<PageContextProvider> <PageContextProvider>
<SettingsProvider> <SettingsProvider>
<AppBoundary> <AppBoundary>
<AppInsightsBoundary> <AppInsightsBoundary cloudRole="ui">
<Routes> <Routes>
<Route path="sign-in/consent" element={<Consent />} /> <Route path="sign-in/consent" element={<Consent />} />
<Route element={<AppLayout />}> <Route element={<AppLayout />}>

View file

@ -23,7 +23,7 @@ const PageMeta = ({ titleKey, trackPageView = true }: Props) => {
useEffect(() => { useEffect(() => {
// Only track once for the same page // Only track once for the same page
if (shouldTrack && isSetupFinished && trackPageView && !pageViewTracked) { if (shouldTrack && isSetupFinished && trackPageView && !pageViewTracked) {
appInsights.trackPageView?.({ name: [rawTitle, 'SIE'].join(' - ') }); appInsights.trackPageView?.({ name: `Main flow: ${rawTitle}` });
setPageViewTracked(true); setPageViewTracked(true);
} }
}, [appInsights, isSetupFinished, pageViewTracked, rawTitle, trackPageView]); }, [appInsights, isSetupFinished, pageViewTracked, rawTitle, trackPageView]);