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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/disco_app/components/filterable_shop_list.js.jsx +65 -0
  3. data/app/assets/javascripts/disco_app/components/shop_filter_tab.js.jsx +34 -0
  4. data/app/assets/javascripts/disco_app/components/shop_filter_tabs.js.jsx +21 -0
  5. data/app/assets/javascripts/disco_app/components/shop_list.js.jsx +140 -0
  6. data/app/assets/javascripts/disco_app/components/shop_row.js.jsx +27 -0
  7. data/app/controllers/disco_app/admin/app_settings_controller.rb +3 -0
  8. data/app/controllers/disco_app/admin/application_controller.rb +3 -0
  9. data/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +24 -0
  10. data/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +20 -0
  11. data/app/controllers/disco_app/admin/concerns/shops_controller.rb +7 -0
  12. data/app/controllers/disco_app/admin/resources/shops_controller.rb +3 -0
  13. data/app/controllers/disco_app/admin/shops_controller.rb +3 -0
  14. data/app/controllers/disco_app/charges_controller.rb +1 -1
  15. data/app/controllers/disco_app/concerns/app_proxy_controller.rb +40 -0
  16. data/app/controllers/disco_app/concerns/authenticated_controller.rb +42 -0
  17. data/app/controllers/disco_app/concerns/carrier_request_controller.rb +21 -0
  18. data/app/controllers/disco_app/install_controller.rb +1 -1
  19. data/app/models/disco_app/app_settings.rb +3 -0
  20. data/app/models/disco_app/concerns/app_settings.rb +7 -0
  21. data/app/models/disco_app/concerns/shop.rb +5 -0
  22. data/app/models/disco_app/concerns/{synchronises_with_shopify.rb → synchronises.rb} +16 -3
  23. data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +46 -0
  24. data/app/resources/disco_app/admin/resources/shop_resource.rb +4 -0
  25. data/app/views/disco_app/admin/app_settings/edit.html.erb +5 -0
  26. data/app/views/disco_app/admin/shops/index.html.erb +12 -0
  27. data/app/views/layouts/admin/_navbar.html.erb +24 -0
  28. data/app/views/layouts/admin.html.erb +27 -0
  29. data/config/routes.rb +15 -0
  30. data/db/migrate/20150525000000_create_shops_if_not_existent.rb +1 -1
  31. data/db/migrate/20160112233706_create_disco_app_sessions.rb +2 -2
  32. data/db/migrate/20160223111044_create_disco_app_settings.rb +8 -0
  33. data/lib/disco_app/engine.rb +1 -0
  34. data/lib/disco_app/version.rb +1 -1
  35. data/lib/generators/disco_app/adminify/adminify_generator.rb +35 -0
  36. data/lib/generators/disco_app/disco_app_generator.rb +1 -0
  37. data/lib/generators/disco_app/templates/controllers/home_controller.rb +2 -2
  38. data/test/controllers/disco_app/admin/shops_controller_test.rb +54 -0
  39. data/test/dummy/app/controllers/disco_app/admin/shops_controller.rb +8 -0
  40. data/test/dummy/app/controllers/home_controller.rb +2 -2
  41. data/test/dummy/app/controllers/proxy_controller.rb +1 -1
  42. data/test/dummy/app/jobs/products_create_job.rb +7 -0
  43. data/test/dummy/app/jobs/products_delete_job.rb +7 -0
  44. data/test/dummy/app/jobs/products_update_job.rb +7 -0
  45. data/test/dummy/app/models/product.rb +6 -0
  46. data/test/dummy/config/database.codeship.yml +23 -0
  47. data/test/dummy/config/database.yml +8 -13
  48. data/test/dummy/db/migrate/20160307182229_create_products.rb +11 -0
  49. data/test/dummy/db/schema.rb +27 -10
  50. data/test/fixtures/products.yml +4 -0
  51. data/test/fixtures/webhooks/product_created.json +167 -0
  52. data/test/fixtures/webhooks/product_deleted.json +3 -0
  53. data/test/fixtures/webhooks/product_updated.json +167 -0
  54. data/test/integration/synchronises_test.rb +55 -0
  55. metadata +69 -9
  56. data/app/controllers/disco_app/app_proxy_controller.rb +0 -42
  57. data/app/controllers/disco_app/authenticated_controller.rb +0 -44
  58. data/app/controllers/disco_app/carrier_request_controller.rb +0 -23
  59. data/test/integration/navigation_test.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1ef0c316e6efff6bf35a0d4306476e47e6f63b31d53040af71522754a8e2de6
4
- data.tar.gz: ed4c9043346a208cab235d6dc451df93cb0893cd7688b780bccdf0c6c876576b
3
+ metadata.gz: 56c1ba4929abd6bf9f7539edd256f66061ed90ddd974f583b229c5803cc71fdc
4
+ data.tar.gz: 75f3dcee0e4087e98c2ec9933211cdc5d5ce586a5d674f6e1507e7134267f3bc
5
5
  SHA512:
6
- metadata.gz: a73f597562488bcf93a07f6fe713857d21304eb5c109f1fc9e8fbbfae870cb2cf6f869ea82edd7053081aaf2da95de8788e3e62d76d240bae4cfc2bd84dc2369
7
- data.tar.gz: 40b93ede5ac8bb089e402a9f73c48a3e42e44ab71188fcc590f879faef8dd1a0886820b531d4d7499607397ea8edbacc76580d54b92b29d7d08cb5629a548372
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="&lsaquo;" isDisabled={this.props.page < 2} onClick={this.handlePreviousClick} />
125
+ <ShopList.PaginatorButton label="&rsaquo;" 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,3 @@
1
+ class DiscoApp::Admin::AppSettingsController < DiscoApp::Admin::ApplicationController
2
+ include DiscoApp::Admin::Concerns::AppSettingsController
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::ApplicationController < ActionController::Base
2
+ include DiscoApp::Admin::Concerns::AuthenticatedController
3
+ end
@@ -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,7 @@
1
+ module DiscoApp::Admin::Concerns::ShopsController
2
+ extend ActiveSupport::Concern
3
+
4
+ def index
5
+ end
6
+
7
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::Resources::ShopsController < JSONAPI::ResourceController
2
+ include DiscoApp::Admin::Concerns::AuthenticatedController
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::ShopsController < DiscoApp::Admin::ApplicationController
2
+ include DiscoApp::Admin::Concerns::ShopsController
3
+ end
@@ -1,6 +1,6 @@
1
1
  module DiscoApp
2
2
  class ChargesController < ApplicationController
3
- include DiscoApp::AuthenticatedController
3
+ include DiscoApp::Concerns::AuthenticatedController
4
4
 
5
5
  skip_before_action :verify_status, only: [:create, :activate]
6
6
 
@@ -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
@@ -0,0 +1,3 @@
1
+ class DiscoApp::AppSettings < ActiveRecord::Base
2
+ include DiscoApp::Concerns::AppSettings
3
+ end
@@ -0,0 +1,7 @@
1
+ module DiscoApp::Concerns::AppSettings
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ acts_as_singleton
6
+ end
7
+ end
@@ -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::SynchronisesWithShopify
1
+ module DiscoApp::Concerns::Synchronises
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  class_methods do
5
5
 
6
- def synchronise_from_shopify(shop, data)
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 synchronise_deletion_from_shopify(shop, data)
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,4 @@
1
+ class DiscoApp::Admin::Resources::ShopResource < JSONAPI::Resource
2
+ include DiscoApp::Admin::Resources::Concerns::ShopResource
3
+
4
+ end
@@ -0,0 +1,5 @@
1
+ <% provide(:title, 'App Settings') %>
2
+
3
+ <p>Update 'app/views/disco_app/admin/app_settings/edit.html.erb' on your own rails app.</p>
4
+
5
+
@@ -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>