]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/reports.tsx
6fe59f1c4bafb2a74835e4168ad8a0d7e0bb6f0d
[lemmy-ui.git] / src / shared / components / person / reports.tsx
1 import {
2   editCommentReport,
3   editPostReport,
4   editPrivateMessageReport,
5   myAuthRequired,
6   setIsoData,
7 } from "@utils/app";
8 import { amAdmin } from "@utils/roles";
9 import { RouteDataResponse } from "@utils/types";
10 import { Component, linkEvent } from "inferno";
11 import {
12   CommentReportResponse,
13   CommentReportView,
14   GetSiteResponse,
15   ListCommentReports,
16   ListCommentReportsResponse,
17   ListPostReports,
18   ListPostReportsResponse,
19   ListPrivateMessageReports,
20   ListPrivateMessageReportsResponse,
21   PostReportResponse,
22   PostReportView,
23   PrivateMessageReportResponse,
24   PrivateMessageReportView,
25   ResolveCommentReport,
26   ResolvePostReport,
27   ResolvePrivateMessageReport,
28 } from "lemmy-js-client";
29 import { fetchLimit } from "../../config";
30 import { i18n } from "../../i18next";
31 import { InitialFetchRequest } from "../../interfaces";
32 import { HttpService, UserService } from "../../services";
33 import { FirstLoadService } from "../../services/FirstLoadService";
34 import { RequestState } from "../../services/HttpService";
35 import { CommentReport } from "../comment/comment-report";
36 import { HtmlTags } from "../common/html-tags";
37 import { Spinner } from "../common/icon";
38 import { Paginator } from "../common/paginator";
39 import { PostReport } from "../post/post-report";
40 import { PrivateMessageReport } from "../private_message/private-message-report";
41
42 enum UnreadOrAll {
43   Unread,
44   All,
45 }
46
47 enum MessageType {
48   All,
49   CommentReport,
50   PostReport,
51   PrivateMessageReport,
52 }
53
54 enum MessageEnum {
55   CommentReport,
56   PostReport,
57   PrivateMessageReport,
58 }
59
60 type ReportsData = RouteDataResponse<{
61   commentReportsRes: ListCommentReportsResponse;
62   postReportsRes: ListPostReportsResponse;
63   messageReportsRes: ListPrivateMessageReportsResponse;
64 }>;
65
66 type ItemType = {
67   id: number;
68   type_: MessageEnum;
69   view: CommentReportView | PostReportView | PrivateMessageReportView;
70   published: string;
71 };
72
73 interface ReportsState {
74   commentReportsRes: RequestState<ListCommentReportsResponse>;
75   postReportsRes: RequestState<ListPostReportsResponse>;
76   messageReportsRes: RequestState<ListPrivateMessageReportsResponse>;
77   unreadOrAll: UnreadOrAll;
78   messageType: MessageType;
79   siteRes: GetSiteResponse;
80   page: number;
81   isIsomorphic: boolean;
82 }
83
84 export class Reports extends Component<any, ReportsState> {
85   private isoData = setIsoData<ReportsData>(this.context);
86   state: ReportsState = {
87     commentReportsRes: { state: "empty" },
88     postReportsRes: { state: "empty" },
89     messageReportsRes: { state: "empty" },
90     unreadOrAll: UnreadOrAll.Unread,
91     messageType: MessageType.All,
92     page: 1,
93     siteRes: this.isoData.site_res,
94     isIsomorphic: false,
95   };
96
97   constructor(props: any, context: any) {
98     super(props, context);
99
100     this.handlePageChange = this.handlePageChange.bind(this);
101     this.handleResolveCommentReport =
102       this.handleResolveCommentReport.bind(this);
103     this.handleResolvePostReport = this.handleResolvePostReport.bind(this);
104     this.handleResolvePrivateMessageReport =
105       this.handleResolvePrivateMessageReport.bind(this);
106
107     // Only fetch the data if coming from another route
108     if (FirstLoadService.isFirstLoad) {
109       const { commentReportsRes, postReportsRes, messageReportsRes } =
110         this.isoData.routeData;
111
112       this.state = {
113         ...this.state,
114         commentReportsRes,
115         postReportsRes,
116         isIsomorphic: true,
117       };
118
119       if (amAdmin()) {
120         this.state = {
121           ...this.state,
122           messageReportsRes: messageReportsRes,
123         };
124       }
125     }
126   }
127
128   async componentDidMount() {
129     if (!this.state.isIsomorphic) {
130       await this.refetch();
131     }
132   }
133
134   get documentTitle(): string {
135     const mui = UserService.Instance.myUserInfo;
136     return mui
137       ? `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${
138           this.state.siteRes.site_view.site.name
139         }`
140       : "";
141   }
142
143   render() {
144     return (
145       <div className="person-reports container-lg">
146         <div className="row">
147           <div className="col-12">
148             <HtmlTags
149               title={this.documentTitle}
150               path={this.context.router.route.match.url}
151             />
152             <h5 className="mb-2">{i18n.t("reports")}</h5>
153             {this.selects()}
154             {this.section}
155             <Paginator
156               page={this.state.page}
157               onChange={this.handlePageChange}
158             />
159           </div>
160         </div>
161       </div>
162     );
163   }
164
165   get section() {
166     switch (this.state.messageType) {
167       case MessageType.All: {
168         return this.all();
169       }
170       case MessageType.CommentReport: {
171         return this.commentReports();
172       }
173       case MessageType.PostReport: {
174         return this.postReports();
175       }
176       case MessageType.PrivateMessageReport: {
177         return this.privateMessageReports();
178       }
179
180       default: {
181         return null;
182       }
183     }
184   }
185
186   unreadOrAllRadios() {
187     return (
188       <div className="btn-group btn-group-toggle flex-wrap mb-2">
189         <label
190           className={`btn btn-outline-secondary pointer
191             ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
192           `}
193         >
194           <input
195             type="radio"
196             className="btn-check"
197             value={UnreadOrAll.Unread}
198             checked={this.state.unreadOrAll == UnreadOrAll.Unread}
199             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
200           />
201           {i18n.t("unread")}
202         </label>
203         <label
204           className={`btn btn-outline-secondary pointer
205             ${this.state.unreadOrAll == UnreadOrAll.All && "active"}
206           `}
207         >
208           <input
209             type="radio"
210             className="btn-check"
211             value={UnreadOrAll.All}
212             checked={this.state.unreadOrAll == UnreadOrAll.All}
213             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
214           />
215           {i18n.t("all")}
216         </label>
217       </div>
218     );
219   }
220
221   messageTypeRadios() {
222     return (
223       <div className="btn-group btn-group-toggle flex-wrap mb-2">
224         <label
225           className={`btn btn-outline-secondary pointer
226             ${this.state.messageType == MessageType.All && "active"}
227           `}
228         >
229           <input
230             type="radio"
231             className="btn-check"
232             value={MessageType.All}
233             checked={this.state.messageType == MessageType.All}
234             onChange={linkEvent(this, this.handleMessageTypeChange)}
235           />
236           {i18n.t("all")}
237         </label>
238         <label
239           className={`btn btn-outline-secondary pointer
240             ${this.state.messageType == MessageType.CommentReport && "active"}
241           `}
242         >
243           <input
244             type="radio"
245             className="btn-check"
246             value={MessageType.CommentReport}
247             checked={this.state.messageType == MessageType.CommentReport}
248             onChange={linkEvent(this, this.handleMessageTypeChange)}
249           />
250           {i18n.t("comments")}
251         </label>
252         <label
253           className={`btn btn-outline-secondary pointer
254             ${this.state.messageType == MessageType.PostReport && "active"}
255           `}
256         >
257           <input
258             type="radio"
259             className="btn-check"
260             value={MessageType.PostReport}
261             checked={this.state.messageType == MessageType.PostReport}
262             onChange={linkEvent(this, this.handleMessageTypeChange)}
263           />
264           {i18n.t("posts")}
265         </label>
266         {amAdmin() && (
267           <label
268             className={`btn btn-outline-secondary pointer
269             ${
270               this.state.messageType == MessageType.PrivateMessageReport &&
271               "active"
272             }
273           `}
274           >
275             <input
276               type="radio"
277               className="btn-check"
278               value={MessageType.PrivateMessageReport}
279               checked={
280                 this.state.messageType == MessageType.PrivateMessageReport
281               }
282               onChange={linkEvent(this, this.handleMessageTypeChange)}
283             />
284             {i18n.t("messages")}
285           </label>
286         )}
287       </div>
288     );
289   }
290
291   selects() {
292     return (
293       <div className="mb-2">
294         <span className="me-3">{this.unreadOrAllRadios()}</span>
295         <span className="me-3">{this.messageTypeRadios()}</span>
296       </div>
297     );
298   }
299
300   commentReportToItemType(r: CommentReportView): ItemType {
301     return {
302       id: r.comment_report.id,
303       type_: MessageEnum.CommentReport,
304       view: r,
305       published: r.comment_report.published,
306     };
307   }
308
309   postReportToItemType(r: PostReportView): ItemType {
310     return {
311       id: r.post_report.id,
312       type_: MessageEnum.PostReport,
313       view: r,
314       published: r.post_report.published,
315     };
316   }
317
318   privateMessageReportToItemType(r: PrivateMessageReportView): ItemType {
319     return {
320       id: r.private_message_report.id,
321       type_: MessageEnum.PrivateMessageReport,
322       view: r,
323       published: r.private_message_report.published,
324     };
325   }
326
327   get buildCombined(): ItemType[] {
328     const commentRes = this.state.commentReportsRes;
329     const comments =
330       commentRes.state == "success"
331         ? commentRes.data.comment_reports.map(this.commentReportToItemType)
332         : [];
333
334     const postRes = this.state.postReportsRes;
335     const posts =
336       postRes.state == "success"
337         ? postRes.data.post_reports.map(this.postReportToItemType)
338         : [];
339     const pmRes = this.state.messageReportsRes;
340     const privateMessages =
341       pmRes.state == "success"
342         ? pmRes.data.private_message_reports.map(
343             this.privateMessageReportToItemType
344           )
345         : [];
346
347     return [...comments, ...posts, ...privateMessages].sort((a, b) =>
348       b.published.localeCompare(a.published)
349     );
350   }
351
352   renderItemType(i: ItemType) {
353     switch (i.type_) {
354       case MessageEnum.CommentReport:
355         return (
356           <CommentReport
357             key={i.id}
358             report={i.view as CommentReportView}
359             onResolveReport={this.handleResolveCommentReport}
360           />
361         );
362       case MessageEnum.PostReport:
363         return (
364           <PostReport
365             key={i.id}
366             report={i.view as PostReportView}
367             onResolveReport={this.handleResolvePostReport}
368           />
369         );
370       case MessageEnum.PrivateMessageReport:
371         return (
372           <PrivateMessageReport
373             key={i.id}
374             report={i.view as PrivateMessageReportView}
375             onResolveReport={this.handleResolvePrivateMessageReport}
376           />
377         );
378       default:
379         return <div />;
380     }
381   }
382
383   all() {
384     return (
385       <div>
386         {this.buildCombined.map(i => (
387           <>
388             <hr />
389             {this.renderItemType(i)}
390           </>
391         ))}
392       </div>
393     );
394   }
395
396   commentReports() {
397     const res = this.state.commentReportsRes;
398     switch (res.state) {
399       case "loading":
400         return (
401           <h5>
402             <Spinner large />
403           </h5>
404         );
405       case "success": {
406         const reports = res.data.comment_reports;
407         return (
408           <div>
409             {reports.map(cr => (
410               <>
411                 <hr />
412                 <CommentReport
413                   key={cr.comment_report.id}
414                   report={cr}
415                   onResolveReport={this.handleResolveCommentReport}
416                 />
417               </>
418             ))}
419           </div>
420         );
421       }
422     }
423   }
424
425   postReports() {
426     const res = this.state.postReportsRes;
427     switch (res.state) {
428       case "loading":
429         return (
430           <h5>
431             <Spinner large />
432           </h5>
433         );
434       case "success": {
435         const reports = res.data.post_reports;
436         return (
437           <div>
438             {reports.map(pr => (
439               <>
440                 <hr />
441                 <PostReport
442                   key={pr.post_report.id}
443                   report={pr}
444                   onResolveReport={this.handleResolvePostReport}
445                 />
446               </>
447             ))}
448           </div>
449         );
450       }
451     }
452   }
453
454   privateMessageReports() {
455     const res = this.state.messageReportsRes;
456     switch (res.state) {
457       case "loading":
458         return (
459           <h5>
460             <Spinner large />
461           </h5>
462         );
463       case "success": {
464         const reports = res.data.private_message_reports;
465         return (
466           <div>
467             {reports.map(pmr => (
468               <>
469                 <hr />
470                 <PrivateMessageReport
471                   key={pmr.private_message_report.id}
472                   report={pmr}
473                   onResolveReport={this.handleResolvePrivateMessageReport}
474                 />
475               </>
476             ))}
477           </div>
478         );
479       }
480     }
481   }
482
483   async handlePageChange(page: number) {
484     this.setState({ page });
485     await this.refetch();
486   }
487
488   async handleUnreadOrAllChange(i: Reports, event: any) {
489     i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
490     await i.refetch();
491   }
492
493   async handleMessageTypeChange(i: Reports, event: any) {
494     i.setState({ messageType: Number(event.target.value), page: 1 });
495     await i.refetch();
496   }
497
498   static async fetchInitialData({
499     auth,
500     client,
501   }: InitialFetchRequest): Promise<ReportsData> {
502     const unresolved_only = true;
503     const page = 1;
504     const limit = fetchLimit;
505
506     const commentReportsForm: ListCommentReports = {
507       unresolved_only,
508       page,
509       limit,
510       auth: auth as string,
511     };
512
513     const postReportsForm: ListPostReports = {
514       unresolved_only,
515       page,
516       limit,
517       auth: auth as string,
518     };
519
520     const data: ReportsData = {
521       commentReportsRes: await client.listCommentReports(commentReportsForm),
522       postReportsRes: await client.listPostReports(postReportsForm),
523       messageReportsRes: { state: "empty" },
524     };
525
526     if (amAdmin()) {
527       const privateMessageReportsForm: ListPrivateMessageReports = {
528         unresolved_only,
529         page,
530         limit,
531         auth: auth as string,
532       };
533
534       data.messageReportsRes = await client.listPrivateMessageReports(
535         privateMessageReportsForm
536       );
537     }
538
539     return data;
540   }
541
542   async refetch() {
543     const unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread;
544     const page = this.state.page;
545     const limit = fetchLimit;
546     const auth = myAuthRequired();
547
548     this.setState({
549       commentReportsRes: { state: "loading" },
550       postReportsRes: { state: "loading" },
551       messageReportsRes: { state: "loading" },
552     });
553
554     const form:
555       | ListCommentReports
556       | ListPostReports
557       | ListPrivateMessageReports = {
558       unresolved_only,
559       page,
560       limit,
561       auth,
562     };
563
564     this.setState({
565       commentReportsRes: await HttpService.client.listCommentReports(form),
566       postReportsRes: await HttpService.client.listPostReports(form),
567     });
568
569     if (amAdmin()) {
570       this.setState({
571         messageReportsRes: await HttpService.client.listPrivateMessageReports(
572           form
573         ),
574       });
575     }
576   }
577
578   async handleResolveCommentReport(form: ResolveCommentReport) {
579     const res = await HttpService.client.resolveCommentReport(form);
580     this.findAndUpdateCommentReport(res);
581   }
582
583   async handleResolvePostReport(form: ResolvePostReport) {
584     const res = await HttpService.client.resolvePostReport(form);
585     this.findAndUpdatePostReport(res);
586   }
587
588   async handleResolvePrivateMessageReport(form: ResolvePrivateMessageReport) {
589     const res = await HttpService.client.resolvePrivateMessageReport(form);
590     this.findAndUpdatePrivateMessageReport(res);
591   }
592
593   findAndUpdateCommentReport(res: RequestState<CommentReportResponse>) {
594     this.setState(s => {
595       if (s.commentReportsRes.state == "success" && res.state == "success") {
596         s.commentReportsRes.data.comment_reports = editCommentReport(
597           res.data.comment_report_view,
598           s.commentReportsRes.data.comment_reports
599         );
600       }
601       return s;
602     });
603   }
604
605   findAndUpdatePostReport(res: RequestState<PostReportResponse>) {
606     this.setState(s => {
607       if (s.postReportsRes.state == "success" && res.state == "success") {
608         s.postReportsRes.data.post_reports = editPostReport(
609           res.data.post_report_view,
610           s.postReportsRes.data.post_reports
611         );
612       }
613       return s;
614     });
615   }
616
617   findAndUpdatePrivateMessageReport(
618     res: RequestState<PrivateMessageReportResponse>
619   ) {
620     this.setState(s => {
621       if (s.messageReportsRes.state == "success" && res.state == "success") {
622         s.messageReportsRes.data.private_message_reports =
623           editPrivateMessageReport(
624             res.data.private_message_report_view,
625             s.messageReportsRes.data.private_message_reports
626           );
627       }
628       return s;
629     });
630   }
631 }