disco_app 0.8.5 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/disco_app/components/filterable_shop_list.js.jsx +65 -0
- data/app/assets/javascripts/disco_app/components/shop_filter_tab.js.jsx +34 -0
- data/app/assets/javascripts/disco_app/components/shop_filter_tabs.js.jsx +21 -0
- data/app/assets/javascripts/disco_app/components/shop_list.js.jsx +140 -0
- data/app/assets/javascripts/disco_app/components/shop_row.js.jsx +27 -0
- data/app/controllers/disco_app/admin/app_settings_controller.rb +3 -0
- data/app/controllers/disco_app/admin/application_controller.rb +3 -0
- data/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +24 -0
- data/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +20 -0
- data/app/controllers/disco_app/admin/concerns/shops_controller.rb +7 -0
- data/app/controllers/disco_app/admin/resources/shops_controller.rb +3 -0
- data/app/controllers/disco_app/admin/shops_controller.rb +3 -0
- data/app/controllers/disco_app/charges_controller.rb +1 -1
- data/app/controllers/disco_app/concerns/app_proxy_controller.rb +40 -0
- data/app/controllers/disco_app/concerns/authenticated_controller.rb +42 -0
- data/app/controllers/disco_app/concerns/carrier_request_controller.rb +21 -0
- data/app/controllers/disco_app/install_controller.rb +1 -1
- data/app/models/disco_app/app_settings.rb +3 -0
- data/app/models/disco_app/concerns/app_settings.rb +7 -0
- data/app/models/disco_app/concerns/shop.rb +5 -0
- data/app/models/disco_app/concerns/{synchronises_with_shopify.rb → synchronises.rb} +16 -3
- data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +46 -0
- data/app/resources/disco_app/admin/resources/shop_resource.rb +4 -0
- data/app/views/disco_app/admin/app_settings/edit.html.erb +5 -0
- data/app/views/disco_app/admin/shops/index.html.erb +12 -0
- data/app/views/layouts/admin/_navbar.html.erb +24 -0
- data/app/views/layouts/admin.html.erb +27 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20150525000000_create_shops_if_not_existent.rb +1 -1
- data/db/migrate/20160112233706_create_disco_app_sessions.rb +2 -2
- data/db/migrate/20160223111044_create_disco_app_settings.rb +8 -0
- data/lib/disco_app/engine.rb +1 -0
- data/lib/disco_app/version.rb +1 -1
- data/lib/generators/disco_app/adminify/adminify_generator.rb +35 -0
- data/lib/generators/disco_app/disco_app_generator.rb +1 -0
- data/lib/generators/disco_app/templates/controllers/home_controller.rb +2 -2
- data/test/controllers/disco_app/admin/shops_controller_test.rb +54 -0
- data/test/dummy/app/controllers/disco_app/admin/shops_controller.rb +8 -0
- data/test/dummy/app/controllers/home_controller.rb +2 -2
- data/test/dummy/app/controllers/proxy_controller.rb +1 -1
- data/test/dummy/app/jobs/products_create_job.rb +7 -0
- data/test/dummy/app/jobs/products_delete_job.rb +7 -0
- data/test/dummy/app/jobs/products_update_job.rb +7 -0
- data/test/dummy/app/models/product.rb +6 -0
- data/test/dummy/config/database.codeship.yml +23 -0
- data/test/dummy/config/database.yml +8 -13
- data/test/dummy/db/migrate/20160307182229_create_products.rb +11 -0
- data/test/dummy/db/schema.rb +27 -10
- data/test/fixtures/products.yml +4 -0
- data/test/fixtures/webhooks/product_created.json +167 -0
- data/test/fixtures/webhooks/product_deleted.json +3 -0
- data/test/fixtures/webhooks/product_updated.json +167 -0
- data/test/integration/synchronises_test.rb +55 -0
- metadata +69 -9
- data/app/controllers/disco_app/app_proxy_controller.rb +0 -42
- data/app/controllers/disco_app/authenticated_controller.rb +0 -44
- data/app/controllers/disco_app/carrier_request_controller.rb +0 -23
- data/test/integration/navigation_test.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56c1ba4929abd6bf9f7539edd256f66061ed90ddd974f583b229c5803cc71fdc
|
4
|
+
data.tar.gz: 75f3dcee0e4087e98c2ec9933211cdc5d5ce586a5d674f6e1507e7134267f3bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae82c3a87f6b50d7c6cf878bf2eb34b047fd2d180e2609e4b15226a85dd742a973c0454363f8d4eb64ba4ea65af23b6adce122f1e4160290c9cab524a38fe45f
|
7
|
+
data.tar.gz: 6d184f92b1aed8bc41c8733b0d67869b7e5f70c82be4462657efb731c49c138bbce16c3f83322f82acfc0723f3d0d58d30c3ee5309ed6f69df19c01515bb9e28
|
@@ -0,0 +1,65 @@
|
|
1
|
+
var FilterableShopList = React.createClass({
|
2
|
+
|
3
|
+
getDefaultProps: function() {
|
4
|
+
return {
|
5
|
+
filterTabs: [
|
6
|
+
{ label: 'All Shops', filter: {} },
|
7
|
+
{ label: 'Never Installed', filter: { 'filter[status]': 'never_installed' } },
|
8
|
+
{ label: 'Awaiting Install', filter: { 'filter[status]': 'awaiting_install' } },
|
9
|
+
{ label: 'Installing', filter: { 'filter[status]': 'installing' } },
|
10
|
+
{ label: 'Installed', filter: { 'filter[status]': 'installed' } },
|
11
|
+
{ label: 'Awaiting Uninstall', filter: { 'filter[status]': 'awaiting_uninstall' } },
|
12
|
+
{ label: 'Uninstalling', filter: { 'filter[status]': 'uninstalling' } },
|
13
|
+
{ label: 'Uninstalled', filter: { 'filter[status]': 'uninstalled' } }
|
14
|
+
],
|
15
|
+
availableFilters: {
|
16
|
+
'filter[status]': {
|
17
|
+
getLabel: function(value) {
|
18
|
+
return 'Status is ' + value;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
},
|
24
|
+
|
25
|
+
getInitialState: function() {
|
26
|
+
return {
|
27
|
+
filter: {}
|
28
|
+
};
|
29
|
+
},
|
30
|
+
|
31
|
+
onFilterReplace: function(filter) {
|
32
|
+
this.setState({
|
33
|
+
filter: filter
|
34
|
+
});
|
35
|
+
},
|
36
|
+
|
37
|
+
onFilterSet: function(name, value) {
|
38
|
+
this.onFiltersSet([{
|
39
|
+
name: name,
|
40
|
+
value: value
|
41
|
+
}]);
|
42
|
+
},
|
43
|
+
|
44
|
+
onFiltersSet: function(filters) {
|
45
|
+
var nextFilter = $.extend({}, this.state.filter);
|
46
|
+
filters.forEach(function(filter) {
|
47
|
+
if(!filter.value) {
|
48
|
+
delete nextFilter[filter.name];
|
49
|
+
} else {
|
50
|
+
nextFilter[filter.name] = filter.value;
|
51
|
+
}
|
52
|
+
});
|
53
|
+
this.onFilterReplace(nextFilter);
|
54
|
+
},
|
55
|
+
|
56
|
+
render: function() {
|
57
|
+
return (
|
58
|
+
<div className="next-card">
|
59
|
+
<ShopFilterTabs filterTabs={this.props.filterTabs} filter={this.state.filter} onFilterReplace={this.onFilterReplace} />
|
60
|
+
<ShopList shopsUrl={this.props.shopsUrl} editShopUrl={this.props.editShopUrl} filter={this.state.filter} />
|
61
|
+
</div>
|
62
|
+
);
|
63
|
+
}
|
64
|
+
|
65
|
+
});
|
@@ -0,0 +1,34 @@
|
|
1
|
+
var ShopFilterTab = React.createClass({
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Handle a click event on the individual tab link.
|
5
|
+
* @param e
|
6
|
+
*/
|
7
|
+
handleClick: function(e) {
|
8
|
+
e.preventDefault();
|
9
|
+
|
10
|
+
// Don't do anything if this is already the currently selected tab.
|
11
|
+
if(this.isActive()) return;
|
12
|
+
|
13
|
+
this.props.onFilterTabSelected(this.props.filterTab);
|
14
|
+
},
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Return true if this is the currently active tab, determined by whether the filter values for this tab match the
|
18
|
+
* currently active filter.
|
19
|
+
* @returns {boolean}
|
20
|
+
*/
|
21
|
+
isActive: function() {
|
22
|
+
return (JSON.stringify(this.props.filterTab.filter) == JSON.stringify(this.props.filter));
|
23
|
+
},
|
24
|
+
|
25
|
+
render: function() {
|
26
|
+
var tabClassName = this.isActive() ? 'next-tab next-tab--is-active' : 'next-tab';
|
27
|
+
return (
|
28
|
+
<li role="presentation">
|
29
|
+
<a href="#" className={tabClassName} onClick={this.handleClick}>{this.props.filterTab.label}</a>
|
30
|
+
</li>
|
31
|
+
)
|
32
|
+
}
|
33
|
+
|
34
|
+
});
|
@@ -0,0 +1,21 @@
|
|
1
|
+
var ShopFilterTabs = React.createClass({
|
2
|
+
|
3
|
+
onFilterTabSelected: function(filterTab) {
|
4
|
+
this.props.onFilterReplace(filterTab.filter);
|
5
|
+
},
|
6
|
+
|
7
|
+
render: function() {
|
8
|
+
var filterTabNodes = this.props.filterTabs.map(function(filterTab) {
|
9
|
+
return (
|
10
|
+
<ShopFilterTab key={filterTab.label} filter={this.props.filter} filterTab={filterTab} onFilterTabSelected={this.onFilterTabSelected} />
|
11
|
+
)
|
12
|
+
}, this);
|
13
|
+
|
14
|
+
return (
|
15
|
+
<ul className="next-tab__list">
|
16
|
+
{filterTabNodes}
|
17
|
+
</ul>
|
18
|
+
)
|
19
|
+
}
|
20
|
+
|
21
|
+
});
|
@@ -0,0 +1,140 @@
|
|
1
|
+
var ShopList = React.createClass({
|
2
|
+
|
3
|
+
getDefaultProps: function() {
|
4
|
+
return {
|
5
|
+
pageSize: 50
|
6
|
+
}
|
7
|
+
},
|
8
|
+
|
9
|
+
getInitialState: function() {
|
10
|
+
return {
|
11
|
+
shops: [],
|
12
|
+
page: 1
|
13
|
+
}
|
14
|
+
},
|
15
|
+
|
16
|
+
componentDidMount: function() {
|
17
|
+
this.getShops();
|
18
|
+
$(document).on('shops.changed', this.onShopsChanged);
|
19
|
+
},
|
20
|
+
|
21
|
+
componentWillUnmount: function() {
|
22
|
+
$(document).off('shops.changed', this.onShopsChanged);
|
23
|
+
},
|
24
|
+
|
25
|
+
componentDidUpdate: function (prevProps, prevState) {
|
26
|
+
if(prevProps.filter != this.props.filter) {
|
27
|
+
this.getShops();
|
28
|
+
}
|
29
|
+
if(prevState.page != this.state.page) {
|
30
|
+
this.getShops();
|
31
|
+
}
|
32
|
+
},
|
33
|
+
|
34
|
+
onPageChanged: function(increment) {
|
35
|
+
this.setState({page: this.state.page + increment});
|
36
|
+
},
|
37
|
+
|
38
|
+
onShopsChanged: function() {
|
39
|
+
this.setState({page: 1});
|
40
|
+
this.getShops();
|
41
|
+
},
|
42
|
+
|
43
|
+
getShops: function() {
|
44
|
+
//ShopifyApp.Bar.loadingOn();
|
45
|
+
|
46
|
+
// Set up request data with filter and pagination parameters.
|
47
|
+
var data = $.extend({
|
48
|
+
'page[size]': this.props.pageSize,
|
49
|
+
'page[number]': this.state.page
|
50
|
+
}, this.props.filter);
|
51
|
+
|
52
|
+
// Make the request to fetch the order list.
|
53
|
+
$.getJSON(this.props.shopsUrl, data, function(result) {
|
54
|
+
if(this.isMounted()) {
|
55
|
+
this.setState({
|
56
|
+
shops: result.data
|
57
|
+
});
|
58
|
+
}
|
59
|
+
}.bind(this))
|
60
|
+
.always(function() {
|
61
|
+
//ShopifyApp.Bar.loadingOff();
|
62
|
+
});
|
63
|
+
},
|
64
|
+
|
65
|
+
render: function() {
|
66
|
+
var shopRows = this.state.shops.map(function(shop) {
|
67
|
+
return (
|
68
|
+
<ShopRow shop={shop} editShopUrl={this.props.editShopUrl} key={shop.id} />
|
69
|
+
)
|
70
|
+
}.bind(this));
|
71
|
+
|
72
|
+
return (
|
73
|
+
<div>
|
74
|
+
<div className="next-card__section">
|
75
|
+
<table className="table-hover expanded">
|
76
|
+
<thead>
|
77
|
+
<tr>
|
78
|
+
<th>ID</th>
|
79
|
+
<th>Shopify Domain</th>
|
80
|
+
<th>Status</th>
|
81
|
+
<th>Email</th>
|
82
|
+
<th>Country Name</th>
|
83
|
+
<th>Currency</th>
|
84
|
+
<th>Domain</th>
|
85
|
+
<th>Plan</th>
|
86
|
+
<th>Created</th>
|
87
|
+
<th>Installed Duration</th>
|
88
|
+
</tr>
|
89
|
+
</thead>
|
90
|
+
<tbody>
|
91
|
+
{shopRows}
|
92
|
+
</tbody>
|
93
|
+
</table>
|
94
|
+
</div>
|
95
|
+
<div className="next-card__section">
|
96
|
+
<div className="next-grid next-grid--no-padding next-grid--center-aligned">
|
97
|
+
<div className="next-grid__cell next-grid__cell--no-flex">
|
98
|
+
<ShopList.Paginator page={this.state.page} pageSize={this.props.pageSize} items={this.state.shops} onPageChanged={this.onPageChanged} />
|
99
|
+
</div>
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
)
|
104
|
+
}
|
105
|
+
|
106
|
+
});
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Paginator.
|
110
|
+
*/
|
111
|
+
ShopList.Paginator = React.createClass({
|
112
|
+
|
113
|
+
handlePreviousClick: function(e) {
|
114
|
+
this.props.onPageChanged(-1);
|
115
|
+
},
|
116
|
+
|
117
|
+
handleNextClick: function(e) {
|
118
|
+
this.props.onPageChanged(1);
|
119
|
+
},
|
120
|
+
|
121
|
+
render: function() {
|
122
|
+
return (
|
123
|
+
<div className="btn-group" role="group">
|
124
|
+
<ShopList.PaginatorButton label="‹" isDisabled={this.props.page < 2} onClick={this.handlePreviousClick} />
|
125
|
+
<ShopList.PaginatorButton label="›" isDisabled={this.props.items.length < this.props.pageSize} onClick={this.handleNextClick} />
|
126
|
+
</div>
|
127
|
+
)
|
128
|
+
}
|
129
|
+
|
130
|
+
});
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Individual button within a paginator.
|
134
|
+
*/
|
135
|
+
ShopList.PaginatorButton = (props) => {
|
136
|
+
var className = 'btn btn-default' + (props.isDisabled ? ' disabled' : '');
|
137
|
+
return (
|
138
|
+
<button className={className} disabled={props.isDisabled} onClick={props.onClick}>{props.label}</button>
|
139
|
+
)
|
140
|
+
};
|
@@ -0,0 +1,27 @@
|
|
1
|
+
var ShopRow = (props) => {
|
2
|
+
|
3
|
+
var shop = props.shop,
|
4
|
+
editShopUrl = props.editShopUrl.replace(':id', shop.id),
|
5
|
+
shopifyDomain = shop.attributes['shopify_domain'],
|
6
|
+
countryName = shop.attributes['country_name'],
|
7
|
+
currency = shop.attributes['currency'],
|
8
|
+
domainName = shop.attributes['domain'],
|
9
|
+
planName = shop.attributes['plan_display_name'],
|
10
|
+
createdDate = shop.attributes['created_at'],
|
11
|
+
installedDuration = shop.attributes['installed_duration']
|
12
|
+
|
13
|
+
return (
|
14
|
+
<tr>
|
15
|
+
<td><a href={editShopUrl}>#{shop.id}</a></td>
|
16
|
+
<td>{shopifyDomain}</td>
|
17
|
+
<td>{shop.attributes.status}</td>
|
18
|
+
<td>{shop.attributes.email}</td>
|
19
|
+
<td>{countryName}</td>
|
20
|
+
<td>{shop.attributes.currency}</td>
|
21
|
+
<td>{shop.attributes.domain}</td>
|
22
|
+
<td>{planName}</td>
|
23
|
+
<td>{createdDate}</td>
|
24
|
+
<td>{installedDuration}</td>
|
25
|
+
</tr>
|
26
|
+
)
|
27
|
+
};
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DiscoApp::Admin::Concerns::AppSettingsController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def edit
|
5
|
+
@app_settings = DiscoApp::AppSettings.instance
|
6
|
+
end
|
7
|
+
|
8
|
+
def update
|
9
|
+
@app_settings = DiscoApp::AppSettings.instance
|
10
|
+
if @app_settings.update_attributes(app_settings_params)
|
11
|
+
flash[:success] = 'Settings updated.'
|
12
|
+
redirect_to edit_admin_app_settings_path
|
13
|
+
else
|
14
|
+
render 'edit'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def app_settings_params
|
21
|
+
params.require(:app_settings)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DiscoApp::Admin::Concerns::AuthenticatedController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
|
6
|
+
protect_from_forgery with: :exception
|
7
|
+
before_action :authenticate_administrator
|
8
|
+
layout 'admin'
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def authenticate_administrator
|
15
|
+
authenticate_or_request_with_http_basic do |username, password|
|
16
|
+
(not username.blank?) && (not password.blank?) && username == ENV['ADMIN_APP_USERNAME'] && password == ENV['ADMIN_APP_PASSWORD']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module DiscoApp::Concerns::AppProxyController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action :verify_proxy_signature
|
6
|
+
before_action :shopify_shop
|
7
|
+
after_action :add_liquid_header
|
8
|
+
|
9
|
+
rescue_from ActiveRecord::RecordNotFound do |exception|
|
10
|
+
render_error 404
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def verify_proxy_signature
|
17
|
+
unless proxy_signature_is_valid?
|
18
|
+
head :unauthorized
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def proxy_signature_is_valid?
|
23
|
+
return true if DiscoApp.configuration.skip_proxy_verification?
|
24
|
+
DiscoApp::ProxyService.proxy_signature_is_valid?(request.query_string, ShopifyApp.configuration.secret)
|
25
|
+
end
|
26
|
+
|
27
|
+
def shopify_shop
|
28
|
+
@shop = DiscoApp::Shop.find_by_shopify_domain!(params[:shop])
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_liquid_header
|
32
|
+
response.headers['Content-Type'] = 'application/liquid'
|
33
|
+
end
|
34
|
+
|
35
|
+
def render_error(status)
|
36
|
+
add_liquid_header
|
37
|
+
render "disco_app/proxy_errors/#{status}", status: status
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module DiscoApp::Concerns::AuthenticatedController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action :login_again_if_different_shop
|
6
|
+
before_action :shopify_shop
|
7
|
+
before_action :verify_status
|
8
|
+
around_filter :shopify_session
|
9
|
+
layout 'embedded_app'
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def shopify_shop
|
15
|
+
if shop_session
|
16
|
+
@shop = DiscoApp::Shop.find_by!(shopify_domain: @shop_session.url)
|
17
|
+
else
|
18
|
+
redirect_to_login
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def verify_status
|
23
|
+
if not (@shop.charge_active? or @shop.charge_waived?)
|
24
|
+
redirect_if_not_current_path(disco_app.new_charge_path)
|
25
|
+
elsif @shop.charge_accepted?
|
26
|
+
redirect_if_not_current_path(disco_app.activate_charge_path)
|
27
|
+
elsif @shop.never_installed? or @shop.uninstalled?
|
28
|
+
redirect_if_not_current_path(disco_app.install_path)
|
29
|
+
elsif @shop.awaiting_install? or @shop.installing?
|
30
|
+
redirect_if_not_current_path(disco_app.installing_path)
|
31
|
+
elsif @shop.awaiting_uninstall? or @shop.uninstalling?
|
32
|
+
redirect_if_not_current_path(disco_app.uninstalling_path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def redirect_if_not_current_path(target)
|
37
|
+
if request.path != target
|
38
|
+
redirect_to target
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DiscoApp::Concerns::CarrierRequestController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action :verify_carrier_request
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def verify_carrier_request
|
11
|
+
unless carrier_request_signature_is_valid?
|
12
|
+
head :unauthorized
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def carrier_request_signature_is_valid?
|
17
|
+
return true if DiscoApp.configuration.skip_carrier_request_verification?
|
18
|
+
DiscoApp::CarrierRequestService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'])
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module DiscoApp
|
2
2
|
class InstallController < ApplicationController
|
3
|
-
include DiscoApp::AuthenticatedController
|
3
|
+
include DiscoApp::Concerns::AuthenticatedController
|
4
4
|
|
5
5
|
# Start the installation process for the current shop, then redirect to the installing screen.
|
6
6
|
def install
|
@@ -3,6 +3,7 @@ module DiscoApp::Concerns::Shop
|
|
3
3
|
|
4
4
|
included do
|
5
5
|
include ShopifyApp::Shop
|
6
|
+
include ActionView::Helpers::DateHelper
|
6
7
|
|
7
8
|
# Define relationships to plans and subscriptions.
|
8
9
|
has_many :subscriptions
|
@@ -65,6 +66,10 @@ module DiscoApp::Concerns::Shop
|
|
65
66
|
"https://#{shopify_domain}/admin"
|
66
67
|
end
|
67
68
|
|
69
|
+
def installed_duration
|
70
|
+
distance_of_time_in_words_to_now(created_at.time)
|
71
|
+
end
|
72
|
+
|
68
73
|
end
|
69
74
|
|
70
75
|
end
|
@@ -1,11 +1,17 @@
|
|
1
|
-
module DiscoApp::Concerns::
|
1
|
+
module DiscoApp::Concerns::Synchronises
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
class_methods do
|
5
5
|
|
6
|
-
def
|
6
|
+
def should_synchronise?(shop, data)
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def synchronise(shop, data)
|
7
11
|
data = data.with_indifferent_access
|
8
12
|
|
13
|
+
return unless should_synchronise?(shop, data)
|
14
|
+
|
9
15
|
instance = self.find_or_create_by!(id: data[:id]) do |instance|
|
10
16
|
instance.shop = shop
|
11
17
|
instance.data = data
|
@@ -16,8 +22,15 @@ module DiscoApp::Concerns::SynchronisesWithShopify
|
|
16
22
|
instance
|
17
23
|
end
|
18
24
|
|
19
|
-
def
|
25
|
+
def should_synchronise_deletion?(shop, data)
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def synchronise_deletion(shop, data)
|
20
30
|
data = data.with_indifferent_access
|
31
|
+
|
32
|
+
return unless should_synchronise_deletion?(shop, data)
|
33
|
+
|
21
34
|
self.destroy_all(shop: shop, id: data[:id])
|
22
35
|
end
|
23
36
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'jsonapi/resource'
|
2
|
+
|
3
|
+
module DiscoApp::Admin::Resources::Concerns::ShopResource
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
|
8
|
+
attributes :shopify_domain, :status, :email, :country_name
|
9
|
+
attributes :currency, :domain, :plan_display_name, :created_at
|
10
|
+
attributes :installed_duration
|
11
|
+
|
12
|
+
model_name 'DiscoApp::Shop'
|
13
|
+
|
14
|
+
filters :status
|
15
|
+
|
16
|
+
# Adjust the base records method to ensure only models for the authenticated domain are retrieved.
|
17
|
+
def self.records(options = {})
|
18
|
+
records = DiscoApp::Shop.order(created_at: :desc)
|
19
|
+
records
|
20
|
+
end
|
21
|
+
|
22
|
+
# Apply filters.
|
23
|
+
def self.apply_filter(records, filter, value, options)
|
24
|
+
return records if value.blank?
|
25
|
+
|
26
|
+
# Perform appropriate filtering.
|
27
|
+
case filter
|
28
|
+
when :status
|
29
|
+
return records.where(status: value.map { |v| DiscoApp::Shop.statuses[v.to_sym] } )
|
30
|
+
else
|
31
|
+
return super(records, filter, value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Don't allow the update of any fields via the API.
|
36
|
+
def self.updatable_fields(context)
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Don't allow the creation of any fields via the API.
|
41
|
+
def self.creatable_fields(context)
|
42
|
+
[]
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% provide(:title, 'Shops') %>
|
2
|
+
|
3
|
+
<div class="next-grid">
|
4
|
+
<div class="next-grid__cell">
|
5
|
+
|
6
|
+
<%= react_component('FilterableShopList', {
|
7
|
+
shopsUrl: admin_resources_shops_path(format: :json),
|
8
|
+
editShopUrl: edit_admin_shop_path(':id')
|
9
|
+
}) %>
|
10
|
+
|
11
|
+
</div>
|
12
|
+
</div>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<nav class="navbar navbar-default navbar-static-top">
|
2
|
+
<div class="container-fluid">
|
3
|
+
|
4
|
+
<!-- Brand & Toggle -->
|
5
|
+
<div class="navbar-header">
|
6
|
+
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
7
|
+
<span class="sr-only">Toggle navigation</span>
|
8
|
+
<span class="icon-bar"></span>
|
9
|
+
<span class="icon-bar"></span>
|
10
|
+
<span class="icon-bar"></span>
|
11
|
+
</button>
|
12
|
+
<a class="navbar-brand" href="<%= admin_shops_path %>">Credible</a>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<!-- Collapsing Navigation Links -->
|
16
|
+
<div class="collapse navbar-collapse">
|
17
|
+
<ul class="nav navbar-nav">
|
18
|
+
<%= active_link_to 'Shops', admin_shops_path, wrap_tag: :li %>
|
19
|
+
<%= active_link_to 'Settings', edit_admin_app_settings_path, wrap_tag: :li %>
|
20
|
+
</ul>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
</div>
|
24
|
+
</nav>
|