[KnackSteem] - User list for moderative actions

in #utopian-io6 years ago (edited)

This PR is about a new component for moderators and supervisors. Supervisors can add/remove the moderator role to a user/contributor, moderators can ban/unban users.

Repository & Pull Request

https://github.com/knacksteem/knacksteem.org
https://github.com/knacksteem/knacksteem.org/pull/12

About knacksteem.org

"Do you have any talent? If yes! then KnackSteem is for you."
"Rewards people with talents, it can be any talent, anything you know how to do best is highly welcome on the platform."

Technology Stack

  • JavaScript ES6+ with Babel
  • React + Redux
  • Ant Design

Changes / New Features

  • New menu entry in the sidebar for the list of users (contributors, moderators, supervisors).
  • User list component with backend search.
  • Single entries have tags for their user roles, if moderator or supervisor. No tag for simple contributors.
  • There is a special "banned" tag for banned users, with a PopOver showing the ban details.
  • Depending on the user role, several buttons for moderative actions show up (see screenshots).
  • Banning users requires a reason and an end date, both data can be entered in a modal (see screenshots).

Screenshots

Tags/Buttons

userlist_tags.png

Ban Modal

userlist_ban_modal.png

Ban Popover

userlist_ban_popover.png

Details

Now let´s go through the most important or interesting parts of the PR. I decided to use one single Redux action for the moderative operations, because there is not much difference and redundant code is uncool. I believe it is easy to read, banReason and bannedUntil are only used for the ban/unban operation, of course. It takes the action as string to specify a dynamic API endpoint. Of course you could just use the endpoint directly as action string, but I want to keep API endpoint information in Redux actions:

export const moderateUser = (username, action, banReason, bannedUntil) => {
  return async (dispatch) => {
    const modEndpoints = {
      addSupervisor: '/moderation/add/supervisor',
      removeSupervisor: '/moderation/remove/supervisor',
      addModerator: '/moderation/add/moderator',
      removeModerator: '/moderation/remove/moderator',
      ban: '/moderation/ban',
      unban: '/moderation/unban'
    };
    try {
      await apiPost(modEndpoints[action], {
        access_token: Cookies.get('accessToken'),
        username: username,
        banReason: banReason,
        bannedUntil: bannedUntil,
      });

      dispatch(getUserList());
    } catch (error) {
      console.log(error);
    }
  };
};

(actions/stats.js)

The mod buttons may be used elsewhere, so I decided to create a separate component for those. Depending on the user role, different mod buttons may appear. Checking for a username directly is the only option to check for a "Master User" as of now, there is no separate role for it right now in the backend:

render() {
  const {showBanModal, banReason, showModalError} = this.state;
  const {user, item} = this.props;

  return (
    <div className="mod-buttons">
      {(user.username === 'knowledges' && item.roles.indexOf('supervisor') === -1) && <Button type="primary" size="small" onClick={this.onMakeSupervisorClick}>Make Supervisor</Button>}
      {(user.username === 'knowledges' && item.roles.indexOf('supervisor') !== -1) && <Button type="primary" size="small" onClick={this.onRemoveSupervisorClick}>Remove Supervisor</Button>}
      {(user.isSupervisor && item.roles.indexOf('moderator') === -1) && <Button type="primary" size="small" onClick={this.onMakeModClick}>Make Mod</Button>}
      {(user.isSupervisor && item.roles.indexOf('moderator') !== -1) && <Button type="primary" size="small" onClick={this.onRemoveModClick}>Remove Mod</Button>}
      {(user.isModerator && !item.isBanned) && <Button type="primary" size="small" onClick={this.onBanClick}>Ban</Button>}
      {(user.isModerator && item.isBanned) && <Button type="primary" size="small" onClick={this.onUnbanClick}>Unban</Button>}
      <Modal
        title="Ban User"
        visible={showBanModal}
        onOk={this.onBanOkClick}
        onCancel={this.onBanCancelClick}
      >
        <div className="mod-modal-input"><Input placeholder="Reason" value={banReason} onChange={this.handleBanReasonInput} /></div>
        <div className="mod-modal-input"><DatePicker onChange={this.handleBannedUntilInput} /></div>
        {showModalError && <div className="mod-modal-error">Reason and End Date have to be entered!</div>}
      </Modal>
    </div>
  );
}

(components/Common/ModButtons.js)

The Modal for banning users includes an Input and a DatePicker, both have to be entered in order to ban a user. There is just one error message for both, because it´s only for two fields anyway.

The Users container includes a stateless component for the title of the list item. This is basically to keep the main render function smaller and it contains a PopOver component showing details for a user ban. It shows the ban reason, the end date of the ban and the user who created the ban:

const Title = ({username, roles, isBanned, bannedBy, bannedReason, bannedUntil}) => {
  const bannedPopover = (
    <div>
      <p>Reason: {bannedReason}</p>
      <p>Banned Until: {timestampToDate(bannedUntil)}</p>
      <p>Banned By: {bannedBy}</p>
    </div>
  );
  return (
    <div>
      <a href={`https://www.steemit.com/@${username}`}>{username}</a>
      <div className="mod-tags">
        {roles.filter(role => role !== 'contributor').map((role) => {
          return (
            <Tag key={`${username}-${role}`} color={(role === 'supervisor') ? 'magenta' : 'blue'}>{role}</Tag>
          );
        })}
        {isBanned && <Popover content={bannedPopover} title="Ban Details"><Tag color="red">banned</Tag></Popover>}
      </div>
    </div>
  );
};

(containers/Users/index.js)

The main list is a simple Ant Design List component and includes the already mentioned Title and ModButtons components in its item renderer:

<List
  dataSource={users}
  renderItem={item => {
    return (
      <List.Item>
        <List.Item.Meta
          avatar={<Avatar src={`https://steemitimages.com/u/${item.username}/avatar`} />}
          title={<Title username={item.username} roles={item.roles} isBanned={item.isBanned} bannedBy={item.bannedBy} bannedReason={item.bannedReasons} bannedUntil={item.bannedUntil} />}
          description={`Contributions: ${item.contributions || 0}`}
        />
        <ModButtons item={item} />
      </List.Item>
    );
  }}
/>

(containers/Users/index.js)

As always, I am happy to get feedback for improvement, so don´t be shy :)

Roadmap

  • Bugfixing and UI improvements.
  • Error handling with Toast messages or Modals.
    ...

How to contribute?

Talk to @knowledges (or me) :)


My GitHub Account: https://github.com/ateufel

Sort:  

Thank you for your contribution. Is there any specific reason why https://github.com/knacksteem/knacksteem.org/pull/12/commits/78f47b1bfd827297fa580afb64d3a956705e8f61#diff-ddf42f71ba795f6c1c20a6f37c330cdbR93 is not db driven?

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Hi! Thanx for the review, I have explained that in my article, if that´s what you mean: "Checking for a username directly is the only option to check for a "Master User" as of now, there is no separate role for it right now in the backend". It would be best to create a separate role in the DB for it, of course. I have informed the backend dev about it already, he needs to implement it before i can use it in the Frontend :)

Hey @luschn
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Congratulations @luschn! You have completed the following achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of comments

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

To support your work, I also upvoted your post!

Do not miss the last post from @steemitboard:
SteemitBoard World Cup Contest - The results, the winners and the prizes

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!