Compare commits
1 Commits
master
...
djoy/use_h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
261769cca9 |
@@ -51,7 +51,7 @@ export class ConfirmationModal extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
status,
|
||||
open,
|
||||
errorType,
|
||||
intl,
|
||||
onCancel,
|
||||
@@ -59,7 +59,6 @@ export class ConfirmationModal extends Component {
|
||||
onSubmit,
|
||||
password,
|
||||
} = this.props;
|
||||
const open = ['confirming', 'pending', 'failed'].includes(status);
|
||||
const passwordFieldId = 'passwordFieldId';
|
||||
const invalidMessage = messages[this.getShortErrorMessageId(errorType)];
|
||||
return (
|
||||
@@ -113,7 +112,7 @@ export class ConfirmationModal extends Component {
|
||||
}
|
||||
|
||||
ConfirmationModal.propTypes = {
|
||||
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),
|
||||
open: PropTypes.bool,
|
||||
errorType: PropTypes.oneOf(['empty-password', 'server']),
|
||||
intl: intlShape.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
@@ -123,7 +122,7 @@ ConfirmationModal.propTypes = {
|
||||
};
|
||||
|
||||
ConfirmationModal.defaultProps = {
|
||||
status: null,
|
||||
open: false,
|
||||
errorType: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('ConfirmationModal', () => {
|
||||
onCancel: jest.fn(),
|
||||
onChange: jest.fn(),
|
||||
onSubmit: jest.fn(),
|
||||
status: null,
|
||||
open: false,
|
||||
errorType: null,
|
||||
password: 'fluffy bunnies',
|
||||
logoutUrl: 'http://localhost/logout',
|
||||
@@ -44,7 +44,8 @@ describe('ConfirmationModal', () => {
|
||||
<IntlProvider locale="en">
|
||||
<IntlConfirmationModal
|
||||
{...props}
|
||||
status="pending" // This will cause 'modal-backdrop' and 'show' to appear on the modal as CSS classes.
|
||||
// This will cause 'modal-backdrop' and 'show' to appear on the modal as CSS classes.
|
||||
open
|
||||
/>
|
||||
</IntlProvider>
|
||||
))
|
||||
@@ -59,7 +60,8 @@ describe('ConfirmationModal', () => {
|
||||
<IntlConfirmationModal
|
||||
{...props}
|
||||
errorType="empty-password"
|
||||
status="pending" // This will cause 'modal-backdrop' and 'show' to appear on the modal as CSS classes.
|
||||
// This will cause 'modal-backdrop' and 'show' to appear on the modal as CSS classes.
|
||||
open
|
||||
/>
|
||||
</IntlProvider>
|
||||
))
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { Button, Hyperlink } from '@edx/paragon';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
deleteAccount,
|
||||
deleteAccountConfirmation,
|
||||
deleteAccountFailure,
|
||||
deleteAccountReset,
|
||||
deleteAccountCancel,
|
||||
} from './data/actions';
|
||||
|
||||
// Messages
|
||||
import messages from './messages';
|
||||
|
||||
@@ -21,106 +11,116 @@ import ConnectedConfirmationModal from './ConfirmationModal';
|
||||
import PrintingInstructions from './PrintingInstructions';
|
||||
import ConnectedSuccessModal from './SuccessModal';
|
||||
import BeforeProceedingBanner from './BeforeProceedingBanner';
|
||||
import { postDeleteAccount } from './data/service';
|
||||
import useAction from '../../common/hooks';
|
||||
|
||||
export class DeleteAccount extends React.Component {
|
||||
state = {
|
||||
password: '',
|
||||
};
|
||||
function DeleteAccount(props) {
|
||||
const {
|
||||
logoutUrl, hasLinkedTPA, isVerifiedAccount, intl,
|
||||
} = props;
|
||||
|
||||
handleSubmit = () => {
|
||||
if (this.state.password === '') {
|
||||
this.props.deleteAccountFailure('empty-password');
|
||||
} else {
|
||||
this.props.deleteAccount(this.state.password);
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||
|
||||
const [validationError, setValidationError] = useState(null);
|
||||
const [
|
||||
deleteAccountState,
|
||||
performDeleteAccount,
|
||||
resetDeleteAccount,
|
||||
] = useAction(postDeleteAccount);
|
||||
|
||||
const canDelete = isVerifiedAccount && !hasLinkedTPA;
|
||||
const successDialogOpen = deleteAccountState.loaded;
|
||||
const error = deleteAccountState.error !== null ? 'server' : validationError;
|
||||
|
||||
// Event handlers
|
||||
const handleDeleteAccountClick = useCallback(() => {
|
||||
if (canDelete) {
|
||||
setConfirmationDialogOpen(true);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
handleCancel = () => {
|
||||
this.setState({ password: '' });
|
||||
this.props.deleteAccountCancel();
|
||||
};
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (password === '') {
|
||||
setValidationError('empty-password');
|
||||
} else {
|
||||
performDeleteAccount(password);
|
||||
}
|
||||
});
|
||||
|
||||
handlePasswordChange = (e) => {
|
||||
this.setState({ password: e.target.value.trim() });
|
||||
this.props.deleteAccountReset();
|
||||
};
|
||||
const handleCancel = useCallback(() => {
|
||||
setPassword('');
|
||||
setConfirmationDialogOpen(false);
|
||||
resetDeleteAccount();
|
||||
});
|
||||
|
||||
handleFinalClose = () => {
|
||||
global.location = this.props.logoutUrl;
|
||||
};
|
||||
const handlePasswordChange = useCallback((e) => {
|
||||
setPassword(e.target.value.trim());
|
||||
setValidationError(null);
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
hasLinkedTPA, isVerifiedAccount, status, errorType, intl,
|
||||
} = this.props;
|
||||
const canDelete = isVerifiedAccount && !hasLinkedTPA;
|
||||
const handleFinalClose = useCallback(() => {
|
||||
global.location = logoutUrl;
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="section-heading">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.header'])}
|
||||
</h2>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.subheader'])}</p>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.text.1'])}</p>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.text.2'])}</p>
|
||||
<p>
|
||||
<PrintingInstructions />
|
||||
</p>
|
||||
<p className="text-danger h6">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.text.warning'])}
|
||||
</p>
|
||||
<p>
|
||||
<Hyperlink destination="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.text.change.instead'])}
|
||||
</Hyperlink>
|
||||
</p>
|
||||
<p>
|
||||
<Button
|
||||
className="btn-outline-danger"
|
||||
onClick={canDelete ? this.props.deleteAccountConfirmation : null}
|
||||
disabled={!canDelete}
|
||||
>
|
||||
{intl.formatMessage(messages['account.settings.delete.account.button'])}
|
||||
</Button>
|
||||
</p>
|
||||
return (
|
||||
<div>
|
||||
<h2 className="section-heading">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.header'])}
|
||||
</h2>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.subheader'])}</p>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.text.1'])}</p>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.text.2'])}</p>
|
||||
<p>
|
||||
<PrintingInstructions />
|
||||
</p>
|
||||
<p className="text-danger h6">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.text.warning'])}
|
||||
</p>
|
||||
<p>
|
||||
<Hyperlink destination="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.text.change.instead'])}
|
||||
</Hyperlink>
|
||||
</p>
|
||||
<p>
|
||||
<Button
|
||||
className="btn-outline-danger"
|
||||
onClick={handleDeleteAccountClick}
|
||||
disabled={!canDelete}
|
||||
>
|
||||
{intl.formatMessage(messages['account.settings.delete.account.button'])}
|
||||
</Button>
|
||||
</p>
|
||||
|
||||
{isVerifiedAccount ? null : (
|
||||
<BeforeProceedingBanner
|
||||
instructionMessageId="account.settings.delete.account.please.activate"
|
||||
supportUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-activate-my-account-"
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasLinkedTPA ? (
|
||||
<BeforeProceedingBanner
|
||||
instructionMessageId="account.settings.delete.account.please.unlink"
|
||||
supportUrl="https://support.edx.org/hc/en-us/articles/207206067"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<ConnectedConfirmationModal
|
||||
status={status}
|
||||
errorType={errorType}
|
||||
onSubmit={this.handleSubmit}
|
||||
onCancel={this.handleCancel}
|
||||
onChange={this.handlePasswordChange}
|
||||
password={this.state.password}
|
||||
{isVerifiedAccount ? null : (
|
||||
<BeforeProceedingBanner
|
||||
instructionMessageId="account.settings.delete.account.please.activate"
|
||||
supportUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-activate-my-account-"
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConnectedSuccessModal status={status} onClose={this.handleFinalClose} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
{hasLinkedTPA ? (
|
||||
<BeforeProceedingBanner
|
||||
instructionMessageId="account.settings.delete.account.please.unlink"
|
||||
supportUrl="https://support.edx.org/hc/en-us/articles/207206067"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<ConnectedConfirmationModal
|
||||
open={confirmationDialogOpen}
|
||||
errorType={error}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
onChange={handlePasswordChange}
|
||||
password={password}
|
||||
/>
|
||||
|
||||
<ConnectedSuccessModal open={successDialogOpen} onClose={handleFinalClose} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DeleteAccount.propTypes = {
|
||||
deleteAccount: PropTypes.func.isRequired,
|
||||
deleteAccountConfirmation: PropTypes.func.isRequired,
|
||||
deleteAccountFailure: PropTypes.func.isRequired,
|
||||
deleteAccountReset: PropTypes.func.isRequired,
|
||||
deleteAccountCancel: PropTypes.func.isRequired,
|
||||
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),
|
||||
errorType: PropTypes.oneOf(['empty-password', 'server']),
|
||||
hasLinkedTPA: PropTypes.bool,
|
||||
isVerifiedAccount: PropTypes.bool,
|
||||
logoutUrl: PropTypes.string.isRequired,
|
||||
@@ -130,20 +130,6 @@ DeleteAccount.propTypes = {
|
||||
DeleteAccount.defaultProps = {
|
||||
hasLinkedTPA: false,
|
||||
isVerifiedAccount: true,
|
||||
status: null,
|
||||
errorType: null,
|
||||
};
|
||||
|
||||
// Assume we're part of the accountSettings state.
|
||||
const mapStateToProps = state => state.accountSettings.deleteAccount;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
deleteAccount,
|
||||
deleteAccountConfirmation,
|
||||
deleteAccountFailure,
|
||||
deleteAccountReset,
|
||||
deleteAccountCancel,
|
||||
},
|
||||
)(injectIntl(DeleteAccount));
|
||||
export default injectIntl(DeleteAccount);
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-i18n';
|
||||
import { IntlProvider } from '@edx/frontend-i18n';
|
||||
|
||||
// Testing the modals separately, they just clutter up the snapshots if included here.
|
||||
jest.mock('./ConfirmationModal');
|
||||
jest.mock('./SuccessModal');
|
||||
|
||||
import { DeleteAccount } from './DeleteAccount'; // eslint-disable-line import/first
|
||||
|
||||
const IntlDeleteAccount = injectIntl(DeleteAccount);
|
||||
import DeleteAccount from './DeleteAccount'; // eslint-disable-line import/first
|
||||
|
||||
describe('DeleteAccount', () => {
|
||||
let props = {};
|
||||
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
deleteAccount: jest.fn(),
|
||||
deleteAccountConfirmation: jest.fn(),
|
||||
deleteAccountFailure: jest.fn(),
|
||||
deleteAccountReset: jest.fn(),
|
||||
deleteAccountCancel: jest.fn(),
|
||||
status: null,
|
||||
errorType: null,
|
||||
hasLinkedTPA: false,
|
||||
isVerifiedAccount: true,
|
||||
logoutUrl: 'http://localhost/logout',
|
||||
@@ -32,7 +23,7 @@ describe('DeleteAccount', () => {
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<IntlDeleteAccount
|
||||
<DeleteAccount
|
||||
{...props}
|
||||
/>
|
||||
</IntlProvider>
|
||||
@@ -45,7 +36,7 @@ describe('DeleteAccount', () => {
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<IntlDeleteAccount
|
||||
<DeleteAccount
|
||||
{...props}
|
||||
isVerifiedAccount={false}
|
||||
/>
|
||||
@@ -59,7 +50,7 @@ describe('DeleteAccount', () => {
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<IntlDeleteAccount
|
||||
<DeleteAccount
|
||||
{...props}
|
||||
hasLinkedTPA
|
||||
/>
|
||||
|
||||
@@ -6,10 +6,10 @@ import { Modal } from '@edx/paragon';
|
||||
import messages from './messages';
|
||||
|
||||
export const SuccessModal = (props) => {
|
||||
const { status, intl, onClose } = props;
|
||||
const { open, intl, onClose } = props;
|
||||
return (
|
||||
<Modal
|
||||
open={status === 'deleted'}
|
||||
open={open}
|
||||
title={intl.formatMessage(messages['account.settings.delete.account.modal.after.header'])}
|
||||
body={
|
||||
<div>
|
||||
@@ -26,13 +26,13 @@ export const SuccessModal = (props) => {
|
||||
};
|
||||
|
||||
SuccessModal.propTypes = {
|
||||
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),
|
||||
open: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
SuccessModal.defaultProps = {
|
||||
status: null,
|
||||
open: false,
|
||||
};
|
||||
|
||||
export default injectIntl(SuccessModal);
|
||||
|
||||
@@ -16,30 +16,15 @@ describe('SuccessModal', () => {
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
onClose: jest.fn(),
|
||||
status: null,
|
||||
open: false,
|
||||
};
|
||||
});
|
||||
|
||||
it('should match default closed success modal snapshot', () => {
|
||||
let tree = renderer.create((
|
||||
const tree = renderer.create((
|
||||
<IntlProvider locale="en"><IntlSuccessModal {...props} /></IntlProvider>))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
||||
tree = renderer.create((
|
||||
<IntlProvider locale="en"><IntlSuccessModal {...props} status="confirming" /></IntlProvider>))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
||||
tree = renderer.create((
|
||||
<IntlProvider locale="en"><IntlSuccessModal {...props} status="pending" /></IntlProvider>))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
||||
tree = renderer.create((
|
||||
<IntlProvider locale="en"><IntlSuccessModal {...props} status="failed" /></IntlProvider>))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match open success modal snapshot', () => {
|
||||
@@ -48,7 +33,8 @@ describe('SuccessModal', () => {
|
||||
<IntlProvider locale="en">
|
||||
<IntlSuccessModal
|
||||
{...props}
|
||||
status="deleted" // This will cause 'modal-backdrop' and 'show' to appear on the modal as CSS classes.
|
||||
// This will cause 'modal-backdrop' and 'show' to appear on the modal as CSS classes.
|
||||
open
|
||||
/>
|
||||
</IntlProvider>
|
||||
))
|
||||
|
||||
@@ -61,21 +61,21 @@ exports[`SuccessModal should match default closed success modal snapshot 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 2`] = `
|
||||
exports[`SuccessModal should match open success modal snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="fade"
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
/>
|
||||
<div
|
||||
className="modal js-close-modal-on-click fade"
|
||||
className="modal js-close-modal-on-click show d-block"
|
||||
onClick={[Function]}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id4"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
@@ -121,186 +121,3 @@ exports[`SuccessModal should match default closed success modal snapshot 2`] = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 3`] = `
|
||||
<div>
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>
|
||||
<div
|
||||
className="modal js-close-modal-on-click fade"
|
||||
onClick={[Function]}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id5"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id5"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 4`] = `
|
||||
<div>
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>
|
||||
<div
|
||||
className="modal js-close-modal-on-click fade"
|
||||
onClick={[Function]}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id6"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id6"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SuccessModal should match open success modal snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
/>
|
||||
<div
|
||||
className="modal js-close-modal-on-click show d-block"
|
||||
onClick={[Function]}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id7"
|
||||
aria-modal={true}
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id7"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { utils } from '../../../common';
|
||||
|
||||
const { AsyncActionType } = utils;
|
||||
|
||||
export const DELETE_ACCOUNT = new AsyncActionType('ACCOUNT_SETTINGS', 'DELETE_ACCOUNT');
|
||||
DELETE_ACCOUNT.CONFIRMATION = 'ACCOUNT_SETTINGS__DELETE_ACCOUNT__CONFIRMATION';
|
||||
DELETE_ACCOUNT.CANCEL = 'ACCOUNT_SETTINGS__DELETE_ACCOUNT__CANCEL';
|
||||
|
||||
export const deleteAccount = password => ({
|
||||
type: DELETE_ACCOUNT.BASE,
|
||||
payload: { password },
|
||||
});
|
||||
|
||||
export const deleteAccountConfirmation = () => ({
|
||||
type: DELETE_ACCOUNT.CONFIRMATION,
|
||||
});
|
||||
|
||||
export const deleteAccountBegin = () => ({
|
||||
type: DELETE_ACCOUNT.BEGIN,
|
||||
});
|
||||
|
||||
export const deleteAccountSuccess = () => ({
|
||||
type: DELETE_ACCOUNT.SUCCESS,
|
||||
});
|
||||
|
||||
export const deleteAccountFailure = reason => ({
|
||||
type: DELETE_ACCOUNT.FAILURE,
|
||||
payload: { reason },
|
||||
});
|
||||
|
||||
// to clear errors from the confirmation modal
|
||||
export const deleteAccountReset = () => ({
|
||||
type: DELETE_ACCOUNT.RESET,
|
||||
});
|
||||
|
||||
// to close the modal
|
||||
export const deleteAccountCancel = () => ({
|
||||
type: DELETE_ACCOUNT.CANCEL,
|
||||
});
|
||||
@@ -1,60 +0,0 @@
|
||||
import { DELETE_ACCOUNT } from './actions';
|
||||
|
||||
export const defaultState = {
|
||||
status: null,
|
||||
errorType: null,
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action = null) => {
|
||||
if (action !== null) {
|
||||
switch (action.type) {
|
||||
case DELETE_ACCOUNT.CONFIRMATION:
|
||||
return {
|
||||
...state,
|
||||
status: 'confirming',
|
||||
};
|
||||
|
||||
case DELETE_ACCOUNT.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
status: 'pending',
|
||||
};
|
||||
|
||||
case DELETE_ACCOUNT.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
status: 'deleted',
|
||||
};
|
||||
|
||||
case DELETE_ACCOUNT.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
status: 'failed',
|
||||
errorType: action.payload.reason || 'server',
|
||||
};
|
||||
|
||||
case DELETE_ACCOUNT.RESET: {
|
||||
const oldStatus = state.status;
|
||||
|
||||
return {
|
||||
...state,
|
||||
// clear the error state if applicable, otherwise don't change state
|
||||
status: oldStatus === 'failed' ? 'confirming' : oldStatus,
|
||||
errorType: null,
|
||||
};
|
||||
}
|
||||
|
||||
case DELETE_ACCOUNT.CANCEL:
|
||||
return {
|
||||
...state,
|
||||
status: null,
|
||||
errorType: null,
|
||||
};
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export default reducer;
|
||||
@@ -1,107 +0,0 @@
|
||||
import reducer from './reducers';
|
||||
import {
|
||||
deleteAccountConfirmation,
|
||||
deleteAccountBegin,
|
||||
deleteAccountSuccess,
|
||||
deleteAccountFailure,
|
||||
deleteAccountReset,
|
||||
deleteAccountCancel,
|
||||
} from './actions';
|
||||
|
||||
describe('delete-account reducer', () => {
|
||||
let state = null;
|
||||
|
||||
beforeEach(() => {
|
||||
state = reducer();
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.CONFIRMATION', () => {
|
||||
const result = reducer(state, deleteAccountConfirmation());
|
||||
expect(result).toEqual({
|
||||
errorType: null,
|
||||
status: 'confirming',
|
||||
});
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.BEGIN', () => {
|
||||
const result = reducer(state, deleteAccountBegin());
|
||||
expect(result).toEqual({
|
||||
errorType: null,
|
||||
status: 'pending',
|
||||
});
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.SUCCESS', () => {
|
||||
const result = reducer(state, deleteAccountSuccess());
|
||||
expect(result).toEqual({
|
||||
errorType: null,
|
||||
status: 'deleted',
|
||||
});
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.FAILURE no reason', () => {
|
||||
const result = reducer(state, deleteAccountFailure());
|
||||
expect(result).toEqual({
|
||||
errorType: 'server',
|
||||
status: 'failed',
|
||||
});
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.FAILURE with reason', () => {
|
||||
const result = reducer(state, deleteAccountFailure('carnivorous buns'));
|
||||
expect(result).toEqual({
|
||||
errorType: 'carnivorous buns',
|
||||
status: 'failed',
|
||||
});
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.RESET no status', () => {
|
||||
const result = reducer(state, deleteAccountReset());
|
||||
expect(result).toEqual({
|
||||
errorType: null,
|
||||
status: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.RESET with failed old status', () => {
|
||||
const result = reducer(
|
||||
{
|
||||
errorType: 'carnivorous buns',
|
||||
status: 'failed',
|
||||
},
|
||||
deleteAccountReset(),
|
||||
);
|
||||
expect(result).toEqual({
|
||||
errorType: null,
|
||||
status: 'confirming',
|
||||
});
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.RESET with pending old status', () => {
|
||||
const result = reducer(
|
||||
{
|
||||
errorType: 'carnivorous buns',
|
||||
status: 'pending',
|
||||
},
|
||||
deleteAccountReset(),
|
||||
);
|
||||
expect(result).toEqual({
|
||||
errorType: null,
|
||||
status: 'pending',
|
||||
});
|
||||
});
|
||||
|
||||
it('should process DELETE_ACCOUNT.CANCEL', () => {
|
||||
const result = reducer(
|
||||
{
|
||||
errorType: 'carnivorous buns',
|
||||
status: 'failed',
|
||||
},
|
||||
deleteAccountCancel(),
|
||||
);
|
||||
expect(result).toEqual({
|
||||
errorType: null,
|
||||
status: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import { put, call, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import {
|
||||
DELETE_ACCOUNT,
|
||||
deleteAccountBegin,
|
||||
deleteAccountSuccess,
|
||||
deleteAccountFailure,
|
||||
} from './actions';
|
||||
|
||||
import { postDeleteAccount } from './service';
|
||||
|
||||
export function* handleDeleteAccount(action) {
|
||||
try {
|
||||
yield put(deleteAccountBegin());
|
||||
const response = yield call(postDeleteAccount, action.payload.password);
|
||||
yield put(deleteAccountSuccess(response));
|
||||
} catch (e) {
|
||||
if (typeof e.response.data === 'string') {
|
||||
yield put(deleteAccountFailure());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function* saga() {
|
||||
yield takeEvery(DELETE_ACCOUNT.BASE, handleDeleteAccount);
|
||||
}
|
||||
@@ -1,5 +1,2 @@
|
||||
export { default } from './DeleteAccount';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { configureService } from './data/service';
|
||||
export { DELETE_ACCOUNT } from './data/actions';
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
RESET_DRAFTS,
|
||||
} from './actions';
|
||||
|
||||
import { reducer as deleteAccountReducer, DELETE_ACCOUNT } from './delete-account';
|
||||
import { reducer as siteLanguageReducer, FETCH_SITE_LANGUAGES } from './site-language';
|
||||
import { reducer as resetPasswordReducer, RESET_PASSWORD } from './reset-password';
|
||||
import { reducer as thirdPartyAuthReducer, DISCONNECT_AUTH } from './third-party-auth';
|
||||
@@ -27,7 +26,6 @@ export const defaultState = {
|
||||
timeZones: [],
|
||||
countryTimeZones: [],
|
||||
previousSiteLanguage: null,
|
||||
deleteAccount: deleteAccountReducer(),
|
||||
siteLanguage: siteLanguageReducer(),
|
||||
resetPassword: resetPasswordReducer(),
|
||||
thirdPartyAuth: thirdPartyAuthReducer(),
|
||||
@@ -154,18 +152,6 @@ const reducer = (state = defaultState, action) => {
|
||||
// TODO: Once all the above cases have been converted into sub-reducers, we can use
|
||||
// combineReducers in this file to greatly simplify it.
|
||||
|
||||
// Delete My Account
|
||||
case DELETE_ACCOUNT.CONFIRMATION:
|
||||
case DELETE_ACCOUNT.BEGIN:
|
||||
case DELETE_ACCOUNT.SUCCESS:
|
||||
case DELETE_ACCOUNT.FAILURE:
|
||||
case DELETE_ACCOUNT.RESET:
|
||||
case DELETE_ACCOUNT.CANCEL:
|
||||
return {
|
||||
...state,
|
||||
deleteAccount: deleteAccountReducer(state.deleteAccount, action),
|
||||
};
|
||||
|
||||
case FETCH_SITE_LANGUAGES.BEGIN:
|
||||
case FETCH_SITE_LANGUAGES.SUCCESS:
|
||||
case FETCH_SITE_LANGUAGES.FAILURE:
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import { usernameSelector, userRolesSelector, siteLanguageSelector } from './selectors';
|
||||
|
||||
// Sub-modules
|
||||
import { saga as deleteAccountSaga } from './delete-account';
|
||||
import { saga as resetPasswordSaga } from './reset-password';
|
||||
import { saga as siteLanguageSaga, ApiService as SiteLanguageApiService } from './site-language';
|
||||
import { saga as thirdPartyAuthSaga } from './third-party-auth';
|
||||
@@ -103,7 +102,6 @@ export default function* saga() {
|
||||
yield takeEvery(SAVE_SETTINGS.BASE, handleSaveSettings);
|
||||
yield takeEvery(FETCH_TIME_ZONES.BASE, handleFetchTimeZones);
|
||||
yield all([
|
||||
deleteAccountSaga(),
|
||||
siteLanguageSaga(),
|
||||
resetPasswordSaga(),
|
||||
thirdPartyAuthSaga(),
|
||||
|
||||
42
src/common/hooks.js
Normal file
42
src/common/hooks.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
const useAction = (action) => {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
const performAction = async (body = null) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setLoaded(false);
|
||||
setData(null);
|
||||
setError(null);
|
||||
const result = await action(body);
|
||||
setData(result);
|
||||
setLoaded(true);
|
||||
} catch (e) {
|
||||
if (e.response.data) {
|
||||
setError(e.response.data);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
setLoaded(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const resetAction = () => {
|
||||
setLoading(false);
|
||||
setLoaded(false);
|
||||
setData(null);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
return [{
|
||||
loaded, loading, data, error,
|
||||
}, performAction, resetAction];
|
||||
};
|
||||
|
||||
export default useAction;
|
||||
Reference in New Issue
Block a user