]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/modlog.tsx
component classes v2
[lemmy-ui.git] / src / shared / components / modlog.tsx
index 556d84a2bcc98e7cb6a660ace1ccd8ae10165324..4527800f3f943cbf32d2840b03b76e16b967eac5 100644 (file)
-import { None, Option, Some } from "@sniptt/monads";
+import { NoOptionI18nKeys } from "i18next";
 import { Component, linkEvent } from "inferno";
+import { T } from "inferno-i18next-dess";
 import { Link } from "inferno-router";
+import { RouteComponentProps } from "inferno-router/dist/Route";
 import {
   AdminPurgeCommentView,
   AdminPurgeCommunityView,
   AdminPurgePersonView,
   AdminPurgePostView,
-  CommunityModeratorView,
   GetCommunity,
   GetCommunityResponse,
   GetModlog,
   GetModlogResponse,
-  GetSiteResponse,
+  GetPersonDetails,
+  GetPersonDetailsResponse,
   ModAddCommunityView,
   ModAddView,
   ModBanFromCommunityView,
   ModBanView,
+  ModFeaturePostView,
   ModLockPostView,
-  ModlogActionType,
   ModRemoveCommentView,
   ModRemoveCommunityView,
   ModRemovePostView,
-  ModStickyPostView,
   ModTransferCommunityView,
-  PersonSafe,
-  toUndefined,
-  UserOperation,
-  wsJsonToRes,
-  wsUserOp,
+  ModlogActionType,
+  Person,
 } from "lemmy-js-client";
 import moment from "moment";
-import { Subscription } from "rxjs";
 import { i18n } from "../i18next";
 import { InitialFetchRequest } from "../interfaces";
-import { WebSocketService } from "../services";
+import { FirstLoadService } from "../services/FirstLoadService";
+import { HttpService, RequestState } from "../services/HttpService";
 import {
+  Choice,
+  QueryParams,
+  RouteDataResponse,
   amAdmin,
   amMod,
-  auth,
-  choicesConfig,
   debounce,
   fetchLimit,
   fetchUsers,
-  isBrowser,
+  getIdFromString,
+  getPageFromString,
+  getQueryParams,
+  getQueryString,
+  getUpdatedSearchId,
+  myAuth,
+  personToChoice,
   setIsoData,
-  toast,
-  wsClient,
-  wsSubscribe,
 } from "../utils";
 import { HtmlTags } from "./common/html-tags";
-import { Spinner } from "./common/icon";
+import { Icon, Spinner } from "./common/icon";
 import { MomentTime } from "./common/moment-time";
 import { Paginator } from "./common/paginator";
+import { SearchableSelect } from "./common/searchable-select";
 import { CommunityLink } from "./community/community-link";
 import { PersonListing } from "./person/person-listing";
-type ModlogType = {
+
+type FilterType = "mod" | "user";
+
+type View =
+  | ModRemovePostView
+  | ModLockPostView
+  | ModFeaturePostView
+  | ModRemoveCommentView
+  | ModRemoveCommunityView
+  | ModBanFromCommunityView
+  | ModBanView
+  | ModAddCommunityView
+  | ModTransferCommunityView
+  | ModAddView
+  | AdminPurgePersonView
+  | AdminPurgeCommunityView
+  | AdminPurgePostView
+  | AdminPurgeCommentView;
+
+type ModlogData = RouteDataResponse<{
+  res: GetModlogResponse;
+  communityRes: GetCommunityResponse;
+  modUserResponse: GetPersonDetailsResponse;
+  userResponse: GetPersonDetailsResponse;
+}>;
+
+interface ModlogType {
   id: number;
   type_: ModlogActionType;
-  moderator: Option<PersonSafe>;
-  view:
-    | ModRemovePostView
-    | ModLockPostView
-    | ModStickyPostView
-    | ModRemoveCommentView
-    | ModRemoveCommunityView
-    | ModBanFromCommunityView
-    | ModBanView
-    | ModAddCommunityView
-    | ModTransferCommunityView
-    | ModAddView
-    | AdminPurgePersonView
-    | AdminPurgeCommunityView
-    | AdminPurgePostView
-    | AdminPurgeCommentView;
+  moderator?: Person;
+  view: View;
   when_: string;
-};
-var Choices: any;
-if (isBrowser()) {
-  Choices = require("choices.js");
 }
 
+const getModlogQueryParams = () =>
+  getQueryParams<ModlogProps>({
+    actionType: getActionFromString,
+    modId: getIdFromString,
+    userId: getIdFromString,
+    page: getPageFromString,
+  });
+
 interface ModlogState {
-  res: Option<GetModlogResponse>;
-  communityId: Option<number>;
-  communityMods: Option<CommunityModeratorView[]>;
-  communityName: Option<string>;
-  page: number;
-  siteRes: GetSiteResponse;
-  loading: boolean;
-  filter_action: ModlogActionType;
-  filter_user: Option<number>;
-  filter_mod: Option<number>;
+  res: RequestState<GetModlogResponse>;
+  communityRes: RequestState<GetCommunityResponse>;
+  loadingModSearch: boolean;
+  loadingUserSearch: boolean;
+  modSearchOptions: Choice[];
+  userSearchOptions: Choice[];
 }
 
-export class Modlog extends Component<any, ModlogState> {
-  private isoData = setIsoData(
-    this.context,
-    GetModlogResponse,
-    GetCommunityResponse
-  );
-  private subscription: Subscription;
-  private userChoices: any;
-  private modChoices: any;
-  private emptyState: ModlogState = {
-    res: None,
-    communityId: None,
-    communityMods: None,
-    communityName: None,
-    page: 1,
-    loading: true,
-    siteRes: this.isoData.site_res,
-    filter_action: ModlogActionType.All,
-    filter_user: None,
-    filter_mod: None,
-  };
+interface ModlogProps {
+  page: number;
+  userId?: number | null;
+  modId?: number | null;
+  actionType: ModlogActionType;
+}
 
-  constructor(props: any, context: any) {
-    super(props, context);
-    this.state = this.emptyState;
-    this.handlePageChange = this.handlePageChange.bind(this);
+function getActionFromString(action?: string): ModlogActionType {
+  return action !== undefined ? (action as ModlogActionType) : "All";
+}
 
-    this.parseMessage = this.parseMessage.bind(this);
-    this.subscription = wsSubscribe(this.parseMessage);
+const getModlogActionMapper =
+  (
+    actionType: ModlogActionType,
+    getAction: (view: View) => { id: number; when_: string }
+  ) =>
+  (view: View & { moderator?: Person; admin?: Person }): ModlogType => {
+    const { id, when_ } = getAction(view);
 
-    this.state = {
-      ...this.state,
-      communityId: this.props.match.params.community_id
-        ? Some(Number(this.props.match.params.community_id))
-        : None,
+    return {
+      id,
+      type_: actionType,
+      view,
+      when_,
+      moderator: view.moderator ?? view.admin,
     };
+  };
 
-    // Only fetch the data if coming from another route
-    if (this.isoData.path == this.context.router.route.match.url) {
-      this.state = {
-        ...this.state,
-        res: Some(this.isoData.routeData[0] as GetModlogResponse),
-      };
+function buildCombined({
+  removed_comments,
+  locked_posts,
+  featured_posts,
+  removed_communities,
+  removed_posts,
+  added,
+  added_to_community,
+  admin_purged_comments,
+  admin_purged_communities,
+  admin_purged_persons,
+  admin_purged_posts,
+  banned,
+  banned_from_community,
+  transferred_to_community,
+}: GetModlogResponse): ModlogType[] {
+  const combined = removed_posts
+    .map(
+      getModlogActionMapper(
+        "ModRemovePost",
+        ({ mod_remove_post }: ModRemovePostView) => mod_remove_post
+      )
+    )
+    .concat(
+      locked_posts.map(
+        getModlogActionMapper(
+          "ModLockPost",
+          ({ mod_lock_post }: ModLockPostView) => mod_lock_post
+        )
+      )
+    )
+    .concat(
+      featured_posts.map(
+        getModlogActionMapper(
+          "ModFeaturePost",
+          ({ mod_feature_post }: ModFeaturePostView) => mod_feature_post
+        )
+      )
+    )
+    .concat(
+      removed_comments.map(
+        getModlogActionMapper(
+          "ModRemoveComment",
+          ({ mod_remove_comment }: ModRemoveCommentView) => mod_remove_comment
+        )
+      )
+    )
+    .concat(
+      removed_communities.map(
+        getModlogActionMapper(
+          "ModRemoveCommunity",
+          ({ mod_remove_community }: ModRemoveCommunityView) =>
+            mod_remove_community
+        )
+      )
+    )
+    .concat(
+      banned_from_community.map(
+        getModlogActionMapper(
+          "ModBanFromCommunity",
+          ({ mod_ban_from_community }: ModBanFromCommunityView) =>
+            mod_ban_from_community
+        )
+      )
+    )
+    .concat(
+      added_to_community.map(
+        getModlogActionMapper(
+          "ModAddCommunity",
+          ({ mod_add_community }: ModAddCommunityView) => mod_add_community
+        )
+      )
+    )
+    .concat(
+      transferred_to_community.map(
+        getModlogActionMapper(
+          "ModTransferCommunity",
+          ({ mod_transfer_community }: ModTransferCommunityView) =>
+            mod_transfer_community
+        )
+      )
+    )
+    .concat(
+      added.map(
+        getModlogActionMapper("ModAdd", ({ mod_add }: ModAddView) => mod_add)
+      )
+    )
+    .concat(
+      banned.map(
+        getModlogActionMapper("ModBan", ({ mod_ban }: ModBanView) => mod_ban)
+      )
+    )
+    .concat(
+      admin_purged_persons.map(
+        getModlogActionMapper(
+          "AdminPurgePerson",
+          ({ admin_purge_person }: AdminPurgePersonView) => admin_purge_person
+        )
+      )
+    )
+    .concat(
+      admin_purged_communities.map(
+        getModlogActionMapper(
+          "AdminPurgeCommunity",
+          ({ admin_purge_community }: AdminPurgeCommunityView) =>
+            admin_purge_community
+        )
+      )
+    )
+    .concat(
+      admin_purged_posts.map(
+        getModlogActionMapper(
+          "AdminPurgePost",
+          ({ admin_purge_post }: AdminPurgePostView) => admin_purge_post
+        )
+      )
+    )
+    .concat(
+      admin_purged_comments.map(
+        getModlogActionMapper(
+          "AdminPurgeComment",
+          ({ admin_purge_comment }: AdminPurgeCommentView) =>
+            admin_purge_comment
+        )
+      )
+    );
 
-      if (this.isoData.routeData[1]) {
-        // Getting the moderators
-        let communityRes = Some(
-          this.isoData.routeData[1] as GetCommunityResponse
-        );
-        this.state = {
-          ...this.state,
-          communityMods: communityRes.map(c => c.moderators),
-        };
-      }
+  // Sort them by time
+  combined.sort((a, b) => b.when_.localeCompare(a.when_));
 
-      this.state = { ...this.state, loading: false };
-    } else {
-      this.refetch();
-    }
-  }
+  return combined;
+}
 
-  componentDidMount() {
-    this.setupUserFilter();
-    this.setupModFilter();
-  }
+function renderModlogType({ type_, view }: ModlogType) {
+  switch (type_) {
+    case "ModRemovePost": {
+      const mrpv = view as ModRemovePostView;
+      const {
+        mod_remove_post: { reason, removed },
+        post: { name, id },
+      } = mrpv;
 
-  componentWillUnmount() {
-    if (isBrowser()) {
-      this.subscription.unsubscribe();
+      return (
+        <>
+          <span>{removed ? "Removed " : "Restored "}</span>
+          <span>
+            Post <Link to={`/post/${id}`}>{name}</Link>
+          </span>
+          {reason && (
+            <span>
+              <div>reason: {reason}</div>
+            </span>
+          )}
+        </>
+      );
     }
-  }
 
-  buildCombined(res: GetModlogResponse): ModlogType[] {
-    let removed_posts: ModlogType[] = res.removed_posts.map(r => ({
-      id: r.mod_remove_post.id,
-      type_: ModlogActionType.ModRemovePost,
-      view: r,
-      moderator: r.moderator,
-      when_: r.mod_remove_post.when_,
-    }));
-
-    let locked_posts: ModlogType[] = res.locked_posts.map(r => ({
-      id: r.mod_lock_post.id,
-      type_: ModlogActionType.ModLockPost,
-      view: r,
-      moderator: r.moderator,
-      when_: r.mod_lock_post.when_,
-    }));
-
-    let stickied_posts: ModlogType[] = res.stickied_posts.map(r => ({
-      id: r.mod_sticky_post.id,
-      type_: ModlogActionType.ModStickyPost,
-      view: r,
-      moderator: r.moderator,
-      when_: r.mod_sticky_post.when_,
-    }));
-
-    let removed_comments: ModlogType[] = res.removed_comments.map(r => ({
-      id: r.mod_remove_comment.id,
-      type_: ModlogActionType.ModRemoveComment,
-      view: r,
-      moderator: r.moderator,
-      when_: r.mod_remove_comment.when_,
-    }));
-
-    let removed_communities: ModlogType[] = res.removed_communities.map(r => ({
-      id: r.mod_remove_community.id,
-      type_: ModlogActionType.ModRemoveCommunity,
-      view: r,
-      moderator: r.moderator,
-      when_: r.mod_remove_community.when_,
-    }));
-
-    let banned_from_community: ModlogType[] = res.banned_from_community.map(
-      r => ({
-        id: r.mod_ban_from_community.id,
-        type_: ModlogActionType.ModBanFromCommunity,
-        view: r,
-        moderator: r.moderator,
-        when_: r.mod_ban_from_community.when_,
-      })
-    );
+    case "ModLockPost": {
+      const {
+        mod_lock_post: { locked },
+        post: { id, name },
+      } = view as ModLockPostView;
 
-    let added_to_community: ModlogType[] = res.added_to_community.map(r => ({
-      id: r.mod_add_community.id,
-      type_: ModlogActionType.ModAddCommunity,
-      view: r,
-      moderator: r.moderator,
-      when_: r.mod_add_community.when_,
-    }));
-
-    let transferred_to_community: ModlogType[] =
-      res.transferred_to_community.map(r => ({
-        id: r.mod_transfer_community.id,
-        type_: ModlogActionType.ModTransferCommunity,
-        view: r,
-        moderator: r.moderator,
-        when_: r.mod_transfer_community.when_,
-      }));
-
-    let added: ModlogType[] = res.added.map(r => ({
-      id: r.mod_add.id,
-      type_: ModlogActionType.ModAdd,
-      view: r,
-      moderator: r.moderator,
-      when_: r.mod_add.when_,
-    }));
-
-    let banned: ModlogType[] = res.banned.map(r => ({
-      id: r.mod_ban.id,
-      type_: ModlogActionType.ModBan,
-      view: r,
-      moderator: r.moderator,
-      when_: r.mod_ban.when_,
-    }));
-
-    let purged_persons: ModlogType[] = res.admin_purged_persons.map(r => ({
-      id: r.admin_purge_person.id,
-      type_: ModlogActionType.AdminPurgePerson,
-      view: r,
-      moderator: r.admin,
-      when_: r.admin_purge_person.when_,
-    }));
-
-    let purged_communities: ModlogType[] = res.admin_purged_communities.map(
-      r => ({
-        id: r.admin_purge_community.id,
-        type_: ModlogActionType.AdminPurgeCommunity,
-        view: r,
-        moderator: r.admin,
-        when_: r.admin_purge_community.when_,
-      })
-    );
+      return (
+        <>
+          <span>{locked ? "Locked " : "Unlocked "}</span>
+          <span>
+            Post <Link to={`/post/${id}`}>{name}</Link>
+          </span>
+        </>
+      );
+    }
 
-    let purged_posts: ModlogType[] = res.admin_purged_posts.map(r => ({
-      id: r.admin_purge_post.id,
-      type_: ModlogActionType.AdminPurgePost,
-      view: r,
-      moderator: r.admin,
-      when_: r.admin_purge_post.when_,
-    }));
-
-    let purged_comments: ModlogType[] = res.admin_purged_comments.map(r => ({
-      id: r.admin_purge_comment.id,
-      type_: ModlogActionType.AdminPurgeComment,
-      view: r,
-      moderator: r.admin,
-      when_: r.admin_purge_comment.when_,
-    }));
-
-    let combined: ModlogType[] = [];
-
-    combined.push(...removed_posts);
-    combined.push(...locked_posts);
-    combined.push(...stickied_posts);
-    combined.push(...removed_comments);
-    combined.push(...removed_communities);
-    combined.push(...banned_from_community);
-    combined.push(...added_to_community);
-    combined.push(...transferred_to_community);
-    combined.push(...added);
-    combined.push(...banned);
-    combined.push(...purged_persons);
-    combined.push(...purged_communities);
-    combined.push(...purged_posts);
-    combined.push(...purged_comments);
-
-    // Sort them by time
-    combined.sort((a, b) => b.when_.localeCompare(a.when_));
-
-    return combined;
-  }
+    case "ModFeaturePost": {
+      const {
+        mod_feature_post: { featured, is_featured_community },
+        post: { id, name },
+      } = view as ModFeaturePostView;
 
-  renderModlogType(i: ModlogType) {
-    switch (i.type_) {
-      case ModlogActionType.ModRemovePost: {
-        let mrpv = i.view as ModRemovePostView;
-        return (
-          <>
-            <span>
-              {mrpv.mod_remove_post.removed.unwrapOr(false)
-                ? "Removed "
-                : "Restored "}
-            </span>
-            <span>
-              Post <Link to={`/post/${mrpv.post.id}`}>{mrpv.post.name}</Link>
-            </span>
-            <span>
-              {mrpv.mod_remove_post.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
-            </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModLockPost: {
-        let mlpv = i.view as ModLockPostView;
-        return (
-          <>
-            <span>
-              {mlpv.mod_lock_post.locked.unwrapOr(false)
-                ? "Locked "
-                : "Unlocked "}
-            </span>
-            <span>
-              Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link>
-            </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModStickyPost: {
-        let mspv = i.view as ModStickyPostView;
-        return (
-          <>
-            <span>
-              {mspv.mod_sticky_post.stickied.unwrapOr(false)
-                ? "Stickied "
-                : "Unstickied "}
-            </span>
-            <span>
-              Post <Link to={`/post/${mspv.post.id}`}>{mspv.post.name}</Link>
-            </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModRemoveComment: {
-        let mrc = i.view as ModRemoveCommentView;
-        return (
-          <>
-            <span>
-              {mrc.mod_remove_comment.removed.unwrapOr(false)
-                ? "Removed "
-                : "Restored "}
-            </span>
-            <span>
-              Comment{" "}
-              <Link to={`/post/${mrc.post.id}/comment/${mrc.comment.id}`}>
-                {mrc.comment.content}
-              </Link>
-            </span>
-            <span>
-              {" "}
-              by <PersonListing person={mrc.commenter} />
-            </span>
-            <span>
-              {mrc.mod_remove_comment.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
-            </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModRemoveCommunity: {
-        let mrco = i.view as ModRemoveCommunityView;
-        return (
-          <>
-            <span>
-              {mrco.mod_remove_community.removed.unwrapOr(false)
-                ? "Removed "
-                : "Restored "}
-            </span>
-            <span>
-              Community <CommunityLink community={mrco.community} />
-            </span>
-            <span>
-              {mrco.mod_remove_community.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
-            </span>
-            <span>
-              {mrco.mod_remove_community.expires.match({
-                some: expires => (
-                  <div>expires: {moment.utc(expires).fromNow()}</div>
-                ),
-                none: <></>,
-              })}
-            </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModBanFromCommunity: {
-        let mbfc = i.view as ModBanFromCommunityView;
-        return (
-          <>
-            <span>
-              {mbfc.mod_ban_from_community.banned.unwrapOr(false)
-                ? "Banned "
-                : "Unbanned "}{" "}
-            </span>
-            <span>
-              <PersonListing person={mbfc.banned_person} />
-            </span>
-            <span> from the community </span>
-            <span>
-              <CommunityLink community={mbfc.community} />
-            </span>
-            <span>
-              {mbfc.mod_ban_from_community.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
-            </span>
-            <span>
-              {mbfc.mod_ban_from_community.expires.match({
-                some: expires => (
-                  <div>expires: {moment.utc(expires).fromNow()}</div>
-                ),
-                none: <></>,
-              })}
-            </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModAddCommunity: {
-        let mac = i.view as ModAddCommunityView;
-        return (
-          <>
-            <span>
-              {mac.mod_add_community.removed.unwrapOr(false)
-                ? "Removed "
-                : "Appointed "}{" "}
-            </span>
-            <span>
-              <PersonListing person={mac.modded_person} />
-            </span>
-            <span> as a mod to the community </span>
-            <span>
-              <CommunityLink community={mac.community} />
-            </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModTransferCommunity: {
-        let mtc = i.view as ModTransferCommunityView;
-        return (
-          <>
+      return (
+        <>
+          <span>{featured ? "Featured " : "Unfeatured "}</span>
+          <span>
+            Post <Link to={`/post/${id}`}>{name}</Link>
+          </span>
+          <span>{is_featured_community ? " In Community" : " In Local"}</span>
+        </>
+      );
+    }
+    case "ModRemoveComment": {
+      const mrc = view as ModRemoveCommentView;
+      const {
+        mod_remove_comment: { reason, removed },
+        comment: { id, content },
+        commenter,
+      } = mrc;
+
+      return (
+        <>
+          <span>{removed ? "Removed " : "Restored "}</span>
+          <span>
+            Comment <Link to={`/comment/${id}`}>{content}</Link>
+          </span>
+          <span>
+            {" "}
+            by <PersonListing person={commenter} />
+          </span>
+          {reason && (
             <span>
-              {mtc.mod_transfer_community.removed.unwrapOr(false)
-                ? "Removed "
-                : "Transferred "}{" "}
+              <div>reason: {reason}</div>
             </span>
+          )}
+        </>
+      );
+    }
+
+    case "ModRemoveCommunity": {
+      const mrco = view as ModRemoveCommunityView;
+      const {
+        mod_remove_community: { reason, expires, removed },
+        community,
+      } = mrco;
+
+      return (
+        <>
+          <span>{removed ? "Removed " : "Restored "}</span>
+          <span>
+            Community <CommunityLink community={community} />
+          </span>
+          {reason && (
             <span>
-              <CommunityLink community={mtc.community} />
+              <div>reason: {reason}</div>
             </span>
-            <span> to </span>
+          )}
+          {expires && (
             <span>
-              <PersonListing person={mtc.modded_person} />
-            </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModBan: {
-        let mb = i.view as ModBanView;
-        return (
-          <>
-            <span>
-              {mb.mod_ban.banned.unwrapOr(false) ? "Banned " : "Unbanned "}{" "}
+              <div>expires: {moment.utc(expires).fromNow()}</div>
             </span>
+          )}
+        </>
+      );
+    }
+
+    case "ModBanFromCommunity": {
+      const mbfc = view as ModBanFromCommunityView;
+      const {
+        mod_ban_from_community: { reason, expires, banned },
+        banned_person,
+        community,
+      } = mbfc;
+
+      return (
+        <>
+          <span>{banned ? "Banned " : "Unbanned "}</span>
+          <span>
+            <PersonListing person={banned_person} />
+          </span>
+          <span> from the community </span>
+          <span>
+            <CommunityLink community={community} />
+          </span>
+          {reason && (
             <span>
-              <PersonListing person={mb.banned_person} />
+              <div>reason: {reason}</div>
             </span>
+          )}
+          {expires && (
             <span>
-              {mb.mod_ban.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
+              <div>expires: {moment.utc(expires).fromNow()}</div>
             </span>
+          )}
+        </>
+      );
+    }
+
+    case "ModAddCommunity": {
+      const {
+        mod_add_community: { removed },
+        modded_person,
+        community,
+      } = view as ModAddCommunityView;
+
+      return (
+        <>
+          <span>{removed ? "Removed " : "Appointed "}</span>
+          <span>
+            <PersonListing person={modded_person} />
+          </span>
+          <span> as a mod to the community </span>
+          <span>
+            <CommunityLink community={community} />
+          </span>
+        </>
+      );
+    }
+
+    case "ModTransferCommunity": {
+      const { community, modded_person } = view as ModTransferCommunityView;
+
+      return (
+        <>
+          <span>Transferred</span>
+          <span>
+            <CommunityLink community={community} />
+          </span>
+          <span> to </span>
+          <span>
+            <PersonListing person={modded_person} />
+          </span>
+        </>
+      );
+    }
+
+    case "ModBan": {
+      const {
+        mod_ban: { reason, expires, banned },
+        banned_person,
+      } = view as ModBanView;
+
+      return (
+        <>
+          <span>{banned ? "Banned " : "Unbanned "}</span>
+          <span>
+            <PersonListing person={banned_person} />
+          </span>
+          {reason && (
             <span>
-              {mb.mod_ban.expires.match({
-                some: expires => (
-                  <div>expires: {moment.utc(expires).fromNow()}</div>
-                ),
-                none: <></>,
-              })}
+              <div>reason: {reason}</div>
             </span>
-          </>
-        );
-      }
-      case ModlogActionType.ModAdd: {
-        let ma = i.view as ModAddView;
-        return (
-          <>
+          )}
+          {expires && (
             <span>
-              {ma.mod_add.removed.unwrapOr(false) ? "Removed " : "Appointed "}{" "}
+              <div>expires: {moment.utc(expires).fromNow()}</div>
             </span>
+          )}
+        </>
+      );
+    }
+
+    case "ModAdd": {
+      const {
+        mod_add: { removed },
+        modded_person,
+      } = view as ModAddView;
+
+      return (
+        <>
+          <span>{removed ? "Removed " : "Appointed "}</span>
+          <span>
+            <PersonListing person={modded_person} />
+          </span>
+          <span> as an admin </span>
+        </>
+      );
+    }
+    case "AdminPurgePerson": {
+      const {
+        admin_purge_person: { reason },
+      } = view as AdminPurgePersonView;
+
+      return (
+        <>
+          <span>Purged a Person</span>
+          {reason && (
             <span>
-              <PersonListing person={ma.modded_person} />
+              <div>reason: {reason}</div>
             </span>
-            <span> as an admin </span>
-          </>
-        );
-      }
-      case ModlogActionType.AdminPurgePerson: {
-        let ap = i.view as AdminPurgePersonView;
-        return (
-          <>
-            <span>Purged a Person</span>
+          )}
+        </>
+      );
+    }
+
+    case "AdminPurgeCommunity": {
+      const {
+        admin_purge_community: { reason },
+      } = view as AdminPurgeCommunityView;
+
+      return (
+        <>
+          <span>Purged a Community</span>
+          {reason && (
             <span>
-              {ap.admin_purge_person.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
+              <div>reason: {reason}</div>
             </span>
-          </>
-        );
-      }
-      case ModlogActionType.AdminPurgeCommunity: {
-        let ap = i.view as AdminPurgeCommunityView;
-        return (
-          <>
-            <span>Purged a Community</span>
+          )}
+        </>
+      );
+    }
+
+    case "AdminPurgePost": {
+      const {
+        admin_purge_post: { reason },
+        community,
+      } = view as AdminPurgePostView;
+
+      return (
+        <>
+          <span>Purged a Post from from </span>
+          <CommunityLink community={community} />
+          {reason && (
             <span>
-              {ap.admin_purge_community.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
+              <div>reason: {reason}</div>
             </span>
-          </>
-        );
-      }
-      case ModlogActionType.AdminPurgePost: {
-        let ap = i.view as AdminPurgePostView;
-        return (
-          <>
-            <span>Purged a Post from from </span>
-            <CommunityLink community={ap.community} />
+          )}
+        </>
+      );
+    }
+
+    case "AdminPurgeComment": {
+      const {
+        admin_purge_comment: { reason },
+        post: { id, name },
+      } = view as AdminPurgeCommentView;
+
+      return (
+        <>
+          <span>
+            Purged a Comment from <Link to={`/post/${id}`}>{name}</Link>
+          </span>
+          {reason && (
             <span>
-              {ap.admin_purge_post.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
+              <div>reason: {reason}</div>
             </span>
-          </>
-        );
+          )}
+        </>
+      );
+    }
+
+    default:
+      return <></>;
+  }
+}
+
+const Filter = ({
+  filterType,
+  onChange,
+  value,
+  onSearch,
+  options,
+  loading,
+}: {
+  filterType: FilterType;
+  onChange: (option: Choice) => void;
+  value?: number | null;
+  onSearch: (text: string) => void;
+  options: Choice[];
+  loading: boolean;
+}) => (
+  <div className="col-sm-6 mb-3">
+    <label className="mb-2" htmlFor={`filter-${filterType}`}>
+      {i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
+    </label>
+    <SearchableSelect
+      id={`filter-${filterType}`}
+      value={value ?? 0}
+      options={[
+        {
+          label: i18n.t("all"),
+          value: "0",
+        },
+      ].concat(options)}
+      onChange={onChange}
+      onSearch={onSearch}
+      loading={loading}
+    />
+  </div>
+);
+
+async function createNewOptions({
+  id,
+  oldOptions,
+  text,
+}: {
+  id?: number | null;
+  oldOptions: Choice[];
+  text: string;
+}) {
+  const newOptions: Choice[] = [];
+
+  if (id) {
+    const selectedUser = oldOptions.find(
+      ({ value }) => value === id.toString()
+    );
+
+    if (selectedUser) {
+      newOptions.push(selectedUser);
+    }
+  }
+
+  if (text.length > 0) {
+    newOptions.push(
+      ...(await fetchUsers(text))
+        .slice(0, Number(fetchLimit))
+        .map<Choice>(personToChoice)
+    );
+  }
+
+  return newOptions;
+}
+
+export class Modlog extends Component<
+  RouteComponentProps<{ communityId?: string }>,
+  ModlogState
+> {
+  private isoData = setIsoData<ModlogData>(this.context);
+
+  state: ModlogState = {
+    res: { state: "empty" },
+    communityRes: { state: "empty" },
+    loadingModSearch: false,
+    loadingUserSearch: false,
+    userSearchOptions: [],
+    modSearchOptions: [],
+  };
+
+  constructor(
+    props: RouteComponentProps<{ communityId?: string }>,
+    context: any
+  ) {
+    super(props, context);
+    this.handlePageChange = this.handlePageChange.bind(this);
+    this.handleUserChange = this.handleUserChange.bind(this);
+    this.handleModChange = this.handleModChange.bind(this);
+
+    // Only fetch the data if coming from another route
+    if (FirstLoadService.isFirstLoad) {
+      const { res, communityRes, modUserResponse, userResponse } =
+        this.isoData.routeData;
+
+      this.state = {
+        ...this.state,
+        res,
+        communityRes,
+      };
+
+      if (modUserResponse.state === "success") {
+        this.state = {
+          ...this.state,
+          modSearchOptions: [personToChoice(modUserResponse.data.person_view)],
+        };
       }
-      case ModlogActionType.AdminPurgeComment: {
-        let ap = i.view as AdminPurgeCommentView;
-        return (
-          <>
-            <span>
-              Purged a Comment from{" "}
-              <Link to={`/post/${ap.post.id}`}>{ap.post.name}</Link>
-            </span>
-            <span>
-              {ap.admin_purge_comment.reason.match({
-                some: reason => <div>reason: {reason}</div>,
-                none: <></>,
-              })}
-            </span>
-          </>
-        );
+
+      if (userResponse.state === "success") {
+        this.state = {
+          ...this.state,
+          userSearchOptions: [personToChoice(userResponse.data.person_view)],
+        };
       }
-      default:
-        return <div />;
     }
   }
 
-  combined() {
-    let combined = this.state.res.map(this.buildCombined).unwrapOr([]);
+  get combined() {
+    const res = this.state.res;
+    const combined = res.state == "success" ? buildCombined(res.data) : [];
 
     return (
       <tbody>
         {combined.map(i => (
           <tr key={i.id}>
             <td>
-              <MomentTime published={i.when_} updated={None} />
+              <MomentTime published={i.when_} />
             </td>
             <td>
-              {this.amAdminOrMod ? (
-                <PersonListing person={i.moderator.unwrap()} />
+              {this.amAdminOrMod && i.moderator ? (
+                <PersonListing person={i.moderator} />
               ) : (
                 <div>{this.modOrAdminText(i.moderator)}</div>
               )}
             </td>
-            <td>{this.renderModlogType(i)}</td>
+            <td>{renderModlogType(i)}</td>
           </tr>
         ))}
       </tbody>
@@ -618,307 +713,335 @@ export class Modlog extends Component<any, ModlogState> {
   }
 
   get amAdminOrMod(): boolean {
-    return (
-      amAdmin(Some(this.state.siteRes.admins)) ||
-      amMod(this.state.communityMods)
-    );
+    const amMod_ =
+      this.state.communityRes.state == "success" &&
+      amMod(this.state.communityRes.data.moderators);
+    return amAdmin() || amMod_;
   }
 
-  modOrAdminText(person: Option<PersonSafe>): string {
-    return person.match({
-      some: res =>
-        this.isoData.site_res.admins.map(a => a.person.id).includes(res.id)
-          ? i18n.t("admin")
-          : i18n.t("mod"),
-      none: i18n.t("mod"),
-    });
+  modOrAdminText(person?: Person): string {
+    return person &&
+      this.isoData.site_res.admins.some(
+        ({ person: { id } }) => id === person.id
+      )
+      ? i18n.t("admin")
+      : i18n.t("mod");
   }
 
   get documentTitle(): string {
-    return this.state.siteRes.site_view.match({
-      some: siteView => `Modlog - ${siteView.site.name}`,
-      none: "",
-    });
+    return `Modlog - ${this.isoData.site_res.site_view.site.name}`;
   }
 
   render() {
+    const {
+      loadingModSearch,
+      loadingUserSearch,
+      userSearchOptions,
+      modSearchOptions,
+    } = this.state;
+    const { actionType, modId, userId } = getModlogQueryParams();
+
     return (
-      <div className="container">
+      <div className="modlog container-lg">
         <HtmlTags
           title={this.documentTitle}
           path={this.context.router.route.match.url}
-          description={None}
-          image={None}
         />
-        {this.state.loading ? (
-          <h5>
-            <Spinner large />
-          </h5>
-        ) : (
-          <div>
+
+        <div>
+          <div
+            className="alert alert-warning text-sm-start text-xs-center"
+            role="alert"
+          >
+            <Icon
+              icon="alert-triangle"
+              inline
+              classes="me-sm-2 mx-auto d-sm-inline d-block"
+            />
+            <T i18nKey="modlog_content_warning" class="d-inline">
+              #<strong>#</strong>#
+            </T>
+          </div>
+          {this.state.communityRes.state === "success" && (
             <h5>
-              {this.state.communityName.match({
-                some: name => (
-                  <Link className="text-body" to={`/c/${name}`}>
-                    /c/{name}{" "}
-                  </Link>
-                ),
-                none: <></>,
-              })}
+              <Link
+                className="text-body"
+                to={`/c/${this.state.communityRes.data.community_view.community.name}`}
+              >
+                /c/{this.state.communityRes.data.community_view.community.name}{" "}
+              </Link>
               <span>{i18n.t("modlog")}</span>
             </h5>
-            <div className="form-row">
-              <div className="form-group col-sm-6">
-                <select
-                  value={this.state.filter_action}
-                  onChange={linkEvent(this, this.handleFilterActionChange)}
-                  className="custom-select mb-2"
-                  aria-label="action"
-                >
-                  <option disabled aria-hidden="true">
-                    {i18n.t("filter_by_action")}
-                  </option>
-                  <option value={ModlogActionType.All}>{i18n.t("all")}</option>
-                  <option value={ModlogActionType.ModRemovePost}>
-                    Removing Posts
-                  </option>
-                  <option value={ModlogActionType.ModLockPost}>
-                    Locking Posts
-                  </option>
-                  <option value={ModlogActionType.ModStickyPost}>
-                    Stickying Posts
-                  </option>
-                  <option value={ModlogActionType.ModRemoveComment}>
-                    Removing Comments
-                  </option>
-                  <option value={ModlogActionType.ModRemoveCommunity}>
-                    Removing Communities
-                  </option>
-                  <option value={ModlogActionType.ModBanFromCommunity}>
-                    Banning From Communities
-                  </option>
-                  <option value={ModlogActionType.ModAddCommunity}>
-                    Adding Mod to Community
-                  </option>
-                  <option value={ModlogActionType.ModTransferCommunity}>
-                    Transfering Communities
-                  </option>
-                  <option value={ModlogActionType.ModAdd}>
-                    Adding Mod to Site
-                  </option>
-                  <option value={ModlogActionType.ModBan}>
-                    Banning From Site
-                  </option>
-                </select>
-              </div>
-              {this.state.siteRes.site_view.match({
-                some: site_view =>
-                  !site_view.site.hide_modlog_mod_names.unwrapOr(false) && (
-                    <div className="form-group col-sm-6">
-                      <select
-                        id="filter-mod"
-                        className="form-control"
-                        value={toUndefined(this.state.filter_mod)}
-                      >
-                        <option>{i18n.t("filter_by_mod")}</option>
-                      </select>
-                    </div>
-                  ),
-                none: <></>,
-              })}
-              <div className="form-group col-sm-6">
-                <select
-                  id="filter-user"
-                  className="form-control"
-                  value={toUndefined(this.state.filter_user)}
-                >
-                  <option>{i18n.t("filter_by_user")}</option>
-                </select>
-              </div>
+          )}
+          <div className="row mb-2">
+            <div className="col-sm-6">
+              <select
+                value={actionType}
+                onChange={linkEvent(this, this.handleFilterActionChange)}
+                className="form-select"
+                aria-label="action"
+              >
+                <option disabled aria-hidden="true">
+                  {i18n.t("filter_by_action")}
+                </option>
+                <option value={"All"}>{i18n.t("all")}</option>
+                <option value={"ModRemovePost"}>Removing Posts</option>
+                <option value={"ModLockPost"}>Locking Posts</option>
+                <option value={"ModFeaturePost"}>Featuring Posts</option>
+                <option value={"ModRemoveComment"}>Removing Comments</option>
+                <option value={"ModRemoveCommunity"}>
+                  Removing Communities
+                </option>
+                <option value={"ModBanFromCommunity"}>
+                  Banning From Communities
+                </option>
+                <option value={"ModAddCommunity"}>
+                  Adding Mod to Community
+                </option>
+                <option value={"ModTransferCommunity"}>
+                  Transferring Communities
+                </option>
+                <option value={"ModAdd"}>Adding Mod to Site</option>
+                <option value={"ModBan"}>Banning From Site</option>
+              </select>
             </div>
-            <div className="table-responsive">
-              <table id="modlog_table" className="table table-sm table-hover">
-                <thead className="pointer">
-                  <tr>
-                    <th> {i18n.t("time")}</th>
-                    <th>{i18n.t("mod")}</th>
-                    <th>{i18n.t("action")}</th>
-                  </tr>
-                </thead>
-                {this.combined()}
-              </table>
-              <Paginator
-                page={this.state.page}
-                onChange={this.handlePageChange}
+          </div>
+          <div className="row mb-2">
+            <Filter
+              filterType="user"
+              onChange={this.handleUserChange}
+              onSearch={this.handleSearchUsers}
+              value={userId}
+              options={userSearchOptions}
+              loading={loadingUserSearch}
+            />
+            {!this.isoData.site_res.site_view.local_site
+              .hide_modlog_mod_names && (
+              <Filter
+                filterType="mod"
+                onChange={this.handleModChange}
+                onSearch={this.handleSearchMods}
+                value={modId}
+                options={modSearchOptions}
+                loading={loadingModSearch}
               />
-            </div>
+            )}
           </div>
-        )}
+          {this.renderModlogTable()}
+        </div>
       </div>
     );
   }
 
+  renderModlogTable() {
+    switch (this.state.res.state) {
+      case "loading":
+        return (
+          <h5>
+            <Spinner large />
+          </h5>
+        );
+      case "success": {
+        const page = getModlogQueryParams().page;
+        return (
+          <div className="table-responsive">
+            <table id="modlog_table" className="table table-sm table-hover">
+              <thead className="pointer">
+                <tr>
+                  <th> {i18n.t("time")}</th>
+                  <th>{i18n.t("mod")}</th>
+                  <th>{i18n.t("action")}</th>
+                </tr>
+              </thead>
+              {this.combined}
+            </table>
+            <Paginator page={page} onChange={this.handlePageChange} />
+          </div>
+        );
+      }
+    }
+  }
+
   handleFilterActionChange(i: Modlog, event: any) {
-    i.setState({ filter_action: event.target.value });
-    i.refetch();
+    i.updateUrl({
+      actionType: event.target.value as ModlogActionType,
+      page: 1,
+    });
   }
 
-  handlePageChange(val: number) {
-    this.setState({ page: val });
-    this.refetch();
+  handlePageChange(page: number) {
+    this.updateUrl({ page });
   }
 
-  refetch() {
-    let modlogForm = new GetModlog({
-      community_id: this.state.communityId,
-      page: Some(this.state.page),
-      limit: Some(fetchLimit),
-      auth: auth(false).ok(),
-      type_: this.state.filter_action,
-      other_person_id: this.state.filter_user,
-      mod_person_id: this.state.filter_mod,
+  handleUserChange(option: Choice) {
+    this.updateUrl({ userId: getIdFromString(option.value) ?? null, page: 1 });
+  }
+
+  handleModChange(option: Choice) {
+    this.updateUrl({ modId: getIdFromString(option.value) ?? null, page: 1 });
+  }
+
+  handleSearchUsers = debounce(async (text: string) => {
+    const { userId } = getModlogQueryParams();
+    const { userSearchOptions } = this.state;
+    this.setState({ loadingUserSearch: true });
+
+    const newOptions = await createNewOptions({
+      id: userId,
+      text,
+      oldOptions: userSearchOptions,
     });
-    WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
-
-    this.state.communityId.match({
-      some: id => {
-        let communityForm = new GetCommunity({
-          id: Some(id),
-          name: None,
-          auth: auth(false).ok(),
-        });
-        WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
-      },
-      none: void 0,
+
+    this.setState({
+      userSearchOptions: newOptions,
+      loadingUserSearch: false,
     });
-  }
+  });
 
-  setupUserFilter() {
-    if (isBrowser()) {
-      let selectId: any = document.getElementById("filter-user");
-      if (selectId) {
-        this.userChoices = new Choices(selectId, choicesConfig);
-        this.userChoices.passedElement.element.addEventListener(
-          "choice",
-          (e: any) => {
-            this.setState({ filter_user: Some(Number(e.detail.choice.value)) });
-            this.refetch();
-          },
-          false
-        );
-        this.userChoices.passedElement.element.addEventListener(
-          "search",
-          debounce(async (e: any) => {
-            try {
-              let users = (await fetchUsers(e.detail.value)).users;
-              this.userChoices.setChoices(
-                users.map(u => {
-                  return {
-                    value: u.person.id.toString(),
-                    label: u.person.name,
-                  };
-                }),
-                "value",
-                "label",
-                true
-              );
-            } catch (err) {
-              console.log(err);
-            }
-          }),
-          false
-        );
-      }
-    }
+  handleSearchMods = debounce(async (text: string) => {
+    const { modId } = getModlogQueryParams();
+    const { modSearchOptions } = this.state;
+    this.setState({ loadingModSearch: true });
+
+    const newOptions = await createNewOptions({
+      id: modId,
+      text,
+      oldOptions: modSearchOptions,
+    });
+
+    this.setState({
+      modSearchOptions: newOptions,
+      loadingModSearch: false,
+    });
+  });
+
+  async updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
+    const {
+      page: urlPage,
+      actionType: urlActionType,
+      modId: urlModId,
+      userId: urlUserId,
+    } = getModlogQueryParams();
+
+    const queryParams: QueryParams<ModlogProps> = {
+      page: (page ?? urlPage).toString(),
+      actionType: actionType ?? urlActionType,
+      modId: getUpdatedSearchId(modId, urlModId),
+      userId: getUpdatedSearchId(userId, urlUserId),
+    };
+
+    const communityId = this.props.match.params.communityId;
+
+    this.props.history.push(
+      `/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
+        queryParams
+      )}`
+    );
+
+    await this.refetch();
   }
 
-  setupModFilter() {
-    if (isBrowser()) {
-      let selectId: any = document.getElementById("filter-mod");
-      if (selectId) {
-        this.modChoices = new Choices(selectId, choicesConfig);
-        this.modChoices.passedElement.element.addEventListener(
-          "choice",
-          (e: any) => {
-            this.setState({ filter_mod: Some(Number(e.detail.choice.value)) });
-            this.refetch();
-          },
-          false
-        );
-        this.modChoices.passedElement.element.addEventListener(
-          "search",
-          debounce(async (e: any) => {
-            try {
-              let mods = (await fetchUsers(e.detail.value)).users;
-              this.modChoices.setChoices(
-                mods.map(u => {
-                  return {
-                    value: u.person.id.toString(),
-                    label: u.person.name,
-                  };
-                }),
-                "value",
-                "label",
-                true
-              );
-            } catch (err) {
-              console.log(err);
-            }
-          }),
-          false
-        );
-      }
+  async refetch() {
+    const auth = myAuth();
+    const { actionType, page, modId, userId } = getModlogQueryParams();
+    const { communityId: urlCommunityId } = this.props.match.params;
+    const communityId = getIdFromString(urlCommunityId);
+
+    this.setState({ res: { state: "loading" } });
+    this.setState({
+      res: await HttpService.client.getModlog({
+        community_id: communityId,
+        page,
+        limit: fetchLimit,
+        type_: actionType,
+        other_person_id: userId ?? undefined,
+        mod_person_id: !this.isoData.site_res.site_view.local_site
+          .hide_modlog_mod_names
+          ? modId ?? undefined
+          : undefined,
+        auth,
+      }),
+    });
+
+    if (communityId) {
+      this.setState({ communityRes: { state: "loading" } });
+      this.setState({
+        communityRes: await HttpService.client.getCommunity({
+          id: communityId,
+          auth,
+        }),
+      });
     }
   }
 
-  static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
-    let pathSplit = req.path.split("/");
-    let communityId = Some(pathSplit[3]).map(Number);
-    let promises: Promise<any>[] = [];
+  static async fetchInitialData({
+    client,
+    path,
+    query: { modId: urlModId, page, userId: urlUserId, actionType },
+    auth,
+    site,
+  }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
+    const pathSplit = path.split("/");
+    const communityId = getIdFromString(pathSplit[2]);
+    const modId = !site.site_view.local_site.hide_modlog_mod_names
+      ? getIdFromString(urlModId)
+      : undefined;
+    const userId = getIdFromString(urlUserId);
 
-    let modlogForm = new GetModlog({
-      page: Some(1),
-      limit: Some(fetchLimit),
+    const modlogForm: GetModlog = {
+      page: getPageFromString(page),
+      limit: fetchLimit,
       community_id: communityId,
-      mod_person_id: None,
-      auth: req.auth,
-      type_: ModlogActionType.All,
-      other_person_id: None,
-    });
+      type_: getActionFromString(actionType),
+      mod_person_id: modId,
+      other_person_id: userId,
+      auth,
+    };
 
-    promises.push(req.client.getModlog(modlogForm));
+    let communityResponse: RequestState<GetCommunityResponse> = {
+      state: "empty",
+    };
 
-    if (communityId.isSome()) {
-      let communityForm = new GetCommunity({
+    if (communityId) {
+      const communityForm: GetCommunity = {
         id: communityId,
-        name: None,
-        auth: req.auth,
-      });
-      promises.push(req.client.getCommunity(communityForm));
-    } else {
-      promises.push(Promise.resolve());
+        auth,
+      };
+
+      communityResponse = await client.getCommunity(communityForm);
     }
-    return promises;
-  }
 
-  parseMessage(msg: any) {
-    let op = wsUserOp(msg);
-    console.log(msg);
-    if (msg.error) {
-      toast(i18n.t(msg.error), "danger");
-      return;
-    } else if (op == UserOperation.GetModlog) {
-      let data = wsJsonToRes<GetModlogResponse>(msg, GetModlogResponse);
-      window.scrollTo(0, 0);
-      this.setState({ res: Some(data), loading: false });
-      this.setupUserFilter();
-      this.setupModFilter();
-    } else if (op == UserOperation.GetCommunity) {
-      let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
-      this.setState({
-        communityMods: Some(data.moderators),
-        communityName: Some(data.community_view.community.name),
-      });
+    let modUserResponse: RequestState<GetPersonDetailsResponse> = {
+      state: "empty",
+    };
+
+    if (modId) {
+      const getPersonForm: GetPersonDetails = {
+        person_id: modId,
+        auth,
+      };
+
+      modUserResponse = await client.getPersonDetails(getPersonForm);
     }
+
+    let userResponse: RequestState<GetPersonDetailsResponse> = {
+      state: "empty",
+    };
+
+    if (userId) {
+      const getPersonForm: GetPersonDetails = {
+        person_id: userId,
+        auth,
+      };
+
+      userResponse = await client.getPersonDetails(getPersonForm);
+    }
+
+    return {
+      res: await client.getModlog(modlogForm),
+      communityRes: communityResponse,
+      modUserResponse,
+      userResponse,
+    };
   }
 }