hippo-fw 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +16 -11
- data/Rakefile +0 -7
- data/bin/hippo +5 -1
- data/client/hippo/__mocks__/config.js +2 -3
- data/client/hippo/boot.jsx +0 -2
- data/client/hippo/components/asset.jsx +1 -1
- data/client/hippo/components/form.jsx +8 -4
- data/client/hippo/components/form/fields.jsx +28 -14
- data/client/hippo/components/form/model.js +65 -20
- data/client/hippo/components/form/wrapper.jsx +11 -5
- data/client/hippo/components/icon.jsx +1 -1
- data/client/hippo/components/master-detail.jsx +66 -0
- data/client/hippo/components/master-detail.scss +50 -0
- data/client/hippo/components/record-finder.jsx +5 -5
- data/client/hippo/components/tool-tip.jsx +20 -0
- data/client/hippo/config.js +5 -3
- data/client/hippo/extensions/base.js +4 -0
- data/client/hippo/lib/smooth-scroll.js +17 -16
- data/client/hippo/models/asset.js +8 -10
- data/client/hippo/models/collection.js +1 -4
- data/client/hippo/models/query/array-result.js +11 -9
- data/client/hippo/models/sync.js +3 -3
- data/client/hippo/models/tenant.js +29 -0
- data/client/hippo/screens/system-settings.jsx +5 -4
- data/client/hippo/screens/system-settings/mailer-config.jsx +11 -17
- data/client/hippo/screens/system-settings/tenant.jsx +90 -0
- data/client/hippo/screens/user-management/edit-form.jsx +15 -25
- data/client/hippo/testing/index.js +1 -0
- data/client/hippo/workspace/styles.scss +0 -23
- data/command-reference-files/initial/.babelrc +10 -8
- data/command-reference-files/initial/Gemfile +1 -1
- data/{views/index.html → command-reference-files/initial/views/index.erb} +1 -0
- data/config/routes.rb +48 -17
- data/config/webpack.config.js +7 -12
- data/db/migrate/01_create_tenants.rb +13 -0
- data/db/migrate/{01_create_system_settings.rb → 02_create_system_settings.rb} +2 -1
- data/db/migrate/{02_create_assets.rb → 03_create_assets.rb} +2 -4
- data/db/migrate/{20140615031600_create_users.rb → 04_create_users.rb} +4 -2
- data/db/seed.rb +10 -1
- data/hippo-fw.gemspec +53 -51
- data/lib/hippo.rb +7 -1
- data/lib/hippo/access.rb +0 -1
- data/lib/hippo/access/roles/basic_user.rb +2 -0
- data/lib/hippo/api.rb +4 -3
- data/lib/hippo/{access → api}/authentication_provider.rb +3 -1
- data/lib/hippo/api/controller_base.rb +2 -2
- data/lib/hippo/api/handlers/asset.rb +28 -2
- data/lib/hippo/api/handlers/tenant.rb +26 -0
- data/lib/hippo/api/helper_methods.rb +5 -13
- data/lib/hippo/api/request_wrapper.rb +8 -1
- data/lib/hippo/api/root.rb +50 -51
- data/lib/hippo/api/route_set.rb +101 -0
- data/lib/hippo/api/routing.rb +9 -98
- data/lib/hippo/api/tenant_domain_router.rb +21 -0
- data/lib/hippo/asset.rb +1 -0
- data/lib/hippo/command.rb +1 -24
- data/lib/hippo/command/app.rb +4 -3
- data/lib/hippo/command/console.rb +7 -0
- data/lib/hippo/command/generate.rb +0 -5
- data/lib/hippo/command/guard.rb +1 -0
- data/lib/hippo/command/jest.rb +2 -2
- data/lib/hippo/command/server.rb +1 -3
- data/lib/hippo/command/webpack.rb +6 -26
- data/lib/hippo/configuration.rb +21 -13
- data/lib/hippo/db.rb +2 -0
- data/lib/hippo/db/migrations.rb +9 -2
- data/lib/hippo/extension.rb +49 -14
- data/lib/hippo/extension/definition.rb +0 -4
- data/lib/hippo/guard_tasks.rb +3 -11
- data/lib/hippo/mailer.rb +28 -16
- data/lib/hippo/model.rb +10 -0
- data/lib/hippo/numbers.rb +1 -1
- data/lib/hippo/rake_tasks.rb +7 -1
- data/lib/hippo/spec_helper.rb +33 -11
- data/lib/hippo/system_settings.rb +1 -0
- data/lib/hippo/templates/base.rb +1 -1
- data/lib/hippo/templates/mail.rb +26 -0
- data/lib/hippo/templates/tenant_change.rb +23 -0
- data/lib/hippo/tenant.rb +53 -0
- data/lib/hippo/user.rb +12 -6
- data/lib/hippo/version.rb +1 -1
- data/lib/hippo/webpack.rb +57 -0
- data/lib/hippo/{command → webpack}/client_config.rb +7 -21
- data/package.json +3 -3
- data/spec/client/components/__snapshots__/master-detail.spec.jsx.snap +22 -0
- data/spec/client/components/form.spec.jsx +14 -14
- data/spec/client/components/master-detail.spec.jsx +24 -0
- data/spec/client/components/record-finder.spec.jsx +5 -2
- data/spec/client/models/asset.spec.js +2 -13
- data/spec/client/models/base.spec.js +1 -11
- data/spec/client/models/query.spec.js +2 -4
- data/spec/client/models/sync.spec.js +7 -0
- data/spec/client/screens/__snapshots__/system-settings.spec.jsx.snap +79 -0
- data/spec/client/screens/system-settings-tenants.spec.jsx +18 -0
- data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +29 -313
- data/spec/client/workspace/menu.spec.jsx +1 -9
- data/spec/factories/tenant.rb +13 -0
- data/spec/fixtures/mail/test_email.liquid +1 -0
- data/spec/fixtures/{test_printer.tex → test_printer.tex.erb} +0 -0
- data/spec/server/api/controller_base_spec.rb +1 -1
- data/spec/server/api/tenant_change_spec.rb +24 -0
- data/spec/server/api/tenant_isolation_spec.rb +37 -0
- data/spec/server/asset_spec.rb +6 -6
- data/spec/server/command_spec.rb +0 -5
- data/spec/server/mailer_spec.rb +25 -23
- data/spec/server/numbers_spec.rb +12 -13
- data/spec/server/print/form_spec.rb +2 -1
- data/spec/server/strings_spec.rb +13 -13
- data/templates/.babelrc +10 -8
- data/templates/js/screen-definitions.js +8 -10
- data/templates/mail/tenant_change.liquid +13 -0
- data/{command-reference-files/initial/views/index.html → views/index.erb} +5 -2
- data/yarn.lock +22 -169
- metadata +56 -30
- data/client/hippo/components/form/field-prop-type.js +0 -16
- data/lib/hippo/api/default_routes.rb +0 -38
- data/lib/hippo/command/generate_component.rb +0 -28
- data/lib/hippo/command/generate_component.usage +0 -11
- data/lib/hippo/command/webpack_view.rb +0 -32
- data/lib/hippo/multi_server_boot.rb +0 -26
- data/lib/hippo/reloadable_view.rb +0 -13
- data/templates/client/components/.gitkeep +0 -0
- data/templates/client/components/BaseComponent.coffee +0 -9
- data/templates/client/components/Component.cjsx +0 -4
- data/templates/client/components/template.html +0 -3
@@ -0,0 +1,50 @@
|
|
1
|
+
.master-detail-wrapper {
|
2
|
+
|
3
|
+
@keyframes slideOutLeft {
|
4
|
+
from { transform: translate3d(0, 0, 0); }
|
5
|
+
to { transform: translate3d(-50%, 0, 0); }
|
6
|
+
}
|
7
|
+
|
8
|
+
@keyframes slideInLeft {
|
9
|
+
from { transform: translate3d(-70%, 0, 0); }
|
10
|
+
to { transform: translate3d(0, 0, 0); }
|
11
|
+
}
|
12
|
+
|
13
|
+
display: flex;
|
14
|
+
flex: 1;
|
15
|
+
animation-duration: 0.5s;
|
16
|
+
animation-fill-mode: both;
|
17
|
+
.master,
|
18
|
+
.detail {
|
19
|
+
flex: 1;
|
20
|
+
display: flex;
|
21
|
+
animation-duration: 0.49s;
|
22
|
+
animation-fill-mode: both;
|
23
|
+
}
|
24
|
+
.detail {
|
25
|
+
display: none;
|
26
|
+
}
|
27
|
+
&.has-detail {
|
28
|
+
width: 200%;
|
29
|
+
animation-name: slideOutLeft;
|
30
|
+
.master {
|
31
|
+
|
32
|
+
}
|
33
|
+
.detail {
|
34
|
+
display: flex;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
&.detail-visible {
|
38
|
+
width: 100%;
|
39
|
+
animation-name: initial;
|
40
|
+
.master {
|
41
|
+
display: none;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
&.detail-removed {
|
45
|
+
animation-name: slideInLeft;
|
46
|
+
.master {
|
47
|
+
display: flex
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
@@ -4,7 +4,7 @@ import { inject, observer } from 'mobx-react';
|
|
4
4
|
import { action, observable } from 'mobx';
|
5
5
|
import { get } from 'lodash';
|
6
6
|
|
7
|
-
import {
|
7
|
+
import { Field } from 'hippo/components/form';
|
8
8
|
|
9
9
|
import Button from 'grommet/components/Button';
|
10
10
|
import SearchIcon from 'grommet/components/icons/base/Search';
|
@@ -17,7 +17,8 @@ import Query from '../models/query';
|
|
17
17
|
import './record-finder/record-finder.scss';
|
18
18
|
import QueryLayer from './record-finder/query-layer';
|
19
19
|
|
20
|
-
@inject('
|
20
|
+
@inject('formState')
|
21
|
+
@observer
|
21
22
|
export default class RecordFinder extends React.PureComponent {
|
22
23
|
static propTypes = {
|
23
24
|
query: PropTypes.instanceOf(Query).isRequired,
|
@@ -25,7 +26,6 @@ export default class RecordFinder extends React.PureComponent {
|
|
25
26
|
label: PropTypes.string,
|
26
27
|
recordsTitle: PropTypes.string.isRequired,
|
27
28
|
onRecordFound: PropTypes.func.isRequired,
|
28
|
-
formFields: FormFieldPropType,
|
29
29
|
}
|
30
30
|
|
31
31
|
@observable showingSearch = false;
|
@@ -43,7 +43,7 @@ export default class RecordFinder extends React.PureComponent {
|
|
43
43
|
@action.bound
|
44
44
|
onRecordSelect(model) {
|
45
45
|
this.showingSearch = false;
|
46
|
-
this.props.
|
46
|
+
this.props.formState.get(this.props.name).value = model[this.props.name]
|
47
47
|
this.props.onRecordFound(model);
|
48
48
|
}
|
49
49
|
|
@@ -54,7 +54,7 @@ export default class RecordFinder extends React.PureComponent {
|
|
54
54
|
|
55
55
|
|
56
56
|
get field() {
|
57
|
-
return this.props.fields
|
57
|
+
return this.props.formState.fields.get(this.props.name);
|
58
58
|
}
|
59
59
|
|
60
60
|
loadCurrentSelection() {
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import React, { PureComponent } from 'react';
|
2
|
+
import { Tooltip as Tippy } from 'react-tippy';
|
3
|
+
|
4
|
+
import 'react-tippy/dist/tippy.css';
|
5
|
+
|
6
|
+
// Use a wrapper component even though it doesn't really add any functionality
|
7
|
+
// In the future we'll add a Manager wrapper so that multiple tooltips cannot be shown at once
|
8
|
+
export default class ToolTip extends PureComponent {
|
9
|
+
|
10
|
+
render() {
|
11
|
+
const { children, ...tipProps } = this.props;
|
12
|
+
return (
|
13
|
+
<Tippy
|
14
|
+
{...tipProps}
|
15
|
+
>
|
16
|
+
{children}
|
17
|
+
</Tippy>
|
18
|
+
);
|
19
|
+
}
|
20
|
+
}
|
data/client/hippo/config.js
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
-
import { observable,
|
2
|
-
import { keysIn, pick, assign,
|
1
|
+
import { observable, observe } from 'mobx';
|
2
|
+
import { keysIn, pick, assign, get } from 'lodash';
|
3
3
|
import Extensions from './extensions';
|
4
4
|
|
5
5
|
const STORAGE_KEY = 'hippo-user-data';
|
6
6
|
|
7
7
|
class Config {
|
8
8
|
|
9
|
-
@observable api_host = window
|
9
|
+
@observable api_host = get(window, 'location.origin', '');
|
10
10
|
@observable api_path = '/api';
|
11
11
|
@observable access_token;
|
12
12
|
@observable root_view;
|
13
13
|
@observable assets_path_prefix = '/assets';
|
14
14
|
@observable user;
|
15
|
+
@observable website_domain;
|
16
|
+
@observable product_name;
|
15
17
|
@observable screens;
|
16
18
|
|
17
19
|
constructor() {
|
@@ -3,24 +3,24 @@ if (!window.Argosity) { window.Argosity = {}; }
|
|
3
3
|
const DEFAULT_DURATION = 750; // milliseconds
|
4
4
|
|
5
5
|
const EASE_IN_OUT = function(t) {
|
6
|
-
if (
|
6
|
+
if (0.5 >= t) {
|
7
|
+
return 4 * t * t * t;
|
8
|
+
}
|
9
|
+
return ((t - 1) * ((2 * t) - 2) * ((2 * t) - 2)) + 1;
|
7
10
|
};
|
8
11
|
|
9
12
|
const POSITION = function(start, end, elapsed, duration) {
|
10
13
|
if (elapsed > duration) {
|
11
14
|
return end;
|
12
|
-
} else {
|
13
|
-
return start + ((end - start) * EASE_IN_OUT(elapsed / duration));
|
14
15
|
}
|
16
|
+
return start + ((end - start) * EASE_IN_OUT(elapsed / duration));
|
15
17
|
};
|
16
18
|
|
17
|
-
|
18
19
|
export default class SmoothScroll {
|
19
20
|
|
20
|
-
constructor(link, destination, options) {
|
21
|
+
constructor(link, destination, options = {}) {
|
21
22
|
this.link = link;
|
22
23
|
this.destination = destination;
|
23
|
-
if (options == null) { options = {}; }
|
24
24
|
this.options = options;
|
25
25
|
if (!(this.destination instanceof Element)) {
|
26
26
|
this.destination = document.querySelector(this.destination);
|
@@ -31,7 +31,7 @@ export default class SmoothScroll {
|
|
31
31
|
if (this.link && this.destination) {
|
32
32
|
this.link.addEventListener('click', () => this.scrollToElement());
|
33
33
|
} else {
|
34
|
-
console.warn("failed to setup link", this.link, this.destination);
|
34
|
+
console.warn("failed to setup link", this.link, this.destination); // eslint-disable-line
|
35
35
|
}
|
36
36
|
}
|
37
37
|
|
@@ -39,14 +39,13 @@ export default class SmoothScroll {
|
|
39
39
|
return this.constructor.scroll(this.destination, this.options.duration || DEFAULT_DURATION);
|
40
40
|
}
|
41
41
|
|
42
|
-
static scroll(destination, duration) {
|
43
|
-
if (duration == null) { duration = DEFAULT_DURATION; }
|
42
|
+
static scroll(destination, duration = DEFAULT_DURATION) {
|
44
43
|
if (!(destination instanceof Element)) {
|
45
|
-
destination = document.querySelector(destination);
|
44
|
+
destination = document.querySelector(destination); // eslint-disable-line
|
46
45
|
}
|
47
46
|
|
48
47
|
if (!destination) {
|
49
|
-
console.warn("failed to scroll to", destination);
|
48
|
+
console.warn("failed to scroll to", destination); // eslint-disable-line
|
50
49
|
return false;
|
51
50
|
}
|
52
51
|
|
@@ -58,11 +57,13 @@ export default class SmoothScroll {
|
|
58
57
|
|
59
58
|
const startTime = Date.now();
|
60
59
|
|
61
|
-
|
60
|
+
function step() {
|
62
61
|
const elapsed = Date.now() - startTime;
|
63
|
-
window.scroll(0, POSITION(startPos, endPos, elapsed, duration)
|
64
|
-
if (elapsed < duration) {
|
65
|
-
|
62
|
+
window.scroll(0, POSITION(startPos, endPos, elapsed, duration));
|
63
|
+
if (elapsed < duration) {
|
64
|
+
window.requestAnimationFrame(step);
|
65
|
+
}
|
66
|
+
}
|
66
67
|
return step();
|
67
68
|
}
|
68
|
-
}
|
69
|
+
}
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import { includes, get
|
2
|
-
import qs from 'qs';
|
1
|
+
import { includes, get } from 'lodash';
|
3
2
|
import { observe } from 'mobx';
|
4
3
|
import {
|
5
4
|
BaseModel, identifiedBy, field, session, identifier, computed,
|
@@ -31,7 +30,6 @@ export default class Asset extends BaseModel {
|
|
31
30
|
|
32
31
|
constructor(props) {
|
33
32
|
super(props);
|
34
|
-
|
35
33
|
observe(this, 'owner', ({ newValue: owner }) => {
|
36
34
|
if (this.ownerSaveDisposer) { this.ownerSaveDisposer(); }
|
37
35
|
if (!owner || !owner.isModel) { return; }
|
@@ -55,6 +53,10 @@ export default class Asset extends BaseModel {
|
|
55
53
|
return !!this.file;
|
56
54
|
}
|
57
55
|
|
56
|
+
@computed get exists() {
|
57
|
+
return !!(this.file || this.id);
|
58
|
+
}
|
59
|
+
|
58
60
|
@computed get isImage() {
|
59
61
|
return includes(IMAGES, this.mimeType);
|
60
62
|
}
|
@@ -85,15 +87,11 @@ export default class Asset extends BaseModel {
|
|
85
87
|
form.append('owner_id', this.owner.identifierFieldValue);
|
86
88
|
form.append('owner_association', this.owner_association_name);
|
87
89
|
|
88
|
-
|
89
|
-
const query = {};
|
90
|
+
const options = { method: 'POST', body: form, headers: {} };
|
90
91
|
if (Config.access_token) {
|
91
|
-
|
92
|
-
}
|
93
|
-
if (!isEmpty(query)) {
|
94
|
-
url += `?${qs.stringify(query, { arrayFormat: 'brackets' })}`;
|
92
|
+
options.headers.Authorization = Config.access_token;
|
95
93
|
}
|
96
|
-
return fetch(
|
94
|
+
return fetch(`${Config.api_path}${Config.assets_path_prefix}`, options)
|
97
95
|
.then(resp => resp.json())
|
98
96
|
.then((json) => {
|
99
97
|
this.file = undefined;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { observable } from 'mobx';
|
2
2
|
import invariant from 'invariant';
|
3
|
-
import { extend,
|
3
|
+
import { extend, isArray, isObject } from 'lodash';
|
4
4
|
import { createCollection } from 'mobx-decorated-models';
|
5
5
|
|
6
6
|
import Sync from './sync';
|
@@ -50,9 +50,6 @@ export default class ModelCollection {
|
|
50
50
|
return this;
|
51
51
|
}
|
52
52
|
create(models = [], options = {}) {
|
53
|
-
if (isObject(models) && isEmpty(options)) {
|
54
|
-
return extendAry(this.$model, [], models, this);
|
55
|
-
}
|
56
53
|
return extendAry(this.$model, models, options, this);
|
57
54
|
}
|
58
55
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import {
|
2
|
-
isEmpty, isNil, extend, map, bindAll, omit,
|
2
|
+
isEmpty, isNil, extend, map, bindAll, omit, inRange, find, range,
|
3
3
|
} from 'lodash';
|
4
|
-
import {
|
4
|
+
import { reaction, observe } from 'mobx';
|
5
5
|
|
6
6
|
import Sync from '../sync';
|
7
7
|
import Result from './result';
|
@@ -15,10 +15,10 @@ export default class ArrayResult extends Result {
|
|
15
15
|
@belongsTo query;
|
16
16
|
@observable totalCount = 0;
|
17
17
|
@observable rows;
|
18
|
-
@observable isLoading;
|
19
18
|
@observable rowUpdateCount = 0;
|
20
19
|
@observable sortAscending;
|
21
20
|
@observable sortField;
|
21
|
+
@observable.shallow loadingRows = [];
|
22
22
|
|
23
23
|
constructor(attrs) {
|
24
24
|
super(attrs);
|
@@ -139,14 +139,12 @@ export default class ArrayResult extends Result {
|
|
139
139
|
}
|
140
140
|
|
141
141
|
isRowLoading(index) {
|
142
|
-
return !!(this.
|
142
|
+
return !!find(this.loadingRows, ([start, end]) => inRange(index, start, end));
|
143
143
|
}
|
144
144
|
|
145
145
|
fetch({ start = this.rows.length, limit = this.query.pageSize } = {}) {
|
146
|
-
|
147
|
-
|
148
|
-
}
|
149
|
-
range(start, start + limit).forEach(i => (this.rows[i].isLoading = true));
|
146
|
+
const inProgress = [start, start + limit];
|
147
|
+
this.loadingRows.push(inProgress);
|
150
148
|
|
151
149
|
const query = {};
|
152
150
|
this.query.clauses.forEach((clause) => {
|
@@ -155,7 +153,7 @@ export default class ArrayResult extends Result {
|
|
155
153
|
}
|
156
154
|
});
|
157
155
|
|
158
|
-
const options = extend({}, this.query.syncOptions, {
|
156
|
+
const options = extend({}, omit(this.query.syncOptions, 'include'), {
|
159
157
|
start,
|
160
158
|
limit,
|
161
159
|
total_count: 't',
|
@@ -179,8 +177,12 @@ export default class ArrayResult extends Result {
|
|
179
177
|
|
180
178
|
return Sync.perform(this.query.info.syncUrl, options).then((resp) => {
|
181
179
|
const rows = resp.data || [];
|
180
|
+
if (start > this.rows.length) {
|
181
|
+
range(this.rows.length, start).forEach(() => this.rows.push([]));
|
182
|
+
}
|
182
183
|
this.rows.splice(start, Math.max(limit, rows.length), ...rows);
|
183
184
|
this.totalCount = resp.total;
|
185
|
+
this.loadingRows.remove(inProgress);
|
184
186
|
delete this.syncInProgress;
|
185
187
|
return this;
|
186
188
|
});
|
data/client/hippo/models/sync.js
CHANGED
@@ -64,15 +64,15 @@ function perform(urlPrefix, defaultOptions = {}) {
|
|
64
64
|
});
|
65
65
|
|
66
66
|
let url = `${Config.api_host}${urlPrefix}.json`;
|
67
|
-
if (Config.access_token) {
|
68
|
-
query.jwt = Config.access_token;
|
69
|
-
}
|
70
67
|
if (!isEmpty(query)) {
|
71
68
|
url += `?${qs.stringify(query, { arrayFormat: 'brackets' })}`;
|
72
69
|
}
|
73
70
|
if (!options.headers) { options.headers = {}; }
|
74
71
|
|
75
72
|
options.headers['Content-Type'] = 'application/json';
|
73
|
+
if (Config.access_token) {
|
74
|
+
options.headers.Authorization = Config.access_token;
|
75
|
+
}
|
76
76
|
return fetch(url, options)
|
77
77
|
.then(resp => resp.json())
|
78
78
|
.then((json) => {
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { observable } from 'mobx';
|
2
|
+
import {
|
3
|
+
BaseModel, identifiedBy, field, identifier, computed,
|
4
|
+
} from './base';
|
5
|
+
import Config from '../config';
|
6
|
+
|
7
|
+
const CACHE = observable({
|
8
|
+
Tenant: undefined,
|
9
|
+
});
|
10
|
+
|
11
|
+
@identifiedBy('hippo/tenant')
|
12
|
+
export default class Tenant extends BaseModel {
|
13
|
+
|
14
|
+
@computed static get current() {
|
15
|
+
if (!CACHE.Tenant) {
|
16
|
+
CACHE.Tenant = new Tenant();
|
17
|
+
CACHE.Tenant.fetch({ query: 'current' });
|
18
|
+
}
|
19
|
+
return CACHE.Tenant;
|
20
|
+
}
|
21
|
+
|
22
|
+
@identifier id;
|
23
|
+
@field slug = Tenant.slug;
|
24
|
+
@field name;
|
25
|
+
|
26
|
+
@computed get domain() {
|
27
|
+
return `${this.slug}.${Config.website_domain}`;
|
28
|
+
}
|
29
|
+
}
|
@@ -16,7 +16,7 @@ import {Row, Col} from 'react-flexbox-grid';
|
|
16
16
|
import ScreenInstance from 'hippo/screens/instance';
|
17
17
|
import Extensions from '../extensions';
|
18
18
|
import MailerConfig from './system-settings/mailer-config';
|
19
|
-
|
19
|
+
import TenantSettings from './system-settings/tenant';
|
20
20
|
import './system-settings/system-settings.scss';
|
21
21
|
|
22
22
|
@observer
|
@@ -26,7 +26,7 @@ export default class SystemSettings extends React.PureComponent {
|
|
26
26
|
extensionPanelRefs = new Map();
|
27
27
|
|
28
28
|
static propTypes = {
|
29
|
-
screen:
|
29
|
+
screen: PropTypes.instanceOf(ScreenInstance).isRequired,
|
30
30
|
}
|
31
31
|
|
32
32
|
renderExtPanel(ext) {
|
@@ -56,6 +56,7 @@ export default class SystemSettings extends React.PureComponent {
|
|
56
56
|
@action.bound
|
57
57
|
onSave() {
|
58
58
|
this.extensionPanelRefs.forEach(panel => invoke(panel, 'onSave'));
|
59
|
+
this.tenantSettings.onSave()
|
59
60
|
this.settings.save();
|
60
61
|
}
|
61
62
|
|
@@ -71,8 +72,7 @@ export default class SystemSettings extends React.PureComponent {
|
|
71
72
|
/>
|
72
73
|
</Header>
|
73
74
|
<Heading>{this.props.screen.definition.title}</Heading>
|
74
|
-
{this.
|
75
|
-
|
75
|
+
<TenantSettings ref={ts => this.tenantSettings = ts} />
|
76
76
|
<Heading tag="h3">Images</Heading>
|
77
77
|
<Row>
|
78
78
|
<Col sm={4} xs={12}>
|
@@ -86,6 +86,7 @@ export default class SystemSettings extends React.PureComponent {
|
|
86
86
|
settings={this.settings.settings}
|
87
87
|
registerForSave={panel => this.extensionPanelRefs.set('mail', panel)}
|
88
88
|
/>
|
89
|
+
{this.extensionPanels}
|
89
90
|
</Screen>
|
90
91
|
);
|
91
92
|
}
|
@@ -4,32 +4,26 @@ import { observer } from 'mobx-react';
|
|
4
4
|
import { Row } from 'react-flexbox-grid';
|
5
5
|
import Heading from 'grommet/components/Heading';
|
6
6
|
|
7
|
-
import { Form, Field,
|
7
|
+
import { Form, Field, FormState, nonBlank, validEmail } from 'hippo/components/form';
|
8
8
|
|
9
9
|
@observer
|
10
10
|
export default class MailerConfig extends React.PureComponent {
|
11
11
|
|
12
|
-
|
13
|
-
user_name: nonBlank,
|
14
|
-
password: nonBlank,
|
15
|
-
address: nonBlank,
|
16
|
-
from_email: validEmail,
|
17
|
-
from_name: nonBlank,
|
18
|
-
})
|
12
|
+
formState = new FormState()
|
19
13
|
|
20
14
|
@action.bound
|
21
15
|
onSave() {
|
22
16
|
if (!this.props.settings.smtp) { this.props.settings.smtp = {}; }
|
23
|
-
this.
|
17
|
+
this.formState.persistTo(this.props.settings.smtp);
|
24
18
|
}
|
25
19
|
|
26
20
|
componentWillMount() {
|
27
21
|
this.props.registerForSave(this);
|
28
|
-
this.
|
22
|
+
this.formState.set(this.props.settings.smtp || {});
|
29
23
|
}
|
30
24
|
|
31
25
|
componentWillReceiveProps(nextProps) {
|
32
|
-
this.
|
26
|
+
this.formState.set(nextProps.settings.smtp || {});
|
33
27
|
}
|
34
28
|
|
35
29
|
|
@@ -37,13 +31,13 @@ export default class MailerConfig extends React.PureComponent {
|
|
37
31
|
return (
|
38
32
|
<div className="section">
|
39
33
|
<Heading tag="h3">Email settings</Heading>
|
40
|
-
<Form
|
34
|
+
<Form state={this.formState}>
|
41
35
|
<Row className="section">
|
42
|
-
<Field md={4} xs={6} name="user_name" />
|
43
|
-
<Field md={4} xs={6} name="password" type="password" />
|
44
|
-
<Field md={4} xs={6} name="address" label="Server Address" />
|
45
|
-
<Field md={4} xs={6} name="from_email" label="From Email" />
|
46
|
-
<Field md={4} xs={6} name="from_name" label="From Name" />
|
36
|
+
<Field md={4} xs={6} name="user_name" validate={nonBlank} />
|
37
|
+
<Field md={4} xs={6} name="password" type="password" validate={nonBlank} />
|
38
|
+
<Field md={4} xs={6} name="address" label="Server Address" validate={nonBlank} />
|
39
|
+
<Field md={4} xs={6} name="from_email" label="From Email" validate={validEmail} />
|
40
|
+
<Field md={4} xs={6} name="from_name" label="From Name" validate={nonBlank} />
|
47
41
|
</Row>
|
48
42
|
</Form>
|
49
43
|
</div>
|