Admin's Member view (#16174)
* init * . * partials * few extras * revert indicators in favor of separate PR * stuff * . * stuff * whoops * . * . * . * . * opacity * partial * cosmetics * move actions to a partial * whoopsy * Added an @admin alias for the frontend and removed the @admin-controllers alias. * Added modal support for admin user actions. * Added modal support for more parts of the user admin. * pills * pills * pills * Added optional chaining for when the overview panel isn't loaded. * styling updates * fix * modals * pass size * tags * . * balance modal * comment * fixes * Improved modal trigger implementation. * Removed default value for dataset. * Moved prevent default to after modal trigger check so forms still submit. * Update app/views/admin/users/widgets/_credits.html.erb Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Update app/views/admin/users/reports/_index.html.erb * Apply suggestions from code review * Apply suggestions from code review * Update app/views/admin/users/actions/_export.html.erb * Update app/views/admin/users/widgets/_tag_moderation.html.erb * Update app/views/admin/users/actions/_merge.html.erb * Removed <AdminUserActionsModal /> component as it wasn't necessary. * Fixed duplicate ID errors detected by Axe. * Redirects to the Show page rather than the Edit page * Adjusts quotations within _social_accounts.html.erb * Ensures that Learn More links open in new tab * updates with latests and greatest * . * fixes * wip. Fixed some issues in the credits form. * Changed Subtract to Remove for credits dropdown. * Cleaned up the edits form. * Wraps functionality in feature flag and calls #set_user_details * Wraps /admin/users/show.html.erb in feature flag * wip: Got credit params form working. * empty select * responsiveness * empty commiit * Updates redirects in spec and controller to show rather than edit * Updates tag moderation copy in Admin Member Detail View * Now the note is saved when changing credits. * Fixed label for attributes as it was affecting writing tests and a11y. * Removes unused index.js component * Refactored credit params. * chore: move method under private and update comment * verified email responsiveness * better opacity variables * better org logo * indicators * refactor the credits method Co-authored-by: Nick Taylor <nick@dev.to> Co-authored-by: Nick Taylor <nick@iamdeveloper.com> Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> Co-authored-by: Nick Taylor <nick@forem.com> Co-authored-by: Ridhwana <ridhwana.khan16@gmail.com>
3
app/assets/images/badge.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 15.245v6.872a.5.5 0 0 1-.757.429L12 20l-4.243 2.546a.5.5 0 0 1-.757-.43v-6.87a8 8 0 1 1 10 0v-.001ZM12 15a6 6 0 1 0 0-12 6 6 0 0 0 0 12Zm0-2a4 4 0 1 1 0-8 4 4 0 0 1 0 8Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 281 B |
3
app/assets/images/bin.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 9v10H8V9h8Zm-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1ZM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 202 B |
3
app/assets/images/edit.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.414 16 16.556 5.858l-1.414-1.414L5 14.586V16h1.414Zm.829 2H3v-4.243L14.435 2.322a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414L7.243 18ZM3 20h18v2H3v-2Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 265 B |
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="408" height="408" viewBox="0 0 408 408">
|
||||
<path d="M346.8 61.2C311.1 22.95 260.1 0 204 0 91.8 0 0 91.8 0 204s91.8 204 204 204c94.35 0 173.4-66.3 196.35-153H346.8c-20.4 58.65-76.5 102-142.8 102-84.15 0-153-68.85-153-153S119.85 51 204 51c43.35 0 79.05 17.85 107.1 45.9l-81.6 81.6H408V0l-61.2 61.2z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 356 B |
3
app/assets/images/refresh.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.463 4.433A9.961 9.961 0 0 1 12 2c5.523 0 10 4.477 10 10 0 2.136-.67 4.116-1.81 5.74L17 12h3A8 8 0 0 0 6.46 6.228l-.997-1.795Zm13.074 15.134A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12c0-2.136.67-4.116 1.81-5.74L7 12H4a8 8 0 0 0 13.54 5.772l.997 1.795Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 365 B |
3
app/assets/images/team.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 19.748V16.4c0-1.283.995-2.292 2.467-2.868A8.482 8.482 0 0 0 9.5 13c-1.89 0-3.636.617-5.047 1.66A8.018 8.018 0 0 0 10 19.748Zm8.88-3.662C18.485 15.553 17.17 15 15.5 15c-2.006 0-3.5.797-3.5 1.4V20a7.995 7.995 0 0 0 6.88-3.914ZM9.55 11.5a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5Zm5.95 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4ZM12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 500 B |
3
app/assets/images/wallet.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22 6h-7a6 6 0 1 0 0 12h7v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v2Zm-7 2h8v8h-8a4 4 0 1 1 0-8Zm0 3v2h3v-2h-3Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 243 B |
|
|
@ -42,3 +42,28 @@
|
|||
padding: var(--su-4);
|
||||
}
|
||||
}
|
||||
|
||||
.c-card {
|
||||
--revealable-opacity: var(--opacity-100);
|
||||
@media (hover: hover) {
|
||||
--revealable-opacity: var(--opacity-0);
|
||||
}
|
||||
|
||||
border-radius: var(--radius-auto);
|
||||
background: var(--card-bg);
|
||||
box-shadow: 0 0 0 1px var(--card-border);
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
&__revealable {
|
||||
opacity: var(--revealable-opacity);
|
||||
}
|
||||
|
||||
&--interactive {
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
--card-border: var(--base-90);
|
||||
--card-bg: var(--card-secondary-bg);
|
||||
--revealable-opacity: var(--opacity-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
app/assets/stylesheets/components/list-items.scss
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
////////////////////////////////////////////////////////////
|
||||
// .c-list-item ////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
.c-list-item {
|
||||
padding: var(--su-3);
|
||||
margin: 0 calc(var(--su-3) * -1);
|
||||
border-bottom: 1px solid var(--divider);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:not(&--inactive) {
|
||||
&:hover,
|
||||
&:focus-visible,
|
||||
&:focus-within {
|
||||
background: var(--list-item-bg-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -276,6 +276,12 @@
|
|||
--indicator-info-bg: var(--accent-brand);
|
||||
--indicator-info-color: rgb(var(--white));
|
||||
|
||||
/***********************************************
|
||||
** List items **********************************
|
||||
***********************************************/
|
||||
|
||||
--list-item-bg-hover: rgb(var(--grey-50));
|
||||
|
||||
/***********************************************
|
||||
** Modals **************************************
|
||||
***********************************************/
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
@import 'components/headers';
|
||||
@import 'components/indicators';
|
||||
@import 'components/links';
|
||||
@import 'components/list-items';
|
||||
@import 'components/loaders';
|
||||
@import 'components/media-cards';
|
||||
@import 'components/modals';
|
||||
|
|
|
|||
|
|
@ -285,6 +285,12 @@
|
|||
--indicator-info-bg: rgb(var(--indigo-700));
|
||||
--indicator-info-color: rgb(var(--indigo-100));
|
||||
|
||||
/***********************************************
|
||||
** List items **********************************
|
||||
***********************************************/
|
||||
|
||||
--list-item-bg-hover: rgb(var(--grey-800));
|
||||
|
||||
/***********************************************
|
||||
** Modals **************************************
|
||||
***********************************************/
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ module Admin
|
|||
merge_user_id add_credits remove_credits
|
||||
add_org_credits remove_org_credits
|
||||
organization_id identity_id
|
||||
credit_action credit_amount
|
||||
].freeze
|
||||
|
||||
EMAIL_ALLOWED_PARAMS = %i[
|
||||
|
|
@ -37,16 +38,12 @@ module Admin
|
|||
|
||||
if FeatureFlag.enabled?(:new_admin_members, current_user)
|
||||
render "admin/users/new/show"
|
||||
elsif FeatureFlag.enabled?(:admin_member_view)
|
||||
set_feedback_messages
|
||||
set_related_reactions
|
||||
set_user_details
|
||||
else
|
||||
@organizations = @user.organizations.order(:name)
|
||||
@notes = @user.notes.order(created_at: :desc).limit(10)
|
||||
@organization_memberships = @user.organization_memberships
|
||||
.joins(:organization)
|
||||
.order("organizations.name" => :asc)
|
||||
.includes(:organization)
|
||||
@last_email_verification_date = EmailAuthorization.last_verification_date(@user)
|
||||
|
||||
render :show
|
||||
set_user_details
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -56,7 +53,7 @@ module Admin
|
|||
# TODO: [@rhymes] in the new Admin Member view this logic has been moved
|
||||
# to Admin::Users::Tools::CreditsController and Admin::Users::Tools::NotesController#create.
|
||||
# It can eventually be removed when we transition away from the old Admin UI
|
||||
Credits::Manage.call(@user, user_params)
|
||||
Credits::Manage.call(@user, credit_params)
|
||||
add_note if user_params[:new_note]
|
||||
|
||||
redirect_to admin_user_path(params[:id])
|
||||
|
|
@ -77,7 +74,7 @@ module Admin
|
|||
else
|
||||
flash[:danger] = response.error_message
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user.id)
|
||||
redirect_to admin_user_path(params[:id])
|
||||
end
|
||||
|
||||
def user_status
|
||||
|
|
@ -88,7 +85,7 @@ module Admin
|
|||
rescue StandardError => e
|
||||
flash[:danger] = e.message
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user.id)
|
||||
redirect_to admin_user_path(params[:id])
|
||||
end
|
||||
|
||||
def export_data
|
||||
|
|
@ -103,13 +100,13 @@ module Admin
|
|||
end
|
||||
ExportContentWorker.perform_async(user.id, email)
|
||||
flash[:success] = "Data exported to the #{receiver}. The job will complete momentarily."
|
||||
redirect_to edit_admin_user_path(user.id)
|
||||
redirect_to admin_user_path(params[:id])
|
||||
end
|
||||
|
||||
def banish
|
||||
Moderator::BanishUserWorker.perform_async(current_user.id, params[:id].to_i)
|
||||
flash[:success] = "This user is being banished in the background. The job will complete soon."
|
||||
redirect_to edit_admin_user_path(params[:id])
|
||||
redirect_to admin_user_path(params[:id])
|
||||
end
|
||||
|
||||
def full_delete
|
||||
|
|
@ -131,7 +128,7 @@ module Admin
|
|||
def unpublish_all_articles
|
||||
Moderator::UnpublishAllArticlesWorker.perform_async(params[:id].to_i)
|
||||
flash[:success] = "Posts are being unpublished in the background. The job will complete soon."
|
||||
redirect_to admin_users_path
|
||||
redirect_to admin_user_path(params[:id])
|
||||
end
|
||||
|
||||
def merge
|
||||
|
|
@ -142,7 +139,7 @@ module Admin
|
|||
flash[:danger] = e.message
|
||||
end
|
||||
|
||||
redirect_to edit_admin_user_path(@user.id)
|
||||
redirect_to admin_user_path(params[:id])
|
||||
end
|
||||
|
||||
def remove_identity
|
||||
|
|
@ -163,7 +160,7 @@ module Admin
|
|||
rescue StandardError => e
|
||||
flash[:danger] = e.message
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user.id)
|
||||
redirect_to admin_user_path(params[:id])
|
||||
end
|
||||
|
||||
# NOTE: [@rhymes] This should be eventually moved in Admin::Users::Tools::EmailsController
|
||||
|
|
@ -181,7 +178,7 @@ module Admin
|
|||
|
||||
format.html do
|
||||
flash[:success] = message
|
||||
redirect_back(fallback_location: admin_users_path)
|
||||
redirect_back(fallback_location: admin_user_path(params[:id]))
|
||||
end
|
||||
|
||||
format.js { render json: { result: message }, content_type: "application/json" }
|
||||
|
|
@ -192,7 +189,7 @@ module Admin
|
|||
|
||||
format.html do
|
||||
flash[:danger] = message
|
||||
redirect_back(fallback_location: admin_users_path)
|
||||
redirect_back(fallback_location: admin_user_path(params[:id]))
|
||||
end
|
||||
|
||||
format.js do
|
||||
|
|
@ -221,7 +218,7 @@ module Admin
|
|||
|
||||
format.html do
|
||||
flash[:success] = message
|
||||
redirect_back(fallback_location: admin_users_path)
|
||||
redirect_back(fallback_location: admin_user_path(params[:id]))
|
||||
end
|
||||
|
||||
format.js { render json: { result: message }, content_type: "application/json" }
|
||||
|
|
@ -232,7 +229,7 @@ module Admin
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
flash[:danger] = message
|
||||
redirect_back(fallback_location: admin_users_path)
|
||||
redirect_back(fallback_location: admin_user_path(params[:id]))
|
||||
end
|
||||
|
||||
format.js { render json: { error: message }, content_type: "application/json", status: :service_unavailable }
|
||||
|
|
@ -249,6 +246,18 @@ module Admin
|
|||
|
||||
private
|
||||
|
||||
def set_user_details
|
||||
@organizations = @user.organizations.order(:name)
|
||||
@notes = @user.notes.order(created_at: :desc).limit(10)
|
||||
@organization_memberships = @user.organization_memberships
|
||||
.joins(:organization)
|
||||
.order("organizations.name" => :asc)
|
||||
.includes(:organization)
|
||||
@last_email_verification_date = EmailAuthorization.last_verification_date(@user)
|
||||
|
||||
render :show
|
||||
end
|
||||
|
||||
def add_note
|
||||
Note.create(
|
||||
author_id: current_user.id,
|
||||
|
|
@ -285,5 +294,39 @@ module Admin
|
|||
params.require(EMAIL_ALLOWED_PARAMS)
|
||||
params.permit(EMAIL_ALLOWED_PARAMS)
|
||||
end
|
||||
|
||||
def credit(_params)
|
||||
return user_params unless FeatureFlag.enabled?(:admin_member_view)
|
||||
|
||||
credit_params = {}
|
||||
if user_params[:credit_action] == "Add"
|
||||
credit_params[:add_credits] = user_params[:credit_amount]
|
||||
end
|
||||
|
||||
if user_params[:credit_action] == "Remove"
|
||||
credit_params[:remove_credits] = user_params[:credit_amount]
|
||||
end
|
||||
|
||||
credit_params
|
||||
end
|
||||
|
||||
def credit_params
|
||||
credit_params = nil
|
||||
if FeatureFlag.enabled?(:admin_member_view)
|
||||
credit_params = {}
|
||||
|
||||
if user_params[:credit_action] == "Add"
|
||||
credit_params[:add_credits] = user_params[:credit_amount]
|
||||
end
|
||||
|
||||
if user_params[:credit_action] == "Remove"
|
||||
credit_params[:remove_credits] = user_params[:credit_amount]
|
||||
end
|
||||
else
|
||||
credit_params = user_params
|
||||
end
|
||||
|
||||
credit_params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
70
app/javascript/packs/admin/editUser.jsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { initializeDropdown } from '@utilities/dropdownUtils';
|
||||
|
||||
let preact;
|
||||
let AdminModal;
|
||||
const modalContents = new Map();
|
||||
|
||||
function getModalContents(modalContentSelector) {
|
||||
if (!modalContents.has(modalContentSelector)) {
|
||||
const modelContentElement = document.querySelector(modalContentSelector);
|
||||
const modalContent = modelContentElement.innerHTML;
|
||||
|
||||
// Remove the element from the DOM to avoid duplicate ID errors in regards to a11y.
|
||||
modelContentElement.remove();
|
||||
modalContents.set(modalContentSelector, modalContent);
|
||||
}
|
||||
|
||||
return modalContents.get(modalContentSelector);
|
||||
}
|
||||
|
||||
// Append an empty div to the end of the document so that is does not affect the layout.
|
||||
const modalContainer = document.createElement('div');
|
||||
document.body.appendChild(modalContainer);
|
||||
|
||||
initializeDropdown({
|
||||
triggerElementId: 'options-dropdown-trigger',
|
||||
dropdownContentId: 'options-dropdown',
|
||||
});
|
||||
|
||||
const openModal = async (event) => {
|
||||
const { dataset } = event.target;
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(dataset, 'modalTrigger')) {
|
||||
// We're not trying to trigger a modal.
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// Only load Preact if we haven't already.
|
||||
if (!preact) {
|
||||
[preact, { Modal: AdminModal }] = await Promise.all([
|
||||
import('preact'),
|
||||
import('@crayons/Modal/Modal'),
|
||||
]);
|
||||
}
|
||||
|
||||
const { h, render } = preact;
|
||||
|
||||
const { modalTitle, modalSize, modalContentSelector } = dataset;
|
||||
|
||||
render(
|
||||
<AdminModal
|
||||
title={modalTitle}
|
||||
size={modalSize}
|
||||
onClose={() => {
|
||||
render(null, modalContainer);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: getModalContents(modalContentSelector),
|
||||
}}
|
||||
/>
|
||||
</AdminModal>,
|
||||
modalContainer,
|
||||
);
|
||||
};
|
||||
|
||||
document.body.addEventListener('click', openModal);
|
||||
41
app/views/admin/users/_actions.html.erb
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<%= 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" %>
|
||||
<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") %>
|
||||
</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>
|
||||
<% end %>
|
||||
<% unless @last_email_verification_date %>
|
||||
<%= form_with url: verify_email_ownership_admin_user_path(@user), local: true do |f| %>
|
||||
<%= f.button "Verify email address", class: "c-btn w-100 align-left" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<li><button type="button" class="c-btn w-100 align-left" data-modal-trigger data-modal-title="Export 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-trigger data-modal-title="Merge accounts" data-modal-size="small" data-modal-content-selector="#merge-accounts">Merge accounts</button></li>
|
||||
<% if @user.articles_count > 0 %>
|
||||
<li><button type="button" class="c-btn w-100 align-left" data-modal-trigger data-modal-title="Unpublish all posts" data-modal-size="small" data-modal-content-selector="#unpublish-all-posts">Unpublish all posts</button></li>
|
||||
<% end %>
|
||||
<% if @user.identities.any? %>
|
||||
<li><button type="button" class="c-btn w-100 align-left" data-modal-trigger data-modal-title="Remove social accounts" data-modal-size="medium" data-modal-content-selector="#remove-social-accounts">Remove social accounts</button></li>
|
||||
<% end %>
|
||||
<li><button type="button" class="c-btn c-btn--destructive w-100 align-left" data-modal-trigger data-modal-title="Banish for spam" data-modal-size="small" data-modal-content-selector="#banish-for-spam">Banish for spam</button></li>
|
||||
<li><button type="button" class="c-btn c-btn--destructive w-100 align-left" data-modal-trigger data-modal-title="Delete user" data-modal-size="small" data-modal-content-selector="#delete-user">Delete user</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# These are contents for modals %>
|
||||
<div class="hidden">
|
||||
<%= render "admin/users/actions/export" %>
|
||||
<%= render "admin/users/actions/merge" %>
|
||||
<%= render "admin/users/actions/unpublish" %>
|
||||
<%= render "admin/users/actions/social_accounts" %>
|
||||
<%= render "admin/users/actions/banish" %>
|
||||
<%= render "admin/users/actions/delete" %>
|
||||
</div>
|
||||
63
app/views/admin/users/_header.html.erb
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<% 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.
|
||||
</p>
|
||||
<%= link_to "Unlock access", unlock_access_admin_user_path(@user), method: :patch, class: "c-link c-link--block c-link--branded" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<section class="crayons-card flex flex-col l:flex-row gap-2 m:gap-4 l:gap-6 mb-2 m:mb-3 p-3 s:p-4 m:p-7">
|
||||
<span class="crayons-avatar crayons-avatar--xl m:crayons-avatar--2xl l:crayons-avatar--3xl">
|
||||
<img src="<%= @user.profile_image_url_for(length: 192) %>" width="100%" height="100%" alt="<%= @user.name %> profile picture">
|
||||
</span>
|
||||
<div class="grid gap-2 flex-1">
|
||||
<div class="s:flex items-center gap-5">
|
||||
<div class="flex flex-1 items-center">
|
||||
<h1 class="crayons-title lh-tight">
|
||||
<%= @user.name %>
|
||||
</h1>
|
||||
<%= render "status" %>
|
||||
</div>
|
||||
<%= render "actions" %>
|
||||
</div>
|
||||
<div class="fs-s mb-1 color-secondary">
|
||||
@<%= @user.username %> <span class="opacity-50">•</span> ID <%= @user.id %> <span class="opacity-50">•</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>
|
||||
<% else %>
|
||||
<span class="color-accent-danger fw-medium">User has not accepted their invitation yet.</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") %>
|
||||
<%= @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") %>
|
||||
<%= @user.github_username %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
<% 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: "GitHub") %>
|
||||
<%= @user.twitter_username %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
9
app/views/admin/users/_navigation.html.erb
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<nav class="mb-2">
|
||||
<ul class="crayons-navigation crayons-navigation--horizontal">
|
||||
<li><a href="#" class="crayons-navigation__item crayons-navigation__item--current" aria-current="page">Overview</a></li>
|
||||
<li><a href="#" class="crayons-navigation__item">Notes</a></li>
|
||||
<li><a href="#" class="crayons-navigation__item">Emails</a></li>
|
||||
<li><a href="#" class="crayons-navigation__item">Reports</a></li>
|
||||
<li><a href="#" class="crayons-navigation__item">Flags</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
30
app/views/admin/users/_stats.html.erb
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<section class="crayons-card mb-7 px-2 py-3 flex flex-col l:flex-row gap-4 l:gap-0">
|
||||
<div class="flex flex-1">
|
||||
<div class="align-center flex-1">
|
||||
<strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.comments_count %></strong>
|
||||
<p class="fs-s l:fs-base color-secondary">Comments</p>
|
||||
</div>
|
||||
<div class="align-center flex-1">
|
||||
<strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.articles_count %></strong>
|
||||
<p class="fs-s l:fs-base color-secondary">Posts</p>
|
||||
</div>
|
||||
<div class="align-center flex-1">
|
||||
<strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.reactions_count %></strong>
|
||||
<p class="fs-s l:fs-base color-secondary">Reactions</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1">
|
||||
<div class="align-center flex-1">
|
||||
<strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.followers_count %></strong>
|
||||
<p class="fs-s l:fs-base color-secondary">Followers</p>
|
||||
</div>
|
||||
<div class="align-center flex-1">
|
||||
<strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.following_users_count %></strong>
|
||||
<p class="fs-s l:fs-base color-secondary">Following</p>
|
||||
</div>
|
||||
<div class="align-center flex-1">
|
||||
<strong class="fs-xl l:fs-2xl fw-heavy lh-tight"><%= @user.badge_achievements_count %></strong>
|
||||
<p class="fs-s l:fs-base color-secondary">Badges</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
13
app/views/admin/users/_status.html.erb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<span class="ml-3 whitespace-nowrap" aria-label="Current status" title="Current status">
|
||||
<% if @user.suspended? %>
|
||||
<span class="c-indicator c-indicator--danger c-indicator--relaxed">Suspended</span>
|
||||
<% elsif @user.warned? %>
|
||||
<span class="c-indicator c-indicator--warning c-indicator--relaxed">Warned</span>
|
||||
<% elsif @user.comment_suspended? %>
|
||||
<span class="c-indicator c-indicator--warning c-indicator--relaxed">Comment suspended</span>
|
||||
<% elsif @user.trusted? %>
|
||||
<span class="c-indicator c-indicator--success c-indicator--relaxed">Trusted</span>
|
||||
<% else %>
|
||||
<span class="c-indicator c-indicator--relaxed">Good standing</span>
|
||||
<% end %>
|
||||
</span>
|
||||
20
app/views/admin/users/actions/_banish.html.erb
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<div id="banish-for-spam">
|
||||
<div class="flex flex-col gap-4">
|
||||
<% if @user.comments.where("created_at < ?", 100.days.ago).empty? && @user.created_at < 100.days.ago %>
|
||||
<p><strong>This is extremely destructive.</strong> Banishing will delete all the user's existing content and change their username to <em>@spam_###</em>.</p>
|
||||
<p><strong>Do not do this lightly.</strong></p>
|
||||
<%= form_for(@user, url: banish_admin_user_path(@user), html: { method: :post, onsubmit: "return confirm('Are you sure? This is extremely destructive and irreversible. Banishing will delete all articles and turn their username into @spam_###')", id: nil }) do %>
|
||||
<button class="c-btn c-btn--primary c-btn--destructive">Banish User for spam</button>
|
||||
<% end %>
|
||||
<% elsif current_user.super_admin? || current_user.support_admin? %>
|
||||
<p><strong>This is not a new user.</strong> You are only allowed to take this action because you are a <strong>Super Admin or a Support Admin.</strong></p>
|
||||
<p><strong>This is extremely destructive.</strong> Banishing will delete all the user's existing content and change their username to <em>@spam_###</em>.</p>
|
||||
<p><strong>Do not do this lightly.</strong></p>
|
||||
<%= form_for(@user, url: banish_admin_user_path(@user), html: { method: :post, onsubmit: "return confirm('Are you sure? This is extremely destructive and irreversible. Banishing will delete all articles and turn their username into @spam_###')", id: nil }) do %>
|
||||
<button class="c-btn c-btn--primary c-btn--destructive">Banish User for spam</button>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="crayons-notice crayons-notice--danger">This is not a new user. Only Super Admins or Support Admins are allowed to banish this user.</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
13
app/views/admin/users/actions/_delete.html.erb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<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 is extremely destructive and irreversible.')", id: nil }) do |f| %>
|
||||
<p>This will <strong>completely destroy the user</strong> and all of their activity from our database. This action is irreversible.</p>
|
||||
<p><strong>Do not do this lightly.</strong></p>
|
||||
<div>
|
||||
<button class="c-btn c-btn--primary c-btn--destructive">Fully Delete User & All Activity</button>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="crayons-notice crayons-notice--danger">Only <strong>Super Admins</strong> are allowed to fully delete users.</div>
|
||||
<% end %>
|
||||
</div>
|
||||
19
app/views/admin/users/actions/_export.html.erb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<div id="export-data">
|
||||
<div class="flex flex-col gap-4">
|
||||
<p>
|
||||
You can export a user's data here. Currently we only support exporting a user's posts and comments.
|
||||
</p>
|
||||
<p>
|
||||
You have the option of exporting to your Admin account to send to the user, or exporting to the user directly.
|
||||
</p>
|
||||
<p>
|
||||
Exporting the data to your Admin account will send it to your contact
|
||||
Admin email (<%= ForemInstance.email %>),
|
||||
and exporting to the user will send it to their email (<%= @user.email %>).
|
||||
</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 the ADMIN?" }, 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 the USER?" }, class: "c-btn c-btn--secondary", params: { send_to_admin: false } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
16
app/views/admin/users/actions/_merge.html.erb
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<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>To merge a duplicate account, first make sure you are currently on the page of the user you want to <strong>keep</strong>. Below, add the user ID of the account to merge information from, and delete.</p>
|
||||
<div class="crayons-field mb-4">
|
||||
<%= f.label :merge_user_id, "User ID", class: "crayons-field__label" %>
|
||||
<p class="crayons-field__description">
|
||||
User ID for the user that will be <strong>deleted</strong> and merged.
|
||||
</p>
|
||||
<%= f.text_field :merge_user_id, placeholder: "1234", class: "crayons-textfield", type: "number", inputmode: "numeric" %>
|
||||
</div>
|
||||
<div>
|
||||
<%= f.button "Merge users", class: "c-btn c-btn--destructive c-btn--primary", type: "submit" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
39
app/views/admin/users/actions/_social_accounts.html.erb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<div id="remove-social-accounts">
|
||||
<div class="flex flex-col gap-4">
|
||||
<p>At times, removing a user's social account will resolve certain sign-in issues that a user may be experiencing.</p>
|
||||
<p>For example, users may experience issues related to their UID when creating a <%= community_name %> account. If a user creates an account by, first, deleting their original social account and, then, creating their new account with the same Twitter/GitHub username,
|
||||
the user may be unable to register their new account. This occurs when a user's Twitter/GitHub account's UID does not match their identity's UID. For more information on whether a user's UID does not match their identity UID, the following third party tools can be
|
||||
helpful:
|
||||
<a href="https://tweeterid.com/?username=<%= @user.twitter_username %>" target="_blank" rel="noopener nofollow">Tweeter ID for Twitter (username is in URL for reference)</a>
|
||||
or <a href="https://caius.github.io/github_id/#<%= @user.github_username %>" target="_blank" rel="noopener nofollow">Caius's GitHub ID Lookup Tool</a>
|
||||
</p>
|
||||
|
||||
<h3 class="crayons-subtitle-2">
|
||||
How do I remove a social account?
|
||||
</h3>
|
||||
<ol>
|
||||
<li>To start, click on one of the links provided above to check the user's social account UID.</li>
|
||||
<li>Next, confirm whether or not the user's social account UID matches the user's identity UID.</li>
|
||||
<li>If the social account UID does not match, delete the respective identity.</li>
|
||||
<li>Finally, ask the user to reauthorize their social account via <%= 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| %>
|
||||
<%= 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 %> • <strong>Username:</strong> <%= identity.auth_data_dump.present? ? identity.auth_data_dump["info"]["nickname"] : "No data available..." %></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
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
8
app/views/admin/users/actions/_unpublish.html.erb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<div id="unpublish-all-posts">
|
||||
<%= form_for(@user, url: unpublish_all_articles_admin_user_path(@user), html: { class: "flex flex-col gap-4", method: :post, onsubmit: "return confirm('Are you sure? All posts will be unavailable to the community.')", id: nil }) do |f| %>
|
||||
<p>This will make any and all of the post links associated with this user unreachable. If the user is not suspended, they will have the ability to republish their posts.</p>
|
||||
<div>
|
||||
<button class="c-btn c-btn--destructive c-btn--primary">Unpublish all posts</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
18
app/views/admin/users/emails/_form.html.erb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<h2 class="crayons-subtitle-1">Send an email</h2>
|
||||
<%= 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: "Short and descriptive subject line..." %>
|
||||
</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?" %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= f.button "Send email", class: "c-btn c-btn--primary", type: "submit" %>
|
||||
</div>
|
||||
<% end %>
|
||||
24
app/views/admin/users/emails/_index.html.erb
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<div class="flex flex-col h-100">
|
||||
<h2 class="crayons-subtitle-1">Previous emails (<%= @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 emails found.</h3>
|
||||
<p class="color-secondary">Any emails sent to <%= @user.email %> can be found here.</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="pt-3">
|
||||
<% @user.email_messages.order(sent_at: :desc).limit(50).each do |email| %>
|
||||
<article class="c-list-item flex justify-between gap-4">
|
||||
<h3 class="flex-1 fw-medium lh-tight fs-base">
|
||||
<a href="<%= admin_user_email_message_path(@user, email) %>" class="c-link" rel="noopener noreferer" target="_blank">
|
||||
<%= 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>
|
||||
</article>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
17
app/views/admin/users/emails/_verification.html.erb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<%= 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">The user's email address, <%= @user.email %>, hasn't been verified yet.</p>
|
||||
</div>
|
||||
<%= f.button "Verify email address", class: "c-btn c-btn--secondary whitespace-nowrap" %>
|
||||
<% else %>
|
||||
<p>
|
||||
Email 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>
|
||||
</p>
|
||||
<%= f.button "Reverify", class: "c-btn c-btn--secondary whitespace-nowrap" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
44
app/views/admin/users/flags/_index.html.erb
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<h2 class="crayons-subtitle-1">Flags received (<%= @related_vomit_reactions.count %>)</h2>
|
||||
|
||||
<% unless @related_vomit_reactions.empty? %>
|
||||
<div class="pt-3">
|
||||
<% @related_vomit_reactions.each do |flag| %>
|
||||
<article class="c-list-item flex gap-2">
|
||||
<span class="color-secondary shrink-0">
|
||||
<% if flag.reactable_type == "Article" %>
|
||||
<%= crayons_icon_tag("post") %>
|
||||
<% elsif flag.reactable_type == "User" %>
|
||||
<%= crayons_icon_tag("user-line") %>
|
||||
<% elsif flag.reactable_type == "Comment" %>
|
||||
<%= crayons_icon_tag("comment") %>
|
||||
<% end %>
|
||||
</span>
|
||||
<div class="flex-1">
|
||||
<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 %>"
|
||||
<% elsif flag.reactable_type == "User" %>
|
||||
User's profile
|
||||
<% elsif flag.reactable_type == "Comment" %>
|
||||
Comment on "<%= 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" %>
|
||||
</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>
|
||||
</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!</h3>
|
||||
<p class="color-secondary">No one flagged anything (profile, posts, or comments) negative about @<%= @user.username %>.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
16
app/views/admin/users/notes/_form.html.erb
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<header>
|
||||
<h2 class="crayons-subtitle-1 mb-1">Add a note</h2>
|
||||
<div class="color-secondary flex items-center">
|
||||
<%= crayons_icon_tag(:lock, width: 18, height: 18, class: "mr-1") %>
|
||||
<p>These notes will <strong>not</strong> be shared with the user.</p>
|
||||
</div>
|
||||
</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: "Write your note here..." %>
|
||||
</div>
|
||||
<div>
|
||||
<%= f.button "Submit", class: "c-btn c-btn--primary", type: "submit" %>
|
||||
</div>
|
||||
<% end %>
|
||||
28
app/views/admin/users/notes/_index.html.erb
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<div class="flex flex-col h-100">
|
||||
<h2 class="crayons-subtitle-1">Notes (<%= @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 notes found.</h3>
|
||||
<p class="color-secondary">Any notes added for @<%= @user.name %> will be found here. </p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="pt-3">
|
||||
<% @notes.each do |note| %>
|
||||
<article class="c-list-item flex justify-between gap-4">
|
||||
<div>
|
||||
<p><%= note.content %></p>
|
||||
<p class="fs-s color-secondary">
|
||||
<%= note.reason.presence %>
|
||||
<% if note.author_id.present? %>
|
||||
by <span class="fw-medium"><%= User.find(note.author_id).username %></span>
|
||||
<% end %>
|
||||
</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>
|
||||
</article>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
32
app/views/admin/users/reports/_index.html.erb
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<h2 class="crayons-subtitle-1">Reports submitted by @<%= @user.username %> (<%= @related_reports.count %>)</h2>
|
||||
|
||||
<% unless @related_reports.empty? %>
|
||||
<div class="pt-3">
|
||||
<% @related_reports.each do |report| %>
|
||||
<article class="c-list-item c-list-item--interactive flex justify-between gap-4">
|
||||
<div>
|
||||
<header class="flex gap-3 items-center">
|
||||
<h3 class="crayons-subtitle-3">
|
||||
<a href="<%= admin_report_path(report.id) %>" class="c-link">
|
||||
<%= report.category.capitalize %>
|
||||
</a>
|
||||
</h3>
|
||||
<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 %>
|
||||
</span>
|
||||
</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>
|
||||
</div>
|
||||
<time datetime="<%= report.created_at&.strftime("%Y-%m-%dT%H:%M:%S%z") %>" class="color-secondary fs-s shrink-0" title="<%= report.created_at&.strftime("%Y-%m-%d, %H:%M") %>">
|
||||
<%= report.created_at&.strftime("%b %e '%y") %>
|
||||
</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 reports submitted.</h3>
|
||||
<p class="color-secondary">This user hasn't reported anything yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -1,50 +1,112 @@
|
|||
<div class="grid gap-6">
|
||||
<div class="crayons-card p-6">
|
||||
<h2 class="d-inline">
|
||||
<%# TODO: incorporate a tab component. For now you have to manually change the value %>
|
||||
<% current_tab = "overview" # overview|emails|notes|reports|flags %>
|
||||
<% if FeatureFlag.enabled?(:admin_member_view) %>
|
||||
<%= render "header" %>
|
||||
<%= render "navigation" %>
|
||||
|
||||
<% if current_tab == 'overview' %>
|
||||
<div class="crayons-card p-3 s:p-4 m:p-7">
|
||||
<%= render "stats" if @user.registered %>
|
||||
|
||||
<div class="l:grid gap-8 grid-cols-2">
|
||||
<div>
|
||||
<section class="mb-7">
|
||||
<%= render "admin/users/widgets/roles" %>
|
||||
</section>
|
||||
|
||||
<section class="mb-7">
|
||||
<%= render "admin/users/widgets/tag_moderation" %>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<section class="mb-7">
|
||||
<%= render "admin/users/widgets/organizations" %>
|
||||
</section>
|
||||
|
||||
<section class="mb-7">
|
||||
<%= render "admin/users/widgets/credits" %>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% elsif current_tab == 'notes' %>
|
||||
<section class="flex flex-col m:flex-row gap-4">
|
||||
<div class="crayons-card p-3 s:p-4 m:p-7 flex-1">
|
||||
<%= render "admin/users/notes/form" %>
|
||||
</div>
|
||||
<div class="crayons-card p-3 s:p-4 m:p-7 flex-1">
|
||||
<%= render "admin/users/notes/index" %>
|
||||
</div>
|
||||
</section>
|
||||
<% elsif current_tab == 'emails' %>
|
||||
<%= render "admin/users/emails/verification" %>
|
||||
<section class="flex flex-col m:flex-row gap-4">
|
||||
<div class="crayons-card p-3 s:p-4 m:p-7 flex-1">
|
||||
<%= render "admin/users/emails/form" %>
|
||||
</div>
|
||||
<div class="crayons-card p-3 s:p-4 m:p-7 flex-1">
|
||||
<%= render "admin/users/emails/index" %>
|
||||
</div>
|
||||
</section>
|
||||
<% elsif current_tab == 'reports' %>
|
||||
<section class="crayons-card p-3 s:p-4 m:p-7">
|
||||
<%= render "admin/users/reports/index" %>
|
||||
</section>
|
||||
<% elsif current_tab == 'flags' %>
|
||||
<section class="crayons-card p-3 s:p-4 m:p-7">
|
||||
<%= render "admin/users/flags/index" %>
|
||||
</section>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="grid gap-6">
|
||||
<div class="crayons-card p-6">
|
||||
<h2 class="d-inline">
|
||||
<% if @user.registered? %>
|
||||
<%= @user.name %><%= link_to "@#{@user.username}", @user.path, class: "ml-2", target: "_blank", rel: "noopener" %>
|
||||
<% else %>
|
||||
@<%= @user.username %> has not accepted their invitation yet.
|
||||
<% end %>
|
||||
</h2>
|
||||
<% if @user.registered? %>
|
||||
<%= @user.name %><%= link_to "@#{@user.username}", @user.path, class: "ml-2", target: "_blank", rel: "noopener" %>
|
||||
<% else %>
|
||||
@<%= @user.username %> has not accepted their invitation yet.
|
||||
<a href="<%= edit_admin_user_path(@user) %>" class="btn btn-primary float-right">Manage User</a>
|
||||
<p class="font-italic">Member since <%= @user.created_at.strftime("%b %e '%y") %></p>
|
||||
<% end %>
|
||||
</h2>
|
||||
<% if @user.registered? %>
|
||||
<a href="<%= edit_admin_user_path(@user) %>" class="btn btn-primary float-right">Manage User</a>
|
||||
<p class="font-italic">Member since <%= @user.created_at.strftime("%b %e '%y") %></p>
|
||||
<% end %>
|
||||
|
||||
<hr>
|
||||
<hr>
|
||||
|
||||
<h2>General Info</h2>
|
||||
<dl>
|
||||
<dt>ID:</dt>
|
||||
<dd><%= @user.id %></dd>
|
||||
<dt>Username:</dt>
|
||||
<dd><%= @user.username %></dd>
|
||||
<dt>Name:</dt>
|
||||
<dd><%= @user.name %></dd>
|
||||
<dt>Email:</dt>
|
||||
<dd><%= @user.email %></dd>
|
||||
<dt>Access Locked:</dt>
|
||||
<dd><%= @user.access_locked? %></dd>
|
||||
<dt>Twitter:</dt>
|
||||
<% if @user.twitter_username %>
|
||||
<dd><%= link_to @user.twitter_username, "https://twitter.com/#{@user.twitter_username}" %></dd>
|
||||
<% else %>
|
||||
<dd>N/A</dd>
|
||||
<% end %>
|
||||
<dt>GitHub:</dt>
|
||||
<% if @user.github_username %>
|
||||
<dd><%= link_to @user.github_username, "https://github.com/#{@user.github_username}" %></dd>
|
||||
<% else %>
|
||||
<dd>N/A</dd>
|
||||
<% end %>
|
||||
</dl>
|
||||
</div>
|
||||
<h2>General Info</h2>
|
||||
<dl>
|
||||
<dt>ID:</dt>
|
||||
<dd><%= @user.id %></dd>
|
||||
<dt>Username:</dt>
|
||||
<dd><%= @user.username %></dd>
|
||||
<dt>Name:</dt>
|
||||
<dd><%= @user.name %></dd>
|
||||
<dt>Email:</dt>
|
||||
<dd><%= @user.email %></dd>
|
||||
<dt>Access Locked:</dt>
|
||||
<dd><%= @user.access_locked? %></dd>
|
||||
<dt>Twitter:</dt>
|
||||
<% if @user.twitter_username %>
|
||||
<dd><%= link_to @user.twitter_username, "https://twitter.com/#{@user.twitter_username}" %></dd>
|
||||
<% else %>
|
||||
<dd>N/A</dd>
|
||||
<% end %>
|
||||
<dt>GitHub:</dt>
|
||||
<% if @user.github_username %>
|
||||
<dd><%= link_to @user.github_username, "https://github.com/#{@user.github_username}" %></dd>
|
||||
<% else %>
|
||||
<dd>N/A</dd>
|
||||
<% end %>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<% if @user.registered? %>
|
||||
<%= render "activity" %>
|
||||
<%= render "credits" %>
|
||||
<%= render "notes" %>
|
||||
<%= render "user_organizations" %>
|
||||
<%= render "email_tools" %>
|
||||
<% if @user.registered? %>
|
||||
<%= render "activity" %>
|
||||
<%= render "credits" %>
|
||||
<%= render "notes" %>
|
||||
<%= render "user_organizations" %>
|
||||
<%= render "email_tools" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
|||
44
app/views/admin/users/widgets/_credits.html.erb
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<header class="mb-4">
|
||||
<h2 class="crayons-subtitle-2 mb-1 flex items-center">
|
||||
<%= crayons_icon_tag(:wallet, title: "Wallet", class: "mr-2") %>
|
||||
Credits
|
||||
</h2>
|
||||
<p class="color-secondary">Credits enable users to purchase classified listings from your site. <a href="https://admin.forem.com/docs/advanced-customization/config/credits" rel="noopener noreferer" target="_blank" class="c-link c-link--branded">Learn more</a></p>
|
||||
</header>
|
||||
<div class="flex justify-between items-center">
|
||||
<p class="fs-2xl fw-heavy"><%= @user.unspent_credits_count %></p>
|
||||
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-trigger data-modal-title="Adjust balance" data-modal-size="small" data-modal-content-selector="#adjust-balance">Adjust balance</button>
|
||||
</div>
|
||||
<% current_credits = @user.unspent_credits_count %>
|
||||
<div id="adjust-balance" class="hidden">
|
||||
<%= form_with scope: :user, url: admin_user_path(@user), method: :patch, local: true, html: { class: "flex flex-col gap-4 fs-base" } do |f| %>
|
||||
<div class="flex items-center gap-2 color-secondary">
|
||||
<%= crayons_icon_tag(:wallet) %>
|
||||
<p>
|
||||
<%= @user.name %> currently has <span class="color-primary"><%= @user.unspent_credits_count %> credits</span>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= f.label :credit_action, "Adjust balance", class: "crayons-field__label" %>
|
||||
<div class="flex gap-2 items-center">
|
||||
<%= f.select :credit_action, options_for_select(current_credits.positive? ? %w[Add Remove] : %w[Add]), {}, class: "crayons-select w-auto" %>
|
||||
<%= f.text_field :credit_amount,
|
||||
type: "number",
|
||||
required: true,
|
||||
class: "crayons-textfield w-auto",
|
||||
value: 1,
|
||||
size: 5,
|
||||
min: 1,
|
||||
max: 9999 %>
|
||||
<%= f.label :credit_delta, "credits", class: "color-secondary flex-1", "aria-label": "Amount of credits to add or remove" %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<label for="balance-note" class="crayons-field__label">Why are you adjusting credits?</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</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
67
app/views/admin/users/widgets/_organizations.html.erb
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<header class="mb-4">
|
||||
<h2 class="crayons-subtitle-2 mb-1 flex items-center">
|
||||
<%= crayons_icon_tag(:team, title: "Organizations", class: "mr-2") %>
|
||||
Organizations
|
||||
</h2>
|
||||
<p class="color-secondary">Assign roles to this member based on permissions. <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">Learn more</a></p>
|
||||
</header>
|
||||
<% if @organization_memberships.load.empty? %>
|
||||
<div class="flex justify-between items-center gap-4">
|
||||
<p>This user is not a part of any organization yet.</p>
|
||||
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-trigger data-modal-title="Add organization" data-modal-size="small" data-modal-content-selector="#add-organization">Add organization</button>
|
||||
</div>
|
||||
<% else %>
|
||||
<% @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">
|
||||
<a href="<%= org_membership.organization.path %>" target="_blank" rel="noopener" class="c-link crayons-logo crayons-logo--l">
|
||||
<img src="<%= org_membership.organization.profile_image_url_for(length: 64) %>" width="32" height="32" alt="<%= org_membership.organization.name %> logo" class="crayons-logo__image">
|
||||
</a>
|
||||
<h3 class="crayons-subtitle-3">
|
||||
<a href="<%= org_membership.organization.path %>" target="_blank" rel="noopener" class="c-link">
|
||||
<%= org_membership.organization.name %>
|
||||
</a>
|
||||
<span class="color-secondary fw-normal">– <%= org_membership.type_of_user.capitalize %></span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="c-card__revealable shrink-0">
|
||||
<button type="button" class="c-btn c-btn--icon-alone" data-modal-trigger data-modal-title="Edit organization" data-modal-size="small" data-modal-content-selector="#edit-organization-<%= org_membership.organization.id %>">
|
||||
<%= crayons_icon_tag(:edit, class: "c-btn__icon") %>
|
||||
</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"), class: "c-btn c-btn--destructive c-btn--icon-alone", data: { confirm: "Are you sure you want to remove them from #{org_membership.organization.name}?" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 "Permission level", class: "crayons-field__label" %>
|
||||
<p class="crayons-field__description">Users can be assigned an Admin role or can remain a regular member of this organization.</p>
|
||||
<%= f.select("type_of_user", options_for_select(%w[member admin], selected: org_membership.type_of_user), {}, class: "crayons-select") %>
|
||||
</div>
|
||||
<%= f.button "Update", 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-trigger data-modal-title="Add organization" data-modal-size="small" data-modal-content-selector="#add-organization">Add another organization</button>
|
||||
<% end %>
|
||||
|
||||
<div id="add-organization" class="hidden">
|
||||
<%= form_with model: [:admin, OrganizationMembership.new], local: true, html: { class: "flex flex-col gap-4" } do |f| %>
|
||||
<%= f.hidden_field :user_id, value: @user.id %>
|
||||
<div class="crayons-field">
|
||||
<%= f.label :organization_id, "Organization 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 "Permission level", class: "crayons-field__label" %>
|
||||
<p class="crayons-field__description">Users can be assigned an Admin role or can remain a regular member of this organization.</p>
|
||||
<%= f.select("type_of_user", options_for_select(%w[member admin]), {}, class: "crayons-select") %>
|
||||
</div>
|
||||
<div>
|
||||
<%= f.submit "Add organization", class: "c-btn c-btn--primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
62
app/views/admin/users/widgets/_roles.html.erb
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<header class="mb-4">
|
||||
<h2 class="crayons-subtitle-2 mb-1 flex items-center">
|
||||
<%= crayons_icon_tag(:badge, title: "Assigned roles", class: "mr-2") %>
|
||||
Assigned roles
|
||||
</h2>
|
||||
<p class="color-secondary">Assign roles to this member based on permissions. <a href="https://admin.forem.com/docs/forem-basics/user-roles" rel="noopener noreferer" target="_blank" class="c-link c-link--branded">Learn more</a></p>
|
||||
</header>
|
||||
<% unless @user.roles.any? %>
|
||||
<div class="flex justify-between items-center">
|
||||
<p>No special roles assigned yet.</p>
|
||||
<button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-trigger data-modal-title="Add role" data-modal-size="m" data-modal-content-selector="#add-role">Add role</button>
|
||||
</div>
|
||||
<% else %>
|
||||
<ul class="flex flex-wrap gap-2">
|
||||
<% @user.roles.reject { |role| role.name == "tag_moderator" }.each do |role| %>
|
||||
<li>
|
||||
<% if role.name == "banned" || role.name == "suspended" || role.name == "super_admin" || @user.id == current_user.id %>
|
||||
<button
|
||||
class="c-pill c-pill--description-icon crayons-tooltip__activator cursor-help"
|
||||
type="button"
|
||||
aria-disabled="true">
|
||||
<%= crayons_icon_tag(:lock, class: "c-pill__description-icon", 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>
|
||||
</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 %>
|
||||
<% if role.resource_name %>
|
||||
<%= role.resource_name.to_s %>
|
||||
<% else %>
|
||||
<%= role_display_name(role) %>
|
||||
<% end %>
|
||||
<%= crayons_icon_tag(:x, class: "c-pill__action-icon", width: 18, height: 18) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<li><button class="c-btn c-btn--secondary whitespace-nowrap" type="button" data-modal-trigger data-modal-title="Add role" data-modal-size="small" data-modal-content-selector="#add-role">Add role</button></li>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<div id="add-role" class="hidden">
|
||||
<%= form_for(@user, url: user_status_admin_user_path(@user), html: { class: "flex flex-col gap-4", id: nil }) do |f| %>
|
||||
<div class="crayons-field">
|
||||
<%= f.label :user_status, "Role", class: "crayons-field__label" %>
|
||||
<% options = { "Base Roles" => Constants::Role::BASE_ROLES } %>
|
||||
<% options["Special Roles"] = Constants::Role::SPECIAL_ROLES if current_user.super_admin? %>
|
||||
<%= f.select(:user_status, grouped_options_for_select(options), { include_blank: "Select..." }, class: "crayons-select") %>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= f.label :note_for_current_role, "Reason", class: "crayons-field__label" %>
|
||||
<%= f.text_area :note_for_current_role, required: true, class: "crayons-textfield", placeholder: "What's the reason for adding a role?" %>
|
||||
</div>
|
||||
<div>
|
||||
<%= f.button "Add", class: "c-btn c-btn--primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
26
app/views/admin/users/widgets/_tag_moderation.html.erb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<header class="mb-4">
|
||||
<h2 class="crayons-subtitle-2 mb-1 flex items-center">
|
||||
<%= crayons_icon_tag("mod-filled", title: "Mod", class: "mr-2") %>
|
||||
Tag moderation
|
||||
</h2>
|
||||
<p class="color-secondary">Assign tags to this member to 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">Learn more</a></p>
|
||||
</header>
|
||||
|
||||
<% moderated_tags = @user.roles.select { |role| role.name == "tag_moderator" } %>
|
||||
|
||||
<% if moderated_tags.any? %>
|
||||
<ul class="flex flex-wrap gap-2 mb-4">
|
||||
<% moderated_tags.each do |role| %>
|
||||
<li>
|
||||
<button class="c-pill c-pill--icon-right" style="--icon-right-color: var(--color-secondary); --icon-right-color-hover: var(--accent-danger);" type="button">
|
||||
#<%= role.resource_name.to_s %>
|
||||
<%= crayons_icon_tag(:x, class: "c-pill__icon", width: 18, height: 18) %>
|
||||
</button>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<div 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>).
|
||||
</div>
|
||||
|
|
@ -158,7 +158,7 @@ RSpec.describe "/admin/users", type: :request do
|
|||
post send_email_admin_user_path(user.id), params: params
|
||||
end
|
||||
|
||||
expect(response).to redirect_to(admin_users_path)
|
||||
expect(response).to redirect_to(admin_user_path)
|
||||
expect(flash[:danger]).to include("failed")
|
||||
end
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ RSpec.describe "/admin/users", type: :request do
|
|||
post send_email_admin_user_path(user.id), params: params
|
||||
end
|
||||
|
||||
expect(response).to redirect_to(admin_users_path)
|
||||
expect(response).to redirect_to(admin_user_path)
|
||||
expect(flash[:success]).to include("sent")
|
||||
|
||||
email = ActionMailer::Base.deliveries.last
|
||||
|
|
@ -235,7 +235,7 @@ RSpec.describe "/admin/users", type: :request do
|
|||
post verify_email_ownership_admin_user_path(user), params: { user_id: user.id }
|
||||
end
|
||||
|
||||
expect(response).to redirect_to(admin_users_path)
|
||||
expect(response).to redirect_to(admin_user_path)
|
||||
expect(flash[:danger]).to include("failed")
|
||||
end
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ RSpec.describe "/admin/users", type: :request do
|
|||
post verify_email_ownership_admin_user_path(user), params: { user_id: user.id }
|
||||
end
|
||||
|
||||
expect(response).to redirect_to(admin_users_path)
|
||||
expect(response).to redirect_to(admin_user_path)
|
||||
expect(flash[:success]).to include("sent")
|
||||
end
|
||||
|
||||
|
|
@ -394,7 +394,7 @@ RSpec.describe "/admin/users", type: :request do
|
|||
it "redirects properly to the user edit page" do
|
||||
sign_in admin
|
||||
post export_data_admin_user_path(user), params: { send_to_admin: "true" }
|
||||
expect(response).to redirect_to edit_admin_user_path(user)
|
||||
expect(response).to redirect_to admin_user_path(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||