aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-12-04 16:46:42 +0100
committerLibravatar Stefan Malzner <stefan@adlk.io>2017-12-04 16:46:42 +0100
commit1f3df7365bdbe34d1998adc30281a16b5a7b5e31 (patch)
tree1a77ff4b67c52453a5a8e7ab67799dd088f94242 /src
parentfix(Shortcuts): Fix service shortcuts for disabled but shown services (@joses... (diff)
downloadferdium-app-1f3df7365bdbe34d1998adc30281a16b5a7b5e31.tar.gz
ferdium-app-1f3df7365bdbe34d1998adc30281a16b5a7b5e31.tar.zst
ferdium-app-1f3df7365bdbe34d1998adc30281a16b5a7b5e31.zip
feat(Account): Enable a user to delete their own account
Diffstat (limited to 'src')
-rw-r--r--src/actions/user.js1
-rw-r--r--src/api/UserApi.js4
-rw-r--r--src/api/server/ServerApi.js13
-rw-r--r--src/components/settings/account/AccountDashboard.js49
-rw-r--r--src/components/ui/Button.js2
-rw-r--r--src/containers/settings/AccountScreen.js5
-rw-r--r--src/i18n/locales/en-US.json4
-rw-r--r--src/stores/UserStore.js6
-rw-r--r--src/styles/settings.scss4
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 }