forked from nutomic/lemmy
Adding change password and email address from user settings.
- Fixes #384 - Fixes #385
This commit is contained in:
parent
a95704d5fc
commit
b63aabfdc2
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
|
lang | done | missing
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
de | 97% | avatar,downvotes_disabled,enable_downvotes,open_registration,registration_closed,enable_nsfw
|
de | 97% | avatar,old_password,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
|
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,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
|
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,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,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 | 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
|
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 | 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
|
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,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
|
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 | 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
|
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:
|
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,
|
default_listing_type: i16,
|
||||||
lang: String,
|
lang: String,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
|
email: Option<String>,
|
||||||
|
new_password: Option<String>,
|
||||||
|
new_password_verify: Option<String>,
|
||||||
|
old_password: Option<String>,
|
||||||
auth: String,
|
auth: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,12 +316,45 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
|
||||||
|
|
||||||
let read_user = User_::read(&conn, user_id)?;
|
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 {
|
let user_form = UserForm {
|
||||||
name: read_user.name,
|
name: read_user.name,
|
||||||
fedi_name: read_user.fedi_name,
|
fedi_name: read_user.fedi_name,
|
||||||
email: read_user.email,
|
email,
|
||||||
avatar: data.avatar.to_owned(),
|
avatar: data.avatar.to_owned(),
|
||||||
password_encrypted: read_user.password_encrypted,
|
password_encrypted,
|
||||||
preferred_username: read_user.preferred_username,
|
preferred_username: read_user.preferred_username,
|
||||||
updated: Some(naive_now()),
|
updated: Some(naive_now()),
|
||||||
admin: read_user.admin,
|
admin: read_user.admin,
|
||||||
|
@ -850,28 +887,8 @@ impl Perform<LoginResponse> for Oper<PasswordChange> {
|
||||||
return Err(APIError::err(&self.op, "passwords_dont_match"))?;
|
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
|
// Update the user with the new password
|
||||||
let user_form = UserForm {
|
let updated_user = match User_::update_password(&conn, user_id, &data.password) {
|
||||||
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) {
|
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?,
|
Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?,
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,14 +75,13 @@ impl User_ {
|
||||||
pub fn update_password(
|
pub fn update_password(
|
||||||
conn: &PgConnection,
|
conn: &PgConnection,
|
||||||
user_id: i32,
|
user_id: i32,
|
||||||
form: &UserForm,
|
new_password: &str,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let mut edited_user = form.clone();
|
let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
|
||||||
let password_hash =
|
|
||||||
hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
|
|
||||||
edited_user.password_encrypted = password_hash;
|
|
||||||
|
|
||||||
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> {
|
pub fn read_from_name(conn: &PgConnection, from_user_name: String) -> Result<Self, Error> {
|
||||||
|
|
|
@ -7,6 +7,7 @@ table! {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
avatar -> Nullable<Text>,
|
avatar -> Nullable<Text>,
|
||||||
|
email -> Nullable<Text>,
|
||||||
fedi_name -> Varchar,
|
fedi_name -> Varchar,
|
||||||
admin -> Bool,
|
admin -> Bool,
|
||||||
banned -> Bool,
|
banned -> Bool,
|
||||||
|
@ -26,6 +27,7 @@ pub struct UserView {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
|
pub email: Option<String>,
|
||||||
pub fedi_name: String,
|
pub fedi_name: String,
|
||||||
pub admin: bool,
|
pub admin: bool,
|
||||||
pub banned: 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_sort_type: null,
|
||||||
default_listing_type: null,
|
default_listing_type: null,
|
||||||
lang: null,
|
lang: null,
|
||||||
avatar: null,
|
|
||||||
auth: null,
|
auth: null,
|
||||||
},
|
},
|
||||||
userSettingsLoading: null,
|
userSettingsLoading: null,
|
||||||
|
@ -437,199 +436,240 @@ export class User extends Component<any, UserState> {
|
||||||
</h5>
|
</h5>
|
||||||
<form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
|
<form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-12">
|
<label>
|
||||||
<label>
|
<T i18nKey="avatar">#</T>
|
||||||
<T i18nKey="avatar">#</T>
|
</label>
|
||||||
</label>
|
<form class="d-inline">
|
||||||
<form class="d-inline">
|
<label
|
||||||
<label
|
htmlFor="file-upload"
|
||||||
htmlFor="file-upload"
|
class="pointer ml-4 text-muted small font-weight-bold"
|
||||||
class="pointer ml-4 text-muted small font-weight-bold"
|
>
|
||||||
>
|
<img
|
||||||
<img
|
height="80"
|
||||||
height="80"
|
width="80"
|
||||||
width="80"
|
src={
|
||||||
src={
|
this.state.userSettingsForm.avatar
|
||||||
this.state.userSettingsForm.avatar
|
? this.state.userSettingsForm.avatar
|
||||||
? this.state.userSettingsForm.avatar
|
: 'https://via.placeholder.com/300/000?text=Avatar'
|
||||||
: 'https://via.placeholder.com/300/000?text=Avatar'
|
}
|
||||||
}
|
class="rounded-circle"
|
||||||
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)}
|
|
||||||
/>
|
/>
|
||||||
</form>
|
</label>
|
||||||
</div>
|
<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>
|
||||||
<div class="form-group">
|
<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>
|
<T i18nKey="language">#</T>
|
||||||
</label>
|
</option>
|
||||||
<select
|
<option value="browser">
|
||||||
value={this.state.userSettingsForm.lang}
|
<T i18nKey="browser_default">#</T>
|
||||||
onChange={linkEvent(
|
</option>
|
||||||
this,
|
<option disabled>──</option>
|
||||||
this.handleUserSettingsLangChange
|
{languages.map(lang => (
|
||||||
)}
|
<option value={lang.code}>{lang.name}</option>
|
||||||
class="ml-2 custom-select custom-select-sm w-auto"
|
))}
|
||||||
>
|
</select>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<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>
|
<T i18nKey="theme">#</T>
|
||||||
</label>
|
</option>
|
||||||
<select
|
{themes.map(theme => (
|
||||||
value={this.state.userSettingsForm.theme}
|
<option value={theme}>{theme}</option>
|
||||||
onChange={linkEvent(
|
))}
|
||||||
this,
|
</select>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
<form className="form-group">
|
<form className="form-group">
|
||||||
<div class="col-12">
|
<label>
|
||||||
<label>
|
<T i18nKey="sort_type" class="mr-2">
|
||||||
<T i18nKey="sort_type" class="mr-2">
|
#
|
||||||
#
|
</T>
|
||||||
</T>
|
</label>
|
||||||
</label>
|
<ListingTypeSelect
|
||||||
<ListingTypeSelect
|
type_={this.state.userSettingsForm.default_listing_type}
|
||||||
type_={this.state.userSettingsForm.default_listing_type}
|
onChange={this.handleUserSettingsListingTypeChange}
|
||||||
onChange={this.handleUserSettingsListingTypeChange}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
<form className="form-group">
|
<form className="form-group">
|
||||||
<div class="col-12">
|
<label>
|
||||||
<label>
|
<T i18nKey="type" class="mr-2">
|
||||||
<T i18nKey="type" class="mr-2">
|
#
|
||||||
#
|
</T>
|
||||||
</T>
|
</label>
|
||||||
</label>
|
<SortSelect
|
||||||
<SortSelect
|
sort={this.state.userSettingsForm.default_sort_type}
|
||||||
sort={this.state.userSettingsForm.default_sort_type}
|
onChange={this.handleUserSettingsSortTypeChange}
|
||||||
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>
|
</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 && (
|
{WebSocketService.Instance.site.enable_nsfw && (
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-12">
|
<div class="form-check">
|
||||||
<div class="form-check">
|
<input
|
||||||
<input
|
class="form-check-input"
|
||||||
class="form-check-input"
|
type="checkbox"
|
||||||
type="checkbox"
|
checked={this.state.userSettingsForm.show_nsfw}
|
||||||
checked={this.state.userSettingsForm.show_nsfw}
|
onChange={linkEvent(
|
||||||
onChange={linkEvent(
|
this,
|
||||||
this,
|
this.handleUserSettingsShowNsfwChange
|
||||||
this.handleUserSettingsShowNsfwChange
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
<label class="form-check-label">
|
||||||
<label class="form-check-label">
|
<T i18nKey="show_nsfw">#</T>
|
||||||
<T i18nKey="show_nsfw">#</T>
|
</label>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-12">
|
<button type="submit" class="btn btn-block btn-secondary mr-4">
|
||||||
<button
|
{this.state.userSettingsLoading ? (
|
||||||
type="submit"
|
<svg class="icon icon-spinner spin">
|
||||||
class="btn btn-block btn-secondary mr-4"
|
<use xlinkHref="#icon-spinner"></use>
|
||||||
>
|
</svg>
|
||||||
{this.state.userSettingsLoading ? (
|
) : (
|
||||||
<svg class="icon icon-spinner spin">
|
capitalizeFirstLetter(i18n.t('save'))
|
||||||
<use xlinkHref="#icon-spinner"></use>
|
)}
|
||||||
</svg>
|
</button>
|
||||||
) : (
|
|
||||||
capitalizeFirstLetter(i18n.t('save'))
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="form-group mb-0">
|
<div class="form-group mb-0">
|
||||||
<div class="col-12">
|
<button
|
||||||
<button
|
class="btn btn-block btn-danger"
|
||||||
class="btn btn-block btn-danger"
|
onClick={linkEvent(
|
||||||
onClick={linkEvent(
|
this,
|
||||||
this,
|
this.handleDeleteAccountShowConfirmToggle
|
||||||
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>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -786,6 +826,38 @@ export class User extends Component<any, UserState> {
|
||||||
this.setState(this.state);
|
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) {
|
handleImageUpload(i: User, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let file = event.target.files[0];
|
let file = event.target.files[0];
|
||||||
|
@ -856,6 +928,8 @@ export class User extends Component<any, UserState> {
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
alert(i18n.t(msg.error));
|
alert(i18n.t(msg.error));
|
||||||
this.state.deleteAccountLoading = false;
|
this.state.deleteAccountLoading = false;
|
||||||
|
this.state.avatarLoading = false;
|
||||||
|
this.state.userSettingsLoading = false;
|
||||||
if (msg.error == 'couldnt_find_that_username_or_email') {
|
if (msg.error == 'couldnt_find_that_username_or_email') {
|
||||||
this.context.router.history.push('/');
|
this.context.router.history.push('/');
|
||||||
}
|
}
|
||||||
|
@ -882,6 +956,7 @@ export class User extends Component<any, UserState> {
|
||||||
UserService.Instance.user.default_listing_type;
|
UserService.Instance.user.default_listing_type;
|
||||||
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
|
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
|
||||||
this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
|
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}`;
|
document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
|
||||||
window.scrollTo(0, 0);
|
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;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
email?: string;
|
||||||
fedi_name: string;
|
fedi_name: string;
|
||||||
published: string;
|
published: string;
|
||||||
number_of_posts: number;
|
number_of_posts: number;
|
||||||
|
@ -481,6 +482,10 @@ export interface UserSettingsForm {
|
||||||
default_listing_type: ListingType;
|
default_listing_type: ListingType;
|
||||||
lang: string;
|
lang: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
email?: string;
|
||||||
|
new_password?: string;
|
||||||
|
new_password_verify?: string;
|
||||||
|
old_password?: string;
|
||||||
auth: 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',
|
unread_messages: 'Unread Messages',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
verify_password: 'Verify Password',
|
verify_password: 'Verify Password',
|
||||||
|
old_password: 'Old Password',
|
||||||
forgot_password: 'forgot password',
|
forgot_password: 'forgot password',
|
||||||
reset_password_mail_sent: 'Sent an Email to reset your password.',
|
reset_password_mail_sent: 'Sent an Email to reset your password.',
|
||||||
password_change: 'Password Change',
|
password_change: 'Password Change',
|
||||||
|
|
Loading…
Reference in a new issue