Skip to content

Commit

Permalink
[native][web] Convert account deletion components to hooks
Browse files Browse the repository at this point in the history
Summary:
While I was refactoring these, I figured they made more sense as function components.

Depends on D9425

Test Plan: Test account deletion components and make sure they still work

Reviewers: varun, atul, ginsu

Reviewed By: ginsu

Subscribers: tomek, wyilio

Differential Revision: https://phab.comm.dev/D9426
  • Loading branch information
Ashoat committed Oct 9, 2023
1 parent ba9a43c commit e8593f2
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 168 deletions.
136 changes: 44 additions & 92 deletions native/profile/delete-account.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import {
} from 'lib/actions/user-actions.js';
import { preRequestUserStateSelector } from 'lib/selectors/account-selectors.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import type { LogOutResult } from 'lib/types/account-types.js';
import type { LoadingStatus } from 'lib/types/loading-types.js';
import type { PreRequestUserState } from 'lib/types/session-types.js';
import type { DispatchActionPromise } from 'lib/utils/action-utils.js';
import {
useServerCall,
useDispatchActionPromise,
Expand All @@ -25,88 +21,69 @@ import { useSelector } from '../redux/redux-utils.js';
import { useStyles } from '../themes/colors.js';
import Alert from '../utils/alert.js';

type Props = {
// Redux state
+loadingStatus: LoadingStatus,
+preRequestUserState: PreRequestUserState,
+styles: typeof unboundStyles,
// Redux dispatch functions
+dispatchActionPromise: DispatchActionPromise,
// async functions that hit server APIs
+deleteAccount: (
preRequestUserState: PreRequestUserState,
) => Promise<LogOutResult>,
};
class DeleteAccount extends React.PureComponent<Props> {
render() {
const loadingStatusSelector = createLoadingStatusSelector(
deleteAccountActionTypes,
);

const DeleteAccount: React.ComponentType<{ ... }> = React.memo<{ ... }>(
function DeleteAccount() {
const loadingStatus = useSelector(loadingStatusSelector);
const preRequestUserState = useSelector(preRequestUserStateSelector);
const styles = useStyles(unboundStyles);

const dispatchActionPromise = useDispatchActionPromise();
const callDeleteAccount = useServerCall(deleteAccount);

const buttonContent =
this.props.loadingStatus === 'loading' ? (
loadingStatus === 'loading' ? (
<ActivityIndicator size="small" color="white" />
) : (
<Text style={this.props.styles.saveText}>Delete account</Text>
<Text style={styles.saveText}>Delete account</Text>
);

const noWayToReverseThisStyles = React.useMemo(
() => [styles.warningText, styles.lastWarningText],
[styles.warningText, styles.lastWarningText],
);

const deleteAction = React.useCallback(async () => {
try {
await deleteNativeCredentialsFor();
return await callDeleteAccount(preRequestUserState);
} catch (e) {
Alert.alert('Unknown error', 'Uhh... try again?', [{ text: 'OK' }], {
cancelable: false,
});
throw e;
}
}, [callDeleteAccount, preRequestUserState]);

const onDelete = React.useCallback(() => {
dispatchActionPromise(deleteAccountActionTypes, deleteAction());
}, [dispatchActionPromise, deleteAction]);

return (
<ScrollView
contentContainerStyle={this.props.styles.scrollViewContentContainer}
style={this.props.styles.scrollView}
contentContainerStyle={styles.scrollViewContentContainer}
style={styles.scrollView}
>
<View>
<Text style={this.props.styles.warningText}>
<Text style={styles.warningText}>
Your account will be permanently deleted.
</Text>
</View>
<View>
<Text
style={[
this.props.styles.warningText,
this.props.styles.lastWarningText,
]}
>
<Text style={noWayToReverseThisStyles}>
There is no way to reverse this.
</Text>
</View>
<Button
onPress={this.submitDeletion}
style={this.props.styles.deleteButton}
>
<Button onPress={onDelete} style={styles.deleteButton}>
{buttonContent}
</Button>
</ScrollView>
);
}

submitDeletion = () => {
this.props.dispatchActionPromise(
deleteAccountActionTypes,
this.deleteAccount(),
);
};

async deleteAccount() {
try {
await deleteNativeCredentialsFor();
const result = await this.props.deleteAccount(
this.props.preRequestUserState,
);
return result;
} catch (e) {
if (e.message === 'invalid_credentials') {
Alert.alert(
'Incorrect password',
'The password you entered is incorrect',
[{ text: 'OK' }],
{ cancelable: false },
);
} else {
Alert.alert('Unknown error', 'Uhh... try again?', [{ text: 'OK' }], {
cancelable: false,
});
}
throw e;
}
}
}
},
);

const unboundStyles = {
deleteButton: {
Expand Down Expand Up @@ -139,29 +116,4 @@ const unboundStyles = {
},
};

const loadingStatusSelector = createLoadingStatusSelector(
deleteAccountActionTypes,
);

const ConnectedDeleteAccount: React.ComponentType<{ ... }> = React.memo<{
...
}>(function ConnectedDeleteAccount() {
const loadingStatus = useSelector(loadingStatusSelector);
const preRequestUserState = useSelector(preRequestUserStateSelector);
const styles = useStyles(unboundStyles);

const dispatchActionPromise = useDispatchActionPromise();
const callDeleteAccount = useServerCall(deleteAccount);

return (
<DeleteAccount
loadingStatus={loadingStatus}
preRequestUserState={preRequestUserState}
styles={styles}
dispatchActionPromise={dispatchActionPromise}
deleteAccount={callDeleteAccount}
/>
);
});

export default ConnectedDeleteAccount;
export default DeleteAccount;
116 changes: 40 additions & 76 deletions web/settings/account-delete-modal.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import { useModalContext } from 'lib/components/modal-provider.react.js';
import SWMansionIcon from 'lib/components/SWMansionIcon.react.js';
import { preRequestUserStateSelector } from 'lib/selectors/account-selectors.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import type { LogOutResult } from 'lib/types/account-types.js';
import type { PreRequestUserState } from 'lib/types/session-types.js';
import type { DispatchActionPromise } from 'lib/utils/action-utils.js';
import {
useDispatchActionPromise,
useServerCall,
Expand All @@ -23,35 +20,50 @@ import Button, { buttonThemes } from '../components/button.react.js';
import Modal from '../modals/modal.react.js';
import { useSelector } from '../redux/redux-utils.js';

type Props = {
+preRequestUserState: PreRequestUserState,
+inputDisabled: boolean,
+dispatchActionPromise: DispatchActionPromise,
+deleteAccount: (
preRequestUserState: PreRequestUserState,
) => Promise<LogOutResult>,
+popModal: () => void,
};
type State = {
+errorMessage: string,
};
const deleteAccountLoadingStatusSelector = createLoadingStatusSelector(
deleteAccountActionTypes,
);

class AccountDeleteModal extends React.PureComponent<Props, State> {
state = {
errorMessage: '',
};
const AccountDeleteModal: React.ComponentType<{}> = React.memo<{}>(
function AccountDeleteModal(): React.Node {
const preRequestUserState = useSelector(preRequestUserStateSelector);
const inputDisabled = useSelector(
state => deleteAccountLoadingStatusSelector(state) === 'loading',
);
const callDeleteAccount = useServerCall(deleteAccount);
const dispatchActionPromise = useDispatchActionPromise();

const { popModal } = useModalContext();

const [errorMessage, setErrorMessage] = React.useState('');

render() {
let errorMsg;
if (this.state.errorMessage) {
errorMsg = (
<div className={css.form_error}>{this.state.errorMessage}</div>
);
if (errorMessage) {
errorMsg = <div className={css.form_error}>{errorMessage}</div>;
}

const { inputDisabled } = this.props;
const deleteAction = React.useCallback(async () => {
try {
setErrorMessage('');
const response = await callDeleteAccount(preRequestUserState);
popModal();
return response;
} catch (e) {
setErrorMessage('unknown error');
throw e;
}
}, [callDeleteAccount, preRequestUserState, popModal]);

const onDelete = React.useCallback(
(event: SyntheticEvent<HTMLButtonElement>) => {
event.preventDefault();
dispatchActionPromise(deleteAccountActionTypes, deleteAction());
},
[dispatchActionPromise, deleteAction],
);

return (
<Modal name="Delete Account" onClose={this.props.popModal} size="large">
<Modal name="Delete Account" onClose={popModal} size="large">
<div className={css.modal_body}>
<form method="POST">
<SWMansionIcon icon="warning-circle" size={22} />
Expand All @@ -64,7 +76,7 @@ class AccountDeleteModal extends React.PureComponent<Props, State> {
variant="filled"
buttonColor={buttonThemes.danger}
type="submit"
onClick={this.onDelete}
onClick={onDelete}
disabled={inputDisabled}
>
Delete Account
Expand All @@ -75,55 +87,7 @@ class AccountDeleteModal extends React.PureComponent<Props, State> {
</div>
</Modal>
);
}

onDelete = (event: SyntheticEvent<HTMLButtonElement>) => {
event.preventDefault();
this.props.dispatchActionPromise(
deleteAccountActionTypes,
this.deleteAction(),
);
};

async deleteAction() {
try {
const response = await this.props.deleteAccount(
this.props.preRequestUserState,
);
this.props.popModal();
return response;
} catch (e) {
this.setState({ errorMessage: 'unknown error' });
throw e;
}
}
}

const deleteAccountLoadingStatusSelector = createLoadingStatusSelector(
deleteAccountActionTypes,
);

const ConnectedAccountDeleteModal: React.ComponentType<{}> = React.memo<{}>(
function ConnectedAccountDeleteModal(): React.Node {
const preRequestUserState = useSelector(preRequestUserStateSelector);
const inputDisabled = useSelector(
state => deleteAccountLoadingStatusSelector(state) === 'loading',
);
const callDeleteAccount = useServerCall(deleteAccount);
const dispatchActionPromise = useDispatchActionPromise();

const modalContext = useModalContext();

return (
<AccountDeleteModal
preRequestUserState={preRequestUserState}
inputDisabled={inputDisabled}
deleteAccount={callDeleteAccount}
dispatchActionPromise={dispatchActionPromise}
popModal={modalContext.popModal}
/>
);
},
);

export default ConnectedAccountDeleteModal;
export default AccountDeleteModal;

0 comments on commit e8593f2

Please sign in to comment.