]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/person/reports.tsx
Merge branch 'main' into route-data-refactor
[lemmy-ui.git] / src / shared / components / person / reports.tsx
index 99edf968130c41f42b54fa528e12d30af969c71c..fb8e8b831870bacc68daada998c165759753ed69 100644 (file)
@@ -2,38 +2,42 @@ import { Component, linkEvent } from "inferno";
 import {
   CommentReportResponse,
   CommentReportView,
+  GetSiteResponse,
   ListCommentReports,
   ListCommentReportsResponse,
   ListPostReports,
   ListPostReportsResponse,
+  ListPrivateMessageReports,
+  ListPrivateMessageReportsResponse,
   PostReportResponse,
   PostReportView,
-  SiteView,
-  UserOperation,
+  PrivateMessageReportResponse,
+  PrivateMessageReportView,
+  ResolveCommentReport,
+  ResolvePostReport,
+  ResolvePrivateMessageReport,
 } from "lemmy-js-client";
-import { Subscription } from "rxjs";
 import { i18n } from "../../i18next";
 import { InitialFetchRequest } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { HttpService, UserService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { RequestState } from "../../services/HttpService";
 import {
-  authField,
+  RouteDataResponse,
+  amAdmin,
+  editCommentReport,
+  editPostReport,
+  editPrivateMessageReport,
   fetchLimit,
-  isBrowser,
+  myAuthRequired,
   setIsoData,
-  setupTippy,
-  toast,
-  updateCommentReportRes,
-  updatePostReportRes,
-  wsClient,
-  wsJsonToRes,
-  wsSubscribe,
-  wsUserOp,
 } from "../../utils";
 import { CommentReport } from "../comment/comment-report";
 import { HtmlTags } from "../common/html-tags";
 import { Spinner } from "../common/icon";
 import { Paginator } from "../common/paginator";
 import { PostReport } from "../post/post-report";
+import { PrivateMessageReport } from "../private_message/private-message-report";
 
 enum UnreadOrAll {
   Unread,
@@ -44,118 +48,147 @@ enum MessageType {
   All,
   CommentReport,
   PostReport,
+  PrivateMessageReport,
 }
 
 enum MessageEnum {
   CommentReport,
   PostReport,
+  PrivateMessageReport,
 }
 
+type ReportsData = RouteDataResponse<{
+  commentReportsResponse: ListCommentReportsResponse;
+  postReportsResponse: ListPostReportsResponse;
+  privateMessageReportsResponse?: ListPrivateMessageReportsResponse;
+}>;
+
 type ItemType = {
   id: number;
   type_: MessageEnum;
-  view: CommentReportView | PostReportView;
+  view: CommentReportView | PostReportView | PrivateMessageReportView;
   published: string;
 };
 
 interface ReportsState {
+  commentReportsRes: RequestState<ListCommentReportsResponse>;
+  postReportsRes: RequestState<ListPostReportsResponse>;
+  messageReportsRes: RequestState<ListPrivateMessageReportsResponse>;
   unreadOrAll: UnreadOrAll;
   messageType: MessageType;
-  commentReports: CommentReportView[];
-  postReports: PostReportView[];
-  combined: ItemType[];
+  siteRes: GetSiteResponse;
   page: number;
-  site_view: SiteView;
-  loading: boolean;
+  isIsomorphic: boolean;
 }
 
 export class Reports extends Component<any, ReportsState> {
-  private isoData = setIsoData(this.context);
-  private subscription: Subscription;
-  private emptyState: ReportsState = {
+  private isoData = setIsoData<ReportsData>(this.context);
+  state: ReportsState = {
+    commentReportsRes: { state: "empty" },
+    postReportsRes: { state: "empty" },
+    messageReportsRes: { state: "empty" },
     unreadOrAll: UnreadOrAll.Unread,
     messageType: MessageType.All,
-    commentReports: [],
-    postReports: [],
-    combined: [],
     page: 1,
-    site_view: this.isoData.site_res.site_view,
-    loading: true,
+    siteRes: this.isoData.site_res,
+    isIsomorphic: false,
   };
 
   constructor(props: any, context: any) {
     super(props, context);
 
-    this.state = this.emptyState;
     this.handlePageChange = this.handlePageChange.bind(this);
-
-    if (!UserService.Instance.myUserInfo && isBrowser()) {
-      toast(i18n.t("not_logged_in"), "danger");
-      this.context.router.history.push(`/login`);
-    }
-
-    this.parseMessage = this.parseMessage.bind(this);
-    this.subscription = wsSubscribe(this.parseMessage);
+    this.handleResolveCommentReport =
+      this.handleResolveCommentReport.bind(this);
+    this.handleResolvePostReport = this.handleResolvePostReport.bind(this);
+    this.handleResolvePrivateMessageReport =
+      this.handleResolvePrivateMessageReport.bind(this);
 
     // Only fetch the data if coming from another route
-    if (this.isoData.path == this.context.router.route.match.url) {
-      this.state.commentReports =
-        this.isoData.routeData[0].comment_reports || [];
-      this.state.postReports = this.isoData.routeData[1].post_reports || [];
-      this.state.combined = this.buildCombined();
-      this.state.loading = false;
-    } else {
-      this.refetch();
+    if (FirstLoadService.isFirstLoad) {
+      const {
+        commentReportsResponse: commentReportsRes,
+        postReportsResponse: postReportsRes,
+        privateMessageReportsResponse: messageReportsRes,
+      } = this.isoData.routeData;
+
+      this.state = {
+        ...this.state,
+        commentReportsRes,
+        postReportsRes,
+        isIsomorphic: true,
+      };
+
+      if (amAdmin()) {
+        this.state = {
+          ...this.state,
+          messageReportsRes: messageReportsRes ?? { state: "empty" },
+        };
+      }
     }
   }
 
-  componentWillUnmount() {
-    if (isBrowser()) {
-      this.subscription.unsubscribe();
+  async componentDidMount() {
+    if (!this.state.isIsomorphic) {
+      await this.refetch();
     }
   }
 
   get documentTitle(): string {
-    return `@${
-      UserService.Instance.myUserInfo.local_user_view.person.name
-    } ${i18n.t("reports")} - ${this.state.site_view.site.name}`;
+    const mui = UserService.Instance.myUserInfo;
+    return mui
+      ? `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${
+          this.state.siteRes.site_view.site.name
+        }`
+      : "";
   }
 
   render() {
     return (
-      <div class="container">
-        {this.state.loading ? (
-          <h5>
-            <Spinner large />
-          </h5>
-        ) : (
-          <div class="row">
-            <div class="col-12">
-              <HtmlTags
-                title={this.documentTitle}
-                path={this.context.router.route.match.url}
-              />
-              <h5 class="mb-2">{i18n.t("reports")}</h5>
-              {this.selects()}
-              {this.state.messageType == MessageType.All && this.all()}
-              {this.state.messageType == MessageType.CommentReport &&
-                this.commentReports()}
-              {this.state.messageType == MessageType.PostReport &&
-                this.postReports()}
-              <Paginator
-                page={this.state.page}
-                onChange={this.handlePageChange}
-              />
-            </div>
+      <div className="container-lg">
+        <div className="row">
+          <div className="col-12">
+            <HtmlTags
+              title={this.documentTitle}
+              path={this.context.router.route.match.url}
+            />
+            <h5 className="mb-2">{i18n.t("reports")}</h5>
+            {this.selects()}
+            {this.section}
+            <Paginator
+              page={this.state.page}
+              onChange={this.handlePageChange}
+            />
           </div>
-        )}
+        </div>
       </div>
     );
   }
 
+  get section() {
+    switch (this.state.messageType) {
+      case MessageType.All: {
+        return this.all();
+      }
+      case MessageType.CommentReport: {
+        return this.commentReports();
+      }
+      case MessageType.PostReport: {
+        return this.postReports();
+      }
+      case MessageType.PrivateMessageReport: {
+        return this.privateMessageReports();
+      }
+
+      default: {
+        return null;
+      }
+    }
+  }
+
   unreadOrAllRadios() {
     return (
-      <div class="btn-group btn-group-toggle flex-wrap mb-2">
+      <div className="btn-group btn-group-toggle flex-wrap mb-2">
         <label
           className={`btn btn-outline-secondary pointer
             ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
@@ -188,7 +221,7 @@ export class Reports extends Component<any, ReportsState> {
 
   messageTypeRadios() {
     return (
-      <div class="btn-group btn-group-toggle flex-wrap mb-2">
+      <div className="btn-group btn-group-toggle flex-wrap mb-2">
         <label
           className={`btn btn-outline-secondary pointer
             ${this.state.messageType == MessageType.All && "active"}
@@ -228,6 +261,26 @@ export class Reports extends Component<any, ReportsState> {
           />
           {i18n.t("posts")}
         </label>
+        {amAdmin() && (
+          <label
+            className={`btn btn-outline-secondary pointer
+            ${
+              this.state.messageType == MessageType.PrivateMessageReport &&
+              "active"
+            }
+          `}
+          >
+            <input
+              type="radio"
+              value={MessageType.PrivateMessageReport}
+              checked={
+                this.state.messageType == MessageType.PrivateMessageReport
+              }
+              onChange={linkEvent(this, this.handleMessageTypeChange)}
+            />
+            {i18n.t("messages")}
+          </label>
+        )}
       </div>
     );
   }
@@ -235,13 +288,13 @@ export class Reports extends Component<any, ReportsState> {
   selects() {
     return (
       <div className="mb-2">
-        <span class="mr-3">{this.unreadOrAllRadios()}</span>
-        <span class="mr-3">{this.messageTypeRadios()}</span>
+        <span className="mr-3">{this.unreadOrAllRadios()}</span>
+        <span className="mr-3">{this.messageTypeRadios()}</span>
       </div>
     );
   }
 
-  replyToReplyType(r: CommentReportView): ItemType {
+  commentReportToItemType(r: CommentReportView): ItemType {
     return {
       id: r.comment_report.id,
       type_: MessageEnum.CommentReport,
@@ -250,7 +303,7 @@ export class Reports extends Component<any, ReportsState> {
     };
   }
 
-  mentionToReplyType(r: PostReportView): ItemType {
+  postReportToItemType(r: PostReportView): ItemType {
     return {
       id: r.post_report.id,
       type_: MessageEnum.PostReport,
@@ -259,15 +312,36 @@ export class Reports extends Component<any, ReportsState> {
     };
   }
 
-  buildCombined(): ItemType[] {
-    let comments: ItemType[] = this.state.commentReports.map(r =>
-      this.replyToReplyType(r)
-    );
-    let posts: ItemType[] = this.state.postReports.map(r =>
-      this.mentionToReplyType(r)
-    );
+  privateMessageReportToItemType(r: PrivateMessageReportView): ItemType {
+    return {
+      id: r.private_message_report.id,
+      type_: MessageEnum.PrivateMessageReport,
+      view: r,
+      published: r.private_message_report.published,
+    };
+  }
 
-    return [...comments, ...posts].sort((a, b) =>
+  get buildCombined(): ItemType[] {
+    const commentRes = this.state.commentReportsRes;
+    const comments =
+      commentRes.state == "success"
+        ? commentRes.data.comment_reports.map(this.commentReportToItemType)
+        : [];
+
+    const postRes = this.state.postReportsRes;
+    const posts =
+      postRes.state == "success"
+        ? postRes.data.post_reports.map(this.postReportToItemType)
+        : [];
+    const pmRes = this.state.messageReportsRes;
+    const privateMessages =
+      pmRes.state == "success"
+        ? pmRes.data.private_message_reports.map(
+            this.privateMessageReportToItemType
+          )
+        : [];
+
+    return [...comments, ...posts, ...privateMessages].sort((a, b) =>
       b.published.localeCompare(a.published)
     );
   }
@@ -276,10 +350,28 @@ export class Reports extends Component<any, ReportsState> {
     switch (i.type_) {
       case MessageEnum.CommentReport:
         return (
-          <CommentReport key={i.id} report={i.view as CommentReportView} />
+          <CommentReport
+            key={i.id}
+            report={i.view as CommentReportView}
+            onResolveReport={this.handleResolveCommentReport}
+          />
         );
       case MessageEnum.PostReport:
-        return <PostReport key={i.id} report={i.view as PostReportView} />;
+        return (
+          <PostReport
+            key={i.id}
+            report={i.view as PostReportView}
+            onResolveReport={this.handleResolvePostReport}
+          />
+        );
+      case MessageEnum.PrivateMessageReport:
+        return (
+          <PrivateMessageReport
+            key={i.id}
+            report={i.view as PrivateMessageReportView}
+            onResolveReport={this.handleResolvePrivateMessageReport}
+          />
+        );
       default:
         return <div />;
     }
@@ -288,7 +380,7 @@ export class Reports extends Component<any, ReportsState> {
   all() {
     return (
       <div>
-        {this.state.combined.map(i => (
+        {this.buildCombined.map(i => (
           <>
             <hr />
             {this.renderItemType(i)}
@@ -299,146 +391,238 @@ export class Reports extends Component<any, ReportsState> {
   }
 
   commentReports() {
-    return (
-      <div>
-        {this.state.commentReports.map(cr => (
-          <>
-            <hr />
-            <CommentReport key={cr.comment_report.id} report={cr} />
-          </>
-        ))}
-      </div>
-    );
+    const res = this.state.commentReportsRes;
+    switch (res.state) {
+      case "loading":
+        return (
+          <h5>
+            <Spinner large />
+          </h5>
+        );
+      case "success": {
+        const reports = res.data.comment_reports;
+        return (
+          <div>
+            {reports.map(cr => (
+              <>
+                <hr />
+                <CommentReport
+                  key={cr.comment_report.id}
+                  report={cr}
+                  onResolveReport={this.handleResolveCommentReport}
+                />
+              </>
+            ))}
+          </div>
+        );
+      }
+    }
   }
 
   postReports() {
-    return (
-      <div>
-        {this.state.postReports.map(pr => (
-          <>
-            <hr />
-            <PostReport key={pr.post_report.id} report={pr} />
-          </>
-        ))}
-      </div>
-    );
+    const res = this.state.postReportsRes;
+    switch (res.state) {
+      case "loading":
+        return (
+          <h5>
+            <Spinner large />
+          </h5>
+        );
+      case "success": {
+        const reports = res.data.post_reports;
+        return (
+          <div>
+            {reports.map(pr => (
+              <>
+                <hr />
+                <PostReport
+                  key={pr.post_report.id}
+                  report={pr}
+                  onResolveReport={this.handleResolvePostReport}
+                />
+              </>
+            ))}
+          </div>
+        );
+      }
+    }
+  }
+
+  privateMessageReports() {
+    const res = this.state.messageReportsRes;
+    switch (res.state) {
+      case "loading":
+        return (
+          <h5>
+            <Spinner large />
+          </h5>
+        );
+      case "success": {
+        const reports = res.data.private_message_reports;
+        return (
+          <div>
+            {reports.map(pmr => (
+              <>
+                <hr />
+                <PrivateMessageReport
+                  key={pmr.private_message_report.id}
+                  report={pmr}
+                  onResolveReport={this.handleResolvePrivateMessageReport}
+                />
+              </>
+            ))}
+          </div>
+        );
+      }
+    }
   }
 
-  handlePageChange(page: number) {
+  async handlePageChange(page: number) {
     this.setState({ page });
-    this.refetch();
+    await this.refetch();
   }
 
-  handleUnreadOrAllChange(i: Reports, event: any) {
-    i.state.unreadOrAll = Number(event.target.value);
-    i.state.page = 1;
-    i.setState(i.state);
-    i.refetch();
+  async handleUnreadOrAllChange(i: Reports, event: any) {
+    i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
+    await i.refetch();
   }
 
-  handleMessageTypeChange(i: Reports, event: any) {
-    i.state.messageType = Number(event.target.value);
-    i.state.page = 1;
-    i.setState(i.state);
-    i.refetch();
+  async handleMessageTypeChange(i: Reports, event: any) {
+    i.setState({ messageType: Number(event.target.value), page: 1 });
+    await i.refetch();
   }
 
-  static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
-    let promises: Promise<any>[] = [];
+  static async fetchInitialData({
+    auth,
+    client,
+  }: InitialFetchRequest): Promise<ReportsData> {
+    const unresolved_only = true;
+    const page = 1;
+    const limit = fetchLimit;
 
-    let commentReportsForm: ListCommentReports = {
-      // TODO community_id
-      unresolved_only: true,
-      page: 1,
-      limit: fetchLimit,
-      auth: req.auth,
+    const commentReportsForm: ListCommentReports = {
+      unresolved_only,
+      page,
+      limit,
+      auth: auth as string,
     };
-    promises.push(req.client.listCommentReports(commentReportsForm));
-
-    let postReportsForm: ListPostReports = {
-      // TODO community_id
-      unresolved_only: true,
-      page: 1,
-      limit: fetchLimit,
-      auth: req.auth,
+
+    const postReportsForm: ListPostReports = {
+      unresolved_only,
+      page,
+      limit,
+      auth: auth as string,
     };
-    promises.push(req.client.listPostReports(postReportsForm));
 
-    return promises;
+    const data: ReportsData = {
+      commentReportsResponse: await client.listCommentReports(
+        commentReportsForm
+      ),
+      postReportsResponse: await client.listPostReports(postReportsForm),
+    };
+
+    if (amAdmin()) {
+      const privateMessageReportsForm: ListPrivateMessageReports = {
+        unresolved_only,
+        page,
+        limit,
+        auth: auth as string,
+      };
+
+      data.privateMessageReportsResponse =
+        await client.listPrivateMessageReports(privateMessageReportsForm);
+    }
+
+    return data;
   }
 
-  refetch() {
-    let unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread;
-    let commentReportsForm: ListCommentReports = {
-      // TODO community_id
+  async refetch() {
+    const unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread;
+    const page = this.state.page;
+    const limit = fetchLimit;
+    const auth = myAuthRequired();
+
+    this.setState({
+      commentReportsRes: { state: "loading" },
+      postReportsRes: { state: "loading" },
+      messageReportsRes: { state: "loading" },
+    });
+
+    const form:
+      | ListCommentReports
+      | ListPostReports
+      | ListPrivateMessageReports = {
       unresolved_only,
-      page: this.state.page,
-      limit: fetchLimit,
-      auth: authField(),
+      page,
+      limit,
+      auth,
     };
-    WebSocketService.Instance.send(
-      wsClient.listCommentReports(commentReportsForm)
-    );
 
-    let postReportsForm: ListPostReports = {
-      // TODO community_id
-      unresolved_only,
-      page: this.state.page,
-      limit: fetchLimit,
-      auth: authField(),
-    };
-    WebSocketService.Instance.send(wsClient.listPostReports(postReportsForm));
+    this.setState({
+      commentReportsRes: await HttpService.client.listCommentReports(form),
+      postReportsRes: await HttpService.client.listPostReports(form),
+    });
+
+    if (amAdmin()) {
+      this.setState({
+        messageReportsRes: await HttpService.client.listPrivateMessageReports(
+          form
+        ),
+      });
+    }
+  }
+
+  async handleResolveCommentReport(form: ResolveCommentReport) {
+    const res = await HttpService.client.resolveCommentReport(form);
+    this.findAndUpdateCommentReport(res);
+  }
+
+  async handleResolvePostReport(form: ResolvePostReport) {
+    const res = await HttpService.client.resolvePostReport(form);
+    this.findAndUpdatePostReport(res);
   }
 
-  parseMessage(msg: any) {
-    let op = wsUserOp(msg);
-    console.log(msg);
-    if (msg.error) {
-      toast(i18n.t(msg.error), "danger");
-      return;
-    } else if (msg.reconnect) {
-      this.refetch();
-    } else if (op == UserOperation.ListCommentReports) {
-      let data = wsJsonToRes<ListCommentReportsResponse>(msg).data;
-      this.state.commentReports = data.comment_reports;
-      this.state.combined = this.buildCombined();
-      this.state.loading = false;
-      // this.sendUnreadCount();
-      window.scrollTo(0, 0);
-      this.setState(this.state);
-      setupTippy();
-    } else if (op == UserOperation.ListPostReports) {
-      let data = wsJsonToRes<ListPostReportsResponse>(msg).data;
-      this.state.postReports = data.post_reports;
-      this.state.combined = this.buildCombined();
-      this.state.loading = false;
-      // this.sendUnreadCount();
-      window.scrollTo(0, 0);
-      this.setState(this.state);
-      setupTippy();
-    } else if (op == UserOperation.ResolvePostReport) {
-      let data = wsJsonToRes<PostReportResponse>(msg).data;
-      updatePostReportRes(data.post_report_view, this.state.postReports);
-      let urcs = UserService.Instance.unreadReportCountSub;
-      if (data.post_report_view.post_report.resolved) {
-        urcs.next(urcs.getValue() - 1);
-      } else {
-        urcs.next(urcs.getValue() + 1);
+  async handleResolvePrivateMessageReport(form: ResolvePrivateMessageReport) {
+    const res = await HttpService.client.resolvePrivateMessageReport(form);
+    this.findAndUpdatePrivateMessageReport(res);
+  }
+
+  findAndUpdateCommentReport(res: RequestState<CommentReportResponse>) {
+    this.setState(s => {
+      if (s.commentReportsRes.state == "success" && res.state == "success") {
+        s.commentReportsRes.data.comment_reports = editCommentReport(
+          res.data.comment_report_view,
+          s.commentReportsRes.data.comment_reports
+        );
       }
-      this.setState(this.state);
-    } else if (op == UserOperation.ResolveCommentReport) {
-      let data = wsJsonToRes<CommentReportResponse>(msg).data;
-      updateCommentReportRes(
-        data.comment_report_view,
-        this.state.commentReports
-      );
-      let urcs = UserService.Instance.unreadReportCountSub;
-      if (data.comment_report_view.comment_report.resolved) {
-        urcs.next(urcs.getValue() - 1);
-      } else {
-        urcs.next(urcs.getValue() + 1);
+      return s;
+    });
+  }
+
+  findAndUpdatePostReport(res: RequestState<PostReportResponse>) {
+    this.setState(s => {
+      if (s.postReportsRes.state == "success" && res.state == "success") {
+        s.postReportsRes.data.post_reports = editPostReport(
+          res.data.post_report_view,
+          s.postReportsRes.data.post_reports
+        );
       }
-      this.setState(this.state);
-    }
+      return s;
+    });
+  }
+
+  findAndUpdatePrivateMessageReport(
+    res: RequestState<PrivateMessageReportResponse>
+  ) {
+    this.setState(s => {
+      if (s.messageReportsRes.state == "success" && res.state == "success") {
+        s.messageReportsRes.data.private_message_reports =
+          editPrivateMessageReport(
+            res.data.private_message_report_view,
+            s.messageReportsRes.data.private_message_reports
+          );
+      }
+      return s;
+    });
   }
 }