spree_admin 5.5.3 → 5.6.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/images/vercel-deploy-button.svg +14 -0
- data/app/assets/tailwind/spree/admin/components/_alerts.css +5 -1
- data/app/assets/tailwind/spree/admin/components/_badges.css +1 -1
- data/app/assets/tailwind/spree/admin/components/_breadcrumbs.css +1 -1
- data/app/assets/tailwind/spree/admin/components/_buttons.css +1 -1
- data/app/assets/tailwind/spree/admin/components/_cards.css +1 -1
- data/app/assets/tailwind/spree/admin/components/_dialogs.css +3 -1
- data/app/assets/tailwind/spree/admin/components/_dropdowns.css +2 -2
- data/app/assets/tailwind/spree/admin/components/_filters.css +1 -1
- data/app/assets/tailwind/spree/admin/components/_forms.css +10 -6
- data/app/assets/tailwind/spree/admin/components/_layout.css +30 -23
- data/app/assets/tailwind/spree/admin/components/_rtl.css +53 -0
- data/app/assets/tailwind/spree/admin/components/_search-picker.css +2 -2
- data/app/assets/tailwind/spree/admin/components/_tables.css +1 -1
- data/app/assets/tailwind/spree/admin/index.css +1 -0
- data/app/assets/tailwind/spree/admin/plugins/_tom-select.css +6 -6
- data/app/assets/tailwind/spree/admin/plugins/_trix.css +1 -1
- data/app/assets/tailwind/spree/admin/plugins/_uppy.css +1 -1
- data/app/assets/tailwind/spree/admin/views/_page-builder.css +8 -5
- data/app/controllers/concerns/spree/admin/locale_concern.rb +76 -0
- data/app/controllers/spree/admin/base_controller.rb +38 -2
- data/app/controllers/spree/admin/countries_controller.rb +18 -4
- data/app/controllers/spree/admin/exports_controller.rb +7 -0
- data/app/controllers/spree/admin/imports_controller.rb +8 -0
- data/app/controllers/spree/admin/products_controller.rb +1 -0
- data/app/controllers/spree/admin/storefront_controller.rb +100 -0
- data/app/controllers/spree/admin/user_sessions_controller.rb +10 -0
- data/app/controllers/spree/admin/variants_controller.rb +3 -1
- data/app/helpers/spree/admin/base_helper.rb +10 -0
- data/app/helpers/spree/admin/channels_helper.rb +17 -0
- data/app/helpers/spree/admin/dropdown_helper.rb +14 -0
- data/app/helpers/spree/admin/navigation_helper.rb +1 -1
- data/app/helpers/spree/admin/onboarding_helper.rb +1 -1
- data/app/helpers/spree/admin/orders_helper.rb +9 -34
- data/app/helpers/spree/admin/products_helper.rb +1 -1
- data/app/helpers/spree/admin/rtl_helper.rb +15 -0
- data/app/helpers/spree/admin/shipment_helper.rb +1 -1
- data/app/helpers/spree/admin/storefront_helper.rb +46 -0
- data/app/helpers/spree/admin/stores_helper.rb +2 -1
- data/app/javascript/spree/admin/application.js +2 -0
- data/app/javascript/spree/admin/controllers/autocomplete_select_controller.js +4 -0
- data/app/javascript/spree/admin/controllers/calendar_range_controller.js +7 -3
- data/app/javascript/spree/admin/controllers/display_name_controller.js +18 -0
- data/app/javascript/spree/admin/controllers/query_builder_controller.js +39 -28
- data/app/javascript/spree/admin/controllers/variants_form_controller.js +49 -1
- data/app/javascript/spree/admin/helpers/display_names.js +54 -0
- data/app/models/spree/admin/table/column.rb +15 -1
- data/app/models/spree/admin/table/filter.rb +23 -16
- data/app/models/spree/admin/table/query_builder.rb +11 -2
- data/app/views/active_storage/_upload_form.html.erb +1 -1
- data/app/views/layouts/spree/admin.html.erb +1 -1
- data/app/views/layouts/spree/admin_settings.html.erb +1 -1
- data/app/views/layouts/spree/admin_wizard.html.erb +2 -2
- data/app/views/layouts/spree/minimal.html.erb +1 -1
- data/app/views/spree/admin/admin_users/_audit_log.html.erb +2 -2
- data/app/views/spree/admin/admin_users/new.html.erb +1 -1
- data/app/views/spree/admin/assets/edit.html.erb +1 -1
- data/app/views/spree/admin/channels/_form.html.erb +10 -0
- data/app/views/spree/admin/dashboard/_setup_progress.html.erb +4 -4
- data/app/views/spree/admin/dashboard/_setup_tasks.html.erb +5 -4
- data/app/views/spree/admin/dashboard/_updater.html.erb +4 -4
- data/app/views/spree/admin/dashboard/_visits.html.erb +12 -12
- data/app/views/spree/admin/dashboard/analytics.html.erb +5 -5
- data/app/views/spree/admin/dashboard/setup_tasks/_add_products.html.erb +4 -4
- data/app/views/spree/admin/dashboard/setup_tasks/_set_customer_support_email.html.erb +3 -4
- data/app/views/spree/admin/dashboard/setup_tasks/_setup_storefront.html.erb +18 -0
- data/app/views/spree/admin/gift_card_batches/_form.html.erb +1 -1
- data/app/views/spree/admin/import_rows/show.html.erb +2 -2
- data/app/views/spree/admin/imports/_footer.html.erb +1 -1
- data/app/views/spree/admin/imports/_mapping_form.html.erb +1 -1
- data/app/views/spree/admin/imports/show.html.erb +3 -3
- data/app/views/spree/admin/integrations/_integration.html.erb +1 -1
- data/app/views/spree/admin/integrations/index.html.erb +2 -4
- data/app/views/spree/admin/invitations/_invitation.html.erb +1 -1
- data/app/views/spree/admin/invitations/new.html.erb +1 -1
- data/app/views/spree/admin/invitations/show.html.erb +1 -1
- data/app/views/spree/admin/line_items/new.html.erb +1 -1
- data/app/views/spree/admin/markets/_form.html.erb +8 -6
- data/app/views/spree/admin/metafields/edit.html.erb +1 -1
- data/app/views/spree/admin/option_types/_form.html.erb +9 -6
- data/app/views/spree/admin/option_types/index.html.erb +2 -3
- data/app/views/spree/admin/orders/_customer.html.erb +1 -1
- data/app/views/spree/admin/orders/_customer_summary.html.erb +1 -1
- data/app/views/spree/admin/orders/_payments.html.erb +1 -1
- data/app/views/spree/admin/orders/_shipment.html.erb +7 -2
- data/app/views/spree/admin/orders/_shipments.html.erb +1 -1
- data/app/views/spree/admin/orders/_user_overview.html.erb +9 -6
- data/app/views/spree/admin/orders/return_authorizations/edit.html.erb +1 -1
- data/app/views/spree/admin/payment_methods/_form.html.erb +1 -1
- data/app/views/spree/admin/payment_methods/index.html.erb +2 -2
- data/app/views/spree/admin/payments/_payment.html.erb +1 -1
- data/app/views/spree/admin/price_lists/index.html.erb +2 -2
- data/app/views/spree/admin/products/form/_inventory.html.erb +19 -0
- data/app/views/spree/admin/products/form/_publishing.html.erb +2 -3
- data/app/views/spree/admin/products/index.html.erb +1 -2
- data/app/views/spree/admin/profile/edit.html.erb +3 -0
- data/app/views/spree/admin/promotion_rules/forms/_channel.html.erb +7 -0
- data/app/views/spree/admin/promotion_rules/forms/_country.html.erb +1 -1
- data/app/views/spree/admin/promotion_rules/forms/_item_total.html.erb +1 -1
- data/app/views/spree/admin/promotion_rules/forms/_market.html.erb +7 -0
- data/app/views/spree/admin/promotions/_form.html.erb +2 -2
- data/app/views/spree/admin/promotions/_sidebar.html.erb +1 -1
- data/app/views/spree/admin/promotions/edit.html.erb +2 -2
- data/app/views/spree/admin/promotions/form/_kind.html.erb +11 -11
- data/app/views/spree/admin/promotions/form/_settings.html.erb +4 -5
- data/app/views/spree/admin/reports/new.html.erb +2 -2
- data/app/views/spree/admin/shared/_address.html.erb +1 -1
- data/app/views/spree/admin/shared/_analytics_loader_skeleton.html.erb +5 -5
- data/app/views/spree/admin/shared/_calendar_range_picker.html.erb +11 -1
- data/app/views/spree/admin/shared/_content_header.html.erb +2 -2
- data/app/views/spree/admin/shared/_edit_resource_links.html.erb +1 -1
- data/app/views/spree/admin/shared/_filters_search_bar.html.erb +1 -1
- data/app/views/spree/admin/shared/_growth_rate_badge.html.erb +1 -1
- data/app/views/spree/admin/shared/_head.html.erb +4 -1
- data/app/views/spree/admin/shared/_index_table_options.html.erb +2 -2
- data/app/views/spree/admin/shared/_media_asset.html.erb +1 -1
- data/app/views/spree/admin/shared/_media_form.html.erb +1 -1
- data/app/views/spree/admin/shared/_seo.html.erb +1 -1
- data/app/views/spree/admin/shared/_sidebar.html.erb +1 -1
- data/app/views/spree/admin/shared/sidebar/_enterprise_edition_notice.html.erb +2 -2
- data/app/views/spree/admin/shared/sortable_tree/_taxonomy.html.erb +1 -1
- data/app/views/spree/admin/shipping_methods/form/_display.html.erb +2 -2
- data/app/views/spree/admin/shipping_methods/form/_estimated_transit_business_days.html.erb +1 -2
- data/app/views/spree/admin/shipping_methods/form/_tracking_url.html.erb +1 -1
- data/app/views/spree/admin/shipping_methods/form/_zones.html.erb +1 -1
- data/app/views/spree/admin/shipping_methods/index.html.erb +1 -1
- data/app/views/spree/admin/storefront/show.html.erb +150 -0
- data/app/views/spree/admin/stores/form/_basic.html.erb +2 -2
- data/app/views/spree/admin/stores/form/_checkout.html.erb +4 -0
- data/app/views/spree/admin/stores/form/_emails.html.erb +4 -6
- data/app/views/spree/admin/tables/_table.html.erb +3 -2
- data/app/views/spree/admin/taxons/_form.html.erb +2 -2
- data/app/views/spree/admin/user_sessions/new.html.erb +11 -2
- data/app/views/spree/admin/users/_tabs.html.erb +1 -1
- data/app/views/spree/admin/variants/_search_result.html.erb +1 -1
- data/app/views/spree/admin/variants/_variant.html.erb +1 -1
- data/app/views/spree/admin/variants/form/_inventory.html.erb +17 -0
- data/app/views/spree/admin/webhook_endpoints/_form.html.erb +2 -2
- data/app/views/spree/admin/zones/index.html.erb +1 -1
- data/config/i18n-tasks.yml +3 -0
- data/config/initializers/spree_admin_navigation.rb +9 -0
- data/config/initializers/spree_admin_tables.rb +20 -18
- data/config/locales/en.yml +647 -0
- data/config/routes.rb +5 -0
- metadata +18 -6
|
@@ -2,14 +2,17 @@ module Spree
|
|
|
2
2
|
module Admin
|
|
3
3
|
class BaseController < Spree::BaseController
|
|
4
4
|
include Spree::Admin::BreadcrumbConcern
|
|
5
|
+
include Spree::Admin::LocaleConcern
|
|
5
6
|
|
|
6
7
|
layout :choose_layout
|
|
7
8
|
default_form_builder Spree::Admin::FormBuilder
|
|
8
9
|
|
|
9
10
|
helper 'spree/base'
|
|
11
|
+
helper 'spree/admin/rtl'
|
|
10
12
|
helper 'spree/admin/navigation'
|
|
11
13
|
helper 'spree/locale'
|
|
12
14
|
helper 'spree/currency'
|
|
15
|
+
helper 'spree/localized_names'
|
|
13
16
|
helper 'spree/addresses'
|
|
14
17
|
helper 'spree/integrations'
|
|
15
18
|
|
|
@@ -84,14 +87,47 @@ module Spree
|
|
|
84
87
|
@default_locale ||= current_store&.preferred_admin_locale.presence || super
|
|
85
88
|
end
|
|
86
89
|
|
|
90
|
+
# Set the UI language (`I18n.locale`, via super) while keeping content
|
|
91
|
+
# reads on the store's locale — see Spree::Admin::LocaleConcern. `super`
|
|
92
|
+
# records the content locale in `Spree::Current` (the admin overrides
|
|
93
|
+
# `default_locale` to the UI language, but `content_locale` stays on the
|
|
94
|
+
# store's default), and `pin_content_locale!` aligns Mobility with it.
|
|
95
|
+
def set_locale
|
|
96
|
+
super
|
|
97
|
+
pin_content_locale!
|
|
98
|
+
sync_pagy_locale!
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Pagy ships its own i18n dictionaries and resolves them through `Pagy::I18n`
|
|
102
|
+
# (a thread-local that defaults to `:en`), not Rails `I18n`. Align it to the
|
|
103
|
+
# admin UI locale so pagination strings (e.g. "Displaying N items") localize.
|
|
104
|
+
def sync_pagy_locale!
|
|
105
|
+
Pagy::I18n.locale = I18n.locale if defined?(Pagy::I18n)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Resolve the admin UI locale for the current request. Unlike the
|
|
109
|
+
# storefront, the admin prefers the signed-in staff member's own saved
|
|
110
|
+
# `selected_locale` (the language of the back-office chrome), then the
|
|
111
|
+
# `spree_admin_locale` cookie (the per-browser choice made on the login
|
|
112
|
+
# screen, which carries from the pre-auth page into the session), then
|
|
113
|
+
# the store-wide `preferred_admin_locale` via `default_locale`.
|
|
114
|
+
def current_locale
|
|
115
|
+
@current_locale ||= [admin_user_selected_locale, admin_locale_cookie].
|
|
116
|
+
detect { |locale| supported_admin_locale?(locale) } || default_locale
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The store's timezone resolved to an +ActiveSupport::TimeZone+, falling
|
|
120
|
+
# back to +Time.zone+ when the preference holds an unknown zone name.
|
|
121
|
+
# @return [ActiveSupport::TimeZone]
|
|
87
122
|
def current_timezone
|
|
88
|
-
@current_timezone ||= current_store.preferred_timezone
|
|
123
|
+
@current_timezone ||= ActiveSupport::TimeZone[current_store.preferred_timezone] || Time.zone
|
|
89
124
|
end
|
|
125
|
+
helper_method :current_timezone
|
|
90
126
|
|
|
91
127
|
# datetime-local inputs submit values without timezone information. Interpret
|
|
92
128
|
# them in the store's timezone so they match what the admin sees in the form.
|
|
93
129
|
def parse_datetime_in_store_timezone(attrs, *fields)
|
|
94
|
-
zone =
|
|
130
|
+
zone = current_timezone
|
|
95
131
|
|
|
96
132
|
fields.each do |field|
|
|
97
133
|
value = attrs[field]
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Admin
|
|
3
3
|
class CountriesController < Spree::Admin::BaseController
|
|
4
|
+
# Returns every accessible country, sorted by its localized name. The set
|
|
5
|
+
# is small and fixed, so the admin autocomplete loads the full list once
|
|
6
|
+
# and filters it client-side (in the admin UI locale) rather than querying
|
|
7
|
+
# the server per keystroke.
|
|
8
|
+
#
|
|
9
|
+
# `name` is the underlying value used for filtering (the stored English
|
|
10
|
+
# country name); `label` is the localized, flag-prefixed display string.
|
|
4
11
|
def select_options
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
countries = Spree::Country.accessible_by(current_ability).ransack(ransack_params).result.order(:name).limit(50)
|
|
12
|
+
countries = Spree::Country.accessible_by(current_ability, :show).
|
|
13
|
+
sort_by(&:localized_name)
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
options = countries.map do |country|
|
|
16
|
+
{
|
|
17
|
+
id: country.id,
|
|
18
|
+
name: country.name,
|
|
19
|
+
label: country.option_label
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
render json: options
|
|
10
24
|
end
|
|
11
25
|
end
|
|
12
26
|
end
|
|
@@ -7,6 +7,7 @@ module Spree
|
|
|
7
7
|
|
|
8
8
|
new_action.before :assign_params
|
|
9
9
|
create.before :set_user
|
|
10
|
+
create.after :remember_results_url
|
|
10
11
|
|
|
11
12
|
def show
|
|
12
13
|
redirect_to @object.attachment.url, status: :see_other, allow_other_host: true
|
|
@@ -30,6 +31,12 @@ module Spree
|
|
|
30
31
|
@object.user = try_spree_current_user
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
# The export-done email links wherever the creating surface serves the
|
|
35
|
+
# file — for the legacy admin that is this export's download page.
|
|
36
|
+
def remember_results_url
|
|
37
|
+
@object.update!(results_url: spree.admin_export_url(@object))
|
|
38
|
+
end
|
|
39
|
+
|
|
33
40
|
def location_after_save
|
|
34
41
|
spree.admin_exports_path
|
|
35
42
|
end
|
|
@@ -8,6 +8,7 @@ module Spree
|
|
|
8
8
|
create.before :set_user
|
|
9
9
|
create.before :set_owner
|
|
10
10
|
create.after :start_mapping
|
|
11
|
+
create.after :remember_results_url
|
|
11
12
|
|
|
12
13
|
# GET /admin/imports/:id
|
|
13
14
|
def show
|
|
@@ -40,6 +41,13 @@ module Spree
|
|
|
40
41
|
@object.start_mapping!
|
|
41
42
|
end
|
|
42
43
|
|
|
44
|
+
# The import-done email links wherever the creating surface shows
|
|
45
|
+
# results — for the legacy admin that is this import's page. The
|
|
46
|
+
# dashboard passes its own URL through the Admin API instead.
|
|
47
|
+
def remember_results_url
|
|
48
|
+
@object.update!(results_url: spree.admin_import_url(@object))
|
|
49
|
+
end
|
|
50
|
+
|
|
43
51
|
def location_after_save
|
|
44
52
|
spree.admin_import_path(@object)
|
|
45
53
|
end
|
|
@@ -311,6 +311,7 @@ module Spree
|
|
|
311
311
|
params.require(:product).permit(permitted_product_attributes)
|
|
312
312
|
end
|
|
313
313
|
parse_datetime_in_store_timezone(attrs, :available_on, :discontinue_on, :make_active_at)
|
|
314
|
+
parse_datetime_in_store_timezone(attrs[:master_attributes], :preorder_ships_at) if attrs[:master_attributes].present?
|
|
314
315
|
attrs
|
|
315
316
|
end
|
|
316
317
|
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Admin
|
|
5
|
+
class StorefrontController < BaseController
|
|
6
|
+
include Spree::Admin::SettingsConcern
|
|
7
|
+
|
|
8
|
+
# GET /admin/storefront
|
|
9
|
+
def show
|
|
10
|
+
@breadcrumb_icon = 'building-store'
|
|
11
|
+
add_breadcrumb Spree.t('admin.storefront_setup.title'), spree.admin_storefront_path
|
|
12
|
+
|
|
13
|
+
@publishable_key = find_or_create_publishable_key
|
|
14
|
+
@deployment_origin = normalize_origin(params[:'deployment-url'])
|
|
15
|
+
@deployment_origin_allowed = @deployment_origin.present? && current_store.allowed_origin?(@deployment_origin)
|
|
16
|
+
@vercel_dashboard_url = vercel_dashboard_url
|
|
17
|
+
@storefront_origins = current_store.allowed_origins.order(:created_at).reject(&:loopback?)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# PATCH /admin/storefront
|
|
21
|
+
#
|
|
22
|
+
# Saves the storefront URL preference (used as the base for links in
|
|
23
|
+
# customer emails and as the setup-task completion signal) and adds it
|
|
24
|
+
# as an allowed origin so browser-based storefronts can call the Store API.
|
|
25
|
+
def update
|
|
26
|
+
origin = normalize_origin(params[:storefront_url])
|
|
27
|
+
|
|
28
|
+
if origin.nil?
|
|
29
|
+
flash[:error] = Spree.t('admin.storefront_setup.invalid_origin')
|
|
30
|
+
elsif current_store.update(preferred_storefront_url: origin)
|
|
31
|
+
current_store.allowed_origins.find_or_create_by(origin: origin)
|
|
32
|
+
flash[:success] = Spree.t('admin.storefront_setup.storefront_url_saved', url: origin)
|
|
33
|
+
else
|
|
34
|
+
flash[:error] = current_store.errors.full_messages.to_sentence
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
redirect_to spree.admin_storefront_path, status: :see_other
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# POST /admin/storefront/allow_origin
|
|
41
|
+
def allow_origin
|
|
42
|
+
origin = normalize_origin(params[:origin])
|
|
43
|
+
|
|
44
|
+
if origin.nil?
|
|
45
|
+
flash[:error] = Spree.t('admin.storefront_setup.invalid_origin')
|
|
46
|
+
else
|
|
47
|
+
allowed_origin = current_store.allowed_origins.find_or_initialize_by(origin: origin)
|
|
48
|
+
|
|
49
|
+
if allowed_origin.persisted? || allowed_origin.save
|
|
50
|
+
current_store.update(preferred_storefront_url: origin) if current_store.preferred_storefront_url.blank?
|
|
51
|
+
flash[:success] = Spree.t('admin.storefront_setup.origin_allowed', origin: origin)
|
|
52
|
+
else
|
|
53
|
+
flash[:error] = allowed_origin.errors.full_messages.to_sentence
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
redirect_to spree.admin_storefront_path, status: :see_other
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# Every action here is store configuration (publishable keys, allowed
|
|
63
|
+
# origins, store preferences), so gate the whole page behind store
|
|
64
|
+
# management — granted by e.g. Spree::PermissionSets::ConfigurationManagement.
|
|
65
|
+
def authorize_admin
|
|
66
|
+
authorize! :admin, Spree::Store
|
|
67
|
+
authorize! :update, current_store
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The storefront needs a publishable key; reuse the oldest active one
|
|
71
|
+
# (usually the seeded "Default") and mint one for stores that have none.
|
|
72
|
+
def find_or_create_publishable_key
|
|
73
|
+
current_store.api_keys.active.publishable.order(:created_at).first ||
|
|
74
|
+
current_store.api_keys.create!(
|
|
75
|
+
name: 'Storefront',
|
|
76
|
+
key_type: 'publishable',
|
|
77
|
+
created_by: try_spree_current_user
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Normalizes user or Vercel-callback input to a canonical origin string,
|
|
82
|
+
# or nil when it's not a valid http(s) URL — pre-normalized here (rather
|
|
83
|
+
# than leaning on the Store validation) so invalid input gets the
|
|
84
|
+
# dedicated invalid_origin flash.
|
|
85
|
+
def normalize_origin(raw)
|
|
86
|
+
Spree::AllowedOrigin.normalize_origin(raw)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Only link back to the Vercel dashboard when the callback param actually
|
|
90
|
+
# points at vercel.com — never reflect an arbitrary URL as a link.
|
|
91
|
+
def vercel_dashboard_url
|
|
92
|
+
url = params[:'project-dashboard-url'].to_s
|
|
93
|
+
parsed = Spree::AllowedOrigin.parse_origin(url)
|
|
94
|
+
return unless parsed && parsed[:scheme] == 'https'
|
|
95
|
+
|
|
96
|
+
url if parsed[:host] == 'vercel.com' || parsed[:host].end_with?('.vercel.com')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -2,9 +2,19 @@ module Spree
|
|
|
2
2
|
module Admin
|
|
3
3
|
class UserSessionsController < defined?(Devise::SessionsController) ? Devise::SessionsController : Spree::Admin::BaseController
|
|
4
4
|
include Spree::Admin::AuthRateLimiting
|
|
5
|
+
include Spree::Admin::LocaleConcern
|
|
6
|
+
|
|
7
|
+
helper 'spree/locale'
|
|
8
|
+
helper 'spree/admin/rtl'
|
|
5
9
|
|
|
6
10
|
layout 'spree/minimal'
|
|
7
11
|
|
|
12
|
+
# This controller inherits from Devise::SessionsController, so the
|
|
13
|
+
# `set_locale` before_action from Spree::Core::ControllerHelpers::Locale
|
|
14
|
+
# (mixed into Spree::Admin::BaseController) never runs here — opt into
|
|
15
|
+
# the concern's pre-auth locale handling instead (see LocaleConcern).
|
|
16
|
+
before_action :set_login_locale
|
|
17
|
+
|
|
8
18
|
auth_rate_limit :rate_limit_login, redirect_to: -> { new_session_path(resource_name) }
|
|
9
19
|
|
|
10
20
|
# We need to overwrite this action because `return_to` url may be in a different domain
|
|
@@ -96,7 +96,9 @@ module Spree
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def permitted_resource_params
|
|
99
|
-
params.require(:variant).permit(permitted_variant_attributes)
|
|
99
|
+
attrs = params.require(:variant).permit(permitted_variant_attributes)
|
|
100
|
+
parse_datetime_in_store_timezone(attrs, :preorder_ships_at)
|
|
101
|
+
attrs
|
|
100
102
|
end
|
|
101
103
|
end
|
|
102
104
|
end
|
|
@@ -41,6 +41,11 @@ module Spree
|
|
|
41
41
|
@available_countries_iso ||= current_store.countries_available_for_checkout.pluck(:iso)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# @return [Array<Spree::Country>] the available countries for checkout
|
|
45
|
+
def available_countries
|
|
46
|
+
@available_countries ||= available_countries_iso.map { |iso| Spree::Country.new(iso: iso) }
|
|
47
|
+
end
|
|
48
|
+
|
|
44
49
|
# returns the available display on options, eg backend, frontend, both
|
|
45
50
|
# @return [Array<Array<String, String>>] the available display on options
|
|
46
51
|
def display_on_options(model = nil)
|
|
@@ -360,6 +365,11 @@ module Spree
|
|
|
360
365
|
return Spree.t(:none) if market_ids.empty?
|
|
361
366
|
|
|
362
367
|
Spree::Market.where(id: market_ids).pluck(:name).join(', ')
|
|
368
|
+
when :channel_ids
|
|
369
|
+
channel_ids = Array(value).reject(&:blank?).map(&:to_i)
|
|
370
|
+
return Spree.t(:none) if channel_ids.empty?
|
|
371
|
+
|
|
372
|
+
Spree::Channel.where(id: channel_ids).pluck(:name).join(', ')
|
|
363
373
|
when :user_ids
|
|
364
374
|
user_ids = Array(value).reject(&:blank?).map(&:to_i)
|
|
365
375
|
return Spree.t(:none) if user_ids.empty?
|
|
@@ -9,6 +9,23 @@ module Spree
|
|
|
9
9
|
def channel_order_routing_strategy_options
|
|
10
10
|
Spree.order_routing.strategies.map { |strategy| [strategy.display_name, strategy.to_s] }
|
|
11
11
|
end
|
|
12
|
+
|
|
13
|
+
# Storefront access levels for the channel edit form. A blank selection
|
|
14
|
+
# clears the channel override and falls back to +Store#preferred_storefront_access+.
|
|
15
|
+
def channel_storefront_access_options
|
|
16
|
+
Spree::Channel::Gating::STOREFRONT_ACCESS.map do |value|
|
|
17
|
+
[Spree.t("admin.channels.storefront_access_options.#{value}"), value]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Tri-state guest-checkout override for the channel edit form. A blank
|
|
22
|
+
# selection clears the override and falls back to +Store#preferred_guest_checkout+.
|
|
23
|
+
def channel_guest_checkout_options
|
|
24
|
+
[
|
|
25
|
+
[Spree.t('admin.channels.guest_checkout_allowed'), 'true'],
|
|
26
|
+
[Spree.t('admin.channels.guest_checkout_blocked'), 'false']
|
|
27
|
+
]
|
|
28
|
+
end
|
|
12
29
|
end
|
|
13
30
|
end
|
|
14
31
|
end
|
|
@@ -16,6 +16,8 @@ module Spree
|
|
|
16
16
|
options[:placement] || 'bottom-start'
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
placement = flip_placement_for_rtl(placement)
|
|
20
|
+
|
|
19
21
|
# Extract portal option
|
|
20
22
|
portal = options.delete(:portal)
|
|
21
23
|
|
|
@@ -49,6 +51,18 @@ module Spree
|
|
|
49
51
|
|
|
50
52
|
content_tag(:div, options, &block)
|
|
51
53
|
end
|
|
54
|
+
|
|
55
|
+
def flip_placement_for_rtl(placement)
|
|
56
|
+
return placement unless rtl_locale?
|
|
57
|
+
|
|
58
|
+
case placement
|
|
59
|
+
when 'bottom-start' then 'bottom-end'
|
|
60
|
+
when 'bottom-end' then 'bottom-start'
|
|
61
|
+
when 'top-start' then 'top-end'
|
|
62
|
+
when 'top-end' then 'top-start'
|
|
63
|
+
else placement
|
|
64
|
+
end
|
|
65
|
+
end
|
|
52
66
|
end
|
|
53
67
|
end
|
|
54
68
|
end
|
|
@@ -404,7 +404,7 @@ module Spree
|
|
|
404
404
|
result = label.to_s
|
|
405
405
|
if badge_value.present?
|
|
406
406
|
css_class = badge_class.presence || 'badge-light'
|
|
407
|
-
result += content_tag(:span, badge_value, class: "badge
|
|
407
|
+
result += content_tag(:span, badge_value, class: "badge ms-auto #{css_class}")
|
|
408
408
|
end
|
|
409
409
|
if tooltip_text.present?
|
|
410
410
|
result += tooltip(tooltip_text)
|
|
@@ -10,7 +10,7 @@ module Spree
|
|
|
10
10
|
if condition == true
|
|
11
11
|
icon('check', class: 'rounded-full inline-flex items-center bg-green-200 text-green-600 border-green-200 p-1')
|
|
12
12
|
else
|
|
13
|
-
icon('check', class: 'rounded-full inline-flex items-center border-dashed text-gray-300 p-1')
|
|
13
|
+
icon('circle-dashed-check', class: 'rounded-full inline-flex items-center border-dashed text-gray-300 p-1')
|
|
14
14
|
end.html_safe
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -82,44 +82,19 @@ module Spree
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def avs_response_code
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
'C' => 'Street address and postal code do not match.',
|
|
89
|
-
'D' => 'Street address and postal code match. ',
|
|
90
|
-
'E' => 'AVS data is invalid or AVS is not allowed for this card type.',
|
|
91
|
-
'F' => "Card member's name does not match, but billing postal code matches.",
|
|
92
|
-
'G' => 'Non-U.S. issuing bank does not support AVS.',
|
|
93
|
-
'H' => "Card member's name does not match. Street address and postal code match.",
|
|
94
|
-
'I' => 'Address not verified.',
|
|
95
|
-
'J' => "Card member's name, billing address, and postal code match.",
|
|
96
|
-
'K' => "Card member's name matches but billing address and billing postal code do not match.",
|
|
97
|
-
'L' => "Card member's name and billing postal code match, but billing address does not match.",
|
|
98
|
-
'M' => 'Street address and postal code match. ',
|
|
99
|
-
'N' => 'Street address and postal code do not match.',
|
|
100
|
-
'O' => "Card member's name and billing address match, but billing postal code does not match.",
|
|
101
|
-
'P' => 'Postal code matches, but street address not verified.',
|
|
102
|
-
'Q' => "Card member's name, billing address, and postal code match.",
|
|
103
|
-
'R' => 'System unavailable.',
|
|
104
|
-
'S' => 'Bank does not support AVS.',
|
|
105
|
-
'T' => "Card member's name does not match, but street address matches.",
|
|
106
|
-
'U' => 'Address information unavailable. Returned if the U.S. bank does not support non-U.S. AVS or if the AVS in a U.S. bank is not functioning properly.',
|
|
107
|
-
'V' => "Card member's name, billing address, and billing postal code match.",
|
|
108
|
-
'W' => 'Street address does not match, but 9-digit postal code matches.',
|
|
109
|
-
'X' => 'Street address and 9-digit postal code match.',
|
|
110
|
-
'Y' => 'Street address and 5-digit postal code match.',
|
|
111
|
-
'Z' => 'Street address does not match, but 5-digit postal code matches.'
|
|
112
|
-
}
|
|
85
|
+
('A'..'Z').each_with_object({}) do |code, codes|
|
|
86
|
+
codes[code] = Spree.t("admin.orders.avs_responses.#{code.downcase}")
|
|
87
|
+
end
|
|
113
88
|
end
|
|
114
89
|
|
|
115
90
|
def cvv_response_code
|
|
116
91
|
{
|
|
117
|
-
'M' => '
|
|
118
|
-
'N' => '
|
|
119
|
-
'P' => '
|
|
120
|
-
'S' => '
|
|
121
|
-
'U' => '
|
|
122
|
-
'' => '
|
|
92
|
+
'M' => Spree.t('admin.orders.cvv_responses.m'),
|
|
93
|
+
'N' => Spree.t('admin.orders.cvv_responses.n'),
|
|
94
|
+
'P' => Spree.t('admin.orders.cvv_responses.p'),
|
|
95
|
+
'S' => Spree.t('admin.orders.cvv_responses.s'),
|
|
96
|
+
'U' => Spree.t('admin.orders.cvv_responses.u'),
|
|
97
|
+
'' => Spree.t('admin.orders.cvv_responses.blank')
|
|
123
98
|
}
|
|
124
99
|
end
|
|
125
100
|
end
|
|
@@ -29,7 +29,7 @@ module Spree
|
|
|
29
29
|
|
|
30
30
|
def product_status(product)
|
|
31
31
|
if product.deleted?
|
|
32
|
-
content_tag(:span, '
|
|
32
|
+
content_tag(:span, Spree.t('admin.products.deleted'), class: 'badge badge-danger')
|
|
33
33
|
else
|
|
34
34
|
content_tag(:span, class: "badge badge-#{product.status}") do
|
|
35
35
|
if product.active?
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
# View helpers for the legacy admin's layout direction (`<html dir>`).
|
|
4
|
+
# RTL detection itself lives on Spree::Locale (the single source of truth).
|
|
5
|
+
module RtlHelper
|
|
6
|
+
def rtl_locale?(locale = I18n.locale)
|
|
7
|
+
Spree::Locale.new(code: locale).rtl?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def html_dir(locale = I18n.locale)
|
|
11
|
+
Spree::Locale.new(code: locale).direction
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -12,7 +12,7 @@ module Spree::Admin
|
|
|
12
12
|
where(spree_stock_items: { variant_id: variant.id }).
|
|
13
13
|
group(:id).
|
|
14
14
|
pluck(:name, "sum(spree_stock_items.count_on_hand)", :id).
|
|
15
|
-
map { |name, count, id| [
|
|
15
|
+
map { |name, count, id| [Spree.t('admin.shipments.stock_location_option', name: name, count: count), "stock-location_#{id}"] }
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def shipments_for_transfer(current_shipment)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Admin
|
|
5
|
+
module StorefrontHelper
|
|
6
|
+
STOREFRONT_REPOSITORY_URL = 'https://github.com/spree/storefront'
|
|
7
|
+
|
|
8
|
+
# Builds the Vercel deploy-button URL for the storefront starter, prefilled
|
|
9
|
+
# with the store's API URL and publishable key (both non-secret), and
|
|
10
|
+
# redirecting back to the admin storefront page after a successful deploy
|
|
11
|
+
# so the deployed domain can be added as an allowed origin.
|
|
12
|
+
#
|
|
13
|
+
# @param store [Spree::Store]
|
|
14
|
+
# @param api_key [Spree::ApiKey] an active publishable key
|
|
15
|
+
# @return [String]
|
|
16
|
+
def vercel_deploy_url(store, api_key)
|
|
17
|
+
query = {
|
|
18
|
+
'repository-url' => STOREFRONT_REPOSITORY_URL,
|
|
19
|
+
'project-name' => "#{store.code}-storefront",
|
|
20
|
+
'repository-name' => "#{store.code}-storefront",
|
|
21
|
+
'env' => 'SPREE_API_URL,SPREE_PUBLISHABLE_KEY',
|
|
22
|
+
'envDefaults' => {
|
|
23
|
+
'SPREE_API_URL' => store.formatted_url,
|
|
24
|
+
'SPREE_PUBLISHABLE_KEY' => api_key.token
|
|
25
|
+
}.to_json,
|
|
26
|
+
'envDescription' => Spree.t('admin.storefront_setup.env_description'),
|
|
27
|
+
'envLink' => "#{STOREFRONT_REPOSITORY_URL}#readme",
|
|
28
|
+
'redirect-url' => spree.admin_storefront_url
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
"https://vercel.com/new/clone?#{query.to_query}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# True when the store's public URL points at a loopback host, which
|
|
35
|
+
# Vercel's build servers cannot reach.
|
|
36
|
+
#
|
|
37
|
+
# @param store [Spree::Store]
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
def store_url_loopback?(store = current_store)
|
|
40
|
+
Spree::AllowedOrigin::LOOPBACK_HOSTS.include?(
|
|
41
|
+
Spree::AllowedOrigin.parse_origin(store.formatted_url)&.dig(:host)
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -30,7 +30,8 @@ module Spree
|
|
|
30
30
|
opts[:width] ||= DEFAULT_ICON_SIZE
|
|
31
31
|
opts[:class] ||= ''
|
|
32
32
|
opts[:class] += ' store-icon rounded-lg text-gray-900 flex items-center justify-center'
|
|
33
|
-
|
|
33
|
+
letter = name.to_s.first&.upcase
|
|
34
|
+
content_tag(:span, letter, class: opts[:class], style: "height: #{opts[:height]}px; width: #{opts[:width]}px;")
|
|
34
35
|
end
|
|
35
36
|
end
|
|
36
37
|
end
|
|
@@ -55,6 +55,7 @@ import CodeMirrorController from 'spree/admin/controllers/codemirror_controller'
|
|
|
55
55
|
import ColorPaletteController from 'spree/admin/controllers/color_palette_controller'
|
|
56
56
|
import ColumnSelectorController from 'spree/admin/controllers/column_selector_controller'
|
|
57
57
|
import ColorPickerController from 'spree/admin/controllers/color_picker_controller'
|
|
58
|
+
import DisplayNameController from 'spree/admin/controllers/display_name_controller'
|
|
58
59
|
import DropdownController from 'spree/admin/controllers/dropdown_controller'
|
|
59
60
|
import FiltersController from 'spree/admin/controllers/filters_controller'
|
|
60
61
|
import FontPickerController from 'spree/admin/controllers/font_picker_controller'
|
|
@@ -120,6 +121,7 @@ application.register('codemirror', CodeMirrorController)
|
|
|
120
121
|
application.register('color-palette', ColorPaletteController)
|
|
121
122
|
application.register('color-picker', ColorPickerController)
|
|
122
123
|
application.register('column-selector', ColumnSelectorController)
|
|
124
|
+
application.register('display-name', DisplayNameController)
|
|
123
125
|
application.register('dialog', Dialog)
|
|
124
126
|
application.register('drawer', Dialog)
|
|
125
127
|
application.register('disable-submit-button', DisableSubmitButtonController)
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
import TomSelect from 'tom-select/dist/esm/tom-select.complete.js';
|
|
3
|
+
import { localizeSelectOptions } from 'spree/admin/helpers/display_names'
|
|
3
4
|
|
|
4
5
|
// this is a very simple autocomplete select controller that uses TomSelect
|
|
5
6
|
// by default it does not support remote data fetching or adding new options
|
|
6
7
|
// that scenarios are handled in the more robust select_controller.js
|
|
7
8
|
export default class extends Controller {
|
|
8
9
|
connect() {
|
|
10
|
+
const displayNamesType = this.element.dataset.displayNamesType
|
|
11
|
+
if (displayNamesType) localizeSelectOptions(this.element, displayNamesType)
|
|
12
|
+
|
|
9
13
|
let selectedValue = this.element.value
|
|
10
14
|
|
|
11
15
|
let plugins = []
|
|
@@ -5,6 +5,12 @@ import { PresetPlugin } from '@easepick/preset-plugin';
|
|
|
5
5
|
|
|
6
6
|
export default class extends Controller {
|
|
7
7
|
static targets = [ "picker", "dateFrom", "dateTo", "label" ]
|
|
8
|
+
static values = {
|
|
9
|
+
presetLabels: {
|
|
10
|
+
type: Array,
|
|
11
|
+
default: ['Today', 'Yesterday', 'Last 7 Days', 'Last 30 Days', 'This Month', 'Last Month']
|
|
12
|
+
}
|
|
13
|
+
}
|
|
8
14
|
|
|
9
15
|
connect() {
|
|
10
16
|
this.picker = new easepick.create({
|
|
@@ -23,9 +29,7 @@ export default class extends Controller {
|
|
|
23
29
|
endDate: this.dateToTarget.value,
|
|
24
30
|
},
|
|
25
31
|
PresetPlugin: {
|
|
26
|
-
customLabels:
|
|
27
|
-
'Last 7 Days', 'Last 30 Days',
|
|
28
|
-
'This Month', 'Last Month'],
|
|
32
|
+
customLabels: this.presetLabelsValue,
|
|
29
33
|
position: 'left'
|
|
30
34
|
},
|
|
31
35
|
lang: Spree.locale
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
import { adminUiLocale, formatCodeName, intlDisplayName } from 'spree/admin/helpers/display_names'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Upgrades a server-rendered code/name label to its `Intl.DisplayNames`
|
|
6
|
+
* equivalent in the admin UI language once JS is available.
|
|
7
|
+
*/
|
|
8
|
+
export default class extends Controller {
|
|
9
|
+
static values = {
|
|
10
|
+
type: String,
|
|
11
|
+
code: String
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
connect() {
|
|
15
|
+
const name = intlDisplayName(this.typeValue, this.codeValue, adminUiLocale())
|
|
16
|
+
if (name) this.element.textContent = formatCodeName(this.codeValue, name)
|
|
17
|
+
}
|
|
18
|
+
}
|