From 900c33547ac64c65f956b826a29bde2ef124ead7 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:37:40 -0400 Subject: [PATCH] feat: add custom brand name + font feature (v1.2.0) (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add custom brand name + font feature (v1.2.0) - Add BRAND_NAME_KEY, BRAND_NAME_FONT_KEY, BRAND_NAME_FONTS whitelist to AccountConfig - Modify _title.html.erb to render custom brand name with configurable font - Create _brand_name_form.html.erb partial with text input + font dropdown - Add brand name form to personalization_settings/show.html.erb - Add BRAND_NAME keys to PersonalizationSettingsController ALLOWED_KEYS - Apply brand name header in mailer layout - Add i18n translations (EN, ES, IT, FR, PT, DE, NL) - Env override support via DOCUSEAL_CONFIG_BRAND_NAME / DOCUSEAL_CONFIG_BRAND_NAME_FONT - When brand_name is unset, upstream DocuSeal text renders as before - When set, configured text renders in chosen font (CSS font-family inline) * fix: address review — correct font CSS fallback, system-ui quoting, server-side validation - Add brand_font_css helper to AccountConfig for proper CSS output - system-ui rendered unquoted; Inter uses sans-serif fallback; script fonts use cursive - Add server-side validation rejecting fonts not in BRAND_NAME_FONTS whitelist - Add invalid_font_selection i18n key * fix: use Rails form helpers for BetterHtml compatibility in brand name and UI visibility forms --------- Co-authored-by: Bob Develop --- .../personalization_settings_controller.rb | 9 +++++ app/models/account_config.rb | 32 ++++++++++++++- app/views/layouts/mailer.html.erb | 15 ++++++- .../_brand_name_form.html.erb | 40 +++++++++++++++++++ .../_ui_visibility_form.html.erb | 8 +--- .../personalization_settings/show.html.erb | 4 ++ app/views/shared/_title.html.erb | 14 ++++++- config/locales/i18n.yml | 15 +++++++ 8 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 app/views/personalization_settings/_brand_name_form.html.erb diff --git a/app/controllers/personalization_settings_controller.rb b/app/controllers/personalization_settings_controller.rb index 1d399bba..85e51fb1 100644 --- a/app/controllers/personalization_settings_controller.rb +++ b/app/controllers/personalization_settings_controller.rb @@ -11,6 +11,8 @@ class PersonalizationSettingsController < ApplicationController AccountConfig::SHOW_CONSOLE_LINK_KEY, AccountConfig::SHOW_API_LINK_KEY, AccountConfig::SHOW_TEST_MODE_KEY, + AccountConfig::BRAND_NAME_KEY, + AccountConfig::BRAND_NAME_FONT_KEY, *(Docuseal.multitenant? ? [] : [AccountConfig::POLICY_LINKS_KEY]) ].freeze @@ -23,6 +25,13 @@ class PersonalizationSettingsController < ApplicationController end def create + if @account_config.key == AccountConfig::BRAND_NAME_FONT_KEY && + @account_config.value.present? && + AccountConfig::BRAND_NAME_FONTS.exclude?(@account_config.value) + return redirect_back(fallback_location: settings_personalization_path, + alert: I18n.t('invalid_font_selection')) + end + if @account_config.value.is_a?(Hash) @account_config.value = @account_config.value.reject do |_, v| v.blank? && v != false diff --git a/app/models/account_config.rb b/app/models/account_config.rb index 6c571d18..346784fb 100644 --- a/app/models/account_config.rb +++ b/app/models/account_config.rb @@ -63,6 +63,36 @@ class AccountConfig < ApplicationRecord SHOW_CONSOLE_LINK_KEY = 'show_console_link' SHOW_API_LINK_KEY = 'show_api_link' SHOW_TEST_MODE_KEY = 'show_test_mode' + BRAND_NAME_KEY = 'brand_name' + BRAND_NAME_FONT_KEY = 'brand_name_font' + + BRAND_NAME_FONTS = [ + 'Inter', + 'system-ui', + 'Dancing Script', + 'Great Vibes', + 'Pacifico', + 'Caveat', + 'Homemade Apple', + 'Mrs Saint Delafield', + 'Shadows Into Light', + 'Alex Brush', + 'Kalam', + 'Sacramento', + 'Herr Von Muellerhoff' + ].freeze + + SANS_SERIF_BRAND_FONTS = %w[Inter system-ui].freeze + + def self.brand_font_css(font_name) + return nil unless font_name.present? && BRAND_NAME_FONTS.include?(font_name) + + if SANS_SERIF_BRAND_FONTS.include?(font_name) + font_name == 'system-ui' ? 'system-ui, sans-serif' : "'#{font_name}', sans-serif" + else + "'#{font_name}', cursive" + end + end EMAIL_VARIABLES = { SUBMITTER_INVITATION_EMAIL_KEY => %w[template.name submitter.link account.name].freeze, @@ -117,7 +147,7 @@ class AccountConfig < ApplicationRecord # Returns the raw ENV value for a key (or nil if not set). def self.env_override(key) - ENV[env_key_for(key)] + ENV.fetch(env_key_for(key), nil) end # True when the corresponding ENV variable is set (non-nil, non-empty). diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index 1812d775..4a686ff1 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -6,8 +6,21 @@ + <% mailer_account = @current_account || @submitter&.account || @submission&.account + if mailer_account + mailer_brand_name, _mbn_locked = mailer_account.config_value(AccountConfig::BRAND_NAME_KEY) + mailer_brand_font, _mbf_locked = mailer_account.config_value(AccountConfig::BRAND_NAME_FONT_KEY) + end + mailer_brand_name = mailer_brand_name.presence + mailer_brand_font = mailer_brand_font.presence %> + <% mailer_font_css = AccountConfig.brand_font_css(mailer_brand_font) %> + <% if mailer_brand_name %> +
"> + <%= mailer_brand_name %> +
+ <% end %> <%= yield %> - <% footer_account = @current_account || (@submitter&.account) || (@submission&.account) %> + <% footer_account = mailer_account %> <% if footer_account %> <% footer_value, _locked = footer_account.config_value(AccountConfig::EMAIL_FOOTER_MESSAGE_KEY) %> <% if footer_value.present? %> diff --git a/app/views/personalization_settings/_brand_name_form.html.erb b/app/views/personalization_settings/_brand_name_form.html.erb new file mode 100644 index 00000000..91afe648 --- /dev/null +++ b/app/views/personalization_settings/_brand_name_form.html.erb @@ -0,0 +1,40 @@ +<% brand_name_value, brand_name_locked = current_account.config_value(AccountConfig::BRAND_NAME_KEY) + brand_font_value, brand_font_locked = current_account.config_value(AccountConfig::BRAND_NAME_FONT_KEY) %> +
+ <%= form_for AccountConfig.new, url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %> + <%= f.hidden_field :key, value: AccountConfig::BRAND_NAME_KEY %> +
+ + <%= f.text_field :value, value: brand_name_value, placeholder: 'DocuSeal', disabled: brand_name_locked, class: 'base-input', dir: 'auto' %> +
+
+ <%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button', disabled: brand_name_locked %> +
+ <% end %> + + <%= form_for AccountConfig.new, url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4 mt-4' } do |f| %> + <%= f.hidden_field :key, value: AccountConfig::BRAND_NAME_FONT_KEY %> +
+ + <%= select_tag 'account_config[value]', + options_for_select( + [[t('default'), '']] + AccountConfig::BRAND_NAME_FONTS.map { |f| [f, f] }, + brand_font_value + ), + class: 'base-input', disabled: brand_font_locked %> +
+
+ <%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button', disabled: brand_font_locked %> +
+ <% end %> +
diff --git a/app/views/personalization_settings/_ui_visibility_form.html.erb b/app/views/personalization_settings/_ui_visibility_form.html.erb index 59f40660..47d66a5b 100644 --- a/app/views/personalization_settings/_ui_visibility_form.html.erb +++ b/app/views/personalization_settings/_ui_visibility_form.html.erb @@ -9,7 +9,7 @@ <% locked = ENV.fetch(env_var_name, nil).present? %> <% current = current_account.ui_visible?(key, default: true) %> <%= form_for(AccountConfig.new, - url: settings_personalization_index_path, + url: settings_personalization_path, method: :post, html: { class: 'flex items-center justify-between py-1' }) do |f| %> <%= f.hidden_field :key, value: key %> @@ -17,11 +17,7 @@