aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/FeaturesApi.js13
-rw-r--r--src/api/RecipesApi.js2
-rw-r--r--src/api/index.js2
-rw-r--r--src/api/server/ServerApi.js29
-rw-r--r--src/components/settings/account/AccountDashboard.js86
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js28
-rw-r--r--src/components/settings/services/EditServiceForm.js4
-rw-r--r--src/components/settings/user/EditUserForm.js8
-rw-r--r--src/components/ui/Input.js1
-rw-r--r--src/containers/auth/AuthLayoutContainer.js23
-rw-r--r--src/containers/layout/AppLayoutContainer.js8
-rw-r--r--src/containers/settings/EditServiceScreen.js12
-rw-r--r--src/containers/settings/EditUserScreen.js1
-rw-r--r--src/stores/FeaturesStore.js36
-rw-r--r--src/stores/index.js2
-rw-r--r--src/styles/badge.scss6
16 files changed, 215 insertions, 46 deletions
diff --git a/src/api/FeaturesApi.js b/src/api/FeaturesApi.js
new file mode 100644
index 000000000..c66f28f5b
--- /dev/null
+++ b/src/api/FeaturesApi.js
@@ -0,0 +1,13 @@
1export default class FeaturesApi {
2 constructor(server) {
3 this.server = server;
4 }
5
6 default() {
7 return this.server.getDefaultFeatures();
8 }
9
10 features() {
11 return this.server.getFeatures();
12 }
13}
diff --git a/src/api/RecipesApi.js b/src/api/RecipesApi.js
index 0573dacaf..800888b00 100644
--- a/src/api/RecipesApi.js
+++ b/src/api/RecipesApi.js
@@ -1,4 +1,4 @@
1export default class ServicesApi { 1export default class RecipesApi {
2 constructor(server) { 2 constructor(server) {
3 this.server = server; 3 this.server = server;
4 } 4 }
diff --git a/src/api/index.js b/src/api/index.js
index 3fc18c4b5..3c87cc087 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -6,12 +6,14 @@ import UserApi from './UserApi';
6import LocalApi from './LocalApi'; 6import LocalApi from './LocalApi';
7import PaymentApi from './PaymentApi'; 7import PaymentApi from './PaymentApi';
8import NewsApi from './NewsApi'; 8import NewsApi from './NewsApi';
9import FeaturesApi from './FeaturesApi';
9 10
10export default (server, local) => ({ 11export default (server, local) => ({
11 app: new AppApi(server, local), 12 app: new AppApi(server, local),
12 services: new ServicesApi(server, local), 13 services: new ServicesApi(server, local),
13 recipePreviews: new RecipePreviewsApi(server, local), 14 recipePreviews: new RecipePreviewsApi(server, local),
14 recipes: new RecipesApi(server, local), 15 recipes: new RecipesApi(server, local),
16 features: new FeaturesApi(server, local),
15 user: new UserApi(server, local), 17 user: new UserApi(server, local),
16 local: new LocalApi(server, local), 18 local: new LocalApi(server, local),
17 payment: new PaymentApi(server, local), 19 payment: new PaymentApi(server, local),
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js
index ad1ffa59a..164419951 100644
--- a/src/api/server/ServerApi.js
+++ b/src/api/server/ServerApi.js
@@ -261,6 +261,35 @@ export default class ServerApi {
261 return data; 261 return data;
262 } 262 }
263 263
264 // Features
265 async getDefaultFeatures() {
266 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/features/default`, this._prepareAuthRequest({
267 method: 'GET',
268 }));
269 if (!request.ok) {
270 throw request;
271 }
272 const data = await request.json();
273
274 const features = data;
275 console.debug('ServerApi::getDefaultFeatures resolves', features);
276 return features;
277 }
278
279 async getFeatures() {
280 const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/features`, this._prepareAuthRequest({
281 method: 'GET',
282 }));
283 if (!request.ok) {
284 throw request;
285 }
286 const data = await request.json();
287
288 const features = data;
289 console.debug('ServerApi::getFeatures resolves', features);
290 return features;
291 }
292
264 // Recipes 293 // Recipes
265 async getInstalledRecipes() { 294 async getInstalledRecipes() {
266 const recipesDirectory = getRecipeDirectory(); 295 const recipesDirectory = getRecipeDirectory();
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 2fcfdfc9a..ede519fd6 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -44,6 +44,10 @@ const messages = defineMessages({
44 id: 'settings.account.accountType.premium', 44 id: 'settings.account.accountType.premium',
45 defaultMessage: '!!!Premium Supporter Account', 45 defaultMessage: '!!!Premium Supporter Account',
46 }, 46 },
47 accountTypeEnterprise: {
48 id: 'settings.account.accountType.enterprise',
49 defaultMessage: '!!!Enterprise Account',
50 },
47 accountEditButton: { 51 accountEditButton: {
48 id: 'settings.account.account.editButton', 52 id: 'settings.account.account.editButton',
49 defaultMessage: '!!!Edit Account', 53 defaultMessage: '!!!Edit Account',
@@ -166,17 +170,21 @@ export default @observer class AccountDashboard extends Component {
166 </h2> 170 </h2>
167 {user.organization && `${user.organization}, `} 171 {user.organization && `${user.organization}, `}
168 {user.email}<br /> 172 {user.email}<br />
169 {!user.isPremium && ( 173 {!user.isEnterprise && !user.isPremium && (
170 <span className="badge badge">{intl.formatMessage(messages.accountTypeBasic)}</span> 174 <span className="badge badge">{intl.formatMessage(messages.accountTypeBasic)}</span>
171 )} 175 )}
172 {user.isPremium && ( 176 {user.isPremium && (
173 <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> 177 <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span>
174 )} 178 )}
179 {user.isEnterprise && (
180 <span className="badge badge--success">{intl.formatMessage(messages.accountTypeEnterprise)}</span>
181 )}
175 </div> 182 </div>
176 <Link to="/settings/user/edit" className="button"> 183 {!user.isSSO && (
177 {intl.formatMessage(messages.accountEditButton)} 184 <Link to="/settings/user/edit" className="button">
178 </Link> 185 {intl.formatMessage(messages.accountEditButton)}
179 186 </Link>
187 )}
180 {user.emailValidated} 188 {user.emailValidated}
181 </div> 189 </div>
182 </div> 190 </div>
@@ -229,6 +237,33 @@ export default @observer class AccountDashboard extends Component {
229 ) 237 )
230 )} 238 )}
231 239
240 {user.isEnterprise && (
241 <div className="account">
242 <div className="account__box">
243 <h2>{user.company.name}</h2>
244 <p>
245 Technical contact:&nbsp;
246 <Link
247 className="link"
248 target="_blank"
249 to={`mailto:${user.company.contact.technical}?subject=Franz`}
250 >
251 {user.company.contact.technical}
252 </Link>
253 <br />
254 General contact:&nbsp;
255 <Link
256 className="link"
257 target="_blank"
258 to={`mailto:${user.company.contact.default}?subject=Franz`}
259 >
260 {user.company.contact.default}
261 </Link>
262 </p>
263 </div>
264 </div>
265 )}
266
232 {user.isMiner && ( 267 {user.isMiner && (
233 <div className="account franz-form"> 268 <div className="account franz-form">
234 <div className="account__box account__box"> 269 <div className="account__box account__box">
@@ -243,7 +278,7 @@ export default @observer class AccountDashboard extends Component {
243 </div> 278 </div>
244 )} 279 )}
245 280
246 {!user.isPremium && ( 281 {!user.isEnterprise && !user.isPremium && (
247 isLoadingPlans ? ( 282 isLoadingPlans ? (
248 <Loader /> 283 <Loader />
249 ) : ( 284 ) : (
@@ -258,28 +293,29 @@ export default @observer class AccountDashboard extends Component {
258 ) 293 )
259 )} 294 )}
260 295
261 <div className="account franz-form"> 296 {!user.isEnterprise && (
262 <div className="account__box"> 297 <div className="account franz-form">
263 <h2>{intl.formatMessage(messages.headlineDangerZone)}</h2> 298 <div className="account__box">
264 {!isDeleteAccountSuccessful && ( 299 <h2>{intl.formatMessage(messages.headlineDangerZone)}</h2>
265 <div className="account__subscription"> 300 {!isDeleteAccountSuccessful && (
266 <p>{intl.formatMessage(messages.deleteInfo)}</p> 301 <div className="account__subscription">
267 <Button 302 <p>{intl.formatMessage(messages.deleteInfo)}</p>
268 label={intl.formatMessage(messages.deleteAccount)} 303 <Button
269 buttonType="danger" 304 label={intl.formatMessage(messages.deleteAccount)}
270 onClick={() => deleteAccount()} 305 buttonType="danger"
271 loaded={!isLoadingDeleteAccount} 306 onClick={() => deleteAccount()}
272 /> 307 loaded={!isLoadingDeleteAccount}
273 </div> 308 />
274 )} 309 </div>
275 {isDeleteAccountSuccessful && ( 310 )}
276 <p>{intl.formatMessage(messages.deleteEmailSent)}</p> 311 {isDeleteAccountSuccessful && (
277 )} 312 <p>{intl.formatMessage(messages.deleteEmailSent)}</p>
313 )}
314 </div>
278 </div> 315 </div>
279 </div> 316 )}
280 </div> 317 </div>
281 )} 318 )}
282
283 </div> 319 </div>
284 <ReactTooltip place="right" type="dark" effect="solid" /> 320 <ReactTooltip place="right" type="dark" effect="solid" />
285 </div> 321 </div>
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index 66539f324..46b2f82fc 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -1,6 +1,7 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, intlShape } from 'react-intl';
4import { inject, observer } from 'mobx-react';
4 5
5import Link from '../../ui/Link'; 6import Link from '../../ui/Link';
6 7
@@ -31,6 +32,7 @@ const messages = defineMessages({
31 }, 32 },
32}); 33});
33 34
35@inject('stores') @observer
34export default class SettingsNavigation extends Component { 36export default class SettingsNavigation extends Component {
35 static propTypes = { 37 static propTypes = {
36 serviceCount: PropTypes.number.isRequired, 38 serviceCount: PropTypes.number.isRequired,
@@ -42,17 +44,20 @@ export default class SettingsNavigation extends Component {
42 44
43 render() { 45 render() {
44 const { serviceCount } = this.props; 46 const { serviceCount } = this.props;
47 const { features } = this.props.stores.features;
45 const { intl } = this.context; 48 const { intl } = this.context;
46 49
47 return ( 50 return (
48 <div className="settings-navigation"> 51 <div className="settings-navigation">
49 <Link 52 {features.userCanManageServices && (
50 to="/settings/recipes" 53 <Link
51 className="settings-navigation__link" 54 to="/settings/recipes"
52 activeClassName="is-active" 55 className="settings-navigation__link"
53 > 56 activeClassName="is-active"
54 {intl.formatMessage(messages.availableServices)} 57 >
55 </Link> 58 {intl.formatMessage(messages.availableServices)}
59 </Link>
60 )}
56 <Link 61 <Link
57 to="/settings/services" 62 to="/settings/services"
58 className="settings-navigation__link" 63 className="settings-navigation__link"
@@ -92,3 +97,12 @@ export default class SettingsNavigation extends Component {
92 ); 97 );
93 } 98 }
94} 99}
100
101SettingsNavigation.wrappedComponent.propTypes = {
102 stores: PropTypes.shape({
103 features: PropTypes.shape({
104 features: PropTypes.object.isRequired,
105 }).isRequired,
106 }).isRequired,
107};
108
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index f9afe4c8d..777a95fcf 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -106,6 +106,7 @@ export default @observer class EditServiceForm extends Component {
106 return null; 106 return null;
107 }, 107 },
108 user: PropTypes.instanceOf(User).isRequired, 108 user: PropTypes.instanceOf(User).isRequired,
109 userCanManageServices: PropTypes.bool.isRequired,
109 action: PropTypes.string.isRequired, 110 action: PropTypes.string.isRequired,
110 form: PropTypes.instanceOf(Form).isRequired, 111 form: PropTypes.instanceOf(Form).isRequired,
111 onSubmit: PropTypes.func.isRequired, 112 onSubmit: PropTypes.func.isRequired,
@@ -168,6 +169,7 @@ export default @observer class EditServiceForm extends Component {
168 service, 169 service,
169 action, 170 action,
170 user, 171 user,
172 userCanManageServices,
171 form, 173 form,
172 isSaving, 174 isSaving,
173 isDeleting, 175 isDeleting,
@@ -326,7 +328,7 @@ export default @observer class EditServiceForm extends Component {
326 </div> 328 </div>
327 <div className="settings__controls"> 329 <div className="settings__controls">
328 {/* Delete Button */} 330 {/* Delete Button */}
329 {action === 'edit' && deleteButton} 331 {action === 'edit' && userCanManageServices && deleteButton}
330 332
331 {/* Save Button */} 333 {/* Save Button */}
332 {isSaving || isValidatingCustomUrl ? ( 334 {isSaving || isValidatingCustomUrl ? (
diff --git a/src/components/settings/user/EditUserForm.js b/src/components/settings/user/EditUserForm.js
index 6c0305089..b825f844a 100644
--- a/src/components/settings/user/EditUserForm.js
+++ b/src/components/settings/user/EditUserForm.js
@@ -45,6 +45,7 @@ export default @observer class EditServiceForm extends Component {
45 form: PropTypes.instanceOf(Form).isRequired, 45 form: PropTypes.instanceOf(Form).isRequired,
46 onSubmit: PropTypes.func.isRequired, 46 onSubmit: PropTypes.func.isRequired,
47 isSaving: PropTypes.bool.isRequired, 47 isSaving: PropTypes.bool.isRequired,
48 isEnterprise: PropTypes.bool.isRequired,
48 }; 49 };
49 50
50 static defaultProps = { 51 static defaultProps = {
@@ -71,6 +72,7 @@ export default @observer class EditServiceForm extends Component {
71 // user, 72 // user,
72 status, 73 status,
73 form, 74 form,
75 isEnterprise,
74 isSaving, 76 isSaving,
75 } = this.props; 77 } = this.props;
76 const { intl } = this.context; 78 const { intl } = this.context;
@@ -104,8 +106,10 @@ export default @observer class EditServiceForm extends Component {
104 <Input field={form.$('lastname')} /> 106 <Input field={form.$('lastname')} />
105 </div> 107 </div>
106 <Input field={form.$('email')} /> 108 <Input field={form.$('email')} />
107 <Radio field={form.$('accountType')} /> 109 {!isEnterprise && (
108 {form.$('accountType').value === 'company' && ( 110 <Radio field={form.$('accountType')} />
111 )}
112 {!isEnterprise && form.$('accountType').value === 'company' && (
109 <Input field={form.$('organization')} /> 113 <Input field={form.$('organization')} />
110 )} 114 )}
111 <h2>{intl.formatMessage(messages.headlinePassword)}</h2> 115 <h2>{intl.formatMessage(messages.headlinePassword)}</h2>
diff --git a/src/components/ui/Input.js b/src/components/ui/Input.js
index 7fc5b69b0..7bf6e1b00 100644
--- a/src/components/ui/Input.js
+++ b/src/components/ui/Input.js
@@ -96,6 +96,7 @@ export default @observer class Input extends Component {
96 onBlur={field.onBlur} 96 onBlur={field.onBlur}
97 onFocus={field.onFocus} 97 onFocus={field.onFocus}
98 ref={(element) => { this.inputElement = element; }} 98 ref={(element) => { this.inputElement = element; }}
99 disabled={field.disabled}
99 /> 100 />
100 {suffix && ( 101 {suffix && (
101 <span className="franz-form__input-suffix">{suffix}</span> 102 <span className="franz-form__input-suffix">{suffix}</span>
diff --git a/src/containers/auth/AuthLayoutContainer.js b/src/containers/auth/AuthLayoutContainer.js
index a25a1fd5e..b73598f3d 100644
--- a/src/containers/auth/AuthLayoutContainer.js
+++ b/src/containers/auth/AuthLayoutContainer.js
@@ -5,6 +5,7 @@ import { inject, observer } from 'mobx-react';
5import AuthLayout from '../../components/auth/AuthLayout'; 5import AuthLayout from '../../components/auth/AuthLayout';
6import AppStore from '../../stores/AppStore'; 6import AppStore from '../../stores/AppStore';
7import GlobalErrorStore from '../../stores/GlobalErrorStore'; 7import GlobalErrorStore from '../../stores/GlobalErrorStore';
8import AppLoader from '../../components/ui/AppLoader';
8 9
9import { oneOrManyChildElements } from '../../prop-types'; 10import { oneOrManyChildElements } from '../../prop-types';
10 11
@@ -18,17 +19,27 @@ export default @inject('stores', 'actions') @observer class AuthLayoutContainer
18 19
19 render() { 20 render() {
20 const { stores, actions, children, location } = this.props; 21 const { stores, actions, children, location } = this.props;
22 const { app, features, globalError } = stores;
23
24 const isLoadingBaseFeatures = features.defaultFeaturesRequest.isExecuting
25 && !features.defaultFeaturesRequest.wasExecuted;
26
27 if (isLoadingBaseFeatures) {
28 return (
29 <AppLoader />
30 );
31 }
21 32
22 return ( 33 return (
23 <AuthLayout 34 <AuthLayout
24 error={stores.globalError.response} 35 error={globalError.response}
25 pathname={location.pathname} 36 pathname={location.pathname}
26 isOnline={stores.app.isOnline} 37 isOnline={app.isOnline}
27 isAPIHealthy={!stores.app.healthCheckRequest.isError} 38 isAPIHealthy={!app.healthCheckRequest.isError}
28 retryHealthCheck={actions.app.healthCheck} 39 retryHealthCheck={actions.app.healthCheck}
29 isHealthCheckLoading={stores.app.healthCheckRequest.isExecuting} 40 isHealthCheckLoading={app.healthCheckRequest.isExecuting}
30 isFullScreen={stores.app.isFullScreen} 41 isFullScreen={app.isFullScreen}
31 darkMode={stores.app.isSystemDarkModeEnabled} 42 darkMode={app.isSystemDarkModeEnabled}
32 > 43 >
33 {children} 44 {children}
34 </AuthLayout> 45 </AuthLayout>
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index fb3be13c0..affc1a0a2 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -5,6 +5,7 @@ import { inject, observer } from 'mobx-react';
5import AppStore from '../../stores/AppStore'; 5import AppStore from '../../stores/AppStore';
6import RecipesStore from '../../stores/RecipesStore'; 6import RecipesStore from '../../stores/RecipesStore';
7import ServicesStore from '../../stores/ServicesStore'; 7import ServicesStore from '../../stores/ServicesStore';
8import FeaturesStore from '../../stores/FeaturesStore';
8import UIStore from '../../stores/UIStore'; 9import UIStore from '../../stores/UIStore';
9import NewsStore from '../../stores/NewsStore'; 10import NewsStore from '../../stores/NewsStore';
10import SettingsStore from '../../stores/SettingsStore'; 11import SettingsStore from '../../stores/SettingsStore';
@@ -25,6 +26,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
25 render() { 26 render() {
26 const { 27 const {
27 app, 28 app,
29 features,
28 services, 30 services,
29 ui, 31 ui,
30 news, 32 news,
@@ -63,10 +65,13 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
63 65
64 const { children } = this.props; 66 const { children } = this.props;
65 67
68 const isLoadingFeatures = features.featuresRequest.isExecuting
69 && !features.featuresRequest.wasExecuted;
70
66 const isLoadingServices = services.allServicesRequest.isExecuting 71 const isLoadingServices = services.allServicesRequest.isExecuting
67 && services.allServicesRequest.isExecutingFirstTime; 72 && services.allServicesRequest.isExecutingFirstTime;
68 73
69 if (isLoadingServices) { 74 if (isLoadingFeatures || isLoadingServices) {
70 return ( 75 return (
71 <AppLoader /> 76 <AppLoader />
72 ); 77 );
@@ -131,6 +136,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
131AppLayoutContainer.wrappedComponent.propTypes = { 136AppLayoutContainer.wrappedComponent.propTypes = {
132 stores: PropTypes.shape({ 137 stores: PropTypes.shape({
133 services: PropTypes.instanceOf(ServicesStore).isRequired, 138 services: PropTypes.instanceOf(ServicesStore).isRequired,
139 features: PropTypes.instanceOf(FeaturesStore).isRequired,
134 recipes: PropTypes.instanceOf(RecipesStore).isRequired, 140 recipes: PropTypes.instanceOf(RecipesStore).isRequired,
135 app: PropTypes.instanceOf(AppStore).isRequired, 141 app: PropTypes.instanceOf(AppStore).isRequired,
136 ui: PropTypes.instanceOf(UIStore).isRequired, 142 ui: PropTypes.instanceOf(UIStore).isRequired,
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
index e69c2c2a8..17d727642 100644
--- a/src/containers/settings/EditServiceScreen.js
+++ b/src/containers/settings/EditServiceScreen.js
@@ -6,6 +6,7 @@ import { defineMessages, intlShape } from 'react-intl';
6import UserStore from '../../stores/UserStore'; 6import UserStore from '../../stores/UserStore';
7import RecipesStore from '../../stores/RecipesStore'; 7import RecipesStore from '../../stores/RecipesStore';
8import ServicesStore from '../../stores/ServicesStore'; 8import ServicesStore from '../../stores/ServicesStore';
9import FeaturesStore from '../../stores/FeaturesStore';
9import SettingsStore from '../../stores/SettingsStore'; 10import SettingsStore from '../../stores/SettingsStore';
10import Form from '../../lib/Form'; 11import Form from '../../lib/Form';
11import { gaPage } from '../../lib/analytics'; 12import { gaPage } from '../../lib/analytics';
@@ -81,7 +82,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
81 } 82 }
82 } 83 }
83 84
84 prepareForm(recipe, service) { 85 prepareForm(recipe, service, userCanManageServices) {
85 const { intl } = this.context; 86 const { intl } = this.context;
86 const config = { 87 const config = {
87 fields: { 88 fields: {
@@ -127,6 +128,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
127 if (recipe.hasTeamId) { 128 if (recipe.hasTeamId) {
128 Object.assign(config.fields, { 129 Object.assign(config.fields, {
129 team: { 130 team: {
131 disabled: !userCanManageServices,
130 label: intl.formatMessage(messages.team), 132 label: intl.formatMessage(messages.team),
131 placeholder: intl.formatMessage(messages.team), 133 placeholder: intl.formatMessage(messages.team),
132 value: service.team, 134 value: service.team,
@@ -138,6 +140,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
138 if (recipe.hasCustomUrl) { 140 if (recipe.hasCustomUrl) {
139 Object.assign(config.fields, { 141 Object.assign(config.fields, {
140 customUrl: { 142 customUrl: {
143 disabled: !userCanManageServices,
141 label: intl.formatMessage(messages.customUrl), 144 label: intl.formatMessage(messages.customUrl),
142 placeholder: 'https://', 145 placeholder: 'https://',
143 value: service.customUrl, 146 value: service.customUrl,
@@ -189,7 +192,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
189 } 192 }
190 193
191 render() { 194 render() {
192 const { recipes, services, user } = this.props.stores; 195 const { recipes, services, user, features } = this.props.stores;
193 const { action } = this.props.router.params; 196 const { action } = this.props.router.params;
194 197
195 let recipe; 198 let recipe;
@@ -224,7 +227,8 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
224 ); 227 );
225 } 228 }
226 229
227 const form = this.prepareForm(recipe, service); 230 const userCanManageServices = features.features.userCanManageServices;
231 const form = this.prepareForm(recipe, service, userCanManageServices);
228 232
229 return ( 233 return (
230 <EditServiceForm 234 <EditServiceForm
@@ -232,6 +236,7 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
232 recipe={recipe} 236 recipe={recipe}
233 service={service} 237 service={service}
234 user={user.data} 238 user={user.data}
239 userCanManageServices={userCanManageServices}
235 form={form} 240 form={form}
236 status={services.actionStatus} 241 status={services.actionStatus}
237 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting} 242 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting}
@@ -248,6 +253,7 @@ EditServiceScreen.wrappedComponent.propTypes = {
248 user: PropTypes.instanceOf(UserStore).isRequired, 253 user: PropTypes.instanceOf(UserStore).isRequired,
249 recipes: PropTypes.instanceOf(RecipesStore).isRequired, 254 recipes: PropTypes.instanceOf(RecipesStore).isRequired,
250 services: PropTypes.instanceOf(ServicesStore).isRequired, 255 services: PropTypes.instanceOf(ServicesStore).isRequired,
256 features: PropTypes.instanceOf(FeaturesStore).isRequired,
251 settings: PropTypes.instanceOf(SettingsStore).isRequired, 257 settings: PropTypes.instanceOf(SettingsStore).isRequired,
252 }).isRequired, 258 }).isRequired,
253 router: PropTypes.shape({ 259 router: PropTypes.shape({
diff --git a/src/containers/settings/EditUserScreen.js b/src/containers/settings/EditUserScreen.js
index 4c5a9117e..3da3e8d2c 100644
--- a/src/containers/settings/EditUserScreen.js
+++ b/src/containers/settings/EditUserScreen.js
@@ -144,6 +144,7 @@ export default @inject('stores', 'actions') @observer class EditUserScreen exten
144 // user={user.data} 144 // user={user.data}
145 status={user.actionStatus} 145 status={user.actionStatus}
146 form={form} 146 form={form}
147 isEnterprise={user.data.isEnterprise}
147 isSaving={user.updateUserInfoRequest.isExecuting} 148 isSaving={user.updateUserInfoRequest.isExecuting}
148 onSubmit={d => this.onSubmit(d)} 149 onSubmit={d => this.onSubmit(d)}
149 /> 150 />
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
new file mode 100644
index 000000000..f788c347d
--- /dev/null
+++ b/src/stores/FeaturesStore.js
@@ -0,0 +1,36 @@
1import { computed, observable } from 'mobx';
2
3import Store from './lib/Store';
4import CachedRequest from './lib/CachedRequest';
5
6export default class FeaturesStore extends Store {
7 @observable defaultFeaturesRequest = new CachedRequest(this.api.features, 'default');
8 @observable featuresRequest = new CachedRequest(this.api.features, 'features');
9
10 setup() {
11 this.registerReactions([
12 this._monitorLoginStatus.bind(this),
13 this._debugFeatures.bind(this),
14 ]);
15 }
16
17 @computed get features() {
18 if (this.stores.user.isLoggedIn) {
19 return this.featuresRequest.execute().result || {};
20 }
21
22 return this.defaultFeaturesRequest.execute().result || {};
23 }
24
25 _debugFeatures() {
26 console.log(this.features);
27 }
28
29 _monitorLoginStatus() {
30 if (this.stores.user.isLoggedIn) {
31 this.featuresRequest.invalidate({ immediately: true });
32 } else {
33 this.defaultFeaturesRequest.invalidate({ immediately: true });
34 }
35 }
36}
diff --git a/src/stores/index.js b/src/stores/index.js
index 2d99e3952..96b844c95 100644
--- a/src/stores/index.js
+++ b/src/stores/index.js
@@ -1,5 +1,6 @@
1import AppStore from './AppStore'; 1import AppStore from './AppStore';
2import UserStore from './UserStore'; 2import UserStore from './UserStore';
3import FeaturesStore from './FeaturesStore';
3import SettingsStore from './SettingsStore'; 4import SettingsStore from './SettingsStore';
4import ServicesStore from './ServicesStore'; 5import ServicesStore from './ServicesStore';
5import RecipesStore from './RecipesStore'; 6import RecipesStore from './RecipesStore';
@@ -16,6 +17,7 @@ export default (api, actions, router) => {
16 router, 17 router,
17 app: new AppStore(stores, api, actions), 18 app: new AppStore(stores, api, actions),
18 user: new UserStore(stores, api, actions), 19 user: new UserStore(stores, api, actions),
20 features: new FeaturesStore(stores, api, actions),
19 settings: new SettingsStore(stores, api, actions), 21 settings: new SettingsStore(stores, api, actions),
20 services: new ServicesStore(stores, api, actions), 22 services: new ServicesStore(stores, api, actions),
21 recipes: new RecipesStore(stores, api, actions), 23 recipes: new RecipesStore(stores, api, actions),
diff --git a/src/styles/badge.scss b/src/styles/badge.scss
index 913a273df..f9fac039a 100644
--- a/src/styles/badge.scss
+++ b/src/styles/badge.scss
@@ -25,4 +25,10 @@
25 background: $theme-brand-primary; 25 background: $theme-brand-primary;
26 color: #FFF; 26 color: #FFF;
27 } 27 }
28
29 &.badge--success {
30 background: $theme-brand-success;
31 color: #FFF;
32 }
33
28} 34}