Skip to content

Commit

Permalink
Merge pull request #3765 from nyette/WV-96
Browse files Browse the repository at this point in the history
Fixed a bug that prevents client-side data from clearing after the user requests data deletion
  • Loading branch information
DaleMcGrew authored Nov 3, 2023
2 parents 36f9e9a + f8bd75c commit feed884
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 44 deletions.
7 changes: 5 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/js/actions/VoterSessionActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import Cookies from '../common/utils/js-cookie/Cookies';
import stringContains from '../common/utils/stringContains';

export default {
voterSignOut () {
voterSignOut (signOutAllDevices = false) {
AppObservableStore.setShowSignInModal(false);
AppObservableStore.unsetStoreSignInStartFullUrl();
Dispatcher.loadEndpoint('voterSignOut', { sign_out_all_devices: false });
Dispatcher.loadEndpoint('voterSignOut', { sign_out_all_devices: signOutAllDevices });
const names = [
'voter_device_id',
'ballot_has_been_visited',
Expand Down
70 changes: 32 additions & 38 deletions src/js/components/Settings/DeleteYourAccountButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@ import withStyles from '@mui/styles/withStyles';
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'styled-components';
import VoterActions from '../../actions/VoterActions';
import historyPush from '../../common/utils/historyPush';
import { isCordova } from '../../common/utils/isCordovaOrWebApp';
import isMobileScreenSize from '../../common/utils/isMobileScreenSize';
import Cookies from '../../common/utils/js-cookie/Cookies';
import { renderLog } from '../../common/utils/logging';
import VoterStore from '../../stores/VoterStore';
import VoterActions from '../../actions/VoterActions';
import VoterSessionActions from '../../actions/VoterSessionActions';
import CircularLinkedList from '../../utils/CircularLinkedList';

const MESSAGE_ARRAY = [
'Scheduling data deletion...',
'Please wait...',
];

const MESSAGE_QUEUE = new CircularLinkedList(MESSAGE_ARRAY);

class DeleteYourAccountButton extends React.Component {
constructor (props) {
super(props);
this.state = {
deleteAllDataConfirm: false,
deletingAllDataNowStatusMessage: 'Deleting all of your data now...',
message: MESSAGE_QUEUE.head.data,
messageNode: MESSAGE_QUEUE.head,
};
}

Expand All @@ -27,54 +34,39 @@ class DeleteYourAccountButton extends React.Component {

componentWillUnmount () {
this.voterStoreListener.remove();
// The component may unmount before some of the callbacks execute in the deletion call
// Cancel them to avoid memory leaks
clearTimeout(this.changeVoterDeviceId);
clearTimeout(this.updateMessage);
clearTimeout(this.remindUser);
if (this.updateMessage) clearInterval(this.updateMessage);
}

onVoterStoreChange () {
// console.log('ReadyTaskBallot, onVoterStoreChange voter: ', VoterStore.getVoter());
const voterDeleted = VoterStore.getVoterDeleted();
// console.log('DeleteYourAccountButton, onVoterStoreChange voterDeleted: ', voterDeleted);
if (voterDeleted) {
historyPush({
pathname: '/ready', // SnackNotifier that SHOULD handle this is in Friends or Values
const voterSignedIn = VoterStore.getVoterIsSignedIn();
if (!voterSignedIn) {
const { history } = this.props;
const location = {
pathname: '/ready',
state: {
message: 'All profile information deleted.',
message: 'Your data will be deleted in 5 minutes.',
severity: 'success',
},
});
};
history.replace(location);
// There is a SnackNotifier in Friends or Values
}
}

deleteAllData = () => {
const deleteVoterAccount = true;
VoterActions.voterAccountDelete(deleteVoterAccount);
// After triggering this action (with delay, so it doesn't interfere
// with voterAccountDelete, delete the voter_device_id cookie
// from the browser so the voter can start fresh
this.changeVoterDeviceId = setTimeout(() => {
Cookies.remove('voter_device_id');
Cookies.remove('voter_device_id', { path: '/' });
Cookies.remove('voter_device_id', { path: '/', domain: 'wevote.us' });
// console.log('DeleteYourAccountButton, deleteAllData, Cookies.remove voter_device_id called');
VoterActions.voterRetrieve(); // Get fresh data from server for Voter
}, 3000);
this.setState({
deletingAllDataNow: true,
});
this.updateMessage = setTimeout(() => {
this.updateMessage = setInterval(() => {
const { messageNode } = this.state;
this.setState({
deletingAllDataNowStatusMessage: 'Please be patient...',
message: messageNode.next.data,
messageNode: messageNode.next,
});
}, 3000);
this.remindUser = setTimeout(() => {
this.setState({
deletingAllDataNowStatusMessage: 'Contact support to verify deletion...',
});
}, 6000);
const deleteVoterAccount = true;
VoterActions.voterAccountDelete(deleteVoterAccount);
VoterSessionActions.voterSignOut();
}

deleteAllDataConfirmToggle = () => {
Expand All @@ -87,7 +79,7 @@ class DeleteYourAccountButton extends React.Component {
render () {
renderLog('DeleteYourAccountButton'); // Set LOG_RENDER_EVENTS to log all renders
const { classes, leftAlign, textSizeSmall } = this.props;
const { deleteAllDataConfirm, deletingAllDataNow, deletingAllDataNowStatusMessage } = this.state;
const { deleteAllDataConfirm, deletingAllDataNow, message } = this.state;
return (
<>
{deleteAllDataConfirm && (
Expand All @@ -113,7 +105,7 @@ class DeleteYourAccountButton extends React.Component {
classes={{ root: classes.deletingAllDataNow }}
variant="contained"
>
{deletingAllDataNow ? deletingAllDataNowStatusMessage : 'Permanently delete all of your data'}
{deletingAllDataNow ? message : 'Permanently delete all of your data'}
</Button>
</DeleteYourAccountButtonInnerWrapper>
{!deletingAllDataNow && (
Expand Down Expand Up @@ -153,8 +145,10 @@ class DeleteYourAccountButton extends React.Component {
);
}
}

DeleteYourAccountButton.propTypes = {
classes: PropTypes.object,
history: PropTypes.object,
leftAlign: PropTypes.bool,
textSizeSmall: PropTypes.bool,
};
Expand Down
2 changes: 0 additions & 2 deletions src/js/stores/VoterStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -1254,10 +1254,8 @@ class VoterStore extends ReduceStore {
};

case 'voterUpdate':
// console.log('VoterStore voterUpdate: ', action.res);
if (action.res.success && action.res.voter_deleted) {
revisedState = state;
revisedState = { ...revisedState, ...this.getInitialState() };
revisedState = { ...revisedState,
voterDeleted: true,
voterNotDeleted: false,
Expand Down
35 changes: 35 additions & 0 deletions src/js/utils/CircularLinkedList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint-disable max-classes-per-file */

class LinkedListNode {
constructor (data, next = null) {
this.data = data;
this.next = next;
}
}

const isValid = (input) => {
// Check type
const isArray = input instanceof Array;
if (!isArray) throw new TypeError('Please enter an array.');
// Check length
const isLongEnough = input.length > 1;
if (!isLongEnough) throw new Error('Please enter an array a, such that a.length > 1.');
return true;
};

export default class CircularLinkedList {
constructor (input) {
if (isValid(input)) {
const dummy = new LinkedListNode(null);
let tail = dummy;
for (let i = 0; i < input.length; i++) {
const element = input.at(i);
tail.next = new LinkedListNode(element);
tail = tail.next;
}
const head = dummy.next;
tail.next = head;
this.head = head;
}
}
}

0 comments on commit feed884

Please sign in to comment.