]> Untitled Git - lemmy-ui.git/blob - src/shared/components/modlog.tsx
fix submodule error
[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         community,
316       } = view as ModFeaturePostView;
317
318       return (
319         <>
320           <span>{featured ? "Featured " : "Unfeatured "}</span>
321           <span>
322             Post <Link to={`/post/${id}`}>{name}</Link>
323           </span>
324           <span>
325             {is_featured_community
326               ? " in community "
327               : " in Local, from community "}
328           </span>
329           <CommunityLink community={community} />
330         </>
331       );
332     }
333     case "ModRemoveComment": {
334       const mrc = view as ModRemoveCommentView;
335       const {
336         mod_remove_comment: { reason, removed },
337         comment: { id, content },
338         commenter,
339       } = mrc;
340
341       return (
342         <>
343           <span>{removed ? "Removed " : "Restored "}</span>
344           <span>
345             Comment <Link to={`/comment/${id}`}>{content}</Link>
346           </span>
347           <span>
348             {" "}
349             by <PersonListing person={commenter} />
350           </span>
351           {reason && (
352             <span>
353               <div>reason: {reason}</div>
354             </span>
355           )}
356         </>
357       );
358     }
359
360     case "ModRemoveCommunity": {
361       const mrco = view as ModRemoveCommunityView;
362       const {
363         mod_remove_community: { reason, expires, removed },
364         community,
365       } = mrco;
366
367       return (
368         <>
369           <span>{removed ? "Removed " : "Restored "}</span>
370           <span>
371             Community <CommunityLink community={community} />
372           </span>
373           {reason && (
374             <span>
375               <div>reason: {reason}</div>
376             </span>
377           )}
378           {expires && (
379             <span>
380               <div>expires: {formatPastDate(expires)}</div>
381             </span>
382           )}
383         </>
384       );
385     }
386
387     case "ModBanFromCommunity": {
388       const mbfc = view as ModBanFromCommunityView;
389       const {
390         mod_ban_from_community: { reason, expires, banned },
391         banned_person,
392         community,
393       } = mbfc;
394
395       return (
396         <>
397           <span>{banned ? "Banned " : "Unbanned "}</span>
398           <span>
399             <PersonListing person={banned_person} />
400           </span>
401           <span> from the community </span>
402           <span>
403             <CommunityLink community={community} />
404           </span>
405           {reason && (
406             <span>
407               <div>reason: {reason}</div>
408             </span>
409           )}
410           {expires && (
411             <span>
412               <div>expires: {formatPastDate(expires)}</div>
413             </span>
414           )}
415         </>
416       );
417     }
418
419     case "ModAddCommunity": {
420       const {
421         mod_add_community: { removed },
422         modded_person,
423         community,
424       } = view as ModAddCommunityView;
425
426       return (
427         <>
428           <span>{removed ? "Removed " : "Appointed "}</span>
429           <span>
430             <PersonListing person={modded_person} />
431           </span>
432           <span> as a mod to the community </span>
433           <span>
434             <CommunityLink community={community} />
435           </span>
436         </>
437       );
438     }
439
440     case "ModTransferCommunity": {
441       const { community, modded_person } = view as ModTransferCommunityView;
442
443       return (
444         <>
445           <span>Transferred</span>
446           <span>
447             <CommunityLink community={community} />
448           </span>
449           <span> to </span>
450           <span>
451             <PersonListing person={modded_person} />
452           </span>
453         </>
454       );
455     }
456
457     case "ModBan": {
458       const {
459         mod_ban: { reason, expires, banned },
460         banned_person,
461       } = view as ModBanView;
462
463       return (
464         <>
465           <span>{banned ? "Banned " : "Unbanned "}</span>
466           <span>
467             <PersonListing person={banned_person} />
468           </span>
469           {reason && (
470             <span>
471               <div>reason: {reason}</div>
472             </span>
473           )}
474           {expires && (
475             <span>
476               <div>expires: {formatPastDate(expires)}</div>
477             </span>
478           )}
479         </>
480       );
481     }
482
483     case "ModAdd": {
484       const {
485         mod_add: { removed },
486         modded_person,
487       } = view as ModAddView;
488
489       return (
490         <>
491           <span>{removed ? "Removed " : "Appointed "}</span>
492           <span>
493             <PersonListing person={modded_person} />
494           </span>
495           <span> as an admin </span>
496         </>
497       );
498     }
499     case "AdminPurgePerson": {
500       const {
501         admin_purge_person: { reason },
502       } = view as AdminPurgePersonView;
503
504       return (
505         <>
506           <span>Purged a Person</span>
507           {reason && (
508             <span>
509               <div>reason: {reason}</div>
510             </span>
511           )}
512         </>
513       );
514     }
515
516     case "AdminPurgeCommunity": {
517       const {
518         admin_purge_community: { reason },
519       } = view as AdminPurgeCommunityView;
520
521       return (
522         <>
523           <span>Purged a Community</span>
524           {reason && (
525             <span>
526               <div>reason: {reason}</div>
527             </span>
528           )}
529         </>
530       );
531     }
532
533     case "AdminPurgePost": {
534       const {
535         admin_purge_post: { reason },
536         community,
537       } = view as AdminPurgePostView;
538
539       return (
540         <>
541           <span>Purged a Post from </span>
542           <CommunityLink community={community} />
543           {reason && (
544             <span>
545               <div>reason: {reason}</div>
546             </span>
547           )}
548         </>
549       );
550     }
551
552     case "AdminPurgeComment": {
553       const {
554         admin_purge_comment: { reason },
555         post: { id, name },
556       } = view as AdminPurgeCommentView;
557
558       return (
559         <>
560           <span>
561             Purged a Comment from <Link to={`/post/${id}`}>{name}</Link>
562           </span>
563           {reason && (
564             <span>
565               <div>reason: {reason}</div>
566             </span>
567           )}
568         </>
569       );
570     }
571
572     default:
573       return <></>;
574   }
575 }
576
577 const Filter = ({
578   filterType,
579   onChange,
580   value,
581   onSearch,
582   options,
583   loading,
584 }: {
585   filterType: FilterType;
586   onChange: (option: Choice) => void;
587   value?: number | null;
588   onSearch: (text: string) => void;
589   options: Choice[];
590   loading: boolean;
591 }) => (
592   <div className="col-sm-6 mb-3">
593     <label className="mb-2" htmlFor={`filter-${filterType}`}>
594       {I18NextService.i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
595     </label>
596     <SearchableSelect
597       id={`filter-${filterType}`}
598       value={value ?? 0}
599       options={[
600         {
601           label: I18NextService.i18n.t("all"),
602           value: "0",
603         },
604       ].concat(options)}
605       onChange={onChange}
606       onSearch={onSearch}
607       loading={loading}
608     />
609   </div>
610 );
611
612 async function createNewOptions({
613   id,
614   oldOptions,
615   text,
616 }: {
617   id?: number | null;
618   oldOptions: Choice[];
619   text: string;
620 }) {
621   const newOptions: Choice[] = [];
622
623   if (id) {
624     const selectedUser = oldOptions.find(
625       ({ value }) => value === id.toString(),
626     );
627
628     if (selectedUser) {
629       newOptions.push(selectedUser);
630     }
631   }
632
633   if (text.length > 0) {
634     newOptions.push(
635       ...(await fetchUsers(text))
636         .slice(0, Number(fetchLimit))
637         .map<Choice>(personToChoice),
638     );
639   }
640
641   return newOptions;
642 }
643
644 export class Modlog extends Component<
645   RouteComponentProps<{ communityId?: string }>,
646   ModlogState
647 > {
648   private isoData = setIsoData<ModlogData>(this.context);
649
650   state: ModlogState = {
651     res: { state: "empty" },
652     communityRes: { state: "empty" },
653     loadingModSearch: false,
654     loadingUserSearch: false,
655     userSearchOptions: [],
656     modSearchOptions: [],
657   };
658
659   constructor(
660     props: RouteComponentProps<{ communityId?: string }>,
661     context: any,
662   ) {
663     super(props, context);
664     this.handlePageChange = this.handlePageChange.bind(this);
665     this.handleUserChange = this.handleUserChange.bind(this);
666     this.handleModChange = this.handleModChange.bind(this);
667
668     // Only fetch the data if coming from another route
669     if (FirstLoadService.isFirstLoad) {
670       const { res, communityRes, modUserResponse, userResponse } =
671         this.isoData.routeData;
672
673       this.state = {
674         ...this.state,
675         res,
676         communityRes,
677       };
678
679       if (modUserResponse.state === "success") {
680         this.state = {
681           ...this.state,
682           modSearchOptions: [personToChoice(modUserResponse.data.person_view)],
683         };
684       }
685
686       if (userResponse.state === "success") {
687         this.state = {
688           ...this.state,
689           userSearchOptions: [personToChoice(userResponse.data.person_view)],
690         };
691       }
692     }
693   }
694
695   async componentDidMount() {
696     await this.refetch();
697   }
698
699   get combined() {
700     const res = this.state.res;
701     const combined = res.state === "success" ? buildCombined(res.data) : [];
702
703     return (
704       <tbody>
705         {combined.map(i => (
706           <tr key={i.id}>
707             <td>
708               <MomentTime published={i.when_} />
709             </td>
710             <td>
711               {this.amAdminOrMod && i.moderator ? (
712                 <PersonListing person={i.moderator} />
713               ) : (
714                 <div>{this.modOrAdminText(i.moderator)}</div>
715               )}
716             </td>
717             <td>{renderModlogType(i)}</td>
718           </tr>
719         ))}
720       </tbody>
721     );
722   }
723
724   get amAdminOrMod(): boolean {
725     const amMod_ =
726       this.state.communityRes.state === "success" &&
727       amMod(this.state.communityRes.data.moderators);
728     return amAdmin() || amMod_;
729   }
730
731   modOrAdminText(person?: Person): string {
732     return person &&
733       this.isoData.site_res.admins.some(
734         ({ person: { id } }) => id === person.id,
735       )
736       ? I18NextService.i18n.t("admin")
737       : I18NextService.i18n.t("mod");
738   }
739
740   get documentTitle(): string {
741     return `Modlog - ${this.isoData.site_res.site_view.site.name}`;
742   }
743
744   render() {
745     const {
746       loadingModSearch,
747       loadingUserSearch,
748       userSearchOptions,
749       modSearchOptions,
750     } = this.state;
751     const { actionType, modId, userId } = getModlogQueryParams();
752
753     return (
754       <div className="modlog container-lg">
755         <HtmlTags
756           title={this.documentTitle}
757           path={this.context.router.route.match.url}
758         />
759
760         <h1 className="h4 mb-4">{I18NextService.i18n.t("modlog")}</h1>
761
762         <div
763           className="alert alert-warning text-sm-start text-xs-center"
764           role="alert"
765         >
766           <Icon
767             icon="alert-triangle"
768             inline
769             classes="me-sm-2 mx-auto d-sm-inline d-block"
770           />
771           <T i18nKey="modlog_content_warning" class="d-inline">
772             #<strong>#</strong>#
773           </T>
774         </div>
775         {this.state.communityRes.state === "success" && (
776           <h5>
777             <Link
778               className="text-body"
779               to={`/c/${this.state.communityRes.data.community_view.community.name}`}
780             >
781               /c/{this.state.communityRes.data.community_view.community.name}{" "}
782             </Link>
783             <span>{I18NextService.i18n.t("modlog")}</span>
784           </h5>
785         )}
786         <div className="row mb-2">
787           <div className="col-sm-6">
788             <select
789               value={actionType}
790               onChange={linkEvent(this, this.handleFilterActionChange)}
791               className="form-select"
792               aria-label="action"
793             >
794               <option disabled aria-hidden="true">
795                 {I18NextService.i18n.t("filter_by_action")}
796               </option>
797               <option value={"All"}>{I18NextService.i18n.t("all")}</option>
798               <option value={"ModRemovePost"}>Removing Posts</option>
799               <option value={"ModLockPost"}>Locking Posts</option>
800               <option value={"ModFeaturePost"}>Featuring Posts</option>
801               <option value={"ModRemoveComment"}>Removing Comments</option>
802               <option value={"ModRemoveCommunity"}>Removing Communities</option>
803               <option value={"ModBanFromCommunity"}>
804                 Banning From Communities
805               </option>
806               <option value={"ModAddCommunity"}>Adding Mod to Community</option>
807               <option value={"ModTransferCommunity"}>
808                 Transferring Communities
809               </option>
810               <option value={"ModAdd"}>Adding Mod to Site</option>
811               <option value={"ModBan"}>Banning From Site</option>
812             </select>
813           </div>
814         </div>
815         <div className="row mb-2">
816           <Filter
817             filterType="user"
818             onChange={this.handleUserChange}
819             onSearch={this.handleSearchUsers}
820             value={userId}
821             options={userSearchOptions}
822             loading={loadingUserSearch}
823           />
824           {!this.isoData.site_res.site_view.local_site
825             .hide_modlog_mod_names && (
826             <Filter
827               filterType="mod"
828               onChange={this.handleModChange}
829               onSearch={this.handleSearchMods}
830               value={modId}
831               options={modSearchOptions}
832               loading={loadingModSearch}
833             />
834           )}
835         </div>
836         {this.renderModlogTable()}
837       </div>
838     );
839   }
840
841   renderModlogTable() {
842     switch (this.state.res.state) {
843       case "loading":
844         return (
845           <h5>
846             <Spinner large />
847           </h5>
848         );
849       case "success": {
850         const page = getModlogQueryParams().page;
851         return (
852           <div className="table-responsive">
853             <table id="modlog_table" className="table table-sm table-hover">
854               <thead className="pointer">
855                 <tr>
856                   <th> {I18NextService.i18n.t("time")}</th>
857                   <th>{I18NextService.i18n.t("mod")}</th>
858                   <th>{I18NextService.i18n.t("action")}</th>
859                 </tr>
860               </thead>
861               {this.combined}
862             </table>
863             <Paginator page={page} onChange={this.handlePageChange} />
864           </div>
865         );
866       }
867     }
868   }
869
870   handleFilterActionChange(i: Modlog, event: any) {
871     i.updateUrl({
872       actionType: event.target.value as ModlogActionType,
873       page: 1,
874     });
875   }
876
877   handlePageChange(page: number) {
878     this.updateUrl({ page });
879   }
880
881   handleUserChange(option: Choice) {
882     this.updateUrl({ userId: getIdFromString(option.value) ?? null, page: 1 });
883   }
884
885   handleModChange(option: Choice) {
886     this.updateUrl({ modId: getIdFromString(option.value) ?? null, page: 1 });
887   }
888
889   handleSearchUsers = debounce(async (text: string) => {
890     const { userId } = getModlogQueryParams();
891     const { userSearchOptions } = this.state;
892     this.setState({ loadingUserSearch: true });
893
894     const newOptions = await createNewOptions({
895       id: userId,
896       text,
897       oldOptions: userSearchOptions,
898     });
899
900     this.setState({
901       userSearchOptions: newOptions,
902       loadingUserSearch: false,
903     });
904   });
905
906   handleSearchMods = debounce(async (text: string) => {
907     const { modId } = getModlogQueryParams();
908     const { modSearchOptions } = this.state;
909     this.setState({ loadingModSearch: true });
910
911     const newOptions = await createNewOptions({
912       id: modId,
913       text,
914       oldOptions: modSearchOptions,
915     });
916
917     this.setState({
918       modSearchOptions: newOptions,
919       loadingModSearch: false,
920     });
921   });
922
923   async updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
924     const {
925       page: urlPage,
926       actionType: urlActionType,
927       modId: urlModId,
928       userId: urlUserId,
929     } = getModlogQueryParams();
930
931     const queryParams: QueryParams<ModlogProps> = {
932       page: (page ?? urlPage).toString(),
933       actionType: actionType ?? urlActionType,
934       modId: getUpdatedSearchId(modId, urlModId),
935       userId: getUpdatedSearchId(userId, urlUserId),
936     };
937
938     const communityId = this.props.match.params.communityId;
939
940     this.props.history.push(
941       `/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
942         queryParams,
943       )}`,
944     );
945
946     await this.refetch();
947   }
948
949   async refetch() {
950     const auth = myAuth();
951     const { actionType, page, modId, userId } = getModlogQueryParams();
952     const { communityId: urlCommunityId } = this.props.match.params;
953     const communityId = getIdFromString(urlCommunityId);
954
955     this.setState({ res: { state: "loading" } });
956     this.setState({
957       res: await HttpService.client.getModlog({
958         community_id: communityId,
959         page,
960         limit: fetchLimit,
961         type_: actionType,
962         other_person_id: userId ?? undefined,
963         mod_person_id: !this.isoData.site_res.site_view.local_site
964           .hide_modlog_mod_names
965           ? modId ?? undefined
966           : undefined,
967         auth,
968       }),
969     });
970
971     if (communityId) {
972       this.setState({ communityRes: { state: "loading" } });
973       this.setState({
974         communityRes: await HttpService.client.getCommunity({
975           id: communityId,
976           auth,
977         }),
978       });
979     }
980   }
981
982   static async fetchInitialData({
983     client,
984     path,
985     query: { modId: urlModId, page, userId: urlUserId, actionType },
986     auth,
987     site,
988   }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
989     const pathSplit = path.split("/");
990     const communityId = getIdFromString(pathSplit[2]);
991     const modId = !site.site_view.local_site.hide_modlog_mod_names
992       ? getIdFromString(urlModId)
993       : undefined;
994     const userId = getIdFromString(urlUserId);
995
996     const modlogForm: GetModlog = {
997       page: getPageFromString(page),
998       limit: fetchLimit,
999       community_id: communityId,
1000       type_: getActionFromString(actionType),
1001       mod_person_id: modId,
1002       other_person_id: userId,
1003       auth,
1004     };
1005
1006     let communityResponse: RequestState<GetCommunityResponse> = {
1007       state: "empty",
1008     };
1009
1010     if (communityId) {
1011       const communityForm: GetCommunity = {
1012         id: communityId,
1013         auth,
1014       };
1015
1016       communityResponse = await client.getCommunity(communityForm);
1017     }
1018
1019     let modUserResponse: RequestState<GetPersonDetailsResponse> = {
1020       state: "empty",
1021     };
1022
1023     if (modId) {
1024       const getPersonForm: GetPersonDetails = {
1025         person_id: modId,
1026         auth,
1027       };
1028
1029       modUserResponse = await client.getPersonDetails(getPersonForm);
1030     }
1031
1032     let userResponse: RequestState<GetPersonDetailsResponse> = {
1033       state: "empty",
1034     };
1035
1036     if (userId) {
1037       const getPersonForm: GetPersonDetails = {
1038         person_id: userId,
1039         auth,
1040       };
1041
1042       userResponse = await client.getPersonDetails(getPersonForm);
1043     }
1044
1045     return {
1046       res: await client.getModlog(modlogForm),
1047       communityRes: communityResponse,
1048       modUserResponse,
1049       userResponse,
1050     };
1051   }
1052 }