]> Untitled Git - lemmy-ui.git/blobdiff - src/shared/components/home/site-form.tsx
Merge branch 'main' into fix/fix-badges-spacing-componentize
[lemmy-ui.git] / src / shared / components / home / site-form.tsx
index c418f2089f448272d289aa8a7fa8aa6ed382e5e3..36859225a4db2c4521d8ba622757714f3c10b655 100644 (file)
@@ -1,94 +1,97 @@
-import { None, Option, Some } from "@sniptt/monads";
-import { Component, InfernoMouseEvent, linkEvent } from "inferno";
-import { Prompt } from "inferno-router";
+import { myAuthRequired } from "@utils/app";
+import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
+import {
+  Component,
+  InfernoKeyboardEvent,
+  InfernoMouseEvent,
+  InfernoNode,
+  linkEvent,
+} from "inferno";
 import {
   CreateSite,
   EditSite,
   GetSiteResponse,
+  Instance,
   ListingType,
-  toUndefined,
 } from "lemmy-js-client";
-import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
-import {
-  auth,
-  capitalizeFirstLetter,
-  fetchThemeList,
-  wsClient,
-} from "../../utils";
+import deepEqual from "lodash.isequal";
+import { I18NextService } from "../../services";
 import { Icon, Spinner } from "../common/icon";
 import { ImageUploadForm } from "../common/image-upload-form";
 import { LanguageSelect } from "../common/language-select";
 import { ListingTypeSelect } from "../common/listing-type-select";
 import { MarkdownTextArea } from "../common/markdown-textarea";
+import NavigationPrompt from "../common/navigation-prompt";
 
 interface SiteFormProps {
-  siteRes: GetSiteResponse;
+  blockedInstances?: Instance[];
+  allowedInstances?: Instance[];
   showLocal?: boolean;
+  themeList?: string[];
+  onSaveSite(form: EditSite): void;
+  siteRes: GetSiteResponse;
+  loading: boolean;
 }
 
 interface SiteFormState {
   siteForm: EditSite;
-  loading: boolean;
-  themeList: Option<string[]>;
+  instance_select: {
+    allowed_instances: string;
+    blocked_instances: string;
+  };
+  submitted: boolean;
 }
 
+type InstanceKey = "allowed_instances" | "blocked_instances";
+
 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
-  private emptyState: SiteFormState = {
-    siteForm: new EditSite({
-      enable_downvotes: None,
-      open_registration: None,
-      enable_nsfw: None,
-      name: None,
-      icon: None,
-      banner: None,
-      require_email_verification: None,
-      require_application: None,
-      application_question: None,
-      private_instance: None,
-      default_theme: None,
-      sidebar: None,
-      default_post_listing_type: None,
-      legal_information: None,
-      description: None,
-      community_creation_admin_only: None,
-      application_email_admins: None,
-      hide_modlog_mod_names: None,
-      discussion_languages: None,
-      slur_filter_regex: None,
-      actor_name_max_length: None,
-      rate_limit_message: None,
-      rate_limit_message_per_second: None,
-      rate_limit_comment: None,
-      rate_limit_comment_per_second: None,
-      rate_limit_image: None,
-      rate_limit_image_per_second: None,
-      rate_limit_post: None,
-      rate_limit_post_per_second: None,
-      rate_limit_register: None,
-      rate_limit_register_per_second: None,
-      rate_limit_search: None,
-      rate_limit_search_per_second: None,
-      federation_enabled: None,
-      federation_debug: None,
-      federation_worker_count: None,
-      federation_strict_allowlist: None,
-      federation_http_fetch_retry_limit: None,
-      captcha_enabled: None,
-      captcha_difficulty: None,
-      allowed_instances: None,
-      blocked_instances: None,
-      taglines: None,
-      auth: undefined,
-    }),
-    loading: false,
-    themeList: None,
+  state: SiteFormState = {
+    siteForm: this.initSiteForm(),
+    instance_select: {
+      allowed_instances: "",
+      blocked_instances: "",
+    },
+    submitted: false,
   };
 
+  initSiteForm(): EditSite {
+    const site = this.props.siteRes.site_view.site;
+    const ls = this.props.siteRes.site_view.local_site;
+
+    return {
+      name: site.name,
+      sidebar: site.sidebar,
+      description: site.description,
+      enable_downvotes: ls.enable_downvotes,
+      registration_mode: ls.registration_mode,
+      enable_nsfw: ls.enable_nsfw,
+      community_creation_admin_only: ls.community_creation_admin_only,
+      icon: site.icon,
+      banner: site.banner,
+      require_email_verification: ls.require_email_verification,
+      application_question: ls.application_question,
+      private_instance: ls.private_instance,
+      default_theme: ls.default_theme,
+      default_post_listing_type: ls.default_post_listing_type,
+      legal_information: ls.legal_information,
+      application_email_admins: ls.application_email_admins,
+      reports_email_admins: ls.reports_email_admins,
+      hide_modlog_mod_names: ls.hide_modlog_mod_names,
+      discussion_languages: this.props.siteRes.discussion_languages,
+      slur_filter_regex: ls.slur_filter_regex,
+      actor_name_max_length: ls.actor_name_max_length,
+      federation_enabled: ls.federation_enabled,
+      captcha_enabled: ls.captcha_enabled,
+      captcha_difficulty: ls.captcha_difficulty,
+      allowed_instances: this.props.allowedInstances?.map(i => i.domain),
+      blocked_instances: this.props.blockedInstances?.map(i => i.domain),
+      auth: "TODO",
+    };
+  }
+
   constructor(props: any, context: any) {
     super(props, context);
 
-    this.state = this.emptyState;
     this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
     this.handleSiteLegalInfoChange = this.handleSiteLegalInfoChange.bind(this);
     this.handleSiteApplicationQuestionChange =
@@ -106,1099 +109,751 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
     this.handleDiscussionLanguageChange =
       this.handleDiscussionLanguageChange.bind(this);
 
-    let site = this.props.siteRes.site_view.site;
-    let ls = this.props.siteRes.site_view.local_site;
-    let lsrl = this.props.siteRes.site_view.local_site_rate_limit;
-    this.state = {
-      ...this.state,
-      siteForm: new EditSite({
-        name: Some(site.name),
-        sidebar: site.sidebar,
-        description: site.description,
-        enable_downvotes: Some(ls.enable_downvotes),
-        open_registration: Some(ls.open_registration),
-        enable_nsfw: Some(ls.enable_nsfw),
-        community_creation_admin_only: Some(ls.community_creation_admin_only),
-        icon: site.icon,
-        banner: site.banner,
-        require_email_verification: Some(ls.require_email_verification),
-        require_application: Some(ls.require_application),
-        application_question: ls.application_question,
-        private_instance: Some(ls.private_instance),
-        default_theme: Some(ls.default_theme),
-        default_post_listing_type: Some(ls.default_post_listing_type),
-        legal_information: ls.legal_information,
-        application_email_admins: Some(ls.application_email_admins),
-        hide_modlog_mod_names: Some(ls.hide_modlog_mod_names),
-        discussion_languages: Some(this.props.siteRes.discussion_languages),
-        slur_filter_regex: ls.slur_filter_regex,
-        actor_name_max_length: Some(ls.actor_name_max_length),
-        rate_limit_message: Some(lsrl.message),
-        rate_limit_message_per_second: Some(lsrl.message_per_second),
-        rate_limit_comment: Some(lsrl.comment),
-        rate_limit_comment_per_second: Some(lsrl.comment_per_second),
-        rate_limit_image: Some(lsrl.image),
-        rate_limit_image_per_second: Some(lsrl.image_per_second),
-        rate_limit_post: Some(lsrl.post),
-        rate_limit_post_per_second: Some(lsrl.post_per_second),
-        rate_limit_register: Some(lsrl.register),
-        rate_limit_register_per_second: Some(lsrl.register_per_second),
-        rate_limit_search: Some(lsrl.search),
-        rate_limit_search_per_second: Some(lsrl.search_per_second),
-        federation_enabled: Some(ls.federation_enabled),
-        federation_debug: Some(ls.federation_debug),
-        federation_worker_count: Some(ls.federation_worker_count),
-        federation_strict_allowlist: Some(ls.federation_strict_allowlist),
-        federation_http_fetch_retry_limit: Some(
-          ls.federation_http_fetch_retry_limit
-        ),
-        captcha_enabled: Some(ls.captcha_enabled),
-        captcha_difficulty: Some(ls.captcha_difficulty),
-        allowed_instances: this.props.siteRes.federated_instances.andThen(
-          f => f.allowed
-        ),
-        blocked_instances: this.props.siteRes.federated_instances.andThen(
-          f => f.blocked
-        ),
-        taglines: this.props.siteRes.taglines.map(x => x.map(y => y.content)),
-        auth: undefined,
-      }),
-    };
-  }
+    this.handleAddInstance = this.handleAddInstance.bind(this);
+    this.handleRemoveInstance = this.handleRemoveInstance.bind(this);
 
-  async componentDidMount() {
-    this.setState({ themeList: Some(await fetchThemeList()) });
-  }
-
-  // Necessary to stop the loading
-  componentWillReceiveProps() {
-    this.setState({ loading: false });
-  }
-
-  componentDidUpdate() {
-    if (
-      !this.state.loading &&
-      !this.props.siteRes.site_view.local_site.site_setup &&
-      (this.state.siteForm.name ||
-        this.state.siteForm.sidebar ||
-        this.state.siteForm.application_question ||
-        this.state.siteForm.description)
-    ) {
-      window.onbeforeunload = () => true;
-    } else {
-      window.onbeforeunload = undefined;
-    }
-  }
-
-  componentWillUnmount() {
-    window.onbeforeunload = null;
+    this.handleInstanceEnterPress = this.handleInstanceEnterPress.bind(this);
+    this.handleInstanceTextChange = this.handleInstanceTextChange.bind(this);
   }
 
   render() {
-    let siteSetup = this.props.siteRes.site_view.local_site.site_setup;
+    const siteSetup = this.props.siteRes.site_view.local_site.site_setup;
     return (
-      <>
-        <Prompt
+      <form
+        className="site-form"
+        onSubmit={linkEvent(this, this.handleSaveSiteSubmit)}
+      >
+        <NavigationPrompt
           when={
-            !this.state.loading &&
+            !this.props.loading &&
             !siteSetup &&
-            (this.state.siteForm.name ||
+            !!(
+              this.state.siteForm.name ||
               this.state.siteForm.sidebar ||
               this.state.siteForm.application_question ||
-              this.state.siteForm.description)
+              this.state.siteForm.description
+            ) &&
+            !this.state.submitted
           }
-          message={i18n.t("block_leaving")}
         />
-        <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
-          <h5>{`${
-            siteSetup
-              ? capitalizeFirstLetter(i18n.t("save"))
-              : capitalizeFirstLetter(i18n.t("name"))
-          } ${i18n.t("your_site")}`}</h5>
-          <div className="form-group row">
-            <label className="col-12 col-form-label" htmlFor="create-site-name">
-              {i18n.t("name")}
-            </label>
-            <div className="col-12">
-              <input
-                type="text"
-                id="create-site-name"
-                className="form-control"
-                value={toUndefined(this.state.siteForm.name)}
-                onInput={linkEvent(this, this.handleSiteNameChange)}
-                required
-                minLength={3}
-                maxLength={20}
-              />
-            </div>
+        <h5>{`${
+          siteSetup
+            ? capitalizeFirstLetter(I18NextService.i18n.t("edit"))
+            : capitalizeFirstLetter(I18NextService.i18n.t("setup"))
+        } ${I18NextService.i18n.t("your_site")}`}</h5>
+        <div className="mb-3 row">
+          <label className="col-12 col-form-label" htmlFor="create-site-name">
+            {I18NextService.i18n.t("name")}
+          </label>
+          <div className="col-12">
+            <input
+              type="text"
+              id="create-site-name"
+              className="form-control"
+              value={this.state.siteForm.name}
+              onInput={linkEvent(this, this.handleSiteNameChange)}
+              required
+              minLength={3}
+              maxLength={20}
+            />
           </div>
-          <div className="form-group">
-            <label>{i18n.t("icon")}</label>
+        </div>
+        <div className="row mb-3">
+          <label className="col-sm-2 col-form-label">
+            {I18NextService.i18n.t("icon")}
+          </label>
+          <div className="col-sm-10">
             <ImageUploadForm
-              uploadTitle={i18n.t("upload_icon")}
+              uploadTitle={I18NextService.i18n.t("upload_icon")}
               imageSrc={this.state.siteForm.icon}
               onUpload={this.handleIconUpload}
               onRemove={this.handleIconRemove}
               rounded
             />
           </div>
-          <div className="form-group">
-            <label>{i18n.t("banner")}</label>
+        </div>
+        <div className="row mb-3">
+          <label className="col-sm-2 col-form-label">
+            {I18NextService.i18n.t("banner")}
+          </label>
+          <div className="col-sm-10">
             <ImageUploadForm
-              uploadTitle={i18n.t("upload_banner")}
+              uploadTitle={I18NextService.i18n.t("upload_banner")}
               imageSrc={this.state.siteForm.banner}
               onUpload={this.handleBannerUpload}
               onRemove={this.handleBannerRemove}
             />
           </div>
-          <div className="form-group row">
-            <label className="col-12 col-form-label" htmlFor="site-desc">
-              {i18n.t("description")}
-            </label>
-            <div className="col-12">
-              <input
-                type="text"
-                className="form-control"
-                id="site-desc"
-                value={toUndefined(this.state.siteForm.description)}
-                onInput={linkEvent(this, this.handleSiteDescChange)}
-                maxLength={150}
-              />
-            </div>
-          </div>
-          <div className="form-group row">
-            <label className="col-12 col-form-label">{i18n.t("sidebar")}</label>
-            <div className="col-12">
-              <MarkdownTextArea
-                initialContent={this.state.siteForm.sidebar}
-                initialLanguageId={None}
-                placeholder={None}
-                buttonTitle={None}
-                maxLength={None}
-                onContentChange={this.handleSiteSidebarChange}
-                hideNavigationWarnings
-                allLanguages={[]}
-              />
-            </div>
-          </div>
-          <div className="form-group row">
-            <label className="col-12 col-form-label">
-              {i18n.t("legal_information")}
-            </label>
-            <div className="col-12">
-              <MarkdownTextArea
-                initialContent={this.state.siteForm.legal_information}
-                initialLanguageId={None}
-                placeholder={None}
-                buttonTitle={None}
-                maxLength={None}
-                onContentChange={this.handleSiteLegalInfoChange}
-                hideNavigationWarnings
-                allLanguages={[]}
-              />
-            </div>
-          </div>
-          {this.state.siteForm.require_application.unwrapOr(false) && (
-            <div className="form-group row">
-              <label className="col-12 col-form-label">
-                {i18n.t("application_questionnaire")}
-              </label>
-              <div className="col-12">
-                <MarkdownTextArea
-                  initialContent={this.state.siteForm.application_question}
-                  initialLanguageId={None}
-                  placeholder={None}
-                  buttonTitle={None}
-                  maxLength={None}
-                  onContentChange={this.handleSiteApplicationQuestionChange}
-                  hideNavigationWarnings
-                  allLanguages={[]}
-                />
-              </div>
-            </div>
-          )}
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-downvotes"
-                  type="checkbox"
-                  checked={toUndefined(this.state.siteForm.enable_downvotes)}
-                  onChange={linkEvent(
-                    this,
-                    this.handleSiteEnableDownvotesChange
-                  )}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-downvotes"
-                >
-                  {i18n.t("enable_downvotes")}
-                </label>
-              </div>
-            </div>
-          </div>
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-enable-nsfw"
-                  type="checkbox"
-                  checked={toUndefined(this.state.siteForm.enable_nsfw)}
-                  onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-enable-nsfw"
-                >
-                  {i18n.t("enable_nsfw")}
-                </label>
-              </div>
-            </div>
-          </div>
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-open-registration"
-                  type="checkbox"
-                  checked={toUndefined(this.state.siteForm.open_registration)}
-                  onChange={linkEvent(
-                    this,
-                    this.handleSiteOpenRegistrationChange
-                  )}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-open-registration"
-                >
-                  {i18n.t("open_registration")}
-                </label>
-              </div>
-            </div>
-          </div>
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-community-creation-admin-only"
-                  type="checkbox"
-                  checked={toUndefined(
-                    this.state.siteForm.community_creation_admin_only
-                  )}
-                  onChange={linkEvent(
-                    this,
-                    this.handleSiteCommunityCreationAdminOnly
-                  )}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-community-creation-admin-only"
-                >
-                  {i18n.t("community_creation_admin_only")}
-                </label>
-              </div>
-            </div>
-          </div>
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-require-email-verification"
-                  type="checkbox"
-                  checked={toUndefined(
-                    this.state.siteForm.require_email_verification
-                  )}
-                  onChange={linkEvent(
-                    this,
-                    this.handleSiteRequireEmailVerification
-                  )}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-require-email-verification"
-                >
-                  {i18n.t("require_email_verification")}
-                </label>
-              </div>
-            </div>
+        </div>
+        <div className="mb-3 row">
+          <label className="col-12 col-form-label" htmlFor="site-desc">
+            {I18NextService.i18n.t("description")}
+          </label>
+          <div className="col-12">
+            <input
+              type="text"
+              className="form-control"
+              id="site-desc"
+              value={this.state.siteForm.description}
+              onInput={linkEvent(this, this.handleSiteDescChange)}
+              maxLength={150}
+            />
           </div>
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-require-application"
-                  type="checkbox"
-                  checked={toUndefined(this.state.siteForm.require_application)}
-                  onChange={linkEvent(this, this.handleSiteRequireApplication)}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-require-application"
-                >
-                  {i18n.t("require_registration_application")}
-                </label>
-              </div>
-            </div>
+        </div>
+        <div className="mb-3 row">
+          <label className="col-12 col-form-label">
+            {I18NextService.i18n.t("sidebar")}
+          </label>
+          <div className="col-12">
+            <MarkdownTextArea
+              initialContent={this.state.siteForm.sidebar}
+              onContentChange={this.handleSiteSidebarChange}
+              hideNavigationWarnings
+              allLanguages={[]}
+              siteLanguages={[]}
+            />
           </div>
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-application-email-admins"
-                  type="checkbox"
-                  checked={toUndefined(
-                    this.state.siteForm.application_email_admins
-                  )}
-                  onChange={linkEvent(
-                    this,
-                    this.handleSiteApplicationEmailAdmins
-                  )}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-email-admins"
-                >
-                  {i18n.t("application_email_admins")}
-                </label>
-              </div>
-            </div>
+        </div>
+        <div className="mb-3 row">
+          <label className="col-12 col-form-label">
+            {I18NextService.i18n.t("legal_information")}
+          </label>
+          <div className="col-12">
+            <MarkdownTextArea
+              initialContent={this.state.siteForm.legal_information}
+              onContentChange={this.handleSiteLegalInfoChange}
+              hideNavigationWarnings
+              allLanguages={[]}
+              siteLanguages={[]}
+            />
           </div>
-          <div className="form-group row">
-            <div className="col-12">
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
+              <input
+                className="form-check-input"
+                id="create-site-downvotes"
+                type="checkbox"
+                checked={this.state.siteForm.enable_downvotes}
+                onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)}
+              />
               <label
-                className="form-check-label mr-2"
-                htmlFor="create-site-default-theme"
+                className="form-check-label"
+                htmlFor="create-site-downvotes"
               >
-                {i18n.t("theme")}
+                {I18NextService.i18n.t("enable_downvotes")}
               </label>
-              <select
-                id="create-site-default-theme"
-                value={toUndefined(this.state.siteForm.default_theme)}
-                onChange={linkEvent(this, this.handleSiteDefaultTheme)}
-                className="custom-select w-auto"
-              >
-                <option value="browser">{i18n.t("browser_default")}</option>
-                {this.state.themeList.unwrapOr([]).map(theme => (
-                  <option key={theme} value={theme}>
-                    {theme}
-                  </option>
-                ))}
-              </select>
-            </div>
-          </div>
-          {this.props.showLocal && (
-            <form className="form-group row">
-              <label className="col-sm-3">{i18n.t("listing_type")}</label>
-              <div className="col-sm-9">
-                <ListingTypeSelect
-                  type_={
-                    ListingType[
-                      this.state.siteForm.default_post_listing_type.unwrapOr(
-                        "Local"
-                      )
-                    ]
-                  }
-                  showLocal
-                  showSubscribed={false}
-                  onChange={this.handleDefaultPostListingTypeChange}
-                />
-              </div>
-            </form>
-          )}
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-private-instance"
-                  type="checkbox"
-                  checked={toUndefined(this.state.siteForm.private_instance)}
-                  onChange={linkEvent(this, this.handleSitePrivateInstance)}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-private-instance"
-                >
-                  {i18n.t("private_instance")}
-                </label>
-              </div>
             </div>
           </div>
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-hide-modlog-mod-names"
-                  type="checkbox"
-                  checked={toUndefined(
-                    this.state.siteForm.hide_modlog_mod_names
-                  )}
-                  onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-hide-modlog-mod-names"
-                >
-                  {i18n.t("hide_modlog_mod_names")}
-                </label>
-              </div>
-            </div>
-          </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-slur-filter-regex"
-            >
-              {i18n.t("slur_filter_regex")}
-            </label>
-            <div className="col-12">
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="text"
-                id="create-site-slur-filter-regex"
-                placeholder="(word1|word2)"
-                className="form-control"
-                value={toUndefined(this.state.siteForm.slur_filter_regex)}
-                onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
-                minLength={3}
+                className="form-check-input"
+                id="create-site-enable-nsfw"
+                type="checkbox"
+                checked={this.state.siteForm.enable_nsfw}
+                onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
               />
+              <label
+                className="form-check-label"
+                htmlFor="create-site-enable-nsfw"
+              >
+                {I18NextService.i18n.t("enable_nsfw")}
+              </label>
             </div>
           </div>
-          <LanguageSelect
-            allLanguages={this.props.siteRes.all_languages}
-            selectedLanguageIds={this.state.siteForm.discussion_languages}
-            multiple={true}
-            onChange={this.handleDiscussionLanguageChange}
-          />
-          <div className="form-group row">
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
             <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-actor-name"
+              className="form-check-label me-2"
+              htmlFor="create-site-registration-mode"
             >
-              {i18n.t("actor_name_max_length")}
+              {I18NextService.i18n.t("registration_mode")}
             </label>
-            <div className="col-12">
-              <input
-                type="number"
-                id="create-site-actor-name"
-                className="form-control"
-                min={5}
-                value={toUndefined(this.state.siteForm.actor_name_max_length)}
-                onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
-              />
-            </div>
-          </div>
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-federation-enabled"
-                  type="checkbox"
-                  checked={toUndefined(this.state.siteForm.federation_enabled)}
-                  onChange={linkEvent(this, this.handleSiteFederationEnabled)}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-federation-enabled"
-                >
-                  {i18n.t("federation_enabled")}
-                </label>
-              </div>
-            </div>
-          </div>
-          {this.state.siteForm.federation_enabled.unwrapOr(false) && (
-            <>
-              <div className="form-group row">
-                <label
-                  className="col-12 col-form-label"
-                  htmlFor="create-site-allowed-instances"
-                >
-                  {i18n.t("allowed_instances")}
-                </label>
-                <div className="col-12">
-                  <input
-                    type="text"
-                    placeholder="instance1.tld,instance2.tld"
-                    id="create-site-allowed-instances"
-                    className="form-control"
-                    value={this.instancesToString(
-                      this.state.siteForm.allowed_instances
-                    )}
-                    onInput={linkEvent(this, this.handleSiteAllowedInstances)}
-                  />
-                </div>
-              </div>
-              <div className="form-group row">
-                <label
-                  className="col-12 col-form-label"
-                  htmlFor="create-site-blocked-instances"
-                >
-                  {i18n.t("blocked_instances")}
-                </label>
-                <div className="col-12">
-                  <input
-                    type="text"
-                    placeholder="instance1.tld,instance2.tld"
-                    id="create-site-blocked-instances"
-                    className="form-control"
-                    value={this.instancesToString(
-                      this.state.siteForm.blocked_instances
-                    )}
-                    onInput={linkEvent(this, this.handleSiteBlockedInstances)}
-                  />
-                </div>
-              </div>
-              <div className="form-group row">
-                <div className="col-12">
-                  <div className="form-check">
-                    <input
-                      className="form-check-input"
-                      id="create-site-federation-debug"
-                      type="checkbox"
-                      checked={toUndefined(
-                        this.state.siteForm.federation_debug
-                      )}
-                      onChange={linkEvent(this, this.handleSiteFederationDebug)}
-                    />
-                    <label
-                      className="form-check-label"
-                      htmlFor="create-site-federation-debug"
-                    >
-                      {i18n.t("federation_debug")}
-                    </label>
-                  </div>
-                </div>
-              </div>
-              <div className="form-group row">
-                <div className="col-12">
-                  <div className="form-check">
-                    <input
-                      className="form-check-input"
-                      id="create-site-federation-strict-allowlist"
-                      type="checkbox"
-                      checked={toUndefined(
-                        this.state.siteForm.federation_strict_allowlist
-                      )}
-                      onChange={linkEvent(
-                        this,
-                        this.handleSiteFederationStrictAllowList
-                      )}
-                    />
-                    <label
-                      className="form-check-label"
-                      htmlFor="create-site-federation-strict-allowlist"
-                    >
-                      {i18n.t("federation_strict_allowlist")}
-                    </label>
-                  </div>
-                </div>
-              </div>
-              <div className="form-group row">
-                <label
-                  className="col-12 col-form-label"
-                  htmlFor="create-site-federation-http-fetch-retry-limit"
-                >
-                  {i18n.t("federation_http_fetch_retry_limit")}
-                </label>
-                <div className="col-12">
-                  <input
-                    type="number"
-                    id="create-site-federation-http-fetch-retry-limit"
-                    className="form-control"
-                    min={0}
-                    value={toUndefined(
-                      this.state.siteForm.federation_http_fetch_retry_limit
-                    )}
-                    onInput={linkEvent(
-                      this,
-                      this.handleSiteFederationHttpFetchRetryLimit
-                    )}
-                  />
-                </div>
-              </div>
-              <div className="form-group row">
-                <label
-                  className="col-12 col-form-label"
-                  htmlFor="create-site-federation-worker-count"
-                >
-                  {i18n.t("federation_worker_count")}
-                </label>
-                <div className="col-12">
-                  <input
-                    type="number"
-                    id="create-site-federation-worker-count"
-                    className="form-control"
-                    min={0}
-                    value={toUndefined(
-                      this.state.siteForm.federation_worker_count
-                    )}
-                    onInput={linkEvent(
-                      this,
-                      this.handleSiteFederationWorkerCount
-                    )}
-                  />
-                </div>
-              </div>
-            </>
-          )}
-          <div className="form-group row">
-            <div className="col-12">
-              <div className="form-check">
-                <input
-                  className="form-check-input"
-                  id="create-site-captcha-enabled"
-                  type="checkbox"
-                  checked={toUndefined(this.state.siteForm.captcha_enabled)}
-                  onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
-                />
-                <label
-                  className="form-check-label"
-                  htmlFor="create-site-captcha-enabled"
-                >
-                  {i18n.t("captcha_enabled")}
-                </label>
-              </div>
-            </div>
-          </div>
-          {this.state.siteForm.captcha_enabled.unwrapOr(false) && (
-            <div className="form-group row">
-              <div className="col-12">
-                <label
-                  className="form-check-label mr-2"
-                  htmlFor="create-site-captcha-difficulty"
-                >
-                  {i18n.t("captcha_difficulty")}
-                </label>
-                <select
-                  id="create-site-captcha-difficulty"
-                  value={toUndefined(this.state.siteForm.captcha_difficulty)}
-                  onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
-                  className="custom-select w-auto"
-                >
-                  <option value="easy">{i18n.t("easy")}</option>
-                  <option value="medium">{i18n.t("medium")}</option>
-                  <option value="hard">{i18n.t("hard")}</option>
-                </select>
-              </div>
-            </div>
-          )}
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-message"
+            <select
+              id="create-site-registration-mode"
+              value={this.state.siteForm.registration_mode}
+              onChange={linkEvent(this, this.handleSiteRegistrationModeChange)}
+              className="form-select d-inline-block w-auto"
             >
-              {i18n.t("rate_limit_message")}
+              <option value={"RequireApplication"}>
+                {I18NextService.i18n.t("require_registration_application")}
+              </option>
+              <option value={"Open"}>
+                {I18NextService.i18n.t("open_registration")}
+              </option>
+              <option value={"Closed"}>
+                {I18NextService.i18n.t("close_registration")}
+              </option>
+            </select>
+          </div>
+        </div>
+        {this.state.siteForm.registration_mode == "RequireApplication" && (
+          <div className="mb-3 row">
+            <label className="col-12 col-form-label">
+              {I18NextService.i18n.t("application_questionnaire")}
             </label>
             <div className="col-12">
-              <input
-                type="number"
-                id="create-site-rate-limit-message"
-                className="form-control"
-                min={0}
-                value={toUndefined(this.state.siteForm.rate_limit_message)}
-                onInput={linkEvent(this, this.handleSiteRateLimitMessage)}
+              <MarkdownTextArea
+                initialContent={this.state.siteForm.application_question}
+                onContentChange={this.handleSiteApplicationQuestionChange}
+                hideNavigationWarnings
+                allLanguages={[]}
+                siteLanguages={[]}
               />
             </div>
           </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-message-per-second"
-            >
-              {i18n.t("per_second")}
-            </label>
-            <div className="col-12">
+        )}
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="number"
-                id="create-site-rate-limit-message-per-second"
-                className="form-control"
-                min={0}
-                value={toUndefined(
-                  this.state.siteForm.rate_limit_message_per_second
-                )}
-                onInput={linkEvent(
+                className="form-check-input"
+                id="create-site-community-creation-admin-only"
+                type="checkbox"
+                checked={this.state.siteForm.community_creation_admin_only}
+                onChange={linkEvent(
                   this,
-                  this.handleSiteRateLimitMessagePerSecond
+                  this.handleSiteCommunityCreationAdminOnly
                 )}
               />
+              <label
+                className="form-check-label"
+                htmlFor="create-site-community-creation-admin-only"
+              >
+                {I18NextService.i18n.t("community_creation_admin_only")}
+              </label>
             </div>
           </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-post"
-            >
-              {i18n.t("rate_limit_post")}
-            </label>
-            <div className="col-12">
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="number"
-                id="create-site-rate-limit-post"
-                className="form-control"
-                min={0}
-                value={toUndefined(this.state.siteForm.rate_limit_post)}
-                onInput={linkEvent(this, this.handleSiteRateLimitPost)}
+                className="form-check-input"
+                id="create-site-require-email-verification"
+                type="checkbox"
+                checked={this.state.siteForm.require_email_verification}
+                onChange={linkEvent(
+                  this,
+                  this.handleSiteRequireEmailVerification
+                )}
               />
+              <label
+                className="form-check-label"
+                htmlFor="create-site-require-email-verification"
+              >
+                {I18NextService.i18n.t("require_email_verification")}
+              </label>
             </div>
           </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-post-per-second"
-            >
-              {i18n.t("per_second")}
-            </label>
-            <div className="col-12">
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="number"
-                id="create-site-rate-limit-post-per-second"
-                className="form-control"
-                min={0}
-                value={toUndefined(
-                  this.state.siteForm.rate_limit_post_per_second
+                className="form-check-input"
+                id="create-site-application-email-admins"
+                type="checkbox"
+                checked={this.state.siteForm.application_email_admins}
+                onChange={linkEvent(
+                  this,
+                  this.handleSiteApplicationEmailAdmins
                 )}
-                onInput={linkEvent(this, this.handleSiteRateLimitPostPerSecond)}
               />
+              <label
+                className="form-check-label"
+                htmlFor="create-site-email-admins"
+              >
+                {I18NextService.i18n.t("application_email_admins")}
+              </label>
             </div>
           </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-register"
-            >
-              {i18n.t("rate_limit_register")}
-            </label>
-            <div className="col-12">
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="number"
-                id="create-site-rate-limit-register"
-                className="form-control"
-                min={0}
-                value={toUndefined(this.state.siteForm.rate_limit_register)}
-                onInput={linkEvent(this, this.handleSiteRateLimitRegister)}
+                className="form-check-input"
+                id="create-site-reports-email-admins"
+                type="checkbox"
+                checked={this.state.siteForm.reports_email_admins}
+                onChange={linkEvent(this, this.handleSiteReportsEmailAdmins)}
               />
+              <label
+                className="form-check-label"
+                htmlFor="create-site-reports-email-admins"
+              >
+                {I18NextService.i18n.t("reports_email_admins")}
+              </label>
             </div>
           </div>
-          <div className="form-group row">
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
             <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-register-per-second"
+              className="form-check-label me-2"
+              htmlFor="create-site-default-theme"
             >
-              {i18n.t("per_second")}
+              {I18NextService.i18n.t("theme")}
             </label>
-            <div className="col-12">
-              <input
-                type="number"
-                id="create-site-rate-limit-register-per-second"
-                className="form-control"
-                min={0}
-                value={toUndefined(
-                  this.state.siteForm.rate_limit_register_per_second
-                )}
-                onInput={linkEvent(
-                  this,
-                  this.handleSiteRateLimitRegisterPerSecond
-                )}
-              />
-            </div>
-          </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-image"
+            <select
+              id="create-site-default-theme"
+              value={this.state.siteForm.default_theme}
+              onChange={linkEvent(this, this.handleSiteDefaultTheme)}
+              className="form-select d-inline-block w-auto"
             >
-              {i18n.t("rate_limit_image")}
+              <option value="browser">
+                {I18NextService.i18n.t("browser_default")}
+              </option>
+              {this.props.themeList?.map(theme => (
+                <option key={theme} value={theme}>
+                  {theme}
+                </option>
+              ))}
+            </select>
+          </div>
+        </div>
+        {this.props.showLocal && (
+          <form className="mb-3 row">
+            <label className="col-sm-3 col-form-label">
+              {I18NextService.i18n.t("listing_type")}
             </label>
-            <div className="col-12">
-              <input
-                type="number"
-                id="create-site-rate-limit-image"
-                className="form-control"
-                min={0}
-                value={toUndefined(this.state.siteForm.rate_limit_image)}
-                onInput={linkEvent(this, this.handleSiteRateLimitImage)}
+            <div className="col-sm-9">
+              <ListingTypeSelect
+                type_={this.state.siteForm.default_post_listing_type ?? "Local"}
+                showLocal
+                showSubscribed={false}
+                onChange={this.handleDefaultPostListingTypeChange}
               />
             </div>
-          </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-image-per-second"
-            >
-              {i18n.t("per_second")}
-            </label>
-            <div className="col-12">
+          </form>
+        )}
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="number"
-                id="create-site-rate-limit-image-per-second"
-                className="form-control"
-                min={0}
-                value={toUndefined(
-                  this.state.siteForm.rate_limit_image_per_second
-                )}
-                onInput={linkEvent(
-                  this,
-                  this.handleSiteRateLimitImagePerSecond
-                )}
+                className="form-check-input"
+                id="create-site-private-instance"
+                type="checkbox"
+                checked={this.state.siteForm.private_instance}
+                onChange={linkEvent(this, this.handleSitePrivateInstance)}
               />
+              <label
+                className="form-check-label"
+                htmlFor="create-site-private-instance"
+              >
+                {I18NextService.i18n.t("private_instance")}
+              </label>
             </div>
           </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-comment"
-            >
-              {i18n.t("rate_limit_comment")}
-            </label>
-            <div className="col-12">
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="number"
-                id="create-site-rate-limit-comment"
-                className="form-control"
-                min={0}
-                value={toUndefined(this.state.siteForm.rate_limit_comment)}
-                onInput={linkEvent(this, this.handleSiteRateLimitComment)}
+                className="form-check-input"
+                id="create-site-hide-modlog-mod-names"
+                type="checkbox"
+                checked={this.state.siteForm.hide_modlog_mod_names}
+                onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
               />
+              <label
+                className="form-check-label"
+                htmlFor="create-site-hide-modlog-mod-names"
+              >
+                {I18NextService.i18n.t("hide_modlog_mod_names")}
+              </label>
             </div>
           </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-comment-per-second"
-            >
-              {i18n.t("per_second")}
-            </label>
-            <div className="col-12">
-              <input
-                type="number"
-                id="create-site-rate-limit-comment-per-second"
-                className="form-control"
-                min={0}
-                value={toUndefined(
-                  this.state.siteForm.rate_limit_comment_per_second
-                )}
-                onInput={linkEvent(
-                  this,
-                  this.handleSiteRateLimitCommentPerSecond
-                )}
-              />
-            </div>
+        </div>
+        <div className="mb-3 row">
+          <label
+            className="col-12 col-form-label"
+            htmlFor="create-site-slur-filter-regex"
+          >
+            {I18NextService.i18n.t("slur_filter_regex")}
+          </label>
+          <div className="col-12">
+            <input
+              type="text"
+              id="create-site-slur-filter-regex"
+              placeholder="(word1|word2)"
+              className="form-control"
+              value={this.state.siteForm.slur_filter_regex}
+              onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
+              minLength={3}
+            />
           </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-search"
-            >
-              {i18n.t("rate_limit_search")}
-            </label>
-            <div className="col-12">
+        </div>
+        <LanguageSelect
+          allLanguages={this.props.siteRes.all_languages}
+          siteLanguages={this.props.siteRes.discussion_languages}
+          selectedLanguageIds={this.state.siteForm.discussion_languages}
+          multiple={true}
+          onChange={this.handleDiscussionLanguageChange}
+          showAll
+        />
+        <div className="mb-3 row">
+          <label
+            className="col-12 col-form-label"
+            htmlFor="create-site-actor-name"
+          >
+            {I18NextService.i18n.t("actor_name_max_length")}
+          </label>
+          <div className="col-12">
+            <input
+              type="number"
+              id="create-site-actor-name"
+              className="form-control"
+              min={5}
+              value={this.state.siteForm.actor_name_max_length}
+              onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
+            />
+          </div>
+        </div>
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="number"
-                id="create-site-rate-limit-search"
-                className="form-control"
-                min={0}
-                value={toUndefined(this.state.siteForm.rate_limit_search)}
-                onInput={linkEvent(this, this.handleSiteRateLimitSearch)}
+                className="form-check-input"
+                id="create-site-federation-enabled"
+                type="checkbox"
+                checked={this.state.siteForm.federation_enabled}
+                onChange={linkEvent(this, this.handleSiteFederationEnabled)}
               />
+              <label
+                className="form-check-label"
+                htmlFor="create-site-federation-enabled"
+              >
+                {I18NextService.i18n.t("federation_enabled")}
+              </label>
             </div>
           </div>
-          <div className="form-group row">
-            <label
-              className="col-12 col-form-label"
-              htmlFor="create-site-rate-limit-search-per-second"
-            >
-              {i18n.t("per_second")}
-            </label>
-            <div className="col-12">
+        </div>
+        {this.state.siteForm.federation_enabled && (
+          <>
+            <div className="mb-3 row">
+              {this.federatedInstanceSelect("allowed_instances")}
+              {this.federatedInstanceSelect("blocked_instances")}
+            </div>
+            <div className="mb-3 row">
+              <div className="col-12">
+                <div className="form-check">
+                  <input
+                    className="form-check-input"
+                    id="create-site-federation-debug"
+                    type="checkbox"
+                    checked={this.state.siteForm.federation_debug}
+                    onChange={linkEvent(this, this.handleSiteFederationDebug)}
+                  />
+                  <label
+                    className="form-check-label"
+                    htmlFor="create-site-federation-debug"
+                  >
+                    {I18NextService.i18n.t("federation_debug")}
+                  </label>
+                </div>
+              </div>
+            </div>
+          </>
+        )}
+        <div className="mb-3 row">
+          <div className="col-12">
+            <div className="form-check">
               <input
-                type="number"
-                id="create-site-rate-limit-search-per-second"
-                className="form-control"
-                min={0}
-                value={toUndefined(
-                  this.state.siteForm.rate_limit_search_per_second
-                )}
-                onInput={linkEvent(
-                  this,
-                  this.handleSiteRateLimitSearchPerSecond
-                )}
+                className="form-check-input"
+                id="create-site-captcha-enabled"
+                type="checkbox"
+                checked={this.state.siteForm.captcha_enabled}
+                onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
               />
-            </div>
-          </div>
-          {siteSetup && <div className="form-group row">
-            <h5 className="col-12">{i18n.t("taglines")}</h5>
-            <div className="table-responsive col-12">
-              <table id="taglines_table" className="table table-sm table-hover">
-                <thead className="pointer"></thead>
-                <tbody>
-                  {this.state.siteForm.taglines
-                    .unwrapOr([])
-                    .map((cv, index) => (
-                      <tr key={index}>
-                        <td>
-                          <MarkdownTextArea
-                            initialContent={Some(cv)}
-                            initialLanguageId={None}
-                            placeholder={None}
-                            buttonTitle={None}
-                            maxLength={None}
-                            onContentChange={s =>
-                              this.handleTaglineChange(this, index, s)
-                            }
-                            hideNavigationWarnings
-                            allLanguages={this.props.siteRes.all_languages}
-                          />
-                        </td>
-                        <td className="text-right">
-                          <button
-                            className="btn btn-link btn-animate text-muted"
-                            onClick={e =>
-                              this.handleDeleteTaglineClick(this, index, e)
-                            }
-                            data-tippy-content={i18n.t("delete")}
-                            aria-label={i18n.t("delete")}
-                          >
-                            <Icon
-                              icon="trash"
-                              classes={`icon-inline text-danger`}
-                            />
-                          </button>
-                        </td>
-                      </tr>
-                    ))}
-                </tbody>
-              </table>
-              <button
-                className="btn btn-sm btn-secondary mr-2"
-                onClick={e => this.handleAddTaglineClick(this, e)}
+              <label
+                className="form-check-label"
+                htmlFor="create-site-captcha-enabled"
               >
-                {i18n.t("add_tagline")}
-              </button>
+                {I18NextService.i18n.t("captcha_enabled")}
+              </label>
             </div>
-          </div>}
-          <div className="form-group row">
+          </div>
+        </div>
+        {this.state.siteForm.captcha_enabled && (
+          <div className="mb-3 row">
             <div className="col-12">
-              <button
-                type="submit"
-                className="btn btn-secondary mr-2"
-                disabled={this.state.loading}
+              <label
+                className="form-check-label me-2"
+                htmlFor="create-site-captcha-difficulty"
               >
-                {this.state.loading ? (
-                  <Spinner />
-                ) : siteSetup ? (
-                  capitalizeFirstLetter(i18n.t("save"))
-                ) : (
-                  capitalizeFirstLetter(i18n.t("create"))
-                )}
-              </button>
+                {I18NextService.i18n.t("captcha_difficulty")}
+              </label>
+              <select
+                id="create-site-captcha-difficulty"
+                value={this.state.siteForm.captcha_difficulty}
+                onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
+                className="form-select d-inline-block w-auto"
+              >
+                <option value="easy">{I18NextService.i18n.t("easy")}</option>
+                <option value="medium">
+                  {I18NextService.i18n.t("medium")}
+                </option>
+                <option value="hard">{I18NextService.i18n.t("hard")}</option>
+              </select>
             </div>
           </div>
-        </form>
-      </>
+        )}
+        <div className="mb-3 row">
+          <div className="col-12">
+            <button
+              type="submit"
+              className="btn btn-secondary me-2"
+              disabled={this.props.loading}
+            >
+              {this.props.loading ? (
+                <Spinner />
+              ) : siteSetup ? (
+                capitalizeFirstLetter(I18NextService.i18n.t("save"))
+              ) : (
+                capitalizeFirstLetter(I18NextService.i18n.t("create"))
+              )}
+            </button>
+          </div>
+        </div>
+      </form>
+    );
+  }
+
+  componentDidUpdate(
+    prevProps: Readonly<{ children?: InfernoNode } & SiteFormProps>
+  ) {
+    if (
+      !(
+        deepEqual(prevProps.allowedInstances, this.props.allowedInstances) ||
+        deepEqual(prevProps.blockedInstances, this.props.blockedInstances)
+      )
+    ) {
+      this.setState({ siteForm: this.initSiteForm() });
+    }
+  }
+
+  federatedInstanceSelect(key: InstanceKey) {
+    const id = `create_site_${key}`;
+    const value = this.state.instance_select[key];
+    const selectedInstances = this.state.siteForm[key];
+    return (
+      <div className="col-12 col-md-6">
+        <label className="col-form-label" htmlFor={id}>
+          {I18NextService.i18n.t(key)}
+        </label>
+        <div className="d-flex justify-content-between align-items-center">
+          <input
+            type="text"
+            placeholder="instance.tld"
+            id={id}
+            className="form-control"
+            value={value}
+            onInput={linkEvent(key, this.handleInstanceTextChange)}
+            onKeyUp={linkEvent(key, this.handleInstanceEnterPress)}
+          />
+          <button
+            type="button"
+            className="btn btn-sm bg-success ms-2"
+            onClick={linkEvent(key, this.handleAddInstance)}
+            style={"width: 2rem; height: 2rem;"}
+            tabIndex={
+              -1 /* Making this untabble because handling enter key in text input makes keyboard support for this button redundant */
+            }
+          >
+            <Icon
+              icon="add"
+              classes="icon-inline text-light m-auto d-block position-static"
+            />
+          </button>
+        </div>
+        {selectedInstances && selectedInstances.length > 0 && (
+          <ul className="mt-3 list-unstyled w-100 d-flex flex-column justify-content-around align-items-center">
+            {selectedInstances.map(instance => (
+              <li
+                key={instance}
+                className="my-1 w-100 w-md-75 d-flex align-items-center justify-content-between"
+              >
+                <label className="d-block m-0 w-100 " htmlFor={instance}>
+                  <strong>{instance}</strong>
+                </label>
+                <button
+                  id={instance}
+                  type="button"
+                  style={"width: 2rem; height: 2rem;"}
+                  className="btn btn-sm bg-danger"
+                  onClick={linkEvent(
+                    { key, instance },
+                    this.handleRemoveInstance
+                  )}
+                >
+                  <Icon
+                    icon="x"
+                    classes="icon-inline text-light m-auto d-block position-static"
+                  />
+                </button>
+              </li>
+            ))}
+          </ul>
+        )}
+      </div>
     );
   }
 
-  handleCreateSiteSubmit(i: SiteForm, event: any) {
+  handleInstanceTextChange(type: InstanceKey, event: any) {
+    this.setState(s => ({
+      ...s,
+      instance_select: {
+        ...s.instance_select,
+        [type]: event.target.value,
+      },
+    }));
+  }
+
+  handleInstanceEnterPress(
+    key: InstanceKey,
+    event: InfernoKeyboardEvent<HTMLInputElement>
+  ) {
+    if (event.code.toLowerCase() === "enter") {
+      event.preventDefault();
+
+      this.handleAddInstance(key);
+    }
+  }
+
+  handleSaveSiteSubmit(i: SiteForm, event: any) {
     event.preventDefault();
-    i.setState({ loading: true });
-    i.setState(s => ((s.siteForm.auth = auth().unwrap()), s));
+    const auth = myAuthRequired();
+    i.setState(s => ((s.siteForm.auth = auth), s));
+    i.setState({ submitted: true });
+
+    const stateSiteForm = i.state.siteForm;
+
+    let form: EditSite | CreateSite;
+
     if (i.props.siteRes.site_view.local_site.site_setup) {
-      WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
+      form = stateSiteForm;
     } else {
-      let sForm = i.state.siteForm;
-      let form = new CreateSite({
-        name: sForm.name.unwrapOr("My site"),
-        sidebar: sForm.sidebar,
-        description: sForm.description,
-        icon: sForm.icon,
-        banner: sForm.banner,
-        community_creation_admin_only: sForm.community_creation_admin_only,
-        enable_nsfw: sForm.enable_nsfw,
-        enable_downvotes: sForm.enable_downvotes,
-        require_application: sForm.require_application,
-        application_question: sForm.application_question,
-        open_registration: sForm.open_registration,
-        require_email_verification: sForm.require_email_verification,
-        private_instance: sForm.private_instance,
-        default_theme: sForm.default_theme,
-        default_post_listing_type: sForm.default_post_listing_type,
-        application_email_admins: sForm.application_email_admins,
-        auth: auth().unwrap(),
-        hide_modlog_mod_names: sForm.hide_modlog_mod_names,
-        legal_information: sForm.legal_information,
-        slur_filter_regex: sForm.slur_filter_regex,
-        actor_name_max_length: sForm.actor_name_max_length,
-        rate_limit_message: sForm.rate_limit_message,
-        rate_limit_message_per_second: sForm.rate_limit_message_per_second,
-        rate_limit_comment: sForm.rate_limit_comment,
-        rate_limit_comment_per_second: sForm.rate_limit_comment_per_second,
-        rate_limit_image: sForm.rate_limit_image,
-        rate_limit_image_per_second: sForm.rate_limit_image_per_second,
-        rate_limit_post: sForm.rate_limit_post,
-        rate_limit_post_per_second: sForm.rate_limit_post_per_second,
-        rate_limit_register: sForm.rate_limit_register,
-        rate_limit_register_per_second: sForm.rate_limit_register_per_second,
-        rate_limit_search: sForm.rate_limit_search,
-        rate_limit_search_per_second: sForm.rate_limit_search_per_second,
-        federation_enabled: sForm.federation_enabled,
-        federation_debug: sForm.federation_debug,
-        federation_worker_count: sForm.federation_worker_count,
-        federation_strict_allowlist: sForm.federation_strict_allowlist,
-        federation_http_fetch_retry_limit:
-          sForm.federation_http_fetch_retry_limit,
-        captcha_enabled: sForm.captcha_enabled,
-        captcha_difficulty: sForm.captcha_difficulty,
-        allowed_instances: sForm.allowed_instances,
-        blocked_instances: sForm.blocked_instances,
-        discussion_languages: sForm.discussion_languages,
-      });
-      WebSocketService.Instance.send(wsClient.createSite(form));
+      form = {
+        name: stateSiteForm.name ?? "My site",
+        sidebar: stateSiteForm.sidebar,
+        description: stateSiteForm.description,
+        icon: stateSiteForm.icon,
+        banner: stateSiteForm.banner,
+        community_creation_admin_only:
+          stateSiteForm.community_creation_admin_only,
+        enable_nsfw: stateSiteForm.enable_nsfw,
+        enable_downvotes: stateSiteForm.enable_downvotes,
+        application_question: stateSiteForm.application_question,
+        registration_mode: stateSiteForm.registration_mode,
+        require_email_verification: stateSiteForm.require_email_verification,
+        private_instance: stateSiteForm.private_instance,
+        default_theme: stateSiteForm.default_theme,
+        default_post_listing_type: stateSiteForm.default_post_listing_type,
+        application_email_admins: stateSiteForm.application_email_admins,
+        hide_modlog_mod_names: stateSiteForm.hide_modlog_mod_names,
+        legal_information: stateSiteForm.legal_information,
+        slur_filter_regex: stateSiteForm.slur_filter_regex,
+        actor_name_max_length: stateSiteForm.actor_name_max_length,
+        rate_limit_message: stateSiteForm.rate_limit_message,
+        rate_limit_message_per_second:
+          stateSiteForm.rate_limit_message_per_second,
+        rate_limit_comment: stateSiteForm.rate_limit_comment,
+        rate_limit_comment_per_second:
+          stateSiteForm.rate_limit_comment_per_second,
+        rate_limit_image: stateSiteForm.rate_limit_image,
+        rate_limit_image_per_second: stateSiteForm.rate_limit_image_per_second,
+        rate_limit_post: stateSiteForm.rate_limit_post,
+        rate_limit_post_per_second: stateSiteForm.rate_limit_post_per_second,
+        rate_limit_register: stateSiteForm.rate_limit_register,
+        rate_limit_register_per_second:
+          stateSiteForm.rate_limit_register_per_second,
+        rate_limit_search: stateSiteForm.rate_limit_search,
+        rate_limit_search_per_second:
+          stateSiteForm.rate_limit_search_per_second,
+        federation_enabled: stateSiteForm.federation_enabled,
+        federation_debug: stateSiteForm.federation_debug,
+        captcha_enabled: stateSiteForm.captcha_enabled,
+        captcha_difficulty: stateSiteForm.captcha_difficulty,
+        allowed_instances: stateSiteForm.allowed_instances,
+        blocked_instances: stateSiteForm.blocked_instances,
+        discussion_languages: stateSiteForm.discussion_languages,
+        auth,
+      };
     }
-    i.setState(i.state);
-  }
 
-  instancesToString(opt: Option<string[]>): string {
-    return opt.map(list => list.join(",")).unwrapOr("");
+    i.props.onSaveSite(form);
   }
 
-  handleSiteAllowedInstances(i: SiteForm, event: any) {
-    let list = splitToList(event.target.value);
-    i.setState(s => ((s.siteForm.allowed_instances = list), s));
+  handleAddInstance(key: InstanceKey) {
+    const instance = this.state.instance_select[key].trim();
+
+    if (!validInstanceTLD(instance)) {
+      return;
+    }
+
+    if (!this.state.siteForm[key]?.includes(instance)) {
+      this.setState(s => ({
+        ...s,
+        siteForm: {
+          ...s.siteForm,
+          [key]: [...(s.siteForm[key] ?? []), instance],
+        },
+        instance_select: {
+          ...s.instance_select,
+          [key]: "",
+        },
+      }));
+
+      const oppositeKey: InstanceKey =
+        key === "allowed_instances" ? "blocked_instances" : "allowed_instances";
+      if (this.state.siteForm[oppositeKey]?.includes(instance)) {
+        this.handleRemoveInstance({ key: oppositeKey, instance });
+      }
+    }
   }
 
-  handleSiteBlockedInstances(i: SiteForm, event: any) {
-    let list = splitToList(event.target.value);
-    i.setState(s => ((s.siteForm.blocked_instances = list), s));
+  handleRemoveInstance({
+    key,
+    instance,
+  }: {
+    key: InstanceKey;
+    instance: string;
+  }) {
+    this.setState(s => ({
+      ...s,
+      siteForm: {
+        ...s.siteForm,
+        [key]: s.siteForm[key]?.filter(i => i !== instance),
+      },
+    }));
   }
 
   handleSiteNameChange(i: SiteForm, event: any) {
-    i.state.siteForm.name = Some(event.target.value);
+    i.state.siteForm.name = event.target.value;
     i.setState(i.state);
   }
 
   handleSiteSidebarChange(val: string) {
-    this.setState(s => ((s.siteForm.sidebar = Some(val)), s));
+    this.setState(s => ((s.siteForm.sidebar = val), s));
   }
 
   handleSiteLegalInfoChange(val: string) {
-    this.setState(s => ((s.siteForm.legal_information = Some(val)), s));
+    this.setState(s => ((s.siteForm.legal_information = val), s));
   }
 
   handleTaglineChange(i: SiteForm, index: number, val: string) {
-    i.state.siteForm.taglines.match({
-      some: tls => { tls[index] = val; },
-      none: void 0
-    });
-    i.setState(i.state);
+    const taglines = i.state.siteForm.taglines;
+    if (taglines) {
+      taglines[index] = val;
+      i.setState(i.state);
+    }
   }
 
   handleDeleteTaglineClick(
@@ -1207,12 +862,12 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
     event: InfernoMouseEvent<HTMLButtonElement>
   ) {
     event.preventDefault();
-    if (i.state.siteForm.taglines.isSome()){
-      let taglines = i.state.siteForm.taglines.unwrap();
+    const taglines = i.state.siteForm.taglines;
+    if (taglines) {
       taglines.splice(index, 1);
-      i.state.siteForm.taglines = None; // force rerender of table rows
+      i.state.siteForm.taglines = undefined;
       i.setState(i.state);
-      i.state.siteForm.taglines = Some(taglines);
+      i.state.siteForm.taglines = taglines;
       i.setState(i.state);
     }
   }
@@ -1222,279 +877,122 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
     event: InfernoMouseEvent<HTMLButtonElement>
   ) {
     event.preventDefault();
-    if (i.state.siteForm.taglines.isNone()){
-      i.state.siteForm.taglines = Some([]);
+    if (!i.state.siteForm.taglines) {
+      i.state.siteForm.taglines = [];
     }
-    i.state.siteForm.taglines.unwrap().push("");
+    i.state.siteForm.taglines.push("");
     i.setState(i.state);
   }
 
   handleSiteApplicationQuestionChange(val: string) {
-    this.setState(s => ((s.siteForm.application_question = Some(val)), s));
+    this.setState(s => ((s.siteForm.application_question = val), s));
   }
 
   handleSiteDescChange(i: SiteForm, event: any) {
-    i.state.siteForm.description = Some(event.target.value);
+    i.state.siteForm.description = event.target.value;
     i.setState(i.state);
   }
 
   handleSiteEnableNsfwChange(i: SiteForm, event: any) {
-    i.state.siteForm.enable_nsfw = Some(event.target.checked);
+    i.state.siteForm.enable_nsfw = event.target.checked;
     i.setState(i.state);
   }
 
-  handleSiteOpenRegistrationChange(i: SiteForm, event: any) {
-    i.state.siteForm.open_registration = Some(event.target.checked);
+  handleSiteRegistrationModeChange(i: SiteForm, event: any) {
+    i.state.siteForm.registration_mode = event.target.value;
     i.setState(i.state);
   }
 
   handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
-    i.state.siteForm.community_creation_admin_only = Some(event.target.checked);
+    i.state.siteForm.community_creation_admin_only = event.target.checked;
     i.setState(i.state);
   }
 
   handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
-    i.state.siteForm.enable_downvotes = Some(event.target.checked);
+    i.state.siteForm.enable_downvotes = event.target.checked;
     i.setState(i.state);
   }
 
-  handleSiteRequireApplication(i: SiteForm, event: any) {
-    i.state.siteForm.require_application = Some(event.target.checked);
+  handleSiteRequireEmailVerification(i: SiteForm, event: any) {
+    i.state.siteForm.require_email_verification = event.target.checked;
     i.setState(i.state);
   }
 
-  handleSiteRequireEmailVerification(i: SiteForm, event: any) {
-    i.state.siteForm.require_email_verification = Some(event.target.checked);
+  handleSiteApplicationEmailAdmins(i: SiteForm, event: any) {
+    i.state.siteForm.application_email_admins = event.target.checked;
     i.setState(i.state);
   }
 
-  handleSiteApplicationEmailAdmins(i: SiteForm, event: any) {
-    i.state.siteForm.application_email_admins = Some(event.target.checked);
+  handleSiteReportsEmailAdmins(i: SiteForm, event: any) {
+    i.state.siteForm.reports_email_admins = event.target.checked;
     i.setState(i.state);
   }
 
   handleSitePrivateInstance(i: SiteForm, event: any) {
-    i.state.siteForm.private_instance = Some(event.target.checked);
+    i.state.siteForm.private_instance = event.target.checked;
     i.setState(i.state);
   }
 
   handleSiteHideModlogModNames(i: SiteForm, event: any) {
-    i.state.siteForm.hide_modlog_mod_names = Some(event.target.checked);
+    i.state.siteForm.hide_modlog_mod_names = event.target.checked;
     i.setState(i.state);
   }
 
   handleSiteDefaultTheme(i: SiteForm, event: any) {
-    i.state.siteForm.default_theme = Some(event.target.value);
+    i.state.siteForm.default_theme = event.target.value;
     i.setState(i.state);
   }
 
   handleIconUpload(url: string) {
-    this.setState(s => ((s.siteForm.icon = Some(url)), s));
+    this.setState(s => ((s.siteForm.icon = url), s));
   }
 
   handleIconRemove() {
-    this.setState(s => ((s.siteForm.icon = Some("")), s));
+    this.setState(s => ((s.siteForm.icon = ""), s));
   }
 
   handleBannerUpload(url: string) {
-    this.setState(s => ((s.siteForm.banner = Some(url)), s));
+    this.setState(s => ((s.siteForm.banner = url), s));
   }
 
   handleBannerRemove() {
-    this.setState(s => ((s.siteForm.banner = Some("")), s));
+    this.setState(s => ((s.siteForm.banner = ""), s));
   }
 
   handleSiteSlurFilterRegex(i: SiteForm, event: any) {
-    i.setState(
-      s => ((s.siteForm.slur_filter_regex = Some(event.target.value)), s)
-    );
+    i.setState(s => ((s.siteForm.slur_filter_regex = event.target.value), s));
   }
 
   handleSiteActorNameMaxLength(i: SiteForm, event: any) {
     i.setState(
-      s => (
-        (s.siteForm.actor_name_max_length = Some(Number(event.target.value))), s
-      )
-    );
-  }
-
-  handleSiteRateLimitMessage(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_message = Some(Number(event.target.value))), s
-      )
-    );
-  }
-
-  handleSiteRateLimitMessagePerSecond(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_message_per_second = Some(
-          Number(event.target.value)
-        )),
-        s
-      )
-    );
-  }
-
-  handleSiteRateLimitPost(i: SiteForm, event: any) {
-    i.setState(
-      s => ((s.siteForm.rate_limit_post = Some(Number(event.target.value))), s)
-    );
-  }
-
-  handleSiteRateLimitPostPerSecond(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_post_per_second = Some(
-          Number(event.target.value)
-        )),
-        s
-      )
-    );
-  }
-
-  handleSiteRateLimitImage(i: SiteForm, event: any) {
-    i.setState(
-      s => ((s.siteForm.rate_limit_image = Some(Number(event.target.value))), s)
-    );
-  }
-
-  handleSiteRateLimitImagePerSecond(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_image_per_second = Some(
-          Number(event.target.value)
-        )),
-        s
-      )
-    );
-  }
-
-  handleSiteRateLimitComment(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_comment = Some(Number(event.target.value))), s
-      )
-    );
-  }
-
-  handleSiteRateLimitCommentPerSecond(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_comment_per_second = Some(
-          Number(event.target.value)
-        )),
-        s
-      )
-    );
-  }
-
-  handleSiteRateLimitSearch(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_search = Some(Number(event.target.value))), s
-      )
-    );
-  }
-
-  handleSiteRateLimitSearchPerSecond(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_search_per_second = Some(
-          Number(event.target.value)
-        )),
-        s
-      )
-    );
-  }
-
-  handleSiteRateLimitRegister(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_register = Some(Number(event.target.value))), s
-      )
-    );
-  }
-
-  handleSiteRateLimitRegisterPerSecond(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.rate_limit_register_per_second = Some(
-          Number(event.target.value)
-        )),
-        s
-      )
+      s => ((s.siteForm.actor_name_max_length = Number(event.target.value)), s)
     );
   }
 
   handleSiteFederationEnabled(i: SiteForm, event: any) {
-    i.state.siteForm.federation_enabled = Some(event.target.checked);
+    i.state.siteForm.federation_enabled = event.target.checked;
     i.setState(i.state);
   }
 
   handleSiteFederationDebug(i: SiteForm, event: any) {
-    i.state.siteForm.federation_debug = Some(event.target.checked);
+    i.state.siteForm.federation_debug = event.target.checked;
     i.setState(i.state);
   }
 
-  handleSiteFederationStrictAllowList(i: SiteForm, event: any) {
-    i.state.siteForm.federation_strict_allowlist = Some(event.target.checked);
-    i.setState(i.state);
-  }
-
-  handleSiteFederationWorkerCount(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.federation_worker_count = Some(Number(event.target.value))),
-        s
-      )
-    );
-  }
-
-  handleSiteFederationHttpFetchRetryLimit(i: SiteForm, event: any) {
-    i.setState(
-      s => (
-        (s.siteForm.federation_http_fetch_retry_limit = Some(
-          Number(event.target.value)
-        )),
-        s
-      )
-    );
-  }
-
   handleSiteCaptchaEnabled(i: SiteForm, event: any) {
-    i.state.siteForm.captcha_enabled = Some(event.target.checked);
+    i.state.siteForm.captcha_enabled = event.target.checked;
     i.setState(i.state);
   }
 
   handleSiteCaptchaDifficulty(i: SiteForm, event: any) {
-    i.setState(
-      s => ((s.siteForm.captcha_difficulty = Some(event.target.value)), s)
-    );
+    i.setState(s => ((s.siteForm.captcha_difficulty = event.target.value), s));
   }
 
   handleDiscussionLanguageChange(val: number[]) {
-    this.setState(s => ((s.siteForm.discussion_languages = Some(val)), s));
+    this.setState(s => ((s.siteForm.discussion_languages = val), s));
   }
 
   handleDefaultPostListingTypeChange(val: ListingType) {
-    this.setState(
-      s => (
-        (s.siteForm.default_post_listing_type = Some(
-          ListingType[ListingType[val]]
-        )),
-        s
-      )
-    );
-  }
-}
-
-function splitToList(commaList: string): Option<string[]> {
-  if (commaList !== "") {
-    let list = commaList.trim().split(",");
-    return Some(list);
-  } else {
-    return Some([]);
+    this.setState(s => ((s.siteForm.default_post_listing_type = val), s));
   }
 }