]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/reports.tsx
0be753791d4bb192456a74f5188319d960852041
[lemmy-ui.git] / src / shared / components / person / reports.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   CommentReportResponse,
4   CommentReportView,
5   GetSiteResponse,
6   ListCommentReports,
7   ListCommentReportsResponse,
8   ListPostReports,
9   ListPostReportsResponse,
10   ListPrivateMessageReports,
11   ListPrivateMessageReportsResponse,
12   PostReportResponse,
13   PostReportView,
14   PrivateMessageReportResponse,
15   PrivateMessageReportView,
16   ResolveCommentReport,
17   ResolvePostReport,
18   ResolvePrivateMessageReport,
19 } from "lemmy-js-client";
20 import { i18n } from "../../i18next";
21 import { InitialFetchRequest } from "../../interfaces";
22 import { HttpService, UserService } from "../../services";
23 import { FirstLoadService } from "../../services/FirstLoadService";
24 import { RequestState } from "../../services/HttpService";
25 import {
26   RouteDataResponse,
27   editCommentReport,
28   editPostReport,
29   editPrivateMessageReport,
30   fetchLimit,
31   myAuthRequired,
32   setIsoData,
33 } from "../../utils";
34 import { amAdmin } from "../../utils/roles/am-admin";
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="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             value={UnreadOrAll.Unread}
197             checked={this.state.unreadOrAll == UnreadOrAll.Unread}
198             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
199           />
200           {i18n.t("unread")}
201         </label>
202         <label
203           className={`btn btn-outline-secondary pointer
204             ${this.state.unreadOrAll == UnreadOrAll.All && "active"}
205           `}
206         >
207           <input
208             type="radio"
209             value={UnreadOrAll.All}
210             checked={this.state.unreadOrAll == UnreadOrAll.All}
211             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
212           />
213           {i18n.t("all")}
214         </label>
215       </div>
216     );
217   }
218
219   messageTypeRadios() {
220     return (
221       <div className="btn-group btn-group-toggle flex-wrap mb-2">
222         <label
223           className={`btn btn-outline-secondary pointer
224             ${this.state.messageType == MessageType.All && "active"}
225           `}
226         >
227           <input
228             type="radio"
229             value={MessageType.All}
230             checked={this.state.messageType == MessageType.All}
231             onChange={linkEvent(this, this.handleMessageTypeChange)}
232           />
233           {i18n.t("all")}
234         </label>
235         <label
236           className={`btn btn-outline-secondary pointer
237             ${this.state.messageType == MessageType.CommentReport && "active"}
238           `}
239         >
240           <input
241             type="radio"
242             value={MessageType.CommentReport}
243             checked={this.state.messageType == MessageType.CommentReport}
244             onChange={linkEvent(this, this.handleMessageTypeChange)}
245           />
246           {i18n.t("comments")}
247         </label>
248         <label
249           className={`btn btn-outline-secondary pointer
250             ${this.state.messageType == MessageType.PostReport && "active"}
251           `}
252         >
253           <input
254             type="radio"
255             value={MessageType.PostReport}
256             checked={this.state.messageType == MessageType.PostReport}
257             onChange={linkEvent(this, this.handleMessageTypeChange)}
258           />
259           {i18n.t("posts")}
260         </label>
261         {amAdmin() && (
262           <label
263             className={`btn btn-outline-secondary pointer
264             ${
265               this.state.messageType == MessageType.PrivateMessageReport &&
266               "active"
267             }
268           `}
269           >
270             <input
271               type="radio"
272               value={MessageType.PrivateMessageReport}
273               checked={
274                 this.state.messageType == MessageType.PrivateMessageReport
275               }
276               onChange={linkEvent(this, this.handleMessageTypeChange)}
277             />
278             {i18n.t("messages")}
279           </label>
280         )}
281       </div>
282     );
283   }
284
285   selects() {
286     return (
287       <div className="mb-2">
288         <span className="mr-3">{this.unreadOrAllRadios()}</span>
289         <span className="mr-3">{this.messageTypeRadios()}</span>
290       </div>
291     );
292   }
293
294   commentReportToItemType(r: CommentReportView): ItemType {
295     return {
296       id: r.comment_report.id,
297       type_: MessageEnum.CommentReport,
298       view: r,
299       published: r.comment_report.published,
300     };
301   }
302
303   postReportToItemType(r: PostReportView): ItemType {
304     return {
305       id: r.post_report.id,
306       type_: MessageEnum.PostReport,
307       view: r,
308       published: r.post_report.published,
309     };
310   }
311
312   privateMessageReportToItemType(r: PrivateMessageReportView): ItemType {
313     return {
314       id: r.private_message_report.id,
315       type_: MessageEnum.PrivateMessageReport,
316       view: r,
317       published: r.private_message_report.published,
318     };
319   }
320
321   get buildCombined(): ItemType[] {
322     const commentRes = this.state.commentReportsRes;
323     const comments =
324       commentRes.state == "success"
325         ? commentRes.data.comment_reports.map(this.commentReportToItemType)
326         : [];
327
328     const postRes = this.state.postReportsRes;
329     const posts =
330       postRes.state == "success"
331         ? postRes.data.post_reports.map(this.postReportToItemType)
332         : [];
333     const pmRes = this.state.messageReportsRes;
334     const privateMessages =
335       pmRes.state == "success"
336         ? pmRes.data.private_message_reports.map(
337             this.privateMessageReportToItemType
338           )
339         : [];
340
341     return [...comments, ...posts, ...privateMessages].sort((a, b) =>
342       b.published.localeCompare(a.published)
343     );
344   }
345
346   renderItemType(i: ItemType) {
347     switch (i.type_) {
348       case MessageEnum.CommentReport:
349         return (
350           <CommentReport
351             key={i.id}
352             report={i.view as CommentReportView}
353             onResolveReport={this.handleResolveCommentReport}
354           />
355         );
356       case MessageEnum.PostReport:
357         return (
358           <PostReport
359             key={i.id}
360             report={i.view as PostReportView}
361             onResolveReport={this.handleResolvePostReport}
362           />
363         );
364       case MessageEnum.PrivateMessageReport:
365         return (
366           <PrivateMessageReport
367             key={i.id}
368             report={i.view as PrivateMessageReportView}
369             onResolveReport={this.handleResolvePrivateMessageReport}
370           />
371         );
372       default:
373         return <div />;
374     }
375   }
376
377   all() {
378     return (
379       <div>
380         {this.buildCombined.map(i => (
381           <>
382             <hr />
383             {this.renderItemType(i)}
384           </>
385         ))}
386       </div>
387     );
388   }
389
390   commentReports() {
391     const res = this.state.commentReportsRes;
392     switch (res.state) {
393       case "loading":
394         return (
395           <h5>
396             <Spinner large />
397           </h5>
398         );
399       case "success": {
400         const reports = res.data.comment_reports;
401         return (
402           <div>
403             {reports.map(cr => (
404               <>
405                 <hr />
406                 <CommentReport
407                   key={cr.comment_report.id}
408                   report={cr}
409                   onResolveReport={this.handleResolveCommentReport}
410                 />
411               </>
412             ))}
413           </div>
414         );
415       }
416     }
417   }
418
419   postReports() {
420     const res = this.state.postReportsRes;
421     switch (res.state) {
422       case "loading":
423         return (
424           <h5>
425             <Spinner large />
426           </h5>
427         );
428       case "success": {
429         const reports = res.data.post_reports;
430         return (
431           <div>
432             {reports.map(pr => (
433               <>
434                 <hr />
435                 <PostReport
436                   key={pr.post_report.id}
437                   report={pr}
438                   onResolveReport={this.handleResolvePostReport}
439                 />
440               </>
441             ))}
442           </div>
443         );
444       }
445     }
446   }
447
448   privateMessageReports() {
449     const res = this.state.messageReportsRes;
450     switch (res.state) {
451       case "loading":
452         return (
453           <h5>
454             <Spinner large />
455           </h5>
456         );
457       case "success": {
458         const reports = res.data.private_message_reports;
459         return (
460           <div>
461             {reports.map(pmr => (
462               <>
463                 <hr />
464                 <PrivateMessageReport
465                   key={pmr.private_message_report.id}
466                   report={pmr}
467                   onResolveReport={this.handleResolvePrivateMessageReport}
468                 />
469               </>
470             ))}
471           </div>
472         );
473       }
474     }
475   }
476
477   async handlePageChange(page: number) {
478     this.setState({ page });
479     await this.refetch();
480   }
481
482   async handleUnreadOrAllChange(i: Reports, event: any) {
483     i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
484     await i.refetch();
485   }
486
487   async handleMessageTypeChange(i: Reports, event: any) {
488     i.setState({ messageType: Number(event.target.value), page: 1 });
489     await i.refetch();
490   }
491
492   static async fetchInitialData({
493     auth,
494     client,
495   }: InitialFetchRequest): Promise<ReportsData> {
496     const unresolved_only = true;
497     const page = 1;
498     const limit = fetchLimit;
499
500     const commentReportsForm: ListCommentReports = {
501       unresolved_only,
502       page,
503       limit,
504       auth: auth as string,
505     };
506
507     const postReportsForm: ListPostReports = {
508       unresolved_only,
509       page,
510       limit,
511       auth: auth as string,
512     };
513
514     const data: ReportsData = {
515       commentReportsRes: await client.listCommentReports(commentReportsForm),
516       postReportsRes: await client.listPostReports(postReportsForm),
517       messageReportsRes: { state: "empty" },
518     };
519
520     if (amAdmin()) {
521       const privateMessageReportsForm: ListPrivateMessageReports = {
522         unresolved_only,
523         page,
524         limit,
525         auth: auth as string,
526       };
527
528       data.messageReportsRes = await client.listPrivateMessageReports(
529         privateMessageReportsForm
530       );
531     }
532
533     return data;
534   }
535
536   async refetch() {
537     const unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread;
538     const page = this.state.page;
539     const limit = fetchLimit;
540     const auth = myAuthRequired();
541
542     this.setState({
543       commentReportsRes: { state: "loading" },
544       postReportsRes: { state: "loading" },
545       messageReportsRes: { state: "loading" },
546     });
547
548     const form:
549       | ListCommentReports
550       | ListPostReports
551       | ListPrivateMessageReports = {
552       unresolved_only,
553       page,
554       limit,
555       auth,
556     };
557
558     this.setState({
559       commentReportsRes: await HttpService.client.listCommentReports(form),
560       postReportsRes: await HttpService.client.listPostReports(form),
561     });
562
563     if (amAdmin()) {
564       this.setState({
565         messageReportsRes: await HttpService.client.listPrivateMessageReports(
566           form
567         ),
568       });
569     }
570   }
571
572   async handleResolveCommentReport(form: ResolveCommentReport) {
573     const res = await HttpService.client.resolveCommentReport(form);
574     this.findAndUpdateCommentReport(res);
575   }
576
577   async handleResolvePostReport(form: ResolvePostReport) {
578     const res = await HttpService.client.resolvePostReport(form);
579     this.findAndUpdatePostReport(res);
580   }
581
582   async handleResolvePrivateMessageReport(form: ResolvePrivateMessageReport) {
583     const res = await HttpService.client.resolvePrivateMessageReport(form);
584     this.findAndUpdatePrivateMessageReport(res);
585   }
586
587   findAndUpdateCommentReport(res: RequestState<CommentReportResponse>) {
588     this.setState(s => {
589       if (s.commentReportsRes.state == "success" && res.state == "success") {
590         s.commentReportsRes.data.comment_reports = editCommentReport(
591           res.data.comment_report_view,
592           s.commentReportsRes.data.comment_reports
593         );
594       }
595       return s;
596     });
597   }
598
599   findAndUpdatePostReport(res: RequestState<PostReportResponse>) {
600     this.setState(s => {
601       if (s.postReportsRes.state == "success" && res.state == "success") {
602         s.postReportsRes.data.post_reports = editPostReport(
603           res.data.post_report_view,
604           s.postReportsRes.data.post_reports
605         );
606       }
607       return s;
608     });
609   }
610
611   findAndUpdatePrivateMessageReport(
612     res: RequestState<PrivateMessageReportResponse>
613   ) {
614     this.setState(s => {
615       if (s.messageReportsRes.state == "success" && res.state == "success") {
616         s.messageReportsRes.data.private_message_reports =
617           editPrivateMessageReport(
618             res.data.private_message_report_view,
619             s.messageReportsRes.data.private_message_reports
620           );
621       }
622       return s;
623     });
624   }
625 }