views/admin/users i18n (#18582)

This commit is contained in:
yheuhtozr 2022-11-11 22:29:48 +09:00 committed by GitHub
parent 5dd33c1fe6
commit 5519fe563c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1048 additions and 301 deletions

View file

@ -16,45 +16,46 @@ module Admin
return if timestamp.blank?
if timestamp.today?
"Today, #{timestamp.strftime('%d %b')}"
I18n.t("helpers.admin.user_helper.today", date: I18n.l(timestamp, format: :members))
elsif timestamp.yesterday?
"Yesterday, #{timestamp.strftime('%d %b')}"
I18n.t("helpers.admin.user_helper.yesterday", date: I18n.l(timestamp, format: :members))
else
timestamp.strftime("%d %b, %Y")
I18n.l(timestamp, format: :members_with_year)
end
end
def cascading_high_level_roles(user)
if user.super_admin?
"Super Admin"
I18n.t("views.admin.users.roles.Super Admin")
elsif user.admin?
"Admin"
I18n.t("views.admin.users.roles.Admin")
elsif user.single_resource_admin_for?(:any)
"Resource Admin"
I18n.t("views.admin.users.roles.Resource Admin")
end
end
def format_role_tooltip(user)
if user.super_admin?
"Super Admin"
I18n.t("views.admin.users.roles.Super Admin")
elsif user.admin?
"Admin"
I18n.t("views.admin.users.roles.Admin")
elsif user.single_resource_admin_for?(:any)
"Resource Admin: #{user.roles.pluck(:resource_type).compact.join(', ')}"
role = "Resource Admin: #{user.roles.pluck(:resource_type).compact.join(', ')}"
I18n.t("views.admin.users.roles.#{role}", default: role)
end
end
def user_status(user)
if user.suspended?
"Suspended"
I18n.t("views.admin.users.statuses.Suspended")
elsif user.warned?
"Warned"
I18n.t("views.admin.users.statuses.Warned")
elsif user.comment_suspended?
"Comment Suspended"
I18n.t("views.admin.users.statuses.Comment Suspended")
elsif user.trusted?
"Trusted"
I18n.t("views.admin.users.statuses.Trusted")
else
"Good Standing"
I18n.t("views.admin.users.statuses.Good Standing")
end
end
@ -83,16 +84,12 @@ module Admin
# @param [Integer] The limit of organizations that we show
# @return [String]
def organization_tooltip(organization_names, count, imposed_limit: 0)
str = organization_names.first(imposed_limit).join(", ").to_s
str = organization_names.first(imposed_limit).join(I18n.t("helpers.admin.user_helper.org_join")).to_s
return str unless count > imposed_limit
overflow = overflow_count(count, imposed_limit: imposed_limit)
if overflow == 1
str + " & #{overflow_count(count, imposed_limit: imposed_limit)} other"
else
str + " & #{overflow_count(count, imposed_limit: imposed_limit)} others"
end
I18n.t("helpers.admin.user_helper.org_overflow", count: overflow_count(count, imposed_limit: imposed_limit),
orgs: str)
end
# Returns a string hex code representing the indicator color for the given status (also known as BASE_ROLE)

View file

@ -1,4 +1,4 @@
<button type="button" id="expand-search-btn" class="relative c-btn c-btn--icon-alone indicator-btn" aria-label="Expand search" aria-expanded="false" aria-controls="search-users">
<%= crayons_icon_tag("search", aria_hidden: true) %>
<button type="button" id="expand-search-btn" class="relative c-btn c-btn--icon-alone indicator-btn" aria-label="<%= t("views.admin.users.search.aria_expand") %>" aria-expanded="false" aria-controls="search-users">
<%= crayons_icon_tag(t("views.admin.users.search.icon"), aria_hidden: true) %>
<span data-testid="search-indicator" class="search-indicator absolute top-1 right-1 c-indicator c-indicator--info <%= params[:search].blank? ? "hidden" : "" %>"></span>
</button>

View file

@ -1,3 +1,3 @@
<button type="button" class="js-export-csv-modal-trigger c-btn" data-modal-title="Download member data" aria-label="Download member data" data-modal-size="small" data-modal-content-selector="#export-csv-modal">
<button type="button" class="js-export-csv-modal-trigger c-btn" data-modal-title="<%= t("views.admin.users.download.heading") %>" aria-label="<%= t("views.admin.users.download.aria_label") %>" data-modal-size="small" data-modal-content-selector="#export-csv-modal">
<%= crayons_icon_tag(:export, class: "c-btn__icon", aria_hidden: true) %>
</button>

View file

@ -1,6 +1,6 @@
<div class="relative">
<%= f.text_field :search, id: "search-#{context}", value: params[:search], class: "crayons-textfield mt-0", placeholder: placeholder, aria: { label: aria_label } %>
<button type="submit" aria-label="Search" class="crayons-btn crayons-btn--ghost crayons-btn--s crayons-btn--icon-rounded absolute right-2 bottom-0 top-0 m-1">
<button type="submit" aria-label="<%= t("views.admin.users.search.aria_label") %>" class="crayons-btn crayons-btn--ghost crayons-btn--s crayons-btn--icon-rounded absolute right-2 bottom-0 top-0 m-1">
<%= crayons_icon_tag(:search, aria_hidden: true) %>
</button>
</div>

View file

@ -1,6 +1,6 @@
<%- headers = ["Name", "Username", "Email address", "Status", "Joining date", "Last activity", "Organizations"] -%>
<%- headers = [t("views.admin.users.download.name"), t("views.admin.users.download.username"), t("views.admin.users.download.email"), t("views.admin.users.download.status"), t("views.admin.users.download.join"), t("views.admin.users.download.last_activity"), t("views.admin.users.download.orgs")] -%>
<%= CSV.generate_line headers -%>
<%- @users.each do |user| -%>
<%- row = [user.name, user.username, user.email, user_status(user), user.registered_at&.strftime("%d %b, %Y"), user.last_activity&.strftime("%d %b, %Y"), user.organizations.pluck(:name)] -%>
<%- row = [user.name, user.username, user.email, user_status(user), user.registered_at ? l(user.registered_at, format: :members_with_year) : '', user.last_activity ? l(user.last_activity, format: :members_with_year) : '', user.organizations.pluck(:name)] -%>
<%= CSV.generate_line(row).html_safe -%>
<%- end -%>

View file

@ -2,14 +2,14 @@
<div id="member-index-content" class="crayons-card overflow-admin-main-layout-padding p-4 xl:p-0">
<header class="xl:p-7 pb-4 mx-2">
<div class="flex flex-col l:flex-row justify-content-between l:items-center">
<h1 class="crayons-title">Members</h1>
<h1 class="crayons-title"><%= t("views.admin.users.heading") %></h1>
</div>
<%= render "admin/users/index/controls" %>
</header>
<%= render "admin/users/index/applied_filters" %>
<!-- Responsive screen (breakpoints below XL) data view start -->
<div class="block xl:hidden fs-s">
<h2 class="member-data-heading fs-s py-2 -mx-4 px-6 color-base-60">Members</h2>
<h2 class="member-data-heading fs-s py-2 -mx-4 px-6 color-base-60"><%= t("views.admin.users.table.compact_subtitle") %></h2>
<ul class="list-none mx-2">
<% @users.each do |user| %>
<li class="py-4">
@ -42,12 +42,12 @@
<div class="flex justify-between items-end">
<dl class="flex gap-4">
<div>
<dt class="color-base-60 fw-normal">Last activity</dt>
<dt class="color-base-60 fw-normal"><%= t("views.admin.users.table.last_activity") %></dt>
<dd><%= format_last_activity_timestamp(user.last_activity) %></dd>
</div>
<div>
<dt class="color-base-60 fw-normal">Joined on</dt>
<dd><%= user.registered_at&.strftime("%d %b, %Y") %></dd>
<dt class="color-base-60 fw-normal"><%= t("views.admin.users.table.joined_on") %></dt>
<dd><%= l(user.registered_at, format: :members_with_year) if user.registered_at %></dd>
</div>
</dl>
<div>
@ -65,11 +65,11 @@
<table class="crayons-table crayons-table--minimal w-100 hidden xl:table">
<thead class="member-data-heading">
<tr>
<th scope="col" class="pl-7">Member</th>
<th scope="col">Status</th>
<th scope="col">Last activity & Joined on</th>
<th scope="col">Organizations</th>
<th scope="col" class="screen-reader-only">Actions</th>
<th scope="col" class="pl-7"><%= t("views.admin.users.table.heading.member") %></th>
<th scope="col"><%= t("views.admin.users.table.heading.status") %></th>
<th scope="col"><%= t("views.admin.users.table.heading.last_joined") %></th>
<th scope="col"><%= t("views.admin.users.table.heading.orgs") %></th>
<th scope="col" class="screen-reader-only"><%= t("views.admin.users.table.heading.actions") %></th>
</tr>
</thead>
<tbody>
@ -87,7 +87,7 @@
<td>
<%= format_last_activity_timestamp(user.last_activity) %>
<p class="fs-xs color-base-60">
<%= user.registered_at&.strftime("%d %b, %Y") %>
<%= l(user.registered_at, format: :members_with_year) if user.registered_at %>
</p>
</td>
<td>
@ -103,28 +103,28 @@
<% if @users.empty? %>
<div class="align-center flex flex-col my-auto py-7" id="empty-state-message-id">
<p class="fs-xl fw-normal mb-2">No members found under these filters.</p>
<p class="color-secondary">Try broadening or removing the filters.</p>
<p class="fs-xl fw-normal mb-2"><%= t("views.admin.users.empty.text1") %></p>
<p class="color-secondary"><%= t("views.admin.users.empty.text2") %></p>
</div>
<% end %>
<!-- XL screen data view end -->
<div class="flex justify-end xl:p-7">
<%= paginate @users, theme: "admin", scope: @users, label: "Paginate users", context: "bottom" %>
<%= paginate @users, theme: "admin", scope: @users, label: t("views.admin.users.aria_paginate"), context: "bottom" %>
</div>
<div id="export-csv-modal" class="hidden">
<div class="flex flex-col gap-4">
<p>
Your data will be downloaded as a Comma Separated Values (.csv) file.
<%= t("views.admin.users.download.desc1") %>
</p>
<p>
Values listed are Name, Username, Email address, Status, Joining date, Last activity, and Organizations.
<%= t("views.admin.users.download.desc2") %>
</p>
<div class="flex gap-2">
<%= link_to "Download", "#{export_admin_users_path}.csv", class: "c-cta c-cta--branded self-end" %>
<%= link_to t("views.admin.users.download.submit"), "#{export_admin_users_path}.csv", class: "c-cta c-cta--branded self-end" %>
<button class="js-export-csv-modal-cancel crayons-btn crayons-btn--ghost">
Cancel
<%= t("views.admin.users.download.cancel") %>
</button>
</div>
</div>

View file

@ -1,13 +1,13 @@
<div role="group" aria-labelledby="applied-filters" class="js-applied-filters hidden m:flex flex-wrap items-center gap-2 mb-4 px-2 xl:px-7">
<h2 id="applied-filters" class="screen-reader-only">Applied filters</h2>
<h2 id="applied-filters" class="screen-reader-only"><%= t("views.admin.users.filters.applied") %></h2>
<% params[:roles]&.each do |role| %>
<button
data-filter-key="roles[]"
data-filter-value="<%= role %>"
aria-label="Remove filter: <%= role %>"
aria-label="<%= t("views.admin.users.filters.aria_remove", value: t("views.admin.users.roles.#{role}")) %>"
class="c-pill c-pill--action-icon c-pill--action-icon--destructive"
type="button">
<%= role %>
<%= t("views.admin.users.roles.#{role}") %>
<%= crayons_icon_tag("x.svg", class: "c-pill__action-icon", aria_hidden: true) %>
</button>
<% end %>
@ -15,7 +15,7 @@
<button
data-filter-key="statuses[]"
data-filter-value="<%= status %>"
aria-label="Remove filter: <%= status %>"
aria-label="<%= t("views.admin.users.filters.aria_remove", value: t("views.admin.users.statuses.#{status}")) %>"
class="c-pill c-pill--action-icon c-pill--action-icon--destructive"
type="button">
<%= render "admin/users/index/status_indicator", status: status %>
@ -27,7 +27,7 @@
<button
data-filter-key="organizations[]"
data-filter-value="<%= org_id %>"
aria-label="Remove filter: <%= org.name %>"
aria-label="<%= t("views.admin.users.filters.aria_remove", value: org.name) %>"
class="c-pill c-pill--action-icon c-pill--action-icon--destructive"
type="button">
<%= org.name %>
@ -37,16 +37,16 @@
<% if params[:joining_start].present? || params[:joining_end].present? %>
<button
data-filter-key="joining_date"
aria-label="Remove filter: Joining date"
aria-label="<%= t("views.admin.users.filters.aria_remove", value: t("views.admin.users.filters.join")) %>"
class="c-pill c-pill--action-icon c-pill--action-icon--destructive"
type="button">
<% start_date = params[:joining_start].presence || "Community creation" %>
<% end_date = params[:joining_end].presence || "Today" %>
<% start_date = params[:joining_start].presence || t("views.admin.users.filters.creation") %>
<% end_date = params[:joining_end].presence || t("views.admin.users.filters.today") %>
<%= start_date %> - <%= end_date %>
<%= crayons_icon_tag("x.svg", class: "c-pill__action-icon", aria_hidden: true) %>
</button>
<% end %>
<% if params[:roles] || params[:organizations] || params[:joining_start].present? || params[:joining_end].present? || params[:statuses] %>
<button class="js-clear-filters-btn c-btn" aria-label="Clear all filters">Clear all</button>
<button class="js-clear-filters-btn c-btn" aria-label="<%= t("views.admin.users.filters.aria_clear") %>"><%= t("views.admin.users.filters.clear") %></button>
<% end %>
</div>

View file

@ -3,17 +3,17 @@
<%= form_with url: admin_users_path, method: :get, local: true do |f| %>
<div class="flex m:flex-col xl:flex-row justify-between m:items-end xl:justify-end xl:items-center mb-4">
<div>
<%= paginate @users, theme: "admin", scope: @users, label: "Paginate users", context: "small" %>
<%= paginate @users, theme: "admin", scope: @users, label: t("views.admin.users.aria_paginate"), context: "small" %>
</div>
<div class="flex justify-between xl:justify-end m:w-100 xl:w-max">
<div class="hidden m:block m:w-50 xl:w-max">
<%= render "admin/users/controls/search_field", f: f, context: "large", placeholder: "Search member...", aria_label: "Search member by name, username or email" %>
<%= render "admin/users/controls/search_field", f: f, context: "large", placeholder: t("views.admin.users.search.placeholder"), aria_label: t("views.admin.users.search.aria_field") %>
</div>
<div class="flex xl:ml-2">
<div class="m:hidden">
<%= render "admin/users/controls/expand_search_button" %>
</div>
<button type="button" aria-label="Filter" class="js-open-filter-modal-btn c-btn c-btn--icon-alone"><%= crayons_icon_tag("filter", aria_hidden: true) %></button>
<button type="button" aria-label="<%= t("views.admin.users.filters.aria_label") %>" class="js-open-filter-modal-btn c-btn c-btn--icon-alone"><%= crayons_icon_tag("filter", aria_hidden: true) %></button>
<%= render "admin/users/controls/export" %>
</div>
</div>
@ -23,7 +23,7 @@
<%= form_with url: admin_users_path, method: :get, local: true do |f| %>
<div class="m:hidden">
<div id="search-users" class="hidden">
<%= render "admin/users/controls/search_field", f: f, context: "small", placeholder: "Search member...", aria_label: "Search member by name, username or email" %>
<%= render "admin/users/controls/search_field", f: f, context: "small", placeholder: t("views.admin.users.search.placeholder"), aria_label: t("views.admin.users.search.aria_field") %>
</div>
</div>
<% end %>

View file

@ -1,15 +1,15 @@
<div class="js-filters-modal hidden">
<%= form_with url: admin_users_path, method: :get, local: true, class: "flex flex-col h-100" do |f| %>
<div class="flex justify-between items-center border-0 border-b-1 border-base-10 border-solid -mx-7 px-7 pb-2">
<h2 class="crayons-subtitle-2">Filters</h2>
<button type="button" class="js-clear-all-filters-btn c-btn">Clear filters</button>
<h2 class="crayons-subtitle-2"><%= t("views.admin.users.filters.heading") %></h2>
<button type="button" class="js-clear-all-filters-btn c-btn"><%= t("views.admin.users.filters.clear_filters") %></button>
</div>
<p id="filters-description" class="mt-4 crayons-field__description">Members who match any of the selected filters will be displayed</p>
<p id="filters-description" class="mt-4 crayons-field__description"><%= t("views.admin.users.filters.desc") %></p>
<div class="py-4">
<details data-section="roles" class="admin-details">
<summary class="py-4 flex justify-between text-uppercase color-base-60 fs-s">
<span class="flex">
Member roles
<%= t("views.admin.users.filters.summary.roles") %>
<span class="js-filtered-indicator-roles relative ml-2 <%= params[:roles].blank? ? "hidden" : "" %>">
<span class="block absolute c-indicator c-indicator--info"></span>
</span>
@ -17,31 +17,34 @@
<%= crayons_icon_tag("chevron-down", aria_hidden: true, class: "summary-icon") %>
</summary>
<fieldset class="js-roles-fieldset pb-4 flex flex-col items-start">
<legend class="screen-reader-only">Member roles</legend>
<legend class="screen-reader-only"><%= t("views.admin.users.filters.legend.roles") %></legend>
<% initially_visible_roles = Constants::Role::SPECIAL_ROLES[0..5] %>
<% initially_hidden_roles = Constants::Role::SPECIAL_ROLES[6..] %>
<% initially_visible_roles.each_with_index do |role, index| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :roles, { multiple: true, class: "crayons-checkbox", id: "filter-role-#{index}", checked: params[:roles]&.include?(role) }, role, false %>
<%= f.label :roles, for: "filter-role-#{index}", class: "crayons-field__label" do %><%= role %><% end %>
<%= f.label :roles, for: "filter-role-#{index}", class: "crayons-field__label" do %><%= t("views.admin.users.roles.#{role}") %><% end %>
</div>
<% end %>
<div class="hidden js-initially-hidden-roles">
<% initially_hidden_roles.each_with_index do |role, index| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :roles, { multiple: true, class: "crayons-checkbox", id: "filter-role-#{6 + index}", checked: params[:roles]&.include?(role) }, role, false %>
<%= f.label :roles, for: "filter-role-#{6 + index}", class: "crayons-field__label" do %><%= role %><% end %>
<%= f.label :roles, for: "filter-role-#{6 + index}", class: "crayons-field__label" do %><%= t("views.admin.users.roles.#{role}") %><% end %>
</div>
<% end %>
</div>
<button type="button" class="js-expand-roles-btn c-btn mt-2" aria-label="See more roles" aria-pressed="false">See more roles</button>
<button type="button" class="js-clear-filter-btn c-btn <%= params[:roles].blank? ? "hidden" : "" %>" data-checkbox-fieldset-selector=".js-roles-fieldset" data-filter-indicator-selector=".js-filtered-indicator-roles" aria-label="Clear member roles filter">Clear filter</button>
<button type="button" class="js-expand-roles-btn c-btn mt-2" aria-label="<%= t("views.admin.users.filters.aria_more_roles") %>" aria-pressed="false"><%= t("views.admin.users.filters.more_roles") %></button>
<button type="button" class="js-clear-filter-btn c-btn <%= params[:roles].blank? ? "hidden" : "" %>" data-checkbox-fieldset-selector=".js-roles-fieldset" data-filter-indicator-selector=".js-filtered-indicator-roles"
aria-label="<%= t("views.admin.users.filters.clear_filter.aria_roles") %>">
<%= t("views.admin.users.filters.clear_filter.text") %>
</button>
</fieldset>
</details>
<details data-section="status" class="admin-details">
<summary class="py-4 flex justify-between text-uppercase color-base-60 fs-s">
<span class="flex">
Status
<%= t("views.admin.users.filters.summary.status") %>
<span class="js-filtered-indicator-statuses relative ml-2 <%= params[:statuses].blank? ? "hidden" : "" %>">
<span class="block absolute c-indicator c-indicator--info"></span>
</span>
@ -49,20 +52,23 @@
<%= crayons_icon_tag("chevron-down", aria_hidden: true, class: "summary-icon") %>
</summary>
<fieldset class="js-statuses-fieldset pb-4 flex flex-col items-start">
<legend class="screen-reader-only">Status</legend>
<legend class="screen-reader-only"><%= t("views.admin.users.filters.legend.status") %></legend>
<% filterable_statuses.each_with_index do |status, index| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :statuses, { multiple: true, class: "crayons-checkbox", id: "filter-status-#{index}", checked: params[:statuses]&.include?(status) }, status, false %>
<%= f.label :statuses, for: "filter-status-#{index}", class: "crayons-field__label" do %><%= render "admin/users/index/status_indicator", status: status %><% end %>
</div>
<% end %>
<button type="button" class="js-clear-filter-btn c-btn <%= params[:statuses].blank? ? "hidden" : "" %>" data-checkbox-fieldset-selector=".js-statuses-fieldset" data-filter-indicator-selector=".js-filtered-indicator-statuses" aria-label="Clear statuses filter">Clear filter</button>
<button type="button" class="js-clear-filter-btn c-btn <%= params[:statuses].blank? ? "hidden" : "" %>" data-checkbox-fieldset-selector=".js-statuses-fieldset" data-filter-indicator-selector=".js-filtered-indicator-statuses"
aria-label="<%= t("views.admin.users.filters.clear_filter.aria_status") %>">
<%= t("views.admin.users.filters.clear_filter.text") %>
</button>
</fieldset>
</details>
<details data-section="joining-date" class="admin-details">
<summary class="py-4 flex justify-between text-uppercase color-base-60 fs-s">
<span class="flex">
Joining date
<%= t("views.admin.users.filters.summary.join") %>
<span class="js-filtered-indicator-joining-date relative ml-2 <%= params[:joining_start].blank? && params[:joining_end].blank? ? "hidden" : "" %>">
<span class="block absolute c-indicator c-indicator--info"></span>
</span>
@ -76,7 +82,7 @@
<details data-section="organizations" class="admin-details">
<summary class="py-4 flex justify-between text-uppercase color-base-60 fs-s">
<span class="flex">
Organizations
<%= t("views.admin.users.filters.summary.orgs") %>
<span class="js-filtered-indicator-organizations relative ml-2 <%= params[:organizations].blank? ? "hidden" : "" %>">
<span class="block absolute c-indicator c-indicator--info"></span>
</span>
@ -84,7 +90,7 @@
<%= crayons_icon_tag("chevron-down", aria_hidden: true, class: "summary-icon") %>
</summary>
<fieldset class="js-organizations-fieldset pb-4 flex flex-col items-start">
<legend class="screen-reader-only">Organizations</legend>
<legend class="screen-reader-only"><%= t("views.admin.users.filters.legend.orgs") %></legend>
<% @organizations.each_with_index do |org, index| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :organizations, { multiple: true, class: "crayons-checkbox", id: "filter-org-#{index}", checked: params[:organizations]&.include?(org.id.to_s) }, org.id, false %>
@ -92,13 +98,16 @@
</div>
<% end %>
</fieldset>
<button type="button" class="js-clear-filter-btn c-btn <%= params[:organizations].blank? ? "hidden" : "" %>" data-checkbox-fieldset-selector=".js-organizations-fieldset" data-filter-indicator-selector=".js-filtered-indicator-organizations" aria-label="Clear organizations filter">Clear filter</button>
<button type="button" class="js-clear-filter-btn c-btn <%= params[:organizations].blank? ? "hidden" : "" %>" data-checkbox-fieldset-selector=".js-organizations-fieldset" data-filter-indicator-selector=".js-filtered-indicator-organizations"
aria-label="<%= t("views.admin.users.filters.clear_filter.aria_orgs") %>">
<%= t("views.admin.users.filters.clear_filter.text") %>
</button>
</details>
</div>
<div class="flex gap-2 mt-auto">
<button class="c-btn c-btn--primary grow-1" aria-describedby="filters-description">Apply filters</button>
<button type="button" class="js-filter-modal-cancel-btn c-btn grow-1">Cancel</button>
<button class="c-btn c-btn--primary grow-1" aria-describedby="filters-description"><%= t("views.admin.users.filters.submit") %></button>
<button type="button" class="js-filter-modal-cancel-btn c-btn grow-1"><%= t("views.admin.users.filters.cancel") %></button>
</div>
<% end %>
</div>

View file

@ -1,14 +1,14 @@
<div class="relative flex">
<button type="button" id="<%= user.id %>-action-dropdown-btn-<%= context %>" data-toggle-dropdown="<%= user.id %>-action-dropdown-<%= context %>" aria-controls="<%= user.id %>-action-dropdown-<%= context %>" aria-haspopup="true" aria-expanded="false"
class="c-btn c-btn--icon-alone ml-auto" aria-label="Invitation actions: <%= user.email %>">
class="c-btn c-btn--icon-alone ml-auto" aria-label="<%= t("views.admin.users.invite.aria_email", email: user.email) %>">
<%= crayons_icon_tag("overflow-vertical", aria_hidden: true) %>
</button>
<div id="<%= user.id %>-action-dropdown-<%= context %>" class="crayons-dropdown right-0">
<%= button_to resend_admin_invitation_path(user.id), class: "c-btn c-btn--icon-left w-100 align-left" do %>
<%= crayons_icon_tag("send", aria_hidden: true) %> Resend invite
<%= crayons_icon_tag("send", aria_hidden: true) %> <%= t("views.admin.users.invite.resend") %>
<% end %>
<%= button_to admin_invitation_path(user.id), method: :delete, class: "c-btn c-btn--icon-left w-100 align-left" do %>
<%= crayons_icon_tag("x", aria_hidden: true) %> Cancel invite
<%= crayons_icon_tag("x", aria_hidden: true) %> <%= t("views.admin.users.invite.cancel") %>
<% end %>
</div>
</div>

View file

@ -5,7 +5,7 @@
<img src="<%= organization.profile_image_url_for(length: 64) %>" width="32" height="32" alt="<%= organization.name %>" class="crayons-logo crayons-logo--l border border-2 border-base-0 <%= index.positive? ? "-ml-4" : "" %>">
<% end %>
<% if org_count > @organization_limit %>
<span class="fs-s pr-1">+ <%= overflow_count(org_count, imposed_limit: @organization_limit) %></span>
<span class="fs-s pr-1"><%= t("views.admin.users.more_orgs", count: overflow_count(org_count, imposed_limit: @organization_limit)) %></span>
<% end %>
</figure>
<% else %>

View file

@ -1 +1 @@
<span class="c-indicator mr-2 inline-block" style="--bg: <%= status_to_indicator_color(status) %>"></span><%= status %>
<span class="c-indicator mr-2 inline-block" style="--bg: <%= status_to_indicator_color(status) %>"></span><%= t("views.admin.users.statuses.#{status}") %>

View file

@ -7,41 +7,41 @@
<ul class="list-none">
<li>
<button class="c-btn align-left w-100 c-btn--icon-left"
data-modal-title="Add role"
data-modal-title="<%= t("views.admin.users.actions.assign_role.heading") %>"
data-modal-size="small"
data-modal-content-selector="#add-role-modal"
data-form-action="<%= user_status_admin_user_path(user) %>">
<%= crayons_icon_tag("badge", aria_hidden: true, class: "c-btn__icon") %>
Assign role
<%= t("views.admin.users.actions.assign_role.text") %>
</button>
</li>
<li>
<button class="c-btn align-left w-100 c-btn--icon-left"
data-modal-title="Add organization"
data-modal-title="<%= t("views.admin.users.actions.add_org.heading") %>"
data-modal-size="small"
data-modal-content-selector="#add-organization"
data-user-name="<%= user.name %>"
data-user-id="<%= user.id %>"
data-form-action="<%= user_status_admin_user_path(user) %>">
<%= crayons_icon_tag("team", aria_hidden: true, class: "c-btn__icon") %>
Add organization
<%= t("views.admin.users.actions.add_org.text") %>
</button>
</li>
<li>
<button class="c-btn align-left w-100 c-btn--icon-left"
data-modal-title="Adjust balance"
data-modal-title="<%= t("views.admin.users.actions.adjust_credit.heading") %>"
data-modal-size="small"
data-modal-content-selector="#adjust-balance"
data-user-name="<%= user.name %>"
data-unspent-credits-count="<%= user.unspent_credits_count %>"
data-form-action="<%= user_status_admin_user_path(user) %>">
<%= crayons_icon_tag("wallet", aria_hidden: true, class: "c-btn__icon") %>
Adjust credit balance
<%= t("views.admin.users.actions.adjust_credit.text") %>
</button>
<hr />
</li>
<li><button data-copy-email="<%= user.email %>" type="button" class="c-btn w-100 align-left c-btn--icon-left"> <%= crayons_icon_tag("copy", aria_hidden: true, class: "c-btn__icon") %>Copy email address</button></li>
<li><button data-copy-email="<%= user.email %>" type="button" class="c-btn w-100 align-left c-btn--icon-left"> <%= crayons_icon_tag("copy", aria_hidden: true, class: "c-btn__icon") %><%= t("views.admin.users.actions.copy_email") %></button></li>
</ul>
</div>
</div>

View file

@ -2,16 +2,16 @@
<%= form_with model: [:admin, OrganizationMembership.new], local: true, html: { class: "flex flex-col gap-4" } do |f| %>
<%= f.hidden_field :user_id %>
<div class="crayons-field">
<%= f.label :organization_id, "Organization ID", class: "crayons-field__label" %>
<%= f.label :organization_id, t("views.admin.users.actions.add_org.id"), class: "crayons-field__label" %>
<%= f.text_field :organization_id, class: "crayons-textfield", size: 8, required: true, inputmode: "numeric", type: "number", placeholder: "1234" %>
</div>
<div class="crayons-field">
<%= f.label :type_of_user, "Role", class: "crayons-field__label" %>
<p id="add-org-permission-description" class="crayons-field__description">You can assign <span class="js-user-name"></span> to be an Admin or a regular member.</p>
<%= f.select(:type_of_user, options_for_select(%w[member admin]), {}, class: "crayons-select", aria: { describedby: "add-org-permission-description" }) %>
<%= f.label :type_of_user, t("views.admin.users.actions.add_org.role"), class: "crayons-field__label" %>
<p id="add-org-permission-description" class="crayons-field__description"><%= t("views.admin.users.actions.add_org.role_desc_html", user: tag.span(class: "js-user-name")) %></p>
<%= f.select(:type_of_user, options_for_select({ t("views.admin.users.actions.add_org.member") => "member", t("views.admin.users.actions.add_org.admin") => "admin" }), {}, class: "crayons-select", aria: { describedby: "add-org-permission-description" }) %>
</div>
<div>
<%= f.submit "Add organization", class: "c-btn c-btn--primary" %>
<%= f.submit t("views.admin.users.actions.add_org.submit"), class: "c-btn c-btn--primary" %>
</div>
<% end %>
</div>

View file

@ -1,15 +1,15 @@
<div id="add-role-modal" class="hidden">
<%= form_for(:user, method: "patch", html: { class: "js-add-role-form flex flex-col gap-4", id: nil }) do |f| %>
<div class="crayons-field">
<%= f.label :user_status, "Role", class: "crayons-field__label" %>
<%= f.select(:user_status, grouped_options_for_select(role_options(current_user)), { include_blank: "Select role" }, class: "crayons-select") %>
<%= f.label :user_status, t("views.admin.users.actions.assign_role.role"), class: "crayons-field__label" %>
<%= f.select(:user_status, grouped_options_for_select(role_options(current_user)), { include_blank: t("views.admin.users.actions.assign_role.select") }, class: "crayons-select") %>
</div>
<div class="crayons-field">
<%= f.label :note_for_current_role, "Add a note to this action:", class: "crayons-field__label" %>
<%= f.label :note_for_current_role, t("views.admin.users.actions.assign_role.note"), class: "crayons-field__label" %>
<%= f.text_area :note_for_current_role, required: true, class: "crayons-textfield", placeholder: "" %>
</div>
<div>
<%= f.button "Add", class: "c-btn c-btn--primary" %>
<%= f.button t("views.admin.users.actions.assign_role.submit"), class: "c-btn c-btn--primary" %>
</div>
<% end %>
</div>

View file

@ -3,30 +3,31 @@
<div class="flex items-center gap-2 color-secondary">
<%= crayons_icon_tag(:wallet) %>
<p>
<span class="js-user-name"></span> currently has <span class="color-primary"><span class="js-unspent-credits-count"></span> credits</span>.
<%= t("views.admin.users.actions.adjust_credit.desc_html", user: tag.span(class: "js-user-name"), credits: tag.span(t("views.admin.users.actions.adjust_credit.credits_html", amount: tag.span(class: "js-unspent-credits-count")), class: "color-primary")) %>
</p>
</div>
<div class="crayons-field">
<%= f.label :credit_action, "Adjust balance", class: "crayons-field__label" %>
<%= f.label :credit_action, t("views.admin.users.actions.adjust_credit.adjust"), class: "crayons-field__label" %>
<div class="flex gap-2 items-center">
<%= f.select :credit_action, options_for_select(%w[Add]), {}, class: "js-credit-action crayons-select w-auto" %>
<%= f.text_field :credit_amount,
type: "number",
required: true,
class: "js-credit-amount crayons-textfield w-auto",
value: 1,
size: 5,
min: 1,
max: 9999 %>
<%= f.label :credit_amount, "credits", class: "color-secondary flex-1", "aria-label": "Amount of credits to add or remove" %>
<%= t("views.admin.users.actions.adjust_credit.selector_html",
action: f.select(:credit_action, options_for_select({ t("views.admin.users.actions.adjust_credit.add") => "Add" }), {}, class: "js-credit-action crayons-select w-auto"),
amount: f.text_field(:credit_amount,
type: "number",
required: true,
class: "js-credit-amount crayons-textfield w-auto",
value: 1,
size: 5,
min: 1,
max: 9999),
credits: f.label(:credit_amount, t("views.admin.users.actions.adjust_credit.unit"), class: "color-secondary flex-1", "aria-label": t("views.admin.users.actions.adjust_credit.aria_credits"))) %>
</div>
</div>
<div class="crayons-field">
<label for="balance-note" class="crayons-field__label">Add a note to this action:</label>
<label for="balance-note" class="crayons-field__label"><%= t("views.admin.users.actions.adjust_credit.note") %></label>
<%= f.text_area :new_note, id: "balance-note", size: 50, required: true, class: "crayons-textfield" %>
</div>
<div>
<button type="submit" class="c-btn c-btn--primary">Adjust balance</button>
<button type="submit" class="c-btn c-btn--primary"><%= t("views.admin.users.actions.adjust_credit.submit") %></button>
</div>
<% end %>
</div>

View file

@ -1,15 +1,15 @@
<div id="banish-for-spam">
<div class="flex flex-col gap-4">
<div class="js-banishable-user hidden">
<p>This action is irreversible.</p>
<p>Once banished, we will delete all content created by <span class="js-user-name"></span> and change their username to @spam_###.</p>
<p>Be careful with this action.</p>
<p><%= t("views.admin.users.danger.desc1") %></p>
<p><%= t("views.admin.users.banish.desc2_html", user: tag.span(class: "js-user-name")) %></p>
<p><%= t("views.admin.users.danger.desc3") %></p>
<%= form_for(:user, html: { method: :post, id: nil, class: "js-banish-form" }) do %>
<button class="c-btn c-btn--primary c-btn--destructive">Banish <span class="js-user-name"></span></button>
<button class="c-btn c-btn--primary c-btn--destructive"><%= t("views.admin.users.banish.submit_html", user: tag.span(class: "js-user-name")) %></button>
<% end %>
</div>
<div class="js-not-banishable-user">
<div class="crayons-notice crayons-notice--danger">This is not a new user. Only Super Admins or Support Admins are allowed to banish <span class="js-user-name"></span>.</div>
<div class="crayons-notice crayons-notice--danger"><%= t("views.admin.users.banish.notice_html", user: tag.span(class: "js-user-name")) %></div>
</div>
</div>
</div>

View file

@ -1,13 +1,13 @@
<div id="unpublish-all-posts">
<%= form_tag("/admin/member_manager/users/#{@user.id}/unpublish_all_articles", html: { class: "js-unpublish-form flex flex-col gap-4",
method: :post,
onsubmit: "return confirm('Are you sure? All posts will be unavailable to the community.')",
onsubmit: "return confirm('#{j t('views.admin.users.unpublish.onsubmit')}')",
id: nil }) do %>
<p>Once unpublished, all posts by <span class="js-user-name"></span> will become hidden and only accessible to themselves.</p>
<p class="mt-2">If <span class="js-user-name"></span> is not suspended, they can still re-publish their posts from their dashboard.</p>
<p class="mt-2"><%= label_tag "note[content]", "Note:" %><%= text_area_tag "note[content]", "", class: "crayons-textfield", placeholder: "Note text" %></p>
<p><%= t("views.admin.users.unpublish.text1_html", username: tag.span(class: "js-user-name")) %></p>
<p class="mt-2"><%= t("views.admin.users.unpublish.text2_html", username: tag.span(class: "js-user-name")) %></p>
<p class="mt-2"><%= label_tag "note[content]", t("views.admin.users.unpublish.note") %><%= text_area_tag "note[content]", "", class: "crayons-textfield", placeholder: t("views.admin.users.unpublish.note_placeholder") %></p>
<div>
<button class="c-btn c-btn--destructive c-btn--primary">Unpublish all posts</button>
<button class="c-btn c-btn--destructive c-btn--primary"><%= t("views.admin.users.unpublish.submit") %></button>
</div>
<% end %>
</div>

View file

@ -1,10 +1,10 @@
<% if @user.access_locked? %>
<div class="crayons-notice crayons-notice--danger py-2 pr-2 mb-3 flex justify-between items-center">
<p class="flex-1 flex">
<%= crayons_icon_tag(:lock, title: "Access locked", class: "mr-2") %>
This user has locked access.
<%= crayons_icon_tag(:lock, title: t("views.admin.users.profile.locked.icon"), class: "mr-2") %>
<%= t("views.admin.users.profile.locked.text") %>
</p>
<%= link_to "Unlock access", unlock_access_admin_user_path(@user), method: :patch, class: "c-link c-link--block c-link--branded" %>
<%= link_to t("views.admin.users.profile.locked.unlock"), unlock_access_admin_user_path(@user), method: :patch, class: "c-link c-link--block c-link--branded" %>
</div>
<% end %>
@ -23,28 +23,25 @@
<%= render "admin/users/show/profile/actions" %>
</div>
<div class="fs-s mb-1 color-secondary">
@<%= @user.username %> <span class="opacity-50">&bull;</span> ID <%= @user.id %> <span class="opacity-50">&bull;</span>
@<%= @user.username %> <span class="opacity-50">&bull;</span> <%= t("views.admin.users.profile.id", userid: @user.id) %> <span class="opacity-50">&bull;</span>
<% if @user.registered? %>
Member since
<time datetime="<%= @user.created_at.strftime("%Y-%m-%dT%H:%M:%S%z") %>" title="<%= @user.created_at.strftime("%Y-%m-%d, %H:%M") %>">
<%= @user.created_at.strftime("%b %e '%y") %>
</time>
<%= t("views.admin.users.profile.since_html", time: tag.time(l(@user.created_at, format: :short_with_yy), datetime: @user.created_at.strftime("%Y-%m-%dT%H:%M:%S%z"), title: l(@user.created_at, format: :admin_user))) %>
<% else %>
<span class="color-accent-danger fw-medium">User has not accepted their invitation yet.</span>
<span class="color-accent-danger fw-medium"><%= t("views.admin.users.profile.not_member") %></span>
<% end %>
</div>
<div>
<ul class="flex flex-col s:flex-row gap-3 s:gap-6 flex-wrap">
<li>
<a href="mailto:<%= @user.email %>" class="c-link c-link--icon-left inline-block" target="_blank">
<%= crayons_icon_tag(:email, title: "Email", class: "c-link__icon") %>
<%= crayons_icon_tag(:email, title: t("views.admin.users.profile.email"), class: "c-link__icon") %>
<%= @user.email %>
</a>
</li>
<% if @user.github_username %>
<li>
<a href="https://github.com/<%= @user.github_username %>" class="c-link c-link--icon-left inline-block" target="_blank">
<%= crayons_icon_tag(:github, title: "GitHub") %>
<%= crayons_icon_tag(:github, title: t("views.admin.users.profile.github")) %>
<%= @user.github_username %>
</a>
</li>
@ -52,7 +49,7 @@
<% if @user.twitter_username %>
<li>
<a href="https://twitter.com/<%= @user.twitter_username %>" class="c-link c-link--icon-left inline-block" target="_blank">
<%= crayons_icon_tag(:twitter, title: "Twitter") %>
<%= crayons_icon_tag(:twitter, title: t("views.admin.users.profile.twitter")) %>
<%= @user.twitter_username %>
</a>
</li>

View file

@ -1,12 +1,15 @@
<nav class="mb-2" aria-label="Member details">
<ul class="crayons-navigation crayons-navigation--horizontal">
<li><%= link_to "Overview", admin_user_path(@user.id), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'overview'}", aria: @current_tab == "overview" ? { current: "page" } : {} %></li>
<li><%= link_to "Notes", admin_user_path(@user.id, tab: :notes), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'notes'}", aria: @current_tab == "notes" ? { current: "page" } : {} %></li>
<li><%= link_to "Emails", admin_user_path(@user.id, tab: :emails), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'emails'}", aria: @current_tab == "emails" ? { current: "page" } : {} %></li></li>
<li><%= link_to "Reports", admin_user_path(@user.id, tab: :reports), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'reports'}", aria: @current_tab == "reports" ? { current: "page" } : {} %></li></li>
<li><%= link_to "Flags", admin_user_path(@user.id, tab: :flags), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'flags'}", aria: @current_tab == "flags" ? { current: "page" } : {} %></li></li>
<li><%= link_to t("views.admin.users.tabs.overview"), admin_user_path(@user.id), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'overview'}", aria: @current_tab == "overview" ? { current: "page" } : {} %></li>
<li><%= link_to t("views.admin.users.tabs.notes"), admin_user_path(@user.id, tab: :notes), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'notes'}", aria: @current_tab == "notes" ? { current: "page" } : {} %></li>
<li><%= link_to t("views.admin.users.tabs.emails"), admin_user_path(@user.id, tab: :emails), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'emails'}", aria: @current_tab == "emails" ? { current: "page" } : {} %></li></li>
<li><%= link_to t("views.admin.users.tabs.reports"), admin_user_path(@user.id, tab: :reports), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'reports'}", aria: @current_tab == "reports" ? { current: "page" } : {} %></li></li>
<li><%= link_to t("views.admin.users.tabs.flags"), admin_user_path(@user.id, tab: :flags), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'flags'}", aria: @current_tab == "flags" ? { current: "page" } : {} %></li></li>
<% if @unpublish_all_data.exists? %>
<li><%= link_to "Unpublish All History", admin_user_path(@user.id, tab: :unpublish_logs), class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'unpublish_logs'}", aria: @current_tab == "flags" ? { current: "page" } : {} %></li>
<li><%= link_to t("views.admin.users.tabs.unpublish_logs"),
admin_user_path(@user.id, tab: :unpublish_logs),
class: "crayons-navigation__item #{'crayons-navigation__item crayons-navigation__item--current' if @current_tab == 'unpublish_logs'}",
aria: @current_tab == "flags" ? { current: "page" } : {} %></li>
<% end %>
</ul>
</nav>

View file

@ -1,19 +1,19 @@
<h2 class="crayons-subtitle-1">Send an email</h2>
<p class="fs-m fw-normal mb-2">To: <%= @user.name %> (<%= @user.email %>)</p>
<h2 class="crayons-subtitle-1"><%= t("views.admin.users.emails.send") %></h2>
<p class="fs-m fw-normal mb-2"><%= t("views.admin.users.emails.to", user: @user.name, email: @user.email) %></p>
<%= form_with url: send_email_admin_user_path(@user), local: true, class: "grid gap-4 pt-3" do |f| %>
<%= f.hidden_field(:user_id, value: @user.id) %>
<div class="crayons-field">
<%= f.label :email_subject, "Subject", class: "crayons-field__label" %>
<%= f.text_field :email_subject, class: "crayons-textfield", placeholder: "What do you want to say in the subject line?", required: true %>
<%= f.label :email_subject, t("views.admin.users.emails.subject"), class: "crayons-field__label" %>
<%= f.text_field :email_subject, class: "crayons-textfield", placeholder: t("views.admin.users.emails.subject_placeholder"), required: true %>
</div>
<div class="crayons-field">
<%= f.label :email_body, "Body", class: "crayons-field__label" %>
<%= f.text_area :email_body, class: "crayons-textfield", rows: "7", placeholder: "What do you want to say?", required: true %>
<%= f.label :email_body, t("views.admin.users.emails.body"), class: "crayons-field__label" %>
<%= f.text_area :email_body, class: "crayons-textfield", rows: "7", placeholder: t("views.admin.users.emails.body_placeholder"), required: true %>
</div>
<div>
<%= f.button "Send email", class: "c-btn c-btn--primary", type: "submit" %>
<%= f.button t("views.admin.users.emails.send_submit"), class: "c-btn c-btn--primary", type: "submit" %>
</div>
<% end %>

View file

@ -1,9 +1,9 @@
<div class="flex flex-col h-100">
<h2 class="crayons-subtitle-1">Previous emails (<%= @user.email_messages.count %>)</h2>
<h2 class="crayons-subtitle-1"><%= t("views.admin.users.emails.previous", num: @user.email_messages.count) %></h2>
<% if @user.email_messages.empty? %>
<div class="align-center flex flex-col justify-center my-auto py-7">
<h3 class="fs-xl fw-normal mb-2">No previous emails found for <%= @user.name %>.</h3>
<p class="color-secondary">Any emails sent to <%= @user.email %> will be found here.</p>
<h3 class="fs-xl fw-normal mb-2"><%= t("views.admin.users.emails.empty1", user: @user.name) %></h3>
<p class="color-secondary"><%= t("views.admin.users.emails.empty2", email: @user.email) %></p>
</div>
<% else %>
<div class="pt-3">
@ -14,8 +14,8 @@
<%= email.subject %>
</a>
</h3>
<time datetime="<%= email.sent_at&.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0" title="<%= email.sent_at&.strftime("%Y-%m-%d, %H:%M") %>">
<%= email.sent_at&.strftime("%b %e '%y") %>
<time datetime="<%= email.sent_at&.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0" title="<%= l(email.sent_at, format: :admin_user) if email.sent_at %>">
<%= l(email.sent_at, format: :short_with_yy) if email.sent_at %>
</time>
</article>
<% end %>

View file

@ -1,17 +1,14 @@
<%= form_with url: verify_email_ownership_admin_user_path(@user), local: true, class: "crayons-card mb-4 p-3 m:p-4 s:pl-4 m:pl-7 pr-4 flex flex-col s:flex-row justify-between gap-4 s:items-center" do |f| %>
<% unless @last_email_verification_date %>
<div>
<h2 class="crayons-subtitle-2">Email not verified</h2>
<p class="color-secondary"><%= @user.name %>'s email hasn't been verified yet.</p>
<h2 class="crayons-subtitle-2"><%= t("views.admin.users.emails.not_verified.subtitle") %></h2>
<p class="color-secondary"><%= t("views.admin.users.emails.not_verified.desc", user: @user.name) %></p>
</div>
<%= f.button "Verify email address", class: "c-btn c-btn--secondary whitespace-nowrap" %>
<%= f.button t("views.admin.users.emails.verify"), class: "c-btn c-btn--secondary whitespace-nowrap" %>
<% else %>
<p>
Last verified on
<time datetime="<%= @last_email_verification_date.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="fw-medium whitespace-nowrap">
<%= @last_email_verification_date.strftime("%b %e '%y, %H:%M") %>
</time>
<%= t("views.admin.users.emails.verified_html", time: tag.time(l(@last_email_verification_date, format: :email_verified), datetime: @last_email_verification_date.strftime("%Y-%m-%dT%H:%M:%S%z"), class: "fw-medium whitespace-nowrap")) %>
</p>
<%= f.button "Reverify", class: "c-btn c-btn--secondary whitespace-nowrap" %>
<%= f.button t("views.admin.users.emails.reverify"), class: "c-btn c-btn--secondary whitespace-nowrap" %>
<% end %>
<% end %>

View file

@ -17,28 +17,27 @@
<h3 class="crayons-subtitle-3 mb-1">
<%= link_to flag.reactable.path, class: "c-link" do %>
<% if flag.reactable_type == "Article" %>
Post "<%= flag.reactable.title %>"
<%= t("views.admin.users.flags.type.article", title: flag.reactable.title) %>
<% elsif flag.reactable_type == "User" %>
User's profile
<%= t("views.admin.users.flags.type.profile") %>
<% elsif flag.reactable_type == "Comment" %>
Comment on "<%= flag.reactable.title %>"
<%= t("views.admin.users.flags.type.comment", title: flag.reactable.title) %>
<% end %>
<% end %>
</h3>
<p class="fs-s color-secondary">
Flagged by
<%= link_to flag.user.username, admin_user_path(flag.user_id), class: "c-link c-link--branded" %>
<%= t("views.admin.users.flags.origin_html", user: link_to(flag.user.username, admin_user_path(flag.user_id), class: "c-link c-link--branded")) %>
</p>
</div>
<time datetime="<%= flag.created_at&.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0" title="<%= flag.created_at&.strftime("%Y-%m-%d, %H:%M") %>">
<%= flag.created_at&.strftime("%b %e '%y") %>
<time datetime="<%= flag.created_at&.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0" title="<%= l(flag.created_at, format: :admin_user) if flag.created_at %>">
<%= l(flag.created_at, format: :short_with_yy) %>
</time>
</article>
<% end %>
</div>
<% else %>
<div class="align-center flex flex-col justify-center my-auto py-8">
<h3 class="fs-xl fw-normal mb-2">No flags received against <%= @user.name %> yet.</h3>
<p class="color-secondary">Any flags received against <%= @user.name %> will be found here.</p>
<h3 class="fs-xl fw-normal mb-2"><%= t("views.admin.users.flags.empty1", user: @user.name) %></h3>
<p class="color-secondary"><%= t("views.admin.users.flags.empty2", user: @user.name) %></p>
</div>
<% end %>

View file

@ -1,12 +1,12 @@
<header>
<h2 class="crayons-subtitle-1 mb-1">Write a user note</h2>
<h2 class="crayons-subtitle-1 mb-1"><%= t("views.admin.users.notes.write") %></h2>
</header>
<%= form_with model: @user, url: admin_user_path(@user), method: :patch, html: { class: "grid gap-4 pt-3 m:sticky top-8" }, local: true do |f| %>
<div class="crayons-field">
<%= f.label :new_note, "Note", class: "crayons-field__label" %>
<%= f.text_area :new_note, class: "crayons-textfield", rows: 5, placeholder: "Add a note...", aria: { describedby: "note-description" }, required: true %>
<%= f.label :new_note, t("views.admin.users.notes.note"), class: "crayons-field__label" %>
<%= f.text_area :new_note, class: "crayons-textfield", rows: 5, placeholder: t("views.admin.users.notes.note_placeholder"), aria: { describedby: "note-description" }, required: true %>
</div>
<div>
<%= f.button "Submit", class: "c-btn c-btn--primary", type: "submit" %>
<%= f.button t("views.admin.users.notes.submit"), class: "c-btn c-btn--primary", type: "submit" %>
</div>
<% end %>

View file

@ -1,27 +1,24 @@
<div class="flex flex-col h-100">
<h2 class="crayons-subtitle-1">Previous notes (<%= @notes.count %>)</h2>
<h2 class="crayons-subtitle-1"><%= t("views.admin.users.notes.previous", num: @notes.count) %></h2>
<% if @notes.load.empty? %>
<div class="align-center flex flex-col justify-center my-auto py-7">
<h3 class="fs-xl fw-normal mb-2">No previous notes found for <%= @user.name %>.</h3>
<p class="color-secondary">Any notes added for <%= @user.name %> will be found here. </p>
<h3 class="fs-xl fw-normal mb-2"><%= t("views.admin.users.notes.empty1", user: @user.name) %></h3>
<p class="color-secondary"><%= t("views.admin.users.notes.empty2", user: @user.name) %></p>
</div>
<% else %>
<div class="pt-3">
<% @notes.each do |note| %>
<% author = User.find(note.author_id).username %>
<article class="c-list-item flex justify-between gap-4">
<h3 class="screen-reader-only">Note by <%= author %> on <%= note.created_at.strftime("%b %e '%y") %></h3>
<h3 class="screen-reader-only"><%= t("views.admin.users.notes.note_by", author: author, time: l(note.created_at, format: :short_with_yy)) %></h3>
<div>
<p><%= note.content %></p>
<p class="fs-s color-secondary">
<%= note.reason.presence %>
<% if note.author_id.present? %>
by <span class="fw-medium"><%= author %></span>
<% end %>
<%= t("views.admin.users.notes.reason_by_html", reason: note.reason.presence, by: note.author_id.present? ? t("views.admin.users.notes.by_html", author: tag.span(author, class: "fw-medium")) : "") %>
</p>
</div>
<time datetime="<%= note.created_at.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0" title="<%= note.created_at.strftime("%Y-%m-%d, %H:%M") %>">
<%= note.created_at.strftime("%b %e '%y") %>
<time datetime="<%= note.created_at.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0" title="<%= l(note.created_at, format: :admin_user) %>">
<%= l(note.created_at, format: :short_with_yy) %>
</time>
</article>
<% end %>

View file

@ -1,18 +1,21 @@
<header class="mb-4">
<h2 class="crayons-subtitle-2 mb-1 flex items-center">
<%= crayons_icon_tag(:wallet, title: "Wallet", aria_hidden: true, class: "mr-2") %>
Credits
<%= crayons_icon_tag(:wallet, title: t("views.admin.users.overview.credits.icon"), aria_hidden: true, class: "mr-2") %>
<%= t("views.admin.users.overview.credits.subtitle") %>
</h2>
<p class="color-secondary">Grant/remove credits to purchase classified listings. <a href="https://admin.forem.com/docs/advanced-customization/config/credits" rel="noopener noreferer" target="_blank" class="c-link c-link--branded" aria-label="Learn more about credits">Learn more</a></p>
<p class="color-secondary">
<%= t("views.admin.users.overview.credits.desc") %>
<a href="https://admin.forem.com/docs/advanced-customization/config/credits" rel="noopener noreferer" target="_blank" class="c-link c-link--branded" aria-label="<%= t("views.admin.users.overview.credits.aria_learn") %>"><%= t("views.admin.users.overview.learn") %></a>
</p>
</header>
<div class="flex justify-between items-center">
<p data-testid="user-credits" class="fs-2xl fw-heavy"><%= @user.unspent_credits_count %></p>
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button"
data-modal-title="Adjust balance"
data-modal-title="<%= t("views.admin.users.actions.adjust_credit.heading") %>"
data-modal-size="small"
data-modal-content-selector="#adjust-balance"
data-user-name="<%= @user.name %>"
data-unspent-credits-count="<%= @user.unspent_credits_count %>"
data-form-action="<%= admin_user_path(@user) %>">Adjust balance</button>
data-form-action="<%= admin_user_path(@user) %>"><%= t("views.admin.users.overview.credits.button") %></button>
</div>
<%= render "admin/users/modals/adjust_credits_modal" %>

View file

@ -1,18 +1,23 @@
<header class="mb-4">
<h2 class="crayons-subtitle-2 mb-1 flex items-center">
<%= crayons_icon_tag(:team, title: "Organizations", aria_hidden: true, class: "mr-2") %>
Organizations
<%= crayons_icon_tag(:team, title: t("views.admin.users.overview.orgs.icon"), aria_hidden: true, class: "mr-2") %>
<%= t("views.admin.users.overview.orgs.subtitle") %>
</h2>
<p class="color-secondary">Add this member to organizations in your community. <a href="https://admin.forem.com/docs/managing-your-community/organization-pages#organization-roles" rel="noopener noreferer" target="_blank" class="c-link c-link--branded" aria-label="Learn more about organizations">Learn more</a></p>
<p class="color-secondary">
<%= t("views.admin.users.overview.orgs.desc") %>
<a href="https://admin.forem.com/docs/managing-your-community/organization-pages#organization-roles" rel="noopener noreferer" target="_blank" class="c-link c-link--branded" aria-label="<%= t("views.admin.users.overview.orgs.aria_learn") %>"><%= t("views.admin.users.overview.learn") %></a>
</p>
</header>
<% if @organization_memberships.load.empty? %>
<div class="flex justify-between items-center gap-4">
<p>Not part of any organization yet.</p>
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-title="Add organization" data-modal-size="small" data-modal-content-selector="#add-organization" data-user-name="<%= @user.name %>" data-user-id="<%= @user.id %>">Add organization</button>
<p><%= t("views.admin.users.overview.orgs.empty") %></p>
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-title="<%= t("views.admin.users.actions.add_org.heading") %>" data-modal-size="small" data-modal-content-selector="#add-organization" data-user-name="<%= @user.name %>" data-user-id="<%= @user.id %>">
<%= t("views.admin.users.overview.orgs.button") %>
</button>
</div>
<% else %>
<%# Used for aria-describedby on input fields, placed here to avoid duplicate IDs when mapping over orgs %>
<span class="screen-reader-only" id="edit-org-permission-description">You can assign <%= @user.name %> to be an Admin or a regular member.</span>
<span class="screen-reader-only" id="edit-org-permission-description"><%= t("views.admin.users.actions.add_org.role_desc_html", user: @user.name) %></span>
<% @organization_memberships.each do |org_membership| %>
<div class="c-card c-card--interactive flex p-2 relative items-center mb-2 pl-3">
<div class="flex items-center gap-2 flex-1">
@ -23,17 +28,17 @@
<a href="<%= org_membership.organization.path %>" target="_blank" rel="noopener" class="c-link">
<%= org_membership.organization.name %>
</a>
<span class="opacity-50">&bull;</span> <%= org_membership.type_of_user.capitalize %></span>
<span class="opacity-50">&bull;</span> <%= t("views.admin.users.overview.orgs.type.#{org_membership.type_of_user}") %></span>
</h3>
</div>
<div class="c-card__revealable shrink-0">
<button type="button" class="c-btn c-btn--icon-alone" aria-label="Edit <%= org_membership.organization.name %> organization membership"
data-modal-title="Edit <%= @user.name %>'s role at an organization" data-modal-size="small" data-modal-content-selector="#edit-organization-<%= org_membership.organization.id %>">
<button type="button" class="c-btn c-btn--icon-alone" aria-label="<%= t("views.admin.users.overview.orgs.aria_edit", org: org_membership.organization.name) %>"
data-modal-title="<%= t("views.admin.users.actions.add_org.edit", user: @user.name) %>" data-modal-size="small" data-modal-content-selector="#edit-organization-<%= org_membership.organization.id %>">
<%= crayons_icon_tag(:edit, class: "c-btn__icon", aria_hidden: true) %>
</button>
<%= form_with model: [:admin, org_membership], method: :delete, local: true, html: { class: "inline" } do |f| %>
<%= f.button crayons_icon_tag(:x, class: "c-btn__icon", aria_hidden: true), aria: { label: "Revoke #{org_membership.organization.name} organization membership" }, class: "c-btn c-btn--destructive c-btn--icon-alone",
data: { confirm: "Are you sure you want to remove #{org_membership.user.name} from #{org_membership.organization.name}?" } %>
<%= f.button crayons_icon_tag(:x, class: "c-btn__icon", aria_hidden: true), aria: { label: t("views.admin.users.overview.orgs.aria_revoke", org: org_membership.organization.name) }, class: "c-btn c-btn--destructive c-btn--icon-alone",
data: { confirm: t("views.admin.users.overview.orgs.revoke_confirm", user: org_membership.user.name, org: org_membership.organization.name) } %>
<% end %>
</div>
</div>
@ -41,22 +46,30 @@
<div id="edit-organization-<%= org_membership.organization.id %>" class="hidden">
<%= form_with model: [:admin, org_membership], method: :patch, local: true do |f| %>
<div class="crayons-field mb-4">
<%= f.label "Role", class: "crayons-field__label", for: "edit_#{org_membership.organization.id}_type_of_user" %>
<p class="crayons-field__description">You can assign <%= @user.name %> to be an Admin or a regular member.</p>
<%= f.select :type_of_user, options_for_select(%w[member admin], selected: org_membership.type_of_user), {}, id: "edit_#{org_membership.organization.id}_type_of_user", class: "crayons-select", aria: { describedby: "edit-org-permission-description" } %>
<%= f.label t("views.admin.users.actions.add_org.role"), class: "crayons-field__label", for: "edit_#{org_membership.organization.id}_type_of_user" %>
<p class="crayons-field__description"><%= t("views.admin.users.actions.add_org.role_desc_html", user: @user.name) %></p>
<%= f.select(:type_of_user,
options_for_select(
{ t("views.admin.users.actions.add_org.member") => "member", t("views.admin.users.actions.add_org.admin") => "admin" },
selected: org_membership.type_of_user,
),
{},
id: "edit_#{org_membership.organization.id}_type_of_user",
class: "crayons-select",
aria: { describedby: "edit-org-permission-description" }) %>
</div>
<%= f.button "Submit", class: "c-btn c-btn--primary", type: "submit" %>
<%= f.button t("views.admin.users.overview.orgs.submit"), class: "c-btn c-btn--primary", type: "submit" %>
<% end %>
</div>
<% end %>
<button
class="c-btn c-btn--secondary whitespace-nowrap w-100"
type="button"
data-modal-title="Add <%= @user.name %> to an organization"
data-modal-title="<%= t("views.admin.users.actions.add_org.add", user: @user.name) %>"
data-modal-size="small"
data-modal-content-selector="#add-organization"
data-user-name="<%= @user.name %>"
data-user-id="<%= @user.id %>">Add organization</button>
data-user-id="<%= @user.id %>"><%= t("views.admin.users.overview.orgs.button") %></button>
<% end %>
<%= render "admin/users/modals/add_organization_modal" %>

View file

@ -1,14 +1,19 @@
<header class="mb-4">
<h2 class="crayons-subtitle-2 mb-1 flex items-center">
<%= crayons_icon_tag(:badge, title: "Roles", aria_hidden: true, class: "mr-2") %>
Roles
<%= crayons_icon_tag(:badge, title: t("views.admin.users.overview.roles.icon"), aria_hidden: true, class: "mr-2") %>
<%= t("views.admin.users.overview.roles.subtitle") %>
</h2>
<p class="color-secondary">Grant this member permissions by assigning a role. <a href="https://admin.forem.com/docs/forem-basics/user-roles" rel="noopener noreferer" target="_blank" class="c-link c-link--branded" aria-label="Learn more about roles">Learn more</a></p>
<p class="color-secondary">
<%= t("views.admin.users.overview.roles.desc") %>
<a href="https://admin.forem.com/docs/forem-basics/user-roles" rel="noopener noreferer" target="_blank" class="c-link c-link--branded" aria-label="<%= t("views.admin.users.overview.roles.aria_learn") %>"><%= t("views.admin.users.overview.learn") %></a>
</p>
</header>
<% unless @user.roles.any? %>
<div class="flex justify-between items-center">
<p>No roles assigned yet.</p>
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-title="Add role" data-modal-size="m" data-modal-content-selector="#add-role-modal" data-form-action="<%= user_status_admin_user_path(@user) %>">Assign role</button>
<p><%= t("views.admin.users.overview.roles.empty") %></p>
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-title="<%= t("views.admin.users.actions.assign_role.heading") %>" data-modal-size="m" data-modal-content-selector="#add-role-modal" data-form-action="<%= user_status_admin_user_path(@user) %>">
<%= t("views.admin.users.overview.roles.button") %>
</button>
</div>
<% else %>
<ul class="flex flex-wrap gap-2">
@ -20,27 +25,23 @@
type="button"
aria-disabled="true">
<%= crayons_icon_tag(:lock, class: "c-pill__description-icon", aria_hidden: true, width: 18, height: 18) %>
<% if role.resource_name %>
<%= role.resource_name.to_s %>
<% else %>
<%= role_display_name(role) %>
<% end %>
<span data-testid="tooltip" class="crayons-tooltip__content">You can't remove this role.</span>
<%= t("views.admin.users.overview.roles.name.#{role.name}", default: role.resource_name ? role.resource_name.to_s : role_display_name(role)) %>
<span data-testid="tooltip" class="crayons-tooltip__content"><%= t("views.admin.users.overview.roles.locked") %></span>
</button>
<% else %>
<%= button_to url_for(action: :destroy, user_id: @user.id, role: role.name.to_sym, resource_type: role.resource_type), method: :delete, data: { confirm: "Are you sure?" }, class: "c-pill c-pill--action-icon" do %>
<span class="screen-reader-only">Remove role:</span>
<% if role.resource_name %>
<%= role.resource_name.to_s %>
<% else %>
<%= role_display_name(role) %>
<% end %>
<%= button_to url_for(action: :destroy, user_id: @user.id, role: role.name.to_sym, resource_type: role.resource_type), method: :delete, data: { confirm: t("views.admin.users.overview.roles.remove_confirm") }, class: "c-pill c-pill--action-icon" do %>
<span class="screen-reader-only"><%= t("views.admin.users.overview.roles.remove") %></span>
<%= t("views.admin.users.overview.roles.name.#{role.name}", default: role.resource_name ? role.resource_name.to_s : role_display_name(role)) %>
<%= crayons_icon_tag(:x, class: "c-pill__action-icon", aria_hidden: true, width: 18, height: 18) %>
<% end %>
<% end %>
</li>
<% end %>
<li><button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-title="Add role" data-modal-size="small" data-modal-content-selector="#add-role-modal" data-form-action="<%= user_status_admin_user_path(@user) %>">Assign role</button></li>
<li>
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-title="<%= t("views.admin.users.actions.assign_role.heading") %>" data-modal-size="small" data-modal-content-selector="#add-role-modal" data-form-action="<%= user_status_admin_user_path(@user) %>">
<%= t("views.admin.users.overview.roles.button") %>
</button>
</li>
</ul>
<% end %>

View file

@ -2,30 +2,30 @@
<dl class="grid grid-flow-col grid-cols-3 grid-rows-2 l:grid-cols-6 l:grid-rows-1">
<div class="flex flex-col items-center">
<dd><strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.comments_count %></strong></dd>
<dt class="fs-s l:fs-base color-secondary fw-normal">Comments</dt>
<dt class="fs-s l:fs-base color-secondary fw-normal"><%= t("views.admin.users.overview.comments") %></dt>
</div>
<div class="flex flex-col items-center">
<dd><strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.articles_count %></strong></dd>
<dt class="fs-s l:fs-base color-secondary fw-normal">Posts</dt>
<dt class="fs-s l:fs-base color-secondary fw-normal"><%= t("views.admin.users.overview.posts") %></dt>
</div>
<div class="flex flex-col items-center">
<dd><strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.reactions_count %></strong></dd>
<dt class="fs-s l:fs-base color-secondary fw-normal">Reactions</dt>
<dt class="fs-s l:fs-base color-secondary fw-normal"><%= t("views.admin.users.overview.reactions") %></dt>
</div>
<div class="flex flex-col items-center">
<dd><strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.followers_count %></strong></dd>
<dt class="fs-s l:fs-base color-secondary fw-normal">Followers</dt>
<dt class="fs-s l:fs-base color-secondary fw-normal"><%= t("views.admin.users.overview.followers") %></dt>
</div>
<div class="flex flex-col items-center">
<dd><strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.following_users_count %></strong></dd>
<dt class="fs-s l:fs-base color-secondary fw-normal">Following</dt>
<dt class="fs-s l:fs-base color-secondary fw-normal"><%= t("views.admin.users.overview.following") %></dt>
</div>
<div class="flex flex-col items-center">
<dd><strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.badge_achievements_count %></strong></dd>
<dt class="fs-s l:fs-base color-secondary fw-normal">Badges</dt>
<dt class="fs-s l:fs-base color-secondary fw-normal"><%= t("views.admin.users.overview.badges") %></dt>
</div>
</dl>

View file

@ -1,9 +1,12 @@
<header class="mb-4">
<h2 class="crayons-subtitle-2 mb-1 flex items-center">
<%= crayons_icon_tag("mod-filled", title: "Mod", aria_hidden: true, class: "mr-2") %>
Tags
<%= crayons_icon_tag("mod-filled", title: t("views.admin.users.overview.tag_mod.icon"), aria_hidden: true, class: "mr-2") %>
<%= t("views.admin.users.overview.tag_mod.subtitle") %>
</h2>
<p class="color-secondary">Add a tag that this member can moderate. <a href="https://admin.forem.com/docs/managing-your-community/tag-moderator-features" rel="noopener noreferer" target="_blank" class="c-link c-link--branded" aria-label="Learn more about tag moderation">Learn more</a></p>
<p class="color-secondary">
<%= t("views.admin.users.overview.tag_mod.desc") %>
<a href="https://admin.forem.com/docs/managing-your-community/tag-moderator-features" rel="noopener noreferer" target="_blank" class="c-link c-link--branded" aria-label="<%= t("views.admin.users.overview.tag_mod.aria_learn") %>"><%= t("views.admin.users.overview.learn") %></a>
</p>
</header>
<% moderated_tags = @user.roles.select { |role| role.name == "tag_moderator" } %>
@ -14,7 +17,7 @@
<li>
<%# You can't currently remove a tag from this view %>
<button class="c-pill cursor-default" aria-disabled="true" aria-describedby="tag-moderation-description" style="--icon-right-color: var(--color-secondary); --icon-right-color-hover: var(--accent-danger);" type="button">
<span class="screen-reader-only">Remove tag:</span>
<span class="screen-reader-only"><%= t("views.admin.users.overview.tag_mod.remove") %></span>
#<%= role.resource_name.to_s %>
</button>
</li>
@ -23,5 +26,5 @@
<% end %>
<div id="tag-moderation-description" class="crayons-notice crayons-notice--warning">
Adding tags for moderation is currently only possible through a particular <a href="<%= admin_tags_path %>" class="c-link c-link--branded">tag's page</a>, by providing the user's ID (<strong><%= @user.id %></strong>).
<%= t("views.admin.users.overview.tag_mod.notice_html", tag_page: link_to(t("views.admin.users.overview.tag_mod.tag_page"), admin_tags_path, class: "c-link c-link--branded"), user_id: tag.strong(@user.id)) %>
</div>

View file

@ -1,42 +1,42 @@
<%= javascript_packs_with_chunks_tag "admin/editUser", defer: true %>
<div class="flex relative justify-between s:justify-end gap-2 my-2 s:my-0">
<%= link_to "Visit profile", @user.path, class: "c-cta", target: "_blank", rel: "noopener" %>
<%= link_to t("views.admin.users.profile.visit"), @user.path, class: "c-cta", target: "_blank", rel: "noopener" %>
<div class="dropdown-trigger-container">
<button type="button" class="c-btn c-btn--icon-alone dropdown-trigger" id="options-dropdown-trigger" aria-haspopup="true" aria-expanded="false" aria-controls="options-dropdown">
<%= crayons_icon_tag("overflow-vertical", title: "Options") %>
<%= crayons_icon_tag("overflow-vertical", title: t("views.admin.users.profile.options.icon")) %>
</button>
<div class="crayons-dropdown right-0 left-0 s:left-auto" id="options-dropdown">
<ul class="p-0">
<% if @user.access_locked? %>
<li><%= link_to "Unlock access", unlock_access_admin_user_path(@user), method: :patch, class: "c-link c-link--block" %></li>
<li><%= link_to t("views.admin.users.profile.locked.unlock"), unlock_access_admin_user_path(@user), method: :patch, class: "c-link c-link--block" %></li>
<% end %>
<li><button type="button" class="c-btn w-100 align-left" data-modal-title="Export <%= @user.name %>'s data" data-modal-size="small" data-modal-content-selector="#export-data">Export data</button></li>
<li><button type="button" class="c-btn w-100 align-left" data-modal-title="Merge users" data-modal-size="small" data-modal-content-selector="#merge-accounts">Merge users</button></li>
<li><button type="button" class="c-btn w-100 align-left" data-modal-title="<%= t("views.admin.users.export.heading", user: @user.name) %>" data-modal-size="small" data-modal-content-selector="#export-data"><%= t("views.admin.users.profile.options.export") %></button></li>
<li><button type="button" class="c-btn w-100 align-left" data-modal-title="<%= t("views.admin.users.merge.heading") %>" data-modal-size="small" data-modal-content-selector="#merge-accounts"><%= t("views.admin.users.profile.options.merge") %></button></li>
<% if @user.articles_count > 0 %>
<li>
<button type="button"
class="c-btn w-100 align-left"
data-modal-title="Unpublish all posts"
data-modal-title="<%= t("views.admin.users.unpublish.heading") %>"
data-modal-size="small"
data-modal-content-selector="#unpublish-all-posts"
data-form-action="<%= unpublish_all_articles_admin_user_path(@user) %>"
data-user-name="<%= @user.name %>">Unpublish all posts</button>
data-user-name="<%= @user.name %>"><%= t("views.admin.users.profile.options.unpublish") %></button>
</li>
<% end %>
<% if @user.identities.any? %>
<li><button type="button" class="c-btn w-100 align-left" data-modal-title="Remove social accounts" data-modal-size="medium" data-modal-content-selector="#remove-social-accounts">Remove social accounts</button></li>
<li><button type="button" class="c-btn w-100 align-left" data-modal-title="<%= t("views.admin.users.social.heading") %>" data-modal-size="medium" data-modal-content-selector="#remove-social-accounts"><%= t("views.admin.users.profile.options.remove_social") %></button></li>
<% end %>
<li>
<button type="button"
class="c-btn c-btn--destructive w-100 align-left"
data-modal-title="Banish <%= @user.name %>"
data-modal-title="<%= t("views.admin.users.banish.heading", user: @user.name) %>"
data-modal-size="small"
data-modal-content-selector="#banish-for-spam"
data-form-action="<%= banish_admin_user_path(@user) %>"
data-user-name="<%= @user.name %>"
data-banishable-user="<%= @banishable_user %>">Banish user</button>
data-banishable-user="<%= @banishable_user %>"><%= t("views.admin.users.profile.options.banish") %></button>
</li>
<li><button type="button" class="c-btn c-btn--destructive w-100 align-left" data-modal-title="Delete <%= @user.name %>'s account" data-modal-size="small" data-modal-content-selector="#delete-user">Delete user</button></li>
<li><button type="button" class="c-btn c-btn--destructive w-100 align-left" data-modal-title="<%= t("views.admin.users.delete.heading", user: @user.name) %>" data-modal-size="small" data-modal-content-selector="#delete-user"><%= t("views.admin.users.profile.options.delete") %></button></li>
</ul>
</div>
</div>

View file

@ -1,14 +1,14 @@
<span class="ml-3 whitespace-nowrap" title="Current status">
<span class="screen-reader-only">Current status:</span>
<span class="ml-3 whitespace-nowrap" title="<%= t("views.admin.users.status_title") %>">
<span class="screen-reader-only"><%= t("views.admin.users.status_reader") %></span>
<% if @user.suspended? %>
<span data-testid="user-status" class="c-indicator c-indicator--danger c-indicator--relaxed">Suspended</span>
<span data-testid="user-status" class="c-indicator c-indicator--danger c-indicator--relaxed"><%= t("views.admin.users.statuses.Suspended") %></span>
<% elsif @user.warned? %>
<span data-testid="user-status" class="c-indicator c-indicator--warning c-indicator--relaxed">Warned</span>
<span data-testid="user-status" class="c-indicator c-indicator--warning c-indicator--relaxed"><%= t("views.admin.users.statuses.Warned") %></span>
<% elsif @user.comment_suspended? %>
<span data-testid="user-status" class="c-indicator c-indicator--warning c-indicator--relaxed">Comment suspended</span>
<span data-testid="user-status" class="c-indicator c-indicator--warning c-indicator--relaxed"><%= t("views.admin.users.statuses.Comment suspended") %></span>
<% elsif @user.trusted? %>
<span data-testid="user-status" class="c-indicator c-indicator--success c-indicator--relaxed">Trusted</span>
<span data-testid="user-status" class="c-indicator c-indicator--success c-indicator--relaxed"><%= t("views.admin.users.statuses.Trusted") %></span>
<% else %>
<span data-testid="user-status" class="c-indicator c-indicator--relaxed">Good standing</span>
<span data-testid="user-status" class="c-indicator c-indicator--relaxed"><%= t("views.admin.users.statuses.Good standing") %></span>
<% end %>
</span>

View file

@ -1,14 +1,14 @@
<div id="delete-user">
<% if current_user.super_admin? %>
<%= form_for(@user, url: full_delete_admin_user_path(@user), html: { class: "flex flex-col gap-4", method: :post, onsubmit: "return confirm('Are you sure? This action is irreversible.')", id: nil }) do |f| %>
<p>This action is irreversible.</p>
<p>Once deleted, all data relating to <%= @user.name %> will be removed from our database.</p>
<p>Be careful with this action.</p>
<%= form_for(@user, url: full_delete_admin_user_path(@user), html: { class: "flex flex-col gap-4", method: :post, onsubmit: "return confirm('#{j t('views.admin.users.delete.onsubmit')}')", id: nil }) do |f| %>
<p><%= t("views.admin.users.danger.desc1") %></p>
<p><%= t("views.admin.users.delete.desc2", user: @user.name) %></p>
<p><%= t("views.admin.users.danger.desc3") %></p>
<div>
<button class="c-btn c-btn--primary c-btn--destructive">Delete now</button>
<button class="c-btn c-btn--primary c-btn--destructive"><%= t("views.admin.users.delete.submit") %></button>
</div>
<% end %>
<% else %>
<div class="crayons-notice crayons-notice--danger">Only Super Admins are allowed to delete users.</div>
<div class="crayons-notice crayons-notice--danger"><%= t("views.admin.users.delete.notice") %></div>
<% end %>
</div>

View file

@ -1,15 +1,14 @@
<div id="export-data">
<div class="flex flex-col gap-4">
<p>
Choose between sending the exported data to either your Admin contact email, <%= ForemInstance.email %>,
or to <%= @user.name %>'s <a href="mailto:<%= @user.email %>">user email</a> directly.
<%= t("views.admin.users.export.desc1_html", contact: ForemInstance.email, user: @user.name, user_email: mail_to(@user.email, t("views.admin.users.export.user_email"))) %>
</p>
<p>
Exported data includes a user's posts and comments.
<%= t("views.admin.users.export.desc2") %>
</p>
<div class="flex gap-2">
<%= button_to "Export to Admin", export_data_admin_user_path(@user.id), data: { confirm: "Are you sure you want to export this user's content to #{ForemInstance.email}?" }, class: "c-btn c-btn--secondary", params: { send_to_admin: true } %>
<%= button_to "Export to User", export_data_admin_user_path(@user.id), data: { confirm: "Are you sure you want to export this user's content to #{@user.email}?" }, class: "c-btn c-btn--secondary", params: { send_to_admin: false } %>
<%= button_to t("views.admin.users.export.to_admin"), export_data_admin_user_path(@user.id), data: { confirm: t("views.admin.users.export.confirm", email: ForemInstance.email) }, class: "c-btn c-btn--secondary", params: { send_to_admin: true } %>
<%= button_to t("views.admin.users.export.to_user"), export_data_admin_user_path(@user.id), data: { confirm: t("views.admin.users.export.confirm", email: @user.email) }, class: "c-btn c-btn--secondary", params: { send_to_admin: false } %>
</div>
</div>
</div>

View file

@ -1,14 +1,14 @@
<div id="merge-accounts">
<%= form_for(@user, url: merge_admin_user_path(@user),
html: { class: "flex flex-col gap-4", method: :post, onsubmit: "return confirm('Are you sure? This is extremely destructive and irreversible. Merging will delete all the other users content and combine it with this user')", id: nil }) do |f| %>
<p>This action is irreversible.</p>
<p>Make sure you are currently on the page of the user you want to <strong>keep</strong>. Once merged, the account entered below will be merged and deleted.</p>
html: { class: "flex flex-col gap-4", method: :post, onsubmit: "return confirm('#{j t('views.admin.users.merge.onsubmit')}')", id: nil }) do |f| %>
<p><%= t("views.admin.users.danger.desc1") %></p>
<p><%= t("views.admin.users.merge.desc2_html") %></p>
<div class="crayons-field mb-4">
<%= f.label :merge_user_id, "User ID", class: "crayons-field__label" %>
<%= f.label :merge_user_id, t("views.admin.users.merge.id"), class: "crayons-field__label" %>
<%= f.text_field :merge_user_id, placeholder: "1234", class: "crayons-textfield", type: "number", inputmode: "numeric", aria: { describedby: "merge-user-description" } %>
</div>
<div>
<%= f.button "Merge and delete", class: "c-btn c-btn--destructive c-btn--primary", type: "submit" %>
<%= f.button t("views.admin.users.merge.submit"), class: "c-btn c-btn--destructive c-btn--primary", type: "submit" %>
</div>
<% end %>
</div>

View file

@ -1,37 +1,39 @@
<div id="remove-social-accounts">
<div class="flex flex-col gap-4">
<p>Sometimes users encounter sign-in issues when they....</p>
<ol>
<li>create an account on <%= community_name %> with a social account, and</li>
<li>remove the original social account, and</li>
<li>attempt to sign in again using the same, removed social account.</li>
</ol>
<p>If this is the case, removing the social accounts below can help restore their social accounts. To verify if this is the case, follow steps 1 and 2 below. If verified, follow through all steps to restore their social accounts.</p>
<%= t("views.admin.users.social.desc_html", community: community_name) %>
<h3 class="crayons-subtitle-2">
How to restore a social account
<%= t("views.admin.users.social.howto.subtitle") %>
</h3>
<ol>
<li>Check their social account's UID via these third party tools: <a href="https://tweeterid.com/?username=<%= @user.twitter_username %>" target="_blank" rel="noopener nofollow">Twitter UID</a> and <a href="https://caius.github.io/github_id/#<%= @user.github_username %>" target="_blank" rel="noopener nofollow">GitHub UID</a></li>
<li>Check whether or not it matches the corresponding UID below.</li>
<li>Remove the respective account if it doesn't match.</li>
<li>Ask the user to reauthorize their social account via <%= app_url(user_settings_path(:account)) %></li>
<li><%= t("views.admin.users.social.howto.first_html",
twitter_uid: link_to(t("views.admin.users.social.howto.twitter_uid"), "https://tweeterid.com/?username=#{@user.twitter_username}", target: "_blank", rel: "noopener nofollow"),
github_uid: link_to(t("views.admin.users.social.howto.github_uid"), "https://caius.github.io/github_id/##{@user.twitter_username}", target: "_blank", rel: "noopener nofollow")) %></li>
<li><%= t("views.admin.users.social.howto.second") %></li>
<li><%= t("views.admin.users.social.howto.third") %></li>
<li><%= t("views.admin.users.social.howto.fourth", app: app_url(user_settings_path(:account))) %></li>
</ol>
<% @user.identities.each do |identity| %>
<%= form_for(@user, url: remove_identity_admin_user_path(@user),
html: { class: "fs-base p-4 py-3 c-card c-card--interactive flex gap-2 justify-between items-center", method: :delete, onsubmit: "return confirm('Are you sure? This should only be done as a solution for the listed example(s).)", id: nil }) do |f| %>
html: { class: "fs-base p-4 py-3 c-card c-card--interactive flex gap-2 justify-between items-center", method: :delete, onsubmit: "return confirm('#{j t('views.admin.users.social.onsubmit')}')", id: nil }) do |f| %>
<%= f.hidden_field :identity_id, value: identity.id %>
<div class="flex-1 flex items-center gap-2">
<%= crayons_icon_tag(identity.provider, class: "shrink-0", width: 32, height: 32) %>
<div class="lh-tight">
<h4 class="crayons-subtitle-3"><%= identity.provider.capitalize %></h4>
<p class="fs-base color-secondary"><strong>UID:</strong> <%= identity.uid %> &bull; <strong>Username:</strong> <%= identity.auth_data_dump.present? ? identity.auth_data_dump["info"]["nickname"] : "No data available..." %></p>
<h4 class="crayons-subtitle-3"><%= t("views.admin.users.identities.#{identity.provider}", default: identity.provider.capitalize) %></h4>
<p class="fs-base color-secondary">
<strong>
<%= t("views.admin.users.social.uid") %>
</strong> <%= identity.uid %> &bull; <strong>
<%= t("views.admin.users.social.username") %>
</strong> <%= identity.auth_data_dump.present? ? identity.auth_data_dump["info"]["nickname"] : t("views.admin.users.social.no_data") %>
</p>
</div>
</div>
<%= f.button class: "c-btn c-btn--icon-left c-btn--destructive", type: "submit" do %>
<%= crayons_icon_tag(:bin, class: "c-btn__icon") %>
Remove
<%= t("views.admin.users.social.submit") %>
<% end %>
<% end %>
<% end %>

View file

@ -1,4 +1,4 @@
<h2 class="crayons-subtitle-1">Reports submitted by <%= @user.name %> (<%= @related_reports.count %>)</h2>
<h2 class="crayons-subtitle-1"><%= t("views.admin.users.reports.subtitle", user: @user.name, num: @related_reports.count) %></h2>
<% unless @related_reports.empty? %>
<div class="pt-3">
@ -9,26 +9,26 @@
<h3 class="crayons-subtitle-3 w-100 flex items-center justify-between">
<span class="flex gap-3">
<a href="<%= admin_report_path(report.id) %>" class="c-link">
<%= report.category.capitalize %>
<%= t("views.admin.users.reports.category.#{report.category}", default: report.category.capitalize) %>
</a>
<span class="c-indicator c-indicator--relaxed <%= "c-indicator--danger" if report.status.casecmp("open").zero? %> <%= "c-indicator--success" if report.status.casecmp("resolved").zero? %>">
<%= report.status %>
<%= t("views.admin.users.reports.status.#{report.status}", default: report.status) %>
</span>
</span>
<time datetime="<%= report.created_at&.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0 fw-normal" title="<%= report.created_at&.strftime("%Y-%m-%d, %H:%M") %>">
<%= report.created_at&.strftime("%b %e '%y") %>
<time datetime="<%= report.created_at&.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0 fw-normal" title="<%= l(report.created_at, format: :admin_user) if report.created_at %>">
<%= l(report.created_at, format: :short_with_yy) if report.created_at %>
</time>
</h3>
</header>
<p class="mb-1"><%= report.message %></p>
<p class="fs-s color-secondary">For <a href="<%= report.reported_url %>" class="c-link" target="_blank" rel="noopener"><%= report.reported_url %></a></p>
<p class="fs-s color-secondary"><%= t("views.admin.users.reports.for_html", url: link_to(report.reported_url, report.reported_url, class: "c-link", target: "_blank", rel: "noopener")) %></p>
</div>
</article>
<% end %>
</div>
<% else %>
<div class="align-center flex flex-col justify-center my-auto py-8">
<h3 class="fs-xl fw-normal mb-2">No comment or post has been reported yet.</h3>
<p class="color-secondary">Any comment or post reported by <%= @user.name %> will be found here.</p>
<h3 class="fs-xl fw-normal mb-2"><%= t("views.admin.users.reports.empty1") %></h3>
<p class="color-secondary"><%= t("views.admin.users.reports.empty2", user: @user.name) %></p>
</div>
<% end %>

View file

@ -1,27 +1,27 @@
<% if @unpublish_all_data.exists? %>
<h2 class="fs-xl fw-normal mb-5">Unpublished by <%= link_to @unpublish_all_data.audit_log.user.username, @unpublish_all_data.audit_log.user.path %> on <%= @unpublish_all_data.audit_log.created_at.strftime("%Y-%m-%d, %H:%M") %></h3>
<h2 class="fs-xl fw-normal mb-5"><%= t("views.admin.users.unpublish_logs.subtitle_html", user: link_to(@unpublish_all_data.audit_log.user.username, @unpublish_all_data.audit_log.user.path), time: l(@unpublish_all_data.audit_log.created_at, format: :admin_user)) %></h3>
<div class="crayons-card p-6 mb-5">
<h4 class="mb-2">Posts:</h4>
<h4 class="mb-2"><%= t("views.admin.users.unpublish_logs.posts") %></h4>
<% @unpublish_all_data.target_articles.each do |article| %>
<div class="mb-1">
<%= link_to article.title, article.current_state_path %>
<a href="<%= article.path %>/edit" target="_blank" rel="noopener" class="c-link">Edit</a>
<a href="<%= article.path %>/edit" target="_blank" rel="noopener" class="c-link"><%= t("views.admin.users.unpublish_logs.edit") %></a>
<% if article.published %>
<span class="color-base-60">(was republished)</span>
<span class="color-base-60"><%= t("views.admin.users.unpublish_logs.republished") %></span>
<% end %>
</div>
<% end %>
</div>
<div class="crayons-card p-6">
<h4 class="mb-2">Comments:</h4>
<h4 class="mb-2"><%= t("views.admin.users.unpublish_logs.comments") %></h4>
<% @unpublish_all_data.target_comments.each do |comment| %>
<div class="crayons-card p-3 mb-3">
<div class="color-base-80">
<%= truncate(strip_tags(comment.processed_html), length: 250).html_safe %>
</div>
<%= link_to "commentable", comment.commentable.current_state_path %>
<%= link_to t("views.admin.users.unpublish_logs.commentable"), comment.commentable.current_state_path %>
<p class="fs-s">
Created at: <%= comment.created_at.strftime("%Y-%m-%d, %H:%M") %>
<%= t("views.admin.users.unpublish_logs.created", time: l(comment.created_at, format: :admin_user)) %>
</p>
</div>
<% end %>

View file

@ -173,10 +173,14 @@ en:
other: is the wrong length (should be %{count} characters)
time:
formats:
admin_user: "%Y-%m-%d, %H:%M"
comment: "%B %d, %Y"
email_verified: "%b %e '%y, %H:%M"
github: "%b %d, %Y"
json: "%b %e, %Y"
medium: "%b %-d, %Y"
members: "%d %b"
members_with_year: "%d %b, %Y"
reddit: "%b %e '%y"
short: "%b %-e"
short_with_yy: "%b %-d '%y"

View file

@ -196,10 +196,14 @@ fr:
other: est la mauvaise longueur (devrait être %{count} caractères)
time:
formats:
admin_user: "%Y-%m-%d, %H:%M"
comment: "%B %d, %Y"
email_verified: "%b %e '%y, %H:%M"
github: "%b %d, %Y"
json: "%b %e, %Y"
medium: "%b %-d, %Y"
members: "%d %b"
members_with_year: "%d %b, %Y"
reddit: "%b %e '%y"
short: "%-e %b"
short_with_yy: "%-e %b '%y"

View file

@ -230,3 +230,11 @@ en:
novice: Novice
social_link_helper:
n_a: N/A
admin:
user_helper:
today: Today, %{date}
yesterday: Yesterday, %{date}
org_join: ", "
org_overflow:
one: "%{orgs} & %{count} other"
other: "%{orgs} & %{count} others"

View file

@ -230,3 +230,11 @@ fr:
novice: Novice
social_link_helper:
n_a: N/A
admin:
user_helper:
today: Today, %{date}
yesterday: Yesterday, %{date}
org_join: ", "
org_overflow:
one: "%{orgs} & %{count} other"
other: "%{orgs} & %{count} others"

View file

@ -0,0 +1,351 @@
---
en:
views:
admin:
users:
heading: Members
aria_paginate: Paginate users
table:
compact_subtitle: Members
joined_on: Joined on
last_activity: Last activity
heading:
member: Member
status: Status
last_joined: Last activity & Joined on
orgs: Organizations
actions: Actions
empty:
text1: No members found under these filters.
text2: Try broadening or removing the filters.
download:
aria_label: Download member data
heading: Download member data
desc1: Your data will be downloaded as a Comma Separated Values (.csv) file.
desc2: Values listed are Name, Username, Email address, Status, Joining date, Last activity, and Organizations.
submit: Download
cancel: Cancel
name: Name
username: Username
email: Email address
status: Status
join: Joining date
last_activity: Last activity
orgs: Organizations
search:
aria_expand: Expand search
aria_field: Search member by name, username or email
aria_label: Search
icon: search
placeholder: Search member...
filters:
aria_clear: Clear all filters
aria_label: Filter
aria_remove: "Remove filter: %{value}"
aria_more_roles: See more roles
heading: Filters
applied: Applied filters
join: Joining date
creation: Community creation
today: Today
clear: Clear all
clear_filter:
aria_roles: Clear member roles filter
aria_status: Clear statuses filter
aria_orgs: Clear organizations filter
text: Clear filter
clear_filters: Clear filters
desc: Members who match any of the selected filters will be displayed
summary:
roles: Member roles
status: Status
join: Joining date
orgs: Organizations
legend:
roles: Member roles
status: Status
orgs: Organizations
more_roles: See more roles
submit: Apply filters
cancel: Cancel
invite:
aria_email: "Invitation actions: %{email}"
resend: Resend invite
cancel: Cancel invite
actions:
assign_role:
text: Assign role
heading: Add role
role: Role
select: Select role
note: "Add a note to this action:"
submit: Add
add_org:
text: Add organization
heading: Add organization
add: Add %{user} to an organization
edit: Edit %{user}'s role at an organization
id: Organization ID
role: Role
role_desc_html: You can assign %{user} to be an Admin or a regular member.
admin: admin
member: member
submit: Add organization
adjust_credit:
text: Adjust credit balance
heading: Adjust balance
desc_html: "%{user} currently has %{credits}."
credits_html: "%{amount} credits"
adjust: Adjust balance
selector_html: "%{action}%{amount}%{credits}"
add: Add
unit: credits
aria_credits: Amount of credits to add or remove
note: "Add a note to this action:"
submit: Adjust balance
copy_email: Copy email address
danger:
desc1: This action is irreversible.
desc3: Be careful with this action.
banish:
heading: Banish %{user}
desc2_html: Once banished, we will delete all content created by %{user} and change their username to @spam_###.
notice_html: This is not a new user. Only Super Admins or Support Admins are allowed to banish %{user}.
submit_html: Banish %{user}
delete:
heading: Delete %{user}'s account
onsubmit: Are you sure? This actions is irreversible.
desc2: Once deleted, all data relating to %{user} will be removed from our database.
notice: Only Super Admins are allowed to delete users.
submit: Delete now
export:
heading: Export %{user}'s data
desc1_html: Choose between sending the exported data to either your Admin contact email, %{contact}, or to %{user}'s %{user_email} directly.
desc2: Exported data includes a user's posts and comments.
user_email: user email
to_admin: Export to Admin
to_user: Export to User
confirm: Are you sure you want to export this user's content to %{email}?
merge:
heading: Merge users
onsubmit: Are you sure? This is extremely destructive and irreversible. Merging will delete all the other users content and combine it with this user
desc2_html: Make sure you are currently on the page of the user you want to <strong>keep</strong>. Once merged, the account entered below will be merged and deleted.
id: User ID
submit: Merge and delete
social:
heading: Remove social accounts
desc_html: |
<p>Sometimes users encounter sign-in issues when they....</p>
<ol>
<li>create an account on %{community} with a social account, and</li>
<li>remove the original social account, and</li>
<li>attempt to sign in again using the same, removed social account.</li>
</ol>
<p>If this is the case, removing the social accounts below can help restore their social accounts. To verify if this is the case, follow steps 1 and 2 below. If verified, follow through all steps to restore their social accounts.</p>
howto:
subtitle: How to restore a social account
first_html: "Check their social account's UID via these third party tools: %{twitter_uid} and %{github_uid}"
twitter_uid: Twitter UID
github_uid: GitHub UID
second: Check whether or not it matches the corresponding UID below.
third: Remove the respective account if it doesn't match.
fourth: Ask the user to reauthorize their social account via %{app}
onsubmit: Are you sure? This should only be done as a solution for the listed example(s).
uid: "UID:"
username: "Username:"
no_data: No data available...
submit: Remove
unpublish:
heading: Unpublish all posts
onsubmit: Are you sure? All posts will be unavailable to the community.
text1_html: Once unpublished, all posts by %{username} will become hidden and only accessible to themselves.
text2_html: If %{username} is not suspended, they can still re-publish their posts from their dashboard.
note: "Note:"
note_placeholder: "Note text"
submit: Unpublish all posts
emails:
empty1: No previous emails found for %{user}.
empty2: Any emails sent to %{email} will be found here.
previous: Previous emails (%{num})
send: Send an email
to: "To: %{user} (%{email})"
subject: Subject
subject_placeholder: What do you want to say in the subject line?
body: Body
body_placeholder: What do you want to say?
send_submit: Send email
not_verified:
subtitle: Email not verified
desc: "%{user}'s email hasn't been verified yet."
verify: Verify email address
verified_html: Last verified on %{time}
reverify: Reverify
flags:
empty1: No flags received against %{user} yet.
empty2: Any flags received against %{user} will be found here.
type:
article: Post "%{title}"
comment: Comment on "%{title}"
profile: User's profile
origin_html: Flagged by %{user}
notes:
empty1: No previous notes found for %{user}.
empty2: Any notes added for %{user} will be found here.
previous: Previous notes (%{num})
note_by: Note by %{author} on %{time}
reason_by_html: "%{reason} %{by}"
by_html: by %{author}
write: Write a user note
note: Note
note_placeholder: Add a note...
submit: Submit
overview:
comments: Comments
posts: Posts
reactions: Reactions
followers: Followers
following: Following
badges: Badges
learn: Learn more
credits:
subtitle: Credits
aria_learn: Learn more about credits
icon: Wallet
desc: "Grant/remove credits to purchase classified listings. "
button: Adjust balance
orgs:
subtitle: Organizations
aria_edit: Edit %{org} organization membership
aria_learn: Learn more about organizations
aria_revoke: Revoke %{org} organization membership
icon: Organizations
desc: "Add this member to organizations in your community. "
empty: Not part of any organization yet.
button: Add organization
type:
admin: Admin
member: Member
guest: Guest
revoke_confirm: Are your sure you want to remove %{user} from %{org}?
submit: Submit
roles:
subtitle: Roles
aria_learn: Learn more about roles
icon: Roles
desc: "Grant this member permissions by assigning a role. "
empty: No roles assigned yet.
button: Assign role
locked: You can't remove this role.
remove: "Remove role:"
remove_confirm: Are you sure?
name:
admin: Admin
codeland_admin: Codeland Admin
comment_suspended: Comment Suspended
creator: Creator
mod_relations_admin: Mod Relations Admin
super_moderator: Super Moderator
podcast_admin: Podcast Admin
restricted_liquid_tag: Restricted Liquid Tag
single_resource_admin: Single Resource Admin
super_admin: Super Admin
support_admin: Support Admin
suspended: Suspended
tag_moderator: Tag Moderator
tech_admin: Tech Admin
trusted: Trusted
warned: Warned
workshop_pass: Workshop Pass
tag_mod:
subtitle: Tags
aria_learn: Learn more about tag moderation
icon: Mod
desc: "Add a tag that this member can moderate. "
remove: "Remove tag:"
notice_html: Adding tags for moderation is currently only possible through a particular %{tag_page}, by providing the user's ID (%{user_id}).
tag_page: tag's page
profile:
locked:
icon: Access locked
text: This user has locked access.
unlock: Unlock access
id: ID %{userid}
since_html: Member since %{time}
not_member: User has not accepted their invitation yet.
email: Email
github: GitHub
twitter: Twitter
visit: Visit profile
options:
icon: Options
export: Export data
merge: Merge users
unpublish: Unpublished all posts
remove_social: Remove social accounts
banish: Banish user
delete: Delete user
reports:
subtitle: Reports submitted by %{user} (%{num})
for_html: For %{url}
empty1: No comment or post has been reported yet.
empty2: Any comment or post reported by %{user} will be found here.
category:
rude or vulgar: Rude or vulgar
harrassment: Harrassment
spam: Spam
listings: Listings
other: Other
status:
Open: Open
Resolved: Resolved
Invalid: Invalid
unpublish_logs:
subtitle_html: Unpublished by %{user} on %{time}
posts: "Posts:"
edit: Edit
republished: (was republished)
comments: "Comments:"
commentable: commentable
created: "Created at: %{time}"
tabs:
overview: Overview
notes: Notes
emails: Emails
reports: Reports
flags: Flags
unpublish_logs: Unpublish All History
status_title: Current status
status_reader: "Current status:"
more_orgs:
one: + %{count}
other: + %{count}
roles:
Admin: Admin
Tech Admin: Tech Admin
Super Admin: Super Admin
Resource Admin: Resource Admin
"Resource Admin: Article": "Resource Admin: Article"
"Resource Admin: Badge": "Resource Admin: Badge"
"Resource Admin: BadgeAchievement": "Resource Admin: BadgeAchievement"
"Resource Admin: Broadcast": "Resource Admin: Broadcast"
"Resource Admin: Comment": "Resource Admin: Comment"
"Resource Admin: Config": "Resource Admin: Config"
"Resource Admin: DisplayAd": "Resource Admin: DisplayAd"
"Resource Admin: DataUpdateScript": "Resource Admin: DataUpdateScript"
"Resource Admin: FeedbackMessage": "Resource Admin: FeedbackMessage"
"Resource Admin: HtmlVariant": "Resource Admin: HtmlVariant"
"Resource Admin: ListingCategory": "Resource Admin: ListingCategory"
"Resource Admin: Page": "Resource Admin: Page"
"Resource Admin: Tag": "Resource Admin: Tag"
Super Moderator: Super Moderator
statuses:
Warned: Warned
Comment Suspended: Comment Suspended
Suspended: Suspended
Good Standing: Good Standing
Good standing: Good standing
Trusted: Trusted
identities:
facebook: Facebook
github: GitHub
twitter: Twitter

View file

@ -0,0 +1,351 @@
---
en:
views:
admin:
users:
heading: Members
aria_paginate: Paginate users
table:
compact_subtitle: Members
joined_on: Joined on
last_activity: Last activity
heading:
member: Member
status: Status
last_joined: Last activity & Joined on
orgs: Organizations
actions: Actions
empty:
text1: No members found under these filters.
text2: Try broadening or removing the filters.
download:
aria_label: Download member data
heading: Download member data
desc1: Your data will be downloaded as a Comma Separated Values (.csv) file.
desc2: Values listed are Name, Username, Email address, Status, Joining date, Last activity, and Organizations.
submit: Download
cancel: Cancel
name: Name
username: Username
email: Email address
status: Status
join: Joining date
last_activity: Last activity
orgs: Organizations
search:
aria_expand: Expand search
aria_field: Search member by name, username or email
aria_label: Search
icon: search
placeholder: Search member...
filters:
aria_clear: Clear all filters
aria_label: Filter
aria_remove: "Remove filter: %{value}"
aria_more_roles: See more roles
heading: Filters
applied: Applied filters
join: Joining date
creation: Community creation
today: Today
clear: Clear all
clear_filter:
aria_roles: Clear member roles filter
aria_status: Clear statuses filter
aria_orgs: Clear organizations filter
text: Clear filter
clear_filters: Clear filters
desc: Members who match any of the selected filters will be displayed
summary:
roles: Member roles
status: Status
join: Joining date
orgs: Organizations
legend:
roles: Member roles
status: Status
orgs: Organizations
more_roles: See more roles
submit: Apply filters
cancel: Cancel
invite:
aria_email: "Invitation actions: %{email}"
resend: Resend invite
cancel: Cancel invite
actions:
assign_role:
text: Assign role
heading: Add role
role: Role
select: Select role
note: "Add a note to this action:"
submit: Add
add_org:
text: Add organization
heading: Add organization
add: Add %{user} to an organization
edit: Edit %{user}'s role at an organization
id: Organization ID
role: Role
role_desc_html: You can assign %{user} to be an Admin or a regular member.
admin: admin
member: member
submit: Add organization
adjust_credit:
text: Adjust credit balance
heading: Adjust balance
desc_html: "%{user} currently has %{credits}."
credits_html: "%{amount} credits"
adjust: Adjust balance
selector_html: "%{action}%{amount}%{credits}"
add: Add
unit: credits
aria_credits: Amount of credits to add or remove
note: "Add a note to this action:"
submit: Adjust balance
copy_email: Copy email address
danger:
desc1: This action is irreversible.
desc3: Be careful with this action.
banish:
heading: Banish %{user}
desc2_html: Once banished, we will delete all content created by %{user} and change their username to @spam_###.
notice_html: This is not a new user. Only Super Admins or Support Admins are allowed to banish %{user}.
submit_html: Banish %{user}
delete:
heading: Delete %{user}'s account
onsubmit: Are you sure? This actions is irreversible.
desc2: Once deleted, all data relating to %{user} will be removed from our database.
notice: Only Super Admins are allowed to delete users.
submit: Delete now
export:
heading: Export %{user}'s data
desc1_html: Choose between sending the exported data to either your Admin contact email, %{contact}, or to %{user}'s %{user_email} directly.
desc2: Exported data includes a user's posts and comments.
user_email: user email
to_admin: Export to Admin
to_user: Export to User
confirm: Are you sure you want to export this user's content to %{email}?
merge:
heading: Merge users
onsubmit: Are you sure? This is extremely destructive and irreversible. Merging will delete all the other users content and combine it with this user
desc2_html: Make sure you are currently on the page of the user you want to <strong>keep</strong>. Once merged, the account entered below will be merged and deleted.
id: User ID
submit: Merge and delete
social:
heading: Remove social accounts
desc_html: |
<p>Sometimes users encounter sign-in issues when they....</p>
<ol>
<li>create an account on %{community} with a social account, and</li>
<li>remove the original social account, and</li>
<li>attempt to sign in again using the same, removed social account.</li>
</ol>
<p>If this is the case, removing the social accounts below can help restore their social accounts. To verify if this is the case, follow steps 1 and 2 below. If verified, follow through all steps to restore their social accounts.</p>
howto:
subtitle: How to restore a social account
first_html: "Check their social account's UID via these third party tools: %{twitter_uid} and %{github_uid}"
twitter_uid: Twitter UID
github_uid: GitHub UID
second: Check whether or not it matches the corresponding UID below.
third: Remove the respective account if it doesn't match.
fourth: Ask the user to reauthorize their social account via %{app}
onsubmit: Are you sure? This should only be done as a solution for the listed example(s).
uid: "UID:"
username: "Username:"
no_data: No data available...
submit: Remove
unpublish:
heading: Unpublish all posts
onsubmit: Are you sure? All posts will be unavailable to the community.
text1_html: Once unpublished, all posts by %{username} will become hidden and only accessible to themselves.
text2_html: If %{username} is not suspended, they can still re-publish their posts from their dashboard.
note: "Note:"
note_placeholder: "Note text"
submit: Unpublish all posts
emails:
empty1: No previous emails found for %{user}.
empty2: Any emails sent to %{email} will be found here.
previous: Previous emails (%{num})
send: Send an email
to: "To: %{user} (%{email})"
subject: Subject
subject_placeholder: What do you want to say in the subject line?
body: Body
body_placeholder: What do you want to say?
send_submit: Send email
not_verified:
subtitle: Email not verified
desc: "%{user}'s email hasn't been verified yet."
verify: Verify email address
verified_html: Last verified on %{time}
reverify: Reverify
flags:
empty1: No flags received against %{user} yet.
empty2: Any flags received against %{user} will be found here.
type:
article: Post "%{title}"
comment: Comment on "%{title}"
profile: User's profile
origin_html: Flagged by %{user}
notes:
empty1: No previous notes found for %{user}.
empty2: Any notes added for %{user} will be found here.
previous: Previous notes (%{num})
note_by: Note by %{author} on %{time}
reason_by_html: "%{reason} %{by}"
by_html: by %{author}
write: Write a user note
note: Note
note_placeholder: Add a note...
submit: Submit
overview:
comments: Comments
posts: Posts
reactions: Reactions
followers: Followers
following: Following
badges: Badges
learn: Learn more
credits:
subtitle: Credits
aria_learn: Learn more about credits
icon: Wallet
desc: "Grant/remove credits to purchase classified listings. "
button: Adjust balance
orgs:
subtitle: Organizations
aria_edit: Edit %{org} organization membership
aria_learn: Learn more about organizations
aria_revoke: Revoke %{org} organization membership
icon: Organizations
desc: "Add this member to organizations in your community. "
empty: Not part of any organization yet.
button: Add organization
type:
admin: Admin
member: Member
guest: Guest
revoke_confirm: Are your sure you want to remove %{user} from %{org}?
submit: Submit
roles:
subtitle: Roles
aria_learn: Learn more about roles
icon: Roles
desc: "Grant this member permissions by assigning a role. "
empty: No roles assigned yet.
button: Assign role
locked: You can't remove this role.
remove: "Remove role:"
remove_confirm: Are you sure?
name:
admin: Admin
codeland_admin: Codeland Admin
comment_suspended: Comment Suspended
creator: Creator
mod_relations_admin: Mod Relations Admin
super_moderator: Super Moderator
podcast_admin: Podcast Admin
restricted_liquid_tag: Restricted Liquid Tag
single_resource_admin: Single Resource Admin
super_admin: Super Admin
support_admin: Support Admin
suspended: Suspended
tag_moderator: Tag Moderator
tech_admin: Tech Admin
trusted: Trusted
warned: Warned
workshop_pass: Workshop Pass
tag_mod:
subtitle: Tags
aria_learn: Learn more about tag moderation
icon: Mod
desc: "Add a tag that this member can moderate. "
remove: "Remove tag:"
notice_html: Adding tags for moderation is currently only possible through a particular %{tag_page}, by providing the user's ID (%{user_id}).
tag_page: tag's page
profile:
locked:
icon: Access locked
text: This user has locked access.
unlock: Unlock access
id: ID %{userid}
since_html: Member since %{time}
not_member: User has not accepted their invitation yet.
email: Email
github: GitHub
twitter: Twitter
visit: Visit profile
options:
icon: Options
export: Export data
merge: Merge users
unpublish: Unpublished all posts
remove_social: Remove social accounts
banish: Banish user
delete: Delete user
reports:
subtitle: Reports submitted by %{user} (%{num})
for_html: For %{url}
empty1: No comment or post has been reported yet.
empty2: Any comment or post reported by %{user} will be found here.
category:
rude or vulgar: Rude or vulgar
harrassment: Harrassment
spam: Spam
listings: Listings
other: Other
status:
Open: Open
Resolved: Resolved
Invalid: Invalid
unpublish_logs:
subtitle_html: Unpublished by %{user} on %{time}
posts: "Posts:"
edit: Edit
republished: (was republished)
comments: "Comments:"
commentable: commentable
created: "Created at: %{time}"
tabs:
overview: Overview
notes: Notes
emails: Emails
reports: Reports
flags: Flags
unpublish_logs: Unpublish All History
status_title: Current status
status_reader: "Current status:"
more_orgs:
one: + %{count}
other: + %{count}
roles:
Admin: Admin
Tech Admin: Tech Admin
Super Admin: Super Admin
Resource Admin: Resource Admin
"Resource Admin: Article": "Resource Admin: Article"
"Resource Admin: Badge": "Resource Admin: Badge"
"Resource Admin: BadgeAchievement": "Resource Admin: BadgeAchievement"
"Resource Admin: Broadcast": "Resource Admin: Broadcast"
"Resource Admin: Comment": "Resource Admin: Comment"
"Resource Admin: Config": "Resource Admin: Config"
"Resource Admin: DisplayAd": "Resource Admin: DisplayAd"
"Resource Admin: DataUpdateScript": "Resource Admin: DataUpdateScript"
"Resource Admin: FeedbackMessage": "Resource Admin: FeedbackMessage"
"Resource Admin: HtmlVariant": "Resource Admin: HtmlVariant"
"Resource Admin: ListingCategory": "Resource Admin: ListingCategory"
"Resource Admin: Page": "Resource Admin: Page"
"Resource Admin: Tag": "Resource Admin: Tag"
Super Moderator: Super Moderator
statuses:
Warned: Warned
Comment Suspended: Comment Suspended
Suspended: Suspended
Good Standing: Good Standing
Good standing: Good standing
Trusted: Trusted
identities:
facebook: Facebook
github: GitHub
twitter: Twitter

View file

@ -30,14 +30,14 @@ describe Admin::UsersHelper do
describe "#format_last_activity_timestamp" do
it "renders the proper 'Last activity' date for a user that was active today" do
timestamp = Time.zone.today
timestamp = Time.zone.today.in_time_zone # Since actual user last_activity class is TimeWithZone
date = timestamp.strftime("%d %b")
formatted_date = helper.format_last_activity_timestamp(timestamp)
expect(formatted_date).to eq "Today, #{date}"
end
it "renders the proper 'Last activity' date for a user that was active yesterday" do
timestamp = Date.yesterday
timestamp = Date.yesterday.in_time_zone # Since actual user last_activity class is TimeWithZone
date = timestamp.strftime("%d %b")
formatted_date = helper.format_last_activity_timestamp(timestamp)
expect(formatted_date).to eq "Yesterday, #{date}"