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