feat: add custom brand name + font feature (v1.2.0) (#11)

* 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 <developbob50@gmail.com>
pull/639/head
devin-ai-integration[bot] 2 weeks ago committed by GitHub
parent db47f795ef
commit 900c33547a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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

@ -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).

@ -6,8 +6,21 @@
<style></style>
</head>
<body dir="<%= TextUtils.rtl?(yield) ? 'rtl' : 'auto' %>">
<% 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 %>
<div style="margin-bottom: 16px; font-size: 20px; font-weight: bold;<%= " font-family: #{mailer_font_css};" if mailer_font_css %>">
<%= mailer_brand_name %>
</div>
<% 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? %>

@ -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) %>
<div class="my-4">
<%= 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 %>
<div class="form-control">
<label class="label">
<span class="label-text font-medium"><%= t('brand_name') %></span>
<% if brand_name_locked %>
<span class="label-text-alt text-base-content/60" title="<%= t('locked_by_env') %>"><%= t('locked_by_env') %></span>
<% end %>
</label>
<%= f.text_field :value, value: brand_name_value, placeholder: 'DocuSeal', disabled: brand_name_locked, class: 'base-input', dir: 'auto' %>
</div>
<div class="form-control pt-2">
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button', disabled: brand_name_locked %>
</div>
<% 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 %>
<div class="form-control">
<label class="label">
<span class="label-text font-medium"><%= t('brand_name_font') %></span>
<% if brand_font_locked %>
<span class="label-text-alt text-base-content/60" title="<%= t('locked_by_env') %>"><%= t('locked_by_env') %></span>
<% end %>
</label>
<%= 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 %>
</div>
<div class="form-control pt-2">
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button', disabled: brand_font_locked %>
</div>
<% end %>
</div>

@ -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 @@
<label class="flex items-center space-x-3 flex-grow"
<%= "title=\"#{t('locked_by_env')}\"".html_safe if locked %>>
<submit-form data-on="change" class="flex">
<input type="checkbox"
class="toggle toggle-sm"
<%= 'checked' if current %>
<%= 'disabled' if locked %>
onchange="this.form.submit()">
<%= check_box_tag nil, '1', current, class: 'toggle toggle-sm', disabled: locked, onchange: 'this.form.submit()' %>
</submit-form>
<span class="text-base"><%= label %></span>
<% if locked %>

@ -13,6 +13,10 @@
<%= t('company_logo') %>
</p>
<%= render 'logo_form' %>
<p class="text-4xl font-bold mb-4 mt-8">
<%= t('brand_name') %>
</p>
<%= render 'brand_name_form' %>
<p class="text-4xl font-bold mb-4 mt-8">
<%= t('submission_form') %>
</p>

@ -1,2 +1,14 @@
<%= render 'shared/logo' %>
<span>DocuSeal</span>
<% title_account = defined?(current_account) && current_account
if title_account
brand_name, _bn_locked = title_account.config_value(AccountConfig::BRAND_NAME_KEY)
brand_font, _bf_locked = title_account.config_value(AccountConfig::BRAND_NAME_FONT_KEY)
end
brand_name = brand_name.presence || 'DocuSeal'
brand_font = brand_font.presence %>
<% brand_font_css = AccountConfig.brand_font_css(brand_font) %>
<% if brand_font_css %>
<span style="font-family: <%= brand_font_css %>;"><%= brand_name %></span>
<% else %>
<span><%= brand_name %></span>
<% end %>

@ -322,6 +322,8 @@ en: &en
verification_code_sms: Verification Code SMS
completed_notification_email: Completed notification email
company_logo: Company Logo
brand_name: Brand Name
brand_name_font: Brand Name Font
submission_form: Submission Form
completed_form_message: Completed Form Message
completed_form_redirect_button: Completed Form Redirect Button
@ -338,6 +340,7 @@ en: &en
attach_audit_log_pdf: Attach audit log PDF
attach_documents: Attach documents
settings_have_been_saved: Settings have been saved.
invalid_font_selection: Invalid font selection.
display_your_company_name_and_logo_when_signing_documents: Display your company name and logo when signing documents.
profile: Profile
signature: Signature
@ -1380,6 +1383,8 @@ es: &es
verification_code_sms: SMS de código de verificación
completed_notification_email: Correo de notificación de formulario completado
company_logo: Logotipo de la empresa
brand_name: Nombre de marca
brand_name_font: Fuente del nombre de marca
submission_form: Formulario de envío
completed_form_message: Mensaje de formulario completado
completed_form_redirect_button: Botón de redirección de formulario completado
@ -2432,6 +2437,8 @@ it: &it
verification_code_sms: SMS con codice di verifica
completed_notification_email: Email di notifica di completamento
company_logo: Logo aziendale
brand_name: Nome del marchio
brand_name_font: Font del nome del marchio
submission_form: Modulo di invio
completed_form_message: Messaggio di completamento del modulo
completed_form_redirect_button: Pulsante di reindirizzamento dopo il completamento del modulo
@ -3488,6 +3495,8 @@ fr: &fr
verification_code_sms: SMS de code de vérification
completed_notification_email: Email de notification de finalisation
company_logo: Logo de lentreprise
brand_name: Nom de la marque
brand_name_font: Police du nom de la marque
submission_form: Formulaire de soumission
completed_form_message: Message de formulaire complété
completed_form_redirect_button: Bouton de redirection après finalisation
@ -4537,6 +4546,8 @@ pt: &pt
verification_code_sms: SMS com código de verificação
completed_notification_email: E-mail de notificação de submissão concluída
company_logo: Logotipo da empresa
brand_name: Nome da marca
brand_name_font: Fonte do nome da marca
submission_form: Formulário de submissão
completed_form_message: Mensagem de formulário concluído
completed_form_redirect_button: Botão de redirecionamento do formulário concluído
@ -5589,6 +5600,8 @@ de: &de
verification_code_sms: SMS mit Verifizierungscode
completed_notification_email: E-Mail-Benachrichtigung bei Abschluss
company_logo: Firmenlogo
brand_name: Markenname
brand_name_font: Schriftart des Markennamens
submission_form: Einreichungsformular
completed_form_message: Nachricht zum abgeschlossenen Formular
completed_form_redirect_button: Weiterleitungsschaltfläche nach Formularabschluss
@ -7046,6 +7059,8 @@ nl: &nl
verification_code_sms: Verificatiecode-SMS
completed_notification_email: E-mailmelding voltooid
company_logo: Bedrijfslogo
brand_name: Merknaam
brand_name_font: Lettertype van de merknaam
submission_form: Indieningsformulier
completed_form_message: Bericht voltooid formulier
completed_form_redirect_button: Knop voor doorverwijzing na voltooid formulier

Loading…
Cancel
Save