mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(console): add custom domain form (#3962)
This commit is contained in:
parent
0c1744e77d
commit
6fcc2a875c
4 changed files with 128 additions and 2 deletions
|
@ -0,0 +1,15 @@
|
||||||
|
@use '@/scss/underscore' as _;
|
||||||
|
|
||||||
|
.addDomain {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.textInput {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: _.unit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { type Domain } from '@logto/schemas';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import TextInput from '@/components/TextInput';
|
||||||
|
import useApi from '@/hooks/use-api';
|
||||||
|
import { onKeyDownHandler } from '@/utils/a11y';
|
||||||
|
|
||||||
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
const subdomainRegex = /^[\dA-Za-z][\dA-Za-z-]*[\dA-Za-z](\.[\dA-Za-z][\dA-Za-z-]*[\dA-Za-z]){2,}$/;
|
||||||
|
|
||||||
|
type FormData = {
|
||||||
|
domain: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onCustomDomainAdded: (domain: Domain) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function AddDomainForm({ onCustomDomainAdded }: Props) {
|
||||||
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
watch,
|
||||||
|
formState: { errors, isSubmitting },
|
||||||
|
handleSubmit,
|
||||||
|
} = useForm<FormData>({
|
||||||
|
defaultValues: {
|
||||||
|
domain: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const domainInput = watch('domain');
|
||||||
|
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit(async (formData) => {
|
||||||
|
const createdDomain = await api.post('api/domains', { json: formData }).json<Domain>();
|
||||||
|
onCustomDomainAdded(createdDomain);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.addDomain}>
|
||||||
|
<TextInput
|
||||||
|
className={styles.textInput}
|
||||||
|
placeholder={t('domain.custom.custom_domain_placeholder')}
|
||||||
|
error={errors.domain?.message}
|
||||||
|
onKeyDown={onKeyDownHandler({ Enter: onSubmit })}
|
||||||
|
{...register('domain', {
|
||||||
|
required: true,
|
||||||
|
pattern: {
|
||||||
|
value: subdomainRegex,
|
||||||
|
message: t('domain.custom.invalid_domain_format'),
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={styles.addButton}
|
||||||
|
type="primary"
|
||||||
|
title="domain.custom.add_domain"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
disabled={domainInput.length === 0}
|
||||||
|
onClick={onSubmit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddDomainForm;
|
|
@ -0,0 +1,7 @@
|
||||||
|
@use '@/scss/underscore' as _;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
> div:not(:first-child) {
|
||||||
|
margin-top: _.unit(4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,45 @@
|
||||||
|
import { type Domain } from '@logto/schemas';
|
||||||
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import FormCard from '@/components/FormCard';
|
import FormCard from '@/components/FormCard';
|
||||||
import FormField from '@/components/FormField';
|
import FormField from '@/components/FormField';
|
||||||
|
import { type RequestError } from '@/hooks/use-api';
|
||||||
|
|
||||||
|
import AddDomainForm from './components/AddDomainForm';
|
||||||
import DefaultDomain from './components/DefaultDomain';
|
import DefaultDomain from './components/DefaultDomain';
|
||||||
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
function TenantDomainSettings() {
|
function TenantDomainSettings() {
|
||||||
|
// Todo: @xiaoyijun setup the auto refresh interval for the domains when implementing the active domain process.
|
||||||
|
const { data, error, mutate } = useSWR<Domain[], RequestError>('api/domains');
|
||||||
|
const isLoading = !data && !error;
|
||||||
|
/**
|
||||||
|
* Note: we can only create a custom domain, and we don't have a default id for it, so the first element of the array is the custom domain.
|
||||||
|
*/
|
||||||
|
const customDomain = conditional(!isLoading && data)?.[0];
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.container}>
|
||||||
{/* TODO: @xiaoyijun add the custom domain form card */}
|
<FormCard
|
||||||
|
title="domain.custom.custom_domain"
|
||||||
|
description="domain.custom.custom_domain_description"
|
||||||
|
>
|
||||||
|
<FormField title="domain.custom.custom_domain_field">
|
||||||
|
{!customDomain && (
|
||||||
|
<AddDomainForm
|
||||||
|
onCustomDomainAdded={(domain) => {
|
||||||
|
void mutate([domain]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* TODO @xiaoyijun add custom domain content if a custom domain is created */}
|
||||||
|
</FormField>
|
||||||
|
</FormCard>
|
||||||
<FormCard
|
<FormCard
|
||||||
title="domain.default.default_domain"
|
title="domain.default.default_domain"
|
||||||
description="domain.default.default_domain_description"
|
description="domain.default.default_domain_description"
|
||||||
|
|
Loading…
Reference in a new issue