Add ability to view individual newsletters in admin UI (#39271)

This commit is contained in:
Eugen Rochko 2026-06-08 09:05:27 +02:00 committed by GitHub
parent a5e763c330
commit ea5b613796
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 248 additions and 72 deletions

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
class Admin::EmailSubscriptions::AccountsController < Admin::BaseController
before_action :require_enabled!
before_action :set_account
def show
authorize :email_subscription, :show?
@email_subscriptions_count = EmailSubscription.where(account: @account).count
@email_subscriptions = EmailSubscription.where(account: @account).page(params[:page])
end
def enable
authorize :email_subscription, :enable?
@account.user.settings['email_subscriptions'] = true
@account.user.save!
redirect_to admin_email_subscriptions_account_path(@account.id)
end
def disable
authorize :email_subscription, :disable?
@account.user.settings['email_subscriptions'] = false
@account.user.save!
redirect_to admin_email_subscriptions_account_path(@account.id)
end
private
def require_enabled!
raise ActionController::RoutingError, 'Feature disabled' unless Rails.application.config.x.email_subscriptions
end
def set_account
@account = Account.find(params[:id])
end
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::EmailSubscriptionsController < Admin::BaseController
before_action :set_email_subscription, only: :destroy
def index
authorize :email_subscription, :index?
@ -9,6 +11,12 @@ class Admin::EmailSubscriptionsController < Admin::BaseController
@accounts = Account.local.where.associated(:email_subscriptions).includes(:user)
end
def destroy
authorize :email_subscription, :destroy?
@email_subscription.destroy!
redirect_to admin_email_subscriptions_account_path(@email_subscription.account_id)
end
def disable
authorize :email_subscription, :disable?
Setting.email_subscriptions = false
@ -20,4 +28,10 @@ class Admin::EmailSubscriptionsController < Admin::BaseController
Admin::EmailSubscriptionsPurgeWorker.perform_async
redirect_to admin_email_subscriptions_path, notice: I18n.t('admin.email_subscriptions.purged_msg')
end
private
def set_email_subscription
@email_subscription = EmailSubscription.find(params[:id])
end
end

View File

@ -468,6 +468,22 @@ $content-width: 840px;
background-color: var(--color-bg-brand-soft);
}
&.variantWarning {
background-color: var(--color-bg-warning-softest);
.icon {
background-color: var(--color-bg-warning-soft);
}
}
&.variantError {
background-color: var(--color-bg-error-softest);
.icon {
background-color: var(--color-bg-error-soft);
}
}
.content {
display: flex;
flex-direction: column;
@ -2451,4 +2467,46 @@ a.sparkline {
background: var(--color-bg-success-softest);
font-size: 13px;
font-weight: 600;
&.positive {
background: var(--color-bg-success-softest);
}
&.negative {
background: var(--color-bg-error-softest);
}
}
.metadata {
display: flex;
gap: 40px;
margin: 16px 0;
& > div {
display: flex;
flex-direction: column;
gap: 4px;
flex-shrink: 0;
}
dt {
font-size: 13px;
color: var(--color-text-secondary);
}
dd {
font-size: 16px;
font-weight: 600;
line-height: 22.4px;
.status-badge {
box-sizing: border-box;
padding: 4px;
font-size: inherit;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
}
}

View File

@ -129,6 +129,10 @@
color: var(--color-text-primary);
font-weight: 600;
}
.status-badge {
padding: 0;
}
}
&.batch-table {
@ -194,6 +198,12 @@ a.table-action-link {
justify-content: center;
padding: 4px;
aspect-ratio: 1;
&.table-icon-link--danger {
border-radius: 8px;
border: 1px solid var(--color-border-primary);
color: var(--color-text-error);
}
}
.batch-table {

View File

@ -10,4 +10,8 @@ class EmailSubscriptionPolicy < ApplicationPolicy
alias disable? index?
alias purge? index?
alias show? index?
alias destroy? index?
end

View File

@ -32,14 +32,13 @@
%strong
%bdi= display_name(account)
%td.valign-middle
- if account.user_can?(:manage_email_subscriptions) && account.user_email_subscriptions_enabled?
%span.status-badge.positive= t('.active')
- else
%span.status-badge.negative= t('.inactive')
= render 'status', account: account
%td.valign-middle
= account.email_subscriptions.count
%td.valign-middle
- if account.last_status_at.present?
= l account.last_status_at
- else
\-
%td.valign-middle.align-end
= link_to material_symbol('chevron_right'), admin_account_path(account.id), class: 'table-icon-link'
= link_to material_symbol('chevron_right'), admin_email_subscriptions_account_path(account.id), class: 'table-icon-link'

View File

@ -0,0 +1,8 @@
- if account.user_can?(:manage_email_subscriptions) && account.user_email_subscriptions_enabled?
%span.status-badge.positive= t('email_subscriptions.active')
- elsif account.user_can?(:manage_email_subscriptions) && !account.user_email_subscriptions_enabled?
%span.status-badge.negative= t('email_subscriptions.disabled')
- elsif account.user_email_subscriptions_enabled? && !account.user_can?(:manage_email_subscriptions)
%span.status-badge.negative= t('email_subscriptions.no_access')
- else
%span.status-badge.negative= t('email_subscriptions.inactive')

View File

@ -0,0 +1,84 @@
- content_for :page_title do
= t('.title', name: display_name(@account))
- content_for :heading do
.content__heading__row
.heading-with-lead
%h1= display_name(@account)
%p.lead= acct(@account)
.content__heading__actions
= link_to t('.view_account'), admin_account_path(@account.id), class: 'button button-secondary'
- if @account.user_can?(:manage_email_subscriptions)
- if @account.user_email_subscriptions_enabled?
= link_to t('.disable_feature'),
disable_admin_email_subscriptions_account_path(@account.id),
class: 'button button-secondary button--destructive',
data: { method: 'post', confirm: t('.confirm_disable_feature', name: display_name(@account)) }
- else
= link_to t('.enable_feature'), enable_admin_email_subscriptions_account_path(@account.id), class: 'button button-secondary', data: { method: 'post' }
%dl.metadata
%div
%dt= t('email_subscriptions.status')
%dd= render 'admin/email_subscriptions/status', account: @account
%div
%dt= t('email_subscriptions.subscribers')
%dd= number_with_delimiter @email_subscriptions_count
%div
%dt= t('admin.email_subscriptions.accounts.last_email')
%dd
- if @account.last_status_at.present?
= l(@account.last_status_at)
- else
\-
- if !@account.user_email_subscriptions_enabled?
%aside.callout.variantError
= material_symbol 'info'
.content
.body
%p= t('.disabled')
- elsif @account.user_email_subscriptions_enabled? && !@account.user_can?(:manage_email_subscriptions)
%aside.callout.variantError
= material_symbol 'info'
.content
.body
%p= t('.no_access_html', roles_path: admin_roles_path)
- else
%aside.callout.variantWarning
= material_symbol 'warning'
.content
.body
%p= t('.consent')
.table-wrapper
- if @email_subscriptions.empty?
.empty-state
= emptyphaunt
.empty-state__title-and-description
.empty-state__title-and-description__title
= t('.empty.no_subscribers_yet')
.empty-state__title-and-description__description
= t('.empty.hint')
- else
%table.table
%thead
%tr
%th= t('.email')
%th= t('.date')
%th
%tbody
- @email_subscriptions.each do |email_subscription|
%tr
%td.valign-middle
= email_subscription.email
%td.valign-middle
= l(email_subscription.created_at)
%td.valign-middle.align-end
= link_to material_symbol('delete'),
admin_email_subscription_path(email_subscription),
data: { method: 'delete', confirm: t('.confirm_remove_subscriber', email: email_subscription.email, name: display_name(@account)) },
class: 'table-icon-link table-icon-link--danger'
= paginate @email_subscriptions

View File

@ -57,7 +57,11 @@
%tbody
%tr
%th= t('email_subscriptions.status')
%td= @account.user_email_subscriptions_enabled? ? t('email_subscriptions.active') : t('email_subscriptions.inactive')
%td
- if @account.user_email_subscriptions_enabled?
%span.status-badge.positive= t('email_subscriptions.active')
- else
%span.status-badge.negative= t('email_subscriptions.inactive')
%tr
%th= t('email_subscriptions.subscribers')
%td= number_with_delimiter @email_subscriptions_count

View File

@ -520,11 +520,9 @@ be:
email_subscriptions:
accounts:
account: Уліковы запіс
active: Актыўны
empty:
hint: У ніводнага ўліковага запісу няма падпісчыкаў.
no_lists_yet: Пакуль няма спісаў
inactive: Неактыўны
last_email: Апошняя электронная пошта
lead: Уліковыя запісы, якія ўключылі гэту функцыю і маюць падпісчыкаў, будуць паказаныя знізу.
status: Стан

View File

@ -508,7 +508,6 @@ cs:
email_subscriptions:
accounts:
account: Účet
inactive: Neaktivní
last_email: Poslední e-mail
status: Status
compliance_settings:

View File

@ -540,11 +540,9 @@ cy:
email_subscriptions:
accounts:
account: Cyfrif
active: Gweithredol
empty:
hint: Does gan unrhyw gyfrifon ddim tanysgrifwyr eto.
no_lists_yet: Dim rhestrau eto
inactive: Anweithredol
last_email: E-bost diwethaf
lead: Bydd cyfrifon sydd wedi galluogi'r nodwedd ac sydd â thanysgrifwyr yn ymddangos isod.
status: Statws

View File

@ -500,11 +500,9 @@ da:
email_subscriptions:
accounts:
account: Konto
active: Aktive
empty:
hint: Ingen konti har abonnenter endnu.
no_lists_yet: Ingen lister endnu
inactive: Inaktive
last_email: Seneste e-mail
lead: Nedenfor vises de konti, der har aktiveret funktionen og har abonnenter.
status: Status

View File

@ -500,11 +500,9 @@ de:
email_subscriptions:
accounts:
account: Konto
active: Aktiviert
empty:
hint: Bisher wurden keine Konten abonniert.
no_lists_yet: Noch keine Listen vorhanden
inactive: Deaktiviert
last_email: Letzte E-Mail
lead: Konten, die die Funktion aktiviert haben und abonniert wurden, werden unten angezeigt.
status: Status

View File

@ -500,11 +500,9 @@ el:
email_subscriptions:
accounts:
account: Λογαριασμός
active: Ενεργός
empty:
hint: Κανένας λογαριασμός δεν έχει συνδρομητές ακόμη.
no_lists_yet: Καμία λίστα ακόμη
inactive: Ανενεργός
last_email: Τελευταίο email
lead: Λογαριασμοί που έχουν ενεργοποιήσει τη λειτουργία και έχουν συνδρομητές θα εμφανίζονται παρακάτω.
status: Κατάσταση

View File

@ -500,13 +500,26 @@ en:
email_subscriptions:
accounts:
account: Account
active: Active
empty:
hint: No accounts have subscribers yet.
no_lists_yet: No lists yet
inactive: Inactive
last_email: Last email
lead: Accounts who have enabled the feature and have subscribers will show below.
show:
confirm_disable_feature: Disable email newsletters for %{name}? Email updates will no longer be sent for this account. The user will still be able to re-enable the feature in their account settings. To permanently remove access to this feature, edit the accounts permission in Roles.
confirm_remove_subscriber: "%{email} will no longer receive emails from %{name}. This action cannot be undone."
consent: Subscribers have only consented to receiving posts via email. Do not use this list for other purposes.
date: Date of sign-up
disable_feature: Disable feature
disabled: The feature was disabled and emails are no longer being sent to this list.
email: Email address
empty:
hint: Nobody has subscribed to this account yet.
no_subscribers_yet: No subscribers yet
enable_feature: Enable feature
no_access_html: This account no longer has the permissions required to enable the feature. Change this in <a href="%{roles_path}">Roles</a>.
title: Email newsletters of %{name}
view_account: View account
status: Status
subscribers: Subscribers
title: Mailing lists
@ -1534,7 +1547,9 @@ en:
success_html: You'll now start receiving emails when %{name} publishes new posts. Add %{sender} to your contacts so these posts don't end up in your Spam folder.
title: You're signed up
unsubscribe: Unsubscribe
disabled: Disabled
inactive: Inactive
no_access: No access
status: Status
subscribers: Subscribers
emoji_styles:

View File

@ -500,11 +500,9 @@ es-AR:
email_subscriptions:
accounts:
account: Cuenta
active: Activa
empty:
hint: Aún no hay cuentas con suscriptores.
no_lists_yet: Aún no hay listas
inactive: Inactiva
last_email: Último correo electrónico
lead: Las cuentas que habilitaron la función y tienen suscriptores se mostrarán a continuación.
status: Estado

View File

@ -500,11 +500,9 @@ es-MX:
email_subscriptions:
accounts:
account: Cuenta
active: Activa
empty:
hint: Ninguna cuenta tiene suscriptores todavía.
no_lists_yet: No hay listas todavía
inactive: Inactiva
last_email: Último correo electrónico
lead: A continuación se mostrarán las cuentas que hayan activado la función y tengan suscriptores.
status: Estado

View File

@ -500,11 +500,9 @@ es:
email_subscriptions:
accounts:
account: Cuenta
active: Activa
empty:
hint: Aún no hay cuentas con suscriptores.
no_lists_yet: Aún no hay listas
inactive: Inactiva
last_email: Último correo electrónico
lead: Las cuentas que han habilitado la función y tienen suscriptores se mostrarán a continuación.
status: Estado

View File

@ -500,11 +500,9 @@ et:
email_subscriptions:
accounts:
account: Konto
active: Aktiivne
empty:
hint: Ühelgi kontol pole veel tellijaid.
no_lists_yet: Veel pole loetelusid
inactive: Mitteaktiivne
last_email: Viimane e-post
lead: Allpool kuvatakse kontod, kus see funktsioon on aktiveeritud ja millel on tellijaid.
status: Olek

View File

@ -499,11 +499,9 @@ fa:
email_subscriptions:
accounts:
account: حساب
active: فعّال
empty:
hint: هنوز هیچ حسابی مشترک نشده.
no_lists_yet: هنوز سیاهه‌ای وجود ندارد
inactive: غیرفعّال
last_email: آخرین رایانامه
status: وضعیت
subscribers: مشترکان

View File

@ -500,11 +500,9 @@ fi:
email_subscriptions:
accounts:
account: Tili
active: Käytössä
empty:
hint: Millään tilillä ei ole vielä tilaajia.
no_lists_yet: Ei vielä listoja
inactive: Poissa käytöstä
last_email: Viimeisin sähköpostiviesti
lead: Alla näkyvät tilit, jotka ovat ottaneet ominaisuuden käyttöön ja joilla on tilaajia.
status: Tila

View File

@ -500,11 +500,9 @@ fr-CA:
email_subscriptions:
accounts:
account: Compte
active: Actif
empty:
hint: Aucun compte n'a d'abonné·e·s pour l'instant.
no_lists_yet: Aucune liste pour l'instant
inactive: Inactif
last_email: Dernier courriel
lead: Les comptes ayant activé la fonctionnalité et qui ont des abonné·e·s apparaîtront ci-dessous.
status: État

View File

@ -500,11 +500,9 @@ fr:
email_subscriptions:
accounts:
account: Compte
active: Actif
empty:
hint: Aucun compte n'a d'abonné·e·s pour l'instant.
no_lists_yet: Aucune liste pour l'instant
inactive: Inactif
last_email: Dernier courriel
lead: Les comptes ayant activé la fonctionnalité et qui ont des abonné·e·s apparaîtront ci-dessous.
status: État

View File

@ -530,11 +530,9 @@ ga:
email_subscriptions:
accounts:
account: Cuntas
active: Gníomhach
empty:
hint: Níl aon síntiúsóirí ag aon chuntas go fóill.
no_lists_yet: Gan aon liostaí fós
inactive: Neamhghníomhach
last_email: Ríomhphost deireanach
lead: Taispeánfar thíos cuntais a bhfuil an ghné cumasaithe acu agus a bhfuil síntiúsóirí acu.
status: Stádas

View File

@ -500,11 +500,9 @@ gl:
email_subscriptions:
accounts:
account: Conta
active: Activa
empty:
hint: Aínda non hai contas con subscritoras.
no_lists_yet: Aínda non hai listas
inactive: Inactiva
last_email: Último correo
lead: Aquí móstranse as contas que activaron esta ferramenta e teñen subscritoras.
status: Estado

View File

@ -520,11 +520,9 @@ he:
email_subscriptions:
accounts:
account: חשבון
active: פעילים
empty:
hint: עדיין אין מנוי לאף חשבון.
no_lists_yet: אין רשימות עדיין
inactive: לא פעילים
last_email: הודעת דואל אחרונה
lead: חשבונות שבהם הופעלה האפשרות ויש להם מנויים יופיעו להלן.
status: מצב

View File

@ -500,11 +500,9 @@ hu:
email_subscriptions:
accounts:
account: Fiók
active: Aktív
empty:
hint: Egyik fióknak sincs feliratkozója.
no_lists_yet: Még nincsenek listák
inactive: Inaktív
last_email: Legutóbbi e-mail
lead: A fiókok, melyek bekapcsolták a funkciót, és vannak feliratkozóik, itt fognak megjelenni alább.
status: Állapot

View File

@ -500,11 +500,9 @@ is:
email_subscriptions:
accounts:
account: Aðgangur
active: Virkur
empty:
hint: Engir aðgangar eru ennþá með neina áskrifendur.
no_lists_yet: Ennþá engir listar
inactive: Óvirkur
last_email: Síðasti tölvupóstur
lead: Aðgangar sem hafa virkjað eiginleikann og eru með áskrifendur munu birtast hér fyrir neðan.
status: Staða

View File

@ -500,11 +500,9 @@ it:
email_subscriptions:
accounts:
account: Account
active: Attivo
empty:
hint: Non ci sono ancora account con iscritti.
no_lists_yet: Non ci sono ancora liste
inactive: Inattivo
last_email: Ultima email
lead: Di seguito verranno visualizzati gli account che hanno attivato la funzionalità e che hanno degli iscritti.
status: Stato

View File

@ -290,8 +290,6 @@ kab:
email_subscriptions:
accounts:
account: Amiḍan
active: D urmid
inactive: D arurmid
last_email: Imayl aneggaru
status: Addad
compliance_settings:

View File

@ -471,7 +471,6 @@ lad:
email_subscriptions:
accounts:
account: Kuento
active: Aktivo
danger_zone:
disable_feature:
action: Inkapasita

View File

@ -490,11 +490,9 @@ nan-TW:
email_subscriptions:
accounts:
account: 口座
active: 有效ê
empty:
hint: Iáu無半ê口座有訂ê lâng。
no_lists_yet: Iáu無列單
inactive: 停止使用ah
last_email: 上新ê電子phue箱
lead: 有啟用tsit ê功能koh有訂ê lâng ê口座ē展示佇下kha。
status: 狀態

View File

@ -500,11 +500,9 @@ nl:
email_subscriptions:
accounts:
account: Account
active: Actief
empty:
hint: Geen enkel account heeft nog abonnees.
no_lists_yet: Nog geen lijsten
inactive: Inactief
last_email: Meest recente e-mail
lead: Accounts die deze functionaliteit hebben ingeschakeld en abonnees hebben, worden hieronder getoond.
status: Status

View File

@ -500,11 +500,9 @@ nn:
email_subscriptions:
accounts:
account: Konto
active: Aktiv
empty:
hint: Ingen brukarkontoar har abonnentar enno.
no_lists_yet: Ingen lister enno
inactive: Ikkje aktiv
last_email: Siste epost
lead: Nedanfor vil du sjå brukarkontoar som har skrudd på dette og som har abonnentar.
status: Status

View File

@ -500,11 +500,9 @@ pt-BR:
email_subscriptions:
accounts:
account: Conta
active: Ativas
empty:
hint: Não há contas com inscritos ainda.
no_lists_yet: Não há listas ainda
inactive: Inativas
last_email: Último email
lead: Contas que ativaram o recurso e têm inscritos aparecerão abaixo.
status: Estado

View File

@ -506,7 +506,6 @@ ru:
email_subscriptions:
accounts:
account: Аккаунт
active: Актив
status: Статус
subscribers: Подписчики
danger_zone:

View File

@ -500,11 +500,9 @@ sq:
email_subscriptions:
accounts:
account: Llogari
active: Aktive
empty:
hint: Ende ska llogari me pajtimtarë.
no_lists_yet: Ende pa lista
inactive: Joaktive
last_email: Email-i i fundit
lead: Llogaritë që kanë aktivizuar veçorinë dhe kanë pajtimtarë do të shfaqen më poshtë.
status: Gjendje

View File

@ -500,11 +500,9 @@ sv:
email_subscriptions:
accounts:
account: Konto
active: Aktiv
empty:
hint: Inga konton har prenumeranter ännu.
no_lists_yet: Inga listor ännu
inactive: Inaktiv
last_email: Senaste e-post
lead: Konton som har aktiverat funktionen och har prenumeranter kommer att visas nedan.
status: Status

View File

@ -500,11 +500,9 @@ tr:
email_subscriptions:
accounts:
account: Hesap
active: Etkin
empty:
hint: Henüz hiçbir hesabın abonesi yok.
no_lists_yet: Henüz liste yok
inactive: Etkin değil
last_email: Son e-posta
lead: Bu özelliği etkinleştirmiş ve abonesi olan hesaplar aşağıda gösterilecektir.
status: Durum

View File

@ -490,11 +490,9 @@ vi:
email_subscriptions:
accounts:
account: Tài khoản
active: Hoạt động
empty:
hint: Hiện chưa có tài khoản nào có người đăng ký đọc.
no_lists_yet: Chưa có danh sách nào
inactive: Không hoạt động
last_email: Email gần nhất
lead: Các tài khoản đã kích hoạt tính năng này và có người đăng ký sẽ hiển thị bên dưới.
status: Trạng thái

View File

@ -490,11 +490,9 @@ zh-CN:
email_subscriptions:
accounts:
account: 账号
active: 已生效
empty:
hint: 目前还没有账号拥有订阅者。
no_lists_yet: 尚无列表
inactive: 未生效
last_email: 最新电子邮件
lead: 已启用此功能并拥有订阅者的账号会在下方显示。
status: 状态

View File

@ -492,11 +492,9 @@ zh-TW:
email_subscriptions:
accounts:
account: 帳號
active: 生效中
empty:
hint: 尚無任何擁有訂閱者之帳號。
no_lists_yet: 尚無列表
inactive: 已停用
last_email: 最新電子郵件地址
lead: 已啟用此功能並擁有訂閱者之帳號將於下方顯示。
status: 狀態

View File

@ -26,7 +26,7 @@ namespace :admin do
resources :email_domain_blocks, only: [:index, :new, :create], concerns: :batch
resources :email_subscriptions, only: [:index] do
resources :email_subscriptions, only: [:index, :destroy] do
collection do
post :purge
post :disable
@ -36,6 +36,13 @@ namespace :admin do
namespace :email_subscriptions do
resource :setup, only: [:show, :create]
resource :additional_footer_text, only: [:show, :update]
resources :accounts, only: :show do
member do
post :enable
post :disable
end
end
end
resources :action_logs, only: [:index]