Skip to content

Commit

Permalink
Merge pull request sharetribe#23 from Skillpickr/feature/delete-account
Browse files Browse the repository at this point in the history
Delete account
  • Loading branch information
AAC-SkillPickr authored Aug 19, 2022
2 parents 35ca2e7 + 758521a commit c9bedea
Show file tree
Hide file tree
Showing 13 changed files with 778 additions and 1 deletion.
68 changes: 68 additions & 0 deletions server/api/delete-account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const { getSdk, getTrustedSdk, handleError } = require('../api-util/sdk');

// The list of non-final transitions depends on the transaction processes
// being used on the marketplace. This list contains the non-final transitions
// of flex-default-process i.e. the ones where we do not want to allow the user
// to delete their account.
const nonFinalTransitions = [
'transition/request-payment',
'transition/request-payment-after-enquiry',
'transition/confirm-payment',
'transition/accept',
'transition/complete',
'transition/review-1-by-customer',
'transition/review-1-by-provider',
];

module.exports = (req, res) => {
const { currentPassword } = req.body;

const sdk = getSdk(req, res);

let incompleteTransactions = null;
let deletable = false;

sdk.transactions
.query({ lastTransitions: nonFinalTransitions })
.then(resp => {
// Determine whether user has transactions that are in a non-final
// state.
incompleteTransactions = resp.data.data;
deletable = incompleteTransactions.length === 0;
return getTrustedSdk(req);
})
.then(trustedSdk => {
// If the user has incomplete transactions, send a 409 Conflict response
// indicating the number of unfinished transactions.
if (!deletable) {
sendConflictResponse(incompleteTransactions, res);
return;
}

// You can use this response for client-side testing purposes before actually
// deleting your users. Uncomment the rows below to call the SDK live.

res.status(200).send({ status: 200, statusText: 'OK' });

// // If the user has only completed transactions, delete the user
// trustedSdk.currentUser.delete({ currentPassword })
// .then(resp => {
// res
// .status(resp.status)
// .send(resp);
// })
// // If deleting fails, use the built-in handler to pass the error as a response
// .catch(e => handleError(res, e))
})
.catch(e => handleError(res, e));
};

// Construct a 409 Conflict response with a message indicating the number of
// incomplete transactions. If you want, you can also include the full list
// of transactions as 'data' in the response body.
const sendConflictResponse = (incompleteTransactions, res) => {
const txLabel = incompleteTransactions.length === 1 ? 'transaction' : 'transactions';
const message = `${incompleteTransactions.length} unfinished ${txLabel}.`;

res.status(409).send({ status: 409, statusText: 'Conflict', message });
};
2 changes: 2 additions & 0 deletions server/apiRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const loginAs = require('./api/login-as');
const transactionLineItems = require('./api/transaction-line-items');
const initiatePrivileged = require('./api/initiate-privileged');
const transitionPrivileged = require('./api/transition-privileged');
const deleteAccount = require('./api/delete-account');

const createUserWithIdp = require('./api/auth/createUserWithIdp');

Expand Down Expand Up @@ -54,6 +55,7 @@ router.get('/login-as', loginAs);
router.post('/transaction-line-items', transactionLineItems);
router.post('/initiate-privileged', initiatePrivileged);
router.post('/transition-privileged', transitionPrivileged);
router.post('/delete-account', deleteAccount);

// Create user with identity provider (e.g. Facebook or Google)
// This endpoint is called to create a new user after user has confirmed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ const LayoutWrapperAccountSettingsSideNavComponent = props => {
name: 'PaymentMethodsPage',
},
},
{
text: <FormattedMessage id="LayoutWrapperAccountSettingsSideNav.deleteAccountTabTitle" />,
selected: currentTab === 'DeleteAccountPage',
id: 'DeleteAccountPageTab',
linkProps: {
name: 'DeleteAccountPage',
},
},
];

return <LayoutWrapperSideNav tabs={tabs} />;
Expand Down Expand Up @@ -142,4 +150,4 @@ const LayoutWrapperAccountSettingsSideNav = compose(withViewport)(
LayoutWrapperAccountSettingsSideNavComponent
);

export default LayoutWrapperAccountSettingsSideNav;
export default LayoutWrapperAccountSettingsSideNav;
117 changes: 117 additions & 0 deletions src/containers/DeleteAccountPage/DeleteAccountPage.duck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { storableError } from '../../util/errors';
// import { deleteUserAccount } from '../../util/api';

// ================ Action types ================ //

export const DELETE_ACCOUNT_REQUEST = 'app/DeleteAccountPage/DELETE_ACCOUNT_REQUEST';
export const DELETE_ACCOUNT_SUCCESS = 'app/DeleteAccountPage/DELETE_ACCOUNT_SUCCESS';
export const DELETE_ACCOUNT_ERROR = 'app/DeleteAccountPage/DELETE_ACCOUNT_ERROR';
export const DELETE_ACCOUNT_CLEANUP = 'app/DeleteAccountPage/DELETE_ACCOUNT_CLEANUP';

export const DELETE_ACCOUNT_CLEAR = 'app/DeleteAccountPage/DELETE_ACCOUNT_CLEAR';

export const RESET_PASSWORD_REQUEST = 'app/DeleteAccountPage/RESET_PASSWORD_REQUEST';
export const RESET_PASSWORD_SUCCESS = 'app/DeleteAccountPage/RESET_PASSWORD_SUCCESS';
export const RESET_PASSWORD_ERROR = 'app/DeleteAccountPage/RESET_PASSWORD_ERROR';

// ================ Reducer ================ //

const initialState = {
deleteAccountError: null,
deleteAccountInProgress: false,
accountDeleted: false,
resetPasswordInProgress: false,
resetPasswordError: null,
};

export default function reducer(state = initialState, action = {}) {
const { type, payload } = action;
switch (type) {
case DELETE_ACCOUNT_REQUEST:
return {
...state,
deleteAccountInProgress: true,
deleteAccountError: null,
accountDeleted: false,
};
case DELETE_ACCOUNT_SUCCESS:
return { ...state, deleteAccountInProgress: false, accountDeleted: true };
case DELETE_ACCOUNT_ERROR:
return {
...state,
deleteAccountInProgress: false,
deleteAccountError: payload,
};

case DELETE_ACCOUNT_CLEAR:
return { ...initialState };

case RESET_PASSWORD_REQUEST:
return {
...state,
resetPasswordInProgress: true,
resetPasswordError: null,
};
case RESET_PASSWORD_SUCCESS:
return { ...state, resetPasswordInProgress: false };
case RESET_PASSWORD_ERROR:
console.error(payload); // eslint-disable-line no-console
return {
...state,
resetPasswordInProgress: false,
resetPasswordError: payload,
};

default:
return state;
}
}

// ================ Action creators ================ //

export const deleteAccountRequest = () => ({ type: DELETE_ACCOUNT_REQUEST });
export const deleteAccountSuccess = () => ({ type: DELETE_ACCOUNT_SUCCESS });
export const deleteAccountError = error => ({
type: DELETE_ACCOUNT_ERROR,
payload: error,
error: true,
});

export const deleteAccountClear = () => ({ type: DELETE_ACCOUNT_CLEAR });

export const resetPasswordRequest = () => ({ type: RESET_PASSWORD_REQUEST });

export const resetPasswordSuccess = () => ({ type: RESET_PASSWORD_SUCCESS });

export const resetPasswordError = e => ({
type: RESET_PASSWORD_ERROR,
error: true,
payload: e,
});

// ================ Thunks ================ //

export const deleteAccount = params => (dispatch, getState, sdk) => {
dispatch(deleteAccountRequest());
const { currentPassword } = params;

return deleteUserAccount({ currentPassword })
.then(() => {
dispatch(deleteAccountSuccess());
return;
})
.catch(e => {
dispatch(deleteAccountError(storableError(storableError(e))));
// This is thrown so that form can be cleared
// after a timeout on deleteAccount submit handler
throw e;
});
};

export const resetPassword = email => (dispatch, getState, sdk) => {
dispatch(resetPasswordRequest());
return sdk.passwordReset
.request({ email })
.then(() => dispatch(resetPasswordSuccess()))
.catch(e => dispatch(resetPasswordError(storableError(e))));
};
Loading

0 comments on commit c9bedea

Please sign in to comment.