beyond_canvas 0.16.1.pre → 0.19.1.pre

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/beyond_canvas_manifest.js +1 -0
  3. data/app/assets/images/icons/arrow_right.svg +1 -0
  4. data/app/assets/images/icons/close.svg +1 -0
  5. data/app/assets/images/icons/home.svg +1 -0
  6. data/app/assets/javascripts/beyond_canvas/base.js +120 -246
  7. data/app/assets/stylesheets/beyond_canvas/base.scss +10 -1
  8. data/app/assets/stylesheets/beyond_canvas/components/_action_bar.scss +28 -0
  9. data/app/assets/stylesheets/beyond_canvas/components/_breadcrumbs.scss +29 -0
  10. data/app/assets/stylesheets/beyond_canvas/components/_buttons.scss +1 -1
  11. data/app/assets/stylesheets/beyond_canvas/components/_debug.scss +10 -0
  12. data/app/assets/stylesheets/beyond_canvas/components/_forms.scss +11 -14
  13. data/app/assets/stylesheets/beyond_canvas/components/_grids.scss +12 -0
  14. data/app/assets/stylesheets/beyond_canvas/components/_inputs.scss +5 -0
  15. data/app/assets/stylesheets/beyond_canvas/components/_main.scss +12 -13
  16. data/app/assets/stylesheets/beyond_canvas/components/_margins.scss +12 -0
  17. data/app/assets/stylesheets/beyond_canvas/components/_menu.scss +50 -0
  18. data/app/assets/stylesheets/beyond_canvas/components/_modals.scss +35 -0
  19. data/app/assets/stylesheets/beyond_canvas/components/_scrollbox.scss +35 -0
  20. data/app/assets/stylesheets/beyond_canvas/components/_sidebar.scss +53 -0
  21. data/app/assets/stylesheets/beyond_canvas/components/_titles.scss +7 -0
  22. data/app/assets/stylesheets/beyond_canvas/settings/_variables.scss +64 -9
  23. data/app/controllers/beyond_canvas/application_controller.rb +2 -0
  24. data/app/controllers/beyond_canvas/authentications_controller.rb +37 -30
  25. data/app/controllers/concerns/beyond_canvas/authentication.rb +1 -12
  26. data/app/controllers/concerns/beyond_canvas/locale_management.rb +5 -4
  27. data/app/form_builders/beyond_canvas/form_builder.rb +8 -6
  28. data/app/helpers/beyond_canvas/authentications_helper.rb +28 -0
  29. data/app/helpers/beyond_canvas/cockpit_app_helper.rb +17 -0
  30. data/app/helpers/beyond_canvas/debug_helper.rb +9 -0
  31. data/app/helpers/beyond_canvas/locale_switch_helper.rb +5 -1
  32. data/app/javascript/beyond_canvas/base.js +3 -0
  33. data/app/javascript/beyond_canvas/initializers/buttons.js +65 -19
  34. data/app/javascript/beyond_canvas/initializers/flash.js +9 -2
  35. data/app/javascript/beyond_canvas/initializers/inputs.js +4 -1
  36. data/app/javascript/beyond_canvas/initializers/modals.js +14 -0
  37. data/app/views/beyond_canvas/authentications/new.html.erb +19 -10
  38. data/app/views/beyond_canvas/shared/_action_bar.html.erb +15 -0
  39. data/app/views/beyond_canvas/shared/_breadcrumbs.html.erb +14 -0
  40. data/app/views/beyond_canvas/shared/_flash.html.erb +22 -12
  41. data/app/views/beyond_canvas/shared/_locales.html.erb +8 -0
  42. data/app/views/beyond_canvas/shared/_menu.html.erb +31 -0
  43. data/app/views/beyond_canvas/shared/_modal.html.erb +6 -0
  44. data/app/views/beyond_canvas/shared/_sidebar.html.erb +16 -0
  45. data/app/views/layouts/beyond_canvas/application.html.erb +31 -0
  46. data/app/views/layouts/beyond_canvas/public.html.erb +10 -1
  47. data/config/locales/en.yml +5 -0
  48. data/config/routes.rb +5 -4
  49. data/lib/beyond_canvas.rb +12 -12
  50. data/lib/beyond_canvas/configuration.rb +11 -6
  51. data/lib/beyond_canvas/engine.rb +4 -2
  52. data/lib/beyond_canvas/menu_item_registration.rb +19 -0
  53. data/lib/beyond_canvas/parameter_sanitizer.rb +1 -1
  54. data/lib/beyond_canvas/rails/routes.rb +8 -7
  55. data/lib/beyond_canvas/version.rb +1 -1
  56. data/lib/generators/beyond_canvas/controller/controller_generator.rb +1 -2
  57. data/lib/generators/beyond_canvas/controller/templates/controller.erb +2 -19
  58. data/lib/generators/beyond_canvas/custom_menu/custom_menu_generator.rb +13 -0
  59. data/lib/generators/beyond_canvas/custom_menu/templates/beyond_canvas_custom_menu.html.erb +32 -0
  60. data/lib/generators/beyond_canvas/custom_styles/custom_styles_generator.rb +1 -1
  61. data/lib/generators/beyond_canvas/custom_styles/templates/beyond_canvas_custom_styles.scss +55 -9
  62. data/lib/generators/beyond_canvas/install/install_generator.rb +3 -5
  63. data/lib/generators/beyond_canvas/install/templates/beyond_canvas.rb.erb +51 -13
  64. data/lib/generators/beyond_canvas/{auth_model/auth_model_generator.rb → model/model_generator.rb} +4 -5
  65. data/lib/generators/beyond_canvas/{auth_model → model}/templates/migration.erb +2 -4
  66. data/lib/generators/beyond_canvas/{auth_model → model}/templates/model.erb +0 -0
  67. data/lib/generators/beyond_canvas/views/views_generator.rb +4 -6
  68. data/lib/models/concerns/authentication.rb +57 -0
  69. data/lib/models/concerns/utils.rb +79 -0
  70. data/lib/models/shop.rb +12 -0
  71. metadata +68 -27
  72. data/app/controllers/concerns/beyond_canvas/resource_management.rb +0 -33
  73. data/app/javascript/beyond_canvas/initializers/functions.js +0 -41
  74. data/app/views/beyond_canvas/locales/_edit.html.erb +0 -8
  75. data/lib/beyond_canvas/models/authentication.rb +0 -66
  76. data/lib/beyond_canvas/models/shop.rb +0 -28
  77. data/lib/beyond_canvas/models/utils.rb +0 -55
@@ -5,5 +5,7 @@ module BeyondCanvas
5
5
  protect_from_forgery with: :exception
6
6
 
7
7
  include ::BeyondCanvas::StatusCodes
8
+ include ::BeyondCanvas::AuthenticationsHelper
9
+ include ::BeyondCanvas::DebugHelper
8
10
  end
9
11
  end
@@ -7,56 +7,63 @@ module BeyondCanvas
7
7
  layout 'beyond_canvas/public'
8
8
 
9
9
  include ::BeyondCanvas::Authentication
10
- include ::BeyondCanvas::ResourceManagement
11
10
 
12
11
  before_action :validate_app_installation_request!, only: :new
13
12
 
14
13
  def new
15
- self.resource = resource_class.new
14
+ @shop = Shop.find_or_initialize_by(beyond_api_url: params[:api_url])
15
+
16
+ if @shop&.authenticated?
17
+ open_app(@shop)
18
+ elsif BeyondCanvas.configuration.preinstalled
19
+ preinstall
20
+ end
16
21
  end
17
22
 
18
- def create
19
- # Search for the api url. If there is no record it creates a new record.
20
- resource_params = new_resource_params
21
- self.resource = resource_class.find_or_create_by(beyond_api_url: resource_params[:api_url])
22
- # Assign the attributes to the record
23
- raise ActiveRecord::RecordNotSaved unless resource.update(resource_params)
24
- # Get and save access_token and refresh_token using the authentication code
25
- raise BeyondApi::Error if resource.authenticate.is_a?(BeyondApi::Error)
23
+ def install
24
+ @shop = Shop.create_with(shop_params).create_or_find_by(beyond_api_url: params[:shop][:api_url])
26
25
 
27
- redirect_to after_create_path
28
- rescue ActiveRecord::RecordNotSaved, BeyondApi::Error, StandardError => e
29
- logger.error "[BeyondCanvas] #{e.message}".red
30
- send "handle_#{e.class.name.split('::').first.underscore}_exception", e
31
- end
26
+ @shop.assign_attributes(shop_params)
32
27
 
33
- def update
34
- create
28
+ if @shop.save
29
+ @shop.authenticate(params[:shop][:code])
30
+
31
+ redirect_to after_installation_path
32
+ else
33
+ render :new
34
+ end
35
35
  end
36
36
 
37
37
  private
38
38
 
39
- def new_resource_params
40
- send "new_#{resource_name}_params"
39
+ def shop_params
40
+ beyond_canvas_parameter_sanitizer.sanitize
41
41
  end
42
42
 
43
- def after_create_path
44
- new_resource_params[:return_url]
43
+ def after_preinstallation_path
44
+ params[:return_url]
45
45
  end
46
46
 
47
- def handle_active_record_exception(_exception)
48
- flash[:error] = t('beyond_canvas.authentications.failure')
49
- render :new
47
+ def after_installation_path
48
+ params[:shop][:return_url]
50
49
  end
51
50
 
52
- def handle_beyond_api_exception(_exception)
53
- flash[:error] = t('beyond_canvas.authentications.failure')
54
- render :new
51
+ def after_sign_in_path
52
+ BeyondCanvas.configuration.open_app_url
55
53
  end
56
54
 
57
- def handle_standard_error_exception(_exception)
58
- flash[:error] = t('beyond_canvas.authentications.failure')
59
- render :new
55
+ def preinstall
56
+ @shop = Shop.create_or_find_by(beyond_api_url: params[:api_url])
57
+ @shop.authenticate(params[:code])
58
+
59
+ redirect_to after_preinstallation_path
60
+ end
61
+
62
+ def open_app(shop)
63
+ reset_session
64
+ log_in shop
65
+
66
+ redirect_to after_sign_in_path
60
67
  end
61
68
  end
62
69
  end
@@ -3,22 +3,11 @@
3
3
  module BeyondCanvas
4
4
  module Authentication # :nodoc:
5
5
  extend ActiveSupport::Concern
6
- AUTH_RESOURCE = BeyondCanvas.auth_model
7
-
8
- class_eval <<-METHODS, __FILE__, __LINE__ + 1
9
- def current_#{AUTH_RESOURCE}
10
- instance_variable_get("@#{AUTH_RESOURCE}")
11
- end
12
-
13
- def new_#{AUTH_RESOURCE}_params
14
- beyond_canvas_parameter_sanitizer.sanitize
15
- end
16
- METHODS
17
6
 
18
7
  private
19
8
 
20
9
  def beyond_canvas_parameter_sanitizer
21
- @beyond_canvas_parameter_sanitizer ||= BeyondCanvas::ParameterSanitizer.new(AUTH_RESOURCE, params)
10
+ @beyond_canvas_parameter_sanitizer ||= BeyondCanvas::ParameterSanitizer.new(:shop, params)
22
11
  end
23
12
  end
24
13
  end
@@ -15,16 +15,13 @@ module BeyondCanvas
15
15
  # browser compatible locale, sets the value to the cookie and set that locale as default locale.
16
16
  #
17
17
  def switch_locale(&action)
18
- # NOTE: Check the HTTP_ACCEPT_LANGUAGE header to identify if the request comes from a browser or a server
19
- return I18n.with_locale(I18n.default_locale, &action) if request.headers['HTTP_ACCEPT_LANGUAGE'].blank?
20
-
21
18
  unless valid_locale?(cookies[:locale])
22
19
  cookies[:locale] = { value: browser_compatible_locale, expires: 1.day.from_now }
23
20
  end
24
21
 
25
22
  I18n.with_locale(cookies[:locale], &action)
26
23
 
27
- logger.debug "[BeyondCanvas] Locale set to: #{cookies[:locale]}".yellow
24
+ logger.debug "[BeyondCanvas] Locale set to: #{cookies[:locale]}".yellow if debug_mode?
28
25
  end
29
26
 
30
27
  #
@@ -36,12 +33,16 @@ module BeyondCanvas
36
33
  # +I18n.default_locale+. (e.g. +'en-GB'+)
37
34
  #
38
35
  def browser_compatible_locale
36
+ return I18n.default_locale if request.headers['HTTP_ACCEPT_LANGUAGE'].blank?
37
+
39
38
  browser_locales = HTTP::Accept::Languages.parse(request.headers['HTTP_ACCEPT_LANGUAGE'])
40
39
  available_locales = HTTP::Accept::Languages::Locales.new(I18n.available_locales.map(&:to_s))
41
40
 
42
41
  locales = available_locales & browser_locales
43
42
 
44
43
  locales.empty? ? I18n.default_locale : locales.first
44
+ rescue
45
+ I18n.default_locale
45
46
  end
46
47
 
47
48
  #
@@ -2,10 +2,6 @@
2
2
 
3
3
  module BeyondCanvas
4
4
  class FormBuilder < ActionView::Helpers::FormBuilder # :nodoc:
5
- ############################################################################
6
- # Wrappers
7
- ############################################################################
8
-
9
5
  def field_wrapper(attribute, args, &block)
10
6
  label = args[:label] == false ? nil : args[:label].presence || attribute.to_s.humanize
11
7
 
@@ -38,14 +34,20 @@ module BeyondCanvas
38
34
  end
39
35
  end
40
36
 
41
- %i[email text number password].each do |method|
42
- define_method :"#{method}_field" do |attribute, args = {}|
37
+ %i[email_field text_field number_field password_field text_area].each do |method|
38
+ define_method method do |attribute, args = {}|
43
39
  field_wrapper(attribute, args) do
44
40
  super(attribute, args)
45
41
  end
46
42
  end
47
43
  end
48
44
 
45
+ def select(attribute, choices, options = {}, args = {})
46
+ field_wrapper(attribute, args) do
47
+ super(attribute, choices, options, args)
48
+ end
49
+ end
50
+
49
51
  def check_box(attribute, args = {})
50
52
  inline_wrapper(attribute, args) do
51
53
  filed_identifyer = filed_identifyer(attribute)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module AuthenticationsHelper # :nodoc:
5
+ #
6
+ # Logs in the given shop
7
+ #
8
+ def log_in(shop)
9
+ session[:shop_id] = shop.id
10
+ end
11
+
12
+ #
13
+ # Returns the current logged-in shop (if any)
14
+ #
15
+ def current_shop
16
+ if session[:shop_id]
17
+ @current_shop ||= Shop.find_by(id: session[:shop_id])
18
+ end
19
+ end
20
+
21
+ #
22
+ # Returns true if the shop is logged in, false otherwise
23
+ #
24
+ def logged_in?
25
+ !current_shop.nil?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module CockpitAppHelper # :nodoc:
5
+ def is_cockpit_app?
6
+ BeyondCanvas.configuration.cockpit_app == true
7
+ end
8
+
9
+ def action_bar_content?
10
+ content_for?(:action_bar_left) || content_for?(:action_bar_right)
11
+ end
12
+
13
+ def menu_content?
14
+ BeyondCanvas.configuration.menu_items.any?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module DebugHelper # :nodoc:
5
+ def debug_mode?
6
+ Rails.env.development? && BeyondCanvas.configuration.debug_mode
7
+ end
8
+ end
9
+ end
@@ -2,11 +2,15 @@
2
2
 
3
3
  module BeyondCanvas
4
4
  module LocaleSwitchHelper # :nodoc:
5
+ def show_locale_switch?
6
+ defined?(I18n) && I18n.available_locales.count > 1
7
+ end
8
+
5
9
  def translate_locale(locale)
6
10
  if I18n.exists?("locales.#{locale}")
7
11
  I18n.t("locales.#{locale}")
8
12
  else
9
- logger.debug "[BeyondCanvas] Missing translation: #{I18n.locale}.locales.#{locale}".yellow
13
+ logger.debug "[BeyondCanvas] Missing translation: #{I18n.locale}.locales.#{locale}".yellow if debug_mode?
10
14
  locale
11
15
  end
12
16
  end
@@ -1,3 +1,6 @@
1
+ import 'jquery';
2
+
1
3
  import './initializers/buttons';
2
4
  import './initializers/flash';
3
5
  import './initializers/inputs';
6
+ import './initializers/modals';
@@ -1,8 +1,11 @@
1
- import { disableActionElements, enableActionElements, hideSpinner, showSpinner } from './functions';
1
+ const SPINNER_ANIMATION_TIMEOUT = 125;
2
+ const BUTTON_SELECTORS = '[class^="button"]';
2
3
 
3
4
  (function ($) {
4
- const onDOMReady = function () {
5
- const inputs = $('input, textarea, select').not(':input[type=button], :input[type=submit], :input[type=reset]');
5
+ const onDOMReady = function (e) {
6
+ const inputs = $('input, textarea, select').not(
7
+ ':input[type=button], :input[type=submit], :input[type=reset]'
8
+ );
6
9
 
7
10
  inputs.each(function () {
8
11
  var input = $(this);
@@ -11,12 +14,11 @@ import { disableActionElements, enableActionElements, hideSpinner, showSpinner }
11
14
  if ($(input).is(':hidden')) {
12
15
  e.preventDefault();
13
16
  }
14
- hideSpinner();
15
- enableActionElements();
17
+ $.restoreActionElements();
16
18
  });
17
19
  });
18
20
 
19
- $('button[class^="button"]').each(function () {
21
+ $(BUTTON_SELECTORS).each(function () {
20
22
  var button = $(this);
21
23
 
22
24
  // Add width attribute and save old width
@@ -24,31 +26,75 @@ import { disableActionElements, enableActionElements, hideSpinner, showSpinner }
24
26
  button.data('oldWidth', button.width());
25
27
 
26
28
  // Add the spinner
27
- button.prepend(`
28
- <div class="spinner">
29
- <div class="bounce1"></div>
30
- <div class="bounce2"></div>
31
- <div class="bounce3"></div>
32
- </div>`);
29
+ if (button.find('.spinner').length == 0) {
30
+ button.prepend(`
31
+ <div class="spinner">
32
+ <div class="bounce1"></div>
33
+ <div class="bounce2"></div>
34
+ <div class="bounce3"></div>
35
+ </div>`);
36
+ }
33
37
 
34
38
  // Bind ajax:success and ajax:error to the form the button belongs to
35
39
  button
36
40
  .closest('form')
37
41
  .on('ajax:success', function () {
38
- hideSpinner();
39
- enableActionElements();
42
+ $.restoreActionElements();
40
43
  })
41
44
  .on('ajax:error', function () {
42
- hideSpinner();
43
- enableActionElements();
45
+ $.restoreActionElements();
44
46
  });
45
47
  });
46
48
  };
47
49
 
48
- $(document).on('click', '[class^="button"]', function () {
49
- disableActionElements();
50
- showSpinner($(this));
50
+ $(document).on('confirm:complete', function () {
51
+ $.restoreActionElements();
52
+ });
53
+
54
+ $(document).on('click', BUTTON_SELECTORS, function () {
55
+ $.disableActionElements();
56
+ $(this).showSpinner();
51
57
  });
52
58
 
53
59
  $(document).on('ready page:load turbolinks:load', onDOMReady);
54
60
  })(jQuery);
61
+
62
+ $.extend({
63
+ restoreActionElements: function () {
64
+ // Hide spinners
65
+ $(BUTTON_SELECTORS).each(function (_, button) {
66
+ setTimeout(function () {
67
+ // Hide the spinner
68
+ $(button).find('.spinner').hide();
69
+ // Adjust the width of the button
70
+ $(button).width($(button).data('oldWidth'));
71
+ }, SPINNER_ANIMATION_TIMEOUT);
72
+ });
73
+
74
+ // Enable action elements
75
+ $('a, input[type="submit"], input[type="button"], input[type="reset"], button').each(function () {
76
+ $(this).removeClass('actions--disabled');
77
+ });
78
+ },
79
+ disableActionElements: function () {
80
+ $('a, input[type="submit"], input[type="button"], input[type="reset"], button').each(function () {
81
+ $(this).addClass('actions--disabled');
82
+ });
83
+ }
84
+ });
85
+
86
+ $.fn.extend({
87
+ showSpinner: function () {
88
+ var button = $(this);
89
+
90
+ // Adjust the width of the button
91
+ button.width(
92
+ button.width() + $('.spinner').outerWidth(true)
93
+ );
94
+
95
+ // Show the spinner
96
+ setTimeout(function () {
97
+ button.find('.spinner').css('display', 'inline-flex');
98
+ }, SPINNER_ANIMATION_TIMEOUT);
99
+ }
100
+ });
@@ -1,5 +1,3 @@
1
- import { closeAlert } from './functions';
2
-
3
1
  (function ($) {
4
2
  const onDOMReady = function () {
5
3
  $('.flash').each(function () {
@@ -17,3 +15,12 @@ import { closeAlert } from './functions';
17
15
 
18
16
  $(document).on('ready page:load turbolinks:load', onDOMReady);
19
17
  })(jQuery);
18
+
19
+ function closeAlert() {
20
+ $('.flash')
21
+ .removeClass('flash--shown')
22
+ .delay(700)
23
+ .queue(function () {
24
+ $(this).remove();
25
+ });
26
+ }
@@ -9,7 +9,10 @@
9
9
  var fileName = '';
10
10
 
11
11
  if (this.files && this.files.length > 1)
12
- fileName = (this.getAttribute('data-multiple-caption') || '').replace('{count}', this.files.length);
12
+ fileName = (this.getAttribute('data-multiple-caption') || '').replace(
13
+ '{count}',
14
+ this.files.length
15
+ );
13
16
  else if (e.target.value) fileName = e.target.value.split('\\').pop();
14
17
 
15
18
  if (fileName)
@@ -0,0 +1,14 @@
1
+ $.extend({
2
+ displayModal: function (content, options = {}) {
3
+ $('#modal').find('#modal__content').html(content);
4
+ $('#modal').css('display', 'flex');
5
+ $.restoreActionElements();
6
+ $(document).trigger('modal:opened', options['extraEventParameters']);
7
+ },
8
+ closeModal: function () {
9
+ $('#modal').find('#modal__content').empty();
10
+ $('#modal').css('display', 'none');
11
+ $.restoreActionElements();
12
+ $(document).trigger('modal:closed');
13
+ }
14
+ });
@@ -1,17 +1,26 @@
1
- <div class='card card--padding'>
1
+ <% app_name = BeyondCanvas.configuration.site_title %>
2
+ <% i18n_scope = 'beyond_canvas.authentications.new' %>
2
3
 
3
- <%= form_for(resource, as: resource_name) do |f| %>
4
+ <div class="card card--padding">
4
5
 
5
- <h2 class='card__headline'>Install <%= BeyondCanvas.configuration.site_title %> in your shop</h2>
6
+ <%= form_with(url: callback_path, method: :post, scope: :shop, model: @shop, local: true, builder: BeyondCanvas::FormBuilder) do |f| %>
6
7
 
7
- <%= f.hidden_field :code, value: params[:code] || resource.code %>
8
- <%= f.hidden_field :signature, value: params[:signature] || resource.signature %>
9
- <%= f.hidden_field :return_url, value: params[:return_url] || resource.return_url %>
10
- <%= f.hidden_field :api_url, value: params[:api_url] || resource.api_url %>
11
- <%= f.hidden_field :access_token_url, value: params[:access_token_url] || resource.access_token_url %>
8
+ <h2 class="card__headline"><%= I18n.t('headline', app_name: app_name, scope: i18n_scope) %></h2>
12
9
 
13
- <div class='form__actions--spaced'>
14
- <%= f.button 'Save', type: :submit, class: 'button__solid--primary' %>
10
+ <p class="margin--bottom"><%= I18n.t('body', app_name: app_name, scope: i18n_scope) %></p>
11
+
12
+ <%# Don't delete or modify any ot these hidden fields. They are used on shop identification and authentication %>
13
+
14
+ <%= f.hidden_field :code, value: params[:code] || @shop.code %>
15
+ <%= f.hidden_field :signature, value: params[:signature] || @shop.signature %>
16
+ <%= f.hidden_field :return_url, value: params[:return_url] || @shop.return_url %>
17
+ <%= f.hidden_field :api_url, value: params[:api_url] || @shop.api_url %>
18
+ <%= f.hidden_field :access_token_url, value: params[:access_token_url] || @shop.access_token_url %>
19
+
20
+ <%# Add HERE your custom shop fields %>
21
+
22
+ <div class="form__actions--spaced">
23
+ <%= f.button I18n.t('actions.install', scope: i18n_scope), type: :submit, class: 'button__solid--primary' %>
15
24
  </div>
16
25
 
17
26
  <% end %>