From 1f3df7365bdbe34d1998adc30281a16b5a7b5e31 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Mon, 4 Dec 2017 16:46:42 +0100 Subject: feat(Account): Enable a user to delete their own account --- src/actions/user.js | 1 + src/api/UserApi.js | 4 ++ src/api/server/ServerApi.js | 13 ++++++ .../settings/account/AccountDashboard.js | 49 ++++++++++++++++++++-- src/components/ui/Button.js | 2 +- src/containers/settings/AccountScreen.js | 5 +++ src/i18n/locales/en-US.json | 4 ++ src/stores/UserStore.js | 6 +++ src/styles/settings.scss | 4 ++ 9 files changed, 84 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/actions/user.js b/src/actions/user.js index fe32b8a05..ccf1fa56a 100644 --- a/src/actions/user.js +++ b/src/actions/user.js @@ -27,4 +27,5 @@ export default { importLegacyServices: PropTypes.arrayOf(PropTypes.shape({ recipe: PropTypes.string.isRequired, })).isRequired, + delete: {}, }; diff --git a/src/api/UserApi.js b/src/api/UserApi.js index e8fd75bed..edfb88988 100644 --- a/src/api/UserApi.js +++ b/src/api/UserApi.js @@ -46,4 +46,8 @@ export default class UserApi { getLegacyServices() { return this.server.getLegacyServices(); } + + delete() { + return this.server.deleteAccount(); + } } diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index f25f02eaa..644bf20cd 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js @@ -125,6 +125,19 @@ export default class ServerApi { return user; } + async deleteAccount() { + const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me`, this._prepareAuthRequest({ + method: 'DELETE', + })); + if (!request.ok) { + throw request; + } + const data = await request.json(); + + console.debug('ServerApi::deleteAccount resolves', data); + return data; + } + // Services async getServices() { const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/services`, this._prepareAuthRequest({ diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 75dbdef49..89fa07800 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js @@ -28,6 +28,10 @@ const messages = defineMessages({ id: 'settings.account.headlineInvoices', defaultMessage: '!!Invoices', }, + headlineDangerZone: { + id: 'settings.account.headlineDangerZone', + defaultMessage: '!!Danger Zone', + }, manageSubscriptionButtonLabel: { id: 'settings.account.manageSubscription.label', defaultMessage: '!!!Manage your subscription', @@ -72,6 +76,18 @@ const messages = defineMessages({ id: 'settings.account.mining.cancel', defaultMessage: '!!!Cancel mining', }, + deleteAccount: { + id: 'settings.account.deleteAccount', + defaultMessage: '!!!Delete account', + }, + deleteInfo: { + id: 'settings.account.deleteInfo', + defaultMessage: '!!!If you don\'t need your Franz account any longer, you can delete your account and all related data here.', + }, + deleteEmailSent: { + id: 'settings.account.deleteEmailSent', + defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', + }, }); @observer @@ -90,6 +106,9 @@ export default class AccountDashboard extends Component { openExternalUrl: PropTypes.func.isRequired, onCloseSubscriptionWindow: PropTypes.func.isRequired, stopMiner: PropTypes.func.isRequired, + deleteAccount: PropTypes.func.isRequired, + isLoadingDeleteAccount: PropTypes.bool.isRequired, + isDeleteAccountSuccessful: PropTypes.bool.isRequired, }; static contextTypes = { @@ -111,6 +130,9 @@ export default class AccountDashboard extends Component { retryUserInfoRequest, onCloseSubscriptionWindow, stopMiner, + deleteAccount, + isLoadingDeleteAccount, + isDeleteAccountSuccessful, } = this.props; const { intl } = this.context; @@ -201,7 +223,7 @@ export default class AccountDashboard extends Component { /> -
+

{intl.formatMessage(messages.headlineInvoices)}

@@ -230,7 +252,7 @@ export default class AccountDashboard extends Component { {user.isMiner && (
-
+

{intl.formatMessage(messages.headlineSubscription)}

@@ -267,7 +289,7 @@ export default class AccountDashboard extends Component { ) : (
-
+

{intl.formatMessage(messages.headlineUpgrade)}

) )} + +
+
+

{intl.formatMessage(messages.headlineDangerZone)}

+ {!isDeleteAccountSuccessful && ( +
+

{intl.formatMessage(messages.deleteInfo)}

+
+ )} + {isDeleteAccountSuccessful && ( +

{intl.formatMessage(messages.deleteEmailSent)}

+ )} +
+
)} +
diff --git a/src/components/ui/Button.js b/src/components/ui/Button.js index 07e94192f..554206cb7 100644 --- a/src/components/ui/Button.js +++ b/src/components/ui/Button.js @@ -68,7 +68,7 @@ export default class Button extends Component { loaded={loaded} lines={10} scale={0.4} - color={buttonType === '' ? '#FFF' : '#373a3c'} + color={buttonType !== 'secondary' ? '#FFF' : '#373a3c'} component="span" /> {label} diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index a1ac8bda3..008c495d4 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js @@ -69,6 +69,7 @@ export default class AccountScreen extends Component { render() { const { user, payment, app } = this.props.stores; const { openExternalUrl } = this.props.actions.app; + const { user: userActions } = this.props.actions; const isLoadingUserInfo = user.getUserInfoRequest.isExecuting; const isLoadingOrdersInfo = payment.ordersDataRequest.isExecuting; @@ -89,6 +90,9 @@ export default class AccountScreen extends Component { openExternalUrl={url => openExternalUrl({ url })} onCloseSubscriptionWindow={() => this.onCloseWindow()} stopMiner={() => this.stopMiner()} + deleteAccount={userActions.delete} + isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} + isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} /> ); } @@ -109,6 +113,7 @@ AccountScreen.wrappedComponent.propTypes = { }).isRequired, user: PropTypes.shape({ update: PropTypes.func.isRequired, + delete: PropTypes.func.isRequired, }).isRequired, }).isRequired, }; diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 380871668..48b408e59 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -70,6 +70,7 @@ "settings.account.headlineSubscription": "Your subscription", "settings.account.headlineUpgrade": "Upgrade your account & support Franz", "settings.account.headlineInvoices": "Invoices", + "settings.account.headlineDangerZone": "Danger Zone", "settings.account.manageSubscription.label": "Manage your subscription", "settings.account.accountType.basic": "Basic Account", "settings.account.accountType.premium": "Premium Supporter Account", @@ -86,6 +87,9 @@ "settings.account.mining.active": "You are right now performing {hashes} calculations per second.", "settings.account.mining.moreInformation": "Get more information", "settings.account.mining.cancel": "Cancel mining", + "settings.account.deleteAccount": "Delete account", + "settings.account.deleteInfo": "If you don't need your Franz account any longer, you can delete your account and all related data here.", + "settings.account.deleteEmailSent": "You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", "settings.navigation.availableServices": "Available services", "settings.navigation.yourServices": "Your services", "settings.navigation.account": "Account", diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 1cb2ecac3..09000dcdb 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js @@ -26,6 +26,7 @@ export default class UserStore extends Store { @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices'); + @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete'); @observable isImportLegacyServicesExecuting = false; @observable isImportLegacyServicesCompleted = false; @@ -57,6 +58,7 @@ export default class UserStore extends Store { this.actions.user.update.listen(this._update.bind(this)); this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this)); + this.actions.user.delete.listen(this._delete.bind(this)); // Reactions this.registerReactions([ @@ -212,6 +214,10 @@ export default class UserStore extends Store { this.isImportLegacyServicesCompleted = true; } + @action async _delete() { + this.deleteAccountRequest.execute(); + } + // This is a mobx autorun which forces the user to login if not authenticated _requireAuthenticatedUser = () => { if (this.isTokenExpired) { diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 6e93094b4..73cef0813 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss @@ -281,6 +281,10 @@ margin-left: auto; } + .franz-form__button { + white-space: nowrap; + } + div { height: auto; } -- cgit v1.2.3-70-g09d2