logistration folder refactored to login and register
This commit is contained in:
@@ -12,7 +12,7 @@ const ConfirmationAlert = (props) => {
|
||||
|
||||
return (
|
||||
<Alert id="confirmation-alert" variant="success">
|
||||
<Alert.Heading>{intl.formatMessage(messages['authn.forgot.password.confirmation.title'])}</Alert.Heading>
|
||||
<Alert.Heading>{intl.formatMessage(messages['forgot.password.confirmation.title'])}</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="logistration.forgot.password.confirmation.message"
|
||||
@@ -22,16 +22,16 @@ const ConfirmationAlert = (props) => {
|
||||
values={{ strongEmail: <strong>{email}</strong> }}
|
||||
/>
|
||||
</p>
|
||||
<p>{intl.formatMessage(messages['authn.forgot.password.confirmation.info'])}</p>
|
||||
<p>{intl.formatMessage(messages['forgot.password.confirmation.info'])}</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="authn.forgot.password.technical.support.help.message"
|
||||
id="forgot.password.technical.support.help.message"
|
||||
defaultMessage="If you need further assistance, {technicalSupportLink}."
|
||||
description="Message to help user contact technical support"
|
||||
values={{
|
||||
technicalSupportLink: (
|
||||
<Alert.Link href={getConfig().PASSWORD_RESET_SUPPORT_LINK}>
|
||||
{intl.formatMessage(messages['authn.forgot.password.confirmation.support.link'])}
|
||||
{intl.formatMessage(messages['forgot.password.confirmation.support.link'])}
|
||||
</Alert.Link>
|
||||
),
|
||||
}}
|
||||
@@ -40,7 +40,7 @@ const InstitutionLogistration = props => {
|
||||
{headingTitle}
|
||||
</h1>
|
||||
<p className="mb-2">
|
||||
{intl.formatMessage(messages['logistration.institution.login.page.sub.heading'])}
|
||||
{intl.formatMessage(messages['institution.login.page.sub.heading'])}
|
||||
</p>
|
||||
<div className="mb-2 ml-2">
|
||||
<ul>
|
||||
22
src/common-components/data/actions.js
Normal file
22
src/common-components/data/actions.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const THIRD_PARTY_AUTH_CONTEXT = new AsyncActionType('THIRD_PARTY_AUTH', 'GET_THIRD_PARTY_AUTH_CONTEXT');
|
||||
|
||||
// Third party auth context
|
||||
export const getThirdPartyAuthContext = (urlParams) => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.BASE,
|
||||
payload: { urlParams },
|
||||
});
|
||||
|
||||
export const getThirdPartyAuthContextBegin = () => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.BEGIN,
|
||||
});
|
||||
|
||||
export const getThirdPartyAuthContextSuccess = (thirdPartyAuthContext) => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS,
|
||||
payload: { thirdPartyAuthContext },
|
||||
});
|
||||
|
||||
export const getThirdPartyAuthContextFailure = () => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.FAILURE,
|
||||
});
|
||||
32
src/common-components/data/reducers.js
Normal file
32
src/common-components/data/reducers.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { THIRD_PARTY_AUTH_CONTEXT } from './actions';
|
||||
|
||||
import { PENDING_STATE, COMPLETE_STATE } from '../../data/constants';
|
||||
|
||||
export const defaultState = {
|
||||
thirdPartyAuthApiStatus: null,
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case THIRD_PARTY_AUTH_CONTEXT.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
};
|
||||
case THIRD_PARTY_AUTH_CONTEXT.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthContext: action.payload.thirdPartyAuthContext,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
};
|
||||
case THIRD_PARTY_AUTH_CONTEXT.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
||||
34
src/common-components/data/sagas.js
Normal file
34
src/common-components/data/sagas.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
THIRD_PARTY_AUTH_CONTEXT,
|
||||
getThirdPartyAuthContextBegin,
|
||||
getThirdPartyAuthContextSuccess,
|
||||
getThirdPartyAuthContextFailure,
|
||||
} from './actions';
|
||||
|
||||
// Services
|
||||
import {
|
||||
getThirdPartyAuthContext,
|
||||
} from './service';
|
||||
|
||||
export function* fetchThirdPartyAuthContext(action) {
|
||||
try {
|
||||
yield put(getThirdPartyAuthContextBegin());
|
||||
const { thirdPartyAuthContext } = yield call(getThirdPartyAuthContext, action.payload.urlParams);
|
||||
|
||||
yield put(getThirdPartyAuthContextSuccess(
|
||||
thirdPartyAuthContext,
|
||||
));
|
||||
} catch (e) {
|
||||
yield put(getThirdPartyAuthContextFailure());
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export default function* saga() {
|
||||
yield takeEvery(THIRD_PARTY_AUTH_CONTEXT.BASE, fetchThirdPartyAuthContext);
|
||||
}
|
||||
10
src/common-components/data/selectors.js
Normal file
10
src/common-components/data/selectors.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
export const storeName = 'commonComponents';
|
||||
|
||||
export const commonComponentsSelector = state => ({ ...state[storeName] });
|
||||
|
||||
export const thirdPartyAuthContextSelector = createSelector(
|
||||
commonComponentsSelector,
|
||||
commonComponents => commonComponents.thirdPartyAuthContext,
|
||||
);
|
||||
23
src/common-components/data/service.js
Normal file
23
src/common-components/data/service.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { camelCaseObject, convertKeyNames, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function getThirdPartyAuthContext(urlParams) {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
params: urlParams,
|
||||
isPublic: true,
|
||||
};
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(
|
||||
`${getConfig().LMS_BASE_URL}/api/third_party_auth_context`,
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
throw (e);
|
||||
});
|
||||
return {
|
||||
thirdPartyAuthContext: camelCaseObject(convertKeyNames(data, { fullname: 'name' })),
|
||||
};
|
||||
}
|
||||
65
src/common-components/data/tests/sagas.test.js
Normal file
65
src/common-components/data/tests/sagas.test.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { runSaga } from 'redux-saga';
|
||||
|
||||
import * as actions from '../actions';
|
||||
import { fetchThirdPartyAuthContext } from '../sagas';
|
||||
import * as api from '../service';
|
||||
import initializeMockLogging from '../../../setupTest';
|
||||
|
||||
const { loggingService } = initializeMockLogging();
|
||||
|
||||
describe('fetchThirdPartyAuthContext', () => {
|
||||
const params = {
|
||||
payload: { urlParams: {} },
|
||||
};
|
||||
|
||||
const data = {
|
||||
currentProvider: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
finishAuthUrl: null,
|
||||
pipelineUserDetails: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
loggingService.logError.mockReset();
|
||||
});
|
||||
|
||||
it('should call service and dispatch success action', async () => {
|
||||
const getThirdPartyAuthContext = jest.spyOn(api, 'getThirdPartyAuthContext')
|
||||
.mockImplementation(() => Promise.resolve({ thirdPartyAuthContext: data }));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
fetchThirdPartyAuthContext,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(getThirdPartyAuthContext).toHaveBeenCalledTimes(1);
|
||||
expect(dispatched).toEqual([
|
||||
actions.getThirdPartyAuthContextBegin(),
|
||||
actions.getThirdPartyAuthContextSuccess(data),
|
||||
]);
|
||||
getThirdPartyAuthContext.mockClear();
|
||||
});
|
||||
|
||||
it('should call service and dispatch error action', async () => {
|
||||
const getThirdPartyAuthContext = jest.spyOn(api, 'getThirdPartyAuthContext')
|
||||
.mockImplementation(() => Promise.reject(new Error('something went wrong')));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
fetchThirdPartyAuthContext,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(getThirdPartyAuthContext).toHaveBeenCalledTimes(1);
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(dispatched).toEqual([
|
||||
actions.getThirdPartyAuthContextBegin(),
|
||||
actions.getThirdPartyAuthContextFailure(),
|
||||
]);
|
||||
getThirdPartyAuthContext.mockClear();
|
||||
});
|
||||
});
|
||||
@@ -3,3 +3,10 @@ export { default as RedirectLogistration } from './RedirectLogistration';
|
||||
export { default as registerIcons } from './RegisterFaIcons';
|
||||
export { default as UnAuthOnlyRoute } from './UnAuthOnlyRoute';
|
||||
export { default as NotFoundPage } from './NotFoundPage';
|
||||
export { default as SocialAuthProviders } from './SocialAuthProviders';
|
||||
export { default as ThirdPartyAuthAlert } from './ThirdPartyAuthAlert';
|
||||
export { default as InstitutionLogistration } from './InstitutionLogistration';
|
||||
export { RenderInstitutionButton } from './InstitutionLogistration';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/selectors';
|
||||
|
||||
28
src/common-components/messages.jsx
Normal file
28
src/common-components/messages.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'institution.login.page.sub.heading': {
|
||||
id: 'institution.login.page.sub.heading',
|
||||
defaultMessage: 'Choose your institution from the list below:',
|
||||
description: 'Heading of the institutions list',
|
||||
},
|
||||
// Confirmation Alert Message
|
||||
'forgot.password.confirmation.title': {
|
||||
id: 'forgot.password.confirmation.title',
|
||||
defaultMessage: 'Check Your Email',
|
||||
description: 'Forgot password confirmation message title',
|
||||
},
|
||||
'forgot.password.confirmation.support.link': {
|
||||
id: 'forgot.password.confirmation.support.link',
|
||||
defaultMessage: 'contact technical support',
|
||||
description: 'Technical support link text',
|
||||
},
|
||||
'forgot.password.confirmation.info': {
|
||||
id: 'forgot.password.confirmation.info',
|
||||
defaultMessage: 'If you do not receive a password reset message after 1 minute, verify that you entered the correct '
|
||||
+ 'email address, or check your spam folder.',
|
||||
description: 'Part of message that appears after user requests password change',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -3,7 +3,7 @@ import renderer from 'react-test-renderer';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import SocialAuthProviders from '../SocialAuthProviders';
|
||||
import { registerIcons } from '../../common-components';
|
||||
import registerIcons from '../RegisterFaIcons';
|
||||
|
||||
registerIcons();
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
import {
|
||||
reducer as logistrationReducer,
|
||||
storeName as logistrationStoreName,
|
||||
} from '../logistration';
|
||||
reducer as loginReducer,
|
||||
storeName as loginStoreName,
|
||||
} from '../login';
|
||||
import {
|
||||
reducer as registerReducer,
|
||||
storeName as registerStoreName,
|
||||
} from '../register';
|
||||
import {
|
||||
reducer as commonComponentsReducer,
|
||||
storeName as commonComponentsStoreName,
|
||||
} from '../common-components';
|
||||
import {
|
||||
reducer as forgotPasswordReducer,
|
||||
storeName as forgotPasswordStoreName,
|
||||
@@ -14,7 +22,9 @@ import {
|
||||
} from '../reset-password';
|
||||
|
||||
const createRootReducer = () => combineReducers({
|
||||
[logistrationStoreName]: logistrationReducer,
|
||||
[loginStoreName]: loginReducer,
|
||||
[registerStoreName]: registerReducer,
|
||||
[commonComponentsStoreName]: commonComponentsReducer,
|
||||
[forgotPasswordStoreName]: forgotPasswordReducer,
|
||||
[resetPasswordStoreName]: resetPasswordReducer,
|
||||
});
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { all } from 'redux-saga/effects';
|
||||
|
||||
import { saga as registrationSaga } from '../logistration';
|
||||
import { saga as registrationSaga } from '../register';
|
||||
import { saga as loginSaga } from '../login';
|
||||
import { saga as commonComponentsSaga } from '../common-components';
|
||||
import { saga as forgotPasswordSaga } from '../forgot-password';
|
||||
import { saga as resetPasswordSaga } from '../reset-password';
|
||||
|
||||
export default function* rootSaga() {
|
||||
yield all([
|
||||
loginSaga(),
|
||||
registrationSaga(),
|
||||
commonComponentsSaga(),
|
||||
forgotPasswordSaga(),
|
||||
resetPasswordSaga(),
|
||||
]);
|
||||
|
||||
@@ -16,14 +16,14 @@ import { forgotPassword } from './data/actions';
|
||||
import { forgotPasswordResultSelector } from './data/selectors';
|
||||
import RequestInProgressAlert from './RequestInProgressAlert';
|
||||
import { LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
||||
import LoginHelpLinks from '../logistration/LoginHelpLinks';
|
||||
import LoginHelpLinks from '../login/LoginHelpLinks';
|
||||
|
||||
const ForgotPasswordPage = (props) => {
|
||||
const { intl, status } = props;
|
||||
let invalidEmailMessage;
|
||||
|
||||
const validateEmail = (e, setFieldValue) => {
|
||||
invalidEmailMessage = intl.formatMessage(messages['logisration.forgot.password.page.invalid.email.message']);
|
||||
invalidEmailMessage = intl.formatMessage(messages['forgot.password.page.invalid.email.message']);
|
||||
const regex = new RegExp(VALID_EMAIL_REGEX, 'i');
|
||||
|
||||
const inputEmail = e.target.value;
|
||||
@@ -31,7 +31,7 @@ const ForgotPasswordPage = (props) => {
|
||||
setFieldValue('email', inputEmail);
|
||||
setFieldValue('isEmailValid', isEmailValid);
|
||||
if (inputEmail.length < 3) {
|
||||
invalidEmailMessage = `${intl.formatMessage(messages['logisration.forgot.password.page.email.invalid.length.message'])} ${invalidEmailMessage}`;
|
||||
invalidEmailMessage = `${intl.formatMessage(messages['forgot.password.page.email.invalid.length.message'])} ${invalidEmailMessage}`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,10 +59,10 @@ const ForgotPasswordPage = (props) => {
|
||||
<Form className="mw-500">
|
||||
{status === 'forbidden' ? <RequestInProgressAlert /> : null}
|
||||
<h3 className="mt-3">
|
||||
{intl.formatMessage(messages['logisration.forgot.password.page.heading'])}
|
||||
{intl.formatMessage(messages['forgot.password.page.heading'])}
|
||||
</h3>
|
||||
<p className="mb-4">
|
||||
{intl.formatMessage(messages['logisration.forgot.password.page.instructions'])}
|
||||
{intl.formatMessage(messages['forgot.password.page.instructions'])}
|
||||
</p>
|
||||
<ValidationFormGroup
|
||||
className="mb-0 w-100"
|
||||
@@ -71,7 +71,7 @@ const ForgotPasswordPage = (props) => {
|
||||
invalidMessage={invalidEmailMessage}
|
||||
>
|
||||
<Form.Label htmlFor="forgot-password-input" className="h6 mr-1">
|
||||
{intl.formatMessage(messages['logisration.forgot.password.page.email.field.label'])}
|
||||
{intl.formatMessage(messages['forgot.password.page.email.field.label'])}
|
||||
</Form.Label>
|
||||
<Input
|
||||
name="email"
|
||||
@@ -82,7 +82,7 @@ const ForgotPasswordPage = (props) => {
|
||||
onChange={e => validateEmail(e, setFieldValue)}
|
||||
/>
|
||||
<p className="mb-2">
|
||||
{intl.formatMessage(messages['logisration.forgot.password.page.email.field.help.text'])}
|
||||
{intl.formatMessage(messages['forgot.password.page.email.field.help.text'])}
|
||||
</p>
|
||||
</ValidationFormGroup>
|
||||
<LoginHelpLinks page="forgot-password" />
|
||||
@@ -91,7 +91,7 @@ const ForgotPasswordPage = (props) => {
|
||||
className="btn-primary mt-3"
|
||||
state={status}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['logisration.forgot.password.page.submit.button']),
|
||||
default: intl.formatMessage(messages['forgot.password.page.submit.button']),
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,7 @@ const RequestInProgressAlert = () => (
|
||||
<Alert variant="warning">
|
||||
<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />
|
||||
<FormattedMessage
|
||||
id="logistration.forgot.password.request.inprogress.message"
|
||||
id="forgot.password.request.inprogress.message"
|
||||
defaultMessage="Your previous request is still in progress, please try again in a few moments."
|
||||
description="A message displayed when a previous password reset request is still in progress."
|
||||
/>
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'logisration.forgot.password.page.heading': {
|
||||
id: 'logisration.forgot.password.page.heading',
|
||||
'forgot.password.page.heading': {
|
||||
id: 'forgot.password.page.heading',
|
||||
defaultMessage: 'Password assistance',
|
||||
description: 'The page heading for the forgot password page.',
|
||||
},
|
||||
'logisration.forgot.password.page.instructions': {
|
||||
id: 'logisration.forgot.password.page.instructions',
|
||||
'forgot.password.page.instructions': {
|
||||
id: 'forgot.password.page.instructions',
|
||||
defaultMessage: 'Please enter your log-in or recovery email address below and we will send you an email with instructions.',
|
||||
description: 'Instructions message for forgot password page.',
|
||||
},
|
||||
'logisration.forgot.password.page.invalid.email.message': {
|
||||
id: 'logisration.forgot.password.page.invalid.email.message',
|
||||
'forgot.password.page.invalid.email.message': {
|
||||
id: 'forgot.password.page.invalid.email.message',
|
||||
defaultMessage: "The email address you've provided isn't formatted correctly.",
|
||||
description: 'Invalid email address message for the forgot password page.',
|
||||
},
|
||||
'logisration.forgot.password.page.email.field.label': {
|
||||
id: 'logisration.forgot.password.page.email.field.label',
|
||||
'forgot.password.page.email.field.label': {
|
||||
id: 'forgot.password.page.email.field.label',
|
||||
defaultMessage: 'Email',
|
||||
description: 'Email field label for the forgot password page.',
|
||||
},
|
||||
'logisration.forgot.password.page.email.field.help.text': {
|
||||
id: 'logisration.forgot.password.page.email.field.help.text',
|
||||
'forgot.password.page.email.field.help.text': {
|
||||
id: 'forgot.password.page.email.field.help.text',
|
||||
defaultMessage: 'The email address you used to register with edX.',
|
||||
description: 'Email field help text for the forgot password page.',
|
||||
},
|
||||
'logisration.forgot.password.page.submit.button': {
|
||||
id: 'logisration.forgot.password.page.submit.button',
|
||||
'forgot.password.page.submit.button': {
|
||||
id: 'forgot.password.page.submit.button',
|
||||
defaultMessage: 'Recover my password',
|
||||
description: 'Submit button text for the forgot password page.',
|
||||
},
|
||||
'logisration.forgot.password.page.email.invalid.length.message': {
|
||||
id: 'logisration.forgot.password.page.email.invalid.length.message',
|
||||
'forgot.password.page.email.invalid.length.message': {
|
||||
id: 'forgot.password.page.email.invalid.length.message',
|
||||
defaultMessage: 'Email must have at least 3 characters.',
|
||||
description: 'Invalid email address length message for the forgot password page.',
|
||||
},
|
||||
|
||||
@@ -11,7 +11,8 @@ import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { messages as headerMessages } from '@edx/frontend-component-header';
|
||||
|
||||
import configureStore from './data/configureStore';
|
||||
import { LoginPage, RegistrationPage } from './logistration';
|
||||
import { RegistrationPage } from './register';
|
||||
import { LoginPage } from './login';
|
||||
import {
|
||||
LOGIN_PAGE, PAGE_NOT_FOUND, REGISTER_PAGE, RESET_PAGE, PASSWORD_RESET_CONFIRM,
|
||||
} from './data/constants';
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
|
||||
@import "~@edx/frontend-component-header/dist/index";
|
||||
|
||||
@import "./logistration/style";
|
||||
@import "./style";
|
||||
|
||||
@@ -17,25 +17,25 @@ const AccountActivationMessage = (props) => {
|
||||
|
||||
switch (messageType) {
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.SUCCESS: {
|
||||
heading = intl.formatMessage(messages['authn.account.activation.success.message.title']);
|
||||
activationMessage = intl.formatMessage(messages['authn.account.activation.success.message']);
|
||||
heading = intl.formatMessage(messages['account.activation.success.message.title']);
|
||||
activationMessage = intl.formatMessage(messages['account.activation.success.message']);
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.INFO: {
|
||||
activationMessage = intl.formatMessage(messages['authn.account.already.activated.message']);
|
||||
activationMessage = intl.formatMessage(messages['account.already.activated.message']);
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
|
||||
const supportLink = (
|
||||
<Alert.Link href={getConfig().ACTIVATION_EMAIL_SUPPORT_LINK}>
|
||||
{intl.formatMessage(messages['authn.account.activation.support.link'])}
|
||||
{intl.formatMessage(messages['account.activation.support.link'])}
|
||||
</Alert.Link>
|
||||
);
|
||||
|
||||
heading = intl.formatMessage(messages['authn.account.activation.error.message.title']);
|
||||
heading = intl.formatMessage(messages['account.activation.error.message.title']);
|
||||
activationMessage = (
|
||||
<FormattedMessage
|
||||
id="authn.account.activation.error.message"
|
||||
id="account.activation.error.message"
|
||||
defaultMessage="Something went wrong, please {supportLink} to resolve this issue."
|
||||
description="Account activation error message"
|
||||
values={{ supportLink }}
|
||||
@@ -24,12 +24,12 @@ const LoginFailureMessage = (props) => {
|
||||
errorList = (
|
||||
<li key="password-non-compliance">
|
||||
<FormattedMessage
|
||||
id="login.non.compliant.password.error"
|
||||
id="non.compliant.password.error"
|
||||
defaultMessage="{passwordComplaintRequirements} {lineBreak}Your current password does not meet the new security
|
||||
requirements. We just sent a password-reset message to the email address associated with this account.
|
||||
Thank you for helping us keep your data safe."
|
||||
values={{
|
||||
passwordComplaintRequirements: <strong>{intl.formatMessage(messages['logistration.non.compliant.password.title'])}</strong>,
|
||||
passwordComplaintRequirements: <strong>{intl.formatMessage(messages['non.compliant.password.title'])}</strong>,
|
||||
lineBreak: <br />,
|
||||
}}
|
||||
/>
|
||||
@@ -40,7 +40,7 @@ const LoginFailureMessage = (props) => {
|
||||
case INACTIVE_USER: {
|
||||
const supportLink = (
|
||||
<Alert.Link href={context.supportLink}>
|
||||
{intl.formatMessage(messages['logistration.contact.support.link'], { platformName: context.platformName })}
|
||||
{intl.formatMessage(messages['contact.support.link'], { platformName: context.platformName })}
|
||||
</Alert.Link>
|
||||
);
|
||||
errorList = (
|
||||
@@ -70,7 +70,7 @@ const LoginFailureMessage = (props) => {
|
||||
case INTERNAL_SERVER_ERROR:
|
||||
errorList = (
|
||||
<li key={INTERNAL_SERVER_ERROR}>
|
||||
{intl.formatMessage(messages['authn.internal.server.error.message'])}
|
||||
{intl.formatMessage(messages['internal.server.error.message'])}
|
||||
</li>
|
||||
);
|
||||
break;
|
||||
@@ -96,7 +96,7 @@ const LoginFailureMessage = (props) => {
|
||||
|
||||
return (
|
||||
<Alert id="login-failure-alert" variant="danger">
|
||||
<Alert.Heading>{intl.formatMessage(messages['logistration.login.failure.header.title'])}</Alert.Heading>
|
||||
<Alert.Heading>{intl.formatMessage(messages['login.failure.header.title'])}</Alert.Heading>
|
||||
<ul>{errorList}</ul>
|
||||
</Alert>
|
||||
);
|
||||
@@ -5,7 +5,7 @@ import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import SwitchContent from './SwitchContent';
|
||||
import SwitchContent from '../common-components/SwitchContent';
|
||||
import {
|
||||
LOGIN_PAGE,
|
||||
REGISTER_PAGE,
|
||||
@@ -24,28 +24,28 @@ const LoginHelpLinks = (props) => {
|
||||
|
||||
const forgotPasswordLink = () => (
|
||||
<a className="field-link" href={RESET_PAGE}>
|
||||
{intl.formatMessage(messages['logistration.forgot.password.link'])}
|
||||
{intl.formatMessage(messages['forgot.password.link'])}
|
||||
</a>
|
||||
);
|
||||
|
||||
const signUpLink = () => (
|
||||
<a className="field-link" href={REGISTER_PAGE}>
|
||||
{intl.formatMessage(messages['logistration.register.link'])}
|
||||
{intl.formatMessage(messages['register.link'])}
|
||||
</a>
|
||||
);
|
||||
|
||||
const loginIssueSupportURL = (config) => (config.LOGIN_ISSUE_SUPPORT_LINK
|
||||
? (
|
||||
<a className="field-link" href={config.LOGIN_ISSUE_SUPPORT_LINK}>
|
||||
{intl.formatMessage(messages['logistration.other.sign.in.issues'])}
|
||||
{intl.formatMessage(messages['other.sign.in.issues'])}
|
||||
</a>
|
||||
)
|
||||
: null);
|
||||
|
||||
const getHelpButtonMessage = () => {
|
||||
let mid = 'logistration.need.other.help.signing.in.collapsible.menu';
|
||||
let mid = 'need.other.help.signing.in.collapsible.menu';
|
||||
if (page === LOGIN_PAGE) {
|
||||
mid = 'logistration.need.help.signing.in.collapsible.menu';
|
||||
mid = 'need.help.signing.in.collapsible.menu';
|
||||
}
|
||||
|
||||
return intl.formatMessage(messages[mid]);
|
||||
@@ -10,17 +10,18 @@ import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import AccountActivationMessage from './AccountActivationMessage';
|
||||
import ConfirmationAlert from './ConfirmationAlert';
|
||||
import { getThirdPartyAuthContext, loginRequest } from './data/actions';
|
||||
import { loginErrorSelector, loginRequestSelector, thirdPartyAuthContextSelector } from './data/selectors';
|
||||
import InstitutionLogistration, { RenderInstitutionButton } from './InstitutionLogistration';
|
||||
import ConfirmationAlert from '../common-components/ConfirmationAlert';
|
||||
import { loginRequest } from './data/actions';
|
||||
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
||||
import { loginErrorSelector, loginRequestSelector } from './data/selectors';
|
||||
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
||||
import LoginHelpLinks from './LoginHelpLinks';
|
||||
import LoginFailureMessage from './LoginFailure';
|
||||
import messages from './messages';
|
||||
import SocialAuthProviders from './SocialAuthProviders';
|
||||
import ThirdPartyAuthAlert from './ThirdPartyAuthAlert';
|
||||
|
||||
import { RedirectLogistration } from '../common-components';
|
||||
import {
|
||||
RedirectLogistration, SocialAuthProviders, ThirdPartyAuthAlert, RenderInstitutionButton,
|
||||
InstitutionLogistration,
|
||||
} from '../common-components';
|
||||
import {
|
||||
DEFAULT_REDIRECT_URL, DEFAULT_STATE, LOGIN_PAGE, REGISTER_PAGE, ENTERPRISE_LOGIN_URL, PENDING_STATE,
|
||||
} from '../data/constants';
|
||||
@@ -130,7 +131,7 @@ class LoginPage extends React.Component {
|
||||
<RenderInstitutionButton
|
||||
onSubmitHandler={this.handleInstitutionLogin}
|
||||
secondaryProviders={secondaryProviders}
|
||||
buttonTitle={intl.formatMessage(messages['logistration.login.institution.login.button'])}
|
||||
buttonTitle={intl.formatMessage(messages['institution.login.button'])}
|
||||
/>
|
||||
<div className="row tpa-container">
|
||||
<SocialAuthProviders socialAuthProviders={providers} />
|
||||
@@ -156,8 +157,8 @@ class LoginPage extends React.Component {
|
||||
<InstitutionLogistration
|
||||
onSubmitHandler={this.handleInstitutionLogin}
|
||||
secondaryProviders={thirdPartyAuthContext.secondaryProviders}
|
||||
headingTitle={intl.formatMessage(messages['logistration.login.institution.login.page.title'])}
|
||||
buttonTitle={intl.formatMessage(messages['logistration.login.institution.login.page.back.button'])}
|
||||
headingTitle={intl.formatMessage(messages['institution.login.page.title'])}
|
||||
buttonTitle={intl.formatMessage(messages['institution.login.page.back.button'])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -183,24 +184,24 @@ class LoginPage extends React.Component {
|
||||
{this.props.forgotPassword.status === 'complete' ? <ConfirmationAlert email={this.props.forgotPassword.email} /> : null}
|
||||
<div className="d-flex flex-row">
|
||||
<p>
|
||||
{intl.formatMessage(messages['logistration.first.time.here'])}
|
||||
{intl.formatMessage(messages['first.time.here'])}
|
||||
<Hyperlink className="ml-1" destination={REGISTER_PAGE}>
|
||||
{intl.formatMessage(messages['logistration.create.an.account'])}.
|
||||
{intl.formatMessage(messages['create.an.account'])}.
|
||||
</Hyperlink>
|
||||
</p>
|
||||
</div>
|
||||
<h2 className="text-left mt-2 mb-3">
|
||||
{intl.formatMessage(messages['logistration.sign.in.heading'])}
|
||||
{intl.formatMessage(messages['sign.in.heading'])}
|
||||
</h2>
|
||||
<Form className="m-0">
|
||||
<ValidationFormGroup
|
||||
for="email"
|
||||
invalid={this.state.errors.email !== ''}
|
||||
invalidMessage={intl.formatMessage(messages['logistration.email.format.validation.message'])}
|
||||
invalidMessage={intl.formatMessage(messages['email.format.validation.message'])}
|
||||
className="mb-0 w-100"
|
||||
>
|
||||
<Form.Label htmlFor="loginEmail" className="h6 mr-1">
|
||||
{intl.formatMessage(messages['logistration.login.page.email.label'])}
|
||||
{intl.formatMessage(messages['email.label'])}
|
||||
</Form.Label>
|
||||
<Input
|
||||
name="email"
|
||||
@@ -210,16 +211,16 @@ class LoginPage extends React.Component {
|
||||
value={this.state.email}
|
||||
onChange={e => this.handleOnChange(e)}
|
||||
/>
|
||||
<p className="mb-4">{intl.formatMessage(messages['logistration.email.help.message'])}</p>
|
||||
<p className="mb-4">{intl.formatMessage(messages['email.help.message'])}</p>
|
||||
</ValidationFormGroup>
|
||||
<ValidationFormGroup
|
||||
for="password"
|
||||
invalid={this.state.errors.password !== ''}
|
||||
invalidMessage={intl.formatMessage(messages['logistration.login.page.password.validation.message'])}
|
||||
invalidMessage={intl.formatMessage(messages['password.validation.message'])}
|
||||
className="mb-0 w-100"
|
||||
>
|
||||
<Form.Label htmlFor="loginPassword" className="h6 mr-1">
|
||||
{intl.formatMessage(messages['logistration.password'])}
|
||||
{intl.formatMessage(messages['password.label'])}
|
||||
</Form.Label>
|
||||
<Input
|
||||
name="password"
|
||||
@@ -231,14 +232,14 @@ class LoginPage extends React.Component {
|
||||
</ValidationFormGroup>
|
||||
<LoginHelpLinks page={LOGIN_PAGE} />
|
||||
<Hyperlink className="field-link mt-0 mb-3" destination={this.getEnterPriseLoginURL()}>
|
||||
{intl.formatMessage(messages['logistration.enterprise.login.link.text'])}
|
||||
{intl.formatMessage(messages['enterprise.login.link.text'])}
|
||||
</Hyperlink>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['logistration.sign.in.button']),
|
||||
default: intl.formatMessage(messages['sign.in.button']),
|
||||
}}
|
||||
onClick={this.handleSubmit}
|
||||
/>
|
||||
@@ -246,7 +247,7 @@ class LoginPage extends React.Component {
|
||||
{(providers.length || secondaryProviders.length || thirdPartyAuthApiStatus === PENDING_STATE)
|
||||
&& !currentProvider ? (
|
||||
<div className="mb-4 pt-10">
|
||||
<h4>{intl.formatMessage(messages['logistration.or.sign.in.with'])}</h4>
|
||||
<h4>{intl.formatMessage(messages['or.sign.in.with'])}</h4>
|
||||
</div>
|
||||
) : null}
|
||||
{this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)}
|
||||
@@ -302,8 +303,8 @@ const mapStateToProps = state => {
|
||||
const thirdPartyAuthContext = thirdPartyAuthContextSelector(state);
|
||||
const loginError = loginErrorSelector(state);
|
||||
return {
|
||||
submitState: state.logistration.submitState,
|
||||
thirdPartyAuthApiStatus: state.logistration.thirdPartyAuthApiStatus,
|
||||
submitState: state.login.submitState,
|
||||
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
||||
forgotPassword,
|
||||
loginError,
|
||||
loginResult,
|
||||
23
src/login/data/actions.js
Normal file
23
src/login/data/actions.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const LOGIN_REQUEST = new AsyncActionType('LOGIN', 'REQUEST');
|
||||
|
||||
// Login
|
||||
export const loginRequest = creds => ({
|
||||
type: LOGIN_REQUEST.BASE,
|
||||
payload: { creds },
|
||||
});
|
||||
|
||||
export const loginRequestBegin = () => ({
|
||||
type: LOGIN_REQUEST.BEGIN,
|
||||
});
|
||||
|
||||
export const loginRequestSuccess = (redirectUrl, success) => ({
|
||||
type: LOGIN_REQUEST.SUCCESS,
|
||||
payload: { redirectUrl, success },
|
||||
});
|
||||
|
||||
export const loginRequestFailure = (loginError) => ({
|
||||
type: LOGIN_REQUEST.FAILURE,
|
||||
payload: { loginError },
|
||||
});
|
||||
33
src/login/data/reducers.js
Normal file
33
src/login/data/reducers.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { LOGIN_REQUEST } from './actions';
|
||||
|
||||
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
|
||||
|
||||
export const defaultState = {
|
||||
loginError: null,
|
||||
loginResult: {},
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case LOGIN_REQUEST.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
submitState: PENDING_STATE,
|
||||
};
|
||||
case LOGIN_REQUEST.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
loginResult: action.payload,
|
||||
};
|
||||
case LOGIN_REQUEST.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loginError: action.payload.loginError,
|
||||
submitState: DEFAULT_STATE,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
||||
45
src/login/data/sagas.js
Normal file
45
src/login/data/sagas.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { INTERNAL_SERVER_ERROR } from './constants';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
LOGIN_REQUEST,
|
||||
loginRequestBegin,
|
||||
loginRequestFailure,
|
||||
loginRequestSuccess,
|
||||
} from './actions';
|
||||
|
||||
// Services
|
||||
import {
|
||||
loginRequest,
|
||||
} from './service';
|
||||
|
||||
export function* handleLoginRequest(action) {
|
||||
try {
|
||||
yield put(loginRequestBegin());
|
||||
|
||||
const { redirectUrl, success } = yield call(loginRequest, action.payload.creds);
|
||||
|
||||
yield put(loginRequestSuccess(
|
||||
redirectUrl,
|
||||
success,
|
||||
));
|
||||
} catch (e) {
|
||||
const statusCodes = [400];
|
||||
if (e.response) {
|
||||
if (statusCodes.includes(e.response.status)) {
|
||||
yield put(loginRequestFailure(camelCaseObject(e.response.data)));
|
||||
} else {
|
||||
yield put(loginRequestFailure(camelCaseObject({ errorCode: INTERNAL_SERVER_ERROR })));
|
||||
}
|
||||
}
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export default function* saga() {
|
||||
yield takeEvery(LOGIN_REQUEST.BASE, handleLoginRequest);
|
||||
}
|
||||
15
src/login/data/selectors.js
Normal file
15
src/login/data/selectors.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
export const storeName = 'login';
|
||||
|
||||
export const loginSelector = state => ({ ...state[storeName] });
|
||||
|
||||
export const loginRequestSelector = createSelector(
|
||||
loginSelector,
|
||||
login => login.loginResult,
|
||||
);
|
||||
|
||||
export const loginErrorSelector = createSelector(
|
||||
loginSelector,
|
||||
login => login.loginError,
|
||||
);
|
||||
26
src/login/data/service.js
Normal file
26
src/login/data/service.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import querystring from 'querystring';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function loginRequest(creds) {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
isPublic: true,
|
||||
};
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(
|
||||
`${getConfig().LMS_BASE_URL}/user_api/v1/account/login_session/`,
|
||||
querystring.stringify(creds),
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
throw (e);
|
||||
});
|
||||
|
||||
return {
|
||||
redirectUrl: data.redirect_url || `${getConfig().LMS_BASE_URL}/dashboard`,
|
||||
success: data.success || false,
|
||||
};
|
||||
}
|
||||
99
src/login/data/tests/sagas.test.js
Normal file
99
src/login/data/tests/sagas.test.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { runSaga } from 'redux-saga';
|
||||
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
|
||||
import * as actions from '../actions';
|
||||
import { handleLoginRequest } from '../sagas';
|
||||
import * as api from '../service';
|
||||
import initializeMockLogging from '../../../setupTest';
|
||||
|
||||
const { loggingService } = initializeMockLogging();
|
||||
|
||||
describe('handleLoginRequest', () => {
|
||||
const params = {
|
||||
payload: {
|
||||
formData: {
|
||||
email: 'test@test.com',
|
||||
password: 'test-password',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
loggingService.logError.mockReset();
|
||||
});
|
||||
|
||||
it('should call service and dispatch success action', async () => {
|
||||
const data = { redirectUrl: '/dashboard', success: true };
|
||||
const loginRequest = jest.spyOn(api, 'loginRequest')
|
||||
.mockImplementation(() => Promise.resolve(data));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
handleLoginRequest,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(loginRequest).toHaveBeenCalledTimes(1);
|
||||
expect(dispatched).toEqual([
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestSuccess(data.redirectUrl, data.success),
|
||||
]);
|
||||
loginRequest.mockClear();
|
||||
});
|
||||
|
||||
it('should call service and dispatch error action', async () => {
|
||||
const loginErrorResponse = {
|
||||
response: {
|
||||
status: 400,
|
||||
data: {
|
||||
login_error: 'something went wrong',
|
||||
},
|
||||
},
|
||||
};
|
||||
const loginRequest = jest.spyOn(api, 'loginRequest')
|
||||
.mockImplementation(() => Promise.reject(loginErrorResponse));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
handleLoginRequest,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(loginRequest).toHaveBeenCalledTimes(1);
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(dispatched).toEqual([
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestFailure(camelCaseObject(loginErrorResponse.response.data)),
|
||||
]);
|
||||
loginRequest.mockClear();
|
||||
});
|
||||
|
||||
it('should handle 500 error code', async () => {
|
||||
const loginErrorResponse = {
|
||||
response: {
|
||||
status: 500,
|
||||
data: {
|
||||
errorCode: 'internal-server-error',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const loginRequest = jest.spyOn(api, 'loginRequest').mockImplementation(() => Promise.reject(loginErrorResponse));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
handleLoginRequest,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(dispatched).toEqual([
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestFailure(camelCaseObject(loginErrorResponse.response.data)),
|
||||
]);
|
||||
loginRequest.mockClear();
|
||||
});
|
||||
});
|
||||
4
src/login/index.js
Normal file
4
src/login/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as LoginPage } from './LoginPage';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/selectors';
|
||||
162
src/login/messages.jsx
Normal file
162
src/login/messages.jsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'sign.in.button': {
|
||||
id: 'sign.in.button',
|
||||
defaultMessage: 'Sign in',
|
||||
description: 'Button label that appears on login page',
|
||||
},
|
||||
'need.help.signing.in.collapsible.menu': {
|
||||
id: 'need.help.signing.in.collapsible.menu',
|
||||
defaultMessage: 'Need help signing in?',
|
||||
description: 'A button for collapsible need help signing in menu on login page',
|
||||
},
|
||||
'forgot.password.link': {
|
||||
id: 'forgot.password.link',
|
||||
defaultMessage: 'Forgot my password',
|
||||
description: 'Forgot password link',
|
||||
},
|
||||
'other.sign.in.issues': {
|
||||
id: 'other.sign.in.issues',
|
||||
defaultMessage: 'Other sign-in issues',
|
||||
description: 'A link that redirects to sign-in issues help',
|
||||
},
|
||||
'need.other.help.signing.in.collapsible.menu': {
|
||||
id: 'need.other.help.signing.in.collapsible.menu',
|
||||
defaultMessage: 'Need other help signing in?',
|
||||
description: 'A button for collapsible need other help signing in menu on forgot password page',
|
||||
},
|
||||
'institution.login.button': {
|
||||
id: 'institution.login.button',
|
||||
defaultMessage: 'Use my university info',
|
||||
description: 'shows institutions list',
|
||||
},
|
||||
'institution.login.page.title': {
|
||||
id: 'institution.login.page.title',
|
||||
defaultMessage: 'Sign in with Institution/Campus Credentials',
|
||||
description: 'Heading of institution page',
|
||||
},
|
||||
'institution.login.page.sub.heading': {
|
||||
id: 'institution.login.page.sub.heading',
|
||||
defaultMessage: 'Choose your institution from the list below:',
|
||||
description: 'Heading of the institutions list',
|
||||
},
|
||||
'institution.login.page.back.button': {
|
||||
id: 'institution.login.page.back.button',
|
||||
defaultMessage: 'Back to sign in',
|
||||
description: 'return to login page',
|
||||
},
|
||||
'create.an.account': {
|
||||
id: 'create.an.account',
|
||||
defaultMessage: 'Create an Account',
|
||||
description: 'Message on button to return to register page',
|
||||
},
|
||||
'institution.login.sign.in': {
|
||||
id: 'institution.login.sign.in',
|
||||
defaultMessage: 'Sign In',
|
||||
description: 'Sign In text',
|
||||
},
|
||||
'or.sign.in.with': {
|
||||
id: 'or.sign.in.with',
|
||||
defaultMessage: 'or sign in with',
|
||||
description: 'gives hint about other sign in options',
|
||||
},
|
||||
'non.compliant.password.title': {
|
||||
id: 'non.compliant.password.title',
|
||||
defaultMessage: 'We recently changed our password requirements',
|
||||
description: 'A title that appears in bold before error message for non-compliant password',
|
||||
},
|
||||
'first.time.here': {
|
||||
id: 'first.time.here',
|
||||
defaultMessage: 'First time here?',
|
||||
description: 'A question that appears before sign up link',
|
||||
},
|
||||
'email.label': {
|
||||
id: 'email.label',
|
||||
defaultMessage: 'Email',
|
||||
description: 'Label that appears above email field',
|
||||
},
|
||||
'email.help.message': {
|
||||
id: 'email.help.message',
|
||||
defaultMessage: 'The email address you used to register with edX.',
|
||||
description: 'Message that appears below email field on login page',
|
||||
},
|
||||
'enterprise.login.link.text': {
|
||||
id: 'enterprise.login.link.text',
|
||||
defaultMessage: 'Sign in with your company or school',
|
||||
description: 'Company or school login link text.',
|
||||
},
|
||||
'email.format.validation.message': {
|
||||
id: 'email.format.validation.message',
|
||||
defaultMessage: 'The email address you\'ve provided isn\'t formatted correctly.',
|
||||
description: 'Validation message that appears when email address format is incorrect',
|
||||
},
|
||||
'password.validation.message': {
|
||||
id: 'password.validation.message',
|
||||
defaultMessage: 'Please enter your password.',
|
||||
description: 'Validation message that appears when password is empty',
|
||||
},
|
||||
'password.label': {
|
||||
id: 'password.label',
|
||||
defaultMessage: 'Password',
|
||||
description: 'Text that appears above password field or as a placeholder',
|
||||
},
|
||||
'register.link': {
|
||||
id: 'register.link',
|
||||
defaultMessage: 'Create an account',
|
||||
description: 'Register page link',
|
||||
},
|
||||
'sign.in.heading': {
|
||||
id: 'sign.in.heading',
|
||||
defaultMessage: 'Sign In',
|
||||
description: 'Sign In text',
|
||||
},
|
||||
// Account Activation Strings
|
||||
'account.activation.success.message.title': {
|
||||
id: 'account.activation.success.message.title',
|
||||
defaultMessage: 'Success! You have activated your account.',
|
||||
description: 'Account Activation success message title',
|
||||
},
|
||||
'account.activation.success.message': {
|
||||
id: 'account.activation.success.message',
|
||||
defaultMessage: 'You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign In to continue.',
|
||||
description: 'Message show to learners when their account has been activated successfully',
|
||||
},
|
||||
'account.already.activated.message': {
|
||||
id: 'account.already.activated.message',
|
||||
defaultMessage: 'This account has already been activated.',
|
||||
description: 'Message shown when learner account has already been activated',
|
||||
},
|
||||
'account.activation.error.message.title': {
|
||||
id: 'account.activation.error.message.title',
|
||||
defaultMessage: 'Your account could not be activated',
|
||||
description: 'Account Activation error message title',
|
||||
},
|
||||
'account.activation.support.link': {
|
||||
id: 'account.activation.support.link',
|
||||
defaultMessage: 'contact support',
|
||||
description: 'Link text used in account activation error message to go to learner help center',
|
||||
},
|
||||
'internal.server.error.message': {
|
||||
id: 'internal.server.error.message',
|
||||
defaultMessage: 'An error has occurred. Try refreshing the page, or check your Internet connection.',
|
||||
description: 'Error message that appears when server responds with 500 error code',
|
||||
},
|
||||
'login.rate.limit.reached.message': {
|
||||
id: 'login.rate.limit.reached.message',
|
||||
defaultMessage: 'Too many failed login attempts. Try again later.',
|
||||
description: 'Error message that appears when an anonymous user has made too many failed login attempts',
|
||||
},
|
||||
'login.failure.header.title': {
|
||||
id: 'login.failure.header.title',
|
||||
defaultMessage: 'We couldn\'t sign you in.',
|
||||
description: 'Login failure header message.',
|
||||
},
|
||||
'contact.support.link': {
|
||||
id: 'contact.support.link',
|
||||
defaultMessage: 'contact {platformName} Support',
|
||||
description: 'Link text used in inactive user error message to go to learner help center',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -7,7 +7,7 @@ import configureStore from 'redux-mock-store';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import LoginPage from '../LoginPage';
|
||||
import { RenderInstitutionButton } from '../InstitutionLogistration';
|
||||
import { RenderInstitutionButton } from '../../common-components';
|
||||
import { PENDING_STATE } from '../../data/constants';
|
||||
|
||||
const IntlLoginPage = injectIntl(LoginPage);
|
||||
@@ -16,9 +16,13 @@ const mockStore = configureStore();
|
||||
describe('LoginPage', () => {
|
||||
const initialState = {
|
||||
forgotPassword: { status: null },
|
||||
logistration: {
|
||||
login: {
|
||||
forgotPassword: { status: null },
|
||||
loginResult: { success: false, redirectUrl: '' },
|
||||
response_error: null,
|
||||
},
|
||||
commonComponents: {
|
||||
thirdPartyAuthApiStatus: null,
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
@@ -68,8 +72,8 @@ describe('LoginPage', () => {
|
||||
it('should match pending button state snapshot', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
login: {
|
||||
...initialState.login,
|
||||
submitState: PENDING_STATE,
|
||||
},
|
||||
});
|
||||
@@ -92,10 +96,10 @@ describe('LoginPage', () => {
|
||||
it('should match TPA provider snapshot', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [appleProvider],
|
||||
},
|
||||
},
|
||||
@@ -108,8 +112,8 @@ describe('LoginPage', () => {
|
||||
it('should show error message', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginError: { value: 'Email or password is incorrect.' },
|
||||
},
|
||||
});
|
||||
@@ -156,8 +160,8 @@ describe('LoginPage', () => {
|
||||
const dasboardUrl = 'http://test.com/testing-dashboard/';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginResult: {
|
||||
success: true,
|
||||
redirectUrl: dasboardUrl,
|
||||
@@ -174,14 +178,17 @@ describe('LoginPage', () => {
|
||||
const authCompleteUrl = '/auth/complete/google-oauth2/';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginResult: {
|
||||
success: true,
|
||||
redirectUrl: '',
|
||||
},
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
finishAuthUrl: authCompleteUrl,
|
||||
},
|
||||
},
|
||||
@@ -197,10 +204,10 @@ describe('LoginPage', () => {
|
||||
const loginUrl = '/auth/login/apple-id/?auth_entry=login&next=/dashboard';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [{
|
||||
...appleProvider,
|
||||
loginUrl,
|
||||
@@ -221,10 +228,10 @@ describe('LoginPage', () => {
|
||||
it('should match third party auth alert', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
currentProvider: 'Apple',
|
||||
platformName: 'edX',
|
||||
},
|
||||
@@ -241,10 +248,10 @@ describe('LoginPage', () => {
|
||||
it('should display institution login button', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
secondaryProviders: [secondaryProviders],
|
||||
},
|
||||
},
|
||||
@@ -261,10 +268,10 @@ describe('LoginPage', () => {
|
||||
it('should display institution login page', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
secondaryProviders: [secondaryProviders],
|
||||
},
|
||||
},
|
||||
@@ -345,25 +345,6 @@ exports[`LoginPage should match default section snapshot 1`] = `
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
<div
|
||||
className="mb-4 pt-10"
|
||||
>
|
||||
<h4>
|
||||
or sign in with
|
||||
</h4>
|
||||
</div>
|
||||
<span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -560,25 +541,6 @@ exports[`LoginPage should match forget password alert message snapshot 1`] = `
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
<div
|
||||
className="mb-4 pt-10"
|
||||
>
|
||||
<h4>
|
||||
or sign in with
|
||||
</h4>
|
||||
</div>
|
||||
<span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -746,25 +708,6 @@ exports[`LoginPage should match pending button state snapshot 1`] = `
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
<div
|
||||
className="mb-4 pt-10"
|
||||
>
|
||||
<h4>
|
||||
or sign in with
|
||||
</h4>
|
||||
</div>
|
||||
<span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -939,25 +882,6 @@ exports[`LoginPage should show error message 1`] = `
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
<div
|
||||
className="mb-4 pt-10"
|
||||
>
|
||||
<h4>
|
||||
or sign in with
|
||||
</h4>
|
||||
</div>
|
||||
<span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,25 +0,0 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
export const storeName = 'logistration';
|
||||
|
||||
export const logistrationSelector = state => ({ ...state[storeName] });
|
||||
|
||||
export const loginRequestSelector = createSelector(
|
||||
logistrationSelector,
|
||||
logistration => logistration.loginResult,
|
||||
);
|
||||
|
||||
export const loginErrorSelector = createSelector(
|
||||
logistrationSelector,
|
||||
logistration => logistration.loginError,
|
||||
);
|
||||
|
||||
export const registrationRequestSelector = createSelector(
|
||||
logistrationSelector,
|
||||
logistration => logistration.registrationResult,
|
||||
);
|
||||
|
||||
export const thirdPartyAuthContextSelector = createSelector(
|
||||
logistrationSelector,
|
||||
logistration => logistration.thirdPartyAuthContext,
|
||||
);
|
||||
@@ -1,285 +0,0 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'logistration.sign.in.button': {
|
||||
id: 'logistration.sign.in.button',
|
||||
defaultMessage: 'Sign in',
|
||||
description: 'Button label that appears on login page',
|
||||
},
|
||||
'logistration.create.account.button': {
|
||||
id: 'logistration.create.account.button',
|
||||
defaultMessage: 'Create Account',
|
||||
description: 'Button label that appears on register page',
|
||||
},
|
||||
'logistration.need.help.signing.in.collapsible.menu': {
|
||||
id: 'logistration.need.help.signing.in.collapsible.menu',
|
||||
defaultMessage: 'Need help signing in?',
|
||||
description: 'A button for collapsible need help signing in menu on login page',
|
||||
},
|
||||
'logistration.need.other.help.signing.in.collapsible.menu': {
|
||||
id: 'logistration.need.other.help.signing.in.collapsible.menu',
|
||||
defaultMessage: 'Need other help signing in?',
|
||||
description: 'A button for collapsible need other help signing in menu on forgot password page',
|
||||
},
|
||||
'logistration.register.link': {
|
||||
id: 'logistration.register.link',
|
||||
defaultMessage: 'Create an account',
|
||||
description: 'Register page link',
|
||||
},
|
||||
'logistration.forgot.password.link': {
|
||||
id: 'logistration.forgot.password.link',
|
||||
defaultMessage: 'Forgot my password',
|
||||
description: 'Forgot password link',
|
||||
},
|
||||
'logistration.already.have.an.edx.account': {
|
||||
id: 'logistration.already.have.an.edx.account',
|
||||
defaultMessage: 'Already have an edX account?',
|
||||
description: 'A message on registration page asking the user if he already has an edX account',
|
||||
},
|
||||
'logistration.sign.in.hyperlink': {
|
||||
id: 'logistration.sign.in.hyperlink',
|
||||
defaultMessage: ' Sign in.',
|
||||
description: 'Text for the hyperlink that takes user to login page',
|
||||
},
|
||||
'logistration.create.an.account.using': {
|
||||
id: 'logistration.create.an.account.using',
|
||||
defaultMessage: 'Create an account using',
|
||||
description: 'A message that appears before social auth buttons',
|
||||
},
|
||||
'logistration.create.a.new.one.here': {
|
||||
id: 'logistration.create.a.new.one.here',
|
||||
defaultMessage: 'or create a new one here',
|
||||
description: 'Text that appears after social auth buttons and before the registration form',
|
||||
},
|
||||
'logistration.other.sign.in.issues': {
|
||||
id: 'logistration.other.sign.in.issues',
|
||||
defaultMessage: 'Other sign-in issues',
|
||||
description: 'A link that redirects to sign-in issues help',
|
||||
},
|
||||
'logistration.login.institution.login.button': {
|
||||
id: 'logistration.login.institution.login.button',
|
||||
defaultMessage: 'Use my university info',
|
||||
description: 'shows institutions list',
|
||||
},
|
||||
'logistration.login.institution.login.page.title': {
|
||||
id: 'logistration.login.institution.login.page.title',
|
||||
defaultMessage: 'Sign in with Institution/Campus Credentials',
|
||||
description: 'Heading of institution page',
|
||||
},
|
||||
'logistration.institution.login.page.sub.heading': {
|
||||
id: 'logistration.institution.login.page.sub.heading',
|
||||
defaultMessage: 'Choose your institution from the list below:',
|
||||
description: 'Heading of the institutions list',
|
||||
},
|
||||
'logistration.login.institution.login.page.back.button': {
|
||||
id: 'logistration.login.institution.login.page.back.button',
|
||||
defaultMessage: 'Back to sign in',
|
||||
description: 'return to login page',
|
||||
},
|
||||
'logistration.register.institution.login.button': {
|
||||
id: 'logistration.register.institution.login.button',
|
||||
defaultMessage: 'Use my institution/campus credentials',
|
||||
description: 'shows institutions list',
|
||||
},
|
||||
'logistration.register.institution.login.page.title': {
|
||||
id: 'logistration.register.institution.login.page.title',
|
||||
defaultMessage: 'Register with Institution/Campus Credentials',
|
||||
description: 'Heading of institution page',
|
||||
},
|
||||
'logistration.create.an.account': {
|
||||
id: 'logistration.create.an.account',
|
||||
defaultMessage: 'Create an Account',
|
||||
description: 'Message on button to return to register page',
|
||||
},
|
||||
'logistration.sign.in.heading': {
|
||||
id: 'logistration.sign.in.heading',
|
||||
defaultMessage: 'Sign In',
|
||||
description: 'Sign In text',
|
||||
},
|
||||
'logistration.or.sign.in.with': {
|
||||
id: 'logistration.or.sign.in.with',
|
||||
defaultMessage: 'or sign in with',
|
||||
description: 'gives hint about other sign in options',
|
||||
},
|
||||
'logistration.non.compliant.password.title': {
|
||||
id: 'logistration.non.compliant.password.title',
|
||||
defaultMessage: 'We recently changed our password requirements',
|
||||
description: 'A title that appears in bold before error message for non-compliant password',
|
||||
},
|
||||
'logistration.first.time.here': {
|
||||
id: 'logistration.first.time.here',
|
||||
defaultMessage: 'First time here?',
|
||||
description: 'A question that appears before sign up link',
|
||||
},
|
||||
'logistration.login.page.email.label': {
|
||||
id: 'logistration.login.page.email.label',
|
||||
defaultMessage: 'Email',
|
||||
description: 'Label that appears above email field',
|
||||
},
|
||||
'logistration.register.page.email.label': {
|
||||
id: 'logistration.register.page.email.label',
|
||||
defaultMessage: 'Email (required)',
|
||||
description: 'Label that appears above email field on register page',
|
||||
},
|
||||
'logistration.email.format.validation.message': {
|
||||
id: 'logistration.email.format.validation.message',
|
||||
defaultMessage: 'The email address you\'ve provided isn\'t formatted correctly.',
|
||||
description: 'Validation message that appears when email address format is incorrect',
|
||||
},
|
||||
'logistration.email.validation.message': {
|
||||
id: 'logistration.email.validation.message',
|
||||
defaultMessage: 'Please enter your Email.',
|
||||
description: 'Validation message that appears when email address is empty',
|
||||
},
|
||||
'logistration.email.ratelimit.less.chars.validation.message': {
|
||||
id: 'logistration.email.ratelimit.less.chars.validation.message',
|
||||
defaultMessage: 'Email must have 3 characters.',
|
||||
description: 'Validation message that appears when email address is less than 3 characters',
|
||||
},
|
||||
'logistration.email.ratelimit.incorrect.format.validation.message': {
|
||||
id: 'logistration.email.ratelimit.incorrect.format.validation.message',
|
||||
defaultMessage: 'The email address you provided isn\'t formatted correctly.',
|
||||
description: 'Validation message that appears when email address is not formatted correctly with no backend validations available.',
|
||||
},
|
||||
'logistration.email.ratelimit.password.validation.message': {
|
||||
id: 'logistration.email.ratelimit.password.validation.message',
|
||||
defaultMessage: 'Your password must contain at least 8 characters',
|
||||
description: 'Validation message that appears when password is not formatted correctly with no backend validations available.',
|
||||
},
|
||||
'logistration.email.help.message': {
|
||||
id: 'logistration.email.help.message',
|
||||
defaultMessage: 'The email address you used to register with edX.',
|
||||
description: 'Message that appears below email field on login page',
|
||||
},
|
||||
'logistration.password': {
|
||||
id: 'logistration.password',
|
||||
defaultMessage: 'Password',
|
||||
description: 'Text that appears above password field or as a placeholder',
|
||||
},
|
||||
'logistration.password.label': {
|
||||
id: 'logistration.password.label',
|
||||
defaultMessage: 'Password (required)',
|
||||
description: 'Label that appears above password field',
|
||||
},
|
||||
'logistration.login.page.password.validation.message': {
|
||||
id: 'logistration.login.page.password.validation.message',
|
||||
defaultMessage: 'Please enter your password.',
|
||||
description: 'Validation message that appears when password is empty',
|
||||
},
|
||||
'logistration.register.page.password.validation.message': {
|
||||
id: 'logistration.register.page.password.validation.message',
|
||||
defaultMessage: 'Please enter your Password.',
|
||||
description: 'Validation message that appears when password is non compliant with edX requirement',
|
||||
},
|
||||
'logistration.fullname.label': {
|
||||
id: 'logistration.fullname.label',
|
||||
defaultMessage: 'Full Name (required)',
|
||||
description: 'Label that appears above fullname field',
|
||||
},
|
||||
'logistration.fullname.validation.message': {
|
||||
id: 'logistration.fullname.validation.message',
|
||||
defaultMessage: 'Please enter your Full Name.',
|
||||
description: 'Validation message that appears when fullname is empty',
|
||||
},
|
||||
'logistration.username.label': {
|
||||
id: 'logistration.username.label',
|
||||
defaultMessage: 'Public Username (required)',
|
||||
description: 'Label that appears above username field',
|
||||
},
|
||||
'logistration.username.validation.message': {
|
||||
id: 'logistration.username.validation.message',
|
||||
defaultMessage: 'Please enter your Public Username.',
|
||||
description: 'Validation message that appears when username is invalid',
|
||||
},
|
||||
'logistration.username.ratelimit.less.chars.message': {
|
||||
id: 'logistration.username.ratelimit.less.chars.message',
|
||||
defaultMessage: 'Public Username must have atleast 2 characters.',
|
||||
description: 'Validation message that appears when username is less than 2 characters and with no backend validations available.',
|
||||
},
|
||||
'logistration.country.validation.message': {
|
||||
id: 'logistration.country.validation.message',
|
||||
defaultMessage: 'Select your country or region of residence.',
|
||||
description: 'Validation message that appears when country is not selected',
|
||||
},
|
||||
'logistration.support.education.research': {
|
||||
id: 'logistration.support.education.research',
|
||||
defaultMessage: 'Support education research by providing additional information',
|
||||
description: 'Text for a checkbox to ask user for if they are willing to provide extra information for education research',
|
||||
},
|
||||
'logistration.register.optional.label': {
|
||||
id: 'logistration.register.optional.label',
|
||||
defaultMessage: '(optional)',
|
||||
description: 'Text that appears with optional field labels',
|
||||
},
|
||||
'logistration.enterprise.login.link.text': {
|
||||
id: 'logistration.enterprise.login.link.text',
|
||||
defaultMessage: 'Sign in with your company or school',
|
||||
description: 'Company or school login link text.',
|
||||
},
|
||||
// Account Activation Strings
|
||||
'authn.account.activation.success.message.title': {
|
||||
id: 'authn.account.activation.success.message.title',
|
||||
defaultMessage: 'Success! You have activated your account.',
|
||||
description: 'Account Activation success message title',
|
||||
},
|
||||
'authn.account.activation.success.message': {
|
||||
id: 'authn.account.activation.success.message',
|
||||
defaultMessage: 'You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign In to continue.',
|
||||
description: 'Message show to learners when their account has been activated successfully',
|
||||
},
|
||||
'authn.account.already.activated.message': {
|
||||
id: 'authn.account.already.activated.message',
|
||||
defaultMessage: 'This account has already been activated.',
|
||||
description: 'Message shown when learner account has already been activated',
|
||||
},
|
||||
'authn.account.activation.error.message.title': {
|
||||
id: 'authn.account.activation.error.message.title',
|
||||
defaultMessage: 'Your account could not be activated',
|
||||
description: 'Account Activation error message title',
|
||||
},
|
||||
'authn.account.activation.support.link': {
|
||||
id: 'authn.account.activation.support.link',
|
||||
defaultMessage: 'contact support',
|
||||
description: 'Link text used in account activation error message to go to learner help center',
|
||||
},
|
||||
// Confirmation Alert Message
|
||||
'authn.forgot.password.confirmation.title': {
|
||||
id: 'authn.forgot.password.confirmation.title',
|
||||
defaultMessage: 'Check Your Email',
|
||||
description: 'Forgot password confirmation message title',
|
||||
},
|
||||
'authn.forgot.password.confirmation.support.link': {
|
||||
id: 'authn.forgot.password.confirmation.support.link',
|
||||
defaultMessage: 'contact technical support',
|
||||
description: 'Technical support link text',
|
||||
},
|
||||
'authn.forgot.password.confirmation.info': {
|
||||
id: 'authn.forgot.password.confirmation.info',
|
||||
defaultMessage: 'If you do not receive a password reset message after 1 minute, verify that you entered the correct '
|
||||
+ 'email address, or check your spam folder.',
|
||||
description: 'Part of message that appears after user requests password change',
|
||||
},
|
||||
// Login Failure Messages
|
||||
'logistration.login.failure.header.title': {
|
||||
id: 'logistration.login.failure.header.title',
|
||||
defaultMessage: 'We couldn\'t sign you in.',
|
||||
description: 'Login failure header message.',
|
||||
},
|
||||
'logistration.contact.support.link': {
|
||||
id: 'logistration.contact.support.link',
|
||||
defaultMessage: 'contact {platformName} Support',
|
||||
description: 'Link text used in inactive user error message to go to learner help center',
|
||||
},
|
||||
'login.rate.limit.reached.message': {
|
||||
id: 'login.rate.limit.reached.message',
|
||||
defaultMessage: 'Too many failed login attempts. Try again later.',
|
||||
description: 'Error message that appears when an anonymous user has made too many failed login attempts',
|
||||
},
|
||||
'authn.internal.server.error.message': {
|
||||
id: 'authn.internal.server.error.message',
|
||||
defaultMessage: 'An error has occurred. Try refreshing the page, or check your Internet connection.',
|
||||
description: 'Error message that appears when server responds with 500 error code',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -32,7 +32,7 @@ const RegistrationFailureMessage = (props) => {
|
||||
<Alert variant="danger">
|
||||
<Alert.Heading className="text-danger">
|
||||
<FormattedMessage
|
||||
id="logistration.registration.request.failure.header.description.message"
|
||||
id="registration.request.failure.header.description.message"
|
||||
defaultMessage="We couldn't create your account."
|
||||
description="error message when registration failure."
|
||||
/>
|
||||
@@ -15,14 +15,18 @@ import {
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
||||
import {
|
||||
getThirdPartyAuthContext,
|
||||
registerNewUser,
|
||||
fetchRegistrationForm,
|
||||
fetchRealtimeValidations,
|
||||
} from './data/actions';
|
||||
import { registrationRequestSelector, thirdPartyAuthContextSelector } from './data/selectors';
|
||||
import { RedirectLogistration } from '../common-components';
|
||||
import { registrationRequestSelector } from './data/selectors';
|
||||
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
||||
import {
|
||||
RedirectLogistration, SocialAuthProviders, ThirdPartyAuthAlert, RenderInstitutionButton,
|
||||
InstitutionLogistration,
|
||||
} from '../common-components';
|
||||
import RegistrationFailure from './RegistrationFailure';
|
||||
import {
|
||||
DEFAULT_REDIRECT_URL,
|
||||
@@ -34,9 +38,6 @@ import {
|
||||
REGISTRATION_OPTIONAL_MAP,
|
||||
REGISTRATION_EXTRA_FIELDS,
|
||||
} from '../data/constants';
|
||||
import SocialAuthProviders from './SocialAuthProviders';
|
||||
import ThirdPartyAuthAlert from './ThirdPartyAuthAlert';
|
||||
import InstitutionLogistration, { RenderInstitutionButton } from './InstitutionLogistration';
|
||||
import messages from './messages';
|
||||
import { processLink } from '../data/utils/dataUtils';
|
||||
|
||||
@@ -262,11 +263,11 @@ class RegistrationPage extends React.Component {
|
||||
if (this.props.statusCode !== 403 && validations && validations.email) {
|
||||
validationErrorsAlertMessages.email = [{ user_message: validations.email }];
|
||||
} else if (value.length < 1) {
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'logistration.email.validation.message');
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'email.validation.message');
|
||||
validationErrorsAlertMessages.email = errorEmpty;
|
||||
} else {
|
||||
const errorCharlength = this.generateUserMessage(value.length <= 2, 'logistration.email.ratelimit.less.chars.validation.message');
|
||||
const formatError = this.generateUserMessage(!value.match(/^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i), 'logistration.email.ratelimit.incorrect.format.validation.message');
|
||||
const errorCharlength = this.generateUserMessage(value.length <= 2, 'email.ratelimit.less.chars.validation.message');
|
||||
const formatError = this.generateUserMessage(!value.match(/^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i), 'email.ratelimit.incorrect.format.validation.message');
|
||||
validationErrorsAlertMessages.email = errorCharlength;
|
||||
validationErrorsAlertMessages.emailFormat = formatError;
|
||||
}
|
||||
@@ -275,7 +276,7 @@ class RegistrationPage extends React.Component {
|
||||
if (this.props.statusCode !== 403 && validations && validations.name) {
|
||||
validationErrorsAlertMessages.name = [{ user_message: validations.name }];
|
||||
} else if (value.length < 1) {
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'logistration.fullname.validation.message');
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'fullname.validation.message');
|
||||
validationErrorsAlertMessages.name = errorEmpty;
|
||||
} else {
|
||||
validationErrorsAlertMessages.name = [{ user_message: '' }];
|
||||
@@ -285,10 +286,10 @@ class RegistrationPage extends React.Component {
|
||||
if (this.props.statusCode !== 403 && validations && validations.username) {
|
||||
validationErrorsAlertMessages.username = [{ user_message: validations.username }];
|
||||
} else if (value.length < 1) {
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'logistration.username.validation.message');
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'username.validation.message');
|
||||
validationErrorsAlertMessages.username = errorEmpty;
|
||||
} else {
|
||||
const errorCharLength = this.generateUserMessage(value.length <= 1, 'logistration.username.ratelimit.less.chars.message');
|
||||
const errorCharLength = this.generateUserMessage(value.length <= 1, 'username.ratelimit.less.chars.message');
|
||||
validationErrorsAlertMessages.username = errorCharLength;
|
||||
}
|
||||
break;
|
||||
@@ -296,10 +297,10 @@ class RegistrationPage extends React.Component {
|
||||
if (this.props.statusCode !== 403 && validations && validations.password) {
|
||||
validationErrorsAlertMessages.password = [{ user_message: validations.password }];
|
||||
} else if (value.length < 1) {
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'logistration.register.page.password.validation.message');
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'register.page.password.validation.message');
|
||||
validationErrorsAlertMessages.password = errorEmpty;
|
||||
} else {
|
||||
const errorCharLength = this.generateUserMessage(value.length < 8, 'logistration.email.ratelimit.password.validation.message');
|
||||
const errorCharLength = this.generateUserMessage(value.length < 8, 'email.ratelimit.password.validation.message');
|
||||
validationErrorsAlertMessages.password = errorCharLength;
|
||||
}
|
||||
break;
|
||||
@@ -307,7 +308,7 @@ class RegistrationPage extends React.Component {
|
||||
if (this.props.statusCode !== 403 && validations && validations.country) {
|
||||
validationErrorsAlertMessages.country = [{ user_message: validations.country }];
|
||||
} else {
|
||||
const emptyError = this.generateUserMessage(value === '', 'logistration.country.validation.message');
|
||||
const emptyError = this.generateUserMessage(value === '', 'country.validation.message');
|
||||
validationErrorsAlertMessages.country = emptyError;
|
||||
}
|
||||
break;
|
||||
@@ -469,7 +470,7 @@ class RegistrationPage extends React.Component {
|
||||
className={cssClass}
|
||||
>
|
||||
<label htmlFor={field.name} className="h6 pt-10">
|
||||
{field.label} {this.props.intl.formatMessage(messages['logistration.register.optional.label'])}
|
||||
{field.label} {this.props.intl.formatMessage(messages['register.optional.label'])}
|
||||
</label>
|
||||
<Input {...props} />
|
||||
</ValidationFormGroup>
|
||||
@@ -493,13 +494,13 @@ class RegistrationPage extends React.Component {
|
||||
<RenderInstitutionButton
|
||||
onSubmitHandler={this.handleInstitutionLogin}
|
||||
secondaryProviders={this.props.thirdPartyAuthContext.secondaryProviders}
|
||||
buttonTitle={intl.formatMessage(messages['logistration.register.institution.login.button'])}
|
||||
buttonTitle={intl.formatMessage(messages['register.institution.login.button'])}
|
||||
/>
|
||||
<div className="row tpa-container">
|
||||
<SocialAuthProviders socialAuthProviders={providers} referrer={REGISTER_PAGE} />
|
||||
</div>
|
||||
<h4 className="d-block mx-auto mt-4">
|
||||
{intl.formatMessage(messages['logistration.create.a.new.one.here'])}
|
||||
{intl.formatMessage(messages['create.a.new.one.here'])}
|
||||
</h4>
|
||||
</>
|
||||
);
|
||||
@@ -536,8 +537,8 @@ class RegistrationPage extends React.Component {
|
||||
<InstitutionLogistration
|
||||
onSubmitHandler={this.handleInstitutionLogin}
|
||||
secondaryProviders={this.props.thirdPartyAuthContext.secondaryProviders}
|
||||
headingTitle={intl.formatMessage(messages['logistration.register.institution.login.page.title'])}
|
||||
buttonTitle={intl.formatMessage(messages['logistration.create.an.account'])}
|
||||
headingTitle={intl.formatMessage(messages['register.institution.login.page.title'])}
|
||||
buttonTitle={intl.formatMessage(messages['create.an.account'])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -561,17 +562,17 @@ class RegistrationPage extends React.Component {
|
||||
/>
|
||||
)}
|
||||
<div className="text-left">
|
||||
<span>{intl.formatMessage(messages['logistration.already.have.an.edx.account'])}</span>
|
||||
<a href={LOGIN_PAGE}>{intl.formatMessage(messages['logistration.sign.in.hyperlink'])}</a>
|
||||
<span>{intl.formatMessage(messages['already.have.an.edx.account'])}</span>
|
||||
<a href={LOGIN_PAGE}>{intl.formatMessage(messages['sign.in.hyperlink'])}</a>
|
||||
</div>
|
||||
{(providers.length || secondaryProviders.length || thirdPartyAuthApiStatus === PENDING_STATE)
|
||||
&& !currentProvider ? (
|
||||
<div className="d-block mb-4 mt-4">
|
||||
<h4 className="d-block mx-auto mb-4">
|
||||
{intl.formatMessage(messages['logistration.create.an.account.using'])}
|
||||
{intl.formatMessage(messages['create.an.account.using'])}
|
||||
</h4>
|
||||
</div>
|
||||
) : <h2 className="mt-4">{intl.formatMessage(messages['logistration.create.an.account'])}</h2>}
|
||||
) : <h2 className="mt-4">{intl.formatMessage(messages['create.an.account'])}</h2>}
|
||||
{this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)}
|
||||
<Form className="mb-4 form-group">
|
||||
<ValidationFormGroup
|
||||
@@ -582,7 +583,7 @@ class RegistrationPage extends React.Component {
|
||||
helpText="This name will be used by any certificates that you earn."
|
||||
>
|
||||
<label htmlFor="name" className="h6 pt-10">
|
||||
{intl.formatMessage(messages['logistration.fullname.label'])}
|
||||
{intl.formatMessage(messages['fullname.label'])}
|
||||
</label>
|
||||
<Input
|
||||
name="name"
|
||||
@@ -604,7 +605,7 @@ class RegistrationPage extends React.Component {
|
||||
helpText="The name that will identify you in your courses. It cannot be changed later."
|
||||
>
|
||||
<label htmlFor="username" className="h6 pt-10">
|
||||
{intl.formatMessage(messages['logistration.username.label'])}
|
||||
{intl.formatMessage(messages['username.label'])}
|
||||
</label>
|
||||
<Input
|
||||
name="username"
|
||||
@@ -627,7 +628,7 @@ class RegistrationPage extends React.Component {
|
||||
helpText="This is what you will use to login."
|
||||
>
|
||||
<label htmlFor="email" className="h6 pt-10">
|
||||
{intl.formatMessage(messages['logistration.register.page.email.label'])}
|
||||
{intl.formatMessage(messages['register.page.email.label'])}
|
||||
</label>
|
||||
<Input
|
||||
name="email"
|
||||
@@ -650,7 +651,7 @@ class RegistrationPage extends React.Component {
|
||||
helpText="Your password must contain at least 8 characters, including 1 letter & 1 number."
|
||||
>
|
||||
<label htmlFor="password" className="h6 pt-10">
|
||||
{intl.formatMessage(messages['logistration.password.label'])}
|
||||
{intl.formatMessage(messages['password.label'])}
|
||||
</label>
|
||||
<Input
|
||||
name="password"
|
||||
@@ -680,7 +681,7 @@ class RegistrationPage extends React.Component {
|
||||
required
|
||||
/>
|
||||
<p role="presentation" id="additionalFields" className="mb-0" onClick={e => this.handleOnOptional(e)}>
|
||||
{intl.formatMessage(messages['logistration.support.education.research'])}
|
||||
{intl.formatMessage(messages['support.education.research'])}
|
||||
</p>
|
||||
</ValidationFormGroup>
|
||||
{ this.state.enableOptionalField ? this.addExtraOptionalFields() : null}
|
||||
@@ -689,7 +690,7 @@ class RegistrationPage extends React.Component {
|
||||
className="btn-primary mt-10"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['logistration.create.account.button']),
|
||||
default: intl.formatMessage(messages['create.account.button']),
|
||||
}}
|
||||
onClick={this.handleSubmit}
|
||||
/>
|
||||
@@ -770,14 +771,14 @@ const mapStateToProps = state => {
|
||||
const registrationResult = registrationRequestSelector(state);
|
||||
const thirdPartyAuthContext = thirdPartyAuthContextSelector(state);
|
||||
return {
|
||||
registrationError: state.logistration.registrationError,
|
||||
submitState: state.logistration.submitState,
|
||||
thirdPartyAuthApiStatus: state.logistration.thirdPartyAuthApiStatus,
|
||||
registrationError: state.register.registrationError,
|
||||
submitState: state.register.submitState,
|
||||
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
||||
registrationResult,
|
||||
thirdPartyAuthContext,
|
||||
formData: state.logistration.formData,
|
||||
validations: state.logistration.validations,
|
||||
statusCode: state.logistration.statusCode,
|
||||
formData: state.register.formData,
|
||||
validations: state.register.validations,
|
||||
statusCode: state.register.statusCode,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const REGISTER_NEW_USER = new AsyncActionType('REGISTRATION', 'REGISTER_NEW_USER');
|
||||
export const LOGIN_REQUEST = new AsyncActionType('LOGIN', 'REQUEST');
|
||||
export const THIRD_PARTY_AUTH_CONTEXT = new AsyncActionType('THIRD_PARTY_AUTH', 'GET_THIRD_PARTY_AUTH_CONTEXT');
|
||||
export const REGISTER_FORM = new AsyncActionType('REGISTRATION', 'GET_FORM_FIELDS');
|
||||
export const REGISTER_FORM_VALIDATIONS = new AsyncActionType('REGISTRATION', 'GET_FORM_VALIDATIONS');
|
||||
|
||||
@@ -28,45 +26,6 @@ export const registerNewUserFailure = (error) => ({
|
||||
payload: { error },
|
||||
});
|
||||
|
||||
// Login
|
||||
export const loginRequest = creds => ({
|
||||
type: LOGIN_REQUEST.BASE,
|
||||
payload: { creds },
|
||||
});
|
||||
|
||||
export const loginRequestBegin = () => ({
|
||||
type: LOGIN_REQUEST.BEGIN,
|
||||
});
|
||||
|
||||
export const loginRequestSuccess = (redirectUrl, success) => ({
|
||||
type: LOGIN_REQUEST.SUCCESS,
|
||||
payload: { redirectUrl, success },
|
||||
});
|
||||
|
||||
export const loginRequestFailure = (loginError) => ({
|
||||
type: LOGIN_REQUEST.FAILURE,
|
||||
payload: { loginError },
|
||||
});
|
||||
|
||||
// Third party auth context
|
||||
export const getThirdPartyAuthContext = (urlParams) => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.BASE,
|
||||
payload: { urlParams },
|
||||
});
|
||||
|
||||
export const getThirdPartyAuthContextBegin = () => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.BEGIN,
|
||||
});
|
||||
|
||||
export const getThirdPartyAuthContextSuccess = (thirdPartyAuthContext) => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS,
|
||||
payload: { thirdPartyAuthContext },
|
||||
});
|
||||
|
||||
export const getThirdPartyAuthContextFailure = () => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.FAILURE,
|
||||
});
|
||||
|
||||
// Registration Form Fields
|
||||
export const fetchRegistrationForm = () => ({
|
||||
type: REGISTER_FORM.BASE,
|
||||
@@ -1,16 +1,12 @@
|
||||
import {
|
||||
REGISTER_NEW_USER,
|
||||
LOGIN_REQUEST,
|
||||
THIRD_PARTY_AUTH_CONTEXT,
|
||||
REGISTER_FORM,
|
||||
REGISTER_FORM_VALIDATIONS,
|
||||
} from './actions';
|
||||
|
||||
import { DEFAULT_STATE, PENDING_STATE, COMPLETE_STATE } from '../../data/constants';
|
||||
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
|
||||
|
||||
export const defaultState = {
|
||||
loginError: null,
|
||||
loginResult: {},
|
||||
registrationError: null,
|
||||
registrationResult: {},
|
||||
formData: null,
|
||||
@@ -36,38 +32,6 @@ const reducer = (state = defaultState, action) => {
|
||||
registrationError: action.payload.error,
|
||||
submitState: DEFAULT_STATE,
|
||||
};
|
||||
case LOGIN_REQUEST.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
submitState: PENDING_STATE,
|
||||
};
|
||||
case LOGIN_REQUEST.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
loginResult: action.payload,
|
||||
};
|
||||
case LOGIN_REQUEST.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loginError: action.payload.loginError,
|
||||
submitState: DEFAULT_STATE,
|
||||
};
|
||||
case THIRD_PARTY_AUTH_CONTEXT.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
};
|
||||
case THIRD_PARTY_AUTH_CONTEXT.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthContext: action.payload.thirdPartyAuthContext,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
};
|
||||
case THIRD_PARTY_AUTH_CONTEXT.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
};
|
||||
case REGISTER_FORM.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
@@ -1,8 +1,6 @@
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from './constants';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
@@ -10,18 +8,10 @@ import {
|
||||
registerNewUserBegin,
|
||||
registerNewUserFailure,
|
||||
registerNewUserSuccess,
|
||||
LOGIN_REQUEST,
|
||||
loginRequestBegin,
|
||||
loginRequestFailure,
|
||||
loginRequestSuccess,
|
||||
REGISTER_FORM_VALIDATIONS,
|
||||
fetchRealtimeValidationsBegin,
|
||||
fetchRealtimeValidationsSuccess,
|
||||
fetchRealtimeValidationsFailure,
|
||||
THIRD_PARTY_AUTH_CONTEXT,
|
||||
getThirdPartyAuthContextBegin,
|
||||
getThirdPartyAuthContextSuccess,
|
||||
getThirdPartyAuthContextFailure,
|
||||
REGISTER_FORM,
|
||||
fetchRegistrationFormBegin,
|
||||
fetchRegistrationFormSuccess,
|
||||
@@ -32,9 +22,7 @@ import {
|
||||
import {
|
||||
getFieldsValidations,
|
||||
getRegistrationForm,
|
||||
getThirdPartyAuthContext,
|
||||
registerRequest,
|
||||
loginRequest,
|
||||
} from './service';
|
||||
|
||||
export function* handleNewUserRegistration(action) {
|
||||
@@ -56,46 +44,6 @@ export function* handleNewUserRegistration(action) {
|
||||
}
|
||||
}
|
||||
|
||||
export function* handleLoginRequest(action) {
|
||||
try {
|
||||
yield put(loginRequestBegin());
|
||||
|
||||
const { redirectUrl, success } = yield call(loginRequest, action.payload.creds);
|
||||
|
||||
yield put(loginRequestSuccess(
|
||||
redirectUrl,
|
||||
success,
|
||||
));
|
||||
} catch (e) {
|
||||
const statusCodes = [400];
|
||||
if (e.response) {
|
||||
const { status } = e.response;
|
||||
if (statusCodes.includes(status)) {
|
||||
yield put(loginRequestFailure(camelCaseObject(e.response.data)));
|
||||
} else if (status === 403) {
|
||||
yield put(loginRequestFailure({ errorCode: FORBIDDEN_REQUEST }));
|
||||
} else {
|
||||
yield put(loginRequestFailure({ errorCode: INTERNAL_SERVER_ERROR }));
|
||||
}
|
||||
}
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function* fetchThirdPartyAuthContext(action) {
|
||||
try {
|
||||
yield put(getThirdPartyAuthContextBegin());
|
||||
const { thirdPartyAuthContext } = yield call(getThirdPartyAuthContext, action.payload.urlParams);
|
||||
|
||||
yield put(getThirdPartyAuthContextSuccess(
|
||||
thirdPartyAuthContext,
|
||||
));
|
||||
} catch (e) {
|
||||
yield put(getThirdPartyAuthContextFailure());
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function* fetchRegistrationForm() {
|
||||
try {
|
||||
yield put(fetchRegistrationFormBegin());
|
||||
@@ -129,8 +77,6 @@ export function* fetchRealtimeValidations(action) {
|
||||
|
||||
export default function* saga() {
|
||||
yield takeEvery(REGISTER_NEW_USER.BASE, handleNewUserRegistration);
|
||||
yield takeEvery(LOGIN_REQUEST.BASE, handleLoginRequest);
|
||||
yield takeEvery(THIRD_PARTY_AUTH_CONTEXT.BASE, fetchThirdPartyAuthContext);
|
||||
yield takeEvery(REGISTER_FORM.BASE, fetchRegistrationForm);
|
||||
yield takeEvery(REGISTER_FORM_VALIDATIONS.BASE, fetchRealtimeValidations);
|
||||
}
|
||||
10
src/register/data/selectors.js
Normal file
10
src/register/data/selectors.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
export const storeName = 'register';
|
||||
|
||||
export const registerSelector = state => ({ ...state[storeName] });
|
||||
|
||||
export const registrationRequestSelector = createSelector(
|
||||
registerSelector,
|
||||
register => register.registrationResult,
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { camelCaseObject, convertKeyNames, getConfig } from '@edx/frontend-platform';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import querystring from 'querystring';
|
||||
|
||||
@@ -24,48 +24,6 @@ export async function registerRequest(registrationInformation) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function loginRequest(creds) {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
isPublic: true,
|
||||
};
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(
|
||||
`${getConfig().LMS_BASE_URL}/user_api/v1/account/login_session/`,
|
||||
querystring.stringify(creds),
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
throw (e);
|
||||
});
|
||||
|
||||
return {
|
||||
redirectUrl: data.redirect_url || `${getConfig().LMS_BASE_URL}/dashboard`,
|
||||
success: data.success || false,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getThirdPartyAuthContext(urlParams) {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
params: urlParams,
|
||||
isPublic: true,
|
||||
};
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(
|
||||
`${getConfig().LMS_BASE_URL}/api/third_party_auth_context`,
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
throw (e);
|
||||
});
|
||||
return {
|
||||
thirdPartyAuthContext: camelCaseObject(convertKeyNames(data, { fullname: 'name' })),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getRegistrationForm() {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
@@ -1,78 +1,16 @@
|
||||
import { runSaga } from 'redux-saga';
|
||||
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
|
||||
import * as actions from '../actions';
|
||||
import {
|
||||
fetchRealtimeValidations,
|
||||
fetchThirdPartyAuthContext,
|
||||
fetchRegistrationForm,
|
||||
handleLoginRequest,
|
||||
handleNewUserRegistration,
|
||||
} from '../sagas';
|
||||
import * as api from '../service';
|
||||
import initializeMockLogging from '../../../setupTest';
|
||||
import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../constants';
|
||||
|
||||
const { loggingService } = initializeMockLogging();
|
||||
|
||||
describe('fetchThirdPartyAuthContext', () => {
|
||||
const params = {
|
||||
payload: { urlParams: {} },
|
||||
};
|
||||
|
||||
const data = {
|
||||
currentProvider: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
finishAuthUrl: null,
|
||||
pipelineUserDetails: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
loggingService.logError.mockReset();
|
||||
});
|
||||
|
||||
it('should call service and dispatch success action', async () => {
|
||||
const getThirdPartyAuthContext = jest.spyOn(api, 'getThirdPartyAuthContext')
|
||||
.mockImplementation(() => Promise.resolve({ thirdPartyAuthContext: data }));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
fetchThirdPartyAuthContext,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(getThirdPartyAuthContext).toHaveBeenCalledTimes(1);
|
||||
expect(dispatched).toEqual([
|
||||
actions.getThirdPartyAuthContextBegin(),
|
||||
actions.getThirdPartyAuthContextSuccess(data),
|
||||
]);
|
||||
getThirdPartyAuthContext.mockClear();
|
||||
});
|
||||
|
||||
it('should call service and dispatch error action', async () => {
|
||||
const getThirdPartyAuthContext = jest.spyOn(api, 'getThirdPartyAuthContext')
|
||||
.mockImplementation(() => Promise.reject(new Error('something went wrong')));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
fetchThirdPartyAuthContext,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(getThirdPartyAuthContext).toHaveBeenCalledTimes(1);
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(dispatched).toEqual([
|
||||
actions.getThirdPartyAuthContextBegin(),
|
||||
actions.getThirdPartyAuthContextFailure(),
|
||||
]);
|
||||
getThirdPartyAuthContext.mockClear();
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchRegistrationForm', () => {
|
||||
const data = {
|
||||
fields: [{
|
||||
@@ -212,105 +150,6 @@ describe('fetchRealtimeValidations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleLoginRequest', () => {
|
||||
const params = {
|
||||
payload: {
|
||||
formData: {
|
||||
email: 'test@test.com',
|
||||
password: 'test-password',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const testErrorResponse = async (loginErrorResponse, expectedDispatchers) => {
|
||||
const loginRequest = jest.spyOn(api, 'loginRequest').mockImplementation(() => Promise.reject(loginErrorResponse));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
handleLoginRequest,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(loginRequest).toHaveBeenCalledTimes(1);
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(dispatched).toEqual(expectedDispatchers);
|
||||
loginRequest.mockClear();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
loggingService.logError.mockReset();
|
||||
});
|
||||
|
||||
it('should call service and dispatch success action', async () => {
|
||||
const data = { redirectUrl: '/dashboard', success: true };
|
||||
const loginRequest = jest.spyOn(api, 'loginRequest')
|
||||
.mockImplementation(() => Promise.resolve(data));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
handleLoginRequest,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(loginRequest).toHaveBeenCalledTimes(1);
|
||||
expect(dispatched).toEqual([
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestSuccess(data.redirectUrl, data.success),
|
||||
]);
|
||||
loginRequest.mockClear();
|
||||
});
|
||||
|
||||
it('should call service and dispatch error action', async () => {
|
||||
const loginErrorResponse = {
|
||||
response: {
|
||||
status: 400,
|
||||
data: {
|
||||
login_error: 'something went wrong',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await testErrorResponse(loginErrorResponse, [
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestFailure(camelCaseObject(loginErrorResponse.response.data)),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle rate limit error code', async () => {
|
||||
const loginErrorResponse = {
|
||||
response: {
|
||||
status: 403,
|
||||
data: {
|
||||
errorCode: FORBIDDEN_REQUEST,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await testErrorResponse(loginErrorResponse, [
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestFailure(loginErrorResponse.response.data),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle 500 error code', async () => {
|
||||
const loginErrorResponse = {
|
||||
response: {
|
||||
status: 500,
|
||||
data: {
|
||||
errorCode: INTERNAL_SERVER_ERROR,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await testErrorResponse(loginErrorResponse, [
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestFailure(loginErrorResponse.response.data),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleNewUserRegistration', () => {
|
||||
const params = {
|
||||
payload: {
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as LoginPage } from './LoginPage';
|
||||
export { default as RegistrationPage } from './RegistrationPage';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
121
src/register/messages.jsx
Normal file
121
src/register/messages.jsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'create.account.button': {
|
||||
id: 'create.account.button',
|
||||
defaultMessage: 'Create Account',
|
||||
description: 'Button label that appears on register page',
|
||||
},
|
||||
'already.have.an.edx.account': {
|
||||
id: 'already.have.an.edx.account',
|
||||
defaultMessage: 'Already have an edX account?',
|
||||
description: 'A message on registration page asking the user if he already has an edX account',
|
||||
},
|
||||
'sign.in.hyperlink': {
|
||||
id: 'sign.in.hyperlink',
|
||||
defaultMessage: ' Sign in.',
|
||||
description: 'Text for the hyperlink that takes user to login page',
|
||||
},
|
||||
'create.an.account.using': {
|
||||
id: 'create.an.account.using',
|
||||
defaultMessage: 'Create an account using',
|
||||
description: 'A message that appears before social auth buttons',
|
||||
},
|
||||
'create.a.new.one.here': {
|
||||
id: 'create.a.new.one.here',
|
||||
defaultMessage: 'or create a new one here',
|
||||
description: 'Text that appears after social auth buttons and before the registration form',
|
||||
},
|
||||
'register.institution.login.button': {
|
||||
id: 'register.institution.login.button',
|
||||
defaultMessage: 'Use my institution/campus credentials',
|
||||
description: 'shows institutions list',
|
||||
},
|
||||
'register.institution.login.page.title': {
|
||||
id: 'register.institution.login.page.title',
|
||||
defaultMessage: 'Register with Institution/Campus Credentials',
|
||||
description: 'Heading of institution page',
|
||||
},
|
||||
'create.an.account': {
|
||||
id: 'create.an.account',
|
||||
defaultMessage: 'Create an Account',
|
||||
description: 'Message on button to return to register page',
|
||||
},
|
||||
'register.page.email.label': {
|
||||
id: 'register.page.email.label',
|
||||
defaultMessage: 'Email (required)',
|
||||
description: 'Label that appears above email field on register page',
|
||||
},
|
||||
'email.validation.message': {
|
||||
id: 'email.validation.message',
|
||||
defaultMessage: 'Please enter your Email.',
|
||||
description: 'Validation message that appears when email address is empty',
|
||||
},
|
||||
'email.ratelimit.less.chars.validation.message': {
|
||||
id: 'email.ratelimit.less.chars.validation.message',
|
||||
defaultMessage: 'Email must have 3 characters.',
|
||||
description: 'Validation message that appears when email address is less than 3 characters',
|
||||
},
|
||||
'email.ratelimit.incorrect.format.validation.message': {
|
||||
id: 'email.ratelimit.incorrect.format.validation.message',
|
||||
defaultMessage: 'The email address you provided isn\'t formatted correctly.',
|
||||
description: 'Validation message that appears when email address is not formatted correctly with no backend validations available.',
|
||||
},
|
||||
'email.ratelimit.password.validation.message': {
|
||||
id: 'email.ratelimit.password.validation.message',
|
||||
defaultMessage: 'Your password must contain at least 8 characters',
|
||||
description: 'Validation message that appears when password is not formatted correctly with no backend validations available.',
|
||||
},
|
||||
'password.label': {
|
||||
id: 'password.label',
|
||||
defaultMessage: 'Password (required)',
|
||||
description: 'Label that appears above password field',
|
||||
},
|
||||
'register.page.password.validation.message': {
|
||||
id: 'register.page.password.validation.message',
|
||||
defaultMessage: 'Please enter your Password.',
|
||||
description: 'Validation message that appears when password is non compliant with edX requirement',
|
||||
},
|
||||
'fullname.label': {
|
||||
id: 'fullname.label',
|
||||
defaultMessage: 'Full Name (required)',
|
||||
description: 'Label that appears above fullname field',
|
||||
},
|
||||
'fullname.validation.message': {
|
||||
id: 'fullname.validation.message',
|
||||
defaultMessage: 'Please enter your Full Name.',
|
||||
description: 'Validation message that appears when fullname is empty',
|
||||
},
|
||||
'username.label': {
|
||||
id: 'username.label',
|
||||
defaultMessage: 'Public Username (required)',
|
||||
description: 'Label that appears above username field',
|
||||
},
|
||||
'username.validation.message': {
|
||||
id: 'username.validation.message',
|
||||
defaultMessage: 'Please enter your Public Username.',
|
||||
description: 'Validation message that appears when username is invalid',
|
||||
},
|
||||
'username.ratelimit.less.chars.message': {
|
||||
id: 'username.ratelimit.less.chars.message',
|
||||
defaultMessage: 'Public Username must have atleast 2 characters.',
|
||||
description: 'Validation message that appears when username is less than 2 characters and with no backend validations available.',
|
||||
},
|
||||
'country.validation.message': {
|
||||
id: 'country.validation.message',
|
||||
defaultMessage: 'Select your country or region of residence.',
|
||||
description: 'Validation message that appears when country is not selected',
|
||||
},
|
||||
'support.education.research': {
|
||||
id: 'support.education.research',
|
||||
defaultMessage: 'Support education research by providing additional information',
|
||||
description: 'Text for a checkbox to ask user for if they are willing to provide extra information for education research',
|
||||
},
|
||||
'register.optional.label': {
|
||||
id: 'register.optional.label',
|
||||
defaultMessage: '(optional)',
|
||||
description: 'Text that appears with optional field labels',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -7,7 +7,7 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import { IntlProvider, injectIntl, configure } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import RegistrationPage from '../RegistrationPage';
|
||||
import { RenderInstitutionButton } from '../InstitutionLogistration';
|
||||
import { RenderInstitutionButton } from '../../common-components';
|
||||
import { PENDING_STATE } from '../../data/constants';
|
||||
import { fetchRegistrationForm, fetchRealtimeValidations, registerNewUser } from '../data/actions';
|
||||
|
||||
@@ -16,15 +16,8 @@ const mockStore = configureStore();
|
||||
|
||||
describe('./RegistrationPage.js', () => {
|
||||
const initialState = {
|
||||
logistration: {
|
||||
register: {
|
||||
registrationResult: { success: false, redirectUrl: '' },
|
||||
thirdPartyAuthContext: {
|
||||
platformName: 'openedX',
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
},
|
||||
registrationError: null,
|
||||
formData: {
|
||||
fields: [{
|
||||
@@ -38,6 +31,16 @@ describe('./RegistrationPage.js', () => {
|
||||
}],
|
||||
},
|
||||
},
|
||||
commonComponents: {
|
||||
thirdPartyAuthApiStatus: null,
|
||||
thirdPartyAuthContext: {
|
||||
platformName: 'openedX',
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let props = {};
|
||||
@@ -94,11 +97,11 @@ describe('./RegistrationPage.js', () => {
|
||||
};
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
register: {
|
||||
...initialState.register,
|
||||
formData: {
|
||||
fields: [
|
||||
...initialState.logistration.formData.fields,
|
||||
...initialState.register.formData.fields,
|
||||
{
|
||||
label: 'The country or region where you live.',
|
||||
name: 'country',
|
||||
@@ -141,8 +144,8 @@ describe('./RegistrationPage.js', () => {
|
||||
it('should show optional fields section on optional check enabled', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
register: {
|
||||
...initialState.register,
|
||||
formData: {
|
||||
fields: [
|
||||
{
|
||||
@@ -299,8 +302,8 @@ describe('./RegistrationPage.js', () => {
|
||||
it('should match pending button state snapshot', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
register: {
|
||||
...initialState.register,
|
||||
submitState: PENDING_STATE,
|
||||
},
|
||||
});
|
||||
@@ -312,9 +315,10 @@ describe('./RegistrationPage.js', () => {
|
||||
it('should match TPA provider snapshot', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [appleProvider],
|
||||
},
|
||||
},
|
||||
@@ -327,10 +331,10 @@ describe('./RegistrationPage.js', () => {
|
||||
it('should display no password field when current provider is present', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
currentProvider: 'Google',
|
||||
},
|
||||
},
|
||||
@@ -362,8 +366,8 @@ describe('./RegistrationPage.js', () => {
|
||||
const dasboardUrl = 'http://test.com/testing-dashboard/';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationResult: {
|
||||
success: true,
|
||||
redirectUrl: dasboardUrl,
|
||||
@@ -379,10 +383,10 @@ describe('./RegistrationPage.js', () => {
|
||||
it('should display institution register button', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
secondaryProviders: [secondaryProviders],
|
||||
},
|
||||
},
|
||||
@@ -394,10 +398,10 @@ describe('./RegistrationPage.js', () => {
|
||||
it('should not display institution register button', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
secondaryProviders: [secondaryProviders],
|
||||
},
|
||||
},
|
||||
@@ -411,14 +415,17 @@ describe('./RegistrationPage.js', () => {
|
||||
const authCompleteUrl = '/auth/complete/google-oauth2/';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationResult: {
|
||||
success: true,
|
||||
redirectUrl: '',
|
||||
},
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
finishAuthUrl: authCompleteUrl,
|
||||
},
|
||||
},
|
||||
@@ -435,14 +442,18 @@ describe('./RegistrationPage.js', () => {
|
||||
const registerUrl = '/auth/login/apple-id/?auth_entry=register&next=/dashboard';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [{
|
||||
...appleProvider,
|
||||
registerUrl,
|
||||
}],
|
||||
},
|
||||
},
|
||||
register: {
|
||||
...initialState.register,
|
||||
formData: {
|
||||
fields: [{
|
||||
label: 'I agree to the Your Platform Name Here <a href="/honor" rel="noopener" target="_blank">Honor Code</a>',
|
||||
@@ -468,10 +479,10 @@ describe('./RegistrationPage.js', () => {
|
||||
it('should match third party auth alert', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.logistration.thirdPartyAuthContext,
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
currentProvider: 'Apple',
|
||||
},
|
||||
},
|
||||
@@ -494,8 +505,8 @@ describe('./RegistrationPage.js', () => {
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
logistration: {
|
||||
...initialState.logistration,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationError: {
|
||||
email: [
|
||||
{
|
||||
@@ -36,28 +36,6 @@ exports[`./RegistrationPage.js should display no password field when current pro
|
||||
>
|
||||
Create an Account
|
||||
</h2>
|
||||
<span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
<form
|
||||
className="mb-4 form-group"
|
||||
>
|
||||
@@ -499,37 +477,11 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
|
||||
Sign in.
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="d-block mb-4 mt-4"
|
||||
<h2
|
||||
className="mt-4"
|
||||
>
|
||||
<h4
|
||||
className="d-block mx-auto mb-4"
|
||||
>
|
||||
Create an account using
|
||||
</h4>
|
||||
</div>
|
||||
<span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
Create an Account
|
||||
</h2>
|
||||
<form
|
||||
className="mb-4 form-group"
|
||||
>
|
||||
@@ -745,37 +697,11 @@ exports[`./RegistrationPage.js should match pending button state snapshot 1`] =
|
||||
Sign in.
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="d-block mb-4 mt-4"
|
||||
<h2
|
||||
className="mt-4"
|
||||
>
|
||||
<h4
|
||||
className="d-block mx-auto mb-4"
|
||||
>
|
||||
Create an account using
|
||||
</h4>
|
||||
</div>
|
||||
<span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
Create an Account
|
||||
</h2>
|
||||
<form
|
||||
className="mb-4 form-group"
|
||||
>
|
||||
@@ -1026,37 +952,11 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
|
||||
Sign in.
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="d-block mb-4 mt-4"
|
||||
<h2
|
||||
className="mt-4"
|
||||
>
|
||||
<h4
|
||||
className="d-block mx-auto mb-4"
|
||||
>
|
||||
Create an account using
|
||||
</h4>
|
||||
</div>
|
||||
<span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-loading-skeleton css-1vmnjpn-skeletonStyles-Skeleton"
|
||||
style={
|
||||
Object {
|
||||
"height": 36,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
Create an Account
|
||||
</h2>
|
||||
<form
|
||||
className="mb-4 form-group"
|
||||
>
|
||||
@@ -6,9 +6,9 @@ const InvalidTokenMessage = () => {
|
||||
const loginPasswordLink = (
|
||||
<Hyperlink destination="/login">
|
||||
<FormattedMessage
|
||||
id="logistration.forgot.password.confirmation.support.link"
|
||||
id="forgot.password.confirmation.support.link"
|
||||
defaultMessage="sign-in"
|
||||
description="link text used in message: logistration.reset.password.request.invalid.token.description.message link 'sign-in.'"
|
||||
description="link text used in message: reset.password.request.invalid.token.description.message link 'sign-in.'"
|
||||
/>
|
||||
</Hyperlink>
|
||||
);
|
||||
@@ -16,7 +16,7 @@ const InvalidTokenMessage = () => {
|
||||
const forgotPassword = (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="logistration.reset.password.request.forgot.password.text"
|
||||
id="reset.password.request.forgot.password.text"
|
||||
defaultMessage="Forgot Password"
|
||||
description="Forgot password page help text."
|
||||
/>
|
||||
@@ -29,13 +29,13 @@ const InvalidTokenMessage = () => {
|
||||
<Alert variant="danger">
|
||||
<Alert.Heading className="text-danger">
|
||||
<FormattedMessage
|
||||
id="logistration.reset.password.request.invalid.token.header.message"
|
||||
id="reset.password.request.invalid.token.header.message"
|
||||
defaultMessage="Invalid Password Reset Link"
|
||||
description="Invalid password reset link help text heading."
|
||||
/>
|
||||
</Alert.Heading>
|
||||
<FormattedMessage
|
||||
id="logistration.reset.password.request.invalid.token.description.message"
|
||||
id="reset.password.request.invalid.token.description.message"
|
||||
defaultMessage="This password reset link is invalid. It may have been used already. To reset your password, go to the {loginPasswordLink} page and select {forgotPassword}"
|
||||
description="Invalid password reset link help text message."
|
||||
values={{
|
||||
|
||||
@@ -11,7 +11,7 @@ const ResetFailureMessage = (props) => {
|
||||
<div className="text-left mw-500">
|
||||
<Alert variant="danger">
|
||||
<FormattedMessage
|
||||
id="logistration.reset.password.request.failure.header.message"
|
||||
id="reset.password.request.failure.header.message"
|
||||
defaultMessage="{errorMessage} "
|
||||
description="error message when password reset failure."
|
||||
values={{
|
||||
|
||||
@@ -79,12 +79,12 @@ const ResetPasswordPage = (props) => {
|
||||
<div className="d-flex justify-content-center m-4">
|
||||
<div className="d-flex flex-column mw-500">
|
||||
<Form>
|
||||
<div>
|
||||
<div className="reset-password-container">
|
||||
<h3 className="mt-3">
|
||||
{intl.formatMessage(messages['logistration.reset.password.page.heading'])}
|
||||
{intl.formatMessage(messages['reset.password.page.heading'])}
|
||||
</h3>
|
||||
<p className="mb-4">
|
||||
{intl.formatMessage(messages['logistration.reset.password.page.instructions'])}
|
||||
{intl.formatMessage(messages['reset.password.page.instructions'])}
|
||||
</p>
|
||||
<div className="d-flex flex-column align-items-start">
|
||||
<ValidationFormGroup
|
||||
@@ -94,7 +94,7 @@ const ResetPasswordPage = (props) => {
|
||||
className="w-100"
|
||||
>
|
||||
<Form.Label htmlFor="reset-password-input" className="h6 mr-1">
|
||||
{intl.formatMessage(messages['logistration.reset.password.page.new.field.label'])}
|
||||
{intl.formatMessage(messages['reset.password.page.new.field.label'])}
|
||||
</Form.Label>
|
||||
<Input
|
||||
name="new-password1"
|
||||
@@ -107,11 +107,11 @@ const ResetPasswordPage = (props) => {
|
||||
<ValidationFormGroup
|
||||
for="confirm-password-input"
|
||||
invalid={!passwordMatch}
|
||||
invalidMessage={intl.formatMessage(messages['logistration.reset.password.page.invalid.match.message'])}
|
||||
invalidMessage={intl.formatMessage(messages['reset.password.page.invalid.match.message'])}
|
||||
className="w-100"
|
||||
>
|
||||
<Form.Label htmlFor="confirm-password-input" className="h6 mr-1">
|
||||
{intl.formatMessage(messages['logistration.reset.password.page.confirm.field.label'])}
|
||||
{intl.formatMessage(messages['reset.password.page.confirm.field.label'])}
|
||||
</Form.Label>
|
||||
<Input
|
||||
name="new-password2"
|
||||
@@ -129,7 +129,7 @@ const ResetPasswordPage = (props) => {
|
||||
className="btn-primary"
|
||||
state={props.status}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['logistration.reset.password.page.submit.button']),
|
||||
default: intl.formatMessage(messages['reset.password.page.submit.button']),
|
||||
}}
|
||||
onClick={e => handleSubmit(e)}
|
||||
/>
|
||||
|
||||
@@ -6,9 +6,9 @@ const ResetSuccessMessage = () => {
|
||||
const loginPasswordLink = (
|
||||
<Alert.Link href="/login" className="font-weight-normal text-info">
|
||||
<FormattedMessage
|
||||
id="logistration.reset.password.confirmation.support.link"
|
||||
id="reset.password.confirmation.support.link"
|
||||
defaultMessage="Sign-in to your account."
|
||||
description="link text used in message: logistration.reset.password.invalid.token.description.message link 'sign-in.'"
|
||||
description="link text used in message: reset.password.invalid.token.description.message link 'sign-in.'"
|
||||
/>
|
||||
</Alert.Link>
|
||||
);
|
||||
@@ -20,13 +20,13 @@ const ResetSuccessMessage = () => {
|
||||
<Alert variant="success">
|
||||
<Alert.Heading className="text-success">
|
||||
<FormattedMessage
|
||||
id="logistration.reset.password.request.success.header.message"
|
||||
id="reset.password.request.success.header.message"
|
||||
defaultMessage="Password Reset Complete."
|
||||
description="header message when reset is successful."
|
||||
/>
|
||||
</Alert.Heading>
|
||||
<FormattedMessage
|
||||
id="logistration.reset.password.request.success.header.description.message"
|
||||
id="reset.password.request.success.header.description.message"
|
||||
defaultMessage="Your password has been reset. {loginPasswordLink}"
|
||||
description="message when reset password is successful."
|
||||
values={{
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'logistration.reset.password.page.heading': {
|
||||
id: 'logistration.reset.password.page.heading',
|
||||
'reset.password.page.heading': {
|
||||
id: 'reset.password.page.heading',
|
||||
defaultMessage: 'Reset Your Password',
|
||||
description: 'The page heading for the Reset password page.',
|
||||
},
|
||||
'logistration.reset.password.page.instructions': {
|
||||
id: 'logistration.reset.password.page.instructions',
|
||||
'reset.password.page.instructions': {
|
||||
id: 'reset.password.page.instructions',
|
||||
defaultMessage: 'Enter and confirm your new password.',
|
||||
description: 'Instructions message for reset password page.',
|
||||
},
|
||||
'logistration.reset.password.page.invalid.match.message': {
|
||||
id: 'logistration.reset.password.page.invalid.match.message',
|
||||
'reset.password.page.invalid.match.message': {
|
||||
id: 'reset.password.page.invalid.match.message',
|
||||
defaultMessage: 'Passwords do not match.',
|
||||
description: 'Password format error.',
|
||||
},
|
||||
'logistration.reset.password.page.new.field.label': {
|
||||
id: 'logistration.forgot.password.page.new.field.label',
|
||||
'reset.password.page.new.field.label': {
|
||||
id: 'forgot.password.page.new.field.label',
|
||||
defaultMessage: 'New Password',
|
||||
description: 'New password field label for the reset password page.',
|
||||
},
|
||||
'logistration.reset.password.page.confirm.field.label': {
|
||||
id: 'logistration.forgot.password.page.confirm.field.label',
|
||||
'reset.password.page.confirm.field.label': {
|
||||
id: 'forgot.password.page.confirm.field.label',
|
||||
defaultMessage: 'Confirm Password',
|
||||
description: 'Confirm password field label for the reset password page.',
|
||||
},
|
||||
'logistration.reset.password.page.submit.button': {
|
||||
id: 'logistration.reset.password.page.submit.button',
|
||||
'reset.password.page.submit.button': {
|
||||
id: 'reset.password.page.submit.button',
|
||||
defaultMessage: 'Reset my password',
|
||||
description: 'Submit button text for the reset password page.',
|
||||
},
|
||||
|
||||
@@ -55,7 +55,9 @@ exports[`ResetPasswordPage should match pending reset message section snapshot 1
|
||||
<form
|
||||
className=""
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="reset-password-container"
|
||||
>
|
||||
<h3
|
||||
className="mt-3"
|
||||
>
|
||||
@@ -154,7 +156,9 @@ exports[`ResetPasswordPage should match reset password default section snapshot
|
||||
<form
|
||||
className=""
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="reset-password-container"
|
||||
>
|
||||
<h3
|
||||
className="mt-3"
|
||||
>
|
||||
@@ -305,7 +309,9 @@ Array [
|
||||
<form
|
||||
className=""
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="reset-password-container"
|
||||
>
|
||||
<h3
|
||||
className="mt-3"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user