diff --git a/backend/config/routes.rb b/backend/config/routes.rb
index f8c70640e61..343d1bc028e 100644
--- a/backend/config/routes.rb
+++ b/backend/config/routes.rb
@@ -6,6 +6,7 @@
get '/search/products', to: "search#products", as: :search_products
put '/locale/set', to: 'locale#set', defaults: { format: :json }, as: :set_locale
+ put '/theme/set', to: 'theme#set', defaults: { format: :json }, as: :set_theme
resources :dashboards, only: [] do
collection do
diff --git a/backend/lib/spree/backend_configuration.rb b/backend/lib/spree/backend_configuration.rb
index 9291730b0d1..2e801149e93 100644
--- a/backend/lib/spree/backend_configuration.rb
+++ b/backend/lib/spree/backend_configuration.rb
@@ -11,15 +11,24 @@ class BackendConfiguration < Preferences::Configuration
# @return [Hash] A hash containing the themes that are available for the admin panel
preference :themes, :hash, default: {
classic: 'spree/backend/all',
+ classic_dark: 'spree/backend/themes/classic_dark',
+ classic_dark_dimmed: 'spree/backend/themes/classic_dimmed',
+ solidus: 'spree/backend/themes/solidus_admin',
+ solidus_dark: 'spree/backend/themes/solidus_admin_dark',
+ solidus_dimmed: 'spree/backend/themes/solidus_admin_dimmed',
solidus_admin: 'spree/backend/themes/solidus_admin'
}
# @!attribute [rw] theme
# @return [String] Default admin theme name
- versioned_preference :theme, :string, initial_value: 'classic', boundaries: { "4.2.0" => "solidus_admin" }
+ versioned_preference :theme, :string, initial_value: 'classic', boundaries: { "4.2.0" => "solidus_admin", "4.4.0" => "solidus" }
- def theme_path(user_theme = nil)
- user_theme ? themes.fetch(user_theme.to_sym) : themes.fetch(theme.to_sym)
+ # @!attribute [rw] dark_theme
+ # @return [String] Dark admin theme name
+ versioned_preference :dark_theme, :string, initial_value: 'classic', boundaries: { "4.2.0" => "solidus_admin", "4.4.0" => 'solidus_dark' }
+
+ def theme_path(user_theme)
+ themes.fetch(user_theme&.to_sym, themes.fetch(theme.to_sym))
end
# @!attribute [rw] admin_updated_navbar
diff --git a/backend/spec/controllers/spree/admin/theme_controller_spec.rb b/backend/spec/controllers/spree/admin/theme_controller_spec.rb
new file mode 100644
index 00000000000..6ba9984f93b
--- /dev/null
+++ b/backend/spec/controllers/spree/admin/theme_controller_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Spree::Admin::ThemeController, type: :controller do
+ stub_authorization!
+
+ it 'sets the theme in a different session key for each system theme' do
+ stub_spree_preferences(Spree::Backend::Config, themes: { foo: 'foo-path', bar: 'bar-path' })
+
+ get :set, params: { switch_to_theme: 'foo', system_theme: 'light', format: :json }
+
+ expect(session[:admin_light_theme]).to eq('foo')
+ expect(session[:admin_dark_theme]).to eq(nil)
+ expect(response).to have_http_status(:redirect)
+
+ get :set, params: { switch_to_theme: 'bar', system_theme: 'dark', format: :json }
+ expect(session[:admin_light_theme]).to eq('foo')
+ expect(session[:admin_dark_theme]).to eq('bar')
+ expect(response).to have_http_status(:redirect)
+ end
+
+ it 'responds with "not found" for a missing theme' do
+ stub_spree_preferences(Spree::Backend::Config, themes: { foo: 'foo-path' })
+
+ get :set, params: { switch_to_theme: 'bar', system_theme: 'dark', format: :json }
+
+ expect(session[:admin_light_theme]).to eq(nil)
+ expect(session[:admin_dark_theme]).to eq(nil)
+ expect(response).to have_http_status(:redirect)
+ end
+end
diff --git a/backend/spec/lib/spree/backend_configuration_spec.rb b/backend/spec/lib/spree/backend_configuration_spec.rb
index ce716412003..75abd233c42 100644
--- a/backend/spec/lib/spree/backend_configuration_spec.rb
+++ b/backend/spec/lib/spree/backend_configuration_spec.rb
@@ -79,25 +79,25 @@ def product_path(product)
end
describe '#theme_path' do
- it 'returns the default theme path' do
+ it 'returns the default theme path if the user theme is not set' do
subject.themes = { foo: 'foo-theme-path' }
subject.theme = :foo
- expect(subject.theme_path).to eq('foo-theme-path')
+ expect(subject.theme_path(nil)).to eq('foo-theme-path')
end
it 'returns the default theme path when the theme is a string' do
subject.themes = { foo: 'foo-theme-path' }
subject.theme = 'foo'
- expect(subject.theme_path).to eq('foo-theme-path')
+ expect(subject.theme_path(nil)).to eq('foo-theme-path')
end
it 'returns the fallback theme path when the default theme is missing' do
subject.themes = { foo: 'foo-theme-path', classic: 'classic-theme-path' }
subject.theme = :bar
- expect{ subject.theme_path }.to raise_error(KeyError)
+ expect{ subject.theme_path(:baz) }.to raise_error(KeyError)
end
it 'gives priority to the user defined theme' do
@@ -107,11 +107,11 @@ def product_path(product)
expect(subject.theme_path(:user)).to eq('user-theme-path')
end
- it 'raises an error if the user theme is missing' do
+ it 'falls back to the configure theme if the user theme is missing' do
subject.themes = { foo: 'foo-theme-path', classic: 'classic-theme-path' }
subject.theme = :foo
- expect{ subject.theme_path(:bar) }.to raise_error(KeyError)
+ expect(subject.theme_path(:bar)).to eq('foo-theme-path')
end
end
diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml
index 1327ebc708d..8c0cd525e3a 100644
--- a/core/config/locales/en.yml
+++ b/core/config/locales/en.yml
@@ -1141,6 +1141,7 @@ en:
choose_a_taxon_to_sort_products_for: Choose a taxon to sort products for
choose_currency: Choose Currency
choose_dashboard_locale: Choose Dashboard Locale
+ choose_dashboard_theme: Choose Dashboard Theme
choose_location: Choose Location
choose_promotion_action: Choose Action
choose_promotion_rule: Choose Rule
@@ -2347,6 +2348,7 @@ en:
subject: Test Mail
test_mode: Test Mode
thank_you_for_your_order: Thank you for your business. Please print out a copy of this confirmation page for your records.
+ theme_changed: Theme Changed
there_are_no_items_for_this_order: There are no items for this order. Please add an item to the order to continue.
there_were_problems_with_the_following_fields: There were problems with the following fields
this_order_has_already_received_a_refund: This order has already received a refund