disco_app 0.8.9 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/images/disco_app/icons.svg +0 -0
- data/app/assets/javascripts/disco_app/components/{filterable_shop_list.js.jsx → custom/filterable_shop_list.js.jsx} +1 -1
- data/app/assets/javascripts/disco_app/components/custom/inline-radio-options.es6.jsx +59 -0
- data/app/assets/javascripts/disco_app/components/custom/rules-editor.es6.jsx +360 -0
- data/app/assets/javascripts/disco_app/components/{shop_list.js.jsx → custom/shop_list.js.jsx} +9 -11
- data/app/assets/javascripts/disco_app/components/custom/shop_row.js.jsx +43 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/cards/card-section.es6.jsx +30 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/cards/card.es6.jsx +9 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/cards/cart-section-title.es6.jsx +7 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_form.es6.jsx +72 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_input.es6.jsx +20 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/button.es6.jsx +13 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-radio.es6.jsx +30 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-select.es6.jsx +39 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-text.es6.jsx +59 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-textarea.es6.jsx +48 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/icons/icon-chevron.es6.jsx +33 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/icons/next-icon.es6.jsx +18 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/input_select.es6.jsx +21 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-annotated-section.es6.jsx +29 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-empty-state.es6.jsx +35 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-footer-help.es6.jsx +13 -0
- data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-page-actions.es6.jsx +39 -0
- data/app/assets/javascripts/disco_app/components.js +1 -4
- data/app/assets/javascripts/disco_app/disco_app.js +3 -0
- data/app/assets/javascripts/disco_app/ui-kit.js +1 -0
- data/app/assets/stylesheets/disco_app/admin/_header.scss +66 -0
- data/app/assets/stylesheets/disco_app/admin/_layout.scss +40 -0
- data/app/assets/stylesheets/disco_app/admin/_nav.scss +172 -0
- data/app/assets/stylesheets/disco_app/admin.scss +11 -0
- data/app/assets/stylesheets/disco_app/disco_app.scss +12 -12
- data/app/assets/stylesheets/disco_app/{disco/mixins → mixins}/_flexbox.scss +6 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-empty-state.scss +94 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-footer-help.scss +25 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-icons.scss +28 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-kit.scss +5086 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-layout.scss +10 -0
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-page-actions.scss +21 -0
- data/app/assets/stylesheets/disco_app/{disco/_tabs.scss → ui-kit/_ui-tabs.scss} +3 -1
- data/app/assets/stylesheets/disco_app/ui-kit/_ui-type.scss +13 -0
- data/app/controllers/disco_app/admin/concerns/plans_controller.rb +8 -5
- data/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb +32 -0
- data/app/controllers/disco_app/admin/subscriptions_controller.rb +3 -0
- data/app/helpers/disco_app/application_helper.rb +22 -0
- data/app/models/disco_app/concerns/plan.rb +2 -2
- data/app/models/disco_app/concerns/shop.rb +0 -4
- data/app/models/disco_app/concerns/subscription.rb +12 -0
- data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +40 -3
- data/app/views/disco_app/admin/plans/_form.html.erb +18 -21
- data/app/views/disco_app/admin/plans/_plan_code_fields.html.erb +15 -0
- data/app/views/disco_app/admin/plans/index.html.erb +2 -0
- data/app/views/disco_app/admin/shops/index.html.erb +2 -1
- data/app/views/disco_app/admin/subscriptions/edit.html.erb +33 -0
- data/app/views/disco_app/shared/_icons.html.erb +3 -0
- data/app/views/layouts/admin/_nav_items.erb +20 -0
- data/app/views/layouts/admin.html.erb +53 -9
- data/app/views/layouts/embedded_app.html.erb +2 -0
- data/app/views/layouts/embedded_app_modal.html.erb +11 -0
- data/config/routes.rb +4 -2
- data/lib/disco_app/engine.rb +2 -1
- data/lib/disco_app/version.rb +1 -1
- data/lib/generators/disco_app/disco_app_generator.rb +35 -2
- data/lib/generators/disco_app/templates/assets/javascripts/components.js +3 -0
- data/lib/generators/disco_app/templates/config/database.yml.tt +20 -0
- data/lib/tasks/database.rake +8 -0
- data/test/controllers/disco_app/charges_controller_test.rb +9 -2
- data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_request.json +1 -1
- data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_request.json +1 -1
- data/test/fixtures/disco_app/subscriptions.yml +1 -0
- data/test/models/disco_app/subscription_test.rb +19 -0
- data/test/services/disco_app/charges_service_test.rb +9 -2
- data/test/test_helper.rb +3 -0
- metadata +80 -21
- data/app/assets/javascripts/disco_app/components/shop_row.js.jsx +0 -27
- data/app/assets/stylesheets/disco_app/bootstrap/_custom.scss +0 -54
- data/app/assets/stylesheets/disco_app/bootstrap/_variables.scss +0 -872
- data/app/assets/stylesheets/disco_app/disco/_buttons.scss +0 -31
- data/app/assets/stylesheets/disco_app/disco/_cards.scss +0 -52
- data/app/assets/stylesheets/disco_app/disco/_forms.scss +0 -23
- data/app/assets/stylesheets/disco_app/disco/_grid.scss +0 -58
- data/app/assets/stylesheets/disco_app/disco/_sections.scss +0 -61
- data/app/assets/stylesheets/disco_app/disco/_tables.scss +0 -57
- data/app/assets/stylesheets/disco_app/disco/_type.scss +0 -39
- data/app/views/layouts/admin/_navbar.html.erb +0 -25
- data/lib/generators/disco_app/adminify/adminify_generator.rb +0 -35
- data/lib/generators/disco_app/reactify/reactify_generator.rb +0 -45
- /data/app/assets/javascripts/disco_app/components/{shop_filter_tab.js.jsx → custom/shop_filter_tab.js.jsx} +0 -0
- /data/app/assets/javascripts/disco_app/components/{shop_filter_tabs.js.jsx → custom/shop_filter_tabs.js.jsx} +0 -0
- /data/app/assets/javascripts/disco_app/components/{shopify_admin_link.js.jsx → custom/shopify_admin_link.js.jsx} +0 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
/**
|
2
|
+
* Base class for form components that provides helper methods
|
3
|
+
* for rendering errors associated with the form
|
4
|
+
**/
|
5
|
+
class BaseForm extends React.Component {
|
6
|
+
|
7
|
+
/**
|
8
|
+
* check if the field parameter has errors associated.
|
9
|
+
* if no parameters are given, it checks if there's at least an error at all
|
10
|
+
**/
|
11
|
+
hasErrors(field) {
|
12
|
+
if ((arguments.length === 0 && this.errorKeys().length > 0) ||
|
13
|
+
(this.props.errors && this.props.errors.errors.indexOf(field) > -1)) {
|
14
|
+
return true;
|
15
|
+
}
|
16
|
+
return false;
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* returns a list of fields that contain errors
|
21
|
+
**/
|
22
|
+
errorKeys() {
|
23
|
+
return this.props.errors.errors;
|
24
|
+
}
|
25
|
+
|
26
|
+
/**
|
27
|
+
* returns the type of resource associate with this error
|
28
|
+
**/
|
29
|
+
errorType() {
|
30
|
+
return this.props.errors.type;
|
31
|
+
}
|
32
|
+
|
33
|
+
/**
|
34
|
+
* returns the error messages
|
35
|
+
**/
|
36
|
+
errorMessages() {
|
37
|
+
return this.props.errors.messages;
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* renders basic form errors
|
42
|
+
**/
|
43
|
+
renderErrors() {
|
44
|
+
if (!this.hasErrors()) {
|
45
|
+
return null;
|
46
|
+
}
|
47
|
+
|
48
|
+
var title = "There " + (this.errorMessages().length == 1 ? "is" : "are") + " " + this.errorMessages().length + " error" + (this.errorMessages().length > 1 ? "s" : "") + " for this " + this.errorType() + ":";
|
49
|
+
var errorMessages = this.errorMessages().map(function(message) {
|
50
|
+
return <li>{message}</li>;
|
51
|
+
});
|
52
|
+
|
53
|
+
return (
|
54
|
+
<div className="ui-banner ui-banner--status-error ui-banner--default-vertical-spacing ui-banner--default-horizontal-spacing">
|
55
|
+
<div className="ui-banner__ribbon">
|
56
|
+
<svg className="next-icon next-icon--24" viewBox="0 0 24 24">
|
57
|
+
<path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.6 0 12 0zm0 4c1.4 0 2.7.4 3.9 1L12 8.8 8.8 12 5 15.9c-.6-1.1-1-2.5-1-3.9 0-4.4 3.6-8 8-8zm0 16c-1.4 0-2.7-.4-3.9-1l3.9-3.9 3.2-3.2L19 8.1c.6 1.1 1 2.5 1 3.9 0 4.4-3.6 8-8 8z"></path>
|
58
|
+
</svg>
|
59
|
+
</div>
|
60
|
+
<div className="ui-banner__content">
|
61
|
+
<h2 className="ui-banner__title">
|
62
|
+
{title}
|
63
|
+
</h2>
|
64
|
+
<ul>
|
65
|
+
{errorMessages}
|
66
|
+
</ul>
|
67
|
+
</div>
|
68
|
+
</div>
|
69
|
+
)
|
70
|
+
}
|
71
|
+
|
72
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
/**
|
2
|
+
* Base class providing helper method for deciding if an input contains errors.
|
3
|
+
* Expects subclasses to have `name` and `errors` props.
|
4
|
+
**/
|
5
|
+
class BaseInput extends React.Component {
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Checks if the component has associated errors or not
|
9
|
+
**/
|
10
|
+
hasError() {
|
11
|
+
if (!(this.props && this.props.name)) {
|
12
|
+
return false;
|
13
|
+
}
|
14
|
+
var fieldName = this.props.name.substring(this.props.name.lastIndexOf("[") + 1, this.props.name.lastIndexOf("]"));
|
15
|
+
return (fieldName &&
|
16
|
+
this.props.errors &&
|
17
|
+
this.props.errors.errors &&
|
18
|
+
this.props.errors.errors.indexOf(fieldName) > -1);
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
const InputRadio = ({ label, name, value, checked, inline, isLast, onChange }) => {
|
2
|
+
|
3
|
+
const id = `${name}[${value}]`;
|
4
|
+
|
5
|
+
const wrapperClassName = classNames({
|
6
|
+
'next-input-wrapper': true,
|
7
|
+
'inline': inline,
|
8
|
+
'sr': !isLast
|
9
|
+
});
|
10
|
+
|
11
|
+
const labelClassName = classNames({
|
12
|
+
'next-label': true,
|
13
|
+
'next-label--switch': true,
|
14
|
+
'inline': inline,
|
15
|
+
'fw-normal': inline
|
16
|
+
});
|
17
|
+
|
18
|
+
const handleChange = (e) => {
|
19
|
+
onChange(e.target.value);
|
20
|
+
};
|
21
|
+
|
22
|
+
return(
|
23
|
+
<div className={wrapperClassName}>
|
24
|
+
<label htmlFor={id} className={labelClassName}>{label}</label>
|
25
|
+
<input id={id} className="next-radio" type="radio" value={value} name={name} checked={checked} onChange={handleChange} />
|
26
|
+
<span className="next-radio--styled" />
|
27
|
+
</div>
|
28
|
+
)
|
29
|
+
|
30
|
+
};
|
@@ -0,0 +1,39 @@
|
|
1
|
+
const InputSelect = ({ id, label, labelHidden, name, options, value, onChange }) => {
|
2
|
+
|
3
|
+
const optionElements = options.map((option) => {
|
4
|
+
return <option key={option.value} value={option.value}>{option.label}</option>;
|
5
|
+
});
|
6
|
+
|
7
|
+
const labelClassName = classNames({
|
8
|
+
'next-label': true,
|
9
|
+
'helper--visually-hidden': labelHidden
|
10
|
+
});
|
11
|
+
|
12
|
+
const handleChange = (e) => {
|
13
|
+
onChange(e.target.value);
|
14
|
+
};
|
15
|
+
|
16
|
+
return(
|
17
|
+
<div className="next-input-wrapper">
|
18
|
+
<label className={labelClassName} htmlFor={id}>{label}</label>
|
19
|
+
<div className="next-select__wrapper next-input--has-content">
|
20
|
+
<select className="next-select rule-field" id={id} name={name} value={value} onChange={handleChange}>
|
21
|
+
{optionElements}
|
22
|
+
</select>
|
23
|
+
<NextIcon name="next-chevron-down" size={12} />
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
)
|
27
|
+
|
28
|
+
};
|
29
|
+
|
30
|
+
InputSelect.propTypes = {
|
31
|
+
label: React.PropTypes.string.isRequired,
|
32
|
+
name: React.PropTypes.string,
|
33
|
+
options: React.PropTypes.arrayOf(
|
34
|
+
React.PropTypes.shape({
|
35
|
+
label: React.PropTypes.string.isRequired,
|
36
|
+
value: React.PropTypes.string.isRequired
|
37
|
+
})
|
38
|
+
).isRequired
|
39
|
+
};
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class InputText extends BaseInput {
|
2
|
+
|
3
|
+
render() {
|
4
|
+
const { errors, name, value, defaultValue, disabled, helpMessage, label, labelHidden, onChange, placeholder } = this.props;
|
5
|
+
|
6
|
+
const wrapperClassName = classNames({
|
7
|
+
'next-input-wrapper': true,
|
8
|
+
'next-input-wrapper--is-error': this.hasError()
|
9
|
+
});
|
10
|
+
|
11
|
+
const labelClassName = classNames({
|
12
|
+
'next-label': true,
|
13
|
+
'helper--visually-hidden': labelHidden
|
14
|
+
});
|
15
|
+
|
16
|
+
const handleChange = (e) => {
|
17
|
+
onChange(e.target.value);
|
18
|
+
};
|
19
|
+
|
20
|
+
let helpElement = null;
|
21
|
+
if(helpMessage) {
|
22
|
+
helpElement = <p className="next-input__help-text">{helpMessage}</p>;
|
23
|
+
}
|
24
|
+
|
25
|
+
return (
|
26
|
+
<div className={wrapperClassName}>
|
27
|
+
<label className={labelClassName} htmlFor={name}>{label}</label>
|
28
|
+
<input
|
29
|
+
id={name}
|
30
|
+
className="next-input"
|
31
|
+
disabled={disabled}
|
32
|
+
value={value}
|
33
|
+
defaultValue={defaultValue}
|
34
|
+
name={name}
|
35
|
+
onChange={handleChange}
|
36
|
+
placeholder={placeholder}
|
37
|
+
type="text"
|
38
|
+
/>
|
39
|
+
{helpElement}
|
40
|
+
</div>
|
41
|
+
);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
InputText.propTypes = {
|
46
|
+
errors: React.PropTypes.object,
|
47
|
+
label: React.PropTypes.string.isRequired,
|
48
|
+
name: React.PropTypes.string,
|
49
|
+
onChange: React.PropTypes.func,
|
50
|
+
placeholder: React.PropTypes.string,
|
51
|
+
helpMessage: React.PropTypes.string,
|
52
|
+
error: React.PropTypes.bool,
|
53
|
+
disabled: React.PropTypes.bool
|
54
|
+
};
|
55
|
+
|
56
|
+
InputText.defaultProps = {
|
57
|
+
errors: {},
|
58
|
+
disabled: false
|
59
|
+
};
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class InputTextArea extends BaseInput {
|
2
|
+
|
3
|
+
render() {
|
4
|
+
const { errors, defaultValue, label, name, onChange, placeholder, helpMessage } = this.props;
|
5
|
+
|
6
|
+
const wrapperClassName = classNames({
|
7
|
+
'next-input-wrapper': true,
|
8
|
+
'next-input-wrapper--is-error': this.hasError()
|
9
|
+
});
|
10
|
+
|
11
|
+
const handleChange = (e) => {
|
12
|
+
onChange(e.target.value);
|
13
|
+
};
|
14
|
+
|
15
|
+
let helpElement = null;
|
16
|
+
if(helpMessage) {
|
17
|
+
helpElement = <p className="next-input__help-text">{helpMessage}</p>;
|
18
|
+
}
|
19
|
+
|
20
|
+
return (
|
21
|
+
<div className={wrapperClassName}>
|
22
|
+
<label className="next-label" htmlFor={name}>{label}</label>
|
23
|
+
<textarea
|
24
|
+
className="next-textarea"
|
25
|
+
defaultValue={defaultValue}
|
26
|
+
name={name}
|
27
|
+
onChange={handleChange}
|
28
|
+
placeholder={placeholder}
|
29
|
+
/>
|
30
|
+
{helpElement}
|
31
|
+
</div>
|
32
|
+
);
|
33
|
+
}
|
34
|
+
};
|
35
|
+
|
36
|
+
InputTextArea.propTypes = {
|
37
|
+
errors: React.PropTypes.object,
|
38
|
+
defaultValue: React.PropTypes.string,
|
39
|
+
label: React.PropTypes.string.isRequired,
|
40
|
+
name: React.PropTypes.string.isRequired,
|
41
|
+
onChange: React.PropTypes.func.isRequired,
|
42
|
+
placeholder: React.PropTypes.string,
|
43
|
+
helpMessage: React.PropTypes.string
|
44
|
+
};
|
45
|
+
|
46
|
+
InputTextArea.defaultProps = {
|
47
|
+
errors: {},
|
48
|
+
};
|
@@ -0,0 +1,33 @@
|
|
1
|
+
const IconChevron = ({ size, direction, disabled }) => {
|
2
|
+
|
3
|
+
const iconClasses = classNames({
|
4
|
+
"next-icon": true,
|
5
|
+
"next-icon--blue": disabled === false,
|
6
|
+
"next-icon--sky-darker": disabled,
|
7
|
+
"next-icon--rotate-180": direction === 'previous',
|
8
|
+
['next-icon--' + size]: true
|
9
|
+
});
|
10
|
+
|
11
|
+
return (
|
12
|
+
<svg
|
13
|
+
aria-labelledby="next-chevron"
|
14
|
+
role="img"
|
15
|
+
className={iconClasses}
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
17
|
+
viewBox="0 0 28 28">
|
18
|
+
|
19
|
+
<path d="M7,3.8l10.5,10.5L7,24.7l2.7,2.7L23,14.3L9.7,1C9.7,1,7,3.8,7,3.8z" />
|
20
|
+
</svg>
|
21
|
+
);
|
22
|
+
};
|
23
|
+
|
24
|
+
IconChevron.propTypes = {
|
25
|
+
direction: React.PropTypes.oneOf(['next', 'previous']).isRequired,
|
26
|
+
disabled: React.PropTypes.bool,
|
27
|
+
size: React.PropTypes.number
|
28
|
+
};
|
29
|
+
|
30
|
+
IconChevron.defaultProps = {
|
31
|
+
disabled: false,
|
32
|
+
size: 10
|
33
|
+
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
const NextIcon = ({ size, name }) => {
|
2
|
+
|
3
|
+
const className = classNames({
|
4
|
+
"next-icon": true,
|
5
|
+
['next-icon--size-' + size]: true
|
6
|
+
});
|
7
|
+
|
8
|
+
return (
|
9
|
+
<svg className={className}>
|
10
|
+
<use xlinkHref={'#' + name} />
|
11
|
+
</svg>
|
12
|
+
);
|
13
|
+
};
|
14
|
+
|
15
|
+
NextIcon.propTypes = {
|
16
|
+
size: React.PropTypes.number,
|
17
|
+
name: React.PropTypes.string
|
18
|
+
};
|
@@ -0,0 +1,21 @@
|
|
1
|
+
const InputSelectOld = ({ label, name, options, value, onChange }) => {
|
2
|
+
|
3
|
+
const id = name;
|
4
|
+
|
5
|
+
const optionElements = options.map((option) => {
|
6
|
+
return <option key={option.value} value={option.value}>{option.label}</option>;
|
7
|
+
});
|
8
|
+
|
9
|
+
const handleChange = (e) => {
|
10
|
+
onChange(e.target.value);
|
11
|
+
};
|
12
|
+
|
13
|
+
return(
|
14
|
+
<div className="next-input-wrapper next-input-wrapper--halved">
|
15
|
+
<select id={id} name={name} value={value} onChange={handleChange}>
|
16
|
+
{optionElements}
|
17
|
+
</select>
|
18
|
+
</div>
|
19
|
+
)
|
20
|
+
|
21
|
+
};
|
data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-annotated-section.es6.jsx
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
const UIAnnotatedSection = ({ title, description, children }) => {
|
2
|
+
|
3
|
+
return (
|
4
|
+
<section className="ui-annotated-section">
|
5
|
+
|
6
|
+
<div className="ui-annotated-section__annotation">
|
7
|
+
<div className="ui-annotated-section__title">
|
8
|
+
<h2 className="next-heading next-heading--no-margin">{title}</h2>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div className="ui-annotated-section__description">
|
12
|
+
<p>{description}</p>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<div className="ui-annotated-section__content">
|
17
|
+
{children}
|
18
|
+
</div>
|
19
|
+
|
20
|
+
</section>
|
21
|
+
);
|
22
|
+
|
23
|
+
};
|
24
|
+
|
25
|
+
UIAnnotatedSection.propTypes = {
|
26
|
+
title: React.PropTypes.string.isRequired,
|
27
|
+
description: React.PropTypes.string,
|
28
|
+
children: React.PropTypes.node
|
29
|
+
};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
const UIEmptyState = ({ title, subtitle, image, href, label }) => {
|
2
|
+
|
3
|
+
let imageSubsection = null;
|
4
|
+
if(image) {
|
5
|
+
imageSubsection = (
|
6
|
+
<div className="ui-empty-state__subsection">
|
7
|
+
<div className="ui-empty-state__items ui-empty-state__items--odd-queries ui-empty-state__items--quantity-queries">
|
8
|
+
<div className="ui-empty-state__item">
|
9
|
+
<div className="ui-empty-state__subitems">
|
10
|
+
<div className="ui-empty-state__subitem">
|
11
|
+
<img src={image} alt={title} />
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
);
|
18
|
+
}
|
19
|
+
|
20
|
+
return(
|
21
|
+
<div className="ui-empty-state">
|
22
|
+
<div className="ui-empty-state__section">
|
23
|
+
<div className="ui-empty-state__subsection">
|
24
|
+
<h1 className="next-heading next-heading--xl">{title}</h1>
|
25
|
+
<h2 className="next-heading next-heading--subdued">{subtitle}</h2>
|
26
|
+
</div>
|
27
|
+
{imageSubsection}
|
28
|
+
<div className="ui-empty-state__subsection">
|
29
|
+
<a className="btn btn-large btn-primary" href={href}>{label}</a>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
)
|
34
|
+
|
35
|
+
};
|
@@ -0,0 +1,39 @@
|
|
1
|
+
const UIPageActions = ({ label, disabled, secondaryHref, secondaryLabel }) => {
|
2
|
+
|
3
|
+
const buttonClassName = classNames({
|
4
|
+
'btn': true,
|
5
|
+
'btn-primary': !disabled,
|
6
|
+
'disabled': disabled
|
7
|
+
});
|
8
|
+
|
9
|
+
let secondaryElement = null;
|
10
|
+
if(secondaryHref) {
|
11
|
+
secondaryElement = (
|
12
|
+
<div className="ui-page-actions__secondary">
|
13
|
+
<div className="button-group">
|
14
|
+
<a className="btn" href={secondaryHref}>{secondaryLabel}</a>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
);
|
18
|
+
}
|
19
|
+
|
20
|
+
return (
|
21
|
+
<div className="ui-page-actions">
|
22
|
+
{secondaryElement}
|
23
|
+
<div className="ui-page-actions__primary">
|
24
|
+
<div className="button-group button-group--right-aligned">
|
25
|
+
<button name="button" type="submit" className={buttonClassName} disabled={disabled}>
|
26
|
+
{label}
|
27
|
+
</button>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
);
|
32
|
+
};
|
33
|
+
|
34
|
+
UIPageActions.propTypes = {
|
35
|
+
label: React.PropTypes.string.isRequired,
|
36
|
+
disabled: React.PropTypes.bool,
|
37
|
+
secondaryHref: React.PropTypes.string,
|
38
|
+
secondaryLabel: React.PropTypes.string
|
39
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
(function(){var t,e,n,s,a,i=function(t,e){return function(){return t.apply(e,arguments)}};t={STYLIZED:"next-input--stylized",SELECT_WRAPPER:"next-select__wrapper",TEXTAREA:"next-textarea",INPUT:"next-input",SELECT:"next-select",LABEL:"next-label"},n={LABEL:{FOCUSED:t.LABEL+"--is-focused"},INPUT:{FOCUSED:t.INPUT+"--is-focused"}},e=[t.INPUT,t.TEXTAREA,t.SELECT],s=function(){function s(){this.handleFocusChange=i(this.handleFocusChange,this),document.addEventListener("focus",this.handleFocusChange,!0),document.addEventListener("blur",this.handleFocusChange,!0)}return s.prototype.handleFocusChange=function(t){var s,a,i,o,r,c,l,u,d;for(u=t.target,a=0,o=e.length;o>a;a++)l=e[a],d=u.classList.contains(l);return d?(c=this.findLabelAndParent(u),i=c[0],r=c[1],s="focus"===t.type,i.classList.toggle(n.LABEL.FOCUSED,s),r.classList.toggle(n.INPUT.FOCUSED,s)):void 0},s.prototype.findLabelAndParent=function(e){var n,s;return n=document.querySelectorAll("label[for='"+e.getAttribute("id")+"']")[0],s=this._findParentNodeByClass(t.SELECT_WRAPPER,e),s||(s=this._findParentNodeByClass(t.STYLIZED,e)),[n,s]},s.prototype._findParentNodeByClass=function(t,e){var n;for(n=e.parentNode;!n.classList.contains(t);)n=n.parentNode;return n.classList.contains(t)?n:void 0},s}(),a=new s}).call(this),function(){}.call(this);
|
@@ -0,0 +1,66 @@
|
|
1
|
+
//
|
2
|
+
// header.scss
|
3
|
+
// Styles for admin header.
|
4
|
+
// --------------------------------------------------
|
5
|
+
|
6
|
+
.header-row {
|
7
|
+
display: block;
|
8
|
+
box-sizing: border-box;
|
9
|
+
position: fixed;
|
10
|
+
top: 0;
|
11
|
+
z-index: 198;
|
12
|
+
left: 230px;
|
13
|
+
right: 0;
|
14
|
+
height: 56px;
|
15
|
+
max-width: 100vw;
|
16
|
+
box-shadow: 0 1px 0 rgba(0,0,0,0.07);
|
17
|
+
background: #ffffff;
|
18
|
+
}
|
19
|
+
|
20
|
+
.header {
|
21
|
+
position: relative;
|
22
|
+
height: 56px;
|
23
|
+
padding: 0 20px;
|
24
|
+
display: -webkit-box;
|
25
|
+
display: -webkit-flex;
|
26
|
+
display: -ms-flexbox;
|
27
|
+
display: flex;
|
28
|
+
-webkit-box-orient: horizontal;
|
29
|
+
-webkit-box-direction: normal;
|
30
|
+
-webkit-flex-direction: row;
|
31
|
+
-ms-flex-direction: row;
|
32
|
+
flex-direction: row;
|
33
|
+
-webkit-box-align: center;
|
34
|
+
-webkit-align-items: center;
|
35
|
+
-ms-flex-align: center;
|
36
|
+
align-items: center;
|
37
|
+
}
|
38
|
+
|
39
|
+
.header__main {
|
40
|
+
-webkit-box-flex: 1;
|
41
|
+
-webkit-flex: 1 0 0%;
|
42
|
+
-ms-flex: 1 0 0%;
|
43
|
+
flex: 1 0 0%;
|
44
|
+
-webkit-box-ordinal-group: 2;
|
45
|
+
-webkit-order: 1;
|
46
|
+
-ms-flex-order: 1;
|
47
|
+
order: 1;
|
48
|
+
font-size: 24px;
|
49
|
+
text-align: left;
|
50
|
+
margin: 0;
|
51
|
+
padding: 10px 0;
|
52
|
+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
53
|
+
font-weight: 300;
|
54
|
+
white-space: nowrap;
|
55
|
+
text-overflow: ellipsis;
|
56
|
+
overflow: hidden;
|
57
|
+
z-index: 2;
|
58
|
+
}
|
59
|
+
|
60
|
+
.breadcrumb {
|
61
|
+
color: #798c9c;
|
62
|
+
}
|
63
|
+
|
64
|
+
.breadcrumb a, .breadcrumb a:hover {
|
65
|
+
color: #798c9c;
|
66
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
//
|
2
|
+
// layout.scss
|
3
|
+
// Styles for admin layout.
|
4
|
+
// --------------------------------------------------
|
5
|
+
|
6
|
+
body {
|
7
|
+
line-height: 18px;
|
8
|
+
font-size: 13px;
|
9
|
+
color: #31373d;
|
10
|
+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
11
|
+
text-rendering: optimizeLegibility;
|
12
|
+
}
|
13
|
+
|
14
|
+
html, body {
|
15
|
+
height: 100%;
|
16
|
+
margin: 0;
|
17
|
+
padding: 0;
|
18
|
+
border: 0;
|
19
|
+
background: #ebeef0;
|
20
|
+
}
|
21
|
+
|
22
|
+
#wrapper {
|
23
|
+
min-height: 100%;
|
24
|
+
background: #ebeef0;
|
25
|
+
}
|
26
|
+
|
27
|
+
#content {
|
28
|
+
padding: 56px 0 0 230px;
|
29
|
+
width: 100%;
|
30
|
+
box-sizing: border-box;
|
31
|
+
outline: 0;
|
32
|
+
display: block;
|
33
|
+
max-width: 3000px;
|
34
|
+
margin: 0 auto;
|
35
|
+
position: relative;
|
36
|
+
-webkit-transition: -webkit-transform 200ms ease;
|
37
|
+
transition: -webkit-transform 200ms ease;
|
38
|
+
transition: transform 200ms ease;
|
39
|
+
transition: transform 200ms ease, -webkit-transform 200ms ease;
|
40
|
+
}
|