Skip to content

Commit

Permalink
Merge pull request #23651 from code-dot-org/new-student-deletion-flow
Browse files Browse the repository at this point in the history
New student deletion flow
  • Loading branch information
Madelyn Kasula committed Jul 12, 2018
2 parents cff4406 + 7dceaff commit fff27de
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 11 deletions.
12 changes: 12 additions & 0 deletions apps/i18n/common/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,18 @@
"defaultProjectNameBasketball": "Basketball Project",
"defaultProjectName": "My Project",
"delete": "Delete",
"deleteAccount": "Delete Account",
"deleteAccount_warning": "Deleting your account will permanently erase all personal information, coursework, and projects connected to this account.",
"deleteAccountDialog_header": "Are you sure you want to delete your account?",
"deleteAccountDialog_body1": "WARNING: ",
"deleteAccountDialog_body2": "Deleting your account will ",
"deleteAccountDialog_body3": "permanently erase ",
"deleteAccountDialog_body4": "all your personal information, coursework, and projects linked to this account after 28 days.",
"deleteAccountDialog_button": "Delete my Account",
"deleteAccountDialog_currentPassword": "Current password:",
"deleteAccountDialog_verification": "To verify, type {verificationString} below:",
"deleteAccountDialog_verificationString": "DELETE MY ACCOUNT",
"deleteAccountDialog_emailUs": "If you delete your account and change your mind, you can email us at support@code.org within 28 days to recover your account.",
"deleteConfirm": "Delete?",
"deleteProject": "Delete Project",
"deleteSection": "Delete Section",
Expand Down
18 changes: 16 additions & 2 deletions apps/src/lib/ui/accounts/BootstrapButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,34 @@ const styles = {
},
};

const BUTTON_TYPE = {
DANGER: 'danger',
};

export default class BootstrapButton extends React.Component {
static propTypes = {
style: PropTypes.object,
onClick: PropTypes.func.isRequired,
disabled: PropTypes.bool,
text: PropTypes.string.isRequired
text: PropTypes.string.isRequired,
type: PropTypes.oneOf(Object.values(BUTTON_TYPE)),
};

buttonClasses = () => {
switch (this.props.type) {
case BUTTON_TYPE.DANGER:
return 'btn btn-danger';
default:
return 'btn';
}
};

render() {
const {style, onClick, disabled, text} = this.props;

return (
<button
className="btn"
className={this.buttonClasses()}
style={{...styles.button, ...style}}
onClick={onClick}
tabIndex="1"
Expand Down
136 changes: 136 additions & 0 deletions apps/src/lib/ui/accounts/DeleteAccount.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, {PropTypes} from 'react';
import $ from 'jquery';
import i18n from '@cdo/locale';
import color from '@cdo/apps/util/color';
import {navigateToHref} from '@cdo/apps/utils';
import BootstrapButton from './BootstrapButton';
import DeleteAccountDialog from './DeleteAccountDialog';

export const DELETE_VERIFICATION_STRING = i18n.deleteAccountDialog_verificationString();
const styles = {
container: {
paddingTop: 20,
},
hr: {
borderColor: color.red,
},
header: {
fontSize: 22,
color: color.red,
},
warning: {
marginTop: 10,
marginBottom: 10,
},
buttonContainer: {
display: 'flex',
justifyContent: 'flex-end',
},
};
const DEFAULT_STATE = {
isDialogOpen: false,
password: '',
passwordError: '',
deleteVerification: '',
deleteError: '',
};

export default class DeleteAccount extends React.Component {
static propTypes = {
isPasswordRequired: PropTypes.bool.isRequired,
};

state = DEFAULT_STATE;

toggleDialog = () => {
this.setState(state => {
return {
...DEFAULT_STATE,
isDialogOpen: !state.isDialogOpen
};
});
};

onPasswordChange = (event) => {
this.setState({
password: event.target.value
});
};

onDeleteVerificationChange = (event) => {
this.setState({
deleteVerification: event.target.value
});
};

isValid = () => {
const {password, deleteVerification} = this.state;
const isPasswordValid = this.props.isPasswordRequired ? (password.length > 0) : true;
return isPasswordValid && deleteVerification === DELETE_VERIFICATION_STRING;
};

deleteUser = () => {
const payload = {
new_destroy_flow: true,
password_confirmation: this.state.password
};

$.ajax({
url: '/users',
method: 'DELETE',
data: payload
}).done(result => {
navigateToHref('/');
}).fail((jqXhr, _) => {
this.onFailure(jqXhr);
});
};

onFailure = (xhr) => {
const responseJSON = xhr.responseJSON;
let newState;
if (responseJSON && responseJSON.error) {
const passwordErrors = responseJSON.error.current_password;
newState = {passwordError: passwordErrors[0]};
} else {
newState = {deleteError: `Unexpected error: ${xhr.status}`};
}

this.setState(newState);
};

render() {
return (
<div style={styles.container}>
<hr style={styles.hr} />
<h2 style={styles.header}>
{i18n.deleteAccount()}
</h2>
<div style={styles.warning}>
{i18n.deleteAccount_warning()}
</div>
<div style={styles.buttonContainer}>
{/* This button intentionally uses BootstrapButton to match other account page buttons */}
<BootstrapButton
type="danger"
text={i18n.deleteAccount()}
onClick={this.toggleDialog}
/>
</div>
<DeleteAccountDialog
isOpen={this.state.isDialogOpen}
isPasswordRequired={this.props.isPasswordRequired}
password={this.state.password}
passwordError={this.state.passwordError}
deleteVerification={this.state.deleteVerification}
onPasswordChange={this.onPasswordChange}
onDeleteVerificationChange={this.onDeleteVerificationChange}
onCancel={this.toggleDialog}
disableConfirm={!this.isValid()}
deleteUser={this.deleteUser}
deleteError={this.state.deleteError}
/>
</div>
);
}
}
132 changes: 132 additions & 0 deletions apps/src/lib/ui/accounts/DeleteAccountDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, {PropTypes} from 'react';
import i18n from '@cdo/locale';
import color from '@cdo/apps/util/color';
import BaseDialog from '@cdo/apps/templates/BaseDialog';
import {Header, Field, ConfirmCancelFooter} from '../SystemDialog/SystemDialog';
import FontAwesome from '@cdo/apps/templates/FontAwesome';
import Button from '@cdo/apps/templates/Button';

const GUTTER = 20;
const styles = {
container: {
margin: GUTTER,
color: color.charcoal,
},
bodyContainer: {
display: 'flex',
alignItems: 'center',
paddingBottom: GUTTER,
},
icon: {
color: color.red,
fontSize: 100,
},
text: {
fontSize: 16,
paddingLeft: GUTTER,
},
dangerText: {
color: color.red,
},
input: {
width: 490
},
};

export default class DeleteAccountDialog extends React.Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
isPasswordRequired: PropTypes.bool.isRequired,
password: PropTypes.string.isRequired,
passwordError: PropTypes.string,
deleteVerification: PropTypes.string.isRequired,
onPasswordChange: PropTypes.func.isRequired,
onDeleteVerificationChange: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
disableConfirm: PropTypes.bool.isRequired,
deleteUser: PropTypes.func.isRequired,
deleteError: PropTypes.string,
};

render() {
const {
isOpen,
isPasswordRequired,
password,
passwordError,
deleteVerification,
onPasswordChange,
onDeleteVerificationChange,
onCancel,
disableConfirm,
deleteUser,
deleteError,
} = this.props;

return (
<BaseDialog
useUpdatedStyles
fixedWidth={550}
isOpen={isOpen}
handleClose={onCancel}
>
<div style={styles.container}>
<Header text={i18n.deleteAccountDialog_header()}/>
<div style={styles.bodyContainer}>
<FontAwesome
icon="exclamation-triangle"
style={styles.icon}
/>
<div style={styles.text}>
<strong>{i18n.deleteAccountDialog_body1()}</strong>
{i18n.deleteAccountDialog_body2()}
<strong style={styles.dangerText}>{i18n.deleteAccountDialog_body3()}</strong>
{i18n.deleteAccountDialog_body4()}
</div>
</div>
{isPasswordRequired &&
<Field
label={i18n.deleteAccountDialog_currentPassword()}
error={passwordError}
>
<input
type="password"
style={styles.input}
value={password}
onChange={onPasswordChange}
/>
</Field>
}
<Field
label={i18n.deleteAccountDialog_verification({verificationString: i18n.deleteAccountDialog_verificationString()})}
>
<input
type="text"
style={styles.input}
value={deleteVerification}
onChange={onDeleteVerificationChange}
/>
</Field>
<div>
{i18n.deleteAccountDialog_emailUs()}
</div>
<ConfirmCancelFooter
confirmText={i18n.deleteAccountDialog_button()}
confirmColor={Button.ButtonColor.red}
onConfirm={deleteUser}
onCancel={onCancel}
disableConfirm={disableConfirm}
tabIndex="1"
>
<span
id="uitest-delete-error"
style={styles.dangerText}
>
{deleteError}
</span>
</ConfirmCancelFooter>
</div>
</BaseDialog>
);
}
}
15 changes: 15 additions & 0 deletions apps/src/sites/studio/pages/devise/registrations/edit.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import ChangeEmailController from '@cdo/apps/lib/ui/accounts/ChangeEmailController';
import AddPasswordController from '@cdo/apps/lib/ui/accounts/AddPasswordController';
import ChangeUserTypeController from '@cdo/apps/lib/ui/accounts/ChangeUserTypeController';
import ManageLinkedAccountsController from '@cdo/apps/lib/ui/accounts/ManageLinkedAccountsController';
import DeleteAccount from '@cdo/apps/lib/ui/accounts/DeleteAccount';
import getScriptData from '@cdo/apps/util/getScriptData';
import experiments from '@cdo/apps/util/experiments';

// Values loaded from scriptData are always initial values, not the latest
// (possibly unsaved) user-edited values on the form.
Expand Down Expand Up @@ -49,6 +53,17 @@ $(document).ready(() => {
);
}

if (experiments.isEnabled(experiments.ACCOUNT_DELETION_NEW_FLOW)) {
const deleteAccountMountPoint = document.getElementById('delete-account');
// Replace deleteAccountMountPoint Rails contents with DeleteAccount component.
if (deleteAccountMountPoint) {
ReactDOM.render(
<DeleteAccount isPasswordRequired={isPasswordRequired}/>,
deleteAccountMountPoint
);
}
}

initializeCreatePersonalAccountControls();
});

Expand Down

0 comments on commit fff27de

Please sign in to comment.