]> Untitled Git - lemmy-ui.git/blob - src/shared/components/modlog.tsx
Merge branch 'main' of github.com:L3v3L/lemmy-ui into main
[lemmy-ui.git] / src / shared / components / modlog.tsx
1 import { debounce, getQueryParams, getQueryString } from "@utils/helpers";
2 import { amAdmin, amMod } from "@utils/roles";
3 import type { QueryParams } from "@utils/types";
4 import { NoOptionI18nKeys } from "i18next";
5 import { Component, linkEvent } from "inferno";
6 import { T } from "inferno-i18next-dess";
7 import { Link } from "inferno-router";
8 import { RouteComponentProps } from "inferno-router/dist/Route";
9 import {
10   AdminPurgeCommentView,
11   AdminPurgeCommunityView,
12   AdminPurgePersonView,
13   AdminPurgePostView,
14   GetCommunity,
15   GetCommunityResponse,
16   GetModlog,
17   GetModlogResponse,
18   GetPersonDetails,
19   GetPersonDetailsResponse,
20   ModAddCommunityView,
21   ModAddView,
22   ModBanFromCommunityView,
23   ModBanView,
24   ModFeaturePostView,
25   ModLockPostView,
26   ModRemoveCommentView,
27   ModRemoveCommunityView,
28   ModRemovePostView,
29   ModTransferCommunityView,
30   ModlogActionType,
31   Person,
32 } from "lemmy-js-client";
33 import moment from "moment";
34 import { i18n } from "../i18next";
35 import { InitialFetchRequest } from "../interfaces";
36 import { FirstLoadService } from "../services/FirstLoadService";
37 import { HttpService, RequestState } from "../services/HttpService";
38 import {
39   Choice,
40   RouteDataResponse,
41   fetchLimit,
42   fetchUsers,
43   getIdFromString,
44   getPageFromString,
45   getUpdatedSearchId,
46   myAuth,
47   personToChoice,
48   setIsoData,
49 } from "../utils";
50 import { HtmlTags } from "./common/html-tags";
51 import { Icon, Spinner } from "./common/icon";
52 import { MomentTime } from "./common/moment-time";
53 import { Paginator } from "./common/paginator";
54 import { SearchableSelect } from "./common/searchable-select";
55 import { CommunityLink } from "./community/community-link";
56 import { PersonListing } from "./person/person-listing";
57
58 type FilterType = "mod" | "user";
59
60 type View =
61   | ModRemovePostView
62   | ModLockPostView
63   | ModFeaturePostView
64   | ModRemoveCommentView
65   | ModRemoveCommunityView
66   | ModBanFromCommunityView
67   | ModBanView
68   | ModAddCommunityView
69   | ModTransferCommunityView
70   | ModAddView
71   | AdminPurgePersonView
72   | AdminPurgeCommunityView
73   | AdminPurgePostView
74   | AdminPurgeCommentView;
75
76 type ModlogData = RouteDataResponse<{
77   res: GetModlogResponse;
78   communityRes: GetCommunityResponse;
79   modUserResponse: GetPersonDetailsResponse;
80   userResponse: GetPersonDetailsResponse;
81 }>;
82
83 interface ModlogType {
84   id: number;
85   type_: ModlogActionType;
86   moderator?: Person;
87   view: View;
88   when_: string;
89 }
90
91 const getModlogQueryParams = () =>
92   getQueryParams<ModlogProps>({
93     actionType: getActionFromString,
94     modId: getIdFromString,
95     userId: getIdFromString,
96     page: getPageFromString,
97   });
98
99 interface ModlogState {
100   res: RequestState<GetModlogResponse>;
101   communityRes: RequestState<GetCommunityResponse>;
102   loadingModSearch: boolean;
103   loadingUserSearch: boolean;
104   modSearchOptions: Choice[];
105   userSearchOptions: Choice[];
106 }
107
108 interface ModlogProps {
109   page: number;
110   userId?: number | null;
111   modId?: number | null;
112   actionType: ModlogActionType;
113 }
114
115 function getActionFromString(action?: string): ModlogActionType {
116   return action !== undefined ? (action as ModlogActionType) : "All";
117 }
118
119 const getModlogActionMapper =
120   (
121     actionType: ModlogActionType,
122     getAction: (view: View) => { id: number; when_: string }
123   ) =>
124   (view: View & { moderator?: Person; admin?: Person }): ModlogType => {
125     const { id, when_ } = getAction(view);
126
127     return {
128       id,
129       type_: actionType,
130       view,
131       when_,
132       moderator: view.moderator ?? view.admin,
133     };
134   };
135
136 function buildCombined({
137   removed_comments,
138   locked_posts,
139   featured_posts,
140   removed_communities,
141   removed_posts,
142   added,
143   added_to_community,
144   admin_purged_comments,
145   admin_purged_communities,
146   admin_purged_persons,
147   admin_purged_posts,
148   banned,
149   banned_from_community,
150   transferred_to_community,
151 }: GetModlogResponse): ModlogType[] {
152   const combined = removed_posts
153     .map(
154       getModlogActionMapper(
155         "ModRemovePost",
156         ({ mod_remove_post }: ModRemovePostView) => mod_remove_post
157       )
158     )
159     .concat(
160       locked_posts.map(
161         getModlogActionMapper(
162           "ModLockPost",
163           ({ mod_lock_post }: ModLockPostView) => mod_lock_post
164         )
165       )
166     )
167     .concat(
168       featured_posts.map(
169         getModlogActionMapper(
170           "ModFeaturePost",
171           ({ mod_feature_post }: ModFeaturePostView) => mod_feature_post
172         )
173       )
174     )
175     .concat(
176       removed_comments.map(
177         getModlogActionMapper(
178           "ModRemoveComment",
179           ({ mod_remove_comment }: ModRemoveCommentView) => mod_remove_comment
180         )
181       )
182     )
183     .concat(
184       removed_communities.map(
185         getModlogActionMapper(
186           "ModRemoveCommunity",
187           ({ mod_remove_community }: ModRemoveCommunityView) =>
188             mod_remove_community
189         )
190       )
191     )
192     .concat(
193       banned_from_community.map(
194         getModlogActionMapper(
195           "ModBanFromCommunity",
196           ({ mod_ban_from_community }: ModBanFromCommunityView) =>
197             mod_ban_from_community
198         )
199       )
200     )
201     .concat(
202       added_to_community.map(
203         getModlogActionMapper(
204           "ModAddCommunity",
205           ({ mod_add_community }: ModAddCommunityView) => mod_add_community
206         )
207       )
208     )
209     .concat(
210       transferred_to_community.map(
211         getModlogActionMapper(
212           "ModTransferCommunity",
213           ({ mod_transfer_community }: ModTransferCommunityView) =>
214             mod_transfer_community
215         )
216       )
217     )
218     .concat(
219       added.map(
220         getModlogActionMapper("ModAdd", ({ mod_add }: ModAddView) => mod_add)
221       )
222     )
223     .concat(
224       banned.map(
225         getModlogActionMapper("ModBan", ({ mod_ban }: ModBanView) => mod_ban)
226       )
227     )
228     .concat(
229       admin_purged_persons.map(
230         getModlogActionMapper(
231           "AdminPurgePerson",
232           ({ admin_purge_person }: AdminPurgePersonView) => admin_purge_person
233         )
234       )
235     )
236     .concat(
237       admin_purged_communities.map(
238         getModlogActionMapper(
239           "AdminPurgeCommunity",
240           ({ admin_purge_community }: AdminPurgeCommunityView) =>
241             admin_purge_community
242         )
243       )
244     )
245     .concat(
246       admin_purged_posts.map(
247         getModlogActionMapper(
248           "AdminPurgePost",
249           ({ admin_purge_post }: AdminPurgePostView) => admin_purge_post
250         )
251       )
252     )
253     .concat(
254       admin_purged_comments.map(
255         getModlogActionMapper(
256           "AdminPurgeComment",
257           ({ admin_purge_comment }: AdminPurgeCommentView) =>
258             admin_purge_comment
259         )
260       )
261     );
262
263   // Sort them by time
264   combined.sort((a, b) => b.when_.localeCompare(a.when_));
265
266   return combined;
267 }
268
269 function renderModlogType({ type_, view }: ModlogType) {
270   switch (type_) {
271     case "ModRemovePost": {
272       const mrpv = view as ModRemovePostView;
273       const {
274         mod_remove_post: { reason, removed },
275         post: { name, id },
276       } = mrpv;
277
278       return (
279         <>
280           <span>{removed ? "Removed " : "Restored "}</span>
281           <span>
282             Post <Link to={`/post/${id}`}>{name}</Link>
283           </span>
284           {reason && (
285             <span>
286               <div>reason: {reason}</div>
287             </span>
288           )}
289         </>
290       );
291     }
292
293     case "ModLockPost": {
294       const {
295         mod_lock_post: { locked },
296         post: { id, name },
297       } = view as ModLockPostView;
298
299       return (
300         <>
301           <span>{locked ? "Locked " : "Unlocked "}</span>
302           <span>
303             Post <Link to={`/post/${id}`}>{name}</Link>
304           </span>
305         </>
306       );
307     }
308
309     case "ModFeaturePost": {
310       const {
311         mod_feature_post: { featured, is_featured_community },
312         post: { id, name },
313       } = view as ModFeaturePostView;
314
315       return (
316         <>
317           <span>{featured ? "Featured " : "Unfeatured "}</span>
318           <span>
319             Post <Link to={`/post/${id}`}>{name}</Link>
320           </span>
321           <span>{is_featured_community ? " In Community" : " In Local"}</span>
322         </>
323       );
324     }
325     case "ModRemoveComment": {
326       const mrc = view as ModRemoveCommentView;
327       const {
328         mod_remove_comment: { reason, removed },
329         comment: { id, content },
330         commenter,
331       } = mrc;
332
333       return (
334         <>
335           <span>{removed ? "Removed " : "Restored "}</span>
336           <span>
337             Comment <Link to={`/comment/${id}`}>{content}</Link>
338           </span>
339           <span>
340             {" "}
341             by <PersonListing person={commenter} />
342           </span>
343           {reason && (
344             <span>
345               <div>reason: {reason}</div>
346             </span>
347           )}
348         </>
349       );
350     }
351
352     case "ModRemoveCommunity": {
353       const mrco = view as ModRemoveCommunityView;
354       const {
355         mod_remove_community: { reason, expires, removed },
356         community,
357       } = mrco;
358
359       return (
360         <>
361           <span>{removed ? "Removed " : "Restored "}</span>
362           <span>
363             Community <CommunityLink community={community} />
364           </span>
365           {reason && (
366             <span>
367               <div>reason: {reason}</div>
368             </span>
369           )}
370           {expires && (
371             <span>
372               <div>expires: {moment.utc(expires).fromNow()}</div>
373             </span>
374           )}
375         </>
376       );
377     }
378
379     case "ModBanFromCommunity": {
380       const mbfc = view as ModBanFromCommunityView;
381       const {
382         mod_ban_from_community: { reason, expires, banned },
383         banned_person,
384         community,
385       } = mbfc;
386
387       return (
388         <>
389           <span>{banned ? "Banned " : "Unbanned "}</span>
390           <span>
391             <PersonListing person={banned_person} />
392           </span>
393           <span> from the community </span>
394           <span>
395             <CommunityLink community={community} />
396           </span>
397           {reason && (
398             <span>
399               <div>reason: {reason}</div>
400             </span>
401           )}
402           {expires && (
403             <span>
404               <div>expires: {moment.utc(expires).fromNow()}</div>
405             </span>
406           )}
407         </>
408       );
409     }
410
411     case "ModAddCommunity": {
412       const {
413         mod_add_community: { removed },
414         modded_person,
415         community,
416       } = view as ModAddCommunityView;
417
418       return (
419         <>
420           <span>{removed ? "Removed " : "Appointed "}</span>
421           <span>
422             <PersonListing person={modded_person} />
423           </span>
424           <span> as a mod to the community </span>
425           <span>
426             <CommunityLink community={community} />
427           </span>
428         </>
429       );
430     }
431
432     case "ModTransferCommunity": {
433       const { community, modded_person } = view as ModTransferCommunityView;
434
435       return (
436         <>
437           <span>Transferred</span>
438           <span>
439             <CommunityLink community={community} />
440           </span>
441           <span> to </span>
442           <span>
443             <PersonListing person={modded_person} />
444           </span>
445         </>
446       );
447     }
448
449     case "ModBan": {
450       const {
451         mod_ban: { reason, expires, banned },
452         banned_person,
453       } = view as ModBanView;
454
455       return (
456         <>
457           <span>{banned ? "Banned " : "Unbanned "}</span>
458           <span>
459             <PersonListing person={banned_person} />
460           </span>
461           {reason && (
462             <span>
463               <div>reason: {reason}</div>
464             </span>
465           )}
466           {expires && (
467             <span>
468               <div>expires: {moment.utc(expires).fromNow()}</div>
469             </span>
470           )}
471         </>
472       );
473     }
474
475     case "ModAdd": {
476       const {
477         mod_add: { removed },
478         modded_person,
479       } = view as ModAddView;
480
481       return (
482         <>
483           <span>{removed ? "Removed " : "Appointed "}</span>
484           <span>
485             <PersonListing person={modded_person} />
486           </span>
487           <span> as an admin </span>
488         </>
489       );
490     }
491     case "AdminPurgePerson": {
492       const {
493         admin_purge_person: { reason },
494       } = view as AdminPurgePersonView;
495
496       return (
497         <>
498           <span>Purged a Person</span>
499           {reason && (
500             <span>
501               <div>reason: {reason}</div>
502             </span>
503           )}
504         </>
505       );
506     }
507
508     case "AdminPurgeCommunity": {
509       const {
510         admin_purge_community: { reason },
511       } = view as AdminPurgeCommunityView;
512
513       return (
514         <>
515           <span>Purged a Community</span>
516           {reason && (
517             <span>
518               <div>reason: {reason}</div>
519             </span>
520           )}
521         </>
522       );
523     }
524
525     case "AdminPurgePost": {
526       const {
527         admin_purge_post: { reason },
528         community,
529       } = view as AdminPurgePostView;
530
531       return (
532         <>
533           <span>Purged a Post from from </span>
534           <CommunityLink community={community} />
535           {reason && (
536             <span>
537               <div>reason: {reason}</div>
538             </span>
539           )}
540         </>
541       );
542     }
543
544     case "AdminPurgeComment": {
545       const {
546         admin_purge_comment: { reason },
547         post: { id, name },
548       } = view as AdminPurgeCommentView;
549
550       return (
551         <>
552           <span>
553             Purged a Comment from <Link to={`/post/${id}`}>{name}</Link>
554           </span>
555           {reason && (
556             <span>
557               <div>reason: {reason}</div>
558             </span>
559           )}
560         </>
561       );
562     }
563
564     default:
565       return <></>;
566   }
567 }
568
569 const Filter = ({
570   filterType,
571   onChange,
572   value,
573   onSearch,
574   options,
575   loading,
576 }: {
577   filterType: FilterType;
578   onChange: (option: Choice) => void;
579   value?: number | null;
580   onSearch: (text: string) => void;
581   options: Choice[];
582   loading: boolean;
583 }) => (
584   <div className="col-sm-6 mb-3">
585     <label className="mb-2" htmlFor={`filter-${filterType}`}>
586       {i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
587     </label>
588     <SearchableSelect
589       id={`filter-${filterType}`}
590       value={value ?? 0}
591       options={[
592         {
593           label: i18n.t("all"),
594           value: "0",
595         },
596       ].concat(options)}
597       onChange={onChange}
598       onSearch={onSearch}
599       loading={loading}
600     />
601   </div>
602 );
603
604 async function createNewOptions({
605   id,
606   oldOptions,
607   text,
608 }: {
609   id?: number | null;
610   oldOptions: Choice[];
611   text: string;
612 }) {
613   const newOptions: Choice[] = [];
614
615   if (id) {
616     const selectedUser = oldOptions.find(
617       ({ value }) => value === id.toString()
618     );
619
620     if (selectedUser) {
621       newOptions.push(selectedUser);
622     }
623   }
624
625   if (text.length > 0) {
626     newOptions.push(
627       ...(await fetchUsers(text))
628         .slice(0, Number(fetchLimit))
629         .map<Choice>(personToChoice)
630     );
631   }
632
633   return newOptions;
634 }
635
636 export class Modlog extends Component<
637   RouteComponentProps<{ communityId?: string }>,
638   ModlogState
639 > {
640   private isoData = setIsoData<ModlogData>(this.context);
641
642   state: ModlogState = {
643     res: { state: "empty" },
644     communityRes: { state: "empty" },
645     loadingModSearch: false,
646     loadingUserSearch: false,
647     userSearchOptions: [],
648     modSearchOptions: [],
649   };
650
651   constructor(
652     props: RouteComponentProps<{ communityId?: string }>,
653     context: any
654   ) {
655     super(props, context);
656     this.handlePageChange = this.handlePageChange.bind(this);
657     this.handleUserChange = this.handleUserChange.bind(this);
658     this.handleModChange = this.handleModChange.bind(this);
659
660     // Only fetch the data if coming from another route
661     if (FirstLoadService.isFirstLoad) {
662       const { res, communityRes, modUserResponse, userResponse } =
663         this.isoData.routeData;
664
665       this.state = {
666         ...this.state,
667         res,
668         communityRes,
669       };
670
671       if (modUserResponse.state === "success") {
672         this.state = {
673           ...this.state,
674           modSearchOptions: [personToChoice(modUserResponse.data.person_view)],
675         };
676       }
677
678       if (userResponse.state === "success") {
679         this.state = {
680           ...this.state,
681           userSearchOptions: [personToChoice(userResponse.data.person_view)],
682         };
683       }
684     }
685   }
686
687   get combined() {
688     const res = this.state.res;
689     const combined = res.state == "success" ? buildCombined(res.data) : [];
690
691     return (
692       <tbody>
693         {combined.map(i => (
694           <tr key={i.id}>
695             <td>
696               <MomentTime published={i.when_} />
697             </td>
698             <td>
699               {this.amAdminOrMod && i.moderator ? (
700                 <PersonListing person={i.moderator} />
701               ) : (
702                 <div>{this.modOrAdminText(i.moderator)}</div>
703               )}
704             </td>
705             <td>{renderModlogType(i)}</td>
706           </tr>
707         ))}
708       </tbody>
709     );
710   }
711
712   get amAdminOrMod(): boolean {
713     const amMod_ =
714       this.state.communityRes.state == "success" &&
715       amMod(this.state.communityRes.data.moderators);
716     return amAdmin() || amMod_;
717   }
718
719   modOrAdminText(person?: Person): string {
720     return person &&
721       this.isoData.site_res.admins.some(
722         ({ person: { id } }) => id === person.id
723       )
724       ? i18n.t("admin")
725       : i18n.t("mod");
726   }
727
728   get documentTitle(): string {
729     return `Modlog - ${this.isoData.site_res.site_view.site.name}`;
730   }
731
732   render() {
733     const {
734       loadingModSearch,
735       loadingUserSearch,
736       userSearchOptions,
737       modSearchOptions,
738     } = this.state;
739     const { actionType, modId, userId } = getModlogQueryParams();
740
741     return (
742       <div className="modlog container-lg">
743         <HtmlTags
744           title={this.documentTitle}
745           path={this.context.router.route.match.url}
746         />
747
748         <div>
749           <div
750             className="alert alert-warning text-sm-start text-xs-center"
751             role="alert"
752           >
753             <Icon
754               icon="alert-triangle"
755               inline
756               classes="me-sm-2 mx-auto d-sm-inline d-block"
757             />
758             <T i18nKey="modlog_content_warning" class="d-inline">
759               #<strong>#</strong>#
760             </T>
761           </div>
762           {this.state.communityRes.state === "success" && (
763             <h5>
764               <Link
765                 className="text-body"
766                 to={`/c/${this.state.communityRes.data.community_view.community.name}`}
767               >
768                 /c/{this.state.communityRes.data.community_view.community.name}{" "}
769               </Link>
770               <span>{i18n.t("modlog")}</span>
771             </h5>
772           )}
773           <div className="row mb-2">
774             <div className="col-sm-6">
775               <select
776                 value={actionType}
777                 onChange={linkEvent(this, this.handleFilterActionChange)}
778                 className="form-select"
779                 aria-label="action"
780               >
781                 <option disabled aria-hidden="true">
782                   {i18n.t("filter_by_action")}
783                 </option>
784                 <option value={"All"}>{i18n.t("all")}</option>
785                 <option value={"ModRemovePost"}>Removing Posts</option>
786                 <option value={"ModLockPost"}>Locking Posts</option>
787                 <option value={"ModFeaturePost"}>Featuring Posts</option>
788                 <option value={"ModRemoveComment"}>Removing Comments</option>
789                 <option value={"ModRemoveCommunity"}>
790                   Removing Communities
791                 </option>
792                 <option value={"ModBanFromCommunity"}>
793                   Banning From Communities
794                 </option>
795                 <option value={"ModAddCommunity"}>
796                   Adding Mod to Community
797                 </option>
798                 <option value={"ModTransferCommunity"}>
799                   Transferring Communities
800                 </option>
801                 <option value={"ModAdd"}>Adding Mod to Site</option>
802                 <option value={"ModBan"}>Banning From Site</option>
803               </select>
804             </div>
805           </div>
806           <div className="row mb-2">
807             <Filter
808               filterType="user"
809               onChange={this.handleUserChange}
810               onSearch={this.handleSearchUsers}
811               value={userId}
812               options={userSearchOptions}
813               loading={loadingUserSearch}
814             />
815             {!this.isoData.site_res.site_view.local_site
816               .hide_modlog_mod_names && (
817               <Filter
818                 filterType="mod"
819                 onChange={this.handleModChange}
820                 onSearch={this.handleSearchMods}
821                 value={modId}
822                 options={modSearchOptions}
823                 loading={loadingModSearch}
824               />
825             )}
826           </div>
827           {this.renderModlogTable()}
828         </div>
829       </div>
830     );
831   }
832
833   renderModlogTable() {
834     switch (this.state.res.state) {
835       case "loading":
836         return (
837           <h5>
838             <Spinner large />
839           </h5>
840         );
841       case "success": {
842         const page = getModlogQueryParams().page;
843         return (
844           <div className="table-responsive">
845             <table id="modlog_table" className="table table-sm table-hover">
846               <thead className="pointer">
847                 <tr>
848                   <th> {i18n.t("time")}</th>
849                   <th>{i18n.t("mod")}</th>
850                   <th>{i18n.t("action")}</th>
851                 </tr>
852               </thead>
853               {this.combined}
854             </table>
855             <Paginator page={page} onChange={this.handlePageChange} />
856           </div>
857         );
858       }
859     }
860   }
861
862   handleFilterActionChange(i: Modlog, event: any) {
863     i.updateUrl({
864       actionType: event.target.value as ModlogActionType,
865       page: 1,
866     });
867   }
868
869   handlePageChange(page: number) {
870     this.updateUrl({ page });
871   }
872
873   handleUserChange(option: Choice) {
874     this.updateUrl({ userId: getIdFromString(option.value) ?? null, page: 1 });
875   }
876
877   handleModChange(option: Choice) {
878     this.updateUrl({ modId: getIdFromString(option.value) ?? null, page: 1 });
879   }
880
881   handleSearchUsers = debounce(async (text: string) => {
882     const { userId } = getModlogQueryParams();
883     const { userSearchOptions } = this.state;
884     this.setState({ loadingUserSearch: true });
885
886     const newOptions = await createNewOptions({
887       id: userId,
888       text,
889       oldOptions: userSearchOptions,
890     });
891
892     this.setState({
893       userSearchOptions: newOptions,
894       loadingUserSearch: false,
895     });
896   });
897
898   handleSearchMods = debounce(async (text: string) => {
899     const { modId } = getModlogQueryParams();
900     const { modSearchOptions } = this.state;
901     this.setState({ loadingModSearch: true });
902
903     const newOptions = await createNewOptions({
904       id: modId,
905       text,
906       oldOptions: modSearchOptions,
907     });
908
909     this.setState({
910       modSearchOptions: newOptions,
911       loadingModSearch: false,
912     });
913   });
914
915   async updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
916     const {
917       page: urlPage,
918       actionType: urlActionType,
919       modId: urlModId,
920       userId: urlUserId,
921     } = getModlogQueryParams();
922
923     const queryParams: QueryParams<ModlogProps> = {
924       page: (page ?? urlPage).toString(),
925       actionType: actionType ?? urlActionType,
926       modId: getUpdatedSearchId(modId, urlModId),
927       userId: getUpdatedSearchId(userId, urlUserId),
928     };
929
930     const communityId = this.props.match.params.communityId;
931
932     this.props.history.push(
933       `/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
934         queryParams
935       )}`
936     );
937
938     await this.refetch();
939   }
940
941   async refetch() {
942     const auth = myAuth();
943     const { actionType, page, modId, userId } = getModlogQueryParams();
944     const { communityId: urlCommunityId } = this.props.match.params;
945     const communityId = getIdFromString(urlCommunityId);
946
947     this.setState({ res: { state: "loading" } });
948     this.setState({
949       res: await HttpService.client.getModlog({
950         community_id: communityId,
951         page,
952         limit: fetchLimit,
953         type_: actionType,
954         other_person_id: userId ?? undefined,
955         mod_person_id: !this.isoData.site_res.site_view.local_site
956           .hide_modlog_mod_names
957           ? modId ?? undefined
958           : undefined,
959         auth,
960       }),
961     });
962
963     if (communityId) {
964       this.setState({ communityRes: { state: "loading" } });
965       this.setState({
966         communityRes: await HttpService.client.getCommunity({
967           id: communityId,
968           auth,
969         }),
970       });
971     }
972   }
973
974   static async fetchInitialData({
975     client,
976     path,
977     query: { modId: urlModId, page, userId: urlUserId, actionType },
978     auth,
979     site,
980   }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
981     const pathSplit = path.split("/");
982     const communityId = getIdFromString(pathSplit[2]);
983     const modId = !site.site_view.local_site.hide_modlog_mod_names
984       ? getIdFromString(urlModId)
985       : undefined;
986     const userId = getIdFromString(urlUserId);
987
988     const modlogForm: GetModlog = {
989       page: getPageFromString(page),
990       limit: fetchLimit,
991       community_id: communityId,
992       type_: getActionFromString(actionType),
993       mod_person_id: modId,
994       other_person_id: userId,
995       auth,
996     };
997
998     let communityResponse: RequestState<GetCommunityResponse> = {
999       state: "empty",
1000     };
1001
1002     if (communityId) {
1003       const communityForm: GetCommunity = {
1004         id: communityId,
1005         auth,
1006       };
1007
1008       communityResponse = await client.getCommunity(communityForm);
1009     }
1010
1011     let modUserResponse: RequestState<GetPersonDetailsResponse> = {
1012       state: "empty",
1013     };
1014
1015     if (modId) {
1016       const getPersonForm: GetPersonDetails = {
1017         person_id: modId,
1018         auth,
1019       };
1020
1021       modUserResponse = await client.getPersonDetails(getPersonForm);
1022     }
1023
1024     let userResponse: RequestState<GetPersonDetailsResponse> = {
1025       state: "empty",
1026     };
1027
1028     if (userId) {
1029       const getPersonForm: GetPersonDetails = {
1030         person_id: userId,
1031         auth,
1032       };
1033
1034       userResponse = await client.getPersonDetails(getPersonForm);
1035     }
1036
1037     return {
1038       res: await client.getModlog(modlogForm),
1039       communityRes: communityResponse,
1040       modUserResponse,
1041       userResponse,
1042     };
1043   }
1044 }