diff --git a/app/controllers/notifications_settings_controller.rb b/app/controllers/notifications_settings_controller.rb index f03f09c8..6c588c32 100644 --- a/app/controllers/notifications_settings_controller.rb +++ b/app/controllers/notifications_settings_controller.rb @@ -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 diff --git a/app/models/account_config.rb b/app/models/account_config.rb index fd8220a6..6c571d18 100644 --- a/app/models/account_config.rb +++ b/app/models/account_config.rb @@ -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' diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index 62b825ff..1812d775 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -7,6 +7,15 @@ <%= 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? %> +
+ <%= footer_value %> +
+ <% end %> + <% end %> <%= render partial: 'shared/mailer_attribution' %> diff --git a/app/views/notifications_settings/_email_footer_form.html.erb b/app/views/notifications_settings/_email_footer_form.html.erb new file mode 100644 index 00000000..205669d9 --- /dev/null +++ b/app/views/notifications_settings/_email_footer_form.html.erb @@ -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 %> +
+ <%= f.label :value, class: 'label' do %> + + <%= t('email_footer_message') %> + + <%= svg_icon('info_circle', class: 'w-4 h-4') %> + + + <% end %> + + <%= f.text_area :value, class: 'base-input w-full py-2', rows: 3, disabled: locked, dir: 'auto' %> + + <% if locked %> + + <% end %> +
+ <% unless locked %> +
+ <%= f.button button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button' %> +
+ <% end %> +<% end %> diff --git a/app/views/notifications_settings/index.html.erb b/app/views/notifications_settings/index.html.erb index b163e8e8..7395a695 100644 --- a/app/views/notifications_settings/index.html.erb +++ b/app/views/notifications_settings/index.html.erb @@ -21,6 +21,9 @@ <% end %> <%= render 'bcc_form', config: @bcc_config %> +
+ <%= render 'email_footer_form', config: @email_footer_config %> +

<%= t('sign_request_email_reminders') %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index ff1b4c77..0d2ee73c 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -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) diff --git a/playwright/tests/v0.8.0-email-footer.spec.ts b/playwright/tests/v0.8.0-email-footer.spec.ts new file mode 100644 index 00000000..2b6af49e --- /dev/null +++ b/playwright/tests/v0.8.0-email-footer.spec.ts @@ -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(); + }); +});