0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00
logto/packages/console/src/mdx-components/UriInputField/index.tsx

135 lines
4.3 KiB
TypeScript

import type { AdminConsoleKey } from '@logto/phrases';
import type { Application } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import type { KeyboardEvent } from 'react';
import { useContext, useRef } from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
import { GuideContext } from '@/components/Guide';
import MultiTextInputField from '@/components/MultiTextInputField';
import Button from '@/ds-components/Button';
import {
convertRhfErrorMessage,
createValidatorForRhf,
} from '@/ds-components/MultiTextInput/utils';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import type { GuideForm } from '@/types/guide';
import { trySubmitSafe } from '@/utils/form';
import { uriValidator } from '@/utils/validator';
import * as styles from './index.module.scss';
type Props = {
name: 'redirectUris' | 'postLogoutRedirectUris';
/** The default value of the input field when there's no data. */
defaultValue?: string;
};
function UriInputField({ name, defaultValue }: Props) {
const methods = useForm<Partial<GuideForm>>();
const {
control,
getValues,
handleSubmit,
reset,
formState: { isSubmitting },
} = methods;
const { app } = useContext(GuideContext);
const appId = app?.id;
const { data, mutate } = useSWR<Application, RequestError>(appId && `api/applications/${appId}`);
const ref = useRef<HTMLDivElement>(null);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useApi();
const title: AdminConsoleKey =
name === 'redirectUris'
? 'application_details.redirect_uri'
: 'application_details.post_sign_out_redirect_uri';
const onSubmit = trySubmitSafe(async (value: string[]) => {
if (!appId) {
return;
}
const updatedApp = await api
.patch(`api/applications/${appId}`, {
json: {
oidcClientMetadata: {
[name]: value.filter(Boolean),
},
},
})
.json<Application>();
void mutate(updatedApp);
toast.success(t('general.saved'));
// Reset form to set 'isDirty' to false
reset(getValues());
});
const onKeyPress = (event: KeyboardEvent<HTMLInputElement>, value: string[]) => {
if (event.key === 'Enter') {
event.preventDefault();
void handleSubmit(async () => onSubmit(value))();
}
};
const clientMetadata = data?.oidcClientMetadata[name];
const defaultValueArray = clientMetadata?.length
? clientMetadata
: conditional(defaultValue && [defaultValue]);
return (
<FormProvider {...methods}>
<form className={styles.form}>
<Controller
name={name}
control={control}
defaultValue={defaultValueArray}
rules={{
validate: createValidatorForRhf({
required: t('errors.required_field_missing_plural', { field: t(title) }),
pattern: {
verify: (value) => !value || uriValidator(value),
message: t('errors.invalid_uri_format'),
},
}),
}}
render={({ field: { onChange, value = [] }, fieldState: { error, isDirty } }) => {
const errorObject = convertRhfErrorMessage(error?.message);
return (
<div ref={ref} className={styles.wrapper}>
<MultiTextInputField
isRequired={name === 'redirectUris'}
formFieldClassName={styles.field}
title={title}
value={value}
error={errorObject}
className={styles.multiTextInput}
onChange={onChange}
onKeyPress={(event) => {
onKeyPress(event, value);
}}
/>
<Button
className={styles.saveButton}
disabled={!isDirty}
isLoading={isSubmitting}
title="general.save"
type="primary"
onClick={handleSubmit(async () => onSubmit(value))}
/>
</div>
);
}}
/>
</form>
</FormProvider>
);
}
export default UriInputField;