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