summaryrefslogtreecommitdiffstats
path: root/src/containers/settings
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
commit58cda9cc7fb79ca9df6746de7f9662bc08dc156a (patch)
tree1211600c2a5d3b5f81c435c6896618111a611720 /src/containers/settings
downloadferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.gz
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.zst
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.zip
initial commit
Diffstat (limited to 'src/containers/settings')
-rw-r--r--src/containers/settings/AccountScreen.js114
-rw-r--r--src/containers/settings/EditServiceScreen.js208
-rw-r--r--src/containers/settings/EditSettingsScreen.js167
-rw-r--r--src/containers/settings/EditUserScreen.js165
-rw-r--r--src/containers/settings/RecipesScreen.js126
-rw-r--r--src/containers/settings/ServicesScreen.js75
-rw-r--r--src/containers/settings/SettingsWindow.js43
7 files changed, 898 insertions, 0 deletions
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js
new file mode 100644
index 000000000..a1ac8bda3
--- /dev/null
+++ b/src/containers/settings/AccountScreen.js
@@ -0,0 +1,114 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react';
5
6import PaymentStore from '../../stores/PaymentStore';
7import UserStore from '../../stores/UserStore';
8import AppStore from '../../stores/AppStore';
9import { gaPage } from '../../lib/analytics';
10
11import AccountDashboard from '../../components/settings/account/AccountDashboard';
12
13const { BrowserWindow } = remote;
14
15@inject('stores', 'actions') @observer
16export default class AccountScreen extends Component {
17 componentDidMount() {
18 gaPage('Settings/Account Dashboard');
19 }
20
21 onCloseWindow() {
22 const { user, payment } = this.props.stores;
23 user.getUserInfoRequest.invalidate({ immediately: true });
24 payment.ordersDataRequest.invalidate({ immediately: true });
25 }
26
27 reloadData() {
28 const { user, payment } = this.props.stores;
29
30 user.getUserInfoRequest.reload();
31 payment.ordersDataRequest.reload();
32 payment.plansRequest.reload();
33 }
34
35 stopMiner() {
36 const { update } = this.props.actions.user;
37
38 update({ userData: {
39 isMiner: false,
40 } });
41 }
42
43 async handlePaymentDashboard() {
44 const { actions, stores } = this.props;
45
46 actions.payment.createDashboardUrl();
47
48 const dashboard = await stores.payment.createDashboardUrlRequest;
49
50 if (dashboard.url) {
51 const paymentWindow = new BrowserWindow({
52 title: '🔒 Franz Subscription Dashboard',
53 parent: remote.getCurrentWindow(),
54 modal: false,
55 width: 900,
56 minWidth: 600,
57 webPreferences: {
58 nodeIntegration: false,
59 },
60 });
61 paymentWindow.loadURL(dashboard.url);
62
63 paymentWindow.on('closed', () => {
64 this.onCloseWindow();
65 });
66 }
67 }
68
69 render() {
70 const { user, payment, app } = this.props.stores;
71 const { openExternalUrl } = this.props.actions.app;
72
73 const isLoadingUserInfo = user.getUserInfoRequest.isExecuting;
74 const isLoadingOrdersInfo = payment.ordersDataRequest.isExecuting;
75 const isLoadingPlans = payment.plansRequest.isExecuting;
76
77 return (
78 <AccountDashboard
79 user={user.data}
80 orders={payment.orders}
81 hashrate={app.minerHashrate}
82 isLoading={isLoadingUserInfo}
83 isLoadingOrdersInfo={isLoadingOrdersInfo}
84 isLoadingPlans={isLoadingPlans}
85 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError}
86 retryUserInfoRequest={() => this.reloadData()}
87 isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting}
88 openDashboard={price => this.handlePaymentDashboard(price)}
89 openExternalUrl={url => openExternalUrl({ url })}
90 onCloseSubscriptionWindow={() => this.onCloseWindow()}
91 stopMiner={() => this.stopMiner()}
92 />
93 );
94 }
95}
96
97AccountScreen.wrappedComponent.propTypes = {
98 stores: PropTypes.shape({
99 user: PropTypes.instanceOf(UserStore).isRequired,
100 payment: PropTypes.instanceOf(PaymentStore).isRequired,
101 app: PropTypes.instanceOf(AppStore).isRequired,
102 }).isRequired,
103 actions: PropTypes.shape({
104 payment: PropTypes.shape({
105 createDashboardUrl: PropTypes.func.isRequired,
106 }).isRequired,
107 app: PropTypes.shape({
108 openExternalUrl: PropTypes.func.isRequired,
109 }).isRequired,
110 user: PropTypes.shape({
111 update: PropTypes.func.isRequired,
112 }).isRequired,
113 }).isRequired,
114};
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
new file mode 100644
index 000000000..6c614b941
--- /dev/null
+++ b/src/containers/settings/EditServiceScreen.js
@@ -0,0 +1,208 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import UserStore from '../../stores/UserStore';
7import RecipesStore from '../../stores/RecipesStore';
8import ServicesStore from '../../stores/ServicesStore';
9import Form from '../../lib/Form';
10import { gaPage } from '../../lib/analytics';
11
12
13import ServiceError from '../../components/settings/services/ServiceError';
14import EditServiceForm from '../../components/settings/services/EditServiceForm';
15import { required, url, oneRequired } from '../../helpers/validation-helpers';
16
17const messages = defineMessages({
18 name: {
19 id: 'settings.service.form.name',
20 defaultMessage: '!!!Name',
21 },
22 enableService: {
23 id: 'settings.service.form.enableService',
24 defaultMessage: '!!!Enable service',
25 },
26 enableNotification: {
27 id: 'settings.service.form.enableNotification',
28 defaultMessage: '!!!Enable Notifications',
29 },
30 team: {
31 id: 'settings.service.form.team',
32 defaultMessage: '!!!Team',
33 },
34 customUrl: {
35 id: 'settings.service.form.customUrl',
36 defaultMessage: '!!!Custom server',
37 },
38 indirectMessages: {
39 id: 'settings.service.form.indirectMessages',
40 defaultMessage: '!!!Show message badge for all new messages',
41 },
42});
43
44@inject('stores', 'actions') @observer
45export default class EditServiceScreen extends Component {
46 static contextTypes = {
47 intl: intlShape,
48 };
49
50 componentDidMount() {
51 gaPage('Settings/Service/Edit');
52 }
53
54 onSubmit(serviceData) {
55 const { action } = this.props.router.params;
56 const { recipes, services } = this.props.stores;
57 const { createService, updateService } = this.props.actions.service;
58
59 if (action === 'edit') {
60 updateService({ serviceId: services.activeSettings.id, serviceData });
61 } else {
62 createService({ recipeId: recipes.active.id, serviceData });
63 }
64 }
65
66 prepareForm(recipe, service) {
67 const { intl } = this.context;
68 const config = {
69 fields: {
70 name: {
71 label: intl.formatMessage(messages.name),
72 placeholder: intl.formatMessage(messages.name),
73 value: service.id ? service.name : recipe.name,
74 },
75 isEnabled: {
76 label: intl.formatMessage(messages.enableService),
77 value: service.isEnabled,
78 default: true,
79 },
80 isNotificationEnabled: {
81 label: intl.formatMessage(messages.enableNotification),
82 value: service.isNotificationEnabled,
83 default: true,
84 },
85 },
86 };
87
88 if (recipe.hasTeamId) {
89 Object.assign(config.fields, {
90 team: {
91 label: intl.formatMessage(messages.team),
92 placeholder: intl.formatMessage(messages.team),
93 value: service.team,
94 validate: [required],
95 },
96 });
97 }
98
99 if (recipe.hasCustomUrl) {
100 Object.assign(config.fields, {
101 customUrl: {
102 label: intl.formatMessage(messages.customUrl),
103 placeholder: 'https://',
104 value: service.customUrl,
105 validate: [required, url],
106 },
107 });
108 }
109
110 if (recipe.hasTeamId && recipe.hasCustomUrl) {
111 config.fields.team.validate = [oneRequired(['team', 'customUrl'])];
112 config.fields.customUrl.validate = [url, oneRequired(['team', 'customUrl'])];
113 }
114
115 if (recipe.hasIndirectMessages) {
116 Object.assign(config.fields, {
117 isIndirectMessageBadgeEnabled: {
118 label: intl.formatMessage(messages.indirectMessages),
119 value: service.isIndirectMessageBadgeEnabled,
120 default: true,
121 },
122 });
123 }
124
125 return new Form(config);
126 }
127
128 deleteService() {
129 const { deleteService } = this.props.actions.service;
130 const { action } = this.props.router.params;
131
132 if (action === 'edit') {
133 const { activeSettings: service } = this.props.stores.services;
134 deleteService({
135 serviceId: service.id,
136 redirect: '/settings/services',
137 });
138 }
139 }
140
141 render() {
142 const { recipes, services, user } = this.props.stores;
143 const { action } = this.props.router.params;
144
145 let recipe;
146 let service = {};
147 let isLoading = false;
148
149 if (action === 'add') {
150 recipe = recipes.active;
151
152 // TODO: render error message when recipe is `null`
153 if (!recipe) {
154 return (
155 <ServiceError />
156 );
157 }
158 } else {
159 service = services.activeSettings;
160 isLoading = services.allServicesRequest.isExecuting;
161
162 if (!isLoading && service) {
163 recipe = service.recipe;
164 }
165 }
166
167 if (isLoading) {
168 return (<div>Loading...</div>);
169 }
170
171 const form = this.prepareForm(recipe, service);
172
173 return (
174 <EditServiceForm
175 action={action}
176 recipe={recipe}
177 service={service}
178 user={user.data}
179 form={form}
180 status={services.actionStatus}
181 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting}
182 isDeleting={services.deleteServiceRequest.isExecuting}
183 onSubmit={d => this.onSubmit(d)}
184 onDelete={() => this.deleteService()}
185 />
186 );
187 }
188}
189
190EditServiceScreen.wrappedComponent.propTypes = {
191 stores: PropTypes.shape({
192 user: PropTypes.instanceOf(UserStore).isRequired,
193 recipes: PropTypes.instanceOf(RecipesStore).isRequired,
194 services: PropTypes.instanceOf(ServicesStore).isRequired,
195 }).isRequired,
196 router: PropTypes.shape({
197 params: PropTypes.shape({
198 action: PropTypes.string.isRequired,
199 }).isRequired,
200 }).isRequired,
201 actions: PropTypes.shape({
202 service: PropTypes.shape({
203 createService: PropTypes.func.isRequired,
204 updateService: PropTypes.func.isRequired,
205 deleteService: PropTypes.func.isRequired,
206 }).isRequired,
207 }).isRequired,
208};
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
new file mode 100644
index 000000000..0e17cafce
--- /dev/null
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -0,0 +1,167 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import AppStore from '../../stores/AppStore';
7import SettingsStore from '../../stores/SettingsStore';
8import UserStore from '../../stores/UserStore';
9import Form from '../../lib/Form';
10import languages from '../../i18n/languages';
11import { gaPage } from '../../lib/analytics';
12
13
14import EditSettingsForm from '../../components/settings/settings/EditSettingsForm';
15
16const messages = defineMessages({
17 autoLaunchOnStart: {
18 id: 'settings.app.form.autoLaunchOnStart',
19 defaultMessage: '!!!Launch Franz on start',
20 },
21 autoLaunchInBackground: {
22 id: 'settings.app.form.autoLaunchInBackground',
23 defaultMessage: '!!!Open in background',
24 },
25 runInBackground: {
26 id: 'settings.app.form.runInBackground',
27 defaultMessage: '!!!Keep Franz in background when closing the window',
28 },
29 minimizeToSystemTray: {
30 id: 'settings.app.form.minimizeToSystemTray',
31 defaultMessage: '!!!Minimize Franz to system tray',
32 },
33 language: {
34 id: 'settings.app.form.language',
35 defaultMessage: '!!!Language',
36 },
37 beta: {
38 id: 'settings.app.form.beta',
39 defaultMessage: '!!!Include beta versions',
40 },
41});
42
43@inject('stores', 'actions') @observer
44export default class EditSettingsScreen extends Component {
45 static contextTypes = {
46 intl: intlShape,
47 };
48
49 componentDidMount() {
50 gaPage('Settings/App');
51 }
52
53 onSubmit(settingsData) {
54 const { app, settings, user } = this.props.actions;
55
56 app.launchOnStartup({
57 enable: settingsData.autoLaunchOnStart,
58 openInBackground: settingsData.autoLaunchInBackground,
59 });
60
61 settings.update({
62 settings: {
63 runInBackground: settingsData.runInBackground,
64 minimizeToSystemTray: settingsData.minimizeToSystemTray,
65 locale: settingsData.locale,
66 beta: settingsData.beta,
67 },
68 });
69
70 user.update({
71 userData: {
72 beta: settingsData.beta,
73 },
74 });
75 }
76
77 prepareForm() {
78 const { app, settings, user } = this.props.stores;
79 const { intl } = this.context;
80
81 const options = [];
82 Object.keys(languages).forEach((key) => {
83 options.push({
84 value: key,
85 label: languages[key],
86 });
87 });
88
89 const config = {
90 fields: {
91 autoLaunchOnStart: {
92 label: intl.formatMessage(messages.autoLaunchOnStart),
93 value: app.autoLaunchOnStart,
94 default: true,
95 },
96 autoLaunchInBackground: {
97 label: intl.formatMessage(messages.autoLaunchInBackground),
98 value: app.launchInBackground,
99 default: false,
100 },
101 runInBackground: {
102 label: intl.formatMessage(messages.runInBackground),
103 value: settings.all.runInBackground,
104 default: true,
105 },
106 minimizeToSystemTray: {
107 label: intl.formatMessage(messages.minimizeToSystemTray),
108 value: settings.all.minimizeToSystemTray,
109 default: false,
110 },
111 locale: {
112 label: intl.formatMessage(messages.language),
113 value: app.locale,
114 options,
115 default: 'en-US',
116 },
117 beta: {
118 label: intl.formatMessage(messages.beta),
119 value: user.data.beta,
120 default: false,
121 },
122 },
123 };
124
125 return new Form(config);
126 }
127
128 render() {
129 const { updateStatus, updateStatusTypes } = this.props.stores.app;
130 const { checkForUpdates, installUpdate } = this.props.actions.app;
131 const form = this.prepareForm();
132
133 return (
134 <EditSettingsForm
135 form={form}
136 checkForUpdates={checkForUpdates}
137 installUpdate={installUpdate}
138 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING}
139 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE}
140 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE}
141 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED}
142 onSubmit={d => this.onSubmit(d)}
143 />
144 );
145 }
146}
147
148EditSettingsScreen.wrappedComponent.propTypes = {
149 stores: PropTypes.shape({
150 app: PropTypes.instanceOf(AppStore).isRequired,
151 user: PropTypes.instanceOf(UserStore).isRequired,
152 settings: PropTypes.instanceOf(SettingsStore).isRequired,
153 }).isRequired,
154 actions: PropTypes.shape({
155 app: PropTypes.shape({
156 launchOnStartup: PropTypes.func.isRequired,
157 checkForUpdates: PropTypes.func.isRequired,
158 installUpdate: PropTypes.func.isRequired,
159 }).isRequired,
160 settings: PropTypes.shape({
161 update: PropTypes.func.isRequired,
162 }).isRequired,
163 user: PropTypes.shape({
164 update: PropTypes.func.isRequired,
165 }).isRequired,
166 }).isRequired,
167};
diff --git a/src/containers/settings/EditUserScreen.js b/src/containers/settings/EditUserScreen.js
new file mode 100644
index 000000000..fb5c5db89
--- /dev/null
+++ b/src/containers/settings/EditUserScreen.js
@@ -0,0 +1,165 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import UserStore from '../../stores/UserStore';
7import Form from '../../lib/Form';
8import EditUserForm from '../../components/settings/user/EditUserForm';
9import { required, email, minLength } from '../../helpers/validation-helpers';
10import { gaPage } from '../../lib/analytics';
11
12const messages = defineMessages({
13 firstname: {
14 id: 'settings.user.form.firstname',
15 defaultMessage: '!!!Firstname',
16 },
17 lastname: {
18 id: 'settings.user.form.lastname',
19 defaultMessage: '!!!Lastname',
20 },
21 email: {
22 id: 'settings.user.form.email',
23 defaultMessage: '!!!Email',
24 },
25 accountType: {
26 label: {
27 id: 'settings.user.form.accountType.label',
28 defaultMessage: '!!!Account type',
29 },
30 individual: {
31 id: 'settings.user.form.accountType.individual',
32 defaultMessage: '!!!Individual',
33 },
34 nonProfit: {
35 id: 'settings.user.form.accountType.non-profit',
36 defaultMessage: '!!!Non-Profit',
37 },
38 company: {
39 id: 'settings.user.form.accountType.company',
40 defaultMessage: '!!!Company',
41 },
42 },
43 currentPassword: {
44 id: 'settings.user.form.currentPassword',
45 defaultMessage: '!!!Current password',
46 },
47 newPassword: {
48 id: 'settings.user.form.newPassword',
49 defaultMessage: '!!!New password',
50 },
51});
52
53@inject('stores', 'actions') @observer
54export default class EditUserScreen extends Component {
55 static contextTypes = {
56 intl: intlShape,
57 };
58
59 componentDidMount() {
60 gaPage('Settings/Account/Edit');
61 }
62
63 componentWillUnmount() {
64 this.props.actions.user.resetStatus();
65 }
66
67 onSubmit(userData) {
68 const { update } = this.props.actions.user;
69
70 update({ userData });
71
72 document.querySelector('#form').scrollIntoView({ behavior: 'smooth' });
73 }
74
75 prepareForm(user) {
76 const { intl } = this.context;
77
78 const config = {
79 fields: {
80 firstname: {
81 label: intl.formatMessage(messages.firstname),
82 placeholder: intl.formatMessage(messages.firstname),
83 value: user.firstname,
84 validate: [required],
85 },
86 lastname: {
87 label: intl.formatMessage(messages.lastname),
88 placeholder: intl.formatMessage(messages.lastname),
89 value: user.lastname,
90 validate: [required],
91 },
92 email: {
93 label: intl.formatMessage(messages.email),
94 placeholder: intl.formatMessage(messages.email),
95 value: user.email,
96 validate: [required, email],
97 },
98 accountType: {
99 value: user.accountType,
100 validate: [required],
101 label: intl.formatMessage(messages.accountType.label),
102 options: [{
103 value: 'individual',
104 label: intl.formatMessage(messages.accountType.individual),
105 }, {
106 value: 'non-profit',
107 label: intl.formatMessage(messages.accountType.nonProfit),
108 }, {
109 value: 'company',
110 label: intl.formatMessage(messages.accountType.company),
111 }],
112 },
113 organization: {
114 label: intl.formatMessage(messages.accountType.company),
115 placeholder: intl.formatMessage(messages.accountType.company),
116 value: user.organization,
117 },
118 oldPassword: {
119 label: intl.formatMessage(messages.currentPassword),
120 type: 'password',
121 validate: [minLength(6)],
122 },
123 newPassword: {
124 label: intl.formatMessage(messages.newPassword),
125 type: 'password',
126 validate: [minLength(6)],
127 },
128 },
129 };
130
131 return new Form(config);
132 }
133
134 render() {
135 const { user } = this.props.stores;
136
137 if (user.getUserInfoRequest.isExecuting) {
138 return (<div>Loading...</div>);
139 }
140
141 const form = this.prepareForm(user.data);
142
143 return (
144 <EditUserForm
145 // user={user.data}
146 status={user.actionStatus}
147 form={form}
148 isSaving={user.updateUserInfoRequest.isExecuting}
149 onSubmit={d => this.onSubmit(d)}
150 />
151 );
152 }
153}
154
155EditUserScreen.wrappedComponent.propTypes = {
156 stores: PropTypes.shape({
157 user: PropTypes.instanceOf(UserStore).isRequired,
158 }).isRequired,
159 actions: PropTypes.shape({
160 user: PropTypes.shape({
161 update: PropTypes.func.isRequired,
162 resetStatus: PropTypes.func.isRequired,
163 }).isRequired,
164 }).isRequired,
165};
diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js
new file mode 100644
index 000000000..65341e9e3
--- /dev/null
+++ b/src/containers/settings/RecipesScreen.js
@@ -0,0 +1,126 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { inject, observer } from 'mobx-react';
5
6import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
7import RecipeStore from '../../stores/RecipesStore';
8import ServiceStore from '../../stores/ServicesStore';
9import UserStore from '../../stores/UserStore';
10import { gaPage } from '../../lib/analytics';
11
12import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard';
13
14@inject('stores', 'actions') @observer
15export default class RecipesScreen extends Component {
16 static propTypes = {
17 params: PropTypes.shape({
18 filter: PropTypes.string,
19 }).isRequired,
20 };
21
22 static defaultProps = {
23 params: {
24 filter: null,
25 },
26 };
27
28 state = {
29 needle: null,
30 currentFilter: 'featured',
31 };
32
33 componentDidMount() {
34 gaPage('Settings/Recipe Dashboard/Featured');
35
36 autorun(() => {
37 const { filter } = this.props.params;
38 const { currentFilter } = this.state;
39
40 if (filter === 'all' && currentFilter !== 'all') {
41 gaPage('Settings/Recipe Dashboard/All');
42 this.setState({ currentFilter: 'all' });
43 } else if (filter === 'featured' && currentFilter !== 'featured') {
44 gaPage('Settings/Recipe Dashboard/Featured');
45 this.setState({ currentFilter: 'featured' });
46 } else if (filter === 'dev' && currentFilter !== 'dev') {
47 gaPage('Settings/Recipe Dashboard/Dev');
48 this.setState({ currentFilter: 'dev' });
49 }
50 });
51 }
52
53 componentWillUnmount() {
54 this.props.stores.services.resetStatus();
55 }
56
57 searchRecipes(needle) {
58 if (needle === '') {
59 this.resetSearch();
60 } else {
61 const { search } = this.props.actions.recipePreview;
62 this.setState({ needle });
63 search({ needle });
64 }
65 }
66
67 resetSearch() {
68 this.setState({ needle: null });
69 }
70
71 render() {
72 const { recipePreviews, recipes, services, user } = this.props.stores;
73 const { showAddServiceInterface } = this.props.actions.service;
74
75 const { filter } = this.props.params;
76 let recipeFilter;
77
78 if (filter === 'all') {
79 recipeFilter = recipePreviews.all;
80 } else if (filter === 'dev') {
81 recipeFilter = recipePreviews.dev;
82 } else {
83 recipeFilter = recipePreviews.featured;
84 }
85
86 const allRecipes = this.state.needle ? recipePreviews.searchResults : recipeFilter;
87
88 const isLoading = recipePreviews.featuredRecipePreviewsRequest.isExecuting
89 || recipePreviews.allRecipePreviewsRequest.isExecuting
90 || recipes.installRecipeRequest.isExecuting
91 || recipePreviews.searchRecipePreviewsRequest.isExecuting;
92
93 return (
94 <RecipesDashboard
95 recipes={allRecipes}
96 isLoading={isLoading}
97 addedServiceCount={services.all.length}
98 isPremium={user.data.isPremium}
99 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted}
100 showAddServiceInterface={showAddServiceInterface}
101 searchRecipes={e => this.searchRecipes(e)}
102 resetSearch={() => this.resetSearch()}
103 searchNeedle={this.state.needle}
104 serviceStatus={services.actionStatus}
105 devRecipesCount={recipePreviews.dev.length}
106 />
107 );
108 }
109}
110
111RecipesScreen.wrappedComponent.propTypes = {
112 stores: PropTypes.shape({
113 recipePreviews: PropTypes.instanceOf(RecipePreviewsStore).isRequired,
114 recipes: PropTypes.instanceOf(RecipeStore).isRequired,
115 services: PropTypes.instanceOf(ServiceStore).isRequired,
116 user: PropTypes.instanceOf(UserStore).isRequired,
117 }).isRequired,
118 actions: PropTypes.shape({
119 service: PropTypes.shape({
120 showAddServiceInterface: PropTypes.func.isRequired,
121 }).isRequired,
122 recipePreview: PropTypes.shape({
123 search: PropTypes.func.isRequired,
124 }).isRequired,
125 }).isRequired,
126};
diff --git a/src/containers/settings/ServicesScreen.js b/src/containers/settings/ServicesScreen.js
new file mode 100644
index 000000000..d0580041f
--- /dev/null
+++ b/src/containers/settings/ServicesScreen.js
@@ -0,0 +1,75 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { RouterStore } from 'mobx-react-router';
5
6// import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
7import UserStore from '../../stores/UserStore';
8import ServiceStore from '../../stores/ServicesStore';
9import { gaPage } from '../../lib/analytics';
10
11import ServicesDashboard from '../../components/settings/services/ServicesDashboard';
12
13@inject('stores', 'actions') @observer
14export default class ServicesScreen extends Component {
15 componentDidMount() {
16 gaPage('Settings/Service Dashboard');
17 }
18
19 componentWillUnmount() {
20 this.props.actions.service.resetFilter();
21 }
22
23 deleteService() {
24 this.props.actions.service.deleteService();
25 this.props.stores.services.resetFilter();
26 }
27
28 render() {
29 const { user, services, router } = this.props.stores;
30 const {
31 toggleService,
32 filter,
33 resetFilter,
34 } = this.props.actions.service;
35 const isLoading = services.allServicesRequest.isExecuting;
36
37 let allServices = services.all;
38 if (services.filterNeedle !== null) {
39 allServices = services.filtered;
40 }
41
42 return (
43 <ServicesDashboard
44 user={user.data}
45 services={allServices}
46 status={services.actionStatus}
47 deleteService={() => this.deleteService()}
48 toggleService={toggleService}
49 isLoading={isLoading}
50 filterServices={filter}
51 resetFilter={resetFilter}
52 goTo={router.push}
53 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError}
54 retryServicesRequest={() => services.allServicesRequest.reload()}
55 />
56 );
57 }
58}
59
60ServicesScreen.wrappedComponent.propTypes = {
61 stores: PropTypes.shape({
62 user: PropTypes.instanceOf(UserStore).isRequired,
63 services: PropTypes.instanceOf(ServiceStore).isRequired,
64 router: PropTypes.instanceOf(RouterStore).isRequired,
65 }).isRequired,
66 actions: PropTypes.shape({
67 service: PropTypes.shape({
68 showAddServiceInterface: PropTypes.func.isRequired,
69 deleteService: PropTypes.func.isRequired,
70 toggleService: PropTypes.func.isRequired,
71 filter: PropTypes.func.isRequired,
72 resetFilter: PropTypes.func.isRequired,
73 }).isRequired,
74 }).isRequired,
75};
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js
new file mode 100644
index 000000000..13ca96f72
--- /dev/null
+++ b/src/containers/settings/SettingsWindow.js
@@ -0,0 +1,43 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, inject } from 'mobx-react';
4
5import ServicesStore from '../../stores/ServicesStore';
6
7import Layout from '../../components/settings/SettingsLayout';
8import Navigation from '../../components/settings/navigation/SettingsNavigation';
9
10@inject('stores', 'actions') @observer
11export default class SettingsContainer extends Component {
12 render() {
13 const { children, stores } = this.props;
14 const { closeSettings } = this.props.actions.ui;
15
16 const navigation = (
17 <Navigation
18 serviceCount={stores.services.all.length}
19 />
20 );
21
22 return (
23 <Layout
24 navigation={navigation}
25 closeSettings={closeSettings}
26 >
27 {children}
28 </Layout>
29 );
30 }
31}
32
33SettingsContainer.wrappedComponent.propTypes = {
34 children: PropTypes.element.isRequired,
35 stores: PropTypes.shape({
36 services: PropTypes.instanceOf(ServicesStore).isRequired,
37 }).isRequired,
38 actions: PropTypes.shape({
39 ui: PropTypes.shape({
40 closeSettings: PropTypes.func.isRequired,
41 }),
42 }).isRequired,
43};