]> Untitled Git - lemmy-ui.git/blob - src/shared/components/modlog.tsx
Merge branch 'LemmyNet:main' into added-darkly-compact-552
[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   formatPastDate,
11   getIdFromString,
12   getPageFromString,
13   getQueryParams,
14   getQueryString,
15 } from "@utils/helpers";
16 import { amAdmin, amMod } from "@utils/roles";
17 import type { QueryParams } from "@utils/types";
18 import { Choice, RouteDataResponse } from "@utils/types";
19 import { NoOptionI18nKeys } from "i18next";
20 import { Component, linkEvent } from "inferno";
21 import { T } from "inferno-i18next-dess";
22 import { Link } from "inferno-router";
23 import { RouteComponentProps } from "inferno-router/dist/Route";
24 import {
25   AdminPurgeCommentView,
26   AdminPurgeCommunityView,
27   AdminPurgePersonView,
28   AdminPurgePostView,
29   GetCommunity,
30   GetCommunityResponse,
31   GetModlog,
32   GetModlogResponse,
33   GetPersonDetails,
34   GetPersonDetailsResponse,
35   ModAddCommunityView,
36   ModAddView,
37   ModBanFromCommunityView,
38   ModBanView,
39   ModFeaturePostView,
40   ModLockPostView,
41   ModRemoveCommentView,
42   ModRemoveCommunityView,
43   ModRemovePostView,
44   ModTransferCommunityView,
45   ModlogActionType,
46   Person,
47 } from "lemmy-js-client";
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: {formatPastDate(expires)}</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: {formatPastDate(expires)}</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: {formatPastDate(expires)}</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   async componentDidMount() {
690     await this.refetch();
691   }
692
693   get combined() {
694     const res = this.state.res;
695     const combined = res.state == "success" ? buildCombined(res.data) : [];
696
697     return (
698       <tbody>
699         {combined.map(i => (
700           <tr key={i.id}>
701             <td>
702               <MomentTime published={i.when_} />
703             </td>
704             <td>
705               {this.amAdminOrMod && i.moderator ? (
706                 <PersonListing person={i.moderator} />
707               ) : (
708                 <div>{this.modOrAdminText(i.moderator)}</div>
709               )}
710             </td>
711             <td>{renderModlogType(i)}</td>
712           </tr>
713         ))}
714       </tbody>
715     );
716   }
717
718   get amAdminOrMod(): boolean {
719     const amMod_ =
720       this.state.communityRes.state == "success" &&
721       amMod(this.state.communityRes.data.moderators);
722     return amAdmin() || amMod_;
723   }
724
725   modOrAdminText(person?: Person): string {
726     return person &&
727       this.isoData.site_res.admins.some(
728         ({ person: { id } }) => id === person.id
729       )
730       ? I18NextService.i18n.t("admin")
731       : I18NextService.i18n.t("mod");
732   }
733
734   get documentTitle(): string {
735     return `Modlog - ${this.isoData.site_res.site_view.site.name}`;
736   }
737
738   render() {
739     const {
740       loadingModSearch,
741       loadingUserSearch,
742       userSearchOptions,
743       modSearchOptions,
744     } = this.state;
745     const { actionType, modId, userId } = getModlogQueryParams();
746
747     return (
748       <div className="modlog container-lg">
749         <HtmlTags
750           title={this.documentTitle}
751           path={this.context.router.route.match.url}
752         />
753
754         <div>
755           <div
756             className="alert alert-warning text-sm-start text-xs-center"
757             role="alert"
758           >
759             <Icon
760               icon="alert-triangle"
761               inline
762               classes="me-sm-2 mx-auto d-sm-inline d-block"
763             />
764             <T i18nKey="modlog_content_warning" class="d-inline">
765               #<strong>#</strong>#
766             </T>
767           </div>
768           {this.state.communityRes.state === "success" && (
769             <h5>
770               <Link
771                 className="text-body"
772                 to={`/c/${this.state.communityRes.data.community_view.community.name}`}
773               >
774                 /c/{this.state.communityRes.data.community_view.community.name}{" "}
775               </Link>
776               <span>{I18NextService.i18n.t("modlog")}</span>
777             </h5>
778           )}
779           <div className="row mb-2">
780             <div className="col-sm-6">
781               <select
782                 value={actionType}
783                 onChange={linkEvent(this, this.handleFilterActionChange)}
784                 className="form-select"
785                 aria-label="action"
786               >
787                 <option disabled aria-hidden="true">
788                   {I18NextService.i18n.t("filter_by_action")}
789                 </option>
790                 <option value={"All"}>{I18NextService.i18n.t("all")}</option>
791                 <option value={"ModRemovePost"}>Removing Posts</option>
792                 <option value={"ModLockPost"}>Locking Posts</option>
793                 <option value={"ModFeaturePost"}>Featuring Posts</option>
794                 <option value={"ModRemoveComment"}>Removing Comments</option>
795                 <option value={"ModRemoveCommunity"}>
796                   Removing Communities
797                 </option>
798                 <option value={"ModBanFromCommunity"}>
799                   Banning From Communities
800                 </option>
801                 <option value={"ModAddCommunity"}>
802                   Adding Mod to Community
803                 </option>
804                 <option value={"ModTransferCommunity"}>
805                   Transferring Communities
806                 </option>
807                 <option value={"ModAdd"}>Adding Mod to Site</option>
808                 <option value={"ModBan"}>Banning From Site</option>
809               </select>
810             </div>
811           </div>
812           <div className="row mb-2">
813             <Filter
814               filterType="user"
815               onChange={this.handleUserChange}
816               onSearch={this.handleSearchUsers}
817               value={userId}
818               options={userSearchOptions}
819               loading={loadingUserSearch}
820             />
821             {!this.isoData.site_res.site_view.local_site
822               .hide_modlog_mod_names && (
823               <Filter
824                 filterType="mod"
825                 onChange={this.handleModChange}
826                 onSearch={this.handleSearchMods}
827                 value={modId}
828                 options={modSearchOptions}
829                 loading={loadingModSearch}
830               />
831             )}
832           </div>
833           {this.renderModlogTable()}
834         </div>
835       </div>
836     );
837   }
838
839   renderModlogTable() {
840     switch (this.state.res.state) {
841       case "loading":
842         return (
843           <h5>
844             <Spinner large />
845           </h5>
846         );
847       case "success": {
848         const page = getModlogQueryParams().page;
849         return (
850           <div className="table-responsive">
851             <table id="modlog_table" className="table table-sm table-hover">
852               <thead className="pointer">
853                 <tr>
854                   <th> {I18NextService.i18n.t("time")}</th>
855                   <th>{I18NextService.i18n.t("mod")}</th>
856                   <th>{I18NextService.i18n.t("action")}</th>
857                 </tr>
858               </thead>
859               {this.combined}
860             </table>
861             <Paginator page={page} onChange={this.handlePageChange} />
862           </div>
863         );
864       }
865     }
866   }
867
868   handleFilterActionChange(i: Modlog, event: any) {
869     i.updateUrl({
870       actionType: event.target.value as ModlogActionType,
871       page: 1,
872     });
873   }
874
875   handlePageChange(page: number) {
876     this.updateUrl({ page });
877   }
878
879   handleUserChange(option: Choice) {
880     this.updateUrl({ userId: getIdFromString(option.value) ?? null, page: 1 });
881   }
882
883   handleModChange(option: Choice) {
884     this.updateUrl({ modId: getIdFromString(option.value) ?? null, page: 1 });
885   }
886
887   handleSearchUsers = debounce(async (text: string) => {
888     const { userId } = getModlogQueryParams();
889     const { userSearchOptions } = this.state;
890     this.setState({ loadingUserSearch: true });
891
892     const newOptions = await createNewOptions({
893       id: userId,
894       text,
895       oldOptions: userSearchOptions,
896     });
897
898     this.setState({
899       userSearchOptions: newOptions,
900       loadingUserSearch: false,
901     });
902   });
903
904   handleSearchMods = debounce(async (text: string) => {
905     const { modId } = getModlogQueryParams();
906     const { modSearchOptions } = this.state;
907     this.setState({ loadingModSearch: true });
908
909     const newOptions = await createNewOptions({
910       id: modId,
911       text,
912       oldOptions: modSearchOptions,
913     });
914
915     this.setState({
916       modSearchOptions: newOptions,
917       loadingModSearch: false,
918     });
919   });
920
921   async updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
922     const {
923       page: urlPage,
924       actionType: urlActionType,
925       modId: urlModId,
926       userId: urlUserId,
927     } = getModlogQueryParams();
928
929     const queryParams: QueryParams<ModlogProps> = {
930       page: (page ?? urlPage).toString(),
931       actionType: actionType ?? urlActionType,
932       modId: getUpdatedSearchId(modId, urlModId),
933       userId: getUpdatedSearchId(userId, urlUserId),
934     };
935
936     const communityId = this.props.match.params.communityId;
937
938     this.props.history.push(
939       `/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
940         queryParams
941       )}`
942     );
943
944     await this.refetch();
945   }
946
947   async refetch() {
948     const auth = myAuth();
949     const { actionType, page, modId, userId } = getModlogQueryParams();
950     const { communityId: urlCommunityId } = this.props.match.params;
951     const communityId = getIdFromString(urlCommunityId);
952
953     this.setState({ res: { state: "loading" } });
954     this.setState({
955       res: await HttpService.client.getModlog({
956         community_id: communityId,
957         page,
958         limit: fetchLimit,
959         type_: actionType,
960         other_person_id: userId ?? undefined,
961         mod_person_id: !this.isoData.site_res.site_view.local_site
962           .hide_modlog_mod_names
963           ? modId ?? undefined
964           : undefined,
965         auth,
966       }),
967     });
968
969     if (communityId) {
970       this.setState({ communityRes: { state: "loading" } });
971       this.setState({
972         communityRes: await HttpService.client.getCommunity({
973           id: communityId,
974           auth,
975         }),
976       });
977     }
978   }
979
980   static async fetchInitialData({
981     client,
982     path,
983     query: { modId: urlModId, page, userId: urlUserId, actionType },
984     auth,
985     site,
986   }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
987     const pathSplit = path.split("/");
988     const communityId = getIdFromString(pathSplit[2]);
989     const modId = !site.site_view.local_site.hide_modlog_mod_names
990       ? getIdFromString(urlModId)
991       : undefined;
992     const userId = getIdFromString(urlUserId);
993
994     const modlogForm: GetModlog = {
995       page: getPageFromString(page),
996       limit: fetchLimit,
997       community_id: communityId,
998       type_: getActionFromString(actionType),
999       mod_person_id: modId,
1000       other_person_id: userId,
1001       auth,
1002     };
1003
1004     let communityResponse: RequestState<GetCommunityResponse> = {
1005       state: "empty",
1006     };
1007
1008     if (communityId) {
1009       const communityForm: GetCommunity = {
1010         id: communityId,
1011         auth,
1012       };
1013
1014       communityResponse = await client.getCommunity(communityForm);
1015     }
1016
1017     let modUserResponse: RequestState<GetPersonDetailsResponse> = {
1018       state: "empty",
1019     };
1020
1021     if (modId) {
1022       const getPersonForm: GetPersonDetails = {
1023         person_id: modId,
1024         auth,
1025       };
1026
1027       modUserResponse = await client.getPersonDetails(getPersonForm);
1028     }
1029
1030     let userResponse: RequestState<GetPersonDetailsResponse> = {
1031       state: "empty",
1032     };
1033
1034     if (userId) {
1035       const getPersonForm: GetPersonDetails = {
1036         person_id: userId,
1037         auth,
1038       };
1039
1040       userResponse = await client.getPersonDetails(getPersonForm);
1041     }
1042
1043     return {
1044       res: await client.getModlog(modlogForm),
1045       communityRes: communityResponse,
1046       modUserResponse,
1047       userResponse,
1048     };
1049   }
1050 }