1 import { NoOptionI18nKeys } from "i18next";
2 import { Component, linkEvent } from "inferno";
4 BlockCommunityResponse,
13 } from "lemmy-js-client";
14 import { i18n, languages } from "../../i18next";
15 import { UserService } from "../../services";
16 import { HttpService, RequestState } from "../../services/HttpService";
19 capitalizeFirstLetter,
41 import { HtmlTags } from "../common/html-tags";
42 import { Icon, Spinner } from "../common/icon";
43 import { ImageUploadForm } from "../common/image-upload-form";
44 import { LanguageSelect } from "../common/language-select";
45 import { ListingTypeSelect } from "../common/listing-type-select";
46 import { MarkdownTextArea } from "../common/markdown-textarea";
47 import { SearchableSelect } from "../common/searchable-select";
48 import { SortSelect } from "../common/sort-select";
49 import Tabs from "../common/tabs";
50 import { CommunityLink } from "../community/community-link";
51 import { PersonListing } from "./person-listing";
53 interface SettingsState {
54 saveRes: RequestState<LoginResponse>;
55 changePasswordRes: RequestState<LoginResponse>;
56 deleteAccountRes: RequestState<DeleteAccountResponse>;
57 // TODO redo these forms
58 saveUserSettingsForm: {
61 default_sort_type?: SortType;
62 default_listing_type?: ListingType;
63 interface_language?: string;
66 display_name?: string;
69 matrix_user_id?: string;
70 show_avatars?: boolean;
71 show_scores?: boolean;
72 send_notifications_to_email?: boolean;
73 bot_account?: boolean;
74 show_bot_accounts?: boolean;
75 show_read_posts?: boolean;
76 show_new_post_notifs?: boolean;
77 discussion_languages?: number[];
78 generate_totp_2fa?: boolean;
81 new_password?: string;
82 new_password_verify?: string;
83 old_password?: string;
88 personBlocks: PersonBlockView[];
89 communityBlocks: CommunityBlockView[];
92 deleteAccountShowConfirm: boolean;
93 siteRes: GetSiteResponse;
94 searchCommunityLoading: boolean;
95 searchCommunityOptions: Choice[];
96 searchPersonLoading: boolean;
97 searchPersonOptions: Choice[];
100 type FilterType = "user" | "community";
109 filterType: FilterType;
111 onSearch: (text: string) => void;
112 onChange: (choice: Choice) => void;
115 <div className="form-group row">
117 className="col-md-4 col-form-label"
118 htmlFor={`block-${filterType}-filter`}
120 {i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
122 <div className="col-md-8">
124 id={`block-${filterType}-filter`}
126 { label: emDash, value: "0", disabled: true } as Choice,
136 export class Settings extends Component<any, SettingsState> {
137 private isoData = setIsoData(this.context);
138 state: SettingsState = {
139 saveRes: { state: "empty" },
140 deleteAccountRes: { state: "empty" },
141 changePasswordRes: { state: "empty" },
142 saveUserSettingsForm: {},
143 changePasswordForm: {},
144 deleteAccountShowConfirm: false,
145 deleteAccountForm: {},
148 currentTab: "settings",
149 siteRes: this.isoData.site_res,
151 searchCommunityLoading: false,
152 searchCommunityOptions: [],
153 searchPersonLoading: false,
154 searchPersonOptions: [],
157 constructor(props: any, context: any) {
158 super(props, context);
160 this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
161 this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
162 this.handleBioChange = this.handleBioChange.bind(this);
163 this.handleDiscussionLanguageChange =
164 this.handleDiscussionLanguageChange.bind(this);
166 this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
167 this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
169 this.handleBannerUpload = this.handleBannerUpload.bind(this);
170 this.handleBannerRemove = this.handleBannerRemove.bind(this);
171 this.userSettings = this.userSettings.bind(this);
172 this.blockCards = this.blockCards.bind(this);
174 this.handleBlockPerson = this.handleBlockPerson.bind(this);
175 this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
177 const mui = UserService.Instance.myUserInfo;
184 default_listing_type,
190 show_new_post_notifs,
191 send_notifications_to_email,
202 } = mui.local_user_view;
206 personBlocks: mui.person_blocks,
207 communityBlocks: mui.community_blocks,
208 saveUserSettingsForm: {
209 ...this.state.saveUserSettingsForm,
211 theme: theme ?? "browser",
213 default_listing_type,
215 discussion_languages: mui.discussion_languages,
224 show_new_post_notifs,
227 send_notifications_to_email,
234 async componentDidMount() {
236 this.setState({ themeList: await fetchThemeList() });
239 get documentTitle(): string {
240 return i18n.t("settings");
245 <div className="container-lg">
247 title={this.documentTitle}
248 path={this.context.router.route.match.url}
249 description={this.documentTitle}
250 image={this.state.saveUserSettingsForm.avatar}
256 label: i18n.t("settings"),
257 getNode: this.userSettings,
261 label: i18n.t("blocks"),
262 getNode: this.blockCards,
272 <div className="row">
273 <div className="col-12 col-md-6">
274 <div className="card border-secondary mb-3">
275 <div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
278 <div className="col-12 col-md-6">
279 <div className="card border-secondary mb-3">
280 <div className="card-body">{this.changePasswordHtmlForm()}</div>
289 <div className="row">
290 <div className="col-12 col-md-6">
291 <div className="card border-secondary mb-3">
292 <div className="card-body">{this.blockUserCard()}</div>
295 <div className="col-12 col-md-6">
296 <div className="card border-secondary mb-3">
297 <div className="card-body">{this.blockCommunityCard()}</div>
304 changePasswordHtmlForm() {
307 <h5>{i18n.t("change_password")}</h5>
308 <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
309 <div className="form-group row">
310 <label className="col-sm-5 col-form-label" htmlFor="user-password">
311 {i18n.t("new_password")}
313 <div className="col-sm-7">
317 className="form-control"
318 value={this.state.changePasswordForm.new_password}
319 autoComplete="new-password"
321 onInput={linkEvent(this, this.handleNewPasswordChange)}
325 <div className="form-group row">
327 className="col-sm-5 col-form-label"
328 htmlFor="user-verify-password"
330 {i18n.t("verify_password")}
332 <div className="col-sm-7">
335 id="user-verify-password"
336 className="form-control"
337 value={this.state.changePasswordForm.new_password_verify}
338 autoComplete="new-password"
340 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
344 <div className="form-group row">
346 className="col-sm-5 col-form-label"
347 htmlFor="user-old-password"
349 {i18n.t("old_password")}
351 <div className="col-sm-7">
354 id="user-old-password"
355 className="form-control"
356 value={this.state.changePasswordForm.old_password}
357 autoComplete="new-password"
359 onInput={linkEvent(this, this.handleOldPasswordChange)}
363 <div className="form-group">
364 <button type="submit" className="btn btn-block btn-secondary mr-4">
365 {this.state.changePasswordRes.state === "loading" ? (
368 capitalizeFirstLetter(i18n.t("save"))
378 const { searchPersonLoading, searchPersonOptions } = this.state;
384 loading={searchPersonLoading}
385 onChange={this.handleBlockPerson}
386 onSearch={this.handlePersonSearch}
387 options={searchPersonOptions}
389 {this.blockedUsersList()}
397 <h5>{i18n.t("blocked_users")}</h5>
398 <ul className="list-unstyled mb-0">
399 {this.state.personBlocks.map(pb => (
400 <li key={pb.target.id}>
402 <PersonListing person={pb.target} />
404 className="btn btn-sm"
406 { ctx: this, recipientId: pb.target.id },
407 this.handleUnblockPerson
409 data-tippy-content={i18n.t("unblock_user")}
411 <Icon icon="x" classes="icon-inline" />
421 blockCommunityCard() {
422 const { searchCommunityLoading, searchCommunityOptions } = this.state;
427 filterType="community"
428 loading={searchCommunityLoading}
429 onChange={this.handleBlockCommunity}
430 onSearch={this.handleCommunitySearch}
431 options={searchCommunityOptions}
433 {this.blockedCommunitiesList()}
438 blockedCommunitiesList() {
441 <h5>{i18n.t("blocked_communities")}</h5>
442 <ul className="list-unstyled mb-0">
443 {this.state.communityBlocks.map(cb => (
444 <li key={cb.community.id}>
446 <CommunityLink community={cb.community} />
448 className="btn btn-sm"
450 { ctx: this, communityId: cb.community.id },
451 this.handleUnblockCommunity
453 data-tippy-content={i18n.t("unblock_community")}
455 <Icon icon="x" classes="icon-inline" />
465 saveUserSettingsHtmlForm() {
466 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
470 <h5>{i18n.t("settings")}</h5>
471 <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
472 <div className="form-group row">
473 <label className="col-sm-5 col-form-label" htmlFor="display-name">
474 {i18n.t("display_name")}
476 <div className="col-sm-7">
480 className="form-control"
481 placeholder={i18n.t("optional")}
482 value={this.state.saveUserSettingsForm.display_name}
483 onInput={linkEvent(this, this.handleDisplayNameChange)}
484 pattern="^(?!@)(.+)$"
489 <div className="form-group row">
490 <label className="col-sm-3 col-form-label" htmlFor="user-bio">
493 <div className="col-sm-9">
495 initialContent={this.state.saveUserSettingsForm.bio}
496 onContentChange={this.handleBioChange}
498 hideNavigationWarnings
499 allLanguages={this.state.siteRes.all_languages}
500 siteLanguages={this.state.siteRes.discussion_languages}
504 <div className="form-group row">
505 <label className="col-sm-3 col-form-label" htmlFor="user-email">
508 <div className="col-sm-9">
512 className="form-control"
513 placeholder={i18n.t("optional")}
514 value={this.state.saveUserSettingsForm.email}
515 onInput={linkEvent(this, this.handleEmailChange)}
520 <div className="form-group row">
521 <label className="col-sm-5 col-form-label" htmlFor="matrix-user-id">
522 <a href={elementUrl} rel={relTags}>
523 {i18n.t("matrix_user_id")}
526 <div className="col-sm-7">
530 className="form-control"
531 placeholder="@user:example.com"
532 value={this.state.saveUserSettingsForm.matrix_user_id}
533 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
534 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
538 <div className="form-group row">
539 <label className="col-sm-3">{i18n.t("avatar")}</label>
540 <div className="col-sm-9">
542 uploadTitle={i18n.t("upload_avatar")}
543 imageSrc={this.state.saveUserSettingsForm.avatar}
544 onUpload={this.handleAvatarUpload}
545 onRemove={this.handleAvatarRemove}
550 <div className="form-group row">
551 <label className="col-sm-3">{i18n.t("banner")}</label>
552 <div className="col-sm-9">
554 uploadTitle={i18n.t("upload_banner")}
555 imageSrc={this.state.saveUserSettingsForm.banner}
556 onUpload={this.handleBannerUpload}
557 onRemove={this.handleBannerRemove}
561 <div className="form-group row">
562 <label className="col-sm-3" htmlFor="user-language">
563 {i18n.t("interface_language")}
565 <div className="col-sm-9">
568 value={this.state.saveUserSettingsForm.interface_language}
569 onChange={linkEvent(this, this.handleInterfaceLangChange)}
570 className="custom-select w-auto"
572 <option disabled aria-hidden="true">
573 {i18n.t("interface_language")}
575 <option value="browser">{i18n.t("browser_default")}</option>
576 <option disabled aria-hidden="true">
580 .sort((a, b) => a.code.localeCompare(b.code))
582 <option key={lang.code} value={lang.code}>
590 allLanguages={this.state.siteRes.all_languages}
591 siteLanguages={this.state.siteRes.discussion_languages}
592 selectedLanguageIds={selectedLangs}
594 showLanguageWarning={true}
596 onChange={this.handleDiscussionLanguageChange}
598 <div className="form-group row">
599 <label className="col-sm-3" htmlFor="user-theme">
602 <div className="col-sm-9">
605 value={this.state.saveUserSettingsForm.theme}
606 onChange={linkEvent(this, this.handleThemeChange)}
607 className="custom-select w-auto"
609 <option disabled aria-hidden="true">
612 <option value="browser">{i18n.t("browser_default")}</option>
613 {this.state.themeList.map(theme => (
614 <option key={theme} value={theme}>
621 <form className="form-group row">
622 <label className="col-sm-3">{i18n.t("type")}</label>
623 <div className="col-sm-9">
626 this.state.saveUserSettingsForm.default_listing_type ??
629 showLocal={showLocal(this.isoData)}
631 onChange={this.handleListingTypeChange}
635 <form className="form-group row">
636 <label className="col-sm-3">{i18n.t("sort_type")}</label>
637 <div className="col-sm-9">
640 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
642 onChange={this.handleSortTypeChange}
646 {enableNsfw(this.state.siteRes) && (
647 <div className="form-group">
648 <div className="form-check">
650 className="form-check-input"
653 checked={this.state.saveUserSettingsForm.show_nsfw}
654 onChange={linkEvent(this, this.handleShowNsfwChange)}
656 <label className="form-check-label" htmlFor="user-show-nsfw">
657 {i18n.t("show_nsfw")}
662 <div className="form-group">
663 <div className="form-check">
665 className="form-check-input"
666 id="user-show-scores"
668 checked={this.state.saveUserSettingsForm.show_scores}
669 onChange={linkEvent(this, this.handleShowScoresChange)}
671 <label className="form-check-label" htmlFor="user-show-scores">
672 {i18n.t("show_scores")}
676 <div className="form-group">
677 <div className="form-check">
679 className="form-check-input"
680 id="user-show-avatars"
682 checked={this.state.saveUserSettingsForm.show_avatars}
683 onChange={linkEvent(this, this.handleShowAvatarsChange)}
685 <label className="form-check-label" htmlFor="user-show-avatars">
686 {i18n.t("show_avatars")}
690 <div className="form-group">
691 <div className="form-check">
693 className="form-check-input"
694 id="user-bot-account"
696 checked={this.state.saveUserSettingsForm.bot_account}
697 onChange={linkEvent(this, this.handleBotAccount)}
699 <label className="form-check-label" htmlFor="user-bot-account">
700 {i18n.t("bot_account")}
704 <div className="form-group">
705 <div className="form-check">
707 className="form-check-input"
708 id="user-show-bot-accounts"
710 checked={this.state.saveUserSettingsForm.show_bot_accounts}
711 onChange={linkEvent(this, this.handleShowBotAccounts)}
714 className="form-check-label"
715 htmlFor="user-show-bot-accounts"
717 {i18n.t("show_bot_accounts")}
721 <div className="form-group">
722 <div className="form-check">
724 className="form-check-input"
725 id="user-show-read-posts"
727 checked={this.state.saveUserSettingsForm.show_read_posts}
728 onChange={linkEvent(this, this.handleReadPosts)}
731 className="form-check-label"
732 htmlFor="user-show-read-posts"
734 {i18n.t("show_read_posts")}
738 <div className="form-group">
739 <div className="form-check">
741 className="form-check-input"
742 id="user-show-new-post-notifs"
744 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
745 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
748 className="form-check-label"
749 htmlFor="user-show-new-post-notifs"
751 {i18n.t("show_new_post_notifs")}
755 <div className="form-group">
756 <div className="form-check">
758 className="form-check-input"
759 id="user-send-notifications-to-email"
761 disabled={!this.state.saveUserSettingsForm.email}
763 this.state.saveUserSettingsForm.send_notifications_to_email
767 this.handleSendNotificationsToEmailChange
771 className="form-check-label"
772 htmlFor="user-send-notifications-to-email"
774 {i18n.t("send_notifications_to_email")}
779 <div className="form-group">
780 <button type="submit" className="btn btn-block btn-secondary mr-4">
781 {this.state.saveRes.state === "loading" ? (
784 capitalizeFirstLetter(i18n.t("save"))
789 <div className="form-group">
791 className="btn btn-block btn-danger"
794 this.handleDeleteAccountShowConfirmToggle
797 {i18n.t("delete_account")}
799 {this.state.deleteAccountShowConfirm && (
801 <div className="my-2 alert alert-danger" role="alert">
802 {i18n.t("delete_account_confirm")}
806 value={this.state.deleteAccountForm.password}
807 autoComplete="new-password"
811 this.handleDeleteAccountPasswordChange
813 className="form-control my-2"
816 className="btn btn-danger mr-4"
817 disabled={!this.state.deleteAccountForm.password}
818 onClick={linkEvent(this, this.handleDeleteAccount)}
820 {this.state.deleteAccountRes.state === "loading" ? (
823 capitalizeFirstLetter(i18n.t("delete"))
827 className="btn btn-secondary"
830 this.handleDeleteAccountShowConfirmToggle
845 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
850 <div className="form-group">
851 <div className="form-check">
853 className="form-check-input"
854 id="user-generate-totp"
856 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
857 onChange={linkEvent(this, this.handleGenerateTotp)}
859 <label className="form-check-label" htmlFor="user-generate-totp">
860 {i18n.t("set_up_two_factor")}
869 <a className="btn btn-secondary mb-2" href={totpUrl}>
870 {i18n.t("two_factor_link")}
873 <div className="form-group">
874 <div className="form-check">
876 className="form-check-input"
877 id="user-remove-totp"
880 this.state.saveUserSettingsForm.generate_totp_2fa == false
882 onChange={linkEvent(this, this.handleRemoveTotp)}
884 <label className="form-check-label" htmlFor="user-remove-totp">
885 {i18n.t("remove_two_factor")}
895 handlePersonSearch = debounce(async (text: string) => {
896 this.setState({ searchPersonLoading: true });
898 const searchPersonOptions: Choice[] = [];
900 if (text.length > 0) {
901 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
905 searchPersonLoading: false,
910 handleCommunitySearch = debounce(async (text: string) => {
911 this.setState({ searchCommunityLoading: true });
913 const searchCommunityOptions: Choice[] = [];
915 if (text.length > 0) {
916 searchCommunityOptions.push(
917 ...(await fetchCommunities(text)).map(communityToChoice)
922 searchCommunityLoading: false,
923 searchCommunityOptions,
927 async handleBlockPerson({ value }: Choice) {
929 const res = await HttpService.client.blockPerson({
930 person_id: Number(value),
932 auth: myAuthRequired(),
934 this.personBlock(res);
938 async handleUnblockPerson({
945 const res = await HttpService.client.blockPerson({
946 person_id: recipientId,
948 auth: myAuthRequired(),
950 ctx.personBlock(res);
953 async handleBlockCommunity({ value }: Choice) {
955 const res = await HttpService.client.blockCommunity({
956 community_id: Number(value),
958 auth: myAuthRequired(),
960 this.communityBlock(res);
964 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
965 const auth = myAuth();
967 const res = await HttpService.client.blockCommunity({
968 community_id: i.communityId,
970 auth: myAuthRequired(),
972 i.ctx.communityBlock(res);
976 handleShowNsfwChange(i: Settings, event: any) {
978 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
982 handleShowAvatarsChange(i: Settings, event: any) {
983 const mui = UserService.Instance.myUserInfo;
985 mui.local_user_view.local_user.show_avatars = event.target.checked;
988 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
992 handleBotAccount(i: Settings, event: any) {
994 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
998 handleShowBotAccounts(i: Settings, event: any) {
1001 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1006 handleReadPosts(i: Settings, event: any) {
1008 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
1012 handleShowNewPostNotifs(i: Settings, event: any) {
1015 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1020 handleShowScoresChange(i: Settings, event: any) {
1021 const mui = UserService.Instance.myUserInfo;
1023 mui.local_user_view.local_user.show_scores = event.target.checked;
1026 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
1030 handleGenerateTotp(i: Settings, event: any) {
1031 // Coerce false to undefined here, so it won't generate it.
1032 const checked: boolean | undefined = event.target.checked || undefined;
1034 toast(i18n.t("two_factor_setup_instructions"));
1036 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1039 handleRemoveTotp(i: Settings, event: any) {
1040 // Coerce true to undefined here, so it won't generate it.
1041 const checked: boolean | undefined = !event.target.checked && undefined;
1042 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1045 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1048 (s.saveUserSettingsForm.send_notifications_to_email =
1049 event.target.checked),
1055 handleThemeChange(i: Settings, event: any) {
1056 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1057 setTheme(event.target.value, true);
1060 handleInterfaceLangChange(i: Settings, event: any) {
1062 s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
1064 i18n.changeLanguage(
1065 getLanguages(i.state.saveUserSettingsForm.interface_language).at(0)
1069 handleDiscussionLanguageChange(val: number[]) {
1071 s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1075 handleSortTypeChange(val: SortType) {
1076 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1079 handleListingTypeChange(val: ListingType) {
1081 s => ((s.saveUserSettingsForm.default_listing_type = val), s)
1085 handleEmailChange(i: Settings, event: any) {
1086 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1089 handleBioChange(val: string) {
1090 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1093 handleAvatarUpload(url: string) {
1094 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1097 handleAvatarRemove() {
1098 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1101 handleBannerUpload(url: string) {
1102 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1105 handleBannerRemove() {
1106 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1109 handleDisplayNameChange(i: Settings, event: any) {
1111 s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
1115 handleMatrixUserIdChange(i: Settings, event: any) {
1117 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
1121 handleNewPasswordChange(i: Settings, event: any) {
1122 const newPass: string | undefined =
1123 event.target.value == "" ? undefined : event.target.value;
1124 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1127 handleNewPasswordVerifyChange(i: Settings, event: any) {
1128 const newPassVerify: string | undefined =
1129 event.target.value == "" ? undefined : event.target.value;
1131 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
1135 handleOldPasswordChange(i: Settings, event: any) {
1136 const oldPass: string | undefined =
1137 event.target.value == "" ? undefined : event.target.value;
1138 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1141 async handleSaveSettingsSubmit(i: Settings, event: any) {
1142 event.preventDefault();
1143 i.setState({ saveRes: { state: "loading" } });
1145 const saveRes = await HttpService.client.saveUserSettings({
1146 ...i.state.saveUserSettingsForm,
1147 auth: myAuthRequired(),
1149 if (saveRes.state === "success") {
1150 UserService.Instance.login(saveRes.data);
1152 toast(i18n.t("saved"));
1153 window.scrollTo(0, 0);
1156 i.setState({ saveRes });
1159 async handleChangePasswordSubmit(i: Settings, event: any) {
1160 event.preventDefault();
1161 const { new_password, new_password_verify, old_password } =
1162 i.state.changePasswordForm;
1164 if (new_password && old_password && new_password_verify) {
1165 i.setState({ changePasswordRes: { state: "loading" } });
1166 const changePasswordRes = await HttpService.client.changePassword({
1168 new_password_verify,
1170 auth: myAuthRequired(),
1172 if (changePasswordRes.state === "success") {
1173 UserService.Instance.login(changePasswordRes.data);
1174 window.scrollTo(0, 0);
1175 toast(i18n.t("password_changed"));
1178 i.setState({ changePasswordRes });
1182 handleDeleteAccountShowConfirmToggle(i: Settings) {
1183 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1186 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1187 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1190 async handleDeleteAccount(i: Settings) {
1191 const password = i.state.deleteAccountForm.password;
1193 i.setState({ deleteAccountRes: { state: "loading" } });
1194 const deleteAccountRes = await HttpService.client.deleteAccount({
1196 auth: myAuthRequired(),
1198 if (deleteAccountRes.state === "success") {
1199 UserService.Instance.logout();
1200 this.context.router.history.replace("/");
1203 i.setState({ deleteAccountRes });
1207 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1208 i.ctx.setState({ currentTab: i.tab });
1211 personBlock(res: RequestState<BlockPersonResponse>) {
1212 if (res.state === "success") {
1213 updatePersonBlock(res.data);
1214 const mui = UserService.Instance.myUserInfo;
1216 this.setState({ personBlocks: mui.person_blocks });
1221 communityBlock(res: RequestState<BlockCommunityResponse>) {
1222 if (res.state === "success") {
1223 updateCommunityBlock(res.data);
1224 const mui = UserService.Instance.myUserInfo;
1226 this.setState({ communityBlocks: mui.community_blocks });