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