1 import { debounce } from "@utils/helpers";
2 import { NoOptionI18nKeys } from "i18next";
3 import { Component, linkEvent } from "inferno";
5 BlockCommunityResponse,
14 } from "lemmy-js-client";
15 import { i18n, languages } from "../../i18next";
16 import { UserService } from "../../services";
17 import { HttpService, RequestState } from "../../services/HttpService";
20 capitalizeFirstLetter,
39 import { HtmlTags } from "../common/html-tags";
40 import { Icon, Spinner } from "../common/icon";
41 import { ImageUploadForm } from "../common/image-upload-form";
42 import { LanguageSelect } from "../common/language-select";
43 import { ListingTypeSelect } from "../common/listing-type-select";
44 import { MarkdownTextArea } from "../common/markdown-textarea";
45 import { SearchableSelect } from "../common/searchable-select";
46 import { SortSelect } from "../common/sort-select";
47 import Tabs from "../common/tabs";
48 import { CommunityLink } from "../community/community-link";
49 import { PersonListing } from "./person-listing";
51 interface SettingsState {
52 saveRes: RequestState<LoginResponse>;
53 changePasswordRes: RequestState<LoginResponse>;
54 deleteAccountRes: RequestState<DeleteAccountResponse>;
55 // TODO redo these forms
56 saveUserSettingsForm: {
59 default_sort_type?: SortType;
60 default_listing_type?: ListingType;
61 interface_language?: string;
64 display_name?: string;
67 matrix_user_id?: string;
68 show_avatars?: boolean;
69 show_scores?: boolean;
70 send_notifications_to_email?: boolean;
71 bot_account?: boolean;
72 show_bot_accounts?: boolean;
73 show_read_posts?: boolean;
74 show_new_post_notifs?: boolean;
75 discussion_languages?: number[];
76 generate_totp_2fa?: boolean;
79 new_password?: string;
80 new_password_verify?: string;
81 old_password?: string;
86 personBlocks: PersonBlockView[];
87 communityBlocks: CommunityBlockView[];
90 deleteAccountShowConfirm: boolean;
91 siteRes: GetSiteResponse;
92 searchCommunityLoading: boolean;
93 searchCommunityOptions: Choice[];
94 searchPersonLoading: boolean;
95 searchPersonOptions: Choice[];
98 type FilterType = "user" | "community";
107 filterType: FilterType;
109 onSearch: (text: string) => void;
110 onChange: (choice: Choice) => void;
113 <div className="form-group row">
115 className="col-md-4 col-form-label"
116 htmlFor={`block-${filterType}-filter`}
118 {i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
120 <div className="col-md-8">
122 id={`block-${filterType}-filter`}
124 { label: emDash, value: "0", disabled: true } as Choice,
134 export class Settings extends Component<any, SettingsState> {
135 private isoData = setIsoData(this.context);
136 state: SettingsState = {
137 saveRes: { state: "empty" },
138 deleteAccountRes: { state: "empty" },
139 changePasswordRes: { state: "empty" },
140 saveUserSettingsForm: {},
141 changePasswordForm: {},
142 deleteAccountShowConfirm: false,
143 deleteAccountForm: {},
146 currentTab: "settings",
147 siteRes: this.isoData.site_res,
149 searchCommunityLoading: false,
150 searchCommunityOptions: [],
151 searchPersonLoading: false,
152 searchPersonOptions: [],
155 constructor(props: any, context: any) {
156 super(props, context);
158 this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
159 this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
160 this.handleBioChange = this.handleBioChange.bind(this);
161 this.handleDiscussionLanguageChange =
162 this.handleDiscussionLanguageChange.bind(this);
164 this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
165 this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
167 this.handleBannerUpload = this.handleBannerUpload.bind(this);
168 this.handleBannerRemove = this.handleBannerRemove.bind(this);
169 this.userSettings = this.userSettings.bind(this);
170 this.blockCards = this.blockCards.bind(this);
172 this.handleBlockPerson = this.handleBlockPerson.bind(this);
173 this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
175 const mui = UserService.Instance.myUserInfo;
182 default_listing_type,
188 show_new_post_notifs,
189 send_notifications_to_email,
200 } = mui.local_user_view;
204 personBlocks: mui.person_blocks,
205 communityBlocks: mui.community_blocks,
206 saveUserSettingsForm: {
207 ...this.state.saveUserSettingsForm,
209 theme: theme ?? "browser",
211 default_listing_type,
213 discussion_languages: mui.discussion_languages,
222 show_new_post_notifs,
225 send_notifications_to_email,
232 async componentDidMount() {
234 this.setState({ themeList: await fetchThemeList() });
237 get documentTitle(): string {
238 return i18n.t("settings");
243 <div className="container-lg">
245 title={this.documentTitle}
246 path={this.context.router.route.match.url}
247 description={this.documentTitle}
248 image={this.state.saveUserSettingsForm.avatar}
254 label: i18n.t("settings"),
255 getNode: this.userSettings,
259 label: i18n.t("blocks"),
260 getNode: this.blockCards,
270 <div className="row">
271 <div className="col-12 col-md-6">
272 <div className="card border-secondary mb-3">
273 <div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
276 <div className="col-12 col-md-6">
277 <div className="card border-secondary mb-3">
278 <div className="card-body">{this.changePasswordHtmlForm()}</div>
287 <div className="row">
288 <div className="col-12 col-md-6">
289 <div className="card border-secondary mb-3">
290 <div className="card-body">{this.blockUserCard()}</div>
293 <div className="col-12 col-md-6">
294 <div className="card border-secondary mb-3">
295 <div className="card-body">{this.blockCommunityCard()}</div>
302 changePasswordHtmlForm() {
305 <h5>{i18n.t("change_password")}</h5>
306 <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
307 <div className="form-group row">
308 <label className="col-sm-5 col-form-label" htmlFor="user-password">
309 {i18n.t("new_password")}
311 <div className="col-sm-7">
315 className="form-control"
316 value={this.state.changePasswordForm.new_password}
317 autoComplete="new-password"
319 onInput={linkEvent(this, this.handleNewPasswordChange)}
323 <div className="form-group row">
325 className="col-sm-5 col-form-label"
326 htmlFor="user-verify-password"
328 {i18n.t("verify_password")}
330 <div className="col-sm-7">
333 id="user-verify-password"
334 className="form-control"
335 value={this.state.changePasswordForm.new_password_verify}
336 autoComplete="new-password"
338 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
342 <div className="form-group row">
344 className="col-sm-5 col-form-label"
345 htmlFor="user-old-password"
347 {i18n.t("old_password")}
349 <div className="col-sm-7">
352 id="user-old-password"
353 className="form-control"
354 value={this.state.changePasswordForm.old_password}
355 autoComplete="new-password"
357 onInput={linkEvent(this, this.handleOldPasswordChange)}
361 <div className="form-group">
362 <button type="submit" className="btn btn-block btn-secondary mr-4">
363 {this.state.changePasswordRes.state === "loading" ? (
366 capitalizeFirstLetter(i18n.t("save"))
376 const { searchPersonLoading, searchPersonOptions } = this.state;
382 loading={searchPersonLoading}
383 onChange={this.handleBlockPerson}
384 onSearch={this.handlePersonSearch}
385 options={searchPersonOptions}
387 {this.blockedUsersList()}
395 <h5>{i18n.t("blocked_users")}</h5>
396 <ul className="list-unstyled mb-0">
397 {this.state.personBlocks.map(pb => (
398 <li key={pb.target.id}>
400 <PersonListing person={pb.target} />
402 className="btn btn-sm"
404 { ctx: this, recipientId: pb.target.id },
405 this.handleUnblockPerson
407 data-tippy-content={i18n.t("unblock_user")}
409 <Icon icon="x" classes="icon-inline" />
419 blockCommunityCard() {
420 const { searchCommunityLoading, searchCommunityOptions } = this.state;
425 filterType="community"
426 loading={searchCommunityLoading}
427 onChange={this.handleBlockCommunity}
428 onSearch={this.handleCommunitySearch}
429 options={searchCommunityOptions}
431 {this.blockedCommunitiesList()}
436 blockedCommunitiesList() {
439 <h5>{i18n.t("blocked_communities")}</h5>
440 <ul className="list-unstyled mb-0">
441 {this.state.communityBlocks.map(cb => (
442 <li key={cb.community.id}>
444 <CommunityLink community={cb.community} />
446 className="btn btn-sm"
448 { ctx: this, communityId: cb.community.id },
449 this.handleUnblockCommunity
451 data-tippy-content={i18n.t("unblock_community")}
453 <Icon icon="x" classes="icon-inline" />
463 saveUserSettingsHtmlForm() {
464 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
468 <h5>{i18n.t("settings")}</h5>
469 <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
470 <div className="form-group row">
471 <label className="col-sm-5 col-form-label" htmlFor="display-name">
472 {i18n.t("display_name")}
474 <div className="col-sm-7">
478 className="form-control"
479 placeholder={i18n.t("optional")}
480 value={this.state.saveUserSettingsForm.display_name}
481 onInput={linkEvent(this, this.handleDisplayNameChange)}
482 pattern="^(?!@)(.+)$"
487 <div className="form-group row">
488 <label className="col-sm-3 col-form-label" htmlFor="user-bio">
491 <div className="col-sm-9">
493 initialContent={this.state.saveUserSettingsForm.bio}
494 onContentChange={this.handleBioChange}
496 hideNavigationWarnings
497 allLanguages={this.state.siteRes.all_languages}
498 siteLanguages={this.state.siteRes.discussion_languages}
502 <div className="form-group row">
503 <label className="col-sm-3 col-form-label" htmlFor="user-email">
506 <div className="col-sm-9">
510 className="form-control"
511 placeholder={i18n.t("optional")}
512 value={this.state.saveUserSettingsForm.email}
513 onInput={linkEvent(this, this.handleEmailChange)}
518 <div className="form-group row">
519 <label className="col-sm-5 col-form-label" htmlFor="matrix-user-id">
520 <a href={elementUrl} rel={relTags}>
521 {i18n.t("matrix_user_id")}
524 <div className="col-sm-7">
528 className="form-control"
529 placeholder="@user:example.com"
530 value={this.state.saveUserSettingsForm.matrix_user_id}
531 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
532 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
536 <div className="form-group row">
537 <label className="col-sm-3">{i18n.t("avatar")}</label>
538 <div className="col-sm-9">
540 uploadTitle={i18n.t("upload_avatar")}
541 imageSrc={this.state.saveUserSettingsForm.avatar}
542 onUpload={this.handleAvatarUpload}
543 onRemove={this.handleAvatarRemove}
548 <div className="form-group row">
549 <label className="col-sm-3">{i18n.t("banner")}</label>
550 <div className="col-sm-9">
552 uploadTitle={i18n.t("upload_banner")}
553 imageSrc={this.state.saveUserSettingsForm.banner}
554 onUpload={this.handleBannerUpload}
555 onRemove={this.handleBannerRemove}
559 <div className="form-group row">
560 <label className="col-sm-3" htmlFor="user-language">
561 {i18n.t("interface_language")}
563 <div className="col-sm-9">
566 value={this.state.saveUserSettingsForm.interface_language}
567 onChange={linkEvent(this, this.handleInterfaceLangChange)}
568 className="custom-select w-auto"
570 <option disabled aria-hidden="true">
571 {i18n.t("interface_language")}
573 <option value="browser">{i18n.t("browser_default")}</option>
574 <option disabled aria-hidden="true">
578 .sort((a, b) => a.code.localeCompare(b.code))
580 <option key={lang.code} value={lang.code}>
588 allLanguages={this.state.siteRes.all_languages}
589 siteLanguages={this.state.siteRes.discussion_languages}
590 selectedLanguageIds={selectedLangs}
592 showLanguageWarning={true}
594 onChange={this.handleDiscussionLanguageChange}
596 <div className="form-group row">
597 <label className="col-sm-3" htmlFor="user-theme">
600 <div className="col-sm-9">
603 value={this.state.saveUserSettingsForm.theme}
604 onChange={linkEvent(this, this.handleThemeChange)}
605 className="custom-select w-auto"
607 <option disabled aria-hidden="true">
610 <option value="browser">{i18n.t("browser_default")}</option>
611 {this.state.themeList.map(theme => (
612 <option key={theme} value={theme}>
619 <form className="form-group row">
620 <label className="col-sm-3">{i18n.t("type")}</label>
621 <div className="col-sm-9">
624 this.state.saveUserSettingsForm.default_listing_type ??
627 showLocal={showLocal(this.isoData)}
629 onChange={this.handleListingTypeChange}
633 <form className="form-group row">
634 <label className="col-sm-3">{i18n.t("sort_type")}</label>
635 <div className="col-sm-9">
638 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
640 onChange={this.handleSortTypeChange}
644 <div className="form-group">
645 <div className="form-check">
647 className="form-check-input"
650 checked={this.state.saveUserSettingsForm.show_nsfw}
651 onChange={linkEvent(this, this.handleShowNsfwChange)}
653 <label className="form-check-label" htmlFor="user-show-nsfw">
654 {i18n.t("show_nsfw")}
658 <div className="form-group">
659 <div className="form-check">
661 className="form-check-input"
662 id="user-show-scores"
664 checked={this.state.saveUserSettingsForm.show_scores}
665 onChange={linkEvent(this, this.handleShowScoresChange)}
667 <label className="form-check-label" htmlFor="user-show-scores">
668 {i18n.t("show_scores")}
672 <div className="form-group">
673 <div className="form-check">
675 className="form-check-input"
676 id="user-show-avatars"
678 checked={this.state.saveUserSettingsForm.show_avatars}
679 onChange={linkEvent(this, this.handleShowAvatarsChange)}
681 <label className="form-check-label" htmlFor="user-show-avatars">
682 {i18n.t("show_avatars")}
686 <div className="form-group">
687 <div className="form-check">
689 className="form-check-input"
690 id="user-bot-account"
692 checked={this.state.saveUserSettingsForm.bot_account}
693 onChange={linkEvent(this, this.handleBotAccount)}
695 <label className="form-check-label" htmlFor="user-bot-account">
696 {i18n.t("bot_account")}
700 <div className="form-group">
701 <div className="form-check">
703 className="form-check-input"
704 id="user-show-bot-accounts"
706 checked={this.state.saveUserSettingsForm.show_bot_accounts}
707 onChange={linkEvent(this, this.handleShowBotAccounts)}
710 className="form-check-label"
711 htmlFor="user-show-bot-accounts"
713 {i18n.t("show_bot_accounts")}
717 <div className="form-group">
718 <div className="form-check">
720 className="form-check-input"
721 id="user-show-read-posts"
723 checked={this.state.saveUserSettingsForm.show_read_posts}
724 onChange={linkEvent(this, this.handleReadPosts)}
727 className="form-check-label"
728 htmlFor="user-show-read-posts"
730 {i18n.t("show_read_posts")}
734 <div className="form-group">
735 <div className="form-check">
737 className="form-check-input"
738 id="user-show-new-post-notifs"
740 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
741 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
744 className="form-check-label"
745 htmlFor="user-show-new-post-notifs"
747 {i18n.t("show_new_post_notifs")}
751 <div className="form-group">
752 <div className="form-check">
754 className="form-check-input"
755 id="user-send-notifications-to-email"
757 disabled={!this.state.saveUserSettingsForm.email}
759 this.state.saveUserSettingsForm.send_notifications_to_email
763 this.handleSendNotificationsToEmailChange
767 className="form-check-label"
768 htmlFor="user-send-notifications-to-email"
770 {i18n.t("send_notifications_to_email")}
775 <div className="form-group">
776 <button type="submit" className="btn btn-block btn-secondary mr-4">
777 {this.state.saveRes.state === "loading" ? (
780 capitalizeFirstLetter(i18n.t("save"))
785 <div className="form-group">
787 className="btn btn-block btn-danger"
790 this.handleDeleteAccountShowConfirmToggle
793 {i18n.t("delete_account")}
795 {this.state.deleteAccountShowConfirm && (
797 <div className="my-2 alert alert-danger" role="alert">
798 {i18n.t("delete_account_confirm")}
802 value={this.state.deleteAccountForm.password}
803 autoComplete="new-password"
807 this.handleDeleteAccountPasswordChange
809 className="form-control my-2"
812 className="btn btn-danger mr-4"
813 disabled={!this.state.deleteAccountForm.password}
814 onClick={linkEvent(this, this.handleDeleteAccount)}
816 {this.state.deleteAccountRes.state === "loading" ? (
819 capitalizeFirstLetter(i18n.t("delete"))
823 className="btn btn-secondary"
826 this.handleDeleteAccountShowConfirmToggle
841 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
846 <div className="form-group">
847 <div className="form-check">
849 className="form-check-input"
850 id="user-generate-totp"
852 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
853 onChange={linkEvent(this, this.handleGenerateTotp)}
855 <label className="form-check-label" htmlFor="user-generate-totp">
856 {i18n.t("set_up_two_factor")}
865 <a className="btn btn-secondary mb-2" href={totpUrl}>
866 {i18n.t("two_factor_link")}
869 <div className="form-group">
870 <div className="form-check">
872 className="form-check-input"
873 id="user-remove-totp"
876 this.state.saveUserSettingsForm.generate_totp_2fa == false
878 onChange={linkEvent(this, this.handleRemoveTotp)}
880 <label className="form-check-label" htmlFor="user-remove-totp">
881 {i18n.t("remove_two_factor")}
891 handlePersonSearch = debounce(async (text: string) => {
892 this.setState({ searchPersonLoading: true });
894 const searchPersonOptions: Choice[] = [];
896 if (text.length > 0) {
897 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
901 searchPersonLoading: false,
906 handleCommunitySearch = debounce(async (text: string) => {
907 this.setState({ searchCommunityLoading: true });
909 const searchCommunityOptions: Choice[] = [];
911 if (text.length > 0) {
912 searchCommunityOptions.push(
913 ...(await fetchCommunities(text)).map(communityToChoice)
918 searchCommunityLoading: false,
919 searchCommunityOptions,
923 async handleBlockPerson({ value }: Choice) {
925 const res = await HttpService.client.blockPerson({
926 person_id: Number(value),
928 auth: myAuthRequired(),
930 this.personBlock(res);
934 async handleUnblockPerson({
941 const res = await HttpService.client.blockPerson({
942 person_id: recipientId,
944 auth: myAuthRequired(),
946 ctx.personBlock(res);
949 async handleBlockCommunity({ value }: Choice) {
951 const res = await HttpService.client.blockCommunity({
952 community_id: Number(value),
954 auth: myAuthRequired(),
956 this.communityBlock(res);
960 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
961 const auth = myAuth();
963 const res = await HttpService.client.blockCommunity({
964 community_id: i.communityId,
966 auth: myAuthRequired(),
968 i.ctx.communityBlock(res);
972 handleShowNsfwChange(i: Settings, event: any) {
974 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
978 handleShowAvatarsChange(i: Settings, event: any) {
979 const mui = UserService.Instance.myUserInfo;
981 mui.local_user_view.local_user.show_avatars = event.target.checked;
984 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
988 handleBotAccount(i: Settings, event: any) {
990 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
994 handleShowBotAccounts(i: Settings, event: any) {
997 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1002 handleReadPosts(i: Settings, event: any) {
1004 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
1008 handleShowNewPostNotifs(i: Settings, event: any) {
1011 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1016 handleShowScoresChange(i: Settings, event: any) {
1017 const mui = UserService.Instance.myUserInfo;
1019 mui.local_user_view.local_user.show_scores = event.target.checked;
1022 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
1026 handleGenerateTotp(i: Settings, event: any) {
1027 // Coerce false to undefined here, so it won't generate it.
1028 const checked: boolean | undefined = event.target.checked || undefined;
1030 toast(i18n.t("two_factor_setup_instructions"));
1032 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1035 handleRemoveTotp(i: Settings, event: any) {
1036 // Coerce true to undefined here, so it won't generate it.
1037 const checked: boolean | undefined = !event.target.checked && undefined;
1038 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1041 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1044 (s.saveUserSettingsForm.send_notifications_to_email =
1045 event.target.checked),
1051 handleThemeChange(i: Settings, event: any) {
1052 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1053 setTheme(event.target.value, true);
1056 handleInterfaceLangChange(i: Settings, event: any) {
1057 const newLang = event.target.value ?? "browser";
1058 i18n.changeLanguage(newLang === "browser" ? navigator.languages : newLang);
1061 s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
1065 handleDiscussionLanguageChange(val: number[]) {
1067 s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1071 handleSortTypeChange(val: SortType) {
1072 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1075 handleListingTypeChange(val: ListingType) {
1077 s => ((s.saveUserSettingsForm.default_listing_type = val), s)
1081 handleEmailChange(i: Settings, event: any) {
1082 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1085 handleBioChange(val: string) {
1086 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1089 handleAvatarUpload(url: string) {
1090 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1093 handleAvatarRemove() {
1094 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1097 handleBannerUpload(url: string) {
1098 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1101 handleBannerRemove() {
1102 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1105 handleDisplayNameChange(i: Settings, event: any) {
1107 s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
1111 handleMatrixUserIdChange(i: Settings, event: any) {
1113 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
1117 handleNewPasswordChange(i: Settings, event: any) {
1118 const newPass: string | undefined =
1119 event.target.value == "" ? undefined : event.target.value;
1120 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1123 handleNewPasswordVerifyChange(i: Settings, event: any) {
1124 const newPassVerify: string | undefined =
1125 event.target.value == "" ? undefined : event.target.value;
1127 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
1131 handleOldPasswordChange(i: Settings, event: any) {
1132 const oldPass: string | undefined =
1133 event.target.value == "" ? undefined : event.target.value;
1134 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1137 async handleSaveSettingsSubmit(i: Settings, event: any) {
1138 event.preventDefault();
1139 i.setState({ saveRes: { state: "loading" } });
1141 const saveRes = await HttpService.client.saveUserSettings({
1142 ...i.state.saveUserSettingsForm,
1143 auth: myAuthRequired(),
1145 if (saveRes.state === "success") {
1146 UserService.Instance.login(saveRes.data);
1148 toast(i18n.t("saved"));
1149 window.scrollTo(0, 0);
1152 i.setState({ saveRes });
1155 async handleChangePasswordSubmit(i: Settings, event: any) {
1156 event.preventDefault();
1157 const { new_password, new_password_verify, old_password } =
1158 i.state.changePasswordForm;
1160 if (new_password && old_password && new_password_verify) {
1161 i.setState({ changePasswordRes: { state: "loading" } });
1162 const changePasswordRes = await HttpService.client.changePassword({
1164 new_password_verify,
1166 auth: myAuthRequired(),
1168 if (changePasswordRes.state === "success") {
1169 UserService.Instance.login(changePasswordRes.data);
1170 window.scrollTo(0, 0);
1171 toast(i18n.t("password_changed"));
1174 i.setState({ changePasswordRes });
1178 handleDeleteAccountShowConfirmToggle(i: Settings) {
1179 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1182 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1183 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1186 async handleDeleteAccount(i: Settings) {
1187 const password = i.state.deleteAccountForm.password;
1189 i.setState({ deleteAccountRes: { state: "loading" } });
1190 const deleteAccountRes = await HttpService.client.deleteAccount({
1192 auth: myAuthRequired(),
1194 if (deleteAccountRes.state === "success") {
1195 UserService.Instance.logout();
1196 this.context.router.history.replace("/");
1199 i.setState({ deleteAccountRes });
1203 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1204 i.ctx.setState({ currentTab: i.tab });
1207 personBlock(res: RequestState<BlockPersonResponse>) {
1208 if (res.state === "success") {
1209 updatePersonBlock(res.data);
1210 const mui = UserService.Instance.myUserInfo;
1212 this.setState({ personBlocks: mui.person_blocks });
1217 communityBlock(res: RequestState<BlockCommunityResponse>) {
1218 if (res.state === "success") {
1219 updateCommunityBlock(res.data);
1220 const mui = UserService.Instance.myUserInfo;
1222 this.setState({ communityBlocks: mui.community_blocks });