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,
40 import { HtmlTags } from "../common/html-tags";
41 import { Icon, Spinner } from "../common/icon";
42 import { ImageUploadForm } from "../common/image-upload-form";
43 import { LanguageSelect } from "../common/language-select";
44 import { ListingTypeSelect } from "../common/listing-type-select";
45 import { MarkdownTextArea } from "../common/markdown-textarea";
46 import { SearchableSelect } from "../common/searchable-select";
47 import { SortSelect } from "../common/sort-select";
48 import Tabs from "../common/tabs";
49 import { CommunityLink } from "../community/community-link";
50 import { PersonListing } from "./person-listing";
52 interface SettingsState {
53 saveRes: RequestState<LoginResponse>;
54 changePasswordRes: RequestState<LoginResponse>;
55 deleteAccountRes: RequestState<DeleteAccountResponse>;
56 // TODO redo these forms
57 saveUserSettingsForm: {
60 default_sort_type?: SortType;
61 default_listing_type?: ListingType;
62 interface_language?: string;
65 display_name?: string;
68 matrix_user_id?: string;
69 show_avatars?: boolean;
70 show_scores?: boolean;
71 send_notifications_to_email?: boolean;
72 bot_account?: boolean;
73 show_bot_accounts?: boolean;
74 show_read_posts?: boolean;
75 show_new_post_notifs?: boolean;
76 discussion_languages?: number[];
77 generate_totp_2fa?: boolean;
80 new_password?: string;
81 new_password_verify?: string;
82 old_password?: string;
87 personBlocks: PersonBlockView[];
88 communityBlocks: CommunityBlockView[];
91 deleteAccountShowConfirm: boolean;
92 siteRes: GetSiteResponse;
93 searchCommunityLoading: boolean;
94 searchCommunityOptions: Choice[];
95 searchPersonLoading: boolean;
96 searchPersonOptions: Choice[];
99 type FilterType = "user" | "community";
108 filterType: FilterType;
110 onSearch: (text: string) => void;
111 onChange: (choice: Choice) => void;
114 <div className="form-group row">
116 className="col-md-4 col-form-label"
117 htmlFor={`block-${filterType}-filter`}
119 {i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
121 <div className="col-md-8">
123 id={`block-${filterType}-filter`}
125 { label: emDash, value: "0", disabled: true } as Choice,
135 export class Settings extends Component<any, SettingsState> {
136 private isoData = setIsoData(this.context);
137 state: SettingsState = {
138 saveRes: { state: "empty" },
139 deleteAccountRes: { state: "empty" },
140 changePasswordRes: { state: "empty" },
141 saveUserSettingsForm: {},
142 changePasswordForm: {},
143 deleteAccountShowConfirm: false,
144 deleteAccountForm: {},
147 currentTab: "settings",
148 siteRes: this.isoData.site_res,
150 searchCommunityLoading: false,
151 searchCommunityOptions: [],
152 searchPersonLoading: false,
153 searchPersonOptions: [],
156 constructor(props: any, context: any) {
157 super(props, context);
159 this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
160 this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
161 this.handleBioChange = this.handleBioChange.bind(this);
162 this.handleDiscussionLanguageChange =
163 this.handleDiscussionLanguageChange.bind(this);
165 this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
166 this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
168 this.handleBannerUpload = this.handleBannerUpload.bind(this);
169 this.handleBannerRemove = this.handleBannerRemove.bind(this);
170 this.userSettings = this.userSettings.bind(this);
171 this.blockCards = this.blockCards.bind(this);
173 this.handleBlockPerson = this.handleBlockPerson.bind(this);
174 this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
176 const mui = UserService.Instance.myUserInfo;
183 default_listing_type,
189 show_new_post_notifs,
190 send_notifications_to_email,
201 } = mui.local_user_view;
205 personBlocks: mui.person_blocks,
206 communityBlocks: mui.community_blocks,
207 saveUserSettingsForm: {
208 ...this.state.saveUserSettingsForm,
210 theme: theme ?? "browser",
212 default_listing_type,
214 discussion_languages: mui.discussion_languages,
223 show_new_post_notifs,
226 send_notifications_to_email,
233 async componentDidMount() {
235 this.setState({ themeList: await fetchThemeList() });
238 get documentTitle(): string {
239 return i18n.t("settings");
244 <div className="container-lg">
246 title={this.documentTitle}
247 path={this.context.router.route.match.url}
248 description={this.documentTitle}
249 image={this.state.saveUserSettingsForm.avatar}
255 label: i18n.t("settings"),
256 getNode: this.userSettings,
260 label: i18n.t("blocks"),
261 getNode: this.blockCards,
271 <div className="row">
272 <div className="col-12 col-md-6">
273 <div className="card border-secondary mb-3">
274 <div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
277 <div className="col-12 col-md-6">
278 <div className="card border-secondary mb-3">
279 <div className="card-body">{this.changePasswordHtmlForm()}</div>
288 <div className="row">
289 <div className="col-12 col-md-6">
290 <div className="card border-secondary mb-3">
291 <div className="card-body">{this.blockUserCard()}</div>
294 <div className="col-12 col-md-6">
295 <div className="card border-secondary mb-3">
296 <div className="card-body">{this.blockCommunityCard()}</div>
303 changePasswordHtmlForm() {
306 <h5>{i18n.t("change_password")}</h5>
307 <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
308 <div className="form-group row">
309 <label className="col-sm-5 col-form-label" htmlFor="user-password">
310 {i18n.t("new_password")}
312 <div className="col-sm-7">
316 className="form-control"
317 value={this.state.changePasswordForm.new_password}
318 autoComplete="new-password"
320 onInput={linkEvent(this, this.handleNewPasswordChange)}
324 <div className="form-group row">
326 className="col-sm-5 col-form-label"
327 htmlFor="user-verify-password"
329 {i18n.t("verify_password")}
331 <div className="col-sm-7">
334 id="user-verify-password"
335 className="form-control"
336 value={this.state.changePasswordForm.new_password_verify}
337 autoComplete="new-password"
339 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
343 <div className="form-group row">
345 className="col-sm-5 col-form-label"
346 htmlFor="user-old-password"
348 {i18n.t("old_password")}
350 <div className="col-sm-7">
353 id="user-old-password"
354 className="form-control"
355 value={this.state.changePasswordForm.old_password}
356 autoComplete="new-password"
358 onInput={linkEvent(this, this.handleOldPasswordChange)}
362 <div className="form-group">
363 <button type="submit" className="btn btn-block btn-secondary mr-4">
364 {this.state.changePasswordRes.state === "loading" ? (
367 capitalizeFirstLetter(i18n.t("save"))
377 const { searchPersonLoading, searchPersonOptions } = this.state;
383 loading={searchPersonLoading}
384 onChange={this.handleBlockPerson}
385 onSearch={this.handlePersonSearch}
386 options={searchPersonOptions}
388 {this.blockedUsersList()}
396 <h5>{i18n.t("blocked_users")}</h5>
397 <ul className="list-unstyled mb-0">
398 {this.state.personBlocks.map(pb => (
399 <li key={pb.target.id}>
401 <PersonListing person={pb.target} />
403 className="btn btn-sm"
405 { ctx: this, recipientId: pb.target.id },
406 this.handleUnblockPerson
408 data-tippy-content={i18n.t("unblock_user")}
410 <Icon icon="x" classes="icon-inline" />
420 blockCommunityCard() {
421 const { searchCommunityLoading, searchCommunityOptions } = this.state;
426 filterType="community"
427 loading={searchCommunityLoading}
428 onChange={this.handleBlockCommunity}
429 onSearch={this.handleCommunitySearch}
430 options={searchCommunityOptions}
432 {this.blockedCommunitiesList()}
437 blockedCommunitiesList() {
440 <h5>{i18n.t("blocked_communities")}</h5>
441 <ul className="list-unstyled mb-0">
442 {this.state.communityBlocks.map(cb => (
443 <li key={cb.community.id}>
445 <CommunityLink community={cb.community} />
447 className="btn btn-sm"
449 { ctx: this, communityId: cb.community.id },
450 this.handleUnblockCommunity
452 data-tippy-content={i18n.t("unblock_community")}
454 <Icon icon="x" classes="icon-inline" />
464 saveUserSettingsHtmlForm() {
465 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
469 <h5>{i18n.t("settings")}</h5>
470 <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
471 <div className="form-group row">
472 <label className="col-sm-5 col-form-label" htmlFor="display-name">
473 {i18n.t("display_name")}
475 <div className="col-sm-7">
479 className="form-control"
480 placeholder={i18n.t("optional")}
481 value={this.state.saveUserSettingsForm.display_name}
482 onInput={linkEvent(this, this.handleDisplayNameChange)}
483 pattern="^(?!@)(.+)$"
488 <div className="form-group row">
489 <label className="col-sm-3 col-form-label" htmlFor="user-bio">
492 <div className="col-sm-9">
494 initialContent={this.state.saveUserSettingsForm.bio}
495 onContentChange={this.handleBioChange}
497 hideNavigationWarnings
498 allLanguages={this.state.siteRes.all_languages}
499 siteLanguages={this.state.siteRes.discussion_languages}
503 <div className="form-group row">
504 <label className="col-sm-3 col-form-label" htmlFor="user-email">
507 <div className="col-sm-9">
511 className="form-control"
512 placeholder={i18n.t("optional")}
513 value={this.state.saveUserSettingsForm.email}
514 onInput={linkEvent(this, this.handleEmailChange)}
519 <div className="form-group row">
520 <label className="col-sm-5 col-form-label" htmlFor="matrix-user-id">
521 <a href={elementUrl} rel={relTags}>
522 {i18n.t("matrix_user_id")}
525 <div className="col-sm-7">
529 className="form-control"
530 placeholder="@user:example.com"
531 value={this.state.saveUserSettingsForm.matrix_user_id}
532 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
533 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
537 <div className="form-group row">
538 <label className="col-sm-3">{i18n.t("avatar")}</label>
539 <div className="col-sm-9">
541 uploadTitle={i18n.t("upload_avatar")}
542 imageSrc={this.state.saveUserSettingsForm.avatar}
543 onUpload={this.handleAvatarUpload}
544 onRemove={this.handleAvatarRemove}
549 <div className="form-group row">
550 <label className="col-sm-3">{i18n.t("banner")}</label>
551 <div className="col-sm-9">
553 uploadTitle={i18n.t("upload_banner")}
554 imageSrc={this.state.saveUserSettingsForm.banner}
555 onUpload={this.handleBannerUpload}
556 onRemove={this.handleBannerRemove}
560 <div className="form-group row">
561 <label className="col-sm-3" htmlFor="user-language">
562 {i18n.t("interface_language")}
564 <div className="col-sm-9">
567 value={this.state.saveUserSettingsForm.interface_language}
568 onChange={linkEvent(this, this.handleInterfaceLangChange)}
569 className="custom-select w-auto"
571 <option disabled aria-hidden="true">
572 {i18n.t("interface_language")}
574 <option value="browser">{i18n.t("browser_default")}</option>
575 <option disabled aria-hidden="true">
579 .sort((a, b) => a.code.localeCompare(b.code))
581 <option key={lang.code} value={lang.code}>
589 allLanguages={this.state.siteRes.all_languages}
590 siteLanguages={this.state.siteRes.discussion_languages}
591 selectedLanguageIds={selectedLangs}
593 showLanguageWarning={true}
595 onChange={this.handleDiscussionLanguageChange}
597 <div className="form-group row">
598 <label className="col-sm-3" htmlFor="user-theme">
601 <div className="col-sm-9">
604 value={this.state.saveUserSettingsForm.theme}
605 onChange={linkEvent(this, this.handleThemeChange)}
606 className="custom-select w-auto"
608 <option disabled aria-hidden="true">
611 <option value="browser">{i18n.t("browser_default")}</option>
612 {this.state.themeList.map(theme => (
613 <option key={theme} value={theme}>
620 <form className="form-group row">
621 <label className="col-sm-3">{i18n.t("type")}</label>
622 <div className="col-sm-9">
625 this.state.saveUserSettingsForm.default_listing_type ??
628 showLocal={showLocal(this.isoData)}
630 onChange={this.handleListingTypeChange}
634 <form className="form-group row">
635 <label className="col-sm-3">{i18n.t("sort_type")}</label>
636 <div className="col-sm-9">
639 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
641 onChange={this.handleSortTypeChange}
645 {enableNsfw(this.state.siteRes) && (
646 <div className="form-group">
647 <div className="form-check">
649 className="form-check-input"
652 checked={this.state.saveUserSettingsForm.show_nsfw}
653 onChange={linkEvent(this, this.handleShowNsfwChange)}
655 <label className="form-check-label" htmlFor="user-show-nsfw">
656 {i18n.t("show_nsfw")}
661 <div className="form-group">
662 <div className="form-check">
664 className="form-check-input"
665 id="user-show-scores"
667 checked={this.state.saveUserSettingsForm.show_scores}
668 onChange={linkEvent(this, this.handleShowScoresChange)}
670 <label className="form-check-label" htmlFor="user-show-scores">
671 {i18n.t("show_scores")}
675 <div className="form-group">
676 <div className="form-check">
678 className="form-check-input"
679 id="user-show-avatars"
681 checked={this.state.saveUserSettingsForm.show_avatars}
682 onChange={linkEvent(this, this.handleShowAvatarsChange)}
684 <label className="form-check-label" htmlFor="user-show-avatars">
685 {i18n.t("show_avatars")}
689 <div className="form-group">
690 <div className="form-check">
692 className="form-check-input"
693 id="user-bot-account"
695 checked={this.state.saveUserSettingsForm.bot_account}
696 onChange={linkEvent(this, this.handleBotAccount)}
698 <label className="form-check-label" htmlFor="user-bot-account">
699 {i18n.t("bot_account")}
703 <div className="form-group">
704 <div className="form-check">
706 className="form-check-input"
707 id="user-show-bot-accounts"
709 checked={this.state.saveUserSettingsForm.show_bot_accounts}
710 onChange={linkEvent(this, this.handleShowBotAccounts)}
713 className="form-check-label"
714 htmlFor="user-show-bot-accounts"
716 {i18n.t("show_bot_accounts")}
720 <div className="form-group">
721 <div className="form-check">
723 className="form-check-input"
724 id="user-show-read-posts"
726 checked={this.state.saveUserSettingsForm.show_read_posts}
727 onChange={linkEvent(this, this.handleReadPosts)}
730 className="form-check-label"
731 htmlFor="user-show-read-posts"
733 {i18n.t("show_read_posts")}
737 <div className="form-group">
738 <div className="form-check">
740 className="form-check-input"
741 id="user-show-new-post-notifs"
743 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
744 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
747 className="form-check-label"
748 htmlFor="user-show-new-post-notifs"
750 {i18n.t("show_new_post_notifs")}
754 <div className="form-group">
755 <div className="form-check">
757 className="form-check-input"
758 id="user-send-notifications-to-email"
760 disabled={!this.state.saveUserSettingsForm.email}
762 this.state.saveUserSettingsForm.send_notifications_to_email
766 this.handleSendNotificationsToEmailChange
770 className="form-check-label"
771 htmlFor="user-send-notifications-to-email"
773 {i18n.t("send_notifications_to_email")}
778 <div className="form-group">
779 <button type="submit" className="btn btn-block btn-secondary mr-4">
780 {this.state.saveRes.state === "loading" ? (
783 capitalizeFirstLetter(i18n.t("save"))
788 <div className="form-group">
790 className="btn btn-block btn-danger"
793 this.handleDeleteAccountShowConfirmToggle
796 {i18n.t("delete_account")}
798 {this.state.deleteAccountShowConfirm && (
800 <div className="my-2 alert alert-danger" role="alert">
801 {i18n.t("delete_account_confirm")}
805 value={this.state.deleteAccountForm.password}
806 autoComplete="new-password"
810 this.handleDeleteAccountPasswordChange
812 className="form-control my-2"
815 className="btn btn-danger mr-4"
816 disabled={!this.state.deleteAccountForm.password}
817 onClick={linkEvent(this, this.handleDeleteAccount)}
819 {this.state.deleteAccountRes.state === "loading" ? (
822 capitalizeFirstLetter(i18n.t("delete"))
826 className="btn btn-secondary"
829 this.handleDeleteAccountShowConfirmToggle
844 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
849 <div className="form-group">
850 <div className="form-check">
852 className="form-check-input"
853 id="user-generate-totp"
855 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
856 onChange={linkEvent(this, this.handleGenerateTotp)}
858 <label className="form-check-label" htmlFor="user-generate-totp">
859 {i18n.t("set_up_two_factor")}
868 <a className="btn btn-secondary mb-2" href={totpUrl}>
869 {i18n.t("two_factor_link")}
872 <div className="form-group">
873 <div className="form-check">
875 className="form-check-input"
876 id="user-remove-totp"
879 this.state.saveUserSettingsForm.generate_totp_2fa == false
881 onChange={linkEvent(this, this.handleRemoveTotp)}
883 <label className="form-check-label" htmlFor="user-remove-totp">
884 {i18n.t("remove_two_factor")}
894 handlePersonSearch = debounce(async (text: string) => {
895 this.setState({ searchPersonLoading: true });
897 const searchPersonOptions: Choice[] = [];
899 if (text.length > 0) {
900 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
904 searchPersonLoading: false,
909 handleCommunitySearch = debounce(async (text: string) => {
910 this.setState({ searchCommunityLoading: true });
912 const searchCommunityOptions: Choice[] = [];
914 if (text.length > 0) {
915 searchCommunityOptions.push(
916 ...(await fetchCommunities(text)).map(communityToChoice)
921 searchCommunityLoading: false,
922 searchCommunityOptions,
926 async handleBlockPerson({ value }: Choice) {
928 const res = await HttpService.client.blockPerson({
929 person_id: Number(value),
931 auth: myAuthRequired(),
933 this.personBlock(res);
937 async handleUnblockPerson({
944 const res = await HttpService.client.blockPerson({
945 person_id: recipientId,
947 auth: myAuthRequired(),
949 ctx.personBlock(res);
952 async handleBlockCommunity({ value }: Choice) {
954 const res = await HttpService.client.blockCommunity({
955 community_id: Number(value),
957 auth: myAuthRequired(),
959 this.communityBlock(res);
963 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
964 const auth = myAuth();
966 const res = await HttpService.client.blockCommunity({
967 community_id: i.communityId,
969 auth: myAuthRequired(),
971 i.ctx.communityBlock(res);
975 handleShowNsfwChange(i: Settings, event: any) {
977 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
981 handleShowAvatarsChange(i: Settings, event: any) {
982 const mui = UserService.Instance.myUserInfo;
984 mui.local_user_view.local_user.show_avatars = event.target.checked;
987 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
991 handleBotAccount(i: Settings, event: any) {
993 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
997 handleShowBotAccounts(i: Settings, event: any) {
1000 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1005 handleReadPosts(i: Settings, event: any) {
1007 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
1011 handleShowNewPostNotifs(i: Settings, event: any) {
1014 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1019 handleShowScoresChange(i: Settings, event: any) {
1020 const mui = UserService.Instance.myUserInfo;
1022 mui.local_user_view.local_user.show_scores = event.target.checked;
1025 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
1029 handleGenerateTotp(i: Settings, event: any) {
1030 // Coerce false to undefined here, so it won't generate it.
1031 const checked: boolean | undefined = event.target.checked || undefined;
1033 toast(i18n.t("two_factor_setup_instructions"));
1035 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1038 handleRemoveTotp(i: Settings, event: any) {
1039 // Coerce true to undefined here, so it won't generate it.
1040 const checked: boolean | undefined = !event.target.checked && undefined;
1041 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1044 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1047 (s.saveUserSettingsForm.send_notifications_to_email =
1048 event.target.checked),
1054 handleThemeChange(i: Settings, event: any) {
1055 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1056 setTheme(event.target.value, true);
1059 handleInterfaceLangChange(i: Settings, event: any) {
1060 const newLang = event.target.value ?? "browser";
1061 i18n.changeLanguage(newLang === "browser" ? navigator.languages : newLang);
1064 s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
1068 handleDiscussionLanguageChange(val: number[]) {
1070 s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1074 handleSortTypeChange(val: SortType) {
1075 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1078 handleListingTypeChange(val: ListingType) {
1080 s => ((s.saveUserSettingsForm.default_listing_type = val), s)
1084 handleEmailChange(i: Settings, event: any) {
1085 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1088 handleBioChange(val: string) {
1089 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1092 handleAvatarUpload(url: string) {
1093 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1096 handleAvatarRemove() {
1097 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1100 handleBannerUpload(url: string) {
1101 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1104 handleBannerRemove() {
1105 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1108 handleDisplayNameChange(i: Settings, event: any) {
1110 s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
1114 handleMatrixUserIdChange(i: Settings, event: any) {
1116 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
1120 handleNewPasswordChange(i: Settings, event: any) {
1121 const newPass: string | undefined =
1122 event.target.value == "" ? undefined : event.target.value;
1123 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1126 handleNewPasswordVerifyChange(i: Settings, event: any) {
1127 const newPassVerify: string | undefined =
1128 event.target.value == "" ? undefined : event.target.value;
1130 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
1134 handleOldPasswordChange(i: Settings, event: any) {
1135 const oldPass: string | undefined =
1136 event.target.value == "" ? undefined : event.target.value;
1137 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1140 async handleSaveSettingsSubmit(i: Settings, event: any) {
1141 event.preventDefault();
1142 i.setState({ saveRes: { state: "loading" } });
1144 const saveRes = await HttpService.client.saveUserSettings({
1145 ...i.state.saveUserSettingsForm,
1146 auth: myAuthRequired(),
1148 if (saveRes.state === "success") {
1149 UserService.Instance.login(saveRes.data);
1151 toast(i18n.t("saved"));
1152 window.scrollTo(0, 0);
1155 i.setState({ saveRes });
1158 async handleChangePasswordSubmit(i: Settings, event: any) {
1159 event.preventDefault();
1160 const { new_password, new_password_verify, old_password } =
1161 i.state.changePasswordForm;
1163 if (new_password && old_password && new_password_verify) {
1164 i.setState({ changePasswordRes: { state: "loading" } });
1165 const changePasswordRes = await HttpService.client.changePassword({
1167 new_password_verify,
1169 auth: myAuthRequired(),
1171 if (changePasswordRes.state === "success") {
1172 UserService.Instance.login(changePasswordRes.data);
1173 window.scrollTo(0, 0);
1174 toast(i18n.t("password_changed"));
1177 i.setState({ changePasswordRes });
1181 handleDeleteAccountShowConfirmToggle(i: Settings) {
1182 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1185 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1186 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1189 async handleDeleteAccount(i: Settings) {
1190 const password = i.state.deleteAccountForm.password;
1192 i.setState({ deleteAccountRes: { state: "loading" } });
1193 const deleteAccountRes = await HttpService.client.deleteAccount({
1195 auth: myAuthRequired(),
1197 if (deleteAccountRes.state === "success") {
1198 UserService.Instance.logout();
1199 this.context.router.history.replace("/");
1202 i.setState({ deleteAccountRes });
1206 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1207 i.ctx.setState({ currentTab: i.tab });
1210 personBlock(res: RequestState<BlockPersonResponse>) {
1211 if (res.state === "success") {
1212 updatePersonBlock(res.data);
1213 const mui = UserService.Instance.myUserInfo;
1215 this.setState({ personBlocks: mui.person_blocks });
1220 communityBlock(res: RequestState<BlockCommunityResponse>) {
1221 if (res.state === "success") {
1222 updateCommunityBlock(res.data);
1223 const mui = UserService.Instance.myUserInfo;
1225 this.setState({ communityBlocks: mui.community_blocks });