From 6ad9e22965b49c35a4e6ac32ce31723aaabff7ca Mon Sep 17 00:00:00 2001
From: xaralis <filip.varecha@fragaria.cz>
Date: Wed, 23 Dec 2020 14:51:35 +0100
Subject: [PATCH] feat: properly handle unbanning

---
 src/actions/users.js              |  2 +-
 src/components/posts/Post.jsx     | 11 ++++++++++-
 src/components/posts/PostList.jsx |  3 +++
 src/containers/PostsContainer.jsx | 33 ++++++++++++++++++++++++++++++-
 src/utils.js                      |  6 +++++-
 src/ws/connection.js              |  8 --------
 src/ws/handlers/global.js         |  9 +++++++++
 src/ws/handlers/index.js          |  2 ++
 src/ws/handlers/users.js          | 18 ++++++++++++++++-
 typings/cf2021.d.ts               |  1 +
 10 files changed, 80 insertions(+), 13 deletions(-)
 create mode 100644 src/ws/handlers/global.js

diff --git a/src/actions/users.js b/src/actions/users.js
index 7fe04d8..49cf58b 100644
--- a/src/actions/users.js
+++ b/src/actions/users.js
@@ -51,7 +51,7 @@ export const ban = createAsyncAction(
   }
 );
 
-export const removeBan = createAsyncAction(
+export const unban = createAsyncAction(
   /**
    * @param {number} userId
    */
diff --git a/src/components/posts/Post.jsx b/src/components/posts/Post.jsx
index dc5fefb..207ed78 100644
--- a/src/components/posts/Post.jsx
+++ b/src/components/posts/Post.jsx
@@ -25,6 +25,7 @@ const Post = ({
   onDislike,
   onHide,
   onBanUser,
+  onUnbanUser,
   onAnnounceProcedureProposal,
   onAcceptProcedureProposal,
   onRejectProcedureProposal,
@@ -131,7 +132,8 @@ const Post = ({
   const showRejectByChairmanAction =
     type === "procedure-proposal" && ["announced", "pending"].includes(state);
   const showEditAction = true;
-  const showBanAction = true;
+  const showBanAction = !author.isBanned;
+  const showUnbanAction = author.isBanned;
   const showHideAction = !archived;
   const showArchiveAction = !archived;
 
@@ -225,6 +227,13 @@ const Post = ({
                       title="Zablokovat uĹľivatele"
                     />
                   )}
+                  {showUnbanAction && (
+                    <DropdownMenuItem
+                      onClick={onUnbanUser}
+                      icon="ico--lock-open"
+                      title="Odblokovat uĹľivatele"
+                    />
+                  )}
                   {showHideAction && (
                     <DropdownMenuItem
                       onClick={onHide}
diff --git a/src/components/posts/PostList.jsx b/src/components/posts/PostList.jsx
index da1ad33..23ba693 100644
--- a/src/components/posts/PostList.jsx
+++ b/src/components/posts/PostList.jsx
@@ -12,6 +12,7 @@ const PostList = ({
   onDislike,
   onHide,
   onBanUser,
+  onUnbanUser,
   onAnnounceProcedureProposal,
   onAcceptProcedureProposal,
   onRejectProcedureProposal,
@@ -31,6 +32,7 @@ const PostList = ({
   const onPostEdit = buildHandler(onEdit);
   const onPostHide = buildHandler(onHide);
   const onPostBanUser = buildHandler(onBanUser);
+  const onPostUnbanUser = buildHandler(onUnbanUser);
   const onPostArchive = buildHandler(onArchive);
   const onPostAnnounceProcedureProposal = buildHandler(
     onAnnounceProcedureProposal
@@ -69,6 +71,7 @@ const PostList = ({
             onDislike={onPostDislike(item)}
             onHide={onPostHide(item)}
             onBanUser={onPostBanUser(item)}
+            onUnbanUser={onPostUnbanUser(item)}
             onAnnounceProcedureProposal={onPostAnnounceProcedureProposal(item)}
             onAcceptProcedureProposal={onPostAcceptProcedureProposal(item)}
             onRejectProcedureProposal={onPostRejectProcedureProposal(item)}
diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx
index 486eafe..863c915 100644
--- a/src/containers/PostsContainer.jsx
+++ b/src/containers/PostsContainer.jsx
@@ -14,7 +14,7 @@ import {
   rejectProposal,
   rejectProposalByChairman,
 } from "actions/posts";
-import { ban } from "actions/users";
+import { ban, unban } from "actions/users";
 import ErrorMessage from "components/ErrorMessage";
 import ModalConfirm from "components/modals/ModalConfirm";
 import PostEditModal from "components/posts/PostEditModal";
@@ -33,6 +33,12 @@ const PostsContainer = ({ className }) => {
     onBanUserConfirm,
     onBanUserCancel,
   ] = useItemActionConfirm(ban);
+  const [
+    userToUnban,
+    setUserToUnban,
+    onUnbanUserConfirm,
+    onUnbanUserCancel,
+  ] = useItemActionConfirm(unban);
   const [
     postToHide,
     setPostToHide,
@@ -79,6 +85,10 @@ const PostsContainer = ({ className }) => {
   );
 
   const [banningUser, banningUserError] = useActionState(ban, userToBan);
+  const [unbanningUser, unbanningUserError] = useActionState(
+    unban,
+    userToUnban
+  );
   const [hidingPost, hidingPostError] = useActionState(hide, postToHide);
   const [archivingPost, archivingPostError] = useActionState(
     archive,
@@ -134,6 +144,13 @@ const PostsContainer = ({ className }) => {
   const onBanUser = (post) => {
     setUserToBan(post.author);
   };
+  /**
+   * Ban a post's author.
+   * @param {CF2021.Post} post
+   */
+  const onUnbanUser = (post) => {
+    setUserToUnban(post.author);
+  };
 
   const sliceStart = (window.page - 1) * window.perPage;
   const sliceEnd = window.page * window.perPage;
@@ -157,6 +174,7 @@ const PostsContainer = ({ className }) => {
         dimArchived={!showingArchivedOnly}
         onHide={setPostToHide}
         onBanUser={onBanUser}
+        onUnbanUser={onUnbanUser}
         onEdit={setPostToEdit}
         onArchive={setPostToArchive}
         onAnnounceProcedureProposal={setPostToAnnounce}
@@ -176,6 +194,19 @@ const PostsContainer = ({ className }) => {
         UĹľivatel <strong>{userToBan ? userToBan.name : null}</strong> bude
         zablokován a nebude dále moci vkládat nové příspěvky. Opravdu to chcete?
       </ModalConfirm>
+      <ModalConfirm
+        isOpen={!!userToUnban}
+        onConfirm={onUnbanUserConfirm}
+        onCancel={onUnbanUserCancel}
+        confirming={unbanningUser}
+        error={unbanningUserError}
+        title={`Odblokovat uĹľivatele ${userToUnban ? userToUnban.name : null}?`}
+        yesActionLabel="Odblokovat"
+      >
+        UĹľivatel <strong>{userToUnban ? userToUnban.name : null}</strong> bude
+        odblokován a bude mu opět umožněno přidávat nové příspěvky. Opravdu to
+        chcete?
+      </ModalConfirm>
       <ModalConfirm
         isOpen={!!postToHide}
         onConfirm={onPostHideConfirm}
diff --git a/src/utils.js b/src/utils.js
index cc62eea..e21427d 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -126,7 +126,11 @@ export const announcementTypeMappingRev = {
  */
 export const parseRawPost = (rawPost) => {
   const post = {
-    ...pick(rawPost, ["id", "content", "author"]),
+    ...pick(rawPost, ["id", "content"]),
+    author: {
+      ...pick(rawPost.author, ["id", "name", "username", "group"]),
+      isBanned: rawPost.author.is_banned === 1,
+    },
     contentHtml: markdownConverter.makeHtml(rawPost.content),
     datetime: parse(rawPost.datetime, "yyyy-MM-dd HH:mm:ss", new Date()),
     historyLog: rawPost.history_log,
diff --git a/src/ws/connection.js b/src/ws/connection.js
index 17f9c2e..4882d1b 100644
--- a/src/ws/connection.js
+++ b/src/ws/connection.js
@@ -1,4 +1,3 @@
-import has from "lodash/has";
 import WaitQueue from "wait-queue";
 
 import { GlobalInfoStore } from "stores";
@@ -20,13 +19,6 @@ function Worker() {
     try {
       const data = JSON.parse(event.data);
 
-      // Special message, TODO: fix
-      if (has(data, "online_users")) {
-        return GlobalInfoStore.update((state) => {
-          state.onlineUsers = data["online_users"];
-        });
-      }
-
       if (!data.event) {
         return console.error("[ws][worker] Missing `event` field");
       }
diff --git a/src/ws/handlers/global.js b/src/ws/handlers/global.js
new file mode 100644
index 0000000..ba8a2e0
--- /dev/null
+++ b/src/ws/handlers/global.js
@@ -0,0 +1,9 @@
+import isNumber from "lodash/isNumber";
+
+import { GlobalInfoStore } from "stores";
+
+export const handleOnlineUsersUpdated = (onlineUserCount) => {
+  GlobalInfoStore.update((state) => {
+    state.onlineUsers = isNumber(onlineUserCount) ? onlineUserCount : 0;
+  });
+};
diff --git a/src/ws/handlers/index.js b/src/ws/handlers/index.js
index fb4f300..fb68618 100644
--- a/src/ws/handlers/index.js
+++ b/src/ws/handlers/index.js
@@ -3,6 +3,7 @@ import {
   handleAnnouncementCreated,
   handleAnnouncementDeleted,
 } from "./announcements";
+import { handleOnlineUsersUpdated } from "./global";
 import {
   handlePostChanged,
   handlePostCreated,
@@ -23,4 +24,5 @@ export const handlers = {
   program_entry_changed: handleProgramEntryChanged,
   user_banned: handleUserBanned,
   user_unbanned: handleUserUnbanned,
+  online_users_updated: handleOnlineUsersUpdated,
 };
diff --git a/src/ws/handlers/users.js b/src/ws/handlers/users.js
index bf9243f..85d8ca2 100644
--- a/src/ws/handlers/users.js
+++ b/src/ws/handlers/users.js
@@ -1,4 +1,4 @@
-import { AuthStore } from "stores";
+import { AuthStore, PostStore } from "stores";
 
 export const handleUserBanned = (payload) => {
   AuthStore.update((state) => {
@@ -6,6 +6,14 @@ export const handleUserBanned = (payload) => {
       state.user.isBanned = true;
     }
   });
+
+  PostStore.update((state) => {
+    Object.keys(state.items).forEach((key) => {
+      if (state.items[key].author.id === payload.id) {
+        state.items[key].author.isBanned = true;
+      }
+    });
+  });
 };
 
 export const handleUserUnbanned = (payload) => {
@@ -14,4 +22,12 @@ export const handleUserUnbanned = (payload) => {
       state.user.isBanned = false;
     }
   });
+
+  PostStore.update((state) => {
+    Object.keys(state.items).forEach((key) => {
+      if (state.items[key].author.id === payload.id) {
+        state.items[key].author.isBanned = false;
+      }
+    });
+  });
 };
diff --git a/typings/cf2021.d.ts b/typings/cf2021.d.ts
index 5b31734..fa510f2 100644
--- a/typings/cf2021.d.ts
+++ b/typings/cf2021.d.ts
@@ -88,6 +88,7 @@ declare namespace CF2021 {
       name: string;
       username: string;
       group: string;
+      isBanned: boolean;
     };
     type: PostType;
     content: string;
-- 
GitLab