From e3e8b14569d5fe03c0a69579bbcde5c72a4b0b95 Mon Sep 17 00:00:00 2001
From: simeng-li <simeng@silverhand.io>
Date: Thu, 28 Mar 2024 16:32:36 +0800
Subject: [PATCH 1/3] refactor(console): rename the jwtClaims page

rename the jwtClaims page
---
 .../CodeEditorLoadingContext.ts               |  0
 .../Main.tsx                                  |  0
 .../ActionButton/CodeClearButton.tsx          |  0
 .../ActionButton/CodeRestoreButton.tsx        |  0
 .../MonacoCodeEditor/ActionButton/index.tsx   |  0
 .../MonacoCodeEditor/config.ts                |  0
 .../MonacoCodeEditor/index.module.scss        |  0
 .../MonacoCodeEditor/index.tsx                |  0
 .../MonacoCodeEditor/type.ts                  |  0
 .../MonacoCodeEditor/use-editor-height.ts     |  0
 .../PageLoadingSkeleton/index.module.scss     |  0
 .../PageLoadingSkeleton/index.tsx             |  0
 .../ScriptSection.tsx                         |  0
 .../EnvironmentVariablesField.tsx             |  0
 .../SettingsSection/GuideCard.tsx             |  0
 .../SettingsSection/InstructionTab.tsx        |  0
 .../SettingsSection/TestResult.tsx            |  0
 .../SettingsSection/TestTab.tsx               |  0
 .../SettingsSection/index.module.scss         |  0
 .../SettingsSection/index.tsx                 |  0
 .../index.module.scss                         |  0
 .../index.tsx                                 | 31 +++++--------------
 .../type.ts                                   |  0
 .../use-jwt-customizer.ts                     |  0
 .../utils/config.tsx                          |  0
 .../utils/format.ts                           |  0
 .../utils/type-definitions.ts                 |  0
 27 files changed, 7 insertions(+), 24 deletions(-)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/CodeEditorLoadingContext.ts (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/Main.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/MonacoCodeEditor/ActionButton/CodeClearButton.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/MonacoCodeEditor/ActionButton/CodeRestoreButton.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/MonacoCodeEditor/ActionButton/index.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/MonacoCodeEditor/config.ts (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/MonacoCodeEditor/index.module.scss (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/MonacoCodeEditor/index.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/MonacoCodeEditor/type.ts (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/MonacoCodeEditor/use-editor-height.ts (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/PageLoadingSkeleton/index.module.scss (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/PageLoadingSkeleton/index.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/ScriptSection.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/SettingsSection/EnvironmentVariablesField.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/SettingsSection/GuideCard.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/SettingsSection/InstructionTab.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/SettingsSection/TestResult.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/SettingsSection/TestTab.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/SettingsSection/index.module.scss (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/SettingsSection/index.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/index.module.scss (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/index.tsx (53%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/type.ts (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/use-jwt-customizer.ts (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/utils/config.tsx (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/utils/format.ts (100%)
 rename packages/console/src/pages/{JwtClaims => CustomizeJwtDetails}/utils/type-definitions.ts (100%)

diff --git a/packages/console/src/pages/JwtClaims/CodeEditorLoadingContext.ts b/packages/console/src/pages/CustomizeJwtDetails/CodeEditorLoadingContext.ts
similarity index 100%
rename from packages/console/src/pages/JwtClaims/CodeEditorLoadingContext.ts
rename to packages/console/src/pages/CustomizeJwtDetails/CodeEditorLoadingContext.ts
diff --git a/packages/console/src/pages/JwtClaims/Main.tsx b/packages/console/src/pages/CustomizeJwtDetails/Main.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/Main.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/Main.tsx
diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/ActionButton/CodeClearButton.tsx b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/CodeClearButton.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/MonacoCodeEditor/ActionButton/CodeClearButton.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/CodeClearButton.tsx
diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/ActionButton/CodeRestoreButton.tsx b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/CodeRestoreButton.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/MonacoCodeEditor/ActionButton/CodeRestoreButton.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/CodeRestoreButton.tsx
diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/ActionButton/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/index.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/MonacoCodeEditor/ActionButton/index.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/index.tsx
diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/config.ts b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/config.ts
similarity index 100%
rename from packages/console/src/pages/JwtClaims/MonacoCodeEditor/config.ts
rename to packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/config.ts
diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/index.module.scss
similarity index 100%
rename from packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.module.scss
rename to packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/index.module.scss
diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/index.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/index.tsx
diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/type.ts b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/type.ts
similarity index 100%
rename from packages/console/src/pages/JwtClaims/MonacoCodeEditor/type.ts
rename to packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/type.ts
diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/use-editor-height.ts b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/use-editor-height.ts
similarity index 100%
rename from packages/console/src/pages/JwtClaims/MonacoCodeEditor/use-editor-height.ts
rename to packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/use-editor-height.ts
diff --git a/packages/console/src/pages/JwtClaims/PageLoadingSkeleton/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.module.scss
similarity index 100%
rename from packages/console/src/pages/JwtClaims/PageLoadingSkeleton/index.module.scss
rename to packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.module.scss
diff --git a/packages/console/src/pages/JwtClaims/PageLoadingSkeleton/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/PageLoadingSkeleton/index.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.tsx
diff --git a/packages/console/src/pages/JwtClaims/ScriptSection.tsx b/packages/console/src/pages/CustomizeJwtDetails/ScriptSection.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/ScriptSection.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/ScriptSection.tsx
diff --git a/packages/console/src/pages/JwtClaims/SettingsSection/EnvironmentVariablesField.tsx b/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/EnvironmentVariablesField.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/SettingsSection/EnvironmentVariablesField.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/SettingsSection/EnvironmentVariablesField.tsx
diff --git a/packages/console/src/pages/JwtClaims/SettingsSection/GuideCard.tsx b/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/GuideCard.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/SettingsSection/GuideCard.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/SettingsSection/GuideCard.tsx
diff --git a/packages/console/src/pages/JwtClaims/SettingsSection/InstructionTab.tsx b/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/InstructionTab.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/SettingsSection/InstructionTab.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/SettingsSection/InstructionTab.tsx
diff --git a/packages/console/src/pages/JwtClaims/SettingsSection/TestResult.tsx b/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestResult.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/SettingsSection/TestResult.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestResult.tsx
diff --git a/packages/console/src/pages/JwtClaims/SettingsSection/TestTab.tsx b/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestTab.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/SettingsSection/TestTab.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestTab.tsx
diff --git a/packages/console/src/pages/JwtClaims/SettingsSection/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/index.module.scss
similarity index 100%
rename from packages/console/src/pages/JwtClaims/SettingsSection/index.module.scss
rename to packages/console/src/pages/CustomizeJwtDetails/SettingsSection/index.module.scss
diff --git a/packages/console/src/pages/JwtClaims/SettingsSection/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/index.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/SettingsSection/index.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/SettingsSection/index.tsx
diff --git a/packages/console/src/pages/JwtClaims/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/index.module.scss
similarity index 100%
rename from packages/console/src/pages/JwtClaims/index.module.scss
rename to packages/console/src/pages/CustomizeJwtDetails/index.module.scss
diff --git a/packages/console/src/pages/JwtClaims/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/index.tsx
similarity index 53%
rename from packages/console/src/pages/JwtClaims/index.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/index.tsx
index e6a42dd5a..92c4fc8e0 100644
--- a/packages/console/src/pages/JwtClaims/index.tsx
+++ b/packages/console/src/pages/CustomizeJwtDetails/index.tsx
@@ -1,10 +1,8 @@
 import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
-import { LogtoJwtTokenPath } from '@logto/schemas';
+import { type LogtoJwtTokenPath } from '@logto/schemas';
 import { useMemo, useState } from 'react';
-import { useTranslation } from 'react-i18next';
 
 import CardTitle from '@/ds-components/CardTitle';
-import TabNav, { TabNavItem } from '@/ds-components/TabNav';
 
 import { CodeEditorLoadingContext } from './CodeEditorLoadingContext';
 import Main from './Main';
@@ -12,20 +10,12 @@ import PageLoadingSkeleton from './PageLoadingSkeleton';
 import * as styles from './index.module.scss';
 import useJwtCustomizer from './use-jwt-customizer';
 
-const tabPhrases = Object.freeze({
-  [LogtoJwtTokenPath.AccessToken]: 'user_jwt.card_field',
-  [LogtoJwtTokenPath.ClientCredentials]: 'machine_to_machine_jwt.card_field',
-});
-
-const getPagePath = (tokenType: LogtoJwtTokenPath) => `/jwt-customizer/${tokenType}`;
-
 type Props = {
-  tab: LogtoJwtTokenPath;
+  tokenType: LogtoJwtTokenPath;
+  action: 'create' | 'edit';
 };
 
-function JwtClaims({ tab }: Props) {
-  const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
-
+function CustomizeJwtDetails({ tokenType }: Props) {
   const { isLoading, ...rest } = useJwtCustomizer();
   const [isMonacoLoaded, setIsMonacoLoaded] = useState(false);
 
@@ -41,18 +31,11 @@ function JwtClaims({ tab }: Props) {
         subtitle="jwt_claims.description"
         className={styles.header}
       />
-      <TabNav className={styles.tabNav}>
-        {Object.values(LogtoJwtTokenPath).map((tokenType) => (
-          <TabNavItem key={tokenType} href={getPagePath(tokenType)} isActive={tokenType === tab}>
-            {t(`jwt_claims.${tabPhrases[tokenType]}`)}
-          </TabNavItem>
-        ))}
-      </TabNav>
-      {(isLoading || !isMonacoLoaded) && <PageLoadingSkeleton tokenType={tab} />}
+      {(isLoading || !isMonacoLoaded) && <PageLoadingSkeleton tokenType={tokenType} />}
 
       {!isLoading && (
         <CodeEditorLoadingContext.Provider value={codeEditorContextValue}>
-          <Main tab={tab} {...rest} className={isMonacoLoaded ? undefined : styles.hidden} />
+          <Main tab={tokenType} {...rest} className={isMonacoLoaded ? undefined : styles.hidden} />
         </CodeEditorLoadingContext.Provider>
       )}
     </div>
@@ -60,4 +43,4 @@ function JwtClaims({ tab }: Props) {
 }
 
 // eslint-disable-next-line import/no-unused-modules -- will update this later
-export default withAppInsights(JwtClaims);
+export default withAppInsights(CustomizeJwtDetails);
diff --git a/packages/console/src/pages/JwtClaims/type.ts b/packages/console/src/pages/CustomizeJwtDetails/type.ts
similarity index 100%
rename from packages/console/src/pages/JwtClaims/type.ts
rename to packages/console/src/pages/CustomizeJwtDetails/type.ts
diff --git a/packages/console/src/pages/JwtClaims/use-jwt-customizer.ts b/packages/console/src/pages/CustomizeJwtDetails/use-jwt-customizer.ts
similarity index 100%
rename from packages/console/src/pages/JwtClaims/use-jwt-customizer.ts
rename to packages/console/src/pages/CustomizeJwtDetails/use-jwt-customizer.ts
diff --git a/packages/console/src/pages/JwtClaims/utils/config.tsx b/packages/console/src/pages/CustomizeJwtDetails/utils/config.tsx
similarity index 100%
rename from packages/console/src/pages/JwtClaims/utils/config.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/utils/config.tsx
diff --git a/packages/console/src/pages/JwtClaims/utils/format.ts b/packages/console/src/pages/CustomizeJwtDetails/utils/format.ts
similarity index 100%
rename from packages/console/src/pages/JwtClaims/utils/format.ts
rename to packages/console/src/pages/CustomizeJwtDetails/utils/format.ts
diff --git a/packages/console/src/pages/JwtClaims/utils/type-definitions.ts b/packages/console/src/pages/CustomizeJwtDetails/utils/type-definitions.ts
similarity index 100%
rename from packages/console/src/pages/JwtClaims/utils/type-definitions.ts
rename to packages/console/src/pages/CustomizeJwtDetails/utils/type-definitions.ts

From 777ef0e483f3de1fd5d0e5a1a539edca33a80d7c Mon Sep 17 00:00:00 2001
From: simeng-li <simeng@silverhand.io>
Date: Fri, 29 Mar 2024 10:20:57 +0800
Subject: [PATCH 2/3] refactor(console,phrases): refactor the customize jwt
 details page

refactor the customize jwt details page
---
 .../ConsoleContent/Sidebar/hook.tsx           |   4 +-
 .../src/containers/ConsoleContent/index.tsx   |   2 +
 packages/console/src/hooks/use-swr-fetcher.ts |   4 +-
 .../pages/CustomizeJwt/CreateButton/index.tsx |   4 +-
 .../CustomizeJwt/CustomizerItem/index.tsx     |   6 +-
 .../src/pages/CustomizeJwt/utils/path.ts      |   4 +-
 .../src/pages/CustomizeJwt/utils/type.ts      |   1 +
 .../src/pages/CustomizeJwtDetails/Main.tsx    | 112 ----------------
 .../ScriptSection/index.module.scss           |  17 +++
 .../MainContent/ScriptSection/index.tsx       |  81 ++++++++++++
 .../EnvironmentVariablesField.tsx             |   6 +-
 .../SettingsSection/GuideCard.tsx             |   0
 .../SettingsSection/InstructionTab.tsx        |  12 +-
 .../SettingsSection/TestResult.tsx            |   0
 .../SettingsSection/TestTab.tsx               |  22 ++--
 .../SettingsSection/index.module.scss         |   0
 .../SettingsSection/index.tsx                 |   0
 .../MainContent/index.module.scss             |  17 +++
 .../CustomizeJwtDetails/MainContent/index.tsx |  97 ++++++++++++++
 .../MonacoCodeEditor/config.ts                |   1 -
 .../MonacoCodeEditor/index.tsx                |  18 +--
 .../PageLoadingSkeleton/index.module.scss     |  15 +++
 .../PageLoadingSkeleton/index.tsx             |   9 +-
 .../CustomizeJwtDetails/ScriptSection.tsx     |  93 --------------
 .../CustomizeJwtDetails/index.module.scss     |  38 +-----
 .../src/pages/CustomizeJwtDetails/index.tsx   |  52 +++++---
 .../src/pages/CustomizeJwtDetails/type.ts     |  21 ++-
 .../CustomizeJwtDetails/use-data-fetch.ts     |  35 +++++
 .../CustomizeJwtDetails/use-jwt-customizer.ts |  67 ----------
 .../CustomizeJwtDetails/utils/config.tsx      |   8 +-
 .../pages/CustomizeJwtDetails/utils/format.ts | 121 +++++++++---------
 .../pages/CustomizeJwtDetails/utils/path.ts   |   4 +
 .../utils/type-definitions.ts                 |   6 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../de/translation/admin-console/tabs.ts      |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../en/translation/admin-console/tabs.ts      |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../es/translation/admin-console/tabs.ts      |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../fr/translation/admin-console/tabs.ts      |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../it/translation/admin-console/tabs.ts      |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../ja/translation/admin-console/tabs.ts      |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../ko/translation/admin-console/tabs.ts      |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../pl-pl/translation/admin-console/tabs.ts   |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../pt-br/translation/admin-console/tabs.ts   |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../pt-pt/translation/admin-console/tabs.ts   |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../ru/translation/admin-console/tabs.ts      |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../tr-tr/translation/admin-console/tabs.ts   |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../zh-cn/translation/admin-console/tabs.ts   |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../zh-hk/translation/admin-console/tabs.ts   |   2 +-
 .../translation/admin-console/jwt-claims.ts   |   2 +-
 .../zh-tw/translation/admin-console/tabs.ts   |   2 +-
 63 files changed, 460 insertions(+), 477 deletions(-)
 create mode 100644 packages/console/src/pages/CustomizeJwt/utils/type.ts
 delete mode 100644 packages/console/src/pages/CustomizeJwtDetails/Main.tsx
 create mode 100644 packages/console/src/pages/CustomizeJwtDetails/MainContent/ScriptSection/index.module.scss
 create mode 100644 packages/console/src/pages/CustomizeJwtDetails/MainContent/ScriptSection/index.tsx
 rename packages/console/src/pages/CustomizeJwtDetails/{ => MainContent}/SettingsSection/EnvironmentVariablesField.tsx (95%)
 rename packages/console/src/pages/CustomizeJwtDetails/{ => MainContent}/SettingsSection/GuideCard.tsx (100%)
 rename packages/console/src/pages/CustomizeJwtDetails/{ => MainContent}/SettingsSection/InstructionTab.tsx (95%)
 rename packages/console/src/pages/CustomizeJwtDetails/{ => MainContent}/SettingsSection/TestResult.tsx (100%)
 rename packages/console/src/pages/CustomizeJwtDetails/{ => MainContent}/SettingsSection/TestTab.tsx (92%)
 rename packages/console/src/pages/CustomizeJwtDetails/{ => MainContent}/SettingsSection/index.module.scss (100%)
 rename packages/console/src/pages/CustomizeJwtDetails/{ => MainContent}/SettingsSection/index.tsx (100%)
 create mode 100644 packages/console/src/pages/CustomizeJwtDetails/MainContent/index.module.scss
 create mode 100644 packages/console/src/pages/CustomizeJwtDetails/MainContent/index.tsx
 delete mode 100644 packages/console/src/pages/CustomizeJwtDetails/ScriptSection.tsx
 create mode 100644 packages/console/src/pages/CustomizeJwtDetails/use-data-fetch.ts
 delete mode 100644 packages/console/src/pages/CustomizeJwtDetails/use-jwt-customizer.ts
 create mode 100644 packages/console/src/pages/CustomizeJwtDetails/utils/path.ts

diff --git a/packages/console/src/containers/ConsoleContent/Sidebar/hook.tsx b/packages/console/src/containers/ConsoleContent/Sidebar/hook.tsx
index ffd9e771f..5c09fa01f 100644
--- a/packages/console/src/containers/ConsoleContent/Sidebar/hook.tsx
+++ b/packages/console/src/containers/ConsoleContent/Sidebar/hook.tsx
@@ -18,7 +18,7 @@ import Role from '@/assets/icons/role.svg';
 import SecurityLock from '@/assets/icons/security-lock.svg';
 import EnterpriseSso from '@/assets/icons/single-sign-on.svg';
 import Web from '@/assets/icons/web.svg';
-import { isDevFeaturesEnabled, isCloud } from '@/consts/env';
+import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
 
 type SidebarItem = {
   Icon: FC;
@@ -128,7 +128,7 @@ export const useSidebarMenuItems = (): {
         },
         {
           Icon: JwtClaims,
-          title: 'jwt_customizer',
+          title: 'customize_jwt',
           isHidden: !isDevFeaturesEnabled,
         },
         {
diff --git a/packages/console/src/containers/ConsoleContent/index.tsx b/packages/console/src/containers/ConsoleContent/index.tsx
index eff2741a0..2bf92992e 100644
--- a/packages/console/src/containers/ConsoleContent/index.tsx
+++ b/packages/console/src/containers/ConsoleContent/index.tsx
@@ -27,6 +27,7 @@ import AuditLogs from '@/pages/AuditLogs';
 import ConnectorDetails from '@/pages/ConnectorDetails';
 import Connectors from '@/pages/Connectors';
 import CustomizeJwt from '@/pages/CustomizeJwt';
+import CustomizeJwtDetails from '@/pages/CustomizeJwtDetails';
 import Dashboard from '@/pages/Dashboard';
 import EnterpriseSsoConnectors from '@/pages/EnterpriseSso';
 import EnterpriseSsoConnectorDetails from '@/pages/EnterpriseSsoDetails';
@@ -244,6 +245,7 @@ function ConsoleContent() {
             {isCloud && isDevFeaturesEnabled && (
               <Route path="jwt-customizer">
                 <Route index element={<CustomizeJwt />} />
+                <Route path=":tokenType/:action" element={<CustomizeJwtDetails />} />
               </Route>
             )}
           </Routes>
diff --git a/packages/console/src/hooks/use-swr-fetcher.ts b/packages/console/src/hooks/use-swr-fetcher.ts
index 9c278cad3..da2acbfa2 100644
--- a/packages/console/src/hooks/use-swr-fetcher.ts
+++ b/packages/console/src/hooks/use-swr-fetcher.ts
@@ -1,5 +1,5 @@
-import { HTTPError } from 'ky';
 import type ky from 'ky';
+import { HTTPError } from 'ky';
 import { useCallback } from 'react';
 import { useTranslation } from 'react-i18next';
 import type { Fetcher } from 'swr';
@@ -22,6 +22,7 @@ const useSwrFetcher: UseSwrFetcherHook = <T>(api: KyInstance) => {
     async (resource: string) => {
       try {
         const response = await api.get(resource);
+
         const data = await response.json<T>();
 
         if (typeof resource === 'string' && resource.includes('?')) {
@@ -42,6 +43,7 @@ const useSwrFetcher: UseSwrFetcherHook = <T>(api: KyInstance) => {
       } catch (error: unknown) {
         if (error instanceof HTTPError) {
           const { response } = error;
+
           // See https://stackoverflow.com/questions/53511974/javascript-fetch-failed-to-execute-json-on-response-body-stream-is-locked
           // for why `.clone()` is needed
           throw new RequestError(response.status, await response.clone().json());
diff --git a/packages/console/src/pages/CustomizeJwt/CreateButton/index.tsx b/packages/console/src/pages/CustomizeJwt/CreateButton/index.tsx
index 81d8db182..52a76cf3b 100644
--- a/packages/console/src/pages/CustomizeJwt/CreateButton/index.tsx
+++ b/packages/console/src/pages/CustomizeJwt/CreateButton/index.tsx
@@ -1,8 +1,8 @@
 import { type LogtoJwtTokenPath } from '@logto/schemas';
-import { useNavigate } from 'react-router-dom';
 
 import PlusIcon from '@/assets/icons/plus.svg';
 import Button from '@/ds-components/Button';
+import useTenantPathname from '@/hooks/use-tenant-pathname';
 import { getPagePath } from '@/pages/CustomizeJwt/utils/path';
 
 import * as styles from './index.module.scss';
@@ -13,7 +13,7 @@ type Props = {
 
 function CreateButton({ tokenType }: Props) {
   const link = getPagePath(tokenType, 'create');
-  const navigate = useNavigate();
+  const { navigate } = useTenantPathname();
 
   return (
     <Button
diff --git a/packages/console/src/pages/CustomizeJwt/CustomizerItem/index.tsx b/packages/console/src/pages/CustomizeJwt/CustomizerItem/index.tsx
index 74254e592..e566dec0e 100644
--- a/packages/console/src/pages/CustomizeJwt/CustomizerItem/index.tsx
+++ b/packages/console/src/pages/CustomizeJwt/CustomizerItem/index.tsx
@@ -1,7 +1,6 @@
 import { LogtoJwtTokenPath } from '@logto/schemas';
 import { useCallback } from 'react';
 import { useTranslation } from 'react-i18next';
-import { useNavigate } from 'react-router-dom';
 import { useSWRConfig } from 'swr';
 
 import DeletIcon from '@/assets/icons/delete.svg';
@@ -9,6 +8,7 @@ import EditIcon from '@/assets/icons/edit.svg';
 import Button from '@/ds-components/Button';
 import useApi from '@/hooks/use-api';
 import { useConfirmModal } from '@/hooks/use-confirm-modal';
+import useTenantPathname from '@/hooks/use-tenant-pathname';
 import { getApiPath, getPagePath } from '@/pages/CustomizeJwt/utils/path';
 
 import * as styles from './index.module.scss';
@@ -21,7 +21,7 @@ function CustomizerItem({ tokenType }: Props) {
   const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
   const apiLink = getApiPath(tokenType);
   const editLink = getPagePath(tokenType, 'edit');
-  const navigate = useNavigate();
+  const { navigate } = useTenantPathname();
   const { show } = useConfirmModal();
   const { mutate } = useSWRConfig();
 
@@ -36,7 +36,7 @@ function CustomizerItem({ tokenType }: Props) {
 
     if (confirm) {
       await api.delete(apiLink);
-      await mutate(apiLink);
+      await mutate(apiLink, undefined);
     }
   }, [api, apiLink, mutate, show, t]);
 
diff --git a/packages/console/src/pages/CustomizeJwt/utils/path.ts b/packages/console/src/pages/CustomizeJwt/utils/path.ts
index b3304e7c1..eb8ae2974 100644
--- a/packages/console/src/pages/CustomizeJwt/utils/path.ts
+++ b/packages/console/src/pages/CustomizeJwt/utils/path.ts
@@ -1,9 +1,11 @@
 import { type LogtoJwtTokenPath } from '@logto/schemas';
 
+import { type Action } from './type';
+
 export const getApiPath = (tokenType: LogtoJwtTokenPath) =>
   `api/configs/jwt-customizer/${tokenType}`;
 
-export const getPagePath = (tokenType?: LogtoJwtTokenPath, action?: 'create' | 'edit') => {
+export const getPagePath = (tokenType?: LogtoJwtTokenPath, action?: Action) => {
   if (!tokenType) {
     return '/customize-jwt';
   }
diff --git a/packages/console/src/pages/CustomizeJwt/utils/type.ts b/packages/console/src/pages/CustomizeJwt/utils/type.ts
new file mode 100644
index 000000000..4c1266ed0
--- /dev/null
+++ b/packages/console/src/pages/CustomizeJwt/utils/type.ts
@@ -0,0 +1 @@
+export type Action = 'create' | 'edit';
diff --git a/packages/console/src/pages/CustomizeJwtDetails/Main.tsx b/packages/console/src/pages/CustomizeJwtDetails/Main.tsx
deleted file mode 100644
index 2fa5e1e8b..000000000
--- a/packages/console/src/pages/CustomizeJwtDetails/Main.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import {
-  type AccessTokenJwtCustomizer,
-  LogtoJwtTokenPath,
-  type ClientCredentialsJwtCustomizer,
-} from '@logto/schemas';
-import classNames from 'classnames';
-import { useMemo } from 'react';
-import { useForm, FormProvider } from 'react-hook-form';
-import { type KeyedMutator } from 'swr';
-
-import SubmitFormChangesActionBar from '@/components/SubmitFormChangesActionBar';
-import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
-import useApi from '@/hooks/use-api';
-import { trySubmitSafe } from '@/utils/form';
-
-import ScriptSection from './ScriptSection';
-import SettingsSection from './SettingsSection';
-import * as styles from './index.module.scss';
-import { type JwtClaimsFormType } from './type';
-import {
-  formatResponseDataToFormData,
-  formatFormDataToRequestData,
-  getApiPath,
-} from './utils/format';
-
-type Props = {
-  className?: string;
-  tab: LogtoJwtTokenPath;
-  accessTokenJwtCustomizer: AccessTokenJwtCustomizer | undefined;
-  clientCredentialsJwtCustomizer: ClientCredentialsJwtCustomizer | undefined;
-  mutateAccessTokenJwtCustomizer: KeyedMutator<AccessTokenJwtCustomizer>;
-  mutateClientCredentialsJwtCustomizer: KeyedMutator<ClientCredentialsJwtCustomizer>;
-};
-
-function Main({
-  className,
-  tab,
-  accessTokenJwtCustomizer,
-  clientCredentialsJwtCustomizer,
-  mutateAccessTokenJwtCustomizer,
-  mutateClientCredentialsJwtCustomizer,
-}: Props) {
-  const api = useApi();
-
-  const userJwtClaimsForm = useForm<JwtClaimsFormType>({
-    defaultValues: formatResponseDataToFormData(
-      LogtoJwtTokenPath.AccessToken,
-      accessTokenJwtCustomizer
-    ),
-  });
-
-  const machineToMachineJwtClaimsForm = useForm<JwtClaimsFormType>({
-    defaultValues: formatResponseDataToFormData(
-      LogtoJwtTokenPath.ClientCredentials,
-      clientCredentialsJwtCustomizer
-    ),
-  });
-
-  const activeForm = useMemo(
-    () =>
-      tab === LogtoJwtTokenPath.AccessToken ? userJwtClaimsForm : machineToMachineJwtClaimsForm,
-    [machineToMachineJwtClaimsForm, tab, userJwtClaimsForm]
-  );
-
-  const {
-    formState: { isDirty, isSubmitting },
-    reset,
-    handleSubmit,
-  } = activeForm;
-
-  const onSubmitHandler = handleSubmit(
-    trySubmitSafe(async (data) => {
-      if (isSubmitting) {
-        return;
-      }
-
-      const { tokenType } = data;
-      const payload = formatFormDataToRequestData(data);
-
-      await api.put(getApiPath(tokenType), { json: payload });
-
-      const mutate =
-        tokenType === LogtoJwtTokenPath.AccessToken
-          ? mutateAccessTokenJwtCustomizer
-          : mutateClientCredentialsJwtCustomizer;
-
-      const result = await mutate();
-
-      reset(formatResponseDataToFormData(tokenType, result));
-    })
-  );
-
-  return (
-    <>
-      <FormProvider {...activeForm}>
-        <form className={classNames(styles.tabContent, className)}>
-          <ScriptSection />
-          <SettingsSection />
-        </form>
-      </FormProvider>
-      <SubmitFormChangesActionBar
-        isOpen={isDirty}
-        isSubmitting={isSubmitting}
-        onDiscard={reset}
-        onSubmit={onSubmitHandler}
-      />
-      <UnsavedChangesAlertModal hasUnsavedChanges={isDirty && !isSubmitting} onConfirm={reset} />
-    </>
-  );
-}
-
-export default Main;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/MainContent/ScriptSection/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/MainContent/ScriptSection/index.module.scss
new file mode 100644
index 000000000..fd959ef67
--- /dev/null
+++ b/packages/console/src/pages/CustomizeJwtDetails/MainContent/ScriptSection/index.module.scss
@@ -0,0 +1,17 @@
+@use '@/scss/underscore' as _;
+
+
+.codePanel {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+
+  .cardTitle {
+    font: var(--font-label-2);
+    margin-bottom: _.unit(3);
+  }
+
+  .flexGrow {
+    flex-grow: 1;
+  }
+}
diff --git a/packages/console/src/pages/CustomizeJwtDetails/MainContent/ScriptSection/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/MainContent/ScriptSection/index.tsx
new file mode 100644
index 000000000..d1d488f8e
--- /dev/null
+++ b/packages/console/src/pages/CustomizeJwtDetails/MainContent/ScriptSection/index.tsx
@@ -0,0 +1,81 @@
+/* Code Editor for the custom JWT claims script. */
+import { LogtoJwtTokenPath } from '@logto/schemas';
+import { useCallback, useContext, useMemo } from 'react';
+import { Controller, useFormContext, useWatch } from 'react-hook-form';
+
+import { CodeEditorLoadingContext } from '@/pages/CustomizeJwtDetails/CodeEditorLoadingContext';
+import MonacoCodeEditor, { type ModelSettings } from '@/pages/CustomizeJwtDetails/MonacoCodeEditor';
+import { type JwtCustomizerForm } from '@/pages/CustomizeJwtDetails/type';
+import {
+  accessTokenJwtCustomizerModel,
+  clientCredentialsModel,
+} from '@/pages/CustomizeJwtDetails/utils/config';
+import { buildEnvironmentVariablesTypeDefinition } from '@/pages/CustomizeJwtDetails/utils/type-definitions';
+
+import * as styles from './index.module.scss';
+
+function ScriptSection() {
+  const { watch, control } = useFormContext<JwtCustomizerForm>();
+  const tokenType = watch('tokenType');
+
+  // Need to use useWatch hook to subscribe the mutation of the environmentVariables field
+  // Otherwise, the default watch function's return value won't mutate when the environmentVariables field changes
+  const envVariables = useWatch({
+    control,
+    name: 'environmentVariables',
+  });
+
+  const environmentVariablesTypeDefinition = useMemo(
+    () => buildEnvironmentVariablesTypeDefinition(envVariables),
+    [envVariables]
+  );
+
+  // Get the active model based on the token type
+  const activeModel = useMemo<ModelSettings>(
+    () =>
+      tokenType === LogtoJwtTokenPath.AccessToken
+        ? accessTokenJwtCustomizerModel
+        : clientCredentialsModel,
+    [tokenType]
+  );
+
+  // Set the Monaco editor loaded state to true when the editor is mounted
+  const { setIsMonacoLoaded } = useContext(CodeEditorLoadingContext);
+
+  const onMountHandler = useCallback(() => {
+    setIsMonacoLoaded(true);
+  }, [setIsMonacoLoaded]);
+
+  return (
+    <Controller
+      // Force rerender the controller when the token type changes
+      // Otherwise the input field will not be updated
+      key={tokenType}
+      control={control}
+      name="script"
+      render={({ field: { onChange, value }, formState: { defaultValues } }) => (
+        <MonacoCodeEditor
+          className={styles.flexGrow}
+          enabledActions={['restore', 'copy']}
+          models={[activeModel]}
+          activeModelName={activeModel.name}
+          value={value}
+          environmentVariablesDefinition={environmentVariablesTypeDefinition}
+          onChange={(newValue) => {
+            // If the value is the same as the default code and the original form script value is undefined, reset the value to undefined as well
+            if (newValue === activeModel.defaultValue && !defaultValues?.script) {
+              onChange('');
+              return;
+            }
+
+            // Input value should not be undefined for react-hook-form @see https://react-hook-form.com/docs/usecontroller/controller
+            onChange(newValue ?? '');
+          }}
+          onMountHandler={onMountHandler}
+        />
+      )}
+    />
+  );
+}
+
+export default ScriptSection;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/EnvironmentVariablesField.tsx b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/EnvironmentVariablesField.tsx
similarity index 95%
rename from packages/console/src/pages/CustomizeJwtDetails/SettingsSection/EnvironmentVariablesField.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/EnvironmentVariablesField.tsx
index 87c803c2f..31ccfdf09 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/EnvironmentVariablesField.tsx
+++ b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/EnvironmentVariablesField.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
 import FormField from '@/ds-components/FormField';
 import KeyValueInputField from '@/ds-components/KeyValueInputField';
 
-import { type JwtClaimsFormType } from '../type';
+import { type JwtCustomizerForm } from '../../type';
 
 const isValidKey = (key: string) => {
   return /^\w+$/.test(key);
@@ -26,10 +26,10 @@ function EnvironmentVariablesField({ className }: Props) {
       errors: { environmentVariables: envVariableErrors },
       submitCount,
     },
-  } = useFormContext<JwtClaimsFormType>();
+  } = useFormContext<JwtCustomizerForm>();
 
   // Read the form controller from the context @see {@link https://react-hook-form.com/docs/usefieldarray}
-  const { fields, remove, append } = useFieldArray<JwtClaimsFormType>({
+  const { fields, remove, append } = useFieldArray<JwtCustomizerForm>({
     name: 'environmentVariables',
   });
 
diff --git a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/GuideCard.tsx b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/GuideCard.tsx
similarity index 100%
rename from packages/console/src/pages/CustomizeJwtDetails/SettingsSection/GuideCard.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/GuideCard.tsx
diff --git a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/InstructionTab.tsx b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/InstructionTab.tsx
similarity index 95%
rename from packages/console/src/pages/CustomizeJwtDetails/SettingsSection/InstructionTab.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/InstructionTab.tsx
index 380cf37b3..475b5a011 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/InstructionTab.tsx
+++ b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/InstructionTab.tsx
@@ -4,18 +4,18 @@ import classNames from 'classnames';
 import { useFormContext } from 'react-hook-form';
 import { useTranslation } from 'react-i18next';
 
-import { type JwtClaimsFormType } from '../type';
+import { type JwtCustomizerForm } from '../../type';
 import {
+  environmentVariablesCodeExample,
+  fetchExternalDataCodeExample,
   sampleCodeEditorOptions,
   typeDefinitionCodeEditorOptions,
-  fetchExternalDataCodeExample,
-  environmentVariablesCodeExample,
-} from '../utils/config';
+} from '../../utils/config';
 import {
   accessTokenPayloadTypeDefinition,
   clientCredentialsPayloadTypeDefinition,
   jwtCustomizerUserContextTypeDefinition,
-} from '../utils/type-definitions';
+} from '../../utils/type-definitions';
 
 import EnvironmentVariablesField from './EnvironmentVariablesField';
 import GuideCard, { CardType } from './GuideCard';
@@ -29,7 +29,7 @@ type Props = {
 function InstructionTab({ isActive }: Props) {
   const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
 
-  const { watch } = useFormContext<JwtClaimsFormType>();
+  const { watch } = useFormContext<JwtCustomizerForm>();
   const tokenType = watch('tokenType');
 
   return (
diff --git a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestResult.tsx b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/TestResult.tsx
similarity index 100%
rename from packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestResult.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/TestResult.tsx
diff --git a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestTab.tsx b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/TestTab.tsx
similarity index 92%
rename from packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestTab.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/TestTab.tsx
index 95dc6a8fd..931be97a0 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/TestTab.tsx
+++ b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/TestTab.tsx
@@ -11,14 +11,14 @@ import Button from '@/ds-components/Button';
 import Card from '@/ds-components/Card';
 import useApi from '@/hooks/use-api';
 
-import MonacoCodeEditor, { type ModelControl, type ModelSettings } from '../MonacoCodeEditor';
-import { type JwtClaimsFormType } from '../type';
+import MonacoCodeEditor, { type ModelControl, type ModelSettings } from '../../MonacoCodeEditor';
+import { type JwtCustomizerForm } from '../../type';
 import {
   accessTokenPayloadTestModel,
   clientCredentialsPayloadTestModel,
   userContextTestModel,
-} from '../utils/config';
-import { formatFormDataToTestRequestPayload } from '../utils/format';
+} from '../../utils/config';
+import { formatFormDataToTestRequestPayload } from '../../utils/format';
 
 import TestResult, { type TestResultData } from './TestResult';
 import * as styles from './index.module.scss';
@@ -45,7 +45,7 @@ function TestTab({ isActive }: Props) {
   const [activeModelName, setActiveModelName] = useState<string>();
   const api = useApi({ hideErrorToast: true });
 
-  const { watch, control, formState, getValues } = useFormContext<JwtClaimsFormType>();
+  const { watch, control, formState, getValues } = useFormContext<JwtCustomizerForm>();
   const tokenType = watch('tokenType');
 
   const editorModels = useMemo(
@@ -99,13 +99,13 @@ function TestTab({ isActive }: Props) {
   }, [api, getValues]);
 
   const getModelControllerProps = useCallback(
-    ({ value, onChange }: ControllerRenderProps<JwtClaimsFormType, 'testSample'>): ModelControl => {
+    ({ value, onChange }: ControllerRenderProps<JwtCustomizerForm, 'testSample'>): ModelControl => {
       return {
         value:
-          activeModelName === userContextTestModel.name ? value?.contextSample : value?.tokenSample,
+          activeModelName === userContextTestModel.name ? value.contextSample : value.tokenSample,
         onChange: (newValue: string | undefined) => {
           // Form value is a object we need to update the specific field
-          const updatedValue: JwtClaimsFormType['testSample'] = {
+          const updatedValue: JwtCustomizerForm['testSample'] = {
             ...value,
             ...conditional(
               activeModelName === userContextTestModel.name && {
@@ -133,11 +133,7 @@ function TestTab({ isActive }: Props) {
   );
 
   const validateSampleCode = useCallback(
-    (value: JwtClaimsFormType['testSample']) => {
-      if (!value) {
-        return true;
-      }
-
+    (value: JwtCustomizerForm['testSample']) => {
       for (const [_, sampleCode] of Object.entries(value)) {
         if (sampleCode) {
           try {
diff --git a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/index.module.scss
similarity index 100%
rename from packages/console/src/pages/CustomizeJwtDetails/SettingsSection/index.module.scss
rename to packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/index.module.scss
diff --git a/packages/console/src/pages/CustomizeJwtDetails/SettingsSection/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/index.tsx
similarity index 100%
rename from packages/console/src/pages/CustomizeJwtDetails/SettingsSection/index.tsx
rename to packages/console/src/pages/CustomizeJwtDetails/MainContent/SettingsSection/index.tsx
diff --git a/packages/console/src/pages/CustomizeJwtDetails/MainContent/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/MainContent/index.module.scss
new file mode 100644
index 000000000..e6da1aa29
--- /dev/null
+++ b/packages/console/src/pages/CustomizeJwtDetails/MainContent/index.module.scss
@@ -0,0 +1,17 @@
+@use '@/scss/underscore' as _;
+
+
+.content {
+  display: flex;
+  flex-direction: row;
+  flex-grow: 1;
+
+  > * {
+    flex: 1;
+    margin-bottom: _.unit(6);
+
+    &:first-child {
+      margin-right: _.unit(3);
+    }
+  }
+}
diff --git a/packages/console/src/pages/CustomizeJwtDetails/MainContent/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/MainContent/index.tsx
new file mode 100644
index 000000000..483fbbe25
--- /dev/null
+++ b/packages/console/src/pages/CustomizeJwtDetails/MainContent/index.tsx
@@ -0,0 +1,97 @@
+import { type LogtoJwtTokenPath } from '@logto/schemas';
+import classNames from 'classnames';
+import { FormProvider, useForm } from 'react-hook-form';
+import { useNavigate } from 'react-router-dom';
+import { type KeyedMutator } from 'swr';
+
+import SubmitFormChangesActionBar from '@/components/SubmitFormChangesActionBar';
+import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
+import useApi from '@/hooks/use-api';
+import { trySubmitSafe } from '@/utils/form';
+
+import { type Action, type JwtCustomizer, type JwtCustomizerForm } from '../type';
+import { formatFormDataToRequestData, formatResponseDataToFormData } from '../utils/format';
+import { getApiPath } from '../utils/path';
+
+import ScriptSection from './ScriptSection';
+import SettingsSection from './SettingsSection';
+import * as styles from './index.module.scss';
+
+type Props<T extends LogtoJwtTokenPath> = {
+  className?: string;
+  token: T;
+  data?: JwtCustomizer<T>;
+  mutate: KeyedMutator<JwtCustomizer<T>>;
+  action: Action;
+};
+
+function MainContent<T extends LogtoJwtTokenPath>({
+  className,
+  token,
+  data,
+  mutate,
+  action,
+}: Props<T>) {
+  const api = useApi();
+  const navigate = useNavigate();
+
+  const methods = useForm<JwtCustomizerForm>({
+    defaultValues: formatResponseDataToFormData(token, data),
+  });
+
+  const {
+    formState: { isDirty, isSubmitting },
+    reset,
+    handleSubmit,
+  } = methods;
+
+  const onSubmitHandler = handleSubmit(
+    trySubmitSafe(async (data) => {
+      if (isSubmitting) {
+        return;
+      }
+
+      const { tokenType } = data;
+      const payload = formatFormDataToRequestData(data);
+
+      await api.put(getApiPath(tokenType), { json: payload });
+
+      if (action === 'create') {
+        navigate(-1);
+        return;
+      }
+
+      const result = await mutate();
+
+      reset(formatResponseDataToFormData(tokenType, result));
+    })
+  );
+
+  return (
+    <>
+      <FormProvider {...methods}>
+        <form className={classNames(styles.content, className)}>
+          <ScriptSection />
+          <SettingsSection />
+        </form>
+      </FormProvider>
+      <SubmitFormChangesActionBar
+        // Always show the action bar if is the create mode
+        isOpen={isDirty || action === 'create'}
+        isSubmitting={isSubmitting}
+        onDiscard={
+          // If the form is in create mode, navigate back to the previous page
+          action === 'create'
+            ? () => {
+                navigate(-1);
+              }
+            : reset
+        }
+        onSubmit={onSubmitHandler}
+      />
+      <UnsavedChangesAlertModal hasUnsavedChanges={isDirty && !isSubmitting} onConfirm={reset} />
+    </>
+  );
+}
+
+export default MainContent;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/config.ts b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/config.ts
index 3906b9189..fbf03cecb 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/config.ts
+++ b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/config.ts
@@ -17,7 +17,6 @@ export const defaultOptions: EditorProps['options'] = {
   minimap: {
     enabled: false,
   },
-  wordWrap: 'on',
   renderLineHighlight: 'none',
   fontFamily: 'Roboto Mono, monospace',
   fontSize: 14,
diff --git a/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/index.tsx
index 485048dc3..377810106 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/index.tsx
+++ b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/index.tsx
@@ -1,4 +1,4 @@
-import { Editor, type BeforeMount, type OnMount, useMonaco } from '@monaco-editor/react';
+import { Editor, useMonaco, type BeforeMount, type OnMount } from '@monaco-editor/react';
 import { type Nullable } from '@silverhand/essentials';
 import classNames from 'classnames';
 import { useCallback, useEffect, useMemo, useRef } from 'react';
@@ -6,16 +6,15 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
 import CopyToClipboard from '@/ds-components/CopyToClipboard';
 import { onKeyDownHandler } from '@/utils/a11y';
 
-import CodeClearButton from './ActionButton/CodeClearButton.js';
 import CodeRestoreButton from './ActionButton/CodeRestoreButton.js';
-import { logtoDarkTheme, defaultOptions } from './config.js';
+import { defaultOptions, logtoDarkTheme } from './config.js';
 import * as styles from './index.module.scss';
 import type { IStandaloneCodeEditor, ModelSettings } from './type.js';
 import useEditorHeight from './use-editor-height.js';
 
-export type { ModelSettings, ModelControl } from './type.js';
+export type { ModelControl, ModelSettings } from './type.js';
 
-type ActionButtonType = 'clear' | 'restore' | 'copy';
+type ActionButtonType = 'restore' | 'copy';
 
 type Props = {
   className?: string;
@@ -136,15 +135,6 @@ function MonacoCodeEditor({
           ))}
         </div>
         <div className={styles.actionButtons}>
-          {enabledActions.includes('clear') && (
-            <CodeClearButton
-              onClick={() => {
-                if (activeModel) {
-                  onChange?.(undefined);
-                }
-              }}
-            />
-          )}
           {enabledActions.includes('restore') && (
             <CodeRestoreButton
               onClick={() => {
diff --git a/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.module.scss
index df0f8cec6..8986a550e 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.module.scss
+++ b/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.module.scss
@@ -1,5 +1,20 @@
 @use '@/scss/underscore' as _;
 
+.content {
+  display: flex;
+  flex-direction: row;
+  flex-grow: 1;
+
+  > * {
+    flex: 1;
+    margin-bottom: _.unit(6);
+
+    &:first-child {
+      margin-right: _.unit(3);
+    }
+  }
+}
+
 .blockShimmer {
   @include _.shimmering-animation;
   border-radius: 8px;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.tsx
index cefdb11fa..b100710d6 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.tsx
+++ b/packages/console/src/pages/CustomizeJwtDetails/PageLoadingSkeleton/index.tsx
@@ -3,8 +3,6 @@ import classNames from 'classnames';
 
 import Card from '@/ds-components/Card';
 
-import * as pageLayoutStyles from '../index.module.scss';
-
 import * as styles from './index.module.scss';
 
 type Props = {
@@ -13,11 +11,8 @@ type Props = {
 
 function PageLoadingSkeleton({ tokenType }: Props) {
   return (
-    <div className={pageLayoutStyles.tabContent}>
-      <Card className={pageLayoutStyles.codePanel}>
-        <div className={classNames(styles.textShimmer, styles.title)} />
-        <div className={styles.blockShimmer} />
-      </Card>
+    <div className={styles.content}>
+      <div className={styles.blockShimmer} />
       <div>
         <div className={classNames(styles.textShimmer, styles.large)} />
         <Card className={styles.card}>
diff --git a/packages/console/src/pages/CustomizeJwtDetails/ScriptSection.tsx b/packages/console/src/pages/CustomizeJwtDetails/ScriptSection.tsx
deleted file mode 100644
index 6a4912a60..000000000
--- a/packages/console/src/pages/CustomizeJwtDetails/ScriptSection.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-/* Code Editor for the custom JWT claims script. */
-import { LogtoJwtTokenPath } from '@logto/schemas';
-import { useMemo, useContext, useCallback } from 'react';
-import { useFormContext, Controller, useWatch } from 'react-hook-form';
-import { useTranslation } from 'react-i18next';
-
-import Card from '@/ds-components/Card';
-
-import { CodeEditorLoadingContext } from './CodeEditorLoadingContext';
-import MonacoCodeEditor, { type ModelSettings } from './MonacoCodeEditor';
-import * as styles from './index.module.scss';
-import { type JwtClaimsFormType } from './type';
-import { accessTokenJwtCustomizerModel, clientCredentialsModel } from './utils/config';
-import { buildEnvironmentVariablesTypeDefinition } from './utils/type-definitions';
-
-const titlePhrases = Object.freeze({
-  [LogtoJwtTokenPath.AccessToken]: 'user_jwt',
-  [LogtoJwtTokenPath.ClientCredentials]: 'machine_to_machine_jwt',
-});
-
-function ScriptSection() {
-  const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
-
-  const { watch, control } = useFormContext<JwtClaimsFormType>();
-
-  const tokenType = watch('tokenType');
-
-  // Need to use useWatch hook to subscribe the mutation of the environmentVariables field
-  // Otherwise, the default watch function's return value won't mutate when the environmentVariables field changes
-  const envVariables = useWatch({
-    control,
-    name: 'environmentVariables',
-  });
-
-  const environmentVariablesTypeDefinition = useMemo(
-    () => buildEnvironmentVariablesTypeDefinition(envVariables),
-    [envVariables]
-  );
-
-  const { setIsMonacoLoaded } = useContext(CodeEditorLoadingContext);
-
-  const activeModel = useMemo<ModelSettings>(
-    () =>
-      tokenType === LogtoJwtTokenPath.AccessToken
-        ? accessTokenJwtCustomizerModel
-        : clientCredentialsModel,
-    [tokenType]
-  );
-
-  const onMountHandler = useCallback(() => {
-    setIsMonacoLoaded(true);
-  }, [setIsMonacoLoaded]);
-
-  return (
-    <Card className={styles.codePanel}>
-      <div className={styles.cardTitle}>
-        {t('jwt_claims.code_editor_title', {
-          token: t(`jwt_claims.${titlePhrases[tokenType]}`),
-        })}
-      </div>
-      <Controller
-        // Force rerender the controller when the token type changes
-        // Otherwise the input field will not be updated
-        key={tokenType}
-        control={control}
-        name="script"
-        render={({ field: { onChange, value }, formState: { defaultValues } }) => (
-          <MonacoCodeEditor
-            className={styles.flexGrow}
-            enabledActions={['clear', 'copy']}
-            models={[activeModel]}
-            activeModelName={activeModel.name}
-            value={value}
-            environmentVariablesDefinition={environmentVariablesTypeDefinition}
-            onChange={(newValue) => {
-              // If the value is the same as the default code and the original form script value is undefined, reset the value to undefined as well
-              if (newValue === activeModel.defaultValue && !defaultValues?.script) {
-                onChange('');
-                return;
-              }
-
-              // Input value should not be undefined for react-hook-form @see https://react-hook-form.com/docs/usecontroller/controller
-              onChange(newValue ?? '');
-            }}
-            onMountHandler={onMountHandler}
-          />
-        )}
-      />
-    </Card>
-  );
-}
-
-export default ScriptSection;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/index.module.scss b/packages/console/src/pages/CustomizeJwtDetails/index.module.scss
index 0f019347f..225d3c08a 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/index.module.scss
+++ b/packages/console/src/pages/CustomizeJwtDetails/index.module.scss
@@ -10,41 +10,7 @@
     margin-bottom: _.unit(4);
   }
 
-  .tabNav {
-    margin-bottom: _.unit(4);
+  .hidden {
+    display: none;
   }
 }
-
-.tabContent {
-  display: flex;
-  flex-direction: row;
-  flex-grow: 1;
-
-  > * {
-    flex: 1;
-    margin-bottom: _.unit(6);
-
-    &:first-child {
-      margin-right: _.unit(3);
-    }
-  }
-}
-
-.codePanel {
-  position: relative;
-  display: flex;
-  flex-direction: column;
-
-  .cardTitle {
-    font: var(--font-label-2);
-    margin-bottom: _.unit(3);
-  }
-
-  .flexGrow {
-    flex-grow: 1;
-  }
-}
-
-.hidden {
-  display: none;
-}
diff --git a/packages/console/src/pages/CustomizeJwtDetails/index.tsx b/packages/console/src/pages/CustomizeJwtDetails/index.tsx
index 92c4fc8e0..47f91704e 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/index.tsx
+++ b/packages/console/src/pages/CustomizeJwtDetails/index.tsx
@@ -1,22 +1,26 @@
 import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
 import { type LogtoJwtTokenPath } from '@logto/schemas';
 import { useMemo, useState } from 'react';
+import { useParams } from 'react-router-dom';
 
-import CardTitle from '@/ds-components/CardTitle';
+import DetailsPage from '@/components/DetailsPage';
+import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
 
 import { CodeEditorLoadingContext } from './CodeEditorLoadingContext';
-import Main from './Main';
+import MainContent from './MainContent';
 import PageLoadingSkeleton from './PageLoadingSkeleton';
 import * as styles from './index.module.scss';
-import useJwtCustomizer from './use-jwt-customizer';
+import { pageParamsGuard, type Action } from './type';
+import useDataFetch from './use-data-fetch';
 
 type Props = {
   tokenType: LogtoJwtTokenPath;
-  action: 'create' | 'edit';
+  action: Action;
 };
 
-function CustomizeJwtDetails({ tokenType }: Props) {
-  const { isLoading, ...rest } = useJwtCustomizer();
+function CustomizeJwtDetails({ tokenType, action }: Props) {
+  const { isLoading, error, ...rest } = useDataFetch(tokenType, action);
+
   const [isMonacoLoaded, setIsMonacoLoaded] = useState(false);
 
   const codeEditorContextValue = useMemo(
@@ -25,22 +29,38 @@ function CustomizeJwtDetails({ tokenType }: Props) {
   );
 
   return (
-    <div className={styles.container}>
-      <CardTitle
-        title="jwt_claims.title"
-        subtitle="jwt_claims.description"
-        className={styles.header}
-      />
+    <DetailsPage
+      backLink="/customize-jwt"
+      backLinkTitle="jwt_claims.title"
+      className={styles.container}
+    >
       {(isLoading || !isMonacoLoaded) && <PageLoadingSkeleton tokenType={tokenType} />}
 
       {!isLoading && (
         <CodeEditorLoadingContext.Provider value={codeEditorContextValue}>
-          <Main tab={tokenType} {...rest} className={isMonacoLoaded ? undefined : styles.hidden} />
+          <MainContent
+            action={action}
+            token={tokenType}
+            {...rest}
+            className={isMonacoLoaded ? undefined : styles.hidden}
+          />
         </CodeEditorLoadingContext.Provider>
       )}
-    </div>
+    </DetailsPage>
   );
 }
 
-// eslint-disable-next-line import/no-unused-modules -- will update this later
-export default withAppInsights(CustomizeJwtDetails);
+// Guard the parameters to ensure they are valid
+function CustomizeJwtDetailsWrapper() {
+  const { tokenType, action } = useParams();
+
+  const params = pageParamsGuard.safeParse({ tokenType, action });
+
+  if (!params.success) {
+    return <EmptyDataPlaceholder />;
+  }
+
+  return <CustomizeJwtDetails tokenType={params.data.tokenType} action={params.data.action} />;
+}
+
+export default withAppInsights(CustomizeJwtDetailsWrapper);
diff --git a/packages/console/src/pages/CustomizeJwtDetails/type.ts b/packages/console/src/pages/CustomizeJwtDetails/type.ts
index a4f0aa187..b52599ba7 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/type.ts
+++ b/packages/console/src/pages/CustomizeJwtDetails/type.ts
@@ -1,11 +1,24 @@
-import type { LogtoJwtTokenPath } from '@logto/schemas';
+import type { AccessTokenJwtCustomizer, ClientCredentialsJwtCustomizer } from '@logto/schemas';
+import { LogtoJwtTokenPath } from '@logto/schemas';
+import { z } from 'zod';
 
-export type JwtClaimsFormType = {
+export type JwtCustomizerForm = {
   tokenType: LogtoJwtTokenPath;
-  script?: string;
+  script: string;
   environmentVariables?: Array<{ key: string; value: string }>;
-  testSample?: {
+  testSample: {
     contextSample?: string;
     tokenSample?: string;
   };
 };
+
+export type Action = 'create' | 'edit';
+
+export type JwtCustomizer<T extends LogtoJwtTokenPath> = T extends LogtoJwtTokenPath.AccessToken
+  ? AccessTokenJwtCustomizer
+  : ClientCredentialsJwtCustomizer;
+
+export const pageParamsGuard = z.object({
+  tokenType: z.nativeEnum(LogtoJwtTokenPath),
+  action: z.union([z.literal('create'), z.literal('edit')]),
+});
diff --git a/packages/console/src/pages/CustomizeJwtDetails/use-data-fetch.ts b/packages/console/src/pages/CustomizeJwtDetails/use-data-fetch.ts
new file mode 100644
index 000000000..abb51bc4c
--- /dev/null
+++ b/packages/console/src/pages/CustomizeJwtDetails/use-data-fetch.ts
@@ -0,0 +1,35 @@
+import { type LogtoJwtTokenPath } from '@logto/schemas';
+import { type ResponseError } from '@withtyped/client';
+import useSWR from 'swr';
+
+import useApi from '@/hooks/use-api';
+import useSwrFetcher from '@/hooks/use-swr-fetcher';
+import { shouldRetryOnError } from '@/utils/request';
+
+import { type Action, type JwtCustomizer } from './type';
+import { getApiPath } from './utils/path';
+
+const useDataFetch = <T extends LogtoJwtTokenPath>(tokenType: T, action: Action) => {
+  const apiPath = getApiPath(tokenType);
+  const fetchApi = useApi({ hideErrorToast: true });
+  const fetcher = useSwrFetcher<JwtCustomizer<T>>(fetchApi);
+
+  // Return undefined if action is create
+  const { isLoading, data, mutate, error } = useSWR<JwtCustomizer<T>, ResponseError>(
+    action === 'create' ? undefined : apiPath,
+    {
+      fetcher,
+      shouldRetryOnError: shouldRetryOnError({ ignore: [404] }),
+    }
+  );
+
+  return {
+    // Show global loading status only if any of the fetchers are loading and no errors are present
+    isLoading: isLoading && !error,
+    data,
+    mutate,
+    error,
+  };
+};
+
+export default useDataFetch;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/use-jwt-customizer.ts b/packages/console/src/pages/CustomizeJwtDetails/use-jwt-customizer.ts
deleted file mode 100644
index 4bd3ab65e..000000000
--- a/packages/console/src/pages/CustomizeJwtDetails/use-jwt-customizer.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import {
-  LogtoJwtTokenPath,
-  type AccessTokenJwtCustomizer,
-  type ClientCredentialsJwtCustomizer,
-} from '@logto/schemas';
-import { type ResponseError } from '@withtyped/client';
-import { useMemo } from 'react';
-import useSWR from 'swr';
-
-import useApi from '@/hooks/use-api';
-import useSwrFetcher from '@/hooks/use-swr-fetcher';
-import { shouldRetryOnError } from '@/utils/request';
-
-import { getApiPath } from './utils/format';
-
-function useJwtCustomizer() {
-  const fetchApi = useApi({ hideErrorToast: true });
-  const accessTokenFetcher = useSwrFetcher<AccessTokenJwtCustomizer>(fetchApi);
-  const clientCredentialsFetcher = useSwrFetcher<ClientCredentialsJwtCustomizer>(fetchApi);
-
-  const {
-    data: accessTokenJwtCustomizer,
-    mutate: mutateAccessTokenJwtCustomizer,
-    isLoading: isAccessTokenJwtDataLoading,
-    error: accessTokenError,
-  } = useSWR<AccessTokenJwtCustomizer, ResponseError>(getApiPath(LogtoJwtTokenPath.AccessToken), {
-    fetcher: accessTokenFetcher,
-    shouldRetryOnError: shouldRetryOnError({ ignore: [404] }),
-  });
-
-  const {
-    data: clientCredentialsJwtCustomizer,
-    mutate: mutateClientCredentialsJwtCustomizer,
-    isLoading: isClientCredentialsJwtDataLoading,
-    error: clientCredentialsError,
-  } = useSWR<ClientCredentialsJwtCustomizer, ResponseError>(
-    getApiPath(LogtoJwtTokenPath.ClientCredentials),
-    {
-      fetcher: clientCredentialsFetcher,
-      shouldRetryOnError: shouldRetryOnError({ ignore: [404] }),
-    }
-  );
-
-  // Show global loading status only if any of the fetchers are loading and no errors are present
-  const isLoading =
-    (isAccessTokenJwtDataLoading && !accessTokenError) ||
-    (isClientCredentialsJwtDataLoading && !clientCredentialsError);
-
-  return useMemo(
-    () => ({
-      accessTokenJwtCustomizer,
-      clientCredentialsJwtCustomizer,
-      isLoading,
-      mutateAccessTokenJwtCustomizer,
-      mutateClientCredentialsJwtCustomizer,
-    }),
-    [
-      accessTokenJwtCustomizer,
-      clientCredentialsJwtCustomizer,
-      isLoading,
-      mutateAccessTokenJwtCustomizer,
-      mutateClientCredentialsJwtCustomizer,
-    ]
-  );
-}
-
-export default useJwtCustomizer;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/utils/config.tsx b/packages/console/src/pages/CustomizeJwtDetails/utils/config.tsx
index ac49ff535..bfafec17d 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/utils/config.tsx
+++ b/packages/console/src/pages/CustomizeJwtDetails/utils/config.tsx
@@ -71,7 +71,7 @@ declare global {
 export { exports as default };
 `;
 
-const defaultAccessTokenJwtCustomizerCode = `/**
+export const defaultAccessTokenJwtCustomizerCode = `/**
 * This function is called to get custom claims for the JWT token.
 * 
 * @param {${JwtCustomizerTypeDefinitionKey.AccessTokenPayload}} token -The JWT token.
@@ -86,7 +86,7 @@ exports.getCustomJwtClaims = async (token, data) => {
   return {};
 }`;
 
-const defaultClientCredentialsJwtCustomizerCode = `/**
+export const defaultClientCredentialsJwtCustomizerCode = `/**
 * This function is called to get custom claims for the JWT token.
 *
 * @param {${JwtCustomizerTypeDefinitionKey.ClientCredentialsPayload}} token -The JWT token.
@@ -101,7 +101,7 @@ exports.getCustomJwtClaims = async (token) => {
 
 export const accessTokenJwtCustomizerModel: ModelSettings = {
   name: 'user-jwt.ts',
-  title: 'TypeScript',
+  title: 'User access token',
   language: 'typescript',
   defaultValue: defaultAccessTokenJwtCustomizerCode,
   extraLibs: [
@@ -118,7 +118,7 @@ export const accessTokenJwtCustomizerModel: ModelSettings = {
 
 export const clientCredentialsModel: ModelSettings = {
   name: 'machine-to-machine-jwt.ts',
-  title: 'TypeScript',
+  title: 'Machine-to-machine token',
   language: 'typescript',
   defaultValue: defaultClientCredentialsJwtCustomizerCode,
   extraLibs: [
diff --git a/packages/console/src/pages/CustomizeJwtDetails/utils/format.ts b/packages/console/src/pages/CustomizeJwtDetails/utils/format.ts
index f6f33abfb..9e8e76899 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/utils/format.ts
+++ b/packages/console/src/pages/CustomizeJwtDetails/utils/format.ts
@@ -1,13 +1,11 @@
-import {
-  LogtoJwtTokenPath,
-  type AccessTokenJwtCustomizer,
-  type ClientCredentialsJwtCustomizer,
-} from '@logto/schemas';
+import { LogtoJwtTokenPath, type AccessTokenJwtCustomizer } from '@logto/schemas';
 
-import type { JwtClaimsFormType } from '../type';
+import type { JwtCustomizer, JwtCustomizerForm } from '../type';
 
 import {
+  defaultAccessTokenJwtCustomizerCode,
   defaultAccessTokenPayload,
+  defaultClientCredentialsJwtCustomizerCode,
   defaultClientCredentialsPayload,
   defaultUserTokenContextData,
 } from './config';
@@ -22,35 +20,9 @@ const formatEnvVariablesResponseToFormData = (
   return Object.entries(enVariables).map(([key, value]) => ({ key, value }));
 };
 
-const formatSampleCodeJsonToString = (sampleJson?: AccessTokenJwtCustomizer['contextSample']) => {
-  if (!sampleJson) {
-    return;
-  }
-
-  return JSON.stringify(sampleJson, null, 2);
-};
-
-export const formatResponseDataToFormData = <T extends LogtoJwtTokenPath>(
-  tokenType: T,
-  data?: T extends LogtoJwtTokenPath.AccessToken
-    ? AccessTokenJwtCustomizer
-    : ClientCredentialsJwtCustomizer
-): JwtClaimsFormType => {
-  return {
-    script: data?.script ?? '', // React-hook-form won't mutate the value if it's undefined
-    tokenType,
-    environmentVariables: formatEnvVariablesResponseToFormData(data?.envVars) ?? [
-      { key: '', value: '' },
-    ],
-    testSample: {
-      tokenSample: formatSampleCodeJsonToString(data?.tokenSample),
-      // Technically, contextSample is always undefined for client credentials token type
-      contextSample: formatSampleCodeJsonToString(data?.contextSample),
-    },
-  };
-};
-
-const formatEnvVariablesFormData = (envVariables: JwtClaimsFormType['environmentVariables']) => {
+const formatEnvVariablesFormDataToRequest = (
+  envVariables: JwtCustomizerForm['environmentVariables']
+) => {
   if (!envVariables) {
     return;
   }
@@ -64,6 +36,14 @@ const formatEnvVariablesFormData = (envVariables: JwtClaimsFormType['environment
   return Object.fromEntries(entries.map(({ key, value }) => [key, value]));
 };
 
+const formatSampleCodeJsonToString = (sampleJson?: AccessTokenJwtCustomizer['contextSample']) => {
+  if (!sampleJson) {
+    return;
+  }
+
+  return JSON.stringify(sampleJson, null, 2);
+};
+
 const formatSampleCodeStringToJson = (sampleCode?: string) => {
   if (!sampleCode) {
     return;
@@ -75,14 +55,46 @@ const formatSampleCodeStringToJson = (sampleCode?: string) => {
   } catch {}
 };
 
-export const formatFormDataToRequestData = (data: JwtClaimsFormType) => {
+const defaultValues = Object.freeze({
+  [LogtoJwtTokenPath.AccessToken]: {
+    script: defaultAccessTokenJwtCustomizerCode,
+    tokenSample: defaultAccessTokenPayload,
+    contextSample: defaultUserTokenContextData,
+  },
+  [LogtoJwtTokenPath.ClientCredentials]: {
+    script: defaultClientCredentialsJwtCustomizerCode,
+    tokenSample: defaultClientCredentialsPayload,
+    contextSample: undefined,
+  },
+});
+
+export const formatResponseDataToFormData = <T extends LogtoJwtTokenPath>(
+  tokenType: T,
+  data?: JwtCustomizer<T>
+): JwtCustomizerForm => {
   return {
-    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- parse empty string as undefined
-    script: data.script || undefined,
-    envVars: formatEnvVariablesFormData(data.environmentVariables),
-    tokenSample: formatSampleCodeStringToJson(data.testSample?.tokenSample),
-    // Technically, contextSample is always undefined for client credentials token type
-    contextSample: formatSampleCodeStringToJson(data.testSample?.contextSample),
+    tokenType,
+    script: data?.script ?? defaultValues[tokenType].script,
+    environmentVariables: formatEnvVariablesResponseToFormData(data?.envVars) ?? [
+      { key: '', value: '' },
+    ],
+    testSample: {
+      tokenSample: formatSampleCodeJsonToString(
+        data?.tokenSample ?? defaultValues[tokenType].tokenSample
+      ),
+      contextSample: formatSampleCodeJsonToString(
+        data?.contextSample ?? defaultValues[tokenType].contextSample
+      ),
+    },
+  };
+};
+
+export const formatFormDataToRequestData = (data: JwtCustomizerForm) => {
+  return {
+    script: data.script,
+    envVars: formatEnvVariablesFormDataToRequest(data.environmentVariables),
+    tokenSample: formatSampleCodeStringToJson(data.testSample.tokenSample),
+    contextSample: formatSampleCodeStringToJson(data.testSample.contextSample),
   };
 };
 
@@ -91,27 +103,18 @@ export const formatFormDataToTestRequestPayload = ({
   script,
   environmentVariables,
   testSample,
-}: JwtClaimsFormType) => {
-  const defaultTokenSample =
-    tokenType === LogtoJwtTokenPath.AccessToken
-      ? defaultAccessTokenPayload
-      : defaultClientCredentialsPayload;
-
-  const defaultContextSample =
-    tokenType === LogtoJwtTokenPath.AccessToken ? defaultUserTokenContextData : undefined;
-
+}: JwtCustomizerForm) => {
   return {
     tokenType,
     payload: {
-      // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- parse empty string as undefined
-      script: script || undefined,
-      envVars: formatEnvVariablesFormData(environmentVariables),
-      tokenSample: formatSampleCodeStringToJson(testSample?.tokenSample) ?? defaultTokenSample,
+      script,
+      envVars: formatEnvVariablesFormDataToRequest(environmentVariables),
+      tokenSample:
+        formatSampleCodeStringToJson(testSample.tokenSample) ??
+        defaultValues[tokenType].tokenSample,
       contextSample:
-        formatSampleCodeStringToJson(testSample?.contextSample) ?? defaultContextSample,
+        formatSampleCodeStringToJson(testSample.contextSample) ??
+        defaultValues[tokenType].contextSample,
     },
   };
 };
-
-export const getApiPath = (tokenType: LogtoJwtTokenPath) =>
-  `api/configs/jwt-customizer/${tokenType}`;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/utils/path.ts b/packages/console/src/pages/CustomizeJwtDetails/utils/path.ts
new file mode 100644
index 000000000..aa4477e53
--- /dev/null
+++ b/packages/console/src/pages/CustomizeJwtDetails/utils/path.ts
@@ -0,0 +1,4 @@
+import { type LogtoJwtTokenPath } from '@logto/schemas';
+
+export const getApiPath = (tokenType: LogtoJwtTokenPath) =>
+  `api/configs/jwt-customizer/${tokenType}`;
diff --git a/packages/console/src/pages/CustomizeJwtDetails/utils/type-definitions.ts b/packages/console/src/pages/CustomizeJwtDetails/utils/type-definitions.ts
index 630468315..a5e3fe8f8 100644
--- a/packages/console/src/pages/CustomizeJwtDetails/utils/type-definitions.ts
+++ b/packages/console/src/pages/CustomizeJwtDetails/utils/type-definitions.ts
@@ -1,11 +1,11 @@
 import {
   JwtCustomizerTypeDefinitionKey,
   accessTokenPayloadTypeDefinition,
-  jwtCustomizerUserContextTypeDefinition,
   clientCredentialsPayloadTypeDefinition,
+  jwtCustomizerUserContextTypeDefinition,
 } from '@/consts/jwt-customizer-type-definition';
 
-import { type JwtClaimsFormType } from '../type';
+import { type JwtCustomizerForm } from '../type';
 
 export {
   JwtCustomizerTypeDefinitionKey,
@@ -24,7 +24,7 @@ export const buildClientCredentialsJwtCustomizerContextTsDefinition = () =>
   `declare ${clientCredentialsPayloadTypeDefinition}`;
 
 export const buildEnvironmentVariablesTypeDefinition = (
-  envVariables?: JwtClaimsFormType['environmentVariables']
+  envVariables?: JwtCustomizerForm['environmentVariables']
 ) => {
   const typeDefinition = envVariables
     ? `{
diff --git a/packages/phrases/src/locales/de/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/de/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/de/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/de/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/de/translation/admin-console/tabs.ts b/packages/phrases/src/locales/de/translation/admin-console/tabs.ts
index 1cdcbfc21..6924fbc0d 100644
--- a/packages/phrases/src/locales/de/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/de/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Einstellungen',
   mfa: 'Multi-Faktor-Authentifizierung',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Signierschlüssel',
   organization_template: 'Organisationstemplate',
 };
diff --git a/packages/phrases/src/locales/en/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/en/translation/admin-console/jwt-claims.ts
index cbb38a36c..2cedb36c7 100644
--- a/packages/phrases/src/locales/en/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/en/translation/admin-console/jwt-claims.ts
@@ -1,5 +1,5 @@
 const jwt_claims = {
-  title: 'JWT claims',
+  title: 'Custom JWT',
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
   user_jwt: {
diff --git a/packages/phrases/src/locales/en/translation/admin-console/tabs.ts b/packages/phrases/src/locales/en/translation/admin-console/tabs.ts
index 05176456a..0000f788c 100644
--- a/packages/phrases/src/locales/en/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/en/translation/admin-console/tabs.ts
@@ -14,7 +14,7 @@ const tabs = {
   docs: 'Docs',
   tenant_settings: 'Settings',
   mfa: 'Multi-factor auth',
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Signing keys',
   organization_template: 'Organization template',
 };
diff --git a/packages/phrases/src/locales/es/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/es/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/es/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/es/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/es/translation/admin-console/tabs.ts b/packages/phrases/src/locales/es/translation/admin-console/tabs.ts
index dfd37b30d..cd4218156 100644
--- a/packages/phrases/src/locales/es/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/es/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Configuraciones del inquilino',
   mfa: 'Autenticación multifactor',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Claves de firma',
   organization_template: 'Plantilla de organización',
 };
diff --git a/packages/phrases/src/locales/fr/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/fr/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/fr/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/fr/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/fr/translation/admin-console/tabs.ts b/packages/phrases/src/locales/fr/translation/admin-console/tabs.ts
index 3f708cecb..f5934376d 100644
--- a/packages/phrases/src/locales/fr/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/fr/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Paramètres du locataire',
   mfa: 'Authentification multi-facteur',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Clés de signature',
   organization_template: "Modèle d'organisation",
 };
diff --git a/packages/phrases/src/locales/it/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/it/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/it/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/it/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/it/translation/admin-console/tabs.ts b/packages/phrases/src/locales/it/translation/admin-console/tabs.ts
index f2e3fd937..7bfabb42d 100644
--- a/packages/phrases/src/locales/it/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/it/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Impostazioni',
   mfa: 'Autenticazione multi-fattore',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Chiavi di firma',
   organization_template: 'Modello di organizzazione',
 };
diff --git a/packages/phrases/src/locales/ja/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/ja/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/ja/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/ja/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/ja/translation/admin-console/tabs.ts b/packages/phrases/src/locales/ja/translation/admin-console/tabs.ts
index 0ae76dc2f..bcc8f098f 100644
--- a/packages/phrases/src/locales/ja/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/ja/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: '設定',
   mfa: 'Multi-factor auth',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: '署名キー',
   organization_template: '組織テンプレート',
 };
diff --git a/packages/phrases/src/locales/ko/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/ko/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/ko/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/ko/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/ko/translation/admin-console/tabs.ts b/packages/phrases/src/locales/ko/translation/admin-console/tabs.ts
index a1b7c3867..173312316 100644
--- a/packages/phrases/src/locales/ko/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/ko/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: '테넌트 설정',
   mfa: '다중 요소 인증',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: '서명 키',
   organization_template: '조직 템플릿',
 };
diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/pl-pl/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/tabs.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/tabs.ts
index f78b8c422..1ecbd96c6 100644
--- a/packages/phrases/src/locales/pl-pl/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Ustawienia',
   mfa: 'Multi-factor auth',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Klucze do podpisu',
   organization_template: 'Szablon organizacji',
 };
diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/pt-br/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/pt-br/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/tabs.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/tabs.ts
index da0a6b130..043196801 100644
--- a/packages/phrases/src/locales/pt-br/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/pt-br/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Configurações',
   mfa: 'Autenticação de multi-fator',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Chaves de assinatura',
   organization_template: 'Modelo de organização',
 };
diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/pt-pt/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/tabs.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/tabs.ts
index 5160cd383..6b5cebe4e 100644
--- a/packages/phrases/src/locales/pt-pt/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Definições do inquilino',
   mfa: 'Autenticação multi-fator',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Chaves de assinatura',
   organization_template: 'Modelo de organização',
 };
diff --git a/packages/phrases/src/locales/ru/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/ru/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/ru/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/ru/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/ru/translation/admin-console/tabs.ts b/packages/phrases/src/locales/ru/translation/admin-console/tabs.ts
index 795488f95..61c881c5a 100644
--- a/packages/phrases/src/locales/ru/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/ru/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Настройки',
   mfa: 'Multi-factor auth',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'Ключи подписи',
   organization_template: 'Шаблон организации',
 };
diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/tr-tr/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/tabs.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/tabs.ts
index 8eaa11913..767355695 100644
--- a/packages/phrases/src/locales/tr-tr/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: 'Ayarlar',
   mfa: 'Çoklu faktörlü kimlik doğrulama',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: 'İmza anahtarları',
   organization_template: 'Kuruluş şablonu',
 };
diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/zh-cn/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/tabs.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/tabs.ts
index c4e0446b8..0cab45285 100644
--- a/packages/phrases/src/locales/zh-cn/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: '租户设置',
   mfa: '多因素认证',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: '签名密钥',
   organization_template: '组织模板',
 };
diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/zh-hk/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/tabs.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/tabs.ts
index 63290d16c..d79ad4d8d 100644
--- a/packages/phrases/src/locales/zh-hk/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: '租戶設置',
   mfa: '多重認證',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: '簽署密鑰',
   organization_template: '組織模板',
 };
diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/jwt-claims.ts
index 9aa5ff9d8..9714214c4 100644
--- a/packages/phrases/src/locales/zh-tw/translation/admin-console/jwt-claims.ts
+++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/jwt-claims.ts
@@ -1,6 +1,6 @@
 const jwt_claims = {
   /** UNTRANSLATED */
-  title: 'JWT claims',
+  title: 'Custom JWT',
   /** UNTRANSLATED */
   description:
     'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/tabs.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/tabs.ts
index f139b3bdc..091000283 100644
--- a/packages/phrases/src/locales/zh-tw/translation/admin-console/tabs.ts
+++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/tabs.ts
@@ -15,7 +15,7 @@ const tabs = {
   tenant_settings: '租戶設定',
   mfa: '多重認證',
   /** UNTRANSLATED */
-  jwt_customizer: 'JWT Claims',
+  customize_jwt: 'JWT Claims',
   signing_keys: '簽署密鑰',
   organization_template: '組織模板',
 };

From 59ffb43cf9d7ff5e8c2ffc4e54f87daea36304f3 Mon Sep 17 00:00:00 2001
From: simeng-li <simeng@silverhand.io>
Date: Fri, 29 Mar 2024 10:49:55 +0800
Subject: [PATCH 3/3] refactor(console): remove unused element

remove unused element
---
 .../ActionButton/CodeClearButton.tsx          | 24 -------------------
 1 file changed, 24 deletions(-)
 delete mode 100644 packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/CodeClearButton.tsx

diff --git a/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/CodeClearButton.tsx b/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/CodeClearButton.tsx
deleted file mode 100644
index 7a08a129b..000000000
--- a/packages/console/src/pages/CustomizeJwtDetails/MonacoCodeEditor/ActionButton/CodeClearButton.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { useTranslation } from 'react-i18next';
-
-import ClearIcon from '@/assets/icons/clear.svg';
-
-import ActionButton from './index';
-
-type Props = {
-  onClick: () => void;
-};
-
-function CodeClearButton({ onClick }: Props) {
-  const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
-
-  return (
-    <ActionButton
-      actionTip={t('jwt_claims.clear')}
-      actionSuccessTip={t('jwt_claims.cleared')}
-      icon={<ClearIcon />}
-      onClick={onClick}
-    />
-  );
-}
-
-export default CodeClearButton;