Compare commits

...

1 Commits

Author SHA1 Message Date
alangsto
37a43f2162 Revert "Upgraded frontend-build version to v12 (#613)"
This reverts commit f0d6a92ab2.
2022-08-18 16:31:55 -04:00
49 changed files with 5368 additions and 3911 deletions

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint');

8669
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -81,7 +81,7 @@
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.0",
"@edx/frontend-build": "^12.0.3",
"@edx/frontend-build": "9.1.2",
"@edx/reactifex": "^1.0.3",
"@testing-library/jest-dom": "5.15.1",
"@testing-library/react": "12.1.5",

View File

@@ -148,13 +148,28 @@ class AccountSettingsPage extends React.Component {
})),
}));
sortDates = (a, b) => {
const aTimeSinceEpoch = new Date(a).getTime();
const bTimeSinceEpoch = new Date(b).getTime();
return bTimeSinceEpoch - aTimeSinceEpoch;
}
sortVerifiedNameRecords = verifiedNameHistory => {
if (Array.isArray(verifiedNameHistory)) {
return [...verifiedNameHistory].sort(this.sortDates);
}
return [];
}
handleEditableFieldChange = (name, value) => {
this.props.updateDraft(name, value);
};
}
handleSubmit = (formId, values) => {
this.props.saveSettings(formId, values);
};
}
handleSubmitProfileName = (formId, values) => {
if (Object.keys(this.props.drafts).includes('useVerifiedNameForCerts')) {
@@ -182,7 +197,7 @@ class AccountSettingsPage extends React.Component {
} else {
this.props.saveSettings(formId, values);
}
};
}
isEditable(fieldName) {
return !this.props.staticFields.includes(fieldName);
@@ -267,7 +282,7 @@ class AccountSettingsPage extends React.Component {
}
return this.props.intl.formatMessage(messages[messageString]);
};
}
renderVerifiedNameSuccessMessage = (verifiedName, created) => {
const dateValue = new Date(created).valueOf();
@@ -282,7 +297,7 @@ class AccountSettingsPage extends React.Component {
body={this.props.intl.formatMessage(messages['account.settings.field.name.verified.success.message'])}
/>
);
};
}
renderVerifiedNameFailureMessage = (verifiedName, created) => {
const dateValue = new Date(created).valueOf();
@@ -313,7 +328,7 @@ class AccountSettingsPage extends React.Component {
}
/>
);
};
}
renderVerifiedNameSubmittedMessage = (willCertNameChange) => (
<Alert
@@ -331,7 +346,7 @@ class AccountSettingsPage extends React.Component {
}
</p>
</Alert>
);
)
renderVerifiedNameMessage = verifiedNameRecord => {
const {
@@ -372,7 +387,7 @@ class AccountSettingsPage extends React.Component {
default:
return null;
}
};
}
renderVerifiedNameIcon = (status) => {
switch (status) {
@@ -383,7 +398,7 @@ class AccountSettingsPage extends React.Component {
default:
return null;
}
};
}
renderVerifiedNameHelpText = (status, proctoredExamId) => {
let messageStr = 'account.settings.field.name.verified.help.text';
@@ -408,7 +423,7 @@ class AccountSettingsPage extends React.Component {
}
return this.props.intl.formatMessage(messages[messageStr]);
};
}
renderEmptyStaticFieldMessage() {
if (this.isManagedProfile()) {

View File

@@ -128,7 +128,7 @@ function EditableField(props) {
options={inputOptions}
{...others}
/>
{others.children}
<>{others.children}</>
</ValidationFormGroup>
<p>
<StatefulButton

View File

@@ -34,7 +34,7 @@ function CertificatePreference({
const [modalIsOpen, setModalIsOpen] = useState(false);
const formId = 'useVerifiedNameForCerts';
const handleCheckboxChange = () => {
function handleCheckboxChange() {
if (!checked) {
if (fieldName === 'verified_name') {
dispatch(updateDraft(formId, true));
@@ -44,22 +44,22 @@ function CertificatePreference({
} else {
setModalIsOpen(true);
}
};
}
const handleCancel = () => {
function handleCancel() {
setModalIsOpen(false);
dispatch(resetDrafts());
};
}
const handleModalChange = (e) => {
function handleModalChange(e) {
if (e.target.value === 'fullName') {
dispatch(updateDraft(formId, false));
} else {
dispatch(updateDraft(formId, true));
}
};
}
const handleSubmit = (e) => {
function handleSubmit(e) {
e.preventDefault();
if (saveState === 'pending') {
@@ -67,7 +67,7 @@ function CertificatePreference({
}
dispatch(saveSettings(formId, useVerifiedNameForCerts));
};
}
useEffect(() => {
if (originalVerifiedName) {

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-import-assign */
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

View File

@@ -17,28 +17,27 @@ import LogoSVG from '../../logo.svg';
import { fetchSettings } from '../data/actions';
import { coachingConsentPageSelector } from '../data/selectors';
function Logo({ src, alt, ...attributes }) {
return <img src={src} alt={alt} {...attributes} />;
}
const Logo = ({ src, alt, ...attributes }) => (
<>
<img src={src} alt={alt} {...attributes} />
</>
);
function SuccessMessage(props) {
return (
<div className="col-12 col-lg-6 shadow-lg mx-auto mt-4 p-5">
<FontAwesomeIcon className="text-success" icon={faCheck} size="5x" />
<div className="h3">{props.header}</div>
<div>{props.message}</div>
<Hyperlink destination={props.continueUrl} className="d-block p-2 my-3 text-center text-white bg-primary rounded">
{props.continue}
</Hyperlink>
</div>
);
}
const SuccessMessage = props => (
<div className="col-12 col-lg-6 shadow-lg mx-auto mt-4 p-5">
<FontAwesomeIcon className="text-success" icon={faCheck} size="5x" />
<div className="h3">{props.header}</div>
<div>{props.message}</div>
<Hyperlink destination={props.continueUrl} className="d-block p-2 my-3 text-center text-white bg-primary rounded">
{props.continue}
</Hyperlink>
</div>
);
function AutoRedirect(props) {
const AutoRedirect = (props) => {
window.location.href = props.redirectUrl;
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
};
const VIEWS = {
NOT_LOADED: 'NOT_LOADED',
@@ -72,23 +71,6 @@ class CoachingConsent extends React.Component {
this.props.fetchSettings();
}
handleSubmit(e) {
e.preventDefault();
const fullName = e.target.fullName.value;
const phoneNumber = e.target.phoneNumber.value;
const body = {
coaching_consent: true,
consent_form_seen: true,
phone_number: phoneNumber,
full_name: fullName,
};
this.setState({
formErrors: {},
formSubmitted: true,
declineSubmitted: false,
}, () => this.patchUsingCoachingConsentForm(body));
}
sanitizeForwardingUrl(url) {
// Redirect to root of MFE if invalid next param is sent
return url && url.startsWith(getConfig().LMS_BASE_URL) ? url : `${getConfig().LMS_BASE_URL}/dashboard/`;
@@ -117,6 +99,23 @@ class CoachingConsent extends React.Component {
}
}
handleSubmit(e) {
e.preventDefault();
const fullName = e.target.fullName.value;
const phoneNumber = e.target.phoneNumber.value;
const body = {
coaching_consent: true,
consent_form_seen: true,
phone_number: phoneNumber,
full_name: fullName,
};
this.setState({
formErrors: {},
formSubmitted: true,
declineSubmitted: false,
}, () => this.patchUsingCoachingConsentForm(body));
}
declineCoaching(e) {
e.preventDefault();
const body = {
@@ -161,7 +160,6 @@ class CoachingConsent extends React.Component {
case VIEWS.DECLINED:
return <AutoRedirect redirectUrl={this.state.redirectUrl} />;
default:
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
}
@@ -255,12 +253,12 @@ CoachingConsent.propTypes = {
}),
}).isRequired,
formErrors: PropTypes.shape({
coaching: PropTypes.shape({}),
coaching: PropTypes.object,
}).isRequired,
confirmationValues: PropTypes.shape({
coaching: PropTypes.shape({}),
name: PropTypes.shape({}),
phone_number: PropTypes.shape({}),
coaching: PropTypes.object,
name: PropTypes.object,
phone_number: PropTypes.object,
}).isRequired,
fetchSettings: PropTypes.func.isRequired,
profileDataManager: PropTypes.string,

View File

@@ -8,86 +8,82 @@ import PropTypes from 'prop-types';
import Alert from '../Alert';
import messages from './CoachingConsent.messages';
function ErrorMessage(props) {
return <div className="alert-warning mb-2">{props.message}</div>;
}
const ErrorMessage = props => (
<div className="alert-warning mb-2">{props.message}</div>
);
function ManagedProfileAlert({ profileDataManager }) {
return (
<Alert className="alert alert-primary" role="alert">
<FormattedMessage
id="account.settings.coaching.managed.alert"
defaultMessage="Your name is managed by {managerTitle}. Contact your administrator for help."
description="Alert message informing the user their account data is managed by a third party"
values={{
managerTitle: <b>{profileDataManager}</b>,
}}
/>
</Alert>
);
}
function CoachingForm(props) {
return (
<div className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg">
<h2 className="h2">
{props.intl.formatMessage(messages['account.settings.coaching.consent.welcome.header'])}
</h2>
<p>{props.intl.formatMessage(messages['account.settings.coaching.consent.description'])}</p>
<div>
<form onSubmit={props.onSubmit}>
<div className="py-3">
{!!props.profileDataManager && (
<ManagedProfileAlert profileDataManager={props.profileDataManager} />
)}
<ErrorMessage message={props.formErrors.full_name} />
<label className="h6" htmlFor="fullName">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.name'])}
</label>
<Input
type="text"
name="full-name"
id="fullName"
disabled={!!props.profileDataManager}
defaultValue={props.formValues.name}
/>
</div>
<div className="py-3">
<ErrorMessage message={props.formErrors.phone_number} />
<label className="h6" htmlFor="phoneNumber">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.phone-number'])}
</label>
<Input
type="text"
name="phone_number"
id="phoneNumber"
defaultValue={props.formValues.phone_number}
/>
</div>
<div className=" py-3">
<p className="small font-italic">
{props.intl.formatMessage(messages['account.settings.coaching.consent.text-messaging.disclaimer'])}
</p>
</div>
<ErrorMessage message={props.formErrors.coaching} />
<div className="d-flex flex-column align-items-center">
<Button variant="outline-primary" className="w-100" type="submit">
{props.intl.formatMessage(messages['account.settings.coaching.consent.accept-coaching'])}
</Button>
</div>
<div className="mt-3">
<Hyperlink
className="mt-3 text-dark btn-link small"
destination={props.redirectUrl}
onClick={props.declineCoaching}
>
{props.intl.formatMessage(messages['account.settings.coaching.consent.decline-coaching'])}
</Hyperlink>
</div>
</form>
</div>
const ManagedProfileAlert = ({ profileDataManager }) => (
<Alert className="alert alert-primary" role="alert">
<FormattedMessage
id="account.settings.coaching.managed.alert"
defaultMessage="Your name is managed by {managerTitle}. Contact your administrator for help."
description="Alert message informing the user their account data is managed by a third party"
values={{
managerTitle: <b>{profileDataManager}</b>,
}}
/>
</Alert>
);
const CoachingForm = props => (
<div className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg">
<h2 className="h2">
{props.intl.formatMessage(messages['account.settings.coaching.consent.welcome.header'])}
</h2>
<p>{props.intl.formatMessage(messages['account.settings.coaching.consent.description'])}</p>
<div>
<form onSubmit={props.onSubmit}>
<div className="py-3">
{!!props.profileDataManager && (
<ManagedProfileAlert profileDataManager={props.profileDataManager} />
)}
<ErrorMessage message={props.formErrors.full_name} />
<label className="h6" htmlFor="fullName">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.name'])}
</label>
<Input
type="text"
name="full-name"
id="fullName"
disabled={!!props.profileDataManager}
defaultValue={props.formValues.name}
/>
</div>
<div className="py-3">
<ErrorMessage message={props.formErrors.phone_number} />
<label className="h6" htmlFor="phoneNumber">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.phone-number'])}
</label>
<Input
type="text"
name="phone_number"
id="phoneNumber"
defaultValue={props.formValues.phone_number}
/>
</div>
<div className=" py-3">
<p className="small font-italic">
{props.intl.formatMessage(messages['account.settings.coaching.consent.text-messaging.disclaimer'])}
</p>
</div>
<ErrorMessage message={props.formErrors.coaching} />
<div className="d-flex flex-column align-items-center">
<Button variant="outline-primary" className="w-100" type="submit">
{props.intl.formatMessage(messages['account.settings.coaching.consent.accept-coaching'])}
</Button>
</div>
<div className="mt-3">
<Hyperlink
className="mt-3 text-dark btn-link small"
destination={props.redirectUrl}
onClick={props.declineCoaching}
>
{props.intl.formatMessage(messages['account.settings.coaching.consent.decline-coaching'])}
</Hyperlink>
</div>
</form>
</div>
);
}
</div>
);
CoachingForm.defaultProps = {
formErrors: {

View File

@@ -8,69 +8,66 @@ import { editableFieldSelector } from '../data/selectors';
import { saveSettings, updateDraft, saveMultipleSettings } from '../data/actions';
import EditableField from '../EditableField';
function CoachingToggle(props) {
return (
<>
<EditableField
name="phone_number"
type="text"
value={props.phone_number}
label={props.intl.formatMessage(messages['account.settings.field.phone_number'])}
emptyLabel={props.intl.formatMessage(messages['account.settings.field.phone_number.empty'])}
onChange={props.updateDraft}
onSubmit={() => {
const { coaching } = props;
if (coaching.coaching_consent === true) {
return props.saveMultipleSettings([
{
formId: 'coaching',
commitValues: {
...coaching,
phone_number: props.phone_number,
},
const CoachingToggle = props => (
<>
<EditableField
name="phone_number"
type="text"
value={props.phone_number}
label={props.intl.formatMessage(messages['account.settings.field.phone_number'])}
emptyLabel={props.intl.formatMessage(messages['account.settings.field.phone_number.empty'])}
onChange={props.updateDraft}
onSubmit={() => {
const { coaching } = props;
if (coaching.coaching_consent === true) {
return props.saveMultipleSettings([
{
formId: 'coaching',
commitValues: {
...coaching,
phone_number: props.phone_number,
},
{
formId: 'phone_number',
commitValues: props.phone_number,
},
], 'phone_number');
}
return props.saveSettings('phone_number', props.phone_number);
},
{
formId: 'phone_number',
commitValues: props.phone_number,
},
], 'phone_number');
}
return props.saveSettings('phone_number', props.phone_number);
}}
/>
<ValidationFormGroup
for="coachingConsent"
helpText={props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
invalid={!!props.error}
invalidMessage={props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
className="custom-control custom-switch"
>
<Input
name={props.name}
className="custom-control-input"
disabled={props.saveState === 'pending'}
type="checkbox"
id="coachingConsent"
checked={props.coaching.coaching_consent}
value={props.coaching.coaching_consent}
onChange={async (e) => {
const { name } = e.target;
// eslint-disable-next-line camelcase
const { user, eligible_for_coaching } = props.coaching;
const value = {
user,
eligible_for_coaching,
coaching_consent: e.target.checked,
};
props.saveSettings(name, value);
}}
/>
<ValidationFormGroup
for="coachingConsent"
helpText={props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
invalid={!!props.error}
invalidMessage={props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
className="custom-control custom-switch"
>
<Input
name={props.name}
className="custom-control-input"
disabled={props.saveState === 'pending'}
type="checkbox"
id="coachingConsent"
checked={props.coaching.coaching_consent}
value={props.coaching.coaching_consent}
onChange={async (e) => {
const { name } = e.target;
// eslint-disable-next-line camelcase
const { user, eligible_for_coaching } = props.coaching;
const value = {
user,
// eslint-disable-next-line camelcase
eligible_for_coaching,
coaching_consent: e.target.checked,
};
props.saveSettings(name, value);
}}
/>
<label className="custom-control-label" htmlFor="coachingConsent">{props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}</label>
</ValidationFormGroup>
</>
);
}
<label className="custom-control-label" htmlFor="coachingConsent">{props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}</label>
</ValidationFormGroup>
</>
);
CoachingToggle.defaultProps = {
phone_number: '',

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-import-assign */
import React from 'react';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';

View File

@@ -41,7 +41,7 @@ export const defaultState = {
verifiedNameHistory: {},
};
const reducer = (state = defaultState, action = {}) => {
const reducer = (state = defaultState, action) => {
let dispatcherIsOpenForm;
switch (action.type) {

View File

@@ -179,7 +179,7 @@ const formValuesSelector = createSelector(
const transformTimeZonesToOptions = timeZoneArr => timeZoneArr
.map(({ time_zone, description }) => ({ // eslint-disable-line camelcase
value: time_zone, label: description, // eslint-disable-line camelcase
value: time_zone, label: description,
}));
const timeZonesSelector = createSelector(

View File

@@ -12,7 +12,7 @@ import messages from './messages';
// Components
import Alert from '../Alert';
function BeforeProceedingBanner(props) {
const BeforeProceedingBanner = (props) => {
const { instructionMessageId, intl, supportArticleUrl } = props;
return (
@@ -35,7 +35,7 @@ function BeforeProceedingBanner(props) {
/>
</Alert>
);
}
};
BeforeProceedingBanner.propTypes = {
instructionMessageId: PropTypes.string.isRequired,

View File

@@ -92,10 +92,8 @@ export class DeleteAccount extends React.Component {
<PrintingInstructions />
</p>
<p className="text-danger h6">
{intl.formatMessage(
messages['account.settings.delete.account.text.warning'],
{ siteName: getConfig().SITE_NAME },
)}
{intl.formatMessage(messages['account.settings.delete.account.text.warning'],
{ siteName: getConfig().SITE_NAME })}
</p>
<p>
<Hyperlink destination="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings">

View File

@@ -3,12 +3,8 @@ import renderer from 'react-test-renderer';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
// Testing the modals separately, they just clutter up the snapshots if included here.
jest.mock('./ConfirmationModal', () => function () {
return null;
});
jest.mock('./SuccessModal', () => function () {
return null;
});
jest.mock('./ConfirmationModal', () => () => (<></>));
jest.mock('./SuccessModal', () => () => (<></>));
import { DeleteAccount } from './DeleteAccount'; // eslint-disable-line import/first

View File

@@ -5,7 +5,7 @@ import { Hyperlink } from '@edx/paragon';
import { getConfig } from '@edx/frontend-platform';
import messages from './messages';
function PrintingInstructions(props) {
const PrintingInstructions = (props) => {
const actionLink = (
<Hyperlink
// TODO: What would a generic version of this link look like? Should
@@ -38,7 +38,7 @@ function PrintingInstructions(props) {
values={{ actionLink }}
/>
);
}
};
PrintingInstructions.propTypes = {
intl: intlShape.isRequired,

View File

@@ -5,7 +5,7 @@ import { Modal } from '@edx/paragon';
import messages from './messages';
export function SuccessModal(props) {
export const SuccessModal = (props) => {
const { status, intl, onClose } = props;
return (
<Modal
@@ -23,7 +23,7 @@ export function SuccessModal(props) {
onClose={onClose}
/>
);
}
};
SuccessModal.propTypes = {
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './DeleteAccount';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { CheckBox } from '@edx/paragon';
import { DECLINED } from '../data/constants';
function Checkboxes(props) {
const Checkboxes = (props) => {
const {
id,
options,
@@ -59,7 +59,7 @@ function Checkboxes(props) {
{renderCheckboxes()}
</div>
);
}
};
Checkboxes.propTypes = {
id: PropTypes.string.isRequired,

View File

@@ -75,7 +75,7 @@ class DemographicsSection extends React.Component {
const matchingOption = demographicsEthnicityOptions.filter(option => option.value === e)[0];
return matchingOption && matchingOption.label;
}).join(', ');
};
}
handleEditableFieldChange = (name, value) => {
this.props.updateDraft(name, value);
@@ -317,7 +317,7 @@ DemographicsSection.propTypes = {
intl: intlShape.isRequired,
formValues: PropTypes.shape({
demographics_gender: PropTypes.string,
demographics_user_ethnicity: PropTypes.shape([]),
demographics_user_ethnicity: PropTypes.array,
demographics_income: PropTypes.string,
demographics_military_history: PropTypes.string,
demographics_learner_education_level: PropTypes.string,
@@ -327,11 +327,11 @@ DemographicsSection.propTypes = {
demographics_future_work_sector: PropTypes.string,
demographics_work_status_description: PropTypes.string,
demographics_gender_description: PropTypes.string,
demographicsOptions: PropTypes.shape({}),
demographicsOptions: PropTypes.object,
}).isRequired,
drafts: PropTypes.shape({
demographics_gender: PropTypes.string,
demographics_user_ethnicity: PropTypes.shape([]),
demographics_user_ethnicity: PropTypes.array,
demographics_income: PropTypes.string,
demographics_military_history: PropTypes.string,
demographics_learner_education_level: PropTypes.string,
@@ -341,7 +341,7 @@ DemographicsSection.propTypes = {
demographics_future_work_sector: PropTypes.string,
demographics_work_status_description: PropTypes.string,
demographics_gender_description: PropTypes.string,
demographicsOptions: PropTypes.shape({}),
demographicsOptions: PropTypes.object,
}).isRequired,
formErrors: PropTypes.shape({
demographicsError: PropTypes.string,

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-import-assign */
import * as auth from '@edx/frontend-platform/auth';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './AccountSettingsPage';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

View File

@@ -39,9 +39,9 @@ function NameChangeModal({
dispatch(requestNameChangeReset());
}, [dispatch]);
const handleChange = (e) => {
function handleChange(e) {
setVerifiedNameInput(e.target.value);
};
}
const handleClose = useCallback(() => {
resetLocalState();
@@ -49,7 +49,7 @@ function NameChangeModal({
dispatch(saveSettingsReset());
}, [dispatch, resetLocalState, targetFormId]);
const handleSubmit = (e) => {
function handleSubmit(e) {
e.preventDefault();
if (saveState === 'pending') {
@@ -64,7 +64,7 @@ function NameChangeModal({
const draftProfileName = targetFormId === 'name' ? formValues.name : null;
dispatch(requestNameChange(username, draftProfileName, verifiedNameInput));
}
};
}
useEffect(() => {
if (saveState === 'complete') {

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './NameChange';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-import-assign */
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

View File

@@ -7,7 +7,7 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import Alert from '../Alert';
function ConfirmationAlert(props) {
const ConfirmationAlert = (props) => {
const { email } = props;
const technicalSupportLink = (
@@ -38,7 +38,7 @@ function ConfirmationAlert(props) {
/>
</Alert>
);
}
};
ConfirmationAlert.propTypes = {
email: PropTypes.string.isRequired,

View File

@@ -5,19 +5,17 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import Alert from '../Alert';
function RequestInProgressAlert() {
return (
<Alert
className="alert-warning mt-n2"
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
>
<FormattedMessage
id="account.settings.editable.field.password.reset.button.forbidden"
defaultMessage="Your previous request is in progress, please try again in few moments."
description="A message displayed when a previous password reset request is still in progress."
/>
</Alert>
);
}
const RequestInProgressAlert = () => (
<Alert
className="alert-warning mt-n2"
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
>
<FormattedMessage
id="account.settings.editable.field.password.reset.button.forbidden"
defaultMessage="Your previous request is in progress, please try again in few moments."
description="A message displayed when a previous password reset request is still in progress."
/>
</Alert>
);
export default RequestInProgressAlert;

View File

@@ -9,7 +9,7 @@ import messages from './messages';
import ConfirmationAlert from './ConfirmationAlert';
import RequestInProgressAlert from './RequestInProgressAlert';
function ResetPassword(props) {
const ResetPassword = (props) => {
const { email, intl, status } = props;
return (
<div className="form-group">
@@ -47,7 +47,7 @@ function ResetPassword(props) {
{status === 'forbidden' ? <RequestInProgressAlert /> : null}
</div>
);
}
};
ResetPassword.propTypes = {
email: PropTypes.string,

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './ResetPassword';
export { default as reducer } from './data/reducers';
export { RESET_PASSWORD } from './data/actions';

View File

@@ -16,7 +16,7 @@ class ThirdPartyAuth extends Component {
}
const disconnectUrl = e.currentTarget.getAttribute('data-disconnect-url');
this.props.disconnectAuth(disconnectUrl, providerId);
};
}
renderUnconnectedProvider(url, name) {
return (

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './ThirdPartyAuth';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

View File

@@ -5,16 +5,14 @@ import { getConfig } from '@edx/frontend-platform';
import messages from './messages';
function Head({ intl }) {
return (
<Helmet>
<title>
{intl.formatMessage(messages['account.page.title'], { siteName: getConfig().SITE_NAME })}
</title>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
);
}
const Head = ({ intl }) => (
<Helmet>
<title>
{intl.formatMessage(messages['account.page.title'], { siteName: getConfig().SITE_NAME })}
</title>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
);
Head.propTypes = {
intl: intlShape.isRequired,

View File

@@ -175,7 +175,7 @@ class Camera extends React.Component {
if (predictions.length === 0) {
this.giveFeedback(predictions.length, [], false);
}
};
}
startDetection() {
setTimeout(() => {

View File

@@ -14,14 +14,14 @@ function CameraHelpWithUpload(props) {
const { setIdPhotoFile, idPhotoFile, userId } = useContext(IdVerificationContext);
const [hasUploadedImage, setHasUploadedImage] = useState(false);
const setAndTrackIdPhotoFile = (image) => {
function setAndTrackIdPhotoFile(image) {
sendTrackEvent('edx.id_verification.upload_id', {
category: 'id_verification',
user_id: userId,
});
setHasUploadedImage(true);
setIdPhotoFile(image);
};
}
return (
<div>

View File

@@ -11,7 +11,7 @@ function CollapsibleImageHelp(props) {
userId, useCameraForId, setUseCameraForId,
} = useContext(IdVerificationContext);
const handleClick = () => {
function handleClick() {
const toggleTo = useCameraForId ? 'upload' : 'camera';
const eventName = `edx.id_verification.toggle_to.${toggleTo}`;
sendTrackEvent(eventName, {
@@ -19,7 +19,7 @@ function CollapsibleImageHelp(props) {
user_id: userId,
});
setUseCameraForId(!useCameraForId);
};
}
return (
<Collapsible

View File

@@ -1,6 +1,4 @@
import React, {
useState, useContext, useEffect, useMemo,
} from 'react';
import React, { useState, useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import { AppContext } from '@edx/frontend-platform/react';
@@ -72,7 +70,7 @@ export default function IdVerificationContextProvider({ children }) {
}
}
const contextValue = useMemo(() => ({
const contextValue = {
existingIdVerification,
facePhotoFile,
idPhotoFile,
@@ -110,9 +108,7 @@ export default function IdVerificationContextProvider({ children }) {
setMediaStream(null);
}
},
}), [authenticatedUser.name, authenticatedUser.userId, existingIdVerification, facePhotoFile,
idPhotoFile, idPhotoName, mediaAccess, mediaStream, profileDataManager, reachedSummary,
useCameraForId, verifiedName]);
};
const loadingStatuses = [IDLE_STATUS, LOADING_STATUS];
// If we are waiting for verification status or verified name history endpoint, show spinner.

View File

@@ -1,4 +1,4 @@
import React, { createContext, useMemo } from 'react';
import React, { createContext } from 'react';
import PropTypes from 'prop-types';
import { getVerifiedNameHistory } from '../account-settings/data/service';
@@ -18,10 +18,10 @@ export function VerifiedNameContextProvider({ children }) {
verifiedName = getMostRecentApprovedOrPendingVerifiedName(results);
}
const value = useMemo(() => ({
const value = {
verifiedNameHistoryCallStatus: status,
verifiedName,
}), [status, verifiedName]);
};
return (<VerifiedNameContext.Provider value={value}>{children}</VerifiedNameContext.Provider>);
}

View File

@@ -1,2 +1 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './IdVerificationPage';

View File

@@ -63,7 +63,6 @@ export function EnableCameraDirectionsPanel(props) {
</>
);
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}

View File

@@ -30,12 +30,12 @@ function GetNameIdPanel(props) {
}
}, [idPhotoName, location.state, nameOnAccountValue, setIdPhotoName]);
const handleSubmit = (e) => {
function handleSubmit(e) {
e.preventDefault();
if (idPhotoName) {
push(nextPanelSlug);
}
};
}
return (
<BasePanel

View File

@@ -59,9 +59,8 @@ function SummaryPanel(props) {
);
}
// eslint-disable-next-line react/no-unstable-nested-components
function SubmitButton() {
const handleClick = async () => {
async function handleClick() {
setIsSubmitting(true);
const verificationData = {
facePhotoFile,
@@ -86,7 +85,7 @@ function SummaryPanel(props) {
setIsSubmitting(false);
setSubmissionError(result);
}
};
}
return (
<Button
title="Confirmation"

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-import-assign */
import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';

View File

@@ -13,7 +13,6 @@ jest.mock('jslib-html5-camera-photo');
jest.mock('@tensorflow-models/blazeface');
jest.mock('@edx/frontend-platform/analytics');
// eslint-disable-next-line no-import-assign
analytics.sendTrackEvent = jest.fn();
window.HTMLMediaElement.prototype.play = () => {};

View File

@@ -18,33 +18,15 @@ jest.mock('../VerifiedNameContext', () => {
VerifiedNameContextProvider: jest.fn(({ children }) => children),
};
});
jest.mock('../panels/ReviewRequirementsPanel', () => function () {
return null;
});
jest.mock('../panels/RequestCameraAccessPanel', () => function () {
return null;
});
jest.mock('../panels/PortraitPhotoContextPanel', () => function () {
return null;
});
jest.mock('../panels/TakePortraitPhotoPanel', () => function () {
return null;
});
jest.mock('../panels/IdContextPanel', () => function () {
return null;
});
jest.mock('../panels/GetNameIdPanel', () => function () {
return null;
});
jest.mock('../panels/TakeIdPhotoPanel', () => function () {
return null;
});
jest.mock('../panels/SummaryPanel', () => function () {
return null;
});
jest.mock('../panels/SubmittedPanel', () => function () {
return null;
});
jest.mock('../panels/ReviewRequirementsPanel', () => () => (<></>));
jest.mock('../panels/RequestCameraAccessPanel', () => () => (<></>));
jest.mock('../panels/PortraitPhotoContextPanel', () => () => (<></>));
jest.mock('../panels/TakePortraitPhotoPanel', () => () => (<></>));
jest.mock('../panels/IdContextPanel', () => () => (<></>));
jest.mock('../panels/GetNameIdPanel', () => () => (<></>));
jest.mock('../panels/TakeIdPhotoPanel', () => () => (<></>));
jest.mock('../panels/SummaryPanel', () => () => (<></>));
jest.mock('../panels/SubmittedPanel', () => () => (<></>));
const IntlIdVerificationPage = injectIntl(IdVerificationPage);

View File

@@ -4,12 +4,14 @@ import { render, cleanup, waitFor } from '@testing-library/react';
import { getVerifiedNameHistory } from '../../account-settings/data/service';
import { VerifiedNameContext, VerifiedNameContextProvider } from '../VerifiedNameContext';
function VerifiedNameContextTestComponent() {
const VerifiedNameContextTestComponent = () => {
const { verifiedName } = useContext(VerifiedNameContext);
return (
verifiedName && (<div data-testid="verified-name">{verifiedName}</div>)
<>
{verifiedName && (<div data-testid="verified-name">{verifiedName}</div>)}
</>
);
}
};
jest.mock('../../account-settings/data/service', () => ({
getVerifiedNameHistory: jest.fn(() => ({})),

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-import-assign */
import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';

View File

@@ -12,9 +12,7 @@ jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
jest.mock('../../Camera', () => function () {
return null;
});
jest.mock('../../Camera', () => () => (<></>));
const history = createMemoryHistory();

View File

@@ -4,7 +4,7 @@ import { render, waitFor } from '@testing-library/react';
import { useAsyncCall } from '../hooks';
import { LOADING_STATUS, SUCCESS_STATUS, FAILURE_STATUS } from '../constants';
function TestUseAsyncCallHookComponent({ asyncFunc }) {
const TestUseAsyncCallHookComponent = ({ asyncFunc }) => {
const { status, data } = useAsyncCall(asyncFunc);
return (
<>
@@ -12,7 +12,7 @@ function TestUseAsyncCallHookComponent({ asyncFunc }) {
{data && Object.keys(data).length !== 0 && <div data-testid="data">{ data.data }</div>}
</>
);
}
};
TestUseAsyncCallHookComponent.propTypes = {
asyncFunc: PropTypes.func.isRequired,