Skip to content

Commit

Permalink
feat(console,phrases): implement environment variables input field
Browse files Browse the repository at this point in the history
implement environment variables input field
  • Loading branch information
simeng-li committed Mar 7, 2024
1 parent 5803ee0 commit 9332c9e
Show file tree
Hide file tree
Showing 19 changed files with 194 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useCallback, useMemo } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import FormField from '@/ds-components/FormField';
import KeyValueInputField from '@/ds-components/KeyValueInputField';

import { type JwtClaimsFormType } from '../type';

const isValidKey = (key: string) => {
return /^\w+$/.test(key);
};

function EnvironmentVariablesField() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });

const {
register,
getValues,
trigger,
formState: {
errors: { environmentVariables: envVariableErrors },
submitCount,
},
} = useFormContext<JwtClaimsFormType>();

// Read the form controller from the context @see {@link https://react-hook-form.com/docs/usefieldarray}
const { fields, remove, append } = useFieldArray<JwtClaimsFormType>({
name: 'environmentVariables',
});

const keyValidator = useCallback(
(key: string, index: number) => {
const envVariables = getValues('environmentVariables');

if (!envVariables) {
return true;
}

// Unique key validation
if (envVariables.filter(({ key: _key }) => _key.length > 0 && _key === key).length > 1) {
// Reuse the same error phrase key from webhook settings
return t('webhook_details.settings.key_duplicated_error');
}

// Empty key validation (if value is present)
const correspondValue = getValues(`environmentVariables.${index}.value`);
if (correspondValue) {
// Reuse the same error phrase key from webhook settings
return Boolean(key) || t('webhook_details.settings.key_missing_error');
}

// Key format validation
if (Boolean(key) && !isValidKey(key)) {
// Reuse the same error phrase key from webhook settings
return t('webhook_details.settings.invalid_key_error');
}

return true;
},
[getValues, t]
);

const valueValidator = useCallback(
(value: string, index: number) => {
return getValues(`environmentVariables.${index}.value`)
? Boolean(value) || t('webhook_details.settings.value_missing_error')
: true;
},
[getValues, t]
);

const revalidate = useCallback(() => {
for (const [index] of fields.entries()) {
void trigger(`environmentVariables.${index}.key`);

// Only trigger value validation if the form has been submitted
if (submitCount > 0) {
void trigger(`environmentVariables.${index}.value`);
}
}
}, [fields, submitCount, trigger]);

const getInputFieldProps = useMemo(
() => ({
key: (index: number) =>
register(`environmentVariables.${index}.key`, {
validate: (key) => keyValidator(key, index),
onChange: revalidate,
}),
value: (index: number) =>
register(`environmentVariables.${index}.value`, {
validate: (value) => valueValidator(value, index),
onChange: revalidate,
}),
}),
[register, revalidate, keyValidator, valueValidator]
);

return (
<FormField title="jwt_claims.environment_variables.input_field_title">
<KeyValueInputField
fields={fields}
// Force envVariableErrors to be an array, otherwise return undefined
errors={envVariableErrors?.map?.((error) => error)}
getInputFieldProps={getInputFieldProps}
onAppend={append}
onRemove={remove}
/>
</FormField>
);
}

export default EnvironmentVariablesField;
15 changes: 14 additions & 1 deletion packages/console/src/pages/JwtClaims/RightPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
fetchExternalDataCodeExample,
} from '../config';

import EnvironmentVariablesField from './EnvironmentVariablesField';
import GuideCard, { CardType } from './GuideCard';
import * as styles from './index.module.scss';

Expand Down Expand Up @@ -107,7 +108,19 @@ function RightPanel({ tokenType }: Props) {
options={fetchExternalDataEditorOptions}
/>
</GuideCard>
<GuideCard name={CardType.EnvironmentVariables} />
<GuideCard name={CardType.EnvironmentVariables}>
{/**
* 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.
*/}
<EnvironmentVariablesField key={tokenType} />
</GuideCard>
</div>
</div>
);
Expand Down
42 changes: 31 additions & 11 deletions packages/console/src/pages/JwtClaims/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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';
Expand All @@ -13,6 +14,7 @@ import { type Model } from './MonacoCodeEditor/type';
import RightPanel from './RightPanel';
import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config';
import * as styles from './index.module.scss';
import { type JwtClaimsFormType } from './type';

export { JwtTokenType } from './config';

Expand All @@ -39,6 +41,18 @@ const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`;
function JwtClaims({ tab }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });

const userJwtClaimsForm = useForm<JwtClaimsFormType>({
defaultValues: {
environmentVariables: [{ key: '', value: '' }],
},
});

const machineToMachineJwtClaimsForm = useForm<JwtClaimsFormType>({
defaultValues: {
environmentVariables: [{ key: '', value: '' }],
},
});

// TODO: API integration, read/write the custom claims code value
const activeModel = useMemo<Model>(() => {
return tab === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile;
Expand All @@ -58,17 +72,23 @@ function JwtClaims({ tab }: Props) {
</TabNavItem>
))}
</TabNav>
<form className={classNames(styles.tabContent)}>
<Card className={styles.codePanel}>
<div className={styles.cardTitle}>
{t('jwt_claims.code_editor_title', {
token: t(`jwt_claims.${phrases.token[tab]}`),
})}
</div>
<MonacoCodeEditor className={styles.flexGrow} models={[activeModel]} />
</Card>
<RightPanel tokenType={tab} />
</form>
<FormProvider
{...(tab === JwtTokenType.UserAccessToken
? userJwtClaimsForm
: machineToMachineJwtClaimsForm)}
>
<form className={classNames(styles.tabContent)}>
<Card className={styles.codePanel}>
<div className={styles.cardTitle}>
{t('jwt_claims.code_editor_title', {
token: t(`jwt_claims.${phrases.token[tab]}`),
})}
</div>
<MonacoCodeEditor className={styles.flexGrow} models={[activeModel]} />
</Card>
<RightPanel tokenType={tab} />
</form>
</FormProvider>
</div>
);
}
Expand Down
6 changes: 6 additions & 0 deletions packages/console/src/pages/JwtClaims/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type JwtClaimsFormType = {
script?: string;
environmentVariables?: Array<{ key: string; value: string }>;
contextSample?: string;
tokenSample?: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const jwt_claims = {
title: 'Set environment variables',
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
input_field_title: 'Add environment variables',
},
jwt_claims_hint:
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const jwt_claims = {
/** UNTRANSLATED */
subtitle:
'Use environment variables to store sensitive information and access them in your custom claims handler.',
/** UNTRANSLATED */
input_field_title: 'Add environment variables',
},
/** UNTRANSLATED */
jwt_claims_hint:
Expand Down

0 comments on commit 9332c9e

Please sign in to comment.