From c31c397c61df08a3aaed8a9bd6f69df0e2241947 Mon Sep 17 00:00:00 2001 From: "kshitij.sobti" Date: Fri, 6 Mar 2026 09:32:07 -0300 Subject: [PATCH] feat: add Slot for login page This change adds a Slot for the login page allowing it to be customised. Since this touched the Login Page, LoginPage and Logistration have also been refactored to move away from redux connect. Adapted for frontend-base: uses Slot from @openedx/frontend-base instead of PluginSlot from @openedx/frontend-plugin-framework, slot files live under src/slots/, and the slot ID follows the frontend-base naming convention (org.openedx.frontend.slot.authn.loginComponent.v1). Co-Authored-By: Adolfo R. Brandes Co-Authored-By: Claude Opus 4.6 --- src/login/LoginPage.jsx | 190 +++++++----------- src/login/tests/LoginPage.test.jsx | 8 + src/logistration/Logistration.jsx | 68 +++---- src/logistration/Logistration.test.jsx | 31 ++- src/slots/LoginComponentSlot/README.md | 19 ++ .../component_with_prefix.png | Bin 0 -> 13317 bytes .../LoginComponentSlot/default_component.png | Bin 0 -> 7681 bytes src/slots/LoginComponentSlot/index.jsx | 27 +++ 8 files changed, 180 insertions(+), 163 deletions(-) create mode 100644 src/slots/LoginComponentSlot/README.md create mode 100644 src/slots/LoginComponentSlot/component_with_prefix.png create mode 100644 src/slots/LoginComponentSlot/default_component.png create mode 100644 src/slots/LoginComponentSlot/index.jsx diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx index 2873f660..011afabf 100644 --- a/src/login/LoginPage.jsx +++ b/src/login/LoginPage.jsx @@ -1,12 +1,12 @@ -import { useEffect, useMemo, useState } from 'react'; -import { connect } from 'react-redux'; +import { + useCallback, useEffect, useMemo, useState, +} from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { getSiteConfig, sendPageEvent, sendTrackEvent, useIntl } from '@openedx/frontend-base'; -import { - Form, StatefulButton, -} from '@openedx/paragon'; +import { Form, StatefulButton } from '@openedx/paragon'; import PropTypes from 'prop-types'; import { Helmet } from 'react-helmet'; import Skeleton from 'react-loading-skeleton'; @@ -23,9 +23,7 @@ import { getThirdPartyAuthContext } from '../common-components/data/actions'; import { thirdPartyAuthContextSelector } from '../common-components/data/selectors'; import EnterpriseSSO from '../common-components/EnterpriseSSO'; import ThirdPartyAuth from '../common-components/ThirdPartyAuth'; -import { - DEFAULT_STATE, PENDING_STATE, RESET_PAGE, -} from '../data/constants'; +import { PENDING_STATE, RESET_PAGE } from '../data/constants'; import { getActivationStatus, getAllPossibleQueryParams, @@ -35,45 +33,57 @@ import { } from '../data/utils'; import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess'; import AccountActivationMessage from './AccountActivationMessage'; -import { - backupLoginFormBegin, - dismissPasswordResetBanner, - loginRequest, -} from './data/actions'; +import { backupLoginFormBegin, dismissPasswordResetBanner, loginRequest } from './data/actions'; import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants'; import LoginFailureMessage from './LoginFailure'; import messages from './messages'; -const LoginPage = (props) => { +const LoginPage = ({ + institutionLogin, + handleInstitutionLogin, +}) => { + const dispatch = useDispatch(); + const backupFormState = useCallback((data) => dispatch(backupLoginFormBegin(data)), [dispatch]); + const getTPADataFromBackend = useCallback(() => dispatch(getThirdPartyAuthContext()), [dispatch]); const { backedUpFormData, loginErrorCode, loginErrorContext, loginResult, shouldBackupState, - thirdPartyAuthContext: { - providers, - currentProvider, - secondaryProviders, - finishAuthUrl, - platformName, - errorMessage: thirdPartyErrorMessage, - }, - thirdPartyAuthApiStatus, - institutionLogin, showResetPasswordSuccessBanner, submitState, - // Actions - backupFormState, - handleInstitutionLogin, - getTPADataFromBackend, - } = props; + thirdPartyAuthContext, + thirdPartyAuthApiStatus, + } = useSelector((state) => ({ + backedUpFormData: state.login.loginFormData, + loginErrorCode: state.login.loginErrorCode, + loginErrorContext: state.login.loginErrorContext, + loginResult: state.login.loginResult, + shouldBackupState: state.login.shouldBackupState, + showResetPasswordSuccessBanner: state.login.showResetPasswordSuccessBanner, + submitState: state.login.submitState, + thirdPartyAuthContext: thirdPartyAuthContextSelector(state), + thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus, + })); + const { + providers, + currentProvider, + secondaryProviders, + finishAuthUrl, + platformName, + errorMessage: thirdPartyErrorMessage, + } = thirdPartyAuthContext; const { formatMessage } = useIntl(); const activationMsgType = getActivationStatus(); const queryParams = useMemo(() => getAllPossibleQueryParams(), []); const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields }); - const [errorCode, setErrorCode] = useState({ type: '', count: 0, context: {} }); + const [errorCode, setErrorCode] = useState({ + type: '', + count: 0, + context: {}, + }); const [errors, setErrors] = useState({ ...backedUpFormData.errors }); const tpaHint = getTpaHint(); @@ -87,7 +97,7 @@ const LoginPage = (props) => { payload.tpa_hint = tpaHint; } getTPADataFromBackend(payload); - }, [getTPADataFromBackend, queryParams, tpaHint]); + }, [queryParams, tpaHint, getTPADataFromBackend]); /** * Backup the login form in redux when login page is toggled. */ @@ -98,7 +108,7 @@ const LoginPage = (props) => { errors: { ...errors }, }); } - }, [shouldBackupState, formFields, errors, backupFormState]); + }, [backupFormState, shouldBackupState, formFields, errors]); useEffect(() => { if (loginErrorCode) { @@ -123,7 +133,10 @@ const LoginPage = (props) => { }, [thirdPartyErrorMessage]); const validateFormFields = (payload) => { - const { emailOrUsername, password } = payload; + const { + emailOrUsername, + password, + } = payload; const fieldErrors = { ...errors }; if (emailOrUsername === '') { @@ -141,14 +154,18 @@ const LoginPage = (props) => { const handleSubmit = (event) => { event.preventDefault(); if (showResetPasswordSuccessBanner) { - props.dismissPasswordResetBanner(); + dispatch(dismissPasswordResetBanner()); } const formData = { ...formFields }; const validationErrors = validateFormFields(formData); if (validationErrors.emailOrUsername || validationErrors.password) { setErrors({ ...validationErrors }); - setErrorCode(prevState => ({ type: INVALID_FORM, count: prevState.count + 1, context: {} })); + setErrorCode(prevState => ({ + type: INVALID_FORM, + count: prevState.count + 1, + context: {}, + })); return; } @@ -158,23 +175,35 @@ const LoginPage = (props) => { password: formData.password, ...queryParams, }; - props.loginRequest(payload); + dispatch(loginRequest(payload)); }; const handleOnChange = (event) => { - const { name, value } = event.target; - setFormFields(prevState => ({ ...prevState, [name]: value })); + const { + name, + value, + } = event.target; + setFormFields(prevState => ({ + ...prevState, + [name]: value, + })); }; const handleOnFocus = (event) => { const { name } = event.target; - setErrors(prevErrors => ({ ...prevErrors, [name]: '' })); + setErrors(prevErrors => ({ + ...prevErrors, + [name]: '', + })); }; const trackForgotPasswordLinkClick = () => { sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' }); }; - const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders); + const { + provider, + skipHintedLogin, + } = getTpaProvider(tpaHint, providers, secondaryProviders); if (tpaHint) { if (thirdPartyAuthApiStatus === PENDING_STATE) { @@ -281,88 +310,9 @@ const LoginPage = (props) => { ); }; -const mapStateToProps = state => { - const loginPageState = state.login; - return { - backedUpFormData: loginPageState.loginFormData, - loginErrorCode: loginPageState.loginErrorCode, - loginErrorContext: loginPageState.loginErrorContext, - loginResult: loginPageState.loginResult, - shouldBackupState: loginPageState.shouldBackupState, - showResetPasswordSuccessBanner: loginPageState.showResetPasswordSuccessBanner, - submitState: loginPageState.submitState, - thirdPartyAuthContext: thirdPartyAuthContextSelector(state), - thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus, - }; -}; - LoginPage.propTypes = { - backedUpFormData: PropTypes.shape({ - formFields: PropTypes.shape({}), - errors: PropTypes.shape({}), - }), - loginErrorCode: PropTypes.string, - loginErrorContext: PropTypes.shape({ - email: PropTypes.string, - redirectUrl: PropTypes.string, - context: PropTypes.shape({}), - }), - loginResult: PropTypes.shape({ - redirectUrl: PropTypes.string, - success: PropTypes.bool, - }), - shouldBackupState: PropTypes.bool, - showResetPasswordSuccessBanner: PropTypes.bool, - submitState: PropTypes.string, - thirdPartyAuthApiStatus: PropTypes.string, institutionLogin: PropTypes.bool.isRequired, - thirdPartyAuthContext: PropTypes.shape({ - currentProvider: PropTypes.string, - errorMessage: PropTypes.string, - platformName: PropTypes.string, - providers: PropTypes.arrayOf(PropTypes.shape({})), - secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})), - finishAuthUrl: PropTypes.string, - }), - // Actions - backupFormState: PropTypes.func.isRequired, - dismissPasswordResetBanner: PropTypes.func.isRequired, - loginRequest: PropTypes.func.isRequired, - getTPADataFromBackend: PropTypes.func.isRequired, handleInstitutionLogin: PropTypes.func.isRequired, }; -LoginPage.defaultProps = { - backedUpFormData: { - formFields: { - emailOrUsername: '', password: '', - }, - errors: { - emailOrUsername: '', password: '', - }, - }, - loginErrorCode: null, - loginErrorContext: {}, - loginResult: {}, - shouldBackupState: false, - showResetPasswordSuccessBanner: false, - submitState: DEFAULT_STATE, - thirdPartyAuthApiStatus: PENDING_STATE, - thirdPartyAuthContext: { - currentProvider: null, - errorMessage: null, - finishAuthUrl: null, - providers: [], - secondaryProviders: [], - }, -}; - -export default connect( - mapStateToProps, - { - backupFormState: backupLoginFormBegin, - dismissPasswordResetBanner, - loginRequest, - getTPADataFromBackend: getThirdPartyAuthContext, - }, -)(LoginPage); +export default LoginPage; diff --git a/src/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx index 94ff754b..d87fe896 100644 --- a/src/login/tests/LoginPage.test.jsx +++ b/src/login/tests/LoginPage.test.jsx @@ -40,6 +40,14 @@ describe('LoginPage', () => { const initialState = { login: { loginResult: { success: false, redirectUrl: '' }, + loginFormData: { + formFields: { + emailOrUsername: '', password: '', + }, + errors: { + emailOrUsername: '', password: '', + }, + }, }, commonComponents: { thirdPartyAuthApiStatus: null, diff --git a/src/logistration/Logistration.jsx b/src/logistration/Logistration.jsx index 25fb805f..fc0dff5c 100644 --- a/src/logistration/Logistration.jsx +++ b/src/logistration/Logistration.jsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useAppConfig, getAuthService, getSiteConfig, sendPageEvent, sendTrackEvent, useIntl @@ -23,16 +23,20 @@ import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants'; import { getTpaHint, getTpaProvider, updatePathWithQueryParams, } from '../data/utils'; -import { LoginPage } from '../login'; import { backupLoginForm } from '../login/data/actions'; import { RegistrationPage } from '../register'; import { backupRegistrationForm } from '../register/data/actions'; +import LoginComponentSlot from '../slots/LoginComponentSlot'; -const Logistration = (props) => { - const { selectedPage, tpaProviders } = props; +const Logistration = ({ + selectedPage, +}) => { const tpaHint = getTpaHint(); + const tpaProviders = useSelector(tpaProvidersSelector); + const dispatch = useDispatch(); const { - providers, secondaryProviders, + providers, + secondaryProviders, } = tpaProviders; const { formatMessage } = useIntl(); const [institutionLogin, setInstitutionLogin] = useState(false); @@ -44,7 +48,8 @@ const Logistration = (props) => { useEffect(() => { const authService = getAuthService(); if (authService) { - authService.getCsrfTokenService().getCsrfToken(getSiteConfig().lmsBaseUrl); + authService.getCsrfTokenService() + .getCsrfToken(getSiteConfig().lmsBaseUrl); } }); @@ -70,11 +75,11 @@ const Logistration = (props) => { return; } sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' }); - props.clearThirdPartyAuthContextErrorMessage(); + dispatch(clearThirdPartyAuthContextErrorMessage()); if (tabKey === LOGIN_PAGE) { - props.backupRegistrationForm(); + dispatch(backupRegistrationForm()); } else if (tabKey === REGISTER_PAGE) { - props.backupLoginForm(); + dispatch(backupLoginForm()); } setKey(tabKey); }; @@ -110,7 +115,10 @@ const Logistration = (props) => { {!institutionLogin && (

{formatMessage(messages['logistration.sign.in'])}

)} - + ) @@ -123,7 +131,11 @@ const Logistration = (props) => { ) : (!isValidTpaHint() && !hideRegistrationLink && ( - handleOnSelect(tabKey, selectedPage)}> + handleOnSelect(tabKey, selectedPage)} + > @@ -138,7 +150,12 @@ const Logistration = (props) => { )} {selectedPage === LOGIN_PAGE - ? + ? ( + + ) : ( { Logistration.propTypes = { selectedPage: PropTypes.string, - backupLoginForm: PropTypes.func.isRequired, - backupRegistrationForm: PropTypes.func.isRequired, - clearThirdPartyAuthContextErrorMessage: PropTypes.func.isRequired, - tpaProviders: PropTypes.shape({ - providers: PropTypes.arrayOf(PropTypes.shape({})), - secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})), - }), -}; - -Logistration.defaultProps = { - tpaProviders: { - providers: [], - secondaryProviders: [], - }, }; Logistration.defaultProps = { selectedPage: REGISTER_PAGE, }; -const mapStateToProps = state => ({ - tpaProviders: tpaProvidersSelector(state), -}); - -export default connect( - mapStateToProps, - { - backupLoginForm, - backupRegistrationForm, - clearThirdPartyAuthContextErrorMessage, - }, -)(Logistration); +export default Logistration; diff --git a/src/logistration/Logistration.test.jsx b/src/logistration/Logistration.test.jsx index 901ab3e1..53496ee7 100644 --- a/src/logistration/Logistration.test.jsx +++ b/src/logistration/Logistration.test.jsx @@ -56,16 +56,26 @@ describe('Logistration', () => { marketingEmailsOptIn: true, }, formFields: { - name: '', email: '', username: '', password: '', + name: '', + email: '', + username: '', + password: '', }, emailSuggestion: { - suggestion: '', type: '', + suggestion: '', + type: '', }, errors: { - name: '', email: '', username: '', password: '', + name: '', + email: '', + username: '', + password: '', }, }, - registrationResult: { success: false, redirectUrl: '' }, + registrationResult: { + success: false, + redirectUrl: '', + }, registrationError: {}, usernameSuggestions: [], validationApiRateLimited: false, @@ -77,7 +87,18 @@ describe('Logistration', () => { }, }, login: { - loginResult: { success: false, redirectUrl: '' }, + loginResult: { + success: false, + redirectUrl: '', + }, + loginFormData: { + formFields: { + emailOrUsername: '', password: '', + }, + errors: { + emailOrUsername: '', password: '', + }, + }, }, }; diff --git a/src/slots/LoginComponentSlot/README.md b/src/slots/LoginComponentSlot/README.md new file mode 100644 index 00000000..d9c35b1f --- /dev/null +++ b/src/slots/LoginComponentSlot/README.md @@ -0,0 +1,19 @@ +# Login Component Slot + +### Slot ID: `org.openedx.frontend.slot.authn.loginComponent.v1` + +## Description + +This slot is used to replace/modify/hide the login component. + +## Example + +### Default content +![Default Login Page](./default_component.png) + +### With a prepended message +![Login Page with prepended message](./component_with_prefix.png) + +The site configuration can be used to add a widget before the login component +using the slot's `PREPEND` operation. Refer to the `@openedx/frontend-base` +Slot documentation for configuration details. diff --git a/src/slots/LoginComponentSlot/component_with_prefix.png b/src/slots/LoginComponentSlot/component_with_prefix.png new file mode 100644 index 0000000000000000000000000000000000000000..394855fa8d0b34f0a123503b92a56f38d16379fb GIT binary patch literal 13317 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 DlIysS literal 0 HcmV?d00001 diff --git a/src/slots/LoginComponentSlot/default_component.png b/src/slots/LoginComponentSlot/default_component.png new file mode 100644 index 0000000000000000000000000000000000000000..7c00670323553675f18413921a4c17721975323a GIT binary patch literal 7681 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 U36VAa(=MQ)s;%-~$s+VW0EobU-2eap literal 0 HcmV?d00001 diff --git a/src/slots/LoginComponentSlot/index.jsx b/src/slots/LoginComponentSlot/index.jsx new file mode 100644 index 00000000..0386ec4b --- /dev/null +++ b/src/slots/LoginComponentSlot/index.jsx @@ -0,0 +1,27 @@ +import { Slot } from '@openedx/frontend-base'; +import PropTypes from 'prop-types'; + +import LoginPage from '../../login/LoginPage'; + +const LoginComponentSlot = ({ + institutionLogin, + handleInstitutionLogin, +}) => ( + + + +); + +LoginComponentSlot.propTypes = { + institutionLogin: PropTypes.bool, + handleInstitutionLogin: PropTypes.func, +}; + +export default LoginComponentSlot;