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