kaui 4.0.9 → 4.0.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68f193d330400858d2b943a2b2e4e356366ebd03fa1099d1279612c40c1f7666
4
- data.tar.gz: 61d17f6e690ca34e6ef8972ba185cf589a0deca6c22159409c040147146d8955
3
+ metadata.gz: 7328709e0c330b84f3e47b4041cf55aa61bf98c7ac80dbb9b2d199b71c3d1f7a
4
+ data.tar.gz: 27b7f69cdb1e950cd327ac7c31141a76ae6c07e7e063d5ccd6d66c020a4fe612
5
5
  SHA512:
6
- metadata.gz: 040f78a63f17a79983d6382eb1547636cdc1274309f72f96ff8fa737a950427940121c7a711c05e2d3a96a752f1cf6bca720c6371625452d371e190aebeecc69
7
- data.tar.gz: 0d54dce07e8dbed25c0e997018d3b9fef5184387003775d6f1b88f6891eec414837e6e0ac6848c2d66b4b3620a5d44b6c03dcf65fbdafa9eed36774a209978c3
6
+ metadata.gz: e7e3008686fb14493dca39a5adcda76d6032c0a8ba23cbf76971dac4829d59ae275a34779d3450aaf7babead74924a66a968cfc07579e74bcd6669763681a632
7
+ data.tar.gz: 9195fcbb008a972b2a8e4b6dbee1b471662e5f30623910ea5b4abe290957f726b8c620c56c82df742ef43587b08f006a7c9d65825833938f01e98a666e3605fe
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M2.6665 5.3332H13.3332M13.3332 5.3332L10.6665 2.6665M13.3332 5.3332L10.6665 7.9999M13.3332 10.6665H2.6665M2.6665 10.6665L5.3332 7.9999M2.6665 10.6665L5.3332 13.3332" stroke="#A4A7AE" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
3
+ </svg>
@@ -687,6 +687,84 @@ form[id^="new_tag_definition"] #object_types div .tag-definition-select {
687
687
  color: #00919d;
688
688
  }
689
689
 
690
+ /* Modern toggle switch */
691
+ .toggle-switch {
692
+ position: relative;
693
+ display: inline-block;
694
+ width: 70px;
695
+ height: 26px;
696
+ }
697
+
698
+ .toggle-switch input {
699
+ opacity: 0;
700
+ width: 0;
701
+ height: 0;
702
+ }
703
+
704
+ .toggle-slider {
705
+ position: absolute;
706
+ cursor: pointer;
707
+ top: 0;
708
+ left: 0;
709
+ right: 0;
710
+ bottom: 0;
711
+ background-color: gray;
712
+ transition: .3s;
713
+ border-radius: 26px;
714
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
715
+ }
716
+
717
+ .toggle-slider:before {
718
+ position: absolute;
719
+ content: "";
720
+ height: 20px;
721
+ width: 20px;
722
+ left: 3px;
723
+ bottom: 3px;
724
+ background-color: white;
725
+ transition: .3s;
726
+ border-radius: 50%;
727
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
728
+ }
729
+
730
+ .toggle-slider:after {
731
+ content: 'OFF';
732
+ color: white;
733
+ display: block;
734
+ position: absolute;
735
+ transform: translate(-50%,-50%);
736
+ top: 50%;
737
+ left: 70%;
738
+ font-size: 10px;
739
+ font-weight: bold;
740
+ }
741
+
742
+ .toggle-switch input:checked + .toggle-slider {
743
+ background-color: #1570ef;
744
+ }
745
+
746
+ .toggle-switch input:checked + .toggle-slider:before {
747
+ transform: translateX(24px);
748
+ }
749
+
750
+ .toggle-switch input:checked + .toggle-slider:after {
751
+ content: 'ON';
752
+ left: 30%;
753
+ }
754
+
755
+ .toggle-switch input:disabled + .toggle-slider {
756
+ cursor: not-allowed;
757
+ opacity: 0.8;
758
+ }
759
+
760
+ .toggle-switch input:not(:checked) + .toggle-slider:hover {
761
+ background-color: #1570ef;
762
+ }
763
+
764
+ .toggle-switch input:checked:not(:disabled) + .toggle-slider:hover {
765
+ background-color: #1570ef;
766
+ }
767
+
690
768
  /* Required for https://github.com/killbill/killbill-admin-ui/issues/262 */
691
769
  .ui-autocomplete {
692
770
  position: absolute;
@@ -15,8 +15,16 @@ module Kaui
15
15
  def new
16
16
  @allowed_user = Kaui::AllowedUser.new
17
17
  @is_killbill_managed = true
18
-
19
18
  @roles = []
19
+
20
+ # Restore form state if returning from role creation
21
+ return unless params[:user_context].present?
22
+
23
+ context = params[:user_context]
24
+ @allowed_user.kb_username = context[:kb_username]
25
+ @allowed_user.description = context[:description]
26
+ @roles = context[:roles].to_s.split(',').reject(&:blank?)
27
+ @external_checked = context[:external] == '1'
20
28
  end
21
29
 
22
30
  def create
@@ -59,7 +67,14 @@ module Kaui
59
67
  @allowed_user = Kaui::AllowedUser.find(params.require(:id))
60
68
  @is_killbill_managed = killbill_managed?(@allowed_user, options_for_klient)
61
69
 
62
- @roles = roles_for_user(@allowed_user)
70
+ # Use roles from context if returning from role creation, otherwise fetch from KB
71
+ if params[:user_context].present?
72
+ context = params[:user_context]
73
+ @roles = context[:roles].to_s.split(',').reject(&:blank?)
74
+ @allowed_user.description = context[:description] if context[:description].present?
75
+ else
76
+ @roles = roles_for_user(@allowed_user)
77
+ end
63
78
  end
64
79
 
65
80
  def update
@@ -76,6 +76,10 @@ module Kaui
76
76
  @tenant = safely_find_tenant_by_id(params[:id])
77
77
  @allowed_users = @tenant.kaui_allowed_users & retrieve_allowed_users_for_current_user
78
78
 
79
+ # Get available users that can be added to this tenant
80
+ actual_allowed_users_ids = (@tenant.kaui_allowed_users || []).map(&:id)
81
+ @available_users = retrieve_allowed_users_for_current_user.reject { |au| actual_allowed_users_ids.include? au.id }
82
+
79
83
  configure_tenant_if_nil(@tenant)
80
84
 
81
85
  # Fetch clock data for the clock component
@@ -28,6 +28,7 @@ module Kaui
28
28
 
29
29
  item = KillBillClient::Model::InvoiceItem.new
30
30
  item.invoice_item_id = ii[0]
31
+ item.description = params.dig(:descriptions, ii[0])
31
32
  # If we tried to do a partial item adjustment, we pass the value, if not we don't send any value and let the system
32
33
  # decide what is the maximum amount we can have on that item
33
34
  item.amount = ii[1].to_f == original_item.amount ? nil : ii[1]
@@ -4,6 +4,17 @@ module Kaui
4
4
  class RoleDefinitionsController < Kaui::EngineController
5
5
  def new
6
6
  @role_definition = Kaui::RoleDefinition.new
7
+
8
+ # Store user form context if coming from user creation/edit
9
+ return unless params[:return_to_user].present?
10
+
11
+ session[:role_return_context] = {
12
+ kb_username: params[:kb_username],
13
+ description: params[:description],
14
+ roles: params[:roles],
15
+ external: params[:external],
16
+ allowed_user_id: params[:allowed_user_id]
17
+ }
7
18
  end
8
19
 
9
20
  def create
@@ -13,7 +24,26 @@ module Kaui
13
24
 
14
25
  begin
15
26
  @role_definition = @role_definition.create(current_user.kb_username, params[:reason], params[:comment], options_for_klient)
16
- redirect_to admin_allowed_users_path, notice: 'Role was successfully created'
27
+
28
+ # Check if we need to return to user form with the new role
29
+ return_context = session.delete(:role_return_context)
30
+ if return_context.present?
31
+ # Add the new role to the existing roles
32
+ existing_roles = return_context[:roles].to_s.split(',').reject(&:blank?)
33
+ existing_roles << @role_definition.role unless existing_roles.include?(@role_definition.role)
34
+ return_context[:roles] = existing_roles.join(',')
35
+
36
+ # Redirect back to user form (new or edit)
37
+ if return_context[:allowed_user_id].present? && return_context[:allowed_user_id] != ''
38
+ redirect_to edit_admin_allowed_user_path(return_context[:allowed_user_id], user_context: return_context),
39
+ notice: "Role '#{@role_definition.role}' was successfully created"
40
+ else
41
+ redirect_to new_admin_allowed_user_path(user_context: return_context),
42
+ notice: "Role '#{@role_definition.role}' was successfully created"
43
+ end
44
+ else
45
+ redirect_to admin_allowed_users_path, notice: 'Role was successfully created'
46
+ end
17
47
  rescue StandardError => e
18
48
  flash.now[:error] = "Error while creating role: #{as_string(e)}"
19
49
  render action: :new
@@ -48,14 +48,14 @@ module Kaui
48
48
 
49
49
  def self.list_transfer_policy_params
50
50
  @policy_params = [
51
- [I18n.translate('start_of_term'), 'START_OF_TERM'],
51
+ # [I18n.translate('start_of_term'), 'START_OF_TERM'], Temporarily removed as it is not supported by Kill Bill
52
52
  [I18n.translate('end_of_term'), 'END_OF_TERM'],
53
53
  [I18n.translate('immediate'), 'IMMEDIATE']
54
54
  ]
55
55
  end
56
56
 
57
57
  def self.list_transfer_policy_params_keys
58
- @policy_params = %w[START_OF_TERM END_OF_TERM IMMEDIATE]
58
+ @policy_params = %w[END_OF_TERM IMMEDIATE]
59
59
  end
60
60
  end
61
61
  end
@@ -1,9 +1,9 @@
1
1
 
2
2
  <div class="filter-bar-container">
3
3
  <div class="filter-bar">
4
- <button class="btn btn-default download-button-right" data-toggle="modal" data-target="#advanceSearchModal">
4
+ <button class="btn btn-default download-button-right" data-toggle="modal" data-target="#advancedSearchModal">
5
5
  <i class="glyphicon glyphicon-search"></i>
6
- <strong>Advance Search</strong>
6
+ <strong>Advanced Search</strong>
7
7
  </button>
8
8
 
9
9
  <div id="search-labels-container" class="ml-2">
@@ -5,7 +5,7 @@
5
5
  <div class="col-sm-9">
6
6
  <div class="checkbox">
7
7
  <label for="external" class="d-flex align-items-center gap-2 control-label">
8
- <%= check_box_tag :external, '1', false, disabled: !@allowed_user.id.blank?, id: 'external', class: "pt-2" %>
8
+ <%= check_box_tag :external, '1', @external_checked || false, disabled: !@allowed_user.id.blank?, id: 'external', class: "pt-2" %>
9
9
  Managed externally (LDAP, Okta, etc.)?
10
10
  </label>
11
11
  </div>
@@ -38,7 +38,7 @@
38
38
  </div>
39
39
  <%= hidden_field_tag :roles, @roles.join(','), id: 'roles-hidden' %>
40
40
 
41
- <%= link_to new_role_definition_path, class: "border-button custom-hover add-role-button" do %>
41
+ <%= link_to '#', class: "border-button custom-hover add-role-button", id: "add-role-link", data: { base_url: new_role_definition_path } do %>
42
42
  <%= image_tag("kaui/modal/plus.svg", width: 16, height: 16) %>
43
43
  <% end %>
44
44
  </div>
@@ -78,6 +78,22 @@
78
78
  <%= javascript_tag do %>
79
79
  $(document).ready(function() {
80
80
 
81
+ // Handle add role button click - pass current form state
82
+ $('#add-role-link').click(function(e) {
83
+ e.preventDefault();
84
+ var baseUrl = $(this).data('base-url');
85
+ var params = {
86
+ return_to_user: '1',
87
+ kb_username: $('#allowed_user_kb_username').val(),
88
+ description: $('#allowed_user_description').val(),
89
+ roles: $('#roles-hidden').val(),
90
+ external: $('#external').is(':checked') ? '1' : '0',
91
+ allowed_user_id: '<%= @allowed_user.id %>'
92
+ };
93
+ var queryString = $.param(params);
94
+ window.location.href = baseUrl + '?' + queryString;
95
+ });
96
+
81
97
  $('#external').change(function() {
82
98
  is_killbill_managed();
83
99
  });
@@ -16,10 +16,10 @@
16
16
  </div>
17
17
  <div class="modal-body">
18
18
  <%= form_for :allowed_user, :url => add_allowed_user_path, :method => :put, :html => {:class => 'form-horizontal', :id => 'tenantAllowedUserForm'} do |f| %>
19
- <div class='form-group d-flex align-items-center p-3'>
20
- <%= f.label :kb_username, 'User Name', :class => 'col-sm-3 control-label' %>
19
+ <div class='form-group row align-items-center p-3'>
20
+ <%= f.label :kb_username, 'User Name', :class => 'col-sm-3 col-form-label text-center' %>
21
21
  <div class="col-sm-9">
22
- <%= f.text_field :kb_username, :class => 'form-control', :required => true %>
22
+ <%= f.select :kb_username, options_for_select(@available_users.map { |u| [u.kb_username, u.kb_username] }), { prompt: 'Select a user' }, { class: 'form-control', required: true } %>
23
23
  </div>
24
24
  </div>
25
25
  <input type="hidden" name="tenant_id" value="<%= @tenant.id %>" />
@@ -54,38 +54,9 @@
54
54
 
55
55
  <%= javascript_tag do %>
56
56
  $(document).ready(function() {
57
- function loadAllowedUser() {
58
- $.ajax(
59
- {
60
- url: Routes.kaui_engine_admin_tenant_allowed_users_path(),
61
- type: "GET",
62
- dataType: "json",
63
- data: {tenant_id: <%= @tenant.id %>},
64
- success: function(data) {
65
- var availableAllowedUser = [];
66
- for (var i=0; i < data.length ; i++) {
67
- availableAllowedUser.push(data[i].kb_username);
68
- }
69
-
70
- $( "#allowed_user_kb_username" ).autocomplete({
71
- source: availableAllowedUser,
72
- appendTo: "#tenantAllowedUserForm",
73
- classes: {
74
- "ui-autocomplete": "autocomplete-allowed-user"
75
- },
76
- });
77
-
78
- }
79
- });
80
- }
81
-
82
57
  $('#addAllowedUserModal').on('show.bs.modal', function (e) {
83
58
  // reset field
84
- $( "#allowed_user_kb_username" ).val('');
85
-
59
+ $('#allowed_user_kb_username').prop('selectedIndex', 0);
86
60
  });
87
-
88
- loadAllowedUser();
89
-
90
61
  });
91
62
  <% end %>
@@ -74,7 +74,6 @@
74
74
  <th id="currency_select"></th>
75
75
  <th><%= I18n.translate('views.catalogs.show.plan_table.trial') %></th>
76
76
  <th><%= I18n.translate('views.catalogs.show.plan_table.final_phase_duration') %></th>
77
- <th></th>
78
77
  </tr>
79
78
  </thead>
80
79
  <tbody id="catalog_detail">
@@ -117,18 +116,6 @@
117
116
  <td>
118
117
  <span class="phase-duration">{{final_phase_duration}}</span>
119
118
  </td>
120
-
121
- <td>
122
- <%= link_to "{{new_plan_currency_path}}", class:"text-decoration-none" do %>
123
- <%= render "kaui/components/button/button", {
124
- label: "Currency",
125
- icon: "kaui/setting/plus.svg",
126
- variant: "outline-secondary d-inline-flex align-items-center",
127
- type: "button",
128
- html_class: "currency-button kaui-button",
129
- } %>
130
- <% end %>
131
- </td>
132
119
  </tr>
133
120
  {{/plans}}
134
121
  {{/catalog}}
@@ -11,11 +11,20 @@
11
11
  <div class="row tenant-details">
12
12
  <div class="col-sm-3">
13
13
  <b>Name:</b>
14
- <p><%= @tenant.name %>&nbsp;
14
+ <p class="d-flex align-items-center gap-2">
15
+ <%= @tenant.name %>
15
16
  <% if session[:kb_tenant_name] == @tenant.name %>
16
- <i class="fa fa-toggle-on switch-tenant-active" title="Currently selected"></i>
17
+ <label class="toggle-switch">
18
+ <input type="checkbox" checked disabled>
19
+ <span class="toggle-slider"></span>
20
+ </label>
17
21
  <% else %>
18
- <%= link_to("<i class=\"fa fa-toggle-off\" aria-hidden=\"true\"></i>".html_safe, switch_tenant_path(:kb_tenant_id => @tenant.kb_tenant_id), title: 'Click the toggle to switch tenant') %>
22
+ <%= link_to switch_tenant_path(:kb_tenant_id => @tenant.kb_tenant_id), method: :get, title: "Click to switch to this tenant", style: "text-decoration: none;" do %>
23
+ <label class="toggle-switch" style="cursor: pointer;">
24
+ <input type="checkbox">
25
+ <span class="toggle-slider"></span>
26
+ </label>
27
+ <% end %>
19
28
  <% end %>
20
29
  </p>
21
30
  </div>
@@ -34,7 +34,7 @@
34
34
  </div>
35
35
  <span>
36
36
  <%= render "kaui/components/button/button", {
37
- label: "Allow Tenant Access",
37
+ label: "Add Allowed User",
38
38
  icon: "kaui/plus.svg",
39
39
  variant: "outline-secondary d-inline-flex align-items-center gap-1",
40
40
  type: "button",
@@ -1,38 +1,56 @@
1
- <div class="register">
2
- <div class="col-md-8 col-md-offset-2">
3
-
4
- <div class="column-block">
5
- <h1> <%= I18n.translate('transfer') %></h1>
1
+ <div class="register kaui-subscription-new">
2
+ <div class="">
3
+ <div class="mx-auto" style="max-width: 37.5rem;">
4
+ <h5 class="add-account-title border-bottom pb-3 mb-3">
5
+ <span class="icon-container">
6
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M3.33325 6.6665H16.6666M16.6666 6.6665L13.3333 3.33317M16.6666 6.6665L13.3333 9.99984M16.6666 13.3332H3.33325M3.33325 13.3332L6.66659 9.99984M3.33325 13.3332L6.66659 16.6665" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
8
+ </svg>
9
+ </span>
10
+ <%= I18n.translate('transfer') %>
11
+ </h5>
6
12
 
7
13
  <%= form_tag do_transfer_bundle_path(@bundle_id), :class => 'form-horizontal', :method => :put do %>
8
- <div class="form-group">
9
- <%= label_tag :new_account_key, I18n.translate('new_account'), :class => 'col-sm-2 control-label' %>
10
- <div class="col-sm-10">
11
- <%= text_field_tag :new_account_key, nil, :class => 'form-control', :placeholder => I18n.translate('new_account_id_or_external_key') %>
12
- </div>
14
+ <div class="form-group d-flex pb-3">
15
+ <%= label_tag :new_account_key, I18n.translate('new_account'), :class => 'col-sm-3 control-label' %>
16
+ <div class="col-sm-9">
17
+ <%= text_field_tag :new_account_key, nil, :class => 'form-control', :placeholder => I18n.translate('new_account_id_or_external_key') %>
13
18
  </div>
19
+ </div>
14
20
 
15
- <div class="form-group">
16
- <%= label_tag :billing_policy, I18n.translate('billing_policy'), :class => 'col-sm-2 control-label' %>
17
- <div class="col-sm-10">
18
- <%= select_tag :billing_policy, options_for_select(Kaui::Bundle.list_transfer_policy_params, selected: "IMMEDIATE") %>
19
- </div>
21
+ <div class="form-group d-flex pb-3">
22
+ <%= label_tag :billing_policy, I18n.translate('billing_policy'), :class => 'col-sm-3 control-label' %>
23
+ <div class="col-sm-9">
24
+ <%= select_tag :billing_policy, options_for_select(Kaui::Bundle.list_transfer_policy_params, selected: "END_OF_TERM"), :class => 'form-control' %>
20
25
  </div>
26
+ </div>
21
27
 
22
- <div class="form-group">
23
- <%= label_tag :comment, I18n.translate('comment'), :class => 'col-sm-2 control-label' %>
24
- <div class="col-sm-10">
25
- <%= text_area_tag :comment, nil, :rows => 3, :class => 'form-control' %>
26
- </div>
27
- </div>
28
- <div class="form-group">
29
- <div class="col-sm-offset-2 col-sm-10">
30
- <%= submit_tag I18n.translate('change'), :class => 'btn btn-default' %>
31
- </div>
28
+ <div class="form-group d-flex pb-3 border-bottom mb-3">
29
+ <%= label_tag :comment, I18n.translate('comment'), :class => 'col-sm-3 control-label' %>
30
+ <div class="col-sm-9">
31
+ <%= text_area_tag :comment, nil, :rows => 3, :class => 'form-control' %>
32
32
  </div>
33
- <% end %>
33
+ </div>
34
34
 
35
+ <div class="form-group d-flex justify-content-end pb-3">
36
+ <%= render "kaui/components/button/button", {
37
+ label: 'Close',
38
+ variant: "outline-secondary d-inline-flex align-items-center gap-1",
39
+ type: "button",
40
+ html_class: "kaui-button custom-hover mx-2",
41
+ html_options: {
42
+ id: "closeButton",
43
+ onclick: "window.history.back();"
44
+ }
45
+ } %>
46
+ <%= render "kaui/components/button/button", {
47
+ label: I18n.translate('transfer'),
48
+ variant: "outline-secondary d-inline-flex align-items-center gap-1",
49
+ type: "submit",
50
+ html_class: "kaui-dropdown custom-hover"
51
+ } %>
52
+ </div>
53
+ <% end %>
35
54
  </div>
36
-
37
55
  </div>
38
56
  </div>
@@ -33,15 +33,15 @@
33
33
  <div class="mb-1 fw-semibold text-muted mb-2">Suggested search queries</div>
34
34
  <div class="mb-3">
35
35
  <div class="inline-block me-2 my-4">
36
- <span class="me-2 header-tag custom-hover text-black-900 fw-500 align-items-center gap-2 border-secondary px-2 py-2 border-2 rounded-4 ">account:</span>
36
+ <span class="me-2 header-tag custom-hover text-black-900 fw-500 align-items-center gap-2 border-secondary px-2 py-2 border-2 rounded-4 search-suggestion-term" data-term="account:" style="cursor: pointer;">account:</span>
37
37
  <span>account: Flavio Ruggiero</span>
38
38
  </div>
39
39
  <div class="inline-block me-2 my-4">
40
- <span class="me-2 header-tag custom-hover text-black-900 fw-500 align-items-center gap-2 border-secondary px-2 py-2 border-2 rounded-4 ">invoice:</span>
40
+ <span class="me-2 header-tag custom-hover text-black-900 fw-500 align-items-center gap-2 border-secondary px-2 py-2 border-2 rounded-4 search-suggestion-term" data-term="invoice:" style="cursor: pointer;">invoice:</span>
41
41
  <span>invoice: af58a92c-bdc4-...</span>
42
42
  </div>
43
43
  <div class="inline-block me-2 my-4">
44
- <span class="me-2 header-tag custom-hover text-black-900 fw-500 align-items-center gap-2 border-secondary px-2 py-2 border-2 rounded-4 ">payment:</span>
44
+ <span class="me-2 header-tag custom-hover text-black-900 fw-500 align-items-center gap-2 border-secondary px-2 py-2 border-2 rounded-4 search-suggestion-term" data-term="payment:" style="cursor: pointer;">payment:</span>
45
45
  <span>payment: 3c4c0263-b06f-...</span>
46
46
  </div>
47
47
  </div>
@@ -49,7 +49,7 @@
49
49
  <div class="fw-semibold text-muted mb-2">Other search queries</div>
50
50
  <div class="d-flex flex-wrap gap-2">
51
51
  <% %w[bundle: custom\ field: subscription: tag:].each do |term| %>
52
- <span class="badge text-dark border border-secondary rounded-3 px-3 py-2 custom-hover"><%= term %></span>
52
+ <span class="badge text-dark border border-secondary rounded-3 px-3 py-2 custom-hover search-suggestion-term" data-term="<%= term %>" style="cursor: pointer;"><%= term %></span>
53
53
  <% end %>
54
54
  </div>
55
55
  </div>
@@ -64,12 +64,27 @@
64
64
  dropdown.classList.remove("d-none");
65
65
  });
66
66
 
67
+ input.addEventListener("focus", () => {
68
+ dropdown.classList.remove("d-none");
69
+ });
70
+
67
71
  document.addEventListener("click", (e) => {
68
72
  if (!dropdown.contains(e.target) && e.target !== input) {
69
73
  dropdown.classList.add("d-none");
70
74
  }
71
75
  });
72
76
 
77
+ // Handle clicks on search suggestion terms
78
+ document.querySelectorAll(".search-suggestion-term").forEach((element) => {
79
+ element.addEventListener("click", (e) => {
80
+ e.preventDefault();
81
+ const term = element.getAttribute("data-term");
82
+ input.value = term;
83
+ input.focus();
84
+ // Keep dropdown open so user can continue typing
85
+ });
86
+ });
87
+
73
88
  // Optional: shortcut CMD+K
74
89
  document.addEventListener("keydown", (e) => {
75
90
  if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
@@ -47,7 +47,7 @@
47
47
  <div class="col-sm-9 checkbox-label">
48
48
  <label for="<%= "cb_adj_#{ii.invoice_item_id}" %>" class="d-flex align-items-center gap-2 mb-1">
49
49
  <input type="checkbox" id="<%= "cb_adj_#{ii.invoice_item_id}" %>">
50
- <%= Kaui.refund_invoice_description.call(index, ii, bundle_result) %>
50
+ <%= text_field_tag "descriptions[#{ii.invoice_item_id}]", Kaui.refund_invoice_description.call(index, ii, bundle_result), id: "desc_#{ii.invoice_item_id}", class: 'form-control' %>
51
51
  </label>
52
52
  <%= text_field_tag "adjustments[#{ii.invoice_item_id}]", index, id: "tf_adj_#{ii.invoice_item_id}", value: ii.amount, class: 'form-control checkbox-label' %>
53
53
  </div>
@@ -131,7 +131,7 @@
131
131
  </div>
132
132
  <% end %>
133
133
 
134
- <% if can?(:create, Kaui::Subscription) || can?(:pause_resume, Kaui::Subscription) || can?(:change_plan, Kaui::Subscription) %>
134
+ <% if can?(:create, Kaui::Subscription) || can?(:pause_resume, Kaui::Subscription) || can?(:change_plan, Kaui::Subscription) || can?(:transfer, Kaui::Bundle) %>
135
135
  <% menu_items = [] %>
136
136
 
137
137
  <% if can?(:change_plan, Kaui::Subscription) %>
@@ -173,6 +173,14 @@
173
173
  } %>
174
174
  <% end %>
175
175
 
176
+ <% if can?(:transfer, Kaui::Bundle) && sub.product_category != 'ADD_ON' %>
177
+ <% menu_items << {
178
+ label: I18n.translate('transfer'),
179
+ path: kaui_engine.transfer_bundle_path(bundle.bundle_id),
180
+ icon: "kaui/subscription/transfer.svg"
181
+ } %>
182
+ <% end %>
183
+
176
184
  <%= render partial: "kaui/components/menu_dropdown/menu_dropdown", locals: {
177
185
  variant: "btn-light",
178
186
  label: "",
@@ -18,9 +18,9 @@ en:
18
18
  custom_field_uuid_exist_in_invoice_payment_db: "UUID do exist in INVOICE PAYMENT object database."
19
19
  custom_field_uuid_do_not_exist_in_db: "UUID do not exist in object database."
20
20
  custom_field_uuid_exist_in_invoice_item_db: "UUID do exist in INVOICE ITEMS object database."
21
- immediate: "Immediate"
22
- start_of_term: "Start of term"
23
- end_of_term: "End of term"
21
+ immediate: "IMMEDIATE"
22
+ start_of_term: "START_OF_TERM"
23
+ end_of_term: "END_OF_TERM"
24
24
  billing_policy: "Billing Policy"
25
25
  new_account_id_or_external_key: "New account id or external key"
26
26
  object_invalid_dont_exist: "Object type INVALID or object id do not exist."
@@ -7,31 +7,41 @@ module Kaui
7
7
 
8
8
  included do
9
9
  rescue_from(StandardError) do |error|
10
- flash[:error] = "Error: #{as_string(error)}"
10
+ error_message = "Error: #{as_string(error)}"
11
11
  try_to_redirect_to_account_path = !params[:controller].ends_with?('accounts')
12
- perform_redirect_after_error(redirect: try_to_redirect_to_account_path)
12
+ perform_redirect_after_error(redirect: try_to_redirect_to_account_path, error:, error_message:)
13
13
  end
14
14
 
15
15
  rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
16
16
  log_rescue_error parameter_missing_exception
17
- flash[:error] = "Required parameter missing: #{parameter_missing_exception.param}"
18
- perform_redirect_after_error
17
+ error_message = "Required parameter missing: #{parameter_missing_exception.param}"
18
+ perform_redirect_after_error(error: parameter_missing_exception, error_message:)
19
19
  end
20
20
 
21
21
  rescue_from(KillBillClient::API::ResponseError) do |killbill_exception|
22
- flash[:error] = "Error while communicating with the Kill Bill server: #{as_string(killbill_exception)}"
22
+ error_message = "Error while communicating with the Kill Bill server: #{as_string(killbill_exception)}"
23
23
  try_to_redirect_to_account_path = !killbill_exception.is_a?(KillBillClient::API::Unauthorized) && !(killbill_exception.is_a?(KillBillClient::API::NotFound) && params[:controller].ends_with?('accounts'))
24
- perform_redirect_after_error(redirect: try_to_redirect_to_account_path)
24
+ perform_redirect_after_error(redirect: try_to_redirect_to_account_path, error: killbill_exception, error_message:)
25
25
  end
26
26
  end
27
27
 
28
- def perform_redirect_after_error(redirect: true)
28
+ def perform_redirect_after_error(error:, error_message:, redirect: true)
29
29
  account_id = nested_hash_value(params.permit!.to_h, :account_id)
30
- if redirect && account_id.present?
31
- redirect_to kaui_engine.account_path(account_id)
32
- else
33
- redirect_to kaui_engine.home_path
34
- end
30
+ home_path = kaui_engine.home_path
31
+ redirect_path = if redirect && account_id.present?
32
+ kaui_engine.account_path(account_id)
33
+ else
34
+ home_path
35
+ end
36
+
37
+ redirect_path_without_query = redirect_path.to_s.split('?').first
38
+ already_on_redirect_target = request.path == redirect_path_without_query
39
+ already_on_home = params[:controller].to_s.ends_with?('home') && action_name == 'index'
40
+
41
+ raise error if already_on_redirect_target || (redirect_path == home_path && already_on_home)
42
+
43
+ flash[:error] = error_message
44
+ redirect_to redirect_path
35
45
  end
36
46
  end
37
47
  end
data/lib/kaui/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kaui
4
- VERSION = '4.0.9'
4
+ VERSION = '4.0.11'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kaui
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.9
4
+ version: 4.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kill Bill core team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-06 00:00:00.000000000 Z
11
+ date: 2026-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -373,6 +373,7 @@ files:
373
373
  - app/assets/images/kaui/subscription/change.svg
374
374
  - app/assets/images/kaui/subscription/date.svg
375
375
  - app/assets/images/kaui/subscription/pause.svg
376
+ - app/assets/images/kaui/subscription/transfer.svg
376
377
  - app/assets/images/kaui/timeline/authorize.svg
377
378
  - app/assets/images/kaui/timeline/capture.svg
378
379
  - app/assets/images/kaui/timeline/chargeback.svg