From cfadd6949ba6cacd1dba1dfe7127ec9acf366d2a Mon Sep 17 00:00:00 2001
From: xaralis <filip.varecha@fragaria.cz>
Date: Tue, 29 Dec 2020 12:02:59 +0100
Subject: [PATCH] refactor: simplify action handlers

---
 src/containers/AnnoucementsContainer.jsx | 61 +++++----------
 src/containers/PostsContainer.jsx        | 96 ++++++++----------------
 src/hooks.js                             | 15 +++-
 src/ws/handlers/announcements.js         |  4 +
 4 files changed, 67 insertions(+), 109 deletions(-)

diff --git a/src/containers/AnnoucementsContainer.jsx b/src/containers/AnnoucementsContainer.jsx
index 2e3de91..a5aa7aa 100644
--- a/src/containers/AnnoucementsContainer.jsx
+++ b/src/containers/AnnoucementsContainer.jsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from "react";
+import React from "react";
 
 import {
   deleteAnnouncement,
@@ -11,13 +11,10 @@ import AnnouncementList from "components/annoucements/AnnouncementList";
 import { CardBody } from "components/cards";
 import ErrorMessage from "components/ErrorMessage";
 import ModalConfirm from "components/modals/ModalConfirm";
-import { useActionState, useItemActionConfirm } from "hooks";
+import { useItemActionConfirm } from "hooks";
 import { AnnouncementStore, AuthStore } from "stores";
 
 const AnnoucementsContainer = ({ className }) => {
-  const [itemToEdit, setItemToEdit] = useState(null);
-  const [confirmingEdit, setConfirmingEdit] = useState(false);
-  const [editError, setEditError] = useState(null);
   const { 2: loadResult } = loadAnnouncements.useWatch();
 
   const [
@@ -25,45 +22,25 @@ const AnnoucementsContainer = ({ className }) => {
     setItemToDelete,
     onDeleteConfirm,
     onDeleteCancel,
+    deleteState,
   ] = useItemActionConfirm(deleteAnnouncement);
 
-  const [deletingAnnouncement, deletingAnnouncementError] = useActionState(
-    deleteAnnouncement,
-    itemToDelete
-  );
+  const [
+    itemToEdit,
+    setItemToEdit,
+    onEditConfirm,
+    onEditCancel,
+    editState,
+  ] = useItemActionConfirm(updateAnnouncement, (item, payload) => ({
+    item,
+    payload,
+  }));
 
   const { isAuthenticated, user } = AuthStore.useState();
   const items = AnnouncementStore.useState((state) =>
     state.itemIds.map((id) => state.items[id])
   );
 
-  const confirmEdit = useCallback(
-    async (payload) => {
-      if (itemToEdit && payload) {
-        setConfirmingEdit(true);
-
-        const result = await updateAnnouncement.run({
-          item: itemToEdit,
-          payload,
-        });
-
-        if (!result.error) {
-          setItemToEdit(null);
-          setEditError(null);
-        } else {
-          setEditError(result.message);
-        }
-
-        setConfirmingEdit(false);
-      }
-    },
-    [itemToEdit, setItemToEdit]
-  );
-
-  const cancelEdit = useCallback(() => {
-    setItemToEdit(null);
-  }, [setItemToEdit]);
-
   return (
     <div className={className}>
       {loadResult && loadResult.error && (
@@ -84,8 +61,8 @@ const AnnoucementsContainer = ({ className }) => {
         isOpen={!!itemToDelete}
         onConfirm={onDeleteConfirm}
         onCancel={onDeleteCancel}
-        confirming={deletingAnnouncement}
-        error={deletingAnnouncementError}
+        confirming={deleteState.loading}
+        error={deleteState.error}
         title="Opravdu smazat?"
         yesActionLabel="Smazat"
       >
@@ -95,10 +72,10 @@ const AnnoucementsContainer = ({ className }) => {
         <AnnouncementEditModal
           isOpen={true}
           announcement={itemToEdit}
-          onConfirm={confirmEdit}
-          onCancel={cancelEdit}
-          confirming={confirmingEdit}
-          error={editError}
+          onConfirm={onEditConfirm}
+          onCancel={onEditCancel}
+          confirming={editState.loading}
+          error={editState.error}
         />
       )}
     </div>
diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx
index 5daa03d..01b902e 100644
--- a/src/containers/PostsContainer.jsx
+++ b/src/containers/PostsContainer.jsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from "react";
+import React from "react";
 import pick from "lodash/pick";
 
 import {
@@ -25,46 +25,58 @@ import { useActionState, useItemActionConfirm } from "hooks";
 import { AuthStore, PostStore } from "stores";
 
 const PostsContainer = ({ className, showAddPostCta }) => {
-  const [postToEdit, setPostToEdit] = useState(null);
-  const [confirmingEdit, setConfirmingEdit] = useState(false);
-  const [editError, setEditError] = useState(null);
-
   const [
     userToBan,
     setUserToBan,
     onBanUserConfirm,
     onBanUserCancel,
+    banUserState,
   ] = useItemActionConfirm(ban);
   const [
     userToUnban,
     setUserToUnban,
     onUnbanUserConfirm,
     onUnbanUserCancel,
+    unbanUserState,
   ] = useItemActionConfirm(unban);
   const [
     postToHide,
     setPostToHide,
     onPostHideConfirm,
     onPostHideCancel,
+    postHideState,
   ] = useItemActionConfirm(hide);
   const [
     postToArchive,
     setPostToArchive,
     onPostArchiveConfirm,
     onPostArchiveCancel,
+    postArchiveState,
   ] = useItemActionConfirm(archive);
   const [
     postToAnnounce,
     setPostToAnnounce,
     onAnnounceConfirm,
     onAnnounceCancel,
+    announceState,
   ] = useItemActionConfirm(announceProposal);
   const [
     postToAccept,
     setPostToAccept,
     onAcceptConfirm,
     onAcceptCancel,
+    acceptState,
   ] = useItemActionConfirm(acceptProposal);
+  const [
+    postToEdit,
+    setPostToEdit,
+    onEditConfirm,
+    onEditCancel,
+    editState,
+  ] = useItemActionConfirm(edit, (item, newContent) => ({
+    post: item,
+    newContent,
+  }));
   const [
     postToReject,
     setPostToReject,
@@ -92,24 +104,6 @@ const PostsContainer = ({ className, showAddPostCta }) => {
     (state) => state.filters.flags === "archived"
   );
 
-  const [banningUser, banningUserError] = useActionState(ban, userToBan);
-  const [unbanningUser, unbanningUserError] = useActionState(
-    unban,
-    userToUnban
-  );
-  const [hidingPost, hidingPostError] = useActionState(hide, postToHide);
-  const [archivingPost, archivingPostError] = useActionState(
-    archive,
-    postToArchive
-  );
-  const [announcingProposal, announcingProposalError] = useActionState(
-    announceProposal,
-    postToAnnounce
-  );
-  const [acceptingProposal, acceptingProposalError] = useActionState(
-    acceptProposal,
-    postToAccept
-  );
   const [rejectingProposal, rejectingProposalError] = useActionState(
     rejectProposal,
     {
@@ -138,30 +132,6 @@ const PostsContainer = ({ className, showAddPostCta }) => {
 
   const { 2: loadResult } = loadPosts.useWatch();
 
-  const confirmEdit = useCallback(
-    async (newContent) => {
-      if (postToEdit && newContent) {
-        setConfirmingEdit(true);
-
-        const result = await edit.run({ post: postToEdit, newContent });
-
-        if (!result.error) {
-          setPostToEdit(null);
-          setEditError(null);
-        } else {
-          setEditError(result.message);
-        }
-
-        setConfirmingEdit(false);
-      }
-    },
-    [postToEdit, setPostToEdit, setConfirmingEdit]
-  );
-
-  const cancelEdit = useCallback(() => {
-    setPostToEdit(null);
-  }, [setPostToEdit]);
-
   /**
    * Ban a post's author.
    * @param {CF2021.Post} post
@@ -212,8 +182,8 @@ const PostsContainer = ({ className, showAddPostCta }) => {
         isOpen={!!userToBan}
         onConfirm={onBanUserConfirm}
         onCancel={onBanUserCancel}
-        confirming={banningUser}
-        error={banningUserError}
+        confirming={banUserState.loading}
+        error={banUserState.error}
         title={`Zablokovat uživatele ${userToBan ? userToBan.name : null}?`}
         yesActionLabel="Zablokovat"
       >
@@ -224,8 +194,8 @@ const PostsContainer = ({ className, showAddPostCta }) => {
         isOpen={!!userToUnban}
         onConfirm={onUnbanUserConfirm}
         onCancel={onUnbanUserCancel}
-        confirming={unbanningUser}
-        error={unbanningUserError}
+        confirming={unbanUserState.loading}
+        error={unbanUserState.error}
         title={`Odblokovat uživatele ${userToUnban ? userToUnban.name : null}?`}
         yesActionLabel="Odblokovat"
       >
@@ -237,8 +207,8 @@ const PostsContainer = ({ className, showAddPostCta }) => {
         isOpen={!!postToHide}
         onConfirm={onPostHideConfirm}
         onCancel={onPostHideCancel}
-        confirming={hidingPost}
-        error={hidingPostError}
+        confirming={postHideState.loading}
+        error={postHideState.error}
         title="Skrýt příspěvek?"
         yesActionLabel="Potvrdit"
       >
@@ -248,8 +218,8 @@ const PostsContainer = ({ className, showAddPostCta }) => {
         isOpen={!!postToArchive}
         onConfirm={onPostArchiveConfirm}
         onCancel={onPostArchiveCancel}
-        confirming={archivingPost}
-        error={archivingPostError}
+        confirming={postArchiveState.loading}
+        error={postArchiveState.error}
         title="Archivovat příspěvek?"
         yesActionLabel="Potvrdit"
       >
@@ -260,8 +230,8 @@ const PostsContainer = ({ className, showAddPostCta }) => {
         isOpen={!!postToAnnounce}
         onConfirm={onAnnounceConfirm}
         onCancel={onAnnounceCancel}
-        confirming={announcingProposal}
-        error={announcingProposalError}
+        confirming={announceState.loading}
+        error={announceState.errror}
         title="Vyhlásit procedurální návrh?"
         yesActionLabel="Vyhlásit návrh"
       >
@@ -271,8 +241,8 @@ const PostsContainer = ({ className, showAddPostCta }) => {
         isOpen={!!postToAccept}
         onConfirm={onAcceptConfirm}
         onCancel={onAcceptCancel}
-        confirming={acceptingProposal}
-        error={acceptingProposalError}
+        confirming={acceptState.loading}
+        error={acceptState.error}
         title="Schválit procedurální návrh?"
         yesActionLabel="Schválit návrh"
       >
@@ -364,10 +334,10 @@ const PostsContainer = ({ className, showAddPostCta }) => {
         <PostEditModal
           isOpen={true}
           post={postToEdit}
-          onConfirm={confirmEdit}
-          onCancel={cancelEdit}
-          confirming={confirmingEdit}
-          error={editError}
+          onConfirm={onEditConfirm}
+          onCancel={onEditCancel}
+          confirming={editState.loading}
+          error={editState.error}
         />
       )}
     </>
diff --git a/src/hooks.js b/src/hooks.js
index 648d410..fcb59e2 100644
--- a/src/hooks.js
+++ b/src/hooks.js
@@ -6,27 +6,34 @@ const baseActionParamsBuilder = (item, args) => {
 
 export const useItemActionConfirm = (actionFn, actionParamsBuilder = null) => {
   const [item, setItem] = useState(null);
+  const [actionArgs, setActionArgs] = useState(null);
 
   const onActionConfirm = useCallback(
     async (args) => {
       if (item) {
-        const result = await actionFn.run(
-          (actionParamsBuilder || baseActionParamsBuilder)(item, args)
+        const newActionArgs = (actionParamsBuilder || baseActionParamsBuilder)(
+          item,
+          args
         );
+        setActionArgs(newActionArgs);
+        const result = await actionFn.run(newActionArgs);
 
         if (!result.error) {
           setItem(null);
         }
       }
     },
-    [item, setItem, actionFn, actionParamsBuilder]
+    [item, setItem, actionFn, actionParamsBuilder, setActionArgs]
   );
 
   const onActionCancel = useCallback(() => {
     setItem(null);
   }, [setItem]);
 
-  return [item, setItem, onActionConfirm, onActionCancel];
+  const [loading, error] = useActionState(actionFn, actionArgs);
+  const unwrappedActionState = { loading, error };
+
+  return [item, setItem, onActionConfirm, onActionCancel, unwrappedActionState];
 };
 
 export const useActionConfirm = (actionFn, actionArgs) => {
diff --git a/src/ws/handlers/announcements.js b/src/ws/handlers/announcements.js
index 1519be0..460fcef 100644
--- a/src/ws/handlers/announcements.js
+++ b/src/ws/handlers/announcements.js
@@ -1,5 +1,6 @@
 import has from "lodash/has";
 
+import { markdownConverter } from "markdown";
 import { AnnouncementStore } from "stores";
 import { parseRawAnnouncement, syncAnnoucementItemIds } from "utils";
 
@@ -8,6 +9,9 @@ export const handleAnnouncementChanged = (payload) => {
     if (state.items[payload.id]) {
       if (has(payload, "content")) {
         state.items[payload.id].content = payload.content;
+        state.items[payload.id].contentHtml = markdownConverter.makeHtml(
+          payload.content
+        );
       }
       if (has(payload, "link")) {
         state.items[payload.id].link = payload.link;
-- 
GitLab