0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

refactor(console,phrases): remove SSO guide on creation and switch tab order (#4972)

This commit is contained in:
Darcy Ye 2023-11-28 11:51:27 +08:00 committed by GitHub
parent 63b795ff50
commit bd2e5bf4b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 74 additions and 467 deletions

View file

@ -42,6 +42,6 @@ export enum TenantSettingsTabs {
}
export enum EnterpriseSsoDetailsTabs {
Settings = 'settings',
Connection = 'connection',
Experience = 'experience',
}

View file

@ -121,11 +121,10 @@ function ConsoleContent() {
<Route path="enterprise-sso">
<Route index element={<EnterpriseSsoConnectors />} />
<Route path="create" element={<EnterpriseSsoConnectors />} />
<Route path=":ssoConnectorId/guide" element={<EnterpriseSsoConnectors />} />
<Route path=":ssoConnectorId">
<Route
index
element={<Navigate replace to={EnterpriseSsoDetailsTabs.Settings} />}
element={<Navigate replace to={EnterpriseSsoDetailsTabs.Connection} />}
/>
<Route path=":tab" element={<EnterpriseSsoConnectorDetails />} />
</Route>

View file

@ -1,135 +0,0 @@
@use '@/scss/underscore' as _;
.samlMetadataForm {
> div:not(:first-child) {
margin-top: _.unit(6);
}
}
.container {
display: flex;
flex-direction: column;
background-color: var(--color-base);
height: 100vh;
overflow-x: auto;
.header {
display: flex;
align-items: center;
background: none;
height: 64px;
padding: 0 _.unit(21) 0 _.unit(2);
button {
margin-left: _.unit(4);
}
.separator {
@include _.vertical-bar;
height: 20px;
margin: 0 _.unit(5) 0 _.unit(4);
}
.closeIcon {
color: var(--color-text-secondary);
}
}
.content {
flex: 1;
display: flex;
overflow: auto;
justify-content: center;
min-width: min-content;
padding: _.unit(2) _.unit(6) _.unit(6);
> * {
flex: 1;
max-width: 800px;
min-width: 400px;
}
.readme {
display: flex;
flex-direction: column;
background-color: var(--color-layer-1);
border: 1.5px solid var(--color-focused-variant);
border-radius: 16px;
margin: 0 _.unit(6) 0 0;
overflow-y: auto;
position: sticky;
top: 0;
.readmeTitle {
font: var(--font-title-2);
padding: _.unit(5) _.unit(6) _.unit(4);
border-bottom: 1px solid var(--color-focused-variant);
}
.readmeContent {
flex: 1;
padding: 0 _.unit(6) _.unit(4);
h2 {
color: var(--color-text);
}
h3 {
color: var(--color-text-secondary);
}
}
}
.setup {
padding-bottom: _.unit(6);
.block {
background-color: var(--color-layer-1);
border-radius: 16px;
padding: 0 _.unit(6) _.unit(6);
margin-bottom: _.unit(4);
.blockTitle {
font: var(--font-title-2);
padding: _.unit(5) 0 _.unit(6);
display: flex;
flex-direction: column;
gap: _.unit(2);
.numberedTitle {
display: flex;
align-items: center;
flex-direction: row;
gap: _.unit(4);
}
.number {
width: 28px;
height: 28px;
border-radius: 50%;
background-color: var(--color-focused-variant);
color: var(--color-primary);
font: var(--font-title-2);
text-align: center;
line-height: 28px;
}
.blockSubtitle {
font: var(--font-body-2);
color: var(--color-text-secondary);
}
}
}
.footer {
padding-bottom: _.unit(10);
display: flex;
justify-content: right;
}
}
form + div {
margin-top: _.unit(6);
}
}
}

View file

@ -1,193 +0,0 @@
import { type AdminConsoleKey } from '@logto/phrases';
import { SsoProviderName, type SsoConnectorWithProviderConfig } from '@logto/schemas';
import cleanDeep from 'clean-deep';
import type { ReactNode } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import Modal from 'react-modal';
import Close from '@/assets/icons/close.svg';
import Markdown from '@/components/Markdown';
import Button from '@/ds-components/Button';
import CardTitle from '@/ds-components/CardTitle';
import DangerousRaw from '@/ds-components/DangerousRaw';
import DynamicT from '@/ds-components/DynamicT';
import IconButton from '@/ds-components/IconButton';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import useApi from '@/hooks/use-api';
import * as modalStyles from '@/scss/modal.module.scss';
import { trySubmitSafe } from '@/utils/form';
import { splitMarkdownByTitle } from '../../Connectors/utils.js';
import { type GuideFormType, type SsoConnectorWithProviderConfigWithGeneric } from '../types.js';
import BasicInfo from './BasicInfo';
import OidcMetadataForm from './OidcMetadataForm';
import SamlAttributeMapping from './SamlAttributeMapping';
import SamlMetadataForm from './SamlMetadataForm';
import * as styles from './index.module.scss';
type Props<T extends SsoProviderName> = {
isOpen: boolean;
connector: SsoConnectorWithProviderConfigWithGeneric<T>;
onClose: (ssoConnectorId?: string) => void;
};
type GuideCardProps = {
cardOrder: number;
children: ReactNode;
title: AdminConsoleKey;
description: AdminConsoleKey;
className?: string;
};
function GuideCard({ cardOrder, title, description, children, className }: GuideCardProps) {
return (
<div className={styles.block}>
<div className={styles.blockTitle}>
<div className={styles.numberedTitle}>
<div className={styles.number}>{cardOrder}</div>
<DynamicT forKey={title} />
</div>
<div className={styles.blockSubtitle}>
<DynamicT forKey={description} />
</div>
</div>
<div className={className}>{children}</div>
</div>
);
}
function Guide<T extends SsoProviderName>({ isOpen, connector, onClose }: Props<T>) {
const {
id: ssoConnectorId,
connectorName: ssoConnectorName,
providerName,
providerConfig,
} = connector;
const api = useApi();
const methods = useForm<GuideFormType<T>>();
const {
formState: { isSubmitting },
handleSubmit,
} = methods;
// TODO: @darcyYe Add SSO connector README.
const { title, content } = splitMarkdownByTitle(
'# SSO connector guide\n\nThis is a guide for Logto Enterprise SSO connector.'
);
const onSubmit = handleSubmit(
trySubmitSafe(async (formData) => {
if (isSubmitting) {
return;
}
await api
.patch(`api/sso-connectors/${ssoConnectorId}/config`, {
json: cleanDeep(formData),
// Do not check whether the config is complete on guide page.
searchParams: new URLSearchParams({ partialValidateConfig: 'true' }),
})
.json<SsoConnectorWithProviderConfig>();
onClose(ssoConnectorId);
})
);
return (
<Modal
shouldCloseOnEsc
isOpen={isOpen}
className={modalStyles.fullScreen}
onRequestClose={() => {
onClose();
}}
>
<div className={styles.container}>
<div className={styles.header}>
<IconButton
size="large"
onClick={() => {
onClose(ssoConnectorId);
}}
>
<Close className={styles.closeIcon} />
</IconButton>
<div className={styles.separator} />
<CardTitle
size="small"
title={<DangerousRaw>{ssoConnectorName}</DangerousRaw>}
subtitle="enterprise_sso.guide.subtitle"
/>
</div>
<div className={styles.content}>
<OverlayScrollbar className={styles.readme}>
<div className={styles.readmeTitle}>README: {title}</div>
<Markdown className={styles.readmeContent}>{content}</Markdown>
</OverlayScrollbar>
<div className={styles.setup}>
<FormProvider {...methods}>
<form autoComplete="off" onSubmit={onSubmit}>
<GuideCard
cardOrder={1}
title="enterprise_sso.basic_info.title"
description="enterprise_sso.basic_info.description"
>
<BasicInfo
ssoConnectorId={ssoConnectorId}
providerName={providerName}
providerConfig={providerConfig}
/>
</GuideCard>
{[
SsoProviderName.OIDC,
SsoProviderName.GOOGLE_WORKSPACE,
SsoProviderName.OKTA,
].includes(providerName) ? (
<GuideCard
cardOrder={2}
title="enterprise_sso.metadata.title"
description="enterprise_sso.metadata.description"
>
<OidcMetadataForm isGuidePage providerName={providerName} />
</GuideCard>
) : (
<>
<GuideCard
cardOrder={2}
title="enterprise_sso.attribute_mapping.title"
description="enterprise_sso.attribute_mapping.description"
>
<SamlAttributeMapping />
</GuideCard>
<GuideCard
cardOrder={3}
title="enterprise_sso.metadata.title"
description="enterprise_sso.metadata.description"
className={styles.samlMetadataForm}
>
<SamlMetadataForm isGuidePage />
</GuideCard>
</>
)}
<div className={styles.footer}>
<Button
title="enterprise_sso.guide.finish_button_text"
type="primary"
htmlType="submit"
isLoading={isSubmitting}
/>
</div>
</form>
</FormProvider>
</div>
</div>
</div>
</Modal>
);
}
export default Guide;

View file

@ -1,9 +1,8 @@
import { withAppInsights } from '@logto/app-insights/react';
import { type SsoConnectorWithProviderConfig, SsoProviderName } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useParams } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import useSWR from 'swr';
import Plus from '@/assets/icons/plus.svg';
@ -20,24 +19,19 @@ import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import { buildUrl } from '@/utils/url';
import Guide from './Guide';
import SsoConnectorLogo from './SsoConnectorLogo';
import SsoCreationModal from './SsoCreationModal';
import * as styles from './index.module.scss';
import { type SsoConnectorWithProviderConfigWithGeneric } from './types';
const pageSize = defaultPageSize;
const enterpriseSsoPathname = '/enterprise-sso';
const createEnterpriseSsoPathname = `${enterpriseSsoPathname}/create`;
const buildDetailsPathname = (id: string) => `${enterpriseSsoPathname}/${id}`;
const buildGuidePathname = (id: string) => `${buildDetailsPathname(id)}/guide`;
function EnterpriseSsoConnectors() {
const { pathname } = useLocation();
const { navigate } = useTenantPathname();
const { ssoConnectorId: id } = useParams();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [connectorForGuide, setConnectorForGuide] = useState<SsoConnectorWithProviderConfig>();
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
});
@ -54,15 +48,6 @@ function EnterpriseSsoConnectors() {
const isLoading = !data && !error;
const [ssoConnectors, totalCount] = data ?? [];
useEffect(() => {
const selectedSsoConnector = ssoConnectors?.find(
({ id: ssoConnectorId }) => ssoConnectorId === id
);
if (selectedSsoConnector) {
setConnectorForGuide(selectedSsoConnector);
}
}, [id, ssoConnectors]);
return (
<ListPage
title={{
@ -185,40 +170,18 @@ function EnterpriseSsoConnectors() {
onRetry: async () => mutate(undefined, true),
}}
widgets={
<>
<SsoCreationModal
isOpen={pathname.endsWith(createEnterpriseSsoPathname)}
onClose={async (ssoConnector) => {
if (ssoConnector) {
await mutate([[...(ssoConnectors ?? []), ssoConnector], totalCount ?? 0 + 1]);
navigate(buildGuidePathname(ssoConnector.id));
return;
}
<SsoCreationModal
isOpen={pathname.endsWith(createEnterpriseSsoPathname)}
onClose={(ssoConnector) => {
if (ssoConnector) {
void mutate();
navigate(buildDetailsPathname(ssoConnector.id));
return;
}
navigate(enterpriseSsoPathname);
}}
/>
{
/** Add this filter to make TypeScript happy, if `connectorForGuide` does not exist, the route will not come to this path. */
connectorForGuide && (
<Guide
isOpen={Boolean(pathname.endsWith(buildGuidePathname(connectorForGuide.id)))}
connector={
// eslint-disable-next-line no-restricted-syntax
connectorForGuide as SsoConnectorWithProviderConfigWithGeneric<SsoProviderName>
}
onClose={async (connectorId) => {
if (connectorId) {
navigate(buildDetailsPathname(connectorId), { replace: true });
return;
}
navigate(enterpriseSsoPathname);
}}
/>
)
}
</>
navigate(enterpriseSsoPathname);
}}
/>
}
/>
);

View file

@ -16,50 +16,39 @@ import ParsedConfigPreview from './ParsedConfigPreview';
import * as styles from './index.module.scss';
type Props = {
isGuidePage?: boolean;
providerConfig?: ParsedSsoIdentityProviderConfig<SsoProviderName.OIDC>;
config?: SsoConnectorConfig<SsoProviderName.OIDC>;
providerName: SsoProviderName;
};
// Do not show inline notification and parsed config preview if it is on guide page.
function OidcMetadataForm({ isGuidePage, providerConfig, config, providerName }: Props) {
function OidcMetadataForm({ providerConfig, config, providerName }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
register,
formState: { errors },
} = useFormContext<OidcGuideFormType>();
const isFieldCheckRequired = !isGuidePage;
const isConfigEmpty = !config || Object.keys(config).length === 0;
return (
<>
{isFieldCheckRequired && !providerConfig && isConfigEmpty && (
{!providerConfig && isConfigEmpty && (
<InlineNotification severity="alert">
{t('enterprise_sso_details.upload_oidc_idp_info_text')}
</InlineNotification>
)}
<FormField
isRequired={isFieldCheckRequired}
title="enterprise_sso.metadata.oidc.client_id_field_name"
>
<TextInput
{...register('clientId', { required: isFieldCheckRequired })}
error={Boolean(errors.clientId)}
/>
<FormField isRequired title="enterprise_sso.metadata.oidc.client_id_field_name">
<TextInput {...register('clientId', { required: true })} error={Boolean(errors.clientId)} />
</FormField>
<FormField
isRequired={isFieldCheckRequired}
title="enterprise_sso.metadata.oidc.client_secret_field_name"
>
<FormField isRequired title="enterprise_sso.metadata.oidc.client_secret_field_name">
<TextInput
{...register('clientSecret', { required: isFieldCheckRequired })}
{...register('clientSecret', { required: true })}
error={Boolean(errors.clientSecret)}
/>
</FormField>
<FormField
isRequired={isFieldCheckRequired && providerName !== SsoProviderName.GOOGLE_WORKSPACE}
isRequired={providerName !== SsoProviderName.GOOGLE_WORKSPACE}
title="enterprise_sso.metadata.oidc.issuer_field_name"
>
{providerName === SsoProviderName.GOOGLE_WORKSPACE ? (
@ -72,13 +61,12 @@ function OidcMetadataForm({ isGuidePage, providerConfig, config, providerName }:
) : (
<TextInput
{...register('issuer', {
required: isFieldCheckRequired,
required: true,
})}
error={Boolean(errors.issuer)}
/>
)}
{isFieldCheckRequired &&
providerConfig &&
{providerConfig &&
(config?.issuer ?? providerName === SsoProviderName.GOOGLE_WORKSPACE) && (
<ParsedConfigPreview
className={styles.oidcConfigPreview}

View file

@ -1,12 +1,15 @@
import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import CopyToClipboard from '@/ds-components/CopyToClipboard';
import DynamicT from '@/ds-components/DynamicT';
import TextInput from '@/ds-components/TextInput';
import { type SamlGuideFormType, type AttributeMapping, attributeKeys } from '../../types.js';
import {
type SamlGuideFormType,
type AttributeMapping,
attributeKeys,
} from '../../../EnterpriseSso/types.js';
import * as styles from './index.module.scss';
@ -17,7 +20,6 @@ type Props = {
const primaryKey = 'attributeMapping';
function SamlAttributeMapping({ isReadOnly }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { watch, register } = useFormContext<SamlGuideFormType>();
// eslint-disable-next-line react-hooks/exhaustive-deps
const attributeMapping = watch(primaryKey) ?? {};

View file

@ -22,18 +22,15 @@ import * as styles from './index.module.scss';
type SamlMetadataFormFieldsProps = Pick<SamlMetadataFormProps, 'config'> & {
identityProviderConfig?: ParsedSsoIdentityProviderConfig<SsoProviderName.SAML>['identityProvider'];
formFormat: FormFormat;
isFieldCheckRequired?: boolean;
};
type SamlMetadataFormProps = {
config?: SsoConnectorConfig<SsoProviderName.SAML>;
isGuidePage?: boolean;
providerConfig?: ParsedSsoIdentityProviderConfig<SsoProviderName.SAML>;
};
function SamlMetadataFormFields({
formFormat,
isFieldCheckRequired,
identityProviderConfig,
config,
}: SamlMetadataFormFieldsProps) {
@ -48,30 +45,21 @@ function SamlMetadataFormFields({
case FormFormat.Manual: {
return (
<>
<FormField
isRequired={isFieldCheckRequired}
title="enterprise_sso.metadata.saml.sign_in_endpoint_field_name"
>
<FormField isRequired title="enterprise_sso.metadata.saml.sign_in_endpoint_field_name">
<TextInput
{...register('signInEndpoint', { required: isFieldCheckRequired })}
{...register('signInEndpoint', { required: true })}
error={Boolean(errors.signInEndpoint)}
/>
</FormField>
<FormField
isRequired={isFieldCheckRequired}
title="enterprise_sso.metadata.saml.idp_entity_id_field_name"
>
<FormField isRequired title="enterprise_sso.metadata.saml.idp_entity_id_field_name">
<TextInput
{...register('entityId', { required: isFieldCheckRequired })}
{...register('entityId', { required: true })}
error={Boolean(errors.entityId)}
/>
</FormField>
<FormField
isRequired={isFieldCheckRequired}
title="enterprise_sso.metadata.saml.certificate_field_name"
>
<FormField isRequired title="enterprise_sso.metadata.saml.certificate_field_name">
<Textarea
{...register('x509Certificate', { required: isFieldCheckRequired })}
{...register('x509Certificate', { required: true })}
placeholder={t('enterprise_sso.metadata.saml.certificate_placeholder')}
error={Boolean(errors.x509Certificate)}
/>
@ -102,13 +90,10 @@ function SamlMetadataFormFields({
case FormFormat.Url: {
return (
<>
<FormField
isRequired={isFieldCheckRequired}
title="enterprise_sso.metadata.saml.metadata_url_field_name"
>
<FormField isRequired title="enterprise_sso.metadata.saml.metadata_url_field_name">
<TextInput
{...register('metadataUrl', {
required: isFieldCheckRequired,
required: true,
})}
error={Boolean(errors.metadataUrl)}
/>
@ -131,12 +116,11 @@ function SamlMetadataFormFields({
}
// Do not show inline notification and parsed config preview if it is on guide page.
function SamlMetadataForm({ config, isGuidePage, providerConfig }: SamlMetadataFormProps) {
function SamlMetadataForm({ config, providerConfig }: SamlMetadataFormProps) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { setValue } = useFormContext<SamlGuideFormType>();
const identityProviderConfig = providerConfig?.identityProvider;
const isFieldCheckRequired = !isGuidePage;
const isConfigEmpty = !config || Object.keys(config).length === 0;
// Default form format could change based on the value of ORIGINAL `config`.
@ -212,7 +196,7 @@ function SamlMetadataForm({ config, isGuidePage, providerConfig }: SamlMetadataF
return (
<>
{isFieldCheckRequired && !identityProviderConfig && isConfigEmpty && (
{!identityProviderConfig && isConfigEmpty && (
<InlineNotification severity="alert">
{t(
formFormat === FormFormat.Url
@ -225,7 +209,6 @@ function SamlMetadataForm({ config, isGuidePage, providerConfig }: SamlMetadataF
)}
<SamlMetadataFormFields
formFormat={formFormat}
isFieldCheckRequired={isFieldCheckRequired}
identityProviderConfig={identityProviderConfig}
config={config}
/>

View file

@ -9,7 +9,7 @@ import UploaderIcon from '@/assets/icons/upload.svg';
import Button from '@/ds-components/Button';
import IconButton from '@/ds-components/IconButton';
import { type SamlGuideFormType } from '../../types';
import { type SamlGuideFormType } from '../../../EnterpriseSso/types';
import { getXmlFileSize } from '../SamlMetadataForm/utils';
import * as styles from './index.module.scss';

View file

@ -10,10 +10,6 @@ import DetailsForm from '@/components/DetailsForm';
import FormCard from '@/components/FormCard';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
import useApi from '@/hooks/use-api';
import BasicInfo from '@/pages/EnterpriseSso/Guide/BasicInfo';
import OidcMetadataForm from '@/pages/EnterpriseSso/Guide/OidcMetadataForm';
import SamlAttributeMapping from '@/pages/EnterpriseSso/Guide/SamlAttributeMapping';
import SamlMetadataForm from '@/pages/EnterpriseSso/Guide/SamlMetadataForm';
import {
type SsoConnectorWithProviderConfigWithGeneric,
type ParsedSsoIdentityProviderConfig,
@ -22,6 +18,10 @@ import {
} from '@/pages/EnterpriseSso/types';
import { trySubmitSafe } from '@/utils/form';
import BasicInfo from './BasicInfo';
import OidcMetadataForm from './OidcMetadataForm';
import SamlAttributeMapping from './SamlAttributeMapping';
import SamlMetadataForm from './SamlMetadataForm';
import * as styles from './index.module.scss';
type Props<T extends SsoProviderName> = {
@ -91,7 +91,6 @@ function Connection<T extends SsoProviderName>({ isDeleted, data, onUpdated }: P
) ? (
// Can not infer the type by narrowing down the value of `providerName`, so we need to cast it.
<OidcMetadataForm
isGuidePage={false}
providerName={providerName}
// eslint-disable-next-line no-restricted-syntax
config={config as SsoConnectorConfig<SsoProviderName.OIDC>}
@ -105,7 +104,6 @@ function Connection<T extends SsoProviderName>({ isDeleted, data, onUpdated }: P
// Modify spacing between form fields and switch button of SAML metadata form.
<div className={styles.samlMetadataForm}>
<SamlMetadataForm
isGuidePage={false}
// eslint-disable-next-line no-restricted-syntax
config={config as SsoConnectorConfig<SsoProviderName.SAML>}
providerConfig={

View file

@ -1,11 +1,11 @@
import { withAppInsights } from '@logto/app-insights/react';
import { SsoProviderName } from '@logto/schemas';
import { pick } from '@silverhand/essentials';
import { useCallback, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useLocation, useParams } from 'react-router-dom';
import useSWR from 'swr';
import useSWR, { useSWRConfig } from 'swr';
import Delete from '@/assets/icons/delete.svg';
import File from '@/assets/icons/file.svg';
@ -39,6 +39,7 @@ const getSsoConnectorDetailsPathname = (ssoConnectorId: string, tab: EnterpriseS
function EnterpriseSsoConnectorDetails<T extends SsoProviderName>() {
const { pathname } = useLocation();
const { ssoConnectorId, tab } = useParams();
const { mutate: mutateGlobal } = useSWRConfig();
const [isDeleted, setIsDeleted] = useState(false);
const [isReadmeOpen, setIsReadmeOpen] = useState(false);
@ -78,7 +79,7 @@ function EnterpriseSsoConnectorDetails<T extends SsoProviderName>() {
setIsDeleteAlertOpen(false);
}, [pathname]);
const handleDelete = useCallback(async () => {
const handleDelete = async () => {
if (!ssoConnectorId || isDeleting) {
return;
}
@ -92,11 +93,12 @@ function EnterpriseSsoConnectorDetails<T extends SsoProviderName>() {
setIsDeleted(true);
toast.success(t('enterprise_sso_details.enterprise_sso_deleted'));
navigate(enterpriseSsoPathname);
await mutateGlobal('api/sso-connectors');
navigate(enterpriseSsoPathname, { replace: true });
} finally {
setIsDeleting(false);
}
}, [api, isDeleting, navigate, ssoConnectorId, t]);
};
if (!ssoConnectorId) {
return null;
@ -164,14 +166,6 @@ function EnterpriseSsoConnectorDetails<T extends SsoProviderName>() {
</Markdown>
</Drawer>
<TabNav>
<TabNavItem
href={getSsoConnectorDetailsPathname(
ssoConnectorId,
EnterpriseSsoDetailsTabs.Settings
)}
>
<DynamicT forKey="enterprise_sso_details.tab_settings" />
</TabNavItem>
<TabNavItem
href={getSsoConnectorDetailsPathname(
ssoConnectorId,
@ -180,8 +174,16 @@ function EnterpriseSsoConnectorDetails<T extends SsoProviderName>() {
>
<DynamicT forKey="enterprise_sso_details.tab_connection" />
</TabNavItem>
<TabNavItem
href={getSsoConnectorDetailsPathname(
ssoConnectorId,
EnterpriseSsoDetailsTabs.Experience
)}
>
<DynamicT forKey="enterprise_sso_details.tab_experience" />
</TabNavItem>
</TabNav>
{tab === EnterpriseSsoDetailsTabs.Settings && (
{tab === EnterpriseSsoDetailsTabs.Experience && (
<Settings
data={ssoConnector}
isDeleted={isDeleted}

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -3,7 +3,7 @@ const enterprise_sso_details = {
page_title: 'Enterprise SSO connector details',
readme_drawer_title: 'Enterprise SSO',
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
tab_settings: 'Settings',
tab_experience: 'Experience',
tab_connection: 'Connection',
general_settings_title: 'General Settings',
custom_branding_title: 'Custom Branding',

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */

View file

@ -8,7 +8,7 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
readme_drawer_subtitle: 'Set up enterprise SSO connectors to enable end users SSO',
/** UNTRANSLATED */
tab_settings: 'Settings',
tab_experience: 'Experience',
/** UNTRANSLATED */
tab_connection: 'Connection',
/** UNTRANSLATED */