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