From 0e49e432452b8db49271ee9dd877dd804a28c751 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Mon, 3 Apr 2023 15:40:56 +0800 Subject: [PATCH] refactor: update title in main flow and report pv for console sign-in (#3651) --- packages/app-insights/package.json | 6 +-- packages/cloud/src/index.ts | 2 +- .../cloud/src/middleware/with-error-report.ts | 2 +- packages/console/src/App.tsx | 1 + packages/core/src/app/init.ts | 2 +- packages/core/src/index.ts | 2 +- .../core/src/middleware/koa-error-handler.ts | 2 +- packages/core/src/oidc/init.ts | 24 +++++++++--- .../phrases-ui/src/locales/de/description.ts | 2 + .../phrases-ui/src/locales/en/description.ts | 2 + .../phrases-ui/src/locales/es/description.ts | 2 + .../phrases-ui/src/locales/fr/description.ts | 2 + .../phrases-ui/src/locales/it/description.ts | 2 + .../phrases-ui/src/locales/ja/description.ts | 2 + .../phrases-ui/src/locales/ko/description.ts | 2 + .../src/locales/pl-pl/description.ts | 8 ++-- .../src/locales/pt-br/description.ts | 2 + .../src/locales/pt-pt/description.ts | 2 + .../phrases-ui/src/locales/ru/description.ts | 2 + .../src/locales/tr-tr/description.ts | 2 + .../src/locales/zh-cn/description.ts | 2 + .../src/locales/zh-hk/description.ts | 2 + .../src/locales/zh-tw/description.ts | 2 + packages/schemas/src/consts/cookie.ts | 1 + packages/schemas/src/consts/index.ts | 1 + packages/schemas/src/types/cookie.ts | 5 +++ packages/schemas/src/types/index.ts | 1 + packages/ui/jest.config.ts | 3 ++ packages/ui/package.json | 6 ++- packages/ui/src/App.tsx | 6 +++ packages/ui/src/Layout/AppLayout/index.tsx | 15 +------- .../ui/src/Layout/LandingPageLayout/index.tsx | 4 +- .../src/Layout/SecondaryPageLayout/index.tsx | 6 ++- packages/ui/src/components/PageMeta/index.tsx | 32 ++++++++++++++++ packages/ui/src/index.html | 2 +- packages/ui/src/pages/ErrorPage/index.tsx | 2 + .../pages/SocialSignInCallback/index.test.tsx | 2 - .../src/pages/VerificationCode/index.test.tsx | 2 +- .../ui/src/pages/VerificationCode/index.tsx | 3 +- packages/ui/src/utils/cookies.ts | 8 ++++ pnpm-lock.yaml | 38 +++++++++++++------ 41 files changed, 163 insertions(+), 51 deletions(-) create mode 100644 packages/schemas/src/consts/cookie.ts create mode 100644 packages/schemas/src/types/cookie.ts create mode 100644 packages/ui/src/components/PageMeta/index.tsx create mode 100644 packages/ui/src/utils/cookies.ts diff --git a/packages/app-insights/package.json b/packages/app-insights/package.json index be6d44755..42362ee99 100644 --- a/packages/app-insights/package.json +++ b/packages/app-insights/package.json @@ -9,11 +9,9 @@ "lib" ], "exports": { - ".": { - "import": "./lib/index.js" - }, "./*": { - "import": "./lib/*" + "import": "./lib/*.js", + "types": "./lib/*.d.ts" } }, "publishConfig": { diff --git a/packages/cloud/src/index.ts b/packages/cloud/src/index.ts index e941fb588..4537b436e 100644 --- a/packages/cloud/src/index.ts +++ b/packages/cloud/src/index.ts @@ -6,7 +6,7 @@ import { findUp } from 'find-up'; dotenv.config({ path: await findUp('.env', {}) }); -const { appInsights } = await import('@logto/app-insights/node.js'); +const { appInsights } = await import('@logto/app-insights/node'); if (appInsights.setup('logto-cloud')) { console.debug('Initialized ApplicationInsights'); diff --git a/packages/cloud/src/middleware/with-error-report.ts b/packages/cloud/src/middleware/with-error-report.ts index 86b387ce0..0972ac636 100644 --- a/packages/cloud/src/middleware/with-error-report.ts +++ b/packages/cloud/src/middleware/with-error-report.ts @@ -1,4 +1,4 @@ -import { appInsights } from '@logto/app-insights/node.js'; +import { appInsights } from '@logto/app-insights/node'; import { tryThat } from '@silverhand/essentials'; import type { BaseContext, NextFunction } from '@withtyped/server'; diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx index ac7c7fad2..eda4dbe02 100644 --- a/packages/console/src/App.tsx +++ b/packages/console/src/App.tsx @@ -29,6 +29,7 @@ import TenantsProvider, { TenantsContext } from './contexts/TenantsProvider'; if (appInsightsReact.setup()) { console.debug('Initialized ApplicationInsights'); } + void initI18n(); function Content() { diff --git a/packages/core/src/app/init.ts b/packages/core/src/app/init.ts index da0b9737b..6cc8a4379 100644 --- a/packages/core/src/app/init.ts +++ b/packages/core/src/app/init.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises'; import http2 from 'node:http2'; -import { appInsights } from '@logto/app-insights/node.js'; +import { appInsights } from '@logto/app-insights/node'; import { toTitle, trySafe } from '@silverhand/essentials'; import chalk from 'chalk'; import type Koa from 'koa'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 49b534e2b..7cabb0b61 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,7 +8,7 @@ import SystemContext from './tenants/SystemContext.js'; dotenv.config({ path: await findUp('.env', {}) }); -const { appInsights } = await import('@logto/app-insights/node.js'); +const { appInsights } = await import('@logto/app-insights/node'); if (appInsights.setup('logto')) { console.debug('Initialized ApplicationInsights'); diff --git a/packages/core/src/middleware/koa-error-handler.ts b/packages/core/src/middleware/koa-error-handler.ts index 2a6e9cfef..5dfe855ce 100644 --- a/packages/core/src/middleware/koa-error-handler.ts +++ b/packages/core/src/middleware/koa-error-handler.ts @@ -1,4 +1,4 @@ -import { appInsights } from '@logto/app-insights/node.js'; +import { appInsights } from '@logto/app-insights/node'; import type { RequestErrorBody } from '@logto/schemas'; import type { Middleware } from 'koa'; import { HttpError } from 'koa'; diff --git a/packages/core/src/oidc/init.ts b/packages/core/src/oidc/init.ts index 94d61d6e3..077f4a564 100644 --- a/packages/core/src/oidc/init.ts +++ b/packages/core/src/oidc/init.ts @@ -4,7 +4,13 @@ import { readFileSync } from 'node:fs'; import { userClaims } from '@logto/core-kit'; import type { I18nKey } from '@logto/phrases'; -import { CustomClientMetadataKey, demoAppApplicationId } from '@logto/schemas'; +import { + CustomClientMetadataKey, + demoAppApplicationId, + logtoCookieKey, + type LogtoUiCookie, +} from '@logto/schemas'; +import { conditional } from '@silverhand/essentials'; import i18next from 'i18next'; import Provider, { errors, type ResourceServer } from 'oidc-provider'; import snakecaseKeys from 'snakecase-keys'; @@ -145,15 +151,23 @@ export default function initOidc( }, }, interactions: { - url: (ctx, interaction) => { - const isDemoApp = interaction.params.client_id === demoAppApplicationId; + url: (ctx, { params: { client_id: appId }, prompt }) => { + const isDemoApp = appId === demoAppApplicationId; + + ctx.cookies.set( + logtoCookieKey, + JSON.stringify({ + appId: conditional(Boolean(appId) && String(appId)), + } satisfies LogtoUiCookie), + { sameSite: 'lax', overwrite: true, httpOnly: false } + ); const appendParameters = (path: string) => { // `notification` is for showing a text banner on the homepage return isDemoApp ? path + `?notification=demo_app.notification&no_cache` : path; }; - switch (interaction.prompt.name) { + switch (prompt.name) { case 'login': { const isSignUp = ctx.oidc.params?.[OIDCExtraParametersKey.InteractionMode] === InteractionMode.signUp; @@ -166,7 +180,7 @@ export default function initOidc( } default: { - throw new Error(`Prompt not supported: ${interaction.prompt.name}`); + throw new Error(`Prompt not supported: ${prompt.name}`); } } }, diff --git a/packages/phrases-ui/src/locales/de/description.ts b/packages/phrases-ui/src/locales/de/description.ts index d2bba651f..3e679ba0f 100644 --- a/packages/phrases-ui/src/locales/de/description.ts +++ b/packages/phrases-ui/src/locales/de/description.ts @@ -60,6 +60,8 @@ const description = { create_your_account: 'Erstelle dein Konto', sign_in_to_your_account: 'Melde dich in deinem Konto an', no_region_code_found: 'Kein Regionencode gefunden', + verify_email: 'Bestätige deine E-Mail-Adresse', + verify_phone: 'Bestätige deine Telefonnummer', }; export default description; diff --git a/packages/phrases-ui/src/locales/en/description.ts b/packages/phrases-ui/src/locales/en/description.ts index cb0f38195..e72e46860 100644 --- a/packages/phrases-ui/src/locales/en/description.ts +++ b/packages/phrases-ui/src/locales/en/description.ts @@ -57,6 +57,8 @@ const description = { create_your_account: 'Create your account', sign_in_to_your_account: 'Sign in to your account', no_region_code_found: 'No region code found', + verify_email: 'Verify your email', + verify_phone: 'Verify your phone number', }; export default description; diff --git a/packages/phrases-ui/src/locales/es/description.ts b/packages/phrases-ui/src/locales/es/description.ts index 379fd7dcc..806e88ba3 100644 --- a/packages/phrases-ui/src/locales/es/description.ts +++ b/packages/phrases-ui/src/locales/es/description.ts @@ -58,6 +58,8 @@ const description = { create_your_account: 'Cree su cuenta', sign_in_to_your_account: 'Inicie sesión en su cuenta', no_region_code_found: 'No se encontró código de región', + verify_email: 'Verificar su correo electrónico', + verify_phone: 'Verificar su número de teléfono', }; export default description; diff --git a/packages/phrases-ui/src/locales/fr/description.ts b/packages/phrases-ui/src/locales/fr/description.ts index 09ae2ce3a..43d87f9f8 100644 --- a/packages/phrases-ui/src/locales/fr/description.ts +++ b/packages/phrases-ui/src/locales/fr/description.ts @@ -60,6 +60,8 @@ const description = { create_your_account: 'Créer votre compte', sign_in_to_your_account: 'Connecte-toi à ton compte', no_region_code_found: 'Aucun code de région trouvé', + verify_email: 'Vérifiez votre e-mail', + verify_phone: 'Vérifiez votre numéro de téléphone', }; export default description; diff --git a/packages/phrases-ui/src/locales/it/description.ts b/packages/phrases-ui/src/locales/it/description.ts index c823bed22..786b49a56 100644 --- a/packages/phrases-ui/src/locales/it/description.ts +++ b/packages/phrases-ui/src/locales/it/description.ts @@ -56,6 +56,8 @@ const description = { create_your_account: 'Crea il tuo account', sign_in_to_your_account: 'Accedi al tuo account', no_region_code_found: 'Nessun codice di regione trovato', + verify_email: 'Verifica la tua email', + verify_phone: 'Verifica il tuo numero di telefono', }; export default description; diff --git a/packages/phrases-ui/src/locales/ja/description.ts b/packages/phrases-ui/src/locales/ja/description.ts index ac0ad5b3c..acea23801 100644 --- a/packages/phrases-ui/src/locales/ja/description.ts +++ b/packages/phrases-ui/src/locales/ja/description.ts @@ -58,6 +58,8 @@ const description = { create_your_account: 'アカウントを作成する', sign_in_to_your_account: 'アカウントにサインインする', no_region_code_found: '地域コードが見つかりません', + verify_email: 'Eメールを確認する', + verify_phone: '電話番号を確認する', }; export default description; diff --git a/packages/phrases-ui/src/locales/ko/description.ts b/packages/phrases-ui/src/locales/ko/description.ts index d41368a3f..4762dc2a5 100644 --- a/packages/phrases-ui/src/locales/ko/description.ts +++ b/packages/phrases-ui/src/locales/ko/description.ts @@ -53,6 +53,8 @@ const description = { create_your_account: '계정 생성하기', sign_in_to_your_account: '계정에 로그인하세요', no_region_code_found: '지역 코드를 찾을 수 없습니다.', + verify_email: '이메일 인증', + verify_phone: '휴대전화번호 인증', }; export default description; diff --git a/packages/phrases-ui/src/locales/pl-pl/description.ts b/packages/phrases-ui/src/locales/pl-pl/description.ts index 2666dce04..41c048b89 100644 --- a/packages/phrases-ui/src/locales/pl-pl/description.ts +++ b/packages/phrases-ui/src/locales/pl-pl/description.ts @@ -1,4 +1,4 @@ -const opis = { +const description = { email: 'adres email', phone_number: 'numer telefonu', username: 'nazwa użytkownika', @@ -29,7 +29,7 @@ const opis = { social_link_email: 'Możesz połączyć kolejny adres email', social_link_phone: 'Możesz połączyć kolejny numer telefonu', social_link_email_or_phone: 'Możesz połączyć kolejny adres email lub numer telefonu', - social_bind_with_existing: 'Znaleźliśmy powiązane konto, możesz go połączyć bezpośrednio.', + social_bind_with_existing: 'Znaleźliśmy powiązane konto, możesz je połączyć bezpośrednio.', reset_password: 'Zresetuj hasło', reset_password_description: 'Wpisz {{types, lista(type: złączonych;)}} związanego z twoim kontem, a wyślemy ci kod weryfikacyjny do zresetowania hasła.', @@ -56,6 +56,8 @@ const opis = { create_your_account: 'Utwórz konto', sign_in_to_your_account: 'Zaloguj się do swojego konta', no_region_code_found: 'Nie znaleziono kodu regionu', + verify_email: 'Potwierdź swój email', + verify_phone: 'Potwierdź swój numer telefonu', }; -export default opis; +export default description; diff --git a/packages/phrases-ui/src/locales/pt-br/description.ts b/packages/phrases-ui/src/locales/pt-br/description.ts index 22d9298ee..37836e1f0 100644 --- a/packages/phrases-ui/src/locales/pt-br/description.ts +++ b/packages/phrases-ui/src/locales/pt-br/description.ts @@ -55,6 +55,8 @@ const description = { create_your_account: 'Crie sua conta', sign_in_to_your_account: 'Faça login na sua conta', no_region_code_found: 'Não foi possível encontrar o código de região do seu telefone.', + verify_email: 'Verificar e-mail', + verify_phone: 'Verificar número de telefone', }; export default description; diff --git a/packages/phrases-ui/src/locales/pt-pt/description.ts b/packages/phrases-ui/src/locales/pt-pt/description.ts index b716a8c48..aa132a064 100644 --- a/packages/phrases-ui/src/locales/pt-pt/description.ts +++ b/packages/phrases-ui/src/locales/pt-pt/description.ts @@ -55,6 +55,8 @@ const description = { create_your_account: 'Crie a sua conta', sign_in_to_your_account: 'Inicie sessão na sua conta', no_region_code_found: 'Não foi possível encontrar o código de região do seu telefone.', + verify_email: 'Verifique o seu email', + verify_phone: 'Verifique o seu número de telefone', }; export default description; diff --git a/packages/phrases-ui/src/locales/ru/description.ts b/packages/phrases-ui/src/locales/ru/description.ts index 689e68b13..766e9a468 100644 --- a/packages/phrases-ui/src/locales/ru/description.ts +++ b/packages/phrases-ui/src/locales/ru/description.ts @@ -59,6 +59,8 @@ const description = { create_your_account: 'Создайте свой аккаунт', sign_in_to_your_account: 'Войди в свой аккаунт', no_region_code_found: 'Не удалось определить код региона', + verify_email: 'Подтвердите Ваш электронный адрес', + verify_phone: 'Подтвердите свой номер телефона', }; export default description; diff --git a/packages/phrases-ui/src/locales/tr-tr/description.ts b/packages/phrases-ui/src/locales/tr-tr/description.ts index 315b313d7..6e6dffbe8 100644 --- a/packages/phrases-ui/src/locales/tr-tr/description.ts +++ b/packages/phrases-ui/src/locales/tr-tr/description.ts @@ -56,6 +56,8 @@ const description = { create_your_account: 'Hesabını oluştur', sign_in_to_your_account: 'Hesabına giriş yap', no_region_code_found: 'Bölge kodu bulunamadı', + verify_email: 'E-postanızın doğrulanması', + verify_phone: 'Telefon numaranızın doğrulanması', }; export default description; diff --git a/packages/phrases-ui/src/locales/zh-cn/description.ts b/packages/phrases-ui/src/locales/zh-cn/description.ts index f898ea36d..7dfb11806 100644 --- a/packages/phrases-ui/src/locales/zh-cn/description.ts +++ b/packages/phrases-ui/src/locales/zh-cn/description.ts @@ -49,6 +49,8 @@ const description = { create_your_account: '注册你的账号', sign_in_to_your_account: '登录你的账号', no_region_code_found: '没有找到区域码', + verify_email: '验证你的邮箱', + verify_phone: '验证你的手机号', }; export default description; diff --git a/packages/phrases-ui/src/locales/zh-hk/description.ts b/packages/phrases-ui/src/locales/zh-hk/description.ts index cf9723c6d..cae1bf8e3 100644 --- a/packages/phrases-ui/src/locales/zh-hk/description.ts +++ b/packages/phrases-ui/src/locales/zh-hk/description.ts @@ -49,6 +49,8 @@ const description = { create_your_account: '註冊你的帳號', sign_in_to_your_account: '登錄你的帳號', no_region_code_found: '沒有找到區域碼', + verify_email: '驗證你的郵箱', + verify_phone: '驗證你的手機號', }; export default description; diff --git a/packages/phrases-ui/src/locales/zh-tw/description.ts b/packages/phrases-ui/src/locales/zh-tw/description.ts index 23a841383..ecd7d41e4 100644 --- a/packages/phrases-ui/src/locales/zh-tw/description.ts +++ b/packages/phrases-ui/src/locales/zh-tw/description.ts @@ -49,6 +49,8 @@ const description = { create_your_account: '註冊你的帳號', sign_in_to_your_account: '登錄你的帳號', no_region_code_found: '沒有找到區域碼', + verify_email: '驗證你的郵箱', + verify_phone: '驗證你的手機號碼', }; export default description; diff --git a/packages/schemas/src/consts/cookie.ts b/packages/schemas/src/consts/cookie.ts new file mode 100644 index 000000000..d211e8114 --- /dev/null +++ b/packages/schemas/src/consts/cookie.ts @@ -0,0 +1 @@ +export const logtoCookieKey = '_logto'; diff --git a/packages/schemas/src/consts/index.ts b/packages/schemas/src/consts/index.ts index 723c62852..8fbbffb3b 100644 --- a/packages/schemas/src/consts/index.ts +++ b/packages/schemas/src/consts/index.ts @@ -1,2 +1,3 @@ +export * from './cookie.js'; export * from './system.js'; export * from './oidc.js'; diff --git a/packages/schemas/src/types/cookie.ts b/packages/schemas/src/types/cookie.ts new file mode 100644 index 000000000..4e76f7bb0 --- /dev/null +++ b/packages/schemas/src/types/cookie.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const logtoUiCookieGuard = z.object({ appId: z.string() }).partial(); + +export type LogtoUiCookie = z.infer; diff --git a/packages/schemas/src/types/index.ts b/packages/schemas/src/types/index.ts index f1ccceb33..b44fd4305 100644 --- a/packages/schemas/src/types/index.ts +++ b/packages/schemas/src/types/index.ts @@ -16,3 +16,4 @@ export * from './user-assets.js'; export * from './hook.js'; export * from './service-log.js'; export * from './theme.js'; +export * from './cookie.js'; diff --git a/packages/ui/jest.config.ts b/packages/ui/jest.config.ts index acb36ac41..20f112d08 100644 --- a/packages/ui/jest.config.ts +++ b/packages/ui/jest.config.ts @@ -9,6 +9,9 @@ const config: Config.InitialOptions = { '\\.(svg)$': 'jest-transformer-svg', '\\.(png)$': 'jest-transform-stub', }, + moduleNameMapper: { + '^@logto/app-insights/(.*)$': '/node_modules/@logto/app-insights/$1', + }, }), // Will update common config soon transformIgnorePatterns: ['node_modules/(?!(.*(nanoid|jose|ky|@logto|@silverhand))/)'], diff --git a/packages/ui/package.json b/packages/ui/package.json index 3739983eb..7eaf937df 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -17,6 +17,7 @@ "test": "jest" }, "devDependencies": { + "@logto/app-insights": "workspace:^", "@logto/connector-kit": "workspace:^", "@logto/core-kit": "workspace:^", "@logto/language-kit": "workspace:^", @@ -42,6 +43,7 @@ "@types/jest": "^29.4.0", "@types/react": "^18.0.31", "@types/react-dom": "^18.0.0", + "@types/react-helmet": "^6.1.6", "@types/react-modal": "^3.13.1", "@types/react-router-dom": "^5.3.2", "camelcase-keys": "^8.0.0", @@ -56,7 +58,7 @@ "jest-environment-jsdom": "^29.0.0", "jest-transform-stub": "^2.0.0", "jest-transformer-svg": "^2.0.0", - "js-base64": "^3.7.2", + "js-base64": "^3.7.5", "ky": "^0.33.0", "libphonenumber-js": "^1.9.49", "lint-staged": "^13.0.0", @@ -68,6 +70,7 @@ "react": "^18.0.0", "react-device-detect": "^2.2.2", "react-dom": "^18.0.0", + "react-helmet": "^6.1.0", "react-hook-form": "^7.34.0", "react-i18next": "^11.18.3", "react-modal": "^3.15.1", @@ -77,6 +80,7 @@ "react-top-loading-bar": "^2.3.1", "stylelint": "^15.0.0", "superstruct": "^0.16.0", + "tiny-cookie": "^2.4.1", "ts-jest": "^29.0.5", "typescript": "^5.0.0", "use-debounced-loader": "^0.1.1", diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 29983e1c1..2be61caf4 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -1,3 +1,4 @@ +import { appInsightsReact } from '@logto/app-insights/lib/react'; import { Route, Routes, BrowserRouter } from 'react-router-dom'; import AppLayout from './Layout/AppLayout'; @@ -20,10 +21,15 @@ import SocialLinkAccount from './pages/SocialLinkAccount'; import SocialSignIn from './pages/SocialSignInCallback'; import Springboard from './pages/Springboard'; import VerificationCode from './pages/VerificationCode'; +import { shouldTrack } from './utils/cookies'; import { handleSearchParametersData } from './utils/search-parameters'; import './scss/normalized.scss'; +if (shouldTrack && appInsightsReact.setup()) { + console.debug('Initialized ApplicationInsights'); +} + handleSearchParametersData(); const App = () => { diff --git a/packages/ui/src/Layout/AppLayout/index.tsx b/packages/ui/src/Layout/AppLayout/index.tsx index 5dad8cd1c..76b519c8b 100644 --- a/packages/ui/src/Layout/AppLayout/index.tsx +++ b/packages/ui/src/Layout/AppLayout/index.tsx @@ -1,28 +1,15 @@ import classNames from 'classnames'; -import { useEffect } from 'react'; -import { Outlet, useLocation } from 'react-router-dom'; +import { Outlet } from 'react-router-dom'; import LogtoSignature from '@/components/LogtoSignature'; import usePlatform from '@/hooks/use-platform'; import { layoutClassNames } from '@/utils/consts'; -import { parseHtmlTitle } from '@/utils/sign-in-experience'; import CustomContent from './CustomContent'; import * as styles from './index.module.scss'; const AppLayout = () => { const { isMobile } = usePlatform(); - const location = useLocation(); - - useEffect(() => { - const { pathname } = location; - const title = parseHtmlTitle(pathname); - - if (title) { - // eslint-disable-next-line @silverhand/fp/no-mutation - document.title = title; - } - }, [location]); return (
diff --git a/packages/ui/src/Layout/LandingPageLayout/index.tsx b/packages/ui/src/Layout/LandingPageLayout/index.tsx index da4301c41..96f9c1956 100644 --- a/packages/ui/src/Layout/LandingPageLayout/index.tsx +++ b/packages/ui/src/Layout/LandingPageLayout/index.tsx @@ -5,6 +5,7 @@ import type { TFuncKey } from 'react-i18next'; import PageContext from '@/Providers/PageContextProvider/PageContext'; import BrandingHeader from '@/components/BrandingHeader'; +import PageMeta from '@/components/PageMeta'; import { layoutClassNames } from '@/utils/consts'; import { getBrandingLogoUrl } from '@/utils/logo'; @@ -15,7 +16,7 @@ import * as styles from './index.module.scss'; type Props = { children: ReactNode; className?: string; - title?: TFuncKey; + title: TFuncKey; }; const LandingPageLayout = ({ children, className, title }: Props) => { @@ -32,6 +33,7 @@ const LandingPageLayout = ({ children, className, title }: Props) => { return ( <> + {platform === 'web' &&
}
; descriptionProps?: Record; @@ -30,13 +31,14 @@ const SecondaryPageLayout = ({ return (
+ {isMobile && notification && ( )}
- {title &&
{t(title, titleProps)}
} +
{t(title, titleProps)}
{description && (
{t(description, descriptionProps)}
)} diff --git a/packages/ui/src/components/PageMeta/index.tsx b/packages/ui/src/components/PageMeta/index.tsx new file mode 100644 index 000000000..54a26ba6c --- /dev/null +++ b/packages/ui/src/components/PageMeta/index.tsx @@ -0,0 +1,32 @@ +import { appInsightsReact } from '@logto/app-insights/lib/react'; +import { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; +import { type TFuncKey, useTranslation } from 'react-i18next'; + +import { shouldTrack } from '@/utils/cookies'; + +type Props = { + titleKey: TFuncKey | TFuncKey[]; + // eslint-disable-next-line react/boolean-prop-naming + trackPageView?: boolean; +}; + +const PageMeta = ({ titleKey, trackPageView = true }: Props) => { + const { t } = useTranslation(); + const [pageViewTracked, setPageViewTracked] = useState(false); + const keys = typeof titleKey === 'string' ? [titleKey] : titleKey; + const rawTitle = keys.map((key) => t(key, { lng: 'en' })).join(' - '); + const title = keys.map((key) => t(key)).join(' - '); + + useEffect(() => { + // Only track once for the same page + if (shouldTrack && trackPageView && !pageViewTracked) { + appInsightsReact.trackPageView?.({ name: [rawTitle, 'SIE'].join(' - ') }); + setPageViewTracked(true); + } + }, [pageViewTracked, rawTitle, trackPageView]); + + return ; +}; + +export default PageMeta; diff --git a/packages/ui/src/index.html b/packages/ui/src/index.html index 6257ec934..8df3f2db5 100644 --- a/packages/ui/src/index.html +++ b/packages/ui/src/index.html @@ -4,7 +4,7 @@ - Logto + diff --git a/packages/ui/src/pages/ErrorPage/index.tsx b/packages/ui/src/pages/ErrorPage/index.tsx index 894b667fb..1acab0293 100644 --- a/packages/ui/src/pages/ErrorPage/index.tsx +++ b/packages/ui/src/pages/ErrorPage/index.tsx @@ -8,6 +8,7 @@ import PageContext from '@/Providers/PageContextProvider/PageContext'; import EmptyStateDark from '@/assets/icons/empty-state-dark.svg'; import EmptyState from '@/assets/icons/empty-state.svg'; import NavBar from '@/components/NavBar'; +import PageMeta from '@/components/PageMeta'; import * as styles from './index.module.scss'; @@ -26,6 +27,7 @@ const ErrorPage = ({ title = 'description.not_found', message, rawMessage }: Pro return ( + {history.length > 1 && }
{theme === Theme.Light ? : } diff --git a/packages/ui/src/pages/SocialSignInCallback/index.test.tsx b/packages/ui/src/pages/SocialSignInCallback/index.test.tsx index 39c6a6653..5929df675 100644 --- a/packages/ui/src/pages/SocialSignInCallback/index.test.tsx +++ b/packages/ui/src/pages/SocialSignInCallback/index.test.tsx @@ -8,8 +8,6 @@ import { generateState, storeState } from '@/utils/social-connectors'; import SocialCallback from '.'; -const origin = 'http://localhost:3000'; - jest.mock('i18next', () => ({ ...jest.requireActual('i18next'), language: 'en', diff --git a/packages/ui/src/pages/VerificationCode/index.test.tsx b/packages/ui/src/pages/VerificationCode/index.test.tsx index de8fb44e4..ff43d74d5 100644 --- a/packages/ui/src/pages/VerificationCode/index.test.tsx +++ b/packages/ui/src/pages/VerificationCode/index.test.tsx @@ -23,7 +23,7 @@ describe('VerificationCode Page', () => { { initialEntries: ['/sign-in/verification-code'] } ); - expect(queryByText('action.enter_passcode')).not.toBeNull(); + expect(queryByText('description.verify_email')).not.toBeNull(); expect(queryByText('description.enter_passcode')).not.toBeNull(); }); diff --git a/packages/ui/src/pages/VerificationCode/index.tsx b/packages/ui/src/pages/VerificationCode/index.tsx index 9d66d6bab..3158c3912 100644 --- a/packages/ui/src/pages/VerificationCode/index.tsx +++ b/packages/ui/src/pages/VerificationCode/index.tsx @@ -41,9 +41,10 @@ const VerificationCode = () => { return ( logtoUiCookieGuard.parse(getCookie(logtoCookieKey, JSON.parse))) ?? {}; + +export const shouldTrack = logtoCookies.appId === adminConsoleApplicationId; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e689e3b0..5174df75c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1342,6 +1342,9 @@ importers: packages/ui: devDependencies: + '@logto/app-insights': + specifier: workspace:^ + version: link:../app-insights '@logto/connector-kit': specifier: workspace:^ version: link:../toolkit/connector-kit @@ -1417,6 +1420,9 @@ importers: '@types/react-dom': specifier: ^18.0.0 version: 18.0.6 + '@types/react-helmet': + specifier: ^6.1.6 + version: 6.1.6 '@types/react-modal': specifier: ^3.13.1 version: 3.13.1 @@ -1460,8 +1466,8 @@ importers: specifier: ^2.0.0 version: 2.0.0(jest@29.5.0)(react@18.2.0) js-base64: - specifier: ^3.7.2 - version: 3.7.2 + specifier: ^3.7.5 + version: 3.7.5 ky: specifier: ^0.33.0 version: 0.33.0 @@ -1495,6 +1501,9 @@ importers: react-dom: specifier: ^18.0.0 version: 18.2.0(react@18.2.0) + react-helmet: + specifier: ^6.1.0 + version: 6.1.0(react@18.2.0) react-hook-form: specifier: ^7.34.0 version: 7.34.0(react@18.2.0) @@ -1522,6 +1531,9 @@ importers: superstruct: specifier: ^0.16.0 version: 0.16.0 + tiny-cookie: + specifier: ^2.4.1 + version: 2.4.1 ts-jest: specifier: ^29.0.5 version: 29.0.5(@babel/core@7.20.2)(@jest/types@29.3.1)(jest@29.5.0)(typescript@5.0.2) @@ -2716,7 +2728,7 @@ packages: '@types/node': 18.11.18 jest-message-util: 29.4.1 jest-mock: 29.4.1 - jest-util: 29.4.1 + jest-util: 29.5.0 dev: true /@jest/fake-timers@29.5.0: @@ -3025,7 +3037,7 @@ packages: dependencies: '@logto/client': 1.1.0 '@silverhand/essentials': 1.3.0 - js-base64: 3.7.4 + js-base64: 3.7.5 dev: true /@logto/client@1.1.0: @@ -9639,7 +9651,7 @@ packages: '@jest/types': 29.5.0 '@types/node': 18.11.18 jest-mock: 29.4.1 - jest-util: 29.4.1 + jest-util: 29.5.0 dev: true /jest-environment-node@29.5.0: @@ -9782,7 +9794,7 @@ packages: dependencies: '@jest/types': 29.5.0 '@types/node': 18.11.18 - jest-util: 29.4.1 + jest-util: 29.5.0 dev: true /jest-mock@29.5.0: @@ -10080,14 +10092,14 @@ packages: /jose@4.11.1: resolution: {integrity: sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q==} - /js-base64@3.7.2: - resolution: {integrity: sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==} - dev: true - /js-base64@3.7.4: resolution: {integrity: sha512-wpM/wi20Tl+3ifTyi0RdDckS4YTD4Lf953mBRrpG8547T7hInHNPEj8+ck4gB8VDcGyeAWFK++Wb/fU1BeavKQ==} dev: true + /js-base64@3.7.5: + resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} + dev: true + /js-sdsl@4.3.0: resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} dev: true @@ -11670,7 +11682,7 @@ packages: dev: true /object-assign@4.1.1: - resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} dev: true @@ -14628,6 +14640,10 @@ packages: resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==} dev: true + /tiny-cookie@2.4.1: + resolution: {integrity: sha512-h8ueaMyvUd/9ZfRqCfa1t+0tXqfVFhdK8WpLHz8VXMqsiaj3Sqg64AOCH/xevLQGZk0ZV+/75ouITdkvp3taVA==} + dev: true + /tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} dependencies: