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