spree_cm_commissioner 1.16.0 → 1.17.0.pre.pre1
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/Gemfile.lock +8 -8
- data/app/assets/images/cm-hangmeas-checkout_image.svg +63 -0
- data/app/assets/images/cm-hangmeas-failed.svg +56 -0
- data/app/assets/images/cm-hangmeas-loader.svg +50 -0
- data/app/assets/images/cm-hangmeas-success.svg +51 -0
- data/app/controllers/spree/admin/tenants_controller.rb +8 -0
- data/app/controllers/spree/api/v2/storefront/dynamic_field_options_controller.rb +28 -0
- data/app/controllers/spree/api/v2/storefront/guests_controller.rb +2 -1
- data/app/interactors/spree_cm_commissioner/create_event.rb +2 -1
- data/app/mailers/spree_cm_commissioner/team_invite_mailer.rb +13 -0
- data/app/models/concerns/spree_cm_commissioner/participation_type_bitwise.rb +47 -0
- data/app/models/concerns/spree_cm_commissioner/tenant_preference.rb +4 -0
- data/app/models/spree_cm_commissioner/dynamic_field.rb +20 -0
- data/app/models/spree_cm_commissioner/dynamic_field_option.rb +18 -0
- data/app/models/spree_cm_commissioner/guest.rb +5 -3
- data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +25 -0
- data/app/models/spree_cm_commissioner/invite_team.rb +44 -0
- data/app/models/spree_cm_commissioner/product_decorator.rb +2 -0
- data/app/models/spree_cm_commissioner/product_dynamic_field.rb +8 -0
- data/app/models/spree_cm_commissioner/taxon_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/user_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/user_taxon.rb +1 -0
- data/app/models/spree_cm_commissioner/vendor_decorator.rb +1 -0
- data/app/models/spree_cm_commissioner/vendor_place.rb +5 -7
- data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +1 -0
- data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +2 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_option_serializer.rb +11 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/dynamic_field_serializer.rb +13 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/guest_dynamic_field_serializer.rb +13 -0
- data/app/serializers/spree_cm_commissioner/v2/storefront/guest_serializer.rb +3 -2
- data/app/serializers/spree_cm_commissioner/v2/storefront/product_dynamic_field_serializer.rb +13 -0
- data/app/services/spree_cm_commissioner/organizer/export_guest_csv_service.rb +23 -15
- data/app/services/spree_cm_commissioner/user_roles_assigner.rb +0 -6
- data/app/views/spree/admin/tenants/_form.html.erb +253 -0
- data/app/views/spree_cm_commissioner/layouts/team_invite_mailer.html.erb +15 -0
- data/app/views/spree_cm_commissioner/team_invite_mailer/_mailer_stylesheets.html.erb +79 -0
- data/app/views/spree_cm_commissioner/team_invite_mailer/send_team_invite_email.html.erb +42 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20250512072817_create_spree_cm_commissioner_invite_team.rb +15 -0
- data/db/migrate/20250605024523_add_participation_type_to_spree_taxon.rb +5 -0
- data/db/migrate/20250606020914_create_cm_dynamic_fields.rb +11 -0
- data/db/migrate/20250606020958_create_cm_guest_dynamic_fields.rb +11 -0
- data/db/migrate/20250606021044_create_cm_dynamic_field_options.rb +10 -0
- data/db/migrate/20250606021045_create_cm_product_dynamic_fields.rb +10 -0
- data/lib/spree_cm_commissioner/test_helper/factories/vendor_place_factory.rb +2 -2
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +29 -4
@@ -1,8 +1,6 @@
|
|
1
|
-
require_dependency 'spree_cm_commissioner'
|
2
|
-
|
3
1
|
module SpreeCmCommissioner
|
4
|
-
class VendorPlace <
|
5
|
-
enum place_type: {
|
2
|
+
class VendorPlace < Base
|
3
|
+
enum place_type: { location: 0, branch: 1, stop: 2 }
|
6
4
|
acts_as_list scope: :vendor
|
7
5
|
|
8
6
|
belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
|
@@ -13,9 +11,9 @@ module SpreeCmCommissioner
|
|
13
11
|
|
14
12
|
accepts_nested_attributes_for :place, allow_destroy: true
|
15
13
|
|
16
|
-
scope :branches,
|
17
|
-
scope :stops,
|
18
|
-
scope :locations, -> { where(place_type: :
|
14
|
+
scope :branches, -> { where(place_type: :branch) }
|
15
|
+
scope :stops, -> { where(place_type: :stop) }
|
16
|
+
scope :locations, -> { where(place_type: :location) }
|
19
17
|
|
20
18
|
def selected
|
21
19
|
vendor.selected_place_references.include?(place&.reference)
|
@@ -7,6 +7,7 @@ module Spree
|
|
7
7
|
base.has_many :product_kind_option_types, serializer: :option_type
|
8
8
|
base.has_many :promoted_option_types, serializer: :option_type
|
9
9
|
base.has_many :possible_promotions, serializer: ::SpreeCmCommissioner::V2::Storefront::PromotionSerializer
|
10
|
+
base.has_many :dynamic_fields, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldSerializer
|
10
11
|
|
11
12
|
base.has_one :default_state, serializer: :state
|
12
13
|
base.has_one :venue, serializer: ::SpreeCmCommissioner::V2::Storefront::ProductPlaceSerializer
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module V2
|
3
|
+
module Storefront
|
4
|
+
class DynamicFieldSerializer < BaseSerializer
|
5
|
+
set_type :dynamic_field
|
6
|
+
|
7
|
+
attributes :id, :label, :data_type, :vendor_id, :created_at, :updated_at
|
8
|
+
|
9
|
+
has_many :dynamic_field_options, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldOptionSerializer
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module V2
|
3
|
+
module Storefront
|
4
|
+
class GuestDynamicFieldSerializer < BaseSerializer
|
5
|
+
set_type :guest_dynamic_field
|
6
|
+
|
7
|
+
attributes :id, :value, :guest_id, :dynamic_field_id, :created_at, :updated_at
|
8
|
+
|
9
|
+
belongs_to :dynamic_field, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldSerializer
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -20,12 +20,13 @@ module SpreeCmCommissioner
|
|
20
20
|
|
21
21
|
belongs_to :occupation, serializer: Spree::V2::Storefront::TaxonSerializer
|
22
22
|
belongs_to :nationality, serializer: Spree::V2::Storefront::TaxonSerializer
|
23
|
+
belongs_to :line_item, serializer: Spree::V2::Storefront::LineItemSerializer
|
24
|
+
|
25
|
+
has_many :guest_dynamic_fields, serializer: SpreeCmCommissioner::V2::Storefront::GuestDynamicFieldSerializer
|
23
26
|
|
24
27
|
has_one :id_card, serializer: Spree::V2::Storefront::IdCardSerializer
|
25
28
|
has_one :check_in
|
26
29
|
|
27
|
-
belongs_to :line_item, serializer: Spree::V2::Storefront::LineItemSerializer
|
28
|
-
|
29
30
|
# allowed_checkout updates frequently
|
30
31
|
cache_options store: nil
|
31
32
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SpreeCmCommissioner
|
2
|
+
module V2
|
3
|
+
module Storefront
|
4
|
+
class ProductDynamicFieldSerializer < BaseSerializer
|
5
|
+
set_type :product_dynamic_field
|
6
|
+
|
7
|
+
attributes :id, :product_id, :dynamic_field_id, :created_at, :updated_at
|
8
|
+
|
9
|
+
belongs_to :dynamic_field, serializer: SpreeCmCommissioner::V2::Storefront::DynamicFieldSerializer
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module SpreeCmCommissioner
|
2
2
|
module Organizer
|
3
3
|
class ExportGuestCsvService
|
4
|
-
attr_reader :event_id, :columns
|
4
|
+
attr_reader :event_id, :columns, :filters
|
5
5
|
|
6
|
-
def initialize(event_id, columns)
|
6
|
+
def initialize(event_id, columns, filters = {})
|
7
7
|
@event_id = event_id
|
8
8
|
@columns = columns
|
9
|
+
@filters = filters
|
9
10
|
end
|
10
11
|
|
11
12
|
def call
|
@@ -18,7 +19,7 @@ module SpreeCmCommissioner
|
|
18
19
|
private
|
19
20
|
|
20
21
|
def headers
|
21
|
-
columns.map(
|
22
|
+
columns.map { |col| col.titleize(keep_id_suffix: true) }.uniq
|
22
23
|
end
|
23
24
|
|
24
25
|
def build_row(guest)
|
@@ -28,10 +29,8 @@ module SpreeCmCommissioner
|
|
28
29
|
def fetch_value(guest, column)
|
29
30
|
if guest_field?(column)
|
30
31
|
fetch_guest_value(guest, column)
|
31
|
-
elsif option_type_field?(column)
|
32
|
-
fetch_option_value(guest, column)
|
33
32
|
else
|
34
|
-
|
33
|
+
fetch_option_value(guest, column)
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
@@ -39,19 +38,21 @@ module SpreeCmCommissioner
|
|
39
38
|
SpreeCmCommissioner::KycBitwise::ORDERED_BIT_FIELDS.include?(column.to_sym)
|
40
39
|
end
|
41
40
|
|
42
|
-
def option_type_field?(column)
|
43
|
-
Spree::OptionType.exists?(name: column)
|
44
|
-
end
|
45
|
-
|
46
41
|
def fetch_guest_value(guest, column)
|
47
42
|
column_mappings(guest)[column] || ''
|
48
43
|
end
|
49
44
|
|
50
|
-
|
51
|
-
|
45
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
46
|
+
def fetch_option_value(guest, column_name)
|
47
|
+
return guest.formatted_bib_number if column_name == 'bib-prefix'
|
48
|
+
return guest.line_item&.number if column_name == 'item_id'
|
49
|
+
return guest.line_item&.order&.number if column_name == 'order_id'
|
50
|
+
return guest.created_at if column_name == 'created_at'
|
51
|
+
return guest.updated_at if column_name == 'updated_at'
|
52
52
|
|
53
|
-
guest.line_item&.variant&.find_option_value_name_for(option_type_name:
|
53
|
+
guest.line_item&.variant&.find_option_value_name_for(option_type_name: column_name) || ''
|
54
54
|
end
|
55
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
55
56
|
|
56
57
|
def column_mappings(guest)
|
57
58
|
{
|
@@ -77,12 +78,19 @@ module SpreeCmCommissioner
|
|
77
78
|
end
|
78
79
|
|
79
80
|
def guests_with_associations
|
80
|
-
event.guests.includes(
|
81
|
+
scope = event.guests.includes(
|
81
82
|
:occupation,
|
82
83
|
:nationality,
|
83
84
|
:id_card,
|
84
85
|
line_item: { variant: { option_values: :option_type } }
|
85
|
-
)
|
86
|
+
).complete
|
87
|
+
scope = scope.where('cm_guests.created_at >= ?', formatted_date_time(filters[:from_date])) if filters[:from_date].present?
|
88
|
+
scope = scope.where('cm_guests.created_at <= ?', formatted_date_time(filters[:to_date])) if filters[:to_date].present?
|
89
|
+
scope
|
90
|
+
end
|
91
|
+
|
92
|
+
def formatted_date_time(date)
|
93
|
+
Time.zone.parse(date.to_s).strftime('%Y-%m-%d %H:%M:%S %z')
|
86
94
|
end
|
87
95
|
|
88
96
|
def event
|
@@ -19,17 +19,11 @@ module SpreeCmCommissioner
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def create
|
22
|
-
return { success: false, message: I18n.t('user_roles_assigner.user_not_found') } unless user
|
23
|
-
return { success: false, message: I18n.t('user_roles_assigner.user_already_assigned') } if user.vendors.exists?(id: vendor_id)
|
24
|
-
return { success: false, message: I18n.t('user_roles_assigner.roles_required') } if role_ids.blank?
|
25
|
-
|
26
22
|
create_roles
|
27
23
|
{ success: true }
|
28
24
|
end
|
29
25
|
|
30
26
|
def update
|
31
|
-
return { success: false, message: I18n.t('user_roles_assigner.roles_required') } if role_ids.blank?
|
32
|
-
|
33
27
|
update_roles
|
34
28
|
{ success: true }
|
35
29
|
end
|
@@ -92,3 +92,256 @@
|
|
92
92
|
<% end %>
|
93
93
|
</div>
|
94
94
|
</div>
|
95
|
+
<div data-hook="admin_tenant_form_fields" class="row">
|
96
|
+
|
97
|
+
<!-- payment icon fields improved UI -->
|
98
|
+
<div class="col-3">
|
99
|
+
<div class="card mb-3">
|
100
|
+
<div class="card-body text-center">
|
101
|
+
<%= f.field_container :preferred_payment_checkout_image do %>
|
102
|
+
<%= f.label :payment_checkout_image, raw(Spree.t(:payment_checkout_image)), class: 'font-weight-bold' %>
|
103
|
+
<div class="mb-2 position-relative">
|
104
|
+
<img id="preview_preferred_payment_checkout_image"
|
105
|
+
src="<%= (f.object.preferred_payment_checkout_image.present? ? asset_path(f.object.preferred_payment_checkout_image) : asset_path('cm-hangmeas-checkout_image.svg')) %>"
|
106
|
+
alt="Icon Preview"
|
107
|
+
class="img-thumbnail bg-light border payment-icon-preview"
|
108
|
+
data-toggle="tooltip" title="Click to select icon"
|
109
|
+
onclick="$('#vector-icons-modal').modal('show'); setOptionValueId('preferred_payment_checkout_image'); setCurrentSelectedIcon(document.getElementById('preferred_payment_checkout_image').value);">
|
110
|
+
<button type="button" class="btn btn-sm btn-outline-danger position-absolute" style="top:5px; right:5px;" title="Remove icon" onclick="document.getElementById('preferred_payment_checkout_image').value=''; document.getElementById('preview_preferred_payment_checkout_image').src='<%= asset_path('cm-hangmeas-checkout_image.svg') %>'; document.getElementById('preview_preferred_payment_checkout_image').style.display='inline'; return false;">
|
111
|
+
<i class="fa fa-times"></i>
|
112
|
+
</button>
|
113
|
+
</div>
|
114
|
+
<div class="input-group">
|
115
|
+
<%= f.text_field :preferred_payment_checkout_image, class: 'form-control', placeholder: 'eg. https://example.com/icon.png', id: 'preferred_payment_checkout_image', 'aria-label': 'Payment Checkout Image' %>
|
116
|
+
<div class="input-group-append">
|
117
|
+
<button type="button" class="btn btn-outline-secondary option-value-toggler" data-toggle="modal" data-target="#vector-icons-modal" data-id="preferred_payment_checkout_image" data-vector-icon="<%= f.object.preferred_payment_checkout_image %>" title="Select icon">
|
118
|
+
<i class="fa fa-image"></i>
|
119
|
+
</button>
|
120
|
+
</div>
|
121
|
+
</div>
|
122
|
+
<%= f.error_message_on :preferred_payment_checkout_image %>
|
123
|
+
<% end %>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
</div>
|
127
|
+
|
128
|
+
<div class="col-3">
|
129
|
+
<div class="card mb-3">
|
130
|
+
<div class="card-body text-center">
|
131
|
+
<%= f.field_container :preferred_payment_failed_image do %>
|
132
|
+
<%= f.label :payment_failed_image, raw(Spree.t(:payment_failed_image)), class: 'font-weight-bold' %>
|
133
|
+
<div class="mb-2 position-relative">
|
134
|
+
<img id="preview_preferred_payment_failed_image"
|
135
|
+
src="<%= (f.object.preferred_payment_failed_image.present? ? asset_path(f.object.preferred_payment_failed_image) : asset_path('cm-hangmeas-failed.svg')) %>"
|
136
|
+
alt="Icon Preview"
|
137
|
+
class="img-thumbnail bg-light border payment-icon-preview"
|
138
|
+
data-toggle="tooltip" title="Click to select icon"
|
139
|
+
onclick="$('#vector-icons-modal').modal('show'); setOptionValueId('preferred_payment_failed_image'); setCurrentSelectedIcon(document.getElementById('preferred_payment_failed_image').value);">
|
140
|
+
<button type="button" class="btn btn-sm btn-outline-danger position-absolute" style="top:5px; right:5px;" title="Remove icon" onclick="document.getElementById('preferred_payment_failed_image').value=''; document.getElementById('preview_preferred_payment_failed_image').src='<%= asset_path('cm-hangmeas-failed.svg') %>'; document.getElementById('preview_preferred_payment_failed_image').style.display='inline'; return false;">
|
141
|
+
<i class="fa fa-times"></i>
|
142
|
+
</button>
|
143
|
+
</div>
|
144
|
+
<div class="input-group">
|
145
|
+
<%= f.text_field :preferred_payment_failed_image, class: 'form-control', placeholder: 'eg. https://example.com/icon.png', id: 'preferred_payment_failed_image', 'aria-label': 'Payment Failed Image' %>
|
146
|
+
<div class="input-group-append">
|
147
|
+
<button type="button" class="btn btn-outline-secondary option-value-toggler" data-toggle="modal" data-target="#vector-icons-modal" data-id="preferred_payment_failed_image" data-vector-icon="<%= f.object.preferred_payment_failed_image %>" title="Select icon">
|
148
|
+
<i class="fa fa-image"></i>
|
149
|
+
</button>
|
150
|
+
</div>
|
151
|
+
</div>
|
152
|
+
<%= f.error_message_on :preferred_payment_failed_image %>
|
153
|
+
<% end %>
|
154
|
+
</div>
|
155
|
+
</div>
|
156
|
+
</div>
|
157
|
+
|
158
|
+
<div class="col-3">
|
159
|
+
<div class="card mb-3">
|
160
|
+
<div class="card-body text-center">
|
161
|
+
<%= f.field_container :preferred_payment_success_image do %>
|
162
|
+
<%= f.label :payment_success_image, raw(Spree.t(:payment_success_image)), class: 'font-weight-bold' %>
|
163
|
+
<div class="mb-2 position-relative">
|
164
|
+
<img id="preview_preferred_payment_success_image"
|
165
|
+
src="<%= (f.object.preferred_payment_success_image.present? ? asset_path(f.object.preferred_payment_success_image) : asset_path('cm-hangmeas-success.svg')) %>"
|
166
|
+
alt="Icon Preview"
|
167
|
+
class="img-thumbnail bg-light border payment-icon-preview"
|
168
|
+
data-toggle="tooltip" title="Click to select icon"
|
169
|
+
onclick="$('#vector-icons-modal').modal('show'); setOptionValueId('preferred_payment_success_image'); setCurrentSelectedIcon(document.getElementById('preferred_payment_success_image').value);">
|
170
|
+
<button type="button" class="btn btn-sm btn-outline-danger position-absolute" style="top:5px; right:5px;" title="Remove icon" onclick="document.getElementById('preferred_payment_success_image').value=''; document.getElementById('preview_preferred_payment_success_image').src='<%= asset_path('cm-hangmeas-success.svg') %>'; document.getElementById('preview_preferred_payment_success_image').style.display='inline'; return false;">
|
171
|
+
<i class="fa fa-times"></i>
|
172
|
+
</button>
|
173
|
+
</div>
|
174
|
+
<div class="input-group">
|
175
|
+
<%= f.text_field :preferred_payment_success_image, class: 'form-control', placeholder: 'eg. https://example.com/icon.png', id: 'preferred_payment_success_image', 'aria-label': 'Payment Success Image' %>
|
176
|
+
<div class="input-group-append">
|
177
|
+
<button type="button" class="btn btn-outline-secondary option-value-toggler" data-toggle="modal" data-target="#vector-icons-modal" data-id="preferred_payment_success_image" data-vector-icon="<%= f.object.preferred_payment_success_image %>" title="Select icon">
|
178
|
+
<i class="fa fa-image"></i>
|
179
|
+
</button>
|
180
|
+
</div>
|
181
|
+
</div>
|
182
|
+
<%= f.error_message_on :preferred_payment_success_image %>
|
183
|
+
<% end %>
|
184
|
+
</div>
|
185
|
+
</div>
|
186
|
+
</div>
|
187
|
+
|
188
|
+
<div class="col-3">
|
189
|
+
<div class="card mb-3">
|
190
|
+
<div class="card-body text-center">
|
191
|
+
<%= f.field_container :preferred_payment_loader do %>
|
192
|
+
<%= f.label :payment_loader, raw(Spree.t(:payment_loader)), class: 'font-weight-bold' %>
|
193
|
+
<div class="mb-2 position-relative">
|
194
|
+
<img id="preview_preferred_payment_loader"
|
195
|
+
src="<%= (f.object.preferred_payment_loader.present? ? asset_path(f.object.preferred_payment_loader) : asset_path('cm-hangmeas-loader.svg')) %>"
|
196
|
+
alt="Icon Preview"
|
197
|
+
class="img-thumbnail bg-light border payment-icon-preview"
|
198
|
+
data-toggle="tooltip" title="Click to select icon"
|
199
|
+
onclick="$('#vector-icons-modal').modal('show'); setOptionValueId('preferred_payment_loader'); setCurrentSelectedIcon(document.getElementById('preferred_payment_loader').value);">
|
200
|
+
<button type="button" class="btn btn-sm btn-outline-danger position-absolute" style="top:5px; right:5px;" title="Remove icon" onclick="document.getElementById('preferred_payment_loader').value=''; document.getElementById('preview_preferred_payment_loader').src='<%= asset_path('cm-hangmeas-loader.svg') %>'; document.getElementById('preview_preferred_payment_loader').style.display='inline'; return false;">
|
201
|
+
<i class="fa fa-times"></i>
|
202
|
+
</button>
|
203
|
+
</div>
|
204
|
+
<div class="input-group">
|
205
|
+
<%= f.text_field :preferred_payment_loader, class: 'form-control', placeholder: 'eg. https://example.com/icon.png', id: 'preferred_payment_loader', 'aria-label': 'Payment Loader Image' %>
|
206
|
+
<div class="input-group-append">
|
207
|
+
<button type="button" class="btn btn-outline-secondary option-value-toggler" data-toggle="modal" data-target="#vector-icons-modal" data-id="preferred_payment_loader" data-vector-icon="<%= f.object.preferred_payment_loader %>" title="Select icon">
|
208
|
+
<i class="fa fa-image"></i>
|
209
|
+
</button>
|
210
|
+
</div>
|
211
|
+
</div>
|
212
|
+
<%= f.error_message_on :preferred_payment_loader %>
|
213
|
+
<% end %>
|
214
|
+
</div>
|
215
|
+
</div>
|
216
|
+
</div>
|
217
|
+
|
218
|
+
</div>
|
219
|
+
|
220
|
+
|
221
|
+
<!-- Vector Icon Selector Modal -->
|
222
|
+
<div class="modal fade" id="vector-icons-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
223
|
+
<div class="modal-dialog modal-dialog-centered" role="document">
|
224
|
+
<div class="modal-content">
|
225
|
+
<div class="modal-header">
|
226
|
+
<h5 class="modal-title">Icons</h5>
|
227
|
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
228
|
+
<span aria-hidden="true">×</span>
|
229
|
+
</button>
|
230
|
+
</div>
|
231
|
+
<div class="modal-body" data-hook="admin_product_form_vector_icons">
|
232
|
+
<div class="form-group">
|
233
|
+
<label for="vector_icon_select">Select Icon</label>
|
234
|
+
<select id="vector_icon_select" class="form-control select2">
|
235
|
+
<option value="">None</option>
|
236
|
+
<% @vector_icons&.each do |icon| %>
|
237
|
+
<option value="<%= icon.path %>"><%= icon.path %></option>
|
238
|
+
<% end %>
|
239
|
+
</select>
|
240
|
+
</div>
|
241
|
+
</div>
|
242
|
+
<div class="modal-footer">
|
243
|
+
<input type="hidden" id="modal-option-value-id" value="" />
|
244
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
245
|
+
<button type="button" class="btn btn-primary" id="select-vector-icon-btn">Select</button>
|
246
|
+
</div>
|
247
|
+
</div>
|
248
|
+
</div>
|
249
|
+
</div>
|
250
|
+
|
251
|
+
<script>
|
252
|
+
function setCurrentSelectedIcon(selectVectorIcon) {
|
253
|
+
var optionsContainer = document.getElementById("vector_icon_select");
|
254
|
+
if (!optionsContainer) return;
|
255
|
+
var options = Array.from(optionsContainer.options).map((e) => e.value || e.innerHTML);
|
256
|
+
var index = Math.max(options.indexOf(selectVectorIcon), 0);
|
257
|
+
optionsContainer.selectedIndex = index;
|
258
|
+
}
|
259
|
+
|
260
|
+
function setOptionValueId(id) {
|
261
|
+
document.getElementById("modal-option-value-id").value = id;
|
262
|
+
}
|
263
|
+
|
264
|
+
document.body.addEventListener('click', function(e){
|
265
|
+
var target = e.target;
|
266
|
+
if (target.classList.contains('option-value-toggler')) {
|
267
|
+
setOptionValueId(target.dataset.id);
|
268
|
+
setCurrentSelectedIcon(target.dataset.vectorIcon);
|
269
|
+
}
|
270
|
+
});
|
271
|
+
|
272
|
+
function getAssetUrl(path) {
|
273
|
+
if (!path) return '';
|
274
|
+
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('/assets/')) return path;
|
275
|
+
return '/assets/' + path;
|
276
|
+
}
|
277
|
+
|
278
|
+
document.getElementById('select-vector-icon-btn').addEventListener('click', function() {
|
279
|
+
var fieldId = document.getElementById("modal-option-value-id").value;
|
280
|
+
var selectedIcon = document.getElementById("vector_icon_select").value;
|
281
|
+
|
282
|
+
if (fieldId) {
|
283
|
+
document.getElementById(fieldId).value = selectedIcon;
|
284
|
+
|
285
|
+
var previewImg = document.getElementById('preview_' + fieldId);
|
286
|
+
if (previewImg) {
|
287
|
+
if (selectedIcon) {
|
288
|
+
previewImg.src = getAssetUrl(selectedIcon);
|
289
|
+
previewImg.style.display = 'inline';
|
290
|
+
} else {
|
291
|
+
previewImg.src = '';
|
292
|
+
previewImg.style.display = 'none';
|
293
|
+
}
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
// close select2 if open
|
298
|
+
$('#vector_icon_select').select2('close');
|
299
|
+
|
300
|
+
// hide modal
|
301
|
+
$('#vector-icons-modal').modal('hide');
|
302
|
+
});
|
303
|
+
|
304
|
+
$(document).ready(function() {
|
305
|
+
$('#vector_icon_select').select2({
|
306
|
+
dropdownParent: $('#vector-icons-modal')
|
307
|
+
});
|
308
|
+
|
309
|
+
// Auto-close modal & save immediately when selecting an option
|
310
|
+
$('#vector_icon_select').on('select2:select', function (e) {
|
311
|
+
$('#select-vector-icon-btn').trigger('click');
|
312
|
+
});
|
313
|
+
});
|
314
|
+
|
315
|
+
// Live preview when typing URLs manually
|
316
|
+
[
|
317
|
+
'preferred_payment_checkout_image',
|
318
|
+
'preferred_payment_failed_image',
|
319
|
+
'preferred_payment_success_image',
|
320
|
+
'preferred_payment_loader'
|
321
|
+
].forEach(function(fieldId) {
|
322
|
+
var input = document.getElementById(fieldId);
|
323
|
+
if (input) {
|
324
|
+
input.addEventListener('input', function() {
|
325
|
+
var previewImg = document.getElementById('preview_' + fieldId);
|
326
|
+
if (previewImg) {
|
327
|
+
if (input.value) {
|
328
|
+
previewImg.src = getAssetUrl(input.value);
|
329
|
+
previewImg.style.display = 'inline';
|
330
|
+
} else {
|
331
|
+
previewImg.src = '';
|
332
|
+
previewImg.style.display = 'none';
|
333
|
+
}
|
334
|
+
}
|
335
|
+
});
|
336
|
+
}
|
337
|
+
});
|
338
|
+
</script>
|
339
|
+
|
340
|
+
<style>
|
341
|
+
.payment-icon-preview {
|
342
|
+
max-height: 60px;
|
343
|
+
max-width: 120px;
|
344
|
+
cursor: pointer;
|
345
|
+
}
|
346
|
+
</style>
|
347
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
6
|
+
<meta name="x-apple-disable-message-reformatting"/>
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
8
|
+
<link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
|
9
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
10
|
+
<%= render partial: 'spree_cm_commissioner/team_invite_mailer/mailer_stylesheets' %>
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<%= yield %>
|
14
|
+
</body>
|
15
|
+
</html>
|
@@ -0,0 +1,79 @@
|
|
1
|
+
<style>
|
2
|
+
body, table, td {
|
3
|
+
font-family: 'Poppins', Arial, sans-serif;
|
4
|
+
margin: 0;
|
5
|
+
padding: 0;
|
6
|
+
}
|
7
|
+
|
8
|
+
.content-wrapper {
|
9
|
+
max-width: 500px;
|
10
|
+
margin: 0 auto;
|
11
|
+
border: 1px solid grey;
|
12
|
+
border-top: none;
|
13
|
+
}
|
14
|
+
|
15
|
+
.main-content{
|
16
|
+
padding-top: 10px;
|
17
|
+
padding-bottom: 25px;
|
18
|
+
}
|
19
|
+
|
20
|
+
.heading-1 {
|
21
|
+
font-size: 24px;
|
22
|
+
text-align: center;
|
23
|
+
line-height: 1.4;
|
24
|
+
margin-top: 10px;
|
25
|
+
margin-bottom: 15px;
|
26
|
+
padding-left: 5px;
|
27
|
+
padding-right: 5px;
|
28
|
+
|
29
|
+
}
|
30
|
+
|
31
|
+
.bar-large {
|
32
|
+
height: 10px;
|
33
|
+
background-color: #5D54D9;
|
34
|
+
margin-bottom: 20px;
|
35
|
+
|
36
|
+
}
|
37
|
+
|
38
|
+
.bar-thin {
|
39
|
+
height: 1px;
|
40
|
+
background-color: grey;
|
41
|
+
width: 100%;
|
42
|
+
margin: 20px auto;
|
43
|
+
}
|
44
|
+
|
45
|
+
.heading-2 {
|
46
|
+
font-size: 16px;
|
47
|
+
text-align: center;
|
48
|
+
line-height: 1.5;
|
49
|
+
}
|
50
|
+
|
51
|
+
.title-bold {
|
52
|
+
font-weight: bold;
|
53
|
+
color: #000;
|
54
|
+
}
|
55
|
+
|
56
|
+
.action-button {
|
57
|
+
display: inline-block;
|
58
|
+
background-color: #5D54D9;
|
59
|
+
border-radius: 24px;
|
60
|
+
font-size: 14px;
|
61
|
+
width: 222px;
|
62
|
+
height: 50px;
|
63
|
+
text-align: center;
|
64
|
+
line-height: 50px;
|
65
|
+
text-decoration: none;
|
66
|
+
cursor: pointer;
|
67
|
+
margin: 15px 0;
|
68
|
+
color: white !important;
|
69
|
+
text-decoration: none;
|
70
|
+
}
|
71
|
+
|
72
|
+
.heading-3 {
|
73
|
+
font-size: 12px;
|
74
|
+
text-align: center;
|
75
|
+
margin-bottom: 15px;
|
76
|
+
color: #888;
|
77
|
+
}
|
78
|
+
</style>
|
79
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%">
|
2
|
+
<tr>
|
3
|
+
<td align="center">
|
4
|
+
<div class="content-wrapper">
|
5
|
+
<div class="bar-large"></div>
|
6
|
+
<div class="main-content">
|
7
|
+
<div class="content">
|
8
|
+
<div>
|
9
|
+
<%= image_tag(main_app.rails_blob_url(Spree::StoreLogo.first.attachment), style: "width: 120px; height: 120px;") %>
|
10
|
+
</div>
|
11
|
+
<div class="heading-1">
|
12
|
+
You're invited to join
|
13
|
+
<span class="title-bold">
|
14
|
+
BookMe+
|
15
|
+
</span>
|
16
|
+
Business.
|
17
|
+
</div>
|
18
|
+
<div class="bar-thin"></div>
|
19
|
+
<div class="heading-2">
|
20
|
+
<span class="title-bold"><%= @invite_team.inviter.full_name.presence || @invite_team.inviter.email %></span> has invited you to manage bookings and grow together on the platform. <br>
|
21
|
+
</div>
|
22
|
+
<div style="text-align: center;">
|
23
|
+
<a href="<%= @invite_team.invitation_link %>" class="action-button">
|
24
|
+
Accept Invitation
|
25
|
+
</a>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
<div class="heading-3">
|
29
|
+
Use this email <span class="title-bold"><%= @invite_team.email %></span> to log in or <br>create an account on BookMe+ Platform
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div class="heading-3">
|
33
|
+
This invitation will expire<br>
|
34
|
+
in <%= ((@invite_team.expires_at - Time.current) / 1.hour).round %> hours.
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
</td>
|
40
|
+
</tr>
|
41
|
+
</table>
|
42
|
+
|
data/config/routes.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateSpreeCmCommissionerInviteTeam < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :cm_invite_teams, if_not_exists: true do |t|
|
4
|
+
t.string :email, null: false
|
5
|
+
t.string :token, null: false, unique: true, index: true
|
6
|
+
t.references :inviter, null: false, foreign_key: { to_table: :spree_users }, index: true
|
7
|
+
t.references :vendor, null:false, foreign_key: { to_table: :spree_vendors }, index: true
|
8
|
+
t.integer :status, default: 0, null: false
|
9
|
+
t.text :preferences
|
10
|
+
t.datetime :expires_at
|
11
|
+
t.datetime :confirmed_at
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateCmDynamicFields < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :cm_dynamic_fields, if_not_exists: true do |t|
|
4
|
+
t.string :label, null: false
|
5
|
+
t.integer :data_type, null: false
|
6
|
+
t.references :vendor, null: false, foreign_key: { to_table: :spree_vendors }
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|