feat(email): global email footer message (account_config + env override)

pull/639/head
Bob Develop 2 weeks ago
parent ae7690047b
commit d99365138d

@ -3,8 +3,10 @@
class NotificationsSettingsController < ApplicationController
before_action :load_bcc_config, only: :index
before_action :load_reminder_config, only: :index
before_action :load_email_footer_config, only: :index
authorize_resource :bcc_config, only: :index
authorize_resource :reminder_config, only: :index
authorize_resource :email_footer_config, only: :index
before_action :build_account_config, only: :create
authorize_resource :account_config, only: :create
@ -36,9 +38,15 @@ class NotificationsSettingsController < ApplicationController
def load_reminder_config
@reminder_config =
AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::SUBMITTER_REMINDERS)
endload_email_footer_config
@email_footer_config =
AccountConfig.find_or_initialize_by(account: currnt_account, key: AccountConfig::EMAIL_FOOTER_MESSAGE_KEY)
end
def email_config_params
def e
allowed = [AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS, AccountConfig::EMAIL_FOOTER_MESSAGE_KEY]
def email_config_paramsallwed
params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
end

@ -59,6 +59,7 @@ class AccountConfig < ApplicationRecord
TEMPLATE_CUSTOM_FIELDS_KEY = 'template_custom_fields'
POLICY_LINKS_KEY = 'policy_links'
ENABLE_MCP_KEY = 'enable_mcp'
EMAIL_FOOTER_MESSAGE_KEY = 'email_footer_message'
SHOW_CONSOLE_LINK_KEY = 'show_console_link'
SHOW_API_LINK_KEY = 'show_api_link'
SHOW_TEST_MODE_KEY = 'show_test_mode'

@ -7,6 +7,15 @@
</head>
<body dir="<%= TextUtils.rtl?(yield) ? 'rtl' : 'auto' %>">
<%= yield %>
<% footer_account = @current_account || (@submitter&.account) || (@submission&.account) %>
<% if footer_account %>
<% footer_value, _locked = footer_account.config_value(AccountConfig::EMAIL_FOOTER_MESSAGE_KEY) %>
<% if footer_value.present? %>
<div style="margin-top: 24px; padding-top: 12px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 12px; white-space: pre-line;">
<%= footer_value %>
</div>
<% end %>
<% end %>
<%= render partial: 'shared/mailer_attribution' %>
</body>
</html>

@ -0,0 +1,27 @@
<% locked = AccountConfig.locked_by_env?(AccountConfig::EMAIL_FOOTER_MESSAGE_KEY) %>
<%= form_for config, url: settings_notifications_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
<%= f.hidden_field :key %>
<div class="form-control">
<%= f.label :value, class: 'label' do %>
<span class="flex items-center space-x-1">
<span><%= t('email_footer_message') %></span>
<span class="tooltip" data-tip="<%= t('email_footer_help') %>">
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
</span>
</span>
<% end %>
<autoresize-textarea>
<%= f.text_area :value, class: 'base-input w-full py-2', rows: 3, disabled: locked, dir: 'auto' %>
</autoresize-textarea>
<% if locked %>
<label class="label">
<span class="label-text-alt"><%= t('locked_by_env') %></span>
</label>
<% end %>
</div>
<% unless locked %>
<div class="form-control pt-2">
<%= f.button button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button' %>
</div>
<% end %>
<% end %>

@ -21,6 +21,9 @@
<% end %>
</div>
<%= render 'bcc_form', config: @bcc_config %>
<div class="mt-6">
<%= render 'email_footer_form', config: @email_footer_config %>
</div>
<div class="flex justify-between items-end mb-4 mt-8">
<h2 class="text-3xl font-bold">
<%= t('sign_request_email_reminders') %>

@ -8,6 +8,8 @@ en: &en
visibility_private: "Private (only you)"
visibility_public: "Public (all account users)"
visibility_help: "Private templates are only visible to their author. Public templates are visible to all users in the account."
email_footer_message: Email footer message
email_footer_help: "Appended at the bottom of every outgoing email (e.g. a confidentiality notice)."
language_en: English
language_en-US: English (US)
language_en-GB: English (UK)

@ -0,0 +1,25 @@
import { test, expect } from '@playwright/test';
import { loginAsAdmin } from './helpers/auth';
// Phase 2.1 — Global email footer.
// Sets a footer via Notifications settings and verifies it saves.
// Actual mail rendering is validated manually with letter_opener in dev.
test.describe('Email footer', () => {
test('admin can configure an email footer message', async ({ page }) => {
await loginAsAdmin(page);
await page.goto('/settings/notifications');
const textarea = page.locator('textarea[name="account_config[value]"]').first();
await expect(textarea).toBeVisible();
const footer = `CONFIDENTIAL - ${Date.now()}`;
await textarea.fill(footer);
await page.getByRole('button', { name: /save|update/i }).first().click();
await page.waitForLoadState('networkidle');
// Reload and ensure the saved footer persists.
await page.goto('/settings/notifications');
await expect(page.locator('textarea').filter({ hasText: footer }).first()).toBeVisible();
});
});
Loading…
Cancel
Save