Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed a bug that prevents client-side data from clearing after the user requests data deletion #3765

Merged
merged 1 commit into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}