mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
fix(console): add in-line error message (#6386)
* fix(console): add in-line error message add in-line error message * refactor(console): remove old validation logic remove old validation logic
This commit is contained in:
parent
08e7c52365
commit
ce63bba018
7 changed files with 73 additions and 77 deletions
|
@ -16,6 +16,7 @@ import {
|
|||
import TextInput from '@/ds-components/TextInput';
|
||||
import TextLink from '@/ds-components/TextLink';
|
||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
import { isJsonObject } from '@/utils/json';
|
||||
|
||||
import ProtectedAppSettings from './ProtectedAppSettings';
|
||||
import { type ApplicationForm } from './utils';
|
||||
|
@ -167,12 +168,21 @@ function Settings({ data }: Props) {
|
|||
name="customData"
|
||||
control={control}
|
||||
defaultValue="{}"
|
||||
rules={{
|
||||
validate: (value) =>
|
||||
isJsonObject(value ?? '') ? true : t('application_details.custom_data_invalid'),
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<FormField
|
||||
title="application_details.field_custom_data"
|
||||
tip={t('application_details.field_custom_data_tip')}
|
||||
>
|
||||
<CodeEditor language="json" value={value} onChange={onChange} />
|
||||
<CodeEditor
|
||||
language="json"
|
||||
value={value}
|
||||
error={errors.customData?.message}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -84,24 +84,17 @@ function ApplicationDetailsContent({ data, secrets, oidcConfig, onApplicationUpd
|
|||
return;
|
||||
}
|
||||
|
||||
const [error, result] = applicationFormDataParser.toRequestPayload(formData);
|
||||
const json = applicationFormDataParser.toRequestPayload(formData);
|
||||
|
||||
if (result) {
|
||||
const updatedData = await api
|
||||
.patch(`api/applications/${data.id}`, {
|
||||
json: result,
|
||||
json,
|
||||
})
|
||||
.json<ApplicationResponse>();
|
||||
|
||||
reset(applicationFormDataParser.fromResponse(updatedData));
|
||||
onApplicationUpdated();
|
||||
toast.success(t('general.saved'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
toast.error(String(t(error)));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import { customClientMetadataDefault, type ApplicationResponse } from '@logto/schemas';
|
||||
import { cond, type DeepPartial, type Nullable } from '@silverhand/essentials';
|
||||
import { cond, conditional, type DeepPartial } from '@silverhand/essentials';
|
||||
|
||||
import { safeParseJsonObject } from '@/utils/json';
|
||||
import { isJsonObject } from '@/utils/json';
|
||||
|
||||
type ProtectedAppMetadataType = ApplicationResponse['protectedAppMetadata'];
|
||||
|
||||
|
@ -14,7 +13,7 @@ export type ApplicationForm = {
|
|||
isAdmin?: ApplicationResponse['isAdmin'];
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
protectedAppMetadata?: Omit<Exclude<ProtectedAppMetadataType, null>, 'customDomains'>; // Custom domains are handled separately
|
||||
customData: string;
|
||||
customData?: string;
|
||||
};
|
||||
|
||||
const mapToUriFormatArrays = (value?: string[]) =>
|
||||
|
@ -46,6 +45,7 @@ export const applicationFormDataParser = {
|
|||
...customClientMetadataDefault,
|
||||
...customClientMetadata,
|
||||
},
|
||||
customData: JSON.stringify(customData, null, 2),
|
||||
isAdmin,
|
||||
}
|
||||
),
|
||||
|
@ -57,12 +57,9 @@ export const applicationFormDataParser = {
|
|||
},
|
||||
}
|
||||
),
|
||||
customData: JSON.stringify(customData, null, 2),
|
||||
};
|
||||
},
|
||||
toRequestPayload: (
|
||||
data: ApplicationForm
|
||||
): [Nullable<AdminConsoleKey>, DeepPartial<ApplicationResponse>?] => {
|
||||
toRequestPayload: (data: ApplicationForm): DeepPartial<ApplicationResponse> => {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
|
@ -73,15 +70,7 @@ export const applicationFormDataParser = {
|
|||
customData,
|
||||
} = data;
|
||||
|
||||
const parsedCustomData = safeParseJsonObject(customData);
|
||||
|
||||
if (!parsedCustomData.success) {
|
||||
return ['application_details.custom_data_invalid'];
|
||||
}
|
||||
|
||||
return [
|
||||
null,
|
||||
{
|
||||
return {
|
||||
name,
|
||||
...cond(
|
||||
!protectedAppMetadata && {
|
||||
|
@ -101,7 +90,11 @@ export const applicationFormDataParser = {
|
|||
customClientMetadata?.corsAllowedOrigins
|
||||
),
|
||||
},
|
||||
customData: parsedCustomData.data,
|
||||
...conditional(
|
||||
// Invalid JSON string will be guarded by the form field validation
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
customData && isJsonObject(customData) && { customData: JSON.parse(customData) }
|
||||
),
|
||||
isAdmin,
|
||||
}
|
||||
),
|
||||
|
@ -113,7 +106,6 @@ export const applicationFormDataParser = {
|
|||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type SignInExperience, type Organization, Theme } from '@logto/schemas';
|
||||
import { Theme, type Organization, type SignInExperience } from '@logto/schemas';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
@ -20,12 +20,13 @@ import useApi, { type RequestError } from '@/hooks/use-api';
|
|||
import { mfa } from '@/hooks/use-console-routes/routes/mfa';
|
||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
import { isJsonObject } from '@/utils/json';
|
||||
|
||||
import { type OrganizationDetailsOutletContext } from '../types';
|
||||
|
||||
import JitSettings from './JitSettings';
|
||||
import styles from './index.module.scss';
|
||||
import { assembleData, isJsonObject, normalizeData, type FormData } from './utils';
|
||||
import { assembleData, normalizeData, type FormData } from './utils';
|
||||
|
||||
function Settings() {
|
||||
const { isDeleting, data, jit, onUpdated } = useOutletContext<OrganizationDetailsOutletContext>();
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { type Organization } from '@logto/schemas';
|
||||
import { trySafe } from '@silverhand/essentials';
|
||||
|
||||
import { type Option } from '@/ds-components/Select/MultiSelect';
|
||||
import { emptyBranding } from '@/types/sign-in-experience';
|
||||
|
@ -11,11 +10,6 @@ export type FormData = Partial<Omit<Organization, 'customData'> & { customData:
|
|||
jitSsoConnectorIds: string[];
|
||||
};
|
||||
|
||||
export const isJsonObject = (value: string) => {
|
||||
const parsed = trySafe<unknown>(() => JSON.parse(value));
|
||||
return Boolean(parsed && typeof parsed === 'object');
|
||||
};
|
||||
|
||||
export const normalizeData = (
|
||||
data: Organization,
|
||||
jit: { emailDomains: string[]; roles: Array<Option<string>>; ssoConnectorIds: string[] }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { jsonGuard, type Json, jsonObjectGuard, type JsonObject } from '@logto/schemas';
|
||||
import { jsonGuard, jsonObjectGuard, type Json, type JsonObject } from '@logto/schemas';
|
||||
import { trySafe } from '@silverhand/essentials';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export const safeParseJson = (
|
||||
|
@ -24,3 +25,8 @@ export const safeParseJsonObject = (
|
|||
return { success: false, error: t('admin_console.errors.invalid_json_format') };
|
||||
}
|
||||
};
|
||||
|
||||
export const isJsonObject = (value: string) => {
|
||||
const parsed = trySafe<unknown>(() => JSON.parse(value));
|
||||
return Boolean(parsed && typeof parsed === 'object');
|
||||
};
|
||||
|
|
|
@ -96,7 +96,7 @@ const application_details = {
|
|||
no_organization_placeholder: 'No organization found. <a>Go to organizations</a>',
|
||||
field_custom_data: 'Custom data',
|
||||
field_custom_data_tip:
|
||||
'Additional custom application metadata not listed in the pre-defined application properties, ',
|
||||
'Additional custom application info not listed in the pre-defined application properties, such as business-specific settings and configurations.',
|
||||
custom_data_invalid: 'Custom data must be a valid JSON object',
|
||||
branding: {
|
||||
name: 'Branding',
|
||||
|
|
Loading…
Reference in a new issue