diff options
author | Stefan Malzner <stefan@adlk.io> | 2017-12-04 16:46:42 +0100 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2017-12-04 16:46:42 +0100 |
commit | 1f3df7365bdbe34d1998adc30281a16b5a7b5e31 (patch) | |
tree | 1a77ff4b67c52453a5a8e7ab67799dd088f94242 | |
parent | fix(Shortcuts): Fix service shortcuts for disabled but shown services (@joses... (diff) | |
download | ferdium-app-1f3df7365bdbe34d1998adc30281a16b5a7b5e31.tar.gz ferdium-app-1f3df7365bdbe34d1998adc30281a16b5a7b5e31.tar.zst ferdium-app-1f3df7365bdbe34d1998adc30281a16b5a7b5e31.zip |
feat(Account): Enable a user to delete their own account
-rw-r--r-- | src/actions/user.js | 1 | ||||
-rw-r--r-- | src/api/UserApi.js | 4 | ||||
-rw-r--r-- | src/api/server/ServerApi.js | 13 | ||||
-rw-r--r-- | src/components/settings/account/AccountDashboard.js | 49 | ||||
-rw-r--r-- | src/components/ui/Button.js | 2 | ||||
-rw-r--r-- | src/containers/settings/AccountScreen.js | 5 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 4 | ||||
-rw-r--r-- | src/stores/UserStore.js | 6 | ||||
-rw-r--r-- | src/styles/settings.scss | 4 |
9 files changed, 84 insertions, 4 deletions
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 { | |||
27 | importLegacyServices: PropTypes.arrayOf(PropTypes.shape({ | 27 | importLegacyServices: PropTypes.arrayOf(PropTypes.shape({ |
28 | recipe: PropTypes.string.isRequired, | 28 | recipe: PropTypes.string.isRequired, |
29 | })).isRequired, | 29 | })).isRequired, |
30 | delete: {}, | ||
30 | }; | 31 | }; |
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 { | |||
46 | getLegacyServices() { | 46 | getLegacyServices() { |
47 | return this.server.getLegacyServices(); | 47 | return this.server.getLegacyServices(); |
48 | } | 48 | } |
49 | |||
50 | delete() { | ||
51 | return this.server.deleteAccount(); | ||
52 | } | ||
49 | } | 53 | } |
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 { | |||
125 | return user; | 125 | return user; |
126 | } | 126 | } |
127 | 127 | ||
128 | async deleteAccount() { | ||
129 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me`, this._prepareAuthRequest({ | ||
130 | method: 'DELETE', | ||
131 | })); | ||
132 | if (!request.ok) { | ||
133 | throw request; | ||
134 | } | ||
135 | const data = await request.json(); | ||
136 | |||
137 | console.debug('ServerApi::deleteAccount resolves', data); | ||
138 | return data; | ||
139 | } | ||
140 | |||
128 | // Services | 141 | // Services |
129 | async getServices() { | 142 | async getServices() { |
130 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/services`, this._prepareAuthRequest({ | 143 | 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({ | |||
28 | id: 'settings.account.headlineInvoices', | 28 | id: 'settings.account.headlineInvoices', |
29 | defaultMessage: '!!Invoices', | 29 | defaultMessage: '!!Invoices', |
30 | }, | 30 | }, |
31 | headlineDangerZone: { | ||
32 | id: 'settings.account.headlineDangerZone', | ||
33 | defaultMessage: '!!Danger Zone', | ||
34 | }, | ||
31 | manageSubscriptionButtonLabel: { | 35 | manageSubscriptionButtonLabel: { |
32 | id: 'settings.account.manageSubscription.label', | 36 | id: 'settings.account.manageSubscription.label', |
33 | defaultMessage: '!!!Manage your subscription', | 37 | defaultMessage: '!!!Manage your subscription', |
@@ -72,6 +76,18 @@ const messages = defineMessages({ | |||
72 | id: 'settings.account.mining.cancel', | 76 | id: 'settings.account.mining.cancel', |
73 | defaultMessage: '!!!Cancel mining', | 77 | defaultMessage: '!!!Cancel mining', |
74 | }, | 78 | }, |
79 | deleteAccount: { | ||
80 | id: 'settings.account.deleteAccount', | ||
81 | defaultMessage: '!!!Delete account', | ||
82 | }, | ||
83 | deleteInfo: { | ||
84 | id: 'settings.account.deleteInfo', | ||
85 | defaultMessage: '!!!If you don\'t need your Franz account any longer, you can delete your account and all related data here.', | ||
86 | }, | ||
87 | deleteEmailSent: { | ||
88 | id: 'settings.account.deleteEmailSent', | ||
89 | defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', | ||
90 | }, | ||
75 | }); | 91 | }); |
76 | 92 | ||
77 | @observer | 93 | @observer |
@@ -90,6 +106,9 @@ export default class AccountDashboard extends Component { | |||
90 | openExternalUrl: PropTypes.func.isRequired, | 106 | openExternalUrl: PropTypes.func.isRequired, |
91 | onCloseSubscriptionWindow: PropTypes.func.isRequired, | 107 | onCloseSubscriptionWindow: PropTypes.func.isRequired, |
92 | stopMiner: PropTypes.func.isRequired, | 108 | stopMiner: PropTypes.func.isRequired, |
109 | deleteAccount: PropTypes.func.isRequired, | ||
110 | isLoadingDeleteAccount: PropTypes.bool.isRequired, | ||
111 | isDeleteAccountSuccessful: PropTypes.bool.isRequired, | ||
93 | }; | 112 | }; |
94 | 113 | ||
95 | static contextTypes = { | 114 | static contextTypes = { |
@@ -111,6 +130,9 @@ export default class AccountDashboard extends Component { | |||
111 | retryUserInfoRequest, | 130 | retryUserInfoRequest, |
112 | onCloseSubscriptionWindow, | 131 | onCloseSubscriptionWindow, |
113 | stopMiner, | 132 | stopMiner, |
133 | deleteAccount, | ||
134 | isLoadingDeleteAccount, | ||
135 | isDeleteAccountSuccessful, | ||
114 | } = this.props; | 136 | } = this.props; |
115 | const { intl } = this.context; | 137 | const { intl } = this.context; |
116 | 138 | ||
@@ -201,7 +223,7 @@ export default class AccountDashboard extends Component { | |||
201 | /> | 223 | /> |
202 | </div> | 224 | </div> |
203 | </div> | 225 | </div> |
204 | <div className="account__box account__box--last"> | 226 | <div className="account__box"> |
205 | <h2>{intl.formatMessage(messages.headlineInvoices)}</h2> | 227 | <h2>{intl.formatMessage(messages.headlineInvoices)}</h2> |
206 | <table className="invoices"> | 228 | <table className="invoices"> |
207 | <tbody> | 229 | <tbody> |
@@ -230,7 +252,7 @@ export default class AccountDashboard extends Component { | |||
230 | 252 | ||
231 | {user.isMiner && ( | 253 | {user.isMiner && ( |
232 | <div className="account franz-form"> | 254 | <div className="account franz-form"> |
233 | <div className="account__box"> | 255 | <div className="account__box account__box--last"> |
234 | <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> | 256 | <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> |
235 | <div className="account__subscription"> | 257 | <div className="account__subscription"> |
236 | <div> | 258 | <div> |
@@ -267,7 +289,7 @@ export default class AccountDashboard extends Component { | |||
267 | <Loader /> | 289 | <Loader /> |
268 | ) : ( | 290 | ) : ( |
269 | <div className="account franz-form"> | 291 | <div className="account franz-form"> |
270 | <div className="account__box account__box--last"> | 292 | <div className="account__box"> |
271 | <h2>{intl.formatMessage(messages.headlineUpgrade)}</h2> | 293 | <h2>{intl.formatMessage(messages.headlineUpgrade)}</h2> |
272 | <SubscriptionForm | 294 | <SubscriptionForm |
273 | onCloseWindow={onCloseSubscriptionWindow} | 295 | onCloseWindow={onCloseSubscriptionWindow} |
@@ -276,8 +298,29 @@ export default class AccountDashboard extends Component { | |||
276 | </div> | 298 | </div> |
277 | ) | 299 | ) |
278 | )} | 300 | )} |
301 | |||
302 | <div className="account franz-form"> | ||
303 | <div className="account__box"> | ||
304 | <h2>{intl.formatMessage(messages.headlineDangerZone)}</h2> | ||
305 | {!isDeleteAccountSuccessful && ( | ||
306 | <div className="account__subscription"> | ||
307 | <p>{intl.formatMessage(messages.deleteInfo)}</p> | ||
308 | <Button | ||
309 | label={intl.formatMessage(messages.deleteAccount)} | ||
310 | buttonType="danger" | ||
311 | onClick={() => deleteAccount()} | ||
312 | loaded={!isLoadingDeleteAccount} | ||
313 | /> | ||
314 | </div> | ||
315 | )} | ||
316 | {isDeleteAccountSuccessful && ( | ||
317 | <p>{intl.formatMessage(messages.deleteEmailSent)}</p> | ||
318 | )} | ||
319 | </div> | ||
320 | </div> | ||
279 | </div> | 321 | </div> |
280 | )} | 322 | )} |
323 | |||
281 | </div> | 324 | </div> |
282 | <ReactTooltip place="right" type="dark" effect="solid" /> | 325 | <ReactTooltip place="right" type="dark" effect="solid" /> |
283 | </div> | 326 | </div> |
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 { | |||
68 | loaded={loaded} | 68 | loaded={loaded} |
69 | lines={10} | 69 | lines={10} |
70 | scale={0.4} | 70 | scale={0.4} |
71 | color={buttonType === '' ? '#FFF' : '#373a3c'} | 71 | color={buttonType !== 'secondary' ? '#FFF' : '#373a3c'} |
72 | component="span" | 72 | component="span" |
73 | /> | 73 | /> |
74 | {label} | 74 | {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 { | |||
69 | render() { | 69 | render() { |
70 | const { user, payment, app } = this.props.stores; | 70 | const { user, payment, app } = this.props.stores; |
71 | const { openExternalUrl } = this.props.actions.app; | 71 | const { openExternalUrl } = this.props.actions.app; |
72 | const { user: userActions } = this.props.actions; | ||
72 | 73 | ||
73 | const isLoadingUserInfo = user.getUserInfoRequest.isExecuting; | 74 | const isLoadingUserInfo = user.getUserInfoRequest.isExecuting; |
74 | const isLoadingOrdersInfo = payment.ordersDataRequest.isExecuting; | 75 | const isLoadingOrdersInfo = payment.ordersDataRequest.isExecuting; |
@@ -89,6 +90,9 @@ export default class AccountScreen extends Component { | |||
89 | openExternalUrl={url => openExternalUrl({ url })} | 90 | openExternalUrl={url => openExternalUrl({ url })} |
90 | onCloseSubscriptionWindow={() => this.onCloseWindow()} | 91 | onCloseSubscriptionWindow={() => this.onCloseWindow()} |
91 | stopMiner={() => this.stopMiner()} | 92 | stopMiner={() => this.stopMiner()} |
93 | deleteAccount={userActions.delete} | ||
94 | isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} | ||
95 | isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} | ||
92 | /> | 96 | /> |
93 | ); | 97 | ); |
94 | } | 98 | } |
@@ -109,6 +113,7 @@ AccountScreen.wrappedComponent.propTypes = { | |||
109 | }).isRequired, | 113 | }).isRequired, |
110 | user: PropTypes.shape({ | 114 | user: PropTypes.shape({ |
111 | update: PropTypes.func.isRequired, | 115 | update: PropTypes.func.isRequired, |
116 | delete: PropTypes.func.isRequired, | ||
112 | }).isRequired, | 117 | }).isRequired, |
113 | }).isRequired, | 118 | }).isRequired, |
114 | }; | 119 | }; |
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 @@ | |||
70 | "settings.account.headlineSubscription": "Your subscription", | 70 | "settings.account.headlineSubscription": "Your subscription", |
71 | "settings.account.headlineUpgrade": "Upgrade your account & support Franz", | 71 | "settings.account.headlineUpgrade": "Upgrade your account & support Franz", |
72 | "settings.account.headlineInvoices": "Invoices", | 72 | "settings.account.headlineInvoices": "Invoices", |
73 | "settings.account.headlineDangerZone": "Danger Zone", | ||
73 | "settings.account.manageSubscription.label": "Manage your subscription", | 74 | "settings.account.manageSubscription.label": "Manage your subscription", |
74 | "settings.account.accountType.basic": "Basic Account", | 75 | "settings.account.accountType.basic": "Basic Account", |
75 | "settings.account.accountType.premium": "Premium Supporter Account", | 76 | "settings.account.accountType.premium": "Premium Supporter Account", |
@@ -86,6 +87,9 @@ | |||
86 | "settings.account.mining.active": "You are right now performing {hashes} calculations per second.", | 87 | "settings.account.mining.active": "You are right now performing {hashes} calculations per second.", |
87 | "settings.account.mining.moreInformation": "Get more information", | 88 | "settings.account.mining.moreInformation": "Get more information", |
88 | "settings.account.mining.cancel": "Cancel mining", | 89 | "settings.account.mining.cancel": "Cancel mining", |
90 | "settings.account.deleteAccount": "Delete account", | ||
91 | "settings.account.deleteInfo": "If you don't need your Franz account any longer, you can delete your account and all related data here.", | ||
92 | "settings.account.deleteEmailSent": "You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", | ||
89 | "settings.navigation.availableServices": "Available services", | 93 | "settings.navigation.availableServices": "Available services", |
90 | "settings.navigation.yourServices": "Your services", | 94 | "settings.navigation.yourServices": "Your services", |
91 | "settings.navigation.account": "Account", | 95 | "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 { | |||
26 | @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); | 26 | @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); |
27 | @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); | 27 | @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); |
28 | @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices'); | 28 | @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices'); |
29 | @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete'); | ||
29 | 30 | ||
30 | @observable isImportLegacyServicesExecuting = false; | 31 | @observable isImportLegacyServicesExecuting = false; |
31 | @observable isImportLegacyServicesCompleted = false; | 32 | @observable isImportLegacyServicesCompleted = false; |
@@ -57,6 +58,7 @@ export default class UserStore extends Store { | |||
57 | this.actions.user.update.listen(this._update.bind(this)); | 58 | this.actions.user.update.listen(this._update.bind(this)); |
58 | this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); | 59 | this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); |
59 | this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this)); | 60 | this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this)); |
61 | this.actions.user.delete.listen(this._delete.bind(this)); | ||
60 | 62 | ||
61 | // Reactions | 63 | // Reactions |
62 | this.registerReactions([ | 64 | this.registerReactions([ |
@@ -212,6 +214,10 @@ export default class UserStore extends Store { | |||
212 | this.isImportLegacyServicesCompleted = true; | 214 | this.isImportLegacyServicesCompleted = true; |
213 | } | 215 | } |
214 | 216 | ||
217 | @action async _delete() { | ||
218 | this.deleteAccountRequest.execute(); | ||
219 | } | ||
220 | |||
215 | // This is a mobx autorun which forces the user to login if not authenticated | 221 | // This is a mobx autorun which forces the user to login if not authenticated |
216 | _requireAuthenticatedUser = () => { | 222 | _requireAuthenticatedUser = () => { |
217 | if (this.isTokenExpired) { | 223 | 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 @@ | |||
281 | margin-left: auto; | 281 | margin-left: auto; |
282 | } | 282 | } |
283 | 283 | ||
284 | .franz-form__button { | ||
285 | white-space: nowrap; | ||
286 | } | ||
287 | |||
284 | div { | 288 | div { |
285 | height: auto; | 289 | height: auto; |
286 | } | 290 | } |