Adding change password and email address from user settings.
- Fixes #384 - Fixes #385
This commit is contained in:
parent
4faa46ac29
commit
f5a13717ea
9 changed files with 339 additions and 210 deletions
19
README.md
vendored
19
README.md
vendored
|
@ -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:
|
||||
|
||||
|
|
15
server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql
vendored
Normal file
15
server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql
vendored
Normal file
|
@ -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;
|
16
server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql
vendored
Normal file
16
server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql
vendored
Normal file
|
@ -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;
|
|
@ -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"))?,
|
||||
};
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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,
|
||||
|
|
417
ui/src/components/user.tsx
vendored
417
ui/src/components/user.tsx
vendored
|
@ -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(
|
||||
this,
|
||||
this.handleUserSettingsLangChange
|
||||
)}
|
||||
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>
|
||||
</div>
|
||||
</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">
|
||||
<div class="col-12">
|
||||
<label>
|
||||
<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>
|
||||
</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>
|
||||
</option>
|
||||
{themes.map(theme => (
|
||||
<option value={theme}>{theme}</option>
|
||||
))}
|
||||
</select>
|
||||
</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>
|
||||
<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">
|
||||
<div class="col-12">
|
||||
<label>
|
||||
<T i18nKey="type" class="mr-2">
|
||||
#
|
||||
</T>
|
||||
</label>
|
||||
<SortSelect
|
||||
sort={this.state.userSettingsForm.default_sort_type}
|
||||
onChange={this.handleUserSettingsSortTypeChange}
|
||||
<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.handleUserSettingsEmailChange
|
||||
)}
|
||||
minLength={3}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<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.handleUserSettingsNewPasswordChange
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</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>
|
||||
</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);
|
||||
|
|
5
ui/src/interfaces.ts
vendored
5
ui/src/interfaces.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
1
ui/src/translations/en.ts
vendored
1
ui/src/translations/en.ts
vendored
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue