diff --git a/packages/console/src/assets/icons/token-file-icon.svg b/packages/console/src/assets/icons/token-file-icon.svg new file mode 100644 index 00000000000..8860e4bf2c2 --- /dev/null +++ b/packages/console/src/assets/icons/token-file-icon.svg @@ -0,0 +1,5 @@ + + + + diff --git a/packages/console/src/assets/icons/user-file-icon.svg b/packages/console/src/assets/icons/user-file-icon.svg new file mode 100644 index 00000000000..2e2da53803e --- /dev/null +++ b/packages/console/src/assets/icons/user-file-icon.svg @@ -0,0 +1,4 @@ + + + diff --git a/packages/console/src/assets/icons/user.svg b/packages/console/src/assets/icons/user.svg index d86997a3186..ee2e0136f84 100644 --- a/packages/console/src/assets/icons/user.svg +++ b/packages/console/src/assets/icons/user.svg @@ -1,3 +1,4 @@ - - + + diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/config.ts b/packages/console/src/pages/JwtClaims/MonacoCodeEditor/config.ts index 3dc7bf76799..28dfc1c01dc 100644 --- a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/config.ts +++ b/packages/console/src/pages/JwtClaims/MonacoCodeEditor/config.ts @@ -21,4 +21,6 @@ export const defaultOptions: EditorProps['options'] = { renderLineHighlight: 'none', fontFamily: 'Roboto Mono, monospace', fontSize: 14, + automaticLayout: true, + tabSize: 2, }; diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.module.scss b/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.module.scss index 0d5736c2bd3..4cfd024bac9 100644 --- a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.module.scss +++ b/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.module.scss @@ -22,27 +22,28 @@ gap: _.unit(2); .tab { - padding: _.unit(1.5) _.unit(3); font: var(--font-label-2); font-family: 'Roboto Mono', monospace; - color: var(--color-code-grey); - cursor: pointer; - - &.active, - &:hover { - color: var(--color-code-white); - background-color: var(--color-code-tab-active-bg); - border-radius: 8px; + padding: _.unit(1.5) _.unit(3); + color: var(--color-code-white); + display: flex; + align-items: center; + gap: _.unit(1); + + &.tabButton { + color: var(--color-code-grey); + cursor: pointer; + + &.active, + &:hover { + color: var(--color-code-white); + background-color: var(--color-code-tab-active-bg); + border-radius: 8px; + } } } } - .title { - font: var(--font-label-2); - font-family: 'Roboto Mono', monospace; - color: var(--color-code-white); - } - .actions { display: flex; gap: _.unit(2); @@ -53,6 +54,5 @@ .editorContainer { position: relative; flex-grow: 1; - padding-bottom: _.unit(4); } } diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx b/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx index c39ebf5f2f5..ee981341254 100644 --- a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx +++ b/packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx @@ -12,6 +12,9 @@ import { onKeyDownHandler } from '@/utils/a11y'; import { logtoDarkTheme, defaultOptions } from './config.js'; import * as styles from './index.module.scss'; import type { IStandaloneCodeEditor, Model } from './type.js'; +import useEditorHeight from './use-editor-height.js'; + +export type { Model } from './type.js'; type Props = { className?: string; @@ -25,11 +28,16 @@ function MonacoCodeEditor({ className, actions, models }: Props) { const editorRef = useRef>(null); const [activeModelName, setActiveModelName] = useState(); + const activeModel = useMemo( () => models.find((model) => model.name === activeModelName), [activeModelName, models] ); + const isMultiModals = useMemo(() => models.length > 1, [models]); + + const { containerRef, editorHeight } = useEditorHeight(); + // Set the first model as the active model useEffect(() => { setActiveModelName(models[0]?.name); @@ -77,28 +85,31 @@ function MonacoCodeEditor({ className, actions, models }: Props) { return (
- {models.length > 1 ? ( -
- {models.map(({ name }) => ( -
{ +
+ {models.map(({ name, title, icon }) => ( +
{ setActiveModelName(name); - }} - onKeyDown={onKeyDownHandler(() => { + }, + onKeyDown: onKeyDownHandler(() => { setActiveModelName(name); - })} - > - {name} -
- ))} -
- ) : ( -
{activeModel?.title}
- )} + }), + })} + > + {icon} + {title} +
+ ))} +
{actions} @@ -106,10 +117,10 @@ function MonacoCodeEditor({ className, actions, models }: Props) {
-
+
[0]; export type Model = { /** Used as the unique key for the monaco editor model @see {@link https://github.com/suren-atoyan/monaco-react?tab=readme-ov-file#multi-model-editor} */ name: string; + /** The icon of the model, will be displayed on the tab */ + icon?: React.ReactNode; /** The title of the model */ title: string; defaultValue: string; diff --git a/packages/console/src/pages/JwtClaims/MonacoCodeEditor/use-editor-height.ts b/packages/console/src/pages/JwtClaims/MonacoCodeEditor/use-editor-height.ts new file mode 100644 index 00000000000..c93707bfe20 --- /dev/null +++ b/packages/console/src/pages/JwtClaims/MonacoCodeEditor/use-editor-height.ts @@ -0,0 +1,35 @@ +import { useRef, useState, useLayoutEffect } from 'react'; + +// Recalculate the height of the editor when the container size changes +// This is to avoid the code editor's height shaking when the content is updated. +// @see {@link https://github.com/react-monaco-editor/react-monaco-editor/issues/391} +const useEditorHeight = () => { + const containerRef = useRef(null); + + const [editorHeight, setEditorHeight] = useState('100%'); + const safeArea = 16; + + useLayoutEffect(() => { + const handleResize = () => { + if (containerRef.current) { + setEditorHeight(containerRef.current.clientHeight - safeArea); + } + }; + + handleResize(); + + const resizeObserver = new ResizeObserver(handleResize); + + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + return { containerRef, editorHeight }; +}; + +export default useEditorHeight; diff --git a/packages/console/src/pages/JwtClaims/RightPanel/index.tsx b/packages/console/src/pages/JwtClaims/RightPanel/index.tsx deleted file mode 100644 index 4dbf92802f7..00000000000 --- a/packages/console/src/pages/JwtClaims/RightPanel/index.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { Editor } from '@monaco-editor/react'; -import classNames from 'classnames'; -import { useCallback, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import BookIcon from '@/assets/icons/book.svg'; -import StartIcon from '@/assets/icons/start.svg'; -import Button from '@/ds-components/Button'; -import Table from '@/ds-components/Table'; - -import { - JwtTokenType, - userDataDescription, - tokenDataDescription, - fetchExternalDataEditorOptions, - fetchExternalDataCodeExample, -} from '../config'; - -import EnvironmentVariablesField from './EnvironmentVariablesField'; -import GuideCard, { CardType } from './GuideCard'; -import * as styles from './index.module.scss'; - -enum Tab { - DataSource = 'data_source_tab', - Test = 'test_tab', -} - -type Props = { - tokenType: JwtTokenType; -}; - -function RightPanel({ tokenType }: Props) { - const [activeTab, setActiveTab] = useState(Tab.DataSource); - const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - - const getDataColumns = useCallback( - (valueColSpan = 10) => [ - { - title: t('domain.custom.dns_table.value_field'), - dataIndex: 'value', - colSpan: valueColSpan, - render: ({ value }: (typeof userDataDescription)[0]) => ( - {value} - ), - }, - { - title: t('general.description'), - dataIndex: 'description', - colSpan: 24 - valueColSpan, - render: ({ description }: (typeof userDataDescription)[0]) => ( - {description} - ), - }, - ], - [t] - ); - - return ( -
-
- {Object.values(Tab).map((tab) => ( -
-
-
{t('jwt_claims.jwt_claims_description')}
- {tokenType === JwtTokenType.UserAccessToken && ( - - - - )} - -
- - -
- {t('jwt_claims.fetch_external_data.description')} -
- -
- - {/** - * We use useFieldArray hook to manage the list of environment variables in the EnvironmentVariablesField component. - * useFieldArray will read the form context and return the necessary methods and values to manage the list. - * The form context will mutate when the tokenType changes. It will provide different form state and methods based on the tokenType. (@see JwtClaims component.) - * However, the form context/controller updates did not trigger a re-render of the useFieldArray hook. (@see {@link https://github.com/react-hook-form/react-hook-form/blob/master/src/useFieldArray.ts#L95}) - * - * This cause issues when the tokenType changes and the environment variables list is not rerendered. The form state will be stale. - * In order to fix this, we need to re-render the EnvironmentVariablesField component when the tokenType changes. - * Achieve this by adding a key to the EnvironmentVariablesField component. Force a re-render when the tokenType changes. - */} - - - - - ); -} - -export default RightPanel; diff --git a/packages/console/src/pages/JwtClaims/ScriptPanel.tsx b/packages/console/src/pages/JwtClaims/ScriptPanel.tsx new file mode 100644 index 00000000000..e138f61d5ed --- /dev/null +++ b/packages/console/src/pages/JwtClaims/ScriptPanel.tsx @@ -0,0 +1,41 @@ +/* Code Editor for the custom JWT claims script. */ +import { useMemo } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; + +import Card from '@/ds-components/Card'; + +import MonacoCodeEditor, { type Model } from './MonacoCodeEditor'; +import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config'; +import * as styles from './index.module.scss'; +import { type JwtClaimsFormType } from './type'; + +const titlePhrases = Object.freeze({ + [JwtTokenType.UserAccessToken]: 'user_jwt', + [JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt', +}); + +function ScriptPanel() { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + + const { watch } = useFormContext(); + const tokenType = watch('tokenType'); + + // TODO: API integration, read/write the custom claims code value + const activeModel = useMemo(() => { + return tokenType === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile; + }, [tokenType]); + + return ( + +
+ {t('jwt_claims.code_editor_title', { + token: t(`jwt_claims.${titlePhrases[tokenType]}`), + })} +
+ +
+ ); +} + +export default ScriptPanel; diff --git a/packages/console/src/pages/JwtClaims/RightPanel/EnvironmentVariablesField.tsx b/packages/console/src/pages/JwtClaims/SettingsPanel/EnvironmentVariablesField.tsx similarity index 100% rename from packages/console/src/pages/JwtClaims/RightPanel/EnvironmentVariablesField.tsx rename to packages/console/src/pages/JwtClaims/SettingsPanel/EnvironmentVariablesField.tsx diff --git a/packages/console/src/pages/JwtClaims/RightPanel/GuideCard.tsx b/packages/console/src/pages/JwtClaims/SettingsPanel/GuideCard.tsx similarity index 93% rename from packages/console/src/pages/JwtClaims/RightPanel/GuideCard.tsx rename to packages/console/src/pages/JwtClaims/SettingsPanel/GuideCard.tsx index 63a7cc55c91..21cdf806bb4 100644 --- a/packages/console/src/pages/JwtClaims/RightPanel/GuideCard.tsx +++ b/packages/console/src/pages/JwtClaims/SettingsPanel/GuideCard.tsx @@ -25,7 +25,7 @@ function GuideCard({ name, children }: GuardCardProps) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.jwt_claims' }); return ( - +
(); + const tokenType = watch('tokenType'); + + const getDataColumns = useCallback( + (valueColSpan = 10) => [ + { + title: t('domain.custom.dns_table.value_field'), + dataIndex: 'value', + colSpan: valueColSpan, + render: ({ value }: (typeof userDataDescription)[0]) => ( + {value} + ), + }, + { + title: t('general.description'), + dataIndex: 'description', + colSpan: 24 - valueColSpan, + render: ({ description }: (typeof userDataDescription)[0]) => ( + {description} + ), + }, + ], + [t] + ); + + return ( +
+
{t('jwt_claims.jwt_claims_description')}
+ {tokenType === JwtTokenType.UserAccessToken && ( + +
+ + )} + +
+ + +
{t('jwt_claims.fetch_external_data.description')}
+ +
+ + {/** + * We use useFieldArray hook to manage the list of environment variables in the EnvironmentVariablesField component. + * useFieldArray will read the form context and return the necessary methods and values to manage the list. + * The form context will mutate when the tokenType changes. It will provide different form state and methods based on the tokenType. (@see JwtClaims component.) + * However, the form context/controller updates did not trigger a re-render of the useFieldArray hook. (@see {@link https://github.com/react-hook-form/react-hook-form/blob/master/src/useFieldArray.ts#L95}) + * + * This cause issues when the tokenType changes and the environment variables list is not rerendered. The form state will be stale. + * In order to fix this, we need to re-render the EnvironmentVariablesField component when the tokenType changes. + * Achieve this by adding a key to the EnvironmentVariablesField component. Force a re-render when the tokenType changes. + */} + + + + ); +} + +export default InstructionTab; diff --git a/packages/console/src/pages/JwtClaims/SettingsPanel/TestTab.tsx b/packages/console/src/pages/JwtClaims/SettingsPanel/TestTab.tsx new file mode 100644 index 00000000000..fe39af1d220 --- /dev/null +++ b/packages/console/src/pages/JwtClaims/SettingsPanel/TestTab.tsx @@ -0,0 +1,56 @@ +import classNames from 'classnames'; +import { useMemo } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; + +import Button from '@/ds-components/Button'; +import Card from '@/ds-components/Card'; + +import MonacoCodeEditor from '../MonacoCodeEditor/index.js'; +import { + userTokenPayloadTestModel, + machineToMachineTokenPayloadTestModel, + userTokenContextTestModel, + JwtTokenType, +} from '../config.js'; +import { type JwtClaimsFormType } from '../type.js'; + +import * as styles from './index.module.scss'; + +type Props = { + isActive: boolean; +}; + +function TestTab({ isActive }: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.jwt_claims' }); + + const { watch } = useFormContext(); + const tokenType = watch('tokenType'); + + const editorModels = useMemo( + () => + tokenType === JwtTokenType.UserAccessToken + ? [userTokenPayloadTestModel, userTokenContextTestModel] + : [machineToMachineTokenPayloadTestModel], + [tokenType] + ); + + return ( +
+ +
+
+
{t('tester.title')}
+
{t('tester.subtitle')}
+
+
+
+ +
+
+
+ ); +} + +export default TestTab; diff --git a/packages/console/src/pages/JwtClaims/RightPanel/index.module.scss b/packages/console/src/pages/JwtClaims/SettingsPanel/index.module.scss similarity index 52% rename from packages/console/src/pages/JwtClaims/RightPanel/index.module.scss rename to packages/console/src/pages/JwtClaims/SettingsPanel/index.module.scss index f5195767fdc..5c53ab777a2 100644 --- a/packages/console/src/pages/JwtClaims/RightPanel/index.module.scss +++ b/packages/console/src/pages/JwtClaims/SettingsPanel/index.module.scss @@ -39,6 +39,7 @@ display: none; &.active { + flex: 1; display: flex; flex-direction: column; gap: _.unit(4); @@ -48,47 +49,54 @@ font: var(--font-body-2); color: var(--color-text-secondary); } +} - .card { - .headerRow { - display: flex; - flex-direction: row; - gap: _.unit(4); - align-items: center; - cursor: pointer; - user-select: none; - } +.card { + .headerRow { + display: flex; + flex-direction: row; + gap: _.unit(4); + align-items: center; + } - .cardHeader { - flex: 1; - } + .cardHeader { + flex: 1; + } - .cardTitle { - font: var(--font-label-2); - color: var(--color-text); - margin-bottom: _.unit(1); - } + .cardTitle { + font: var(--font-label-2); + color: var(--color-text); + margin-bottom: _.unit(1); + } + + .cardSubtitle { + font: var(--font-body-2); + color: var(--color-text-secondary); + } - .cardSubtitle { - font: var(--font-body-2); - color: var(--color-text-secondary); + .cardContent { + > *:first-child { + margin-top: _.unit(6); } + } + + .expandButton { + width: 24px; + height: 24px; + transition: transform 0.3s ease; + color: var(--color-text-secondary); + } - .expandButton { - width: 24px; - height: 24px; - transition: transform 0.3s ease; - color: var(--color-text-secondary); + &.collapsible { + .headerRow { + cursor: pointer; + user-select: none; } .cardContent { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; - - > *:first-child { - margin-top: _.unit(6); - } } &.expanded { @@ -100,37 +108,46 @@ max-height: 1000; } } + } +} - .table { - .value { - display: inline-block; - padding: _.unit(0.5) _.unit(2); - color: var(--color-text); - background-color: var(--color-bg-info-tag); - border-radius: 4px; - font-family: 'Roboto Mono', monospace; - } - } +.table { + .value { + display: inline-block; + padding: _.unit(0.5) _.unit(2); + color: var(--color-text); + background-color: var(--color-bg-info-tag); + border-radius: 4px; + font-family: 'Roboto Mono', monospace; } +} + +.sampleCode { + margin-top: _.unit(4); - .editor { - margin-top: _.unit(4); + :global { + /* stylelint-disable-next-line selector-class-pattern */ + .monaco-editor { + border-radius: 8px; - :global { /* stylelint-disable-next-line selector-class-pattern */ - .monaco-editor { + .overflow-guard { border-radius: 8px; + } - /* stylelint-disable-next-line selector-class-pattern */ - .overflow-guard { - border-radius: 8px; - } - - /* stylelint-disable-next-line selector-class-pattern */ - .lines-content { - padding: 0 16px; - } + /* stylelint-disable-next-line selector-class-pattern */ + .lines-content { + padding: 0 16px; } } } } + +.flexColumn { + display: flex; + flex-direction: column; +} + +.flexGrow { + flex-grow: 1; +} diff --git a/packages/console/src/pages/JwtClaims/SettingsPanel/index.tsx b/packages/console/src/pages/JwtClaims/SettingsPanel/index.tsx new file mode 100644 index 00000000000..4b64c614789 --- /dev/null +++ b/packages/console/src/pages/JwtClaims/SettingsPanel/index.tsx @@ -0,0 +1,42 @@ +import classNames from 'classnames'; +import { useState } from 'react'; + +import BookIcon from '@/assets/icons/book.svg'; +import StartIcon from '@/assets/icons/start.svg'; +import Button from '@/ds-components/Button'; + +import InstructionTab from './InstructionTab'; +import TestTab from './TestTab'; +import * as styles from './index.module.scss'; + +enum Tab { + DataSource = 'data_source_tab', + Test = 'test_tab', +} + +function SettingsPanel() { + const [activeTab, setActiveTab] = useState(Tab.DataSource); + + return ( +
+
+ {Object.values(Tab).map((tab) => ( +
+ + +
+ ); +} + +export default SettingsPanel; diff --git a/packages/console/src/pages/JwtClaims/config.ts b/packages/console/src/pages/JwtClaims/config.tsx similarity index 73% rename from packages/console/src/pages/JwtClaims/config.ts rename to packages/console/src/pages/JwtClaims/config.tsx index 40e4d71f414..117d0cf89c5 100644 --- a/packages/console/src/pages/JwtClaims/config.ts +++ b/packages/console/src/pages/JwtClaims/config.tsx @@ -1,5 +1,8 @@ import { type EditorProps } from '@monaco-editor/react'; +import TokenFileIcon from '@/assets/icons/token-file-icon.svg'; +import UserFileIcon from '@/assets/icons/user-file-icon.svg'; + import type { Model } from './MonacoCodeEditor/type.js'; /** @@ -171,33 +174,34 @@ export const userDataDescription: GuideTableData[] = [ export const tokenDataDescription: GuideTableData[] = [ { - value: 'iss', - description: '(issuer) Issuer of the JWT.', - }, - { - value: 'sub', - description: '(subject) Subject of the JWT.', + value: 'jti', + description: + '(JWT ID) Unique identifier for the JWT. Useful for tracking and preventing reuse of the token.', }, { - value: 'aud', - description: '(audience) Recipient for which the JWT is intended.', + value: 'iat', + description: '(issued at) Time at which the JWT was issued.', }, { value: 'exp', description: '(expiration) Time after which the JWT expires.', }, { - value: 'nbf', - description: '(not before) Time before which the JWT must not be accepted for processing.', + value: 'client_id', + description: 'Client ID of the application that requested the JWT.', }, { - value: 'iat', - description: '(issued at) Time at which the JWT was issued.', + value: 'kind', + description: + 'Type of the token. `AccessToken` for user access tokens and `ClientCredentials` for machine-to-machine access tokens.', }, { - value: 'jti', - description: - '(JWT ID) Unique identifier for the JWT. Useful for tracking and preventing reuse of the token.', + value: 'scope', + description: 'Scopes requested by the client joint by space.', + }, + { + value: 'aud', + description: '(audience) Audience for which the JWT is intended.', }, ]; @@ -226,3 +230,62 @@ const data = await response.json(); return { externalData: data, };`; + +/** + * Tester Code Editor configs + */ +const standardTokenPayloadData = { + jti: '1234567890', + iat: 1_516_239_022, + exp: 1_516_239_022, + client_id: 'my_app', + scope: 'read write', + aud: 'http://localhost:3000/api', +}; + +const defaultUserTokenPayloadData = { + ...standardTokenPayloadData, + kind: 'AccessToken', +}; + +const defaultMachineToMachineTokenPayloadData = { + ...standardTokenPayloadData, + kind: 'ClientCredentials', +}; + +const defaultUserTokenContextData = { + user: { + id: '123', + primaryEmail: 'foo@logto.io', + primaryPhone: '+1234567890', + username: 'foo', + name: 'Foo Bar', + avatar: 'https://example.com/avatar.png', + identities: {}, + customData: {}, + }, +}; + +export const userTokenPayloadTestModel: Model = { + language: 'json', + icon: , + name: 'user-token-payload.json', + title: 'Token', + defaultValue: JSON.stringify(defaultUserTokenPayloadData, null, '\t'), +}; + +export const machineToMachineTokenPayloadTestModel: Model = { + language: 'json', + icon: , + name: 'machine-to-machine-token-payload.json', + title: 'Token', + defaultValue: JSON.stringify(defaultMachineToMachineTokenPayloadData, null, '\t'), +}; + +export const userTokenContextTestModel: Model = { + language: 'json', + icon: , + name: 'user-token-context.json', + title: 'User Context', + defaultValue: JSON.stringify(defaultUserTokenContextData, null, '\t'), +}; diff --git a/packages/console/src/pages/JwtClaims/index.module.scss b/packages/console/src/pages/JwtClaims/index.module.scss index 605aa076cea..abae9a5575a 100644 --- a/packages/console/src/pages/JwtClaims/index.module.scss +++ b/packages/console/src/pages/JwtClaims/index.module.scss @@ -18,22 +18,25 @@ .tabContent { display: flex; flex-direction: row; - gap: _.unit(3); flex-grow: 1; > * { flex: 1; + margin-bottom: _.unit(6); + + &:first-child { + margin-right: _.unit(3); + } } } .codePanel { - margin-bottom: _.unit(6); position: relative; display: flex; flex-direction: column; .cardTitle { - font: var(--font-title-2); + font: var(--font-label-2); margin-bottom: _.unit(3); } diff --git a/packages/console/src/pages/JwtClaims/index.tsx b/packages/console/src/pages/JwtClaims/index.tsx index b2eefa8043c..24e760d6684 100644 --- a/packages/console/src/pages/JwtClaims/index.tsx +++ b/packages/console/src/pages/JwtClaims/index.tsx @@ -1,63 +1,47 @@ import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact'; import classNames from 'classnames'; -import type { TFuncKey } from 'i18next'; -import { useMemo } from 'react'; import { useForm, FormProvider } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import Card from '@/ds-components/Card'; import CardTitle from '@/ds-components/CardTitle'; import TabNav, { TabNavItem } from '@/ds-components/TabNav'; -import MonacoCodeEditor from './MonacoCodeEditor'; -import { type Model } from './MonacoCodeEditor/type'; -import RightPanel from './RightPanel'; -import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config'; +import ScriptPanel from './ScriptPanel'; +import SettingsPanel from './SettingsPanel'; +import { JwtTokenType } from './config'; import * as styles from './index.module.scss'; import { type JwtClaimsFormType } from './type'; export { JwtTokenType } from './config'; +const tabPhrases = Object.freeze({ + [JwtTokenType.UserAccessToken]: 'user_jwt_tab', + [JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt_tab', +}); + +const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`; + type Props = { tab: JwtTokenType; }; -const phrases = Object.freeze({ - tab: { - [JwtTokenType.UserAccessToken]: 'user_jwt_tab', - [JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt_tab', - }, - token: { - [JwtTokenType.UserAccessToken]: 'user_jwt', - [JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt', - }, -} satisfies Record< - string, - Record> ->); - -const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`; - function JwtClaims({ tab }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const userJwtClaimsForm = useForm({ defaultValues: { + tokenType: JwtTokenType.UserAccessToken, environmentVariables: [{ key: '', value: '' }], }, }); const machineToMachineJwtClaimsForm = useForm({ defaultValues: { + tokenType: JwtTokenType.MachineToMachineAccessToken, environmentVariables: [{ key: '', value: '' }], }, }); - // TODO: API integration, read/write the custom claims code value - const activeModel = useMemo(() => { - return tab === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile; - }, [tab]); - return (
{Object.values(JwtTokenType).map((tokenType) => ( - {t(`jwt_claims.${phrases.tab[tokenType]}`)} + {t(`jwt_claims.${tabPhrases[tokenType]}`)} ))} @@ -78,15 +62,8 @@ function JwtClaims({ tab }: Props) { : machineToMachineJwtClaimsForm)} >
- -
- {t('jwt_claims.code_editor_title', { - token: t(`jwt_claims.${phrases.token[tab]}`), - })} -
- -
- + +
diff --git a/packages/console/src/pages/JwtClaims/type.ts b/packages/console/src/pages/JwtClaims/type.ts index cea2f1f60f0..fef9323b330 100644 --- a/packages/console/src/pages/JwtClaims/type.ts +++ b/packages/console/src/pages/JwtClaims/type.ts @@ -1,4 +1,7 @@ +import { type JwtTokenType } from './config.js'; + export type JwtClaimsFormType = { + tokenType: JwtTokenType; script?: string; environmentVariables?: Array<{ key: string; value: string }>; contextSample?: string; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 052e2ebd040..7c3e888a778 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 @@ -35,9 +35,10 @@ const jwt_claims = { }, jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { title: 'Test', subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, }; 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 a2d579d72d1..af30c2823a6 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 @@ -56,11 +56,13 @@ const jwt_claims = { /** UNTRANSLATED */ jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', - test_data: { + tester: { /** UNTRANSLATED */ title: 'Test', /** UNTRANSLATED */ subtitle: "Edit the context to adjust the token's request states and test your custom claims.", + /** UNTRANSLATED */ + run_button: 'Run', }, };