From 19cfd4be4c9519ec9119865024c3677a691532a9 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Fri, 21 Feb 2025 17:01:36 -0800 Subject: [PATCH] chore: Clean up the feature presentation in super admin (#10949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - improve styling of the feature toggle displayed in super admin Screenshot 2025-02-21 at 4 47 58 PM Screenshot 2025-02-21 at 4 48 11 PM --------- Co-authored-by: Pranav --- .../super_admin/account_features_helper.rb | 43 +++- .../assets/scss/super_admin/index.scss | 206 ++++++++++++++++++ .../account_features_field/_form.html.erb | 42 ++-- .../account_features_field/_show.html.erb | 46 ++-- .../accounts/_reset_cache.html.erb | 4 +- .../super_admin/accounts/_seed_data.html.erb | 9 +- app/views/super_admin/accounts/show.html.erb | 197 ++++++++++------- app/views/super_admin/settings/show.html.erb | 53 +++-- config/features.yml | 55 ++++- 9 files changed, 512 insertions(+), 143 deletions(-) diff --git a/app/helpers/super_admin/account_features_helper.rb b/app/helpers/super_admin/account_features_helper.rb index 515c82000..c24e65e68 100644 --- a/app/helpers/super_admin/account_features_helper.rb +++ b/app/helpers/super_admin/account_features_helper.rb @@ -7,13 +7,46 @@ module SuperAdmin::AccountFeaturesHelper account_features.filter { |feature| feature['premium'] }.pluck('name') end - # Accepts account.features as argument - def self.filtered_features(features) - deployment_env = GlobalConfig.get_value('DEPLOYMENT_ENV') - return features if deployment_env == 'cloud' + # Returns a hash mapping feature names to their display names + def self.feature_display_names + account_features.each_with_object({}) do |feature, hash| + hash[feature['name']] = feature['display_name'] + end + end + + def self.filter_internal_features(features) + return features if GlobalConfig.get_value('DEPLOYMENT_ENV') == 'cloud' - # Filter out internal features for non-cloud environments internal_features = account_features.select { |f| f['chatwoot_internal'] }.pluck('name') features.except(*internal_features) end + + def self.filter_deprecated_features(features) + deprecated_features = account_features.select { |f| f['deprecated'] }.pluck('name') + features.except(*deprecated_features) + end + + def self.sort_and_transform_features(features, display_names) + features.sort_by { |key, _| display_names[key] || key } + .to_h + .transform_keys { |key| [key, display_names[key]] } + end + + def self.partition_features(features) + filtered = filter_internal_features(features) + filtered = filter_deprecated_features(filtered) + display_names = feature_display_names + + regular, premium = filtered.partition { |key, _value| account_premium_features.exclude?(key) } + + [ + sort_and_transform_features(regular, display_names), + sort_and_transform_features(premium, display_names) + ] + end + + def self.filtered_features(features) + regular, premium = partition_features(features) + regular.merge(premium) + end end diff --git a/app/javascript/dashboard/assets/scss/super_admin/index.scss b/app/javascript/dashboard/assets/scss/super_admin/index.scss index 91f7835d0..ddac5f7c8 100644 --- a/app/javascript/dashboard/assets/scss/super_admin/index.scss +++ b/app/javascript/dashboard/assets/scss/super_admin/index.scss @@ -6,3 +6,209 @@ body { font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; } + +@layer base { + // FIXME: Use a common color file for all packs + // scss-lint:disable PropertySortOrder + :root { + --slate-1: 252 252 253; + --slate-2: 249 249 251; + --slate-3: 240 240 243; + --slate-4: 232 232 236; + --slate-5: 224 225 230; + --slate-6: 217 217 224; + --slate-7: 205 206 214; + --slate-8: 185 187 198; + --slate-9: 139 141 152; + --slate-10: 128 131 141; + --slate-11: 96 100 108; + --slate-12: 28 32 36; + + --iris-1: 253 253 255; + --iris-2: 248 248 255; + --iris-3: 240 241 254; + --iris-4: 230 231 255; + --iris-5: 218 220 255; + --iris-6: 203 205 255; + --iris-7: 184 186 248; + --iris-8: 155 158 240; + --iris-9: 91 91 214; + --iris-10: 81 81 205; + --iris-11: 87 83 198; + --iris-12: 39 41 98; + + --ruby-1: 255 252 253; + --ruby-2: 255 247 248; + --ruby-3: 254 234 237; + --ruby-4: 255 220 225; + --ruby-5: 255 206 214; + --ruby-6: 248 191 200; + --ruby-7: 239 172 184; + --ruby-8: 229 146 163; + --ruby-9: 229 70 102; + --ruby-10: 220 59 93; + --ruby-11: 202 36 77; + --ruby-12: 100 23 43; + + --amber-1: 254 253 251; + --amber-2: 254 251 233; + --amber-3: 255 247 194; + --amber-4: 255 238 156; + --amber-5: 251 229 119; + --amber-6: 243 214 115; + --amber-7: 233 193 98; + --amber-8: 226 163 54; + --amber-9: 255 197 61; + --amber-10: 255 186 24; + --amber-11: 171 100 0; + --amber-12: 79 52 34; + + --teal-1: 250 254 253; + --teal-2: 243 251 249; + --teal-3: 224 248 243; + --teal-4: 204 243 234; + --teal-5: 184 234 224; + --teal-6: 161 222 210; + --teal-7: 131 205 193; + --teal-8: 83 185 171; + --teal-9: 18 165 148; + --teal-10: 13 155 138; + --teal-11: 0 133 115; + --teal-12: 13 61 56; + + --gray-1: 252 252 252; + --gray-2: 249 249 249; + --gray-3: 240 240 240; + --gray-4: 232 232 232; + --gray-5: 224 224 224; + --gray-6: 217 217 217; + --gray-7: 206 206 206; + --gray-8: 187 187 187; + --gray-9: 141 141 141; + --gray-10: 131 131 131; + --gray-11: 100 100 100; + --gray-12: 32 32 32; + + --background-color: 253 253 253; + --text-blue: 8 109 224; + --border-container: 236 236 236; + --border-strong: 235 235 235; + --border-weak: 234 234 234; + --solid-1: 255 255 255; + --solid-2: 255 255 255; + --solid-3: 255 255 255; + --solid-active: 255 255 255; + --solid-amber: 252 232 193; + --solid-blue: 218 236 255; + --solid-iris: 230 231 255; + + --alpha-1: 67, 67, 67, 0.06; + --alpha-2: 201, 202, 207, 0.15; + --alpha-3: 255, 255, 255, 0.96; + --black-alpha-1: 0, 0, 0, 0.12; + --black-alpha-2: 0, 0, 0, 0.04; + --border-blue: 39, 129, 246, 0.5; + --white-alpha: 255, 255, 255, 0.8; + } + + .dark { + --slate-1: 17 17 19; + --slate-2: 24 25 27; + --slate-3: 33 34 37; + --slate-4: 39 42 45; + --slate-5: 46 49 53; + --slate-6: 54 58 63; + --slate-7: 67 72 78; + --slate-8: 90 97 105; + --slate-9: 105 110 119; + --slate-10: 119 123 132; + --slate-11: 176 180 186; + --slate-12: 237 238 240; + + --iris-1: 19 19 30; + --iris-2: 23 22 37; + --iris-3: 32 34 72; + --iris-4: 38 42 101; + --iris-5: 48 51 116; + --iris-6: 61 62 130; + --iris-7: 74 74 149; + --iris-8: 89 88 177; + --iris-9: 91 91 214; + --iris-10: 84 114 228; + --iris-11: 158 177 255; + --iris-12: 224 223 254; + + --ruby-1: 25 17 19; + --ruby-2: 30 21 23; + --ruby-3: 58 20 30; + --ruby-4: 78 19 37; + --ruby-5: 94 26 46; + --ruby-6: 111 37 57; + --ruby-7: 136 52 71; + --ruby-8: 179 68 90; + --ruby-9: 229 70 102; + --ruby-10: 236 90 114; + --ruby-11: 255 148 157; + --ruby-12: 254 210 225; + + --amber-1: 22 18 12; + --amber-2: 29 24 15; + --amber-3: 48 32 8; + --amber-4: 63 39 0; + --amber-5: 77 48 0; + --amber-6: 92 61 5; + --amber-7: 113 79 25; + --amber-8: 143 100 36; + --amber-9: 255 197 61; + --amber-10: 255 214 10; + --amber-11: 255 202 22; + --amber-12: 255 231 179; + + --teal-1: 13 21 20; + --teal-2: 17 28 27; + --teal-3: 13 45 42; + --teal-4: 2 59 55; + --teal-5: 8 72 67; + --teal-6: 20 87 80; + --teal-7: 28 105 97; + --teal-8: 32 126 115; + --teal-9: 18 165 148; + --teal-10: 14 179 158; + --teal-11: 11 216 182; + --teal-12: 173 240 221; + + --gray-1: 17 17 17; + --gray-2: 25 25 25; + --gray-3: 34 34 34; + --gray-4: 42 42 42; + --gray-5: 49 49 49; + --gray-6: 58 58 58; + --gray-7: 72 72 72; + --gray-8: 96 96 96; + --gray-9: 110 110 110; + --gray-10: 123 123 123; + --gray-11: 180 180 180; + --gray-12: 238 238 238; + + --background-color: 18 18 19; + --border-strong: 52 52 52; + --border-weak: 38 38 42; + --solid-1: 23 23 26; + --solid-2: 29 30 36; + --solid-3: 44 45 54; + --solid-active: 53 57 66; + --solid-amber: 42 37 30; + --solid-blue: 16 49 91; + --solid-iris: 38 42 101; + --text-blue: 126 182 255; + + --alpha-1: 36, 36, 36, 0.8; + --alpha-2: 139, 147, 182, 0.15; + --alpha-3: 36, 38, 45, 0.9; + --black-alpha-1: 0, 0, 0, 0.3; + --black-alpha-2: 0, 0, 0, 0.2; + --border-blue: 39, 129, 246, 0.5; + --border-container: 236, 236, 236, 0; + --white-alpha: 255, 255, 255, 0.1; + } +} diff --git a/app/views/fields/account_features_field/_form.html.erb b/app/views/fields/account_features_field/_form.html.erb index 67fdfb73a..6c3b33ae9 100644 --- a/app/views/fields/account_features_field/_form.html.erb +++ b/app/views/fields/account_features_field/_form.html.erb @@ -2,17 +2,33 @@ <%= f.label field.attribute %>
- <% SuperAdmin::AccountFeaturesHelper.filtered_features(field.data).each do |key, val| %> -
- <% is_premium = SuperAdmin::AccountFeaturesHelper.account_premium_features.include? key %> - <% if is_premium %> - - - - <% end %> - <%= key %> - <% should_disable = is_premium && ChatwootHub.pricing_plan == 'community' %> - <%= check_box "enabled_features", "feature_#{key}", { checked: val, disabled: should_disable }, true, false %> -
- <% end %> + <% regular_features, premium_features = SuperAdmin::AccountFeaturesHelper.filtered_features(field.data).partition { |key_array, _val| !SuperAdmin::AccountFeaturesHelper.account_premium_features.include?(key_array.first) } %> + +
+ <% regular_features.each do |key_array, val| %> + <% feature_key, display_name = key_array %> +
+ <%= display_name %> + <%= check_box "enabled_features", "feature_#{feature_key}", { checked: val, class: "h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-600" }, true, false %> +
+ <% end %> +
+ +
+ +
+ <% premium_features.each do |key_array, val| %> + <% feature_key, display_name = key_array %> +
+
+ + + + <%= display_name %> +
+ <% should_disable = ChatwootHub.pricing_plan == 'community' %> + <%= check_box "enabled_features", "feature_#{feature_key}", { checked: val, disabled: should_disable, class: "h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-600" }, true, false %> +
+ <% end %> +
diff --git a/app/views/fields/account_features_field/_show.html.erb b/app/views/fields/account_features_field/_show.html.erb index 3bc6cf15e..beb79d01a 100644 --- a/app/views/fields/account_features_field/_show.html.erb +++ b/app/views/fields/account_features_field/_show.html.erb @@ -1,14 +1,34 @@ -
- <% SuperAdmin::AccountFeaturesHelper.filtered_features(field.data).each do |key, val| %> -
- <% if SuperAdmin::AccountFeaturesHelper.account_premium_features.include? key %> - - - - <% end %> - <%= key %> - <%= val.present? ? '✅' : '❌' %> -
- <% end %> -
+
+ <% regular_features, premium_features = SuperAdmin::AccountFeaturesHelper.partition_features(field.data) %> +
+ <% regular_features.each do |key_array, val| %> + <% feature_key, display_name = key_array %> +
+ <%= display_name %> + + + +
+ <% end %> +
+ +
+ +
+ <% premium_features.each do |key_array, val| %> + <% feature_key, display_name = key_array %> +
+
+ + + + <%= display_name %> +
+ + + +
+ <% end %> +
+
diff --git a/app/views/super_admin/accounts/_reset_cache.html.erb b/app/views/super_admin/accounts/_reset_cache.html.erb index 7710693b7..cb758eb7a 100644 --- a/app/views/super_admin/accounts/_reset_cache.html.erb +++ b/app/views/super_admin/accounts/_reset_cache.html.erb @@ -2,8 +2,8 @@
<%= form_for([:reset_cache, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %>
-

This will clear the IndexedDB cache keys from redis.
The next load will fetch the data from backend.

+

This will clear the IndexedDB cache keys from redis.
The next load will fetch the data from backend.

<%= f.submit 'Reset Frontend Cache' %>
<% end %> - \ No newline at end of file + diff --git a/app/views/super_admin/accounts/_seed_data.html.erb b/app/views/super_admin/accounts/_seed_data.html.erb index 80bb6c7eb..93984e098 100644 --- a/app/views/super_admin/accounts/_seed_data.html.erb +++ b/app/views/super_admin/accounts/_seed_data.html.erb @@ -4,10 +4,11 @@ <%= form_for([:seed, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %>
-

Click the button to generate seed data into this account for demos.

-

Note: This will clear all the existing data in this account.

-
- <%= f.submit 'Generate Seed Data' %> +
+

Click the button to generate seed data into this account for demos.

+

Note: This will clear all the existing data in this account.

+
+ <%= f.submit 'Generate Seed Data' %>
<% end %> diff --git a/app/views/super_admin/accounts/show.html.erb b/app/views/super_admin/accounts/show.html.erb index 04d92c9d9..bb22735f1 100644 --- a/app/views/super_admin/accounts/show.html.erb +++ b/app/views/super_admin/accounts/show.html.erb @@ -13,89 +13,134 @@ as well as a link to its edit page. as well as helpers for describing how each attribute of the resource should be displayed. -[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show -%> +[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show %> -<% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %> + <% content_for(:title) do + t("administrate.actions.show_resource", name: page.page_title) + end %> - - <% account_user_page.attributes.each do |title, attributes| -%> - <% attributes.each do |attribute| %> - <% if attribute.name == "account" %> - <%= f.hidden_field('account_id', value: page.resource.id) %> - <% else %> -
- <%= render_field attribute, f: f %> -
+
+
+ <% page.attributes.each do |title, attributes| %> + <% if title.present? && title == 'all_features' %> + <% regular_features, premium_features = + attributes.partition do |attr| + !SuperAdmin::AccountFeaturesHelper.account_premium_features.include?( + attr.data.keys.first, + ) + end %> + +
+ <% regular_features.each do |attribute| %> +
+ <%= t( + "helpers.label.#{resource_name}.#{attribute.name}", + default: page.resource.class.human_attribute_name(attribute.name), + ) %> +
+ +
<%= render_field attribute, page: page %>
+ <% end %> +
+ +
+ +
+ <% premium_features.each do |attribute| %> +
+ <%= t( + "helpers.label.#{resource_name}.#{attribute.name}", + default: page.resource.class.human_attribute_name(attribute.name), + ) %> +
+ +
<%= render_field attribute, page: page %>
+ <% end %> +
+ <% else %> +
"> + <% if title.present? %> + <%= t "helpers.label.#{page.resource_name}.#{title}", default: title %> + <% end %> + + <% attributes.each do |attribute| %> +
+ <%= t( + "helpers.label.#{resource_name}.#{attribute.name}", + default: page.resource.class.human_attribute_name(attribute.name), + ) %> +
+ +
<%= render_field attribute, page: page %>
+ <% end %> +
+ <% end %> <% end %> +
+
+ +
+ <% account_user_page = + Administrate::Page::Form.new(AccountUserDashboard.new, AccountUser.new) %> + <%= form_for([namespace, account_user_page.resource], html: { class: "form" }) do |f| %> + <% if account_user_page.resource.errors.any? %> +
+

+ <%= t( + "administrate.form.errors", + pluralized_errors: + pluralize( + account_user_page.resource.errors.count, + t("administrate.form.error"), + ), + resource_name: display_resource_name(account_user_page.resource_name), + ) %> +

+ +
    + <% account_user_page.resource.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + + <% account_user_page.attributes.each do |title, attributes| -%> + <% attributes.each do |attribute| %> + <% if attribute.name == "account" %> + <%= f.hidden_field("account_id", value: page.resource.id) %> + <% else %> +
+ <%= render_field attribute, f: f %> +
+ <% end %> + <% end %> + <% end -%> + +
+ <%= f.submit %> +
<% end %> - <% end -%> -
- <%= f.submit %> -
-<% end %> +
- + <%= render partial: "seed_data", locals: { page: page } %> -<%= render partial: "seed_data", locals: {page: page} %> - -<%= render partial: "reset_cache", locals: {page: page} %> + <%= render partial: "reset_cache", locals: { page: page } %> diff --git a/app/views/super_admin/settings/show.html.erb b/app/views/super_admin/settings/show.html.erb index db8db06e5..98b8d04ad 100644 --- a/app/views/super_admin/settings/show.html.erb +++ b/app/views/super_admin/settings/show.html.erb @@ -1,49 +1,48 @@ <% content_for(:title) do %> Settings <% end %> -