15 import { capitalizeFirstLetter, debounce } from "@utils/helpers";
16 import { Choice } from "@utils/types";
17 import classNames from "classnames";
18 import { NoOptionI18nKeys } from "i18next";
19 import { Component, linkEvent } from "inferno";
21 BlockCommunityResponse,
24 DeleteAccountResponse,
30 } from "lemmy-js-client";
31 import { elementUrl, emDash, relTags } from "../../config";
32 import { i18n, languages } from "../../i18next";
33 import { UserService } from "../../services";
34 import { HttpService, RequestState } from "../../services/HttpService";
35 import { setupTippy } from "../../tippy";
36 import { toast } from "../../toast";
37 import { HtmlTags } from "../common/html-tags";
38 import { Icon, Spinner } from "../common/icon";
39 import { ImageUploadForm } from "../common/image-upload-form";
40 import { LanguageSelect } from "../common/language-select";
41 import { ListingTypeSelect } from "../common/listing-type-select";
42 import { MarkdownTextArea } from "../common/markdown-textarea";
43 import { SearchableSelect } from "../common/searchable-select";
44 import { SortSelect } from "../common/sort-select";
45 import Tabs from "../common/tabs";
46 import { CommunityLink } from "../community/community-link";
47 import { PersonListing } from "./person-listing";
49 interface SettingsState {
50 saveRes: RequestState<LoginResponse>;
51 changePasswordRes: RequestState<LoginResponse>;
52 deleteAccountRes: RequestState<DeleteAccountResponse>;
53 // TODO redo these forms
54 saveUserSettingsForm: {
57 default_sort_type?: SortType;
58 default_listing_type?: ListingType;
59 interface_language?: string;
62 display_name?: string;
65 matrix_user_id?: string;
66 show_avatars?: boolean;
67 show_scores?: boolean;
68 send_notifications_to_email?: boolean;
69 bot_account?: boolean;
70 show_bot_accounts?: boolean;
71 show_read_posts?: boolean;
72 show_new_post_notifs?: boolean;
73 discussion_languages?: number[];
74 generate_totp_2fa?: boolean;
77 new_password?: string;
78 new_password_verify?: string;
79 old_password?: string;
84 personBlocks: PersonBlockView[];
85 communityBlocks: CommunityBlockView[];
88 deleteAccountShowConfirm: boolean;
89 siteRes: GetSiteResponse;
90 searchCommunityLoading: boolean;
91 searchCommunityOptions: Choice[];
92 searchPersonLoading: boolean;
93 searchPersonOptions: Choice[];
96 type FilterType = "user" | "community";
105 filterType: FilterType;
107 onSearch: (text: string) => void;
108 onChange: (choice: Choice) => void;
111 <div className="mb-3 row">
113 className="col-md-4 col-form-label"
114 htmlFor={`block-${filterType}-filter`}
116 {i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
118 <div className="col-md-8">
120 id={`block-${filterType}-filter`}
122 { label: emDash, value: "0", disabled: true } as Choice,
132 export class Settings extends Component<any, SettingsState> {
133 private isoData = setIsoData(this.context);
134 state: SettingsState = {
135 saveRes: { state: "empty" },
136 deleteAccountRes: { state: "empty" },
137 changePasswordRes: { state: "empty" },
138 saveUserSettingsForm: {},
139 changePasswordForm: {},
140 deleteAccountShowConfirm: false,
141 deleteAccountForm: {},
144 currentTab: "settings",
145 siteRes: this.isoData.site_res,
147 searchCommunityLoading: false,
148 searchCommunityOptions: [],
149 searchPersonLoading: false,
150 searchPersonOptions: [],
153 constructor(props: any, context: any) {
154 super(props, context);
156 this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
157 this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
158 this.handleBioChange = this.handleBioChange.bind(this);
159 this.handleDiscussionLanguageChange =
160 this.handleDiscussionLanguageChange.bind(this);
162 this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
163 this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
165 this.handleBannerUpload = this.handleBannerUpload.bind(this);
166 this.handleBannerRemove = this.handleBannerRemove.bind(this);
167 this.userSettings = this.userSettings.bind(this);
168 this.blockCards = this.blockCards.bind(this);
170 this.handleBlockPerson = this.handleBlockPerson.bind(this);
171 this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
173 const mui = UserService.Instance.myUserInfo;
180 default_listing_type,
186 show_new_post_notifs,
187 send_notifications_to_email,
198 } = mui.local_user_view;
202 personBlocks: mui.person_blocks,
203 communityBlocks: mui.community_blocks,
204 saveUserSettingsForm: {
205 ...this.state.saveUserSettingsForm,
207 theme: theme ?? "browser",
209 default_listing_type,
211 discussion_languages: mui.discussion_languages,
220 show_new_post_notifs,
223 send_notifications_to_email,
230 async componentDidMount() {
232 this.setState({ themeList: await fetchThemeList() });
235 get documentTitle(): string {
236 return i18n.t("settings");
241 <div className="person-settings container-lg">
243 title={this.documentTitle}
244 path={this.context.router.route.match.url}
245 description={this.documentTitle}
246 image={this.state.saveUserSettingsForm.avatar}
252 label: i18n.t("settings"),
253 getNode: this.userSettings,
257 label: i18n.t("blocks"),
258 getNode: this.blockCards,
266 userSettings(isSelected) {
269 className={classNames("tab-pane show", {
273 id="settings-tab-pane"
275 <div className="row">
276 <div className="col-12 col-md-6">
277 <div className="card border-secondary mb-3">
278 <div className="card-body">{this.saveUserSettingsHtmlForm()}</div>
281 <div className="col-12 col-md-6">
282 <div className="card border-secondary mb-3">
283 <div className="card-body">{this.changePasswordHtmlForm()}</div>
291 blockCards(isSelected) {
294 className={classNames("tab-pane", {
300 <div className="row">
301 <div className="col-12 col-md-6">
302 <div className="card border-secondary mb-3">
303 <div className="card-body">{this.blockUserCard()}</div>
306 <div className="col-12 col-md-6">
307 <div className="card border-secondary mb-3">
308 <div className="card-body">{this.blockCommunityCard()}</div>
316 changePasswordHtmlForm() {
319 <h5>{i18n.t("change_password")}</h5>
320 <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
321 <div className="mb-3 row">
322 <label className="col-sm-5 col-form-label" htmlFor="user-password">
323 {i18n.t("new_password")}
325 <div className="col-sm-7">
329 className="form-control"
330 value={this.state.changePasswordForm.new_password}
331 autoComplete="new-password"
333 onInput={linkEvent(this, this.handleNewPasswordChange)}
337 <div className="mb-3 row">
339 className="col-sm-5 col-form-label"
340 htmlFor="user-verify-password"
342 {i18n.t("verify_password")}
344 <div className="col-sm-7">
347 id="user-verify-password"
348 className="form-control"
349 value={this.state.changePasswordForm.new_password_verify}
350 autoComplete="new-password"
352 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
356 <div className="mb-3 row">
358 className="col-sm-5 col-form-label"
359 htmlFor="user-old-password"
361 {i18n.t("old_password")}
363 <div className="col-sm-7">
366 id="user-old-password"
367 className="form-control"
368 value={this.state.changePasswordForm.old_password}
369 autoComplete="new-password"
371 onInput={linkEvent(this, this.handleOldPasswordChange)}
375 <div className="input-group mb-3">
378 className="btn d-block btn-secondary me-4 w-100"
380 {this.state.changePasswordRes.state === "loading" ? (
383 capitalizeFirstLetter(i18n.t("save"))
393 const { searchPersonLoading, searchPersonOptions } = this.state;
399 loading={searchPersonLoading}
400 onChange={this.handleBlockPerson}
401 onSearch={this.handlePersonSearch}
402 options={searchPersonOptions}
404 {this.blockedUsersList()}
412 <h5>{i18n.t("blocked_users")}</h5>
413 <ul className="list-unstyled mb-0">
414 {this.state.personBlocks.map(pb => (
415 <li key={pb.target.id}>
417 <PersonListing person={pb.target} />
419 className="btn btn-sm"
421 { ctx: this, recipientId: pb.target.id },
422 this.handleUnblockPerson
424 data-tippy-content={i18n.t("unblock_user")}
426 <Icon icon="x" classes="icon-inline" />
436 blockCommunityCard() {
437 const { searchCommunityLoading, searchCommunityOptions } = this.state;
442 filterType="community"
443 loading={searchCommunityLoading}
444 onChange={this.handleBlockCommunity}
445 onSearch={this.handleCommunitySearch}
446 options={searchCommunityOptions}
448 {this.blockedCommunitiesList()}
453 blockedCommunitiesList() {
456 <h5>{i18n.t("blocked_communities")}</h5>
457 <ul className="list-unstyled mb-0">
458 {this.state.communityBlocks.map(cb => (
459 <li key={cb.community.id}>
461 <CommunityLink community={cb.community} />
463 className="btn btn-sm"
465 { ctx: this, communityId: cb.community.id },
466 this.handleUnblockCommunity
468 data-tippy-content={i18n.t("unblock_community")}
470 <Icon icon="x" classes="icon-inline" />
480 saveUserSettingsHtmlForm() {
481 const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;
485 <h5>{i18n.t("settings")}</h5>
486 <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
487 <div className="mb-3 row">
488 <label className="col-sm-3 col-form-label" htmlFor="display-name">
489 {i18n.t("display_name")}
491 <div className="col-sm-9">
495 className="form-control"
496 placeholder={i18n.t("optional")}
497 value={this.state.saveUserSettingsForm.display_name}
498 onInput={linkEvent(this, this.handleDisplayNameChange)}
499 pattern="^(?!@)(.+)$"
504 <div className="mb-3 row">
505 <label className="col-sm-3 col-form-label" htmlFor="user-bio">
508 <div className="col-sm-9">
510 initialContent={this.state.saveUserSettingsForm.bio}
511 onContentChange={this.handleBioChange}
513 hideNavigationWarnings
514 allLanguages={this.state.siteRes.all_languages}
515 siteLanguages={this.state.siteRes.discussion_languages}
519 <div className="mb-3 row">
520 <label className="col-sm-3 col-form-label" htmlFor="user-email">
523 <div className="col-sm-9">
527 className="form-control"
528 placeholder={i18n.t("optional")}
529 value={this.state.saveUserSettingsForm.email}
530 onInput={linkEvent(this, this.handleEmailChange)}
535 <div className="mb-3 row">
536 <label className="col-sm-3 col-form-label" htmlFor="matrix-user-id">
537 <a href={elementUrl} rel={relTags}>
538 {i18n.t("matrix_user_id")}
541 <div className="col-sm-9">
545 className="form-control"
546 placeholder="@user:example.com"
547 value={this.state.saveUserSettingsForm.matrix_user_id}
548 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
549 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
553 <div className="mb-3 row">
554 <label className="col-sm-3 col-form-label">
557 <div className="col-sm-9">
559 uploadTitle={i18n.t("upload_avatar")}
560 imageSrc={this.state.saveUserSettingsForm.avatar}
561 onUpload={this.handleAvatarUpload}
562 onRemove={this.handleAvatarRemove}
567 <div className="mb-3 row">
568 <label className="col-sm-3 col-form-label">
571 <div className="col-sm-9">
573 uploadTitle={i18n.t("upload_banner")}
574 imageSrc={this.state.saveUserSettingsForm.banner}
575 onUpload={this.handleBannerUpload}
576 onRemove={this.handleBannerRemove}
580 <div className="mb-3 row">
581 <label className="col-sm-3 form-label" htmlFor="user-language">
582 {i18n.t("interface_language")}
584 <div className="col-sm-9">
587 value={this.state.saveUserSettingsForm.interface_language}
588 onChange={linkEvent(this, this.handleInterfaceLangChange)}
589 className="form-select d-inline-block w-auto"
591 <option disabled aria-hidden="true">
592 {i18n.t("interface_language")}
594 <option value="browser">{i18n.t("browser_default")}</option>
595 <option disabled aria-hidden="true">
599 .sort((a, b) => a.code.localeCompare(b.code))
601 <option key={lang.code} value={lang.code}>
609 allLanguages={this.state.siteRes.all_languages}
610 siteLanguages={this.state.siteRes.discussion_languages}
611 selectedLanguageIds={selectedLangs}
613 showLanguageWarning={true}
615 onChange={this.handleDiscussionLanguageChange}
617 <div className="mb-3 row">
618 <label className="col-sm-3 col-form-label" htmlFor="user-theme">
621 <div className="col-sm-9">
624 value={this.state.saveUserSettingsForm.theme}
625 onChange={linkEvent(this, this.handleThemeChange)}
626 className="form-select d-inline-block w-auto"
628 <option disabled aria-hidden="true">
631 <option value="browser">{i18n.t("browser_default")}</option>
632 {this.state.themeList.map(theme => (
633 <option key={theme} value={theme}>
640 <form className="mb-3 row">
641 <label className="col-sm-3 col-form-label">{i18n.t("type")}</label>
642 <div className="col-sm-9">
645 this.state.saveUserSettingsForm.default_listing_type ??
648 showLocal={showLocal(this.isoData)}
650 onChange={this.handleListingTypeChange}
654 <form className="mb-3 row">
655 <label className="col-sm-3 col-form-label">
656 {i18n.t("sort_type")}
658 <div className="col-sm-9">
661 this.state.saveUserSettingsForm.default_sort_type ?? "Active"
663 onChange={this.handleSortTypeChange}
667 <div className="input-group mb-3">
668 <div className="form-check">
670 className="form-check-input"
673 checked={this.state.saveUserSettingsForm.show_nsfw}
674 onChange={linkEvent(this, this.handleShowNsfwChange)}
676 <label className="form-check-label" htmlFor="user-show-nsfw">
677 {i18n.t("show_nsfw")}
681 <div className="input-group mb-3">
682 <div className="form-check">
684 className="form-check-input"
685 id="user-show-scores"
687 checked={this.state.saveUserSettingsForm.show_scores}
688 onChange={linkEvent(this, this.handleShowScoresChange)}
690 <label className="form-check-label" htmlFor="user-show-scores">
691 {i18n.t("show_scores")}
695 <div className="input-group mb-3">
696 <div className="form-check">
698 className="form-check-input"
699 id="user-show-avatars"
701 checked={this.state.saveUserSettingsForm.show_avatars}
702 onChange={linkEvent(this, this.handleShowAvatarsChange)}
704 <label className="form-check-label" htmlFor="user-show-avatars">
705 {i18n.t("show_avatars")}
709 <div className="input-group mb-3">
710 <div className="form-check">
712 className="form-check-input"
713 id="user-bot-account"
715 checked={this.state.saveUserSettingsForm.bot_account}
716 onChange={linkEvent(this, this.handleBotAccount)}
718 <label className="form-check-label" htmlFor="user-bot-account">
719 {i18n.t("bot_account")}
723 <div className="input-group mb-3">
724 <div className="form-check">
726 className="form-check-input"
727 id="user-show-bot-accounts"
729 checked={this.state.saveUserSettingsForm.show_bot_accounts}
730 onChange={linkEvent(this, this.handleShowBotAccounts)}
733 className="form-check-label"
734 htmlFor="user-show-bot-accounts"
736 {i18n.t("show_bot_accounts")}
740 <div className="input-group mb-3">
741 <div className="form-check">
743 className="form-check-input"
744 id="user-show-read-posts"
746 checked={this.state.saveUserSettingsForm.show_read_posts}
747 onChange={linkEvent(this, this.handleReadPosts)}
750 className="form-check-label"
751 htmlFor="user-show-read-posts"
753 {i18n.t("show_read_posts")}
757 <div className="input-group mb-3">
758 <div className="form-check">
760 className="form-check-input"
761 id="user-show-new-post-notifs"
763 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
764 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
767 className="form-check-label"
768 htmlFor="user-show-new-post-notifs"
770 {i18n.t("show_new_post_notifs")}
774 <div className="input-group mb-3">
775 <div className="form-check">
777 className="form-check-input"
778 id="user-send-notifications-to-email"
780 disabled={!this.state.saveUserSettingsForm.email}
782 this.state.saveUserSettingsForm.send_notifications_to_email
786 this.handleSendNotificationsToEmailChange
790 className="form-check-label"
791 htmlFor="user-send-notifications-to-email"
793 {i18n.t("send_notifications_to_email")}
798 <div className="input-group mb-3">
799 <button type="submit" className="btn d-block btn-secondary me-4">
800 {this.state.saveRes.state === "loading" ? (
803 capitalizeFirstLetter(i18n.t("save"))
808 <div className="input-group mb-3">
810 className="btn d-block btn-danger"
813 this.handleDeleteAccountShowConfirmToggle
816 {i18n.t("delete_account")}
818 {this.state.deleteAccountShowConfirm && (
820 <div className="my-2 alert alert-danger" role="alert">
821 {i18n.t("delete_account_confirm")}
825 value={this.state.deleteAccountForm.password}
826 autoComplete="new-password"
830 this.handleDeleteAccountPasswordChange
832 className="form-control my-2"
835 className="btn btn-danger me-4"
836 disabled={!this.state.deleteAccountForm.password}
837 onClick={linkEvent(this, this.handleDeleteAccount)}
839 {this.state.deleteAccountRes.state === "loading" ? (
842 capitalizeFirstLetter(i18n.t("delete"))
846 className="btn btn-secondary"
849 this.handleDeleteAccountShowConfirmToggle
864 UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
869 <div className="input-group mb-3">
870 <div className="form-check">
872 className="form-check-input"
873 id="user-generate-totp"
875 checked={this.state.saveUserSettingsForm.generate_totp_2fa}
876 onChange={linkEvent(this, this.handleGenerateTotp)}
878 <label className="form-check-label" htmlFor="user-generate-totp">
879 {i18n.t("set_up_two_factor")}
888 <a className="btn btn-secondary mb-2" href={totpUrl}>
889 {i18n.t("two_factor_link")}
892 <div className="input-group mb-3">
893 <div className="form-check">
895 className="form-check-input"
896 id="user-remove-totp"
899 this.state.saveUserSettingsForm.generate_totp_2fa == false
901 onChange={linkEvent(this, this.handleRemoveTotp)}
903 <label className="form-check-label" htmlFor="user-remove-totp">
904 {i18n.t("remove_two_factor")}
914 handlePersonSearch = debounce(async (text: string) => {
915 this.setState({ searchPersonLoading: true });
917 const searchPersonOptions: Choice[] = [];
919 if (text.length > 0) {
920 searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
924 searchPersonLoading: false,
929 handleCommunitySearch = debounce(async (text: string) => {
930 this.setState({ searchCommunityLoading: true });
932 const searchCommunityOptions: Choice[] = [];
934 if (text.length > 0) {
935 searchCommunityOptions.push(
936 ...(await fetchCommunities(text)).map(communityToChoice)
941 searchCommunityLoading: false,
942 searchCommunityOptions,
946 async handleBlockPerson({ value }: Choice) {
948 const res = await HttpService.client.blockPerson({
949 person_id: Number(value),
951 auth: myAuthRequired(),
953 this.personBlock(res);
957 async handleUnblockPerson({
964 const res = await HttpService.client.blockPerson({
965 person_id: recipientId,
967 auth: myAuthRequired(),
969 ctx.personBlock(res);
972 async handleBlockCommunity({ value }: Choice) {
974 const res = await HttpService.client.blockCommunity({
975 community_id: Number(value),
977 auth: myAuthRequired(),
979 this.communityBlock(res);
983 async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
984 const auth = myAuth();
986 const res = await HttpService.client.blockCommunity({
987 community_id: i.communityId,
989 auth: myAuthRequired(),
991 i.ctx.communityBlock(res);
995 handleShowNsfwChange(i: Settings, event: any) {
997 s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
1001 handleShowAvatarsChange(i: Settings, event: any) {
1002 const mui = UserService.Instance.myUserInfo;
1004 mui.local_user_view.local_user.show_avatars = event.target.checked;
1007 s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
1011 handleBotAccount(i: Settings, event: any) {
1013 s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
1017 handleShowBotAccounts(i: Settings, event: any) {
1020 (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
1025 handleReadPosts(i: Settings, event: any) {
1027 s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
1031 handleShowNewPostNotifs(i: Settings, event: any) {
1034 (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
1039 handleShowScoresChange(i: Settings, event: any) {
1040 const mui = UserService.Instance.myUserInfo;
1042 mui.local_user_view.local_user.show_scores = event.target.checked;
1045 s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
1049 handleGenerateTotp(i: Settings, event: any) {
1050 // Coerce false to undefined here, so it won't generate it.
1051 const checked: boolean | undefined = event.target.checked || undefined;
1053 toast(i18n.t("two_factor_setup_instructions"));
1055 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1058 handleRemoveTotp(i: Settings, event: any) {
1059 // Coerce true to undefined here, so it won't generate it.
1060 const checked: boolean | undefined = !event.target.checked && undefined;
1061 i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
1064 handleSendNotificationsToEmailChange(i: Settings, event: any) {
1067 (s.saveUserSettingsForm.send_notifications_to_email =
1068 event.target.checked),
1074 handleThemeChange(i: Settings, event: any) {
1075 i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
1076 setTheme(event.target.value, true);
1079 handleInterfaceLangChange(i: Settings, event: any) {
1080 const newLang = event.target.value ?? "browser";
1081 i18n.changeLanguage(newLang === "browser" ? navigator.languages : newLang);
1084 s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
1088 handleDiscussionLanguageChange(val: number[]) {
1090 s => ((s.saveUserSettingsForm.discussion_languages = val), s)
1094 handleSortTypeChange(val: SortType) {
1095 this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
1098 handleListingTypeChange(val: ListingType) {
1100 s => ((s.saveUserSettingsForm.default_listing_type = val), s)
1104 handleEmailChange(i: Settings, event: any) {
1105 i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
1108 handleBioChange(val: string) {
1109 this.setState(s => ((s.saveUserSettingsForm.bio = val), s));
1112 handleAvatarUpload(url: string) {
1113 this.setState(s => ((s.saveUserSettingsForm.avatar = url), s));
1116 handleAvatarRemove() {
1117 this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s));
1120 handleBannerUpload(url: string) {
1121 this.setState(s => ((s.saveUserSettingsForm.banner = url), s));
1124 handleBannerRemove() {
1125 this.setState(s => ((s.saveUserSettingsForm.banner = ""), s));
1128 handleDisplayNameChange(i: Settings, event: any) {
1130 s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
1134 handleMatrixUserIdChange(i: Settings, event: any) {
1136 s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
1140 handleNewPasswordChange(i: Settings, event: any) {
1141 const newPass: string | undefined =
1142 event.target.value == "" ? undefined : event.target.value;
1143 i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
1146 handleNewPasswordVerifyChange(i: Settings, event: any) {
1147 const newPassVerify: string | undefined =
1148 event.target.value == "" ? undefined : event.target.value;
1150 s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
1154 handleOldPasswordChange(i: Settings, event: any) {
1155 const oldPass: string | undefined =
1156 event.target.value == "" ? undefined : event.target.value;
1157 i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
1160 async handleSaveSettingsSubmit(i: Settings, event: any) {
1161 event.preventDefault();
1162 i.setState({ saveRes: { state: "loading" } });
1164 const saveRes = await HttpService.client.saveUserSettings({
1165 ...i.state.saveUserSettingsForm,
1166 auth: myAuthRequired(),
1168 if (saveRes.state === "success") {
1169 UserService.Instance.login(saveRes.data);
1171 toast(i18n.t("saved"));
1172 window.scrollTo(0, 0);
1175 i.setState({ saveRes });
1178 async handleChangePasswordSubmit(i: Settings, event: any) {
1179 event.preventDefault();
1180 const { new_password, new_password_verify, old_password } =
1181 i.state.changePasswordForm;
1183 if (new_password && old_password && new_password_verify) {
1184 i.setState({ changePasswordRes: { state: "loading" } });
1185 const changePasswordRes = await HttpService.client.changePassword({
1187 new_password_verify,
1189 auth: myAuthRequired(),
1191 if (changePasswordRes.state === "success") {
1192 UserService.Instance.login(changePasswordRes.data);
1193 window.scrollTo(0, 0);
1194 toast(i18n.t("password_changed"));
1197 i.setState({ changePasswordRes });
1201 handleDeleteAccountShowConfirmToggle(i: Settings) {
1202 i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
1205 handleDeleteAccountPasswordChange(i: Settings, event: any) {
1206 i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
1209 async handleDeleteAccount(i: Settings) {
1210 const password = i.state.deleteAccountForm.password;
1212 i.setState({ deleteAccountRes: { state: "loading" } });
1213 const deleteAccountRes = await HttpService.client.deleteAccount({
1215 auth: myAuthRequired(),
1217 if (deleteAccountRes.state === "success") {
1218 UserService.Instance.logout();
1219 this.context.router.history.replace("/");
1222 i.setState({ deleteAccountRes });
1226 handleSwitchTab(i: { ctx: Settings; tab: string }) {
1227 i.ctx.setState({ currentTab: i.tab });
1230 personBlock(res: RequestState<BlockPersonResponse>) {
1231 if (res.state === "success") {
1232 updatePersonBlock(res.data);
1233 const mui = UserService.Instance.myUserInfo;
1235 this.setState({ personBlocks: mui.person_blocks });
1240 communityBlock(res: RequestState<BlockCommunityResponse>) {
1241 if (res.state === "success") {
1242 updateCommunityBlock(res.data);
1243 const mui = UserService.Instance.myUserInfo;
1245 this.setState({ communityBlocks: mui.community_blocks });