From 5d0df31ce281f5d80338e3cf6a983c004cb64bd6 Mon Sep 17 00:00:00 2001
From: xaralis <filip.varecha@fragaria.cz>
Date: Fri, 18 Dec 2020 10:08:16 +0100
Subject: [PATCH] feat: announcement delete, post actions (ban user, hide post,
 announce/accept/reject proposal)

---
 package.json                             |   2 +-
 src/actions/posts.js                     | 117 +++++++++++++++++
 src/actions/program.js                   |   2 +-
 src/actions/users.js                     |  10 ++
 src/api.js                               |   2 +-
 src/containers/AnnoucementsContainer.jsx |  23 ++--
 src/containers/PostsContainer.jsx        | 153 ++++++++++++++++++-----
 src/hooks.js                             |  18 +++
 src/stores.js                            |  11 ++
 typings/cf2021.d.ts                      |   1 +
 10 files changed, 292 insertions(+), 47 deletions(-)
 create mode 100644 src/actions/users.js
 create mode 100644 src/hooks.js

diff --git a/package.json b/package.json
index a0a7779..1c41852 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
               "^@?\\w"
             ],
             [
-              "^(actions|components|containers|pages|utils|stores|keycloak)(/.*|$)"
+              "^(api|actions|config|hooks|components|containers|pages|utils|stores|keycloak)(/.*|$)"
             ],
             [
               "^(test-utils)(/.*|$)"
diff --git a/src/actions/posts.js b/src/actions/posts.js
index 6d15bd7..017c74e 100644
--- a/src/actions/posts.js
+++ b/src/actions/posts.js
@@ -204,3 +204,120 @@ export const addProposal = createAsyncAction(
     },
   }
 );
+
+/**
+ * Hide existing post.
+ */
+export const hide = createAsyncAction(
+  /**
+   * @param {CF2021.Post} post
+   */
+  async (post) => {
+    return successResult(post);
+  },
+  {
+    postActionHook: ({ result }) => {
+      if (!result.error) {
+        PostStore.update((state) => {
+          state.items[result.payload.id].hidden = true;
+        });
+      }
+    },
+  }
+);
+
+/**
+ * Announce procedure proposal.
+ */
+export const announceProposal = createAsyncAction(
+  /**
+   * @param {CF2021.ProposalPost} proposal
+   */
+  async (proposal) => {
+    return successResult(proposal);
+  },
+  {
+    shortCircuitHook: ({ args }) => {
+      if (args.type !== "procedure-proposal") {
+        return errorResult();
+      }
+
+      if (args.state !== "pending") {
+        return errorResult();
+      }
+
+      return false;
+    },
+    postActionHook: ({ result }) => {
+      if (!result.error) {
+        PostStore.update((state) => {
+          state.items[result.payload.id].state = "announced";
+        });
+      }
+    },
+  }
+);
+
+/**
+ * Announce procedure proposal.
+ */
+export const acceptProposal = createAsyncAction(
+  /**
+   * @param {CF2021.ProposalPost} proposal
+   */
+  async (proposal) => {
+    return successResult(proposal);
+  },
+  {
+    shortCircuitHook: ({ args }) => {
+      if (args.type !== "procedure-proposal") {
+        return errorResult();
+      }
+
+      if (args.state !== "announced") {
+        return errorResult();
+      }
+
+      return false;
+    },
+    postActionHook: ({ result }) => {
+      if (!result.error) {
+        PostStore.update((state) => {
+          state.items[result.payload.id].state = "accepted";
+        });
+      }
+    },
+  }
+);
+
+/**
+ * Reject procedure proposal.
+ */
+export const rejectProposal = createAsyncAction(
+  /**
+   * @param {CF2021.ProposalPost} proposal
+   */
+  async (proposal) => {
+    return successResult(proposal);
+  },
+  {
+    shortCircuitHook: ({ args }) => {
+      if (args.type !== "procedure-proposal") {
+        return errorResult();
+      }
+
+      if (args.state !== "announced") {
+        return errorResult();
+      }
+
+      return false;
+    },
+    postActionHook: ({ result }) => {
+      if (!result.error) {
+        PostStore.update((state) => {
+          state.items[result.payload.id].state = "rejected";
+        });
+      }
+    },
+  }
+);
diff --git a/src/actions/program.js b/src/actions/program.js
index 7a70b07..f1d0fa3 100644
--- a/src/actions/program.js
+++ b/src/actions/program.js
@@ -1,7 +1,7 @@
-import { fetch } from "api";
 import pick from "lodash/pick";
 import { createAsyncAction, errorResult, successResult } from "pullstate";
 
+import { fetch } from "api";
 import { ProgramStore } from "stores";
 
 export const loadProgram = createAsyncAction(
diff --git a/src/actions/users.js b/src/actions/users.js
new file mode 100644
index 0000000..7e3d1ae
--- /dev/null
+++ b/src/actions/users.js
@@ -0,0 +1,10 @@
+import { createAsyncAction, successResult } from "pullstate";
+
+export const ban = createAsyncAction(
+  /**
+   * @param {number} userId
+   */
+  async (userId) => {
+    return successResult(userId);
+  }
+);
diff --git a/src/api.js b/src/api.js
index b5674b2..707c7c2 100644
--- a/src/api.js
+++ b/src/api.js
@@ -9,7 +9,7 @@ export const fetch = (url, opts) => {
   opts.headers = opts.headers || {};
 
   if (isAuthenticated) {
-    // opts.headers.Authorization = "Bearer " + user.accessToken;
+    opts.headers.Authorization = "Bearer " + user.accessToken;
   }
 
   return baseFetch(process.env.REACT_APP_API_BASE_URL + url, opts);
diff --git a/src/containers/AnnoucementsContainer.jsx b/src/containers/AnnoucementsContainer.jsx
index 3ea32c8..daeafc6 100644
--- a/src/containers/AnnoucementsContainer.jsx
+++ b/src/containers/AnnoucementsContainer.jsx
@@ -7,11 +7,19 @@ import {
 import AnnouncementEditModal from "components/annoucements/AnnouncementEditModal";
 import AnnouncementList from "components/annoucements/AnnouncementList";
 import ModalConfirm from "components/modals/ModalConfirm";
+import { useModalConfirmControl } from "hooks";
 import { AnnouncementStore } from "stores";
 
 const AnnoucementsContainer = () => {
-  const [itemToDelete, setItemToDelete] = useState(null);
   const [itemToEdit, setItemToEdit] = useState(null);
+
+  const [
+    itemToDelete,
+    setItemToDelete,
+    onDeleteConfirm,
+    onDeleteCancel,
+  ] = useModalConfirmControl(deleteAnnouncement);
+
   const items = AnnouncementStore.useState((state) => state.items);
 
   const confirmEdit = useCallback(
@@ -28,15 +36,6 @@ const AnnoucementsContainer = () => {
     setItemToEdit(null);
   }, [setItemToEdit]);
 
-  const confirmDelete = useCallback(async () => {
-    await deleteAnnouncement.run(itemToDelete);
-    setItemToDelete(null);
-  }, [setItemToDelete, itemToDelete]);
-
-  const cancelDelete = useCallback(() => {
-    setItemToDelete(null);
-  }, [setItemToDelete]);
-
   return (
     <>
       <AnnouncementList
@@ -47,8 +46,8 @@ const AnnoucementsContainer = () => {
       />
       <ModalConfirm
         isOpen={!!itemToDelete}
-        onConfirm={confirmDelete}
-        onCancel={cancelDelete}
+        onConfirm={onDeleteConfirm}
+        onCancel={onDeleteCancel}
         title="Opravdu chcete toto oznámení smazat?"
         yesActionLabel="Smazat"
       >
diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx
index f1e7fea..0b512ec 100644
--- a/src/containers/PostsContainer.jsx
+++ b/src/containers/PostsContainer.jsx
@@ -1,11 +1,54 @@
 import React from "react";
 import pick from "lodash/pick";
 
-import { dislike, like, removeDislike, removeLike } from "actions/posts";
+import {
+  acceptProposal,
+  announceProposal,
+  dislike,
+  hide,
+  like,
+  rejectProposal,
+  removeDislike,
+  removeLike,
+} from "actions/posts";
+import { ban } from "actions/users";
+import ModalConfirm from "components/modals/ModalConfirm";
 import PostList from "components/posts/PostList";
+import { useModalConfirmControl } from "hooks";
 import { PostStore } from "stores";
 
 const PostsContainer = ({ className }) => {
+  const [
+    userToBan,
+    setUserToBan,
+    onBanUserConfirm,
+    onBanUserCancel,
+  ] = useModalConfirmControl(ban);
+  const [
+    postToHide,
+    setPostToHide,
+    onPostHideConfirm,
+    onPostHideCancel,
+  ] = useModalConfirmControl(hide);
+  const [
+    postToAnnounce,
+    setPostToAnnounce,
+    onAnnounceConfirm,
+    onAnnounceCancel,
+  ] = useModalConfirmControl(announceProposal);
+  const [
+    postToAccept,
+    setPostToAccept,
+    onAcceptConfirm,
+    onAcceptCancel,
+  ] = useModalConfirmControl(acceptProposal);
+  const [
+    postToReject,
+    setPostToReject,
+    onRejectConfirm,
+    onRejectCancel,
+  ] = useModalConfirmControl(rejectProposal);
+
   const { window, items } = PostStore.useState((state) =>
     pick(state, ["window", "items"])
   );
@@ -13,9 +56,6 @@ const PostsContainer = ({ className }) => {
     (state) => state.filters.flags === "archived"
   );
 
-  // const onLike = (post) => like.run();
-  // const onDislike = (post) => console.log("dislike", post);
-
   /**
    *
    * @param {CF2021.Post} post
@@ -45,38 +85,87 @@ const PostsContainer = ({ className }) => {
   const sliceStart = (window.page - 1) * window.perPage;
   const sliceEnd = window.page * window.perPage;
 
-  const onHide = (post) => {
-    console.log("hide", post);
-  };
+  /**
+   * Ban a post's author.
+   * @param {CF2021.Post} post
+   */
   const onBanUser = (post) => {
-    console.log("banUser", post);
-  };
-  const onAnnounceProcedureProposal = (post) => {
-    console.log("announce", post);
-  };
-  const onAcceptProcedureProposal = (post) => {
-    console.log("accept", post);
-  };
-  const onRejectProcedureProposal = (post) => {
-    console.log("reject", post);
+    setUserToBan(post.author);
   };
 
   return (
-    <PostList
-      items={window.items
-        .slice(sliceStart, sliceEnd)
-        .map((postId) => items[postId])}
-      onLike={onLike}
-      onDislike={onDislike}
-      className={className}
-      dimArchived={!showingArchivedOnly}
-      displayActions={true}
-      onHide={onHide}
-      onBanUser={onBanUser}
-      onAnnounceProcedureProposal={onAnnounceProcedureProposal}
-      onAcceptProcedureProposal={onAcceptProcedureProposal}
-      onRejectProcedureProposal={onRejectProcedureProposal}
-    />
+    <>
+      <PostList
+        items={window.items
+          .slice(sliceStart, sliceEnd)
+          .map((postId) => items[postId])}
+        onLike={onLike}
+        onDislike={onDislike}
+        className={className}
+        dimArchived={!showingArchivedOnly}
+        displayActions={true}
+        onHide={setPostToHide}
+        onBanUser={onBanUser}
+        onAnnounceProcedureProposal={setPostToAnnounce}
+        onAcceptProcedureProposal={setPostToAccept}
+        onRejectProcedureProposal={setPostToReject}
+      />
+      <ModalConfirm
+        isOpen={!!userToBan}
+        onConfirm={onBanUserConfirm}
+        onCancel={onBanUserCancel}
+        title={`Zablokovat uživatele ${userToBan ? userToBan.name : null}?`}
+        yesActionLabel="Zablokovat"
+      >
+        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={!!postToHide}
+        onConfirm={onPostHideConfirm}
+        onCancel={onPostHideCancel}
+        title="Skrýt příspěvek?"
+        yesActionLabel="Potvrdit"
+      >
+        Příspěvek se skryje a uživatelé ho neuvidí. Opravdu to chcete?
+      </ModalConfirm>
+      <ModalConfirm
+        isOpen={!!postToHide}
+        onConfirm={onPostHideConfirm}
+        onCancel={onPostHideCancel}
+        title="Skrýt příspěvek?"
+        yesActionLabel="Potvrdit"
+      >
+        Příspěvek se skryje a uživatelé ho neuvidí. Opravdu to chcete?
+      </ModalConfirm>
+      <ModalConfirm
+        isOpen={!!postToAnnounce}
+        onConfirm={onAnnounceConfirm}
+        onCancel={onAnnounceCancel}
+        title="Vyhlásit procedurální návrh?"
+        yesActionLabel="Vyhlásit návrh"
+      >
+        Procedurální návrh bude <strong>vyhlášen</strong>. Opravdu to chcete?
+      </ModalConfirm>
+      <ModalConfirm
+        isOpen={!!postToAccept}
+        onConfirm={onAcceptConfirm}
+        onCancel={onAcceptCancel}
+        title="Schválit procedurální návrh?"
+        yesActionLabel="Schválit návrh"
+      >
+        Procedurální návrh bude <strong>schválen</strong>. Opravdu to chcete?
+      </ModalConfirm>
+      <ModalConfirm
+        isOpen={!!postToReject}
+        onConfirm={onRejectConfirm}
+        onCancel={onRejectCancel}
+        title="Zamítnout procedurální návrh?"
+        yesActionLabel="Zamítnout návrh"
+      >
+        Procedurální návrh bude <strong>zamítnut</strong>. Opravdu to chcete?
+      </ModalConfirm>
+    </>
   );
 };
 
diff --git a/src/hooks.js b/src/hooks.js
new file mode 100644
index 0000000..6942ed6
--- /dev/null
+++ b/src/hooks.js
@@ -0,0 +1,18 @@
+import { useCallback, useState } from "react";
+
+export const useModalConfirmControl = (actionFn) => {
+  const [item, setItem] = useState(null);
+
+  const onActionConfirm = useCallback(() => {
+    if (item) {
+      actionFn.run(item);
+      setItem(null);
+    }
+  }, [item, setItem, actionFn]);
+
+  const onActionCancel = useCallback(() => {
+    setItem(null);
+  }, [setItem]);
+
+  return [item, setItem, onActionConfirm, onActionCancel];
+};
diff --git a/src/stores.js b/src/stores.js
index aaf7353..599d26c 100644
--- a/src/stores.js
+++ b/src/stores.js
@@ -84,6 +84,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "cf",
     },
@@ -103,6 +104,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "cf",
     },
@@ -122,6 +124,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "cf",
     },
@@ -141,6 +144,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "KS Pardubický kraj",
     },
@@ -160,6 +164,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "KS Pardubický kraj",
     },
@@ -179,6 +184,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "KS Pardubický kraj",
     },
@@ -198,6 +204,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "KS Pardubický kraj",
     },
@@ -218,6 +225,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "KS Pardubický kraj",
     },
@@ -246,6 +254,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "KS Pardubický kraj",
     },
@@ -266,6 +275,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "KS Pardubický kraj",
     },
@@ -286,6 +296,7 @@ const allPosts = [
       "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
     datetime: new Date(),
     author: {
+      id: 1,
       name: "John Doe",
       group: "KS Pardubický kraj",
     },
diff --git a/typings/cf2021.d.ts b/typings/cf2021.d.ts
index 539ebe8..35ff79b 100644
--- a/typings/cf2021.d.ts
+++ b/typings/cf2021.d.ts
@@ -69,6 +69,7 @@ declare namespace CF2021 {
         id: string;
         datetime: Date;
         author: {
+            id: number;
             name: string;
             group: string;
         };
-- 
GitLab