]> Untitled Git - lemmy.git/commitdiff
Adding change password and email address from user settings.
authorDessalines <tyhou13@gmx.com>
Wed, 1 Jan 2020 20:46:14 +0000 (15:46 -0500)
committerDessalines <tyhou13@gmx.com>
Wed, 1 Jan 2020 20:46:14 +0000 (15:46 -0500)
- Fixes #384
- Fixes #385

README.md
server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql [new file with mode: 0644]
server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql [new file with mode: 0644]
server/src/api/user.rs
server/src/db/user.rs
server/src/db/user_view.rs
ui/src/components/user.tsx
ui/src/interfaces.ts
ui/src/translations/en.ts

index 9812dbf2ff71ab022913b4430bd2fbab6b0301f9..c5a30598d03dec9a0cfefddd196b5d5891eccd99 100644 (file)
--- a/README.md
+++ b/README.md
@@ -257,16 +257,15 @@ If you'd like to add translations, take a look a look at the [English translatio
 
 lang | done | missing
 --- | --- | ---
-de | 97% | avatar,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
-eo | 84% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no 
-es | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
-fr | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
-it | 93% | avatar,archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
-nl | 86% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme 
-ru | 80% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no 
-sv | 92% | avatar,archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
-zh | 78% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no 
-
+de | 97% | avatar,old_password,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+eo | 83% | number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme,are_you_sure,yes,no 
+es | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+fr | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+it | 93% | avatar,archive_link,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+nl | 85% | preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,theme 
+ru | 79% | cross_posts,cross_post,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no 
+sv | 92% | avatar,archive_link,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw 
+zh | 77% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,avatar,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,old_password,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no 
 
 If you'd like to update this report, run:
 
diff --git a/server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql b/server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql
new file mode 100644 (file)
index 0000000..92f771f
--- /dev/null
@@ -0,0 +1,15 @@
+-- user
+drop view user_view;
+create view user_view as 
+select id,
+name,
+avatar,
+fedi_name,
+admin,
+banned,
+published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
diff --git a/server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql b/server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql
new file mode 100644 (file)
index 0000000..59972df
--- /dev/null
@@ -0,0 +1,16 @@
+-- user
+drop view user_view;
+create view user_view as 
+select id,
+name,
+avatar,
+email,
+fedi_name,
+admin,
+banned,
+published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
index e8ad20aa41dafc63f7ef029f6524484003fa57a0..c074228f7c7cd4e5821fef17a2c3eaaec60634ae 100644 (file)
@@ -28,6 +28,10 @@ pub struct SaveUserSettings {
   default_listing_type: i16,
   lang: String,
   avatar: Option<String>,
+  email: Option<String>,
+  new_password: Option<String>,
+  new_password_verify: Option<String>,
+  old_password: Option<String>,
   auth: String,
 }
 
@@ -312,12 +316,45 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
 
     let read_user = User_::read(&conn, user_id)?;
 
+    let email = match &data.email {
+      Some(email) => Some(email.to_owned()),
+      None => read_user.email,
+    };
+
+    let password_encrypted = match &data.new_password {
+      Some(new_password) => {
+        match &data.new_password_verify {
+          Some(new_password_verify) => {
+            // Make sure passwords match
+            if new_password != new_password_verify {
+              return Err(APIError::err(&self.op, "passwords_dont_match"))?;
+            }
+
+            // Check the old password
+            match &data.old_password {
+              Some(old_password) => {
+                let valid: bool =
+                  verify(old_password, &read_user.password_encrypted).unwrap_or(false);
+                if !valid {
+                  return Err(APIError::err(&self.op, "password_incorrect"))?;
+                }
+                User_::update_password(&conn, user_id, &new_password)?.password_encrypted
+              }
+              None => return Err(APIError::err(&self.op, "password_incorrect"))?,
+            }
+          }
+          None => return Err(APIError::err(&self.op, "passwords_dont_match"))?,
+        }
+      }
+      None => read_user.password_encrypted,
+    };
+
     let user_form = UserForm {
       name: read_user.name,
       fedi_name: read_user.fedi_name,
-      email: read_user.email,
+      email,
       avatar: data.avatar.to_owned(),
-      password_encrypted: read_user.password_encrypted,
+      password_encrypted,
       preferred_username: read_user.preferred_username,
       updated: Some(naive_now()),
       admin: read_user.admin,
@@ -850,28 +887,8 @@ impl Perform<LoginResponse> for Oper<PasswordChange> {
       return Err(APIError::err(&self.op, "passwords_dont_match"))?;
     }
 
-    // Fetch the user
-    let read_user = User_::read(&conn, user_id)?;
-
     // Update the user with the new password
-    let user_form = UserForm {
-      name: read_user.name,
-      fedi_name: read_user.fedi_name,
-      email: read_user.email,
-      avatar: read_user.avatar,
-      password_encrypted: data.password.to_owned(),
-      preferred_username: read_user.preferred_username,
-      updated: Some(naive_now()),
-      admin: read_user.admin,
-      banned: read_user.banned,
-      show_nsfw: read_user.show_nsfw,
-      theme: read_user.theme,
-      default_sort_type: read_user.default_sort_type,
-      default_listing_type: read_user.default_listing_type,
-      lang: read_user.lang,
-    };
-
-    let updated_user = match User_::update_password(&conn, user_id, &user_form) {
+    let updated_user = match User_::update_password(&conn, user_id, &data.password) {
       Ok(user) => user,
       Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?,
     };
index db4aa453c778cebe331a0305ba6534e3a5ca0cd1..82736f8e2457869464e6ddcfc4e0eb856fc4d599 100644 (file)
@@ -75,14 +75,13 @@ impl User_ {
   pub fn update_password(
     conn: &PgConnection,
     user_id: i32,
-    form: &UserForm,
+    new_password: &str,
   ) -> Result<Self, Error> {
-    let mut edited_user = form.clone();
-    let password_hash =
-      hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
-    edited_user.password_encrypted = password_hash;
+    let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
 
-    Self::update(&conn, user_id, &edited_user)
+    diesel::update(user_.find(user_id))
+      .set(password_encrypted.eq(password_hash))
+      .get_result::<Self>(conn)
   }
 
   pub fn read_from_name(conn: &PgConnection, from_user_name: String) -> Result<Self, Error> {
index 616159de5e1e3b85a22bfcc0de3348a3f0b90202..2d50b40c44f4ef1740531a463514ec4ef26da63e 100644 (file)
@@ -7,6 +7,7 @@ table! {
     id -> Int4,
     name -> Varchar,
     avatar -> Nullable<Text>,
+    email -> Nullable<Text>,
     fedi_name -> Varchar,
     admin -> Bool,
     banned -> Bool,
@@ -26,6 +27,7 @@ pub struct UserView {
   pub id: i32,
   pub name: String,
   pub avatar: Option<String>,
+  pub email: Option<String>,
   pub fedi_name: String,
   pub admin: bool,
   pub banned: bool,
index e97b26f90d3fd2f5bef59d9f2e7f728efc04dddf..99c340c5e0a1cae406924efc1cf362b0f0052d2e 100644 (file)
@@ -99,7 +99,6 @@ export class User extends Component<any, UserState> {
       default_sort_type: null,
       default_listing_type: null,
       lang: null,
-      avatar: null,
       auth: null,
     },
     userSettingsLoading: null,
@@ -437,199 +436,240 @@ export class User extends Component<any, UserState> {
             </h5>
             <form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
               <div class="form-group">
-                <div class="col-12">
-                  <label>
-                    <T i18nKey="avatar">#</T>
-                  </label>
-                  <form class="d-inline">
-                    <label
-                      htmlFor="file-upload"
-                      class="pointer ml-4 text-muted small font-weight-bold"
-                    >
-                      <img
-                        height="80"
-                        width="80"
-                        src={
-                          this.state.userSettingsForm.avatar
-                            ? this.state.userSettingsForm.avatar
-                            : 'https://via.placeholder.com/300/000?text=Avatar'
-                        }
-                        class="rounded-circle"
-                      />
-                    </label>
-                    <input
-                      id="file-upload"
-                      type="file"
-                      accept="image/*,video/*"
-                      name="file"
-                      class="d-none"
-                      disabled={!UserService.Instance.user}
-                      onChange={linkEvent(this, this.handleImageUpload)}
+                <label>
+                  <T i18nKey="avatar">#</T>
+                </label>
+                <form class="d-inline">
+                  <label
+                    htmlFor="file-upload"
+                    class="pointer ml-4 text-muted small font-weight-bold"
+                  >
+                    <img
+                      height="80"
+                      width="80"
+                      src={
+                        this.state.userSettingsForm.avatar
+                          ? this.state.userSettingsForm.avatar
+                          : 'https://via.placeholder.com/300/000?text=Avatar'
+                      }
+                      class="rounded-circle"
                     />
-                  </form>
-                </div>
+                  </label>
+                  <input
+                    id="file-upload"
+                    type="file"
+                    accept="image/*,video/*"
+                    name="file"
+                    class="d-none"
+                    disabled={!UserService.Instance.user}
+                    onChange={linkEvent(this, this.handleImageUpload)}
+                  />
+                </form>
               </div>
               <div class="form-group">
-                <div class="col-12">
-                  <label>
+                <label>
+                  <T i18nKey="language">#</T>
+                </label>
+                <select
+                  value={this.state.userSettingsForm.lang}
+                  onChange={linkEvent(this, this.handleUserSettingsLangChange)}
+                  class="ml-2 custom-select custom-select-sm w-auto"
+                >
+                  <option disabled>
                     <T i18nKey="language">#</T>
-                  </label>
-                  <select
-                    value={this.state.userSettingsForm.lang}
-                    onChange={linkEvent(
+                  </option>
+                  <option value="browser">
+                    <T i18nKey="browser_default">#</T>
+                  </option>
+                  <option disabled>──</option>
+                  {languages.map(lang => (
+                    <option value={lang.code}>{lang.name}</option>
+                  ))}
+                </select>
+              </div>
+              <div class="form-group">
+                <label>
+                  <T i18nKey="theme">#</T>
+                </label>
+                <select
+                  value={this.state.userSettingsForm.theme}
+                  onChange={linkEvent(this, this.handleUserSettingsThemeChange)}
+                  class="ml-2 custom-select custom-select-sm w-auto"
+                >
+                  <option disabled>
+                    <T i18nKey="theme">#</T>
+                  </option>
+                  {themes.map(theme => (
+                    <option value={theme}>{theme}</option>
+                  ))}
+                </select>
+              </div>
+              <form className="form-group">
+                <label>
+                  <T i18nKey="sort_type" class="mr-2">
+                    #
+                  </T>
+                </label>
+                <ListingTypeSelect
+                  type_={this.state.userSettingsForm.default_listing_type}
+                  onChange={this.handleUserSettingsListingTypeChange}
+                />
+              </form>
+              <form className="form-group">
+                <label>
+                  <T i18nKey="type" class="mr-2">
+                    #
+                  </T>
+                </label>
+                <SortSelect
+                  sort={this.state.userSettingsForm.default_sort_type}
+                  onChange={this.handleUserSettingsSortTypeChange}
+                />
+              </form>
+              <div class="form-group row">
+                <label class="col-lg-3 col-form-label">
+                  <T i18nKey="email">#</T>
+                </label>
+                <div class="col-lg-9">
+                  <input
+                    type="email"
+                    class="form-control"
+                    placeholder={i18n.t('optional')}
+                    value={this.state.userSettingsForm.email}
+                    onInput={linkEvent(
                       this,
-                      this.handleUserSettingsLangChange
+                      this.handleUserSettingsEmailChange
                     )}
-                    class="ml-2 custom-select custom-select-sm w-auto"
-                  >
-                    <option disabled>
-                      <T i18nKey="language">#</T>
-                    </option>
-                    <option value="browser">
-                      <T i18nKey="browser_default">#</T>
-                    </option>
-                    <option disabled>──</option>
-                    {languages.map(lang => (
-                      <option value={lang.code}>{lang.name}</option>
-                    ))}
-                  </select>
+                    minLength={3}
+                  />
                 </div>
               </div>
-              <div class="form-group">
-                <div class="col-12">
-                  <label>
-                    <T i18nKey="theme">#</T>
-                  </label>
-                  <select
-                    value={this.state.userSettingsForm.theme}
-                    onChange={linkEvent(
+              <div class="form-group row">
+                <label class="col-lg-5 col-form-label">
+                  <T i18nKey="new_password">#</T>
+                </label>
+                <div class="col-lg-7">
+                  <input
+                    type="password"
+                    class="form-control"
+                    value={this.state.userSettingsForm.new_password}
+                    onInput={linkEvent(
                       this,
-                      this.handleUserSettingsThemeChange
+                      this.handleUserSettingsNewPasswordChange
                     )}
-                    class="ml-2 custom-select custom-select-sm w-auto"
-                  >
-                    <option disabled>
-                      <T i18nKey="theme">#</T>
-                    </option>
-                    {themes.map(theme => (
-                      <option value={theme}>{theme}</option>
-                    ))}
-                  </select>
+                  />
                 </div>
               </div>
-              <form className="form-group">
-                <div class="col-12">
-                  <label>
-                    <T i18nKey="sort_type" class="mr-2">
-                      #
-                    </T>
-                  </label>
-                  <ListingTypeSelect
-                    type_={this.state.userSettingsForm.default_listing_type}
-                    onChange={this.handleUserSettingsListingTypeChange}
+              <div class="form-group row">
+                <label class="col-lg-5 col-form-label">
+                  <T i18nKey="verify_password">#</T>
+                </label>
+                <div class="col-lg-7">
+                  <input
+                    type="password"
+                    class="form-control"
+                    value={this.state.userSettingsForm.new_password_verify}
+                    onInput={linkEvent(
+                      this,
+                      this.handleUserSettingsNewPasswordVerifyChange
+                    )}
                   />
                 </div>
-              </form>
-              <form className="form-group">
-                <div class="col-12">
-                  <label>
-                    <T i18nKey="type" class="mr-2">
-                      #
-                    </T>
-                  </label>
-                  <SortSelect
-                    sort={this.state.userSettingsForm.default_sort_type}
-                    onChange={this.handleUserSettingsSortTypeChange}
+              </div>
+              <div class="form-group row">
+                <label class="col-lg-5 col-form-label">
+                  <T i18nKey="old_password">#</T>
+                </label>
+                <div class="col-lg-7">
+                  <input
+                    type="password"
+                    class="form-control"
+                    value={this.state.userSettingsForm.old_password}
+                    onInput={linkEvent(
+                      this,
+                      this.handleUserSettingsOldPasswordChange
+                    )}
                   />
                 </div>
-              </form>
+              </div>
               {WebSocketService.Instance.site.enable_nsfw && (
                 <div class="form-group">
-                  <div class="col-12">
-                    <div class="form-check">
-                      <input
-                        class="form-check-input"
-                        type="checkbox"
-                        checked={this.state.userSettingsForm.show_nsfw}
-                        onChange={linkEvent(
-                          this,
-                          this.handleUserSettingsShowNsfwChange
-                        )}
-                      />
-                      <label class="form-check-label">
-                        <T i18nKey="show_nsfw">#</T>
-                      </label>
-                    </div>
+                  <div class="form-check">
+                    <input
+                      class="form-check-input"
+                      type="checkbox"
+                      checked={this.state.userSettingsForm.show_nsfw}
+                      onChange={linkEvent(
+                        this,
+                        this.handleUserSettingsShowNsfwChange
+                      )}
+                    />
+                    <label class="form-check-label">
+                      <T i18nKey="show_nsfw">#</T>
+                    </label>
                   </div>
                 </div>
               )}
               <div class="form-group">
-                <div class="col-12">
-                  <button
-                    type="submit"
-                    class="btn btn-block btn-secondary mr-4"
-                  >
-                    {this.state.userSettingsLoading ? (
-                      <svg class="icon icon-spinner spin">
-                        <use xlinkHref="#icon-spinner"></use>
-                      </svg>
-                    ) : (
-                      capitalizeFirstLetter(i18n.t('save'))
-                    )}
-                  </button>
-                </div>
+                <button type="submit" class="btn btn-block btn-secondary mr-4">
+                  {this.state.userSettingsLoading ? (
+                    <svg class="icon icon-spinner spin">
+                      <use xlinkHref="#icon-spinner"></use>
+                    </svg>
+                  ) : (
+                    capitalizeFirstLetter(i18n.t('save'))
+                  )}
+                </button>
               </div>
               <hr />
               <div class="form-group mb-0">
-                <div class="col-12">
-                  <button
-                    class="btn btn-block btn-danger"
-                    onClick={linkEvent(
-                      this,
-                      this.handleDeleteAccountShowConfirmToggle
-                    )}
-                  >
-                    <T i18nKey="delete_account">#</T>
-                  </button>
-                  {this.state.deleteAccountShowConfirm && (
-                    <>
-                      <div class="my-2 alert alert-danger" role="alert">
-                        <T i18nKey="delete_account_confirm">#</T>
-                      </div>
-                      <input
-                        type="password"
-                        value={this.state.deleteAccountForm.password}
-                        onInput={linkEvent(
-                          this,
-                          this.handleDeleteAccountPasswordChange
-                        )}
-                        class="form-control my-2"
-                      />
-                      <button
-                        class="btn btn-danger mr-4"
-                        disabled={!this.state.deleteAccountForm.password}
-                        onClick={linkEvent(this, this.handleDeleteAccount)}
-                      >
-                        {this.state.deleteAccountLoading ? (
-                          <svg class="icon icon-spinner spin">
-                            <use xlinkHref="#icon-spinner"></use>
-                          </svg>
-                        ) : (
-                          capitalizeFirstLetter(i18n.t('delete'))
-                        )}
-                      </button>
-                      <button
-                        class="btn btn-secondary"
-                        onClick={linkEvent(
-                          this,
-                          this.handleDeleteAccountShowConfirmToggle
-                        )}
-                      >
-                        <T i18nKey="cancel">#</T>
-                      </button>
-                    </>
+                <button
+                  class="btn btn-block btn-danger"
+                  onClick={linkEvent(
+                    this,
+                    this.handleDeleteAccountShowConfirmToggle
                   )}
-                </div>
+                >
+                  <T i18nKey="delete_account">#</T>
+                </button>
+                {this.state.deleteAccountShowConfirm && (
+                  <>
+                    <div class="my-2 alert alert-danger" role="alert">
+                      <T i18nKey="delete_account_confirm">#</T>
+                    </div>
+                    <input
+                      type="password"
+                      value={this.state.deleteAccountForm.password}
+                      onInput={linkEvent(
+                        this,
+                        this.handleDeleteAccountPasswordChange
+                      )}
+                      class="form-control my-2"
+                    />
+                    <button
+                      class="btn btn-danger mr-4"
+                      disabled={!this.state.deleteAccountForm.password}
+                      onClick={linkEvent(this, this.handleDeleteAccount)}
+                    >
+                      {this.state.deleteAccountLoading ? (
+                        <svg class="icon icon-spinner spin">
+                          <use xlinkHref="#icon-spinner"></use>
+                        </svg>
+                      ) : (
+                        capitalizeFirstLetter(i18n.t('delete'))
+                      )}
+                    </button>
+                    <button
+                      class="btn btn-secondary"
+                      onClick={linkEvent(
+                        this,
+                        this.handleDeleteAccountShowConfirmToggle
+                      )}
+                    >
+                      <T i18nKey="cancel">#</T>
+                    </button>
+                  </>
+                )}
               </div>
             </form>
           </div>
@@ -786,6 +826,38 @@ export class User extends Component<any, UserState> {
     this.setState(this.state);
   }
 
+  handleUserSettingsEmailChange(i: User, event: any) {
+    i.state.userSettingsForm.email = event.target.value;
+    if (i.state.userSettingsForm.email == '' && !i.state.user.email) {
+      i.state.userSettingsForm.email = undefined;
+    }
+    i.setState(i.state);
+  }
+
+  handleUserSettingsNewPasswordChange(i: User, event: any) {
+    i.state.userSettingsForm.new_password = event.target.value;
+    if (i.state.userSettingsForm.new_password == '') {
+      i.state.userSettingsForm.new_password = undefined;
+    }
+    i.setState(i.state);
+  }
+
+  handleUserSettingsNewPasswordVerifyChange(i: User, event: any) {
+    i.state.userSettingsForm.new_password_verify = event.target.value;
+    if (i.state.userSettingsForm.new_password_verify == '') {
+      i.state.userSettingsForm.new_password_verify = undefined;
+    }
+    i.setState(i.state);
+  }
+
+  handleUserSettingsOldPasswordChange(i: User, event: any) {
+    i.state.userSettingsForm.old_password = event.target.value;
+    if (i.state.userSettingsForm.old_password == '') {
+      i.state.userSettingsForm.old_password = undefined;
+    }
+    i.setState(i.state);
+  }
+
   handleImageUpload(i: User, event: any) {
     event.preventDefault();
     let file = event.target.files[0];
@@ -856,6 +928,8 @@ export class User extends Component<any, UserState> {
     if (msg.error) {
       alert(i18n.t(msg.error));
       this.state.deleteAccountLoading = false;
+      this.state.avatarLoading = false;
+      this.state.userSettingsLoading = false;
       if (msg.error == 'couldnt_find_that_username_or_email') {
         this.context.router.history.push('/');
       }
@@ -882,6 +956,7 @@ export class User extends Component<any, UserState> {
           UserService.Instance.user.default_listing_type;
         this.state.userSettingsForm.lang = UserService.Instance.user.lang;
         this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
+        this.state.userSettingsForm.email = this.state.user.email;
       }
       document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
       window.scrollTo(0, 0);
index 7020ea492f1c079cdf43689212c6d8e404a645b8..1762bd60891cd0b140ef095649653b264e618307 100644 (file)
@@ -87,6 +87,7 @@ export interface UserView {
   id: number;
   name: string;
   avatar?: string;
+  email?: string;
   fedi_name: string;
   published: string;
   number_of_posts: number;
@@ -481,6 +482,10 @@ export interface UserSettingsForm {
   default_listing_type: ListingType;
   lang: string;
   avatar?: string;
+  email?: string;
+  new_password?: string;
+  new_password_verify?: string;
+  old_password?: string;
   auth: string;
 }
 
index 108620813bd1141994df9fe68328230d799d446c..73726808281297b6f8ce5937f595229de01c70ea 100644 (file)
@@ -118,6 +118,7 @@ export const en = {
     unread_messages: 'Unread Messages',
     password: 'Password',
     verify_password: 'Verify Password',
+    old_password: 'Old Password',
     forgot_password: 'forgot password',
     reset_password_mail_sent: 'Sent an Email to reset your password.',
     password_change: 'Password Change',