From 28e38cbb7d03b635c067f1b41a82cbfb46f844fd Mon Sep 17 00:00:00 2001 From: Michael Kohl Date: Thu, 1 Apr 2021 16:26:43 +0700 Subject: [PATCH] Add flag link to profile dropdown view (#12862) * Add flag link to profile dropdown view * Add JS for flag button on profile * Only show flag button to trusted users * Quick fix for accidental unflagging This will be more properly addressed in a future refactoring. * Update reaction specs * Update spec * Make flagging togglable * Make CodeClimate happy * Revert accidentally changed file * CodeClimate * Update app/javascript/profileDropdown/flagButton.js Co-authored-by: Vaidehi Joshi * Add JSDoc * Remove second popup * Refactor and add system spec * CodeClimate * Change send to public_send * Address PR feedback by @aitchiss Co-authored-by: Vaidehi Joshi --- app/controllers/stories_controller.rb | 1 + app/javascript/packs/profileDropdown.js | 10 ++- app/javascript/profileDropdown/flagButton.js | 67 +++++++++++++++++++ app/views/users/show.html.erb | 17 +++-- spec/requests/reactions_spec.rb | 21 +++--- .../user/trusted_user_flags_user_spec.rb | 43 ++++++++++++ 6 files changed, 145 insertions(+), 14 deletions(-) create mode 100644 app/javascript/profileDropdown/flagButton.js create mode 100644 spec/system/user/trusted_user_flags_user_spec.rb diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index dc1f35e34..807b6bb4b 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -236,6 +236,7 @@ class StoriesController < ApplicationController # 2nd with 1 badge (!) <-- and that would look off. @badges_limit = 6 @profile = @user.profile.decorate + @is_user_flagged = Reaction.where(user_id: session_current_user_id, reactable: @user).any? set_surrogate_key_header "articles-user-#{@user.id}" set_user_json_ld diff --git a/app/javascript/packs/profileDropdown.js b/app/javascript/packs/profileDropdown.js index 3d8338083..6e0c35664 100644 --- a/app/javascript/packs/profileDropdown.js +++ b/app/javascript/packs/profileDropdown.js @@ -1,7 +1,13 @@ import { initBlock } from '../profileDropdown/blockButton'; +import { initFlag } from '../profileDropdown/flagButton'; + +function initButtons() { + initBlock(); + initFlag(); +} window.InstantClick.on('change', () => { - initBlock(); + initButtons(); }); -initBlock(); +initButtons(); diff --git a/app/javascript/profileDropdown/flagButton.js b/app/javascript/profileDropdown/flagButton.js new file mode 100644 index 000000000..1cd76ecd4 --- /dev/null +++ b/app/javascript/profileDropdown/flagButton.js @@ -0,0 +1,67 @@ +/* global userData */ +/* eslint-disable no-alert */ +/** + * Adds a flag button visible only to trusted users on profile pages. + * @function initFlag + * @returns {(void|undefined)} This function has no useable return value. + */ +export function initFlag() { + const flagButton = document.getElementById( + 'user-profile-dropdownmenu-flag-button', + ); + + if (!flagButton) { + // button not always present when this is called + return; + } + + const user = userData(); + if (!user) { + return; + } + + const { profileUserId, profileUserName } = flagButton.dataset; + let isUserFlagged = flagButton.dataset.isUserFlagged === 'true'; + const trustedOrAdmin = user.trusted || user.admin; + + if (!trustedOrAdmin || user.id === parseInt(profileUserId, 10)) { + flagButton.remove(); + } + + function flag() { + const confirmFlag = window.confirm( + isUserFlagged + ? 'Are you sure you want to unflag this person? This will make all of their posts visible again.' + : 'Are you sure you want to flag this person? This will make all of their posts less visible.', + ); + + if (confirmFlag) { + fetch('/reactions', { + method: 'POST', + headers: { + 'X-CSRF-Token': window.csrfToken, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + reactable_type: 'User', + category: 'vomit', + reactable_id: profileUserId, + }), + }) + .then((response) => response.json()) + .then((response) => { + if (response.result === 'create') { + isUserFlagged = true; + flagButton.innerHTML = `Unflag ${profileUserName}`; + } else { + isUserFlagged = false; + flagButton.innerHTML = `Flag ${profileUserName}`; + } + }) + .catch((e) => window.alert(`Something went wrong: ${e}`)); + } + } + + flagButton.addEventListener('click', flag); +} +/* eslint-enable no-alert */ diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 746fffdc2..58690a59b 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -33,8 +33,17 @@
- Block @<%= @user.username %> <% if user_signed_in? %> + Block @<%= @user.username %> + + <%= @flag_status ? "Unflag" : "Flag" %> @<%= @user.username %> + <%= javascript_packs_with_chunks_tag "profileDropdown", defer: true %> <% end %> @@ -75,9 +84,9 @@ <% end %> <% Profile.special_social_link_attributes.each do |attribute| %> - <% if @user.send(attribute).present? %> - - <%= inline_svg_tag("#{attribute.gsub("_url","")}.svg", class: "crayons-icon", aria: true, title: "#{attribute.split("_").first.titleize} logo") %> + <% if @user.public_send(attribute).present? %> + + <%= inline_svg_tag("#{attribute.gsub('_url', '')}.svg", class: "crayons-icon", aria: true, title: "#{attribute.split('_').first.titleize} logo") %> <% end %> <% end %> diff --git a/spec/requests/reactions_spec.rb b/spec/requests/reactions_spec.rb index b2f46ad84..2929220e8 100644 --- a/spec/requests/reactions_spec.rb +++ b/spec/requests/reactions_spec.rb @@ -190,17 +190,20 @@ RSpec.describe "Reactions", type: :request do context "when reacting to an article" do before do sign_in user - post "/reactions", params: article_params end it "creates reaction" do - expect(Reaction.last.reactable_id).to eq(article.id) + expect do + post "/reactions", params: article_params + end.to change(Reaction, :count).by(1) end it "destroys existing reaction" do - # same route to destroy, so sending POST request again post "/reactions", params: article_params - expect(Reaction.all.size).to eq(0) + expect do + # same route to destroy, so sending POST request again + post "/reactions", params: article_params + end.to change(Reaction, :count).by(-1) end end @@ -270,17 +273,19 @@ RSpec.describe "Reactions", type: :request do context "when vomiting on a user" do before do sign_in trusted_user - post "/reactions", params: user_params end it "creates reaction" do - expect(Reaction.last.reactable_id).to eq(user.id) + expect do + post "/reactions", params: user_params + end.to change(Reaction, :count).by(1) end it "destroys existing reaction" do - # same route to destroy, so sending POST request again post "/reactions", params: user_params - expect(Reaction.all.size).to eq(0) + expect do + post "/reactions", params: user_params + end.to change(Reaction, :count).by(-1) end end diff --git a/spec/system/user/trusted_user_flags_user_spec.rb b/spec/system/user/trusted_user_flags_user_spec.rb new file mode 100644 index 000000000..82f5aacee --- /dev/null +++ b/spec/system/user/trusted_user_flags_user_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.describe "Flagging users from profile pages", type: :system, js: true do + let(:user) { create :user } + let(:flag_text) { "Flag @#{user.username}" } + + context "when not logged in" do + it "does not show the flag button" do + visit user_profile_path(user.username) + click_button(id: "user-profile-dropdown") + expect(page).not_to have_link(flag_text) + end + end + + context "when signed in as a non-trusted user" do + it "does not show the flag button" do + sign_in create(:user) + + visit user_profile_path(user.username) + click_button(id: "user-profile-dropdown") + expect(page).not_to have_link(flag_text) + end + end + + context "when signed in as the user" do + it "does not show a button for flagging yourself" do + sign_in user + + visit user_profile_path(user.username) + expect(page).not_to have_selector("user-profile-dropdown") + end + end + + context "when signed in as a trusted user" do + it "allows toggling the flagged status" do + sign_in create(:user, :trusted) + + visit user_profile_path(user.username) + click_button(id: "user-profile-dropdown") + expect(page).to have_link(flag_text) + end + end +end