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