mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
fix(console): should display custom domain as alternative endpoint in app details and guides (#4501)
fix(console): should display custom domain as alternative endpoint in app guide and details
This commit is contained in:
parent
e1fac554db
commit
926da108e8
6 changed files with 35 additions and 31 deletions
|
@ -12,7 +12,6 @@ import DynamicT from '@/ds-components/DynamicT';
|
||||||
import FormField from '@/ds-components/FormField';
|
import FormField from '@/ds-components/FormField';
|
||||||
import useCustomDomain from '@/hooks/use-custom-domain';
|
import useCustomDomain from '@/hooks/use-custom-domain';
|
||||||
import type { ConnectorFormType } from '@/types/connector';
|
import type { ConnectorFormType } from '@/types/connector';
|
||||||
import { applyDomain } from '@/utils/domain';
|
|
||||||
import { jsonValidator } from '@/utils/validator';
|
import { jsonValidator } from '@/utils/validator';
|
||||||
|
|
||||||
import ConfigFormFields from './ConfigFormFields';
|
import ConfigFormFields from './ConfigFormFields';
|
||||||
|
@ -32,7 +31,7 @@ function ConfigForm({ formItems, className, connectorId, connectorType }: Props)
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useFormContext<ConnectorFormType>();
|
} = useFormContext<ConnectorFormType>();
|
||||||
const { tenantEndpoint } = useContext(AppDataContext);
|
const { tenantEndpoint } = useContext(AppDataContext);
|
||||||
const { data: customDomain } = useCustomDomain();
|
const { data: customDomain, applyDomain: applyCustomDomain } = useCustomDomain();
|
||||||
const callbackUri = new URL(`/callback/${connectorId}`, tenantEndpoint).toString();
|
const callbackUri = new URL(`/callback/${connectorId}`, tenantEndpoint).toString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -45,11 +44,7 @@ function ConfigForm({ formItems, className, connectorId, connectorType }: Props)
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
className={styles.copyToClipboard}
|
className={styles.copyToClipboard}
|
||||||
variant="border"
|
variant="border"
|
||||||
value={
|
value={applyCustomDomain(callbackUri)}
|
||||||
customDomain?.status === DomainStatus.Active
|
|
||||||
? applyDomain(callbackUri, customDomain.domain)
|
|
||||||
: callbackUri
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{customDomain?.status === DomainStatus.Active && tenantEndpoint && (
|
{customDomain?.status === DomainStatus.Active && tenantEndpoint && (
|
||||||
<div className={styles.description}>
|
<div className={styles.description}>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { type Domain } from '@logto/schemas';
|
import { DomainStatus, type Domain } from '@logto/schemas';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import { customDomainSyncInterval } from '@/consts/custom-domain';
|
import { customDomainSyncInterval } from '@/consts/custom-domain';
|
||||||
import { isCloud } from '@/consts/env';
|
import { isCloud } from '@/consts/env';
|
||||||
|
import { isAbsoluteUrl } from '@/utils/url';
|
||||||
|
|
||||||
import { type RequestError } from './use-api';
|
import { type RequestError } from './use-api';
|
||||||
|
|
||||||
|
@ -22,18 +24,30 @@ const useCustomDomain = (autoSync = false) => {
|
||||||
/**
|
/**
|
||||||
* 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.
|
* 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];
|
const customDomain = useMemo(() => data?.[0], [data]);
|
||||||
|
|
||||||
|
const mutateDomain = useCallback(
|
||||||
|
(domain?: Domain) => {
|
||||||
|
void mutate(domain ? [domain] : undefined);
|
||||||
|
},
|
||||||
|
[mutate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const applyDomain = useCallback(
|
||||||
|
(url: string) => {
|
||||||
|
if (customDomain?.status !== DomainStatus.Active || !isAbsoluteUrl(url)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return url.replace(new URL(url).host, customDomain.domain);
|
||||||
|
},
|
||||||
|
[customDomain]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: customDomain,
|
data: customDomain,
|
||||||
isLoading,
|
isLoading,
|
||||||
mutate: (domain?: Domain) => {
|
mutate: mutateDomain,
|
||||||
if (domain) {
|
applyDomain,
|
||||||
void mutate([domain]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
void mutate();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ import Switch from '@/ds-components/Switch';
|
||||||
import TextInput from '@/ds-components/TextInput';
|
import TextInput from '@/ds-components/TextInput';
|
||||||
import TextLink from '@/ds-components/TextLink';
|
import TextLink from '@/ds-components/TextLink';
|
||||||
import useCustomDomain from '@/hooks/use-custom-domain';
|
import useCustomDomain from '@/hooks/use-custom-domain';
|
||||||
import { applyDomain } from '@/utils/domain';
|
|
||||||
|
|
||||||
import * as styles from '../index.module.scss';
|
import * as styles from '../index.module.scss';
|
||||||
|
|
||||||
|
@ -44,10 +43,7 @@ function AdvancedSettings({ app: { type }, oidcConfig }: Props) {
|
||||||
min: minTtl,
|
min: minTtl,
|
||||||
max: maxTtl,
|
max: maxTtl,
|
||||||
});
|
});
|
||||||
const { data: customDomain } = useCustomDomain();
|
const { data: customDomain, applyDomain: applyCustomDomain } = useCustomDomain();
|
||||||
|
|
||||||
const tryApplyCustomDomain = (url: string) =>
|
|
||||||
customDomain?.status === DomainStatus.Active ? applyDomain(url, customDomain.domain) : url;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormCard
|
<FormCard
|
||||||
|
@ -59,7 +55,7 @@ function AdvancedSettings({ app: { type }, oidcConfig }: Props) {
|
||||||
<FormField title="application_details.config_endpoint">
|
<FormField title="application_details.config_endpoint">
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
value={tryApplyCustomDomain(appendPath(tenantEndpoint, openIdProviderConfigPath).href)}
|
value={applyCustomDomain(appendPath(tenantEndpoint, openIdProviderConfigPath).href)}
|
||||||
variant="border"
|
variant="border"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
@ -84,21 +80,21 @@ function AdvancedSettings({ app: { type }, oidcConfig }: Props) {
|
||||||
>
|
>
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
value={tryApplyCustomDomain(oidcConfig.authorization_endpoint)}
|
value={applyCustomDomain(oidcConfig.authorization_endpoint)}
|
||||||
variant="border"
|
variant="border"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField title="application_details.token_endpoint">
|
<FormField title="application_details.token_endpoint">
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
value={tryApplyCustomDomain(oidcConfig.token_endpoint)}
|
value={applyCustomDomain(oidcConfig.token_endpoint)}
|
||||||
variant="border"
|
variant="border"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField title="application_details.user_info_endpoint">
|
<FormField title="application_details.user_info_endpoint">
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
value={tryApplyCustomDomain(oidcConfig.userinfo_endpoint)}
|
value={applyCustomDomain(oidcConfig.userinfo_endpoint)}
|
||||||
variant="border"
|
variant="border"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
|
@ -21,8 +21,6 @@ function AppGuide({ className, guideId, app, isCompact, onClose }: Props) {
|
||||||
const isCustomDomainActive = customDomain?.status === DomainStatus.Active;
|
const isCustomDomainActive = customDomain?.status === DomainStatus.Active;
|
||||||
const guide = guides.find(({ id }) => id === guideId);
|
const guide = guides.find(({ id }) => id === guideId);
|
||||||
|
|
||||||
const GuideComponent = guide?.Component;
|
|
||||||
|
|
||||||
const memorizedContext = useMemo(
|
const memorizedContext = useMemo(
|
||||||
() =>
|
() =>
|
||||||
conditional(
|
conditional(
|
||||||
|
@ -32,7 +30,7 @@ function AppGuide({ className, guideId, app, isCompact, onClose }: Props) {
|
||||||
Logo: guide.Logo,
|
Logo: guide.Logo,
|
||||||
app,
|
app,
|
||||||
endpoint: tenantEndpoint?.toString() ?? '',
|
endpoint: tenantEndpoint?.toString() ?? '',
|
||||||
alternativeEndpoint: conditional(isCustomDomainActive && tenantEndpoint?.toString()),
|
alternativeEndpoint: conditional(isCustomDomainActive && customDomain.domain),
|
||||||
redirectUris: app.oidcClientMetadata.redirectUris,
|
redirectUris: app.oidcClientMetadata.redirectUris,
|
||||||
postLogoutRedirectUris: app.oidcClientMetadata.postLogoutRedirectUris,
|
postLogoutRedirectUris: app.oidcClientMetadata.postLogoutRedirectUris,
|
||||||
isCompact: Boolean(isCompact),
|
isCompact: Boolean(isCompact),
|
||||||
|
@ -42,7 +40,7 @@ function AppGuide({ className, guideId, app, isCompact, onClose }: Props) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
) satisfies GuideContextType | undefined,
|
) satisfies GuideContextType | undefined,
|
||||||
[guide, app, tenantEndpoint, isCustomDomainActive, isCompact]
|
[guide, app, tenantEndpoint, isCustomDomainActive, customDomain?.domain, isCompact]
|
||||||
);
|
);
|
||||||
|
|
||||||
return memorizedContext ? (
|
return memorizedContext ? (
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
} from '@/ds-components/MultiTextInput/utils';
|
} from '@/ds-components/MultiTextInput/utils';
|
||||||
import TextInput from '@/ds-components/TextInput';
|
import TextInput from '@/ds-components/TextInput';
|
||||||
import TextLink from '@/ds-components/TextLink';
|
import TextLink from '@/ds-components/TextLink';
|
||||||
|
import useCustomDomain from '@/hooks/use-custom-domain';
|
||||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||||
|
|
||||||
import * as styles from '../index.module.scss';
|
import * as styles from '../index.module.scss';
|
||||||
|
@ -27,6 +28,7 @@ type Props = {
|
||||||
|
|
||||||
function Settings({ data }: Props) {
|
function Settings({ data }: Props) {
|
||||||
const { tenantEndpoint } = useContext(AppDataContext);
|
const { tenantEndpoint } = useContext(AppDataContext);
|
||||||
|
const { applyDomain: applyCustomDomain } = useCustomDomain();
|
||||||
|
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const { getDocumentationUrl } = useDocumentationUrl();
|
const { getDocumentationUrl } = useDocumentationUrl();
|
||||||
|
@ -62,7 +64,7 @@ function Settings({ data }: Props) {
|
||||||
{tenantEndpoint && (
|
{tenantEndpoint && (
|
||||||
<FormField title="application_details.logto_endpoint">
|
<FormField title="application_details.logto_endpoint">
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
value={tenantEndpoint.href}
|
value={applyCustomDomain(tenantEndpoint.href)}
|
||||||
variant="border"
|
variant="border"
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export const applyDomain = (url: string, domain: string) => url.replace(new URL(url).host, domain);
|
|
Loading…
Reference in a new issue