diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api/FeaturesApi.js | 13 | ||||
-rw-r--r-- | src/api/RecipesApi.js | 2 | ||||
-rw-r--r-- | src/api/index.js | 2 | ||||
-rw-r--r-- | src/api/server/ServerApi.js | 29 | ||||
-rw-r--r-- | src/components/settings/account/AccountDashboard.js | 86 | ||||
-rw-r--r-- | src/components/settings/navigation/SettingsNavigation.js | 28 | ||||
-rw-r--r-- | src/components/settings/services/EditServiceForm.js | 4 | ||||
-rw-r--r-- | src/components/settings/user/EditUserForm.js | 8 | ||||
-rw-r--r-- | src/components/ui/Input.js | 1 | ||||
-rw-r--r-- | src/containers/auth/AuthLayoutContainer.js | 23 | ||||
-rw-r--r-- | src/containers/layout/AppLayoutContainer.js | 8 | ||||
-rw-r--r-- | src/containers/settings/EditServiceScreen.js | 12 | ||||
-rw-r--r-- | src/containers/settings/EditUserScreen.js | 1 | ||||
-rw-r--r-- | src/stores/FeaturesStore.js | 36 | ||||
-rw-r--r-- | src/stores/index.js | 2 | ||||
-rw-r--r-- | src/styles/badge.scss | 6 |
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 @@ | |||
1 | export 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 @@ | |||
1 | export default class ServicesApi { | 1 | export 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'; | |||
6 | import LocalApi from './LocalApi'; | 6 | import LocalApi from './LocalApi'; |
7 | import PaymentApi from './PaymentApi'; | 7 | import PaymentApi from './PaymentApi'; |
8 | import NewsApi from './NewsApi'; | 8 | import NewsApi from './NewsApi'; |
9 | import FeaturesApi from './FeaturesApi'; | ||
9 | 10 | ||
10 | export default (server, local) => ({ | 11 | export 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: | ||
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: | ||
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 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { defineMessages, intlShape } from 'react-intl'; | 3 | import { defineMessages, intlShape } from 'react-intl'; |
4 | import { inject, observer } from 'mobx-react'; | ||
4 | 5 | ||
5 | import Link from '../../ui/Link'; | 6 | import Link from '../../ui/Link'; |
6 | 7 | ||
@@ -31,6 +32,7 @@ const messages = defineMessages({ | |||
31 | }, | 32 | }, |
32 | }); | 33 | }); |
33 | 34 | ||
35 | @inject('stores') @observer | ||
34 | export default class SettingsNavigation extends Component { | 36 | export 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 | |||
101 | SettingsNavigation.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'; | |||
5 | import AuthLayout from '../../components/auth/AuthLayout'; | 5 | import AuthLayout from '../../components/auth/AuthLayout'; |
6 | import AppStore from '../../stores/AppStore'; | 6 | import AppStore from '../../stores/AppStore'; |
7 | import GlobalErrorStore from '../../stores/GlobalErrorStore'; | 7 | import GlobalErrorStore from '../../stores/GlobalErrorStore'; |
8 | import AppLoader from '../../components/ui/AppLoader'; | ||
8 | 9 | ||
9 | import { oneOrManyChildElements } from '../../prop-types'; | 10 | import { 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'; | |||
5 | import AppStore from '../../stores/AppStore'; | 5 | import AppStore from '../../stores/AppStore'; |
6 | import RecipesStore from '../../stores/RecipesStore'; | 6 | import RecipesStore from '../../stores/RecipesStore'; |
7 | import ServicesStore from '../../stores/ServicesStore'; | 7 | import ServicesStore from '../../stores/ServicesStore'; |
8 | import FeaturesStore from '../../stores/FeaturesStore'; | ||
8 | import UIStore from '../../stores/UIStore'; | 9 | import UIStore from '../../stores/UIStore'; |
9 | import NewsStore from '../../stores/NewsStore'; | 10 | import NewsStore from '../../stores/NewsStore'; |
10 | import SettingsStore from '../../stores/SettingsStore'; | 11 | import 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 | |||
131 | AppLayoutContainer.wrappedComponent.propTypes = { | 136 | AppLayoutContainer.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'; | |||
6 | import UserStore from '../../stores/UserStore'; | 6 | import UserStore from '../../stores/UserStore'; |
7 | import RecipesStore from '../../stores/RecipesStore'; | 7 | import RecipesStore from '../../stores/RecipesStore'; |
8 | import ServicesStore from '../../stores/ServicesStore'; | 8 | import ServicesStore from '../../stores/ServicesStore'; |
9 | import FeaturesStore from '../../stores/FeaturesStore'; | ||
9 | import SettingsStore from '../../stores/SettingsStore'; | 10 | import SettingsStore from '../../stores/SettingsStore'; |
10 | import Form from '../../lib/Form'; | 11 | import Form from '../../lib/Form'; |
11 | import { gaPage } from '../../lib/analytics'; | 12 | import { 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 @@ | |||
1 | import { computed, observable } from 'mobx'; | ||
2 | |||
3 | import Store from './lib/Store'; | ||
4 | import CachedRequest from './lib/CachedRequest'; | ||
5 | |||
6 | export 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 @@ | |||
1 | import AppStore from './AppStore'; | 1 | import AppStore from './AppStore'; |
2 | import UserStore from './UserStore'; | 2 | import UserStore from './UserStore'; |
3 | import FeaturesStore from './FeaturesStore'; | ||
3 | import SettingsStore from './SettingsStore'; | 4 | import SettingsStore from './SettingsStore'; |
4 | import ServicesStore from './ServicesStore'; | 5 | import ServicesStore from './ServicesStore'; |
5 | import RecipesStore from './RecipesStore'; | 6 | import 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 | } |