From 8f0b4a1edae96d82d8c9962f10736ca7d622aab1 Mon Sep 17 00:00:00 2001 From: Saleem Latif Date: Thu, 30 May 2024 17:38:10 +0500 Subject: [PATCH] chore: Marked strings for SSO tab in admin portal settings page. --- .../forms/ValidatedFormCheckbox.tsx | 4 +- .../SettingsSSOTab/NewExistingSSOConfigs.jsx | 31 +- .../SettingsSSOTab/NewSSOConfigAlerts.jsx | 133 ++++++-- .../SettingsSSOTab/NewSSOConfigCard.jsx | 80 ++++- .../SettingsSSOTab/NewSSOConfigForm.jsx | 8 +- .../settings/SettingsSSOTab/NewSSOStepper.jsx | 11 +- .../settings/SettingsSSOTab/NoSSOCard.jsx | 34 ++- .../SettingsSSOTab/SSOFormWorkflowConfig.tsx | 62 +++- .../settings/SettingsSSOTab/SsoErrorPage.jsx | 71 ++++- .../SettingsSSOTab/UnsavedSSOChangesModal.tsx | 86 ++++-- .../settings/SettingsSSOTab/index.jsx | 61 +++- .../steps/NewSSOConfigAuthorizeStep.tsx | 84 ++++- .../steps/NewSSOConfigConfigureStep.tsx | 288 +++++++++++++++--- .../steps/NewSSOConfigConfirmStep.tsx | 85 +++++- .../steps/NewSSOConfigConnectStep.tsx | 156 +++++++--- .../tests/NewSSOConfigCard.test.jsx | 21 +- .../tests/NewSSOConfigForm.test.jsx | 30 +- .../tests/SSOConfigPage.test.jsx | 11 +- .../tests/SettingsSSOTab.test.jsx | 8 +- src/components/settings/data/constants.js | 3 - 20 files changed, 1001 insertions(+), 266 deletions(-) diff --git a/src/components/forms/ValidatedFormCheckbox.tsx b/src/components/forms/ValidatedFormCheckbox.tsx index fbd97f31de..589bcf4c0f 100644 --- a/src/components/forms/ValidatedFormCheckbox.tsx +++ b/src/components/forms/ValidatedFormCheckbox.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import omit from 'lodash/omit'; import isString from 'lodash/isString'; @@ -9,7 +9,7 @@ import { useFormContext } from './FormContext'; type InheritedParagonCheckboxProps = { className?: string; - children: string; + children: ReactNode; }; export type ValidatedFormCheckboxProps = { diff --git a/src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx b/src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx index 79ea22446c..18e28b208d 100644 --- a/src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx +++ b/src/components/settings/SettingsSSOTab/NewExistingSSOConfigs.jsx @@ -10,6 +10,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import PropTypes from 'prop-types'; import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import LmsApiService from '../../../data/services/LmsApiService'; import NewSSOConfigAlerts from './NewSSOConfigAlerts'; import NewSSOConfigCard from './NewSSOConfigCard'; @@ -37,6 +38,7 @@ const NewExistingSSOConfigs = ({ const [updateError, setUpdateError] = useState(null); const queryClient = useQueryClient(); + const intl = useIntl(); const renderCards = (gridTitle, configList) => { if (configList.length > 0) { @@ -72,11 +74,20 @@ const NewExistingSSOConfigs = ({ icon={Info} onClose={() => (setUpdateError(null))} > - Something went wrong behind the scenes + + +

- We were unable to {updateError?.action} your SSO configuration due to an internal error. Please - {' '}try again in a couple of minutes. If the problem persists, contact enterprise customer - {' '}support. +

@@ -211,8 +222,16 @@ const NewExistingSSOConfigs = ({ setIsStepperOpen={setIsStepperOpen} /> )} - {renderCards('Active', activeConfigs)} - {renderCards('Inactive', inactiveConfigs)} + {renderCards(intl.formatMessage({ + id: 'adminPortal.settings.sso.active', + defaultMessage: 'Active', + description: 'Title for the active SSO configurations grid', + }), activeConfigs)} + {renderCards(intl.formatMessage({ + id: 'adminPortal.settings.sso.inactive', + defaultMessage: 'Inactive', + description: 'Title for the inactive SSO configurations grid', + }), inactiveConfigs)} )} {loading && ( diff --git a/src/components/settings/SettingsSSOTab/NewSSOConfigAlerts.jsx b/src/components/settings/SettingsSSOTab/NewSSOConfigAlerts.jsx index f4a92c9038..3b78857ca4 100644 --- a/src/components/settings/SettingsSSOTab/NewSSOConfigAlerts.jsx +++ b/src/components/settings/SettingsSSOTab/NewSSOConfigAlerts.jsx @@ -6,6 +6,7 @@ import { } from '@openedx/paragon/icons'; import { Alert, Button } from '@openedx/paragon'; import Cookies from 'universal-cookie'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; import { SSOConfigContext } from './SSOConfigContext'; export const SSO_SETUP_COMPLETION_COOKIE_NAME = 'dismissed-sso-completion-alert'; @@ -56,17 +57,32 @@ const NewSSOConfigAlerts = ({ icon={Info} className="sso-alert-width" actions={[ - , + , ]} dismissible show={showTimeoutAlert} onClose={() => { setShowTimeoutAlert(false); }} stacked > - SSO Configuration timed out + + +

- Your SSO configuration failed due to an internal error. Please try again by selecting “Configure” below and - {' '}verifying your integration details. Then reconfigure, reauthorize, and test your connection. +

); @@ -80,16 +96,31 @@ const NewSSOConfigAlerts = ({ className="sso-alert-width" show={showErrorAlert} actions={[ - , + , ]} dismissible onClose={() => { setShowErrorAlert(false); }} stacked > - SSO Configuration failed + + +

- Please verify integration details have been entered correctly. Select “Configure” below and verify your - {' '}integration details. Then reconfigure, reauthorize, and test your connection. +

); @@ -105,10 +136,30 @@ const NewSSOConfigAlerts = ({ dismissible onClose={closeAlerts} > - Your SSO Integration is in progress + + +

- edX is configuring your SSO. This step takes approximately{' '} - {notConfigured.length > 0 ? `five minutes. You will receive an email at ${contactEmail} when the configuration is complete` : 'fifteen seconds'}. + {notConfigured.length > 0 ? ( + + ) : ( + + )}

)} @@ -120,22 +171,50 @@ const NewSSOConfigAlerts = ({ onClose={closeAlerts} dismissible > - You need to test your SSO connection + + +

- Your SSO configuration has been completed, - and you should have received an email with the following instructions:
+ +
+
+
- 1: Copy the URL for your Learner Portal dashboard below:

  http://courses.edx.org/dashboard?tpa_hint={enterpriseSlug}

- 2: Launch a new incognito or private window and paste the copied URL into the URL bar to load your - Learner Portal dashboard.
+ +
+
+ +

- 3: When prompted, enter login credentials supported by your IDP to test your connection to edX.
+
- Return to this window after completing the testing instructions. - This window will automatically update when a successful test is detected.

)} @@ -150,9 +229,19 @@ const NewSSOConfigAlerts = ({ onClose={dismissSetupCompleteAlert} dismissible > - Your SSO integration is live! + + +

- Great news! Your test was successful and your new SSO integration is live and ready to use. +

)} diff --git a/src/components/settings/SettingsSSOTab/NewSSOConfigCard.jsx b/src/components/settings/SettingsSSOTab/NewSSOConfigCard.jsx index e423a17759..d93075ff28 100644 --- a/src/components/settings/SettingsSSOTab/NewSSOConfigCard.jsx +++ b/src/components/settings/SettingsSSOTab/NewSSOConfigCard.jsx @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import { Key, KeyOff, MoreVert, } from '@openedx/paragon/icons'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { SSOConfigContext } from './SSOConfigContext'; import LmsApiService from '../../../data/services/LmsApiService'; @@ -29,6 +30,7 @@ const NewSSOConfigCard = ({ const TIMED_OUT = SUBMITTED && !CONFIGURED && !config.is_pending_configuration; const { setProviderConfig } = useContext(SSOConfigContext); + const intl = useIntl(); const onConfigureClick = (selectedConfig) => { setProviderConfig(selectedConfig); @@ -84,7 +86,15 @@ const NewSSOConfigCard = ({ The integration is verified and working} + overlay={( + + + + )} > This integration has not been validated. Please follow the testing instructions to validate your integration. - } + overlay={( + + + + )} > {renderKeyOffIcon('existing-sso-config-card-off-not-validated-icon')} @@ -119,7 +135,11 @@ const NewSSOConfigCard = ({ variant="light" data-testid="existing-sso-config-card-badge-in-progress" > - In-progress + )} {VALIDATED && CONFIGURED && !ENABLED && ( @@ -128,7 +148,11 @@ const NewSSOConfigCard = ({ variant="light" data-testid="existing-sso-config-card-badge-disabled" > - Disabled + )} @@ -143,7 +167,11 @@ const NewSSOConfigCard = ({ variant="outline-primary" data-testid="existing-sso-config-card-configure-button" > - Configure + )} {VALIDATED && CONFIGURED && !ENABLED && ( @@ -153,7 +181,11 @@ const NewSSOConfigCard = ({ variant="outline-primary" data-testid="existing-sso-config-card-enable-button" > - Enable + )} @@ -175,7 +207,12 @@ const NewSSOConfigCard = ({ )} subtitle={(
- Last modified {convertToReadableDate(config.modified)} +
)} actions={((!SUBMITTED || CONFIGURED) || (ERRORED || TIMED_OUT)) && ( @@ -187,7 +224,12 @@ const NewSSOConfigCard = ({ src={MoreVert} iconAs={Icon} variant="primary" - alt="Actions dropdown" + alt={intl.formatMessage({ + id: 'adminPortal.settings.sso.actionsDropdown', + defaultMessage: 'Actions dropdown', + description: 'Alt text for actions dropdown', + + })} /> {VALIDATED && ( @@ -195,7 +237,11 @@ const NewSSOConfigCard = ({ data-testid="existing-sso-config-configure-dropdown" onClick={() => onConfigureClick(config)} > - Configure + )} {((!ENABLED || !VALIDATED) || (ERRORED || TIMED_OUT)) && ( @@ -203,7 +249,11 @@ const NewSSOConfigCard = ({ data-testid="existing-sso-config-delete-dropdown" onClick={() => onDeleteClick(config)} > - Delete + )} {ENABLED && VALIDATED && ( @@ -211,7 +261,11 @@ const NewSSOConfigCard = ({ data-testid="existing-sso-config-disable-dropdown" onClick={() => onDisableClick(config)} > - Disable + )} diff --git a/src/components/settings/SettingsSSOTab/NewSSOConfigForm.jsx b/src/components/settings/SettingsSSOTab/NewSSOConfigForm.jsx index 86a6c4193f..3b91160e24 100644 --- a/src/components/settings/SettingsSSOTab/NewSSOConfigForm.jsx +++ b/src/components/settings/SettingsSSOTab/NewSSOConfigForm.jsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { Alert, Hyperlink } from '@openedx/paragon'; import { WarningFilled } from '@openedx/paragon/icons'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; import { SSOConfigContext } from './SSOConfigContext'; import SSOStepper from './SSOStepper'; import { HELP_CENTER_SAML_LINK } from '../data/constants'; @@ -16,8 +17,11 @@ const NewSSOConfigForm = ({ setIsStepperOpen, isStepperOpen }) => {
{!AUTH0_SELF_SERVICE_INTEGRATION && ( - Connect to a SAML identity provider for single sign-on - to allow quick access to your organization's learning catalog. + )} {AUTH0_SELF_SERVICE_INTEGRATION ? ( diff --git a/src/components/settings/SettingsSSOTab/NewSSOStepper.jsx b/src/components/settings/SettingsSSOTab/NewSSOStepper.jsx index dabed9ccff..88ee5bad51 100644 --- a/src/components/settings/SettingsSSOTab/NewSSOStepper.jsx +++ b/src/components/settings/SettingsSSOTab/NewSSOStepper.jsx @@ -3,6 +3,7 @@ import React, { } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { useIntl } from '@edx/frontend-platform/i18n'; import FormContextWrapper from '../../forms/FormContextWrapper'; import { SSOConfigContext } from './SSOConfigContext'; import SSOFormWorkflowConfig from './SSOFormWorkflowConfig'; @@ -17,6 +18,8 @@ const NewSSOStepper = ({ enterpriseId, isStepperOpen, setIsStepperOpen }) => { } = useContext(SSOConfigContext); const providerConfigCamelCase = camelCaseDict(providerConfig || {}); const [configureError, setConfigureError] = useState(null); + const intl = useIntl(); + const handleCloseWorkflow = () => { setProviderConfig?.(null); setIsStepperOpen(false); @@ -33,8 +36,12 @@ const NewSSOStepper = ({ enterpriseId, isStepperOpen, setIsStepperOpen }) => { {isStepperOpen && !configureError && (
{ + const intl = useIntl(); const onClick = () => { setShowNoSSOCard(false); setIsStepperOpen(true); @@ -19,18 +21,36 @@ const NoSSOCard = ({ -

You don't have any SSOs integrated yet.

-

SSO enables learners who are signed in to their enterprise LMS - or other system to easily register and enroll in courses on edX without needing to - sign in again. edX for Business uses SAML 2.0 to implement SSO between an enterprise - system and edX.org. +

+ +

+

+

- + ); diff --git a/src/components/settings/SettingsSSOTab/SSOFormWorkflowConfig.tsx b/src/components/settings/SettingsSSOTab/SSOFormWorkflowConfig.tsx index 6f2bde79b1..63c907edc1 100644 --- a/src/components/settings/SettingsSSOTab/SSOFormWorkflowConfig.tsx +++ b/src/components/settings/SettingsSSOTab/SSOFormWorkflowConfig.tsx @@ -2,9 +2,9 @@ import omit from 'lodash/omit'; import { AxiosError } from 'axios'; import type { FormWorkflowHandlerArgs, FormWorkflowStep } from '../../forms/FormWorkflow'; -import SSOConfigConnectStep, { validations as SSOConfigConnectStepValidations } from './steps/NewSSOConfigConnectStep'; -import SSOConfigConfigureStep, { validations as SSOConfigConfigureStepValidations } from './steps/NewSSOConfigConfigureStep'; -import SSOConfigAuthorizeStep, { validations as SSOConfigAuthorizeStepValidations } from './steps/NewSSOConfigAuthorizeStep'; +import SSOConfigConnectStep, { getValidations as getSSOConfigConnectStepValidations } from './steps/NewSSOConfigConnectStep'; +import SSOConfigConfigureStep, { getValidations as getSSOConfigConfigureStepValidations } from './steps/NewSSOConfigConfigureStep'; +import SSOConfigAuthorizeStep, { getValidations as getSSOConfigAuthorizeStepValidations } from './steps/NewSSOConfigAuthorizeStep'; import SSOConfigConfirmStep from './steps/NewSSOConfigConfirmStep'; import LmsApiService from '../../../data/services/LmsApiService'; import handleErrors from '../utils'; @@ -84,7 +84,7 @@ type SSOConfigFormControlVariables = { type SSOConfigFormContextData = SSOConfigCamelCase & SSOConfigFormControlVariables; -export const SSOFormWorkflowConfig = ({ enterpriseId, setConfigureError }) => { +export const SSOFormWorkflowConfig = ({ enterpriseId, setConfigureError, intl }) => { const advanceConnectStep = async ({ formFields, errHandler, @@ -163,10 +163,18 @@ export const SSOFormWorkflowConfig = ({ enterpriseId, setConfigureError }) => { { index: 0, formComponent: SSOConfigConnectStep, - validations: SSOConfigConnectStepValidations, - stepName: 'Connect', + validations: getSSOConfigConnectStepValidations(intl), + stepName: intl.formatMessage({ + id: 'adminPortal.settings.sso.connect', + defaultMessage: 'Connect', + description: 'Step name for connecting to an identity provider', + }), nextButtonConfig: () => ({ - buttonText: 'Next', + buttonText: intl.formatMessage({ + id: 'adminPortal.settings.sso.next', + defaultMessage: 'Next', + description: 'Button text for moving to the next step', + }), opensNewWindow: false, onClick: advanceConnectStep, preventDefaultErrorModal: true, @@ -174,10 +182,18 @@ export const SSOFormWorkflowConfig = ({ enterpriseId, setConfigureError }) => { }, { index: 1, formComponent: SSOConfigConfigureStep, - validations: SSOConfigConfigureStepValidations, - stepName: 'Configure', + validations: getSSOConfigConfigureStepValidations(intl), + stepName: intl.formatMessage({ + id: 'adminPortal.settings.sso.configure.stepName', + defaultMessage: 'Configure', + description: 'Step name for configuring an identity provider', + }), nextButtonConfig: () => ({ - buttonText: 'Configure', + buttonText: intl.formatMessage({ + id: 'adminPortal.settings.sso.configure.buttonText', + defaultMessage: 'Configure', + description: 'Button text for configuring an identity provider', + }), opensNewWindow: false, onClick: saveChanges, preventDefaultErrorModal: true, @@ -187,10 +203,18 @@ export const SSOFormWorkflowConfig = ({ enterpriseId, setConfigureError }) => { }, { index: 2, formComponent: SSOConfigAuthorizeStep, - validations: SSOConfigAuthorizeStepValidations, - stepName: 'Authorize', + validations: getSSOConfigAuthorizeStepValidations(intl), + stepName: intl.formatMessage({ + id: 'adminPortal.settings.sso.authorize', + defaultMessage: 'Authorize', + description: 'Step name for authorizing an identity provider', + }), nextButtonConfig: () => ({ - buttonText: 'Next', + buttonText: intl.formatMessage({ + id: 'adminPortal.settings.sso.next', + defaultMessage: 'Next', + description: 'Button text for moving to the next step', + }), opensNewWindow: false, onClick: saveChanges, preventDefaultErrorModal: false, @@ -201,9 +225,17 @@ export const SSOFormWorkflowConfig = ({ enterpriseId, setConfigureError }) => { index: 3, formComponent: SSOConfigConfirmStep, validations: [], - stepName: 'Confirm and Test', + stepName: intl.formatMessage({ + id: 'adminPortal.settings.sso.confirmAndTest', + defaultMessage: 'Confirm and Test', + description: 'Step name for confirming and testing an identity provider', + }), nextButtonConfig: () => ({ - buttonText: 'Finish', + buttonText: intl.formatMessage({ + id: 'adminPortal.settings.sso.finish', + defaultMessage: 'Finish', + description: 'Button text for finishing the configuration', + }), opensNewWindow: false, onClick: () => {}, preventDefaultErrorModal: false, diff --git a/src/components/settings/SettingsSSOTab/SsoErrorPage.jsx b/src/components/settings/SettingsSSOTab/SsoErrorPage.jsx index bd80ab2c81..b079fad9a1 100644 --- a/src/components/settings/SettingsSSOTab/SsoErrorPage.jsx +++ b/src/components/settings/SettingsSSOTab/SsoErrorPage.jsx @@ -7,15 +7,17 @@ import { Image, Stack, } from '@openedx/paragon'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { configuration } from '../../../config'; -import { ssoStepperNetworkErrorText, ssoLPNetworkErrorText, HELP_CENTER_SAML_LINK } from '../data/constants'; +import { HELP_CENTER_SAML_LINK } from '../data/constants'; import cardImage from '../../../data/images/SomethingWentWrong.svg'; const SsoErrorPage = ({ isOpen, stepperError, }) => { - const stepperText = stepperError ? ssoStepperNetworkErrorText : ssoLPNetworkErrorText; + const intl = useIntl(); + return ( )} @@ -36,22 +42,65 @@ const SsoErrorPage = ({ src={cardImage} className="ml-auto mr-auto mt-4 d-flex" fluid - alt="Something went wrong" + alt={intl.formatMessage({ + id: 'sso.error.image.alt', + defaultMessage: 'Something went wrong', + description: 'Alt text for the image displayed when an error occurs', + })} />

- We're sorry.{' '} -  Something went wrong. + + + +   +

- {stepperText}{' '} - Please close this window and try again in a couple of minutes. If the problem persists, contact enterprise - customer support. + { + stepperError ? ( + + ) : ( + + ) + } + &npsp; +

- Helpful link:{' '} - Enterprise Help Center: Single Sign-On +   + + +

diff --git a/src/components/settings/SettingsSSOTab/UnsavedSSOChangesModal.tsx b/src/components/settings/SettingsSSOTab/UnsavedSSOChangesModal.tsx index d0a3ae41db..d3da7110a2 100644 --- a/src/components/settings/SettingsSSOTab/UnsavedSSOChangesModal.tsx +++ b/src/components/settings/SettingsSSOTab/UnsavedSSOChangesModal.tsx @@ -1,36 +1,70 @@ import React from 'react'; import { ModalDialog, ActionRow, Button } from '@openedx/paragon'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { UnsavedChangesModalProps } from '../../forms/FormWorkflow'; const UnsavedSSOChangesModal = ({ isOpen, close, exitWithoutSaving, -}: UnsavedChangesModalProps) => ( - - - Exit configuration? - - -

Your in-progress data will not be saved.

-

Your SSO connection will not be active until you restart and complete the SSO configuration process.

-
- - - - - - -
-); +}: UnsavedChangesModalProps) => { + const intl = useIntl(); + + return ( + + + + + + + +

+ +

+

+ +

+
+ + + + + + +
+ ); +}; export default UnsavedSSOChangesModal; diff --git a/src/components/settings/SettingsSSOTab/index.jsx b/src/components/settings/SettingsSSOTab/index.jsx index 54b27d8320..4f5b07f4c2 100644 --- a/src/components/settings/SettingsSSOTab/index.jsx +++ b/src/components/settings/SettingsSSOTab/index.jsx @@ -4,6 +4,7 @@ import { Alert, ActionRow, Button, Hyperlink, ModalDialog, Toast, Skeleton, Spinner, useToggle, } from '@openedx/paragon'; import { Add, WarningFilled } from '@openedx/paragon/icons'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { HELP_CENTER_SAML_LINK } from '../data/constants'; import { useExistingSSOConfigs, useExistingProviderData } from './hooks'; import NoSSOCard from './NoSSOCard'; @@ -31,6 +32,7 @@ const SettingsSSOTab = ({ enterpriseId, setHasSSOConfig }) => { const [pollingNetworkError, setPollingNetworkError] = useState(false); const [isStepperOpen, setIsStepperOpen] = useState(true); const [isDeletingOldConfigs, setIsDeletingOldConfigs] = useState(false); + const intl = useIntl(); const newConfigurationButtonOnClick = async () => { setIsDeletingOldConfigs(true); @@ -65,7 +67,11 @@ const SettingsSSOTab = ({ enterpriseId, setHasSSOConfig }) => { return (
{ > - Create new SSO configuration? +

- Only one SSO integration is supported at a time.
+
- To continue updating and editing your SSO integration, select "Cancel" and then - "Configure" on the integration card. Creating a new SSO configuration will overwrite and delete - your existing SSO configuration. +
+

- Cancel +
-

Single Sign-On (SSO) Integrations

+

+ +

{newButtonVisible && ( )} { className="btn btn-outline-primary my-2" target="_blank" > - Help Center: Single Sign-On +
diff --git a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigAuthorizeStep.tsx b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigAuthorizeStep.tsx index 2469fc6c27..2ffcba214e 100644 --- a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigAuthorizeStep.tsx +++ b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigAuthorizeStep.tsx @@ -5,16 +5,25 @@ import { } from '@openedx/paragon'; import { Info, Download } from '@openedx/paragon/icons'; import { getConfig } from '@edx/frontend-platform/config'; +import { defineMessages, FormattedMessage } from '@edx/frontend-platform/i18n'; import { createSAMLURLs } from '../utils'; import { SSOConfigContext } from '../SSOConfigContext'; import { FormFieldValidation, useFormContext } from '../../../forms/FormContext'; import ValidatedFormCheckbox from '../../../forms/ValidatedFormCheckbox'; -export const validations: FormFieldValidation[] = [ +const messages = defineMessages({ + markedAuthorized: { + id: 'adminPortal.settings.ssoConfigAuthorizeStep.markedAuthorized', + defaultMessage: 'Please verify authorization of edX as a Service Provider.', + description: 'Helper message displayed against the option to verify authorization of edX as a Service Provider.', + }, +}); + +export const getValidations = (intl) : FormFieldValidation[] => [ { formFieldId: 'markedAuthorized', validator: (fields) => { - const ret = !fields.markedAuthorized && 'Please verify authorization of edX as a Service Provider.'; + const ret = !fields.markedAuthorized && intl.formatMessage(messages.markedAuthorized); return ret; }, }, @@ -52,34 +61,77 @@ const SSOConfigAuthorizeStep = () => { return ( <> -

Authorize edX as a Service Provider

+

+ +

-

Action required in a new window

- Return to this window after completing the following steps - in a new window to finish configuring your integration. +

+ +

+

- 1. Download the edX Service Provider metadata as an XML file: +

- 2. - - Launch a new window - - {' '} and upload the XML file to the list of - authorized SAML Service Providers on your Identity Provider's portal or website. + + + + ), + }} + />


-

Return to this window and check the box once complete

+

+ +

- I have authorized edX as a Service Provider + ); diff --git a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfigureStep.tsx b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfigureStep.tsx index 35c7a96aac..4e1806a9ac 100644 --- a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfigureStep.tsx +++ b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfigureStep.tsx @@ -4,6 +4,7 @@ import { } from '@openedx/paragon'; import { Info } from '@openedx/paragon/icons'; +import { defineMessages, FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import ValidatedFormControl from '../../../forms/ValidatedFormControl'; import { FormContext, FormFieldValidation, useFormContext } from '../../../forms/FormContext'; import { urlValidation } from '../../../../utils'; @@ -12,28 +13,62 @@ import { FORM_ERROR_MESSAGE, setStepAction } from '../../../forms/data/actions'; import { INVALID_IDP_METADATA_ERROR, RECORD_UNDER_CONFIGURATIONS_ERROR } from '../../data/constants'; import { SSOConfigCamelCase } from '../SSOFormWorkflowConfig'; +const messages = defineMessages({ + sapsfOauthRootUrl: { + id: 'adminPortal.settings.ssoConfigConfigureStep.sapsfOauthRootUrl', + defaultMessage: 'Please enter an OAuth Root URL.', + description: 'Helper message displayed against the option to enter an OAuth Root URL.', + }, + odataApiRootUrl: { + id: 'adminPortal.settings.ssoConfigConfigureStep.odataApiRootUrl', + defaultMessage: 'Please enter an API Root URL.', + description: 'Helper message displayed against the option to enter an API Root URL.', + }, + sapsfPrivateKey: { + id: 'adminPortal.settings.ssoConfigConfigureStep.sapsfPrivateKey', + defaultMessage: 'Please enter a Private Key.', + description: 'Helper message displayed against the option to enter a Private Key.', + }, + odataCompanyId: { + id: 'adminPortal.settings.ssoConfigConfigureStep.odataCompanyId', + defaultMessage: 'Please enter a Company ID.', + description: 'Helper message displayed against the option to enter a Company ID.', + }, + oauthUserId: { + id: 'adminPortal.settings.ssoConfigConfigureStep.oauthUserId', + defaultMessage: 'Please enter an OAuth User ID.', + description: 'Helper message displayed against the option to enter an OAuth User ID.', + }, +}); + const isSAPConfig = (fields) => fields.identityProvider === 'sap_success_factors'; -export const validations: FormFieldValidation[] = [ +export const getValidations = (intl) : FormFieldValidation[] => [ { formFieldId: 'sapsfOauthRootUrl', - validator: (fields) => isSAPConfig(fields) && (!fields.sapsfOauthRootUrl || !urlValidation(fields.sapsfOauthRootUrl)) && 'Please enter an OAuth Root URL.', + validator: (fields) => isSAPConfig(fields) && ( + !fields.sapsfOauthRootUrl || !urlValidation(fields.sapsfOauthRootUrl) + ) && intl.formatMessage(messages.sapsfOauthRootUrl), }, { formFieldId: 'odataApiRootUrl', - validator: (fields) => isSAPConfig(fields) && (!fields.odataApiRootUrl || !urlValidation(fields.odataApiRootUrl)) && 'Please enter an API Root URL.', + validator: (fields) => isSAPConfig(fields) && ( + !fields.odataApiRootUrl || !urlValidation(fields.odataApiRootUrl) + ) && intl.formatMessage(messages.odataApiRootUrl), }, { formFieldId: 'sapsfPrivateKey', - validator: (fields) => isSAPConfig(fields) && !fields.sapsfPrivateKey && 'Please enter a Private Key.', + validator: (fields) => ( + isSAPConfig(fields) && !fields.sapsfPrivateKey + ) && intl.formatMessage(messages.sapsfPrivateKey), }, { formFieldId: 'odataCompanyId', - validator: (fields) => isSAPConfig(fields) && !fields.odataCompanyId && 'Please enter a Company ID.', + validator: (fields) => isSAPConfig(fields) && !fields.odataCompanyId && intl.formatMessage(messages.odataCompanyId), }, { formFieldId: 'oauthUserId', - validator: (fields) => isSAPConfig(fields) && !fields.oauthUserId && 'Please enter an OAuth User ID.', + validator: (fields) => isSAPConfig(fields) && !fields.oauthUserId && intl.formatMessage(messages.oauthUserId), }, ]; @@ -44,71 +79,139 @@ const SSOConfigConfigureStep = () => { allSteps, stateMap, }: FormContext = useFormContext(); + const intl = useIntl(); const usingSAP = formFields?.identityProvider === 'sap_success_factors'; const renderBaseFields = () => ( <> -

Enter user attributes

+

+ +

- Please enter the SAML user attributes from your Identity Provider. - All attributes are space and case sensitive. +

@@ -116,30 +219,60 @@ const SSOConfigConfigureStep = () => { const renderSAPFields = () => ( <> -

Enable learner account auto-registration

+

+ +

@@ -148,16 +281,32 @@ const SSOConfigConfigureStep = () => { type="text" as="textarea" rows={4} - floatingLabel="Private Key" - fieldInstructions="The Private Key value found in the PEM file generated from the OAuth2 Client Application Profile." + floatingLabel={intl.formatMessage({ + id: 'adminPortal.settings.ssoConfigConfigureStep.sapsfPrivateKey.label', + defaultMessage: 'Private Key', + description: 'Helper message displayed against the option to enter a Private Key.', + })} + fieldInstructions={intl.formatMessage({ + id: 'adminPortal.settings.ssoConfigConfigureStep.sapsfPrivateKey.instructions', + defaultMessage: 'The Private Key value found in the PEM file generated from the OAuth2 Client Application Profile.', + description: 'Instructions for the Private Key input field.', + })} /> @@ -171,11 +320,16 @@ const SSOConfigConfigureStep = () => { }; return ( -
-

Enter integration details

+

+ +

{stateMap?.[FORM_ERROR_MESSAGE] === RECORD_UNDER_CONFIGURATIONS_ERROR && ( { className="ml-3" onClick={returnToConnectStep} > - Record under configuration + , ]} className="mt-3 mb-3" @@ -192,10 +350,19 @@ const SSOConfigConfigureStep = () => { stacked icon={Info} > - Configuration Error + + +

- Your record was recently submitted for configuration and must completed before you can resubmit. Please - check back in a few minutes. If the problem persists, contact enterprise customer support. +

)} @@ -207,7 +374,11 @@ const SSOConfigConfigureStep = () => { className="ml-3" onClick={returnToConnectStep} > - Return to Connect step + , ]} className="mt-3 mb-3" @@ -215,22 +386,45 @@ const SSOConfigConfigureStep = () => { stacked icon={Info} > - Metadata Error + + +

- Please return to the “Connect” step and verify that your metadata URL or metadata file is correct. After - verifying, please try again. If the problem persists, contact enterprise customer support. +

)} -

Set display name

+

+ +

diff --git a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfirmStep.tsx b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfirmStep.tsx index 8499aca344..2db5dc6fc9 100644 --- a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfirmStep.tsx +++ b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConfirmStep.tsx @@ -3,6 +3,7 @@ import { Alert, Hyperlink, OverlayTrigger, Popover, } from '@openedx/paragon'; import { Info } from '@openedx/paragon/icons'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; const IncognitoPopover = () => ( ( overlay={( - Steps to open a new window in incognito mode (also known as private mode) - may vary based on the browser you are using. - Review your browser's help documentation as needed. + )} > - incognito window + + + ); const SSOConfigConfirmStep = () => ( <> -

Wait for SSO configuration confirmation

+

+ +

-

Action required from email

- Great news! You have completed the configuration steps, edX is actively configuring your SSO connection. - You will receive an email within about five minutes when the configuration is complete. - The email will include instructions for testing. +

+ +

+

-

What to expect:

+

+ +

    -
  • SSO configuration confirmation email.
  • +
  • + +
    • -
    • Testing instructions involve copying and pasting a custom URL into an
    • -
    • A link back to the SSO Settings page
    • +
    • + +
    • +
    • + +

- Select the "Finish" button below or close this form via the - {' '}"X" in the upper right corner while you wait for your - configuration email. Your SSO testing status will display on the following SSO settings screen. + "Finish", + xButtonText: "X", + }} + />

); diff --git a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConnectStep.tsx b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConnectStep.tsx index 2cbb193d2b..97aa7b10c1 100644 --- a/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConnectStep.tsx +++ b/src/components/settings/SettingsSSOTab/steps/NewSSOConfigConnectStep.tsx @@ -1,8 +1,9 @@ import React, { useState } from 'react'; import { - Container, Dropzone, Form, Stack, + Container, Dropzone, Form, } from '@openedx/paragon'; +import { defineMessages, FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import ValidatedFormRadio from '../../../forms/ValidatedFormRadio'; import ValidatedFormControl from '../../../forms/ValidatedFormControl'; import { FormContext, FormFieldValidation, useFormContext } from '../../../forms/FormContext'; @@ -14,50 +15,90 @@ export const IDP_XML_SELECTION = 'idp_metadata_xml'; const urlEntrySelected = (formFields) => formFields?.idpConnectOption === IDP_URL_SELECTION; const xmlEntrySelected = (formFields) => formFields?.idpConnectOption === IDP_XML_SELECTION; -export const validations: FormFieldValidation[] = [ +const messages = defineMessages({ + identityProvider: { + id: 'adminPortal.settings.ssoConfigConnectStep.identityProvider', + defaultMessage: 'Please select an SSO Identity Provider', + description: 'Helper message displayed against the option to select an SSO Identity Provider.', + }, + idpConnectOption: { + id: 'adminPortal.settings.ssoConfigConnectStep.idpConnectOption', + defaultMessage: 'Please select a connection method', + description: 'Helper message displayed against the option to select a connection method.', + }, + metadataUrl: { + id: 'adminPortal.settings.ssoConfigConnectStep.metadataUrl', + defaultMessage: 'Please enter an Identity Provider Metadata URL', + description: 'Helper message displayed against the option to enter an Identity Provider Metadata URL.', + }, + metadataXml: { + id: 'adminPortal.settings.ssoConfigConnectStep.metadataXml', + defaultMessage: 'Please upload an Identity Provider Metadata XML file', + description: 'Helper message displayed against the option to upload an Identity Provider Metadata XML file.', + }, + other: { + id: 'adminPortal.settings.ssoConfigConnectStep.other', + defaultMessage: 'Other', + description: 'Other identity provider option.', + }, + enterMetadataUrl: { + id: 'adminPortal.settings.ssoConfigConnectStep.enterMetadataUrl', + defaultMessage: 'Enter identity Provider Metadata URL', + description: 'Option to enter Identity Provider Metadata URL.', + }, + uploadMetadataXml: { + id: 'adminPortal.settings.ssoConfigConnectStep.uploadMetadataXml', + defaultMessage: 'Upload Identity Provider Metadata XML file', + description: 'Option to upload Identity Provider Metadata XML file.', + }, + invalidType: { + id: 'adminPortal.settings.ssoConfigConnectStep.invalidType', + defaultMessage: 'Invalid file type, only xml images allowed.', + description: 'Error message displayed when an invalid file type is uploaded.', + }, + invalidSize: { + id: 'adminPortal.settings.ssoConfigConnectStep.invalidSize', + defaultMessage: 'The file size must be under 5 gb.', + description: 'Error message displayed when the uploaded file size exceeds the limit.', + }, + multipleDragged: { + id: 'adminPortal.settings.ssoConfigConnectStep.multipleDragged', + defaultMessage: 'Cannot upload more than one file.', + description: 'Error message displayed when more than one file is uploaded.', + }, +}); + +export const getValidations = (intl) : FormFieldValidation[] => [ { formFieldId: 'identityProvider', - validator: (fields) => !fields.identityProvider && 'Please select an SSO Identity Provider', + validator: (fields) => !fields.identityProvider && intl.formatMessage(messages.identityProvider), }, { formFieldId: 'idpConnectOption', - validator: (fields) => !fields.idpConnectOption && 'Please select a connection method', + validator: (fields) => !fields.idpConnectOption && intl.formatMessage(messages.idpConnectOption), }, { formFieldId: 'metadataUrl', validator: (fields) => { const error = urlEntrySelected(fields) && !urlValidation(fields.metadataUrl); - return error && 'Please enter an Identity Provider Metadata URL'; + return error && intl.formatMessage(messages.metadataUrl); }, }, { formFieldId: 'metadataXml', validator: (fields) => { const error = !fields.metadataXml && xmlEntrySelected(fields); - return error && 'Please upload an Identity Provider Metadata XML file'; + return error && intl.formatMessage(messages.metadataXml); }, }, ]; const SSOConfigConnectStep = () => { - const fiveGbInBytes = 5368709120; - const ssoIdpOptions = [ - ['Microsoft Entra ID', 'microsoft_entra_id'], - ['Google Workspace', 'google_workspace'], - ['Okta', 'okta'], - ['OneLogin', 'one_login'], - ['SAP SuccessFactors', 'sap_success_factors'], - ['Other', 'other'], - ]; - const idpConnectOptions = [ - ['Enter identity Provider Metadata URL', IDP_URL_SELECTION], - ['Upload Identity Provider Metadata XML file', IDP_XML_SELECTION], - ]; - const { formFields, dispatch, showErrors, errorMap, }: FormContext = useFormContext(); const [xmlUploadFileName, setXmlUploadFileName] = useState(''); + const intl = useIntl(); const showUrlEntry = urlEntrySelected(formFields); const showXmlUpload = xmlEntrySelected(formFields); const xmlUploadError = errorMap?.metadataXml; @@ -70,13 +111,37 @@ const SSOConfigConnectStep = () => { }); }; + const fiveGbInBytes = 5368709120; + const ssoIdpOptions = [ + ['Microsoft Entra ID', 'microsoft_entra_id'], + ['Google Workspace', 'google_workspace'], + ['Okta', 'okta'], + ['OneLogin', 'one_login'], + ['SAP SuccessFactors', 'sap_success_factors'], + [intl.formatMessage(messages.other), 'other'], + ]; + const idpConnectOptions = [ + [intl.formatMessage(messages.enterMetadataUrl), IDP_URL_SELECTION], + [intl.formatMessage(messages.uploadMetadataXml), IDP_XML_SELECTION], + ]; + return ( - - -

Let's get started

-

What is your organization's SSO Identity Provider?

+

+ +

+

+ +

{ options={ssoIdpOptions} /> - -

Connect edX to your Identity Provider

-

Select a method to connect edX to your Identity Provider

+

+ +

+

+ +

{ )} @@ -112,9 +196,9 @@ const SSOConfigConnectStep = () => { { /> {xmlUploadFileName && ( - Uploaded{' '} - {xmlUploadFileName} + )} {showErrors && xmlUploadError && {xmlUploadError}} diff --git a/src/components/settings/SettingsSSOTab/tests/NewSSOConfigCard.test.jsx b/src/components/settings/SettingsSSOTab/tests/NewSSOConfigCard.test.jsx index 887ade767c..47b3e0dcb8 100644 --- a/src/components/settings/SettingsSSOTab/tests/NewSSOConfigCard.test.jsx +++ b/src/components/settings/SettingsSSOTab/tests/NewSSOConfigCard.test.jsx @@ -1,14 +1,15 @@ import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import userEvent from '@testing-library/user-event'; -import { act, render, screen } from '@testing-library/react'; +import { act, screen } from '@testing-library/react'; import { SSOConfigContext, SSO_INITIAL_STATE } from '../SSOConfigContext'; import NewSSOConfigCard from '../NewSSOConfigCard'; import LmsApiService from '../../../../data/services/LmsApiService'; +import { renderWithI18nProvider } from '../../../test/testUtils'; describe('New SSO Config Card Tests', () => { test('displays enabled and validated status icon properly', async () => { - render( + renderWithI18nProvider( { ).toBeInTheDocument(); }); test('displays not validated status icon properly', async () => { - render( + renderWithI18nProvider( { ).toBeInTheDocument(); }); test('displays key off icon status icon properly', async () => { - render( + renderWithI18nProvider( { ).toBeInTheDocument(); }); test('displays badges properly', async () => { - render( + renderWithI18nProvider( { 'existing-sso-config-card-badge-in-progress', ), ).toBeInTheDocument(); - render( + renderWithI18nProvider( { setProviderConfig: mockSetProviderConfig, setRefreshBool: jest.fn(), }; - render( + renderWithI18nProvider( { expect(mockSetProviderConfig).toHaveBeenCalled(); }); test('displays enable button properly', async () => { - render( + renderWithI18nProvider( { test('handles kebob Delete dropdown option', async () => { const spy = jest.spyOn(LmsApiService, 'deleteEnterpriseSsoOrchestrationRecord'); spy.mockImplementation(() => Promise.resolve({})); - render( + renderWithI18nProvider( { test('handles kebob Disable dropdown option', async () => { const spy = jest.spyOn(LmsApiService, 'updateEnterpriseSsoOrchestrationRecord'); spy.mockImplementation(() => Promise.resolve({})); - render( + renderWithI18nProvider( { const mockGetProviderConfig = jest.spyOn(LmsApiService, 'getProviderConfig'); mockGetProviderConfig.mockResolvedValue({ data: { result: [{ woohoo: 'success!' }] } }); contextValue.ssoState.currentStep = 'connect'; - render( + renderWithI18nProvider( { }); test('canceling service provider step', async () => { contextValue.ssoState.currentStep = 'serviceprovider'; - render( + renderWithI18nProvider( { }); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( { const mockUpdateProviderConfig = jest.spyOn(LmsApiService, 'updateProviderConfig'); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( { test('update config method does not make api call if form is not updated', async () => { const mockUpdateProviderConfig = jest.spyOn(LmsApiService, 'updateProviderConfig'); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( { throw new Error({ response: { data: 'foobar' } }); }); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( { test('canceling without saving configure form', async () => { const mockUpdateProviderConfig = jest.spyOn(LmsApiService, 'updateProviderConfig'); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( { const mockUpdateProviderConfig = jest.spyOn(LmsApiService, 'updateProviderConfig'); mockUpdateProviderConfig.mockResolvedValue('success!'); - render( + renderWithI18nProvider( { const mockUpdateProviderConfig = jest.spyOn(LmsApiService, 'updateProviderConfig'); mockUpdateProviderConfig.mockResolvedValue({ data: { result: [{ woohoo: 'ayylmao!' }] } }); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( { test('idp completed check for url entry', async () => { // Setup contextValue.ssoState.currentStep = 'idp'; - render( + renderWithI18nProvider( { { data: { results: [{ entity_id: 'ayylmao!', public_key: '123abc!', sso_url: 'https://ayylmao.com' }] } }, ); contextValue.ssoState.currentStep = 'idp'; - render( + renderWithI18nProvider( { const mockUpdateProviderConfig = jest.spyOn(LmsApiService, 'updateProviderConfig'); mockUpdateProviderConfig.mockResolvedValue({ data: { result: [{ woohoo: 'ayylmao!' }] } }); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( { const mockUpdateProviderConfig = jest.spyOn(LmsApiService, 'updateProviderConfig'); mockUpdateProviderConfig.mockResolvedValue({ data: { result: [{ woohoo: 'ayylmao!' }] } }); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( { const mockUpdateProviderConfig = jest.spyOn(LmsApiService, 'updateProviderConfig'); mockUpdateProviderConfig.mockResolvedValue({ data: { result: [{ woohoo: 'ayylmao!' }] } }); contextValue.ssoState.currentStep = 'configure'; - render( + renderWithI18nProvider( ( - + + + ); diff --git a/src/components/settings/SettingsSSOTab/tests/SettingsSSOTab.test.jsx b/src/components/settings/SettingsSSOTab/tests/SettingsSSOTab.test.jsx index a65f515723..d7fca0abcc 100644 --- a/src/components/settings/SettingsSSOTab/tests/SettingsSSOTab.test.jsx +++ b/src/components/settings/SettingsSSOTab/tests/SettingsSSOTab.test.jsx @@ -13,7 +13,7 @@ import { HELP_CENTER_SAML_LINK } from '../../data/constants'; import { features } from '../../../../config'; import SettingsSSOTab from '..'; import LmsApiService from '../../../../data/services/LmsApiService'; -import { queryClient } from '../../../test/testUtils'; +import { queryClient, renderWithI18nProvider } from '../../../test/testUtils'; const enterpriseId = 'an-id-1'; jest.mock('../../../../data/services/LmsApiService'); @@ -42,7 +42,7 @@ describe('SAML Config Tab', () => { LmsApiService.getProviderConfig.mockImplementation(() => ( { data: { results: [] } } )); - render( + renderWithI18nProvider( , @@ -58,7 +58,7 @@ describe('SAML Config Tab', () => { LmsApiService.getProviderConfig.mockImplementation(() => ( { data: { results: [] } } )); - render( + renderWithI18nProvider( , @@ -71,7 +71,7 @@ describe('SAML Config Tab', () => { LmsApiService.getProviderConfig.mockImplementation(() => ( { data: { results: [{ was_valid_at: '10/10/22' }] } } )); - render( + renderWithI18nProvider( , diff --git a/src/components/settings/data/constants.js b/src/components/settings/data/constants.js index ab78c7e7cc..33c8a4026e 100644 --- a/src/components/settings/data/constants.js +++ b/src/components/settings/data/constants.js @@ -65,9 +65,6 @@ export const INVALID_ODATA_API_TIMEOUT_INTERVAL = 'OData API timeout interval mu export const MAX_UNIVERSAL_LINKS = 100; -export const ssoStepperNetworkErrorText = 'We were unable to configure your SSO due to an internal error.'; -export const ssoLPNetworkErrorText = 'We were unable to load your SSO details due to an internal error.'; - /** * Used as tab values and in router params */