From 9948783325ebfa2319faf44569086e49c6ec2ed9 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Thu, 9 Dec 2021 10:12:35 -0500
Subject: [PATCH 01/13] Updating translations.

---
 lemmy-translations | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lemmy-translations b/lemmy-translations
index 0412b6b34..abab502ec 160000
--- a/lemmy-translations
+++ b/lemmy-translations
@@ -1 +1 @@
-Subproject commit 0412b6b349e5e8d6ac3ed88801187833e95c72c9
+Subproject commit abab502ec9d7164d7c55834d6b0de4250d491253

From 1683a6b857aa0541274fcb9622f4996cccb84520 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Thu, 9 Dec 2021 11:39:09 -0500
Subject: [PATCH 02/13] Adding registration applications.

---
 package.json                                  |   2 +-
 src/shared/components/app/navbar.tsx          |  86 +++++-
 ...{comment_report.tsx => comment-report.tsx} |  11 +-
 .../components/common/markdown-textarea.tsx   |   2 +-
 .../common/registration-application.tsx       | 129 +++++++++
 src/shared/components/home/login.tsx          |   2 -
 ...assword_change.tsx => password-change.tsx} |   0
 src/shared/components/home/signup.tsx         |  88 ++++++-
 src/shared/components/home/site-form.tsx      | 138 ++++++++--
 .../person/registration-applications.tsx      | 249 ++++++++++++++++++
 src/shared/components/person/reports.tsx      |   4 +-
 .../post/{post_report.tsx => post-report.tsx} |  11 +-
 src/shared/routes.ts                          |   8 +-
 src/shared/services/UserService.ts            |   2 +
 src/shared/utils.ts                           |  15 ++
 yarn.lock                                     |   8 +-
 16 files changed, 696 insertions(+), 59 deletions(-)
 rename src/shared/components/comment/{comment_report.tsx => comment-report.tsx} (92%)
 create mode 100644 src/shared/components/common/registration-application.tsx
 rename src/shared/components/home/{password_change.tsx => password-change.tsx} (100%)
 create mode 100644 src/shared/components/person/registration-applications.tsx
 rename src/shared/components/post/{post_report.tsx => post-report.tsx} (92%)

diff --git a/package.json b/package.json
index 7621abb58..4d901f84c 100644
--- a/package.json
+++ b/package.json
@@ -72,7 +72,7 @@
     "husky": "^7.0.4",
     "import-sort-style-module": "^6.0.0",
     "iso-639-1": "^2.1.10",
-    "lemmy-js-client": "0.14.0-rc.1",
+    "lemmy-js-client": "0.15.0-rc.1",
     "lint-staged": "^12.1.2",
     "mini-css-extract-plugin": "^2.4.5",
     "node-fetch": "^2.6.1",
diff --git a/src/shared/components/app/navbar.tsx b/src/shared/components/app/navbar.tsx
index 2a40915d0..52f9aa387 100644
--- a/src/shared/components/app/navbar.tsx
+++ b/src/shared/components/app/navbar.tsx
@@ -7,6 +7,8 @@ import {
   GetSiteResponse,
   GetUnreadCount,
   GetUnreadCountResponse,
+  GetUnreadRegistrationApplicationCount,
+  GetUnreadRegistrationApplicationCountResponse,
   PrivateMessageResponse,
   UserOperation,
 } from "lemmy-js-client";
@@ -41,6 +43,7 @@ interface NavbarState {
   expanded: boolean;
   unreadInboxCount: number;
   unreadReportCount: number;
+  unreadApplicationCount: number;
   searchParam: string;
   toggleSearch: boolean;
   showDropdown: boolean;
@@ -52,11 +55,13 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
   private userSub: Subscription;
   private unreadInboxCountSub: Subscription;
   private unreadReportCountSub: Subscription;
+  private unreadApplicationCountSub: Subscription;
   private searchTextField: RefObject<HTMLInputElement>;
   emptyState: NavbarState = {
     isLoggedIn: !!this.props.site_res.my_user,
     unreadInboxCount: 0,
     unreadReportCount: 0,
+    unreadApplicationCount: 0,
     expanded: false,
     searchParam: "",
     toggleSearch: false,
@@ -115,6 +120,11 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
         UserService.Instance.unreadReportCountSub.subscribe(res => {
           this.setState({ unreadReportCount: res });
         });
+      // Subscribe to unread application count
+      this.unreadApplicationCountSub =
+        UserService.Instance.unreadApplicationCountSub.subscribe(res => {
+          this.setState({ unreadApplicationCount: res });
+        });
     }
   }
 
@@ -123,6 +133,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     this.userSub.unsubscribe();
     this.unreadInboxCountSub.unsubscribe();
     this.unreadReportCountSub.unsubscribe();
+    this.unreadApplicationCountSub.unsubscribe();
   }
 
   updateUrl() {
@@ -215,6 +226,31 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                   </li>
                 </ul>
               )}
+              {UserService.Instance.myUserInfo?.local_user_view.person
+                .admin && (
+                <ul class="navbar-nav ml-1">
+                  <li className="nav-item">
+                    <NavLink
+                      to="/registration_applications"
+                      className="p-1 navbar-toggler nav-link border-0"
+                      onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                      title={i18n.t("unread_registration_applications", {
+                        count: this.state.unreadApplicationCount,
+                        formattedCount: numToSI(
+                          this.state.unreadApplicationCount
+                        ),
+                      })}
+                    >
+                      <Icon icon="edit" />
+                      {this.state.unreadApplicationCount > 0 && (
+                        <span class="mx-1 badge badge-light">
+                          {numToSI(this.state.unreadApplicationCount)}
+                        </span>
+                      )}
+                    </NavLink>
+                  </li>
+                </ul>
+              )}
             </>
           )}
           <button
@@ -366,6 +402,31 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                     </li>
                   </ul>
                 )}
+                {UserService.Instance.myUserInfo?.local_user_view.person
+                  .admin && (
+                  <ul class="navbar-nav my-2">
+                    <li className="nav-item">
+                      <NavLink
+                        to="/registration_applications"
+                        className="nav-link"
+                        onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+                        title={i18n.t("unread_registration_applications", {
+                          count: this.state.unreadApplicationCount,
+                          formattedCount: numToSI(
+                            this.state.unreadApplicationCount
+                          ),
+                        })}
+                      >
+                        <Icon icon="edit" />
+                        {this.state.unreadApplicationCount > 0 && (
+                          <span class="mx-1 badge badge-light">
+                            {numToSI(this.state.unreadApplicationCount)}
+                          </span>
+                        )}
+                      </NavLink>
+                    </li>
+                  </ul>
+                )}
                 <ul class="navbar-nav">
                   <li class="nav-item dropdown">
                     <button
@@ -537,6 +598,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
       this.state.unreadReportCount = data.post_reports + data.comment_reports;
       this.setState(this.state);
       this.sendReportUnread();
+    } else if (op == UserOperation.GetUnreadRegistrationApplicationCount) {
+      let data =
+        wsJsonToRes<GetUnreadRegistrationApplicationCountResponse>(msg).data;
+      this.state.unreadApplicationCount = data.registration_applications;
+      this.setState(this.state);
+      this.sendApplicationUnread();
     } else if (op == UserOperation.GetSite) {
       // This is only called on a successful login
       let data = wsJsonToRes<GetSiteResponse>(msg).data;
@@ -586,7 +653,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     let unreadForm: GetUnreadCount = {
       auth: authField(),
     };
-
     WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm));
 
     console.log("Fetching reports...");
@@ -594,8 +660,18 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     let reportCountForm: GetReportCount = {
       auth: authField(),
     };
-
     WebSocketService.Instance.send(wsClient.getReportCount(reportCountForm));
+
+    if (UserService.Instance.myUserInfo?.local_user_view.person.admin) {
+      console.log("Fetching applications...");
+
+      let applicationCountForm: GetUnreadRegistrationApplicationCount = {
+        auth: authField(),
+      };
+      WebSocketService.Instance.send(
+        wsClient.getUnreadRegistrationApplicationCount(applicationCountForm)
+      );
+    }
   }
 
   get currentLocation() {
@@ -612,6 +688,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
     );
   }
 
+  sendApplicationUnread() {
+    UserService.Instance.unreadApplicationCountSub.next(
+      this.state.unreadApplicationCount
+    );
+  }
+
   get canAdmin(): boolean {
     return (
       UserService.Instance.myUserInfo &&
diff --git a/src/shared/components/comment/comment_report.tsx b/src/shared/components/comment/comment-report.tsx
similarity index 92%
rename from src/shared/components/comment/comment_report.tsx
rename to src/shared/components/comment/comment-report.tsx
index 87f6ebcc1..8e0496283 100644
--- a/src/shared/components/comment/comment_report.tsx
+++ b/src/shared/components/comment/comment-report.tsx
@@ -25,6 +25,9 @@ export class CommentReport extends Component<CommentReportProps, any> {
   render() {
     let r = this.props.report;
     let comment = r.comment;
+    let tippyContent = i18n.t(
+      r.comment_report.resolved ? "unresolve_report" : "resolve_report"
+    );
 
     // Set the original post data ( a troll could change it )
     comment.content = r.comment_report.original_comment_text;
@@ -78,12 +81,8 @@ export class CommentReport extends Component<CommentReportProps, any> {
         <button
           className="btn btn-link btn-animate text-muted py-0"
           onClick={linkEvent(this, this.handleResolveReport)}
-          data-tippy-content={
-            r.comment_report.resolved ? "unresolve_report" : "resolve_report"
-          }
-          aria-label={
-            r.comment_report.resolved ? "unresolve_report" : "resolve_report"
-          }
+          data-tippy-content={tippyContent}
+          aria-label={tippyContent}
         >
           <Icon
             icon="check"
diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx
index ea5bea179..5b1c8ee0b 100644
--- a/src/shared/components/common/markdown-textarea.tsx
+++ b/src/shared/components/common/markdown-textarea.tsx
@@ -17,7 +17,7 @@ import {
 import { Icon, Spinner } from "./icon";
 
 interface MarkdownTextAreaProps {
-  initialContent: string;
+  initialContent?: string;
   finished?: boolean;
   buttonTitle?: string;
   replyType?: boolean;
diff --git a/src/shared/components/common/registration-application.tsx b/src/shared/components/common/registration-application.tsx
new file mode 100644
index 000000000..3106260c5
--- /dev/null
+++ b/src/shared/components/common/registration-application.tsx
@@ -0,0 +1,129 @@
+import { Component, linkEvent } from "inferno";
+import { T } from "inferno-i18next-dess";
+import {
+  ApproveRegistrationApplication,
+  RegistrationApplicationView,
+} from "lemmy-js-client";
+import { i18n } from "../../i18next";
+import { WebSocketService } from "../../services";
+import { authField, mdToHtml, wsClient } from "../../utils";
+import { PersonListing } from "../person/person-listing";
+import { MarkdownTextArea } from "./markdown-textarea";
+import { MomentTime } from "./moment-time";
+
+interface RegistrationApplicationProps {
+  application: RegistrationApplicationView;
+}
+
+interface RegistrationApplicationState {
+  denyReason?: string;
+}
+
+export class RegistrationApplication extends Component<
+  RegistrationApplicationProps,
+  RegistrationApplicationState
+> {
+  private emptyState: RegistrationApplicationState = {
+    denyReason: this.props.application.registration_application.deny_reason,
+  };
+
+  constructor(props: any, context: any) {
+    super(props, context);
+
+    this.state = this.emptyState;
+    this.handleDenyReasonChange = this.handleDenyReasonChange.bind(this);
+  }
+
+  render() {
+    let a = this.props.application;
+    let ra = this.props.application.registration_application;
+    let accepted = a.creator_local_user.accepted_application;
+
+    return (
+      <div>
+        <div>
+          {i18n.t("applicant")}: <PersonListing person={a.creator} />
+        </div>
+        <div>
+          {i18n.t("created")}: <MomentTime showAgo data={ra} />
+        </div>
+        <div>{i18n.t("answer")}:</div>
+        <div className="md-div" dangerouslySetInnerHTML={mdToHtml(ra.answer)} />
+
+        {a.admin && (
+          <div>
+            {accepted ? (
+              <T i18nKey="approved_by">
+                #
+                <PersonListing person={a.admin} />
+              </T>
+            ) : (
+              <div>
+                <T i18nKey="denied_by">
+                  #
+                  <PersonListing person={a.admin} />
+                </T>
+              </div>
+            )}
+          </div>
+        )}
+
+        <div class="form-group row">
+          <label class="col-sm-2 col-form-label">{i18n.t("deny_reason")}</label>
+          <div class="col-sm-10">
+            <MarkdownTextArea
+              initialContent={this.state.denyReason}
+              onContentChange={this.handleDenyReasonChange}
+              hideNavigationWarnings
+            />
+          </div>
+        </div>
+        <button
+          className="btn btn-secondary mr-2"
+          onClick={linkEvent(this, this.handleApprove)}
+          aria-label={i18n.t("approve")}
+        >
+          {i18n.t("approve")}
+        </button>
+        {!this.props.application.registration_application.deny_reason && (
+          <button
+            className="btn btn-secondary mr-2"
+            onClick={linkEvent(this, this.handleDeny)}
+            aria-label={i18n.t("deny")}
+          >
+            {i18n.t("deny")}
+          </button>
+        )}
+      </div>
+    );
+  }
+
+  handleApprove(i: RegistrationApplication) {
+    let form: ApproveRegistrationApplication = {
+      id: i.props.application.registration_application.id,
+      deny_reason: "",
+      approve: true,
+      auth: authField(),
+    };
+    WebSocketService.Instance.send(
+      wsClient.approveRegistrationApplication(form)
+    );
+  }
+
+  handleDeny(i: RegistrationApplication) {
+    let form: ApproveRegistrationApplication = {
+      id: i.props.application.registration_application.id,
+      approve: false,
+      deny_reason: i.state.denyReason,
+      auth: authField(),
+    };
+    WebSocketService.Instance.send(
+      wsClient.approveRegistrationApplication(form)
+    );
+  }
+
+  handleDenyReasonChange(val: string) {
+    this.state.denyReason = val;
+    this.setState(this.state);
+  }
+}
diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx
index 73d7dbeba..5a61d1363 100644
--- a/src/shared/components/home/login.tsx
+++ b/src/shared/components/home/login.tsx
@@ -185,8 +185,6 @@ export class Login extends Component<any, State> {
     if (msg.error) {
       toast(i18n.t(msg.error), "danger");
       this.state = this.emptyState;
-      // Refetch another captcha
-      WebSocketService.Instance.send(wsClient.getCaptcha());
       this.setState(this.state);
       return;
     } else {
diff --git a/src/shared/components/home/password_change.tsx b/src/shared/components/home/password-change.tsx
similarity index 100%
rename from src/shared/components/home/password_change.tsx
rename to src/shared/components/home/password-change.tsx
diff --git a/src/shared/components/home/signup.tsx b/src/shared/components/home/signup.tsx
index ebccac989..25b2bf4d6 100644
--- a/src/shared/components/home/signup.tsx
+++ b/src/shared/components/home/signup.tsx
@@ -17,6 +17,7 @@ import {
   authField,
   isBrowser,
   joinLemmyUrl,
+  mdToHtml,
   setIsoData,
   toast,
   validEmail,
@@ -27,6 +28,7 @@ import {
 } from "../../utils";
 import { HtmlTags } from "../common/html-tags";
 import { Icon, Spinner } from "../common/icon";
+import { MarkdownTextArea } from "../common/markdown-textarea";
 
 const passwordStrengthOptions: Options<string> = [
   {
@@ -77,6 +79,7 @@ export class Signup extends Component<any, State> {
       captcha_uuid: undefined,
       captcha_answer: undefined,
       honeypot: undefined,
+      answer: undefined,
     },
     registerLoading: false,
     captcha: undefined,
@@ -88,6 +91,7 @@ export class Signup extends Component<any, State> {
     super(props, context);
 
     this.state = this.emptyState;
+    this.handleAnswerChange = this.handleAnswerChange.bind(this);
 
     this.parseMessage = this.parseMessage.bind(this);
     this.subscription = wsSubscribe(this.parseMessage);
@@ -159,18 +163,24 @@ export class Signup extends Component<any, State> {
               type="email"
               id="register-email"
               class="form-control"
-              placeholder={i18n.t("optional")}
+              placeholder={
+                this.state.site_view.site.require_email_verification
+                  ? i18n.t("required")
+                  : i18n.t("optional")
+              }
               value={this.state.registerForm.email}
               autoComplete="email"
               onInput={linkEvent(this, this.handleRegisterEmailChange)}
+              required={this.state.site_view.site.require_email_verification}
               minLength={3}
             />
-            {!validEmail(this.state.registerForm.email) && (
-              <div class="mt-2 mb-0 alert alert-light" role="alert">
-                <Icon icon="alert-triangle" classes="icon-inline mr-2" />
-                {i18n.t("no_password_reset")}
-              </div>
-            )}
+            {!this.state.site_view.site.require_email_verification &&
+              !validEmail(this.state.registerForm.email) && (
+                <div class="mt-2 mb-0 alert alert-light" role="alert">
+                  <Icon icon="alert-triangle" classes="icon-inline mr-2" />
+                  {i18n.t("no_password_reset")}
+                </div>
+              )}
           </div>
         </div>
 
@@ -219,6 +229,40 @@ export class Signup extends Component<any, State> {
           </div>
         </div>
 
+        {this.state.site_view.site.require_application && (
+          <>
+            <div class="form-group row">
+              <div class="offset-sm-2 col-sm-10">
+                <div class="mt-2 alert alert-light" role="alert">
+                  <Icon icon="alert-triangle" classes="icon-inline mr-2" />
+                  {i18n.t("fill_out_application")}
+                </div>
+                <div
+                  className="md-div"
+                  dangerouslySetInnerHTML={mdToHtml(
+                    this.state.site_view.site.application_question
+                  )}
+                />
+              </div>
+            </div>
+
+            <div class="form-group row">
+              <label
+                class="col-sm-2 col-form-label"
+                htmlFor="application_answer"
+              >
+                {i18n.t("answer")}
+              </label>
+              <div class="col-sm-10">
+                <MarkdownTextArea
+                  onContentChange={this.handleAnswerChange}
+                  hideNavigationWarnings
+                />
+              </div>
+            </div>
+          </>
+        )}
+
         {this.state.captcha && (
           <div class="form-group row">
             <label class="col-sm-2" htmlFor="register-captcha">
@@ -382,6 +426,11 @@ export class Signup extends Component<any, State> {
     i.setState(i.state);
   }
 
+  handleAnswerChange(val: string) {
+    this.state.registerForm.answer = val;
+    this.setState(this.state);
+  }
+
   handleHoneyPotChange(i: Signup, event: any) {
     i.state.registerForm.honeypot = event.target.value;
     i.setState(i.state);
@@ -434,13 +483,24 @@ export class Signup extends Component<any, State> {
         let data = wsJsonToRes<LoginResponse>(msg).data;
         this.state = this.emptyState;
         this.setState(this.state);
-        UserService.Instance.login(data);
-        WebSocketService.Instance.send(
-          wsClient.userJoin({
-            auth: authField(),
-          })
-        );
-        this.props.history.push("/communities");
+        // Only log them in if a jwt was set
+        if (data.jwt) {
+          UserService.Instance.login(data);
+          WebSocketService.Instance.send(
+            wsClient.userJoin({
+              auth: authField(),
+            })
+          );
+          this.props.history.push("/communities");
+        } else {
+          if (data.verify_email_sent) {
+            toast(i18n.t("verify_email_sent"));
+          }
+          if (data.registration_created) {
+            toast(i18n.t("registration_application_sent"));
+          }
+          this.props.history.push("/");
+        }
       } else if (op == UserOperation.GetCaptcha) {
         let data = wsJsonToRes<GetCaptchaResponse>(msg).data;
         if (data.ok) {
diff --git a/src/shared/components/home/site-form.tsx b/src/shared/components/home/site-form.tsx
index 6bfed4279..ba1ce3800 100644
--- a/src/shared/components/home/site-form.tsx
+++ b/src/shared/components/home/site-form.tsx
@@ -3,12 +3,7 @@ import { Prompt } from "inferno-router";
 import { CreateSite, EditSite, Site } from "lemmy-js-client";
 import { i18n } from "../../i18next";
 import { WebSocketService } from "../../services";
-import {
-  authField,
-  capitalizeFirstLetter,
-  randomStr,
-  wsClient,
-} from "../../utils";
+import { authField, capitalizeFirstLetter, wsClient } from "../../utils";
 import { Spinner } from "../common/icon";
 import { ImageUploadForm } from "../common/image-upload-form";
 import { MarkdownTextArea } from "../common/markdown-textarea";
@@ -24,7 +19,6 @@ interface SiteFormState {
 }
 
 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
-  private id = `site-form-${randomStr()}`;
   private emptyState: SiteFormState = {
     siteForm: {
       enable_downvotes: true,
@@ -33,6 +27,10 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
       name: null,
       icon: null,
       banner: null,
+      require_email_verification: null,
+      require_application: null,
+      application_question: null,
+      private_instance: null,
       auth: authField(),
     },
     loading: false,
@@ -43,6 +41,8 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
 
     this.state = this.emptyState;
     this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
+    this.handleSiteApplicationQuestionChange =
+      this.handleSiteApplicationQuestionChange.bind(this);
 
     this.handleIconUpload = this.handleIconUpload.bind(this);
     this.handleIconRemove = this.handleIconRemove.bind(this);
@@ -51,17 +51,21 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
     this.handleBannerRemove = this.handleBannerRemove.bind(this);
 
     if (this.props.site) {
+      let site = this.props.site;
       this.state.siteForm = {
-        name: this.props.site.name,
-        sidebar: this.props.site.sidebar,
-        description: this.props.site.description,
-        enable_downvotes: this.props.site.enable_downvotes,
-        open_registration: this.props.site.open_registration,
-        enable_nsfw: this.props.site.enable_nsfw,
-        community_creation_admin_only:
-          this.props.site.community_creation_admin_only,
-        icon: this.props.site.icon,
-        banner: this.props.site.banner,
+        name: site.name,
+        sidebar: site.sidebar,
+        description: site.description,
+        enable_downvotes: site.enable_downvotes,
+        open_registration: site.open_registration,
+        enable_nsfw: site.enable_nsfw,
+        community_creation_admin_only: site.community_creation_admin_only,
+        icon: site.icon,
+        banner: site.banner,
+        require_email_verification: site.require_email_verification,
+        require_application: site.require_application,
+        application_question: site.application_question,
+        private_instance: site.private_instance,
         auth: authField(),
       };
     }
@@ -79,6 +83,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
       !this.props.site &&
       (this.state.siteForm.name ||
         this.state.siteForm.sidebar ||
+        this.state.siteForm.application_question ||
         this.state.siteForm.description)
     ) {
       window.onbeforeunload = () => true;
@@ -100,6 +105,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
             !this.props.site &&
             (this.state.siteForm.name ||
               this.state.siteForm.sidebar ||
+              this.state.siteForm.application_question ||
               this.state.siteForm.description)
           }
           message={i18n.t("block_leaving")}
@@ -162,9 +168,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
             </div>
           </div>
           <div class="form-group row">
-            <label class="col-12 col-form-label" htmlFor={this.id}>
-              {i18n.t("sidebar")}
-            </label>
+            <label class="col-12 col-form-label">{i18n.t("sidebar")}</label>
             <div class="col-12">
               <MarkdownTextArea
                 initialContent={this.state.siteForm.sidebar}
@@ -173,6 +177,20 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
               />
             </div>
           </div>
+          {this.state.siteForm.require_application && (
+            <div class="form-group row">
+              <label class="col-12 col-form-label">
+                {i18n.t("application_questionnaire")}
+              </label>
+              <div class="col-12">
+                <MarkdownTextArea
+                  initialContent={this.state.siteForm.application_question}
+                  onContentChange={this.handleSiteApplicationQuestionChange}
+                  hideNavigationWarnings
+                />
+              </div>
+            </div>
+          )}
           <div class="form-group row">
             <div class="col-12">
               <div class="form-check">
@@ -255,6 +273,66 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
               </div>
             </div>
           </div>
+          <div class="form-group row">
+            <div class="col-12">
+              <div class="form-check">
+                <input
+                  class="form-check-input"
+                  id="create-site-require-email-verification"
+                  type="checkbox"
+                  checked={this.state.siteForm.require_email_verification}
+                  onChange={linkEvent(
+                    this,
+                    this.handleSiteRequireEmailVerification
+                  )}
+                />
+                <label
+                  class="form-check-label"
+                  htmlFor="create-site-require-email-verification"
+                >
+                  {i18n.t("require_email_verification")}
+                </label>
+              </div>
+            </div>
+          </div>
+          <div class="form-group row">
+            <div class="col-12">
+              <div class="form-check">
+                <input
+                  class="form-check-input"
+                  id="create-site-require-application"
+                  type="checkbox"
+                  checked={this.state.siteForm.require_application}
+                  onChange={linkEvent(this, this.handleSiteRequireApplication)}
+                />
+                <label
+                  class="form-check-label"
+                  htmlFor="create-site-require-application"
+                >
+                  {i18n.t("require_registration_application")}
+                </label>
+              </div>
+            </div>
+          </div>
+          <div class="form-group row">
+            <div class="col-12">
+              <div class="form-check">
+                <input
+                  class="form-check-input"
+                  id="create-site-private-instance"
+                  type="checkbox"
+                  checked={this.state.siteForm.private_instance}
+                  onChange={linkEvent(this, this.handleSitePrivateInstance)}
+                />
+                <label
+                  class="form-check-label"
+                  htmlFor="create-site-private-instance"
+                >
+                  {i18n.t("private_instance")}
+                </label>
+              </div>
+            </div>
+          </div>
           <div class="form-group row">
             <div class="col-12">
               <button
@@ -311,6 +389,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
     this.setState(this.state);
   }
 
+  handleSiteApplicationQuestionChange(val: string) {
+    this.state.siteForm.application_question = val;
+    this.setState(this.state);
+  }
+
   handleSiteDescChange(i: SiteForm, event: any) {
     i.state.siteForm.description = event.target.value;
     i.setState(i.state);
@@ -336,6 +419,21 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
     i.setState(i.state);
   }
 
+  handleSiteRequireApplication(i: SiteForm, event: any) {
+    i.state.siteForm.require_application = event.target.checked;
+    i.setState(i.state);
+  }
+
+  handleSiteRequireEmailVerification(i: SiteForm, event: any) {
+    i.state.siteForm.require_email_verification = event.target.checked;
+    i.setState(i.state);
+  }
+
+  handleSitePrivateInstance(i: SiteForm, event: any) {
+    i.state.siteForm.private_instance = event.target.checked;
+    i.setState(i.state);
+  }
+
   handleCancel(i: SiteForm) {
     i.props.onCancel();
   }
diff --git a/src/shared/components/person/registration-applications.tsx b/src/shared/components/person/registration-applications.tsx
new file mode 100644
index 000000000..9009f7462
--- /dev/null
+++ b/src/shared/components/person/registration-applications.tsx
@@ -0,0 +1,249 @@
+import { Component, linkEvent } from "inferno";
+import {
+  ListRegistrationApplications,
+  ListRegistrationApplicationsResponse,
+  RegistrationApplicationResponse,
+  RegistrationApplicationView,
+  SiteView,
+  UserOperation,
+} from "lemmy-js-client";
+import { Subscription } from "rxjs";
+import { i18n } from "../../i18next";
+import { InitialFetchRequest } from "../../interfaces";
+import { UserService, WebSocketService } from "../../services";
+import {
+  authField,
+  fetchLimit,
+  isBrowser,
+  setIsoData,
+  setupTippy,
+  toast,
+  updateRegistrationApplicationRes,
+  wsClient,
+  wsJsonToRes,
+  wsSubscribe,
+  wsUserOp,
+} from "../../utils";
+import { HtmlTags } from "../common/html-tags";
+import { Spinner } from "../common/icon";
+import { Paginator } from "../common/paginator";
+import { RegistrationApplication } from "../common/registration-application";
+
+enum UnreadOrAll {
+  Unread,
+  All,
+}
+
+interface RegistrationApplicationsState {
+  applications: RegistrationApplicationView[];
+  page: number;
+  site_view: SiteView;
+  unreadOrAll: UnreadOrAll;
+  loading: boolean;
+}
+
+export class RegistrationApplications extends Component<
+  any,
+  RegistrationApplicationsState
+> {
+  private isoData = setIsoData(this.context);
+  private subscription: Subscription;
+  private emptyState: RegistrationApplicationsState = {
+    unreadOrAll: UnreadOrAll.Unread,
+    applications: [],
+    page: 1,
+    site_view: this.isoData.site_res.site_view,
+    loading: true,
+  };
+
+  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);
+
+    // Only fetch the data if coming from another route
+    if (this.isoData.path == this.context.router.route.match.url) {
+      this.state.applications =
+        this.isoData.routeData[0].registration_applications || []; // TODO test
+      this.state.loading = false;
+    } else {
+      this.refetch();
+    }
+  }
+
+  componentDidMount() {
+    setupTippy();
+  }
+
+  componentWillUnmount() {
+    if (isBrowser()) {
+      this.subscription.unsubscribe();
+    }
+  }
+
+  get documentTitle(): string {
+    return `@${
+      UserService.Instance.myUserInfo.local_user_view.person.name
+    } ${i18n.t("registration_applications")} - ${
+      this.state.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("registration_applications")}</h5>
+              {this.selects()}
+              {this.applicationList()}
+              <Paginator
+                page={this.state.page}
+                onChange={this.handlePageChange}
+              />
+            </div>
+          </div>
+        )}
+      </div>
+    );
+  }
+
+  unreadOrAllRadios() {
+    return (
+      <div class="btn-group btn-group-toggle flex-wrap mb-2">
+        <label
+          className={`btn btn-outline-secondary pointer
+            ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
+          `}
+        >
+          <input
+            type="radio"
+            value={UnreadOrAll.Unread}
+            checked={this.state.unreadOrAll == UnreadOrAll.Unread}
+            onChange={linkEvent(this, this.handleUnreadOrAllChange)}
+          />
+          {i18n.t("unread")}
+        </label>
+        <label
+          className={`btn btn-outline-secondary pointer
+            ${this.state.unreadOrAll == UnreadOrAll.All && "active"}
+          `}
+        >
+          <input
+            type="radio"
+            value={UnreadOrAll.All}
+            checked={this.state.unreadOrAll == UnreadOrAll.All}
+            onChange={linkEvent(this, this.handleUnreadOrAllChange)}
+          />
+          {i18n.t("all")}
+        </label>
+      </div>
+    );
+  }
+
+  selects() {
+    return (
+      <div className="mb-2">
+        <span class="mr-3">{this.unreadOrAllRadios()}</span>
+      </div>
+    );
+  }
+
+  applicationList() {
+    return (
+      <div>
+        {this.state.applications.map(ra => (
+          <>
+            <hr />
+            <RegistrationApplication
+              key={ra.registration_application.id}
+              application={ra}
+            />
+          </>
+        ))}
+      </div>
+    );
+  }
+
+  handleUnreadOrAllChange(i: RegistrationApplications, event: any) {
+    i.state.unreadOrAll = Number(event.target.value);
+    i.state.page = 1;
+    i.setState(i.state);
+    i.refetch();
+  }
+
+  handlePageChange(page: number) {
+    this.setState({ page });
+    this.refetch();
+  }
+
+  static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
+    let promises: Promise<any>[] = [];
+
+    let form: ListRegistrationApplications = {
+      unread_only: true,
+      page: 1,
+      limit: fetchLimit,
+      auth: req.auth,
+    };
+    promises.push(req.client.listRegistrationApplications(form));
+
+    return promises;
+  }
+
+  refetch() {
+    let unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
+    let form: ListRegistrationApplications = {
+      unread_only: unread_only,
+      page: this.state.page,
+      limit: fetchLimit,
+      auth: authField(),
+    };
+    WebSocketService.Instance.send(wsClient.listRegistrationApplications(form));
+  }
+
+  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.ListRegistrationApplications) {
+      let data = wsJsonToRes<ListRegistrationApplicationsResponse>(msg).data;
+      this.state.applications = data.registration_applications;
+      this.state.loading = false;
+      window.scrollTo(0, 0);
+      this.setState(this.state);
+    } else if (op == UserOperation.ApproveRegistrationApplication) {
+      let data = wsJsonToRes<RegistrationApplicationResponse>(msg).data;
+      updateRegistrationApplicationRes(
+        data.registration_application,
+        this.state.applications
+      );
+      let uacs = UserService.Instance.unreadApplicationCountSub;
+      // Minor bug, where if the application switches from deny to approve, the count will still go down
+      uacs.next(uacs.getValue() - 1);
+      this.setState(this.state);
+    }
+  }
+}
diff --git a/src/shared/components/person/reports.tsx b/src/shared/components/person/reports.tsx
index 2c83ff2a4..99edf9681 100644
--- a/src/shared/components/person/reports.tsx
+++ b/src/shared/components/person/reports.tsx
@@ -29,11 +29,11 @@ import {
   wsSubscribe,
   wsUserOp,
 } from "../../utils";
-import { CommentReport } from "../comment/comment_report";
+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 { PostReport } from "../post/post-report";
 
 enum UnreadOrAll {
   Unread,
diff --git a/src/shared/components/post/post_report.tsx b/src/shared/components/post/post-report.tsx
similarity index 92%
rename from src/shared/components/post/post_report.tsx
rename to src/shared/components/post/post-report.tsx
index f2e17343f..ff3368eb5 100644
--- a/src/shared/components/post/post_report.tsx
+++ b/src/shared/components/post/post-report.tsx
@@ -20,6 +20,9 @@ export class PostReport extends Component<PostReportProps, any> {
   render() {
     let r = this.props.report;
     let post = r.post;
+    let tippyContent = i18n.t(
+      r.post_report.resolved ? "unresolve_report" : "resolve_report"
+    );
 
     // Set the original post data ( a troll could change it )
     post.name = r.post_report.original_post_name;
@@ -70,12 +73,8 @@ export class PostReport extends Component<PostReportProps, any> {
         <button
           className="btn btn-link btn-animate text-muted py-0"
           onClick={linkEvent(this, this.handleResolveReport)}
-          data-tippy-content={
-            r.post_report.resolved ? "unresolve_report" : "resolve_report"
-          }
-          aria-label={
-            r.post_report.resolved ? "unresolve_report" : "resolve_report"
-          }
+          data-tippy-content={tippyContent}
+          aria-label={tippyContent}
         >
           <Icon
             icon="check"
diff --git a/src/shared/routes.ts b/src/shared/routes.ts
index ddc3b621f..71ab79ed5 100644
--- a/src/shared/routes.ts
+++ b/src/shared/routes.ts
@@ -6,12 +6,13 @@ import { AdminSettings } from "./components/home/admin-settings";
 import { Home } from "./components/home/home";
 import { Instances } from "./components/home/instances";
 import { Login } from "./components/home/login";
-import { PasswordChange } from "./components/home/password_change";
+import { PasswordChange } from "./components/home/password-change";
 import { Setup } from "./components/home/setup";
 import { Signup } from "./components/home/signup";
 import { Modlog } from "./components/modlog";
 import { Inbox } from "./components/person/inbox";
 import { Profile } from "./components/person/profile";
+import { RegistrationApplications } from "./components/person/registration-applications";
 import { Reports } from "./components/person/reports";
 import { Settings } from "./components/person/settings";
 import { CreatePost } from "./components/post/create-post";
@@ -128,6 +129,11 @@ export const routes: IRoutePropsWithFetch[] = [
     component: Reports,
     fetchInitialData: req => Reports.fetchInitialData(req),
   },
+  {
+    path: `/registration_applications`,
+    component: RegistrationApplications,
+    fetchInitialData: req => RegistrationApplications.fetchInitialData(req),
+  },
   {
     path: `/search/q/:q/type/:type/sort/:sort/listing_type/:listing_type/community_id/:community_id/creator_id/:creator_id/page/:page`,
     component: Search,
diff --git a/src/shared/services/UserService.ts b/src/shared/services/UserService.ts
index 0c87f7da7..031cf7d8d 100644
--- a/src/shared/services/UserService.ts
+++ b/src/shared/services/UserService.ts
@@ -20,6 +20,8 @@ export class UserService {
     new BehaviorSubject<number>(0);
   public unreadReportCountSub: BehaviorSubject<number> =
     new BehaviorSubject<number>(0);
+  public unreadApplicationCountSub: BehaviorSubject<number> =
+    new BehaviorSubject<number>(0);
 
   private constructor() {
     if (this.auth) {
diff --git a/src/shared/utils.ts b/src/shared/utils.ts
index f273ab285..ba9dd06f5 100644
--- a/src/shared/utils.ts
+++ b/src/shared/utils.ts
@@ -18,6 +18,7 @@ import {
   PostReportView,
   PostView,
   PrivateMessageView,
+  RegistrationApplicationView,
   Search,
   SearchResponse,
   SearchType,
@@ -1104,6 +1105,20 @@ export function updateCommentReportRes(
   }
 }
 
+export function updateRegistrationApplicationRes(
+  data: RegistrationApplicationView,
+  applications: RegistrationApplicationView[]
+) {
+  let found = applications.find(
+    ra => ra.registration_application.id == data.registration_application.id
+  );
+  if (found) {
+    found.registration_application = data.registration_application;
+    found.admin = data.admin;
+    found.creator_local_user = data.creator_local_user;
+  }
+}
+
 export function commentsToFlatNodes(comments: CommentView[]): CommentNodeI[] {
   let nodes: CommentNodeI[] = [];
   for (let comment of comments) {
diff --git a/yarn.lock b/yarn.lock
index 62df36c54..ae68871ab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4997,10 +4997,10 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-lemmy-js-client@0.14.0-rc.1:
-  version "0.14.0-rc.1"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.14.0-rc.1.tgz#c714d5f308fa20d5244db3844630f7b197eafa1c"
-  integrity sha512-UF3I+80WTYWwQg2+96HTl0O2Yv0wy6rYFjlLNyzfqMXUZBnsr1O/SdJD1/9yAFPFbGkKgWusdncLoGgzFyn8eg==
+lemmy-js-client@0.15.0-rc.1:
+  version "0.15.0-rc.1"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.15.0-rc.1.tgz#7f5f0f069c76c5377a2a8654ce3a10563f405e14"
+  integrity sha512-Dgq7G2jGLURoswV40DfUNNvxQ/Dg8Jkz9jarwJuOMmBF9MfM/wD1nlaPtvMHlxePXtgDc8Y/Ig84k1OEF8Myqw==
 
 levn@^0.4.1:
   version "0.4.1"

From 57026d0feb123ca117d20b0fff893ca264acc13d Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Thu, 9 Dec 2021 14:14:53 -0500
Subject: [PATCH 03/13] Updating translations.

---
 lemmy-translations | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lemmy-translations b/lemmy-translations
index abab502ec..a0a09aac8 160000
--- a/lemmy-translations
+++ b/lemmy-translations
@@ -1 +1 @@
-Subproject commit abab502ec9d7164d7c55834d6b0de4250d491253
+Subproject commit a0a09aac8d822e1136a186d0fec8493f9830be35

From 74a54a52efa633ff3fb6192c7020918d300c6a41 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Thu, 9 Dec 2021 16:24:28 -0500
Subject: [PATCH 04/13] Adding verify email route.

---
 package.json                                  |  2 +-
 src/server/index.tsx                          |  6 +-
 src/shared/components/home/home.tsx           |  1 +
 .../{home => person}/password-change.tsx      |  0
 src/shared/components/person/verify-email.tsx | 98 +++++++++++++++++++
 src/shared/routes.ts                          |  7 +-
 yarn.lock                                     |  8 +-
 7 files changed, 115 insertions(+), 7 deletions(-)
 rename src/shared/components/{home => person}/password-change.tsx (100%)
 create mode 100644 src/shared/components/person/verify-email.tsx

diff --git a/package.json b/package.json
index 4d901f84c..4cee8c4ed 100644
--- a/package.json
+++ b/package.json
@@ -72,7 +72,7 @@
     "husky": "^7.0.4",
     "import-sort-style-module": "^6.0.0",
     "iso-639-1": "^2.1.10",
-    "lemmy-js-client": "0.15.0-rc.1",
+    "lemmy-js-client": "0.15.0-rc.3",
     "lint-staged": "^12.1.2",
     "mini-css-extract-plugin": "^2.4.5",
     "node-fetch": "^2.6.1",
diff --git a/src/server/index.tsx b/src/server/index.tsx
index 5bf79f17f..cedb22da9 100644
--- a/src/server/index.tsx
+++ b/src/server/index.tsx
@@ -91,7 +91,11 @@ server.get("/*", async (req, res) => {
     if (routeData[0] && routeData[0].error) {
       let errCode = routeData[0].error;
       console.error(errCode);
-      return res.redirect(`/404?err=${errCode}`);
+      if (errCode == "instance_is_private") {
+        return res.redirect(`/login`);
+      } else {
+        return res.redirect(`/404?err=${errCode}`);
+      }
     }
 
     let isoData: IsoData = {
diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx
index f44d4cc04..d14514515 100644
--- a/src/shared/components/home/home.tsx
+++ b/src/shared/components/home/home.tsx
@@ -239,6 +239,7 @@ export class Home extends Component<any, HomeState> {
       sort: SortType.Hot,
       limit: 6,
     };
+    setOptionalAuth(trendingCommunitiesForm, req.auth);
     promises.push(req.client.listCommunities(trendingCommunitiesForm));
 
     return promises;
diff --git a/src/shared/components/home/password-change.tsx b/src/shared/components/person/password-change.tsx
similarity index 100%
rename from src/shared/components/home/password-change.tsx
rename to src/shared/components/person/password-change.tsx
diff --git a/src/shared/components/person/verify-email.tsx b/src/shared/components/person/verify-email.tsx
new file mode 100644
index 000000000..bfcca3f7e
--- /dev/null
+++ b/src/shared/components/person/verify-email.tsx
@@ -0,0 +1,98 @@
+import { Component } from "inferno";
+import {
+  LoginResponse,
+  SiteView,
+  UserOperation,
+  VerifyEmail as VerifyEmailForm,
+} from "lemmy-js-client";
+import { Subscription } from "rxjs";
+import { i18n } from "../../i18next";
+import { UserService, WebSocketService } from "../../services";
+import {
+  isBrowser,
+  setIsoData,
+  toast,
+  wsClient,
+  wsJsonToRes,
+  wsSubscribe,
+  wsUserOp,
+} from "../../utils";
+import { HtmlTags } from "../common/html-tags";
+
+interface State {
+  verifyEmailForm: VerifyEmailForm;
+  site_view: SiteView;
+}
+
+export class VerifyEmail extends Component<any, State> {
+  private isoData = setIsoData(this.context);
+  private subscription: Subscription;
+
+  emptyState: State = {
+    verifyEmailForm: {
+      token: this.props.match.params.token,
+    },
+    site_view: this.isoData.site_res.site_view,
+  };
+
+  constructor(props: any, context: any) {
+    super(props, context);
+
+    this.state = this.emptyState;
+
+    this.parseMessage = this.parseMessage.bind(this);
+    this.subscription = wsSubscribe(this.parseMessage);
+  }
+
+  componentDidMount() {
+    WebSocketService.Instance.send(
+      wsClient.verifyEmail(this.state.verifyEmailForm)
+    );
+  }
+
+  componentWillUnmount() {
+    if (isBrowser()) {
+      this.subscription.unsubscribe();
+    }
+  }
+
+  get documentTitle(): string {
+    return `${i18n.t("verify_email")} - ${this.state.site_view.site.name}`;
+  }
+
+  render() {
+    return (
+      <div class="container">
+        <HtmlTags
+          title={this.documentTitle}
+          path={this.context.router.route.match.url}
+        />
+        <div class="row">
+          <div class="col-12 col-lg-6 offset-lg-3 mb-4">
+            <h5>{i18n.t("verify_email")}</h5>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  parseMessage(msg: any) {
+    let op = wsUserOp(msg);
+    console.log(msg);
+    if (msg.error) {
+      toast(i18n.t(msg.error), "danger");
+      this.setState(this.state);
+      this.props.history.push("/");
+      return;
+    } else if (op == UserOperation.VerifyEmail) {
+      let data = wsJsonToRes<LoginResponse>(msg).data;
+      if (data.jwt) {
+        toast(i18n.t("email_verified"));
+        this.state = this.emptyState;
+        this.setState(this.state);
+        UserService.Instance.login(data);
+        this.props.history.push("/");
+      }
+    }
+  }
+}
diff --git a/src/shared/routes.ts b/src/shared/routes.ts
index 71ab79ed5..86c0b3c35 100644
--- a/src/shared/routes.ts
+++ b/src/shared/routes.ts
@@ -6,15 +6,16 @@ import { AdminSettings } from "./components/home/admin-settings";
 import { Home } from "./components/home/home";
 import { Instances } from "./components/home/instances";
 import { Login } from "./components/home/login";
-import { PasswordChange } from "./components/home/password-change";
 import { Setup } from "./components/home/setup";
 import { Signup } from "./components/home/signup";
 import { Modlog } from "./components/modlog";
 import { Inbox } from "./components/person/inbox";
+import { PasswordChange } from "./components/person/password-change";
 import { Profile } from "./components/person/profile";
 import { RegistrationApplications } from "./components/person/registration-applications";
 import { Reports } from "./components/person/reports";
 import { Settings } from "./components/person/settings";
+import { VerifyEmail } from "./components/person/verify-email";
 import { CreatePost } from "./components/post/create-post";
 import { Post } from "./components/post/post";
 import { CreatePrivateMessage } from "./components/private_message/create-private-message";
@@ -148,5 +149,9 @@ export const routes: IRoutePropsWithFetch[] = [
     path: `/password_change/:token`,
     component: PasswordChange,
   },
+  {
+    path: `/verify_email/:token`,
+    component: VerifyEmail,
+  },
   { path: `/instances`, component: Instances },
 ];
diff --git a/yarn.lock b/yarn.lock
index ae68871ab..21e32d2c7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4997,10 +4997,10 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-lemmy-js-client@0.15.0-rc.1:
-  version "0.15.0-rc.1"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.15.0-rc.1.tgz#7f5f0f069c76c5377a2a8654ce3a10563f405e14"
-  integrity sha512-Dgq7G2jGLURoswV40DfUNNvxQ/Dg8Jkz9jarwJuOMmBF9MfM/wD1nlaPtvMHlxePXtgDc8Y/Ig84k1OEF8Myqw==
+lemmy-js-client@0.15.0-rc.3:
+  version "0.15.0-rc.3"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.15.0-rc.3.tgz#acc762981f2bbd4381857d12bf576b0d85451c4f"
+  integrity sha512-Yk1Y32ILc2E/eTfnjtvZBoPGwPUqokPKlouOpng7cJohFMqZUWocy8Z3yK+tx81/l0Lss9RT4HaqsRmvmEmfJw==
 
 levn@^0.4.1:
   version "0.4.1"

From b042cf56682646d58b7ea4e45c936b79818922e1 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Thu, 9 Dec 2021 17:23:42 -0500
Subject: [PATCH 05/13] Fix missing signup question bug.

---
 src/shared/components/home/signup.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/shared/components/home/signup.tsx b/src/shared/components/home/signup.tsx
index 25b2bf4d6..e5706a7de 100644
--- a/src/shared/components/home/signup.tsx
+++ b/src/shared/components/home/signup.tsx
@@ -240,7 +240,7 @@ export class Signup extends Component<any, State> {
                 <div
                   className="md-div"
                   dangerouslySetInnerHTML={mdToHtml(
-                    this.state.site_view.site.application_question
+                    this.state.site_view.site.application_question || ""
                   )}
                 />
               </div>

From f23488fb0b27b322ea1d0a1649bc8f202d27e614 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Sun, 12 Dec 2021 13:04:51 -0500
Subject: [PATCH 06/13] Updating translations.

---
 lemmy-translations | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lemmy-translations b/lemmy-translations
index a0a09aac8..1e0bb9920 160000
--- a/lemmy-translations
+++ b/lemmy-translations
@@ -1 +1 @@
-Subproject commit a0a09aac8d822e1136a186d0fec8493f9830be35
+Subproject commit 1e0bb9920cda13bb128c87e85125b98ab8f319b6

From 88c3b166accfae9302c1e64b3309fc1f695b3154 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Sun, 12 Dec 2021 13:15:23 -0500
Subject: [PATCH 07/13] A few fixes from comments on lemmy PR.

---
 package.json                                  |  2 +-
 src/server/index.tsx                          |  2 +-
 src/shared/components/home/signup.tsx         | 12 +++++++++---
 src/shared/components/person/settings.tsx     |  5 +++++
 src/shared/components/person/verify-email.tsx | 11 +++++------
 yarn.lock                                     |  8 ++++----
 6 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/package.json b/package.json
index 4cee8c4ed..a6f4d3454 100644
--- a/package.json
+++ b/package.json
@@ -72,7 +72,7 @@
     "husky": "^7.0.4",
     "import-sort-style-module": "^6.0.0",
     "iso-639-1": "^2.1.10",
-    "lemmy-js-client": "0.15.0-rc.3",
+    "lemmy-js-client": "0.15.0-rc.2",
     "lint-staged": "^12.1.2",
     "mini-css-extract-plugin": "^2.4.5",
     "node-fetch": "^2.6.1",
diff --git a/src/server/index.tsx b/src/server/index.tsx
index cedb22da9..82d0379b9 100644
--- a/src/server/index.tsx
+++ b/src/server/index.tsx
@@ -92,7 +92,7 @@ server.get("/*", async (req, res) => {
       let errCode = routeData[0].error;
       console.error(errCode);
       if (errCode == "instance_is_private") {
-        return res.redirect(`/login`);
+        return res.redirect(`/signup`);
       } else {
         return res.redirect(`/404?err=${errCode}`);
       }
diff --git a/src/shared/components/home/signup.tsx b/src/shared/components/home/signup.tsx
index e5706a7de..8effa9586 100644
--- a/src/shared/components/home/signup.tsx
+++ b/src/shared/components/home/signup.tsx
@@ -108,7 +108,13 @@ export class Signup extends Component<any, State> {
   }
 
   get documentTitle(): string {
-    return `${i18n.t("login")} - ${this.state.site_view.site.name}`;
+    return `${this.titleName} - ${this.state.site_view.site.name}`;
+  }
+
+  get titleName(): string {
+    return `${i18n.t(
+      this.state.site_view.site.private_instance ? "apply_to_join" : "sign_up"
+    )}`;
   }
 
   get isLemmyMl(): boolean {
@@ -132,7 +138,7 @@ export class Signup extends Component<any, State> {
   registerForm() {
     return (
       <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
-        <h5>{i18n.t("sign_up")}</h5>
+        <h5>{this.titleName}</h5>
 
         <div class="form-group row">
           <label class="col-sm-2 col-form-label" htmlFor="register-username">
@@ -330,7 +336,7 @@ export class Signup extends Component<any, State> {
         <div class="form-group row">
           <div class="col-sm-10">
             <button type="submit" class="btn btn-secondary">
-              {this.state.registerLoading ? <Spinner /> : i18n.t("sign_up")}
+              {this.state.registerLoading ? <Spinner /> : this.titleName}
             </button>
           </div>
         </div>
diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx
index dff29590d..322dfa981 100644
--- a/src/shared/components/person/settings.tsx
+++ b/src/shared/components/person/settings.tsx
@@ -1108,6 +1108,11 @@ export class Settings extends Component<any, SettingsState> {
     let op = wsUserOp(msg);
     console.log(msg);
     if (msg.error) {
+      this.setState({
+        saveUserSettingsLoading: false,
+        changePasswordLoading: false,
+        deleteAccountLoading: false,
+      });
       toast(i18n.t(msg.error), "danger");
       return;
     } else if (op == UserOperation.SaveUserSettings) {
diff --git a/src/shared/components/person/verify-email.tsx b/src/shared/components/person/verify-email.tsx
index bfcca3f7e..d27a8bbc3 100644
--- a/src/shared/components/person/verify-email.tsx
+++ b/src/shared/components/person/verify-email.tsx
@@ -1,13 +1,13 @@
 import { Component } from "inferno";
 import {
-  LoginResponse,
   SiteView,
   UserOperation,
   VerifyEmail as VerifyEmailForm,
+  VerifyEmailResponse,
 } from "lemmy-js-client";
 import { Subscription } from "rxjs";
 import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
+import { WebSocketService } from "../../services";
 import {
   isBrowser,
   setIsoData,
@@ -85,13 +85,12 @@ export class VerifyEmail extends Component<any, State> {
       this.props.history.push("/");
       return;
     } else if (op == UserOperation.VerifyEmail) {
-      let data = wsJsonToRes<LoginResponse>(msg).data;
-      if (data.jwt) {
+      let data = wsJsonToRes<VerifyEmailResponse>(msg).data;
+      if (data) {
         toast(i18n.t("email_verified"));
         this.state = this.emptyState;
         this.setState(this.state);
-        UserService.Instance.login(data);
-        this.props.history.push("/");
+        this.props.history.push("/login");
       }
     }
   }
diff --git a/yarn.lock b/yarn.lock
index 21e32d2c7..60e4a05be 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4997,10 +4997,10 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-lemmy-js-client@0.15.0-rc.3:
-  version "0.15.0-rc.3"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.15.0-rc.3.tgz#acc762981f2bbd4381857d12bf576b0d85451c4f"
-  integrity sha512-Yk1Y32ILc2E/eTfnjtvZBoPGwPUqokPKlouOpng7cJohFMqZUWocy8Z3yK+tx81/l0Lss9RT4HaqsRmvmEmfJw==
+lemmy-js-client@0.15.0-rc.2:
+  version "0.15.0-rc.2"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.15.0-rc.2.tgz#e4e10edf844d90bde9915e578ced319cbd058109"
+  integrity sha512-qb+70MQQJ2pMNxroW+E8MNSXOrSxr3/qLO81GuAa/JT2AzbbY7mO/aXPFSrbyZWu5kn0K99alctEOC3m7mwEGw==
 
 levn@^0.4.1:
   version "0.4.1"

From cee864372bd2f0d720a1f61186a989bdb5787e6b Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Sun, 12 Dec 2021 13:16:05 -0500
Subject: [PATCH 08/13] v0.15.0-rc.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a6f4d3454..d9afb5391 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "lemmy-ui",
   "description": "An isomorphic UI for lemmy",
-  "version": "0.14.5",
+  "version": "0.15.0-rc.4",
   "author": "Dessalines <tyhou13@gmx.com>",
   "license": "AGPL-3.0",
   "scripts": {

From 576fff1ebe45a52f12066459a247a333bd32a71d Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Tue, 14 Dec 2021 11:20:44 -0500
Subject: [PATCH 09/13] Some suggestions from PR.

---
 src/shared/components/app/navbar.tsx          |  4 +-
 .../common/registration-application.tsx       | 57 ++++++++++++-------
 src/shared/components/common/symbols.tsx      |  3 +
 3 files changed, 43 insertions(+), 21 deletions(-)

diff --git a/src/shared/components/app/navbar.tsx b/src/shared/components/app/navbar.tsx
index 52f9aa387..57555410c 100644
--- a/src/shared/components/app/navbar.tsx
+++ b/src/shared/components/app/navbar.tsx
@@ -241,7 +241,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                         ),
                       })}
                     >
-                      <Icon icon="edit" />
+                      <Icon icon="clipboard" />
                       {this.state.unreadApplicationCount > 0 && (
                         <span class="mx-1 badge badge-light">
                           {numToSI(this.state.unreadApplicationCount)}
@@ -417,7 +417,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
                           ),
                         })}
                       >
-                        <Icon icon="edit" />
+                        <Icon icon="clipboard" />
                         {this.state.unreadApplicationCount > 0 && (
                           <span class="mx-1 badge badge-light">
                             {numToSI(this.state.unreadApplicationCount)}
diff --git a/src/shared/components/common/registration-application.tsx b/src/shared/components/common/registration-application.tsx
index 3106260c5..8b3951c93 100644
--- a/src/shared/components/common/registration-application.tsx
+++ b/src/shared/components/common/registration-application.tsx
@@ -17,6 +17,7 @@ interface RegistrationApplicationProps {
 
 interface RegistrationApplicationState {
   denyReason?: string;
+  denyExpanded: boolean;
 }
 
 export class RegistrationApplication extends Component<
@@ -25,6 +26,7 @@ export class RegistrationApplication extends Component<
 > {
   private emptyState: RegistrationApplicationState = {
     denyReason: this.props.application.registration_application.deny_reason,
+    denyExpanded: false,
   };
 
   constructor(props: any, context: any) {
@@ -63,23 +65,34 @@ export class RegistrationApplication extends Component<
                   #
                   <PersonListing person={a.admin} />
                 </T>
+                <div>
+                  {i18n.t("deny_reason")}:{" "}
+                  <div
+                    className="md-div d-inline-flex"
+                    dangerouslySetInnerHTML={mdToHtml(ra.deny_reason || "")}
+                  />
+                </div>
               </div>
             )}
           </div>
         )}
 
-        <div class="form-group row">
-          <label class="col-sm-2 col-form-label">{i18n.t("deny_reason")}</label>
-          <div class="col-sm-10">
-            <MarkdownTextArea
-              initialContent={this.state.denyReason}
-              onContentChange={this.handleDenyReasonChange}
-              hideNavigationWarnings
-            />
+        {this.state.denyExpanded && (
+          <div class="form-group row">
+            <label class="col-sm-2 col-form-label">
+              {i18n.t("deny_reason")}
+            </label>
+            <div class="col-sm-10">
+              <MarkdownTextArea
+                initialContent={this.state.denyReason}
+                onContentChange={this.handleDenyReasonChange}
+                hideNavigationWarnings
+              />
+            </div>
           </div>
-        </div>
+        )}
         <button
-          className="btn btn-secondary mr-2"
+          className="btn btn-secondary mr-2 my-2"
           onClick={linkEvent(this, this.handleApprove)}
           aria-label={i18n.t("approve")}
         >
@@ -99,6 +112,7 @@ export class RegistrationApplication extends Component<
   }
 
   handleApprove(i: RegistrationApplication) {
+    i.setState({ denyExpanded: false });
     let form: ApproveRegistrationApplication = {
       id: i.props.application.registration_application.id,
       deny_reason: "",
@@ -111,15 +125,20 @@ export class RegistrationApplication extends Component<
   }
 
   handleDeny(i: RegistrationApplication) {
-    let form: ApproveRegistrationApplication = {
-      id: i.props.application.registration_application.id,
-      approve: false,
-      deny_reason: i.state.denyReason,
-      auth: authField(),
-    };
-    WebSocketService.Instance.send(
-      wsClient.approveRegistrationApplication(form)
-    );
+    if (i.state.denyExpanded) {
+      i.setState({ denyExpanded: false });
+      let form: ApproveRegistrationApplication = {
+        id: i.props.application.registration_application.id,
+        approve: false,
+        deny_reason: i.state.denyReason,
+        auth: authField(),
+      };
+      WebSocketService.Instance.send(
+        wsClient.approveRegistrationApplication(form)
+      );
+    } else {
+      i.setState({ denyExpanded: true });
+    }
   }
 
   handleDenyReasonChange(val: string) {
diff --git a/src/shared/components/common/symbols.tsx b/src/shared/components/common/symbols.tsx
index 2035d3c48..d730c15ea 100644
--- a/src/shared/components/common/symbols.tsx
+++ b/src/shared/components/common/symbols.tsx
@@ -12,6 +12,9 @@ export const SYMBOLS = (
     xmlnsXlink="http://www.w3.org/1999/xlink"
   >
     <defs>
+      <symbol id="icon-clipboard" viewBox="0 0 24 24">
+        <path d="M7 5c0 0.552 0.225 1.053 0.586 1.414s0.862 0.586 1.414 0.586h6c0.552 0 1.053-0.225 1.414-0.586s0.586-0.862 0.586-1.414h1c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707v14c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-12c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-14c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293zM9 1c-0.552 0-1.053 0.225-1.414 0.586s-0.586 0.862-0.586 1.414h-1c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v14c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h12c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-14c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879h-1c0-0.552-0.225-1.053-0.586-1.414s-0.862-0.586-1.414-0.586zM9 3h6v2h-6z"></path>
+      </symbol>
       <symbol id="icon-shield" viewBox="0 0 24 24">
         <path d="M12 20.862c-1.184-0.672-4.42-2.695-6.050-5.549-0.079-0.138-0.153-0.276-0.223-0.417-0.456-0.911-0.727-1.878-0.727-2.896v-6.307l7-2.625 7 2.625v6.307c0 1.018-0.271 1.985-0.726 2.897-0.070 0.14-0.145 0.279-0.223 0.417-1.631 2.854-4.867 4.876-6.050 5.549zM12.447 22.894c0 0 4.989-2.475 7.34-6.589 0.096-0.168 0.188-0.34 0.276-0.515 0.568-1.135 0.937-2.408 0.937-3.79v-7c0-0.426-0.267-0.79-0.649-0.936l-8-3c-0.236-0.089-0.485-0.082-0.702 0l-8 3c-0.399 0.149-0.646 0.527-0.649 0.936v7c0 1.382 0.369 2.655 0.938 3.791 0.087 0.175 0.179 0.346 0.276 0.515 2.351 4.114 7.34 6.589 7.34 6.589 0.292 0.146 0.62 0.136 0.894 0z"></path>
       </symbol>

From 733cbf8829ab6bcbf45d9eb2d24db55ad9866197 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Tue, 14 Dec 2021 11:21:04 -0500
Subject: [PATCH 10/13] v0.15.0-rc.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d9afb5391..042adde60 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "lemmy-ui",
   "description": "An isomorphic UI for lemmy",
-  "version": "0.15.0-rc.4",
+  "version": "0.15.0-rc.5",
   "author": "Dessalines <tyhou13@gmx.com>",
   "license": "AGPL-3.0",
   "scripts": {

From a1cb49994f08570141dc4266c1dde11b29ae4ebe Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Tue, 14 Dec 2021 13:03:14 -0500
Subject: [PATCH 11/13] Adding optional auth to modlog fetches.

---
 package.json                     | 2 +-
 src/shared/components/modlog.tsx | 5 +++++
 yarn.lock                        | 8 ++++----
 3 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/package.json b/package.json
index 042adde60..bc940df65 100644
--- a/package.json
+++ b/package.json
@@ -72,7 +72,7 @@
     "husky": "^7.0.4",
     "import-sort-style-module": "^6.0.0",
     "iso-639-1": "^2.1.10",
-    "lemmy-js-client": "0.15.0-rc.2",
+    "lemmy-js-client": "0.15.0-rc.6",
     "lint-staged": "^12.1.2",
     "mini-css-extract-plugin": "^2.4.5",
     "node-fetch": "^2.6.1",
diff --git a/src/shared/components/modlog.tsx b/src/shared/components/modlog.tsx
index 2e8527d1f..cc3c7a1ef 100644
--- a/src/shared/components/modlog.tsx
+++ b/src/shared/components/modlog.tsx
@@ -25,9 +25,11 @@ import { i18n } from "../i18next";
 import { InitialFetchRequest } from "../interfaces";
 import { UserService, WebSocketService } from "../services";
 import {
+  authField,
   fetchLimit,
   isBrowser,
   setIsoData,
+  setOptionalAuth,
   toast,
   wsClient,
   wsJsonToRes,
@@ -482,6 +484,7 @@ export class Modlog extends Component<any, ModlogState> {
       community_id: this.state.communityId,
       page: this.state.page,
       limit: fetchLimit,
+      auth: authField(false),
     };
     WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
 
@@ -507,6 +510,7 @@ export class Modlog extends Component<any, ModlogState> {
     if (communityId) {
       modlogForm.community_id = Number(communityId);
     }
+    setOptionalAuth(modlogForm, req.auth);
 
     promises.push(req.client.getModlog(modlogForm));
 
@@ -514,6 +518,7 @@ export class Modlog extends Component<any, ModlogState> {
       let communityForm: GetCommunity = {
         id: Number(communityId),
       };
+      setOptionalAuth(communityForm, req.auth);
       promises.push(req.client.getCommunity(communityForm));
     }
     return promises;
diff --git a/yarn.lock b/yarn.lock
index 60e4a05be..0815ba316 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4997,10 +4997,10 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-lemmy-js-client@0.15.0-rc.2:
-  version "0.15.0-rc.2"
-  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.15.0-rc.2.tgz#e4e10edf844d90bde9915e578ced319cbd058109"
-  integrity sha512-qb+70MQQJ2pMNxroW+E8MNSXOrSxr3/qLO81GuAa/JT2AzbbY7mO/aXPFSrbyZWu5kn0K99alctEOC3m7mwEGw==
+lemmy-js-client@0.15.0-rc.6:
+  version "0.15.0-rc.6"
+  resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.15.0-rc.6.tgz#5f8552488ed82b8c0962c158edccb8ce1d56389e"
+  integrity sha512-eSEZ5+F2ScKVtx+wwjdReHirJBNLQL2YdTV4aMCBWaSsxfsXUcz18/urbNxo+fNMc7Q4u0aRd3737yKBeMP9Kw==
 
 levn@^0.4.1:
   version "0.4.1"

From 6d214a464607be11adcf606c6b3d5bd4b903bdce Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Tue, 14 Dec 2021 13:03:40 -0500
Subject: [PATCH 12/13] v0.15.0-rc.6

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index bc940df65..ba0665ae5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "lemmy-ui",
   "description": "An isomorphic UI for lemmy",
-  "version": "0.15.0-rc.5",
+  "version": "0.15.0-rc.6",
   "author": "Dessalines <tyhou13@gmx.com>",
   "license": "AGPL-3.0",
   "scripts": {

From b7b6ca7b44ff598139af0638a4e77626580bcf6a Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Wed, 15 Dec 2021 15:16:26 -0500
Subject: [PATCH 13/13] Hide deny / approve buttons

---
 .../common/registration-application.tsx        | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/src/shared/components/common/registration-application.tsx b/src/shared/components/common/registration-application.tsx
index 8b3951c93..cad47b832 100644
--- a/src/shared/components/common/registration-application.tsx
+++ b/src/shared/components/common/registration-application.tsx
@@ -91,14 +91,16 @@ export class RegistrationApplication extends Component<
             </div>
           </div>
         )}
-        <button
-          className="btn btn-secondary mr-2 my-2"
-          onClick={linkEvent(this, this.handleApprove)}
-          aria-label={i18n.t("approve")}
-        >
-          {i18n.t("approve")}
-        </button>
-        {!this.props.application.registration_application.deny_reason && (
+        {(!ra.admin_id || (ra.admin_id && !accepted)) && (
+          <button
+            className="btn btn-secondary mr-2 my-2"
+            onClick={linkEvent(this, this.handleApprove)}
+            aria-label={i18n.t("approve")}
+          >
+            {i18n.t("approve")}
+          </button>
+        )}
+        {(!ra.admin_id || (ra.admin_id && accepted)) && (
           <button
             className="btn btn-secondary mr-2"
             onClick={linkEvent(this, this.handleDeny)}