chore: Clean up the feature presentation in super admin (#10949)

- improve styling of the feature toggle displayed in super admin

<img width="1540" alt="Screenshot 2025-02-21 at 4 47 58 PM"
src="https://github.com/user-attachments/assets/c111ff0d-c2ce-4609-832b-0ea631c97471"
/>

<img width="1451" alt="Screenshot 2025-02-21 at 4 48 11 PM"
src="https://github.com/user-attachments/assets/fe5df4ae-984b-4f9d-baba-9555935fbd55"
/>

---------

Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
Sojan Jose
2025-02-21 17:01:36 -08:00
committed by GitHub
parent 161024db9d
commit 19cfd4be4c
9 changed files with 512 additions and 143 deletions

View File

@@ -7,13 +7,46 @@ module SuperAdmin::AccountFeaturesHelper
account_features.filter { |feature| feature['premium'] }.pluck('name') account_features.filter { |feature| feature['premium'] }.pluck('name')
end end
# Accepts account.features as argument # Returns a hash mapping feature names to their display names
def self.filtered_features(features) def self.feature_display_names
deployment_env = GlobalConfig.get_value('DEPLOYMENT_ENV') account_features.each_with_object({}) do |feature, hash|
return features if deployment_env == 'cloud' 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') internal_features = account_features.select { |f| f['chatwoot_internal'] }.pluck('name')
features.except(*internal_features) features.except(*internal_features)
end 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 end

View File

@@ -6,3 +6,209 @@
body { body {
font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; 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;
}
}

View File

@@ -2,17 +2,33 @@
<%= f.label field.attribute %> <%= f.label field.attribute %>
</div> </div>
<div class="field-unit__field feature-container"> <div class="field-unit__field feature-container">
<% SuperAdmin::AccountFeaturesHelper.filtered_features(field.data).each do |key, val| %> <% regular_features, premium_features = SuperAdmin::AccountFeaturesHelper.filtered_features(field.data).partition { |key_array, _val| !SuperAdmin::AccountFeaturesHelper.account_premium_features.include?(key_array.first) } %>
<div class='feature-cell'>
<% is_premium = SuperAdmin::AccountFeaturesHelper.account_premium_features.include? key %> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<% if is_premium %> <% regular_features.each do |key_array, val| %>
<span class='icon-container'> <% feature_key, display_name = key_array %>
<svg class="inline" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><path d="M480 224l-186.828 7.487L401.688 64l-59.247-32L256 208 169.824 32l-59.496 32 108.5 167.487L32 224v64l185.537-10.066L113.65 448l55.969 32L256 304l86.381 176 55.949-32-103.867-170.066L480 288z" fill="currentColor"/></svg> <div class="flex items-center justify-between p-3 bg-white rounded-lg shadow-sm outline outline-1 outline-n-container">
</span> <span class="text-sm text-slate-700"><%= display_name %></span>
<% end %> <span><%= 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 %></span>
<span><%= key %></span> </div>
<% should_disable = is_premium && ChatwootHub.pricing_plan == 'community' %> <% end %>
<span class='value-container'><%= check_box "enabled_features", "feature_#{key}", { checked: val, disabled: should_disable }, true, false %></span> </div>
</div>
<% end %> <hr class="my-8 boshadow-sm outline outline-1 outline-n-container">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<% premium_features.each do |key_array, val| %>
<% feature_key, display_name = key_array %>
<div class="flex items-center justify-between p-3 bg-white rounded-lg shadow-sm outline outline-1 outline-n-container">
<div class="flex items-center gap-2">
<span class="text-amber-500">
<svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M480 224l-186.828 7.487L401.688 64l-59.247-32L256 208 169.824 32l-59.496 32 108.5 167.487L32 224v64l185.537-10.066L113.65 448l55.969 32L256 304l86.381 176 55.949-32-103.867-170.066L480 288z" fill="currentColor"/></svg>
</span>
<span class="text-sm text-slate-700"><%= display_name %></span>
</div>
<% should_disable = ChatwootHub.pricing_plan == 'community' %>
<span><%= 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 %></span>
</div>
<% end %>
</div>
</div> </div>

View File

@@ -1,14 +1,34 @@
<div class='feature-container'> <div class="w-full">
<% SuperAdmin::AccountFeaturesHelper.filtered_features(field.data).each do |key, val| %> <% regular_features, premium_features = SuperAdmin::AccountFeaturesHelper.partition_features(field.data) %>
<div class='feature-cell'>
<% if SuperAdmin::AccountFeaturesHelper.account_premium_features.include? key %>
<span class='icon-container'>
<svg class="inline" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><path d="M480 224l-186.828 7.487L401.688 64l-59.247-32L256 208 169.824 32l-59.496 32 108.5 167.487L32 224v64l185.537-10.066L113.65 448l55.969 32L256 304l86.381 176 55.949-32-103.867-170.066L480 288z" fill="currentColor"/></svg>
</span>
<% end %>
<span><%= key %></span>
<span class='value-container'><%= val.present? ? '✅' : '❌' %> </span>
</div>
<% end %>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<% regular_features.each do |key_array, val| %>
<% feature_key, display_name = key_array %>
<div class="flex items-center justify-between p-3 bg-white rounded-md outline outline-n-container outline-1 shadow-sm">
<span class="text-sm text-n-slate-12"><%= display_name %></span>
<span class="<%= val.present? ? 'bg-green-400 text-white': 'bg-slate-50 text-slate-800' %> rounded-full p-1 inline-flex right-4 top-5">
<svg width="12" height="12"><use xlink:href="#icon-tick-line" /></svg>
</span>
</div>
<% end %>
</div>
<hr class="my-8 border-t border-n-weak">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<% premium_features.each do |key_array, val| %>
<% feature_key, display_name = key_array %>
<div class="flex items-center justify-between p-3 bg-white rounded-md outline outline-n-container outline-1 shadow-sm">
<div class="flex items-center gap-2">
<span class="bg-n-amber-3 text-n-amber-12 rounded-full p-1 inline-flex right-4 top-5">
<svg width="12" height="12"><use xlink:href="#icon-lock-line" /></svg>
</span>
<span class="text-sm text-n-slate-12"><%= display_name %></span>
</div>
<span class="<%= val.present? ? 'bg-green-400 text-white': 'bg-slate-50 text-slate-800' %> rounded-full p-1 inline-flex right-4 top-5">
<svg width="12" height="12"><use xlink:href="#icon-tick-line" /></svg>
</span>
</div>
<% end %>
</div>
</div>

View File

@@ -2,8 +2,8 @@
<hr/> <hr/>
<%= form_for([:reset_cache, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %> <%= form_for([:reset_cache, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %>
<div class="form-actions"> <div class="form-actions">
<p>This will clear the IndexedDB cache keys from redis. <br>The next load will fetch the data from backend.</p> <p class="pb-3">This will clear the IndexedDB cache keys from redis. <br>The next load will fetch the data from backend.</p>
<%= f.submit 'Reset Frontend Cache' %> <%= f.submit 'Reset Frontend Cache' %>
</div> </div>
<% end %> <% end %>
</section> </section>

View File

@@ -4,10 +4,11 @@
<%= form_for([:seed, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %> <%= form_for([:seed, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %>
<div class="form-actions"> <div class="form-actions">
<div><p> Click the button to generate seed data into this account for demos.</p> <div class="pb-3">
<p class="text-color-red">Note: This will clear all the existing data in this account.</p> <p>Click the button to generate seed data into this account for demos.</p>
</div> <p class="text-color-red">Note: This will clear all the existing data in this account.</p>
<%= f.submit 'Generate Seed Data' %> </div>
<%= f.submit 'Generate Seed Data' %>
</div> </div>
<% end %> <% end %>
</section> </section>

View File

@@ -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 as well as helpers for describing how each attribute of the resource
should be displayed. 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 %>
<header class="main-content__header" role="banner"> <header class="main-content__header" role="banner">
<h1 class="main-content__page-title"> <h1 class="main-content__page-title">
<%= content_for(:title) %> <%= content_for(:title) %>
</h1> </h1>
<div> <div>
<%= link_to( <%= if accessible_action?(page.resource, :edit)
"Edit", link_to("Edit", [:edit, namespace, page.resource], class: "button")
[:edit, namespace, page.resource], end %>
class: "button",
) if accessible_action?(page.resource, :edit) %>
</div>
</header>
<section class="main-content__body">
<dl>
<% page.attributes.each do |title, attributes| %>
<fieldset class="<%= "field-unit--nested" if title.present? %>">
<% if title.present? %>
<legend><%= t "helpers.label.#{page.resource_name}.#{title}", default: title %></legend>
<% end %>
<% attributes.each do |attribute| %>
<dt class="attribute-label" id="<%= attribute.name %>">
<%= t(
"helpers.label.#{resource_name}.#{attribute.name}",
default: page.resource.class.human_attribute_name(attribute.name),
) %>
</dt>
<dd class="attribute-data attribute-data--<%=attribute.html_class%>"
><%= render_field attribute, page: page %></dd>
<% end %>
</fieldset>
<% end %>
</dl>
</section>
<section class="main-content__body">
<% 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? %>
<div id="error_explanation">
<h2>
<%= 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)
) %>
</h2>
<ul>
<% account_user_page.resource.errors.full_messages.each do |message| %>
<li class="flash-error"><%= message %></li>
<% end %>
</ul>
</div> </div>
<% end %> </header>
<% account_user_page.attributes.each do |title, attributes| -%> <section class="main-content__body">
<% attributes.each do |attribute| %> <div class="pr-16">
<% if attribute.name == "account" %> <% page.attributes.each do |title, attributes| %>
<%= f.hidden_field('account_id', value: page.resource.id) %> <% if title.present? && title == 'all_features' %>
<% else %> <% regular_features, premium_features =
<div class="field-unit field-unit--<%= attribute.html_class %> field-unit--<%= requireness(attribute) %>"> attributes.partition do |attr|
<%= render_field attribute, f: f %> !SuperAdmin::AccountFeaturesHelper.account_premium_features.include?(
</div> attr.data.keys.first,
)
end %>
<div class="space-y-4">
<% regular_features.each do |attribute| %>
<div class="attribute-label" id="<%= attribute.name %>">
<%= t(
"helpers.label.#{resource_name}.#{attribute.name}",
default: page.resource.class.human_attribute_name(attribute.name),
) %>
</div>
<div class="attribute-data attribute-data--<%=attribute.html_class%>"><%= render_field attribute, page: page %></div>
<% end %>
</div>
<hr class="my-8 border-n-weak">
<div class="space-y-4">
<% premium_features.each do |attribute| %>
<div class="attribute-label" id="<%= attribute.name %>">
<%= t(
"helpers.label.#{resource_name}.#{attribute.name}",
default: page.resource.class.human_attribute_name(attribute.name),
) %>
</div>
<div class="attribute-data attribute-data--<%=attribute.html_class%>"><%= render_field attribute, page: page %></div>
<% end %>
</div>
<% else %>
<fieldset class="<%= "field-unit--nested" if title.present? %>">
<% if title.present? %>
<legend><%= t "helpers.label.#{page.resource_name}.#{title}", default: title %></legend>
<% end %>
<% attributes.each do |attribute| %>
<div class="attribute-label" id="<%= attribute.name %>">
<%= t(
"helpers.label.#{resource_name}.#{attribute.name}",
default: page.resource.class.human_attribute_name(attribute.name),
) %>
</div>
<div class="attribute-data attribute-data--<%=attribute.html_class%>"><%= render_field attribute, page: page %></div>
<% end %>
</fieldset>
<% end %>
<% end %> <% end %>
</div>
</section>
<section class="main-content__body">
<% 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? %>
<div id="error_explanation">
<h2>
<%= 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),
) %>
</h2>
<ul>
<% account_user_page.resource.errors.full_messages.each do |message| %>
<li class="flash-error"><%= message %></li>
<% end %>
</ul>
</div>
<% 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 %>
<div
class="
field-unit field-unit--<%= attribute.html_class %>
field-unit--<%= requireness(attribute) %>
"
>
<%= render_field attribute, f: f %>
</div>
<% end %>
<% end %>
<% end -%>
<div class="form-actions">
<%= f.submit %>
</div>
<% end %> <% end %>
<% end -%>
<div class="form-actions"> </section>
<%= f.submit %>
</div>
<% end %>
</section> <%= 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} %>

View File

@@ -1,49 +1,48 @@
<% content_for(:title) do %> <% content_for(:title) do %>
Settings Settings
<% end %> <% end %>
<header class="flex mx-8 py-4 items-center border-b border-solid border-slate-100" role="banner"> <header class="flex px-8 py-4 items-center border-b border-n-weak" role="banner">
<div class="border border-solid border-slate-100 text-slate-700 mr-4 p-2 rounded-full"> <div class="border border-n-weak mr-4 p-2 rounded-full">
<svg width="24" height="24"><use xlink:href="#icon-settings-2-line" /></svg> <svg width="24" height="24"><use xlink:href="#icon-settings-2-line" /></svg>
</div> </div>
<div class="flex flex-col h-14 justify-center"> <div class="flex flex-col h-14 justify-center">
<h1 class="text-base font-medium leading-6 text-slate-900" id="page-title"> <h1 class="text-base font-medium leading-6 text-n-slate-12" id="page-title">
<%= content_for(:title) %> <%= content_for(:title) %>
</h1> </h1>
<p class="text-sm font-normal leading-5 text-slate-500 m-0">Update your instance settings, access billing portal</p> <p class="text-sm font-normal leading-5 text-slate-500 m-0">Update your instance settings, access billing portal</p>
</div> </div>
</header> </header>
<section class="main-content__body"> <section class="main-content__body px-8">
<% if Redis::Alfred.get(Redis::Alfred::CHATWOOT_INSTALLATION_CONFIG_RESET_WARNING) %> <% if Redis::Alfred.get(Redis::Alfred::CHATWOOT_INSTALLATION_CONFIG_RESET_WARNING) %>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-5" role="alert"> <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-5" role="alert">
<strong class="font-bold">Alert!</strong> <strong class="font-bold">Alert!</strong>
<span class="block sm:inline">Unauthorized premium changes detected in Chatwoot. To keep using them, please upgrade your plan. <span class="block sm:inline">Unauthorized premium changes detected in Chatwoot. To keep using them, please upgrade your plan.
Contact for help :</span><span class="inline rounded-full bg-red-200 px-2 text-white ml-2">sales@chatwoot.com</span> Contact for help :</span><span class="inline rounded-full bg-red-200 px-2 text-white ml-2">sales@chatwoot.com</span>
</div> </div>
<% end %> <% end %>
<div class="bg-white py-2 px-3"> <div class="bg-white py-2 px-4 xl:px-0">
<div class="mb-4"> <div class="mb-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h2 class="h-5 leading-5 text-slate-900 font-medium">Current plan</h2> <h2 class="h-5 leading-5 text-n-slate-12 font-medium">Current plan</h2>
<a href="<%= refresh_super_admin_settings_url %>" class="inline-flex gap-1 text-xs font-medium items-center text-woot-500 hover:text-woot-700"> <a href="<%= refresh_super_admin_settings_url %>" class="inline-flex gap-1 text-xs font-medium items-center text-woot-500 hover:text-woot-700">
<svg width="16" height="16"><use xlink:href="#icon-refresh-line" /></svg> <svg width="16" height="16"><use xlink:href="#icon-refresh-line" /></svg>
<span>Refresh</span> <span>Refresh</span>
</a> </a>
</div> </div>
<p class="text-slate-600 mt-1"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p> <p class="text-n-slate-11 mt-1"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p>
<div class="flex items-center mt-6"> <div class="flex items-center mt-6">
<h4 class="text-sm font-medium leading-5 h-5 text-slate-900 mr-4">Installation Identifier</h4> <h4 class="text-sm font-medium leading-5 h-5 text-n-slate-12 mr-4">Installation Identifier</h4>
<span class="text-sm leading-5 h-5 text-slate-600"><%= ChatwootHub.installation_identifier %></span> <span class="text-sm leading-5 h-5 text-n-slate-11"><%= ChatwootHub.installation_identifier %></span>
</div> </div>
</div> </div>
<div class="flex p-4 border border-solid border-slate-100 rounded-lg mt-8 items-start md:items-center shadow-sm flex-col md:flex-row"> <div class="flex p-4 outline outline-1 outline-n-container rounded-lg mt-8 items-start md:items-center shadow-sm flex-col md:flex-row">
<div class="flex flex-col flex-grow gap-1"> <div class="flex flex-col flex-grow gap-1">
<h2 class="h-5 leading-5 text-slate-900 text-sm font-medium">Current plan</h2> <h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Current plan</h2>
<p class="text-slate-600 m-0 text-sm"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p> <p class="text-n-slate-11 m-0 text-sm"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p>
</div> </div>
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer"> <a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer">
<button class="mt-4 md:mt-0 flex gap-1 items-center bg-transparent shadow-sm h-9 hover:bg-slate-100 hover:text-slate-800 border border-solid border-slate-100 rounded text-slate-700 font-medium p-2"> <button class="mt-4 md:mt-0 flex gap-1 items-center bg-transparent shadow-sm h-9 hover:text-n-slate-12 hover:bg-slate-50 outline outline-1 outline-n-container rounded text-n-slate-11 font-medium p-2">
<svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg> <svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg>
<span class="px-1">Manage</span> <span class="px-1">Manage</span>
</button> </button>
@@ -59,30 +58,30 @@
<% end %> <% end %>
<div class="flex p-4 border border-solid border-slate-100 rounded-lg mt-4 items-start md:items-center shadow-sm flex-col md:flex-row"> <div class="flex p-4 outline outline-1 outline-n-container rounded-lg mt-4 items-start md:items-center shadow-sm flex-col md:flex-row">
<div class="flex flex-col flex-grow gap-1"> <div class="flex flex-col flex-grow gap-1">
<h2 class="h-5 leading-5 text-slate-900 text-sm font-medium">Need help?</h2> <h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Need help?</h2>
<p class="text-slate-600 m-0 text-sm">Do you face any issues? We are here to help.</p> <p class="text-n-slate-11 m-0 text-sm">Do you face any issues? We are here to help.</p>
</div> </div>
<a href="https://discord.gg/cJXdrwS" target="_blank"> <a href="https://discord.gg/cJXdrwS" target="_blank">
<button class="flex mt-4 md:mt-0 gap-1 items-center bg-transparent shadow-sm h-9 bg-violet-500 hover:bg-violet-600 text-violet-100 hover:text-violet-200 border border-solid border-violet-600 rounded font-medium p-2"> <button class="flex mt-4 md:mt-0 gap-1 items-center bg-transparent shadow-sm h-9 bg-violet-500 hover:bg-violet-600 text-white border border-solid border-violet-600 rounded font-medium p-2">
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z" fill="currentColor"/></svg> <svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z" fill="currentColor"/></svg>
<span class="px-1">Community Support</span> <span class="px-1">Community Support</span>
</button> </button>
</a> </a>
<% if ChatwootHub.pricing_plan !='community' %> <% if ChatwootHub.pricing_plan !='community' %>
<button class="ml-4 flex gap-1 items-center bg-transparent h-9 hover:bg-slate-100 hover:text-slate-800 border border-solid border-slate-100 rounded text-slate-700 font-medium p-2" onclick="window.$chatwoot.toggle('open')"> <button class="ml-4 flex gap-1 items-center bg-transparent h-9 hover:text-n-slate-12 hover:bg-slate-50 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2" onclick="window.$chatwoot.toggle('open')">
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z" fill="currentColor"/></svg> <svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z" fill="currentColor"/></svg>
<span class="px-1">Chat Support</span> <span class="px-1">Chat Support</span>
</button> </button>
<% end %> <% end %>
</div> </div>
<div class="mt-10 py-4"> <div class="mt-10 py-4">
<h3 class="h-5 leading-5 text-slate-900 font-medium text-base">Features</h3> <h3 class="h-5 leading-5 text-n-slate-12 font-medium text-base">Features</h3>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-x-2 gap-y-3"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-2 gap-y-3">
<% SuperAdmin::FeaturesHelper.available_features.each do |feature, attrs| %> <% SuperAdmin::FeaturesHelper.available_features.each do |feature, attrs| %>
<div class="group border-slate-100 border p-4 rounded-lg relative shadow-sm"> <div class="group outline outline-1 outline-n-container p-4 rounded-lg relative shadow-sm">
<span class="<%= attrs[:enabled] ? 'bg-green-400 text-white': 'bg-slate-300 text-slate-800' %> absolute rounded-full p-1 inline-flex right-4 top-5"> <span class="<%= attrs[:enabled] ? 'bg-green-400 text-white': 'bg-slate-300 text-slate-800' %> absolute rounded-full p-1 inline-flex right-4 top-5">
<svg width="14" height="14"><use xlink:href="<%= attrs[:enabled] ? '#icon-tick-line': '#icon-lock-line' %>" /></svg> <svg width="14" height="14"><use xlink:href="<%= attrs[:enabled] ? '#icon-tick-line': '#icon-lock-line' %>" /></svg>
</span> </span>
@@ -91,7 +90,7 @@
</div> </div>
<% if !attrs[:enabled] %> <% if !attrs[:enabled] %>
<div class="flex h-9 absolute top-5 items-center invisible group-hover:visible"> <div class="flex h-9 absolute top-5 items-center invisible group-hover:visible">
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer" class="flex gap-1 items-center bg-slate-100 h-9 hover:bg-slate-300 hover:text-slate-900 border border-solid border-slate-100 rounded text-slate-600 font-medium p-2"> <a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer" class="flex gap-1 items-center bg-slate-100 h-9 hover:bg-slate-300 hover:text-n-slate-12 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2">
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg> <svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
<span class="px-1">Upgrade now</span> <span class="px-1">Upgrade now</span>
</a> </a>
@@ -99,9 +98,9 @@
<% end %> <% end %>
<div class="flex items-center justify-between mb-1.5 mt-4"> <div class="flex items-center justify-between mb-1.5 mt-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h3 class="text-slate-900 font-medium"><%= attrs[:name] %></h3> <h3 class="text-n-slate-12 font-medium"><%= attrs[:name] %></h3>
<% if attrs[:enterprise] %> <% if attrs[:enterprise] %>
<span class="px-2 h-4 leading-4 rounded-xl text-green-800 font-medium bg-green-100/70 text-xxs">EE</span> <span class="px-2 h-4 leading-4 rounded-xl text-green-800 font-medium bg-green-100/70 text-xxs">EE</span>
<% end %> <% end %>
<% if attrs[:config_key].present? && attrs[:enabled] %> <% if attrs[:config_key].present? && attrs[:enabled] %>
<a class="inline-flex items-center justify-center h-5 w-5 hover:bg-slate-50 rounded-sm" href="/super_admin/app_config?config=<%= attrs[:config_key] %>"> <a class="inline-flex items-center justify-center h-5 w-5 hover:bg-slate-50 rounded-sm" href="/super_admin/app_config?config=<%= attrs[:config_key] %>">
@@ -110,7 +109,7 @@
<% end %> <% end %>
</div> </div>
</div> </div>
<p class="text-slate-600 mb-0"><%= attrs[:description] %></p> <p class="text-n-slate-11 mb-0"><%= attrs[:description] %></p>
</div> </div>
<% end %> <% end %>
</div> </div>

View File

@@ -1,107 +1,156 @@
# DO NOT change the order of features EVER # DO NOT change the order of features EVER
############################################
# name: the name to be used internally in the code
# display_name: the name to be used in the UI
# enabled: whether the feature is enabled by default
# help_url: the url to the help center article
# chatwoot_internal: whether the feature is internal to Chatwoot and should not be shown in the UI for other self hosted installations
# deprecated: purpose of feature flag is done, no need to show it in the UI anymore
- name: inbound_emails - name: inbound_emails
display_name: Inbound Emails
enabled: true enabled: true
- name: channel_email - name: channel_email
display_name: Email Channel
enabled: true enabled: true
help_url: https://chwt.app/hc/email help_url: https://chwt.app/hc/email
- name: channel_facebook - name: channel_facebook
display_name: Facebook Channel
enabled: true enabled: true
help_url: https://chwt.app/hc/fb help_url: https://chwt.app/hc/fb
- name: channel_twitter - name: channel_twitter
display_name: Twitter Channel
enabled: true enabled: true
deprecated: true
- name: ip_lookup - name: ip_lookup
display_name: IP Lookup
enabled: false enabled: false
- name: disable_branding - name: disable_branding
display_name: Disable Branding
enabled: false enabled: false
premium: true premium: true
- name: email_continuity_on_api_channel - name: email_continuity_on_api_channel
display_name: Email Continuity on API Channel
enabled: false enabled: false
- name: help_center - name: help_center
display_name: Help Center
enabled: true enabled: true
help_url: https://chwt.app/hc/help-center help_url: https://chwt.app/hc/help-center
- name: agent_bots - name: agent_bots
display_name: Agent Bots
enabled: false enabled: false
help_url: https://chwt.app/hc/agent-bots help_url: https://chwt.app/hc/agent-bots
- name: macros - name: macros
display_name: Macros
enabled: true enabled: true
- name: agent_management - name: agent_management
display_name: Agent Management
enabled: true enabled: true
- name: team_management - name: team_management
display_name: Team Management
enabled: true enabled: true
help_url: https://chwt.app/hc/teams help_url: https://chwt.app/hc/teams
- name: inbox_management - name: inbox_management
display_name: Inbox Management
enabled: true enabled: true
- name: labels - name: labels
display_name: Labels
enabled: true enabled: true
help_url: https://chwt.app/hc/labels help_url: https://chwt.app/hc/labels
- name: custom_attributes - name: custom_attributes
display_name: Custom Attributes
enabled: true enabled: true
help_url: https://chwt.app/hc/custom-attributes help_url: https://chwt.app/hc/custom-attributes
- name: automations - name: automations
display_name: Automations
enabled: true enabled: true
- name: canned_responses - name: canned_responses
display_name: Canned Responses
enabled: true enabled: true
help_url: https://chwt.app/hc/canned help_url: https://chwt.app/hc/canned
- name: integrations - name: integrations
display_name: Integrations
enabled: true enabled: true
help_url: https://chwt.app/hc/integrations help_url: https://chwt.app/hc/integrations
- name: voice_recorder - name: voice_recorder
display_name: Voice Recorder
enabled: true enabled: true
- name: mobile_v2 - name: mobile_v2
display_name: Mobile App V2
enabled: false enabled: false
deprecated: true
- name: channel_website - name: channel_website
display_name: Website Channel
enabled: true enabled: true
- name: campaigns - name: campaigns
display_name: Campaigns
enabled: true enabled: true
help_url: https://chwt.app/hc/campaigns help_url: https://chwt.app/hc/campaigns
- name: reports - name: reports
display_name: Reports
enabled: true enabled: true
help_url: https://chwt.app/hc/reports help_url: https://chwt.app/hc/reports
- name: crm - name: crm
display_name: CRM
enabled: true enabled: true
- name: auto_resolve_conversations - name: auto_resolve_conversations
display_name: Auto Resolve Conversations
enabled: true enabled: true
- name: custom_reply_email - name: custom_reply_email
display_name: Custom Reply Email
enabled: false enabled: false
- name: custom_reply_domain - name: custom_reply_domain
display_name: Custom Reply Domain
enabled: false enabled: false
- name: audit_logs - name: audit_logs
display_name: Audit Logs
enabled: false enabled: false
premium: true premium: true
- name: response_bot - name: response_bot
display_name: Response Bot
enabled: false enabled: false
premium: true premium: true
chatwoot_internal: true deprecated: true
- name: message_reply_to - name: message_reply_to
display_name: Message Reply To
enabled: false enabled: false
help_url: https://chwt.app/hc/reply-to help_url: https://chwt.app/hc/reply-to
chatwoot_internal: true deprecated: true
- name: insert_article_in_reply - name: insert_article_in_reply
display_name: Insert Article in Reply
enabled: false enabled: false
chatwoot_internal: true deprecated: true
- name: inbox_view - name: inbox_view
display_name: Inbox View
enabled: false enabled: false
chatwoot_internal: true chatwoot_internal: true
- name: sla - name: sla
display_name: SLA
enabled: false enabled: false
premium: true premium: true
help_url: https://chwt.app/hc/sla help_url: https://chwt.app/hc/sla
- name: help_center_embedding_search - name: help_center_embedding_search
display_name: Help Center Embedding Search
enabled: false enabled: false
premium: true premium: true
chatwoot_internal: true chatwoot_internal: true
- name: linear_integration - name: linear_integration
display_name: Linear Integration
enabled: false enabled: false
- name: captain_integration - name: captain_integration
display_name: Captain
enabled: false enabled: false
premium: true premium: true
- name: custom_roles - name: custom_roles
display_name: Custom Roles
enabled: false enabled: false
premium: true premium: true
- name: chatwoot_v4 - name: chatwoot_v4
display_name: Chatwoot V4
enabled: false enabled: false
- name: report_v4 - name: report_v4
display_name: Report V4
enabled: false enabled: false
- name: contact_chatwoot_support_team - name: contact_chatwoot_support_team
display_name: Contact Chatwoot Support Team
enabled: true enabled: true
chatwoot_internal: true chatwoot_internal: true